OptionalAPI przyjazne API dla typu Optional w Swift

  • by

Photo by Gary Butterfield on Unsplash

Zaczęło się niewinnie od komentarza w PR w pracy. Potem pokazałem to znajomemu a tem powiedział, że powinienem zrobić z tego pakiet do Swifta. Nie jest to wymyślna biblioteka ratująca świat. Raczej coś co wyrywa ten drażniący włosek w nosie 😜

A prawie zapomniałem o najważniejszym. Całe to API jest opcjonalne, więc można ale nie trzeba go używać 🤓

OptionalAPI Swift Package

Repozytorium jest tu, wystarczy kliknąć w ten długi link.

Instalacja

Można skopiować plik lub zawartość pliku OptionalAPI.swift do swojego projektu 🍝 Lub używając Xcode dodając zależność wyszukać OptionalAPI 😎 I oczywiście jest też opcja z dodaniem zależności do własnego pakietu. Wystarczy w odpowiedniej sekcji dopisać linijkę:

.package(path: "https://github.com/sloik/OptionalAPI.git")

Przykłady

Wybranie ścieżki w zależności czy none czy some

Stare:

someOptional == nil ? True branch : False branch

Nowe:

someOptional.isSome ? True branch : False branch
someOptional.isNone ? True branch : False branch

someOptional.isNotSome ? True branch : False branch
someOptional.isNotNone ? True branch : False branch

“Kolejkowanie” operacji, które również mogą zwrócić none

Przykład funkcji, która może zwrócić none:

func maybeIncrement(_ i: Int) -> Int? { i + 1 }

Stary sposób:

if let trueInt = someIntOptional {
    let incrementedOnce = maybeIncrement(trueInt) {
        // you get the idea ;)
    }
}

Nowy:

someOptional
    .andThen(maybeIncrement)
    .andThen(maybeIncrement)
    // ... you get the idea :)

W tym przykładzie na końcu uzyskamy instancje typu Int?. Gdyby someOptional był nil to żadne obliczenia nie zostały by wykonane i zwrócony zostałby nil. Gdyby natomiast w tym Optional-u była wartość (42) to została by powiększona.

Reagowanie na none

Powiedzmy, że w tym całym łańcuszku operacji jest jakaś, która może zwrócić none.

func returningNone(_ i: Int) -> Int? { Bool.random() ? .none : i }

someOptional
    .andThen(maybeIncrement)
    .andThen(returningNone)  // <-- returns nil
    .andThen(maybeIncrement)  

Powiedzmy nie mieliśmy szczęścia i funkcja zwróciła none. To sprawia, że wartość dla tego całego wyrażenia jest nil nil. Użycie ?? w tym miejscu jest jakieś koślawe. To co chcemy osiągnąć to obsłużyć ten przypadek i dać jakąś inna wartość.

Do tego celu można skorzystać z mapNone. Działa jak normalna map na Optional-u ale jest uruchomiona tylko w przypadku gdy jest nil.

func returningNone(_ i: Int) -> Int? { .none }

someOptional
    .andThen(maybeIncrement)
    .andThen(returningNone)  
    .mapNone(42)
    .andThen(maybeIncrement)  

Gdyby someOptional miał wartość 10 i szczęście by sprzyjało (returningNone zwróciła by nie nil)to ostatecznym wynikiem byłoby 12. Gdyby jednak bogowie nie byliby tak łaskawi to mapNone przejęła by stery i wstrzyknęła swoją wartość. Co ostatecznie dałoby 43.

Można wykorzystać więcej jak jedno wywołanie do mapNone aby obsłużyć nil-e napotykane po drodze. Jest też użyteczny alias defaultSome z którego można skorzystać tak:

someOptional
    // if someOptional is nil then start computation with defaultSome value
    .defaultSome(5)     
    // increment whatever is there         
    .andThen(maybeIncrement)
    // are you feeling lucky?
    .andThen(returningNone)  
    // cover your ass if you had bad luck
    .defaultSome(42)
    // do some work with what's there
    .andThen(maybeIncrement) 
    // what... again
    .andThen(returningNone)  
    // saved
    .defaultSome(10)

Mam nadzieję, że widać jasno jak bardzo takie API ułatwia pracę z Optional-ami w Swift.

Ale zaczekaj! Jest więcej!

Czasami zdarza się tak, że wewnątrz Optional-a znajduje się jakaś kolekcja. Najprawdopodobniej będzie to jakiś String lub jakaś tablica. OptionalAPI może pomóc również i w tym przypadku.

W przykładach niżej będę używać takich stałych:

let noneString     : String? = .none
let emptySomeString: String? = ""
let someSomeString : String? = "some string"

let noneIntArray : [Int]? = .none
let emptyIntArray: [Int]? = []
let someIntArray : [Int]? = [11, 22, 33]

Myślę, że to powinno pokryć wszystkie wartości z jakimi można się spotkać w przyrodzie.

Opcjonalna kolekcja ma wartości, jest pusta lub optional jest nil-em

Pracując z kolekcją wewnątrz kontekstu Optional-a możemy skończyć z dużą ilością ifów. Poniższe API pozwalają nieco ten kod uprościć i czytelniej przekazać intencję autora.

hasElements

noneString.hasElements      // false
emptySomeString.hasElements // false
someSomeString.hasElements  // true

noneIntArray.hasElements  // false
emptyIntArray.hasElements // false
someIntArray.hasElements  // true

isNoneOrEmpty

noneString.isNoneOrEmpty      // true
emptySomeString.isNoneOrEmpty // true
someSomeString.isNoneOrEmpty  // false

noneIntArray.isNoneOrEmpty  // true
emptyIntArray.isNoneOrEmpty // true
someIntArray.isNoneOrEmpty  // false

recoverFromEmpty

Ta metoda jest wołana tylko gdy kolekcja jest pusta. To znaczy, że jak optional jest nil lub kolekcja posiada jakieś wartości to ta metoda nie będzie wywołana. Ponieważ String jest kolekcją to dalsze przykłady zaprezentuję tylko dla [Int]?.

noneIntArray.recoverFromEmpty([42])  // nil
emptyIntArray.recoverFromEmpty([42]) // [42]
someIntArray.recoverFromEmpty([42])  // [11, 22, 33]

Jeżeli przypadek dla nil i dla pustej kolekcji można lub trzeba obsłużyć tak samo to do tego celu jest metoda defaultSome:

noneIntArray.defaultSome([42])  // [42]
emptyIntArray.defaultSome([42]) // [42]
someIntArray.defaultSome([42])  // [11, 22, 33]

To tyle

Mam nadzieję, że ten prosty pakiecik Ci się przyda 😎 Zachęcam do rzucenia okiem do repozytorium i przejrzenie kilku testów. Powinny one dać jakiś pogląd jakiego kodu już nie trzeba więcej pisać.

Zdrówko 🍸

Tagi:

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *