DEV Community

loading...

Improving our functions with Bow

Apiumhub
Tech Hub from Barcelona specializing in software, web and app development.
Originally published at apiumhub.com on ・5 min read

**Bow is a library for Typed Functional Programming in Swift**

But first of all…

What is Functional Programming?

Functional Programming is a programming paradigm – a style of building the structure and elements of computer programs – that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. -Wikipedia

TL;DR: *Functional Programming is programming with functions.*

Functions must be:

-Total: There is an output for every input

– Deterministic: For a given input, the function always return the same output.

– Pure: The evaluation of the functions does not cause other effects besides computing the output.

But… All state is bad?

No, hidden, implicit state is bad.

Functional programming do not eliminate state, it just make it visible and explicit.

func add(x: Int, y: Int) -> Int {
    return x + y
}
Enter fullscreen mode Exit fullscreen mode

For example this functions is **total**, **deterministic** and **pure**. Always will return the same output for the same input, will return always an output and does not fire any side effects.


func divide(x: Int, y: Int) -> Int {
    guard y != 0 else { fatalError("Division by 0") }
    return x / y
}

Enter fullscreen mode Exit fullscreen mode

This function is **deterministic** and **pure** but not **total**. Is a **partial** functions because if the seconds argument is 0, theres is no way yo provide a valid output.


func dice(withSides sides: UInt32) -> UInt32 {
    return 1 + arc4random_uniform(sides)
}

Enter fullscreen mode Exit fullscreen mode

Two invocations with the same input will (most likely) yield different values, as the output is randomized. This means the function is **non-deterministic.**


func save(data: String) -> Bool {
    print("Saving data: \(data)")
    return true
}

Enter fullscreen mode Exit fullscreen mode

That functions is **total** and **deterministic** but is not pure because on every execution will do something more besides computing the output. It prints a message in that example. That makes this function **impure.**

What we can we do?

Working with impure functions

Real world software rarely has functions as nicely written as the **add** function above. We usually have to deal with partial, non-deterministic and/or impure functions, since software has to deal with errors and perform side effects in order to be useful. Besides presenting issues breaking referential transparency, function composition cannot be done in these circumstances. How can we deal with such situations? Bow provides numerous data types that can be used to model different effects in our functions. Using those data types, such as **Option**, **Either** or **IO** can help us transform partial, non-deterministic or impure functions into total, deterministic and pure ones. Once we have them, we can use combinators that will help us compose those functions nicely. We will use bow data types to transform some partial, impure and non-deterministic. **Working with the previous function:**


func divide(x: Int, y: Int) -> Int {
    guard y != 0 else { fatalError("Division by 0") }
    return x / y
}

Enter fullscreen mode Exit fullscreen mode

We will use a data type that bow provides. **Option**


func divideOption(x : Int, y : Int) -> Option {
    guard y != 0 else { return Option.none() }
    return Option.some(x / y)
}

Enter fullscreen mode Exit fullscreen mode

Now, divideOption is able to return a value for each possible input; i.e., it is a total function. Option is similar to Swift Optional. That means Option and Optional are isomorphic: there is a pair of functions (namely toOption and fromOption) that, when composed, their result is the identity function. Also we can rewrite the divide function using Either.


func divideEither(x : Int, y : Int) -> Either<DivideError, Int> {
    guard y != 0 else { return Either.left(.divisionByZero) }
    return Either.right(x / y)
}

Enter fullscreen mode Exit fullscreen mode

With Either allows us to be more explicit about the error in the return type, helping the caller to be prepared to deal with the possible outputs it may receive. Nonetheless, **the left type does not need to be an error.** That’s the main different between swift data type **Result vs Either.** With the Result type we have the method to migrate it to an either or options importing **BowResult.**


import BowResult

let result = Result<Int, DivideError>(value: 2)
let eitherFromResult = result.toEither()
let optionFromResult = result.toOption()

Enter fullscreen mode Exit fullscreen mode

Effects

So far we have seen some data types that Bow provides in order to help us convert our partial functions into total ones. However, in many cases we need to perform effects, which make our functions to be impure. For these cases, Bow provides the IO data type, in order to encapsulate effects.


