2015-03-02 18 views
10

Tôi đã làm việc thông qua cuốn sách Functional Programming in Swift và tôi thực sự không có cách nào tốt để hiểu sự khác biệt trong khái niệm được giới thiệu trong chương Optionals.Lập trình chức năng nhanh - "Bind tùy chọn" so với "Bản đồ tùy chọn"

Các mô hình khi làm việc với optionals có xu hướng được:

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

Thành ngữ này được xử lý một cách ngắn gọn với chức năng thư viện chuẩn map

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

Cuốn sách này sau đó tiếp tục trên và giới thiệu các khái niệm về không bắt buộc ràng buộc, đó là nơi khả năng phân biệt của tôi bắt đầu bị phá vỡ.

Cuốn sách hướng dẫn chúng ta để xác định map chức năng:

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

Và cũng hướng dẫn chúng ta xác định một chức năng ràng buộc bắt buộc. Lưu ý: cuốn sách sử dụng toán tử >>=, nhưng tôi đã chọn sử dụng hàm được đặt tên vì nó giúp tôi thấy những điểm giống nhau.

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

Việc triển khai cho cả hai phương pháp này trông giống hệt với tôi. Sự khác biệt duy nhất giữa hai là lập luận chức năng họ mất:

  • map mất một chức năng cho phép bạn biến một T thành một U
  • optionalBind mất một chức năng cho phép bạn biến một T vào một tùy chọn U

kết quả của việc "làm tổ" các cuộc gọi chức năng đau não của tôi:

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 
     } 
    } 
} 

  • Chức năng addOptionalsBind thực hiện chính xác những gì bạn mong đợi.
  • Các addOptionalsMap chức năng thất bại trong việc biên dịch nêu:

    'Int ??' không thể chuyển đổi thành 'Int?'

Tôi cảm thấy như tôi đã gần hiểu được những gì đang xảy ra ở đây (và số nguyên tùy chọn đang được bao bọc lại trong tùy chọn? Nhưng tại sao? Hu?), Nhưng tôi cũng đủ xa để hiểu rằng tôi không hoàn toàn chắc chắn về một câu hỏi thông minh để hỏi.

Trả lời

5

Điều gì đang xảy ra có thể rõ ràng hơn với việc triển khai chi tiết hơn addOptionalsMap. Hãy bắt đầu với các cuộc gọi trong cùng để map -instead về những gì bạn có ở đó, hãy sử dụng này để thay thế:

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

Việc đóng cửa cung cấp cho map mất một Int và lợi nhuận và Int, trong khi cuộc gọi đến map tự trả về một tùy chọn : Int?. Không có bất ngờ ở đó!Hãy di chuyển một bước ra ngoài và xem những gì sẽ xảy ra:

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

Ở đây chúng ta có thể thấy được giá trị của chúng tôi mappedInternal từ trên cao, nhưng có một vài loại trái không xác định. map có chữ ký là (T?, T -> U) -> U?, vì vậy, chúng tôi chỉ cần tìm hiểu xem TU là gì trong trường hợp này. Chúng tôi biết giá trị trả lại của khoản đóng, mappedInternal, là Int?, vì vậy U sẽ trở thành Int? tại đây. Mặt khác, T có thể giữ nguyên tùy chọn Int. Thay, chúng tôi có được điều này:

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

Việc đóng cửa là T -> U, mà đánh giá để Int -> Int?, và toàn bộ biểu map kết thúc lên bản đồ Int?-Int??. Không phải những gì bạn có trong tâm trí!


Contrast rằng với phiên bản sử dụng optionalBind, đầy đủ kiểu được chỉ định:

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

Hãy nhìn vào những loại ??? cho phiên bản này. Đối với optionalBind, chúng tôi cần T -> U? đóng và có giá trị trả lại Int? trong boundInternal. Vì vậy, cả hai TU trong trường hợp này chỉ có thể được Int, và thực hiện của chúng tôi trông như thế này:

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

nhầm lẫn của bạn có thể đến từ các biến cách có thể được "nâng lên" như optionals. Thật dễ dàng để nhìn thấy khi làm việc với một lớp duy nhất:

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

optionalOpposite có thể được gọi với một trong hai một biến kiểu Int?, như nó một cách rõ ràng hy vọng, hoặc một biến không bắt buộc kiểu Int. Trong trường hợp thứ hai này, biến không bắt buộc được chuyển đổi hoàn toàn thành tùy chọn (tức là, được dỡ bỏ) trong suốt cuộc gọi.

