LoginSignup
4
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-08-07

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)
    esc(:(($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 + _)

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

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

追記

2019/9/5 エスケープが足りていなかったため、ローカルスコープの変数がキャプチャできないことが判明。escを追加。やっぱりマクロは難しい。

4
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2