Ludovik Lax

Greenhorn

Posts: 17

posted 1 year ago

- 1

Hi Pierre Yves,

Wikipedia states a monad "is a structure that represents computations defined as sequences of steps: a type with a monad structure defines what it means to chain operations, or nest functions of that type together. "

Does the book covers this concept in details with clarity and samples ? How monads can be designed and implemented in Java 8 ?

Many thanks

L.

Wikipedia states a monad "is a structure that represents computations defined as sequences of steps: a type with a monad structure defines what it means to chain operations, or nest functions of that type together. "

Does the book covers this concept in details with clarity and samples ? How monads can be designed and implemented in Java 8 ?

Many thanks

L.

Pierre-Yves Saumont

Author

Ranch Hand

Ranch Hand

Posts: 98

17

posted 1 year ago

Hi Ludovik,

The book has not a specific chapter about monads, although there are many monads built and used along the book. Up to now (I am now writing chapter 13).

One reason is that there is an old saying about monads having a kind of magic: as soon as you understand them, you loose the ability to explain them to others.

However, this could be a good place to test my ability to explain the concept.

Searching on internet, one may find many other definitions such as:

- "A monad is just a monoid in the category of endofunctors"

- "A monad is a computational context for some value"

- "A monad is a class with a unit and a flatmap method"

You may also find some more exotic definitions such as:

- "Monads are buritos"

- "Monads are elephants"

The first three definitio are valid... in some context. The first one is probably the most rigourous definition in the context of Category Theory, a branch of maths about which most programmers don't care.

The third definition is probably the most easily understandable by Java programmers. But of course, the names of the methods are unimportant. What matters are the rules these methods must respect.

The second definition is probably the most useful to understand monads. So, monads are computational contexts. Functional programming is programming with functions. Safe functional programming is programming with total functions. Functions that are not total are said to be partial. This means that they do not always have a value. And when they have no value, they are not happy and start doing awful things. And they stop being pure.

Take a function like f(x, y) = x * y. Is this a pure function? No one can say. It depends on the context. In some languages, it could throw an arithmetic overflow exception. So it would not be a total function since it would not be defined for all pairs (x, y). As a consequence, it would not be a pure function because throwing an exception is a side effect.

In the context of Java with integers, this function is pure. This means that whatever couple of integer you give to the function, it will always return a value, and always the same value for the same pair. So, you can trust the function. This does not mean the result will always be correct. In case of overflow, the result might not be what you intend, but this is another problem. There will always be a result (meaning the program will not hang in the wild) and this result will always be the same.

What about this function: g(x, y) = x / y

Considering Java and integers, which means a function taking a pair of integers as its argument and returning an integer, this is not a total function. It might have no result for some pairs of integers. If the second argument is 0, there is no result and the function throws an exception. This is because g is not a total function if considered as a function from (integer, integer) to integer.

There are two ways to make g a total function: change the domain, making it a function of (integer, non null integer), or change the codomain, making it a function of (integer, integer) to (integer | exception).

To implement the first option, we would have to create a new type:

To implement the second option, we would also have to create a new type:

Functional programmers prefer the second approach.

But wait, if we change function g to return

The solution is to create a computational context in which the functions can be safely executed. If you like metaphores, you can think about the context as a safe box.

So, what you need is:

- a safe box

- a way to put the parameter value inside the box

- a way to put the modified function inside the box so that it can be applied to the parameter value.

And that's it. The result will be a box containing the result of the function.

To take a simple example in Java, we have to slightly modify the requirements, because Java does not offer such a safe box. It offers three types of safe boxes, but not one suitable for this use case. So, we will modify the requirement by saying that in case of error, the result will not be throwing an exception but simply nothing. (Note that Java as a type for this: Void. However, instantiating this type is a bit tricky.)

The type of safe box we may use for returning a result or nothing is

The method to put a value in the box should have been named

The method allowing to apply the safe operation to the value inside the box is called

To make this function return either the first character or nothing, we must change it into:

To use these tools, we need to put a

You could do it with:

But this is not very efficient since the function will wrap the result in an

Note that if you were to use

you would obtain an

If you wonder what this

This is a method returning a function. It should have been declared as a property:

but this does not compile.

Lots of other use cases may be abstracted inside monads, although they are not necessary to make a type a monad. One of the most often used is

and in

(Thechnically, this is a left fold, but the difference is irrelevant for this example.)

As the

But we are using

But this is ugly. The less ugly implementation would probably be:

Anyway, folding an

Yes again, this is totally useless, but it becomes useful to understand it when studying other monads such as

Lots of other methods could have been added to

The book has not a specific chapter about monads, although there are many monads built and used along the book. Up to now (I am now writing chapter 13).

One reason is that there is an old saying about monads having a kind of magic: as soon as you understand them, you loose the ability to explain them to others.

However, this could be a good place to test my ability to explain the concept.

Searching on internet, one may find many other definitions such as:

- "A monad is just a monoid in the category of endofunctors"

- "A monad is a computational context for some value"

- "A monad is a class with a unit and a flatmap method"

You may also find some more exotic definitions such as:

- "Monads are buritos"

- "Monads are elephants"

The first three definitio are valid... in some context. The first one is probably the most rigourous definition in the context of Category Theory, a branch of maths about which most programmers don't care.

