データ指向 (Data Orientation)
Clojureを設計したRich Hickeyさんのプレゼン Clojure Made Simple - YouTube を観た。
特に印象的なのは、Clojureを作るきっかけになった経緯を語ったくだり。 簡単にいえば、20年間Java/C++/C#でエンタープライズ系のソフトウェアを組んできたキャリアのあと、 あるとき他の人がLispで面白いシステムをシンプルに実現しているのを見て、自分もやってみたら本当にシンプルにできたと。
かなり衝撃を受けたようで、いままで本当に時間を無駄にした!と落ち込んで(I was unhappy)、 やり方を変えないといけないと思ったと。 ここのくだりは気持ちがこもってるから悔しさが伝わってくるなW。「wasting my time」を連発している。
オブジェクト指向がいかに問題の本質とは関係ないところで、無駄にソフトウェアを複雑さにしているか、 このプレゼンの後半も具体例を使って説明している。 対比させているのは、Clojureの言語の特徴として説明している「データ指向(Data Orientation)」。
データ指向というのは例えばClojureのベクターやマップを使って、 問題領域にある処理対象のデータ構造を、ダイレクトに表現して扱っちまうことだ。と理解した。 この背景には、ほとんどのプログラムは本質的にデータ(情報)を処理するもの、という明快な思想がある。
データ構造を中心にプログラムを組むさまは、「Clojure for the Brave and True」の Functional Programming | Clojure for the Brave and True の章のサンプルゲーム開発の流れで要領がつかめる。 この本の著者はRich Hickeyのビデオは全部見ているようで、 The Unofficial Guide to Rich Hickey's Brain というブログ記事を書いている。(この記事のリプライが白熱した議論になっている。まじめに読んでないが・・・)
個人的にClojureを触り始めて、最初にクールに思った一つは ベクターとマップの存在で、これ自体が関数になる!ことだった。 Clojureを設計するにあたって、データ指向が念頭にあったとすると、ベクター/マップ/セットの出現は必然だったということか。 コードをデータとして表現できるLispを、言語のベースとして選択したことも必然だったと考えられる。
いやあでも、個人的にはLispをベースにしているとはいえ、 この人の言語設計のセンス(バランス感覚?)半端ないと思う。天才だと思います。 とちょっと最近はけっこう熱があがっている。 (いやなにせLispはLispでもCommon Lispはちょっと無理だな・・・)
Clojureの危険性
Clojureを始めたきっかけはPaul Grahamの「ハッカーと画家」を読んでだった。 この本はそれこそ情熱的なLisp啓蒙本と言っていい。 言語仕様はまったく紹介されてないにも関わらず、何だかLispのすごさだけは伝わってきた。 感化され「ANSI Common Lisp」「On Lisp」を買って読んだが、挫折。 (がんばって理解したいと思うほど言語が面白く感じなかった・・・)
しかし、その後「7つの言語7つの世界」のClojureの章を読んで、かなり面白い言語だと思った。 Common Lispに挫折してもClojureは楽しく入れたな。 どこが違いを生んだかというと、特に関数の書き方、ベクター、マップあたりが大きいと思う。
最近は仕事のちょっとした補助ツールをClojureで書いたり、あまった時間に色々戯れている。 せいぜい100〜200行程度のコードだが、それでもつくづく思う。 やばいくらいビューティフルな言語だなと!
で、また「ハッカーと画家」を読み直して、以前ピント来なかった部分が理解できて、新たに刺激を受けた。 この本は自分にとってバイブルになりつつある^^。
そして今日ふと積んでた「On Lisp」が目に入り、手に取って最初から読み直し始めた。 以下の一文に出くわす。
事実、Lispの持つ最大の危険はLispがユーザを堕落させてしまうかもしれないことだ。一度Lispをしばらく使うと、プログラミング言語とアプリケーションとの相性に敏感になりすぎ、元々使っていたプログラミング言語に戻っても、これでは必要な柔軟性が入らないという思いに常に囚われるようになりかねない
わかるわ〜。 自分もClojureでプログラムを組んでると、 「これ、他の言語が面倒くさく感じるようになるな。やばいな。」 と思っていたところだった。 プログラマーの三大美徳がだいぶ磨かれるな。これ。
On Lispの原著が読みたくなって調べたら、なんとフリーになっている!(http://www.paulgraham.com/onlisptext.html)
「SICP」、「Clojure for the Brave and True」もそうだが、Lisp界隈は本をフリーにする文化があるのかしら。
vimにlightline導入とvim-powerlineフォントパッチあてる
今月号のSoftware Designのvim特集を読んでいたら、ちょっとvim熱が上がった。 あましごりごりカスタマイズして使ってこなかったが、少しカッコよくしようかと。
vim-airlineてやつを思い出して使おうと思ってググってたら、lightlineというのが良さそうなので、こっちを導入してみた。
GitHub - itchyny/lightline.vim: A light and configurable statusline/tabline for Vim
.vimrcの編集
自分はNeoBundleを使っているので、
call neobundle#begin(expand('~/.vim/bundle/')) … NeoBundle 'itchyny/lightline.vim' … call neobundle#end()
でプラグインを追加。
もともとステータスバーも表示してなかったが、laststatus=2
にしつつ、lightlineの最低限の設定。
set laststatus=2 let g:lightline = { \ 'colorscheme': 'wombat', \ 'separator': { 'left': '<U+2B80>', 'right': '<U+2B82>' }, \ 'subseparator': { 'left': '<U+2B81>', 'right': '<U+2B83>' } \ }
しかし、フォントにパッチをあててないので、区切り線が>とか<にならない。
フォントにパッチを当てる
今OS Xで使っているフォントはRictyで、以前 OS X Yosemite で iTerm2 にRicty を導入 - てしりこじり を参考にして入れてある。
で、vim-powerlineの方のパッチを当てることにした。
$ git clone https://github.com/Lokaltog/vim-powerline.git vim-powerline $ cd vim-powerline/fontpatcher/ $ cp /usr/local/Cellar/ricty/3.2.3/share/fonts/Ricty-Regular.ttf . $ fontforge -script fontpatcher Ricty-Regular.ttf
必要なツールはfontforge
で、これbrew install fontforge
で手に入る。
上記で、Ricty-Regular-Powerline.ttf
というフォントが生成される。
これをFinderでダブルクリックしてインストールする。
その後iTerm2でフォント設定を変えると、
無事、区切り線が>とか<になりました。と。
Clojureの標準入力とファイル読み込み(文字列カウント)
ファイルのリードと標準入力の扱いのメモ。 「プログラミング言語C」の例題にもあるword count的なものを作ってみる。
ソース
leiningen環境にて
$ lein new app t_stdin
とやったあとにsrc/t_stdin/core.clj
を編集。
(ns t-stdin.core (:require [clojure.java.io :as io] [clojure.string :as str]) (:gen-class)) (defn count-words [line] (let [w (-> line (str/replace #"^\s+" "") (str/split #"[\s\t]+"))] (if (or (= line "") (= w [""])) 0 (count w)))) (defn -main [& args] (let [f (if-not (empty? args) (io/file (first args)) *in*) dat (line-seq (io/reader f))] (println (str "lines: " (count dat))) (println (str "words: " (reduce + (map count-words dat))))))
実行結果
このソースコード自体を入力に与えてみる。
t_stdin $lein run < src/t_stdin/core.clj lines: 32 words: 85
上記はプログラムに引数を与える形で、lein run src/t_stdin/core.clj
と打っても同じ。
wc
コマンドの結果で答え合わせ。
t_stdin $wc src/t_stdin/core.clj 32 85 713 src/t_stdin/core.clj
メモ
Clojureのファイルのリードは、下記のようにすれば
(line-seq (clojure.java.io/reader (clojure.java.io/file "file-name")))
行に分割したシーケンスとして返してくれる。
"file-name"
はファイル名の文字列。
(ファイルが見つからないときのエラー処理はまったく無視してるが・・・)
標準入力の場合は、
(line-seq (clojure.java.io/reader (clojure.java.io/file *in*)))
とするだけ。
とりあえず、今回は
(let [f (if-not (empty? args) (io/file (first args)) *in*) dat (line-seq (io/reader f))]
引数が与えられいればそれをファイル名として使う。
そうでない場合*in*
を使う。
一行の中の文字数のカウントは関数count-words
で行っている。
文字列のカウントは結局、空白で文字列を分割して得たシーケンスの要素の数を数える。
(count (str/split line #"[\s\t]+"))
的な感じでやるのだが、split関数は行の先頭に空白があった場合に、""
が要素に追加されてしまう。
t-stdin.core=> (str/split " aa bb" #"[\s\t]+") ["" "aa" "bb"]
ので、splitに与える前にreplaceで先頭の空白を除いている。
(-> line (str/replace #"^\s+" "") (str/split #"[\s\t]+"))
一方で、空行の場合文字列が""で、上記の正規表現のフィルターにひっかからない。
また、先頭が空白だけの行は結果が[""]
になるので、
(if (or (= line "") (= w [""])) 0 (count w))))
で特別扱いしている。 もっとうまいやり方ありそうだけど、まあとりあえず。
Clojureの楽しい入門書
いやあClojure楽しす。 "Clojure for The Brave and True" (紙本)を購入して読んでいるのだが、この本はすばらしいね。
ノリが軽いが、内容はよく整理されていて、構成がしっかりしている。 あとから目次頼りにリファレンスとしても使える。著者の奥さんのイラストも魅力的だしな。 直近は、Macroの仕組み&書き方の章を読み終えたばかりだが、Lispのエレガントなところが実に良く理解できた。
自分が他人に勧めるとしたら、まずは「7つの言語7つの世界」でClojureを試してみて、面白いと思ったなら、次はこの本だな。 オンラインで無料で公開しているのが信じられないくらいだ。(Learn to Program the World's Most Bodacious Language with Clojure for the Brave and True)
著者はClojureへの想いがよほど強いのか、どしどしeverybodyに使った欲しいからかな。
そして、この本を読んで良かったこと。 なんといっても、いまいちClojure (Lisp)のプログラミングをどう手を付けたらいいか分からなかったのが、この人のプログラムの組み方で要領がつかめたことだな。
「そうか、まずはデータ構造を考えればいいのか。特に難しく考えることはないんだな。」と。
プログラミングの基本は、結局、 UNIX哲学の”パイク: Cプログラミングに関する覚え書き”の
ルール5: データはすべてを決定づける。もし、正しいデータ構造を選び、ものごとをうまく構成すれば、アルゴリズムはほとんどいつも自明のものになるだろう。プログラミングの中心は、アルゴリズムではなくデータ構造にある。
に尽きるかもしれん。
この本の中でも、以下の格言を引用している。(参照:"Chapter 3: Do Things")
It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. —Alan Perlis
Alan Perlisはその時代の何を指して言っているの分からんが、Clojureの文脈で語るなら "10 functions on 10 data structures"の方はOOPを指してるだろうなと。
巷のブログなどを読むと、LISPerの人はよくOOPのだめな点と対比してLispの素晴らしさを強調すること多い。 そして、それが納得できて実に面白い。 つまるところOOPのいちいち無駄なところがどうしても我慢ならん人はClojureが楽しいと思う。
ClojureとSwingでボール落ちるアニメーション
ClojureからSwingを使って簡単な描画アニメーションを試してみるテスト。 ClojureはJavaの世界をかなり透過的にたたけるが、書き方のパターンさえ覚えてしまえば、コード量が全然少なくなるので、はっきり言ってJavaで書くよりラクだ(笑)。
とりあえず、上から下に小さなボールが落ちていくアニメーション作ってみた。
ソースコード
(ns test-paint.core (:import [javax.swing JFrame JPanel Timer] [java.awt.event ActionListener]) (:gen-class)) (def py (atom 0)) (defn next-y [y h] (if (<= y (- h 10)) (inc y) y)) (def panel (proxy [JPanel ActionListener] [] (paintComponent [g] (let [w (proxy-super getWidth) h (proxy-super getHeight) x (/ w 2) y (next-y @py h)] (doto g (.setColor java.awt.Color/BLACK) (.fillOval x y 10 10)) (reset! py y))) (actionPerformed [e] (.repaint this)))) (defn -main [& args] (let [frame (JFrame. "Test Paint") timer (Timer. 10 panel)] (doto frame (.add panel) (.pack) (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE) (.setSize 400 400) (.setVisible true)) (.start timer)))
Clojureでjavax.swing.JFrameを使ってみる
Clojureからjavax.swingを使い、GUIプログラミングをかじり始めつつ、Javaのメソッド呼び出しのやり方も練習もする。
Javaメソッドの呼び出し方
例えば、文字列Hello
の長さを返す。
repl=> (.length "Hello") 5
これはJavaで"Hello".length()
を呼びだしたと同じ。
staticメソッドを呼び出す
repl=> (java.lang.Math/abs -1) 1
これはそのままで分かりやすい。
デフォルトの状態でjava.lang
はnamespaceにはimport済みのようなので、下記も同様の動きをする。
repl=> (Math/abs -1) 1
JFrameを使ってウィンドウを出す
いつものとおり、leinで新しいプロジェクトを作って試す。
swing $lein new app hello_frame
単純に400x400の大きさのフレームを出してみる。
以下ソース。
(ns hello-frame.core (import (javax.swing JFrame)) (:gen-class)) (def frame (new JFrame)) (defn -main [& args] (.setSize frame 400 400) (.setTitle frame "Hello,JFrame") (.setVisible frame true))
JFrameのインスタンスは(new Frame)
で生成し、以降(.setSize ...)
などでメソッド呼び出しをしている。
(.setSize frame 400 400)
とかは、実はマクロを使っていて、内部的にはドットスペシャルフォームに変換される。(.
を使った特殊形式)
macroexpand-1
を使って展開してみると、以下のようになる。
repl=> (macroexpand-1 '(.setSize frame 400 400)) (. frame setSize 400 400)
逆に言うと、.
特殊フォームを使っても書ける訳で、下記も同じ動作をする。
(ns hello-frame.core (import (javax.swing JFrame)) (:gen-class)) (def frame (new JFrame)) (defn -main [& args] (. frame setSize 400 400) (. frame setTitle "Hello,JFrame") (. frame setVisible true))
こっちの方が分かりやすいよな。と個人的に思ったが・・・。
さて、Javaのクラスのインスタンスを作るのは
(new JFrame)
でできるが、こちらは(JFrame.)
という表記でも可。
引数も与えることができて、
(JFrame. "Heeloooo")
とするとタイトルの文字列を与えてインスタンス生成する。
同じオブジェクトのメソッドを連続して呼び出したい時に、便利なマクロdoto
が用意されている。
今回の場合では、frame
に対して、setSize
、setTitle
、setVisible
を呼びたい。以下のように書ける。
(doto frame (.setSize 400 400) (.setTitle "Hello,JFrame") (.setVisible true)))
うむ、便利。
とりあえず、JFrameをnewするときにタイトルも渡すようにすると、最終ソースは下記のようになる。
(ns hello-frame.core3 (import (javax.swing JFrame)) (:gen-class)) (def frame (JFrame. "Heeellooo!!")) (defn -main [& args] (doto frame (.setSize 400 400) (.setVisible true)))