Search code examples
scalamonadstraitshigher-kinded-types

How can I define an HKT for instance (over object) methods in Scala?


How do you define an HKT in Scala to provide methods, such as map, as methods on instances instead of functions on objects?

I know you can

trait M[F[_]] {
  def map[A, B](f: F[A])(fn: A => B): F[B]
}

object Q extends M[Q] {
  def map[A, B](f: Q[A])(fn: A => B) = new Q(fn(f.e))
}

class Q[A](val e: A)

Q.map(new Q(1))(_.toDouble)

However, I want to use map on instances instead. I haven't seen any literature that does this, so I've written the following.

trait M[A, F[A]] {
  def map[B](f: A => B): F[B]
}

class Q[A](val e: A) extends M[A, Q] {
  def map[B](f: A => B) = new Q(f(e))
}

new Q(1).map(_.toDouble)

Is it idiomatic Scala? Does it do what map should do? Are there limitations compared with the object version above? (An aside, with extends M[A, Q[A]] it won't compile - why?)

I have seen

trait M[A] {
  def map[B](f: A => B): M[B]
}

but then Q's map could return a class R[A] extends M[A] which isn't desired.


Solution

  • There's a library specifically for this: https://github.com/mpilquist/simulacrum

    You can also do it yourself. The typical pattern is to define a corresponding implicit MOps class or whatever:

    trait M[F[_]] {
      def map[A, B](f: F[A])(fn: A => B): F[B]
    }
    
    implicit class MOps[F[_] : M, A](x: F[A]) {
      def map[B](f: A => B): F[B] = implicitly[M[F]].map(x)(f)
    }
    

    Then example usage:

    case class Box[A](a: A)
    
    implicit val BoxM = new M[Box] {
      def map[A, B](f: Box[A])(fn: A => B): Box[B] = Box(fn(f.a))
    }
    
    Box(2).map(_.toString)
    

    But it's a lot of boilerplate, so simulacrum does it all for you (and more).