2014年12月18日木曜日

「このEmacs Lispを使っている人はこんなEmacs Lispも使っています」がわかるwebサービス、el-moreを作っています

これは Emacs Advent Calendar 2014 の18日目の記事です。

Emacs Lispパッケージをおすすめするwebサービス、el-more を作っています。 「このEmacs Lispを使っている人はこんなEmacs Lispも使っています」がわかります。

現在、Herokuで動いています。

このEmacs Lispを使っている人はこんなEmacs Lispも使っています

expand-region を使っている人は multiple-cursorsyasnippet も使っているようです。

yaml-mode を使っている人は markdown-modecoffee-mode みたいな言語用のmajor-modeも使っているようです。

おもしろいですね。

みなさんもお気に入りのEmacs Lispのページを見てみてください。知らなかった素敵なEmacs Lispが見つかるかもしれません。

しくみ

  • GitHubで公開されているCaskファイルを検索 ()。 これで誰がどのEmacs Lispパッケージを使っているかがわかります。
  • MELPA からEmacs Lispパッケージの情報を取得します。
  • パッケージどうしの類似度 (この場合は一緒にインストールされる確率) を計算します。

実装

Ruby on Railsで作りました。

Herokuで動いています。Herokuで無料で使えるのは

  • DB (Heroku Postgres) 10000行
  • key-value store (Redis Cloud) 25MB

とかで、データ量をこれ以下に抑えるために泣く泣く集計対象のユーザ数を絞っています。(現在は100人)

ソースコード

ソースコードはGitHubにあります。

おわりに

公開したばかりでまだまだ未完成だと思います。

問題点、改善案、疑問、使ってみた感想など、何でも気軽にご連絡ください (Twitter)。

2014年9月2日火曜日

MacのEmacsでmozc.elを使う (mozc-serverをVagrantで動かす)

前回 、MacのEmacsでmozc.elを使うために、mozc-serverをビルドする手順を紹介しました。しかしその手順でビルドできない方がいたので、別の方法を試してみます。

今回はmozc-serverを仮想マシンで動かします。

  • Vagrant
  • VirtualBox

が必要です。

手順

結果

mozc.elが動きました。

mozc.elが動けばac-mozcも動くはずです。

2014年4月17日木曜日

MacのEmacsでmozc.elを使う (Mozcのビルド)

Mozcは、Google 日本語入力のオープンソース版のIMEです。 そしてmozc.elは、Mozcを使ってEmacsで日本語入力するためのEmacs Lispです。

今回は、Mac上のEmacsでmozc.elを使って日本語入力できるようにします。

EmacsとMozcは以下のような感じで通信しているみたいです。

[Emacs + mozc.el] <-- S式 --> [mozc_emacs_helper] <-- IPC --> [Mozc server]

EmacsとMozc serverが通信するのにmozc_emacs_helperが必要ですが、公式にはMac上でのビルドに対応していません。 なのでビルド用のスクリプトをちょっといじって、ビルドできるようにしました。

要件

公式のMacBuildInstructionsには

  • システム要件
    • OSX 10.5 or later intel only
  • ソフトウェア要件
    • Xcode
    • Qt 4.6.3 or later (optional)

とあります。Qtはオプションということなので今回は無しでビルドします。

私は OS X 10.9.2 + Xcode 5.1.1 で実行しました。

ビルド手順

詳細は公式のMacBuildInstructionsも参照してください。

ソースコードの取得

$ cd ~/
$ svn co http://src.chromium.org/svn/trunk/tools/depot_tools
$ export PATH=`pwd`/depot_tools:"$PATH"
$ mkdir -p ~/src/mozc
$ cd ~/src/mozc
$ gclient config http://mozc.googlecode.com/svn/trunk/src
$ gclient sync

gclient sync

Error: Command svn checkout https://src.chromium.org/chrome/trunk/src/tools/clang/scripts@171017 ...

みたいなエラーが出ましたが気にしないことにします。

