这些scala方法中下划线用法的区别

The differences between underscore usage in these scala's methods

这些代码中的下划线用法有什么区别和术语名称:(请参阅 handler(resource) 部分)

1.

def readFile[T](f: File)(handler: FileInputStream => Byte => T): T = {
    val resource = new java.io.FileInputStream(f)
    try {
        val hh = handler(resource)_
        hh(2)
    } finally {
        resource.close()
    }
}

val bs = new Array[Byte](4)

readFile(new File("scala.txt")) {
    input => b: Byte => println("Read: " + (input.read(bs) + b))
}

我遇到编译错误:

Error:(55, 29) _ must follow method; cannot follow Byte => T
            val hh = handler(resource)_
                        ^

这是什么意思?

2.

def readFile[T](f: File)(handler: FileInputStream => Byte => T): T = {
    val resource = new java.io.FileInputStream(f)
    try {
        val hh = handler(resource) _
        hh(2)
    } finally {
        resource.close()
    }
}

// Lower parts are same, so removed for brevity...
// ...

结果同否。 1,我得到:_ must follow method编译错误。

我读到这是因为下划线用于将方法转换为函数(ETA Expansion),但我也看到相同的下划线用于 Partial应用Function没有问题,例如:

val sum = (x: Int, y: Int) => x + y
val sum2 = sum _

在这种情况下没有错误。

3.

def readFile[T](f: File)(handler: FileInputStream => Byte => T): T = {
    val resource = new java.io.FileInputStream(f)
    try {
        val hh = handler(resource)(_)
        hh(2)
    } finally {
        resource.close()
    }
}

//...

这个很好用。如果我没记错的话,这种情况下的下划线叫做 ETA Expansion,对吗?但是我也看了this Q/A,这种下划线是针对Partial Applied Function的。在同一个页面中,有人还说这是一个 Placeholder syntax。那么哪一个是正确的?

4.

def readFile[T](f: File)(handler: FileInputStream => Byte => T): T = {
    val resource = new java.io.FileInputStream(f)
    try {
        val hh = handler(resource)
        hh(2)
    } finally {
        resource.close()
    }
}

//...

这个不使用下划线,但它也可以正常工作,就像不一样。 3.我对这个案例的问题,和no有什么区别。 3?我应该使用否。 4比没有。 3?下划线是否多余。 3?

很抱歉这里有很多问题,但是下划线的东西真的很混乱。

不知何故,我认为 Scala 中下划线的复杂性与 C/C++ 中指针和引用 (*/&/&&) 的复杂性相匹配。

更新:

5.

关于下划线,我又发现了一些有趣的东西:

scala> def sum(x: Int, y: Int) = x + y     // sum is a method because of def
sum: (x: Int, y: Int)Int

scala> val sum2 = sum _    // sum2 is explicit ETA expanded function from sum
sum2: (Int, Int) => Int = <function2>

scala> sum2(2,3)      // testing
res0: Int = 5

scala> val sum3 = (x: Int, y: Int) => x + y      // sum3 is a function object
sum3: (Int, Int) => Int = <function2>

scala> val sum4 = sum3 _           // what happpened here?
sum4: () => (Int, Int) => Int = <function0>

scala> sum4()(2,3)
res2: Int = 5

你能告诉我 sum4 发生了什么事吗?为什么 sum3 _ 的结果具有函数类型:() => (Int, Int) => Int?

6.

List(1, 2, 3) foreach println _

根据this answer,这是部分应用的函数。好的,我可以看到下划线之前的 space 有点棘手。它实际上与:

List(1, 2, 3).foreach(println(_))

所以这确实是部分应用函数。

但如果我这样做:

scala> List(1, 2, 3).foreach(println _+1)  //#1
<console>:8: error: type mismatch;
 found   : Int(1)
 required: String
              List(1, 2, 3).foreach(println _+1)
                                          ^

scala> List(1, 2, 3).foreach(println _+"#")    //#2 printed out nothing (why?)

scala> List(1, 2, 3).foreach(println 1+_)      //#3
<console>:1: error: ')' expected but integer literal found.
       List(1, 2, 3).foreach(println 1+_)
                                     ^

scala> List(1, 2, 3).foreach(println "#"+_)    //#4
<console>:1: error: ')' expected but string literal found.
       List(1, 2, 3).foreach(println "#"+_)
                                     ^

新手通常会认为这种情况下的下划线是占位符,但我相信不是,不是吗?

1 & 2 - 是相同的,这是 eta-expansion,这意味着函数正在从仅作为语言一部分的函数转换为某些语言的真实对象FunctionN class:

scala> def f(a: Int) = a
f: (a: Int)Int

scala> f.apply(1)
<console>:9: error: missing arguments for method f;
follow this method with `_' if you want to treat it as a partially applied function
              f.apply(1)
              ^
scala> f _
res1: Int => Int = <function1>    

scala> (f _).apply(1)
res2: Int = 1

它在你的例子中不起作用,因为 handler(resource) 是一个 returns 函数对象 Byte => T 的表达式(因为 handler 是一个函数对象 FileInputStream => Byte => T 并且您对其进行了部分应用),因此 Scala 无法对表达式进行 eta 扩展(仅适用于值和方法)。

4 部分应用为 scala 的柯里化函数支持的副作用(curried 我的意思是能够逐一获取参数)。

3 就是明确的 partially applied.

请注意,在所有 3 个示例中 - 您的 handler: FileInputStream => Byte => T 函数是一个对象(因此它已经经过 eta 扩展),如果您尝试使用多参数列表方法(尚未扩展为 curried 函数) - 您将收到 1&2&4 的相反结果:

scala> def f(a: Int)(b: Int) = a //it's not a curried function, as it's just multi-parameter-list method
f: (a: Int)(b: Int)Int

scala> f(2) 
<console>:9: error: missing arguments for method f;
follow this method with `_' if you want to treat it as a partially applied function
              f(2)
           ^
