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にも貼っておきます。