Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Container types in Swift without 🤯

Container types in Swift without 🤯

Given at the swift.map #5 meetup in Poznań on 26.06.2018

Sample code: https://github.com/siejkowski/swift.map.5

References:

* Yasuhiro Inami's talk on Result vs Result: https://speakerdeck.com/inamiy/result-v-dot-s-result-t-e-english

* Elviro Rocca's talk on protocol based monad transformers: https://www.youtube.com/watch?v=Zmb86zblcto

* Elviro Rocca's library using the approach presented above: https://github.com/facile-it/FunctionalKit

* Yasuhiro Inami's talk on Higher-Kinded Types: https://speakerdeck.com/inamiy/swiftdegao-kaindoduo-xiang

* Yasuhiro Inami's library implementing HKT: https://github.com/inamiy/HigherKindSwift

Krzysztof Siejkowski

June 26, 2018
Tweet

More Decks by Krzysztof Siejkowski

Other Decks in Programming

Transcript

  1. as a user, I want to see all my github

    repos written in Swift language
  2. extension URLSession { func dataTask( with url: URL, completionHandler: @escaping

    (Data?, URLResponse?, Error?) -> Void ) -> URLSessionDataTask }
  3. // DESERIALIZATION (Data) -> Result<[Result<Repo, DeserializationError>], DeserializationError> Deferred< Result< Result<

    [Result<Repo, DeserializationError>], DeserializationError >?, NetworkError > >
  4. // VALIDATION (Repo) -> Result<Repo, ValidationError> Deferred< Result< Result< [Result<

    Result<Repo, ValidationError>, DeserializationError >], DeserializationError >?, NetworkError > >
  5. // PRESENTATION (Repo) -> String Deferred< Result< Result< [Result< Result<String,

    ValidationError>, DeserializationError >], DeserializationError >?, NetworkError > >
  6. Result< Result< [Result<String, RepoError>], RepoError >?, NetworkError > enum NetworkAPIError:

    Error { // previous cases case lackOfData // the (nil, nil) case }
  7. [Result<String, RepoError>] 1. check if any is error 2. if

    yes, return it as result 3. if no, return the values Result<[String], RepoError>
  8. Transformers are APIs for working with nested containers They're providing

    functionality similar to Haskell types called Monad Transformers
  9. // WHAT WE WANT TO WRITE extension Deferred where DataType

    == Result { func mapInner<OtherData>( _ f: (DataType.T) -> OtherData ) -> Deferred<Result<DataType.T, DataType.E>> { return map { result in result.map(f) } } }
  10. // WHAT WE WANT TO WRITE extension Deferred where DataType

    == Result { func mapInner<OtherData>( _ f: (Result.T) -> OtherData ) -> Deferred<Result<Result.T, Result.E>> { return map { result in result.map(f) } } } // WHAT WE GET ERROR: Reference to generic type 'Result' requires arguments in <...>
  11. // WHAT WE WANT TO WRITE extension Deferred where DataType

    == Result<ResultData, ResultError> { func mapInner<OtherData>( _ f: (ResultData) -> OtherData ) -> Deferred<Result<ResultData, ResultError>> { return map { result in result.map(f) } } } // WHAT WE GET ERROR: Use of undeclared type 'ResultData'
  12. // WHAT WE WANT TO WRITE typealias DeferredResult<ResultData, ResultError> =

    Deferred<Result<ResultData, ResultError>> where ResultError: Error extension DeferredResult<ResultData, ResultError> { func mapInner<OtherData>( _ f: (ResultData) -> OtherData ) -> DeferredResult<ResultData, ResultError> { return map { result in result.map(f) } } } // WHAT WE GET ERROR: Constrained extension must be declared on the unspecialized generic type 'DeferredResult' with constraints specified by a 'where' clause
  13. 1. Create protocol protocol ResultType { associatedtype PT associatedtype PE:

    Error } extension Result: ResultType { typealias PT = T typealias PE = E }
  14. 2. Duplicate API protocol ResultType { associatedtype PT associatedtype PE:

    Error func map<OtherData>(_ f: (PT) -> OtherData) -> Result<OtherData, PE> func flatMap<OtherData>(_ f: (PT) -> Result<OtherData, PE>) -> Result<OtherData, PE> } extension Result: ResultType {}
  15. 3. Constraint extensions with protocols extension Deferred where T: ResultType

    { func map<OtherData>( _ f: @escaping (T.PT) -> OtherData ) -> Deferred<Result<OtherData, T.PE>> { return map { result in result.map(f) } } } // WORKS! !
  16. extension Deferred { func map<ResultData, ResultError: Error, OtherData>( _ f:

    @escaping (ResultData) -> OtherData ) -> Deferred<Result<OtherData, ResultError>> where DataType == Result<ResultData, ResultError> { return map { result in result.map(f) } } }
  17. extension Deferred { func map<ResultData, ResultError: Error, OtherData>( _ f:

    @escaping (ResultData) -> OtherData ) -> Deferred<Result<OtherData, ResultError>> where DataType == Result<ResultData, ResultError> { return map { result in result.map(f) } } } ! "
  18. func repoNames(forUser user: String) -> Deferred<Result<Result<[String], RepoError>, NetworkAPIError>> { return

    Deferred { // (user: String) -> Result<Data, NetworkAPIError> // fetching }.map { (data: Data) -> Result<[Repo], RepoError> in // deserialization }.map { (repos: [Repo]) -> Result<[Repo], RepoError> in // validation }.map { (repo: Repo) -> String in // presentation }
  19. 1. Stick to what you use You may then give

    them nice names like mapInternalResult
  20. 1. use types for fun and profit 2. for nested

    types, do - transform data model - discard some information - provide transformers 3. write transformers either/or - protocol-based ("rasure") - method-based (constraints on methods) 4. when too many transformers - write only what you need - generate (Sourcery may help) - use wide types (like Observable)