How to use R.pipe with multiple arguments? - javascript

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.

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:
Why does your function not work?
How can you make it work?
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.

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"});

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

Related

Possible to use R.__ with semver functions?

const semvers = ["5.100.0-rc.0", "5.97.3", "5.97.1"];
const newRecord = "5.97.2";
Given the above test data, I wish to insert newRecord into the right order, defined/sorted by semver package.
result = ["5.100.0-rc.0", "5.97.3", "5.97.2", "5.97.1"];
Below is my attempt which gave me the correct result
const semvers = ["5.100.0-rc.0", "5.97.3", "5.97.1"];
const newRecord = "5.97.2";
const indexResult = R.findIndex(x => semver.lt(x, newRecord))(semvers);
const result = R.insert(indexResult, newRecord, semvers)
Then, i was wondering if I can replace x with R.__, so i attempted below
const indexResult = R.findIndex(semver.lt(R.__, newRecord))(semvers);
I had the impression that R.__ referring to the arguments that was gonna passed but seems like it's not, or it was simply due to the fact that semver.lt is not a curried function and hence couldn't comprehend R.__?
R.__ works with Ramda functions or functions curried with Ramda e.g.,
const semvers = ["5.100.0-rc.0", "5.97.3", "5.97.1"];
const newRecord = "5.97.2";
const findVer = R.curryN(2, semver.lt)(R.__, newRecord);
const indexResult = R.findIndex(findVer, semvers);
const result = R.insert(indexResult, newRecord, semvers);
My preferred option would have been: R.flip(semver.lt)(newRecord) unfortunately semver.lt arity is 3 (third argument is a loose parameter) so R.flip doesn't work straight out of the box.
With R.partialRight you could supply the last two arguments (including that undocumented (?) loose parameter):
const findVer = R.partialRight(semver.lt, [newRecord, false]);
But honestly what you had originally is fine.

Remove a URL search parameter when there is duplicate names?

