前回の記事でも最後にマクロを取り扱いましたが、もう少し深堀りしてみます。今度は単純にrange!を作ってみます。rustではご存知のように 1..=nといったような形で簡単な記述ができますので基本的に必要はないです。そこでオブジェクト指向でいうポリモーフィズムをマクロでやってしまえってことです。
ポリモーフィズム系の宣言マクロ
早速range! version1を見せてみましょう。
macro_rules! range {
($end:expr) => {
1..=$end
};
($start:expr, $end:expr) => {
$start..=$end
};
($start:expr, $end:expr, $step:expr) => {
($start..)
.step_by($step)
.take_while(|&x| x <= $end)
};
}Code language: PHP (php)
こんな感じで記述できます。range!(10) で 1..=10と等価のものです。コンパイラーに通すときはこのように化けます。このくらいのマクロなら直感でも動作がわかりやすいので可読性を上げてくれますよね? range!(0,10)はこちらも 0..=10と等価のものです。次が少しややこしくって、ステップを含めてしまいます。range!(3,12,3)とすれば、いいわけです。 なんか見覚えある人もいると思いますが、pythonのrangeと同じです。 このマクロのendも正であるということがあります。
そこはマクロでも変態的な解決方法がじつはあります。
マクロでDSL風にやってみる
今度はへっ!?という解決方法になりますね。早速見てみましょうrange! version2です。
macro_rules! range {
($start:expr .. $end:expr step $step:expr) => {
($start..)
.step_by($step)
.take_while(|&x| x < $end)
};
($start:expr .. $end:expr nstep $step:expr) => {
($end..=$start)
.rev()
.step_by($step)
.take_while(|&x| x >= $end)
};
($start:expr .. $end:expr) => {
$start..$end
};
($end:expr) => {
1..=$end
};
}Code language: PHP (php)
こんなふうにするのです。おもしろいのは、range!(10), range!(3..10)や range!(3..10 step 3), range!(3..-15 nstep 3)という記述です。 ヘッ!?となりませんか?実はこれ、.. はトークンで step nstepは識別子に使ってるんです。宣言マクロで一番変態的なところなんですね。おそらく、これが使われてるコードを見たとき一瞬凝視するはずです step!? そんな予約語あったっけ?なん何これ!????って、それでも可読性を損なわずに動作は理解できるでしょう。
次の世界は一歩足を踏み込めばやばい秘密の花園です。もう引き返すことはできない世界ですよ。
良い子は真似をしてはいけません。
禁断の魔術へのいざないです。これはやり過ぎのマクロです。先に使い方をお見せします。
let v = range!(3..20 step 2 if x % 3 == 0 collect);
println!("{:?}", v);Code language: JavaScript (javascript)
これ、見覚えありません?rustではこれみればぶっ飛んでしまいますね。気がついた人は。。。そうですpythonやhaskellの内包表記風です。良い子は真似してはいけません。
| 言語 | 記述 |
|---|---|
| Python | [x for x in range(3, 20, 2) if x % 3 == 0] |
| Haskell | [x,x <- [3, 5..19], x mod 3 == 0] |
| Rust (マクロ版) | range!(3..20 step 2 if x % 3 == 0 collect) |
macro_rules! range {
(
$start:expr .. $end:expr
step $step:expr
if $var:ident $cond:tt $rhs:expr
collect
) => {
($start..)
.step_by($step)
.take_while(|&$var| $var < $end)
.filter(|&$var| $var $cond $rhs)
.collect::<Vec<_>>()
};
}Code language: PHP (php)
あまりにもrust変態マクロ道です。つまり、やりすぎな危険という世界です。 マクロといえばcommon lispの何でもできちゃうものがありますが、それを彷彿させる変態度です。読む人がネジ壊れますから絶対にやめましょう。最後に少し説明をすると、宣言マクロでは、expr、ident、tt といった指定で「ここにどんなトークンが来るか」を決めています。ident は変数名、tt は不等号や等号などを含むトークン、expr は値になる式全般を受け取るものです。実はこんな細かい指示ができるのが宣言マクロだったりします。
これで一度知ってしまいましたね?もう過去には戻ることはできなくなりました。