3

I am trying to use a functional approach to solve a particular issue as part of an exercise in learning 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 lookup sequence. I need to convert a series of strings specfied as a single char, to be the longer name equivalent by referring to the options sequence. So the character "t" should be converted to "turky" as defined in options.

However, this isn't quite structured the way I need to it to be to be useful. The function 'lookupByShortName' is not general, it is hard coded with the value "t". What I want is to omit the "t" parameter, so that when you call lookupByShortName, because it should be curried (by R.find), it should return a function that requires the missing argument. So if I do this, the test fails:

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

so here, lookupByShortName should become a function that requires a single missing parameter, so theoretically, I think I should be able to invoke this function as follows:

lookupByShortName("t")

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

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

... but I am mistaken, because this does not work, the test fails:

1) Map short arg name to long option name map short name to long name POINTFREE: TypeError: lookupByShortName is not a function at Context.it.only (test/validator.spec.js:744:20)

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

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

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

I have worked through a series of articles on a blog, and although my understanding is better, I'm still not there yet.

Can you shed any light as to where I'm going wrong, thanks.

1 Answer 1

3

The first question is why your code doesn't work.

The easiest way to explain this is with function signatures.

We start with propEq:

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

This is how it would be in a language like Hakell. 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 it more explicitly like

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

You would call this with syntax something like:

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

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

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

which, respectively, would mean calling 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 similar reasons mean one of these 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 to find, which wants as its first argument a -> Boolean. Although Javascript is not strongly typed, and Ramda does not try to offer much help with strong typing, you have a type mismatch. You're in fact already sunk, although Ramda will accept your function as though it would work, and return a function of the type [a] -> a. But this function won't work properly, because what find is doing is to pass each a in [a] into our propEq('short') until one of them returns true. That will never happen as the signature of propEq('short') is a -> Object -> Boolean, so when we pass an a, we don't get a Boolean, but instead a function from Object to Boolean.

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


The second question is how to make it work.

The most straightforward 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, clear code. I would probably leave it like that. But if you really want it to be point-free, Ramda offers the 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 viewed as a (curried) function of two parameters. The first parameter is passed to propEq('short'), returning a new function of type (a -> Boolean) and the second parameter is passed to identity, which does no tranformation, just passing the values intact. Then those two results are passed into find

useWith and the similar converge are very specific to Ramda. If you don't need to point-free version (for example, as a learning exercise), the first version of this is probably prefereable.

Sign up to request clarification or add additional context in comments.

1 Comment

Many thanks Scott for your answer, I need some time to consider and properly understand it, before I comment on it properly. Cheers for now ...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.