func fetchSomething() -> (String) {
        //Server call
        return "{ \"MyData\ }"
}

Enter fullscreen mode Exit fullscreen mode

The IO type encapsulate an effect of type A, but does not execute it


func fetchSomething() -> IO {
        return IO.invoke {
                return "{ \"MyData\ }"
    }
}

Enter fullscreen mode Exit fullscreen mode

Then, we can use IO functions to transform the result across the layers of our application, and active the side effect with .unsafePerformIO when necessary.


fetchSomething()
    .map(jsonToOurModel)
    .unsafePerformIO()

Enter fullscreen mode Exit fullscreen mode

What we get: R**eferential transparency**

If a function is total, deterministic and pure, it has a property called **referential transparency**. Referential transparency generally means that we can always replace a function with its return value without an effect on the application’s behavior.

What we get: R**eferential transparency**


func multiply(x: Int, y: Int) -> Int {
    return x * y
}

let multi = multiply(x: 2, y: 2) * multiply(x: 2, y: 2)

//Produce the same result
let multiplyResult = multi * multi

Enter fullscreen mode Exit fullscreen mode

Memoization

Another consequence of having a function that is referentially transparent is that it can be memoized. Memoization is a technique used to cache already computed values, specially if they have a high cost to be computed. When a function is memoized, you can think of it as a lookup table where inputs and their corresponding outputs are saved. Subsequent invocations just retrieve the value from the table instead of actually computing it.

Example


func longRunningFunction(_ id: Int) -> Data {
    //Assuming a long running function
    return someData
}

let memoizedLongRunningFunc = memoize(longRunningFunction)

//Will compute for the first time that function and save the output 
let longRunning1 = memoizedLongRunningFunc(1)
//Will use the memoized result given 0 computation cost
let longRunning2 = memoizedLongRunningFunc(1)

Enter fullscreen mode Exit fullscreen mode

Function composition

At this point if we had a pure, deterministic and total functions is easy to compose them. As the functions are referentially transparent too we need some operation to combine them. The essential operation for functions is function composition. In bow we had the compose or <<< operators to receive two functions and provide a new function which behaves like applying both functions sequentially. In some cases, compose can be difficult to read right to left, or simply is not convenient to use. For those cases, Bow has utility functions that reverse the order of the arguments using andThen or >>>.

Example

We had some functions that take a String and return a String based on that String with some modifications.


func shout(_ argument: String) -> String {
    return argument.uppercased()
}

func exclaim(_ argument: String) -> String {
    return argument + "!!!"
}

func applefy(_ argument: String) -> String {
    return argument + "🍏"
}

func bananafy(_ argument: String) -> String {
    return argument + "🍌"
}

func kiwify(_ argument: String) -> String {
    return argument + "🥝"
}

Enter fullscreen mode Exit fullscreen mode

With bow’s function composition we can do it like that:


let shoutFruitComposed = shout
                      >>> exclaim
                      >>> applefy
                      >>> bananafy
                      >>> kiwify

print(shoutFruit("I love fruit"))

// I LOVE FRUIT!!!🍏🍌🥝

Enter fullscreen mode Exit fullscreen mode

Compare that latter operation with this version without composing:


func shoutFruit(_ argument: String) -> String {
                    // Hard to read
    return kiwify(bananafy(applefy(exclaim(shout(argument)))))
}

print(shoutFruitComposed("I love fruit"))

// I LOVE FRUIT!!!🍏🍌🥝

Enter fullscreen mode Exit fullscreen mode

Maybe you can thing that example is not useful at all in a daily basis application. So here is a more practical example using function composition from *pointfree.co’s* site!

Conclusions

Bow is still in early stage of development. It has been thoroughly tested but needs to be applied into some projects to evaluate its usability.

Some of the benefits of using Bow and Functional Programming are:

– Code easier to reason

– Maintainability

– Testability

– Reusability

This article only covers a small portion of all the power that Bow framework has.

The post Improving our functions with Bow appeared first on Apiumhub.

Discussion (0)