Optional API for Swift Optional

  • by

Photo by Gary Butterfield on Unsplash

This has started as a code review comment at work and a comment from my colleague that I should make a package out of it. This does not save the world is fancy or clever. But it’s something that removes some annoying thing that I had wrote in code many times.

I almost forgot the most important part. It’s optional, so you do not have to use it ๐Ÿค“

OptionalAPI Swift Package

Link to the repository is there.

Installation

Just copy and paste contents of OptionalAPI.swift file ๐Ÿ Or use SPM search in Xcode ๐Ÿ˜Žor add to the dependencies:

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

Examples

Running some code if none or some

Old:

someOptional == nil ? True branch : False branch

New:

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

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

Sequencing of operations that might also return optional

Operation that returns optional:

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

Old the terrible way:

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

New:

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

In this case result of this chaining is an instance of Int?. If the someOptional was nil then whole computation results with nil. If it had some value (42) then it would be incremented so many times.

Recovering from none case

Let’s say you have a chain of operations and there is a chance that the result might return none.

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

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

Final result is nil. And you can’t use a ??. Use mapNone it’s like normal map on Optional but for the nil case.

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

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

If someOptional started with 10 and we had luck (returningNone did not return nil) then the final result is 12. But if we’re not so lucky then the mapNone would take over and the final result would be 43.

You can also use more than one mapNone to handle any failures along the way. Oh, and you can use a more friendly name defaultSome like so:

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)

I hope you can see that this gives you a very flexible API to handle Optionals in your code.

But wait there’s more!

Sometimes you are working with an Optional collection. The most common case is a String and Optional Array of something. This Optional API has you covered too!

In the examples below I will be using those Optionals:

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]

I  think this should cover all the cases.

Optional collection has values, is nil or empty

A lot of ifology is made when working whit a collection inside an Optional context. Those properties should help.

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

This is called only if the underlying collection is empty. That is if your optional is nil or has some value this will not be called. As String is a collection I will only show examples for [Int]? ๐Ÿ™‚

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

If you need a defaultSome value for the none and empty case then defaultSome is the thing you want.

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

That’s it

I hope it will help you ๐Ÿ˜Ž Checkout the repository README and tests to get a better understanding of what you do not have to type anymore.

Cheers ๐Ÿธ

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *