Edited at

Place Holder 構文をJuliaのマクロで書く


Scala のPlace Holder構文

「Scalaでカリー化はあまり役に立たない」

という水島さんの記事を読み、本題とはあまり関係なくScalaのプレースホルダー構文が便利そうだと思った。

List(1, 2, 3, 4).map(add(2, _))

要するに、アンダースコアで書かれた特殊な変数を引数とみなして、それを含む式をクロージャとしてくくりだす、ということなのだろう。どの範囲をくくりだすのかというのを考え出すと、実は相当セマンティクス的に微妙な気もしないでもないが、便利っぽい。

ちなみに同じような式をどこかで見たな、と思ったらC++のBoostのLambdaだった。

後に正式に採用されたLambdaとは違って、BoostのLambdaはこんなふうに書く。

for_each(a.begin(), a.end(), std::cout << _1 << ' ');

std::cout << _1 << ' 'の部分が匿名関数になっている。これもすごいよな。


Juliaのマクロで実現

同じものは無理だけど、マクロを使えばそれっぽいものは作れそうなので書いてみた。要するに

@ph(2+_)

(sym) -> (2+sym)

に展開されればいいわけだ。symは適当なシンボルを作る。

まずは構文木の中の_を、指定したシンボルに置き換える総称関数を書く。3つのメソッドで構成されていて、再帰的に構文木を下ってコピーしながらアンダースコアをシンボルに置き換える。

replaceUnderScore(a::Any, sym::Symbol) = a

function replaceUnderScore(a::Symbol, sym::Symbol)
a == :_ ? sym : a
end

function replaceUnderScore(e::Expr, sym::Symbol) Expr
e = copy(e)
for i in 1:length(e.args)
e.args[i] = replaceUnderScore(e.args[i], sym)
end
e
end

でこれを使うマクロを書けばいい

macro ph(expr::Expr)

sym = gensym()
replaced = replaceUnderScore(expr, sym)
:(($sym) -> $replaced)
end

試してみよう。

julia> x = 10

julia> @ph(x+_)(1)
11

とりあえず動いたようだ。いろいろ抜けがある気がするけど。。


まとめ

これを使うとScalaの

List(1, 2, 3, 4).map(add(2, _))

は次のように書ける。

[1, 2, 3, 4] .|> @ph(2 + _)

.|>というのは、パイプライン演算子|>.でブロードキャストしたものだ。

この例の場合はブロードキャストできれいに左から右に書けるけど、フィルタリングとかはかけそうにないな。なにかうまい書き方がないだろうか。