-
Notifications
You must be signed in to change notification settings - Fork 205
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
Pipeline-operator for inline invocation of static functions. #43
Comments
The main disadvantage I see is that it doesn't provide a way to disambiguate functions with the same name for different types. Besides that it appears to provide the most value for the amount of concepts/syntax necessary to learn and understand. |
I would prefer a means of currying to the option of passing additional args and requiring parenthesis. Practically by definition, I expect code using I'd rather, for the case of I actually really like this proposal because it's simple. However, seeing even my own examples of how it could be used, (and I lean functional in my style), I'm concerned about how well it fits Dart as currently designed. I'd want to see many other people also liking it too before seeing it chosen as the solution to #40. |
Also the issue of precedence is a very big one. I read
|
The issue of precedence is why I prefer to have explicit parentheses where the "LHS" is inserted. foo->bar();
foo->bar.baz();
foo->(bar()).baz(qux); becomes bar(foo);
bar.baz(foo);
(bar()).baz(foo, qux); (I use One important thing to consider about a language feature is how much it costs to work around it. If A user wants something slightly different from the most common examples, how much extra will it cost them to get there. So, for If If Both of these workarounds are annoying. None of them allow me to pass extra arguments along with By using the first argument block, |
I agree with @zoechi that the main limitation of this is that by desugaring to top-level functions, you are forced to come up with long, unique function names. That defeats much of the value of OOP-style left-to-right method invocations where you take advantage of the fact that receiver type is the namespace, and not the top-level lexical one. |
I don't know whether this is a good solution for #40, but I would really like to have this operator for another purpose. As @zoechi argued, it might not be a great fit for extension methods that I want to export for use in other libraries/packages. But it could be very helpful for local helper functions, when I want to apply a series of transformations to an object (as discussed in #166).
FWIW, I strongly dislike this syntax for several reasons.
But I really like @MichaelRFairhurst's suggestion, if I understand it correctly! First, I agree that the RHS of a pipeline operator should be a callable object, and that it shouldn't be marked with an additional pair of parens. If I want to pass additional arguments, then I should use a dedicated currying syntax (that is also valid outside of the pipeline operator). Second, the extra operator to avoid parenthesizing. If I understand you correctly, " " |> ("I don't like foo/bar/baz examples".split) |> print;
" " |> "I don't like foo/bar/baz examples".split |> print; Now, if I wanted to access a property on the result of the RHS, parenthesizing looks bad ... (" " |> "I don't like foo/bar/baz examples".split)
.where(RegExp(r'^\w+$').hasMatch)
|> print; ... but I could use the " "
|> "I don't like foo/bar/baz examples".split
|. where(RegExp(r'^\w+$').hasMatch)
|> print; Again, this is just my opinion, but I think this would be a great solution. The precedence is made clear by the whitespace around the pipeline operator, and unary postfix operators can be used in both LHS and RHS without the need to parenthesize. |
Oh, and to give an explicit example how I could pass additional arguments: Let's suppose Dart had a syntax to partially apply a function. For this example, let's use String Function(String) callback;
callback = "hello world!".replaceRange&(6, 11);
callback = (replacement) => "hello world!".replaceRange(6, 11, replacement); Then I can obviously use this mechanism in a pipeline chain: "Philipp" |> "hello world!".replaceRange&(6, 11) |> print; Partially applied functions are also useful outside of the context of a pipeline operator, so it would make sense to use the same mechanism everywhere. |
Solution to #40. This is not a full proposal, just a primer for discussion.
A very simple solution to making static functions easier to use in long chains of invocations, is to allow them to be used in-line.
The "pipelie" operator (e.g., JavaScript strawman, F#) is, at its simplest, just an infix operator taking first an object and then a unary function, and then calling the function with the object. Example:
Dart can define a similar operator (
|>
is an option, so is.>
,:>
or->
).The F# language is very functional, more than dart, and the pipe operator is one of a number of function composition operators.
In Dart, we want to allow easy chaining of object operations, so the operator should bind as tightly as
.
. That makes parsing harder if just writinge1 |> foo.bar
. Does it meanfoo(e1).bar
orfoo.bar(e1)
?So, it is probably easier to parse if we require parentheses.
Example:
something|>function().foo
is the same asfunction(something).foo
(except thatsomething
is evaluated beforefunction
, we do want a consistent left-to-right evaluation). Likewisesomething|>foo.bar()
is similar tofoo.bar(something)
- the|>
binds to the next argument block.If we always have the parentheses, then we can also add more arguments:
e1|>foo(2, 3)
is the same asfoo(e1, 2, 3)
. We could put the pre-pipe expression as the last argument instead of the first, but we want to treat the first expression like a kind of "receiver" for the function, and that object should be the one that's always required by the function, so we put it first.Both are really reasonable, we just have to pick one.
Examples:
Since the syntax is just syntactic sugar for a static function call, type inference can likely not treat the first argument different from the other arguments. Type inference for will have to proceed normally. The type of the first parameter of the function can be used as context type for the first pipe operand expression.
This is a very simple idea, compared to static extension methods (#41) or static extension types (#42).
It has the advantage of working with any static function, not just one explicitly written as an extension method. It has the disadvantage of using a different syntax to call the function.
The text was updated successfully, but these errors were encountered: