Clojureの関数の形式は実は
(defn function-name doc-string? attr-map? [parameter-list] conditions-map? (expressions))
です。 doc-string?はドキュメント、attr-map?はメタ情報です。引数の後にあるcondition-map?はなんぞやというと、関数の入出力の条件を設定できるmapです。(Clojure 1.1.0からかな?)
condition-map?の形式は
{:pre 入力値をチェックする条件S式のシーケンス, :post 出力値をチェックする条件S式のシーケンス}
です。例えば、引数が全て正でかつ、関数の結果が10より大きいという条件を付けたい場合、
(defn hoge [x y]
{:pre [(> x 0) (> y 0)]
:post [(> % 10)]}
(+ x y))
という風になります。%には返り値が入ります。
条件を満たさない場合は、java.lang.AssertionErrorが発生します。
(ちなみにleiningen1.0.1のreplのバージョンであるClojure 1.1.0-master-SNAPSHOTではjava.lang.Exceptionでした。。。これはExceptionかErrorかという大きな違いであり、明文化が待たれます)
実行してみると、
user=> (hoge 5 6) 11 user=> (hoge 0 11) java.lang.AssertionError: Assert failed: (> x 0) (NO_SOURCE_FILE:0) user=> (hoge 11 0) java.lang.AssertionError: Assert failed: (> y 0) (NO_SOURCE_FILE:0) user=> (hoge 5 5) java.lang.AssertionError: Assert failed: (> % 10) (NO_SOURCE_FILE:0)
となります。
このままだと常にチェックが走りますが、単純に関数を使う場合を分けたいことがあると思います。次のように書くと、関数の機能とチェック機能を分離させることができ、モジュラリティを高めることができます。
user=> (defn hoge [x y] (+ x y))
#'user/hoge
user=> (hoge 5 5)
10
user=> (defn with-condition [f x y] {:pre [(> x 0) (> y 0)], :post [(> % 10)]} (f x y))
#'user/with-condition
user=> (with-condition hoge 5 5)
java.lang.AssertionError: Assert failed: (> % 10) (NO_SOURCE_FILE:0)
partialで部分適用しておけば、元の関数を同じものを定義できますね。
user=> (def always-condition (partial with-condition hoge)) #'user/always-condition user=> (always-condition 5 5) java.lang.AssertionError: Assert failed: (> % 10) (NO_SOURCE_FILE:0)
エンタープライズを乗っとるには堅牢性が必要になると思います。condition-map?を上手に使うことは乗っ取りの第一歩ですね!
ちなみにここ数回のClojureネタはClojure in Action(MEAP版)を読んで、おおっと思ったことのまとめです。プログラミングClojureとぼくの書いた記事を読んでおけばClojure in Actionはいまのところ不要かもですw
Created at : 2010-03-25 02:08:44
Updated at : 2010-03-25 03:15:05
Category : Programming::Lisp::Clojure
そろそろClojureでのloop/recurによる末尾再帰最適化に慣れてきたんじゃないでしょうか。
Clojureでは相互再帰も最適化されません。例えばこんな例
;; ちょっと変わったみんな大好きFizzBuzz
(declare fizz buzz)
(defn fizz-buzz [n]
(when (> n 0)
(print n " = ")
(fizz n)))
(defn- fizz [n]
(if (zero? (rem n 3))
(print 'fizz))
(buzz n))
(defn- buzz [n]
(if (zero? (rem n 5))
(print 'buzz))
(println)
(fizz-buzz (dec n)))

このような再帰もスタックを消費します。手元の環境ではn = 2000でjava.lang.StackOverflowErrorが発生しました。
このような状況を最適化するために用意されている関数は。。。trampolineです。
プログラミングClojureをお持ちの方はP.149を開いてください。さらっと説明されていますw
trampoline化させるには返り値の括弧の前に#をつけてクロージャ(closureの方)を返すように修正します。
(declare fizz buzz)
(defn fizz-buzz [n]
(when (> n 0)
(print n " = ")
#(fizz n)))
(defn- fizz [n]
(if (zero? (rem n 3))
(print 'fizz))
#(buzz n))
(defn- buzz [n]
(if (zero? (rem n 5))
(print 'buzz))
(println)
#(fizz-buzz (dec n)))
呼び出すときは(trampoline f & args)です。
user> (trampoline fizz-buzz 2000) ; これでjava.lang.StackOverflowErrorが発生しなくなりました!
このままだと使うとき毎回trampolineを書かなくてはいけなくて面倒ですね。本来渡したいのfizzbuzzの引数だけです。こんなときのためにpartial部分適用があります!
プログラミングClojureをお持ちの方はP.146を開いてください。trampolineの第一引数がfizz-buzzである新しい関数を作っちゃいます。
(declare fizz buzz)
;; rename
(defn- fizz-buzz-1 [n]
(when (> n 0)
(print n " = ")
#(fizz n)))
(defn- fizz [n]
(if (zero? (rem n 3))
(print 'fizz))
#(buzz n))
(defn- buzz [n]
(if (zero? (rem n 5))
(print 'buzz))
(println)
#(fizz-buzz-1 (dec n)))
;; 部分適用
(def fizz-buzz (partial trampoline fizz-buzz-1))
user> (fizz-buzz 2000) ; これでもともと期待していたインタフェースで最適化できました!
面白いですねClojure!
まだプログラミングClojureを買っていない人はぽちりましょう!
Created at : 2010-03-24 01:38:59
Updated at : 2010-03-24 02:34:42
Category : Programming::Lisp::Clojure
Shibuya.lispでClojureはデータ構造が貧弱といわれていましたが、Mapはもうちょっと評価されてもいいと思うので使い方メモ。
Clojure1.1.0で確認。
リテラルは{}です。key valueの組み合わせを並べます。必ず要素数は偶数。 またClojureでは半角カンマは空白とみなされるので、key valueの後に半角カンマを入れると見やすい。
user> (def m {:a 1 :b "a" :c 'foo})
#'user/m
user> m
{:a 1, :b "a", :c foo}
user> (class m)
clojure.lang.PersistentArrayMap
リテラルのはArrayMapですね(仕様ではない気がするが)。array-map関数でも作成できます。
user> (def am (array-map :a 1 :b "a" :c 'foo))
#'user/am
user> am
{:a 1, :b "a", :c foo}
user> (class am)
clojure.lang.PersistentArrayMap
HashMapはhash-map関数でつくれます。入力と順番が違う点に注意。
user> (def hm (hash-map :a 1 :b "a" :c 'foo))
#'user/hm
user> hm
{:a 1, :c foo, :b "a"}
user> (class hm)
clojure.lang.PersistentHashMap
sorted-mapもあるよ。
user> (def sm (sorted-map :a 1 :b "a" :c 'foo))
#'user/sm
user> sm
{:a 1, :b "a", :c foo}
user> (class sm)
clojure.lang.PersistentTreeMap
(key map)または(map key)の形式で値取得。
user> (:a m) 1 user> (:b m) "a" user> (m :a) 1
keyを先頭にできるのはkeyがSymbolかKeywordのときだけだと思う。多分。
この場合、keyを関数のように適用できる利点がある。変な例だけど
user> (map :b [m m m])
("a" "a" "a")
関数ではgetが使える。
user> (get m :a) 1
keyやvalueのシーケンスがほしいときはkeys、valsで。
user> (keys m) (:a :b :c) user> (vals m) (1 "a" foo)
assocです。
user> (def updated-m (assoc m :d 100))
#'user/updated-m
user> updated-m
{:d 100, :a 1, :b "a", :c foo}
user> (assoc m :d 100 :e 200)
{:e 200, :d 100, :a 1, :b "a", :c foo}
user> (assoc m :b 100)
{:a 1, :b 100, :c foo}
immutableなのはいわずもがな。
dissocです。
user> (dissoc updated-m :c)
{:d 100, :a 1, :b "a"}
user> (dissoc updated-m :d :c)
{:a 1, :b "a"}
immutableな(ry
便利なデータ構造として使うからにはネストしたい。たとえばこんな感じ。
(def nested-map {:lisp {:cl {:c 'sbcl, :java 'abcl},
:scheme {:c 'gauche, :java 'kawa}},
:ruby {:c 'matz-ruby :java 'jruby}})
いままでの知識だけでsbclをaclに変えたいとき、
user> (let [lisp-map (:lisp nested-map),
cl-map (:cl lisp-map)]
(assoc lisp-map :lisp (assoc cl-map :c 'acl)))
{:lisp {:c acl, :java abcl}, :cl {:c sbcl, :java abcl}, :scheme {:c gauche, :java kawa}}
こんな感じになります。さらにネストが深くなると。。。目も当てられないことになりますね。
こんなときのためにちゃんと関数が用意されています。
assoc-inです。使い方は(assoc-in m [k & ks] v)。↑の例だと
user=> (assoc-in nested-map [:lisp :cl :c] 'acl)
{:lisp {:cl {:c acl, :java abcl}, :scheme {:c gauche, :java kawa}}, :ruby {:c matz-ruby, :java jruby}}
追加ももちろんできる。
user> (assoc-in nested-map [:lisp :cl :lisp] 'oreore-lisp)
{:lisp {:cl {:lisp oreore-lisp, :c sbcl, :java abcl}, :scheme {:c gauche, :java kawa}}, :ruby {:c matz-ruby, :java jruby}}
ちなみにdissoc-inはない。
現在の要素に関数を適用させて値を更新するupdate-inってのもある。使い方は(update-in m [k & ks] f & args)。
変な例だけど、こんな感じ。
user> (update-in nested-map [:lisp :cl :c] str "-1.0.36")
{:lisp {:cl {:c "sbcl-1.0.36", :java abcl}, :scheme {:c gauche, :java kawa}}, :ruby {:c matz-ruby, :java jruby}}
get-inもあるよー。使い方は(get-in m ks)。
user> (get-in nested-map [:lisp :cl :java]) abcl
なんで(get-in m k & ks)にしなかったんだろ。。(assoc,updateと合わせるためかな。)
とりあえず、mapの使い方はこんな感じです。(他にも破壊的なassoc!、dissoc!もあるけど、使わないほうが良いと思う。)
まあまあ使えると思いませんか?
Created at : 2010-03-23 01:50:57
Updated at : 2010-03-23 03:33:40
Category : Programming::Lisp::Clojure