通过为变量(或值?)指定带有空格和下划线的方法名称,您可以告诉scala将该方法视为一个函数,这显然意味着不仅仅是通过调用方法生成的值来做并分配给变量。还有什么/可以通过这样的任务继续进行?
答案 0 :(得分:4)
由于Scala在JVM上运行,因此在没有Scala的语法糖的情况下,就简单的Java类更容易理解。
请记住,Scala函数本质上是类似于以下类的成员(签名故意简化):
class Function[X, Y] {
def apply(x: X): Y
}
将函数f
应用于参数x
将被移植到方法应用程序f.apply(x)
中。
现在假设您有一个方法为Foo
的另一个班级bar
:
class Foo {
def bar(x: Int): String
}
如果您现在有foo
类型的实例Foo
,那么每当其方法bar
通过编写转换为函数时,
val f = foo.bar(_)
创建了Function
的匿名子类的新实例:
val f = new Function[Int, String] {
def apply(x: Int) = foo.bar(x)
}
如果您在课程中使用此语法,则会关闭this
而不是实例foo
。
这就是所有那些奇怪命名的类Main$$anon$1$$anonfun$1
:它们是代表函数的匿名类。这些函数可以非常隐式地出现(例如,作为传递给for
- 循环的块)。
这就是语义上的一切。其余的只是语法糖。
这是一个完整的可运行示例,演示了将实例方法转换为函数:
您可以将其保存到文件中并使用scala <filename.scala>
执行:
/** A simple greeter that prints 'hello name' multiple times */
case class Hey(name: String) { thisHeyInst =>
def hello(x: Int): String = ("hello " + name + " ") * x
def withSugarFromInside = hello(_)
def noSugarFromInside = new Function[Int, String] {
def apply(y: Int) = thisHeyInst.hello(y)
}
}
val heyAlice = Hey("Alice")
val heyBob = Hey("Bob")
val heyCharlie = Hey("Charlie")
val heyDonald = Hey("Donald")
val a = heyAlice.hello(_)
val b = heyBob.withSugarFromInside
val c = new Function[Int, String] { def apply(y: Int) = heyCharlie.hello(y) }
val d = heyDonald.noSugarFromInside
println(a(3))
println(b(3))
println(c(3))
println(d(3))
在所有四种情况下,问候语被打印三次。
答案 1 :(得分:1)
_
实际上做的是eta-conversion。它需要编译时构造调用方法并返回名为anonymous function的 runtime 构造,它实际上是scala Function
的一个实例。确切地说,类依赖于arity,因此它可能是Function1,Function2,Function3等等。这里的要点是制作First-class citizen,它可能就像一个值。
OOP需要比一些新对象多一点。在创建实例的代码之前,编译器在编译时生成一个新类(扩展FunctionN),但理论上它不应该是一个全新的类。对于Java 8,它可以是本机Java-lambdas。
顺便说一下,你可以自己延长Function1
,甚至再抽象一次:
scala> object f extends (Int => Int) { def apply(a: Int) = a }
scala> f(1)
res0: Int = 1
scala> f.apply _
res1: Int => Int = <function1>
scala> res1(5)
res2: Int = 5
作为结论,来自@Daniel C. Sobral的answer的一点点复制粘贴:
前者可以很容易地转换成后者:
val f = m _
Scala将展开 ,假设
m
类型为(List[Int])AnyRef
进入(Scala 2.7):val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }
在Scala 2.8上,它实际上使用
AbstractFunction1
类来减少 班级人数。
或者只是说val f = m _
与val f = (x: List[Int]) => m(x)
为了使这个答案更加现代和精确,让我们看看使用scalac
2.11.2和javap
发生了什么:
$ echo "object Z{def f(a: Int) = a}" > Z.scala //no eta-abstraction here
$ scalac Z.scala
$ ls
Z$.class Z.class Z.scala
$ echo "object Z{def f(a: Int) = a; val k = f _}" > Z.scala
$ scalac Z.scala
$ ls
Z$$anonfun$1.class Z.class //new anonfun class added for lambda
Z$.class Z.scala
$ javap -c Z\$\$anonfun\$1.class
Compiled from "Z.scala" // I've simplified output a bit
public final class Z$$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
public final int apply(int);
Code:
calling apply$mcII$sp(int)
public int apply$mcII$sp(int); //that's it
Code:
0: getstatic #25 // reading Field Z$.MODULE$:LZ$;, which points to `object Z`
3: iload_1
4: invokevirtual #28 // calling Method Z$.f
7: ireturn
public final java.lang.Object apply(java.lang.Object); //just boxed version of `apply`
Code:
unboxToInt
calling apply(int) method
boxToInteger
public Z$$anonfun$1();
Code:
AbstractFunction1$mcII$sp."<init>":()V //initialize
}
所以它仍然延伸AbstractFunction1
答案 2 :(得分:0)
我将尝试提供一些示例,说明如何将函数或方法分配给带下划线的值。
如果需要引用零参数函数
scala> val uuid = java.util.UUID.randomUUID _
uuid: () => java.util.UUID = <function0>
scala> uuid()
res15: java.util.UUID = 3057ef51-8407-44c8-a09e-e2f4396f566e
scala> uuid()
uuid: java.util.UUID = c1e934e4-e722-4279-8a86-004fed8b9090
检查
时的不同之处scala> val uuid = java.util.UUID.randomUUID
uuid: java.util.UUID = 292708cb-14dc-4ace-a56b-4ed80d7ccfc7
在第一种情况下,一个人分配了对函数的引用。然后调用uuid()每次都会生成新的UUID。
第二,调用函数randomUUID
并将值赋值给val uuid
。
还有一些其他案例可能有用_。 可以使用带有两个参数的函数,并创建一个带有单个参数的函数。
scala> def multiply(n: Int)(m: Int) = n*m
multiply: (n: Int)(m: Int)Int
scala> val by2 = multiply(2) _
by2: Int => Int = <function1>
scala> by2(3)
res16: Int = 6
为了能够做到,将函数multiply
定义为curried至关重要。它被称为函数currying。