Yet another way to curry underscore to create anonymous fn

This is a long story and ideas/PRs have been proposed (eg. #24990), but it seems still unclear when and if such a syntax will be implemented. Combining command literals with few functions from Underscores.jl and I got a simple prototype.

julia> using Underscores: replace_

julia> import Base: @cmd

julia> macro cmd(str)
           replace_(Meta.parse(str))
       end
@cmd (macro with 1 method)

Thereafter `_[2]` becomes x -> x[2] and
`_1 + _2` results in (x,y) -> x + y.

julia> map(`_1 ^ 3 * 100 + 10*_2 + _1`, 1:3, 4:6)
3-element Vector{Int64}:
  141
  852
 2763

julia> `_^3`.(1:3) |> `_[2]`
8

more examples:

julia> using DataFrames, Chain
julia> df = DataFrame(x = [1, 3, 2, 1], y = 1:4);
julia> @chain df begin
           filter(`_.x > 1 && isodd(_.y)`, _)
           transform([:x, :y] => ByRow(`_1 *100 + _2`) => :z)
       end
1Ă—3 DataFrame
 Row │ x      y      z    
     │ Int64  Int64  Int64 
─────┼─────────────────────
   1 │     2      3    203

However we should define a macro , say @sh_cmd, for the original Base.@cmd, which produces a Cmd. How to do it in a clean way?
Also, is it worth putting this into Base or another package? Thanks.

Edit: after thinking twice, I’d like to propose f` ` instead, i.e., just define @f_cmd, whose difference with ` ` to my use case for curring underscore is negligible.

1 Like

Command literals are already widely used, and such a fundamental change would break a ton of libraries if it were introduced into Base. The earliest it could be incorporated would be v2.0, and that seems very unlikely given the existence of several packages that do the same thing without requiring breaking changes. You’re welcome to create a package that provides this macro, and your creativity is appreciated, but you’ll be facing some stiff headwinds by pirating @cmd.

4 Likes

I know it’s not good to overwrite @cmd, maybe we can do a custom command literals, like @fn_cmd, i.e. fn`_[2]` .

1 Like

This is a good idea, but the piracy makes it unlikely to work out. There’s always the possibility to do @f_str or something, but it’s a bit unfortunate that it breaks things like syntax highlighting.

Thanks for your reply and I thought twice and changed the original post.
Also, I prefer @f_cmd to @f_str because of the pairing thing, e.g.
f"startswith("Julia", _)".

Note that the { ... } syntax parses into an AST which you can use in a macro, eg @f { }. Eg PGFPlotsX.@pgf does that.

2 Likes

Why is new syntax needed instead of just defining a @qsong_str as a normal string macro?

@qsong is proposing a command macro f`...` instead of a string macro f"...". Both work the same (see the documentation) and this seems like a reasonable use of a command macro: a function is conceptually close to a command?

1 Like

I get that part, but I don’t yet see why

fn`a + b`

is better than

fn"a + b"

which we already have support for? Adding syntax is usually a tradeoff about increasing the complexity of the language (by some amount) in order to make something more convenient (by some amount), so I’d like to understand both sides of the tradeoff in this case.

Hm, this is strange. One should be able to locally shadow @cmd in your module so you don’t have to pirate Base.@cmd, but that’s not happening for me.

julia> using Underscores: replace_

julia> macro cmd(str)
           replace_(Meta.parse(str))
       end
@cmd (macro with 1 method)

julia> @which `_[2]`
var"@cmd"(__source__::LineNumberNode, __module__::Module, str) in Base at cmd.jl:426

julia> @which @cmd "_[2]"
var"@cmd"(__source__::LineNumberNode, __module__::Module, str) in Main at REPL[2]:1

Does anyone know why this is happening?

I guess @cmd has more special sauce baked in than I realized.

The parser emits @cmd as a GlobalRef for command literals, so @cmd is always looked up in Core:

julia> Meta.@dump `a`
Expr
  head: Symbol macrocall
  args: Array{Any}((3,))
    1: GlobalRef
      mod: Module Core
      name: Symbol @cmd
    2: LineNumberNode
      line: Int64 1
      file: Symbol REPL[10]
    3: String "a"

This could be changed, but not sure there is good enough reason to.

1 Like

I don’t understand your point: we already have as much support for fn`a + b` as for fn"a + b" : in both cases you just need to define a macro (fn_cmd or fn_str).

The following already works without adding new language features, you can try it:

julia> using Underscores: replace_

julia> macro fn_cmd(str) replace_(Meta.parse(str)) end
@fn_cmd (macro with 1 method)

julia> map(fn`_[2]`, [(1,2,3,4), (10,20,30,40)])
2-element Vector{Int64}:
  2
 20
1 Like

I’d never before heard of command macros outside the context of creating shell commands. So seeing something like

I would assume it was somehow interacting with the shell. A quick search doesn’t tell me much about command macros. So why would it be a better choice here than a string macro?

Also, I they are awkward to type into discourse: fn`a+b` (this almost works.)

1 Like

Yeah non-standard command literals are probably a little known feature of the language (which is no reason not to use it).

To defend its use here: I would find a string at least as strange: writing Julia code in a string (in a Julia program) really rubs me the wrong way.

But looking at the bigger picture, I think both string and command literals are the wrong solutions to the “curry” problem. I think the simple approach implemented in this PR is an excellent solution (and that it’s actually a good thing that a macro like @_ is required more complicated cases). I hope it gets adopted…

1 Like

Thanks for your replies. I guess currying underscore is like keyboard shortcut. I mean some people use it on daily basis while others might not. I have read the long discussion about the PR #24990 and it seems not the solution to Admins. Last December another idea proposed.
There are some issues when I try to use Chain.jl and Underscore.jl simultaneously. I hope this simple command literals f` ` would be helpful for others when prototyping.

I am not sure about this, I think that it is the favored solution in the long run, but some corner cases need to be hashed out first and there is no particular urgency about it.

Personally, I like that core devs are cautious and circumspect about adding new syntax. Any extension to Julia will be with us for a long time, so it is important to get it right.

6 Likes