Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Providing implicit instances in the inheritor of a type that needs them

Tags:

scala

implicit

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.

like image 245
Nikita Volkov Avatar asked Jan 27 '14 09:01

Nikita Volkov


2 Answers

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)
like image 180
Chris Avatar answered Oct 04 '22 01:10

Chris


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.

like image 32
Kevin Wright Avatar answered Oct 04 '22 01:10

Kevin Wright