2015-03-02 57 views
10

我一直在努力通過Functional Programming in Swift這本書,而我沒有真正理解Optionals章節中介紹的概念的不同之處。Swift函數式編程 - 「可選綁定」與「可選映射」

模式時,與自選的工作往往是:

if let thing = optionalThing { 
    return doThing(thing) 
} 
else { 
    return nil 
} 

這個成語用標準庫函數map

map(optionalThing) { thing in doThing(thing) } 

該書然後繼續並介紹了可選的概念簡潔處理綁定,這是我的區分能力開始崩潰的地方。

這本書指導我們定義map功能:

func map<T, U>(optional: T?, f: T -> U) -> U? 
{ 
    if let x = optional { 
     return f(x) 
    } 
    else { 
     return nil 
    } 
} 

而且還指導我們定義一個可選的綁定功能。 注意:本書使用的是運算符>>=,但我選擇使用命名函數,因爲它可以幫助我看到相似之處。

func optionalBind<T, U>(optional: T?, f: T -> U?) -> U? 
{ 
    if let x = optional { 
     return f(x) 
    } 
    else { 
     return nil 
    } 
} 

這兩種方法的實現看起來與我完全相同。兩者之間唯一的區別是它們採取函數參數:

  • map需要該變換爲T成U
  • optionalBind一個函數使用變換爲T插入可選ü
的函數

「嵌套」 這些函數調用的結果傷害了我的大腦:

func addOptionalsBind(optionalX: Int?, optionalY: Int?) -> Int? 
{ 
    return optionalBind(optionalX) { x in 
     optionalBind(optionalY) { y in 
      x + y 
     } 
    } 
} 

func addOptionalsMap(optionalX: Int?, optionalY: Int?) -> Int? 
{ 
    return map(optionalX) { x in 
     map(optionalY) { y in 
      x + y 
     } 
    } 
} 

  • addOptionalsBind函數完全符合您期望的功能。
  • addOptionalsMap功能無法編譯,指出:

    '詮釋?'是不是可以轉換成'Int?'

我覺得我已經接近了解這裏發生了什麼(可選的整數再次被包裹在可選的?但怎麼?爲什麼?胡?),但我也遠遠不瞭解我是不完全確定要問一個聰明的問題。

回答

5

發生了什麼事情可能會更清晰,更詳細的執行addOptionalsMap。讓我們先從最裏面調用map -instead的你有什麼,讓我們用這個來代替:

let mappedInternal: Int? = map(optionalY) { (y: Int) -> Int in 
    return x + y 
} 

提供給map的封閉需要一個Int及回報,Int,一邊map本身的調用返回可選:Int?。那裏沒有驚喜!讓我們繼續前進了一步,看看會發生什麼:

let mappedExternal: ??? = map(optionalX) { (x: ???) -> ??? in 
    let mappedInternal: Int? = map(optionalY) { (y: Int) -> Int in 
     return x + y 
    } 
    return mappedInternal 
} 

在這裏我們可以看到從上面我們mappedInternal價值,但也有少數種類未定義。 map的簽名爲(T?, T -> U) -> U?,所以我們只需要弄清楚在這種情況下TU。我們知道關閉的返回值mappedInternalInt?,所以U在這裏變成了Int?。另一方面,T可以保留非可選Int。代,我們得到這樣的:

let mappedExternal: Int?? = map(optionalX) { (x: Int) -> Int? in 
    let mappedInternal: Int? = map(optionalY) { (y: Int) -> Int in 
     return x + y 
    } 
    return mappedInternal 
} 

關閉是T -> U,其計算結果爲Int -> Int?,整個map表達最終映射Int?Int??。不是你想到的!


對比,與使用optionalBind版本,完全通過型式指定:

let boundExternal: ??? = optionalBind(optionalX) { (x: ???) -> ??? in 
    let boundInternal: Int? = optionalBind(optionalY) { (y: Int) -> Int? in 
     return x + y 
    } 
    return boundInternal 
} 

讓我們來看看那些???類型此版本。對於optionalBind,我們需要關閉T -> U?,並在boundInternal中有Int?返回值。因此,在這種情況下,兩個TU可以簡單地Int,和我們的實現看起來是這樣的:

