scalascala-cats

Create FunctionK instance with anonymous function


I want to create a FunctionK instance with anonymous function. Here is the minimal example of that:

import cats.~>

given (Option ~> List) = {
  case Some(a) => a :: Nil
  case None    => Nil
}

However, this snippet throws compile error:

Missing parameter type

I could not infer the type of the parameter x$1
in expanded function:
  x$1 =>
  x$1 match 
    {
      case Some(a) =>
        a :: Nil
      case None =>
        Nil
    }
Expected type for the whole anonymous function:
  cats.arrow.FunctionK[Option, List]

Is the compiler not smart enough to infer all the types, or am I using anonymous function not the right way?

Compiled with Scala==3.4.0-RC1 and cats.core==2.10.0


Solution

  • Let's analyze it step by step.

    FunctionK is actually not a function. In Scala 3-only world it could be defined as:

    type FunctionK[F[_], G[_]] = [A] => F[A] => G[A]
    

    which would make it a polymorphic function type. But it was defined before Scala 3 became a thing and needs to be backward compatible with the old representation, that it:

    trait FunctionK[F[_], G[_]] {
    
      def apply[A](fa: F[A]): G[A]
    }
    

    which was a Scala 2's workaround for not having polymorphic function types.

    In situations like:

    trait FunctionLike[A, B] {
    
      def method(a: A): B
    }
    
    val fl: FunctionLike[Int, String] = _.toString
    

    we are using Single Abstract Method syntax - compiler sees that we are using lambda syntax for something where we could be using new FunctionLike[Int, String] { ... } but it also sees that this trait/abstract class has only 1 abstract method so it can assume that we want to implement the whole trait/abstract class by implementing this method (and so we can pretend that it's a function).

    Thing is, SAM seems to be currently implemented only for normal (monomorphic) methods, and when you are using lambdas for polymorphic ones, it will not make the connection:

    import cats.~>
    
    given (Option ~> List) = [A] => (opt: Option[A]) => opt match {
      case Some(a) => a :: Nil
      case None    => Nil
    }
    // error:
    // Found:    [A] => (opt: Option[A]) => List[A]
    // Required: cats.arrow.FunctionK[Option, List]
    

    In your case, you additionally used syntax for monomorphic function type: currently when you need a polymorphic function type you have to write: [A] => (a: A) => ... - type parameters have to be explicitly defined and explicitly used in one of the arguments.

    Gaël's answers explains how you can actually implements it.