Scala 中的 lambdas 类型是什么? 它们的好处是什么?

有时我偶然发现一个半神秘的符号

def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}

在 Scala 的博客文章中,给出了一个“我们使用了 type-lambda 技巧”的手势。

虽然我对此有一些直觉(我们获得一个匿名类型参数 A,而不必用它来污染定义?),我发现没有明确的来源描述类型 lambda 技巧是什么,它的好处是什么。它仅仅是句法上的糖,还是它打开了一些新的维度?

29642 次浏览

当您使用高类型类型时,类型 lambdas 在很多时候都是至关重要的。

考虑一个为任意[ A,B ]的正确投影定义单子的简单例子。Monad 类型类如下所示:

trait Monad[M[_]] {
def point[A](a: A): M[A]
def bind[A, B](m: M[A])(f: A => M[B]): M[B]
}

现在,任何一个都是两个参数的型别构造器,但是要实现 Monad,你需要给它一个参数的型别构造器。解决这个问题的方法是使用一个类型 lambda:

class EitherMonad[A] extends Monad[({type λ[α] = Either[A, α]})#λ] {
def point[B](b: B): Either[A, B]
def bind[B, C](m: Either[A, B])(f: B => Either[A, C]): Either[A, C]
}

这是在类型系统中进行局部套用的一个例子——您已经套用了“要么”的类型,这样当您想创建“要么”的实例时,您必须指定其中一种类型; 当然,另一种类型是在您调用 point 或 bind 时提供的。

类型 lambda 技巧利用类型位置中的空块创建匿名结构类型这一事实。然后使用 # 语法获取类型成员。

在某些情况下,您可能需要更复杂的 lambda 类型,这些类型很难内联地写出来。下面是我今天的代码中的一个例子:

// types X and E are defined in an enclosing scope
private[iteratee] class FG[F[_[_], _], G[_]] {
type FGA[A] = F[G, A]
type IterateeM[A] = IterateeT[X, E, FGA, A]
}

这个类是独立存在的,因此我可以使用类似 FG [ F,G ] # IterateeM 这样的名称来引用 IterateeT 单子的类型,该类型专门用于第二个单子的某个转换器版本,而第二个单子专门用于第三个单子。当您开始堆栈时,这些类型的结构变得非常必要。当然,我从未实例化一个 FG; 它只是作为一个黑客,让我在类型系统中表达我想要的东西。

其好处与匿名函数赋予的好处完全相同。

def inc(a: Int) = a + 1; List(1, 2, 3).map(inc)


List(1, 2, 3).map(a => a + 1)

Scalaz 7的一个用法示例。我们希望使用一个 Functor,它可以将函数映射到 Tuple2中的第二个元素上。

type IntTuple[+A]=(Int, A)
Functor[IntTuple].map((1, 2))(a => a + 1)) // (1, 3)


Functor[({type l[a] = (Int, a)})#l].map((1, 2))(a => a + 1)) // (1, 3)

Scalaz 提供了一些隐式转换,可以将类型参数推断为 Functor,因此我们通常避免完全编写这些转换。前一行可以改写为:

(1, 2).map(a => a + 1) // (1, 3)

如果您使用 IntelliJ,您可以启用设置,代码样式,Scala,折叠,类型 Lambdas。然后是 隐藏了语法的复杂部分,呈现出更可口的:

Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3)

Scala 的未来版本可能直接支持这样的语法。

把事情放在上下文中: 这个答案最初发布在另一个线程中。您可以在这里看到它,因为两个线程已经合并。该帖子中的问题陈述如下:

如何解析这个类型定义: Pure [({ type? [ a ] = (R,a)}) # ? ] ?

采用这种结构的原因是什么?

来自 Scalaz 图书馆的剪报:

trait Pure[P[_]] {
def pure[A](a: => A): P[A]
}


object Pure {
import Scalaz._
//...
implicit def Tuple2Pure[R: Zero]: Pure[({type ?[a]=(R, a)})#?] = new Pure[({type ?[a]=(R, a)})#?] {
def pure[A](a: => A) = (Ø, a)
}


//...
}

回答:

trait Pure[P[_]] {
def pure[A](a: => A): P[A]
}

P后面的下划线暗示这是一个型别构造器接受一种类型并返回另一种类型。具有这种类型的类型构造函数的示例: ListOption

List一个 Int,一个具体的类型,它给你 List[Int],另一个具体的类型。给 List一个 String,它就给你一个 List[String]。等等。

因此,ListOption可以被认为是1的类型级函数。形式上我们说,他们有一种 * -> *。星号表示类型。

现在,Tuple2[_, _]是一个带有类型 (*, *) -> *的型别构造器,也就是说,你需要给它两个类型才能得到一个新类型。

因为它们的签名不匹配,所以不能用 Tuple2代替 P。你需要做的是在它的一个参数上使用 部分适用 Tuple2这样我们就能得到一个类型为 * -> *的型别构造器我们可以用它来代替 P

不幸的是,Scala 对于类型构造函数的部分应用没有特殊的语法,因此我们不得不求助于名为类型 lambdas 的庞然大物。(你的例子中有什么)之所以这样称呼它们,是因为它们类似于存在于值级别的 lambda 表达式。

下面的例子可能会有所帮助:

// VALUE LEVEL


// foo has signature: (String, String) => String
scala> def foo(x: String, y: String): String = x + " " + y
foo: (x: String, y: String)String


// world wants a parameter of type String => String
scala> def world(f: String => String): String = f("world")
world: (f: String => String)String


// So we use a lambda expression that partially applies foo on one parameter
// to yield a value of type String => String
scala> world(x => foo("hello", x))
res0: String = hello world




// TYPE LEVEL


// Foo has a kind (*, *) -> *
scala> type Foo[A, B] = Map[A, B]
defined type alias Foo


// World wants a parameter of kind * -> *
scala> type World[M[_]] = M[Int]
defined type alias World


// So we use a lambda lambda that partially applies Foo on one parameter
// to yield a type of kind * -> *
scala> type X[A] = World[({ type M[A] = Foo[String, A] })#M]
defined type alias X


// Test the equality of two types. (If this compiles, it means they're equal.)
scala> implicitly[X[Int] =:= Foo[String, Int]]
res2: =:=[X[Int],Foo[String,Int]] = <function1>

编辑:

更多的值级别和类型级别并行。

// VALUE LEVEL


// Instead of a lambda, you can define a named function beforehand...
scala> val g: String => String = x => foo("hello", x)
g: String => String = <function1>


// ...and use it.
scala> world(g)
res3: String = hello world


// TYPE LEVEL


// Same applies at type level too.
scala> type G[A] = Foo[String, A]
defined type alias G


scala> implicitly[X =:= Foo[String, Int]]
res5: =:=[X,Foo[String,Int]] = <function1>


scala> type T = World[G]
defined type alias T


scala> implicitly[T =:= Foo[String, Int]]
res6: =:=[T,Foo[String,Int]] = <function1>

在您提供的示例中,类型参数 R是函数 Tuple2Pure的本地参数,因此您不能简单地定义 type PartialTuple2[A] = Tuple2[R, A],因为根本没有可以放置该同义词的地方。

为了处理这种情况,我使用以下技巧来利用类型成员。(希望这个例子是不言而喻的。)

scala> type Partial2[F[_, _], A] = {
|   type Get[B] = F[A, B]
| }
defined type alias Partial2


scala> implicit def Tuple2Pure[R]: Pure[Partial2[Tuple2, R]#Get] = sys.error("")
Tuple2Pure: [R]=> Pure[[B](R, B)]