什么是Scala中的类型lambda,它们有什么好处?
什么是Scala中的类型lambda,它们有什么好处?
有时我会在Scala博客文章中遇到半神秘的符号
def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}
,并且作者称其为\"我们使用了类型λ技巧\"。虽然我对此有些直觉(我们获得了一个匿名类型参数A而不必向定义中添加它?),但是我找不到清晰的来源来描述类型λ技巧以及其好处。它只是一种语法糖,还是它开启了一些新的维度?
这些好处与匿名函数所带来的好处完全相同。
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,您可以启用 Settings,Code Style,Scala,Folding,Type Lambdas。然后,这将隐藏语法的糟糕部分,呈现更可接受的内容:
Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3)
Scala的未来版本可能会直接支持这样的语法。
在使用高阶类型时,类型lambda非常重要。
考虑一个简单的例子,定义Either[A, B]的右投影的monad。Monad类型类看起来像这样:
trait Monad[M[_]] { def point[A](a: A): M[A] def bind[A, B](m: M[A])(f: A => M[B]): M[B] }
现在,Either是一个具有两个参数的类型构造器,但是为了实现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] }
这是类型系统中的柯里化示例-您已将Either的类型柯里化,因此当您要创建EitherMonad的实例时,您必须指定其中一个类型;当然,其他的在调用point或bind时提供。
类型lambda技巧利用了类型位置中的空块创建匿名结构类型的事实。然后我们使用#语法来获取类型成员。
在某些情况下,您可能需要更复杂的类型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这样的名称来引用特定于某个变换器版本的第二个单子的单调函数specialized到一些第三个单子的类型。当您开始堆叠时,这些构造变得非常必要。当然,我从不实例化FG; 它只是一种Hack,让我在类型系统中表达我想要的内容。