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

43d2bef703ec7165166f161f137ac54f?s=128

Krzysztof Siejkowski

June 26, 2018
Tweet

Transcript

  1. Container types without ! Krzysztof Siejkowski / @_siejkowski

  2. None
  3. Questions are fine Seriously They're fine

  4. Types

  5. Types?

  6. Type<T>

  7. Optional<T>

  8. Array<T>

  9. Result<T, E: Error>

  10. Result<T,E: Error> !

  11. Result<T> !

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

  13. Deferred<T>

  14. Writer<T, A: Accumulative>

  15. Observable<T>

  16. Types? !

  17. Types!

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

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

  20. Types ❤

  21. Types !

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

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

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

  25. Deferred<Result<Data?, NetworkError>>

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

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

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

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

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

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

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

    NetworkError > > !
  33. Deferred<Result<Result<[Result<Result<String, ValidationError>, DeserializationError>], DeserializationError>?, NetworkError>> THIS IS MADNESS !

  34. 3 solutions

  35. #1 Redesign your data model

  36. Errors are part of your data model

  37. Deferred< Result< Result< [ Result< Result<String, ValidationError>, DeserializationError > ],

    DeserializationError >?, NetworkError > >
  38. Result<Result<String, ValidationError>, DeserializationError> enum RepoError: Error { // expressing both

    deserializaton and validation } Result<String, RepoError>
  39. Deferred< Result< Result< [ Result<String, RepoError> ], RepoError >?, NetworkError

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

    > >
  41. Result< Result< [Result<String, RepoError>], RepoError >?, NetworkError > enum NetworkAPIError:

    Error { // previous cases case lackOfData // the (nil, nil) case }
  42. Deferred< Result< Result< [ Result<String, RepoError> ], RepoError >, NetworkAPIError

    > >
  43. #2 Discard information

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

    > >
  45. [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>
  46. Deferred< Result< Result< Result<[String], RepoError>, RepoError >, NetworkAPIError > >

  47. Deferred< Result< Result<[String], RepoError>, NetworkAPIError > >

  48. #3 Provide transformers

  49. Transformers are APIs for working with nested containers They're providing

    functionality similar to Haskell types called Monad Transformers
  50. // 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) } } }
  51. Generic ! type constraints

  52. // 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 <...>
  53. // 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'
  54. // 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
  55. 2 ways of writing transformers

  56. Rasure (protocol-based)

  57. Rasure == Inversion of type erasure

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

    Constrain extensions with protocol
  59. 1. Create protocol protocol ResultType { associatedtype PT associatedtype PE:

    Error } extension Result: ResultType { typealias PT = T typealias PE = E }
  60. 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 {}
  61. 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! !
  62. OptionalType, ResultType, ArrayType, DeferredType ...

  63. Constrained (method-based)

  64. 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) } } }
  65. 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) } } } ! "
  66. 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 }
  67. Does it really prevent ! ?

  68. Too many transformers? 4 solutions

  69. 1. Stick to what you use You may then give

    them nice names like mapInternalResult
  70. 2. Generate them all Elviro Rocca Protocol-Oriented Monad Transformers https://www.youtube.com/watch?v=Zmb86zblcto

    Generation using Sourcery
  71. 3. Use wide types Observable<T> == Future<Result<[T]>>

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

  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)
  74. May the types be with you!

  75. Thanks Questions?