Scala 如何转换 case 类 以被接受为函数?
How does Scala transform case classes to be accepted as functions?
我想了解如何将案例 class 作为参数传递给接受函数作为参数的函数。下面是一个例子:
考虑以下函数
def !: In[B] = { ... }
如果我没理解错的话,这是一个多态方法,它有一个类型参数 B
并接受一个函数 h
作为参数。 Out
和 In
是之前定义的另外两个 class。
此函数的使用如下所示:
case class Q(p: boolean)(val cont: Out[R])
case class R(p: Int)
def g(c: Out[Q]) = {
val rin = c !! Q(true)_
...
}
我知道正在使用柯里化来避免编写类型注释,而只是编写 _
。但是,我无法理解 class Q
为什么以及如何转换为 Out[B] => A
.
类型的函数 (h)
编辑 1 已更新!!以上以及 In
和 Out
定义:
abstract class In[+A] {
def future: Future[A]
def receive(implicit d: Duration): A = {
Await.result[A](future, d)
}
def ?[B](f: A => B)(implicit d: Duration): B = {
f(receive)
}
}
abstract class Out[-A]{
def promise[B <: A]: Promise[B]
def send(msg: A): Unit = promise.success(msg)
def !(msg: A) = send(msg)
def create[B](): (In[B], Out[B])
}
这些代码示例摘自以下论文:http://drops.dagstuhl.de/opus/volltexte/2016/6115/
TLDR;
使用带有多个参数列表的案例 class 并部分应用它会产生部分应用的 apply
调用 + eta 扩展会将方法转换为函数值:
val res: Out[Q] => Q = Q.apply(true) _
更长的解释
要了解它在 Scala 中的工作方式,我们必须了解 case classes 背后的一些基础知识以及方法和函数之间的区别。
Scala 中的 Case classes 是一种表示数据的紧凑方式。当你定义一个 case class 时,你会得到一堆由编译器为你创建的便捷方法,例如 hashCode
和 equals
.
此外,编译器还生成了一个名为apply
的方法,它允许您在不使用new
关键字的情况下创建一个caseclass实例:
case class X(a: Int)
val x = X(1)
编译器会将此调用扩展为
val x = X.apply(1)
你的案例 class 也会发生同样的事情,只是你的案例 class 有多个参数列表:
case class Q(p: boolean)(val cont: Out[R])
val q: Q = Q(true)(new Out[Int] { })
将被翻译成
val q: Q = Q.apply(true)(new Out[Int] { })
最重要的是,Scala 有一种方法可以转换方法,即 non value type, into a function type which has the type of FunctionX
, X being the arity of the function. In order to transform a method into a function value, we use a trick called eta expansion 我们用下划线调用方法。
def foo(i: Int): Int = i
val f: Int => Int = foo _
这会将方法 foo
转换为类型 Function1[Int, Int]
的函数值。
现在我们掌握了这些知识,让我们回到您的示例:
val rin = c !! Q(true) _
如果我们在这里隔离 Q,则此调用将转换为:
val rin = Q.apply(true) _
由于 apply
方法与多个参数列表一起使用,我们将返回一个给定 Out[Q]
的函数,将创建一个 Q
:
val rin: Out[R] => Q = Q.apply(true) _
I cannot grasp why and how the case class Q
is transformed to a function (h) of type Out[B] => A
.
不是。事实上,case class Q
与此完全无关!这是关于 object Q
的全部内容,它是 配套模块 到 case class Q
.
每个 case class has an automatically generated companion module,其中包含(除其他外)一个 apply
方法,其签名与同伴 class 的主构造函数相匹配,并且构造同伴 class.
即当你写
case class Foo(bar: Baz)(quux: Corge)
您不仅可以获得自动定义的 case class 便利方法,例如所有 元素的访问器 、toString
、hashCode
, copy
和 equals
,但您还会得到一个自动定义的伴随模块,它既用作模式匹配的提取器,又用作对象构造的工厂:
object Foo {
def apply(bar: Baz)(quux: Corge) = new Foo(bar)(quux)
def unapply(that: Foo): Option[Baz] = ???
}
在 Scala 中,apply
是一种允许您创建 "function-like" 对象的方法:如果 foo
是一个对象(而不是方法),那么 foo(bar, baz)
is translated to foo.apply(bar, baz)
.
拼图的最后一块是η-expansion,它将方法(不是对象)提升为函数(其中 是 一个对象,因此可以作为参数传递,存储在变量中等。) η 展开有两种形式:explicit 使用 _
运算符的 η 展开:
val printFunction = println _
和隐式 η-展开:在 Scala 100% 知道你指的是一个函数但你给它一个方法名称的情况下,Scala 将执行 η-展开你:
Seq(1, 2, 3) foreach println
而且您已经知道柯里化了。
所以,如果我们把它们放在一起:
Q(true)_
首先,我们知道这里的Q
不可能是classQ
。我们怎么知道的?因为这里的Q
是作为值使用的,但是classes是类型,和大多数编程语言一样,Scala 严格区分类型和值。因此,Q
必须是一个值。特别是,由于我们知道 class Q
是一个案例 class,对象 Q
是 class Q
.[=53 的伴随模块=]
其次,我们知道对于值 Q
Q(true)
是
的语法糖
Q.apply(true)
第三,我们知道对于案例classes,伴随模块有一个自动生成的apply
方法匹配主构造函数,所以我们知道Q.apply
有两个参数列出。
所以,最后,我们有
Q.apply(true) _
将第一个参数列表传递给 Q.apply
,然后将 Q.apply
提升到接受第二个参数列表的函数中。
请注意,具有多个参数列表的案例 class 是不常见的,因为只有第一个参数列表中的参数被视为案例 [=111= 的 元素 ],并且只有元素受益于 "case class magic",即只有元素获得自动实现的访问器,只有元素用于 copy
方法的签名,只有元素用于自动生成的 equals
、hashCode
和 toString()
方法等。
我想了解如何将案例 class 作为参数传递给接受函数作为参数的函数。下面是一个例子:
考虑以下函数
def !: In[B] = { ... }
如果我没理解错的话,这是一个多态方法,它有一个类型参数 B
并接受一个函数 h
作为参数。 Out
和 In
是之前定义的另外两个 class。
此函数的使用如下所示:
case class Q(p: boolean)(val cont: Out[R])
case class R(p: Int)
def g(c: Out[Q]) = {
val rin = c !! Q(true)_
...
}
我知道正在使用柯里化来避免编写类型注释,而只是编写 _
。但是,我无法理解 class Q
为什么以及如何转换为 Out[B] => A
.
编辑 1 已更新!!以上以及 In
和 Out
定义:
abstract class In[+A] {
def future: Future[A]
def receive(implicit d: Duration): A = {
Await.result[A](future, d)
}
def ?[B](f: A => B)(implicit d: Duration): B = {
f(receive)
}
}
abstract class Out[-A]{
def promise[B <: A]: Promise[B]
def send(msg: A): Unit = promise.success(msg)
def !(msg: A) = send(msg)
def create[B](): (In[B], Out[B])
}
这些代码示例摘自以下论文:http://drops.dagstuhl.de/opus/volltexte/2016/6115/
TLDR;
使用带有多个参数列表的案例 class 并部分应用它会产生部分应用的 apply
调用 + eta 扩展会将方法转换为函数值:
val res: Out[Q] => Q = Q.apply(true) _
更长的解释
要了解它在 Scala 中的工作方式,我们必须了解 case classes 背后的一些基础知识以及方法和函数之间的区别。
Scala 中的 Case classes 是一种表示数据的紧凑方式。当你定义一个 case class 时,你会得到一堆由编译器为你创建的便捷方法,例如 hashCode
和 equals
.
此外,编译器还生成了一个名为apply
的方法,它允许您在不使用new
关键字的情况下创建一个caseclass实例:
case class X(a: Int)
val x = X(1)
编译器会将此调用扩展为
val x = X.apply(1)
你的案例 class 也会发生同样的事情,只是你的案例 class 有多个参数列表:
case class Q(p: boolean)(val cont: Out[R])
val q: Q = Q(true)(new Out[Int] { })
将被翻译成
val q: Q = Q.apply(true)(new Out[Int] { })
最重要的是,Scala 有一种方法可以转换方法,即 non value type, into a function type which has the type of FunctionX
, X being the arity of the function. In order to transform a method into a function value, we use a trick called eta expansion 我们用下划线调用方法。
def foo(i: Int): Int = i
val f: Int => Int = foo _
这会将方法 foo
转换为类型 Function1[Int, Int]
的函数值。
现在我们掌握了这些知识,让我们回到您的示例:
val rin = c !! Q(true) _
如果我们在这里隔离 Q,则此调用将转换为:
val rin = Q.apply(true) _
由于 apply
方法与多个参数列表一起使用,我们将返回一个给定 Out[Q]
的函数,将创建一个 Q
:
val rin: Out[R] => Q = Q.apply(true) _
I cannot grasp why and how the case class
Q
is transformed to a function (h) of typeOut[B] => A
.
不是。事实上,case class Q
与此完全无关!这是关于 object Q
的全部内容,它是 配套模块 到 case class Q
.
每个 case class has an automatically generated companion module,其中包含(除其他外)一个 apply
方法,其签名与同伴 class 的主构造函数相匹配,并且构造同伴 class.
即当你写
case class Foo(bar: Baz)(quux: Corge)
您不仅可以获得自动定义的 case class 便利方法,例如所有 元素的访问器 、toString
、hashCode
, copy
和 equals
,但您还会得到一个自动定义的伴随模块,它既用作模式匹配的提取器,又用作对象构造的工厂:
object Foo {
def apply(bar: Baz)(quux: Corge) = new Foo(bar)(quux)
def unapply(that: Foo): Option[Baz] = ???
}
在 Scala 中,apply
是一种允许您创建 "function-like" 对象的方法:如果 foo
是一个对象(而不是方法),那么 foo(bar, baz)
is translated to foo.apply(bar, baz)
.
拼图的最后一块是η-expansion,它将方法(不是对象)提升为函数(其中 是 一个对象,因此可以作为参数传递,存储在变量中等。) η 展开有两种形式:explicit 使用 _
运算符的 η 展开:
val printFunction = println _
和隐式 η-展开:在 Scala 100% 知道你指的是一个函数但你给它一个方法名称的情况下,Scala 将执行 η-展开你:
Seq(1, 2, 3) foreach println
而且您已经知道柯里化了。
所以,如果我们把它们放在一起:
Q(true)_
首先,我们知道这里的Q
不可能是classQ
。我们怎么知道的?因为这里的Q
是作为值使用的,但是classes是类型,和大多数编程语言一样,Scala 严格区分类型和值。因此,Q
必须是一个值。特别是,由于我们知道 class Q
是一个案例 class,对象 Q
是 class Q
.[=53 的伴随模块=]
其次,我们知道对于值 Q
Q(true)
是
的语法糖Q.apply(true)
第三,我们知道对于案例classes,伴随模块有一个自动生成的apply
方法匹配主构造函数,所以我们知道Q.apply
有两个参数列出。
所以,最后,我们有
Q.apply(true) _
将第一个参数列表传递给 Q.apply
,然后将 Q.apply
提升到接受第二个参数列表的函数中。
请注意,具有多个参数列表的案例 class 是不常见的,因为只有第一个参数列表中的参数被视为案例 [=111= 的 元素 ],并且只有元素受益于 "case class magic",即只有元素获得自动实现的访问器,只有元素用于 copy
方法的签名,只有元素用于自动生成的 equals
、hashCode
和 toString()
方法等。