nul*_*ull 6 functional-programming 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))
}
Run Code Online (Sandbox Code Playgroud)
我收到编译错误:
Error:(55, 29) _ must follow method; cannot follow Byte => T
val hh = handler(resource)_
^
Run Code Online (Sandbox Code Playgroud)
这是什么意思?
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...
// ...
Run Code Online (Sandbox Code Playgroud)
结果与否相同.1,我得到:_ must follow method编译错误.
我读到这是因为下划线用于将方法转换为函数(ETA扩展),但我也看到相同的下划线用于部分应用函数没有问题,例如:
val sum = (x: Int, y: Int) => x + y
val sum2 = sum _
Run Code Online (Sandbox Code Playgroud)
在这种情况下没有错误.
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()
}
}
//...
Run Code Online (Sandbox Code Playgroud)
这个工作正常.如果我没错,这种情况下的下划线称为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()
}
}
//...
Run Code Online (Sandbox Code Playgroud)
这个不使用下划线但它也可以正常工作.我对这个案子的问题是,与否有什么区别.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
Run Code Online (Sandbox Code Playgroud)
你能告诉我发生了什么事sum4吗?为什么结果sum3 _有函数类型:() => (Int, Int) => Int?
6.
List(1, 2, 3) foreach println _
Run Code Online (Sandbox Code Playgroud)
根据这个答案,这是部分应用的功能.好吧,我可以看到下划线之前的空间有点棘手.它实际上与:
List(1, 2, 3).foreach(println(_))
Run Code Online (Sandbox Code Playgroud)
所以这确实是部分应用的功能.
但如果我这样做:
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 "#"+_)
^
Run Code Online (Sandbox Code Playgroud)
新手通常会认为这个案例中的下划线是占位符,但我相信它不是,不是吗?
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
Run Code Online (Sandbox Code Playgroud)
它在你的例子中不起作用,因为它handler(resource)是一个返回function-object的表达式Byte => T(因为它handler是一个函数对象FileInputStream => Byte => T,你对它进行了部分应用),因此scala不能对表达式进行eta扩展(仅适用于值和方法) ).
图4的部分应用作为scala的curried函数支持的副作用(通过curry表示逐个获取参数的能力).
图3仅明确地部分应用.
请注意,在所有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>
Run Code Online (Sandbox Code Playgroud)
因此,如果需要,部分应用程序也会为您进行eta扩展.您也可以将eta-expansion视为(非精确)函数,使用0个部分应用的参数,因此它是非常接近的术语,因为部分应用的函数总是scala中的对象(在haskell中它是一流的函数),因为你总是需要部分应用功能成为一流公民(如对象或fc功能),以便在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>
Run Code Online (Sandbox Code Playgroud)
在您的示例中 - value只是另一个函数 - 对象本身.还(Int, Int) => Int没有完全做到令行禁止功能(它采取一些参数计数一些数),但斯卡拉也能做到自然而然部分申请这类.为了使它充分咖喱:
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>
Run Code Online (Sandbox Code Playgroud)
这个过程实际上叫做currying.
使其成为咖喱的另一种方法是使用元组.它不是那么纯粹,因为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>
Run Code Online (Sandbox Code Playgroud)
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) _
^
Run Code Online (Sandbox Code Playgroud)
您可以在错误消息中看到"方法",但scala旨在以相同的方式(至少部分地)支持UAP来处理方法/值/变量(当它们是类/对象的成员时).
6它是eta扩展,默认返回Function0:
scala> val a = println _
a: () => Unit = <function0>
Run Code Online (Sandbox Code Playgroud)
你可以在这里看到function1,但是println过载并且eta-expansion机制选择最少的签名.当预期其他类型(如Function1in foreach)时 - 它可能会选择另一种:
scala> val a: String => Unit = println _
a: String => Unit = <function1>
Run Code Online (Sandbox Code Playgroud)
正如我所说,您可以将函数对象视为部分应用0参数的函数(如果需要,其中包括eta-expansion),这是与另一个答案混淆的来源(我会在那里选择更好的例子).
正如我在PS2中所说,这种扩展可以自动应用:
scala> List(1,2) foreach println
1
2
Run Code Online (Sandbox Code Playgroud)
关于println _ +"#"- 它的工作原理是因为Function1在Scala中的任何类(包括)implicit def + (s: String)(这就是为什么Int在这里不起作用)在Predef中定义(参见SI-194):
scala> println _
res50: () => Unit = <function0>
scala> println _ + "#"
res51: String = <function0>#
Run Code Online (Sandbox Code Playgroud)
由于5 vs 1和2,所有其他选项都不起作用,实际上scala甚至无法解析单参数函数后的字符串:
scala> println "#"
<console>:1: error: ';' expected but string literal found.
println "#"
^
Run Code Online (Sandbox Code Playgroud)
您应该指定对象主机来修复它,因为scala期望类似"obj方法参数"(但这是实验性功能,有时您需要粘贴一些空行或";"以使其工作):
scala> Predef println "aaa"
aaa
Run Code Online (Sandbox Code Playgroud)
PS关于C++参考/指针.函数没有任何价值,因为它是编译时结构,因此编译器只是为它创建一个值,该过程称为eta-expansion(或纯函数的eta-abstraction).该值可能是指针(引用它的对象)的一部分,或者只是引用本身 - 无关紧要.问题是函数在这里从编译(方法)移动到运行时(fc函数),所以它"变得活着".
PS2.当部分应用多参数列表方法作为参数显式传递时,scala有时会自动进行eta扩展(如此处).
PSN您可能会发现有关@Daniel C.索布拉尔下划线一些额外的信息回答有关Scala的标点符号.
| 归档时间: |
|
| 查看次数: |
957 次 |
| 最近记录: |