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

Sequencing combinator (not S-combinator) #173

Closed
rockymadden opened this issue Oct 19, 2017 · 10 comments
Closed

Sequencing combinator (not S-combinator) #173

rockymadden opened this issue Oct 19, 2017 · 10 comments
Assignees
Milestone

Comments

@rockymadden
Copy link
Collaborator

The seq combinator is used to loop over a sequence of functions. It takes two or more functions as parameters and returns a new function, which runs all of them in sequence against the same value. This is the implementation:

const seq = function(/*funcs*/) {
    const funcs = Array.prototype.slice.call(arguments);
    return function (val) {
       funcs.forEach(function (fn) {
          fn(val);
       });
    };
};

With it, you can perform a sequence of related, yet independent, operations. For instance, after finding the student object, you can use seq to both render it on the HTML page and log it to the console. All functions will run in that order against the same student object:

const showStudent = R.compose(
  seq(
    append('#student-info'),
    consoleLog),
  csv,
  findStudent));

The seq combinator doesn’t return a value; it just performs a set of actions one after the other. If you want to inject it into the middle of a composition, you can use R.tap to bridge the function with the rest.

From page 114 of Functional Programming in JavaScript

@char0n
Copy link
Owner

char0n commented Oct 19, 2017

Ramda gives us limited S-combinator via ap. Do you really think that S-combinator is practical to have in the library ? I can achieve the same effect using tap.

const showStudent = R.compose(
  tap(append('#student-info')),
  tap(consoleLog),
  csv,
  findStudent));

But I guess the selling point for it is its declarativness. It logically groups related operation into one sequence. You know what ? Let's go for it, just for having a reference implementation of S-combinator as a part of ramda-adjunct. But pls don't use Luis Atencio's implementation. Use utils that are already part of ramda or ramda-adjunct to compose the s-combinator.

@rockymadden
Copy link
Collaborator Author

Yep, it's a complete convenience because, as you stated, it is possible to accomplish already.

@char0n
Copy link
Owner

char0n commented Nov 27, 2017

@rockymadden wanna go for this one in v2.1.0 release ? ;]

@rockymadden
Copy link
Collaborator Author

rockymadden commented Dec 21, 2017

@char0n I've been on back-to-back on call rotations and then slammed. I am interested in implementing this and other combinators for sure. I end up using combinators more and more as I solve problems by reducing the problems into common patterns. I fairly often find that these functions I wrote are indeed combinators I just didn't know already existed. While I can't speak for anyone else, I'd love to have a home for these combinators that are not otherwise covered in Ramda (or are further refinements/extensions of existing Ramda functions). I think the author is wrong, this is not the S-combinator as it is defined in combinatory logic. Sanctuary used to have it. I need to dig in more why it was removed (renamed?). The S-combinator being something along the lines of:

const S = R.curry((f, g, a) => R.converge(f, [g, R.identity])(a));
// OR point-free
const S = R.curry((f, g) => R.converge(f, [g, R.identity]));

@char0n
Copy link
Owner

char0n commented Dec 21, 2017

More info here: https://gist.github.com/buzzdecafe/6261503

@rockymadden
Copy link
Collaborator Author

rockymadden commented Dec 21, 2017

His opinion is there for sure 😆. I think you could argue the same for many functions that refine behavior of more generic functions. I at least use the S combinator semi-regularly, I only didn't realize I was using it until a while back. Why is it named that? Because in combinatory logic it is called that and it adheres to those laws. The name is weird, but you could also say the same of fantasy land laws and other things are weird. Not super approachable, I get. I like how the I combinator is approachably called identity in Ramda, so I appreciate there is another path.

@char0n
Copy link
Owner

char0n commented Dec 21, 2017

Ok as far as I understand, Luis Atencios explanation is incorrect ? It seems like that this is the case. S-combinator works a bit different than Luis implemented it. Or in different words: he implements only subset of what S-combinator should be ? Anyway I would like to invite Luis to participate in this debate so that we get it right. And it also seems that we already have an S-combinator in ramda implemented as ap:

R.ap(R.concat, R.toUpper)('Ramda') //=> 'RamdaRAMDA'

This works exactly as you described the S-combinator.

@rockymadden
Copy link
Collaborator Author

rockymadden commented Dec 22, 2017

Yep, ap is the S-combinator I am speaking of and got "replaced" in Sanctuary as ap as well (sanctuary-js/sanctuary@7e54645). A lot of the combinators in Sanctuary 0.11 were renamed to more friendly/ramda-like things in 0.12. Interesting.

@rockymadden
Copy link
Collaborator Author

rockymadden commented Dec 22, 2017

I think I found it. It is not the S-combinator as is stated in the heading in the book (which matches the title of this issue). It is the sequencing combinator! Look under section 2.7.

A primitive implementation being:

const seq = curry((x, y) => converge(always(undefined), [x, y]));
seq(_ => console.log('1st: ', _), _ => console.log('2nd: ', _))(0);

Heck, I guess you could even do:

const seq = converge(always(undefined));
seq([_ => console.log('1st: ', _), _ => console.log('2nd: ', _)])(0);

I don't see it explicitly stated in the Ramda docs that the branching functions are evaluated in order, but looking at the source it seems safe to rely upon. It handles for a few edge cases by using coverage and aligns tightly with Ramda behaviors. Try running seq where the branching function(s) arity is greater than the number of passed args. Tada:

seq([(a, b) => console.log('1st: ', a, b), (a, b) => console.log('2nd: ', a, b)])(0);
seq([(a, b) => console.log('1st: ', a, b), (a, b) => console.log('2nd: ', a, b)])(0)(1);

Yes, yes we could clean up all those consoles with invoker and __ :)

@char0n
Copy link
Owner

char0n commented Dec 22, 2017

Ok nice research. So there are two combinators, S-combinator and Sequencing combinator. I see them as two completely different things. Let's implement the sequencing combinator here and heal with the rest in #250

@char0n char0n changed the title Sequence (S-combinator) Sequencing combinator (not S-combinator) Dec 22, 2017
@char0n char0n added this to the v2.3.0 milestone Dec 31, 2017
char0n pushed a commit that referenced this issue Jan 5, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants