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 ;)
}
}
andThen
someOptional
.andThen(maybeIncrement)
.andThen(maybeIncrement)
// ... you get the idea :)
In this case result of this chaining is a instance of Int?. If the someOptional was nil then whole computation results with nil. If it had some value (42) ten 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.
If someOptional started with 10 and we had luck (returningNone did not returned nil) then the final result is 12. But if were 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 an more friendly name defaultSome like so:
someOptional
// if someOptional is nil then start computation with default 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.
andThenTry
This operator expects an transformation that may throw an error. When this happens it returns .none which alows to recover with other operators.
let jsonData: Data? = ...
jsonData
.andThenTry{ data in
try JSONDecoder().decode(CodableStruct.self, from: data)
}
// this can also explode!
.andThenTry( functionTakingCodbaleStructAndThrowing )
// if any did thow an error then just recover with this one
.defaultSome( CodableStruct.validInstance )
You can revocer differently after different tries. Or you can totaly ignore it. Either way you have a nice API.
But wait there’s more!
Sometimes you are working with a Optional collection. Most common case is a String and and Optional Array of something. This Optional API has you covered to!
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 a Optional context. Those properties should help.
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]? :)
There are cases when you need an actual result from an Optional or a default non optional value. This is exactly the case for or
let noneInt: Int? = .none
let someInt: Int? = .some(42)
var result: Int = someInt.or(69) // 42
In this case result variable stores value 42. It’s an honest Int not an optional. But what happens when it’s none:
result = noneInt.or(69) // 69
Here the final result is 69 as everything evaluates to none. Once again after or you have a honest value or some default.
default value with or
If the wrapped type has a empty initializer (init that takes no arguments) you can call it to get an instance:
someOptional
.or(.init()) // creates an instance
To put it in a context if you have some optionals you can use this to get zero value like so:
let noneInt: Int? = nil
noneInt.or( .init() ) // 0
noneInt.or( .zero ) // 0
let noneDouble: Double? = nil
noneDouble.or( .init() ) // 0
let defaults: UserDefaults? = nil
defaults.or( .standard ) // custom or "standard"
let view: UIView? = nil
view.or( .init() )
// or any other init ;)
view.or( .init(frame: .zero) )
// Collections
let noneIntArray : [Int]? = .none
noneIntArray.or( .init() ) // []
let emptySomeString: String? = ""
noneString.or( .init() ) // ""
// Enums
enum Either {
case left, right
}
let noneEither: Either? = nil
noneEither.or(.right)
Anything that you can call on this type (static methods) can be used here.
cast
Have you ever wrote code similar to this one:
if let customVC = mysteryVC as? CustomVC {
// do stuff
}
With cast you can streamline your code to this:
let someViewController: UIViewController? = ...
someViewController
.cast( CustomVC.self )
.andThen({ (vc: CustomVC) in
// work with a non optional instance of CustomVC
})
If the type can be inferred from the context then you do not have to type it in.
let anyString: Any? = ...
let result: String? = anyString.cast()
As you can see compiler is able to inferred the correct type. But be aware that in more complex cases this can slow down your compilation.
If you want to have faster compilation then always be explicit about your types. In all of your code not only using this package.
encode & decode
One of the common places when you want to encode or decode something is when you have some data from the network. Flow might look something like this:
make a API call for a resource
get JSON data
To keep is simple let’s say our Data Transfer Model (DTO) looks like this:
struct CodableStruct: Codable, Equatable {
let number: Int
let message: String
}
What happens is that a JSON string is send thru the network as data. To simulate this in code one could write this:
To get the desired encoded vale just use the method:
codableStruct
.encode() // <- encoding part if you missed it ;)
.andThen({ instance in
// work with not optional instance
})
whenSome and whenNone
When working with optionals it happens that you want to run some code but not change the optional. This is where whenSome and whenNone can be used.
let life: Int? = 42
life
.whenSome { value in
print("Value of life is:", value)
}
This code prints to the console: Value of life is: 42.
whenSome also comes in a favor that does not need the argument.
let life: Int? = 42
life
.whenSome {
print("Life is a mistery. But I know it's there!")
}
This is a very nice way of triggering some logic without having to write if statements. But what about when the optional is none (or how it’s known nil)?
whenNone is here for the rescue.
let life: Int? = .none
life
.whenNone {
print("No life here!")
}
No life here! will be printed in the console.
But what’s eaven more cool is that you can chain them!
let life: Int? = 42
life
.whenSome { value in
print("Value of life is:", value)
}
.whenSome {
print("Life is a mistery. But I know it's there!")
}
.whenNone {
print("No life here!")
}
Depending on the operator and the value of optional different blocks will be called. And efcourse other operators can be thrown in to the mix.
filter
Sometimes you need a value only when it passes some predicate.
let arrayWithTwoElements: [Int]? = [42, 69]
arrayWithTwoElements
.filter { array in array.count > 1 }
.andThen { ... } // work with array
Use it to create a filter functions with a given predicate baked in.
Async/Await
With new API for handeling asynchronous you can write code that uses asynchronous functions.
// we are in asynchronous context
let someInt: Int? = 42
let result: Int? = await someInt
.asyncFlatMap {
try! await Task.sleep(nanoseconds: 42)
return $0 + 1
}
.flatMap { fromAsync in
fromAsync * 10
}
As you can see it’s easy to mix synchronous code with asynchronous. Just rember that await must be at the start of the pipeline. If you don’t then you will have a friendly reminder from the compiler.
tryAsyncMap & tryAsyncFlatMap
tryAsyncMap & tryAsyncFlatMap are methods that allow you to perform an asynchronous transformation on an optional value in Swift. They take a closure that performs an asynchronous operation on the optional value, and return an optional value of a different type.
Usage
Here’s an example of how to use tryAsyncMap:
enum MyError: Error {
case invalidInput
}
func doAsyncTransformation(value: Int?) async throws -> String {
guard let value = value else {
throw MyError.invalidInput
}
await Task.sleep(1_000_000_000) // Simulate long-running task.
return "Transformed value: \(value)"
}
let optionalValue: Int? = 42
do {
let transformedValue = try await optionalValue.tryAsyncMap { value in
try doAsyncTransformation(value: value)
}
print(transformedValue) // Prints "Transformed value: 42".
} catch {
print(error)
}
zip – moved
This functionality was moved to Zippy 🤐 Swift Package. It has definitions for zip functions for more types than just optionals.
OptionalAPI
Optional extensions for Swift Optional Monad… use it or not… it’s optional.
Why
Some common idioms popup when working with Optionals in Swift. Here is a bunch of useful extensions for some types.
Installation
Just copy and paste files to your project 🍝
Or use SPM 😎
Examples:
Running some code if none or some
Old:
New:
Sequencing of operations that might also return optional
Operation that returns optional:
Old the terrible way:
andThen
In this case result of this chaining is a instance of
Int?
. If thesomeOptional
was nil then whole computation results with nil. If it had some value (42) ten it would be incremented so many times.Recovering from
none
caseLet’s say you have a chain of operations and there is a chance that the result might return
none
.Final result is
nil
. And you can’t use a??
. UsemapNone
it’s like normalmap
on Optional but for thenil
case.If
someOptional
started with10
and we had luck (returningNone did not returned nil) then the final result is12
. But if were not so lucky then themapNone
would take over and the final result would be43
.You can also use more than one
mapNone
to handle any failures along the way. Oh and you can use an more friendly namedefaultSome
like so:I hope you can see that this gives you a very flexible API to handle Optionals in your code.
andThenTry
This operator expects an transformation that may throw an error. When this happens it returns
.none
which alows to recover with other operators.You can revocer differently after different tries. Or you can totaly ignore it. Either way you have a nice API.
But wait there’s more!
Sometimes you are working with a Optional collection. Most common case is a
String
and and Optional Array of something. This Optional API has you covered to!In the examples below I will be using those Optionals:
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 a Optional context. Those properties should help.
hasElements
isNoneOrEmpty
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]?
:)If you need a default value for the none case then defaultSome is the thing you want.
or
There are cases when you need an actual result from an Optional
or
a default non optional value. This is exactly the case foror
In this case
result
variable stores value42
. It’s an honest Int not an optional. But what happens when it’snone
:Here the final result is
69
as everything evaluates tonone
. Once again afteror
you have a honest value or some default.default value with
or
If the wrapped type has a empty initializer (init that takes no arguments) you can call it to get an instance:
To put it in a context if you have some optionals you can use this to get zero value like so:
Anything that you can call on this type (static methods) can be used here.
cast
Have you ever wrote code similar to this one:
With
cast
you can streamline your code to this:If the type can be inferred from the context then you do not have to type it in.
As you can see compiler is able to inferred the correct type. But be aware that in more complex cases this can slow down your compilation.
encode
&decode
One of the common places when you want to encode or decode something is when you have some data from the network. Flow might look something like this:
To keep is simple let’s say our Data Transfer Model (DTO) looks like this:
What happens is that a JSON string is send thru the network as data. To simulate this in code one could write this:
Stage is set:
decode
Networking code will hand us an instance of
Data?
that we want to decode.It’s that simple. Compiler can infer the type so there’s no need to add it explicitly. Buy you can do it in some longer pipelines eg.:
encode
Encode goes other way. You have a instance that you want to encode to send it as a json.
To get the desired encoded vale just use the method:
whenSome
andwhenNone
When working with optionals it happens that you want to run some code but not change the optional. This is where
whenSome
andwhenNone
can be used.This code prints to the console: Value of life is: 42.
whenSome
also comes in a favor that does not need the argument.This is a very nice way of triggering some logic without having to write
if
statements. But what about when the optional is none (or how it’s known nil)?whenNone
is here for the rescue.No life here! will be printed in the console.
But what’s eaven more cool is that you can chain them!
Depending on the operator and the value of optional different blocks will be called. And efcourse other operators can be thrown in to the mix.
filter
Sometimes you need a value only when it passes some predicate.
There is also a free version of this operator:
Use it to create a filter functions with a given predicate baked in.
Async/Await
With new API for handeling asynchronous you can write code that uses asynchronous functions.
As you can see it’s easy to mix synchronous code with asynchronous. Just rember that
await
must be at the start of the pipeline. If you don’t then you will have a friendly reminder from the compiler.tryAsyncMap
&tryAsyncFlatMap
tryAsyncMap
&tryAsyncFlatMap
are methods that allow you to perform an asynchronous transformation on an optional value in Swift. They take a closure that performs an asynchronous operation on the optional value, and return an optional value of a different type.Usage
Here’s an example of how to use
tryAsyncMap
:zip
– movedThis functionality was moved to Zippy 🤐 Swift Package. It has definitions for
zip
functions for more types than just optionals.🐇🕳 Rabbit Hole
This project is part of the 🐇🕳 Rabbit Hole Packages Collection
That’s it
Hope it will help you :)
Cheers! :D