99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

Swift 關(guān)聯(lián)類型

發(fā)布時間:2016-10-22 21:54  回復(fù):0  查看:2372   最后回復(fù):2016-10-22 21:54  

有時候我認(rèn)為類型理論是故意弄的很復(fù)雜,以及所有的那些函數(shù)式編程追隨者都只是胡說八道,仿佛他們理解了其中的含義。真的嗎?你有一篇5000 字的博客是寫關(guān)于插入隨機類型理論概念的嗎?毫無疑問的沒有。a)為什么有人會關(guān)心這些以及b)通過這個高大上的概念能幫我們解決什么問題?我想把你裝進(jìn)麻布袋里,扔進(jìn)河里,并且砸進(jìn)一個 里。

  我們在討論什么?當(dāng)然,關(guān)聯(lián)類型。

  當(dāng)我第一次看到 Swift 范型的實現(xiàn)時, 關(guān)聯(lián)類型的用法的出現(xiàn),讓我感到很奇怪。

  在這篇文章,我將通過類型概念和一些實踐經(jīng)驗,這幾乎都是我用自己的思考嘗試解釋這些概念(如果我犯了錯誤,請告訴我)。

范型

  在 Swift 中,如果我想有一個抽象的類型(也就是創(chuàng)建一個范型的 東西 ),在類中的語法是這個樣子:

class Wat{ ... }

  類似的,帶范型的結(jié)構(gòu)體:

struct WatWat{ ... }

  或者帶范型的枚舉:

enum GoodDaySir{ ... }

  但如果我想有一個抽象的協(xié)議:

protocol WellINever {

typealias T

}

  嗯哼?

基本概念

protocol 和 class 、struct 以及 enum 不同,它不支持范型類型 參數(shù) 。代替支持 抽象類型成員 ;在 Swift 術(shù)語中稱作 關(guān)聯(lián)類型 。盡管你可以用其它系統(tǒng)完成類似的事情,但這里有一些使用關(guān)聯(lián)類型的好處(以及當(dāng)前存在的一些缺點)。

  協(xié)議中的一個關(guān)聯(lián)類型表示:我不知道具體類型是什么,一些服從我的類、結(jié)構(gòu)體、枚舉會幫我實現(xiàn)這個細(xì)節(jié)。

  你會很驚奇: “非常棒,但和類型參數(shù)有什么不同呢?” 。一個很好的問題。類型參數(shù)強迫每個人知道相關(guān)的類型以及需要反復(fù)的指明該類型(當(dāng)你在構(gòu)建他們的時候,這會讓你寫很多的類型參數(shù))。他們是公共接口的一部分。這些代碼 使用 多種結(jié)構(gòu)(類、結(jié)構(gòu)體、枚舉)的代碼會確定具體選擇什么類型。

  通過對比關(guān)聯(lián)類型實現(xiàn)細(xì)節(jié)的部分。它被隱藏了,就像是一個類可以隱藏內(nèi)部的實例變量。使用抽象的類型成員的目的是推遲指明具體類型的時機。和泛型不同, 它不是在實例化一個類或者結(jié)構(gòu)體時指明具體類型 ,而且在服從該協(xié)議時,指明其具體類型。這讓我們多了一種選擇類型的方式。

有用的

Scala 的創(chuàng)建者 Mark Odersky 在一次交流時討論了一個例子 。在 Swift 術(shù)語中,如果沒有關(guān)聯(lián)類型的話,此時你有一個帶有 eat(f:Food) 的方法的基類或者協(xié)議 Animal ,之后的 Cow 類的沒有辦法指定 Food 只能是 Grass 。你很清楚不能通過重載這個方法 - 協(xié)變參數(shù)類型(在子類中添加一個更明確的參數(shù))在大多數(shù)的語言都是不支持的,并且是一種不安全的方式 ,當(dāng)從基類進(jìn)行類型轉(zhuǎn)換的時候可能得到意料之外的值。

  如果 Swift 的協(xié)議已經(jīng)支持類型參數(shù),那代碼大概是這個樣子:

protocol Food { }class Grass : Food { }protocol Animal<F:Food> {

func eat(f:F)

}class Cow : Animal<Grass> {

func eat(f:Grass) { ... }

}

  非常棒。那當(dāng)我們需要再增加些東西呢?

