Programmer's Note

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

世界観と世界を変える一冊

ほぼ一年ぶりの更新か^^;

さて、「7つの言語7つの世界」を読了。一通り全言語のエッセンスは学べたかな。(Prolog , Haskellは最後はちょっと飛ばしたが、もう少し知りたい時に読めばいいかなと)
章順ではなく気になる言語から読んでいって、途中ブランクがだいぶ空いて、気分が向いたら別の言語を読んでみる。というやり方で足がけ1年くらいかかっただろうか。
全部読み通した今、改めてこの本は素晴らしい本だなと思う。プログラマとして確実にレベルアップできる貴重な教材だ。
冒頭を読み返すと、最初はピンと来なかった概要が実によく分かるようになったし、構成も実はOOPから関数型プログラミングへ、一本の道筋になっている。というところに感動する。
今まさに、この本をガイドに未知の土地に旅した後、その余韻に浸っている感じだ。
最後まで楽しく読めたのは、やはり著者の力量が大きい。それぞれの言語の良い点悪い点が実に分かりやすく的確に書いている。映画の人物になぞらえたメタファも楽しい。
この本でClojureを知ったおかげで、関数型プログラミングLISPの世界に入れて随分と視野が広がった。
7つの言語それぞれユニークなポジションにいるが、言語パラダイムの観点からあえて3つ選ぶとしたら、Ruby(OOP)、Prolog(論理型)、Clojure(関数型)かな。ここはもう個人の好みだが。
実はすでに次作の「Seven more languages in seven weeks」を購入したが、冒頭のElixir言語作者のprefaceを読んで驚いた。Elixirはこの本がなかったら生まれていなかった。
一冊の本が新しい言語を生んだのか。確かにこの本を読んで世界観は変わるが、実際に世界をも変えていたとは!

RubyとLispとClojureのコードの読みやすさ

なぜRubyは許容可能なLISPなのか - 翡翠はコンピュータに卵を生むか」という記事が面白かった。

この記事の中でサンプルコードを使って、 プログラムの読みやすさを対比させていた部分があったので、 Clojureでやってみたくなった。

Ruby
[1,2,3].map {|n| n*n }.reject {|n| n%3==1 }
LISP
(remove-if (lambda (n) (= (mod n 3) 1))
           (mapcar (lambda (n) (* n n))
                   '(1 2 3)))

(Clojure以外のLisp系言語はよく知らないが、たぶんこれはCommon Lisp)

同じことをClojureで記述してみる

Clojureのlambdaは無名関数なので、同じことをやると以下のようになる。

(remove (fn [n] (= (mod n 3) 1)) 
                (map (fn [n] (* n n))
                     [1 2 3]))

上記LISPよりかは読みやすいが、ほぼ同等だな。

リーダーマクロ#()を使ってみると。

(remove #(= (mod % 3) 1) 
        (map #(* % %)
             [1 2 3]))

ぐっと簡潔になった。 さらに、スレッディングマクロを使うと、 Rubyメソッドチェーンのように処理の流れの通りに書ける。

(->> [1 2 3]
     (map #(* % %))
     (remove #(= (mod % 3) 1)))

一行の場合

(->> [1 2 3] (map #(* % %)) (remove #(= (mod % 3) 1)))

スレッディングマクロは、かなりプログラムの読みやすさに貢献している。 慣れてしまえばこれはRubyに匹敵する読みやすさになる。

とはいえ、しょっぱなはかなり戸惑ったけどね…。

データ指向 (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をベースにしているとはいえ、 この人の言語設計のセンス(バランス感覚?)半端ないと思う。天才だと思います。 とちょっと最近はけっこう熱があがっている。 (いやなにせLispLispでも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 Designvim特集を読んでいたら、ちょっと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でフォント設定を変えると、

f:id:hifistar:20160421232421p:plain

無事、区切り線が>とか<になりました。と。

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が楽しいと思う。