Search code examples
scalaimplicithigher-kinded-types

Provide implicit evidence for a type alias in Scala


Is there a way to get the compiler to somehow consider type aliases when looking for implicit evidence?

Here is an example of problem I am trying to solve:

// Third party library
class Foo[T, P]
class FooOps[FTP, T] {
  def apply[F[_, _], P](t: T)(implicit ev: F[T, P] =:= FTP): FTP = ???
}

type StringFoo = Foo[String, Boolean]
object StringFoo extends FooOps[StringFoo, String]

StringFoo("hello")

// Attempt to wrap the third party type but have the same ops
class WrappedFoo[FTP](val foo: FTP)
object WrappedFoo {
  type F[T, P] = WrappedFoo[Foo[T, P]]
}

type WrappedStringFoo = WrappedFoo[StringFoo]
object WrappedStringFoo extends FooOps[WrappedStringFoo, String]

WrappedStringFoo("hello") // Cannot prove that F[String, P] =:= WrappedStringFoo
WrappedStringFoo[WrappedFoo.F, Boolean]("hello”) // This works

I don't quite understand how the compiler infers the types in:

StringFoo("hello")

Does it somehow use the available implicits to choose a value for F[_, _]? I always thought that it had to work out the types first.

However it works for StringFoo it doesn't work for WrappedStringFoo. Likely since the number of type parameters is different.

How can I get:

WrappedStringFoo("hello")

to compile without specifying the types explicitly?


Solution

  • Try to add necessary implicit to scope:

    import scala.language.higherKinds
    
    class Foo[T, P]
    class FooOps[FTP, T] {
      def apply[F[_, _], P](t: T)(implicit ev: F[T, P] =:= FTP): FTP = ???
    }
    
    type StringFoo = Foo[String, Boolean]
    object StringFoo extends FooOps[StringFoo, String]
    
    class WrappedFoo[FTP](val foo: FTP)
    object WrappedFoo {
      type F[T, P] = WrappedFoo[Foo[T, P]]
    
      //implicit val ev0: WrappedFoo.F[String, Boolean] =:= WrappedStringFoo = ???
      implicit val ev0: WrappedFoo.F[String, Boolean] =:= WrappedStringFoo = 
        null.asInstanceOf[WrappedFoo.F[String, Boolean] =:= WrappedStringFoo]
    }
    
    type WrappedStringFoo = WrappedFoo[StringFoo]
    object WrappedStringFoo extends FooOps[WrappedStringFoo, String]
    
    WrappedStringFoo("hello") 
    

    When you do StringFoo("hello") compiler solves equation F[String, P] = Foo[String, Boolean] and it's smart enough to deduce P = Boolean, F = Foo. But when you do WrappedStringFoo("hello") compiler has to solve equation F[String, P] = WrappedFoo[Foo[String, Boolean]] and algorithms it uses are not smart enough to deduce P = Boolean, F = ({ type λ[A, B] = WrappedFoo[Foo[A, B]] })#λ i.e. WrappedFoo.F (and possibly such equations can't be solved in general if they are advanced enough). So you should provide a hint. Either specifying type parameters explicitly or providing necessary implicit evidence.

    Resolving implicits and type inference make impact on each other. You can read section 4.4.3 of Eugene Burmako's thesis.