2
votes

I read in several places that pipes in Julia only work with functions that take only one argument. This is not true, since I can do the following:

function power(a, b = 2) a^b end
3 |> power
> 9

and it works fine.

However, I but can't completely get my head around the pipe. E.g. why is this not working?? :

3 |> power()
> MethodError: no method matching power()

What I would actually like to do is using a pipe and define additional arguments, e.g. keyword arguments so that it is actually clear which argument to pass when piping (namely the only positional one):

function power(a; b = 2) a^b end
3 |> power(b = 3)

Is there any way to do something like this?


I know I could do a work-around with the Pipe package, but to honest it feels kind of clunky to write @pipe at the start of half of the lines.

In R the magritrr package has convincing logic (in my opinion): it passes what's left of the pipe by default as the first argument to the function on the right - I'm looking for something similar.

1
Unrelated, but for such short function definitions you can use power(a, b = 2) = a^b instead of function power(a, b = 2) a^b end - Antonello

1 Answers

4
votes

power as defined in the first snippet has two methods. One with one argument, one with two. So the point about |> working only with one-argument methods still holds.

The kind of thing you want to do is called "partial application", and very common in functional languages. You can always write

3 |> (a -> power(a, 3))

but that gets clunky quickly. Other language have syntax like power(%1, 3) to denote that lambda. There's discussion to add something similar to Julia, but it's difficult to get right. Pipe is exactly the macro-based fix for it.

If you have control over the defined method, you can also implement methods with an interface that return partially applied versions as you like -- many predicates in Base do this already, e.g., ==(1). There's also the option of Base.Fix2(power, 3), but that's not really an improvement, if you ask me (apart from maybe being nicer to the compiler).

And note that magrittrs pipes are also "macro"-based. The difference is that argument passing in R is way more complicated, and you can't see from outside whether an argument is used as a value or as an expression (essentially, R passes a thunk containing the expression and a pointer to the parent environment, and automatically evaluates and caches it if you use it as a value; see substitute)