scala> f(2) _ //you need to convert f(2) to object first
res4: Int => Int = <function1>

scala> f(2)(_)
res5: Int => Int = <function1>

scala> f _  //expand method to the function object
res6: Int => (Int => Int) = <function1>

因此,如果需要,部分应用程序还会为您进行 eta 扩展。您可能还会将 eta-expansion 视为(不准确)具有 0 个部分应用参数的函数,因此它非常接近,因为部分应用函数始终是 scala 中的对象(在 haskell 中它是 first-class function) because you always need partially applied function to be first-class-citizen(如对象或 f-c 函数)在 eta 扩展后应用它。

5。 Scala 可以对值本身进行 eta 扩展,因为它们可能被视为具有 0 个参数的编译时函数(这就是您看到 () => ... 的原因)。它可以将任意值扩展到函数对象:

scala> val k = 5
k: Int = 5

scala> val kk = k _
kk: () => Int = <function0>

scala> val kkk = kk _
kkk: () => () => Int = <function0>

scala> 

在您的示例中 - 值只是另一个函数对象本身。 (Int, Int) => Int 也不是完全柯里化的函数(它逐个计算参数),但 scala 也可以自动部分应用这样的函数。使其完全咖喱:

scala> def f(a: Int, b: Int) = a
f: (a: Int, b: Int)Int

scala> (f _).curried
res23: Int => (Int => Int) = <function1>

scala> def f(a: Int, b: Int)(z: Int) = a
f: (a: Int, b: Int)(z: Int)Int

scala> (f _).curried
res22: Int => (Int => (Int => Int)) = <function1>

这个过程实际上称为 currying。

另一种使其咖喱化的方法是使用元组。它并不像 currying 实际上是删除 tuples 那样纯粹,但是 scala 的 Tuple 只是一个 class 而不是参数列表中的元组: (Int, Int) => Int - 输入不是 scala 术语中的元组,而是 ((Int, Int)) => Int,输入是一个元组(不管从 FP 的角度来看,它在第一种情况下是一个对象的元组,在第二种情况下是一个对象的元组)。伪叠加示例:

 scala> def f(a: Int, b: Int) = a
 f: (a: Int, b: Int)Int

 scala> (f _).tupled
 res24: ((Int, Int)) => Int = <function1>

5 vs 1&2 如您之前所见,您不能对表达式应用 eta 扩展,只能 methods/values/vars:

 scala> 5 _
 <console>:8: error: _ must follow method; cannot follow Int(5)
          5 _
          ^

 scala> val (a, b) = (5, 5)

 scala> (a + b) _
 <console>:10: error: _ must follow method; cannot follow Int
              (a + b) _
                 ^

您在错误消息中看到了 "method" 要求,但 scala 旨在以相同的方式(至少部分地)对待 methods/values/vars(当它们是 class/object 的成员时) ) 支持 UAP.

6 是eta扩展,默认returns Function0:

scala> val a = println _
a: () => Unit = <function0>

你可能期望 function1 在这里,但是 println 被重载并且 eta-expansion 机制选择最少的签名。当需要其他类型时(如 Function1 中的 foreach)- 它可能会选择另一个:

scala> val a: String => Unit = println _
a: String => Unit = <function1>

正如我所说,您可以将函数对象视为部分应用了 0 个参数的函数(如果需要,它包括 eta 扩展),所以这是与另一个 answer 混淆的根源(我会选择更好的例子那里)。

正如我在 P.S.2 中所说,此扩展可能会自动应用:

scala> List(1,2) foreach println
1
2

关于 println _ +"#" - 它之所以有效,是因为 scala 中的任何 class(包括 Function1)都有 implicit def + (s: String)(这就是为什么 Int 在这里不起作用) 在 Predef 中定义(参见 SI-194):

scala> println _
res50: () => Unit = <function0>

scala> println _ + "#"
res51: String = <function0>#

由于 5 vs 1&2,所有其他选项都不起作用,实际上 scala 甚至无法在单参数函数后解析字符串:

scala> println "#"
<console>:1: error: ';' expected but string literal found.
   println "#"
           ^

您应该指定对象主机来修复它,因为 scala 期望类似 "obj method param" 的东西(但这是实验性功能,有时您需要粘贴一些空行或“;”才能使其工作):

scala> Predef println "aaa"
aaa

P.S。关于 C++ reference/pointer。函数没有价值,因为它是编译时结构,因此编译器只是为它创建一个值,该过程称为 eta-expansion(或纯函数的 eta-abstraction)。这个值可能是指针的一部分(一个引用它的对象)或者只是引用它自己——没关系。重要的是函数在这里从编译(方法)移动到运行时(f-c-函数),所以它 "becomes alive".

P.S.2。有时当部分应用的多参数列表方法作为参数显式传递时,scala 会自动进行 eta 扩展(如 here)。

P.S.N.您可能会在@Daniel C. Sobral answer about scala punctuation.

中找到一些关于下划线的额外信息。