Polymorphic Function Types
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. Method foo
above is an example, accepting a type parameter A
. So far, it was not possible to turn such methods into polymorphic function values like bar
above, 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 List[A]
.
Example Usage
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 Expr
. 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 wrap
function, 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 bar
above 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]
.