编写函数文字时通过下划线进行 Scala 部分应用

Scala partial application via underscore when composing function literals

我正在编写函数文字,尽管与我见过的大多数示例不同,我从一个多参数函数开始,然后进行柯里化。

我有:

//types
case class Thing1(v: Double)
case class Thing2(v: Double)
case class Thing3(v: Double)
type Multiplier = Double

//functions
val f1 = (t: Thing1, m: Multiplier) => Thing2(m * t.v)
val f2 = (t: Thing2) => Thing3(t.v)

我想组合 f1 和 f2 以获得组合函数

Thing1 => (Multiplier => Thing3)

不出所料,以下代码无法编译:

val fcomposed1 = f1.curried.andThen(f2) // does not compile

通过实验,我能够确定以下内容确实可以编译并且具有正确的 fcomposed 签名:

val fcomposed2 = f1.curried(_:Thing1).andThen(f2)   

我已经阅读了 What are all the uses of an underscore in Scala? and possibly relevant Why does Scala apply thunks automatically, sometimes? 等各种来源,但不幸的是,我仍然无法一步一步地弄清楚这里发生了什么以及它为什么有效。

此外,我希望上面分成两个表达式的工作方式与 fcomposed2 相同,但是第二个不编译:

val f1partial = f1.curried(_:Thing1)
val fcomposed3 = f1partial.andThen(f2) // does not compile - same error as fcomposed1

看起来 f1partial returns 与 f1.curried 具有相同的签名,这让我进一步想知道早期的 fcomposed2 是如何工作的。

有人可以逐步解释这两种行为吗?

在这里,_ 充当 lambda 表达式的语法糖,但其级别可能出乎您的意料。

f1.curried 具有类型 Thing1 => Multiplier => Thing2

f1.curried(_:Thing1) 等同于 { x: Thing1 => f1.curried(x) }。由于 f1.curried(x) 的结果类型为 Multiplier => Thing2,因此整个表达式的最终类型仍然是 Thing1 => Multiplier => Thing2。所以在结果(f1partial)上调用andThen(f2)是无效的,因为函数f2(Thing2)的输入类型与前一个函数(Multiplier => Thing2).

相比之下,f1.curried(_:Thing1).andThen(f2) 扩展为 { x: Thing1 => f1.curried(x).andThen(f2) }。如前所述,f1.curried(x) 的计算结果为类型 Multiplier => Thing2,因此您 可以 对其调用 andThen(f2),从而得到 Multiplier => Thing3。那么整个表达式的计算结果为 Thing1 => Multiplier => Thing3

如果您考虑一下这两个表达式之间的区别,也许会更清楚:

val fcomposed1 = { x: Thing1 => f1.curried(x).andThen(f2) } // valid
val fcomposed2 = { x: Thing1 => f1.curried(x) }.andThen(f2) // error