Ramda selfComposeWhile - javascript

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.

Related

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

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.

Lodash FP Compose on Multiple parameters [duplicate]

import {flow, curry} from 'lodash';
const add = (a, b) => a + b;
const square = n => n * n;
const tap = curry((interceptor, n) => {
interceptor(n);
return n;
});
const trace2 = curry((message, n) => {
return tap((n) => console.log(`${message} is ${n}`), n);
});
const trace = label => {
return tap(x => console.log(`== ${ label }: ${ x }`));
};
const addSquare = flow([add, trace('after add'), square]);
console.log(addSquare(3, 1));
I started by writing trace2 thinking that trace wouldn't work because "How can tap possibly know about n or x whatever?".
But trace does work and I do not understand how it can “inject” the x coming from the flow into the tap call. Any explanation will be greatly appreciated :)
Silver Spoon Evaluation
We'll just start with tracing the evaluation of
addSquare(3, 1) // ...
Ok, here goes
= flow([add, trace('after add'), square]) (3, 1)
add(3,1)
4
trace('after add') (4)
tap(x => console.log(`== ${ 'after add' }: ${ x }`)) (4)
curry((interceptor, n) => { interceptor(n); return n; }) (x => console.log(`== ${ 'after add' }: ${ x }`)) (4)
(x => console.log(`== ${ 'after add' }: ${ x }`)) (4); return 4;
console.log(`== ${ 'after add' }: ${ 4 }`); return 4;
~log effect~ "== after add: 4"; return 4
4
square(4)
4 * 4
16
= 16
So the basic "trick" you're having trouble seeing is that trace('after add') returns a function that's waiting for the last argument. This is because trace is a 2-parameter function that was curried.
Futility
I can't express how useless and misunderstood the flow function is
function flow(funcs) {
const length = funcs ? funcs.length : 0
let index = length
while (index--) {
if (typeof funcs[index] != 'function') {
throw new TypeError('Expected a function')
}
}
return function(...args) {
let index = 0
let result = length ? funcs[index].apply(this, args) : args[0]
while (++index < length) {
result = funcs[index].call(this, result)
}
return result
}
}
Sure, it "works" as it's described to work, but it allows you to create awful, fragile code.
loop thru all provided functions to type check them
loop thru all provided functions again to apply them
for some reason, allow for the first function (and only the first function) to have special behaviour of accepting 1 or more arguments passed in
all non-first functions will only accept 1 argument at most
in the event an empty flow is used, all but your first input argument is discarded
Pretty weird f' contract, if you ask me. You should be asking:
why are we looping thru twice ?
why does the first function get special exceptions ?
what do I gain with at the cost of this complexity ?
Classic Function Composition
Composition of two functions, f and g – allows data to seemingly teleport from state A directly to state C. Of course state B still happens behind the scenes, but the fact that we can remove this from our cognitive load is a tremendous gift.
Composition and currying play so well together because
function composition works best with unary (single-argument) functions
curried functions accept 1 argument per application
Let's rewrite your code now
const add = a => b => a + b
const square = n => n * n;
const comp = f => g => x => f(g(x))
const comp2 = comp (comp) (comp)
const addSquare = comp2 (square) (add)
console.log(addSquare(3)(1)) // 16
"Hey you tricked me! That comp2 wasn't easy to follow at all!" – and I'm sorry. But it's because the function was doomed from the start. Why tho?
Because composition works best with unary functions! We tried composing a binary function add with a unary function square.
To better illustrate classical composition and how simple it can be, let's look at a sequence using just unary functions.
const mult = x => y => x * y
const square = n => n * n;
const tap = f => x => (f(x), x)
const trace = str => tap (x => console.log(`== ${str}: ${x}`))
const flow = ([f,...fs]) => x =>
f === undefined ? x : flow (fs) (f(x))
const tripleSquare = flow([mult(3), trace('triple'), square])
console.log(tripleSquare(2))
// == "triple: 6"
// => 36
Oh, by the way, we reimplemented flow with a single line of code, too.
Tricked again
OK, so you probably noticed that the 3 and 2 arguments were passed in separate places. You'll think you've been cheated again.
const tripleSquare = flow([mult(3), trace('triple'), square])
console.log(tripleSquare(2)) //=> 36
But the fact of the matter is this: As soon as you introduce a single non-unary function into your function composition, you might as well refactor your code. Readability plummets immediately. There's absolutely no point in trying to keep the code point-free if it's going to hurt readability.
Let's say we had to keep both arguments available to your original addSquare function … what would that look like ?
const add = x => y => x + y
const square = n => n * n;
const tap = f => x => (f(x), x)
const trace = str => tap (x => console.log(`== ${str}: ${x}`))
const flow = ([f,...fs]) => x =>
f === undefined ? x : flow (fs) (f(x))
const addSquare = (x,y) => flow([add(x), trace('add'), square]) (y)
console.log(addSquare(3,1))
// == "add: 4"
// => 16
OK, so we had to define addSquare as this
const addSquare = (x,y) => flow([add(x), trace('add'), square]) (y)
It's certainly not as clever as the lodash version, but it's explicit in how the terms are combined and there is virtually zero complexity.
In fact, the 7 lines of code here implements your entire program in less than it takes to implement just the lodash flow function alone.
The fuss, and why
Everything in your program is a trade off. I hate to see beginners struggle with things that should be simple. Working with libraries that make these things so complex is extremely disheartening – and don't even get me started on Lodash's curry implementation (including it's insanely complex createWrap)
My 2 cents: if you're just starting out with this stuff, libraries are a sledgehammer. They have their reasons for every choice they made, but know that each one involved a trade off. All of that complexity is not totally unwarranted, but it's not something you need to be concerned with as a beginner. Cut your teeth on basic functions and work your way up from there.
Curry
Since I mentioned curry, here's 3 lines of code that replace pretty much any practical use of Lodash's curry.
If you later trade these for a more complex implementation of curry, make sure you know what you're getting out of the deal – otherwise you just take on more overhead with little-to-no gain.
// for binary (2-arity) functions
const curry2 = f => x => y => f(x,y)
// for ternary (3-arity) functions
const curry3 = f => x => y => z => f(x,y,z)
// for arbitrary arity
const partial = (f, ...xs) => (...ys) => f(...xs, ...ys)
Two types of function composition
One more thing I should mention: classic function composition applies functions right-to-left. Because some people find that hard to read/reason about, left-to-right function composers like flow and pipe have showed up in popular libs
Left-to-right composer, flow, is aptly named because your eyes will flow in a spaghetti shape as your try to trace the data as it moves thru your program. (LOL)
Right-to-left composer, composer, will make you feel like you're reading backwards at first, but after a little practice, it begins to feel very natural. It does not suffer from spaghetti shape data tracing.

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.)

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

