Underscores.jl icon indicating copy to clipboard operation
Underscores.jl copied to clipboard

Feature Request: if no `__` is specified then insert into first argument

Open xiaodaigh opened this issue 5 years ago • 9 comments

Currently

@_ a |> f(_, b) is needed but in other tools like Lazy.jl and R's magrittr which allows the first argument to be the previous one without _ e.g.

@_ a |> f(b) would be equivalent to f(a,b) and @_ a |> f(_, b)

xiaodaigh avatar Aug 15 '20 05:08 xiaodaigh

I think being explicit is a virtue of this package. Automatically inserting __ as the first (or should it be last?) argument would also make it impossible to chain functions which do already return a closure:

g(x) = begin y=exp(x); z -> z .* y end
@_ data |> filter(abs(_)>1, __) |> g(x) |> scatter

(Note that @_ a |> f(_, b) means a |> f(identity, b), but I assume you meant @_ a |> f(__, b) with two underscores.)

mcabbott avatar Aug 15 '20 10:08 mcabbott

Interesting argument. But g(x)() would work. And also it's more often the case that we need _ as first argument then it is to get a closure as return from a function.

xiaodaigh avatar Aug 15 '20 11:08 xiaodaigh

Ah right, there's a way... and I guess |> g(x)(__) |> scatter would likewise be the most explicit way to write my example today.

Do you have examples of where you'd want first rather than last-argument __? The tests have many map / sum / filter (last) and one sort (only).

mcabbott avatar Aug 15 '20 11:08 mcabbott

The way that Lazy.jl deals with it is that

@> and @>> are for first and last. Perhaps @_ will optionally allow _ anywhere and also have a @_last macro?

Also

@_ obj begin
  function1
  function2
  function3
end

without needing |> would be nice.

xiaodaigh avatar Aug 15 '20 12:08 xiaodaigh

Overall I think this is an interesting idea, and might resolve some of the uglyness of __. I think the rule might be:

  • For Expr(:call) on the right hand side of an |> expression, if __ doesn't appear in the call, insert it as the last argument.

Thus, we'd have the following work:

@_ data |> filter(abs(_)>1) |> map(_+1) |> scatter

If there's no piping present, no __ would be inserted.

The need to explicitly use __ for use with functions-which-return-functions is unfortunate, however, and would make Underscores.jl unattractive for use with libraries which are designed with currying in mind. @tkf — that's probably your case case?

Another option could be to... just add more underscores, and have a macro @__ which does this transformation, leaving @_ as it is?


It's worth keeping in mind that this package is not just about pipelines, though that's one of the nice use cases for it (I think where you mention _ above, you really mean __.)

without needing |> would be nice.

Personally I don't like the implicit chaining syntax in Lazy.jl at all so I'd rather not emulate it!

c42f avatar Aug 27 '20 06:08 c42f

Yeah, transducers are always "curried". So, we'd need extra () like @_ collection |> Filter(abs(_) > 1)() |> sum if __ is automatically inserted.

tkf avatar Aug 27 '20 07:08 tkf

@xiaodaigh I still think this is a good idea; we'd just need to modify it a little.

What if we implemented a @__ macro which followed the rule I laid out in https://github.com/c42f/Underscores.jl/issues/17#issuecomment-681630491 ? Would that serve your use case?

c42f avatar Sep 03 '20 05:09 c42f

A bit crazy idea is to use some infix combinator like

julia> f $ args::Tuple = x -> f(args..., x)
       f $ a = x -> f(a, x)
$ (generic function with 2 methods)

julia> @_ -3:3 |> filter$(abs(_)>1) |> map$(_+1) |> collect
4-element Array{Int64,1}:
 -2
 -1
  3
  4

Though it's not ideal that f$(x) may or may not splat x when its type is unknown. You'd have to write f$(x,) or f$(x...,) to be very specific.

tkf avatar Sep 06 '20 18:09 tkf

I've found a couple of uses of @_ in the last few weeks while where having __ as the last parameter wouldn't work. For example

@_ read(`git status --porcelain`) |> String |> split(__,'\n')

c42f avatar Sep 09 '20 00:09 c42f