Successfully reported this slideshow.

Clojure programming-chapter-2

1,701 views

Published on

Clojre Programming 第2章

Published in: Education
  • Be the first to comment

Clojure programming-chapter-2

  1. 1. ClojureProgramming reading (※お菓子会)(CHAPTER 2. Functional Programming) 2012/07/28 @ponkore 1
  2. 2. 自己紹介• @ponkore• 職業:とあるSIerのSE• 業務では最近コード書いてません(どちらかというと管理する側) • Excel方眼紙でドキュメント、Excel&VBAで進 ・品質管理... orz • Java, C#,VB.Net とかが多い• Lispやりてぇ→何か無いかな→Java, Lispでぐぐる →Clojure発見(2011年秋) →2012/04/22 kyoto.clj 顔出し(この時は聞くだけでした...) → 今日はなんとか発表(今ここ) 2
  3. 3. はじめに• 大きな「節」単位で整理してみました。• それなりに端折ります(細かいところまで完全には読み込 めていません)。• 本の中で出てきているコードはなるべく引用しています。 • Preface|xvii ページ “Using Code Examples”によると作者に contactをする必要もなさそうなので。• ツッコミ、誤りのご指摘、補足は大歓迎です。 3
  4. 4. Chapter 2. Functional Programming • この章で書いてある内容(ざっくり) • (Immutable な)値の重要性 • 高階関数(Higher Order Function:HOF) • 関数の部分適用 • 関数の合成(Composition) • Logging System を作ってみよう • 純関数(Pure Functions)とメモ化とか 4
  5. 5. What does FunctionalProgramming Mean?• 「関数型プログラミング」→いろんな言語 で定義はまちまち(少しあいまいな概念)• Clojure 的には... - immutableなデータ構造 - 高階関数(higher-order-functions) - 関数合成、等々 5
  6. 6. On the Importance ofValues(1)• About Values - JVM標準のBoolean、Number、 Character、String、といった型は、 Clojureでは immutable な値として利用 できるようにしている(安心して使っ てよし!) 6
  7. 7. On the Importance ofValues(2) • Clojure では以下の比較式はすべてtrue (= 5 5) (= 5 (+ 2 3)) (= "boot" (str "bo" "ot")) ; 下記Memo 参照 (= nil nil) (let [a 5] (do-something-with-a-number a) (= a 5))Memo: Clojure における ‘=’ は、imutable な’値’に対して比較。javaの == とはちょっと違う。clojure.core/=([x] [x y] [x y & more]) Equality. Returns true if x equals y, false if not. Same as Java x.equals(y) except it also works for nil, and compares numbers and collections in a type-independent manner. Clojures immutable data structures define equals() (and thus =) as a value, not an identity, comparison. 7
  8. 8. On the Importance ofValues(3) • Comparing Values to Mutable Objects Mutable Object の例(StatefulInteger class)public class StatefulInteger extends Number { private int state; public StatefulInteger (int initialState) { this.state = initialState; } public void setInt (int newState) { this.state = newState; } public int intValue () { return state; } public int hashCode () { return state; } public boolean equals (Object obj) { return obj instanceof StatefulInteger && state == ((StatefulInteger)obj).state; }} 8
  9. 9. On the Importance ofValues(4)• (ちょっと横道) StatefulInteger クラスを Java で書くと、クラスパスを通し たりなにかとめんどい。ので、Clojure で書いてみた。 ;;; gist に上げてあります https://gist.github.com/3162439 (defprotocol IStatefulInteger   (setInt [this new-state])   (intValue [this])) (deftype StatefulInteger [^{:volatile-mutable true} state]   IStatefulInteger   (setInt [this new-state] (set! state new-state))   (intValue [this] state)   Object   (hashCode [this] state)   (equals [this obj]     (and (instance? (class this) obj)          (= state (.intValue obj))))) 9
  10. 10. On the Importance of Values(5) • StatefulInteger をClojure からいじってみる(def five (StatefulInteger. 5));= #user/five(def six (StatefulInteger. 6));= #user/six(.intValue five);= 5(= five six) fiveは“5”のつもり;= false なのに“6”に(.setInt five 6) なってしまった;= nil(= five six);= true 10
  11. 11. On the Importance of Values(6) • StatefulInteger をさらに邪悪にいじる(defn print-number ※前ページの続き:five=6, six=6 [n] (println (.intValue n)) (.setInt n 42)) ; printlnした後に値を42に変更!;= #user/print-number(print-number six); 6;= nil(= five six);= false ※前ページの結果はtrue→状態が変わった!(= five (StatefulInteger. 42)) ;P.56の例ではfiveだがsixの誤りでは?;= true 11
  12. 12. On the Importance ofValues(7)• Ruby は String でさえ Mutable >> s= "hello" => "hello" >> s << "*" => "hello*" >> s == "hello" => false ただし.freezeを使えば不用意な変更は阻止できる >> s.freeze => "hello*" >> s << "*" RuntimeError: cant modify frozen String from (irb):4 from /opt/local/bin/irb:12:in `<main> 12
  13. 13. On the Importance ofValues(8)• Ruby は Hash の key でさえ変更可能 >> h = {[1, 2] => 3} # [1,2]というキーをもつHash hを作る => {[1, 2]=>3} >> h[[1,2]] => 3 >> h.keys => [[1, 2]] >> h.keys[0] => [1, 2] >> h.keys[0] << 3 => [1, 2, 3] # h.keys[0]はもともと[1,2]だった >> h[[1,2]] # [1,2]にhitする値はもうhには無い => nil ※h.keys[0].freeze で keys[0]を保護できなくはない 13
  14. 14. On the Importance ofValues(9)• Clojureのmapのkeyは変更不可 (def h {[1, 2] 3}) ;= #user/h (h [1 2]) ;= 3 (conj (first (keys h)) 3) ;= [1 2 3] ※h のkey [1 2] が変更 (h [1 2]) されたわけではない ;= 3 元のまま h ;= {[1 2] 3} 14
  15. 15. On the Importance ofValues(10)• A Critical Choice • “自由に変更可能な状態を持つ”(Mutable な)オブジェクトを使 えてしまうと... ◦ Mutableなオブジェクトは、安全にメソッドに渡せない ◦ Mutableなオブジェクトは、hashのkeyやsetのentryとかには安心して使え ない ◦ Mutableなオブジェクトは、安全にキャッシュできない(キーが変更されうる) ◦ Mutableなオブジェクトは、マルチスレッド環境では安心して使えない(ス レッド間で正しく同期させる必要がある) ◦ 色々理由はあるけど、今後のことを考えてMutableなオブジェクトはなるべ く使わない方向にしたよ、ということ。 15
  16. 16. First-Class and HigherOrder-Functions(1) • 関数型プログラミングの特徴(要件) ◦ 関数自身を値として他のデータと同様に取り扱える =関数を引数や戻り値として取り扱える • 例:call_twice# Ruby # Pythondef call_twice(x, &f) def call_twice(f, x): f.call(x) f(x) f.call(x) f(x)end call_twice(print, 123)call_twice(123) {|x| puts x} 16
  17. 17. First-Class and HigherOrder-Functions(2)• Clojure では... まあ普通な感じ ;Clojure (defn call-twice [f x] (f x) (f x)) (call-twice println 123) 17
  18. 18. First-Class and Higher Order-Functions(3) • map (clojure.core/map)(map clojure.string/lower-case [“Java” “Imperative” “Weeping” “Clojure” “Learning” “Peace”]);= (“java” “imperative” “weeping” “clojure” “learning” “peace”)(map * [1 2 3 4] [5 6 7 8]) ;collection が複数ある場合;= (5 12 21 32);中身としては (map #(* % %2) [1 2 3 4] [5 6 7 8])(map * [1 2 3] [4 5 6 7] [8 9 10 11 12]);= (32 90 180) ;※要素の数は少ない方に合わせられる;中身としては (map #(* % %2 %3) [1 2 3] [4 5 6 7] [8 9 10 11 12]) 18
  19. 19. First-Class and HigherOrder-Functions(4)• reduce (clojure.core/reduce) (reduce max [0 -3 10 48]) (max 0 -3) ;= 0 (max 0 10) ;= 10 (max 10 48) ;= 48 ;= 48 (のはず P.63 には 10と書いてある...) (max (max (max 0 -3) 10) 48) ;= 48 ;あるいは以下のようも書ける (reduce #(max % %2) [0 -3 10 48]) 19
  20. 20. First-Class and Higher Order-Functions(5) •(reduce reduce (clojure.core/reduce) 続き (fn [m v] ; (fn []... ) anonymous function (assoc m v (* v v))) {} [1 2 3 4]) ;(assoc {} 1 (* 1 1)) => {1 1} ;(assoc {1 1} 2 (* 2 2)) => {2 4,1 1} ;(assoc {2 4,1 1} 3 (* 3 3)) => {3 9,1 1,2 4} ;(assoc {3 9,1 1,2 4} 4 (* 4 4)) => {4 16,1 1,2 4,3 9};= {4 16, 3 9, 2 4, 1 1};あるいは以下のようも書ける(reduce #(assoc % %2 (* %2 %2))) ; #(...) function literal {} [1 2 3 4]) 20
  21. 21. First-Class and Higher Order-Functions(6) • Applying Ourselves Partially (関数部分適用の話)(def only-strings (partial filter string?));= #’user/only-strings(only-strings [“a” 5 “b” 6]);= (“a” “b”);ところで only-strings の正体って何?only-strings;= #<core$partial$fn__3794 clojure.core$partial$fn__3794@5719510f>;関数っぽい(class only-strings);= user$only_strings ;だそうで... 21
  22. 22. First-Class and Higher Order-Functions(7) • Applying Ourselves Partially (関数部分適用の話)partial versus function literals. ; only-strings 相当のことを関数リテラルでやってみる (#(filter string? %) [“a” 5 “b” 6]) ;= (“a” “b”) ; できた!簡単! ;filter の述語を置き換える、なんてことも... (#(filter % [“a” 5 “b” 6]) string?) ;= (“a” “b”) (#(filter % [“a” 5 “b” 6]) number?) ;= (5 6) ;partial じゃなく関数リテラルだけでもいいんじゃ?.... ; => partial の場合、引数を厳密に指定しなくてもよい場合 にすっきり見える (次ページ) 22
  23. 23. First-Class and Higher Order-Functions(8) • Applying Ourselves Partially (関数部分適用の話)partial versus function literals. (続き) ;まずは関数リテラル (#(map *) [1 2 3] [4 5 6] [7 8 9]) ;= ArityException Wrong number of args (3) passed ... (#(map * % %2 %3) [1 2 3] [4 5 6] [7 8 9]) ;= (28 80 162) (#(map * % %2 %3) [1 2 3] [4 5 6]) ;= ArityException Wrong number of args (2) passed ... (#(apply map * %&) [1 2 3] [4 5 6] [7 8 9]) ;= (28 80 162) (#(apply map * %&) [1 2 3]) ;= (1 2 3) ; %& で引数の数は気にしなくて良くなったが apply がうっとおしい... ;次に partial の出番 ((partial map *) [1 2 3] [4 5 6] [7 8 9]) ;= (28 80 162) ((partial map *) [1 2 3]) ;= (1 2 3) 23
  24. 24. Composition ofFunction(ality)(1)• Composition って何? (defn negated-sum-str [& numbers] 10 12 3.4 (str (- (apply + numbers)))) ;= #’user/negated-num-str (negated-sum-str 10 12 3.4) + ;= “-25.4” 25.4 (def negated-sum-str (comp str - +)) - ;= #’user/negated-num-str ;※関数を返してくれる! -25.4 ;しかもpartial同様引数の個数とか気にしない! str (negated-sum-str 10 12 3.4) ;= “-25.4” “-25.4” ※P71.脚注26 “point-free style” (引数を明示的に指定・参照しないスタイル) 24
  25. 25. Composition ofFunction(ality)(2)• Composition もう一つの例(camel->keyword)(require [clojure.string :as str])(def camel->keyword (comp keyword str/join (partial interpose -) (partial map str/lower-case) #(str/split % #"(?<=[a-z])(?=[A-Z])")));= #user/camel->keyword(camel->keyword "CamelCase");= :camel-case(camel->keyword "lowerCamelCase");= :lower-camel-case;; 上記場面では、以下のように ->> を使うこともできる(こっちのほうが多いかな?);; ※comp とは順番が異なることに注意。またcompで必要だった partial 等も不要。(defn camel->keyword [s] (->> (str/split % #"(?<=[a-z])(?=[A-Z])") (map str/lower-case) (interpose -) str/join keyword)) 25
  26. 26. Composition of Function(ality)(3) • camel->keywordを応用してみる(camel-pairs->map)(def camel-pairs->map (comp (partial apply hash-map) (partial map-indexed (fn [i x] (if (odd? i) x (camel->keyword x))))));= #user/camel-pairs->map(camel-pairs->map ["CamelCase" 5 "lowerCamelCase" 3]);= {:camel-case 5, :lower-camel-case 3} 26
  27. 27. Composition of Function(ality)(4) • Writing Higher-Order Functions;お約束の adder(defn adder ; 「関数」を戻り値として返す [n] (fn [x] (+ n x)));= #user/adder((adder 5) 18);= 23(defn doubler ; 関数をパラメータとして渡す [f] (fn [& args] (* 2 (apply f args))));= #user/doubler(def double-+ (doubler +));= #user/doubler-+(double-+ 1 2 3) ; Clojureの+はいくつでも引数を取れる。便利。;= 12 27
  28. 28. Composition of Function(ality)(5) • Building a Primitive Logging System with Composable Higher-Order Functions • 高階関数を使ってログ出力をいろいろ作ってみる。 print-logger パラメータwriterを出力先に指定できる関数を返す。 *out-logger* print-logger をつかった、*out* に出力する関数。 retained-logger print-logger をつかった、StringWriter に出力する関数。 file-logger パラメータfileを出力先に指定できる関数を返す。 log->file “message.log”というファイルに出力する関数。 multi-logger logger関数を複数順番に呼び出す(doseq)関数を返す。 log multi-loggerを使って、*out*と”message.log”に出力する関数。 timestamped-logger ログメッセージに日時を付加する関数を返す。 log-timestamped 今までの集大成。コードは長いのでスライド上は省略します。gist に貼ってありますので、そちらをご参照願います。https://gist.github.com/3173902/ 28
  29. 29. Pure Functions• Pure Functionって何? • 「副作用」(side effects)のない関数、のこと • じゃあ「副作用のある関数」って何? 1. ランダムな状態に依存(乱数を使ってる、とか) 2. 1回めの呼び出しと次回以降の呼び出しで結果が変わる ※I/Oを実行する関数は副作用あり。 (P.77 では @ClojureBook のフォロワー数の出力を例示 →呼び出したタイミングにより値が変わる) 29
  30. 30. Pure Functions• Why Are Pure Functions Interesting? • Pure functions are easier to reason about. • 入力パラメータが決まれば出力は必ず同じ出力になる。 • Pure functions are easier to test. • (関数を実行する時点の)状態を考える必要がないのでテストしやすい。 • Pure functions are cacheable and trivial to parallelize. • 入力パラメータが決まれば出力は必ず同じー>入力パラメータに対応し た出力結果をキャッシュ(メモ化)することで、2回目以降の呼び出しを 高速化できる。 (次ページにメモ化の例) 30
  31. 31. Pure Functions • メモ化の例(素数判定)(defn prime? [n] (cond (== 1 n) false (== 2 n) true (even? n) false :else (->> (range 3 (inc (Math/sqrt n)) 2) (filter #(zero? (rem n %))) empty?)))(time (prime? 1125899906842679)); "Elapsed time: 2181.014 msecs";= true(let [m-prime? (memoize prime?)] ; memoize:「関数をメモ化した関数」を返す。 (time (m-prime? 1125899906842679)) (time (m-prime? 1125899906842679))); "Elapsed time: 2085.029 msecs"; "Elapsed time: 0.042 msecs" 31
  32. 32. Functional Programmingin the Real World Functional Programming in the Real World メリット •予測しやすいコード •テストの容易さ •再利用の容易さ 32
  33. 33. ご清聴ありがとうございました。 33

×