Полиморфные типы функций

Полиморфный тип функции — это тип функции, который принимает параметры типа.

Например:

// полиморфный метод:
def foo[A](xs: List[A]): List[A] = xs.reverse

// полиморфное значение функции:
val bar: [A] => List[A] => List[A] = [A] => (xs: List[A]) => foo[A](xs)
//       ^^^^^^^^^^^^^^^^^^^^^^^^^
//       полиморфный тип функции

В Scala есть полиморфные методы, т.е. методы, которые принимают параметры типа. Метод foo является примером метода, принимающим параметр типа A. Теперь полиморфные методы можно превратить в значения полиморфных функций, как указано выше, которые можно передавать в качестве параметров другим функциям или возвращать в качестве результатов.

Тип значения bar - [A] => List[A] => List[A] описывает значения функций, которые принимают тип A в качестве параметра, затем принимают список List[A] и возвращают список того же типа List[A].

Подробнее

Пример использования

Полиморфный тип функции особенно полезен, когда от вызывающего метода требуется предоставить функцию, которая должна быть полиморфной, что означает, что она должна принимать произвольные типы как часть своих входных данных.

Например, рассмотрим ситуацию, когда есть тип данных для представления выражений простого языка (состоящего только из переменных и приложений функций) в строго типизированном виде:

enum Expr[A]:
  case Var(name: String)
  case Apply[A, B](fun: Expr[B => A], arg: Expr[B]) extends Expr[A]

Желательно предоставить пользователям способ отображать функцию на все непосредственные подвыражения данного Expr. Это требует, чтобы данная функция была полиморфной, поскольку каждое подвыражение может иметь свой тип. Вот как это реализовать с помощью полиморфных типов функций:

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)

А вот как использовать эту функцию для переноса каждого подвыражения в данное выражение с вызовом некоторой функции wrap, определенной как переменная:

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)))

Связь с лямбда-выражениями типа

Полиморфные типы функций не следует путать с лямбда-типами. В то время как первое описывает тип полиморфного значения, второе является фактическим значением функции на уровне типа.

Хороший способ понять разницу — заметить, что лямбда-выражения типов применяются в типах, тогда как полиморфные функции применяются в терминах: можно было бы вызвать приведенную выше функцию bar, передав ей аргумент типа bar[Int] в теле метода. С другой стороны, при наличии лямбда-типа, такого как type F = [A] =>> List[A], можно было бы вызвать F внутри выражения типа, как в type Bar = F[Int].


Ссылки: