Scala中的lambda类型是什么,它们有什么好处?


152

有时我迷迷糊糊的

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

在Scala博客文章中,这给了它“我们使用了type-lambda技巧”的通知。

尽管对此有所了解(我们A无需匿名定义就可以获取匿名类型参数?),但我找不到明确的来源来描述lambda类型的技巧是什么,以及它的好处。它只是语法糖,还是开辟了新的领域?


Answers:


148

当您使用类型较高的类型时,类型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的实例时,必须指定一种类型;另一个当然是在您调用或绑定时提供的。

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之类的名称来指代专门用于第二个monad的某些转换器版本的IterateeT monad的类型,该转换器版本专门用于某些第三monad。当您开始堆叠时,这类构造就变得非常必要。当然,我从来没有实例化FG。它只是一种让我表达类型系统要求的技巧。


3
有趣的是,哈斯克尔并不能直接支持的类型级lambda表达式,虽然有些NEWTYPE两轮牛车(例如TypeCompose库)的方式来排序的GET的解决这一问题。
丹·伯顿

1
我很好奇,看到您bind为您的EitherMonad类定义了方法。:-)除此之外,如果我可以在此介绍Adriaan一秒钟,那么在该示例中您将不会使用类型较高的类型。您在FG,但不在EitherMonad。相反,您正在使用具有kind的类型构造函数* => *。这种类型是1阶,不是“更高”。
Daniel Spiewak

2
我以为那种*是1阶,但无论如何Monad都有一种(* => *) => *。另外,您会注意到我指定了“的正确投影Either[A, B]-实现非常简单(如果您以前没有做过,那是个好
习惯

我猜想Daniel不叫*=>*更高的观点的理由是,我们没有调用普通函数(将非函数映射到非函数,换句话说就是将普通值映射到普通值)作为高阶函数。
jhegedus 2014年

1
皮尔斯的TAPL书,第442页:Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
jhegedus 2014年

52

好处与匿名功能所赋予的好处完全相同。

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

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

Scalaz 7的用法示例。我们要使用一个Functor可以在a的第二个元素上映射函数的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的未来版本可能会直接支持这种语法。


最后一个片段看起来非常不错。IntelliJ scala插件肯定很棒!
AndreasScheinert 2012年

1
谢谢!在上一个示例中可能缺少lambda。除了,元组函子为什么选择转换最后一个值?是惯例还是实际的默认设置?
罗恩2012年

1
我正在为Nika举办夜间活动,但没有描述IDEA选项。有趣的,对“可简化应用型Lambda”进行了检查。
兰德尔·舒尔茨

6
它已移至“设置”->“编辑器”->“代码折叠”。
2012年

@retronym,(1, 2).map(a => a + 1)在REPL中尝试时遇到错误:`<console>:11:错误:值映射不是(Int,Int)(1、2).map(a => a + 1)^`的成员
凯文·梅雷迪斯

41

将内容放在上下文中:此答案最初发布在另一个线程中。您在这里看到它是因为两个线程已合并。所述线程中的问题语句如下:

如何解析此类型定义: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

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

所以ListOption可以认为是元数的类型层次的功能1.在形式上,我们说,他们有一种* -> *。星号表示类型。

现在Tuple2[_, _]是一个带有kind的类型构造函数,(*, *) -> *即您需要给它两个类型以获取新类型。

由于他们的签名不匹配,你不能代替Tuple2P。您需要做的是部分地应用 Tuple2其参数之一,这将为我们提供一个具有kind的类型构造函数* -> *,我们可以将其替换为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>

在您介绍的情况下,type参数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)]

0

type World[M[_]] = M[Int]导致,无论我们把AX[A]implicitly[X[A] =:= Foo[String,Int]]永远是真实的,我猜。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.