Flame_Phoenix
Flame_Phoenix

Reputation: 17574

How to use R.pipe with multiple arguments?

Background

I am learning Ramda and I am trying to use pipe. To this effect I made this simple example that doesn't work:

var getSQLQuery = ( { lang } ) => `My query is ${lang}`;
var addAnd = str => str + " and";
var getMarket = country => data => `${data} my country is ${country}`;

var comp = ( country, queryParams ) => R.pipe( 
        getSQLQuery( queryParams ),
        addAnd,
        getMarket( country ),
        R.tap( console.log ) 
    )(country, queryParams);

comp("Spain", {lang: "uk"}); //Blows Up!?

The error I get is

First argument to _arity must be a non-negative integer no greater than ten

​I don't know how to fix this. How can I do it?

You can see it live here.

Upvotes: 1

Views: 3890

Answers (3)

Scott Sauyet
Scott Sauyet

Reputation: 50797

There are many ways one could write such a function. I know your goal is to learn how to use pipe, but let me first show a technique that starts with something similar to your functions:

const getSQLQuery = ( { lang } ) => `My query is ${lang}`;
const getMarket = country => `my country is ${country}`;
const flipAndJoin = pipe(reverse, join(' and '))

const comp = useWith(unapply(flipAndJoin), [getMarket, getSQLQuery])

comp("Spain", {lang: "uk"}); //=> ""My query is uk and my country is Spain"

Now the questions are:

  1. Why does your function not work?
  2. How can you make it work?
  3. How do you make pipe work as desired?

Why does your function not work?

It's simple: pipe takes a number of functions as parameters, with at least one required. The first argument you supply is getSQLQuery( queryParams ), which is the result of calling getSQLQuery with an argument. That is a string, not a function. So when you try to wrap this in pipe, it fails. (The note about 'arity' has to do with the internals of Ramda: it uses the first function to pipe in order to determine how many parameters the resulting function should take.)

How can you make it work?

I gave an answer up above. The answer from MarioF does so with minimal change to your initial functions.

But none of these are as simple as

const comp2 = (country, queryParams) => 
              `My query is ${queryParams.lang} and my country is ${country}`

comp2("Spain", {lang: "uk"}); //=> ""My query is uk and my country is Spain"

How do you make pipe work as desired?

You need to realize what pipe does.

Think of a function like this:

const getUpperAddr(userName, collection) {
    const configStr = getUserConfig(userName, collection);
    const config = JSON.parse(configStr);
    const address = prop('address')(config);
    const addrLine1 = prop('addrLine1')(address);
    const upperAddr = toUpper(addrLine1);
    return upperAddr;
}

Forgetting the details, especially of how getUserConfig works, and forgetting any potential errors, we can see one interesting feature of this function: each successive local variable is created by applying a function to the one before. The only exception to this is the first one, which uses the parameters to the function. The result is the final local variable.

pipe is simply a way to make this more declarative, and remove the need for all the local variables (and even the parameter names.) This is equivalent:

const getUpperAddr = pipe(
    getUserConfig,
    JSON.parse,
    prop('address'),
    prop('addrLine1'),
    toUpper
);

This has the same signature as the above and returns the same result for the same input. If you can write your function in the first format, you can mechanically change to pipe. After a while, this becomes second nature, and you can skip the first step.

Upvotes: 10

davidkomer
davidkomer

Reputation: 3078

In answer to the core question "how to use x with multiple arguments", technically you can use R.nthArg, but that doesn't immediately help you pass data down the pipe.

In my opinion, it's better to pass in an array - or use rest parameters. This works:

//Kept as original
var getSQLQuery = ( { lang } ) => `My query is ${lang}`;
var addAnd = str => str + " and";
var getMarket = country => data => `${data} my country is ${country}`;

//only modified this function
const comp = (...args) => 
  getMarket(args[0]) (
    R.compose(addAnd, getSQLQuery)(args[1])
  );

comp("Spain", {lang: "uk"});

Repl here

Though I don't think R.compose really makes that any easier to reason about. Maybe if it's separated out into a named function like this?

const enhanceQuery = R.compose(addAnd, getSQLQuery)

const comp = (...args) => 
  getMarket(args[0]) (enhanceQuery(args[1]));

Repl here

Upvotes: 1

Mario F
Mario F

Reputation: 47279

It is quite arguable whether this makes the code more readable than just using a single function, but this way you get what you are looking for:

var getSQLQuery = (_, {lang}) => `My query is ${lang}`;
var addAnd = str => str + " and";
var getMarket = country => data => `${data} my country is ${country}`;

var comp = ( country, queryParams ) => R.pipe( 
    getSQLQuery,
    addAnd,
    getMarket( country ),
    R.tap( console.log ) 
)(country, queryParams);

comp("Spain", {lang: "uk"});

Upvotes: 2

Related Questions