Scala中的lambdas类型是什么?它们的优点是什么?

时间:2021-08-23 16:10:59

Sometime I stumble into the semi-mysterious notation of

有时我偶然发现了半神秘的符号。

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

in Scala blog posts, which give it a "we used that type-lambda trick" handwave.

在Scala的博客文章中,它给出了一个“我们使用了那种类型-lambda戏法”的手势。

While I have some intutition about this (we gain an anonymous type parameter A without having to pollute the definition with it?), I found no clear source describing what the type lambda trick is, and what are its benefits. Is it just syntactic sugar, or does it open some new dimensions?

虽然我对此有一些直觉(我们获得了一个匿名类型参数A,而不必用它污染定义吗?),但是我没有找到明确的来源来描述lambda戏法是什么类型,它的好处是什么。它只是语法糖,还是开辟了一些新的领域?

4 个解决方案

#1


138  

Type lambdas are vital quite a bit of the time when you are working with higher-kinded types.

类型lambdas在处理高亲属类型的时候非常重要。

Consider a simple example of defining a monad for the right projection of Either[A, B]. The monad typeclass looks like this:

考虑一个简单的例子,定义一个monad来表示任意一个[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]
}

Now, Either is a type constructor of two arguments, but to implement Monad, you need to give it a type constructor of one argument. The solution to this is to use a type lambda:

现在,其中之一是两个参数的类型构造函数,但是要实现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]
}

This is an example of currying in the type system - you have curried the type of Either, such that when you want to create an instance of EitherMonad, you have to specify one of the types; the other of course is supplied at the time you call point or bind.

这是一个在类型系统中进行同步的例子——您已经对其中任何一个进行了处理,这样当您想要创建EitherMonad的实例时,您必须指定其中的一个类型;另一个当然是在调用point或bind时提供的。

The type lambda trick exploits the fact that an empty block in a type position creates an anonymous structural type. We then use the # syntax to get a type member.

lambda戏法利用了在类型位置上的空块创建匿名结构类型的事实。然后使用#语法获取类型成员。

In some cases, you may need more sophisticated type lambdas that are a pain to write out inline. Here's an example from my code from today:

在某些情况下,您可能需要更复杂的类型lambdas,这种类型对于内联编写非常困难。下面是我今天的代码中的一个例子:

// 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] 
}

This class exists exclusively so that I can use a name like FG[F, G]#IterateeM to refer to the type of the IterateeT monad specialized to some transformer version of a second monad which is specialized to some third monad. When you start to stack, these kinds of constructs become very necessary. I never instantiate an FG, of course; it's just there as a hack to let me express what I want in the type system.

这个类只存在,所以我可以使用一个名称,比如FG[F, G]#IterateeM来引用IterateeT monad的类型,该类型专门用于第二个monad的某些转换器版本,该版本专门用于第三个monad。当您开始堆栈时,这些类型的构造变得非常必要。当然,我从来没有实例化一个FG;它只是为了让我在类型系统中表达我想要的东西。

#2


50  

The benefits are exactly the same as those conferred by anonymous functions.

这些好处与匿名函数的好处完全相同。

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

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

An example usage, with Scalaz 7. We want to use a Functor that can map a function over the second element in a Tuple2.

使用Scalaz 7的示例用法。我们希望使用一个函子,它可以将一个函数映射到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 provides some implicit conversions that can infer the type argument to Functor, so we often avoid writing these altogether. The previous line can be rewritten as:

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

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

If you use IntelliJ, you can enable Settings, Code Style, Scala, Folding, Type Lambdas. This then hides the crufty parts of the syntax, and presents the more palatable:

如果您使用IntelliJ,您可以启用设置、代码样式、Scala、折叠、类型Lambdas。然后,它隐藏了语法中最粗糙的部分,并呈现出更美味的部分:

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

A future version of Scala might directly support such a syntax.

将来的Scala版本可能会直接支持这种语法。

#3


39  

To put things in context: This answer was originally posted in another thread. You are seeing it here because the two threads have been merged. The question statement in the said thread was as follows:

把事情放在背景中:这个答案最初是在另一个线程中发布的。您在这里看到它是因为两个线程已经合并。该主题的问题陈述如下:

How to resolve this type definition: Pure[({type ?[a]=(R, a)})#?] ?

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

What are the reasons of using such construction?

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

Snipped comes from scalaz library:

片段来自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)
  }

//...
}

Answer:

答:

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

The one underscore in the boxes after P implies that it is a type constructor takes one type and returns another type. Examples of type constructors with this kind: List, Option.

