読者です 読者をやめる 読者になる 読者になる

真・自然言語プログラミングの"the"演算子をClojureで

clojure

みなさん、明けましておめでとうございます。今年もよろしくお願いします(遅


さて、今回のネタは"the"演算子Clojureで実装してみたという話。"the"演算子といってもCommon Lisptheではありません。
2年半ほど前に、FLTV(Future Language TV)という未来の言語を妄想するイベントがありました。そこで@kinabaさんが「Rhetorical Programming 真・自然言語プログラミング」というタイトルで発表をされていました*1。その発表は、自然言語から(構文ではなく)機能を借りてくるという発想のもと、3つの強力な機能について紹介したものでした。そのうちのひとつが"the"という演算子で、「文脈から何を指しているのか特定できる」英語の"the"から機能を借りてきたものです。発表資料では「型を1つ引数にとり現在のスコープに唯一存在するその型のオブジェクトを返す演算子」と説明されています。Clojure的に書くと、

(let [^File _ (File. "foo.txt")
      ^FileReader __ (FileReader. (the File))
      ^BufferedReader ___ (BufferedReader (the FileReader))]
  (.readLine (the BufferedReader)))

みたいなコードが動くようになるわけです。で、これの何が嬉しいかというと、変数の名前をいちいち考えなくていいよね、ということみたいです。実際には、上の例のように、_とか__といったような適当な一意な名前をつけてやる必要があって意外と面倒なんですが、これについては下のようなマクロを書いてやれば対処できます(letに関して、は)。

(defmacro anonymous-let [bindings & body]
  `(let ~(vec (mapcat #(list (with-meta (gensym) {:tag (:tag (meta %))}) %)
                      bindings))
     ~@body))

(anonymous-let [^File (File. "project.clj")
                ^FileReader (FileReader. (the File))
                ^BufferedReader (BufferedReader. (the FileReader))]
  (.readLine (the BufferedReader)))

実装

それで、theマクロの実装ですが、これは実は非常に簡単に実現できます。

(defmacro the [t]
  (first (filter #(= (:tag (meta %)) t)
                 (keys &env))))

キモはこのブログで何回か紹介しているマクロの暗黙の引数&envです。&envで受け取るローカル環境には、変数につけられたメタデータもついてくるので、theマクロの引数に与えられた型がタグとしてつけれられている変数を抜き出してくれば所望の挙動が得られます。&envすげー。

後記

なんやかんやと書きましたが、実際の着想は順序が逆で、&envとメタデータを使って何かおもしろいことができないかなぁと思っていたところに、ふと以前見た@kinabaさんの発表のことを思い出したという感じ。
僕がマクロの暗黙の引数&envと&formに関心を持っているのはなぜかというと、通常マクロではマクロの引数に与えた情報しか知ることができないのだけど、&envや&formを使うことでマクロ呼び出しの外側からマクロに情報を与えることができるからです。ただ、これを使うことでできることは確実に増えてるはずだけど、具体的にどういう場面で使うのが便利なのか未だよく分からず。今回の例はそれなりに実用できそうな応用例かなぁと思うけど、もっとおもしろい使い方を知ってる方がいればぜひ教えてもらいたいですね。

*1:ちなみに、発表資料はここからダウンロードできるみたいです。http://www.kmonos.net/pub/Presen/fltv/FLTV.pdf