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

这些代码中这些下划线用法之间有什么区别和术语名称:(请参阅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扩展 ),但我也看到相同的下划线用于没有问题的部分应用函数 ,例如:

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扩展 ,是否正确? 但是我也从这个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.我对这个案子的问题,与没有的区别是什么? 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 _

根据这个答案,这是部分应用功能 。 好的,我可以看到下划线之前的空格有点棘手。 它实际上与以下内容相同:

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类的实际对象:

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 => T的表达式(因为handler是一个函数对象FileInputStream => Byte => T并且你部分应用了它),所以scala不能为表达式执行eta-expansion(仅适用于值和方法)。

4是部分应用scala的curry函数支持的副作用(curried我的意思是一个接一个地参数的能力)。

3只是部分应用。

请注意,在所有3个示例中 - 您的handler: FileInputStream => Byte => 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中它是一流的函数),因为你总是需要部分应用功能作为一等公民(如对象或fc功能)在eta-expansion之后应用。

5 。 Scala可以为值本身做eta-expansion,因为它们可以被认为是具有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不是完全curried函数(它将参数some-count按某种计数),但是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实际上是去除元组,但是scala的Tuple只是参数列表中的一个类而不是元组: (Int, Int) => Int - 输入不是scala术语中的元组,而是在((Int, Int)) => Int ,输入是一个元组(不管来自FP-perspecive它是第一种情况下的对象的元组,还是第二种情况下的一个对象的元组)。 伪tuplying的例子:

 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它是默认返回Function0的eta扩展:

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

您可能会在这里预期function1,但是println过载并且eta-expansion机制选择最少的签名。 当预期其他类型时(如在foreach中的Function1中) - 可以选择另一种类型:

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

正如我所说,你可能认为函数对象作为函数部分应用0参数(其中包括eta扩展,如果需要的话),所以这是另一个答案(我会选择更好的例子)混淆的来源。

正如我在PS2中所说的那样,这种扩展可能会自动应用:

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

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

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

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

每个其他选项都不起作用,因为5对1和2 ,实际上scala甚至不能在单参数函数之后解析字符串:

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

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

scala> Predef println "aaa"
aaa

PS关于C ++参考/指针。 函数没有任何价值,因为它是编译时结构,所以编译器只是为它创建一个值,该进程被称为eta-expansion(或纯粹功能的eta-abstraction)。 这个值可能是指针的一部分(引用它的对象)或者只是引用它自己 - 无关紧要。 问题在于,函数在这里从编译(方法)移动到运行时(fc函数),因此它“变得活跃”。

PS2。 当部分应用的多参数列表方法显式作为参数传递时,有时候scala会自动执行eta-expansion(就像这里)。

PSN您可以在@Daniel C. Sobral关于scala标点符号的答案中找到一些关于下划线的其他信息。

链接地址: http://www.djcxy.com/p/72839.html

上一篇: The differences between underscore usage in these scala's methods

下一篇: Underscore Causing Difficulties