天天看點

《深入了解Scala》——第1章,第1.3節靜态類型和表達力

本節書摘來自異步社群《深入了解scala》一書中的第1章,第1.3節靜态類型和表達力,作者[美]josh suereth,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

1.3 靜态類型和表達力

深入了解scala

開發人員中有一個誤解,認為靜态類型必然導緻冗長的代碼。之是以如此是因為很多繼承自c的語言強制要求程式員必須在代碼中多處明确地指定類型。随着軟體開發技術和編譯器理論的發展,情況已經改變。scala利用了其中一些技術進步來減少樣闆(boilerplate)代碼,保持代碼簡潔。

scala做了以下幾個簡單的設計決策,以提高代碼表達力。

• 把類型标注(type annotation)換到變量右邊。

• 類型推斷。

• 可擴充的文法。

• 使用者自定義的隐式轉換。

我們先看看scala是怎麼改變類型标注的位置的。

1.3.1 換邊

scala把類型标注放在變量的右側。像java或c++等幾種靜态類型語言,一般都必須聲明變量、傳回值和參數的類型。在指定變量或參數的類型時,延續自c的做法是把類型标注放在變量名的左邊。對于方法的參數和傳回值來說,這是可以接受的,但在構造不同風格(style)的變量時就容易産生混淆。c++是最好的例子,它有很豐富的變量修飾符選項,比如volatile、const、指針和引用等。

《深入了解Scala》——第1章,第1.3節靜态類型和表達力

把變量的類型和其修飾符混在一起的做法引緻一些極其複雜的類型定義。而scala,和其他幾種語言,則把類型标注放在變量的右邊。把變量類型和修飾符分開能幫助程式員在讀代碼時減少一些複雜性。

《深入了解Scala》——第1章,第1.3節靜态類型和表達力

上例示範了僅僅把類型标準搬到變量右邊,我們就可以使代碼簡化不少,而這還不是全部,scala能通過類型推斷進一步減少文法噪音。

1.3.2 類型推斷

隻要能夠進行類型推斷,scala就會執行類型推斷,進而進一步降低文法噪音(syntactic noise)。類型推斷是指編譯器自行判斷類型标注,而不是強迫使用者去指定。使用者當然可以提供類型标注(如果他想),但他也可以選擇讓編譯器來幹這活。

《深入了解Scala》——第1章,第1.3節靜态類型和表達力

這個特性能夠極大地減少在其他強類型的語言中常見的文法噪音。scala更進一步對傳遞給方法的參數也進行某種程度的類型推斷,特别是對(作為參數的)一等函數。如果已知一個方法接受一個函數參數,編譯器能夠推斷出函數字面量(function literal)裡面使用的類型。

《深入了解Scala》——第1章,第1.3節靜态類型和表達力

1.3.3 抛開文法

scala文法采取了一種政策:如果一行代碼的含義非常明确,就可以抛棄掉一些冗長的文法。這個特性可能會讓剛學習scala的使用者感到困惑,但如果使用得當,這個特性是極其強大的。我們來看個重構代碼的例子,從一個完全符合scala标準文法的代碼,簡化為scala社群的慣用寫法。下面是scala實作的quicksort函數。

《深入了解Scala》——第1章,第1.3節靜态類型和表達力

這段代碼接受一個t類型的清單,t可以被隐式轉換為ordered[t]類型(t <% ordered[t])。我們會在第6章詳細讨論類型參數及其限制,目前先不要關注于此。簡單來說,我們需要一個清單,裡面的元素是可以排序的,是以該元素應該有個判斷是否小于的方法“<”。然後我們檢查清單,如果是空清單或nil,我們就傳回個nil清單。如果清單裡有内容,我們取出清單的頭(x)和尾(xs)。我們用頭元素來把尾清單切分成兩個清單。接着我們對這兩個清單分别遞歸調用quicksort方法。在同一行上,我們把排序後的清單和頭元素結合為一個完整的(排序後的)清單。

你可能會想,“哇哦,scala代碼看上去真醜”。就這個例子來說,你可能是對的。代碼相當淩亂,難以閱讀。有很多文法噪音掩蓋了代碼本身的含義。不僅如此,qsort後面還有很多吓人的類型資訊。讓我們拿出手術刀,割掉那些讨厭的玩意。首先,scala可以自行推斷分号。編譯器會假定一行結束就是一個表達式的結束,除非你在行末留了什麼未完結的文法片段,比如方法調用前的那個“.”(like the . before a method call)。

光删除分号顯然沒多大幫助。我們還需要應用“操作符”(operator notation)。操作符是scala的一個能力,可以把方法當作操作符。無參數的方法可以用作字尾操作符(postfix operator),隻有一個參數的方法可以當作中綴操作符(infix operator)。還有一些對特殊字元的專門規定,比如方法名的最後一個字元如果是“:”,則方法的調用方向反轉。下面的代碼示範了這些規則。