The third definition is probably the most easily understandable by Java programmers. But of course, the names of the methods are unimportant. What matters are the rules these methods must respect.

The second definition is probably the most useful to understand monads. So, monads are computational contexts. Functional programming is programming with functions. Safe functional programming is programming with total functions. Functions that are not total are said to be partial. This means that they do not always have a value. And when they have no value, they are not happy and start doing awful things. And they stop being pure.

Take a function like f(x, y) = x * y. Is this a pure function? No one can say. It depends on the context. In some languages, it could throw an arithmetic overflow exception. So it would not be a total function since it would not be defined for all pairs (x, y). As a consequence, it would not be a pure function because throwing an exception is a side effect.

In the context of Java with integers, this function is pure. This means that whatever couple of integer you give to the function, it will always return a value, and always the same value for the same pair. So, you can trust the function. This does not mean the result will always be correct. In case of overflow, the result might not be what you intend, but this is another problem. There will always be a result (meaning the program will not hang in the wild) and this result will always be the same.

What about this function: g(x, y) = x / y

Considering Java and integers, which means a function taking a pair of integers as its argument and returning an integer, this is not a total function. It might have no result for some pairs of integers. If the second argument is 0, there is no result and the function throws an exception. This is because g is not a total function if considered as a function from (integer, integer) to integer.

There are two ways to make g a total function: change the domain, making it a function of (integer, non null integer), or change the codomain, making it a function of (integer, integer) to (integer | exception).

To implement the first option, we would have to create a new type:

`NonNullInteger`. This is perfectly possible.To implement the second option, we would also have to create a new type:

`IntegerOrException`.Functional programmers prefer the second approach.

But wait, if we change function g to return

`IntegerOrException`, we can no longer compose it whit f. More precisely, f . g (x), or f(g(x)) if you prefer this notation, will no longer compile because the types no longer match.The solution is to create a computational context in which the functions can be safely executed. If you like metaphores, you can think about the context as a safe box.

So, what you need is:

- a safe box

- a way to put the parameter value inside the box

- a way to put the modified function inside the box so that it can be applied to the parameter value.

And that's it. The result will be a box containing the result of the function.

To take a simple example in Java, we have to slightly modify the requirements, because Java does not offer such a safe box. It offers three types of safe boxes, but not one suitable for this use case. So, we will modify the requirement by saying that in case of error, the result will not be throwing an exception but simply nothing. (Note that Java as a type for this: Void. However, instantiating this type is a bit tricky.)

The type of safe box we may use for returning a result or nothing is

`Optional`.The method to put a value in the box should have been named

`unit`, but it was named`of`. It does not change anything.The method allowing to apply the safe operation to the value inside the box is called

`flatMap`. Let's take the example of a simpler function taking a`String`and returning the first character. A "normal" method could be:To make this function return either the first character or nothing, we must change it into:

To use these tools, we need to put a

`String`in context:`unit`(`of`) and`flatMap`are all what is needed to make`Optional`a monad. There are however other use cases that are frequent enough to have been added in most monads implementations. For example, you might have to use a function returning a raw value, such as:You could do it with:

But this is not very efficient since the function will wrap the result in an

`Optional`, just for the`flatMap`method to unwrap it. So, there is a`map`method for this use case:`Optional<Integer> upperChar = character.map(ThisClass::toUpper);`Note that if you were to use

`map`with a function returning an`Optional`, such as:you would obtain an

`Optional<Optional<Character>>`. This can be changed into an`Optional<Character>`using a method called`flatten`, but this method is missing in`Optional`. However, as you can see, there is a strong relation between`unit`(`of`),`flatMap`,`map`and`flatten`.`flatten`can be implemented as:If you wonder what this

`identity`method is, you are realizing that we were just hit by a Java generic problem: the absence of parameterized properties. In Java, you can declare parameterized classes and methods, you can parameterize methods calls, but you cannot declare parameterized properties if the parameter is not already in scope. If you look at the implementation for`Function.identity()`, you will see that this is a trick:This is a method returning a function. It should have been declared as a property:

but this does not compile.

Lots of other use cases may be abstracted inside monads, although they are not necessary to make a type a monad. One of the most often used is

`fold`. This method is generally seen as specific to vectorial types, such as`List`or`Stream`, but it is not. If`Optional`was the parent class of two sub classes, say`None`and`Some`,`fold`could be implemented in`None<T>`as:and in

`Some<T>`as:(Thechnically, this is a left fold, but the difference is irrelevant for this example.)

As the

`Optional`class is final, we can't add this method. We can write an external implementation:But we are using

`Optional.get()`, which is awfully bad. (It is forbidden to access the value from outside of the context, which means this method should not be public.) There is no smart solution to this problem. We know that the`get`method will never be called if the value is not present, so we could write:But this is ugly. The less ugly implementation would probably be:

Anyway, folding an

`Optional`is useless, except to understand that the`orElse`method is in fact a fold, and could be defined as:Yes again, this is totally useless, but it becomes useful to understand it when studying other monads such as

`Stream`(or`List`, but not the`java.util.List`)Lots of other methods could have been added to

`Optional`and are missing, and you can't add them since`Optional`is final. This is a good reason to develop a totally new`Option`monad. But at the same time,`Optional`is almost useless because it can't carry the reason for the absence of data. This is why an other monad is needed. In the book, I called it`Result`. It roughly corresponds to the Scala`Try`class.