Scala Functions vs Methods

写在开头,今年不经意间接触到了scala语言,之前一直在使用java语言,如今对scala比较有兴趣,最近用业余时间在学习这方面知识,已经看完《快学scala》正在看《scala编程》,这边文章是我在其余我的博客中看到的,感受对我理解scala中函数式编程颇有帮助,因此想翻译过来,让本身对这内容理解更透彻些。html

附上博客原文地址Scala Functions vs Methods 如下是本文的翻译,不足之处还望指出帮助改正:java

scala中既有函数也有方法。大多数时候,咱们忽略了二者之间的不一样,可是有时咱们必须面对它们不是一样一件事这个事实。es6

在个人scala基本语法中我有提过在讨论中我使用方法和函数互相替换使用。那是简单化的。在大多数场景下,你能够忽略函数和方法二者之间的区别,仅仅把他们当作同一件事就能够,可是偶尔你可能会在某个场景下遇到他们不一样之处。与咱们对待质量和重量的见解十分类似。在咱们平常在地球表面生活,咱们把二者当作能够互换的单元,1Kg与2.2磅相同。可是它们也不是彻底同样,当宇航员在月球的表面行走,他的质量(千克)没有改变,可是他的重量(磅)已经变成在地球的六分之一了。编程

在千克与磅的对比中,相比你在月球表面走动,你更有可能遇到在某个场景下区分scala中函数和方法不一样之处。因此何时忽略二者之间的不一样,何时你又须要注意二者之间的不一样呢?一旦你理解这不一样之处你就能够回答这个问题了。app

一个scala 方法,在java中是类的一部分,它有一个名字,一个签名,可选的注解 和字节码。函数式编程

在scala中一个函数是一个完整的object,在scala中有一系列的特质表明各类各样的带参数的函数:Function0, Function1, Function2等等。 做为一个混入了这些特质之一的类的实例化,一个函数object有许多方法。这些方法之一就是apply方法,这个方法包含了实现这个函数的代码。 scala中apply有特殊的语法:若是你写一个函数名紧接着是一个带一系列参数列表的括号(或者仅有不带参数的括号),scala会将调用转换成对应名的object的apply方法。当咱们建立一个变量,值是一个函数的object以后咱们引用这个带着括号的变量,它将会自动转成这个函数object的apply方法的调用(译者注:看过scala语法的应该知道,就是隐式的调用了apply方法,这里翻译的有点拗口)。函数

当咱们把一个方法当作一个函数,好比咱们把它赋值给一个变量,scala确实会建立一个函数object,其中的apply方法调用原始的方法,而且是将这个object赋值给这个变量。定义一个函数的对象,把它赋值给一个变量,由于须要定义一个功能上与原始方法等价的函数,并实例化这个函数对象,这种方式的开销会更加消耗内存。所以,你不会想让每个方法都变成一个函数;可是函数会赋予你更增强大的功能,这功能不只仅是方法 同时在某些状况下所提供的功能值的咱们使用更多的内存(译者注:丫的,不直接说明白具体什么地方。哈哈,其实在后面)。学习

让咱们看看这个机制的详细细节。建立一个test.scala文件,内容以下:测试

class test {
    def m1(x:Int) = x+3
    val f1 = (x:Int) => x+3
}

用scalac编译这个文件,获得编译后的文件,scala建立了两个文件:test.class 和 test$$anonfun$1.class。这个多余的奇怪文件是函数对象的匿名类,是scala建立用来对应赋值给f1的函数表达式函数。若是在你的测试类中使用更多的函数表达式,一样将会有更多的匿名类产生,即便你再一次将一样的函数表达式写了一遍。this

若是你用javap运行这个测试类,你将会看到:

Compiled from "test.scala"
public class test extends java.lang.Object implements scala.ScalaObject{
    public test();
    public scala.Function1 f1();
    public int m1(int);
    public int $tag()       throws java.rmi.RemoteException;
}

使用javap运行test$$anonfun$1函数类,产出:

Compiled from "test.scala"
public final class test$$anonfun$1 extends java.lang.Object implements scala.Function1,scala.ScalaObject{
    public test$$anonfun$1(test);
    public final java.lang.Object apply(java.lang.Object);
    public final int apply(int);
    public int $tag()       throws java.rmi.RemoteException;
    public scala.Function1 andThen(scala.Function1);
    public scala.Function1 compose(scala.Function1);
    public java.lang.String toString();
}

这个类实现了Function1的接口,咱们知道那是带一个参数的函数。你能够看到这个类中包含一把方法,包含apply方法。

你也能够经过使用一个存在的方法来定义一个函数,引用函数名 接着一个空格和一个下划线。修改test.scala 加上另外一行:

class test {
    def m1(x:Int) = x+3
    val f1 = (x:Int) => x+3
    val f2 = m1 _
}

m1 _语法 告诉scala 把m1当作一个函数而不是经过调用那个方法来使用产生的值。做为另外一种选择,你能够详细的声明 类型f2,这样你不须要包含一个末尾下划线:

val f2 : (Int) => Int = m1

通常状况下,若是scala指望一个函数类型,你能够传递一个方法名,让它自动转换成一个函数。例如,若是你调用一个接收一个函数做为它的参数的方法,你能够经过恰当的方法签名来提供这个参数,而不用包含末尾下划线。

回到咱们的测试文件上来,如今当你编译这个test.scala,将会有两个匿名的类,一个是f1类一个是f2类。你可使用两个方式任意一个来定义f2,都会产生一个惟一的类文件。

若是你使用javap时 加上选项-c,你能够获得第二个匿名类的源码,你能够看到测试类中的apply方法调用了m1的方法。

public final int apply(int);
  Code:
   0:   aload_0
   1:   getfield        #17; //Field $outer:Ltest;
   4:   astore_2
   5:   aload_0
   6:   getfield        #17; //Field $outer:Ltest;
   9:   iload_1
   10:  invokevirtual   #51; //Method test.m1:(I)I
   13:  ireturn

让咱们瞄准scala的编译器,看看它是如何工做的。在接下来的例子中,加粗的黑体表示输入的文字,紧接着标注的是输出的文字。

scala> def m1(x:Int) = x+3 m1: (Int)Int

scala> val f1 = (x:Int) => x+3 f1: (Int) => Int = <function>

scala> val f2 = m1 _ f2: (Int) => Int = <function>

scala> m1(2) res0: Int = 5

scala> f1(2) res1: Int = 5

scala> f2(2) res2: Int = 5

注意这m1f1的签名是不一样的,(Int)Int对于一个方法签名来讲表明接受一个Int类型的参数,返回一个Int类型的值,这(Int) =>Int签名意味着一个接受一个Int参数的函数,返回一个Int值。

在这点上,咱们彷佛有一个方法m1和两个函数f1f2,他们的功能都是同样的。可是f1f2确实是两个变量,他们是由一个是实现了Function1接口的生成类生成的实例。实例对象有不少m1没有的方法。

scala> f1.toString
res3: java.lang.String = <function>

scala> (f1 andThen f2)(2)
res4: Int = 8

由于m1自己就是方法,不一样于f1,你不能在它上面调用方法:

scala> m1.toString
<console>:6: error: missing arguments for method m1 in object $iw;
follow this method with `_' if you want to treat it as a partially applied function
       m1.toString
       ^

注意,每一次你引用一个方法做为一个函数,scala都会建立一个单独的对象。

scala> val f3 = m1 _
f3: (Int) => Int = <function>

scala> f2 == f3
res6: Boolean = false

即便f2f3都引用了m1,二者作的事情是同样的,可是在scala中它们不认为是相等的,由于默认继承的比较方法是经过惟一标识去比较的,这是两个不一样的对象。若是你想让两个函数值相等,你须要确保引用它们引用相同的函数对象。

scala> val f4 = f2
f4: (Int) => Int = <function>

scala> f2 == f4
res7: Boolean = true

这有几个例子来代表表达式m1 _事实上是一个函数对象:

scala> m1 _
res8: (Int) => Int = <function>

scala> (m1 _).toString
res9: java.lang.String = <function>

scala> (m1 _).apply(3)
res10: Int = 6

scala 2.8.0版本中,表达式(m1 _)(3)也会返回相同的值(之前的版本中有一个bug,会引发类型没匹配的错误)

方法与函数之间还有一些其余的不一样之处,方法能够是一个类型参数化,可是一个匿名的函数则没法作到:

scala> def m2[T](x:T) = x.toString.substring(0,4)
m2: [T](T)java.lang.String

scala> m2("abcdefg")
res11: java.lang.String = abcd

scala> m2(1234567)
res12: java.lang.String = 1234

可是,若是你愿意将你的函数定义为一个明确的类的话,你一样能够类型参数化它:

scala> class myfunc[T] extends Function1[T,String] {
     |     def apply(x:T) = x.toString.substring(0,4)
     | }
defined class myfunc

scala> val f5 = new myfunc[String]
f5: myfunc[String] = <function>

scala> f5("abcdefg")
res13: java.lang.String = abcd

scala> val f6 = new myfunc[Int]
f6: myfunc[Int] = <function>

scala> f6(1234567)
res14: java.lang.String = 1234

因此让咱们继续,经过将除以2.2,将磅转换成千克(除非你是一个宇航员),可是当你在scala中混用函数和方法时,必定要记住他们不是在作同一件事。

相关文章
相关标签/搜索