How to compose curried functions in a point free style in Ramda? - javascript

My team is moving from Lodash to Ramda and entering the deeper parts of Functional Programming style. We've been experimenting more with compose, etc, and have run into this pattern:
const myFunc = state => obj => id => R.compose(
R.isNil,
getOtherStuff(obj),
getStuff(state)(obj)
)(id)
(We can of course omit the => id and (id) parts. Added for clarity.)
In other words, we have lots of functions in our app (it's React+Redux for some context) where we need to compose functions that take similar arguments or where the last function needs to get all its arguments before passing on to the next function in the compose line. In the example I gave, that would be id then obj then state for getStuff.
If it weren't for the getOtherStuff function, we could R.curry the myFunc.
Is there an elegant solution to this that would be point-free? This seems a common enough pattern in FP.

Here's one rationale for not pushing point-free too far. I managed to make a point-free version of the above. But I can't really understand it, and I really doubt that most readers of my code would either. Here it is,
const myFunc2 = o (o (o (isNil)), o (liftN (2, o) (getOtherStuff), getStuff))
Note that o is just a (Ramda-curried) binary version of Ramda's usual variadic compose function.
I didn't really figure this out. I cheated. If you can read Haskell code and write some basic things with it, you can use the wonderful Pointfree.io site to convert pointed code into point-free.
I entered this Haskell version of your function:
\state -> \obj -> \id -> isNil (getOtherStuff obj (getStuff state obj id))
and got back this:
((isNil .) .) . liftM2 (.) getOtherStuff . getStuff
which, with a little stumbling, I was able to convert to the version above. I knew I'd have to use o rather than compose, but it took a little while to understand that I'd have to use liftN (2, o) rather than just lift (o). I still haven't tried to figure out why, but Haskell really wouldn't understand Ramda's magic currying, and I'm guessing it has to do with that.
This snippet shows it in action, with your functions stubbed out.
const isNil = (x) =>
`isNil (${x})`
const getStuff = (state) => (obj) => (id) =>
`getStuff (${state}) (${obj}) (${id})`
const getOtherStuff = (obj) => (x) =>
`getOtherStuff (${obj}) (${x})`
const myFunc = state => obj => id => R.compose(
isNil,
getOtherStuff (obj),
getStuff (state) (obj)
)(id)
const myFunc2 = o (o (o (isNil)), o (liftN (2, o) (getOtherStuff), getStuff))
console .log ('Original : ', myFunc ('state') ('obj') ('id'))
console .log ('Point-free : ', myFunc2 ('state') ('obj') ('id'))
.as-console-wrapper {min-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {o, liftN} = R </script>
Not worth it
While this is very interesting, I would never use that in production code. Reading it over now, I'm starting to get it. But I will have forgotten it in a month, and many readers would probably never understand.
Point-free can lead to some elegant code. But it's worth using only when it does so; when it obscures your intent, skip it.

I don't know why you can't curry though:
const myFunc = curry(state, obj) => R.compose(
R.isNil,
getOtherStuff(obj),
getStuff(state)(obj)
));
or
const myFunc = curry(state, obj, id) => R.compose(
R.isNil,
getOtherStuff(obj),
getStuff(state)(obj)
)(id));
I am not sure I see a point free solution here (as it stands). There are some less intuitive combinators that may apply. The other thing I would consider is whether the getStuff and getOtherStuff functions have their signatures in the correct order. Maybe it't be better if they were defined in this order: obj, state, id.
The problem is that the obj is needed in two differnt funcitons. Perhaps restating getStuff to return a pair and getOtherStuff to take a pair.
const myFunc = R.compose(
R.isNil, // val2 -> boolean
snd, // (obj, val2) -> val2
getOtherStuff, // (obj, val) -> (obj, val2)
getStuff // (obj, state, id) -> (obj, val)
);
myFunc(obj)(state)(id)
I have found it helpful to think of multiple parameter functions as functions that take a single parameter which happens to be a tuple of some sort.
getStuff = curry((obj, state, id) => {
const val = null;
return R.pair(obj, val);
}
getOtherStuff = curry((myPair) => {
const obj = fst(myPair)
const val2 = null;
return R.pair(obj, val2);
}
fst = ([f, _]) => f
snd = ([_, s]) => s
=====
Update per the question on combinators. From http://www.angelfire.com/tx4/cus/combinator/birds.html there is the starling (S) combinator:
λa.λb.λc.(ac)(bc)
written in a more es6 way
const S = a => b => c => a(c, b(c))
or a function that takes three parameters a,b,c. We apply c to a leaving a new function, and c to b leaving whatever which is immediately applied to the function resuilting from c being applied to a.
in your example we could write it like
S(getOtherStuff, getStuff, obj)
but that might not work now that I look at it. because getStuff isn't fully satisfied before being being applied to getOtherStuff... You can start to peice together a solution to a puzzle, which is sometimes fun, but also not something you want in your production code. There is the book https://en.wikipedia.org/wiki/To_Mock_a_Mockingbird people like it, though it is challenging for me.
My biggest advice is start thiking about all functions as unary.

Related

Ramda selfComposeWhile

The Problem:
I'm learning functional programming
Just kidding, but also...
I have a helper function that composes a function with itself over and over again until some condition is met. Like f(f(f(f(f(f(f(x))))))) or compose(f,f,f,f,f,f,f)(x), except it keeps going unless told to stop.
The way I've implemented it, it doesn't really feel like composition (and perhaps that's the wrong word to use here regardless)
This is my current solution:
const selfComposeWhile = curry(
(pred, fn, init) => {
let prevVal = null;
let nextVal = init;
while(prevVal == null || pred(prevVal, nextVal)){
prevVal = nextVal;
nextVal = fn(nextVal);
}
return nextVal;
}
);
and here it is in use:
const incOrDec = ifElse(gt(30), inc, dec);
console.log(
selfComposeWhile(lt, incOrDec, 0)
); // -> 29
I don't want to use recursion as JavaScript doesn't have proper tail recursion and the namesake of this site (Stack Overflow) is a real concern for how I use this.
There's nothing wrong with it as is, but I've been trying to learn functional programming techniques by applying them to a dummy problem and this is one of the few places my code stands out as decidedly imperative.
I also have
useWith(selfComposeWhile, [pipe(nthArg(1), always)]);
That takes a predicate that is only concerned with the nextVal, which seems like the more general case of this.
The Question:
Can anybody think of a more functional (sans recursion) way to write selfComposeWhile and its cousin?
R.unfold does mostly what you, it accepts a seed value (init), and transforms it, and on each iteration it returns the current value, and the new seed value. On each iteration you need to decide to continue or stop using a predicate.
The main difference between your function, and R.unfold is that the last one produces an array, and this is easily solvable with R.last:
const { curry, pipe, unfold, last } = R
const selfComposeWhile = curry(
(pred, fn, init) => pipe(
unfold(n => pred(n, fn(n)) && [n, fn(n)]),
last
)(init)
)
const { ifElse, gt, inc, dec, lt } = R
const incOrDec = ifElse(gt(30), inc, dec)
console.log(selfComposeWhile(lt, incOrDec, 0)) // -> 29
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
The solution I've settled on for now.
I've taken selfComposeWhile and named it unfoldUntil. I'm not sure that's the best name as it doesn't return a list. It is basically R.until where the predicate can access the previous and the next value.
To bring them a bit more in alignment, I've changed my while behavior into until behavior (R.complement the predicate).
unfoldUntil
If it were typed:
unfoldUntil: <T>(
pred: (p:T, n:T) => boolean,
fn: (a:T) => T,
init: T
) => T
Implemented
const unfoldUntil = curry(
(pred, fn, init) => pipe(
unfold(n =>
isNil(n) ?
false :
call((next = fn(n)) =>
(pred(n, next) ?
[next, null] :
[next, next])
)
),
last
)(init)
);
Notes: This will never pass null/undefined into the transformation function (fn). You can use a transformation that returns null as a stopping condition and be returned the previous value. Otherwise, you'll be returned the first value of next that causes the predicate to return true.

What is the flow of execution with this compose function passed into Javascripts reduce?

I just want to know how reduce works in the case of code below(which was provided by a stackoverflow user in my previous question, i'm asking this question as his code snippet led to me having more questions that weren't cleared up and are too long to fit in a comment section). An array of functions is passed into a reducer. There is a compose function which runs on the array of functions. From my understanding of this f is the accumulator and g is the next item in the array. What is returned each cycle of the reduce becomes the accumulator for the next cycle. If there is no initalValue parameter passed into reduce then the first item in the array will be used as the initial accumulator value.
const compose = (f, g, i) => (...args) => {
console.log(i, g);
console.log(i, f);
return f(g(...args));
}
const f_xe = (x) => x + 'e',
f_xd = (x) => x + 'd',
f_xc = (x) => x + 'c',
f_xy = (x, y) => x + y;
console.log([f_xe, f_xd, f_xc, f_xy].reduce(compose)('a','b'));
// 3 [Function: f_xy]
// 3 [Function]
// 2 [Function: f_xc]
// 2 [Function]
// 1 [Function: f_xd]
// 1 [Function: f_xe]
// abcde
I visualize it like this:
cycle #1:
f = f_xe
g = f_xd
return f(g(...args))
^ which is f_xe(f_xd('a', 'b'))
cycle #2:
f = what was returned previously
^^ which will be f_xe(f_xd('a', 'b'))
g = f_xc
return f(g(...args))
^^ which is f_xe(f_xd('a', 'b'))(f_xc('a', 'b'))
I already know this line of thinking is wrong the way the flow works it works in an encapsulating manner, like so: f_xe(f_xd((f_xc(f_xy('a', 'b')))))
but why is this the case. If someone can intricately explain why it wraps this way and break down each cycle of the reduce step by step it would be immensely appreciated. Another thing I was wondering is, why doesn't f just try to evaluate immediately on the first cycle? f_xe(f_xd('a', 'b')) when this piece of code is returned wouldn't it try to evaluate that and produce an error instead of proceeding to the next item in the array? Instead the code starts evaluating from the last item in the array even though the compose function is instructed to be applied from the beginning. Which I do understand as with a composition function the last item will be ran first and then so on, however shouldn't the console log statements be ran in the order of first to last?
Again, I know my line of thinking is completely off with this one, but I was hoping if I shared my train of thought someone could push it in the right direction. Thank you to anyone who can shed some light on this.
Forget about the 'a' and 'b' arguments first. The important part is
const f = [f_xe, f_xd, f_xc, f_xy].reduce(compose);
This is what we need to look at, and where we can apply our definition of reduce for. The call of f('a','b') comes later.
When expanding the reduce call, we find
const f = compose(compose(compose(f_xe, f_xd, 1), f_xc, 2), f_xy, 3);
(This is a bit weird actually. I'd recommend using reduceRight for composing functions. Also pass the identify function as the initial value for the accumulator.)
Now we can expand the compose calls:
const f1 = (...args) => {
console.log(1, f_xe);
console.log(1, f_xd);
return f_xe(f_xd(...args));
}
const f2 = (...args) => {
console.log(2, f1);
console.log(2, f_xc);
return f1(f_xc(...args));
}
const f3 = (...args) => {
console.log(3, f2);
console.log(3, f_xy);
return f2(f_xy(...args));
}
const f = f3;
Now when you call f3('a', 'b'), you can see why the logs happen "backwards".
shouldn't the console log statements be ran in the order of first to last?
If you want that, you maybe better put them in the compose function and not in the closure that it returns. Try with
const compose = (f, g, i) => {
console.log(i, g);
console.log(i, f);
return (...args) => f(g(...args));
}

Mapping using higher-order functions with ramda.js

I have a pattern in my code that keeps recurring and seems like it should be pretty common, but I can't for the life of me figure out what it's called or whether there are common ways of handling it: maping using a function that takes an argument that is itself the result of a function taking the maped element as an argument.
Here's the pattern itself. I've named the function I want mapply (map-apply), but that seems like the wrong name:
const mapply = (outer, inner) => el => outer(inner(el))(el)
What is this actually called? How can I achieve it in idiomatic Ramda? It just seems like it has to be a thing in the world with smart people telling me how to handle it.
My use case is doing some basic quasi-Newtonian physics work, applying forces to objects. To calculate some forces, you need some information about the object—location, mass, velocity, etc. A (very) simplified example:
const g = Vector.create(0, 1),
gravity = ({ mass }) => Vector.multiply(mass)(g),
applyForce = force => body => {
const { mass } = body,
acceleration = Vector.divide(mass)(force)
return R.merge(body, { acceleration })
}
//...
const gravitated = R.map(mapply(applyForce, gravity))(bodies)
Can somebody tell me: What is this? How would you Ramda-fy it? What pitfalls, edge cases, difficulties should I watch out for? What are the smart ways to handle it?
(I've searched and searched—SO, Ramda's GitHub repo, some other functional programming resources. But perhaps my Google-fu just isn't where it needs to be. Apologies if I have overlooked something obvious. Thanks!)
This is a composition. It is specifically compose (or pipe, if you're into being backwards).
In math (consider, say, single variable calculus), you would have some statement like fx or f(x) signifying that there is some function, f, which transforms x, and the transformation shall be described elsewhere...
Then you get into craziness, when you see (g º f)(x). "G of F" (or many other descriptions).
(g º f)(x) == g(f(x))
Look familiar?
const compose = (g, f) => x => g(f(x));
Of course, you can extend this paradigm by using composed functions as operations inside of composed functions.
const tripleAddOneAndHalve = compose(halve, compose(add1, triple));
tripleAddOneAndHalve(3); // 5
For a variadic version of this, you can do one of two things, depending on whether you'd like to get deeper into function composition, or straighten out just a little bit.
// easier for most people to follow
const compose = (...fs) => x =>
fs.reduceRight((x, f) => f(x), x);
// bakes many a noodle
const compose = (...fs) => x =>
fs.reduceRight((f, g) => x => g(f(x)));
But now, if you take something like a curried, or partial map, for instance:
const curry = (f, ...initialArgs) => (...additionalArgs) => {
const arity = f.length;
const args = [...initialArgs, ...additionalArgs];
return args.length >= arity ? f(...args) : curry(f, ...args);
};
const map = curry((transform, functor) =>
functor.map(transform));
const reduce = ((reducer, seed, reducible) =>
reducible.reduce(reducer, seed));
const concat = (a, b) => a.concat(b);
const flatMap = curry((transform, arr) =>
arr.map(transform).reduce(concat, []));
You can do some spiffy things:
const calculateCombinedAge = compose(
reduce((total, age) => total + age, 0),
map(employee => employee.age),
flatMap(team => team.members));
const totalAge = calculateCombinedAge([{
teamName: "A",
members: [{ name: "Bob", age: 32 }, { name: "Sally", age: 20 }],
}, {
teamName: "B",
members: [{ name: "Doug", age: 35 }, { name: "Hannah", age: 41 }],
}]); // 128
Pretty powerful stuff. Of course, all of this is available in Ramda, too.
const mapply0 = (outer, inner) => el => outer(inner(el))(el);
const mapply1 = (outer, inner) => R.converge(
R.uncurryN(2, outer),
[
inner,
R.identity,
],
);
const mapply2 = R.useWith(
R.converge,
[
R.uncurry(2),
R.prepend(R.__, [R.identity]),
],
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script>
I haven't tested this but it will probably work.
The first is your function.
The second uses converge to pass 'el' through the inner function and then the identity function and pass both into an uncurried version of outer.
R.uncurryN(2, outer) works like this outer(inner(el), el), this means that converge can supply the parameters.
the third might be too far but it's fun anyway, you are calling converge with the first parameter as an uncurried version of outer and the second as an array containing inner and the identity, useWith does this which completely removes function definitions from the solution.
I'm not sure if this is what you were looking for but these are the 3 ways of writing it I found.
Paraphrased from the comments on the question:
mapply is, actually, chain:
R.chain(f, g)(x); //=> f(g(x), x)
Well, mostly. In this case, note that x must be an array.
My solution to the problem, then, is:
const gravitated = R.map(
R.chain(applyForce, R.compose(R.of, gravity))
)(bodies)
The Ramda documentation for chain is not terribly helpful in this case: it reads simply, "chain maps a function over a list and concatenates the results." (ramdajs.com/docs/#chain)
The answer is lurking in the second example there, where two functions are passed to chain and partially applied. I could not see that until after reading these answers here.
(Thanks to ftor, bergi, and Scott Sauyet.)

What would be a good example of an endofunctor that is not the identity functor?

In Professor Frisby Introduces Composable Functional JavaScript the identity functor was introduced:
const Box = x =>
({
map: f => Box(f(x)),
fold: f => f(x) // for testing
})
I spent the better part of the day understanding functors and why the above JavaScript code is actually the identity functor. So I thought I would alter it to get a "real" functor that is not the identity functor. I came up with this:
const Endo = x =>
({
map: f => Endo(f(x).split('')),
fold: f => f(x).split('') // for testing
})
My reasoning is that with Box, Id_Box: Box -> Box and Id_Box f = f. Endo would also map to itself but Endo(f): Endo(x) -> Endo(y) (if f: x -> y).
Am I on the right track?
EDIT:
Replaced string with the more generic x as it was in the original examples.
As pointed out in this answer, for our purposes as programmers we can treat all functors as endofunctors so don't get too caught up on the differences.
As for what a functor is, in brief it is
a data structure (Box in your example)
that can support a mapping operation (think Array.prototype.map)
and that mapping operation respects identity: xs === xs.map(x => x)
...and composition: xs.map(f).map(g) === xs.map(f . g) where . is function composition.
That's it. No more, no less. Looking at your Box, it's a data structure that has a map function (check 1 & 2) and that map function looks like it should respect identity and composition (check 3 & 4). So it's a functor. But it doesn't do anything, which is why it's the identity functor. The fold function isn't strictly necessary, it just provides a way to 'unwrap' the box.
For a useful functor, let's look at JavaScript arrays. Arrays actually do something: namely they contain multiple values rather than just a single one. If an array could only have one element, it'd be your Box. For our purposes we'll pretend that they can only hold values of the same type to simply things. So an array is a data structure, that has a map function, that respects identity and composition.
let plus = x => y => x + y;
let mult = x => y => x * y;
let plus2 = plus(2);
let times3 = mult(3);
let id = x => x;
let compose = (...fs) => arg => fs.reverse().reduce((x, f) => { return f(x) }, arg);
// Here we need to stringify the arrays as JS will compare on
// ref rather than value. I'm omitting it after the first for
// brevity, but know that it's necessary.
[1,2,3].map(plus2).toString() === [3,4,5].toString(); // true
[1,2,3].map(id) === [1,2,3]; // true
[1,2,3].map(plus2).map(times3) === [1,2,3].map(compose(times3, plus2)); // true
So when we map a function over a functor (array) we get back another instance of the same functor (a new Array) with the function applied to whatever the functor (array) was holding.
So now lets look at another ubiquitous JavaScript data structure, the object. There's no built in map function for objects. Can we make them a functor? Assume again that the object is homogenous (only has keys to one type of value, in this example Number):
let mapOverObj = obj => f => {
return Object.entries(obj).reduce((newObj, [key, value]) => {
newObj[key] = f(value);
return newObj;
}, {});
};
let foo = { 'bar': 2 };
let fooPrime = mapOverObj(foo)(plus2); // { 'bar': 4 }
And you can continue on to test that the function accurately (as far as is possible in JavaScript) supports identity and composition to satisfy the functor laws.

functional composition of a boolean 'not' function (not a boolean value)

I am working in TS but will show the tsc -> ES6 code below.
I have a function 'isDigit' that returns true if the the character code is in the range of digits 0-9. This function (isDigit) must be passed as an argument into a higher order function.
const isDigit = (char, charC = char.charCodeAt(0)) => (charC > 47 && charC < 58);
As part of another higher order function, I need to know if a character is NOT a digit. Of course something like below would work...
const notDigit = (char, charC = char.charCodeAt(0)) => !isDigit(char);
BUT it would be more satisfying if I could compose isDigit with another function (I'll call notFun) to apply a not operator to the result of isDigit to make notDigit. In the code below 'boolCond' serves to control if the not operator is applied or not. The code below 'almost' works, but it returns a boolean not a function which does not work when dealing with higher order functions.
const notFun = (myFun, boolCond = Boolean(condition)) => (boolCond) ? !myFun : myFun;
As is usually the case, while preparing this question I wound up finding an answer, so I will share my answer and see what improvements come from the community.
The issue observed above (getting a boolean instead of a function) is an issue of 'functional composition, I found several optional approaches in the post of Eric Elliot, from which I selected the 'pipe' functional composition method.
see Eric Elliot's excellent post
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
The implementation of this pipe composition function looked like the below TS... For those following along at home, I have included the recursive count while 'recCountWhile' function that is the ultimate consumer of the composed (i.e. piped) function (please excuse the inverted order that these functions appear but this was done for clarity).
export const removeLeadingChars: (str: string) => string =
(str, arr = str.split(''),
dummy = pipe(isDigit, notFun), cnt = recCountWhile(arr, dummy, 0)) =>
arr.slice(cnt).reduce((acc, e) => acc.concat(e),'');
export const recCountWhile: (arr: string[], fun: (char: string) => boolean, sum: number) => number =
(arr, fun, sum, test = (!(arr[0])) || !fun(arr[0]) ) =>
(test) ? sum : recCountWhile(arr.slice(1), fun, sum + 1);
The result is a composed function 'removeLeadingChars' that composes the 'isDigit' with the 'notFun' (using the pipe function ) into 'dummy' function that is passed to the recCountWhile function. This returns the count of 'not digits' (i.e. characters other than digits) that lead the string, these characters that are then 'sliced' from the head of the array, and the array is reduced back to a string.
I would be very keen to hear about any tweaks that may improve on this approach.
Good on you to find your answer and still post the question. I think this is a nice way to learn.
For the sake of a function composition exercise, here's how I might structure your functions.
see Keep it simple below for how I would handle this with practical code
const comp = f => g => x => f(g(x))
const ord = char => char.charCodeAt(0)
const isBetween = (min,max) => x => (x >= min && x <= max)
const isDigit = comp (isBetween(48,57)) (ord)
const not = x => !x
const notDigit = comp (not) (isDigit)
console.log(isDigit('0')) // true
console.log(isDigit('1')) // true
console.log(isDigit('2')) // true
console.log(isDigit('a')) // false
console.log(notDigit('0')) // false
console.log(notDigit('a')) // true
Code review
Btw, this thing you're doing with the default parameters and leaked private API is pretty wonky
// charC is leaked API
const isDigit = (char, charC = char.charCodeAt(0)) => (charC > 47 && charC < 58);
isDigit('9') // true
isDigit('9', 1) // false wtf
isDigit('a', 50) // true wtf
I understand you're probably doing it so you don't have to write this
// I'm guessing you want to avoid this
const isDigit = char => {
let charC = char.charCodeAt(0)
return charC > 47 && charC < 58
}
... but that function is actually a lot better because it doesn't leak private API (the charC var) to the external caller
You'll notice the way I solved this in mine was to use my own isBetween combinator and currying which results in a pretty clean implementation, imo
const comp = f => g => x => f(g(x))
const ord = char => char.charCodeAt(0)
const isBetween = (min,max) => x => (x >= min && x <= max)
const isDigit = comp (isBetween(48,57)) (ord)
More of your code that does this awful default parameters thing
// is suspect you think this is somehow better because it's a one-liner
// abusing default parameters like this is bad, bad, bad
const removeLeadingChars: (str: string) => string =
(str, arr = str.split(''),
dummy = pipe(isDigit, notFun), cnt = recCountWhile(arr, dummy, 0)) =>
arr.slice(cnt).reduce((acc, e) => acc.concat(e),'');
Try to avoid compromising the quality of your code for the sake of making everything a one-liner. The above function is much worse than this one here
// huge improvement in readability and reliability
// no leaked variables!
const removeLeadingChars: (str: string) => string = (str) => {
let arr = str.split('')
let dummy = pipe(isDigit, notFun)
let count = recCountWhile(arr, dummy, 0)
return arr.slice(count).reduce((acc, e) => acc.concat(e), '')
}
Keep it simple
Instead of splitting the string into an array, then iterating over the array to count the leading non digits, then slicing the head of the array based on the count, then finally reassembling the array into an output string, you can... keep it simple
const isDigit = x => ! Number.isNaN (Number (x))
const removeLeadingNonDigits = str => {
if (str.length === 0)
return ''
else if (isDigit(str[0]))
return str
else
return removeLeadingNonDigits(str.substr(1))
}
console.log(removeLeadingNonDigits('hello123abc')) // '123abc'
console.log(removeLeadingNonDigits('123abc')) // '123abc'
console.log(removeLeadingNonDigits('abc')) // ''
So yeah, I'm not sure if the code in your question was merely there for an exercise, but there's really a much simpler way to remove leading non digits from a string, if that's really the end goal.
The removeLeadningNonDigits function provided here is pure function, does not leak private variables, handles all inputs for its given domain (String), and maintains an easy-to-read style. I would suggest this (or something like this) compared to the proposed solution in your question.
Function Composition and "Pipe"
Composing two functions is usually done in right-to-left order. Some people find that hard to read/reason about, so they came up with a left-to-right function composer and most people seem to agree that pipe is a good name.
There's nothing wrong with your pipe implementation, but I think it's nice to see how if you strive to keep things as simple as possible, the resulting code cleans up a bit.
const identity = x => x
const comp = (f,g) => x => f(g(x))
const compose = (...fs) => fs.reduce(comp, identity)
Or if you'd like to work with my curried comp presented earlier in the post
const identity = x => x
const comp = f => g => x => f(g(x))
const uncurry = f => (x,y) => f(x)(y)
const compose = (...fs) => fs.reduce(uncurry(comp), identity)
Each of these functions have their own independent utility. So if you define compose in this way, you get the other 3 functions for free.
Contrast this to the pipe implementation provided in your question:
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
Again, it's fine, but it mixes all of these things together in one function.
(v,f) => f(v) is useful function on its own, why not define it separately and then use it by name?
the => x is merging the identity function which has countless uses

Categories

Resources