Pipeline operator |>

Is there anything in Rust for chaining the operation like the pipeline operator |> used in Julia and F#

  |>(x, f)

  Applies a function to the preceding argument. This allows for easy function chaining.

  Examples
  ≡≡≡≡≡≡≡≡≡≡

  julia> [1:5;] |> x->x.^2 |> sum |> inv
  0.01818181818181818

1 Like

Nope, there's not. There was a crate though, where you can use (I guess it was) Haskell-like chaining as a "special syntax"... Cannot remember the name though...

In rust this tends to look like

let s = vec!["string1", "string2"]
    .map(String::from)
    .map(|s| s.split('-'))
    // etc...

Note that julia is doing some magic there - it knows that the fn x->x.^2 applies to the elements, and sum applies to the whole array. In haskell you'd probably (I'm not an expert) need to use the functor or applicative typeclasses when mapping the first function over the array. I wrote a blurb about these higher order constructs on urlo.

Edit There's also operator precedence to consider. In haskell, there are 2 ways of doing function application: func arg and func $ arg. The latter has much lower precedence, so you can do things like
func1 . func2 . func3 $ arg (where . is the compose operator).

4 Likes

^ This one I guess.

1 Like

I sometimes use this:

pub trait MapSelfExt: Sized {
    fn map_self<F, R>(self, f: F) -> R
    where F: FnOnce(Self) -> R {
        f(self)
    }

    fn map_self_mut<F>(mut self, f: F) -> Self
    where F: FnOnce(&mut Self) {
        f(&mut self);
        self
    }
}

impl<T> MapSelfExt for T {}
3 Likes

Yeah. The dot operator.

value
|> first()
|> second()
|> third()

is equivalent to

value
.first()
.second()
.third()

It does add type-based dispatch, which Julia does in a more general way and Elixir doesn't really have at all (no idea what F# does), but it's otherwise the same.

1 Like

How can replicate Julia code above with your proposal!

let x = vec![1,2,3].

My recommendation is not to try to do things like you would do in Julia. Better is first learning Rust to a certain extent.

As a metapher: If, for example, you learn to prepare Russian food you would first try to get somehow fluent doing it before mixing it with for instance French food subtleties. Just my 2 Cents.

2 Likes

Thanks, I replicated the Julia example as below in Rust:

let v = ([1,2,3,4,5].iter().map(|&amp;x: &amp;i32| x.pow(2)).sum::<i32>() as f32).recip();

or as:

let v: f32 = ((1i32 ..= 5).map(|x| x.pow(2)).sum::<i32>() as f32).recip();

1 Like

Couple of comments:

  • You can do |x| x.pow(2) instead of |&x: &i32| x.pow(2) because of type inference and auto-deref. (untested)

  • If you are writing this for an arbitrary integer iterator, you could do

    fn square_sum_recip(i: impl IntoIterator<Item=i32>) {
      i.into_iter()
        .map(|x| x.pow(2))
        .sum() as f32
        .recip()
    }
    

    And then pass anything resembling an iterator. (Again untested, sorry if this doesn't compile).

1 Like

I've had some fun with this:

https://crates.io/crates/pipeline

5 Likes

I'm getting a bit of a dejá vu; this style of piping and how it fits (or doesn't) in Rust has been discussed before.

To prevent us from repeating the same discussion again (although the situation may of course have changed!)

I have made an RFC (one more): https://github.com/rust-lang/rfcs/pull/2656
Please, do not hesitate to discuss my RFC there.

2 Likes