在Scala中将方法视为函数意味着什么?

时间:2015-04-21 12:59:24

标签: scala

通过为变量(或值?)指定带有空格和下划线的方法名称,您可以告诉scala将该方法视为一个函数,这显然意味着不仅仅是通过调用方法生成的值来做并分配给变量。还有什么/可以通过这样的任务继续进行?

3 个答案:

答案 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 - 循环的块)。

这就是语义上的一切。其余的只是语法糖。

这是一个完整的可运行示例,演示了将实例方法转换为函数:

  1. 含糖,来自外面(a)
  2. 含糖,来自里面(b)
  3. 不含糖,来自外面(c)
  4. 无糖,来自内部(d)
  5. 您可以将其保存到文件中并使用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。