Clojure Focus で Reader Conditionals について発表してきた
目次に「まとめ」って書いてるのに「まとめ」のページ作ってなかったのが今日のハイライト。
だいたい参考にしたのは仕様とか設計とかのページ。ここ以外のブログ記事とかみても結局同じこと書いてあるので略。
Reader Conditionals - Clojure Design - Clojure Development
Clojure - reader
Clojure 出来る勢が沢山いる中で発表するの怖かったけど、後で懇親会で聞いたらわりと皆さんこの機能そんなに使っていないようで、まぁ多少は役に立てたかもしれないなーというところ。
ちなみに使ってない理由も「ライブラリとか作ってないからあまり…」というのもあれば、「そもそも同じファイルに必死になって書く必要あるの?見通し悪くならない?」みたいなのもあって、うーん確かに。というところだった。
assoc と update どっち使う?
Clojure 1.7 から導入された update 関数。 update-in はあるけど、むしろ今までなんで update がなかったんだろうと思ったりする今日このごろです。
まぁともあれどういうものかというと普通にドキュメント引けば分かりますが、 update-in で指定するキーワードがひとつのときに代わりに使うと良さそうです。
clojure.core/update ([m k f] [m k f x] [m k f x y] [m k f x y z] [m k f x y z & more]) 'Updates' a value in an associative structure, where k is a key and f is a function that will take the old value and any supplied args and return the new value, and returns a new structure. If the key does not exist, nil is passed as the old value.
で、早速プロジェクトの Clojure のバージョンを 1.7 にあげて、 update 使えるヒャッハー!!してたんですが、 Clojure デキルマンに止められました。
デキルマン「それ assoc で良くないですか?」
ボク「 update の方が意図が伝わりやすいかなーって…」
デキルマン「奇妙に見えるからやめましょう」
ボク「ア、ハイ」
ちなみにどういうコードを書いていたかというとこんな感じ。
(update m :foo (fn [_] x))
まあ確かに奇妙かもしれない。ちなみに定数を返す関数は (constantly x) と書ける。これを assoc で書き直すとこうなる。
(assoc m :foo x)
じゃあ、 assoc と update って何が違うんだろうって思った。ドキュメント引けば答えは書いてある。
clojure.core/assoc ([map key val] [map key val & kvs]) assoc[iate]. When applied to a map, returns a new map of the same (hashed/sorted) type, that contains the mapping of key(s) to val(s). When applied to a vector, returns a new vector that contains val at index. Note - index must be <= (count vector).
assoc は key に対して新しい val をマッピングしたマップを返すもの。かたや update は既に上で書いた通り、古い値に対して関数を適用し得た新しい値をマッピングしたマップを返すといったもの。
ちょっとだけ具体例を書いてみましょう。
(def x {:foo 10 :bar 20}) ;; => #'boot.user/x (assoc x :foo 30) ;; => {:foo 30, :bar 20} (update x :foo (constantly 30)) ;; => {:foo 30, :bar 20} (assoc x :baz 30) ;; => {:foo 10, :bar 20, :baz 30} (update x :baz (constantly 30)) ;; => {:foo 10, :bar 20, :baz 30} (defn square [x] (* x x)) ;; => #'boot.user/square (assoc x :bar (square (:bar x))) ;; => {:foo 10, :bar 400} (update x :bar square) ;; => {:foo 10, :bar 400} (assoc x :baz (square (:baz x))) ;; => java.lang.NullPointerException (update x :baz square) ;; => java.lang.NullPointerException
とまぁこんな感じになります。確かに定数値へと更新する場合は assoc を使ったほうがスマートぽいです。古い値を取得して新しい値に更新するという場合には update の方が便利というわけですね。
元々僕の update 関数に対する理解が悪かったんですが、 update を「 m を f 関数を利用して k にマップされている値を更新する」ではなくて、「 m の k にマップされた値を f 関数で更新する」というふうに理解すれば自ずと使いドコロは見えてくるわけですね。
ちなみに、 update 関数の中身に assoc が使われているので、 assoc で書くのがめんどくさいときのショートハンド(or 構文糖衣)として定義されたと考えるのが普通そうです。
(defn update "'Updates' a value in an associative structure, where k is a key and f is a function that will take the old value and any supplied args and return the new value, and returns a new structure. If the key does not exist, nil is passed as the old value." {:added "1.7" :static true} ([m k f] (assoc m k (f (get m k)))) ([m k f x] (assoc m k (f (get m k) x))) ([m k f x y] (assoc m k (f (get m k) x y))) ([m k f x y z] (assoc m k (f (get m k) x y z))) ([m k f x y z & more] (assoc m k (apply f (get m k) x y z more))))
マクロ書いて、これ IFn にキャスト出来ないんだけどってエラーになったとき
たまに馬鹿やります。あやぴーです。
(defmacro foo [] (map inc (range 10))) (foo) ;; java.lang.ClassCastException: java.lang.Long cannot be cast to clojure.lang.IFn
こんな感じで上のようなマクロを定義するとエラー吐くけど、よく考えたら当たり前で (foo) は次のように展開されます。
(1 2 3 4 5 6 7 8 9 10)
このとき map で返却されていたのはクォートされいないリストであり、こんなものを関数として実行しようとすればエラーが起きて当然です。なので、この場合は mapv などを使ってベクタを返すと良さそうです。どうしてもリストがいいなら、 quote で囲みましょう。
というどうでもいい小ネタでした。
どうやってベクターのネストを削除したらいい?
久しぶりにこういうの書く*1。
さて、質問ですが
([[AA ww me bl qw 100] [AA ee rr aa aa 100] [AA qq rr aa aa 90]] [[CC ww me bl qw 100] [CC ee rr aa aa 67]])
このようなデータ構造を持っているときに、次のようなデータの形にしたいとのこと。
([AA ww me bl qw 100] [AA ee rr aa aa 100] [AA qq rr aa aa 90] [CC ww me bl qw 100] [CC ee rr aa aa 67])
一見 flatten で良さそうに見えますが、 flatten を使うと再帰的に平滑化していまうため上記のようになりません。
回答はシンプルなんですが、 concat と apply 組み合わせればいいよとのこと。
(apply concat ([[AA ww me bl qw 100] [AA ee rr aa aa 100] [AA qq rr aa aa 90]] [[CC ww me bl qw 100] [CC ee rr aa aa 67]]))
ちなみに僕もこれ似たようなことで Clojure デキルマンに注意されたことあって、そのときは flatten は再帰的に平滑化するから使うない方がいいときもあると教わったんですが、だいたいこれはイディオムぽいので覚えてしまったほうが良さそうですね。
*1:最近、びみょーに忙し…