Passing in NULL as a parameter in ES6 does not use the default parameter when one is provided

Is there a known reason why passing in null as a parameter in ES6 does not use the default parameter when one is provided?
function sayHello(name = "World") {
console.log("Hello, " + name + "!");
}
sayHello("Jim"); // Hello, Jim!
sayHello(undefined); // Hello, World!
sayHello(null); // Hello, null!
This is not that obvious
I've read some comments of why undefined is completely different than null and that's why it explains the current behavior of default parameters.
One could argue that explicitly passing undefined should not trigger the default value substitution because when I have a function:
const f = (x = 'default') => console.log(x);
I would like it to print "default" when I run it as:
f();
but I would like it to print "undefined" when I explicitly run it as:
f(undefined);
because otherwise why would I use f(undefined) in the first place? Clearly my intention here is to provide some argument instead of leaving it out.
Examples to the contrary
Now, consider this function:
const g = (...a) => console.log(JSON.stringify(a));
When I use it as:
g();
I get: []
But when I use it as:
g(undefined);
I get: [null]
which clearly demonstrates that:
passing undefined is not the same as not passing an argument at all
sometimes null can be a default value instead of undefined
TC39 decision
Some background on the current behavior of the default parameters can be seen in the July 24 2012 Meeting Notes by TC39:
https://github.com/rwaldron/tc39-notes/blob/master/meetings/2012-07/july-24.md
Incidentally, it shows that explicitly passing undefined originally did not trigger the default value in the first draft and there was a discussion about whether or not it should do that. So as you can see the current behavior was not so obvious to the TC39 members as it now seems to be to people who comment here.
Other languages
That having been said, the decision of what should and what should not trigger the default value substitution is completely arbitrary at the end of the day. Even having a separate undefined and null can be though of as quite strange if you think about it. Some language have only undefined (like undef in Perl), some have only null (like Java), some languages use equivalents of false or an empty list or array for that (like Scheme where you can have an empty list or #f (false) but there is no equivalent of null that would be distinct from both an empty list and a false value) and some languages don't even have equivalents of null, false or undefined (like C which uses integers instead of true and false and a NULL pointer which is actually a normal pointer pointing to address 0 - making that address inaccessible even when mapped by any code that tests for null pointers).
What you can do
Now, I can understand your need to substitute default values for null. Unfortunately this is not a default behavior but you can make a simple function to help you:
const N = f => (...a) => f(...a.map(v => (v === null ? undefined : v)));
Now every time you want defaults substituted for null values you can use it like this. E.g. if you have this function from one of the examples above:
const f = (x = 'default') => console.log(x);
it will print "default" for f() and f(undefined) but not for f(null). But when you use the N function defined above to define the f function like this:
const f = N((x = 'default') => console.log(x));
now f() and f(undefined) but also f(null) prints "default".
If you want somewhat different behavior, e.g. substituting default values for empty strings - useful for environment variables that can sometimes be set to empty strings instead of not existing, you can use:
const N = f => (...a) => f(...a.map(v => (v === '' ? undefined : v)));
If you want all falsy values to be substituted you can use it like this:
const N = f => (...a) => f(...a.map(v => (v || undefined)));
If you wanted empty objects to be substituted you could use:
const N = f => (...a) => f(...a.map(v => (Object.keys(v).length ? v : undefined)));
and so on...
Conclusion
The point is that it's your code and you know what should be the API of your functions and how the default values should work. Fortunately JavaScript is powerful enough to let you easily achieve what you need (even if that is not the default behavior of default values, so to speak) with some higher order function magic.
That's just how default parameters are defined in the spec. See MDN: (emphasis mine)
Default function parameters allow formal parameters to be initialized with default values if no value or undefined is passed.
null is neither no value, nor undefined, so it doesn't trigger the default initialization.
null is a value that won't trigger the default value to be used, the default values will be used when the argument is undefined.
ramdajs version code
// import ramda
// infra code
const isEmptyOrNil = (value) => isEmpty(value) || isNil(value)
const nullChange = (defaultValue) => (value) => isEmptyOrNil(value)? defaultValue: value
// single null
const _somef = (value) => value
const somefWithDefault = (defaultValue) => pipe(nullChange(defaultValue), _somef)
const somef = somefWithDefault('myValue')
somef(null)
// many args
const _somef2 = (value, value2, value3) => value + value2 + value3
const nullChangeMyValue = nullChange('myValue')
const somef2 = useWith(_somef2, [nullChangeMyValue, nullChangeMyValue, nullChangeMyValue])
somef2(null, undefined, 1234)
// many args2 version2
const nullChangeWith = (defaultValue) => nullChange(defaultValue)
const arrayNullChangeWith = (defaultValue) => (array) => array.map(nullChangeWith(defaultValue))
const arg2Array = (...args) => args
const funcApplyDefaultValue = (defaultValue) => (func) => pipe(
arg2Array, arrayNullChangeWith(defaultValue), apply(func)
)
const v2somef2 = funcApplyDefaultValue(8)(_somef2)
v2somef2(1,2,null)
gits: https://gist.github.com/otwm/3a6358e53ca794cc2e57ade4af91d3bb

Categories

Resources