build_mozc.pyを上書きします。

$ cd ~/src/mozc/src
$ curl -O https://raw.githubusercontent.com/igjit/mac-emacs-mozc/master/build_mozc.py

ビルドの実行

$ python build_mozc.py gyp --noqt
$ python build_mozc.py build_tools -c Release
$ python build_mozc.py build -c Release mac/mac.gyp:GoogleJapaneseInput mac/mac.gyp:gen_launchd_confs unix/emacs/emacs.gyp:mozc_emacs_helper

インストールします。

$ sudo cp -r out_mac/Release/Mozc.app /Library/Input\ Methods/
$ sudo cp out_mac/DerivedSources/Release/mac/org.mozc.inputmethod.Japanese.Converter.plist /Library/LaunchAgents
$ sudo cp out_mac/DerivedSources/Release/mac/org.mozc.inputmethod.Japanese.Renderer.plist /Library/LaunchAgents

ここで一旦GUIをログアウトします。 再ログイン後にMozcが使えるようになります。

動作確認

$ cd ~/src/mozc/src
$ echo -e '(0 CreateSession)\n(1 SendKey 1 97)' | out_mac/Release/mozc_emacs_helper
((mozc-emacs-helper . t)(version . "1.13.1651.101")(config . ((preedit-method . roman))))
((emacs-event-id . 0)(emacs-session-id . 1)(output . ()))
((emacs-event-id . 1)(emacs-session-id . 1)(output . ((id . "9469176874988818267")(mode . hiragana)(consumed . t)(preedit . ((cursor . 1)(segment ((annotation . underline)(value . "あ")(value-length . 1)(key . "あ")))))(candidates . ((size . 1)(candidate ((index . 0)(value . "あ")(annotation . ((description . "ひらがな")))(id . 0)))(position . 0)(category . suggestion)(display-type . main)(footer . ((label . "Tabキーで選択")))))(status . ((activated . t)(mode . hiragana)(comeback-mode . hiragana)))(all-candidate-words . ((candidates ((id . 0)(index . 0)(value . "あ")(annotation . ((description . "ひらがな")))))(category . suggestion))))))
$ 

Emacsの設定

init.elに以下を追記します。

