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