99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Swift函數(shù)式編程 不變性

2022-08-11 13:57 更新

Swift函數(shù)式編程-不變性

Swift支持函數(shù)式編程,這一篇介紹不變性(immutable)。

不變性

不變性是函數(shù)式編程的基礎。

先討論一下Haskell這類純函數(shù)式語言。簡單而言,Haskell沒有變量。這是因為,Haskell追求更高級別的抽象,而變量其實是對一類低級計算機硬件:存儲器空間(寄存器,內(nèi)存)的抽象。變量存在的原因,可以視為計算機語言進化的遺跡,比如在初期直接操作硬件的匯編語言中,需要變量來使用操作存儲過程。而在計算機出現(xiàn)之前,解決數(shù)學計算問題都是圍繞構(gòu)建數(shù)學函數(shù)。數(shù)學中,不存在計算機語言中這種需要重復賦值的變量。

而Haskell則基于更抽象的數(shù)學模型。使用Haskell編程只需專注于設計數(shù)據(jù)之間的映射關系。而在數(shù)學上,表示兩個數(shù)據(jù)之間映射關系的實體就是函數(shù)。這使得編寫Haskell代碼和設計數(shù)學函數(shù)的過程是一致的,Haskell程序員的思路也更接近數(shù)學的本質(zhì)。

Haskell摒棄了變量的同時,也拋棄了循環(huán)控制。這是因為沒有變量,也就沒有了控制循環(huán)位置的循環(huán)變量。這也很好理解?;貞浺幌挛覀冊趯W習計算機之前的數(shù)學課程中,也無需使用到for這類概念。我們還是使用函數(shù)處理一個序列到另外一個序列的轉(zhuǎn)換。

Swift提供了一定程度的不變性。在Swift中,被聲明為不變的對象在完成對其初始構(gòu)造之后就不可改變。換句話說,構(gòu)造器是唯一個可以改變對象狀態(tài)的地方。如果你想改變一個對象的值,只能使用修改后的值來創(chuàng)建新的對象。

不變性是為了減少或者消滅狀態(tài)。面向?qū)ο缶幊陶Z言中,狀態(tài)是計算的基礎信息。如何可控地修改狀態(tài),Java,Ruby等編程語言都給出了大量的語言機制,比如,可見性分級。但是,由于大量可變狀態(tài)的存在,使用面向?qū)ο缶幊陶Z言在編寫高并發(fā),多線程代碼時會有很多困難。因為,你無法知道并行進行的諸多狀態(tài)讀寫中是否有順序上的錯誤。而且這種錯誤又是難以察覺的。而不變性解決了這個問題。不變性意味函數(shù)沒有副作用,無論多少次執(zhí)行,相同的輸入就意味著相同的輸出。那么,多線程環(huán)境中就沒有了煩人的同步機制。所有線程都可以無所顧忌的執(zhí)行同一個函數(shù)的代碼。

而在Java這類面向?qū)ο缶幊陶Z言中,變量用于表示對象本身的狀態(tài)。Swift作為支持多種范型的編程語言,即支持變量,也支持方便地申明不變量。

變量和不變量

Java中,聲明不變量:

#變量
private string mutable;
#不變量
private final String immutable;

Scala中,聲明不變量:

#變量
var mutable
#不變量
val immutable = 1

Swift中聲明變量和不變量:

#變量
var mutable
#不變量
let immutable = 1

Swift中聲明了不變量,就必須在聲明時同時初始化,或者在構(gòu)造器中初始化。這兩個地方之外,就無法再改變不變量了。Swift區(qū)分varlet不僅僅是為了區(qū)分變量和不變量,同時也是為了使用編譯器來強制這種區(qū)分。聲明不變量是受到鼓勵的。因為,使用不變量更容易寫出,容易理解,容易測試,松耦合的代碼。

不可變類

由于不可變性具有例如線程安全性這類天生優(yōu)勢,在編寫面向?qū)ο笳Z言時,我們也會有使用到不變對象的場景。但由于編程范式不同的原因,在面向?qū)ο笳Z言中構(gòu)造不可變類是一件非常麻煩的事情。

以Java為例,如果將一個類構(gòu)造成不可變的類,需要做如下事情:

  • 將類聲明為final。這樣就不能繼承該類。無法繼承該類,就無法重寫它的方法的行為。Java 中的String 類就使用了這種策略。

  • 所有的實例變量都聲明為final。這樣,你就必須在申明時初始化它,或者在構(gòu)造器中初始化它們。在其他地方,你都將無法改變聲明為final的實例變量。

  • 提供合適的構(gòu)造過程。對于不可變類,構(gòu)造器是唯一可以初始化它的地方。所以,提供一個合適的構(gòu)造器是實用不可變類的必要條件。

  • 除構(gòu)造器之外不提供任何可以改變狀態(tài)的方法。實例變量被聲明為final,其實就已經(jīng)無法改變它們了。但是,仍然可以改變它們所指向的內(nèi)容。因此,在 getter 方法中考慮防御方法:不直接返回內(nèi)容的引用,而是返回復制內(nèi)容的引用。

一個Java實現(xiàn)的不可變類的例子如下:

public final class Person {
    private final String name;
    private final List<String> interests;

    public Person(String name, List<String> interests) {
        this.name = name;
        this.streets = streets;
        this.city = city;
    }

    public String getName() {
        return name;
    }       

    public List<String> getInterests() {
        return Collections.unmodifiableList(interests);
    }
}

具有函數(shù)特性的多范式編程語言中,大多數(shù)會為構(gòu)造不變類提供方便。比如Groovy提供了@Immutable注釋來表示不可變類。

@Immutable
class Preson {
    String name
    String[] interests
}

