lastminute.com logo

Technology

How to live in a pure world

angelo_sciarra
angelo sciarra

Make side effects pure


Hello folks, here I am with a follow-up of the previous article A matter of purity where I talked about what is a pure function and why should you even care.

I can imagine some questions may have arisen while reading it:

  • how to make partial functions total?
  • how to make pure and referentially transparent functions that perform some sort of side effect (like IO)?

Let’s address these issues one at a time.

Make partial functions total

Just to get everybody on the same page let me recall what a partial function is:

given f: A -> B f is partial if it is not defined for all a in A.

How can one work on f to get an f' such that f' is total?

Well, there are 2 paths one can take:

  1. restrict f domain A to those elements where f is defined, i.e.

    f': A' -> B where A' is made of all a in A such that it exists a b in B where b = f(a)

    Thinking about f as a function we can write in Kotlin or Java this path seems a bit tortuous;

  2. augment B with a synthetic value that maps all a in A where f is not defined

    f': A -> B' where B’ is B union { epsilon } and for all a in A where f is not defined then epsilon = f'(a)

    This second approach is the one we can pursue in our code using data types like Option and Either because when we say that our function

    val f: (A) -> Option<B>

    what we are saying is exactly that whenever we cannot return Some(a) we will return None. (Same goes for Either, whenever we cannot return Right(a) we will return Left(error)).

So that should give an answer about totality.

Make side effects pure

Once we have made our functions total, have we made them pure?

What about functions performing IO?

Let’s see an example

fun readFileLines(path: String): List<String> =
	File(path).readLines()

We know that this function may throw an IOException so, from what we said above about making partial functions total, we could turn it into

fun readFileLines(path: String): Either<IOException, List<String>> =
	try {
		File(path).readLines().right()
	} catch (e: IOException) {
		e.left()
	}

Now the function is total (you can argue that returning IOException as the error type may not be the best design choice, but we’ll leave it like that for the time being).

But is it pure? Is it true that:

given the same path input it will always return the same Either value, be it a Right value or a Left one?

The answer is no because anytime we call this function we are interacting with the external world and we have no control or knowledge of what this world may look like at any time.

In the functional universe (well, to be precise, in the Haskell orbiting part) there is an abstraction meant exactly to describe this interaction. Let me introduce you the

IO (monad)!

Leaving aside the scary m-word, let’s focus on the IO abstraction.

The idea behind it is simple: given that the interaction with the external world is not predictable, we are going to work with a type that is designed like:

data class IO<A>(run: () -> A) {

	fun map<B>(f: (A) -> B): IO<B> = // omitted

	fun flatMap<B>(f: (A) -> IO<B>): IO<B> = // omitted

	[...]
}

Can you see it? All that matters is that () -> A.

Why?

Because it is as if you are saying: since I promised to give you an A, you can transform it via map, if your transformation is a pure function, or via flatMap, if your transformation has again some kind of interaction with the external world.

We have transformed the real, unpredictable interaction with the external world with a description of it, simply introducing a delay. What we gained by doing so is that this description is once again a value, and we can play around with values freely.

NOTE: Kotlin has a construct built in the language, suspended functions, that can be thought a bit as our delay function (() -> A). For that reason, the creators and maintainers of Arrow (the functional companion for Kotlin) have decided to redesign their implementation of the IO data type to exploit suspended functions (and coroutines). I promise I will make an article dedicated to it, as soon as I manage to fully understand it myself :D

Conclusion

Is it easy to live in a pure world?

That is not really for me to tell you, only you can judge if what you gain is worth the pain (if you want to call it so). Nonetheless, I think that expanding your horizons and knowing about what it means to live in a pure world will provide you with a new perspective on how to interact with the world that can be useful also if you choose to remain in an impure world.

I hope to have given you some explanations of concepts you may have heard of but never managed to wrap your head around.

Well folks, what else to add?

To infinity and beyond!

Originally posted on https://dev.to/eureka84/how-to-live-in-a-pure-world-5fbj


Read next

SwiftUI and the Text concatenations super powers

SwiftUI and the Text concatenations super powers

fabrizio_duroni
fabrizio duroni
marco_de_lucchi
marco de lucchi

Do you need a way to compose beautiful text with images and custom font like you are used with Attributed String. The Text component has everything we need to create some sort of 'attributed text' directly in SwiftUI. Let's go!!! [...]

A Monorepo Experiment: reuniting a JVM-based codebase

A Monorepo Experiment: reuniting a JVM-based codebase

luigi_noto
luigi noto

Continuing the Monorepo exploration series, we’ll see in action a real-life example of a monorepo for JVM-based languages, implemented with Maven, that runs in continuous integration. The experiment of reuniting a codebase of ~700K lines of code from many projects and shared libraries, into a single repository. [...]