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

Request: Add pipeline operator #165

Open
damon-kwok opened this issue May 4, 2020 · 11 comments
Open

Request: Add pipeline operator #165

damon-kwok opened this issue May 4, 2020 · 11 comments

Comments

@damon-kwok
Copy link

damon-kwok commented May 4, 2020

Pipe Operator

The pipe operator |> passes the result of an expression as the first parameter of another expression.
The pipe operator |>> passes the result of an expression as the last parameter of another expression.

Example

primitive Str
  fun trim (s: String): String =>
    ...

  fun upper (s: String): String =>
    ...
  
 fun print(s: String, env: Env) =>
   env.out.print(s)

primitive Trace
 fun print(env: Env, s: String) =>
   env.out.print(s)

actor Main
  new create(env: Env) =>
    let s = " I Love Pony "
    s |> Str.trim |> Str.upper |> Str.print(env)    //"I LOVE PONY"
    s |>> Str.trim |>> Str.upper |>> Trace.print(env)    //"I LOVE PONY"
@SeanTAllen
Copy link
Member

Can you provide some motivation for why this would be desirable? Every RFC should contain some motivation as to why it is desirable. Even requests.

Please note in case you are aware. Opening an issue such as this is a request for another human (other than you) to write up the full RFC so the more details you provide them, the better.

@damon-kwok
Copy link
Author

damon-kwok commented May 5, 2020

What's the pipeline operators ?

The pipeline operator |> and |>> pipes the value of an expression into a function. This allows the creation of chained function calls in a readable manner. The result is syntactic sugar in which a function call with a single argument can be written like this:

let url = "%21" |> decodeURI()

The equivalent call in traditional syntax looks like this:

let url = decodeURI("%21")

The pipe operator |> passes the result of an expression as the first parameter of another expression.
The pipe operator |>> passes the result of an expression as the last parameter of another expression.

Why need pipeline operators ?

Pipe Operator allows a clear, concise expression of the programmer's intent.
Example :

fun inc[A: (Real[A] val & Number) = I32](x: A): A => x+1
fun double[A: (Real[A] val & Number) = I32] (x: A): A => x*2
fun print[A: (Real[A] val & Number) = I32](x: A) => _env.out.print(x.string())

// without pipeline operator
let x: I32 = 3
let x'= inc(x)
let x''= double(x')
print(x'') //8

// with pipeline operator
3 |> inc() |> double() |> print() //8

// without pipeline operator
print(add(1,double(inc(double(double(5)))))) // 43

// with pipeline operator
5 |> double() |> double() |> inc() |> double() |>> add(1) |> print() // 43

// without pipeline operator
let arr: Array[I32] ref = [1;2;3;4;5]
let arr2 = Seqs.map(arr, Num.mul(2))
let arr3 = Seqs.reverse(arr2)
let arr4 =Seqs.join(arr3, "=")
lSeqs.trace(arr4)
//prints:
"10=8=6=4=2"

// with pipeline operator
[1;2;3;4;5]
  |> Seqs.map(Num.mul(2))
  |> Seqs.reverse()
  |> Seqs.join("=")
  |> Seqs.trace()
//prints:
"10=8=6=4=2"

// without pipeline operator
let str = "abc\ndef\nghi"
let arr = Str.lines(str) // ["abc"; "def"; "ghi"]
let arr2 = Seqs.map(arr, Str.upper) // ["ABC"; "DEF"; "GHI"]
let arr3 = Seqs.map(arr, Str.reverse) //["CBA"; "FED"; "IHG"]
let path = Seqs.join(arr3, "/") // "CBA/FED/IHG"
let path_win = Str.winpath(path) // "CBA\\FED\\IHG"
let dirname = Str.dirname(path_win) //"IHG"

// with pipeline operator
"abc\ndef\nghi"
  |>Str.lines()
  |>Seqs.map(Str.upper)
  |>Seqs.map(Str.reverse)
  |>Seqs.join("/")
  |>Str.winpath
  |>Str.dirname  //prints: "IHG"

The return value of each function is used as the first argument of the next function in the pipe. It's a beautiful expression that makes the intent of the programmer clear.

How to implements pipeline operators ?

I do n’t have much say on how to achieve it, but only contribute a possible implementation method, just for reference:

primitive Num[A: (Real[A] val & Number) = I32]
  fun inc(x: A): A => x+1
  fun double (x: A): A => x*2
  fun add(a: A, b: B): A => a+b
  fun print(x: A, env: Env) => env.out.print(x.string())
  fun trace(env: Env, x: A) => env.out.print(x.string())

Design the pipeline as a Partial Application syntactic sugar, |> will automatically wrap the first parameter|>> automatically wrap the last parameter

3 |> Num~inc() |> Num~add(3) |>> Num~trace(env)

@damon-kwok
Copy link
Author

damon-kwok commented May 5, 2020

Here are some pipeline usage in other languages:
Elixir pipeline:
https://elixirschool.com/en/lessons/basics/pipe-operator/

Javascript Pipeline:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Pipeline_operator

React Pipeline:
https://simon.lc/use-new-pipeline-operator-with-react-hocs

Clojure 's pipe : Threading macros
https://clojure.org/guides/threading_macros

Ruby 2.7: The Pipeline Operator
https://dev.to/baweaver/ruby-2-7-the-pipeline-operator-1b2d

Emacs Lisp:
https://github.com/magnars/dash.el#threading-macros

F# pipeline:
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/functions/#function-composition-and-pipelining
https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/prim-types.fs#L3412

ReasonML pipeline:
https://reasonml.github.io/docs/en/pipe-first

TypeScript pipeline:
https://github.com/Dabolus/typescript-pipeline-operator/blob/master/pipeline.ts

Python pipeline:
https://pypi.org/project/pipe/

Haskell pipeline:

(|>) :: a -> (a -> b) -> b
(|>) x f = f x

Bash pipeline:
https://www.gnu.org/software/bash/manual/html_node/Pipelines.html

Rust pipeline:

use std::ops::Shr;

struct Wrapped<T>(T);

impl<A, B, F> Shr<F> for Wrapped<A>
where
    F: FnOnce(A) -> B,
{
    type Output = Wrapped<B>;

    fn shr(self, f: F) -> Wrapped<B> {
        Wrapped(f(self.0))
    }
}

fn main() {
    let string = Wrapped(1) >> (|x| x + 1) >> (|x| 2 * x) >> (|x: i32| x.to_string());
    println!("{}", string.0);
}
// prints `4`

Julia pipeline:

julia> Base.|>(xs::Tuple, f) = f(xs...)
|> (generic function with 7 methods)

julia> let
           x = 1
           y = 2

           # just messing around...
           (x, y) |> (x, y) -> (2x, 5y) |>
                     divrem             |>
                     complex            |>
                     x -> (x.re, x.im)  |>
                     divrem             |>
                     (x...) -> [x...]   |>
                     sum                |>
                     float

       end
0.0

CommonLisp:
https://github.com/nightfly19/cl-arrows#examples
https://github.com/hipeta/arrow-macros#examples

C#
http://www.mkmurray.com/blog/2010/09/06/f-pipeline-operator-in-c/

static class Extn {
public static U Then<T,U>(this T o, Func<T, U> f) { return f(o); }
public static U Then<T, U>(this T o, Func<T, Func<U>> fe) { return fe(o)(); }
}which lets you write the more-readable:

var blah = "576 "
.Then<string,string>(x => x.Trim)
.Then(Convert.ToInt32);

C++ pipeline:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3534.html

C++20 pipeline:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2011r0.html

auto dangerous_teams(std::string const& s) -> bool {
    return s
         | views::group_by(std::equal_to{})
         | views::transform(ranges::distance)
         | ranges::any_of([](std::size_t s){
                return s >= 7;
            });
}

@jemc
Copy link
Member

jemc commented May 5, 2020

I am in favor of this 👍

@SeanTAllen
Copy link
Member

I'm not in favor, but I'm also not super opposed. I'm mostly ¯_(ツ)_/¯.

@jemc
Copy link
Member

jemc commented May 5, 2020

We discussed this briefly in today's sync call.

We agreed that we want to continue discussing here in this issue ticket and in Zulip to try make sure that what we come up with is as useful as we can make it.

@SeanTAllen mentions that he is wanting us to try to come up with something that will solve problems more broadly than this current proposal does.

I mentioned that I'm generally in favor of things like this if they solve some problems and mostly stay out of the way when you don't need them. But of course if we can solve more problems with a different proposal, I'm likely in favor of that.

Others mentioned that they can remember wanting something like this in Pony at various points in time.

@jemc
Copy link
Member

jemc commented May 12, 2020

Discussed in today's sync call:

  • If we end up with multiple pipeline operators, we have to explicitly talk about operator precedence (Sylvan mentioned being against precedence differentiation, which would require parentheses, which I think would really break up the pipeline in undesirable ways) so that may be another reason to not prefer multiple pipeline operators, on top of Sean already not liking having multiple operators and wanting to find a better way to target position.

  • Sylvan suggested lambda-wrapping any pipeline steps that need to juggle the value to a different argument position.

@jkankiewicz
Copy link

I'm not in favor, but I'm also not super opposed. I'm mostly ¯_(ツ)_/¯.

@SeanTAllen, if you have 4½ minutes to spare, Dave Thomas makes a compelling argument for the pipeline operator in his presentation "Think Different".

@jkankiewicz
Copy link

Others mentioned that they can remember wanting something like this in Pony at various points in time.

@sylvanc suggested the addition of a pipeline operator in PR #4.

@aturley
Copy link
Member

aturley commented Aug 11, 2020

Let's get more comment on this and see where we are next week. If anybody is interested in writing an RFC (@damon-kwok, @jkankiewicz?) please feel free to do that. If you have any questions about doing that please ask for help in this issue.

@igotfr
Copy link

igotfr commented Jun 6, 2021

a |> f
// equivalent to
f(a)
b |2> f(a) // or b |1> f(a) if starting at 0
// equivalent to
f(a, b)
b |b_arg> f(a)
// equivalent to
f(a, where b_arg = b)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants