淺談Swift程式語言

InfoQ發表於2014-11-30

在過去的幾年中,移動應用程式風靡全世界並且已經改變了我們使用網際網路進行工作或者休閒的方式。為了建立移動應用程式,各種技術應運而生,同時開發過程也開始將其作為一等公民來對待。儘管移動似乎已經無處不在了,但是它的未來才剛剛開始。我們正面對著新一代的移動裝置,例如可穿戴裝置以及組成物聯網的大量移動工具。我們將會面對新的用來展示資料和接受命令的使用者介面。同時,我們將會看到越來越多的公司真正地實現移動優先。所有的這一切都將會影響我們在未來的幾年中設計、開發和測試軟體的方式。

InfoQ的這篇文章是快速變化的移動技術世界中一系列文章的一部分。你可以通過這裡訂閱這一系列,屆時如果有新文章釋出那麼會通知你。

蘋果公司最近推出了Swift 1.0——一門針對iOS和OSX開發的新程式語言。不要將蘋果的Swift與老的並行指令碼語言混淆。Swift的目標是讓iOS和OSX開發變得更簡單,更有樂趣。在本文中,我將會解釋我認為Swift所具有的最具殺傷力的5個特性以及我為什麼會這樣認為的原因,雖然這些特性現在依然出於測試階段,但是卻值得我們一試。

蘋果已經擁有了一門程式語言——Objective-C。那麼為什麼還要引入另一門程式語言呢?這是因為雖然Objective-C在被建立的時候可能已經非常地獨特,同時也很先進,但是它現在並沒有當今語言的味道。例如,在消費者方面像Ruby這樣的指令碼語言已經被廣泛採用,這很大程度上得益於它乾淨的語法。在企業領域,具有型別推理能力的強型別(型別安全的)語言更受歡迎,為了將函數語言程式設計語言所具有的函式即物件、Lambda表示式等經典特性引入進來,C#和Java(或者Scala)等語言都做出了大量的努力。Objective-C一直都缺少這類東西,例如干淨的語法(和語法糖),型別推理。而Swift正是為了填補這個空白。

這並不是說Objective-C並不是一門優秀的程式語言。實際上,它是一門優秀的語言。但是我確實看到有足夠的空間可以成功地替代Objective-C。進一步講,同時也要感謝Swift的優秀,我認為Swift一定會像野火那樣迅速蔓延開來。

現在,就讓我們看看Swift都提供了什麼吧。從語言的角度看,Swift是非常了不起的。蘋果借鑑了Scala和C#這些現代語言的優點,構建了一門非常簡單,但是功能非常強大的語言。它非常完美地融合了物件導向和函數語言程式設計正規化——但是說Swift是一門函式式語言是一種極大的延伸。下面就讓我們看看Swift最具殺傷力的5個特性。

#1: 語法糖

從語法上講Swift非常華麗。它是一門非常簡單、乾淨的語言,同時可讀性也非常好,即使以現在的標準來衡量也是如此。你馬上就會發現在設計一門語言的時候簡單性是一個關鍵要素。例如,大家所熟知的語句末尾的分號。蘋果決定將分號作為可選的,雖然這看起來相關性並不是非常強,但是它卻讓我們看到了蘋果為了儘可能地保持語法乾淨所做出的努力。

簡單性方面的其他例子包括字串插入,以及語言對陣列和迴圈處理的支援。

字串插入

var message = “Hello World” “The message is \(message)” //The message is Hello world var a = 1, b = 2 “The sum is \(a + b)” //The sum is 3

迴圈

var length = 10
for i in 0..

陣列

var list = ["a", "b"]
list += ["c", "d"]

以上僅是Swift為簡單性提供語言支援的一部分示例。需要注意的是你依然可以使用Array類的“append”方法連線陣列,而蘋果之所以走了額外的一英里將其構建為語言的一部分目的是為了展示他們設計Swift的目標。

如果你想學習Swift並對一些這樣的示例程式碼進行測試,那麼可以嘗試下Xcode 6,它的程式碼實時預覽(Playground)功能太酷了,簡直無法用語言形容。實時預覽功能讓你能夠實時地隨著你的輸入對程式碼進行測試。它會執行你在開發環境中輸入的所有內容,提供與變數值、函式呼叫返回值以及特定程式碼塊被執行的次數相關的詳細資訊。開啟Xcode 6中的實時預覽功能非常簡單:

(單擊放大圖片)

下面的圖片展示了實時預覽功能:

(單擊放大圖片)

#2: 函式是一等物件

越來越多的語言將函式作為一等公民並支援高階函式。例如,最近釋出的Java 8引入了Lambda表示式。它的理念很簡單,就是讓函式可以接受函式型別的引數,同時也可以將函式作為返回值。理念的簡單性奠定了它強大的基礎,因為這樣支援更多的抽象。例如,我們可以將一個“過濾(filter)”函式應用到一個陣列,該過濾函式接受陣列中的每一個條目作為引數,通過特定的標準對條目進行判定從而完成對給定陣列的過濾。使用更加通用的方法的關鍵是能夠接受函式型別的引數。下面就讓我們看看定義函式的語法。

Swift定義函式的語法與傳統的Haskell這樣的函式型語言相似,但並不完全一樣。箭頭(->)的左邊是引數以及引數的型別,右邊是返回值型別。在本文的示例中,我們想要過濾一個數字列表,因而基於一個給定的數字我們會返回一個Bool型別。在這種情況下,函式看起來可能是這樣的:

(Item:Int) -> Bool

這段程式碼的意思是接受一個Int型別的引數,返回一個Bool型別的值。很顯然,你可以包含多個引數,但是不太明顯的是,你還可以返回多個值,而這不需要建立一個容器物件。在後面的示例中,函式會返回一個元組 。

一個過濾整數的函式定義可能是這樣:

funct bigNumbersOnly (item:Int) -> Bool {
return item > 3
}

現在,我們已經建立了自己的過濾函式,下面讓我們看看可以接受函式引數型別的“filter”函式。

var numbers = [1, 2, 3, 4, 5]
var bigOnes = numbers.filter(bigNumbersOnly)

在這個示例中,filter是一個高階函式,因為它的引數型別是函式。在Swift中,函式也是物件,這意味著我們可以定義行內函數:

var numbers = [1, 2, 3, 4, 5]
var bigOnes = numbers.filter({(item:Int) -> Bool in return item > 3})

或者我們也可以將函式賦值給某個變數,稍後再使用它:

//define two variables of type function
var biggies = {(item:Int) -> Bool in return item > 3 }
var threes = {(item:Int) -> Bool in return item == 3 }
//decide which one to apply at runtime
var result = numbers.filter(onlyThrees ? threes : biggies)

當今,將函式作為物件,讓使用者能夠像使用引數那樣引用、傳遞函式已經成為一種優美的標準。而Swift實現方式的簡潔性依然是一個值得稱道的地方,這一核心概念配合型別推理可以讓你事半功倍。

#3:強型別與型別推理

在企業開發領域,我們非常習慣於使用強型別(或者說型別安全的)語言。強型別語言通常會給我們帶來一點額外的自信,因為它能夠在編譯時進行錯誤檢查,如果這些語言也能夠支援型別推理那麼將會是一種非常好的體驗。例如,可以這樣:

//Without Type inference
var x:String = "bar"
//With Type inference
var y = "bar"

注意,第二行的語句並沒有宣告變數的型別。因為Swift知道“bar”是一個字串,所以我們並不需要顯式地定義它的型別,當然我們也可以指定,正如第一個語句那樣。這看起來好像並沒有特別大的作用,但是如果推理的是函式的型別那麼它就會變得十分有趣。

那麼如果不使用型別我們應該如何定義之前例子中的函式呢?下面的程式碼展現了Swift的實現:

//$0 will map to the first parameter, $1 to the second...
var result = numbers.filter({ return $0 == 3})

沒有比這更簡潔的了!如果你的引數不止一個,同時想要通過名字引用引數,那麼可以這樣做:

{a, b in return a > b }

