问题 如何在scala中获得泛型(多态)lambda?


更新(2018年):我的祈祷在Dotty回答(输入Lambdas),所以下面的问答更加“Scalac”相关


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 (make tuple)不可能用作值。更一般 - 如果你想传递一些依赖于泛型类型的lambda(例如:使用泛型函数,例如:创建列表,元组,以某种方式修改它们) - 你不能这样做。

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

附:我使用了术语多态lambda(而不是函数),因为函数只是名为lambda


7647
2017-07-21 04:42


起源

@ SOM-snytt Poly 似乎是一个很好的解决方法,所以我必须等待它的良好语法(实际上看到里面的类型 Poly 对象并有eta扩展)。我的意思是 Shapeless 可以将eta扩展定义为 Poly有更好的语法。不管怎么说,还是要谢谢你。 - dk14
@ dk15 Scala中的Eta扩展是方法和函数之间的一种桥梁 - 它对Shapeless的多态函数值没有任何意义。 - Travis Brown
@Travis Brown - 我的意思是扩展方法 Poly 代替 Function。所以我需要方便的桥梁 Shapeless的多功能而不是斯卡拉 Function - dk14
@ dk14啊,明白了。 Poly(identity _)等在某些情况下会起作用。 - Travis Brown
val a: (Int) => Int = f _; a(4) ? - Jatin


答案:


只有方法在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
}

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


7
2017-07-21 09:06



实际上它与JVM没有多大关系,因为它发生在编译时(常规) Function 也可以看到解决方法)。我现在同意 Poly 似乎是最好的解决方案 - dk14


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

import shapeless._

scala> Poly(identity _)
res2: shapeless.PolyDefns.~>[shapeless.Id,shapeless.Id] = fresh$macro$1$2$@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$2$2$@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$3$2$@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 功能,(更多)真正的通用。但它不适用于多参数(即使是多类型参数),因此必须“实现” Poly2 同 implicit + 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)

附:我真的希望将它视为语言的一部分。


1
2017-07-21 09:59



我想我们都会,但在这个阶段Scala可能发生的变化太大了。您可以查看例如Idris是一种从Scala中获得灵感的语言,但对类型级编程有更多的支持。 - lmm


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

身份函数作为值具有类型 ∀A. A => A。要将其转换为Scala,请假设一个特征

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

然后身份函数有类型 ForAll[λ[A => A => A]],我在哪里使用 实物投影机 语法,或者没有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 每种变体的特征。


1
2017-08-10 15:23



这是非常好的!我提到那种用投影仪(typelevel 插件),我们(可能,还没试过)可以写: type PolyId = ForAll[Lambda[A => A]] 甚至 val id = ν[ForAll[Lambda[A => A]]](a => a)。也许,您可以添加一些互操作性快捷方式(它可以在一个单独的项目中,以保持您的解决方案免受不必要的依赖),以及 val id = poly[A => A](a => a) - dk14
对于 poly[A => A](a => a),我们需要确定和 硬编码 要实施什么特质(这里, ForAll)。所以插件必须带有一个包含很多变体的库 ForAll[F[_]], 喜欢 ForAll2[F[_, _]], ForAllH[F[_[_]]], ForAllHA[F[_[_]], G[_]],...另一个选择是使用结构类型,根据一些 是一个非首发。 - Tomas Mikula
我同意这种图书馆看起来“难看”;然而,作为一个单独的(实用的)项目,为什么不呢?即使让我们说无形也有其局限性 nats和每一个都好。此外,您可以使用宏生成这些类型(您可以为Intellij IDEA添加自定义扩展)。无论如何,对于一个平面参数,这个单线程工作(Scala 2.12,鉴于此 ForAll 被定义为): val id = Λ[a](a => a) : ForAll[Lambda[a => a => a]],真的很酷:) - dk14
(也适用于在这里阅读评论的人) val toList = Λ[a](x => List(x)) : ForAll[Lambda[a => a => List[a]]]。 toList.apply(5)  - 斯卡拉强迫我明确打电话 apply 因为某些原因 - dk14
你可以生成/硬编码在一个完全独立的项目中的所有内容,因为它们根本不需要任何插件。快捷方式更复杂(也许它可能是一个hacky解决方案),但它们仍然可以是一个单独的插件,使用你的插件作为一个库(插件本质上是一个罐子,实际上是“插件”接口 - 甚至是亲切的 - 投影机的KindRewriter正式成为公共课程) - dk14


它似乎这样做你需要做一点类型提示来帮助Scala类型推理系统。

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

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


0
2017-07-21 05:14



它仍然不是一个值 - 没有办法以通用的方式将它传递给另一个函数。简单地说,你已经在这里定义了一个方法 - 而不是一个函数 - dk14
看到 stackoverflow.com/questions/2529184/... - dk14
我的意思是 id _ 还是 Nothing => Nothing。我可以将它用作别名的解决方法,但不能作为通用解决方案。 - dk14
看一下: stackoverflow.com/questions/15264838/... - Ankur
好吧,我的问题有点重复,但我正在寻找任何通用的解决方案 - 现在我们有宏和无形 - 也许有人创建了可以做到这一点的库。 - dk14