什么是 Scala 中的类型 lambda,它们有什么好处?
Posted
技术标签:
【中文标题】什么是 Scala 中的类型 lambda,它们有什么好处?【英文标题】:What are type lambdas in Scala and what are their benefits? 【发布时间】:2012-02-02 21:40:44 【问题描述】:有时我偶然发现了
的半神秘符号def f[T](..) = new T[(type l[A]=SomeType[A,..])#l] ..
在 Scala 博客文章中,它给出了“我们使用了那种类型 lambda 技巧”的手波。
虽然我对此有一些直觉(我们获得了一个匿名类型参数A
,而不必用它污染定义?),我发现没有明确的来源描述什么是类型 lambda 技巧,以及它有什么好处。它只是语法糖,还是打开了一些新的维度?
【问题讨论】:
见also。 【参考方案1】:把事情放在上下文中:这个答案最初发布在另一个线程中。您在这里看到它是因为这两个线程已合并。该线程中的问题陈述如下:
如何解析这种类型定义:Pure[(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) //...
答案:
trait Pure[P[_]]
def pure[A](a: => A): P[A]
P
后面的方框中的一个下划线表示它是一个类型构造函数,采用一种类型并返回另一种类型。此类类型构造函数的示例:List
、Option
。
给List
一个Int
,一个具体类型,它给你List[Int]
,另一个具体类型。给List
一个String
它给你List[String]
。等等。
所以,List
、Option
可以被认为是 arity 1 的类型级函数。正式地说,它们有一种 * -> *
。星号表示类型。
现在Tuple2[_, _]
是一个类型构造函数,类型为(*, *) -> *
,即你需要给它两种类型来获得一个新类型。
由于他们的签名不匹配,您不能用Tuple2
替换P
。您需要做的是部分应用 Tuple2
在它的一个参数上,这将为我们提供一个类型为* -> *
的类型构造函数,我们可以用它替换P
。
不幸的是,Scala 没有用于类型构造函数的部分应用的特殊语法,因此我们不得不求助于称为 lambda 类型的怪物。 (您的示例中有什么。)之所以这样称呼它们,是因为它们类似于存在于值级别的 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)]
【讨论】:
【参考方案2】:好处与匿名函数所赋予的完全一样。
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。这再hides the crufty parts of the syntax,又呈现出更可口的:
Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3)
Scala 的未来版本可能会直接支持这种语法。
【讨论】:
最后一个 sn-p 看起来真不错。 IntelliJ scala 插件肯定很棒! 谢谢!最后一个示例中可能缺少 lambda。另外,为什么元组函子选择转换最后一个值?它是惯例/实际默认值吗? 我正在为 Nika 提供夜间服务,但我没有描述 IDEA 选项。有趣的是,有检查“应用类型 Lambda 可以简化。” 已移至设置 -> 编辑器 -> 代码折叠。 “隐藏语法中难懂的部分”链接已损坏。【参考方案3】:当您使用更高种类的类型时,类型 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,但内联写出来会很痛苦。这是我今天的代码示例:
// 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 专门用于某个第三个 monad。当你开始堆叠时,这些类型的构造变得非常必要。当然,我从不实例化 FG。它只是作为一种技巧,让我在类型系统中表达我想要的东西。
【讨论】:
有趣的是Haskell does not directly support type-level lambdas,尽管一些新类型的黑客(例如 TypeCompose 库)有办法解决这个问题。 我很想看到您为您的EitherMonad
类定义 bind
方法。 :-) 除此之外,如果我可以在这里引导 Adriaan 一秒钟,那么在该示例中您没有使用更高种类的类型。你在FG
,但不在EitherMonad
。相反,您正在使用 类型构造函数,它具有类型 * => *
。这种是 1 阶的,不是“更高”的。
我认为那种*
是order-1,但无论如何Monad 有一种(* => *) => *
。此外,您会注意到我指定了“Either[A, B]
的正确投影” - 实现很简单(但如果您以前没有做过,这是一个很好的练习!)
我猜 Daniel 的观点不调用 *=>*
更高是通过我们不调用普通函数的类比来证明的(将非函数映射到非函数,换句话说,将普通值映射到普通值)高阶函数。
皮尔斯的 TAPL 书,第 442 页:Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
以上是关于什么是 Scala 中的类型 lambda,它们有什么好处?的主要内容,如果未能解决你的问题,请参考以下文章
Rust闭包和Haskell lambda有什么区别? [关闭]