Slide 1

Slide 1 text

Container types without ! Krzysztof Siejkowski / @_siejkowski

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Questions are fine Seriously They're fine

Slide 4

Slide 4 text

Types

Slide 5

Slide 5 text

Types?

Slide 6

Slide 6 text

Type

Slide 7

Slide 7 text

Optional

Slide 8

Slide 8 text

Array

Slide 9

Slide 9 text

Result

Slide 10

Slide 10 text

Result !

Slide 11

Slide 11 text

Result !

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Deferred

Slide 14

Slide 14 text

Writer

Slide 15

Slide 15 text

Observable

Slide 16

Slide 16 text

Types? !

Slide 17

Slide 17 text

Types!

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Types ❤

Slide 21

Slide 21 text

Types !

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Deferred>

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

3 solutions

Slide 35

Slide 35 text

#1 Redesign your data model

Slide 36

Slide 36 text

Errors are part of your data model

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

#2 Discard information

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

#3 Provide transformers

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Generic ! type constraints

Slide 52

Slide 52 text

// 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 <...>

Slide 53

Slide 53 text

// 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'

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

2 ways of writing transformers

Slide 56

Slide 56 text

Rasure (protocol-based)

Slide 57

Slide 57 text

Rasure == Inversion of type erasure

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

OptionalType, ResultType, ArrayType, DeferredType ...

Slide 63

Slide 63 text

Constrained (method-based)

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Does it really prevent ! ?

Slide 68

Slide 68 text

Too many transformers? 4 solutions

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

3. Use wide types Observable == Future>

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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)

Slide 74

Slide 74 text

May the types be with you!

Slide 75

Slide 75 text

Thanks Questions?