読者です 読者をやめる 読者になる 読者になる

レガシーコード生産ガイド

私に教えられることなら

少しだけClojure 2

プログラミングClojure第2版を読みながら続き

シンボル

シンボル・キーワード共に大文字小文字は区別される

user=> :hoge
:hoge
user=> :Hoge
:Hoge
user=> :HoGe
:HoGe
user=> 'hoge
hoge
user=> 'HoGE
HoGE

マップにキーワードがなかったらnilが返る。値自体がnilだった場合、キーワードを持つかどうかはどう判定するんだろう。多値じゃないっぽいので専用の関数があるのかな。

user=> (inventors :Clojure)
"Hickey"

user=> (inventors :Clojur)
nil

user=> (:Clojure inventors)
"Hickey"

user=> (:Clojur inventors)
nil

キーワード・マップともに関数として使える。ということはフォームの先頭に置いても使える?

user=> ({:karin "Karin Miyamoto"} :karin)
"Karin Miyamoto"

user=> (:utachan {:utachan "Uta Shimamura"})
"Uta Shimamura"

使えた。面白いな〜

レコード

「どういうキーを取りうるか明示し、強制できる」と書いてあるが、どう強制されるんだろう?

user=> (defrecord Group [name members])
user.Group

user=> (->Group "Country Girls" '())
#user.Group{:name "Country Girls", :members ()}

user=> (->Group "Country Girls" '() 'WrongArg)

ArityException Wrong number of args (3) passed to: user/eval1263/->Group--1278  clojure.lang.AFn.throwArity (AFn.java:429)

例外だった。

レコードのフィールドを書き換える

前回やったatomとswap!を使えばできそう。

やり方として考えられるのは

1.書き換えたいフィールドをatomで初期化する 2.レコード全体をatomにする

なんとなく1はできて2はダメそうな気がする。

まず1

user=> (def CountryGirls (Group. "Country Girls" (atom '())))
#'user/CountryGirls

user=> CountryGirls
#user.Group{:name "Country Girls", :members #<Atom@1ac68d16: ()>}

user=> (swap! (:members CountryGirls) conj "Utachan")
("Utachan")

user=> CountryGirls
#user.Group{:name "Country Girls", :members #<Atom@1ac68d16: ("Utachan")>}

:membersにUtachanが追加されている。予想的中!!

2はどうだろう?

user=> (def CountryGirls (atom (Group. "Country Girls" '())))
#'user/CountryGirls

user=> CountryGirls
#<Atom@35e6855c: #user.Group{:name "Country Girls", :members ()}>

user=> (swap! (:members CountryGirls) conj "Utachan")

NullPointerException   clojure.core/swap! (core.clj:2235)

swap!で問題起きるかな〜と思ってたけどNullPointerは予想外だった。とりあえずここらへん罠になりそうだから気をつけておこう。(せっかくClojureなのでそもそも変数頻繁に書き換えるのは避けるけど)

REPLで定義を消す

例えばレコードを間違えて定義した場合、次のようにして定義を消すことができる。

user=> (defrecord Group [name])
user.Group

user=> Group
user.Group

user=> (ns-unmap 'user 'Group)
nil

user=> Group

CompilerException java.lang.RuntimeException: Unable to resolve symbol: Group in this context, compiling:(/tmp/form-init2454257403690527720.clj:1:1010) 

ただ、REPL内でdefn/def/defrecordはそのまま再定義できることを確認したので、わざわざ再定義のために消す必要はなかった。

FizzBuzzやってみる

マップをふんだんに使った。分配便利だなあ

(defn divisivle? [x n] (= (rem x n) 0))

(defn fzbz->fn [{n :number mes :message}]
  "FizzBuzzの定義1つを、それを処理する関数に変える"
  (fn [{x :number mess :message}]
    {:number x
     :message (str mess (when (divisivle? x n) mes))}))

(defn reduce-fn [fns]
  "関数 f1 f2 f3..のシーケンスを、(fn [x] (f1 (f2 (f3 .. x))))の形にする"
  (reduce (fn [f acc] (fn [x] (acc (f x)))) (fn [x] x) fns))

(defn fizzbuzz-x [fb]
  "最終的に表示する数字又はメッセージを返す関数を作る"
  (fn [x]
    (let [{num :number mes :message} (fb {:number x})]
      (if (or (nil? mes) (empty? mes)) num mes))))

(defn fizzbuzz-maker [& defs]
  "FizzBuzzの定義のリストを受け取り、数字のシーケンスに適用する関数を作る"
  (let [fzbz (fizzbuzz-x (reduce-fn (map fzbz->fn defs)))]
    (fn [xs] (map fzbz xs))))

(defn fizzbuzz [n]
  ((fizzbuzz-maker
    {:number 3  :message "Fizz"}
    {:number 5  :message "Buzz"}
    {:number 11 :message "Jazz"}
    {:number 17 :message "Funk"}
    ) (take n (iterate inc 1))))

実行例

user=> (fizzbuzz 100)

(1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" "Jazz" "Fizz" 13 14 "FizzBuzz" 16 "Funk"
 "Fizz" 19 "Buzz" "Fizz" "Jazz" 23 "Fizz" "Buzz" 26 "Fizz" 28 29 "FizzBuzz" 31 32
 "FizzJazz" "Funk" "Buzz" "Fizz" 37 38 "Fizz" "Buzz" 41 "Fizz" 43 "Jazz" "FizzBuzz" 46 47
 "Fizz" 49 "Buzz" "FizzFunk" 52 53 "Fizz" "BuzzJazz" 56 "Fizz" 58 59 "FizzBuzz" 61 62
 "Fizz" 64 "Buzz" "FizzJazz" 67 "Funk" "Fizz" "Buzz" 71 "Fizz" 73 74 "FizzBuzz" 76 "Jazz"
 "Fizz" 79 "Buzz" "Fizz" 82 83 "Fizz" "BuzzFunk" 86 "Fizz" "Jazz" 89 "FizzBuzz" 91 92
 "Fizz" 94 "Buzz" "Fizz" 97 98 "FizzJazz" "Buzz")

感想

  • 寄り道楽しい
  • Clojure、痒いところに手が届く感・一貫性ある感がLispらしくて好き

うたちゃん

www.youtube.com

広告を非表示にする