A simple macro to transform `newcol = fn(:col)` to `:col => fn => :newcol`; friendly with DataFrames.jl

In DataFrames.jl there are a number of places where you have use this syntax :col => fn => :newcol to compute new columns. This makes sense once you get used to it, but sometimes you want a traditional syntax of newcol = functionName(:col)

So I decided to learn a bit of metaprogramming and try my hands at writing a simple macro. I think DataFramesMeta.jl does so some transformations like this but more complete. I hope that I can contribute to that in the future.

I call the macro PairAsPipe so @pap.

Coding tips for macros more than welcome.


df = DataFrame(X = 1:3)
replacex(x) = x .* 2

# macro-less DataFrames.jl way
transform(df, :X => replacex => :newX)

# pap way with named new col
transform(df, @pap newX = replacex(:X))

# pap way without named new col
transform(df, @pap replacex(:X))


using MacroTools

ex = :(replacex(:X))
macro pap(ex)
    has_newcol = @capture(ex, newcol_ = rhs_)

    if !has_newcol
        rhs = ex

    # for obtaining symbols
    symbols = QuoteNode[]
    gen_symbols = Symbol[]
    rhs = MacroTools.postwalk(function(x)
        if x isa QuoteNode
            push!(symbols, x)
            push!(gen_symbols, MacroTools.gensym(x.value))
            return gen_symbols[end]
            return x
    end, rhs)

    lhs = Expr(:tuple, gen_symbols...)

    # the fn in
    # :col => fn
    fn = Expr(:->, lhs, rhs)

    # the (:col1, :col2) in
    # the (:col1, :col2) => fn
    cols = Expr(:vect, symbols...)

    if has_newcol
        fn = Expr(:call, :(=>), fn, QuoteNode(newcol))

    Expr(:call, :(=>), cols, fn)

This has been released as https://github.com/xiaodaigh/PairAsPipe.jl