【问题标题】:The differences between underscore usage in these scala's methods这些scala方法中下划线用法的区别
【发布时间】:2015-01-01 16:32:51
【问题描述】:

这些下划线用法与这些代码有什么区别和术语名称:(参见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 扩展),但我也看到相同的下划线用于 Partial Applied 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 扩展,对吗?但我也从this Q/A 读到,这种下划线用于部分应用函数。在同一页面中,也有人说这是一个占位符语法。那么哪一个是正确的呢?

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? no中的下划线是否多余。 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 _的结果有函数类型:() =&gt; (Int, Int) =&gt; Int

6.

List(1, 2, 3) foreach println _

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

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 "#"+_)
                                     ^

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

【问题讨论】:

  • ETA 扩展是您的飞机真的要晚点的时候。
  • @som-snytt 两年后我得到了这个笑话 :)))

标签: scala functional-programming


【解决方案1】:

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

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) 是一个返回函数对象 Byte =&gt; T 的表达式(因为 handler 是一个函数对象 FileInputStream =&gt; Byte =&gt; T 并且您对其进行了部分应用),所以 scala不能对表达式进行 eta 扩展(仅适用于值和方法)。

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

3 只是明确的partially applied

请注意,在所有 3 个示例中 - 您的 handler: FileInputStream =&gt; Byte =&gt; T 函数是一个对象(因此它已经是 eta 扩展的),如果您尝试使用多参数列表方法(尚未扩展为函数) - 您将收到 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),因为您总是需要部分应用函数为 first-class-citizen(如对象或 f-c 函数)以在 eta-expansion 后应用它。

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

scala> val k = 5
k: Int = 5

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

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

scala> 

在您的示例中 - value 只是另一个函数对象本身。 (Int, Int) =&gt; 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 实际上删除 tuples 那样纯粹,但 scala 的 Tuple 只是一个类而不是参数列表中的元组:(Int, Int) =&gt; Int - input 不是 scala 术语中的元组,但在((Int, Int)) =&gt; 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-expansion 应用于表达式,只有方法/值/变量:

 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) _
                 ^

您在错误消息中看到“方法”要求,但 scala 旨在以与(至少部分)支持UAP 相同的方式处理方法/值/变量(当它们是类/对象的成员时)。

6是eta扩展,默认返回Function0:

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

您可能期望这里有 function1,但 println 已重载,并且 eta-expansion 机制选择最少的签名。当需要其他类型时(如Function1foreach) - 它可能会选择其他类型:

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 中的任何类(包括Function1)在Predef 中定义了implicit def + (s: String)(这就是Int 在这里不起作用的原因)(参见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 方法参数”的东西(但这是实验性功能,有时您需要粘贴一些空行或“;”以使其工作):

scala> Predef println "aaa"
aaa

附:关于 C++ 参考/指针。函数没有价值,因为它是编译时结构,所以编译器只是为它创建一个值,这个过程称为 eta-expansion(或纯函数的 eta-abstraction)。该值可能是指针的一部分(引用它的对象)或只是引用自身 - 没关系。重要的是函数在这里从编译(方法)移动到运行时(f-c-function),所以它“变得活跃”。

附注 2。有时,当部分应用的多参数列表方法作为参数显式传递时,scala 会自动执行 eta-expansion(如 here)。

P.S.N.您可以在 @Daniel C. Sobral answer 中找到有关 scala 标点符号的更多信息。

【讨论】:

  • 我的意思是 handler: FileInputStream =&gt; Byte =&gt; T 是一个对象(所以它已经 eta 扩展了)
  • 例如,如果你有像f(a: FileInputStream)(b: Byte)T 这样的方法 - 你必须先将其扩展为函数对象,然后再传递给readFile: readFile(file)(f _),但实际上 scala 可以做到(eta 扩展)自动
  • readFile 不是一个函数——它是一个方法,只有你做 eta-expansion 才能成为一个函数。但是你说得对,我实际上是指咖喱函数,所以稍微纠正了我的答案。
  • 是的,因为sum4 被解释为只是值(不是函数),所以简单地说() =&gt; 是在_ 之前添加的
  • 想一想...println _ 是类Function1 的对象(带有toString = "&lt;function1&gt;")你对这个对象做了+ "#" 并收到字符串“#”,返回到foreach - foreach 忽略结果,所以它什么也没做。 println 没有执行,因为你没有将它应用到任何东西上——你只是把它变成了字符串。例如尝试拨打anyobjectyouwant + "#",或者单独拨打println _ + "#" - 你会看到。
猜你喜欢
  • 2017-06-03
  • 2015-03-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-19
  • 1970-01-01
  • 2018-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多