天天看点

Scala Functions vs Methods(scala中的函数和方法)

被墙的文章,转过来一下。

Scala has both functions and methods. Most of the time we can ignore this distinction, but sometimes we have to deal with the fact that they are not quite the same thing. 

In my  Scala Syntax Primer  I mention that I use the terms method and function interchangeably in the discussion. This is a simplification. In many situations, you can ignore the difference between functions and methods and just think of them as the same thing, but occasionally you may run into a  situation  in which the difference matters. This is analogous to how most of us treat mass and weight. In our daily lives on the surface of planet Earth, we treat them as interchangeable units, with 1Kg being the same as 2.2 pounds. But they are not quite the same thing: when an astronaut walks on the surface of the moon, his mass (kilograms) has not changed but his weight (pounds) is only about  one sixth  of what it was on Earth. 

In contrast to Kg vs pounds, you are rather more likely to come across a situation in which the distinction between Scala functions and methods is important than you are to be walking on the surface of the moon. So when can you ignore the difference between functions and methods, and when do you need to pay attention to it? You can answer that question once you understand the difference. 

A Scala method, as in Java, is a part of a class. It has a name, a signature, optionally some annotations, and some bytecode. 

A function in Scala is a complete object. There are a series of traits in Scala to represent functions with various numbers of arguments: 

Function0

Function1

,

Function2

, etc. As an instance of a class that implements one of these traits, a function object has methods. One of these methods is the 

apply

 method, which contains the code that implements the body of the function. Scala has special "apply" syntax: if you write a symbol name followed by an argument list in parentheses (or just a pair of parentheses for an empty argument list), Scala converts that into a call to the 

apply

 method for the named object. When we create a variable whose value is a function object and we then reference that variable followed by parentheses, that gets converted into a call to the 

apply

 method of the function object. 

When we treat a method as a function, such as by assigning it to a variable, Scala actually creates a function object whose apply method calls the original method, and that is the object that gets assigned to the variable. Defining a function object and assigning it to an instance variable this way consumes more memory than just defining the functionally equivalent method because of the additional instance variable and the overhead of another object instance for the function. Thus you would not want every method to be a function; but functions give you a great deal of power that is not available with just methods, and in those situations that power is well worth the additional memory used. 

Let's look at some details of this mechanism. Create a file 

test.scala

 with this in it:

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

       

Compile that file with scalac and list the resulting files. Scala creates two files: 

test.class

 and 

test$$anonfun$1.class

. That strange extra class file is the anonymous class for the function object that Scala created in response to the function expression assigned to 

f1

. If you use more than one function expression in your 

test

 class, there will be more than one anonymous class file, even if you write the same function expression over again. 

If you run 

javap

 on the 

test

 class, you will see this:

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;
}

       

Running 

javap

 on the function class 

test$$anonfun$1

 yields this:

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();
}

       

Because this class implements the 

Function1

 interface we know it is a function of one argument. You can see that it contains a handful of methods, including the

apply

 method. 

You can also define a function in terms of an existing method by referencing that method name followed by a space and an underscore. Modify 

test.scala

 to add another line that does this:

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

       

The 

m1 _

 syntax tells Scala to treat 

m1

 as a function rather than taking the value generated by a call to that method. Alternatively, you can explicitly declare the type of 

f2

, in which case you don't need to include the trailing underscore:

val f2 : (Int) => Int = m1

       

In general, if Scala expects a function type, you can pass it a method name and have it automatically converted to a function. For example, if you are calling a method that accepts a function as one of its parameters, you can supply as that argument a method of the appropriate signature without having to include the trailing underscore. 

Back to our test file, now when you compile 

test.scala

 there will be two anonymous class files, one for the 

f1

 class and one for the 

f2

 class. You can use either definition for 

f2

, they generate identical class files. 

If you use the 

-c

 option to 

javap

 to get the code of the second anonymous class, you can see the call to the 

m1

 method of the 

test

 class in the 

apply

 method:

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

       

Let's fire up the scala interpreter and see how this works. In the following examples, input text is shown in  bold  and output text in regular weight.

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

       

Note the difference in the signatures between 

m1

 and 

f1

: the signature 

(Int)Int

 is for a  method  that takes one 

Int

 argument and returns an 

Int

 value, whereas the signature 

(Int) => Int

 is for a  function  that takes one 

Int

 argument and returns an 

Int

 value. 

At this point we seem to have a method 

m1

 and two functions 

f1

 and 

f2

 that all do the same thing. But 

f1

 and 

f2

 are actually variables that contain an instance of a generated class that implements the 

Function1

 interface, and that object instance has methods that 

m1

 does not have.

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

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

       

Because 

m1

 is itself a method, unlike 

f1

 you can't call methods on it:

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
       ^

       

Note that each time you separately reference a method as a function Scala will create a separate object.

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

scala> 
  f2 == f3
res6: Boolean = false

       

Even though 

f2

 and 

f3

 both refer to 

m1

, and both do the same thing, they are not considered equal by Scala because function objects inherit the default equality method that compares identity, and these are two different objects. If you want two function values to be equal, you must ensure that they refer to the same instance of function object:

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

scala> 
  f2 == f4
res7: Boolean = true

       

Here are a few other examples showing that the expression 

m1 _

 is in fact a function object:

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

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

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

       

As of Scala version 2.8.0, the expression 

(m1 _)(3)

 will also return the same value (there is a  bug  in previous versions that causes this syntax to give a 

type mismatch

 error). 

There are some other differences between methods and functions. A method  can be  type-parameterized, but an anonymous function can not:

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

       

However, if you are willing to define an explicit class for your function, then you can type-parameterize it similarly:

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

       

So go ahead and keep converting pounds to kilograms by dividing by 2.2 (unless you are an astronaut), but when you start mixing functions and methods in Scala, keep in mind that they are not quite the same thing.