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

Special case unary hack operator? #260

Closed
cowboyd opened this issue Jan 28, 2022 · 6 comments
Closed

Special case unary hack operator? #260

cowboyd opened this issue Jan 28, 2022 · 6 comments
Labels
follow-on proposal Discussion about a future follow-on proposal

Comments

@cowboyd
Copy link

cowboyd commented Jan 28, 2022

Just another library author here arriving here out of curiosity about what the future might look like for how to structure our APIs. Effection's use-case is pretty much the same as Rx, so I was excited to try out a potential pipeline operator.

I wanted to re-write one of our actual snippets with it. And If I understand correctly:

stream.
  .match({ topic: 'available' })
  .filter(status => ['started', 'exited'].includes(status.type))
  .forEach(function*(status) {
    yield send(status);
  }));
});

would become

stream.
  |> match({ topic: 'available' })(%)
  |> filter(status => ['started', 'exited'].includes(status.type))(%)
  |> forEach(function*(status) {
        yield send(status);
      })(%);
});

Which, like apparently most folks here, I have very mixed feelings about. I like the idea of the raw the power of the Hack pipeline, but having to manually add the placeholder to the end of each combinator in what I envision as my 95% use-case, feels decidedly meh. I find the last case, where you have a multiline operator, to be particularly cumbersome in terms of cognitive load.

That said, it would feel a lot more palatable if there were a special case added for things we know to be unary functions. What if there were a special syntax for unary function expressions; For example %expr within a pipeline could be an alias for expr(%).

stream.
  |> %match({ topic: 'available' })
  |> %filter(status => ['started', 'exited'].includes(status.type))
  |> %forEach(function*(status) {
    yield send(status);
  });
});

It's still a bit weird, but imho far more readable, and yet still retains the all the powers and advantages of the Hack proposal.

Anyhow, just one datapoint coming into a nearly five years long discussion and missing plenty of context, but I didn't see anything like this in any of the proposals, so figured I throw this out there.

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Jan 28, 2022

With the current proposal, it could become this:

stream
  |> match(%, { topic: 'available' })
  |> filter(%, status => ['started', 'exited'].includes(status.type))
  |> forEach(%, function*(status) {
        yield send(status);
      });

@cowboyd
Copy link
Author

cowboyd commented Jan 28, 2022

@nicolo-ribaudo That is one way, but then it wouldn't be a unary function, and would be incompatible with other approaches like pipe(). I was thinking about ways to have the cake and eat it too 😃

@js-choi js-choi added the follow-on proposal Discussion about a future follow-on proposal label Jan 28, 2022
@js-choi
Copy link
Collaborator

js-choi commented Jan 28, 2022

Could you clarify whether .match, .filter, and .forEach in your original example are object methods or non-method curried unary functions?

// The original post’s snippet.
stream.
  .match({ topic: 'available' }) // Is this a method call?
  .filter(status => ['started', 'exited'].includes(status.type))
  .forEach(function*(status) {
    yield send(status);
  }));
});

Assuming that .match, .filter, and .forEach are actually curried non-method curried unary functions, then—as I mentioned in #202—I am planning to pursue a proposal for a standard Function.pipe function at TC39. With that, one would write this:

Function.pipe(stream,
  match({ topic: 'available' }),
  filter(status => ['started', 'exited'].includes(status.type)),
  forEach(function*(status) {
    yield send(status);
  }),
});

Would that satisfy your use case?

@cowboyd
Copy link
Author

cowboyd commented Jan 28, 2022

Could you clarify whether .match, .filter, and .forEach in your original example are object methods or non-method curried unary functions?

@js-choi Currently, they are instance methods that are bound so that they can be freely disassociated, and they are immutable and so return a new chainable instance with every invocation.

That's what they are now, but I'm eyeing refactoring them to be free-standing functions that can be imported individually (which is how I came to be researching the pipe operator in the first place), so the form they ultimately take will depend on what happens here.

With my examples such as %match({ topic: 'available' }), I was imagining that each operator would return a normal unary function (curried or otherwise).

Would that satisfy your use case?

At first blush, yes. I'm not seeing why not, and when I read that it makes me feel like perhaps less is truly more.

@Pokute
Copy link
Contributor

Pokute commented Jan 28, 2022

There is already a suggested follow-up proposal Tacit unary function application syntax. It does everything the same as your suggestion, but with slightly different syntax.

In this case, with the help of |>> operator, your example would become:

stream
    |>> match({ topic: 'available' })
    |>> filter(status => ['started', 'exited'].includes(status.type))
    |>> forEach(function*(status) {
          yield send(status);
        });

@cowboyd
Copy link
Author

cowboyd commented Jan 29, 2022

I see, although having multiple pipe operators seems questionable to me. For example, I never could be fully comfortable with Clojure threading macros. But either way, it sounds like it's a pretty safe route to go with free standing unary functions since they'll be composable with Function.pipe (hopefully) and might eventually work with a |>> operator.

I'll close this since it sounds like there are several alternatives other than a potential unification by extending the hack syntax to allow for unary functions.

@cowboyd cowboyd closed this as completed Jan 29, 2022
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 6, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
follow-on proposal Discussion about a future follow-on proposal
Projects
None yet
Development

No branches or pull requests

4 participants