The nature of pipes

Could I somehow pipe the output of addition to subtraction:

addition(x, y) = x + y
subtraction(a, b) = a - b

function calculation()
    addition(10, 2)
    subtraction(12)
end   

calculation()

I am used to this kind of workflow from FSharp, and Julia only tells me:
|> is not a unary operator

Something along

julia> 2 + 3
5

julia> 2 + 3 |> (r) -> r - 1
4

is what you look for?

1 Like
function calculation()
    addition(10, 2)
    |> subtraction(12)
end

Currently I believe it only works with one argument functions by default, and you would typically define an anonymous function like addition(10, 2) |> x->subtraction(x, 4) or similar.
There have been plenty of discussions on this relatively recently IIRC, with suggestions of how piping could be more general (can’t find the threads right now but I think it was on discourse), and there are packages like e.g. Chain.jl or Pipe.jl among other trying to provide a more flexible way to do this.

6 Likes

Yeah, I like this proposal here.

But I would have hoped, that there is something with a bit less boilerplate now.
Like, in the standard language.

Thanks :slight_smile:

1 Like

Those are two separate statements, which is why you get the |> is not a unary operator error. You need to put |> at the end of the first line, or surround the two lines with parentheses to make it a single expression. But of course then you get a different error:

ERROR: MethodError: no method matching subtraction(::Int64)
3 Likes

There’s also function composition with \circ<Tab> if you can define curried forms of your functions:

julia> subtr(x) = y -> y - x
subtr (generic function with 1 method)

julia> addtn(x) = y -> y + x
addtn (generic function with 1 method)

julia> (subtr(12) ∘ addtn(10))(2)
0
1 Like

my impression from the last few times this discussion happened, is that “general” underscore partial application just has too many ambiguities and conflicting design desires, and will likely never happen, so I would mostly shelve any optimism for that particular feature.

However, I (personally) would still love to see /> and />> as “pipefront” and “pipeback” operators though! until then, Chain.jl works great for me

1 Like

Yeah, I was already at that part :sweat_smile: :ok_hand:t3:

Yep, I also considered that. But the main interesting thing for me is, to pipe logically, from one function to the next.

So that the flow of the evaluation goes from one to the next, and I can line them up syntactically.

The composition operator is backwards. I like it from the left to right, how we read.
Like so:

I find that really unfortunate, particularly since Julia is otherwise a quite capable functional language, and struggles with one of the most appealing features from that paradigm.

What’s the “struggle”? There are existing solutions that have been available for many years already.
Just import a package and you are done! Surely you use packages in Julia, and adding one more for convenient piping should be straightforward.

IMO, for data processing pipelines:

  • Underscores.jl is among the least opinionated but requires more boilerplate
  • Chain.jl should be convenient when your functions typically accept “data” as the first argument and don’t use anonymous functions inside
  • DataPipes.jl (mine) is most straightforward with functions taking “data” as the last argument (very common in Julia), and still as convenient as Underscores in the general case. Also, helps with lambda-heavy pipelines.
4 Likes

Thanks a lot.

Taking data in as the last argument, is one of the things I desired, and little boilerplate as possible is essential to me, so your package seems to be quite convenient.

I actually like your implementation quite a bit :slight_smile:
And still, it seems tacked on and not really an intricate part of the language.

I don’t mind packages, but I think there should be a proper pipe as part of the standard language.

If we had PR #24990, that pipe could be written in Julia as (I’m mixing the F# and Julia function names a bit):

(
    x
    |> stringToCharList
    |> map(expectChar, _)
    |> sequenceParsers
    |> map(charListAsString, _)
)
2 Likes

or with JuliaSyntax #148, as

(
    x
    |> stringToCharList
    />> map(expectChar)
    |> sequenceParsers
    />> map(charListAsString)
)

if one prefers the visual consistency, note that all the pipes are the same for one-arg funcs so can also be written

(
    x
    />> stringToCharList
    />> map(expectChar)
    />> sequenceParsers
    />> map(charListAsString)
)
2 Likes

As an example: It seems like I need to plaster _ all over the place.

Your documentation does mention, this is only necessary when it’s putted outside the “common argument order” - And I see not a single demonstration, that goes without an underscore.

And that is not, how it’s in my FSharp example.

@CameronBieganek

Yeah, you certainly understood, what I mean. :slight_smile:
And that also sums it up: IF we had a PR, that is already stalled for almost 6 years, THEN we would have something, that still has more boilerplate :sweat_smile:

Regarding the ubiquity of underscores, it is somewhat unavoidable, because Julia uses multiple dispatch, so there are no privileged arguments. Argument number 3 out of 4 is just as important as the first argument or the last argument.

5 Likes

I have heard this oft-repeated, but I am really not convinced :slight_smile:

IME, maybe about 85% the time (so not all, but a very sizeable majority), the argument I want to pipe is one of the first or last. for one/two arg functions which are plentiful this is tautological

@ShalokShalom not to be negative, but I would give up on underscores like this being in Base, and highly recommend learning to be content with one of the existing packages/macros. like I said there is unfortunately immense controversy over what exactly the semantics should be for the general case

1 Like

And we can set at least a sensible standard (last) and then go for the underscores opt out.

Exactly. What about the other 15% of the time? Front pipe and back pipe would certainly be useful, but I think underscores fit the language better and they’re more explicit about which argument you’re piping into.

3 Likes

But why do they need to be the first tool, to reach for?

Why the most boilerplate heavy stuff as the primary solution, if that can be just as much the specialty it really is?

Doing the most common stuff the most convenient way, is an age-old way to implement things?

Like, there is currently just no reasonable default, and we could leave the underscores as they are.