How often do you use the |> operator?

I also use it mostly interactively, almost always specifically as |> clipboard or |> plot.
I occasionally use it with broadcasting, values .|> callables, since callables.(values) doesn’t broadcast over callables. But it’s not a very common thing to do.


Never. I am fine with nested functions, or in source code

result1 = function1(input_data) # of course with functions and ...
result2 = function2(result2)    # ... variables given meaningful names
a = gpu(result2)

Better locations for stack traces, more understandable code.


I use it almost always (in REPL but mainly in source code), and nested functions are extremely rare. To allow that, I use heavily the Pipe and Chain modules.

I use classical syntax if there are at least 2 arguments with only one function:

JSON.print(file, data, 4)
result = expression |> JSON.parse

and I use nested functions sometimes to have some symmetry:

for (position, item) ∈ enumerate(items)

but I already surprised myself with:

for (position, item) ∈ items |> enumerate

For example, I really prefer this syntax over the classical one:

@pipe data |> filter(!isnothing, _) |> join(_, " ")

So, from the day I discovered this operator, its usage has grown continuously to become a typical trait of my own coding style… I must say that I don’t like parentheses and braces a lot, especially in streaks, even worse if there are trailing arguments!

It is also related to the way I read my code, and usually it comes from the start to the end (it is reversed with nested functions) so having the pipe operator allows me to follow the same way what happens with successive elements…


I use it a lot for chaining data transformations. Outside of that I spontaneously use it at the REPL.


I use it in the REPL as @yha. When writing code, I prefer to be more explicit as mentioned by @Tamas_Papp (easier to debug), or when suitable, use Chain.jl:


For chains of function calls, I often write two versions of the code: one with |> and one without. Then I keep the one that looks most readable. At a guess I’d say I keep |> 30% to 50% of the time.

I’ll probably use it more often if/when Julia finally supports the _ syntax for partial function application, since that would increase the number of cases where |> makes the code more readable. (There are packages like Pipe.jl and Chain.jl to help with that but the readability improvement doesn’t always outweigh the cost of an extra dependency.)


What I found using Chain.jl, was that it’s nice to have an almost normal-looking block of code, just that I’m spared from coming up with intermediate variable names. So I think that communicates quite well to a reader that you’re looking at a sequence of transformations where only the end result matters. I still don’t use it in packages, because the gain seems too low for another dependency, and also you’d have to decide a consistent style. You can’t switch all the time between normal function application and chaining or it will confuse people.


Personally I use the pipe operator almost all the time, especially in conjunction with Underscores.jl or Chain.jl.

When developing, I’l have a jupyter notebook cell with something like x |> f, tweak it until f does what I expect, then look at x |> f |> g, etc. This workflow also makes it easy to delete or comment out certain lines. I also really like reading left to right or top to bottom, as opposed to right to left with nested functions.


I believed, and the answers you received convinced me more, that the reason the |> operator is not widely used in Julia is mostly related to the fact that the base operator is pretty limited, and to have convenient implementations one has to use packages as Pipe.jl or Chain.jl, and at that time people don’t bother…


Do these chaining operations hurt performance?

No. Not unless the compiler completely gives up, in which case, you have bigger problems.


Do you have a reference supporting this claim? I remember chaining operators in R and Python slow down codes.

A lot of things hurt performance in R and Python because they don’t have optimizing compilers, or when you use a compiler, the language is too dynamic in the wrong ways to allow much optimization

julia> f(x) = x |> identity |> identity |> identity |> y -> y + 1
f (generic function with 1 method)

julia> code_llvm(f, (Int,); debuginfo=:none)
define i64 @julia_f_355(i64 signext %0) {
  %1 = add i64 %0, 1
  ret i64 %1
julia> let x = Ref(1)
           @btime $x[] + 1
           @btime f($x[])
  1.299 ns (0 allocations: 0 bytes)
  1.299 ns (0 allocations: 0 bytes)

Things like zero-runtime-cost abstractions are a core feature of julia. I guess I sometimes forget this even needs to be mentioned.


One thing I really like about Scala is that it has underscore syntax for anonymous functions, which makes piping or chaining much easier. Of course, in Julia you can do this with any one of several packages, but you still have to call the macro each time, which adds some friction.

Compare these two out of the box examples:

val z = (1 to 3)
  .map(_ + 1)
  .map(math.pow(_, 2))

z = 1:3 .|> 
  (x -> x + 1) .|>
  (x -> x ^ 2)

Julia’s syntax wins on many fronts, but the slightly clunky anonymous functions really hurt function chaining/pipelining, especially when you have functions of multiple arguments.


It’s coincidental, but I use it quite a bit in Unitful: (u::Units)(q::Quantity) is defined such that 25u"kg" |> u"me" converts 25 kilogram into electron masses. But that’s more Unitful hijacking the notation.


I use it very rarely.
Basically only in the REPL because I have issues with my home/end keys.

I used to use it a fair bit, then I spent time around @ararslan.
Now I am convicted by their argument that it makes for harder reading in a lot of places as the function that controls the type of the return values is furthers from the assignment.

Though I do appreciate that it does work well for a pipeline like approach common to some workflows.
The distant function thing + just cognitive overhead of multiple ways to write the same thing means it isn’t worth it for me.
But it might be for others.


In response to the home/end keys. Do you know that CTRL+A and CTRL+E jump respectively backward and forward on the line? This works in shells and the Julia REPL.


cough, à la emacs, cough cough


The form above has a significant weak point of readability in the chaining variables, sometimes I use it with indentation vertically aligning the intermediate results variables, so for example seems you (1+11=author+upvoters) would have noticed that the given example accidentally do not chain result1 to result2, here is adjusted with my preferred readability.

# coder is not drunk, keep it aligned also vertically
                            result1 = function1(input_data)
        result2 = function2(result1)
a = gpu(result2)
1 Like

Good catch! In real life-examples I try to use descriptive variable names though, so typos like this are less of a concern.

1 Like