let boundExternal: Int? = optionalBind(optionalX) { (x: Int) -> Int? in 
    let boundInternal: Int? = optionalBind(optionalY) { (y: Int) -> Int? in 
     return x + y 
    } 
    return boundInternal 
} 

你的困惑可能來自於這樣的變量可以「解禁」爲選配。可以很容易地看到單層工作時:

func optionalOpposite(num: Int?) -> Int? { 
    if let num = num { 
     return -num 
    } 
    return nil 
} 

optionalOpposite可與一個Int?類型的變量進行調用,就像它明確預期,或Int類型的非可選變量。在第二種情況下,在呼叫期間,非可選變量隱含地轉換爲可選(即,提升)。

map(x: T, f: T -> U) -> U?正在做它的返回值的提升。由於f被宣佈爲T -> U,因此它永遠不會返回可選U?。然而,map的返回值爲U?意味着f(x)在返回時被解除爲U?

在您的示例中,內部封閉返回x + yInt被解除爲Int?。由於您已聲明addOptionalsMap返回Int?,因此該值再次被提升至Int??,導致類型不匹配。

12
  • map取該轉變爲T成U
  • optionalBind一個函數使用變換爲T插入可選ü

沒錯的功能。這是完全不同的。讓我們考慮一個非常簡單的函數lift()。它將把T轉換爲T?。 (在Haskell中,該函數將被稱爲return,但對於非Haskell程序員來說有點太混亂了,此外,return是關鍵字)。

func lift<T>(x: T) -> T? { 
    return x 
} 

println([1].map(lift)) // [Optional(1)] 

太好了。現在如果我們再次這樣做:

println([1].map(lift).map(lift)) // [Optional(Optional(1))] 

嗯。所以現在我們有一個Int??,這是一個痛苦的處理。我們真的寧願只有一種選擇性。讓我們建立一個功能來做到這一點。我們將其稱爲flatten,並將雙重選項平鋪爲單個選項。

func flatten<T>(x: T??) -> T? { 
    switch x { 
    case .Some(let x): return x 
    case .None : return nil 
    } 
} 

println([1].map(lift).map(lift).map(flatten)) // [Optional(1)] 

太棒了。正是我們想要的。你知道,.map(flatten)發生了很多,所以讓我們給它一個名字:flatMap(這是Scala稱之爲的語言)。幾分鐘的遊戲應該向你證明flatMap()的實現恰恰是bindOptional的實現,他們也做同樣的事情。選擇一個可選項,返回一個可選項,並從中獲得一個單一的「可選項」級別。

這是一個確實是的常見問題。這很常見,Haskell有一個內置的運算符(>>=)。 Swift 也有有一個內置的運算符如果您使用方法而不是函數。這就是所謂的可選-鏈接(這是一個真正的恥辱,斯威夫特沒有這個擴展到功能,但斯威夫特愛的方法有很多超過了它喜歡的功能):

struct Name { 
    let first: String? = nil 
    let last: String? = nil 
} 

struct Person { 
    let name: Name? = nil 
} 

let p:Person? = Person(name: Name(first: "Bob", last: "Jones")) 
println(p?.name?.first) // Optional("Bob"), not Optional(Optional(Optional("Bob"))) 

?.真的只是flatMap(*),這是真的只是bindOptional。爲什麼不同的名字?那麼,事實證明,「地圖然後扁平化」就等於另一個被稱爲monadic綁定的想法,它以不同的方式來思考這個問題。是的,單子和所有這一切。如果你認爲T?是monad(它是),那麼flatMap證明是必需的綁定操作。 (所以「綁定」是一個適用於所有monad的更一般的術語,而「flat map」指的是實現細節。我發現「flat map」更容易教人們,但YMMV)

如果你想本討論的更長版本以及它如何適用於除Optional以外的其他類型,請參閱Flattenin' Your Mappenin'

(*).?也可以排序爲map,具體取決於您傳遞的內容。如果您通過T->U?那麼它是flatMap。如果你通過T->U,那麼你可以認爲它是map,或者你仍然認爲它是flatMap其中U隱式升級到U?(Swift會自動執行)。