6

To my astonishment I found out that the compiler can't resolve an implicit in the following example:

trait API {
  def f(implicit a: Int) = ???
}

class Impl extends API {
  implicit val int = 2
}

(new Impl).f

As well as in

class Impl extends API
object Impl {
  implicit val int = 2
}

I am deeply frustrated by this. Why is it so and is there any workaround for this? I must note that importing the implicit to the outer scope is not an option however, as the sole purpose of the above is in hiding the details of implementation from the user of Impl.

The above is a summarization of a pattern where implementors of an API provide specific typeclass instances, which get used by the functions implemented in the API. The end-user works with the implementations.

Nikita Volkov
  • 41,289
  • 10
  • 85
  • 162

3 Answers3

3

How about this?

trait API {
  implicit val i: Int
  protected def fImplementation(implicit a: Int) = a * 2
  def f = fImplementation
}

class Impl extends API {
  implicit val i = 2
}

(new Impl).f // <-- should be Int(4)
Chris
  • 1,074
  • 1
  • 10
  • 26
  • While it qualifies as a workaround, I have a few questions. 1. The thing I don't understand is why it doesn't compile if we elude the intermediate `fImplementation`. Can you explain? 2. It turns out to be a simple overriding of abstract members, which is basically the same as `trait API{ val i: Int; def f = i * 2 }`, which is why we lose the sole purpose of using implicits. 3. Doesn't imposing an interface on implicit declaration defeat the purpose too? I mean, what would you do if the implementor declared it like so: `implicit def i(implicit someOtherImplicit: ...) = ...`? – Nikita Volkov Jan 27 '14 at 12:11
  • The problem here is that you sacrifice the ability to call `f` with a different implicit value. Given the original question, this would seem to be the only benefit of using implicits in the fist place (as opposed to default params and/or overriding abstract methods) – Kevin Wright Jan 27 '14 at 12:29
3

The problem is that your Int int isn't in implicit scope at the point you call the function:

(new Impl).f

The compiler will not search inside a class for implicits used when invoking other members on that class.

You can use Chris's solution, or you can:

val newImpl = newImpl
import newImpl.int
newImpl.f

Or... If you want the value inside the class to be a default, yet have the ability to override it externally, then implicit parameters can also be default params:

trait Api {
  def defaultInt: Int
  def f(implicit a: Int = defaultInt) = ???
}

class Impl extends Api {
  val defaultInt = 2
}

UPDATE

Based on the comment to your own answer, implicit chaining is also a requirement, which is possible here:

trait HasInt { def intVal: Int }
sealed trait HasIntTypeClass[T] extends HasInt
implicit object StringHasInt extends HasIntTypeClass[String] { val intVal = 42 }

trait Api {
  def defaultHasInt: HasInt
  def f(implicit a: HasInt = defaultHasInt): Int = a.intVal * 2
}

class Impl extends Api {
  def defaultHasInt: HasInt = implicitly[HasIntTypeClass[String]]
}

By all means, put a type param on defaultHasInt if necessary.

Kevin Wright
  • 48,726
  • 9
  • 100
  • 155
  • I used an `Int` only for the sake of simplicity of example. Sorry, I thought it was evident. As mentioned, the real use case is about typeclasses and the implicit resolvers can actually be `def`s with implicit dependencies of their own. Also, as mentioned as well, importing to the outer scope is not an option. So basically we're left with standard overriding of abstract members. While not at all a solution for the implicits resolution, as it looks, sadly, it may be the best we can do here. – Nikita Volkov Jan 27 '14 at 11:54
  • It should be okay, given that a value can be an implicit and have a default at the same time. It's a powerful combination that underpins e.g. most of the shapeless library. – Kevin Wright Jan 27 '14 at 12:12
  • Sure, but in a situation where I can't declare the implicit instance so that it can be found (since so far we haven't found a solution which doesn't involve importing to the calling scope), having an implicit dependency as well as default parameters for it simply has no purpose. – Nikita Volkov Jan 27 '14 at 12:16
  • One question... If you post a question unrelated to your actual use-case, how can you be confident of getting the most helpful answer? It may well be that there's a far better solution using a completely different mechanism. It seems that I've answered what you asked, but not what you needed. – Kevin Wright Jan 27 '14 at 12:18
  • Concerning your update. 1. Above all, the code just doesn't compile. 2. Once again, it does not even attempt to solve the problem of "Providing implicit instances **in the inheritor**", i.e. in `Impl`. 3. So you call my solution complex? ) Also, please stop making remarks about the clarity of the question. What you call implicit chaining is implied by the sole meaning of implicit instance, because its one of the ways of declaring them. FYI, it was at least the 5th time I repeated to you what was already stated in the question, so just maybe I am not the problem here? – Nikita Volkov Jan 28 '14 at 13:06
  • That'll teach me to try an clean things up after copying from the REPL, compilation fixed. The point I'm trying to demonstrate is that implicits are a poor mechanism for pushing information from an inheritor to its superclass, and trying to do so results in convoluted code. There are cleaner ways to pass a typeclass "upstream" (such as the "template method" illustrated above), and default params then allow you to re-insert such typeclasses into the implicit scope. – Kevin Wright Jan 28 '14 at 13:19
  • I won't argue that implicits aren't at all easy to grasp. My point is that they can be the only solution to a certain set of problems. But, I guess you won't get me without a more real-life example, so check out [updates to my answer](http://stackoverflow.com/a/21383572/485115). – Nikita Volkov Jan 28 '14 at 14:13
0

Okay. It's solvable. The problem obviously is with the implicit resolution scope. We need to somehow add our implicit declaration to it. According to this answer one of the scopes is Implicit scope of type arguments. So what we'll do is muscle our way into it.

We obviously can't do anything with the implicit scopes of the type Int, but we can with the ones of types that we define. So to solve the stated problem the trick is to use a phantom type to determine an instance of a typeclass, which will provide what we need.

// The typeclass, which we'll resolve implicitly.
trait IntResolver[ instancesProvider ]{ val int: Int }

// Notice the `instancesProvider` parameter.
trait Trait[ instancesProvider ] {
  def f( implicit intResolver: IntResolver[ instancesProvider ] ) = 
    intResolver.int * 2
}

// We specify `Impl` as a type-parameter to `Trait` to explicitly state
// that the compiler should include the implicit scope of `Impl` in its search 
// for instances.
class Impl extends Trait[ Impl ] 
object Impl {
  implicit val intResolver = new IntResolver[ Impl ] { val int = 4 }
}

assert( (new Impl).f == 8 )

This approach is scalable to solve more involved cases with typeclasses, but you'll need modified versions of those typeclasses, e.g. instead of Show[a], you'll need Show[instancesProvider, a].


What's this pattern useful for?

Here's an example in the spirit of how it'll be used in the coming SORM 0.4 to check for support of certain operations on certain types by certain drivers on a type-level:

trait API[ driver ]{
  def regex
    [ ref, value ]
    ( ref: ref, value: value )
    ( implicit compiler: RegexCompiler[ driver, ref, value ] )
    = sys.error("The function is implemeneted here")
}

// A typeclass with ops, that don't matter in this example
trait RegexCompiler[ driver, ref, value ] 

class Mysql extends API[ Mysql ]
object Mysql {
  implicit def stringRegexCompiler[ ref ] = new RegexCompiler[ Mysql, ref, String ] {}
}

class CouchDB extends API[ CouchDB ]
object CouchDB {
  // This driver has no support for regex, so there's no instance
}

(new Mysql).regex("someref", "a")     // compiles fine
(new Mysql).regex("someref", 2)       // doesn't compile
(new CouchDB).regex("someref", "a")   // doesn't compile
Community
  • 1
  • 1
Nikita Volkov
  • 41,289
  • 10
  • 85
  • 162
  • I'd be *very* careful with this level of complexity, what exactly do you gain over & above using the implicit + default approach? It's starting to look a little like: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition – Kevin Wright Jan 28 '14 at 10:11
  • The "implicit + default" does not solve the declared problem of "Providing implicit instances in the inheritor of a type that needs them". You use overloading (with all the consequences involved) to provide what is supposed to be provided implicitly. Conversely, what you gain with the pattern I posted is exactly the solution to the declared problem. `implicit val intResolver` can easily be `implicit def intResolver(implicit anotherImplicit..)`, or there may be no instance of it at all - you can't achieve any of this with overloading. – Nikita Volkov Jan 28 '14 at 10:52
  • See the update to my own answer, specifically to handle the scenario of implicit chaining – Kevin Wright Jan 28 '14 at 11:24