MONAD 稱號 怎麼 拿

This article is inspired by (The Best Introduction to Monad)[https://blog.jcoglan.com/2011/03/05/translation-from-haskell-to-javascript-of-selected-portions-of-the-best-introduction-to-monads-ive-ever-read/#], but is adapted to the OO-context with Java.

Let's say we have:

1
2
3
4
5
6
7

double sin(double x) {
  return Math.sin(x);
}

double cube(double x) {
  return x*x*x;
}

We can easily chain the methods together:

1
2

sin(cube(5.0));
cube(sin(5.0));

But, what if we need to print something while doing this operation?

We can easily do:

1
2
3
4
5
6
7
8
9

double sin(double x) {
  System.out.println("called sin");
  return Math.sin(x);
}

double cube(double x) {
  System.out.println("called cubed");
  return x*x*x;
}

But that has side effects, so it violates the spirit of functional programming. We should concat the logs into a string (just like what you did in Lab 4). So we need a class that encapsulates the variable with its log.

1
2
3
4
5
6
7
8
9

class DoubleString {
  Double x;
  String log;

  DoubleString(double x, String log) {
    this.x = x;
    this.log = log;
  }
}

Now, we can write the methods as:

1
2
3
4
5
6
7

DoubleString sinAndLog(double x) {
  return new DoubleString(sin(x), "called sin");
}

DoubleString cubeAndLog(double x) {
  return new DoubleString(cube(x), "called cube");
}

In a way, we are writing methods that take in a value (

1
2
6) and add some context to it (the log). We wrap both the value and its context in a box (the
1
2
7).

But these new functions do not compose anymore. We cannot do

1
2
8

We need methods that takes in a

1
2
7 and return a
1
2
7

1
2
3
4
5
6
7

double sin(double x) {
  return Math.sin(x);
}

double cube(double x) {
  return x*x*x;
}
1

Great, now we can write the methods to compose them:

double sin(double x) {
  return Math.sin(x);
}

double cube(double x) {
  return x*x*x;
}
2

double sin(double x) {
  return Math.sin(x);
}

double cube(double x) {
  return x*x*x;
}
3

or in the OO-way

double sin(double x) {
  return Math.sin(x);
}

double cube(double x) {
  return x*x*x;
}
4

double sin(double x) {
  return Math.sin(x);
}

double cube(double x) {
  return x*x*x;
}
5

In the OO-way, we chain the methods together.

double sin(double x) {
  return Math.sin(x);
}

double cube(double x) {
  return x*x*x;
}
2

double sin(double x) {
  return Math.sin(x);
}

double cube(double x) {
  return x*x*x;
}
7

Making It A Monad

Now, here is where I jump to creating a monad. I do not want to convert all my methods that takes in

sin(cube(5.0));
cube(sin(5.0));
1 and returns
1
2
7 into something that takes in
1
2
7 and returns
1
2
7. Yet, I want to be able to compose them and chain them together. So I write a general method that allows that, and that is our
sin(cube(5.0));
cube(sin(5.0));
5 method:

double sin(double x) {
  return Math.sin(x);
}

double cube(double x) {
  return x*x*x;
}
8

double sin(double x) {
  return Math.sin(x);
}

double cube(double x) {
  return x*x*x;
}
9

We can now use

sin(cube(5.0));
cube(sin(5.0));
5 to chain different operations together.

1
2
0

1
2
1

Now

1
2
7 is a monad!

Going back to our "wrap a value in a box with context" explanation. If we have two such wrappers, how do we wrap twice? We have to (i) wrap it one time, (ii) unwrap to get the new value and new context, and wrap it again.

The two lines in

sin(cube(5.0));
cube(sin(5.0));
5 corresponds to (i) and (ii) respectively.

Line

sin(cube(5.0));
cube(sin(5.0));
9 wraps it once; The next line unwraps the value and the context (
1
2
3
4
5
6
7
8
9
0 and
1
2
3
4
5
6
7
8
9
1) and wraps it again (
1
2
3
4
5
6
7
8
9
2) with the next context (
1
2
3
4
5
6
7
8
9
3).

Here is our monad:

1
2
2

1
2
3

Making a Generic Monad that Logs

We can make

1
2
7 a generic class that logs what happen to a variable.

1
2
2

1
2
5

Functor

Can we do this with a functor? Note that a functor has a

1
2
3
4
5
6
7
8
9
5 method with type
1
2
3
4
5
6
7
8
9
6. A
1
2
3
4
5
6
7
8
9
5 method for
1
2
7 would looks like
1
2
3
4
5
6
7
8
9
9. So it cannot do what the monad does. A functor can only change the value inside the box, but it cannot rewrap it with an updated context.

Wait, What is a Monad Again?

I hope the example above helps explain what is a monad -- it is a value wrapped in a box with context, and it allows us to compose wrappers (wrap multuple times), operate on its value and update the context if needed.