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

Comments & Questions on "Why Y?" #134

norswap opened this issue Apr 3, 2019 · 0 comments


Copy link

commented Apr 3, 2019

Hey Reginald!

I've been reading "Why Y? Deriving the Y Combinator in JavaScript" with great pleasure. I was somewhat familiar with the concepts, but following through really helped them click. Thank you!

My process of understanding did involve tinkering with some code, and it seemed to me that the code for the various combinators was at times less simple than it could have been.

For instance, your formulate the why bird as:

const why = fn =>
    maker =>
        (...args) => fn(maker(maker), ...args)
    maker =>
        (...args) => fn(maker(maker), ...args)

Why not opt for the more intuitive:

const why = fn =>
    const wrapper = (...args) => fn(wrapper, ...args);
    return wrapper;

I do understand the interest of presenting the first one, it does help with understanding the classical approach based on lambda-calculus which enables recursion without names (the real mind-bender here), but I'd much rather have the second one in my actual code!

In fact, I found it easier to understand the anonymous version by working backward from the version that uses the wrapper binding.

You do say that of a combinator that "It cannot create a named function expression." and "It cannot declare any bindings other than via parameters." But since you relax some other requirements for the idiomatic version, surely those could be relaxed too in practice.

The difference is even more dramatic regarding the long-tailed widowbird/trampoline:

const longtailed =
  fn =>
    (...initialArgs) => {
      let value =
        (x => x(x))(
          maker =>
            (...args) =>
              fn((...argsmm) => new Thunk(() => maker(maker)(...argsmm)), ...args)

      while (value instanceof Thunk) {
        value = value.evaluate();

      return value;

My simplified version:

const longtailed = fn => (...args0) =>
    const wrapper = (...args) =>
        new Thunk(() => fn(wrapper, ...args));

    let value = fn(wrapper, ...args0);

    while (value instanceof Thunk)
        value = value.evaluate();

    return value;

Finally, a question, just to be sure I understood correctly. You write:

Let’s begin our cleanup by moving Thunk inside our function. This has certain technical advantages if we ever create a recursive program that itself returns thunks.

Do you mean that we want to distinguish the thunks returned by a why-form function passed to to the trampoline from the thunk that the trampoline uses internally?

Thanks again, and cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
None yet
1 participant
You can’t perform that action at this time.