@Immutable 提供了以下功能:

  • 它是final的,即不可被繼承的;
  • 屬性自動擁有了私有的,并且自動產(chǎn)生了getter方法;
  • 任何改變屬性的企圖都會導致拋出 ReadOnlyPropertyException 異常;
  • Groovy創(chuàng)建了合適的構(gòu)造函數(shù):即創(chuàng)建了有序的構(gòu)造函數(shù),又創(chuàng)建了基于映射的構(gòu)造函數(shù);
  • 集合類被封裝在適當?shù)陌b器中,數(shù)組(及其他可克隆的對象)被克隆。
  • 自動生成默認的 equals、hashcode 和 toString 方法。

Swift實現(xiàn)一個不可變類的方法的例子:

struct Person {
    let name:String
    let interests:[String]
}
  • 結(jié)構(gòu)體(struct)是final的,即不可被繼承;
  • let 聲明的實例變量,保證了類初始化之后,實例變量無法再被改變;
  • struct 是值類型,將一個struct賦給另外一個變量,其實是拷貝了對像,將拷貝的對象賦值給另一個變量。

Swift中實現(xiàn)一個不可變的類的方法是:聲明一個結(jié)構(gòu)體(struct),并將該結(jié)構(gòu)體的所有實例變量以let開頭聲明為不變量。在不變性這方面,枚舉(enum)具有和結(jié)構(gòu)體相同的特性。所以,上面例子中的結(jié)構(gòu)體在合適的場景下,也可以被枚舉類型替換。

值類型和引用類型

?值類型在賦值和作為函數(shù)參數(shù)的時候被傳遞給一個函數(shù)的時候,實際上操作的是其的拷貝。Swift中有大量值類型,包括數(shù)字,字符串,數(shù)組,字典,元組,枚舉和結(jié)構(gòu)體等。

struct PersonStruct {
    var name:String
}

var structPerson = PersonStruct(name:"Totty")
var sameStructPerson = structPerson
sameStructPerson.name = "John"
print(structPerson.name)
print(sameStructPerson.name)

// result:
// "Totty"
// "John"

可以看到,structPerson和sameStructPerson的值不一樣了。在賦值的時候,sameStructPerson的到是structPerson的拷貝。

引用類的實例 (主要是類) 可以有多個所有者。在賦值和作為函數(shù)參數(shù)的時候被傳遞給一個函數(shù)的時候,操作的是其引用,而并不是其拷貝。這些引用都指向同一個實例。對這些引用的操作,都將影響同一個實例。

class PersonClass {
    var name:String
}

var classPerson = PersonClass(name:"Totty")
var sameClassPerson = structPerson
sameClassPerson.name = "John"
print(classPerson.name)
print(sameClassPerson.name)

// result:
// "John"
// "John"

可以看到,sameClassPerson的改變,同樣也影響到了classPerson。其實它們指向同一個實例。這種區(qū)別在作為函數(shù)參數(shù)時也是存在的。

在Swift中區(qū)分值類型和引用類型是為了讓你將可變的對象和不可變的數(shù)據(jù)區(qū)分開來。Swift增強了對值類型的支持,鼓勵我們使用值類型。使用值類型,函數(shù)可以自由拷貝,改變值,而不用擔心產(chǎn)生副作用。

純函數(shù)

不變性導致另外一個結(jié)果,就是純函數(shù)。純函數(shù)即沒有副作用的函數(shù),無論多少次執(zhí)行,相同的輸入就意味著相同的輸出。一個純函數(shù)的行為并不取決于全局變量、數(shù)據(jù)庫的內(nèi)容或者網(wǎng)絡連接狀態(tài)。純代碼天然就是模塊化的:每個函數(shù)都是自包容的,并且都帶有定義良好的接口。純函數(shù)具有非常好的特性。它意味著理解起來更簡單,更容易組合,測試起來更方便,線程安全性。

Objective-C中的不變性

Objective-C中,蘋果的Foundation庫提供了不少具有不變性的類:NString相對于NSMutableString,NSArray相對于NSMutableArray,以及NSURL等等。在Objective-C中,絕大多數(shù)情況下,使用不變類是缺省選擇。但是,Objective-C中沒有如Swift中let這樣簡單強制不變性的方法。

總結(jié)

不變性的好處:

  • 更高層次的抽象。程序員可以以更接近數(shù)學的方式思考問題。

  • 更容易理解的代碼。由于不存在副作用,無論多少次執(zhí)行,相同的輸入就意味著相同的輸出。純函數(shù)比有可變狀態(tài)的函數(shù)和對象理解起來要容易簡單得多。你無需再擔心對象的某個狀態(tài)的改變,會對它的某個行為(函數(shù))產(chǎn)生影響。

  • 更容易測試的代碼。更容易理解的代碼,也就意味著測試會更簡單。測試的存在是為了檢查代碼中成功發(fā)生的轉(zhuǎn)變。換句話說,測試的真正目的是驗證改變,改變越多,就需要越多的測試來確保您的做法是正確的。如果你能有效的限制變化,那么錯誤的發(fā)生的可能就更小,需要測試的地方也就更少。變化只會發(fā)生構(gòu)造器中,因此為不可變類編寫單元測試就成了一件簡單而愉快的事情。

  • 線程安全的代碼。這意味著多線程環(huán)境下,運行代碼沒有同步問題。它們也不可能因為異常的發(fā)生而處于無法預測的狀態(tài)中。

不像Haskell這種純函數(shù)式編程語言只能申明不可變量,Swift提供變量和不可變量兩種申明方式。這使得程序員有選擇的余地:在使用面向?qū)ο缶幊谭妒綍r,可以使用變量。在需要的情況下,Swift也提供不變性的支持。

原文出處:http://lincode.github.io/Swift-Immutable
作者:LinGuo

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號