protocol Animal<F:Food, S:Supplement> {

func eat(f:F)

func supplement(s:S)

}class Cow : Animal<Grass, Salt> {

func eat(f:Grass) { ... }

func supplement(s:Salt) { ... }

}

  增加了類型參數(shù)的數(shù)量是很不爽的,但這并不是我們的唯一問題。我們到處泄露實現(xiàn)的細(xì)節(jié),需要我們?nèi)ブ匦轮该骶唧w的類型。 var c = Cow() 的類型就變成了 Cow<GRASS,SALT> 。一個 doCowThings 方法將變成 func doCowThings(c:Cow<GRASS,SALT>) 。那如果我們想讓所有的動物都吃草呢?并且我們沒有方式表明我們不關(guān)心 Supplement 類型參數(shù)。

  當(dāng)我們從 Cow 中獲得了創(chuàng)建特別的品種,我們的類就會很白癡的定義成這樣: class Holstein<food:grass, supplement:salt="">: Cow<GRASS,SALT> 。

  更糟糕的是,一個買食物來喂養(yǎng)這些動物的方法變成這個樣子了: func buyFoodAndFeed<T,F where T:Animal<FOOD,SUPPLEMENT>>(a:T, s:Store) 。這真的很丑很啰嗦,我們已經(jīng)無法把 F  Food 關(guān)聯(lián)起來了。如果我們重寫這個方法,我們可以這樣寫 func buyFoodAndFeed<f:food,s:supplement>(a:Animal<FOOD,SUPPLEMENT>, s:Store) ,但這并不會有作用 - 當(dāng)我們嘗試傳入一個 Cow<GRASS, salt=""> 參數(shù),Swift 會抱怨 ’Grass’ is not identical to ‘Food’ ’Grass’ 和 ‘Food’ 不相同)。再補充一點,注意到這個方法并不關(guān)心 Supplement ,但這里我們卻不得不處理它。

  現(xiàn)在讓我們看看如何用關(guān)聯(lián)類型幫我們解決問題:

protocol Animal {

typealias EdibleFood

typealias SupplementKind

func eat(f:EdibleFood)

func supplement(s:SupplementKind)

}class Cow : Animal {

func eat(f: Grass) { ... }

func supplement(s: Salt) { ... }

}class Holstein : Cow { ... }func buyFoodAndFeed<t:animal, s.foodtype="" t.ediblefood="=" where="" s:store="">(a:T, s:S){ ... }

  現(xiàn)在的類型簽名清晰多了。Swift 指向這個關(guān)聯(lián)類型,只是通過查找 Cow 的方法簽名。我們的 buyFoodAndFeed 方法,可以清晰的表達(dá)商店賣的食物是動物吃的食物。事實上,Cow 需要一個特別的食物類型,而這個具體實現(xiàn)是在 Cow 類里面,但這些信息仍然要在在編譯時確定。

真實的例子

  討論了一會關(guān)于動物的事情,讓我們再來看看 Swift 中的 CollectionType 。

  筆記:作為一個具體實現(xiàn),許多 Swift 協(xié)議都有帶前導(dǎo)下劃線的嵌套協(xié)議;比如 CollectionType -> _CollectionType 或者SequenceType -> _Sequence_Type -> _SequenceType 。簡單來說,當(dāng)我們討論這些協(xié)議時,我即將打平這些層級。所以當(dāng)我說CollectionType  ItemType 、 IndexType  GeneratorType 關(guān)聯(lián)類型時,你并不能在協(xié)議 CollectionType 本身中找到這些。

  顯然,我們需要元素 T 的類型,但我們也需要這個索引和生成器(generator)/計數(shù)器 (enumerator)的類型,這樣我們才可以處理subscript(index:S) -> T { get }  func generate() -> G 。如果我們只是使用類型參數(shù),唯一的方法就是提供一個帶泛型的 Collection 協(xié)議,在一個假想的 CollectionOf<T,S,G> 中指明 T S G 。

  其他語言是怎么處理的呢?C# 并沒有抽象類型成員。他首先處理這些是通過不支持任何東西而不是一個開放式的索引,這里的類型系統(tǒng)不會表明索引是否只能單向移動,是否支持隨機存取等等。數(shù)字的索引就只是個整型,以及類型系統(tǒng)也只會表明這一信息。

  其次,對于生成器 IEnumerable 會生成一個 IEnumerator 。起初這個不同看起來非常的微妙,但 C# 的解決方案是用一個接口(協(xié)議)直接的抽象覆蓋掉這個生成器,允許它避免必須去聲明特別的生成器類型,作為一個參數(shù),像 IEnumerable 

Swift 目的是做一個傳統(tǒng)的編譯系統(tǒng)(non-VM , non-JIT)編程語言,考慮到性能的需求,需要動態(tài)行為類型并不是一個好主意。編譯器真的傾向于知道你的索引和生成器的類型,以便于它可以做一些奇妙的事情,比如代碼嵌入(inlining)以及知道需要分配多少內(nèi)存這樣奇妙的事情。

  唯一的方法就是,通過 香腸研磨機 在編譯時便利出所有的泛型。如果你強迫將它推遲到運行時,這也就意味著你需要一些間接的、裝箱和其他的類似比較好的技巧,但這些都是有門檻的。

