这些scala方法中下划线用法之间的差异
这些代码中这些下划线用法的区别和术语名称有什么区别:(请参阅处理程序(资源)部分)
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,我得到:_必须遵循方法编译错误. 我读到这是因为下划线用于将方法转换为函数(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扩展,这是正确的吗?但是我也从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?我应该不使用. 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)=>诠释? 6. List(1,2,3) foreach println _ 根据this answer,这是部分应用的功能.好吧,我可以看到下划线之前的空间有点棘手.它实际上与: List(1,3).foreach(println(_)) 所以这确实是部分应用的功能. 但如果我这样做: scala> List(1,3).foreach(println _+1) //#1 <console>:8: error: type mismatch; found : Int(1) required: String List(1,3).foreach(println _+1) ^ scala> List(1,3).foreach(println _+"#") //#2 printed out nothing (why?) scala> List(1,3).foreach(println 1+_) //#3 <console>:1: error: ')' expected but integer literal found. List(1,3).foreach(println 1+_) ^ scala> List(1,3).foreach(println "#"+_) //#4 <console>:1: error: ')' expected but string literal found. List(1,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)是一个返回function-object的表达式Byte => T(作为处理程序是一个函数对象FileInputStream => Byte => T并且你对它进行了部分应用),因此scala不能对表达式进行eta扩展(仅适用于值和方法). 图4的部分应用是scala的curried函数支持的副作用(到curried我的意思是逐个获取参数的能力). 3明确地是partially applied. 请注意,在所有3个示例中 – 您的处理程序:FileInputStream =>字节=> 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),因为您总是需要部分应用在eta-expansion之后将函数设置为first-class-citizen(如object或fc-function). 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> 在您的示例中 – value只是另一个函数 – 对象本身.另外(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. 使其成为咖喱的另一种方法是使用元组.它不是那么纯粹,因为currying实际上是删除了元组,但是scala的Tuple只是一个类而不是参数列表中的元组:(Int,Int)=> Int – input不是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应用于表达式,只有方法/值/ 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) _ ^ 您可以在错误消息中看到“方法”,但scala旨在以相同的方式处理方法/值/变量(当它们是类/对象的成员时)(至少部分地)支持UAP. 6它是eta扩展,默认返回Function0: 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-expansion),这是与另一个answer混淆的原因(我会在那里选择更好的例子). 正如我在P.S.2中所说,这种扩展可以自动应用: scala> List(1,2) foreach println 1 2 关于println _“#” – 它的工作原理是因为scala中的任何类(包括Function1)都在Predef中定义了隐式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函数),因此它“变得活着”. P.S.2.当部分应用多参数列表方法作为参数显式传递时,scala有时会自动进行eta扩展(如here). P.S.N.你可以在@Daniel C. Sobral answer中找到关于scala标点符号的下划线的更多信息. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |