【问题标题】:Underscore after function?函数后下划线?
【发布时间】:2016-09-19 23:09:36
【问题描述】:

给定

scala> def method(x: Int) = x
method: (x: Int)Int

scala> val func = (x: Int) => x
func: Int => Int = <function1>

考虑以下代码:

scala> method _
res0: Int => Int = <function1>

scala> func(_)
res1: Int => Int = <function1>

scala> func _
res2: () => Int => Int = <function0>

我可以理解res0是eta扩展,res1等价于lambda函数(x) =&gt; func(x)。但我无法弄清楚res2 的输出。谁能帮我解释一下?

【问题讨论】:

  • 你的意思是res2吗?
  • @kokorins 是的,我已经编辑过了。谢谢!

标签: scala


【解决方案1】:

这实际上有点棘手。首先,让我们看看 REPL 之外发生了什么:

func 是局部变量时,它不起作用

object Main extends App {
  def foo() = {
    val f = (_: Int) + 1
    f _
  }

  println(foo())
}

[error] /tmp/rendereraEZGWf9f1Q/src/main/scala/test.scala:8: _ must follow method; cannot follow Int => Int
[error]     f _
[error]     ^

但是如果你把它放在def foo之外,它会编译:

object Main extends App {
  val f = (_: Int) + 1
  val f1 = f _

  println(f1)
}

因为f 既是Main 的一个字段,又是一个不带参数的方法,它返回该字段的值。

最后一点是 REPL 将每一行包装成一个对象(因为 Scala 不允许代码出现在 trait/class/object 之外),所以

scala> val func = (x: Int) => x
func: Int => Int = <function1>    

真的很像

object Line1 {
  val func = (x: Int) => x
}
import Line1._
// print result

所以下一行的func 指的是Line1.func,这是一种方法,因此可以进行 eta 扩展。

【讨论】:

  • 所以它仍然是 eta 扩展工作。非常好的解释,谢谢!
  • 出色的分析。谢谢。
  • 只是好奇,关于解释“因为 f 既是 Main 的一个字段,又是一个没有参数的方法,它返回这个字段的值。”..... .. 问题是,你打电话f 以与此处讨论的相同方式作为“方法”*.com/a/2530007/4582240 ?对我来说,它不是一种方法,而是值声明和定义(函数类型和匿名函数)。
  • @MinnieShi 也许我可以说“可以被认为是一种方法”。请参阅scala-lang.org/files/archive/spec/2.13/…:“如果e 是方法类型或e 是按名称调用参数,则表达式e _ 是格式良好的。”而f 肯定不是一个别名参数。
  • @MinnieShi 另外,是的,它是函数类型的值声明;但在局部变量情况下也是如此,我回答的重点是解释这些情况之间的区别。
【解决方案2】:

您已使用 eta 扩展将 res2 转换为接受 0 个参数的函数并返回一个接受单个参数的函数。

res2: () => Int => Int = <function0>

所以你现在可以这样做了:

val zero = func _
val f: Int => Int = zero()

【讨论】:

  • 所以你的意思是它又是 eta 扩展?我对 eta 扩展的了解是它从方法中提取方法值(即函数)。但是在我的示例中我看不出为什么会这样。
  • 您可以在任何函数中添加下划线。如果你定义了一个函数val x = (a: Int) =&gt; a this:val y = x _ 可以顺利运行并返回一个Function0
  • 是的,我知道。我要问的是为什么会这样?
  • 嗨@Luka,一开始我不明白你的意思。在阅读了 Alexey Romanov 的回答后,我明白了发生了什么。你是对的,它只是 eta 扩展。但我没有意识到 REPL 中的每一行都会被包装到一个对象中(我是 Scala 的新手:P)。谢谢你的回答。
【解决方案3】:

要回答您关于 res2 的问题 - 附加的下划线 _ 语法用于部分应用函数。

所以。

scala> func _

表示您已部分应用您的&lt;function1&gt;。 这导致了一个新的柯里化形式,它的第一个函数接受零参数(因此&lt;function0&gt;

() =>

返回你原来的&lt;function1&gt;,它需要1个参数。

Int => Int = <function1>

完整的结果是函数链。

res2: () => Int => Int = <function0>

可能对您有用的规则是函数关联到右侧,因此以下是等价的。

() => Int => Int    
() => (Int => Int)

This other post 可能对你有用。

【讨论】:

    【解决方案4】:
    val func1 = func _
    

    这将返回一个不带参数的 function0,并返回 func 函数。

    你可以这样使用:

    func1()(2) // outputs 2
    

    你可以无限地继续做这种扩展广告:

    val func2 = func1 _
    
    func2()()(2) // outputs 2
    

    【讨论】:

    • 我看到发生了什么,但我无法解释原因。它是一种特殊的语法吗?还是只是对某些已知语法的不太明显的使用?
    • 我明白你想了解什么。似乎 eta 扩展也在函数上发生。但是语言规范说它仅适用于方法或按名称调用类型的参数:=> T
    • 是的,所以我很好奇发生了什么; )
    • Alexey Romanov 给出了很好的解释。事实证明,正在发生 eta 扩展。希望对您有所帮助!