#4 泛型

Swift提供的另一個非常便利的特性是泛型。在企業開發領域,泛型首先被引入到了C#中,在獲得了大量的關注之後Java也引入了該特性。使用泛型可以讓開發者消除型別轉換,因為編譯器能夠執行特定的型別檢查,而對於不支援泛型的語言而言這是無法做到的。雖然剛開始將泛型引入C#的時候確實產生了一些爭論,但是現在它已經被C#和Java社群所普遍接受。

泛型提供了一種方式可以讓我們推遲型別的定義,通常(但不限於)是引數和返回值的型別。雖然它聽起來很複雜,但是實際上通過一個簡單的示例我們就能非常容易地理解它。

//At the moment of creating this function
//I am not defining what T is. The actual
//Type of T will be deferred to the call
//of the doNothing function.
func doNothing (item:T) -> T {
   return item
}

//When I call doNothing, I am implicitly
//setting the type of T to the Type of the
//parameter I am sending. In this case,
//an Array.
//Notice how the compiler knows, that the
//return type is an Array.
doNothing([1, 2, 3]).count

雖然上面並不是一個真實的例子,但是通過它我們能夠看到在編譯時確定陣列內部元素型別的便利。下面是一個更簡單的示例,注意編譯器是如何知道陣列是包含字串型別的:

var list = ["hello", "world"]
list[0].uppercaseString //HELLO

泛型的使用範圍並沒有被限定於引數,我們也可以定義泛型類、列舉和結構。事實上,在前面的示例中,list的型別是Array<String>。

你可能會將Swift中的泛型與Objective-C中的協議類比,雖然它們的語法非常相似,但是概念是非常不同的。Objective-C並不支援泛型。協議提供了一種方式去宣告一個確定的實現符合某個訊息契約,但是契約必須預先指定。例如,使用協議你並不能強迫陣列中的所有條目都是同一型別的(無論是什麼型別),但是使用泛型能夠做到。除了概念上的不同之外,Swift並不支援協議,就像Objective-C不支援泛型一樣。

#5 元組

元組是非常簡單的概念,你可以定義一個有序的值組。當你需要將多個值作為一個引數來回傳遞、或者被呼叫的函式需要返回多個值的時候元組會非常有用。元組並不需要我們為它的值定義任何型別,編譯時會完成所有的型別推理和型別檢查工作。定義元組的語法如下:

(1, "Two", ["Three"])

在上面的例子中,我們建立了一個帶有三個值的元組,第一個是一個整數,第二個是字串,第三個是一個字串型別的陣列。乍一看這很像一個陣列,但概念完全不同。你不能從一個元組中刪除或者向裡追加元素,同時注意編譯器是如何知道每一個值的確切型別的:

你可以通過元素在元組內部的位置引用它的值,正如圖片所提示的那樣;或者你也可以為每一個值指定一個名稱。如果一個函式需要返回幾個值,那麼這是非常方便的,同時它也能讓我們避免定義那些特定於某個函式的類或者結構。下面讓我們看一個這樣的例子:

func info(items:[Int]) -> (avg:Int, min:Int, max:Int) {
   var sum = items.reduce(0, { $0 + $1 })
   var min = items.reduce(Int.max, { $0 > $1 ? $1 : $0})
   var max = items.reduce(Int.min, { $0 < $1 ? $1 : $0})
   return (sum / items.count, min, max)
}

var result = info([1, 2, 3, 4, 5, 6])
result.avg //3
result.min //1
result.max //6

元組提供了一種非常簡單的使用多個值的方法,讓我們省去了定義一個特定的類或者結構的額外工作。

還有更多

除了上面介紹的特性之外Swift還有很多其他的優秀特性值得我們一看,例如屬性觀察器、可選連結以及擴充套件。

我相信Swift具備快速成為一門流行的iOS和OSX程式語言所需要的所有必須條件,無論是在企業領域還是在消費者領域。強型別和型別推理特性將會讓它非常適合於企業開發,而它的簡單性和乾淨的語法則會吸引那些從事消費者專案的開發人員。

相關文章