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

new function: recall #2987

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

shiftinterrupt
Copy link

@shiftinterrupt shiftinterrupt commented Feb 18, 2020

This is in reference to #2980. It supports a particular class of function: A unary function that if returns a function, is also unary with this same rule.

@@ -177,6 +177,7 @@ export { default as propOr } from './propOr';
export { default as propSatisfies } from './propSatisfies';
export { default as props } from './props';
export { default as range } from './range';
export { default as recall } from './recall';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the choice of the name "recall?" To me that mostly has to do with remembering things.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm terrible at naming. The idea is that it calls, then re-calls, etc. collapse also works.

*
* @symb R.recall(f, a, b) = f(a)(b)
*/
var recall = uncurryN(2, function(fn) {
Copy link
Member

@CrossEye CrossEye Feb 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm extremely loathe to add new variadic functions to Ramda.

I would much rather deal with an API such as this:

recall (f) (a, b) //=> f (a) (b)

(Even that probably isn't possible. I'm quite certain that we will need to supply the final arity. But that's a different comment.)

I know that the generated function is going to be polyadic. That's the whole point. And it's not even as though it always returns functions of a specific arity. But I'm hoping to make pipe, compose and other leftover variadic functions into fixed-arity ones and not add new ones.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the same way, I was just mirroring call. The list of values can be passed as an array instead, as in apply.

Copy link
Member

@CrossEye CrossEye Feb 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the call API was an early mistake as we felt our way around this space.

But I wouldn't want the arguments to be wrapped in an array. I was just thinking that this function would have to be a two-pass call: recall takes a curried function and returns a function. You call that with the n arguments you would have supplied one at a time to the original:

const foo = (a) => (b) => (c) => a + b + c

const bar = recall (foo)

bar (1, 2, 3) => 6

(Or some equivalent of this that accepts an arity instead of depending on the function's arity.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to call a function with a set of values in the fashion of call and apply. The difference here is there is no notion of arity since the n-order function is composed of unary functions. The list of values supplied doesn't represent an arity of a function, rather a serial list of singular inputs fed to the n-order function. So returning a function defeats the purpose in which case I'd rather just use uncurryN somehow.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps then we're at cross purposes. I was thinking that the goal was a to bring behavior similar to uncurryN to work with Ramda-styles curried functions. I thought we were mostly just removing the word "manually" from the docs.

I'm not sure what your goal is hers.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally uncurryN would be made to work but I'd still want this functionality, otherwise I'd have just submitted a patch. Is anyone looking into it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. I've been pulled into something else, and haven't had any real free time for Ramda in the past week. If no one does it before then, I will try to get to it this weekend. (You can do it yourself. Any conversations of importance about this would be in the issues here; so a GitHub search should probably show anything of importance that isn't already captured in the source or the tests.)

*/
var recall = uncurryN(2, function(fn) {
return function() {
var numArgs = arguments.length;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm quite sure that we need to supply the arity here as we do with uncurryN.

What happens if our target output is a function? What if we want to convert

const makeHandler = (a) => (b) => (evt) => ({do: 'something', with: 'a, b, and evt'})

so that it can be used as

something .addHandler (recall (makeHandler, 42, false) )

where addHandler accepts a function?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good use. The value returned can be whatever it needs to be, including just another function. If we supply a function a => b => c => d => a + b * c + d and want a function a particular depth, we just provide values up to that depth. For example, [ 2, 3 ] if we want the function c => d => 5 * c + d.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I misread before and didn't notice && i < numArgs. My initial reading suggested that this use case wouldn't be possible with that implementation.

it('accepts no more than N arguments for an N-order function', function() {
eq(R.recall(fn, 1, 2, 3), R.recall(fn, 1, 2, 3, 4));
});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel that this tests proves what it claims to prove. Perhaps with an innermost function in fn that has an optional parameter and acts differently based on its presence.

var fn = function(a) {
  var x = a + 1;
  return function(b) {
    var y = x * b;
    return function(c, d = 0) {
      return y * c + d;
    };
  };
};

where

fn (1) (2) (3)          //=> 12
fn (1) (2) (3, 4)       //=> 16
recall (fn, 1, 2, 3, 4) //=> 12

But then that makes me wonder if this is the correct behavior. It seems to me that if we do this as :: Int -> (a -> b -> ... -> n -> x) -> ((a, b, ..., n) -> x), then we could write this so that

recall (3, fn) (1, 2, 3, 4) //~> fn (1) (2) (3, 4)

which feels more in keeping with other Ramda functions.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's intended to support a special class of high-order function, those composed of unary functions. E.g. const f = g => h => v, where f, g, and h are unary functions. In compose, all functions must be unary except the last because the inputs to the last function are outside the workings of compose. Similarily, the last value of the high-order unary function can be anything, including a polyadic function, because it's considered to be the value and outside the workings of recall. The tests themselves could use some work though, I agree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was responding to the test case. I don't think it really demonstrated that it "accepts no more than N arguments for an N-order function". But I am curious about which behavior we actually want. I'm thinking that it might be a little different from what's in this implementation.

@CrossEye
Copy link
Member

Thank your for the PR. I'm afraid I forgot to do the research into why uncurryN doesn't already handle this. I will try to do so soon.

Did you look into updating uncurryN to also handle this case? Because if there's not some compelling reason not to do so, it seems that would be a much better solution.

@shiftinterrupt
Copy link
Author

@CrossEye No problem. I gave a cursory look into uncurryN. It seemed fine. I didn't notice anything glaring and figured I'd need to dig further and see how currying was being handled. I'll need to make some time for that.

@CrossEye CrossEye self-assigned this Jan 22, 2022
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

Successfully merging this pull request may close these issues.

None yet

2 participants