Clojure 1.4が出ました! 前々から話題になっていたReader Literalsが使えるようになったので ちょっと遊んでみました。
まずは下準備†
基本はこちらに書いてある通りです。
$ lein new myreader $ cd myreader $ vi project.clj
(defproject myreader "1.0.0-SNAPSHOT" :description "FIXME: write description" :dependencies [[org.clojure/clojure "1.4.0"]]) ; 1.4.0
readerの定義は src ディレクトリ直下に data_readers.clj というファイルを作り、そこに定義します。
手始めに文字列を大文字に変換するreaderを定義してみましょう。
src/data_readers.clj
{
upper myreader.core/upper-case
}src/myreader/core.clj
(ns myreader.core) (defn upper-case [s] `(.toUpperCase s))
ここまで書いたらreplで試してみましょう。
$ lein repl
#upper"hello, world" ;=> "HELLO, WORLD"
なおreaderの定義は data-readers にbindingすることでも定義できます。ただ当然のことながらbinding後のreadから有効になるので、それ以前のものには適用されません。
(defn upper-case2 [s]
`(str "_" (.toUpperCase ~s) "_"))
;=> #'fuga.core/upper-case2
(binding [*data-readers* {'upper fuga.core/upper-case2}]
(println #upper"hello")
;=> HELLO
(println (eval (read-string "#upper\"hello\""))))
;=> _HELLO_Gaucheのデバッグプリント†
少し使えそうなものとしてGaucheのデバッグプリント?=を実装してみましょう。
src/data_readers.clj
{
?= myreader.core/debug-print
}src/myreader/core.clj
(ns myreader.core)
(defn debug-print [x]
`(let [res# ~x]
(println "?=" res#)
res#))こんなものを定義してあげると以下のようなことができます。
(map inc #?=(range 10)) ;?= (0 1 2 3 4 5 6 7 8 9) ;=> (1 2 3 4 5 6 7 8 9 10)
ここの値がどうなってるか確認したいけど、わざわざletで囲んでprintlnするの面倒!ということは多いと思いますがこれで解決ですね!
文字列中の式を展開†
perlの "$var" 然り、rubyの "#{var}" 然り、他言語では文字列中で変数を展開する構文があります。これをClojureで実装してみましょう。
今回は文字列中のバッククオートに囲まれた部分を式として評価するように変換します。
src/data_readers.clj
{
str myreader.core/expand-sexp
}src/myreader/core.clj
(defn expand-sexp [s]
(let [ls (map-indexed #(if (even? %) %2 (read-string %2))
(str/split s #"`"))]
`(apply str (list ~@ls))))ではreplで試してみましょう。
(def i 100) ;=> #'hoge.core/i #str"i = `i`" ;=> "i = 100" #str"(+ 1 2) = `(+ 1 2)`" ;=> "(+ 1 2) = 3"
イイネ!
最後に†
使いどころはちゃんと見極めないとですが、可能性がぐんっと広がりますね! そんな感じでReader Literalsで遊んでみました。
コードは一応Gistにも貼っておきます。
