この記事は こふ語 / ブチミリ Advent Calendar 2016 の20日目の記事ですっ
ちなみにこふ語カレンダーは初投稿ですっ
アナフォリックッ
突然ですがっ アナフォリック ないし アナフォラ とは何でしょうかっ!!皆大好き weblio センセに聞いてみましょうっ
- 【修辞】 首句反復 《連続した節や語句の初めに同じ語[表現]を反復すること》.
- 【文法】 前方照応.
anaphoraの意味 - Weblio
2つの意味がありますがっ今回はコードを書くので意味的には前方照応ということになるでしょうかっ
〘言〙 〔anaphora〕 代名詞や冠詞が文中や文章中(発話中)の物事をさすこと。先に現れた物事なら前方照応,後に出てくる物事ならば後方照応という。
前方照応の意味 - Weblio
仕組みっ
実際に書いてみましょうっ
(defmacro anakov
[& body]
`(let [~'アンッ! "アンッ!"])
~@body)
(println (anakov (take 4 (repeat アンッ!)))) ; 実行すると…(アンッ! アンッ! アンッ! アンッ!)
どうして外側のスコープで アンッ!
が参照できているか説明していきますっ 実はマクロに body として渡したコードは展開時に anakov
と同じスコープに入るのですがっ その時に body の展開と同時に let のベクタの中にある ~'アンッ!
も unquote されて実行する段階で アンッ!
というシンボルが出来上がるのですっ なのでっ let のベクタの中の unquote と body に渡している アンッ!
の解決がマクロ展開時に行われっ 外側でそのシンボルを使っても問題なく振る舞われるということなのですっ 今回はっ この振る舞いを利用して簡単なこふ語ジェネレータっぽいマクロを組んでみようと思いますっ!!
レッユトヤイッ!!
(defmacro anakov
[& body]
`(let [~'アンッ! "アンッ!"
~'ゴクッ! "ゴクッ!"
~'シュッ! "シュッ!"
~'ズブッ! "ズブッ!"
~'ドピュ "ドピュ"
~'ル "ル"
~'モグッ! "モグッ"
~'ブリッ! "ブリッ!"]
~@body))
(println (anakov (take 4 (repeat アンッ!)))) ; => (アンッ! アンッ! アンッ! アンッ!)
(println (anakov (take 4 (repeat ゴクッ!)))) ; => (ゴクッ! ゴクッ! ゴクッ! ゴクッ!)
(println (anakov (take 4 (repeat モグッ!)))) ; => (モグッ! モグッ! モグッ! モグッ!)
(println (anakov (take 4 (repeat ブリッ!)))) ; => (ブリッ! ブリッ! ブリッ! ブリッ!)
(println (anakov (cons ドピュッ (take 10 (repeat ル))))) ; => (ドピュ ル ル ル ル ル ル ル ル ル ル)
一応動きますがっ take
とか repeat
とかを外側で何度も書く必要があって使いにくいですねっ ではっ これらをマクロに閉じ込めてしまいましょうっ
(defmacro anakov
[& body]
(let [f #(take (if (pos? %1) %1 4) (repeat %2))] ; 負数でシーケンスは作れないので念の為っ
`(let [~'アンッ! #(~f % "アンッ!")
~'ゴクッ! #(~f % "ゴクッ!")
~'シュッ! #(~f % "シュッ!")
~'ズブッ! #(~f % "ズブッ!")
~'ドピュ "ドピュ"
~'ル #(~f % "ル")
~'モグッ! #(~f % "モグッ!")
~'ブリッ! #(~f % "ブリッ!")]
~@body)))
(println (anakov (アンッ! 4))) ; => (アンッ! アンッ! アンッ! アンッ!)
(println (anakov (ゴクッ! 4))) ; => (ゴクッ! ゴクッ! ゴクッ! ゴクッ!)
(println (anakov (モグッ! 4))) ; => (モグッ! モグッ! モグッ! モグッ!)
(println (anakov (ブリッ! 4))) ; => (ブリッ! ブリッ! ブリッ! ブリッ!)
(println (anakov (cons (ドピュ 1) (ル 10)))) ; => (ドピュ ル ル ル ル ル ル ル ル ル ル)
おっ いい感じになってきましたっ 次は括弧を取ってちゃんと文字列にしてみましょうっ
(ns anakov.core
(:require [clojure.string :refer [join]]))
(defmacro anakov
[& body]
(let [f #(take (if (pos? %1) %1 4) (repeat %2))] ; 負数でシーケンスは作れないので念の為っ
`(let [~'アンッ! #(~f % "アンッ!")
~'ゴクッ! #(~f % "ゴクッ!")
~'シュッ! #(~f % "シュッ!")
~'ズブッ! #(~f % "ズブッ!")
~'ドピュ "ドピュ"
~'ル #(~f % "ル")
~'モグッ! #(~f % "モグッ!")
~'ブリッ! #(~f % "ブリッ!")]
(join ~@body))))
(println (anakov (アンッ! 4))) ; => アンッ!アンッ!アンッ!アンッ!
(println (anakov (ゴクッ! 4))) ; => ゴクッ!ゴクッ!ゴクッ!ゴクッ!
(println (anakov (モグッ! 4))) ; => モグッ!モグッ!モグッ!モグッ!
(println (anakov (ブリッ! 4))) ; => ブリッ!ブリッ!ブリッ!ブリッ!
(println (str (anakov (cons ドピュ (ル 10))) "ッ!")) ; => ドピュルルルルルルルルルルッ!
わおっ シンボル並べるだけでこふ語が作れてしまいましたっ
でも気をつけてっ
一見便利だったり面白そうだったりするアナフォリックマクロですがっ let のスコープを用いるということはつまりっ より外側に同じ名前のシンボルがある場合はその参照を覆い隠してしまいますっ このことをシャドウイングと言いますっ 普通にコードを書く限りは意識もしない落とし穴ですがっ マクロを書く必要が出てきた時には気をつけてくださいっ(もちろん対処法はちゃんとあるのですがっ この記事では触れませんっ ググッテッ!!)
以上になりますっ それではみなさんよいお年をっ