Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method-Level Higher-Kinded Types in Scala

I've just started playing around with higher-kinded types in Scala and I'm experiencing behavior that I do not understand. I'm doing all of this in the REPL on Scala 2.9.0.1.

First I create a mapper trait so that I can map over the elements of any type M:

 trait Mapper {
    def mapper[M[_], A, B](m: M[A], f: A => B): M[B]
 }

Here's my implementation of the mapper:

 val mymapper = new Mapper {
  def mapper[List, Int, Double](m: List[Int], f: Int => Double): List[Double] = m.map(f)
 }

But the REPL complains...

 <console>:9: error: List does not take type parameters
   def mapper[List, Int, Double](m: List[Int], f: Int => Double): List[Double] = m.map(f)
                                                                  ^
 <console>:9: error: List does not take type parameters
   def mapper[List, Int, Double](m: List[Int], f: Int => Double): List[Double] = m.map(f)
                                    ^

This code works fine if I move the declaration of M[_] to be at the class level:

trait Mapper[M[_]] {
  def mapper[A,B](m: M[A], f: A => B): M[B]
}
val mymapper = new Mapper[List] {
  def mapper[Int, Double](m: List[Int], f: Int => Double): List[Double] = m.map(f)
}
mymapper.mapper(List(1,2,3), (x: Int) => x.toDouble)
// returns List(1.0, 2.0, 3.0)

Why is this the case? Why is that Scala can figure out the correct type for M if it's located at a class-level, but fails at the method level?

Thanks!

like image 380
shj Avatar asked Feb 25 '23 01:02

shj


1 Answers

This code does not mean what you think it means:

def mapper[List, Int, Double](m: List[Int], f: Int => Double): List[Double] = m.map(f)

List, Int and Double here are the names of type parameters, the exact types will be defined by the values used to call the method. Yes, they happen to also be the names of actual concrete types, but in this case you're shadowing that meaning.

If you use your original names of M, A and B, the error becomes clearer:

def mapper[M, A, B](m: M[A], f: A => B): M[B] = m.map(f)

M really does not take a type parameter...


It becomes even more obvious if you do the same thing with parameter names in your "working" example:

trait Mapper[M[_]] {
  def mapper[A,B](m: M[A], f: A => B): M[B]
}

val mymapper = new Mapper[List] {
  def mapper[A, B](m: List[A], f: A => B): List[B] = m.map(f)
}
like image 108
Kevin Wright Avatar answered Mar 05 '23 05:03

Kevin Wright