Clojure入門 - Project Eulerを解いてみる 問2
ネタバレ
(comment まずは超基本的な関数から。 このレベルなら本当はfnで匿名関数にしてもいいかも 少し面白いのはleが無名関数を返す高次関数であるところか) (defn le [n] (fn [x] (<= x n))) (defn even [x] (= 0 (mod x 2))) (comment fibonacciを計算するのに、 まず2-element vector [v0, v1]を入力として [v1, v0+v1]を出力として返すnext-fib-vec関数を用意) (defn next-fib-vec [v] [(v 1) (+ (v 0) (v 1))]) (comment iterateを使ってn, f(n), f(f(n))... と無限に続く2-element vectorのlazy seqを作成) (def fib-vec (iterate next-fib-vec [1 1])) (comment 2-element vectorの最初のelementのみ摘出して 無限に続くfibonacciのlazy sequenceを作成) (def fib (map first fib-vec)) (comment あとは問題文に従って、400万以下のfibのうち 偶数のものをすべて足し合わせて結果を表示するのみ) (println (reduce + (filter even (take-while (le 4000000) fib))))
第二問にして、Pythonとは相当違う書き方になった。Pythonだったらノータイムでfib
を以下のようなステート有りのジェネレータにしている。
def fib(): a, b = 1, 1 while True: yield a a, b = b, a+b
Clojureではステートはないのでiterate
を使って無限に続く二つの数字のvectorを作成。iterate
はStackOverflowでfactorialの問題から発見。
まあよく見ると根本的な考えは同じだが・・・
気になった点としては、匿名関数fn
やlet
ではなくdef
やdefn
で直接命名してしまっていること。はじめは匿名関数でやってみようとするんだが、なんとなく読みにくく感じて明示的に名前を付けてしまう。これは慣れの問題なのだろうか・・・
あと、改行もどこでやるのがいいのか。改行の位置が決まればインデントは比較的明快に決まるのだが、例えば(reduce +
を二行にわけたほうがいいのか・・・ +
の部分の関数が多少複雑なら問答無用で改行するが、一語(あるいは一文字!)の場合はむしろバランスが悪いように思えてしまう。reduce +
やfilter even
はほとんど一つの関数のように感じてしまうのも一因か。
このようなもやもやは、模索しながらコードを書いてみてその上でいいコードを読むことで改善していくものだろう。今年は意識的にidiomatic clojureにたくさん触れていこうと思う。