0

Context

I'm trying to understand the problematic that monades try to solve and I'm a bit confused while trying to compose a container and a promise.

Example

For the purpose of the exercise, I've modified the chain method of my monad to a then, so that I can compose promises with my custom container:

const assert = require("assert");
const R = require("ramda");

const makeMonad = value => ({
  get: () => value,
  map: transform => makeMonad(transform(value)),
  then: createMonad => makeMonad(createMonad(value).get())
});

const asyncAdd2 = x => Promise.resolve(x + 2);

const composeP = R.composeWith((f, last) => last.then(f));
const asyncResult = composeP([asyncAdd2, makeMonad])(1);

asyncResult.then(x => assert.equal(x, 3));
console.log("Passed");

In this example, I have an error thrown because the Promise API doesn't own a get function. In fact, I need this get function in my custom then function to allow composability.

So at the end of the application execution, depending on the order of my arguments in the composeWith call, I'm in a promise world or in a custom monad world.

Questions

  • I m now wondering if I have to lift everything (even promises?) while I'm working and trying to compose monades?

  • What is the impact if I'm working with 10 kind of different monades? I mean, depending on the order, I'll probably change the world I'm working on no?

  • Is this something common to create monades creators? I mean creating the monade definition like the makeMonad I've written

Thanks for your help, I hope I've been clear ^^'

mfrachet
  • 8,772
  • 17
  • 55
  • 110
  • 3
    A ES6 Promise is not a monad, even though it is monad-like. Treating it like a monad will cause you lots of trouble. –  Feb 16 '19 at 14:14
  • 2
    Unfortunately, monads of different type don't compose. So you're right, you have to lift the base monad computation into the combined monad by using a monad transformer type. Monad transformers are an advanced topic and hard to understand. Here is a simplified [implementation](https://stackoverflow.com/q/39598029/10675354) –  Feb 16 '19 at 14:28
  • 1
    And btw, saying _the problematic that monades try to solve_ sounds odd. Monads rather add features to a language. From an imperative perspective they are [programmable semicolons](https://zacharyvoase.com/2014/04/30/monads/) –  Feb 16 '19 at 14:32
  • 1
    The only problem that `Monad` solves is providing a common abstraction to different types that behave in a similar specific way. Each individual type has its own semantics and usage. Your `makeMonad` doesn't "make a monad", but instead it is just an implementation of the `Identity` monad (which provides a pretty useless wrapper that does nothing) and should be named accordingly. – Bergi Feb 16 '19 at 14:40
  • Thanks for your explanations. I can see where I'm making mistakes. I will continue my studies in these directions. – mfrachet Feb 16 '19 at 16:11

1 Answers1

0

I'm trying to understand the problematic that monades try to solve

You might find helpful to look at the examples and references in this repo https://github.com/dmitriz/functional-examples

I'm a bit confused while trying to compose a container and a promise.

I presume you are aware that the Promise is not a Monad.

For the purpose of the exercise, I've modified the chain method of my monad to a then, so that I can compose promises with my custom container

As then method breaks Monadic law, it is not suitable for safe composition. Basically every time you compose with function, you have to carefully inspect all possible return values for being promises or not and ensure the composition is always unwrapped correctly. Which basically defeats the point of Monad that allows you to safely compose without spending time on the details inspection. If leveraging Monads is your goal, you might prefer to switch from then to chain instead.

A great library bringing FP safety to Promises is Creed


  • I m now wondering if I have to lift everything (even promises?) while I'm working and trying to compose monades?

You might want to wrap all promises in creed and then use the provided monadic methods the usual way.

  • What is the impact if I'm working with 10 kind of different monades? I mean, depending on the order, I'll probably change the world I'm working on no?

Monadic laws are for a fixed monad, that is they only hold for a fixed implementation of the of and chain methods. Also chain expect the correct signature:

chain :: Monad m => m a ~> (a -> m b) -> m b

Decrypted: If m is a fixed Monad, a and b are any types, the chain method requires its argument to be a function of type a → m b, with m being the same monad.

In other words

monad.chain(a => makeMonad(a))

assumes the function makeMonad to make the same monad. Otherwise you need to wrap the result into the correct monad whenever chain is used.

To make it work safely with promises, always wrap them into Creed to guarantee the same monad is used, then only use map and chain whenever composing and relying on the monadic laws. Or use then at your risk and worry about details :)

  • Is this something common to create monades creators? I mean creating the monade definition like the makeMonad I've written

This will only work when you have actual value to begin with, see e.g. here: https://github.com/dmitriz/functional-examples/blob/master/examples/16-monad.js

But one of the Monad's power is to deal with values that you cannot (or don't want to) access directly, such as IO Monad. In those cases you can only use provided operators to may or may not be able to create a Monad. Which illustrate the problem with native JS promises - you cannot make them into a Monad in any way without changing their values. A native JS promise will always try to unrap a "theneable", which therefore cannot be stored as value, making any composition generally unsafe.

Dmitri Zaitsev
  • 13,548
  • 11
  • 76
  • 110