Ramda with FP Types

469 views Asked by At

Recently I have decided to switch from lodash to ramda to play with functional way of composing my logic. I love it! After some extensive digging into FP I have found that's it's not only about handy pure/point free utilities (ramda), but more about complex (at least for me) math abstractions (fantasy-land). I don't get all of it, but Either and Task pattern looks very handy. Problem is that I am not sure how to merge it with ramda utilities. I know about ramda-fantasy, but it's no longer maintained. Ramda-fantasy suggested libraries doesn't work the same way as ramda-fantasy. With all this new information about Monads/Monoids/Functors types I am completely lost.

For example, what the convention for this?

Right('boo')
  .map(x => x + '!')
  .map(x => x.toUpperCase())

vs 

R.pipe(
  R.map(x => x + '!')
  R.map(x => x.toUpperCase())
)(Right('boo'))

Am I don't need ramda If I will decide to switch to Monads all the way?

1

There are 1 answers

6
Scott Sauyet On BEST ANSWER

One way to think about it is to think about types versus functions.

Ramda offers a large collection of utility functions. They operate on arrays, on objects, on functions, on strings, on numbers, etc. But they also operate on user-defined types. So in your example, R.map operates on anything which matches the Functor specification. If the implementation of Either you use matches that specification, then Ramda's map will operate on it.

But Ramda does not supply types. It works with the built-in types such as Object, Array, Function, etc. But -- arguably outside Lens -- it does not supply any types of its own. Libraries such as Folktale provide large collections of types, such as Maybe, Result, Validation, Task and Future; more dedicated ones such as Fluture provide powerful versions of one specific type (Future). All of these types implement the Functor specification. A very incomplete list of such implementations is supplied by FantasyLand.

These two notions, functions on an abstract type and collections of types are complementary. A Ramda function which works on any functor will work on whatever version of Either you use (so long as it matches the specification.) More on this relationship is in this StackOverflow Q+A.

The question compared these two snippets:

Right('boo')
  .map(x => x + '!')
  .map(x => x.toUpperCase())

and

R.pipe(
  R.map(x => x + '!')
  R.map(x => x.toUpperCase())
)(Right('boo'))

But neither is how I would think of the problem from a Ramda perspective. Ramda is all about functions. It supplies functions and expects you to use them to build more sophisticated functions, and then to use those to build even higher level functions.

If we wrote this function:

const bigBang = pipe(
  map (x => x + '!'),
  map (x => x .toUpperCase ())
)

or perhaps this version

const bigBang = pipe (
  map (concat (__, '!')),
  map (toUpper)
)

Then this function is now available to use on many types. For example:

bigBang (['boo', 'scared', 'you'])     //=> ['BOO!', 'SCARED!', 'YOU!']
bigBang ({a: 'boo', b: 'ya'})          //=> {a: 'BOO!', b: 'YA!}
bigBang ((s) => s .repeat (2)) ('boo') //=> 'BOOBOO!'
bigBang (Right ('boo'))                //=> Right ('BOO!') 
bigBang (Left ('oops'))                //=> Left ('oops') 
bigBang (Just ('boo'))                 //=> Just ('BOO!') 
bigBang (Nothing())                    //=> Nothing ()
bigBang (Future ('boo'))               //=> Future ('BOO!')

The first three -- Array, Object, and Function implementations -- are supplied by Ramda. But the others still work since Ramda interoperates with the FantasyLand Functor specification. And it will work if you supply your own map method on your type (or even better a fantasy-land/map method.)

So no, you don't need Ramda to work with Monads or with other abstract types. You can work directly with their implementation. But Ramda offers some nice ways to interoperate with them in a generic manner.