如何在 Scala 中获取通用(多态)lambda?

How to get generic (polymorphic) lambda in scala?

更新(2018):我的祈祷在 Dotty 中得到了回应(Type Lambdas),所以下面的问答更与“Scala 2.x”相关


只是一个来自 Scala 的简单例子:

scala> def f(x: Int) = x
f: (x: Int)Int

scala> (f _)(5)
res0: Int = 5

让它通用化:

scala> def f[T](x: T) = x
f: [T](x: T)T

scala> (f _)(5)
<console>:9: error: type mismatch;
 found   : Int(5)
 required: Nothing
              (f _)(5)
                    ^

让我们看看 Scala 中多态方法的 eta 扩展:

scala> f _ 
res2: Nothing => Nothing = <function1>

与Haskell比较:

Prelude> let f x = x

Prelude> f 5
5
Prelude> f "a"
"a"
Prelude> :t f
f :: t -> t

Haskell 确实在此处推断出正确的类型 [T] => [T]

更现实的例子?

scala> identity _
res2: Nothing => Nothing = <function1>

更现实:

scala> def f[T](l: List[T]) = l.head
f: [T](l: List[T])T

scala> f _
res3: List[Nothing] => Nothing = <function1>

您不能为身份创建别名 - 必须编写您自己的函数。 [T,U](t: T, u: U) => t -> u(生成元组)之类的东西不可能用作值。更一般 - 如果你想传递一些依赖泛型类型的 lambda(例如使用泛型函数,例如:创建列表,元组,以某种方式修改它们) - 你不能那样做。

那么,如何解决这个问题呢?任何解决方法、解决方案或推理?

P.S。我使用术语多态 lambda(而不是函数),因为函数只是命名为 lambda

似乎要做到这一点,您需要做一些类型提示来帮助 Scala 类型推理系统。

def id[T] : T => T = identity _

所以我想如果您尝试将身份作为参数传递给函数调用并且该参数的类型是通用的,那么应该没有问题。

只有方法可以在 JVM/Scala 上通用,值不能。您可以创建一个实现某个接口的匿名实例(并为您要使用的每个类型复制它):

trait ~>[A[_], B[_]] { //exists in scalaz
  def apply[T](a: A[T]): B[T]
}

val f = new (List ~> Id) {
  def apply[T](a: List[T]) = a.head
}

或使用 shapeless' Poly,它支持更复杂的类型案例。但是,是的,这是一个限制,需要解决。

我非常喜欢@Travis Brown 的解决方案:

import shapeless._

scala> Poly(identity _)
res2: shapeless.PolyDefns.~>[shapeless.Id,shapeless.Id] = fresh$macro$@797aa352

-

scala> def f[T](x: T) = x
f: [T](x: T)T

scala> Poly(f _)
res3: shapeless.PolyDefns.~>[shapeless.Id,shapeless.Id] = fresh$macro$@664ea816

-

scala> def f[T](l: List[T]) = l.head
f: [T](l: List[T])T

scala> val ff = Poly(f _)
ff: shapeless.PolyDefns.~>[List,shapeless.Id] = fresh$macro$@51254c50

scala> ff(List(1,2,3))
res5: shapeless.Id[Int] = 1

scala> ff(List("1","2","3"))
res6: shapeless.Id[String] = 1

Poly 构造函数(在某些情况下)会给你 eta 扩展到 Shapeless2 Poly1 函数,这是(或多或少)真正通用的。但是它不适用于多参数(即使是多类型参数),所以必须 "implement" Poly2implicit + at 方法(如@som- snytt 建议),类似于:

object myF extends Poly2 {
  implicit def caseA[T, U] = at[T, U]{ (a, b) => a -> b}
}

scala> myF(1,2)
res15: (Int, Int) = (1,2)

scala> myF("a",2)
res16: (String, Int) = (a,2)

P.S。我真的很想把它看作语言的一部分。

P∀scal 是一个编译器插件,它提供了更简洁的语法,可以使用泛型方法将多态值编码为对象。

标识函数作为一个值,类型为 ∀A. A => A。要将其转换为 Scala,假设一个特征

trait ForAll[F[_]] {
  def apply[A]: F[A]
}

然后恒等函数的类型为 ForAll[λ[A => A => A]],我在这里使用 kind-projector 语法,或者,没有 kind-projector:

type IdFun[A] = A => A
type PolyId = ForAll[IdFun]

P∀scal语法糖来了:

val id = Λ[Α](a => a) : PolyId

或等同于

val id = ν[PolyId](a => a)

("ν"为希腊小写字母"Nu",读作"new")

这些实际上只是

的简写
new PolyId {
  def apply[A] = a => a
}

P∀scal 支持多种类型参数和任意种类的参数,但您需要为每个变体在上述 ForAll 特性上进行专用变体。