dart-lang / dart-lang/language

Pipeline-operator for inline invocation of static functions.

Open
#43 7 comments 18 reactions 1 assignee Claimed by @lrhn View on GitHub
feature state-backlog
Dominant language
TeX
Stars
2.9k
Forks
239
Avg merge
2d 18h
Merged PRs (30d)
14

Description

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 "pipeline" operator (e.g., [JavaScript strawman](https://github.com/tc39/proposal-pipeline-operator), [F#](https://msdn.microsoft.com/en-us/visualfsharpdocs/conceptual/operators.%5b-h%5d-%5d%5b't1,'u%5d-function-%5bfsharp%5d)) 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:

```F#
let double(s: string) = string + string
"42" |> double |> printfn // prints "4242\n"
```

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 writing `e1 |> foo.bar`. Does it mean `foo(e1).bar` or `foo.bar(e1)`?
So, it is probably easier to parse if we require parentheses.

Example: `something|>function().foo` is the same as `function(something).foo` (except that `something` is evaluated before `function`, we do want a consistent left-to-right evaluation). Likewise `something|>foo.bar()` is similar to `foo.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 as `foo(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:
```dart
String double(String x) => x + x;
var x= someList.map((x) => "$x").firstWhere(someTest)|>double().indexOf("foo");
```

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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.