Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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を追加。やっぱりマクロは難しい。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away