A polymorphic function type is a function type which accepts type parameters. For example:
// A polymorphic method: def foo[A](xs: List[A]): List[A] = xs.reverse // A polymorphic function value: val bar: [A] => List[A] => List[A] // ^^^^^^^^^^^^^^^^^^^^^^^^^ // a polymorphic function type = [A] => (xs: List[A]) => foo[A](xs)
Scala already has polymorphic methods, i.e. methods which accepts type parameters.
foo above is an example, accepting a type parameter
So far, it
was not possible to turn such methods into polymorphic function values like
which can be passed as parameters to other functions, or returned as results.
In Scala 3 this is now possible. The type of the
bar value above is
[A] => List[A] => List[A]
This type describes function values which take a type
A as a parameter,
then take a list of type
List[A], and return a list of the same type
Polymorphic function type are particularly useful when callers of a method are required to provide a function which has to be polymorphic, meaning that it should accept arbitrary types as part of its inputs.
For instance, consider the situation where we have a data type to represent the expressions of a simple language (consisting only of variables and function applications) in a strongly-typed way:
enum Expr[A]: case Var(name: String) case Apply[A, B](fun: Expr[B => A], arg: Expr[B]) extends Expr[A]
We would like to provide a way for users to map a function
over all immediate subexpressions of a given
This requires the given function to be polymorphic,
since each subexpression may have a different type.
Here is how to implement this using polymorphic function types:
def mapSubexpressions[A](e: Expr[A])(f: [B] => Expr[B] => Expr[B]): Expr[A] = e match case Apply(fun, arg) => Apply(f(fun), f(arg)) case Var(n) => Var(n)
And here is how to use this function to wrap each subexpression
in a given expression with a call to some
defined as a variable:
val e0 = Apply(Var("f"), Var("a")) val e1 = mapSubexpressions(e0)( [B] => (se: Expr[B]) => Apply(Var[B => B]("wrap"), se)) println(e1) // Apply(Apply(Var(wrap),Var(f)),Apply(Var(wrap),Var(a)))
Relationship With Type Lambdas
Polymorphic function types are not to be confused with type lambdas. While the former describes the type of a polymorphic value, the latter is an actual function value at the type level.
A good way of understanding the difference is to notice that
type lambdas are applied in types,
whereas polymorphic functions are applied in terms:
One would call the function
by passing it a type argument
bar[Int] within a method body.
On the other hand, given a type lambda such as
type F = [A] =>> List[A],
one would call
F within a type expression, as in
type Bar = F[Int].