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.
Usage
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))
Code
using MacroTools
ex = :(replacex(:X))
macro pap(ex)
has_newcol = @capture(ex, newcol_ = rhs_)
if !has_newcol
rhs = ex
end
# 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]
else
return x
end
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))
end
Expr(:call, :(=>), cols, fn)
end