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

ron*_*ron 149 types scala

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

def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..} 
Run Code Online (Sandbox Code Playgroud)

在Scala博客文章中,它给了它一个"我们使用那种类型 - lambda技巧"的手动波.

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

Kri*_*mbe 147

当你使用更高级的类型时,类型lambda是非常重要的.

考虑一个为Aither [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]
}
Run Code Online (Sandbox Code Playgroud)

现在,要么是两个参数的类型构造函数,但要实现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]
}
Run Code Online (Sandbox Code Playgroud)

这是在类型系统中进行currying的一个示例 - 您已经知道了Either的类型,这样当您想要创建EitherMonad的实例时,您必须指定其中一种类型; 另一方面当然是在你调用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] 
}
Run Code Online (Sandbox Code Playgroud)

这个类专门存在,所以我可以使用像FG [F,G] #IterateeM这样的名称来引用IterateeT monad的类型,专门用于第二个monad的某些变换器版本,专门用于某些第三个monad.当你开始堆叠时,这些类型的构造变得非常必要.当然,我从未实例化过FG; 它只是作为一个黑客让我在类型系统中表达我想要的东西.

  • 有趣的是,[Haskell确实*不*直接支持类型级lambda](http://stackoverflow.com/questions/4069840/lambda-for-type-expressions-in-haskell),虽然有些新类型hackery(例如TypeCompose库)有办法解决这个问题. (3认同)
  • 我认为那种`*`是order-1,但无论如何Monad有点`(*=>*)=>*`.另外,你会注意到我指定了"对[A,B]"的正确投影 - 实现是微不足道的(但如果你以前没有这样做,那么这是一个很好的练习!) (2认同)
  • Scala 3 引入了更简单的[类型 lambda 语法](https://docs.scala-lang.org/scala3/reference/new-types/type-lambdas.html)。使用新语法,上面的 `EitherMonad` 定义变为 `EitherMonad[A] extends Monad[[α] =>> Either[A, α]]` (2认同)

ret*_*nym 52

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

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

List(1, 2, 3).map(a => a + 1)
Run Code Online (Sandbox Code Playgroud)

使用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)
Run Code Online (Sandbox Code Playgroud)

Scalaz提供了一些可以推断类型参数的隐式转换Functor,因此我们经常避免完全编写这些转换.上一行可以重写为:

(1, 2).map(a => a + 1) // (1, 3)
Run Code Online (Sandbox Code Playgroud)

如果使用IntelliJ,则可以启用"设置","代码样式","Scala","折叠","键入Lambdas".然后,这隐藏了语法的苛刻部分,并呈现更可口:

Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3)
Run Code Online (Sandbox Code Playgroud)

Scala的未来版本可能直接支持这种语法.

  • 它被移动到设置 - >编辑器 - >代码折叠. (6认同)

mis*_*tor 40

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

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

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

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

//...
}
Run Code Online (Sandbox Code Playgroud)

回答:

trait Pure[P[_]] {
  def pure[A](a: => A): P[A]
}
Run Code Online (Sandbox Code Playgroud)

后面的方框中的下划线P暗示它是一个类型构造函数采用一种类型并返回另一种类型.这种类型构造函数的示例:List,Option.

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

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

现在Tuple2[_, _]是一个类型的构造函数,(*, *) -> *即你需要给它两种类型来获得一个新类型.

由于他们的签名不匹配,你不能代替Tuple2P.您需要做的是部分应用于 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>
Run Code Online (Sandbox Code Playgroud)

编辑:

更多价值水平和类型水平相似.

// 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>
Run Code Online (Sandbox Code Playgroud)

在您提供的情况下,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)]
Run Code Online (Sandbox Code Playgroud)