Programmer's Note

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

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のクラスのようだ。

Clojureを遊ぶ:リストとマクロ

「7つの言語7つの世界」 を読んでClojureを始めた。

まったく思考がLISPerになってなく、すらすらプログラムが組める状態ではない。が、実に面白い。

Clojureの強力な点は、コレクションに対する操作を、非常に簡潔に書けるところだ。

まずはリスト。

リストは(list 1 2 3)と書く。あるいはリーダーマクロの'を使って'(1 2 3)と書く。

user=> (def a '(1 2 3))
#'user/a
user=> a
(1 2 3)
user=> (take 2 a)
(1 2)

LISPのプログラムそのものがリストで構成されているデータで、

user=> (def b '(println 1))
#'user/b
user=> b
(println 1)
user=> (eval b)
1
nil
user=>

なんか、これだけでミニマクロみたいなものだな。

本当のマクロを使ってみる。

user=> (defmacro hello [txt] (println "Hello," txt))
#'user/hello
user=> (hello "Clojure")
Hello, Clojure
nil

これくらいなら、普通に関数を定義すればよさそうだ。 実際やってみる。

user=> (defn hello_f [txt] (println "Hello," txt))
#'user/hello_f
user=> (hello "Clojure")
Hello, Clojure
nil

同じ結果が出る。

しかし、以下で明確に処理の違いが分かる。

user=> (hello (print 1))
Hello, (print 1)
nil
user=> (hello_f (print 1))
1Hello, nil
nil

マクロの場合は引数を評価せずに、そのままリストとして渡すのに対して、 関数の場合は引数を評価してから渡す。

というところまでは分かったが、まだまだ感覚をつかめていない。

マコネル本の読書

遅ればせながら、最近やっとCode Completeを読んだ。まだ上巻を読了したばかりだが、この本がこんなに面白いとは思わなかった。

ずいぶん前に買って積んでいたが、言わば食わず嫌いだったな。なぜかって一言で言えばMicrosoft Pressだから。
この本は名著ともっぱら評判は良いみたいだけど、MSの人が書いたっぽいからつまらないに違いない。と思い込んでた。反省。
MSの製品はつまらないが、必ずしも出す本はつまらない訳じゃないのね。
(著者のSteve McConnelさんは、厳密にはMSのプロジェクトに関わってたが社員じゃなかったようだ)
元MSの人でやはり面白い本を出してるJoelさんの例もあって、最近はすっかりMSに対するイメージがアップしている。

さて、McConnelさんの他の著作で、エッセイ的なProfessional Software Developmentを今読んでいるが、これも面白い。この人は重厚に文献を読んで分かりやすく本質的な問題を分析する人だなと。

とりあえず前半で「ソフトウェアはソフトではない(変更が簡単だと思うど素人の多いこと)」「銀の弾丸は失敗する(組織がプロセス改善の流行に流されたらおしまいよ)」。
まあ原文ままではないが、そんなことを言っていて、読んでため息し膝を打つのだった。いやまさに、あるある。だなあと。

sicpの導入部

MITのプログラミングの授業で使われていた伝説的な教科書、「Structure and Interpretation of Computer Programs」、略してsicpを読み始めた。
いわゆるプログラミングの古典的名著として数えられ、「ハッカーと画家」のポール・グレアムAmazonレビューで絶賛している。(というかLISPerにとってはバイブルなのかしら。)
内容的に課題として小難しい数学の問題を解いたりとあるみたいで、どこまで続くか分からんが、楽しく読んでいきたい。
というか、導入文はもうえらく名文である。実にわくわくする。
Wizard本*1と呼ばれているらしいが、この冒頭ではComputer Programを魔法のスペルに喩えているのだ。

    A computational process, in a correctly working computer, execute programs precisely and accurately. Thus like the sorcerer's apprentice, novice programmer must learn to understand and to anticipate the consequence of thier conjuring.
    Fortunately, learning to program is considerably less dangerous than learning sorcery, because the spirits we deal with are conveniently contained in a secure way.


教科書でこのユーモアかつ想像力に富んだ導入は日本では考えられない。
古くは数学、科学が占星術錬金術と同等に列していた西欧の感覚が織り込まれていて、なんともロマンを感じる。

*1:本書の表紙が魔法使いの師匠と弟子の絵になっているのもWizard本と言われるゆえんだろう