P后面框中的下划线表示它是一个类型构造函数,它接受一个类型并返回另一个类型。此类构造函数的示例:列表、选项。

Give List an Int, a concrete type, and it gives you List[Int], another concrete type. Give List a String and it gives you List[String]. Etc.

给List一个Int类型,一个具体类型,它会给你List[Int],另一个具体类型。给List一个字符串,它会给你List[String]。等。

So, List, Option can be considered to be type level functions of arity 1. Formally we say, they have a kind * -> *. The asterisk denotes a type.

因此,List, Option可以被认为是1的type level函数。正式地说,它们有一个类* -> *。星号表示类型。

Now Tuple2[_, _] is a type constructor with kind (*, *) -> * i.e. you need to give it two types to get a new type.

Tuple2[_, _]是一个具有kind(*, *) -> *的类型构造函数,也就是说,您需要给它两个类型来获得一个新的类型。

Since their signatures do not match, you cannot substitute Tuple2 for P. What you need to do is partially apply Tuple2 on one of its arguments, which will give us a type constructor with kind * -> *, and we can substitue it for P.

因为它们的签名不匹配,所以不能用Tuple2替换P,你需要做的是对Tuple2的一个参数部分应用Tuple2,这将给我们一个带有kind * -> *的类型构造函数,我们可以用P替换它。

Unfortunately Scala has no special syntax for partial application of type constructors, and so we have to resort to the monstrosity called type lambdas. (What you have in your example.) They are called that because they are analogous to lambda expressions that exist at value level.

不幸的是,Scala对于类型构造函数的部分应用程序没有特殊的语法,因此我们不得不求助于称为类型lambdas的怪物。(你的例子里有什么?)之所以叫它们,是因为它们类似于存在于值级的lambda表达式。

The following example might help:

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

// 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>

Edit:

编辑:

More value level and type level parallels.

更多的值级别和类型级别相似。

// 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>

In the case you have presented, the type parameter R is local to function Tuple2Pure and so you cannot simply define type PartialTuple2[A] = Tuple2[R, A], because there is simply no place where you can put that synonym.

在本文中,类型参数R是函数Tuple2Pure的本地参数,因此不能简单地定义类型PartialTuple2[A] = Tuple2[R, A],因为您无法将同义词放在任何位置。

To deal with such a case, I use the following trick that makes use of type members. (Hopefully the example is self-explanatory.)

要处理这种情况,我使用以下技巧来使用类型成员。(希望这个例子不言自明。)

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)]

#4


0  

type World[M[_]] = M[Int] causes that whatever we put in A in X[A] the implicitly[X[A] =:= Foo[String,Int]] is always true I guess.

type World[M[_]] = M[Int]导致无论我们在X[A]中输入什么,隐式的[X[A] =:= Foo[String,Int]]总是正确的。

#1


138  

Type lambdas are vital quite a bit of the time when you are working with higher-kinded types.

类型lambdas在处理高亲属类型的时候非常重要。

Consider a simple example of defining a monad for the right projection of Either[A, B]. The monad typeclass looks like this:

考虑一个简单的例子,定义一个monad来表示任意一个[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]
}

Now, Either is a type constructor of two arguments, but to implement Monad, you need to give it a type constructor of one argument. The solution to this is to use a type lambda:

现在,其中之一是两个参数的类型构造函数,但是要实现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]
}

This is an example of currying in the type system - you have curried the type of Either, such that when you want to create an instance of EitherMonad, you have to specify one of the types; the other of course is supplied at the time you call point or bind.

这是一个在类型系统中进行同步的例子——您已经对其中任何一个进行了处理,这样当您想要创建EitherMonad的实例时,您必须指定其中的一个类型;另一个当然是在调用point或bind时提供的。

The type lambda trick exploits the fact that an empty block in a type position creates an anonymous structural type. We then use the # syntax to get a type member.

lambda戏法利用了在类型位置上的空块创建匿名结构类型的事实。然后使用#语法获取类型成员。

In some cases, you may need more sophisticated type lambdas that are a pain to write out inline. Here's an example from my code from today:

在某些情况下,您可能需要更复杂的类型lambdas,这种类型对于内联编写非常困难。下面是我今天的代码中的一个例子:

// 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] 
}

This class exists exclusively so that I can use a name like FG[F, G]#IterateeM to refer to the type of the IterateeT monad specialized to some transformer version of a second monad which is specialized to some third monad. When you start to stack, these kinds of constructs become very necessary. I never instantiate an FG, of course; it's just there as a hack to let me express what I want in the type system.