;; (require 'mozc)
(load "~/src/mozc/src/unix/emacs/mozc.el")
(setq default-input-method "japanese-mozc")

;; exec-pathが通っていれば不要
(setq mozc-helper-program-name "~/src/mozc/src/out_mac/Release/mozc_emacs_helper")

C-\ で日本語入力が切り替わるはずです。 動作確認ができたら mozc.el, mozc_emacs_helper をそれぞれ load-path, exec-path の通った場所に置いてください。

See also

mozc.elを使ったモードレス日本語入力インタフェース、ac-mozc を作っています。 良かったらこちらも試してみてください。

(追記)

この手順でビルドできない方がいたので、別の方法を試してみました。

MacのEmacsでmozc.elを使う (mozc-serverをVagrantで動かす)

2013年11月10日日曜日

((Rで) 書く ((もっとRっぽい) Lisp) インタプリタ)

((Rで) 書く (Lisp) インタプリタ)の続きです。

以前の記事を見た@kohskeさんから、 環境はRの環境オブジェクトを使えば良いのでは、とアドバイスを頂きました。

やってみたら自前で環境を実装しなくて良い分簡潔になったし、より面白い使い方ができるようになりました。

コード

前回同様Gistに上げておきました。 変なところがあったら教えてください。

## Scheme Interpreter in R
## (more R-ish implementation of "lisp.R")
addGlobals <- function(env) {
procs <- list("+" = sum,
"*" = prod,
"-" = function(...) Reduce(`-`, list(...)),
"/" = function(...) Reduce(`/`, list(...)),
"=" = `==`,
"eq?" = `==`,
"equal?" = identical,
"not" = `!`,
"cons" = function(x, y) append(list(x), y),
"car" = function(x) x[[1]],
"cdr" = function(x) x[-1],
"list?" = is.list,
"null?" = function(x) identical(x, list()),
"symbol?" = is.character
)
rfname <- c(">", "<", ">=", "<=", "list", "length")
rfunc <- list()
rfunc[rfname] <- rfname
procs <- append(procs, rfunc)
for (name in names(procs)) assign(name, procs[[name]], envir=env)
}
evaluate <- function(x, env) {
if (is.character(x)) { # variable reference
get(x, envir=env)
} else if (!is.list(x)) { # constant literal
x
} else if (identical(x[[1]], "quote")) { # (quote exp)
x[[2]]
} else if (identical(x[[1]], "if")) { # (if test conseq alt)
test <- x[[2]]
conseq <- x[[3]]
alt <- x[[4]]
if (evaluate(test, env)) {
evaluate(conseq, env)
} else {
evaluate(alt, env)
}
} else if (identical(x[[1]], "set!")) { # (set! var exp)
var <- x[[2]]
exp <- x[[3]]
assign(var, evaluate(exp, env), envir=env)
} else if (identical(x[[1]], "define")) { # (define var exp)
var <- x[[2]]
exp <- x[[3]]
assign(var, evaluate(exp, env), envir=env)
} else if (identical(x[[1]], "lambda")) { # (lambda (var*) exp*)
vars <- x[[2]]
exps <- x[-c(1, 2)]
function(...) {
args = list(...)
inner <- new.env(parent=env)
for (i in seq(vars)) assign(vars[[i]], args[[i]], envir=inner)
for (exp in exps) val <- evaluate(exp, inner)
val
}
} else if (identical(x[[1]], "begin")) { # (begin exp*)
for (exp in x[-1]) val <- evaluate(exp, env)
val
} else { # (proc exp*)
xeval <- lapply(x, function(exp) evaluate(exp, env))
proc <- xeval[[1]]
exps <- xeval[-1]
do.call(proc, exps)
}
}
read <- function(s) {
readFrom(tokenize(s), 1)[[1]]
}
parse <- read
tokenize <- function(s) {
s <- gsub("\\(", " ( ", s)
s <- gsub("\\)", " ) ", s)
s <- sub("^\\s+", "", s)
strsplit(s, "\\s+")[[1]]
}
readFrom <- function(tokens, i) {
if (length(tokens) < i) stop("unexpected EOF while reading")
if (tokens[i] == "(") {
L <- list()
i <- i + 1 # skip "("
while(tokens[i] != ")") {
res <- readFrom(tokens, i)
L <- append(L, res[1])
i <- res[[2]]
}
i <- i + 1 # skip ")"
return(list(L, i))
} else if (tokens[i] == ")") {
stop("unexpected )")
} else {
return(list(atom(tokens[i]), i + 1))
}
}
atom <- function(token) {
num <- suppressWarnings(as.numeric(token))
if (is.na(num)) token else num
}
toString <- function(exp) {
if (is.list(exp)) {
sprintf("(%s)", do.call(paste, lapply(exp, toString)))
} else {
tryCatch(as.character(exp),
error=function(e) sprintf("#<%s>", typeof(exp)))
}
}
repl <- function(prompt='lispr> ', parent=.GlobalEnv) {
env <- new.env(parent=parent)
addGlobals(env)
while(TRUE) {
val <- evaluate(parse(readline(prompt)), env)
cat(toString(val))
cat("\n")
}
}
view raw lispr.R hosted with ❤ by GitHub

主な変更点は

  • Rの環境オブジェクトを流用
  • Lispの関数の呼び出し方法を変更: proc(exps) から do.call(proc, exps) に

遊び方

前回と同様です。

Rインタプリタを起動してコードを読み込ませます。
repl() を実行するとLispの対話式インタプリタが起動します。

$ ls
lispr.R
$ R -q
> source("lispr.R")
> repl()
lispr> (+ 1 2)
3
lispr> (define add2 (lambda (x) (+ x 2)))
#<closure>
lispr> (add2 40)
42
lispr>

関数 repl の引数 parent に渡した環境がLispの環境の親になります。(デフォルトだとRのグローバル環境)
よって今回はLispの環境からRの環境を参照できるので、LispでRの関数が使えます。

lispr> (: 1 10)
1 2 3 4 5 6 7 8 9 10
lispr> (rnorm 3)
0.536481224524994 -0.547993231580984 -2.14041393248752
lispr> (seq 0 10 2)
0 2 4 6 8 10
lispr> (begin (plot cars) (lines (lowess cars)) 1)
1
lispr> 

S式でRを使える!楽しい!

感想

やはり言語処理系を作るのは楽しいです。

2013年8月9日金曜日

((Rubyで) 書く (Lisp) インタプリタ)

最近Rubyを始めました。

手始めにLispインタプリタを書いてみます。

元ねたはPeter Norvigの (How to Write a (Lisp) Interpreter (in Python)) (日本語訳: ((Pythonで) 書く (Lisp) インタプリタ))です。

(ちなみに以前、Rでも同じことをやりました: ((Rで) 書く (Lisp) インタプリタ))

コード

Gistに上げておきました。 変なところがあったら教えてください。

# -*- coding: utf-8 -*-
# Scheme Interpreter in Ruby
# (a Ruby port of the Peter Norvig's "lis.py")
class Env < Hash
def initialize(parms = [], args = [], outer = nil)
@outer = outer
update(Hash[parms.zip(args)])
end
def find(var)
if self.has_key? var
self
else
@outer.find(var)
end
end
end
def add_globals(env)
[:+, :-, :*, :/].each do |s|
env[s] = ->(*x) { x.reduce { |a, e| a.send(s, e) } }
end
[:>, :<, :>=, :<=].each do |s|
env[s] = ->(x, y) { x.send(s, y) }
end
env.update({
:not => ->(x) { !x },
:'=' => ->(x, y) { x == y },
:equal? => ->(x, y) { x == y },
:eq? => ->(x, y) { x.equal? y },
:length => ->(x) { x.length },
:cons => ->(x, y) { [x] + y },
:car => ->(x) { x[0] },
:cdr => ->(x) { x[1..-1] },
:append => ->(x, y) { x + y },
:list => ->(*x) { x },
:list? => ->(x) { x.is_a? Array },
:null? => ->(x) { x == [] },
:symbol? => ->(x) { x.is_a? Symbol }
})
end
def evaluate(x, env)
if x.is_a? Symbol # variable reference
env.find(x)[x]
elsif !x.is_a? Array # constant literal
x
elsif x[0] == :quote # (quote exp)
x[1]
elsif x[0] == :if # (if test conseq alt)
_, test, conseq, alt = x
evaluate(evaluate(test, env) ? conseq : alt, env)
elsif x[0] == :set! # (set! var exp)
_, var, exp = x
env.find(var)[var] = evaluate(exp, env)
elsif x[0] == :define # (define var exp)
_, var, exp = x
env[var] = evaluate(exp, env)
elsif x[0] == :lambda # (lambda (var*) exp)
_, vars, exp = x
->(*args) { evaluate(exp, Env.new(vars, args, env)) }
elsif x[0] == :begin # (begin exp*)
x[1..-1].map { |exp| evaluate(exp, env) }[-1]
else # (proc exp*)
exps = x.map { |exp| evaluate(exp, env) }
exps[0].call(*exps[1..-1])
end
end
def read(s)
read_from(tokenize(s))
end
alias parse read
def tokenize(s)
s.gsub('(', ' ( ').gsub(')', ' ) ').split
end
def read_from(tokens, cont_tokens_proc = nil)
raise 'unexpected EOF while reading' if tokens.empty?
token = tokens.shift
if '(' == token
l = []
while tokens[0] != ')'
if tokens.empty? && !cont_tokens_proc.nil?
tokens.concat(cont_tokens_proc.call) while tokens.empty?
break if tokens[0] == ')'
end
l << read_from(tokens, cont_tokens_proc)
end
tokens.shift # pop off ')'
l
elsif ')' == token
raise 'unexpected )'
else
atom token
end
end
def atom(token)
begin
Integer(token)
rescue
begin
Float(token)
rescue
token.to_sym
end
end
end
def to_string(exp)
if exp.is_a? Array
'(' + exp.map { |x| to_string x }.join(' ') + ')'
else
exp.to_s
end
end
def repl(prompt = 'lisp.rb> ')
env = add_globals(Env.new)
tokens_proc = ->() { tokenize(gets) }
while true
print prompt
puts to_string(evaluate(read_from(tokens_proc.call, tokens_proc), env))
end
end
if __FILE__ == $0
repl
end
view raw lisp.rb hosted with ❤ by GitHub

遊び方

$ ruby lisp.rb 
lisp.rb> (+ 1 2 3)
6
lisp.rb> (define add2 (lambda (x) (+ x 2)))
#<Proc:0x00000001647958@lisp.rb:62 (lambda)>
lisp.rb> (add2 40)
42
lisp.rb> (define map (lambda (f l) (if (null? l) (quote ()) (cons (f (car l)) (map f (cdr l))))))
#<Proc:0x0000000164f1a8@lisp.rb:62 (lambda)>
lisp.rb> (map add2 (list 10 20 30))
(12 22 32)
lisp.rb> 

感想

Rubyを使ってみて思ったことをメモしておきます。

  • Perlっぽい

    • メソッドのかっこを省略できる
    • ifとかwhileを後置できる
  • Lisp(関数型)っぽい

    • Symbol
    • いろんなものが値を返す(ifとか)
    • returnを省略できる
  • オブジェクト指向

    • 何でもオブジェクト!
    • mapとかの高階関数がメソッドなのはなんだか違和感があるけど、 メソッドチェインができるのは良いかも。

割と良い感じです。もうちょっと使ってみます。 ワンライナーとかちょっとしたスクリプトとかPerlのかわりに使ってみようかな。

参考文献

同じことをやっている方は他にもいます。
元ねたのページ のコメントで紹介されていた 以下の実装が簡潔で素敵です。 わからないところはこちらをチラ見しながら作りました。

(追記)

その後少し手を加えて、REPLで式の途中に改行を入れられるようにしました。
(上に貼ってあるGistは修正後です。)

こんな感じに入力できます。

lisp.rb> (+ (* 3
               (+ (* 2 4)
                  (+ 3 5)))
            (+ (- 10 7)
               6))
57
lisp.rb> 

思ったより少ない変更で実現できました。 (Gistの履歴)

2013年7月22日月曜日

Common LispでR (RCLを使ってみる)

前回の続きです。

今回はRCLを使って、Common LispからRの関数を呼んでグラフを描いてみます。

インストール

こちらに書いてある通り、Quicklispでインストールできました。

> (ql:quickload :rcl)

使い方

詳しいマニュアルは無いみたいです。

RCL examples に短いサンプル、 Comparing RCL with RpyRPyとの比較があります。

あとインストール先の examples/ 以下にサンプルコードがいくつかあります。

グラフ描画のコード

サンプルコードを参考にしながら何となく書いてみました。

(asdf:oos 'asdf:load-op :rcl)
(r:r-init)
(r:enable-rcl-syntax)

(defun plot-num-of-animals (animal-state)
  (let ((day (mapcar #'car animal-state))
        (num (mapcar (lambda (x) (length (cadr x))) animal-state)))
    [plot day num :type "o" :pch 20 :ylim [c 0 [max num]]
         :main "動物の個体数" :xlab "経過日数" :ylab "動物の個体数"]))

(defun plot-hist (animal-state)
  (let ((max-x 1.0)
        (max-y 160))
    (mapc (lambda (state)
            (let ((day (car state))
                  (rate (cadr state)))
              [hist rate :breaks [seq 0 max-x 0.02]
                   :ylim [c 0 max-y] :main (format nil "~7d 日後" day)
                   :col "gray" :xlab "直進率" :ylab "動物の個体数"]))
          animal-state)))

(defun animal-state->png (out-dir animal-state)
  (r:with-device ((format nil "~A/%03d" out-dir) :png :width 400 :height 300)
    (plot-num-of-animals animal-state)
    (plot-hist animal-state)))

角括弧[] の部分でRの関数を呼んでいます。 Lispのコードと混ぜて書けるのがおもしろいです。

あと、r:with-device で描画のコードを包むのがLispっぽくて好きです。 (Rだと デバイス開く、描画、デバイス閉じる、と手続き的)

描画

描画を実行します。

まずは前回同様、20万日分シミュレーションを走らせ、1万日間隔で動物の状態をdumpします。

> (defparameter *dump* (dump-animal-state 200000 10000))

グラフを描画します。

> (animal-state->png "Rplot" *dump*)

Rplot/ 以下に連番のpngファイルが生成されます。

前回同様のグラフができました。

感想

今回試した範囲では割と良い感じに使えました。 Rに慣れた人が、Common Lispでデータをいじっていて、ちょっとグラフを描いて確かめたい、 みたいなときに便利かも。

ちなみにCommon Lispでグラフを描く方法は他にもいろいろあるみたいです。 (CLiki: plotting)

2013年6月9日日曜日

Land of Lispの進化シミュレーションの様子をRでグラフ化する

Land of Lisp の10章で進化シミュレーションを実装します。

こんな感じ。

* が草で M が動物です。
ちなみに翻訳者のShiro氏が、遺伝子の傾向の違いを色で表示する版のGaucheのコードを公開しています。 (Island Life - GaucheでもLand of Lisp)

ところでシミュレーションを続けると、行動パターンが異なる2つの種に進化します。

  • ゾウ : ジャングル(画面中央部)の豊富な食物に集中する。ジャングルから離れないように周りをうろうろする。
  • ロバ : 乾燥帯(ジャングル以外の部分)で食物を探し回る。広い範囲を移動する。

面白いですね。

この種の分化がどのように進むのか気になったので、Rを使ってグラフ化してみます。

動物の遺伝子

進化シミュレーションのコードは公式ページで公開されています。(evolution.lisp)

動物の構造体の中身はこんな感じ。

#S(ANIMAL :X 50 :Y 15 :ENERGY 114 :DIR 0 :GENES (5 4 10 9 5 5 9 10))

genes フィードが遺伝子情報で、動物の行動パターンを決定します。 8つの整数は動物が8方向どちらに向きを変えやすいかを表します。 先頭 (スロット 0) の値が大きければその動物は向きを変えずに直進しやすく、 次 (スロット 1) の値が大きければ時計回りに45度向きを変えやすい、といった感じです。

ゾウ? それともロバ?

動物の性質を表すために、 直進率 という指標を考えました。これは動物がまっすぐ進む確率です。 直進率が高い = 長距離を移動する = ロバ、直進率が低い = うろうろする = ゾウ、と考えて良いですかね。

動物の直進率を計算する straight-rate 関数:

(defun straight-rate (animal)
  (let ((genes (animal-genes animal)))
    (/ (car genes)
       (apply #'+ genes))))

動物の状態をdump

進化の過程を知りたいので、一定間隔で動物たちの直進率をdumpします。

(defun dump-animal-state (days by)
  (loop for i
     from 1
     to days
     do (update-world)
     if (zerop (mod i by))
     collect (list i
                   (mapcar #'straight-rate *animals*))))

days で指定した日数分シミュレーションを走らせ、by で指定した間隔で全動物の直進率を計算します。
この関数は (経過日数 全動物の直進率のリスト) のリストを返します。

3日間シミュレーションを走らせて1日間隔でdumpする例:

> (defparameter *dump* (dump-animal-state 3 1))
*DUMP*
> *dump*
((1 (5/23 5/23)) (2 (1/5 1/5 5/23 5/23))
 (3 (5/23 5/23 1/5 1/5 1/5 1/5 5/23 5/23)))

Rにデータを渡す

指定期間の動物の状態を計算できるようになりましたが、 Common LispとRの間でどうやってデータをやり取りしましょう。

今回はCommon Lisp側でS式をRのデータ表現に変換することにしました。 R側はそれをevalするだけなので楽です。evalは危険だけどこういう時は手軽で便利ですね。

animal-state->r 関数は動物の状態のリストをRのデータ表現に変換します。

(defun animal-state->r (animal-state)
  (format nil "list(~{~a~^,~%     ~})~%"
          (mapcar (lambda (x)
                    (format nil
                            "list(day=~a, straightRate=c(~{~f~^, ~}))"
                            (car x)
                            (cadr x)))
                  animal-state)))

先ほどの *dump* を変換してみます。

> (animal-state->r *dump*)
"list(list(day=1, straightRate=c(0.2173913, 0.2173913)),
     list(day=2, straightRate=c(0.2, 0.2, 0.2173913, 0.2173913)),
     list(day=3, straightRate=c(0.2173913, 0.2173913, 0.2, 0.2, 0.2, 0.2, 0.2173913, 0.2173913)))
"

ファイルに書き出す関数:

(defun write-animal-state (fname animal-state)
  (with-open-file (my-stream
                   fname
                   :direction :output
                   :if-exists :supersede)
    (princ (animal-state->r animal-state)
           my-stream)))

では実際にシミュレーションを走らせて、結果をファイルに書き出します。

> (load "evolution")
T
> (write-animal-state "animal-state.R" (dump-animal-state 200000 10000))

20万日分シミュレーションを走らせ、1万日間隔で animal-state.R というファイルに書き出しました。

ちなみにdump前に (load "evolution") を実行しているのは世界をリセットするためです。 オリジナルのコードに手を入れずに手っ取り早く世界をリセットする方法が他に思いつきませんでした...

グラフの描画

Rでグラフを描画します。

plot-evolution.R

stateFile <- "animal-state.R"
stateList <- eval(parse(stateFile))

outDir <- "Rplot"
png(filename=file.path(outDir, "%03d.png"), width=400, height=300)

numTable <- data.frame(t(sapply(stateList,
                                function(x) {
                                    c(day=x[["day"]],
                                      num=length(x[["straightRate"]]))
                                })))
plot(numTable, type="o", pch=20, ylim=c(0, max(numTable[, 2])),
     main="動物の個体数", xlab="経過日数", ylab="動物の個体数")

maxX <- 1.0
maxY <- 160

for (state in stateList) {
    hist(state[["straightRate"]], breaks=seq(0, maxX, 0.02), ylim=c(0, maxY),
         main=sprintf("%7d 日後", state[["day"]]), col="gray",
         xlab="直進率", ylab="動物の個体数")
}

dev.off()

このRスクリプトを実行します。

$ mkdir Rplot
$ Rscript --vanilla plot-evolution.R

Rplot/ 以下に連番のpngファイルが生成されます。

結果

まずは動物の個体数の遷移です。

個体数は160前後で安定しています。これは植物が生える速度が常に一定のため、世界が平衡状態を保っているということでしょうか。

次に動物の直進率と個体数のヒストグラムを見てみます。

1万日後。まだ同じ傾向の動物しかいません。

3万日後。目立った変化無し。

4万日後。種の分化が始まった!

5万日後。完全に分かれました。


14万日後。ロバの直進率がさらに上がっています。


おわりに

  • 種の分化の様子を見ることができて楽しかったです。
  • Common LispとRの連携はやっつけだったので他の手法も試してみたいです。
  • 本の写経以外でCommon Lispのコードを書いたのは初めてなので、おかしなところがあったらどんどん指摘してください。

(追記)

他の手法を試してみました: Common LispでR (RCLを使ってみる)