Contenu connexe Similaire à Clojure programming-chapter-2 (20) Clojure programming-chapter-21. Clojure
Programming
reading (※お菓子会)
(CHAPTER 2. Functional Programming)
2012/07/28 @ponkore
1
2. 自己紹介
• @ponkore
• 職業:とあるSIerのSE
• 業務では最近コード書いてません(どちらかというと管理する側)
• Excel方眼紙でドキュメント、Excel&VBAで進 ・品質管理... orz
• Java, C#,VB.Net とかが多い
• Lispやりてぇ→何か無いかな→Java, Lispでぐぐる
→Clojure発見(2011年秋)
→2012/04/22 kyoto.clj 顔出し(この時は聞くだけでした...)
→ 今日はなんとか発表(今ここ)
2
3. はじめに
• 大きな「節」単位で整理してみました。
• それなりに端折ります(細かいところまで完全には読み込
めていません)。
• 本の中で出てきているコードはなるべく引用しています。
• Preface|xvii ページ “Using Code Examples”によると作者に
contactをする必要もなさそうなので。
• ツッコミ、誤りのご指摘、補足は大歓迎です。
3
4. Chapter 2. Functional Programming
• この章で書いてある内容(ざっくり)
• (Immutable な)値の重要性
• 高階関数(Higher Order Function:HOF)
• 関数の部分適用
• 関数の合成(Composition)
• Logging System を作ってみよう
• 純関数(Pure Functions)とメモ化とか
4
5. What does Functional
Programming Mean?
• 「関数型プログラミング」→いろんな言語
で定義はまちまち(少しあいまいな概念)
• Clojure 的には...
- immutableなデータ構造
- 高階関数(higher-order-functions)
- 関数合成、等々
5
6. On the Importance of
Values(1)
• About Values
- JVM標準のBoolean、Number、
Character、String、といった型は、
Clojureでは immutable な値として利用
できるようにしている(安心して使っ
てよし!)
6
7. On the Importance of
Values(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. Clojure's immutable data
structures define equals() (and thus =) as a value, not an identity,
comparison.
7
8. On the Importance of
Values(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. On the Importance of
Values(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. 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. 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. On the Importance of
Values(7)
• Ruby は String でさえ Mutable
>> s= "hello"
=> "hello"
>> s << "*"
=> "hello*"
>> s == "hello"
=> false
ただし.freezeを使えば不用意な変更は阻止できる
>> s.freeze
=> "hello*"
>> s << "*"
RuntimeError: can't modify frozen String
from (irb):4
from /opt/local/bin/irb:12:in `<main>'
12
13. On the Importance of
Values(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. On the Importance of
Values(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. On the Importance of
Values(10)
• A Critical Choice
• “自由に変更可能な状態を持つ”(Mutable な)オブジェクトを使
えてしまうと...
◦ Mutableなオブジェクトは、安全にメソッドに渡せない
◦ Mutableなオブジェクトは、hashのkeyやsetのentryとかには安心して使え
ない
◦ Mutableなオブジェクトは、安全にキャッシュできない(キーが変更されうる)
◦ Mutableなオブジェクトは、マルチスレッド環境では安心して使えない(ス
レッド間で正しく同期させる必要がある)
◦ 色々理由はあるけど、今後のことを考えてMutableなオブジェクトはなるべ
く使わない方向にしたよ、ということ。
15
16. First-Class and Higher
Order-Functions(1)
• 関数型プログラミングの特徴(要件)
◦ 関数自身を値として他のデータと同様に取り扱える
=関数を引数や戻り値として取り扱える
• 例:call_twice
# Ruby # Python
def 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
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. First-Class and Higher
Order-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. 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. 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. 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. 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. Composition of
Function(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. Composition of
Function(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. 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. 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. 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. Pure Functions
• Pure Functionって何?
• 「副作用」(side effects)のない関数、のこと
• じゃあ「副作用のある関数」って何?
1. ランダムな状態に依存(乱数を使ってる、とか)
2. 1回めの呼び出しと次回以降の呼び出しで結果が変わる
※I/Oを実行する関数は副作用あり。
(P.77 では @ClojureBook のフォロワー数の出力を例示
→呼び出したタイミングにより値が変わる)
29
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. 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
Notes de l'éditeur \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n