Search code examples
scalascala-catstype-constructor

Does Scala support partial application of type constructors?


I'm learning Cats from scala-exercises.
Wonder how to use high order type, there are some attempt with it:

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

 def someThingFail1[In]() = new Functor[Function1[In, _]] {
    override def map[A, B](fa: Function1[In, A])(f: A => B): Function1[In, B] = ???
  }

  def someThingFail2[In]() = new Functor[Either[In, _]] {
    override def map[A, B](fa: Either[In, A])(f: A => B): Either[In, B] = ???
  }

  def someThingFail3() = new Functor[List[_]] {
    override def map[A, B](fa: List[A])(f: A => B): List[B] = ???
  }

  //only this one can compile 
  def someThingRight1() = new Functor[List] {
    override def map[A, B](fa: List[A])(f: A => B): List[B] = ???
  }

ahead of three function can't compiled, error message like this:

[error] /Users/lorancechen/version_control_project/_tutorials/learn-cats/src/main/scala/mycats/Main.scala:16:42: Either[In, _] takes no type parameters, expected: one
[error]   def someThingFail2[In]() = new Functor[Either[In, _]] {
[error]                                          ^

Why Scala not support type hole? Will Dotty compiler support it? Thanks


Solution

  • (Update 2023, for 3.x)

    Scala-3 has a nice new syntax for type lambdas, which can also be used for partially applied types. For example, the binary type constructor Map, partially applied to Int, can now be written as:

    [V] =>> Map[Int, V]
    

    According to the migration strategy, this will eventually be replaced by just Map[Int, _] after 3.2, so that the code in the original post will just work as expected.

    Until the dust with ?/*/_ has settled, however, I would suggest to avoid using any shortcuts such as _ / ? altogether, and just write out the full type-lambda =>> with explicit names for all parameter types.


    (original answer from 2018 for 2.x)

    That's because in Scala 2.x

    Either[X, _]
    

    is an existential type, it can be written out explicitly as

    Either[X, Y] forSome { type Y }
    

    This is somewhat similar to Java's wildcards, and not what you want as an argument for a higher order Functor type constructor. What you want is a type lambda. Originally, this could be written down as follows:

    ({type lam[Y] = Either[X, Y]})#lam
    

    The fact that type lambdas could be used in Scala at all was not a planned feature, but rather an accidental discovery, and the syntax was somewhat lengthy. However, there is the non/kind-projector plugin that simplifies it dramatically. With this plugin, your code becomes:

    trait Functor[F[_]] {
      def map[A, B](fa: F[A])(f: A => B): F[B]
    }
    
    def someThingFail1[In]() = new Functor[Function1[In, ?]] {
      override def map[A, B](fa: Function1[In, A])(f: A => B): Function1[In, B] = ???
    }
    
    def someThingFail2[In]() = new Functor[Either[In, ?]] {
      override def map[A, B](fa: Either[In, A])(f: A => B): Either[In, B] = ???
    }
    
    def someThingFail3() = new Functor[List[?]] {
      override def map[A, B](fa: List[A])(f: A => B): List[B] = ???
    }
    
    //only this one can compile 
    def someThingRight1() = new Functor[List] {
      override def map[A, B](fa: List[A])(f: A => B): List[B] = ???
    }
    

    (Tested with scala 2.12.4, cats 1.0.1, kind-projector 0.9.4).

    Note that this plugin is used in cats source code (search for ?).

    Everything will become much better in dotty, it already supports a neat syntax for type lambdas.