Search code examples
scalaimplicitscalazshapelesshigher-kinded-types

How to decompose a type constructor via implicits?


I have a class Foo that takes a type constructor F as type parameter:

case class Foo[F[_]](x: F[String])

Now I want to define a member method bar that is only applicable, if F[T] = Outer[Inner[T]] for some fixed outer type Outer, e.g. Option:

def bar[Inner[_]](implicit ev: ???): Foo[Inner]

The ??? must be something along a natural transformation F ~> Outer·Inner with · being composition of type constructors.

  1. How does this implicit argument look like?
  2. How can I get it from somewhere?

How to write type constructor composition?

Also how to best write composition of type constructors? I currently write using a type lambda ({type L[X] = Outer[Inner[X]]})#L.


Solution

  • Type equality for type constructors

    I don't think there's one defined in ScalaZ, but it's fairly simple to make one, taking scalaz.Leibniz as a template. I won't focus on composition and convenience methods, and only get the substance:

    sealed abstract class LeibnizK[F[_], G[_]] {
      def subst[Z[_[_]]](p: Z[F]): Z[G]
    }
    
    object LeibnizK {
      implicit def refl[F[_]] = new LeibnizK[F, F] {
        override def subst[Z[_[_]]](p: Z[F]): Z[F] = p
      }
    }
    

    Which seems to be the implicit you're looking for:

    type Outer[A] = Option[A]
    type Id[A] = A
    
    case class Foo[F[_]](me: F[String]) {
      // Oh boy, here comes type lambda
      def bar[Inner[_]](implicit leibk: LeibnizK[
        F,
        ({type L[A] = Outer[Inner[A]]})#L
      ]): Outer[Foo[Inner]] = leibk.subst(this).me.map(Foo(_)) // <- OK to use methods of Outer
    }
    
    assert(Foo[Option](Some("meh")).bar[Id] == Some(Foo[Id]("meh")))
    

    Better syntax for type composition

    Check out kind projector compiler plugin. It allows you to write:

    λ[A => Outer[Inner[A]]
    // or
    Lambda[A => Outer[Inner[A]]
    

    instead of

    ({type L[A] = Outer[Inner[A]]})#L
    

    And for simple cases (no nesting), there's even shorter syntax

    (?, Int)
    

    instead of

    ({type L[A] = (A, Int)})#L