《深入了解Scala》——第1章,第1.3節靜态類型和表達力

在定義匿名函數時(又稱lambda),scala提供了占位符文法。可以使用“_”關鍵字作為函數參數的占位符。如果使用多個占位符,每個相應位置的占位符對應于相應位置的參數。這種寫法通常在定義很簡單的函數時使用,比如我們的quicksort例子裡面比較元素大小的那個函數字面量。

我們可以結合使用占位符文法和操作符來改進我們的快速排序算法。

《深入了解Scala》——第1章,第1.3節靜态類型和表達力

scala不僅為簡單場景提供了文法糖,它還提供了隐式轉換和隐式參數機制讓我們可以扭曲(bend)類型系統。

1.3.4 隐式轉換概念早已有之

scala隐式轉換是老概念的新用法。我是在c++的基礎類型上第一次接觸到隐式轉換的概念。隻要不損失精度,c++可以自動轉換基礎類型,比如我可以在聲明long值的時候給它個int。對于編譯器來說,實際的類型“double”,“float”,“int”和“long”型都是不同的,但在混用這些值時編譯器嘗試智能地去“做正确的事”(do the right thing(tm))。scala提供了相同的機制,但是是作為一個語言特性給大家使用(而不是隻讓編譯器用)。

scala會自動地加載scala.predef對象,使它的成員方法對所有程式可用。這樣可以很友善地給使用者提供一些常用函數,比如可以直接寫println而不是console.println或system.out.println。predef還提供了“基礎類型拓寬”(primitive widenings)。也就是一些能夠把低精度類型自動轉換為高精度類型的隐式轉換。我們來看一下為byte類型定義的轉換方法。

《深入了解Scala》——第1章,第1.3節靜态類型和表達力

這些方法隻是簡單地調用運作時轉換(runtime-conversion)方法。方法名前面的implicit關鍵字說明編譯器會在需要對應的類型時嘗試對byte調用對應的方法。比如說,如果我們給一個需要short類型的方法傳了一個byte,編譯器會調用隐式轉換方法byte2short。scala還把這個機制更進一步:如果對一個類型調用一個它沒有的方法,scala也會通過隐式轉換來查找這個方法。這比僅僅提供基礎類型轉換提供了更多的便利。

scala也把隐式轉換機制作為擴充java基礎類型(integer、string、double等)的一種手段。這允許scala直接使用java類以友善內建,同時提供更豐富的方法(method)以便利用scala更為先進的特性。隐式轉換是非常強大的特性,也是以引起一些人的疑慮,關鍵在于知道如何以及何時使用隐式轉換。

1.3.5 使用scala的implicit關鍵字

用好隐式轉換是操縱scala類型系統的關鍵。隐式轉換的基礎應用場景是按需自動地把一種類型轉換為另一種,但它也可以用于有限形式的編譯時元程式設計(limited forms of compiler time metaprogramming)。要使用隐式轉換必須把它關聯到某個作用域。可以通過伴生對象或明确的導入來做關聯。

implicit關鍵字在scala裡有兩種不同用法。第一種用法是給方法聲明一種特殊參數,如果編譯器在作用域裡找到了合适的值就會自動傳遞給方法。這可以用來把某api的某些特性限定在某個作用域裡。因為implict采用了繼承線性化(inheritance linearizion)的查找政策,是以可以用來修改方法的傳回值。這使使用者可以寫出非常進階的api以及玩一些類型系統的小把戲,在scala collections api裡就使用了這種技術。這些技術會在第7章詳加解釋。

implicit關鍵字的另一種用法是把一種類型轉換為另一種。有兩種場景會發生隐式轉換,第一種場景是當你給一個函數傳遞參數的時候,如果scala發現函數需要的參數類型(跟傳給它的)不一樣,scala會首先檢查類型繼承關系,如果沒找到,就會去查找有沒有合适的隐式轉換方法。隐式轉換方法隻是普通的方法,用implicit關鍵字做了标注,該方法接受一個參數,傳回某些結果(譯著:實際上是接受轉換前的參數,傳回轉換後的結果,然後scala用轉換後的結果作為參數去調之前那個函數)。第二種場景是當調用某類型的某方法時,如果編譯器發現該類型沒有這個方

法,scala會對該查找适用于該類型的隐式轉換,直到找到一個轉換後具有該方法的結果,或者找不到(編譯出錯)。這種做法在scala的“pimp my library”模式中得以應用,這些内容也會在第7章詳解。

這些特性的組合給scala帶來了非常有表達力的文法,同時保持其進階的類型系統。創造有表達力的庫需要深入了解類型系統,也必須徹底了解隐式轉換的知識。第6章會全面地覆寫類型系統的知識。scala類型系統跟java也能很好地互動,這是scala的關鍵設計之一。