在Scala中,方法、函数和部分应用函数的函数组合

18
有点类似于 Stack Overflow 的问题Compose and andThen methods,我一直在学习 Twitter 的Scala School教程,并迅速遇到了与评论者相同的问题(这很棒,因为我睡觉之前认为我的问题已经解决了)。
在教程中,它将两种方法定义如下:
def addUmm(x: String) = x + " umm"
def addAhem(x: String) = x + " ahem"

在较新版本的Scala中,您不能像这样调用它们的compose:addUmm(_).compose(addAhem(_)),但是接受的答案(以及一些其他答案似乎依赖于addUmmaddAhem是方法而不是函数,这在尝试调用compose时会产生问题。我满意地上床睡觉了,成功运行了:

scala> ((s: String) => s + " umm").compose((s: String) => s + " ahem")
res0: String => java.lang.String = <function1>

很好。问题在于,虽然不能组合方法有一定道理,但当我对我知道会评估为Function1的值做同样的事情时:
val a = (s: String) => s + " umm"
val b = (s: String) => s + " ahem"
val c = a(_).compose(b(_))

嗯,即使这次它们是函数的部分应用而不是方法,最后一行仍然会引发与原始问题相同的错误。原始问题中的一个答案(排名很高,但不是被接受的答案)似乎暗示了这与部分应用的扩展方式有关,这是什么解释?

对于 Scala 新手来说,推断器无论你是否在两个位置显式指定 _: String,都会错误地得出 a(_).compose(b(_)),但 a.compose(b) 不会,这有些令人困惑。


如果使用下划线快捷方式的lambda表达式无法正常工作,请先尝试使用正常语法。下划线经常会出现意外行为。 - ziggystar
你应该阅读我在那个问题中的回答。我准确地解释了那行代码的问题,这与方法/函数的区别无关。链接为:https://dev59.com/Dms05IYBdhLWcg3wAdWT#7506870 - Daniel C. Sobral
1
@DanielC.Sobral,其实你的回答(排名很高)就是我参考的答案(很久以前我就赞过你的回答了: ))。我抱怨的那行代码似乎是这个问题的核心,所以被接受的答案(正如你指出的)似乎有些离题。我只是想在这里梳理一下。 - Marc Bollinger
2个回答

24

a(_).compose(b(_)) 扩展为 x => { a(x).compose(y => b(y) }。因此,出现了错误。你想要的是 (x => a(x)).compose(y => b(y))。添加一对括号即可修复此问题。

scala> (a(_)).compose(b(_: String))
res56: String => java.lang.String = <function1>

scala> res56("hello")
res57: java.lang.String = helloahemumm

但由于ab都是函数,你可以避免所有这些复杂的内容,只需使用a compose b即可。


从纯风格上讲,a compose b 是否比 a.compose(b) 更受社区欢迎(尚未阅读任何实际的Scala项目)? - Marc Bollinger
1
@Marc:是的。个人而言,我没有任何硬性规定,但我倾向于仅在非字母数字方法名称中使用前者,在其他情况下则使用后者。 - missingfaktor
2
@Marc:想象一下你有这行代码:xs filter { f } map { g }。假设在以后的某个时间点,你需要在末尾添加一个 toList。你可以这样写:xs filter { f } map { g } toList。这可能会导致分号推断问题。为了避免这些问题,你可以在末尾加上分号,或者换行。我认为这两个选项都很丑陋。为了避免所有这些无聊的问题,我总是更喜欢使用 xs.filter(f).map(g)。这种语法总是更容易重构。 - missingfaktor
@Marc:在我之前的评论中规则的一个例外是:内部领域特定语言。它们使用空格语法看起来更好,通常会精心设计,以避免在以这种方式编写时导致解析问题。 - missingfaktor
谢谢!正是我在寻找的洞见类型。 - Marc Bollinger
@Marc:很高兴能帮忙。 :-) - missingfaktor

4
您可以简单地使用“a compose b”。
scala> val c = a compose b
c: String => java.lang.String = <function1>

好的,我知道我可以使用a.compose(b)实现,因为这相当于上面的内容。更多关注的是其他情况下为什么不这样做。我想我更关心部分组合的内部运作方式(以及它们的扩展),与其他问题中回答的不同的是,方法和函数的部分组合确实产生相同的类型。如果这有意义的话。如果我说的有道理的话。 - Marc Bollinger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接