V: Pipe operator

Created on 5 Aug 2020  路  11Comments  路  Source: vlang/v

Too many languages realize the need for a pipe operator after already existing for a while. It would be amazing if V adopted this pattern earlier rather than later.

The ECMAScript proposal currently in flight provides a good introduction to how this feature operates and why it is so helpful. The short pitch is that it gives much greater readability when chaining multiple function calls together (rather than, for example, deeply nesting the function calls).

Feature Request

Most helpful comment

I would also like to voice support for this feature. My reason for supporting this is that it makes the code read a lot better.

The rescript (different language) docs for their pipe operator contains a good explanation of why this could be useful. https://rescript-lang.org/docs/manual/latest/pipe

validateAge(getAge(parseData(person)))

becomes

 ->parseData
 ->getAge
 ->validateAge

The reason I'm referring to rescript is because their implementation allows the piped functions to accept the argument at any location using a placeholder. So if you want the output of a function to be piped as the second argument of another function, that would be possible. For example:

  -> validateInput
  -> processInput('hard-coded value', _, 'another value')  

Here, the output of validateInput gets sent as the second argument to processInput

If this is implemented, it doesn't really matter what symbol is used for piping. It could be ->, |> (my preference) or anything else. It could be whatever works best in the context of V

_Aside: If there are three things I could add in Go, it would be sum types, generics and pipe support. Out of those, V already has sum types and generics. Having pipe support would make it a no brainer (for me) to start using V once it stabilizes._

All 11 comments

That is an ugly operator, and completely contrary to everything else in V. Not sure why is is called a "pipe operator" either, since it is used for chaining multiple functions.

V already supports "chaining" function calls, by simply doing foo().bar().baz(). Example:

println('hello, world'.capitalize().trim_right('ld')+'k!')

prints "Hello, work!"

What makes it "contrary to everything else in V"? Why would something so useful in the context of UNIX pipes, Haskell, F#, OCaml, Elixir, Elm, JavaScript/TypeScript, Julia, Hack, and others not be useful in V?

The operator itself is unlike anything else in V.

Yes, Unix pipes are useful, but they will likely be done a different way.

V already supports "chaining" function calls

No, it only supports method calls. I think this was closed too soon without discussion, so I'm reopening it.

@fritzblue it would help if you gave a motivating example in V of using this feature.

Method-call order for function calls (a.k.a JS |> pipe operator proposal) is very useful for better readability of code. Besides |>, I think another syntax option is to use a colon after a closing bracket: f1(expr):f2(arg2) or ->. Languages like D (and C++ soon) just reuse . for this (https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax) - for V we probably want a distinguishing syntax.

Whichever syntax you use, the result of calling f1 becomes the first argument of f2, with arg2 the second argument (then any remaining arguments).

Currently you may say that we just add methods instead. This is not flexible for users because:

  • Users may want custom algorithms that aren't acceptable for inclusion as methods in vlib.
  • Submitting algorithms to vlib shouldn't be necessary just to be able to use method-call order syntax. Submitting pull requests takes time to clean up code, then time to review and approve. Then you might need to wait until the compiler is released and has stabilized with patch releases (e.g. if your company mandates this in its code dependency requirements). Even then your users may demand you support an older compiler that doesn't have your method you submitted.
  • Your algorithm might not work on a container in vlib but on a struct defined in another library somewhere that is no longer maintained and so it isn't accepting changes.
  • Each container type has to implement the algorithm separately. For truly generic code you only write an algorithm once. So you have to write the algorithm as a function. Also writing a collection of methods will never cover custom types you don't even know about.

I do not agree that f1(expr):f2(arg2) is useful for better readability. For me that is unreadable.
Can you provide a larger example, where this new syntax makes code easier to read in your opinion?

In the TC39 |> proposal, each intermediate function in the pipeline takes exactly 1 argument, and produces exactly one result.
As they say:

The pipeline operator does not need any special rules for functions with multiple arguments; JavaScript already has ways to handle such cases.

They use arrow functions instead:
https://github.com/tc39/proposal-pipeline-operator#functions-with-multiple-arguments

provide a larger example, where this new syntax makes code easier to read

For calling generic functions on a container which can be of any type, not just arrays:

// Method call order with -> operator
container->filter(fn(e){e > 7})->map(fn(e){e * 2})->array()

// Existing function calls
array(map(filter(container, fn(e){e > 7}), fn(e){e * 2}))

The map and filter functions work with any container and use lazy evaluation, returning an iterator that produces each element only when needed. So an array function iterates the final iterator in the chain to produce an array.

thanks

I would also like to voice support for this feature. My reason for supporting this is that it makes the code read a lot better.

The rescript (different language) docs for their pipe operator contains a good explanation of why this could be useful. https://rescript-lang.org/docs/manual/latest/pipe

validateAge(getAge(parseData(person)))

becomes

 ->parseData
 ->getAge
 ->validateAge

The reason I'm referring to rescript is because their implementation allows the piped functions to accept the argument at any location using a placeholder. So if you want the output of a function to be piped as the second argument of another function, that would be possible. For example:

  -> validateInput
  -> processInput('hard-coded value', _, 'another value')  

Here, the output of validateInput gets sent as the second argument to processInput

If this is implemented, it doesn't really matter what symbol is used for piping. It could be ->, |> (my preference) or anything else. It could be whatever works best in the context of V

_Aside: If there are three things I could add in Go, it would be sum types, generics and pipe support. Out of those, V already has sum types and generics. Having pipe support would make it a no brainer (for me) to start using V once it stabilizes._

Was this page helpful?
0 / 5 - 0 ratings

Related issues

PavelVozenilek picture PavelVozenilek  路  3Comments

vtereshkov picture vtereshkov  路  3Comments

radare picture radare  路  3Comments

ArcDrake picture ArcDrake  路  3Comments

taojy123 picture taojy123  路  3Comments