Programmer's Note

コード読み書きの備忘録。

Clojureメモ: loopとrecur

プログラミングClojureに乗っていた例を写経実行したメモ。

関数を使わずに、再帰的にループするやり方。

  (println (loop [result [] x 5]
         (if (zero? x)
           result
           (recur (conj result x) (dec x)))))

実行結果:

[5 4 3 2 1]

ループ分の構造としては

(loop [※A]
   ...
   ...
   (recur ※B))

になっていて、※Aでは関数でいう引数の定義と、加え初期値の代入をしている。 [result [] x 5]の意味は、result = []x = 5

(recur ※B)では、loopの先頭に戻って、※Bで次のresultxを与えている。

これは、イメージ的には無名関数の再帰呼び出しような感じだなあ。と。

関数でrecurを使う場合は以下のようになる。

(defn count-down [result x]
  (if (zero? x)
    result
    (recur (conj result x) (dec x))))

(println (count-down [] 5))

(loop [result [] x 5](defn count-down [result x]に置き換えただけだ。

初期値は呼び出し側が与えるので、関数の場合は引数の初期値ない。

関数のrecurを使う再帰呼び出し、loopのrecurの記述の一貫性はビューティフルだな。

Clojureメモ: 無名関数

「プログラミングClojure」で出てきたサンプル。

user=> (filter (fn [w] (> (count w) 2)) (str/split "A fine day" #"\W+"))
("fine" "day")

filter関数は、

(filter pred coll)

コレクションcollの各要素に対して、条件をチェックする関数predを適応して、trueが返ってきたものだけを、 戻りのシーケンスに入れて返す。 rubyでいうとArray#selectに相当する。

さて、上記の(fn [w] (> (count w) 2))が無名関数で、リーダマクロ#()を使って、#(> (count %) 2)という形で書ける。

user=> (filter #(> (count %) 2) (str/split "A fine day" #"\W+"))
("fine" "day")

無名関数は任意の変数にバインドできる。 (Clojureのvarは変数とは厳密に違うようだが)

aにバインドして実行してみる。

user=> (def a #(> (count %) 2))
#'user/a
user=> (a "abc")
true
user=> (a "jk")
false

ここら辺の関数の扱いの気軽さはjavascript並みだ。 javascriptで書くと下記のようなものになるかと。

var a = function(w) { return (count(w)>2); }
a("abc);

最後に冒頭の例で、aにバインドされた無名関数を使ってみる。

user=> (filter a (str/split "A fine day" #"\W+"))
("fine" "day")

Clojureメモ: 遅延評価

さて、(for ...)はシーケンスを生成して返すが、これは遅延評価されるので、実際に使用されるまではシーケンスは生成されない。

REPL環境だと入力した式がその場で評価されるので、これが遅延評価されるものとは気づかない。

user=> (for [x (range 1 3)] (println x))
1
2
(nil nil)

実際に、lein new app sampleとかでテンプレートつくって、以下のようなソースを書いてみる。

(ns sample.core
  (:gen-class))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (for [x (range 1 3)] (println x)))

実行してみると

sample $lein run
sample $

、、、なにも表示されない?

CとかRubyとかの感覚で書いていると、まったくよくわからない。

で、いろいろ試してみて、結局、以下のように理解した。

  1. まず、上記(for ...)では2要素のシーケンス(nil, nil)を返す。
  2. 返り値の中身がnilなのは、(for ...)の中の実行関数(print x)nilを返すから
  3. REPLの場合は、REPLの環境自体がそのシーケンスを(表示するために)使ったので、その場で評価された。
  4. lein runで実行した場合は、プログラムのなかで(for ...)の返り値のシーケンスを使うコードがどこにもないので、評価されずに終わった

ということか。

プログラムを下記のように書き直す。

(ns sample.core
  (:gen-class))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println (for [x (range 1 3)] (println x))))

実行結果:

sample $lein run
(1
2
nil nil)

微妙にREPLの場合と結果が違うのが気になる。こちらは()の中に12が表示されている。 これ、ぎりぎりまでシーケンス生成((for..)の中の実行)を遅らせているのだな、と理解した。

遅延評価は一回しかやらないようで、例えば、

(ns sample.core
  (:gen-class))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (def a (for [x (range 1 3)] (println x)))
  (println a)
  (println a))

の実行結果は、

sample $lein run
(1
2
nil nil)
(nil nil)

になる。

2回目はできあいのシーケンスをそのまま使うてことだな。

Clojureを遊ぶ:括弧とか

「プログラミングClojure」で出てきたforループのサンプル。

(defn indexed [coll]
  (map vector (iterate inc 0) coll))

(defn index-filter [pred coll]
  (when pred
    (for [[idx elt] (indexed coll) :when (pred elt)]
      idx)))

実行結果:

(index-filter #{\a} "dfajklfabce")
-> (2 7)

初学者の自分がこれを読み解くのに苦労した。

お遊びで、Rubyっぽく表記を変えてみる。

def indexed (coll)
  map(vector, iterate(inc, 0), coll)
end

def index-filter (pred, coll)
  when pred
    for (idx, elt) in indexed(coll) :when pred(elt)
      idx
    end
  end
end

こういうお遊びは楽しい。

括弧の()[]という表記は、範囲を表すためのものだが、 ネストすると脳内で解読が必要になってしまう。 元のコードだと[[idx elt] ...]あたりで目がちかちかするW。

処理によって表現に使う記号を変えるだけで、ずいぶんと読みやすくなる。

一方で、Paul Grahamがどっかの本で書いてあったのだが、 LISPer人たちは、括弧は無視してて、エディタの力を借りて、 インデントで構文を読んでいると。(最近はシンタックスハイライトもあるしね)

元のコードから、人間には解読可能な範囲で、括弧をなくすだけでも、ずいぶんと読みやすくなる。

defn index-filter [pred coll]
  when pred
    for [idx elt] (indexed coll) :when (pred elt)
      idx

こうするとPythonっぽいよな。

Clojureメモ:forを使う(その2)

まずは、2次元配列的なものを作ってみる。

user=> (def aa [[1 \a] [2 \b]])
#'user/aa

user=> aa
[[1 \a] [2 \b]]

これを、一個ずつ取り出してみる。

user=> (for [x aa] (println x))
[1 a]
[2 b]
(nil nil)

で、上記[1 a][2 b]の第1要素だけをとりたい。

C言語とかはこういうときは2重ループでやるしかないが、Clojureは非常にシンプルに記述できる。

user=> (for [[x y] aa] (println x))
1
2
(nil nil)

便利!

Clojureのdestructingの機能を使っていて、[[x y] aa]は、Rubyのタプルみたいなものでx, y = aaだ。

2重ループもスーパーシンプル。

user=> (for [i [1 2] j [3 4]] [i j])
([1 3] [1 4] [2 3] [2 4])

一行だと分かりにくいので、改行してインデントしてみる。

  (for [i [1 2]
        j [3 4]]
    [i j])

これRuby的に書くと

ret = []
for i in [1,2]
    for j in [3,4]
        ret.push([i,j])
    end
end
ret

Clojure (LISP)は簡潔すぎるほど、簡潔にかけるな。 定義されている関数の仕様の理解が肝で、油断すると意味不明なプログラムになりそう。

Clojureメモ:forを使う

Clojureを始めて1週間ちょいといったところか。 とりあえず本を読んでいる、ちょくちょく試してみている感じ。

ループのまわし方とかも、手続き型プログラミングと勝手が違って戸惑う。

Clojureのforは命令型言語のforループとは違うらしい。(リスト内包表記と呼ばれる。)

forの一番シンプルな例。

user=> (for [i [0 1 2 3]] (println i))
0
1
2
3
(nil nil nil nil)

最後の(nil ...)は関数の返り値をREPL環境が表示している。 (for ...)は制御文ではなく関数なので戻り値がある。 (for ...)はつまり、ループ分だけ要素があるコレクションを作って返すということか。

さて、ループをまわすには、要はまず何かの集合が必要だということだな。 上記の例だと、集合[0 1 2 3]の中から一個ずつアイテムを取り出してiに代入し、それを使って処理している。 (Clojureの用語だとシンボルiに値をバインドする、と言う。)

上記はClojureベクター(要は配列)を使ったが、次にマップを使ってみる。

user=> (def a {:m1 1 :m2 2})
#'user/a
user=> a
{:m1 1, :m2 2}
user=> (for [i a] (println i))
[:m1 1]
[:m2 2]
(nil nil)

おお、期待通りの動作。マップの順番って定義された順だろうか・・・まあ、あとで調べるとしよう。

1から10の数字を表示する関数を書いてみる。 1〜10をまわすが、[1 2 ...]で定義するのも面倒で、range関数を使う。

user=> (range 1 11)
(1 2 3 4 5 6 7 8 9 10)

でとりあえず、

user=> (for [i (range 1 11)] (println i))
1
2
3
4
5
6
7
8
9
10
(nil nil nil nil nil nil nil nil nil nil)

てな感じだ。

Clojureメモ:文字列分割

「プログラミングClojure」を読んでClojureを学んでいる。 正直、この本は入門者向けではない…。著者の気持ちが先走りすぎている。 いやいや、面白いのは分かるけど、もうちょっと落ち着いて教えてくれよ。という感じだ。

まあ謎解きしながら読むのも面白いが…。

軽く文字列処理のメモ。 文字列をスペースとかで分割する例。

user=> (require '[clojure.string :as str])
nil
user=> (str/split "This is a pen." #"\W+")
["This" "is" "a" "pen"]

#"\W+"正規表現で英数字以外の文字を表す。 (正規表現はリーダマクロ表記の#""で囲む)

正規表現もいろいろ方言があるが、Clojureのは標準的なのだろうか。 軽くググっても出てこんかった。 本家のHPからhttp://www.regular-expressions.info/がたどれた。 あとで調べてみよう。

user=> (class #"")
java.util.regex.Pattern

とすると下の層ではjavaのクラスのようだ。