Juliaにはパイプライン演算子があり、一引数の関数であればチェーンして書くことができる。
[1, 4, 1, 3, 2] |> sort |> unique
しかし1引数関数しか書けないというのはいかにも不便。例えば、sort
にはrev
というフラグがあって、ソートの向きを変えられるのだけど、これも使えない。
もちろん、無名関数を使えばいいのだけど、ちょっと見通しが悪い。
[1, 4, 1, 3, 2] |> x -> sort(x, rev=true) |> unique
関数がcurry化というか部分評価できればいいわけなのだけど。ここでやりたいのは、第一引数以外の引数を与えて、第一引数だけを返す関数が作れればいいので、フルのcurry化というのもやりすぎ感ただよう。
ということでマクロの勉強を兼ねて部分評価するマクロを書いてみた。
add(x)
という式に対して、
(y) -> add(y, x)
という無名関数を返してやればいいわけだ。
macro partial(x)
if typeof(x) == Expr
if x.head == :call
name = x.args[1]
rest = view(x.args, 2:length(x.args))
return esc(quote
(y) -> $name(y, $(rest...))
end)
end
end
return x
end
このマクロは、引数が関数呼び出し型の式になっていた場合に、第一引数に引数を挿入する無名関数を返す。それ以外のときはとりあえず、受け取った式をそのまま返している。
このマクロを使うと、上のサンプルはこんなふうに書ける。
[1, 4, 1, 3, 2] |> (@partial sort(rev=true)) |> unique
文字数的には無名関数を使ったほうが短いぐらいなのだけど、新しい変数を導入しなくていいという点だけはこちらのほうがマシかな。
それにしてもJuliaのマクロ、ASTが直接いじれるのはいいけど、書きにくいなあ。。マクロに限らず、パターンマッチが貧弱だからか。この辺、強化されてほしい。
追記
2019/9/15 こちらもエスケープが足りていなかった。追加した。
また、filter
などの場合は第一引数ではなく、第二引数を置き換えたい。
プレースホルダーマクロを使えばいずれの場合でも、
[1, 4, 1, 3, 2] |> (@ph sort(_, rev=true)) |> unique
のように書けるので、こちらのほうがいいかもしれない。