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

Matryoshka in Swift: handle nested containers w...

Matryoshka in Swift: handle nested containers with grace

Do you like container types? I mean, the Results, Optionals, Futures, Observables? Would you feel alright if your child married one?
If the answer is yes, then how would you like a container inside a container? And what about a container inside a container inside a container? Or a container inside a container inside a container inside a container? Oh boy, things are quickly getting out of hands!
In this talk I'll show some ideas on how to work with the real-world container nesting and the tradeoffs that are coming along.

Krzysztof Siejkowski

November 19, 2018

Transcript

56 slides
  1. What is container? Container<A> A instance of type A Container

    holds 
 the instance 
 of type A while adding
 its context
  2. Either<A, B> A B Result<A, E> A E Future<A> ((A)

    -> Void) -> Void Observable<A> () -> Event<A> (A) -> Void
  3. As a user, I want to see all my Github

    repos written in Swift language
  4. 1. Fetch data from Github extension URLSession { func dataTask(

    with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void ) -> URLSessionDataTask }
  5. 1. Fetch data from Github completionHandler => Future (Data?, URLResponse?,

    Error?) => Result<Data, NetworkError> Future<Result<Data, NetworkError >>
  6. 2. Deserialize Result is a JSON object in form of

    an array of repos: [ { “name”: “repo_name”, ... }, ... We want to know which ones of the repos couldn’t be deserialized deserialize: (Data) throws -> [Data] throws -> [Repo]
  7. 2. Deserialize deserialize: (Data) throws -> [Data] throws -> [Repo]

    => (Data) -> Result<[Data], DeserializationError> -> Result<[Result<Repo, DeserializationError>], DeserializationError> Future<Result< Result< [Result<Repo, DeserializationError>], DeserializationError >, NetworkError >>
  8. 3. Filter Swift repos filter: (Repo) -> Bool repos.filter {

    (repo: Repo) -> Bool in repo.language == "Swift" } "Swift"
  9. 3. Filter Swift repos let repos: Future<Result< Result< [Result<Repo, DeserializationError>],

    DeserializationError >, NetworkError >> = githubClient.fetch(.repos, for: user) .map { deserialize($0) } repos.filter { }
  10. You can write a transform! Optional<A> A ∅ Either<A, Void>

    A Void Optional<A> A ∅ Either<A, Void> A Void
  11. extension Result { func asValidated() -> Validated<A, E> { ...

    } } Future< Validated< Validated< Array<Validated<Repo, DeserializationError >>, DeserializationError >, NetworkError > >
  12. Given: 
 Array<Optional<A >> Sequence: If any element of array

    is nil, then return nil Else, return array of unwrapped, non-nil elements Then: Optional<Array<A >>
  13. func sequence<A, E>(_ vs: [Validated<A, E>]) -> Validated<[A], E> Future<

    Validated< Validated< Validated< Array<Repo>, DeserializationError >, DeserializationError >, NetworkError > >
  14. extension Validated { func flatten<B>() -> Validated<B, E> where A

    == Validated<B, E> { ... } } Future< Validated< Validated< Array<Repo>, DeserializationError >, NetworkError > >
  15. extension Validated { func mapErrors<F>(_ f: (E) -> F) ->

    Validated<A, F> { ... } } // put errors into Either container Future< Validated< Validated< Array<Repo>, Either<NetworkError, DeserializationError> >, Either<NetworkError, DeserializationError> > >
  16. We’re left with 3 containers-deep nesting // after flattening again

    Future< // 1st level Validated< // 2nd level [Repo], // 3rd level Either<NetworkError, DeserializationError> > >
  17. Filter Swift repos let repos: Future< Validated< Array<Repo>, Either<NetworkError, DeserializationError>

    > > = githubClient.fetch(.repos, for: user) .map { deserialize($0) } .map { // transforms described before } repos.filter { }
  18. Inverse of type erasure protocol Protocol { associatedtype A }

    Type erasure struct AnyProtocol<A> 
 : Protocol struct Container<A> “Type rasure” protocol ContainerType { associatedtype A }
  19. 1. Create protocol protocol ValidatedType { associatedtype PA associatedtype PE

    } extension Validated: ValidatedType { typealias PA = A typealias PE = E }
  20. 2. Duplicate API in protocol (return concrete type) protocol ValidatedType

    { associatedtype PA associatedtype PE func map<B>(_ f: (PA) -> B) -> Validated<B, PE> } extension Validated: ValidatedType {}
  21. 3. Implement the nested method in a constrained extension extension

    Future where A: ValidatedType { func map<B>( _ f: @escaping (A.PA) -> B ) -> Future<Validated<B, A.PE >> { return map { $0.map(f) } } }
  22. 1. Generalize the method, not extension extension Future { func

    map<VA, VE, B>( _ f: @escaping (VA) -> B ) -> Future<Validated<B, VE >> { // work in progress } }
  23. 2. Add constraints to the method, not extension extension Future

    { func map<VA, VE, B>( _ f: @escaping (VA) -> B ) -> Future<Validated<B, VE >> where A == Validated<VA, VE> { return map { $0.map(f) } } }
  24. Now we can filter Swift repos! let repos: Future< Validated<

    Array<Repo>, Either<NetworkError, DeserializationError> >> = githubClient.fetch(.repos, for: user) .map { deserialize($0) } .map { // transforms described before } repos.filter { (repo: Repo) -> Bool in repo.language == “Swift” } // it works!
  25. Use code generation (like Sourcery) Please see this great talk

    for details:
 Elviro Rocca — Protocol-Oriented Monad Transformers https://www.youtube.com/watch?v=Zmb86zblcto
  26. Future< <== observable is async Validated< <== observable can return

    error Array< <== observable is sequence Repo >, Either<NetworkError, DeserializationError> > >

More Decks by Krzysztof Siejkowski

Other Decks in Technology