How to use Ramda / JS correctly to compose functions

I am trying to take a functional approach to solve a specific problem as part of an exercise to learn Ramda.js.

So, I have this test:

it.only("map short name to long name POINTFREE", () => {
  let options = [ { long: "perky", short: "p" }, { long: "turky", short: "t" } ];

  let lookupByShortName = R.find(R.propEq("short", "t"));
  let result = lookupByShortName(options);
  expect(result).to.have.property("long", "turky");
});

      

"options" is used as a search sequence. I need to convert a series of strings designated as a single char to be the longer equivalent of the name, referencing a sequence of options. Therefore, the "t" character must be converted to "turky" as specified in the parameters.

However, this is not quite structured in the way I need it to be useful. The lookupByShortName function is not generic, it is hard-coded with the value "t". I want to omit the "t" parameter, so that when you call lookupByShortName because it needs to be drawn (by R.find), it needs to return a function requiring the missing argument. Therefore, if I do this, the test fails:

let lookupByShortName = R.find(R.propEq("short"));

      

so here lookupByShortName should become a function that requires one missing parameter, so in theory I should be able to call this function like this:

lookupByShortName("t")

      

or more specifically ("t" is added at the end):

let lookupByShortName = R.find(R.propEq("short"))("t");

      

... but I'm wrong because it doesn't work, the test fails:

1) Shorten short name arg for long name POINTFREE name: TypeError: lookupByShortName is not a function in Context.it.only (test / validator.spec.js: 744: 20)

So, I thought of another solution (which doesn't work, but I don't understand why):

Since "t" is the second parameter that is passed to R.propEq, use the R .__ placeholder and then pass "t" at the end:

let lookupByShortName = R.find(R.propEq("short", R.__))("t");

      

I've been working on a series of blog posts and although my understanding is better, I'm still not there yet.

Can you shed some light on where I am going wrong, thanks.

+3


source to share


1 answer


The first question is why is your code not working.

The easiest way to explain this is with function signatures.

Let's start with propEq

:

propEq :: String -> a -> Object -> Boolean

      

This is how it would be in a language like Hackell. propEq

is a function that takes a string and returns a function that takes something of an arbitrary type and returns a function that takes an object and returns a boolean. You could write this more explicitly like

propEq :: String -> (a -> (Object -> Boolean))

      

You would call it syntax something like this:

propEq('short')('t')({ long: "perky", short: "p" }); //=> false

      

Ramda has a slightly different idea, namely that you don't need to transmit these one at a time. Thus, there are several equally valid ways to call a Ramda function:

propEq :: String -> (a -> (Object -> Boolean))
          String -> ((a, Object) -> Boolean)
          (String, a) -> (Object -> Boolean)
          (String, a, Object) -> Boolean

      

which would accordingly mean to call it like this:

propEq('short')('t')({ long: "perky", short: "p" }); //=> false
propEq('short')('t', { long: "perky", short: "p" }); //=> false
propEq('short', 't')({ long: "perky", short: "p" }); //=> false
propEq('short', 't', { long: "perky", short: "p" }); //=> false

      

Next we have find

, which looks like this:

find :: (a -> Boolean) -> [a] -> a 

      



which for the same reasons means one of them in Ramda:

find :: (a -> Boolean) -> ([a] -> a)
     :: ((a -> Boolean), [a]) -> a

      

When you call

find(propEq('short'))

      

you are trying to pass a -> Object -> Boolean

as the first argument you find

want as your first argument a -> Boolean

. While Javascript is not strongly typed and Ramda doesn't try to help much with strong typing, you have a type mismatch. You are actually already sunk, although Ramda will accept your function as if it would work and return a function of type [a] -> a

. But this function will not work properly, because that makes find

it pass each a

in [a]

our propEq('short')

, until one of them returns true

. This will never happen, since the signature propEq('short')

is equal a -> Object -> Boolean

, so when we pass a

, we don't get a boolean expression, but instead a function from Object

to Boolean

.

This is a type mismatch, which is why your current approach doesn't work.


The second question is how to make it work.

The simplest approach is to use something like this:

let lookupByShortName = (abbrv, options) => R.find(R.propEq("short", abbrv), options);
lookupByShortName('t', options); //=> {"long": "turky", "short": "t"}

      

This is clean, clean code. I would probably leave it that way. But if you really want it to be unlimited, Ramda offers useWith

for situations like this. You can use it like this:

let lookupByShortName = R.useWith(R.find, [R.propEq('short'), R.identity]);

      

This can be thought of as a (curried) function of two parameters. The first parameter is passed to propEq('short')

, returning a new type function (a -> Boolean)

, and the second parameter is passed to identity

, which does no conversions, just passing the values ​​unchanged. Then these two results are passed tofind

useWith

and similar ones are converge

very specific to Ramda. If you don't want the free version (for example, as an educational exercise), the first version of this option is probably preferred.

+3


source







All Articles