Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend when() to receive N arguments #2842

Closed
vladejs opened this issue Jun 6, 2019 · 9 comments
Closed

Extend when() to receive N arguments #2842

vladejs opened this issue Jun 6, 2019 · 9 comments

Comments

@vladejs
Copy link

vladejs commented Jun 6, 2019

const hasValue = x => x != null
const hasUsername = pathSatisfies(hasValue, ['user', 'username'])

const startRealtime = (session, clients) => {
    // work with both arguments
}

const maybeInit = when( hasUsername, startRealtime );

maybeInit( sessionStore, clientsStore )

Here, when correctly gives sessionStore to both hasUsername and startRealtime fns,
But startRealtime also expects clientStore variable to be available, and when doesn’t pass it, because the fn only works with 1 argument.

So, my recommendation is to extend when or create a similar fn that does exactly that.
Currently, this is how I achieve it with eitherWhen:

const eitherWhen = (left, right, cond) => (input, …args) =>
  cond(input) ? right(input, …args) : left(input, …args)

const maybeInit = eitherWhen( console.error, startRealtime, hasUsername);

maybeInit( sessionStore, clientsStore )
@CrossEye
Copy link
Member

CrossEye commented Jun 7, 2019

I don't recall why we gave this a different feel than ifElse. Note that you could use ifElse instead here, with identity. For example,

ifElse(gt, divide, multiply)(5, 10) //=> 50
ifElse(gt, divide, multiply)(10, 5) //=> 2

But while I would welcome a PR for this, there is no guarantee that everyone would accept it.

@vladejs
Copy link
Author

vladejs commented Jun 8, 2019

actually, my question is not to create a similar to ifElse, is just, extend when to propagate the arguments given to the matchTrue function. I know it will make when a variadic function, but think it's useful to.

@CrossEye
Copy link
Member

CrossEye commented Jun 9, 2019

Sorry, I misread your question a bit.

But that means that ifElse is even more relevant. I believe you would get what you want just by using

const maybeInit = ifElse (hasUsername, startRealtime, console.error);

The only difference is that hasUsername would be passed all arguments, not just the first.

when and unless are (logically) simple glosses:

const when   = (pred, whenTrue)  => ifElse (pred, whenTrue, identity)
const unless = (pred, whenFalse) => ifElse (pred, identity, whenFalse)

These are not the actual implementations, and there is a difference I alluded to earlier: The functions generated by when and unless, as you've noted, accept only a single parameter. I expressed confusion about why we did this, when ifElse accepts the number parameters the largest of the functions supplied to it takes. The answer is fairly clear. We can't really return identity on your list of parameters, but only on a single one. And since the user can simply use ifElse when more parameters are necessary, this isn't any real loss.

@vladejs
Copy link
Author

vladejs commented Jun 9, 2019

boom goes the dynamite!.... ifElse to the rescue, I think I’ll spend some hours learning to read this hefty language: (*… → Boolean) → (*… → *) → (*… → *) → (*… → *).

The only tradeoff is that console.error I don’t need, because I only need to run startRealtime() if the condition is met, hence the use of when.

Nevertheless, before closing the issue, any ideas in how to replace const hasValue = x => x != null?
Ramda has isNil and complement, which per the documentation:

const hasValue = complement(isNil)

But I wonder if there is a more compact way, because then hasUsername will resemble this:

const hasUsername = pathSatisfies(complement(isNil), ['user', 'username'])

@CrossEye
Copy link
Member

CrossEye commented Jun 9, 2019

As to hasValue, there is a PR now to add isNotNil: #2818. But note that there is nothing wrong with having your hasValue helper function in scope and passing it to pathSatisfies. That's how functional programming works, by combining many small functions.

You can just supply a no-op in place of console.error, either a named noOp function or just () => {}.

I want to point out, though, that all of this feels a bit like an abuse of Ramda. While side-effects are obviously necessary for any programs that interact with the world, Ramda is not in the business of catering to side-effects. While I described when and unless as glosses on ifElse, ifElse itself is a gloss on cond. The functions returned by each of these functions are meant to act as pure functions: you give them arguments and they return a value. They are all ways of avoiding control flow code with if - else or switch - case, making your code more declarative. They are definitely not meant to simply replicate code flow instructions.

@vladejs
Copy link
Author

vladejs commented Jun 9, 2019 via email

@CrossEye
Copy link
Member

My point was simply that not everything needs to be -- or even should be -- done with Ramda tools. This is a library put in the hands of its users. It's not up to me to decide how you should use it, but it is designed to be useful for a certain category of problems. If you try to solve other ones that don't match its sweet spot, Ramda won't add particular clarity to your program, and may even make the code harder to read. Ramda's core is probably in data transformations, making it easy to break complex transformations into simpler ones joined together through functional composition.

In this, Ramda is different from the more ambitious underscore or lodash, which would like to be your general-purpose utility library. But even for underscore, it's hard to imagine a utility library improving on the simplicity of your imperative version.

@vladejs
Copy link
Author

vladejs commented Jun 10, 2019

Great explanation.
In the case of the imperative version, I dislike it because is unsafe, so, in order to be fully safe, it would need to be changed to:

// $session may be null so I need an extra check

if ($session && $session.user != null) {
  startRealtime();
}

With ramda, pathSatisfies just returns undefined instead of blowing up with trying to get .user of undefined or null error.

@CrossEye
Copy link
Member

By all means, use pathSatisfies if it makes sense. But that does not mean that all the code around it needs to also use Ramda.

You could also try

if ( prop ('user', $session) != null ) {
  startRealtime();
}

or

const hasUser = pipe ( prop ('user'), isNil, not )

// ...

if ( hasUser ($session) ) {
  startRealtime();
}

or

const isNotNil = pipe (isNil, not)
const hasUser = pipe ( prop('user'), isNotNil )

// ...

if ( hasUser ($session) ) {
  startRealtime();
}

or perhaps

const isNotNil = pipe (isNil, not)
const hasUser = lift (isNotNil) ( prop ('user') )

// ...

if ( hasUser ($session) ) {
  startRealtime();
}

In every one of these we still keep your if statement and the imperative call to startRealtime, using Ramda where it can offer help: doing a safe property access.

@vladejs vladejs closed this as completed Jun 13, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants