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. Container types without
    !
    Krzysztof Siejkowski / @_siejkowski

    View Slide

  2. View Slide

  3. Questions are fine
    Seriously
    They're fine

    View Slide

  4. Types

    View Slide

  5. Types?

    View Slide

  6. Type

    View Slide

  7. Optional

    View Slide

  8. Array

    View Slide

  9. Result

    View Slide

  10. Result
    !

    View Slide

  11. Result
    !

    View Slide

  12. Result V.S. Result
    Yasuhiro Inami
    https://speakerdeck.com/inamiy/result-v-dot-s-result-t-e-english

    View Slide

  13. Deferred

    View Slide

  14. Writer

    View Slide

  15. Observable

    View Slide

  16. Types?
    !

    View Slide

  17. Types!

    View Slide

  18. optional.map( )
    result.map( )
    array.map( )
    deferred.map( )

    View Slide

  19. type.map( ).filter( ).flatMap( ).reduce( )

    View Slide

  20. Types

    View Slide

  21. Types
    !

    View Slide

  22. as a user, I want to see all
    my github repos written
    in Swift language

    View Slide

  23. extension URLSession {
    func dataTask(
    with url: URL,
    completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
    ) -> URLSessionDataTask
    }

    View Slide

  24. completionHandler:
    @escaping (Data?, URLResponse?, Error?) -> Void

    View Slide

  25. Deferred>

    View Slide

  26. // DESERIALIZATION
    (Data) -> [Repo]
    Deferred>

    View Slide

  27. // DESERIALIZATION
    (Data) -> Result
    Deferred<
    Result<
    Result?,
    NetworkError
    >
    >

    View Slide

  28. // DESERIALIZATION
    (Data) -> Result],
    DeserializationError>
    Deferred<
    Result<
    Result<
    [Result],
    DeserializationError
    >?,
    NetworkError
    >
    >

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  32. Deferred<
    Result<
    Result<
    [Result<
    Result,
    DeserializationError
    >],
    DeserializationError
    >?,
    NetworkError
    >
    >
    !

    View Slide

  33. Deferred, DeserializationError>], DeserializationError>?, NetworkError>>
    THIS IS
    MADNESS
    !

    View Slide

  34. 3 solutions

    View Slide

  35. #1
    Redesign your
    data model

    View Slide

  36. Errors
    are part of your
    data model

    View Slide

  37. Deferred<
    Result<
    Result<
    [
    Result<
    Result,
    DeserializationError
    >
    ],
    DeserializationError
    >?,
    NetworkError
    >
    >

    View Slide

  38. Result, DeserializationError>
    enum RepoError: Error {
    // expressing both deserializaton and validation
    }
    Result

    View Slide

  39. Deferred<
    Result<
    Result<
    [
    Result
    ],
    RepoError
    >?,
    NetworkError
    >
    >

    View Slide

  40. Deferred<
    Result<
    Result<
    [
    Result
    ],
    RepoError
    >?,
    NetworkError
    >
    >

    View Slide

  41. Result<
    Result<
    [Result], RepoError
    >?,
    NetworkError
    >
    enum NetworkAPIError: Error {
    // previous cases
    case lackOfData // the (nil, nil) case
    }

    View Slide

  42. Deferred<
    Result<
    Result<
    [
    Result
    ],
    RepoError
    >,
    NetworkAPIError
    >
    >

    View Slide

  43. #2
    Discard
    information

    View Slide

  44. Deferred<
    Result<
    Result<
    [
    Result
    ],
    RepoError
    >,
    NetworkAPIError
    >
    >

    View Slide

  45. [Result]
    1. check if any is error
    2. if yes, return it as result
    3. if no, return the values
    Result

    View Slide

  46. Deferred<
    Result<
    Result<
    Result,
    RepoError
    >,
    NetworkAPIError
    >
    >

    View Slide

  47. Deferred<
    Result<
    Result,
    NetworkAPIError
    >
    >

    View Slide

  48. #3
    Provide
    transformers

    View Slide

  49. Transformers
    are APIs
    for working with
    nested containers
    They're providing functionality
    similar to Haskell types called Monad Transformers

    View Slide

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

    View Slide

  51. Generic
    !
    type constraints

    View Slide

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

    View Slide

  53. // WHAT WE WANT TO WRITE
    extension Deferred where DataType == Result {
    func mapInner(
    _ f: (ResultData) -> OtherData
    ) -> Deferred> {
    return map { result in result.map(f) }
    }
    }
    // WHAT WE GET
    ERROR: Use of undeclared type 'ResultData'

    View Slide

  54. // WHAT WE WANT TO WRITE
    typealias DeferredResult =
    Deferred>
    where ResultError: Error
    extension DeferredResult {
    func mapInner(
    _ f: (ResultData) -> OtherData
    ) -> DeferredResult {
    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

    View Slide

  55. 2 ways
    of writing
    transformers

    View Slide

  56. Rasure
    (protocol-based)

    View Slide

  57. Rasure
    ==
    Inversion of
    type erasure

    View Slide

  58. 1. Create protocol
    2. Duplicate API (return concrete type)
    3. Constrain extensions with protocol

    View Slide

  59. 1. Create protocol
    protocol ResultType {
    associatedtype PT
    associatedtype PE: Error
    }
    extension Result: ResultType {
    typealias PT = T
    typealias PE = E
    }

    View Slide

  60. 2. Duplicate API
    protocol ResultType {
    associatedtype PT
    associatedtype PE: Error
    func map(_ f: (PT) -> OtherData)
    -> Result
    func flatMap(_ f: (PT) -> Result)
    -> Result
    }
    extension Result: ResultType {}

    View Slide

  61. 3. Constraint extensions with protocols
    extension Deferred where T: ResultType {
    func map(
    _ f: @escaping (T.PT) -> OtherData
    ) -> Deferred> {
    return map { result in result.map(f) }
    }
    }
    // WORKS!
    !

    View Slide

  62. OptionalType, ResultType,
    ArrayType, DeferredType ...

    View Slide

  63. Constrained
    (method-based)

    View Slide

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

    View Slide

  65. extension Deferred {
    func map(
    _ f: @escaping (ResultData) -> OtherData
    ) -> Deferred>
    where DataType == Result {
    return map { result in result.map(f) }
    }
    }
    ! "

    View Slide

  66. func repoNames(forUser user: String)
    -> Deferred, NetworkAPIError>> {
    return Deferred {
    // (user: String) -> Result
    // fetching
    }.map {
    (data: Data) -> Result in
    // deserialization
    }.map {
    (repos: [Repo]) -> Result in
    // validation
    }.map {
    (repo: Repo) -> String in
    // presentation
    }

    View Slide

  67. Does it really prevent
    !
    ?

    View Slide

  68. Too many transformers?
    4 solutions

    View Slide

  69. 1. Stick to what you use
    You may then give them nice names like mapInternalResult

    View Slide

  70. 2. Generate them all
    Elviro Rocca
    Protocol-Oriented Monad Transformers
    https://www.youtube.com/watch?v=Zmb86zblcto
    Generation using Sourcery

    View Slide

  71. 3. Use wide types
    Observable
    ==
    Future>

    View Slide

  72. 4. Check out Higher-Kinded Types
    Yasuhiro Inami
    https://github.com/inamiy/HigherKindSwift

    View Slide

  73. 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)

    View Slide

  74. May the types be with you!

    View Slide

  75. Thanks
    Questions?

    View Slide