愚蠢的事實

  這里主要的帶有抽象類型成員的 “gotcha” Swift 不會完全地讓你確定他們是變量還是參數(shù)類型,畢竟這是不必要的事情。只有在使用到泛型約束的時候,你才會用到帶有關(guān)聯(lián)類型的協(xié)議。

  在我們的之前的 Animal 例子中,調(diào)用 Animal().eat 是不安全的,因為它只是一個抽象的 EdibleFood ,并且我們不知道這個具體的類型。

  理論上,這些代碼本應(yīng)該可以工作的,只要泛型在這個方法上強迫動物吃商店銷售的食物的約束,但實際上,當(dāng)測試它的時候,我遇到了一些 EXC_BAD_ACCESS 的崩潰,我不確定這是情況是不是因為編譯器的問題。

func buyFoodAndFeed<t:animal,s:storetype s.foodtype="" t.ediblefood="=" where="">(a:T, s:S) {

a.eat(s.buyFood()) //crash!

}

  我們沒有辦法使用這些協(xié)議作為參數(shù)或者變量類型。這只是需要考慮的更遠(yuǎn)一些。這是一個我希望在未來 Swift 會支持的一個特性。我希望聲明變量或者類型時能夠?qū)懗蛇@樣的代碼:

typealias GrassEatingAnimal = protocol<A:Animal where A.EdibleFood == Grass>

var x:GrassEatingAnimal = ...

  注意:使用 typealias 只是創(chuàng)建一個類型別名,而不是在協(xié)議中的關(guān)聯(lián)類型。我知道這可能有些讓人感覺困惑。

  這個語法將會讓我聲明一個變量可以持有一些動物的一些類型,而這里的動物關(guān)聯(lián)的 EdiableFoof  Grass 。它可能是很有用的,如果在協(xié)議中約束其關(guān)聯(lián)類型,但這看起來你可能會進(jìn)入一個不安全的位置,導(dǎo)致需要考慮的更多一些。如果你開始運行時,有一件事,你需要 約束 關(guān)聯(lián)類型在這個編譯器的定義的協(xié)議不能安全的約束任何帶泛型的方法(見下文)。

  當(dāng)前情況下,為了獲得一個類型參數(shù),你必須通過創(chuàng)建一個封裝的結(jié)構(gòu)體擦除其關(guān)聯(lián)類型。進(jìn)一步的警告:這很丑陋。

struct SpecificAnimal<F,S> : Animal {

let _eat:(f:F)->()

let _supplement:(s:S)->()

init<A:Animal where A.EdibleFood == F, A.SupplementKind == S>(var _ selfie:A) {

_eat = { selfie.eat($0) }

_supplement = { selfie.supplement($0) }

}

func eat(f:F) {

_eat(f:f)

}

func supplement(s:S) {

_supplement(s:s)

}

}

  如果你曾考慮過為什么 Swift 標(biāo)準(zhǔn)庫會包括 GeneratorOf:Generator 、 SequenceOf:Sequence  SinkOf:Sink … 我想現(xiàn)在你知道了。

  我上面提到的這個 bug ,如果 Animal 指明了 typealias EdibleFood:Food 之后,即使你給它定義了 typealias EdibleFood:Food ,這個結(jié)構(gòu)體仍然是無法編譯的。即使是在結(jié)構(gòu)體中進(jìn)行了清晰的約束, Swift 將會抱怨 F 不是 Food 。詳情可以見 rdar://19371678 。

總結(jié)

  就像我們之前看到的,關(guān)聯(lián)類型允許在編譯時提供多個具體的類型,只要該類型服從對應(yīng)的協(xié)議,從而不會用一堆類型參數(shù)污染類型定義。對于這個問題,它們是一個很有趣的解決方案,用泛型類型參數(shù)表達(dá)出不同類型的抽象成員。

  更進(jìn)一步考慮,我在想,如果采取 Scala 的方案,簡單的為 class 、 struct enum 以及 protocol 提供類型參數(shù)和關(guān)聯(lián)類型兩個方法會是否更好一些。我還沒有進(jìn)行更深入的思考,所以還有一些想法就先不討論了。對于一個新語言最讓人興奮的部分是 關(guān)注它的發(fā)展以及改進(jìn)進(jìn)度。

現(xiàn)在走的更遠(yuǎn)一些,并且向你的同事開始炫耀類似 抽象類型成員 的東西。之后你也可以稱霸他們,講一些很難理解的東西。

要遠(yuǎn)離麻袋。

還有河水。

沒有坑,坑是令人驚奇的。

 

文章來源:SegmentFault

您還未登錄,請先登錄

熱門帖子

最新帖子

?