函数f
定义为:
def f(i1: Int, i2: Int)(i3: Int) = i1 + i2 + i3
可以使用_
如下定义部分应用的函数:
val f12 = f(1, 2) _ // f12: Int => Int = <function>
f12(3) // res0: Int = 6
现在,当我从函数返回部分应用的函数时,不需要使用_
:
def f23(f: (Int, Int) => Int => Int) = f(2, 3) // f23: (f: (Int, Int) => Int => Int)Int => Int
val f23f = f23(f) // f23f: Int => Int = <function>
f23f(4) // res1: Int = 9
如果将_
放在f23
的定义上,我将得到一个错误:
def f23(f: (Int, Int) => Int => Int) = f(2, 3) _
Error:(6, 49) _ must follow method; cannot follow Int => Int
def f23(f: (Int, Int) => Int => Int) = f(2, 3) _
这种不一致的原因是什么?
答案 0 :(得分:3)
f
是方法,而不是函数。您可以阅读有关here的一些差异。
f12
是通过eta扩展从f
派生的函数。它不是部分功能。 PartialFunction
是在输入值的有限域内定义的函数。例如,如果f12
仅针对小于500的Int
值定义,而对于大于500的输入值则未定义,那么它将是部分函数。
def f23(f: (Int, Int) => Int => Int) = f(2, 3) _
之所以失败,是因为此处定义的f
是一个具有2个Int
值并返回一个具有Int
并返回Int
的函数。在这种情况下,下划线应该代表什么? f(2,3)
是一个完整的调用,返回一个Int=>Int
函数。有点像写5 + 7 _
。尚不清楚_
所代替的是什么。
另一方面,您可以执行此操作:... = f(2,3)(_)
。那么很明显,返回的函数正在使用缺少的参数_
来调用。与... = f(2,3)
相同。
答案 1 :(得分:1)
这是Scala中设计的,目的是防止开发人员感到困惑。 如果您明确告诉编译器f12的类型,它将按预期工作:
`val f12:Int=>Int = f(1, 2)`
这是由语言创建者(Martin Odersky)解释的:
为什么下划线是下划线?
Scala的部分应用函数的语法突显了Scala与经典功能语言(例如Haskell或ML)在设计权衡方面的差异。在这些语言中,部分应用的功能被认为是正常情况。此外,这些语言具有相当严格的静态类型系统,该系统通常会突出显示您可以创建的部分应用程序中的每个错误。 Scala与命令式语言(例如Java)有着更紧密的联系,在Java中,未应用到其所有参数的方法被视为错误。此外,子类型化和通用根类型的面向对象传统接受了一些在经典功能语言中被认为是错误的程序。
例如,假设您误认为List的drop(n:Int)方法用于tail(),因此您忘记了需要传递一个数字才能删除。您可以编写“ println(drop)”。如果Scala采纳了部分地方都可以使用部分函数的经典函数传统,那么该代码将进行类型检查。但是,您可能会惊讶地发现此println语句输出的输出始终为!将会发生的事情是表达式删除将被视为函数对象。因为println可以接受任何类型的对象,所以可以编译OK,但是会产生意外的结果。
为避免这种情况,Scala通常要求您指定显式省略的函数参数,即使指示像“ _”一样简单。 Scala允许您仅在需要函数类型时才使用_。