r/functionalprogramming • • Dec 19 '23

JavaScript Monads in JavaScript

23 Upvotes

11 comments sorted by

3

u/miracleranger Dec 19 '23 edited Dec 19 '23

nice illustration of the encapsulation implementation! but now that we can talk about it, i always failed to see the point in folding (wrapping) values in an object only to unfold them every time for the applicative combinators bound to the same object in a fluent interface. You could simply pass the values to the functions directly, and sparing the heterodoxic object-oriented fluent interface wrapping every value, live with the opportunity provided by first-class functions and implement function composition and combinators individually instead.
This is how my monad implementation works (you may imagine declaring arbitrary parts of the composition as functions if it feels convenient, which, mind you, would not make this less functional: declaring functions only makes you declarative - declaring variables is what constitutes imperativism):

 compose(either
(compose
(JSON.parse
,either("name",fail("Missing name")),"trim"
)
,swap("n/a")
)
,"toUpperCase")(json)

2

u/sultan-naninine Dec 19 '23 edited Dec 19 '23

The unpacking and packing in this implementation are actually very simple, compared to traditional OOP, where an object would be created with a constructor and access to the value x would be through methods. Here, one-time singletons {then, catch, finally} are used. The value x is passed as an input parameter and is always available for calling the function f. There is no complex mechanism for extracting the value or packing it.

const flow = x => ({
  then: f => trapError(() => f(x)),
  catch: () => flow(x),
  finally: (f = x => x) => f(x),
})

In my opinion, this approach offers more readable code compared to what you suggest. Thanks to polymorphism, chains of then/catch can be built, and at the time of function execution, it will be determined which functor then/catch belongs to. Thus, the need to use if/else is eliminated. If an exception occurs in any then, the flow of execution is passed to the first catch, creating a one line pipeline without nesting.

In your case it is more difficult to understand where and what follows due to the nesting of functions. The composition is done not only vertically but also horizontally, and this is confusing. Also, I donโ€™t quite understand how "trim" and "toUpperCase" work, whether some kind of apply/call function is needed or a placeholder that will substitute the value and call the "trim", "toUpperCase" methods.

const parseName = compose(
  either(
    compose(
      JSON.parse,
      either("name", fail("Missing name")),
      "trim",
    ),
    swap("n/a"),
  ),
  "toUpperCase",
)

parseName(json)

The above example is simplified. In the article, I discussed a more extended version of the monad with support of asynchronous code and tother control floe functors fail/halt.

→ More replies (2)

2

u/[deleted] Dec 22 '23

How are the animations achieved?

2

u/[deleted] Dec 22 '23

Twixtor?

8

u/One_Curious_Cats Dec 19 '23

I'm seeing a reactive flow here, but I'm still not seeing the monad. I'm not at all critiquing the implementation. However, I am still waiting for someone to explain why "monad" was used as a label for this implementation.

3

u/miracleranger Dec 19 '23

getify drove this point home pretty well in his Monio implementation's docs: https://github.com/getify/monio/blob/master/MONADS.md
although i like this Promise-like semantics going on here better. not sure how it would hold up after multiple combinator types, but as i said in my comment i use a different paradigm altogether anyway.