Returning a value from a method which must implement a typeclass

Viewed 63

I am trying to have a method return a value that must implement a typeclass, and I think it is highlighting to me more generally how I don't understand Scala's generic parameter resolution process. I have a situation something like this:

trait IsContainer[A, T] {
  def getOtherContainer[O](implicit tc2: IsUnit[O]): O
}

trait IsUnit[A] { }
implicit val anIntIsUnit = new IsUnit[Int] { }

implicit def aListIsContainer[T] = new IsContainer[List[T], T] {
  def getOtherContainer[Int] = 3
}

This is raising a compile error: Missing implementation for: def getOtherContainer. My uninformed guess about what should be happening here is that Scala sees I have passed the generic parameter O, and considers the method implemented if all instances of the O type are consistent. So in this case (since I have explicitly told it that O = Int, it checks that there is an instance of IsUnit[Int] in scope, and that the output type of the method is of type O. If this is correct (and I'm not saying it is!) then why is this not working?

More generally, if I skipped the [O] generic parameter and let it guess - so I just implemented the method with getOtherContainer = 3 - should I also expect it work? To infer what O should be, does it scan over the line and see if O has been concretely filled out in any of the three places it is mentioned, and infer from that?

Thanks!

2 Answers

Correct is

implicit def aListIsContainer[T] = new IsContainer[List[T], T] {
  override def getOtherContainer[O](implicit tc2: IsUnit[O]): O = ???
}

Your type class

trait IsContainer[A, T] {
  def getOtherContainer[O](implicit tc2: IsUnit[O]): O
}

means that if a tuple of types A, T have an instance of the type class then you know how to do getOtherContainer for any type O having an instance of type class IsUnit.

When you're trying to remove (implicit tc2: IsUnit[O]) or [O] in the definition of an instance you're actually trying to violate the contract of type class.

If you want to specialize O in an instance (for example O := Int) then you should move type parameter O to the type class level

trait IsContainer[A, T, O] {
  def getOtherContainer(implicit tc2: IsUnit[O]): O
}

or

abstract class IsContainer[A, T, O](implicit tc2: IsUnit[O]) {
  def getOtherContainer: O
}

or

trait IsContainer[A, T] {
  type O
  def getOtherContainer(implicit tc2: IsUnit[O]): O
}

Then you can define an instance

implicit def aListIsContainer[T] = new IsContainer[List[T], T, Int] {
  override def getOtherContainer(implicit tc2: IsUnit[Int]): Int = 3
}

or

implicit def aListIsContainer[T] = new IsContainer[List[T], T, Int] {
  override def getOtherContainer: Int = 3
}

or

implicit def aListIsContainer[T] = new IsContainer[List[T], T] {
  override type O = Int
  override def getOtherContainer(implicit tc2: IsUnit[O]): O = 3
}

correspondingly.

The main problem is this definition:

def getOtherContainer[Int] = 3

In this definition, Int is a type parameter, not the type Int. It is exactly the same as

def getOtherContainer[T] = 3

So you have not "explicitly told it that O = Int", you have just used Int as the name of type parameter rather than O. Since this does not match the required signature in the trait, there is no valid implementation of getOtherContainer and you get the error.

Related