这个类只存在,所以我可以使用一个名称,比如FG[F, G]#IterateeM来引用IterateeT monad的类型,该类型专门用于第二个monad的某些转换器版本,该版本专门用于第三个monad。当您开始堆栈时,这些类型的构造变得非常必要。当然,我从来没有实例化一个FG;它只是为了让我在类型系统中表达我想要的东西。

#2


50  

The benefits are exactly the same as those conferred by anonymous functions.

这些好处与匿名函数的好处完全相同。

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

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

An example usage, with Scalaz 7. We want to use a Functor that can map a function over the second element in a Tuple2.

使用Scalaz 7的示例用法。我们希望使用一个函子,它可以将一个函数映射到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 provides some implicit conversions that can infer the type argument to Functor, so we often avoid writing these altogether. The previous line can be rewritten as:

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

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

If you use IntelliJ, you can enable Settings, Code Style, Scala, Folding, Type Lambdas. This then hides the crufty parts of the syntax, and presents the more palatable:

如果您使用IntelliJ,您可以启用设置、代码样式、Scala、折叠、类型Lambdas。然后,它隐藏了语法中最粗糙的部分,并呈现出更美味的部分:

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

A future version of Scala might directly support such a syntax.

将来的Scala版本可能会直接支持这种语法。

#3


39  

To put things in context: This answer was originally posted in another thread. You are seeing it here because the two threads have been merged. The question statement in the said thread was as follows:

把事情放在背景中:这个答案最初是在另一个线程中发布的。您在这里看到它是因为两个线程已经合并。该主题的问题陈述如下:

How to resolve this type definition: Pure[({type ?[a]=(R, a)})#?] ?

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

What are the reasons of using such construction?

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

Snipped comes from scalaz library:

片段来自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)
  }

//...
}

Answer:

答:

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

The one underscore in the boxes after P implies that it is a type constructor takes one type and returns another type. Examples of type constructors with this kind: List, Option.

P后面框中的下划线表示它是一个类型构造函数,它接受一个类型并返回另一个类型。此类构造函数的示例:列表、选项。

Give List an Int, a concrete type, and it gives you List[Int], another concrete type. Give List a String and it gives you List[String]. Etc.

给List一个Int类型,一个具体类型,它会给你List[Int],另一个具体类型。给List一个字符串,它会给你List[String]。等。

So, List, Option can be considered to be type level functions of arity 1. Formally we say, they have a kind * -> *. The asterisk denotes a type.

因此,List, Option可以被认为是1的type level函数。正式地说,它们有一个类* -> *。星号表示类型。

Now Tuple2[_, _] is a type constructor with kind (*, *) -> * i.e. you need to give it two types to get a new type.

Tuple2[_, _]是一个具有kind(*, *) -> *的类型构造函数,也就是说,您需要给它两个类型来获得一个新的类型。

Since their signatures do not match, you cannot substitute Tuple2 for P. What you need to do is partially apply Tuple2 on one of its arguments, which will give us a type constructor with kind * -> *, and we can substitue it for P.

因为它们的签名不匹配,所以不能用Tuple2替换P,你需要做的是对Tuple2的一个参数部分应用Tuple2,这将给我们一个带有kind * -> *的类型构造函数,我们可以用P替换它。

Unfortunately Scala has no special syntax for partial application of type constructors, and so we have to resort to the monstrosity called type lambdas. (What you have in your example.) They are called that because they are analogous to lambda expressions that exist at value level.

不幸的是,Scala对于类型构造函数的部分应用程序没有特殊的语法,因此我们不得不求助于称为类型lambdas的怪物。(你的例子里有什么?)之所以叫它们,是因为它们类似于存在于值级的lambda表达式。

The following example might help:

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

// 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>

Edit:

编辑:

More value level and type level parallels.

更多的值级别和类型级别相似。

// 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>

In the case you have presented, the type parameter R is local to function Tuple2Pure and so you cannot simply define type PartialTuple2[A] = Tuple2[R, A], because there is simply no place where you can put that synonym.

在本文中,类型参数R是函数Tuple2Pure的本地参数,因此不能简单地定义类型PartialTuple2[A] = Tuple2[R, A],因为您无法将同义词放在任何位置。

To deal with such a case, I use the following trick that makes use of type members. (Hopefully the example is self-explanatory.)

要处理这种情况,我使用以下技巧来使用类型成员。(希望这个例子不言自明。)

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)]

#4


0  

type World[M[_]] = M[Int] causes that whatever we put in A in X[A] the implicitly[X[A] =:= Foo[String,Int]] is always true I guess.

type World[M[_]] = M[Int]导致无论我们在X[A]中输入什么,隐式的[X[A] =:= Foo[String,Int]]总是正确的。