map(x: T, f: T -> U) -> U? đang thực hiện nâng trong giá trị trả về của nó. Vì f được khai báo là T -> U, nó không bao giờ trả về tùy chọn U?. Tuy nhiên, giá trị trả lại của mapU? có nghĩa là f(x) được nâng lên U? khi trả lại.

Trong ví dụ của bạn, kết thúc bên trong trả về x + y, một số Int được nâng lên Int?. Giá trị đó sau đó được nâng lên một lần nữa để Int?? dẫn đến loại không khớp, vì bạn đã khai báo addOptionalsMap để trả lại một số Int?.

12
  • bản đồ có một chức năng cho phép bạn biến một T thành một U
  • optionalBind mất một chức năng cho phép bạn biến một T thành một U tùy chọn

Chính xác. Đó là toàn bộ sự khác biệt.Hãy xem xét một hàm thực sự đơn giản, lift(). Nó sẽ chuyển đổi T thành T?. (Trong Haskell, chức năng đó sẽ được gọi là return, nhưng đó là một chút quá khó hiểu cho các lập trình viên không phải Haskell, và bên cạnh đó, return là một từ khóa).

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

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

Tuyệt vời. Bây giờ nếu chúng ta làm điều đó một lần nữa:

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

Hmmm. Vì vậy, bây giờ chúng tôi có một Int??, và đó là một nỗi đau để đối phó với. Chúng tôi thực sự chỉ muốn có một mức độ tùy chọn. Hãy xây dựng một hàm để làm điều đó. Chúng tôi sẽ gọi nó là flatten và làm phẳng xuống một tùy chọn kép thành tùy chọn đơn.

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)] 

Tuyệt vời. Chỉ là những gì chúng tôi muốn. Bạn biết đấy, .map(flatten) xảy ra rất nhiều, vì vậy, hãy đặt tên cho nó: flatMap (đó là những ngôn ngữ như Scala gọi nó). Một vài phút chơi sẽ chứng minh với bạn rằng việc thực hiện flatMap() chính xác là việc thực hiện bindOptional và chúng cũng làm như vậy. Có một tùy chọn và cái gì đó trả về một tùy chọn, và nhận được chỉ là một mức độ duy nhất của "tùy chọn-ness" ra khỏi nó.

Đây là một sự cố thường gặp . Nó rất phổ biến mà Haskell có một nhà điều hành tích hợp cho nó (>>=). Nó phổ biến đến nỗi Swift cũng có một toán tử tích hợp cho nó nếu bạn sử dụng các phương thức thay vì hàm. Nó được gọi là tùy chọn xâu chuỗi (đó là một sự xấu hổ thực sự mà Swift không mở rộng này để chức năng, nhưng Swift yêu phương pháp nhiều hơn nó yêu chức năng):

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"))) 

?. thực sự chỉ là flatMap (*) là thực sự chỉ bindOptional. Tại sao các tên khác nhau? Vâng, nó chỉ ra rằng "bản đồ và sau đó flatten" là tương đương với một ý tưởng gọi là ràng buộc monadic mà suy nghĩ về vấn đề này một cách khác nhau. Yep, monads và tất cả những điều đó. Nếu bạn nghĩ về T? là một đơn nguyên (có nghĩa là), thì flatMap hóa ra là thao tác liên kết bắt buộc. (Vì vậy, "ràng buộc" là một thuật ngữ chung chung hơn áp dụng cho tất cả các monads, trong khi "bản đồ phẳng" đề cập đến chi tiết thực hiện. Tôi tìm thấy "bản đồ phẳng" dễ dàng hơn để dạy mọi người đầu tiên, nhưng YMMV.)

Nếu bạn muốn một phiên bản dài hơn của cuộc thảo luận này và cách nó có thể áp dụng cho các loại khác hơn Optional, xem Flattenin' Your Mappenin'.

(*) .? cũng có thể là map tùy thuộc vào nội dung bạn vượt qua. Nếu bạn vượt qua T->U? thì đó là flatMap. Nếu bạn vượt qua T->U thì bạn có thể nghĩ về điều này là map hoặc bạn vẫn nghĩ rằng đó là flatMap trong đó U được quảng bá hoàn toàn đến U? (Swift sẽ tự động thực hiện).

Các vấn đề liên quan