Create FunctionK instance with anonymous function

61 views Asked by At

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

2

There are 2 answers

0
Mateusz Kubuszok On BEST ANSWER

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.

1
Gaël J On

Indeed, in this case the compiler cannot infer the types automatically.

You have to use the verbose syntax:

import cats.~>

given (Option ~> List) = new ~>[Option, List] {
  def apply[A](o: Option[A]): List[A] = o match {
    case Some(a) => a :: Nil
    case None => Nil
  }
}

Mateusz' answer explains very well why :)