寫在開頭,今年不經意間接觸到了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
注意這m1
和f1
的簽名是不一樣的,(Int)Int
對於一個方法簽名來講表明接受一個Int類型的參數,返回一個Int類型的值,這(Int) =>Int
簽名意味着一個接受一個Int參數的函數,返回一個Int值。
在這點上,咱們彷佛有一個方法m1
和兩個函數f1
和f2
,他們的功能都是同樣的。可是f1
和f2
確實是兩個變量,他們是由一個是實現了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
即便f2
和f3
都引用了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中混用函數和方法時,必定要記住他們不是在作同一件事。