I am trying to manipulate my URL using URLSearchParams. However URLSearchParams.delete() expects the name of the param. If I have params with the same name, (from what I've tested in chrome) It will delete all params with that name. Is there a way to delete by both name and value?
My query looks something like this:
?color[]=Black&color[]=Green&material[]=Steel
So when I call .delete("color[]") it will remove both color[]= params, but what if I want to only remove a specific one?
The reason for the duplicate names is the backend (PHP) is leveraging this functionallity to auto parse the parameters into arrays...which requires the syntax above.
Big picture is- I'm trying to add/remove "filters" from this array-to-be. Also, some filter categories could have matching values so I don't want remove by value either. I am open to considering an entirely new approach...just trying to do it in the least hacky way.
-- Edit --
For any Laravel users, I recommend not using the index-less syntax. Just use color[0]=, color[1]= etc. I didn't realize laravel supports both syntaxes.
To remove a specific key/value pair, loop over the entries, filter out the unwanted one(s) and create a new URLSearchParams:
function deleteParamsEntry(params, key, value) {
const newEntries = Array.from(params.entries()).filter(
([k, v]) => !(k === key && v === value)
);
return new URLSearchParams(newEntries);
}
const query = "?color[]=Black&color[]=Green&material[]=Steel";
const params = new URLSearchParams(query);
const newParams = deleteParamsEntry(params, "color[]", "Green");
console.log(newParams.toString());
Try this approach:
const deleteURLParamsByNameAndValue = (urlString, paramName, paramValue) => {
const url = new URL(urlString)
const params = url.searchParams
const newParamArray = []
for (var kvPair of params.entries()) {
const k = kvPair[0]
const v = kvPair[1]
if (k!==paramName || v!==paramValue) {
newParamArray.push(kvPair)
}
}
const newSearch = new URLSearchParams(newParamArray)
return decodeURI(`${url.origin}${url.pathname}?${newSearch}`)
}
const urlString = 'https://example.com/path1/path2?color[]=Black&color[]=Green&material[]=Steel'
deleteURLParamsByNameAndValue(urlString,'color[]','Black')
// returns 'https://example.com/path1/path2?color[]=Green&material[]=Steel'

How to use pattern matching without switch case in javascript (in functional programming way)

I've been reading the clean code book which ask for eliminate using switch case, So I wanted to implement it in JavaScript, and I wanted to use it in functional programming way, so I decided to use pattern matching.
I have the code below, that I want to omit the switch case, how can I do it, not using the polymorphism.
function calculateSalary(role) {
switch (true) {
case /CTO/.test(role):
return getCTOSallary();
case /CEO/.test(role):
return getCEOSallary();
case /engineer/.test(role):
return getEngineeringSallary();
default:
return getGeneralSallary();
}
}
One way to do so would be to use dynamic dispatch.
const Engineer = (name, bonus) => ({ constructor: Engineer, name, bonus });
Engineer.calculateSalary = engineer => 20 + engineer.bonus;
const Employee = (name, bonus) => ({ constructor: Employee, name, bonus });
Employee.calculateSalary = employee => 10 + employee.bonus;
const calculateSalary = role => role.constructor.calculateSalary(role);
console.log("John's salary is", calculateSalary(Engineer("John", 10))); // 30
console.log("Mary's salary is", calculateSalary(Employee("Mary", 10))); // 20
Note that we could have used classes for dynamic dispatch too. However, I wanted to show that it's possible to have dynamic dispatch without using classes, using a purely functional style of programming.
Disclaimer: I am the author of the library mentioned in this answer.
I like the way it is done in Clojure with multimethods:
A Clojure multimethod is a combination of a dispatching function, and one or more methods.
A dispatching function takes parameter(s) and return a value
The dispatched value is used to determine which method to call
The library #customcommander/multifun is an attempt at bringing multimethods to JavaScript.
You first need a dispatching function
This will be getRole. It returns the part you're interested in or null if the role is unknown:
const getRole = role => {
const match = role.match(/(cto|ceo|engineer)/i);
return match ? match[1].toLowerCase() : null;
};
Then you'll need a series of value/function pairs
'cto', getCTOSalary
'ceo', getCEOSalary
...
Finally you need a function when there are no matches
This will be the getGeneralSalary function.
How does it work?
getRole is the dispatching function, it is applied to the parameters and returns a value
The dispatched value is compared with the value in each value/function pair.
If there's a match, the function is applied to the parameters
The function getGeneralSalary is applied to the parameters when there are no matches.
const multifun = require("#customcommander/multifun");
const getCTOSalary = (_, {bonus = 0}) => 10 + bonus;
const getCEOSalary = (_, {bonus = 0}) => 20 + bonus;
const getEngineeringSalary = (_, {bonus = 0}) => 30 + bonus;
const getGeneralSalary = (_, {bonus = 0}) => 40 + bonus;
const getRole = role => {
const match = role.match(/(cto|ceo|engineer)/i);
return match ? match[1].toLowerCase() : null;
};
const calculateSalary =
multifun
( getRole
, 'cto', getCTOSalary
, 'ceo', getCEOSalary
, 'engineer', getEngineeringSalary
, getGeneralSalary
);
calculateSalary('Chief Technology Officer (CTO)', {bonus: 1}); //=> 11
calculateSalary('Chief Executive Officer (CEO)', {bonus: 2}); //=> 22
calculateSalary('Senior Software Engineer', {bonus: 3}); //=> 33
calculateSalary('random title', {bonus: 4}); //=> 44
Do you mean something like this?
function test(str) {
let tests = [/xyz/, /test/, /ing/];
if(tests.some(patt => patt.test(str))) {
console.log("Match found");
}else {
console.log("No match found");
}
}
test("testing");

How to use reduce and the ramda "hex2color" function to count list of hex values than have r in their color name?

"Use reduce and the hex2color function to count list of hex values than have r in their name";
My current attempt is below. The first piece I know needs to be fixed is the filter function. I need to be able to filter out any colors that have the letter "r", but cannot seem to find a way to easily fit that into the filter function. It could easily be a syntax issue as I think I am asking the filter to find any strings that === "r", even though I am trying to use "contains" to solve that and have it check the whole color word.
Once the filter function is working, I assume the next step is to simply use the reduce function, then compose them together. ( I could be way off off, however).
I am quite new to programming, any insight is extremely welcome. Thanks!!
const exercise3 = _ => {
const hexes = ["#0000ff", "#f5f5dc", "#cd853f", "#663399", "#ffa500"];
const letter = "r";
const mapper = hex2color;
console.log(map(mapper, hexes)); //blue,beige,peru,rebeccapurple,orange
const filterer = el => contains(hex2color(el), letter);
console.log(filter(filterer, hexes)); //yields nothing, I assume to using the filter wrong with "r".
const reducer = (acc, el) => acc + 1;
const mappedFn = map(mapper);
const filtererFn = filter(filterer);
const reducerFn = reduce(reducer, 0);
const composedFn = compose(reducerFn, filtererFn, mappedFn);
return composedFn(hexes);
};

Regex using array.filter in javascript

I'm trying to use a regular expression to filter the name column in a csv file that has be put into an object array. Everything works fine if I type the exact value.
values I want "king" to match are below:
kingkong, king kong, king-kong, king_kong, king11, kongking, kong-king, kong_king, kong king, 11king
I've tried using filter and find methods but I want to use filter to return multiple values if they exist. I have tried the following regex but can't figure out the proper sytax if it is even correct.
const CSVToJSON = require('csvtojson');
const user = "king";
CSVToJSON().fromFile("./locations.csv").then(source => {
var found = source.filter(function(v, i){
return ((v["name"]== /\bking.*/g));
})
You can use the following approach.
const CSVToJSON = require('csvtojson');
CSVToJSON().fromFile("./locations.csv").then(source => {
var found = source.filter(function(v, i){
return ((v["name"].match(/king/g)));
});
return statement could be something like
return ((/king/g).test(v["name"]));
OR
return ((v["name"].match(/king/g)));
Both should work
However, your sample patterns show that king might stand either at the beginning or at the end of the target (bot can't have both prefix and suffix). If I am right, that means you don't need regex for that.
const CSVToJSON = require('csvtojson');
const user = "king";
CSVToJSON().fromFile("./locations.csv").then(source => {
var found = source.filter((v, i) => v.startsWith(user) || v.endsWith(user))
/*rest of the code */
});
If king can stand anywhere, you can simply use includes instead.
This is what worked, I'm totally new to JavaScript:
const user = args;
var regex = new RegExp(user, "g");
CSVToJSON().fromFile("./locations.csv").then(source => {
var found = source.filter(function(v, i){
return ((v["name"].match(regex)));
})

Categories

Resources