无法弄清楚Scala撰写函数中的语法
我正在阅读红皮书,在检查我的练习答案时,我发现其中一个练习 (6.11) 的解决方案完全不同(并且比我自己的解决方案更优雅和神秘)。
这是代码:
object Candy {
def update: Input => Machine => Machine = (i: Input) => (s: Machine) =>
(i, s) match {
case (_, Machine(_, 0, _)) => s
case (Coin, Machine(false, _, _)) => s
case (Turn, Machine(true, _, _)) => s
case (Coin, Machine(true, candy, coin)) =>
Machine(false, candy, coin + 1)
case (Turn, Machine(false, candy, coin)) =>
Machine(true, candy - 1, coin)
}
def simulateMachine(inputs: List[Input]): State[Machine, (Int, Int)] = for {
_ <- State.sequence(inputs map (modify[Machine] _ compose update))
s <- get
} yield (s.coins, s.candies)
def modify[S](f: S => S): State[S, Unit] = for {
s <- get // Gets the current state and assigns it to `s`.
_ <- set(f(s)) // Sets the new state to `f` applied to `s`.
} yield ()
def get[S]: State[S, S] = State(s => (s, s))
def set[S](s: S): State[S, Unit] = State(_ => ((), s))
我不确定的是这一行:
inputs map (modify[Machine] _ compose update)
我了解 compose 的工作原理,但这种特殊的语法确实让我陷入了循环。有没有一种方法可以重写它,而不是那么紧凑以帮助菜鸟理解?
提前致谢。
对于其他试图更好地理解此代码如何工作的人,我发现这篇文章很有帮助。
回答
这里使用了几个语法特性:
运算符语法
Scala 允许消息发送使用空格而不是句点作为消息发送操作符,即
foo.bar(baz, quux)
也可以写成
foo bar(baz, quux)
运算符语法,第 2 部分
当使用带有单个参数的单个参数列表的运算符语法时,括号可以省略,即
foo bar(baz)
也可以写成
foo bar baz
占位符语法
Scala 中的匿名函数可以使用下划线_作为参数的占位符来编写。粗略地说,每个下划线都被最接近的词法封闭匿名函数的每个参数替换,按照在源文本中出现的顺序,即
val adder: (Int, Int) => Int = (x, y) => x + y
也可以写成
val adder: (Int, Int) => Int = _ + _
不是 ?-扩展
下划线_在 Scala 中有很多用途,所以有时如果你不够仔细,你可能会混淆不同的用法。在这种情况下,粗略一看,似乎下划线_可能意味着modify? 扩展为方法值,但事实并非如此。下划线_是匿名函数参数占位符。
结论
因此,如果我们将上述三个语法特征放在一起,则有问题的代码段将脱糖为
inputs.map(
// ? Operator Syntax
(
f => modify[Machine](f)
// ? Placeholder Syntax ?
).compose(update)
// ? Operator Syntax
)
如果你有什么特定的代码方式的语法,你可以打印出Scala编译器或斯卡拉REPL /“解释”在使用的各个阶段的内部状态的疑虑-Xprint:<name-of-phase>命令行选项scala。例如,这是-Xprint:parser相关代码段的打印内容:
inputs.map((modify[Machine]: (() => <empty>)).compose(update))
这是-Xprint:typer:
inputs.map[this.State[this.Machine, Unit]](
(
(f: this.Machine => this.Machine) => $anon.this.State.modify[this.Machine](f)
).compose[this.Input](Candy.this.update)
)