Scala 如何转换 case 类 以被接受为函数?

How does Scala transform case classes to be accepted as functions?

我想了解如何将案例 class 作为参数传递给接受函数作为参数的函数。下面是一个例子:

考虑以下函数

def !![B](h: Out[B] => A): In[B] = { ... }

如果我没理解错的话,这是一个多态方法,它有一个类型参数 B 并接受一个函数 h 作为参数。 OutIn 是之前定义的另外两个 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 已更新!!以上以及 InOut 定义:

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 时,你会得到一堆由编译器为你创建的便捷方法,例如 hashCodeequals.

此外,编译器还生成了一个名为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 便利方法,例如所有 元素的访问器 toStringhashCodecopyequals,但您还会得到一个自动定义的伴随模块,它既用作模式匹配的提取器,又用作对象构造的工厂:

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 方法的签名,只有元素用于自动生成的 equalshashCodetoString() 方法等。