-
Notifications
You must be signed in to change notification settings - Fork 110
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
Alternate syntax #4
Comments
My initial impression is that these alternatives don't quite gel with how function application usually works in JavaScript. They make more sense in languages that have currying. |
@acdlite I agree with you with respect to the Elixir-style syntax; what looks like a function call is no longer just a function call. However, I think the placeholder-style does look distinctly different enough to give the impression, "something else must be going on semantically, now". |
If it's necessary to extend to multi argument (I agree that arrow functions are easy to understand here) the # placeholder does make it clear that something new is happening. What happens if you put two placeholders? What do nested pipeline operators do with placeholders? |
@mindeavor have you looked at LiveScript? It also has support for the |
Expanding on @mindeavor's comment, LiveScript has a simple syntax for partial application: |
@eplawless Great points, this is something that would need to be worked out (assuming people are really against just using arrow functions). @seanstrom @AriaMinaei Thank you for the links to LiveScript's prior art, it's good to know that others already find the |
@mindeavor How about dashes? |
Do you mean what happens if we do: run("hello") |> withThis(1, #, #) ?
Can you provide an example of what you mean? |
@mindeavor yeah underscores could confused things, but LiveScript also implements the placeholder arguments in a way that doesn't reserve the |
@seanstrom although the moment you actually want to pass underscore for some reason, you'd have to do it with another name. It would make it harder too debug, I think |
@dralletje correct, I'm not sure if anyone has brought that to the LiveScript teams attention. It's probably one of those things where most LiveScript users use the prelude. |
@seanstrom Yes, your example is what I meant. Should the result be stored in a temporary and passed in both places? If so, can that violate the normal order of operations? Should "run" be executed twice, instead? Here's what I mean by nested pipeline operators: 1234 |> (5678 |> bar(#)) I assume it binds to its nearest ancestor pipe operator in the AST (so, the second one). Are there any problems with that? @mindeavor |
Strictly going off of LiveScript, Something like this: const add = (x, y, z) => x + y +z
var add3 = 1 |> add(2, #, #) // add(2, 1, #)
var result = add3(3) So the left hand of operator would go into the first Nested expressions like this: 3 |> (1 |> add(2, # , #)) Would most likely be evaluated by precedence from the parens from left to right I believe. |
I imagine the |
I agree that the arrow functions are explicit and clear. They seem like the most intuitive option to me, and would also allow for easy destructuring where necessary (assuming it would be necessary - I suppose the piped functions could handle accepting objects / iterables and destructure internally, as well). |
Note that function piping is just syntactic sugar, so we could bikeshed on it all day. Personally, I like the Elixir version, but then, I also write Elixir. The LiveScript version looks like it'd work too, but I'd rather see a keyword there rather than an |
What about restricting use to unary functions? Then onus would be on the programmer to partially apply, would eliminate a lot of potential cognitive overhead. |
@jasmith79 I agree, in fact that is the original proposal :) |
I'm super, super excited about having a pipeline operator, but to just throw my two cents in here, I'd prefer the Elixir-style syntax. I think the pipeline itself semantically describes enough of what is going on. I don't feel the need to be super explicit about where the left-hand value is going. It also makes it cleaner visually. The only concern obviously would be learning curve for new users (especially ones not used to that kind of magic). That's just my two cents, though. Take it or leave it :) |
Another thing that could be done is waiting for a macro system in JS in the On Mon, Dec 7, 2015 at 2:07 PM, Nicholas Kircher [email protected]
|
Perhaps more fuel to the fire: it looks like someone made an Elixir-style pipe operator pull request to CoffeeScript jashkenas/coffeescript#4144 It seems to me the programming community at large is moving towards this functional programming direction. |
Perhaps a different function definition to handle piped data (e.g. generators are From there, piped data could be accessed through a special keyword (or maybe const example = function~(arg) {
return this + arg;
};
let test = 10
|> example(5);
// test === 15 This is similar to how a native JavaScript object prototype methods would work, using the |
@jamen that's the ES7 bind operator.
Non-arrow functions can already have |
@barneycarroll, yeah that's a good point (I didn't know about the bind operator). Now that I think about it, there isn't really a way to use arrow functions with my method. However I'd like to bring up @mindeavor thoughts in the original post:
Arrow functions work perfectly then imo. Is there a reason to add alternative ways to use it if we already have a clear-cut way? const example = data1, data2 => {
return data1 + data2;
};
let test = 10
|> x => example(x, 5);
// test === 15 This seems perfectly fine. There is no abstraction because you know what data is going where, and there is no new syntax besides the pipe operator (which is a given). If you really really needed a shorthand syntax to eliminate the arrow function out of each pipe, you could just "double wrap" your function (or a different function) to return another arrow function holding the piped data: // My function:
const example = data => pipe => data + pipe;
// Converting another function into a pipe-able function:
const pow = data => pipe => Math.pow(pipe, data);
let test = 10
|> example(5);
|> pow(2)
// test === 225 I personally don't think it's good to "sugarcoat" standards when you can have a perfectly working method that's already in the current syntax. However I do understand the initial concern, I just don't think it's that big of a deal. |
My only concern with that is it's diverging from what many other languages On Tue, Dec 8, 2015 at 12:21 PM, Jamen Marz [email protected]
|
@MiracleBlue, that's a good point as well. I suppose if there isn't really a big issue to begin with (at least in my opinion), we can make it similar to how it would be done in another language, to limit the learning curve I suppose. |
Yeah I feel like that makes the most sense. At the very least, if you're On Tue, Dec 8, 2015 at 12:45 PM, Jamen Marz [email protected]
|
How does the Elixir way and the proposed Coffeescript way handle curried functions? Or any time we have a function that returns a function? For example I may have Also, what if I want to point the piped value to a certain argument slot? For example I may have functions that don't want to take the piped data as the first argument. At that point I would have to wrap my function, which is what we would do without the Elixir implementation. |
Note that multi-parameters was brought up in the Elixir project elixir-lang/elixir#1156. But it turns out it isn't a priority since the
Anything that I missed? |
Couldn't we just use a arrow function in this case? reducerFn
|> x => _.reduce(data, x, {}) That would eliminate the need for new syntax. |
@robbiespeed let takesThree = (x, y, z) => x + y * z
1 |> (10, #, 5) |> takesThree // takesThree(10,1,5) The example uses the |
@jamen reducerFn |> (data |> _.reduce(#, #, {}))
// which is actually
reducerFn |> x => (data |> y => _.reduce(y, x)) |
@seanstrom I don't think anyone should use pipe in that case... it's just a single level function call. Can you give me a case with multiple levels that wouldn't work with the proposed syntax? Modified your example:
|
reducerFn |> (data |> _.reduce(#, #, {})) I still think the arrow functions are better. How would the interpreter know what order they're in? If there is no signification and it just defaults to one way and we would have to resort to arrow functions to work around the it anyways. I think arrow functions are more practical thank placeholders in the long run. |
@robbiespeed // placeholders
someVal
|> someFn
|> myFn(otherFn, #, {})
// arrow functions
someVal
|> someFn
result |> x => myFn(otherFn, x, {})
// your syntax -- this is what I want to do essentially
someVal
|> someFn
result |> (otherFn, result, {}) |> myFn |
@jamen I believe the nearest value being piped would be applied to the nearest open slot. let add = (x, y, z) => x + y + z
3 |> (2 |> add(1, #, #)) // add(1,2,3) Though this behavior is mostly based on LiveScript's implementation of placeholder arguments. |
@seanstrom okay so in that example, it seems like you want to pass a function as an argument to a function at the end of the pipe, and that's tricky with my proposed syntax. If we said that wrapping a function in braces made it a reference rather than a function to be applied to the pipe value:
or just use arrow functions when we need to pass a function as an argument. |
@robbiespeed let addWorld = str => str + " World"
someVal |> ((addWorld("Hello") + "!"), someFn, {}) |> myFn
// myFn("Hello World!", someFn(someVal), {}) |
@seanstrom thinking about it some more I've realised my syntax would not be able to be parsed easily and optimized to es6. I think something like this would work however:
or if you simply want to pipe the previous value:
|
@robbiespeed |
I'd much rather a keyword than a placeholder. And I find the syntax: Would prefer: Not sold on using arrow syntax in place of some added syntax specific to the case. Mostly because I'm not convinced rules could be written to reliably parse it to the equivalent performance of standard nested function calls. |
Yeah i'm not convinced a keyword would be worth it, Could you clarify your second statement about parsing the arrow functions? Do you mean the performance between functions with bounded contexts (this) and normal functions? let funcOne = function (x) { return function (y) { return x + y } }
let funcTwo = x => y => x + y Like the performance between those two functions? If so I would think that would have to be an optimization by the run-time for when the function does use value
|> funcA
|> (function (result) { return funcB(otherStuff, result) })
// ---
let newFn = function (result) { return funcB(otherStuff, result) })
value |> funcA |> newFn |
@seanstrom No I mean in terms of using something like babel to compile the es7 down to es5/6, as well the fact that
Arrow functions allow for some complex things to happen. It would add unneeded complexity to a javascript engine, if it were to first check if the arrow function does complex or simple things, then optimize if it only does simple things. What would be better IMO is to define a syntax where the simple things (or what we can already achieve with nested function calls) are accounted for. |
@robbiespeed I believe you're pointing out two things there:
Is this correct? I just want to make sure I'm on the same page here. |
@seanstrom Yes to 2, but not to 1. Depending on implementation arrow functions should be just as fast as regular functions, the problem comes when your wrapping a function call in another function, it's wasted computing. Yes that was the goal, to represent a clear arguments list for the next function in the pipe to use. |
@robbiespeed Cool. I can see why an arguments tuple is better for illustrating that. Normally I would just go through currying the functions and eat the performance cost, since it would be negligible to me. But I can see why the performance cost or the added complexity in engine to optimize that would be a downer. |
I could be wrong, but my experience with writing compilers tells me the optimization would not be difficult; you would only need the extra logic when you see a pipe operator, and the transformation is entirely based on the AST. For example:
This could be optimized to:
with zero consequences. The reason a "simple" arrow function is optimizable is because the body is a single expression; in any other case, the optimization would simply be skipped. |
Furthermore, arrow functions are 'unbindable': they have no |
Jumping in without reading most of the thread, sorry: I rather like the placeholder syntax, as it seems useful when decoupled from pipeline as an extremely lightweight arrow function. That is, |
Ok, so my takeaway from this thread is that the placeholder syntax is not only unnecessary, but also a different issue (partial application). Elixir style is interesting, but I think in the long run piping to the end is more useful than piping to the beginning. I've aggregated all these ideas into a new version of the pipeline operator. Let's move our discussion to the new issue: #20 |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Although I wrote the proposal, I do recognize a potential objection: functions with multiple arguments.
Although to me using arrow functions is perfectly explicit and clear, for the sake of argument let's explore two possible alternatives to handling this situation.
1. Elixir-style Inlining
Elixir's pipeline operator is actually different than the original proposal you see in this repo (of which we will call F# style). Instead of simply calling the right-hand side with the left-hand side, it instead inserts the left-hand side as the first argument to the right:
Pros / Cons
withThis(10)
is no longer just calling that function with one argument2. Placeholder Arguments
Another alternative is to have a new syntax for "placeholder arguments" – basically, slots waiting to be filled by the next function call. For example:
Pros / Cons
Any thoughts and feedback would be greatly appreciated. Even if you are reiterating some of the points I've made, it's important to express them so we can see what the community thinks. Thanks for reading.
The text was updated successfully, but these errors were encountered: