Question

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.

Was it helpful?

Solution 3

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

OTHER TIPS

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)

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top