3/26メモ

heroku

わかりやすい。

heroku 上で Rails アプリが動くところまでざっと見てみました。てか、heroku の前にまともに Rails の勉強しなきゃ。

ほほう。

vagrant

上の heroku の動画で vagrant を使っていて、docker や virtualbox はコマンドラインからガリガリ使っていたけど、vagrant は名前くらいしか知らなかったのでちょっとメモしておく。

うんうん、virtualbox のイメージも空から降ってくるべきですよね。

swig

sirenmruby から MRI に移植してるんだけど、どうせなら SWIG で OpenCASCADE ラッパーな ruby-occt を作ってゴニョゴニョすべきかなと思いつつ

を見てたんだけど、PythonOCC では大量の SWIG 用ラッパーファイルで頑張ってらっしゃる。*.i だけなら流用できるのかなー、でも python なんちゃらって文字があるぞ。めんどいなあ…。そもそも siren は OCCT の無駄に多いクラスや機能を適度にまとめて使えることが利点だから、フル機能の OCCT を使いたいんだったら PythonOCC やネイティブな OpenCASCADE を使えばいいと思ってたりして悩みどころです。

Kinect 360 買っちゃった

前々回と前回の OpenCV まわりに関連して、とうとう Kinect を購入しました。Kinect for Windows じゃなく 360 でもとりあえずは研究に使えるらしいし、ドライバや SDK が厳格そうな Windows で開発するわけでもない、360なら価格も中古で低価格程度ということで Amazon で5000円の中古をポチ。ヤフオクで2000円弱で出品されてるんですが、動作保証と電源ケーブル同梱版、手続きの早さで割高だけど Amazon のを購入しました。

月曜日(明日)届く予定です。楽しみ。Kinect の到着を待つ間、Python で書いていた(というかコピペしてた)ステレオマッチング処理を Ruby で書き直そうと思ってドキュメントを見てたら ruby-opencv には StereoSGBM()ない模様

こんな時に颯爽と実装してプルリクをぶん投げられる人になりたい…。

デジタルデバイス処分

PS3、楽天 KOBO、NEC LifeTouch NOTE、Lavie Tab W 708 など使ってないデバイスを処分中。ハンディカムや液タブも売り出そうか悩み中です。

Ruby と OpenCV でカメラを使う

前回に引き続き、ruby-opencv でカメラデバイスをソースにしてリアルタイム検知をやってみたいと思います。

使っているカメラ

Microsoft 社製の USB 接続 HD ウェブカムです。エンドユーザ向けにしてはちょっと高めですが画質も良いです。LMDE2 では UVC で使えています。

camera

コード

カメラデバイスを使うには OpenCV::CvCapture.open() メソッドを使います。引数の番号はデバイス番号。対象のデバイスが /dev/video0 だったら 0 を指定すれば良いようです。

camera.rb

#!/usr/bin/env ruby

require 'opencv'
include OpenCV

camera = CvCapture.open(0)
camera.width = 320
camera.height = 240

window = GUI::Window.new('camera')
pattern_size = CvSize.new(7, 10)

while true

  image = camera.query
  gray = image.BGR2GRAY
  corners, found = gray.find_chessboard_corners(pattern_size, CV_CALIB_CB_ADAPTIVE_THRESH)

  if found
    corners = gray.find_corner_sub_pix(corners, CvSize.new(3, 3), CvSize.new(-1, -1), CvTermCriteria.new(20, 0.03))
    result = image.draw_chessboard_corners(pattern_size, corners, found)
    window.show result
  else
    window.show image
  end

  key = GUI::wait_key 100
  break if key == 'q'
end

CvCapture#query() メソッドで取得すると IplImage オブジェクトが返ってくるので、あとは前回と同様にチェッカーボード検知・描画を行なっているだけです。タイムアウト付きのキー入力待ち GUI::wait_key() で 100 ミリ秒待って、 Q キーが押下されなければ繰り返します。

実行

ruby camera.rb

実行するとこんな感じ↓

画面がちらついているのは、GTK recordMyDesktop で Cinnamon 上のウィンドウを撮影しているせいだと思います。 検知している時より、してない時の方がフレームレートが落ちてる気がするのはなぜだろう…。

Ruby と OpenCV でチェッカーボード検知

前日に引き続き、ruby-opencv でチェッカーボードを認識させてみたいと思います。チェッカーボードとは市松模様のパターンのことで、これを認識することにより映像中の3次元座標系(の行列)を確定させることができ、映像の上に3Dモデルをマッピングする第一歩となります。

1. コード

checkerboard.rb

#!/usr/bin/ruby

require 'opencv'
include OpenCV

if ARGV.size < 1
  STDERR.puts "#{$0} srcfile [destfile]"
  exit 1
end
src = ARGV.shift
dest = nil
if ARGV.size >= 1
  dest = ARGV.shift
end

mat = CvMat.load(src, 1)
gray = mat.BGR2GRAY
pattern_size = CvSize.new(7, 10)
corners, found = gray.find_chessboard_corners(pattern_size, CV_CALIB_CB_ADAPTIVE_THRESH)

if found
  puts "found"
  corners = gray.find_corner_sub_pix(corners, CvSize.new(3, 3), CvSize.new(-1, -1), CvTermCriteria.new(20, 0.03))
end

img = mat.draw_chessboard_corners(pattern_size, corners, found)

if dest
  img.save dest
else
  w = GUI::Window.new('Result')
  w.show img
  GUI::wait_key
end

映像をグレイスケールイメージに変換してから、 find_chessboard_corners でチェスボードを検知します。チェスボードのパターンはここと同じく 7×10 サイズのものでやってみました。

2. 実行

ruby checkerboard.rb src.jpg dest.jpg

とタイプして実行すると、

のように、ちゃんとパターンを認識できています。カメラから遠ざけると

のように検出した点に沿って線が出てきます。ここで検知した点列と各々の2次元座標値から3次元座標系を逆算していき…たいところですが、また次回です。

あ、部屋が汚いのは見ないでください… _(┐「ε:)_

Ruby と OpenCV で顔認識

去年の秋に組んだ新しいメインマシンは、MSI のB150M MORTAR ARCTIC (MS-7A45) という新しいマザボで、LMDE2 x64 を乗せて使おうとすると USB デバイスまわりが突然使用不能になったりして厄介でした。

B150M

USB デバイスの抜き差し時にホストコントローラが全部死ぬという惨事。PS/2キーボードもマウスもインターフェイスがないので、USB接続なんですが、それも使えなくなっちゃいます。何度かネットワーク経由でログインして再起動してました。Linux カーネル 4.x 系では USB デバイス関連のモジュールが充実していたようですが、なかなか時間が取れずに騙し騙し使っていました。ここ数ヶ月の LMDE のアップデートで同様の症状はなくなったみたいなので、ようやくメインマシンにカメラなどの USB デバイスを差して遊んでいます。ビバ他力本願。

仕事で上司が「ARをやってみたいな~」と言っていたので、久々に OpenCV を触ってみようと思い、今更感はあるものの Ruby で顔認識をやってみました。 OpenCV は2013年2月に Python でスネークさんのフィギュアの顔認識をして遊んだ程度…今度はちゃんと長続きするかな!

以下、自分用のメモです。

環境

  • LMDE2 (Linux Mint Debian Edition) x64
  • Ruby 2.1.0
  • ruby-opencv 0.0.7

1. ライブラリのインストール

sudo gem install opencv

2. 顔認識用の定義ファイルのダウンロード

wget https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml

3. コード

ほぼサンプルのままです。認識した部分に赤い枠線を描いて保存します。

facedetect.rb

#!/usr/bin/ruby

require 'opencv'
include OpenCV

if ARGV.size < 2
  STDERR.puts "Usage: #{$0} srcfile destdest"
  exit 1
end

src = ARGV.shift
dest = ARGV.shift

img = IplImage::load src
d = CvHaarClassifierCascade::load "./haarcascade_frontalface_default.xml"

d.detect_objects(img).each do |r|
  img.rectangle! r.top_left, r.bottom_right, :color => CvColor::Red, :thickness => 3
end

img.save dest

4. 実行

ruby facedetect.rb srcfile.jpg destfile.jpg

動きました!

変なところを認識してるけれど、まずはひと安心 _(:3」∠)_

Raspberry Pi 3 で mruby を動かす

DSC_0193
DSC_0193

Raspberry Pi 3 が届きました!

発売より半年ほど出遅れましたが、ようやく Raspberry Pi 3 Model B を購入したので、組み込み向け軽量 Ruby である mruby を動かしていきたいと思います。

RPi3 は 1.2GHz クアッドコア 64bit ARM と 1GB の RAM を搭載したコンピュータです。私が4年ほど前まで使っていたネットブック LaVie Light PC-BL300 が Intel Atom N280 (1コア、1.66GHz) の 1GB RAM 構成でしたので、単純比較はできないものの35ドルで購入できるのはスゴいですね。

(おっと、mruby が想定している組み込み機器と比較してもはるかに高性能な上、MRI そのものも RPi で動いているので意味ないなんて言わないでください! mruby を ARM でセルフビルド・実行してみたかったんだよー!)

数年前に購入した初代 RPi Model B+ と比べても、Wi-Fi や Bluetooth 搭載であることはありがたいです。

RPi3 は Zero のように価格高騰もしていなかったので、普通に Amazon で購入しました。ソニーの英国工場で生産されているらしい RS 版と、中国で生産されている element14 版がありますが、ちょっとだけ安い element14 版、ケースとヒートシンク2個付きを購入しました。5,980円でした。購入時のレートで35ドルは3,722円。日本の Amazon で購入ができる本体のみは4,200円。差額は1,780円ですが、ケースが1,200円、ヒートシンクが2個で580円と考えると妥当な値段だと思います。

DSC_0184
DSC_0184

上が初代 Model B、下が購入した Pi 3 です。緑色のケースが「アリの巣コロリ」っぽくて気に入っています。 このケースがかもし出す昆虫採集感のおかげで、ラズパイを縁の下に設置しなければならなくなった時も安心です。

環境の確認

Raspbian の大きい方のディスクイメージを入れて、最初から色々とプログラミング環境が入っていました。

pi@raspberrypi:~ $ which ruby python perl node lua
/usr/bin/ruby
/usr/bin/python
/usr/bin/perl
/usr/bin/node
/usr/bin/lua

lua まで入っているんですねー。さすが教育用といったところか、私は使わないけど軽量 IDE の Geany もプリインストール済みです。X 環境が入っていない Raspbian LITE 版もありますが、ここまでは充実してないのかな。

pi@raspberrypi:~ $ ruby --version
ruby 2.1.5p273 (2014-11-13) [arm-linux-gnueabihf]
pi@raspberrypi:~ $ git --version
git version 2.1.4
pi@raspberrypi:~ $ gcc --version
gcc (Raspbian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
pi@raspberrypi:~ $ make --version
GNU Make 4.0
このプログラムは arm-unknown-linux-gnueabihf 用にビルドされました
Copyright (C) 1988-2013 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL バージョン 3 以降 <http://gnu.org/licenses/gpl.html>
これはフリーソフトウェアです: 自由に変更および配布できます.
法律の許す限り、 無保証 です.

基本的なものはすべて入っているようです。バージョンは念の為 apt upgrade した後のものです。

mruby の取得→セルフビルド

git で clone します。鍵ファイルを RPi に転送してないので、SSH ではなく HTTPS 経由で clone してます。

pi@raspberrypi:~ $ git clone https://github.com/mruby/mruby.git
Cloning into 'mruby'...
remote: Counting objects: 34141, done.
remote: Compressing objects: 100% (21/21), done.
remote: Total 34141 (delta 3), reused 0 (delta 0), pack-reused 34117
Receiving objects: 100% (34141/34141), 7.02 MiB | 951.00 KiB/s, done.
Resolving deltas: 100% (21335/21335), done.
Checking connectivity... done.
pi@raspberrypi:~ $ cd mruby/
pi@raspberrypi:~/mruby $ ls
AUTHORS          Makefile   appveyor.yml        doc       mrbgems               test
CONTRIBUTING.md  NEWS       appveyor_config.rb  examples  mrblib                travis_config.rb
ChangeLog        README.md  benchmark           include   mruby-source.gemspec
LEGAL            Rakefile   bin                 lib       src
MITL             TODO       build_config.rb     minirake  tasks

無事 clone できました。 minirake でビルドしてみます。

pi@raspberrypi:~/mruby $ ./minirake

できるかな…!

....
YACC  mrbgems/mruby-compiler/core/parse.y -> build/host/mrbgems/mruby-compiler/core/y.tab.c
sh: 1: bison: not found
sh: 1: bison: not found
rake aborted!
Command Failed: [bison -o "/home/pi/mruby/build/host/mrbgems/mruby-compiler/core/y.tab.c"
  "/home/pi/mruby/mrbgems/mruby-compiler/core/parse.y"]

bison がないって怒られてしまいました。さすがにないか~、ということは flex もなさそうですね。ささっと入れます。

pi@raspberrypi:~/mruby $ sudo apt install bison flex

私の環境では関連ライブラリも含めて3.7MBのファイルが入りました。さあ、気を取り直して再ビルドに挑戦です。

pi@raspberrypi:~/mruby $ ./minirake

結構時間がかかったものの、エラーなくビルドできました!

pi@raspberrypi:~/mruby $ ls bin/
mirb  mrbc  mrdb  mruby  mruby-strip
pi@raspberrypi:~/mruby $ bin/mruby --version
mruby 1.2.0 (2015-11-17)
pi@raspberrypi:~/mruby $ bin/mruby -e 'puts "hello, mruby on pi!"'
hello, mruby on pi!

わあい。ビルドが想像以上に長かったので、早いマシン上の mruby-cli でクロスビルドした方が良さそうです。

ビルド設定を変更

mruby-cli でのクロスビルドは後日として、とりあえずすぐに手元でできるビルド設定を変更していきます。 build_config.rb を開いて、enable_debug をコメントアウトします。念の為、MRuby::Build.new()host-debug, test, bench のところもブロックまるごとコメントアウト。(ここのところ触ってなかったのでもうちょっと良い方法があるのかもしれません)

mrbgems/default.gembox に次の使いたいライブラリを追加しました。

conf.gem :github => "mattn/mruby-xquote"
conf.gem :github => "matsumoto-r/mruby-sleep"
conf.gem :github => "iij/mruby-io"
conf.gem :github => "iij/mruby-regexp-pcre"
conf.gem :github => "iij/mruby-require"
conf.gem :github => "iij/mruby-dir"
conf.gem :github => "iij/mruby-env"

再度、

pi@raspberrypi:~/mruby $ ./minirake

すると、ビルドすることができました。

需要があるかどうか分かりませんが、上の構成でビルドして strip した mruby, mrib, mrbc, mrdb, mruby-strip のバイナリをこちらに置いておきます。Raspberry Pi と Raspbian があれば、ダウンロードして解凍するだけで使える…かもしれません。

次回は GPIO を制御して L チカをやってみたいと思います。

マルコフ連鎖を使って文章の自動生成を行う

cosmo

割と古典ですが、マルコフ連鎖の考え方を用いて、自動生成するスクリプトを書いてみました。入力した文章の単語同士の結合情報を重みづけをして「それっぽい文章」を生成してくれるやつです。以前からよく人工無能などでの実装で使われていますね。

ソースとなる文章をわかち書きに分割する処理に形態素解析コマンドの Chasen を使用しています。スクリプト本体は、文字分割の N-gram ではなく、単語分割したものを入力しますので、わかち書きが出せれば MeCab でも kakashi でも似たような感じでいけると思います。


単語リストに接尾単語リストを関連づけする方が精度が上がりますが、今回はちょっとしたテストコードですので、単語をリストで持たず1単語→接尾単語リストとして実装しています。

試しに、青空文庫で公開されている宮沢賢治の「銀河鉄道の夜」を入力してみると、次のように出力されました。

どうも今晩は見て、暗のところに見えるきれいだからこっちへかけて、 松や本の方はきちんとそろえて行きましょうと時計屋のしげみの底を 一つのやぐらが軽くひっぱましたともう着くころにふりうごかしました。 僕はそっちをもった。 孔雀が見えるのかたちが持っても手がだんだん横の旗を避けるようにした家でした。 いかがです。 美しい頬にふりを走っていました。 大きな黒い外套を揃えていましたよ。 ああそこでいて、おっかさんは空か。 わたくしたちが顔を大切に見えるのふしが云いました。 君もらわなかった。 ここはすぐ横手をつぶってるんなようにかかえられそうなことないか。 今日かがほとんどいちめんの口笛を擦りながら、私どものです。

意味はめちゃくちゃな文章ですが、スクリプトの短さの割にちょっと前の機械翻訳程度の和文は生成されますね。 Twitter や IRC, Slack などの bot や、音声合成システムの Open JTalk と組み合わせたら面白そうです。

参考リンク

括弧を省略したLispコードのプロセッサーを書いてみました。

supoo(旧実装)

これまで業務でも趣味でも Lisp をガリガリ書くということはなく、せいぜい Emacs の設定ファイルを書くことにしか使ったことがありませんでした。 構文は非常にシンプルで、「これなら字句解析も構文解析も自分で書けそうだ。C と Lisp の勉強がてらやってみよう!」と思い立って書いたのが「supoo」です。

はい、考えが甘かったです。

lex や yacc を使わずにそれなりに動く実装を目指していたのですが、いつまで経ってもパーサ周りのエラーと例外処理に悩まされ続け、コードも複雑化してしまいました。C99ベースで正規表現ライブラリも使っていません。

なぜ既存の解析器を用いなかったかというと、単に lex や yacc ファイルの読み書きがあんまり出来なかったからというのも理由の一つですです。こちらが王道というのは薄々気付いていたので、過去にも何度も挑戦しましたが、その度に挫折…。これらを使った電卓レベルの入門ページはインターネットにたくさんあるんですが、それなりにプログラミング言語の形をしたものを作りたい場合はしっかりとマニュアルを読んで、実際に使用している他言語の実装を読まないとダメのようだーと思っていました。

streem

そういうのを書いたことも忘れて半年以上経った最近、まつもとゆきひろさんが一昨年の暮れあたりから開発している新プログラミング言語「streem」のコードを Github で見て、lex.lparse.yMakefile を眺めているうちに、自分でも書きたくなっていきました。ここ数年の一般的なツールや概念を用いて、何もないところから言語を組み立てていくところを見学することはとても勉強になります。

プログラミング言語を新しく作るところを見たいだけであれば、古い ML やら CVS やら漁れば色々と見れますが、その当時の歴史的な背景まで知っていなければ「なぜそういう実装になったのか」の経緯を本質的に知ることができないような気もしていました。今、リアルタイムで見るからこそ得るものもありそうです。

streem はパイプで連結してちょっとした処理をささっとこなすことができる「より良いシェル(スクリプト)」のようなものを目指しているのかな、と感じました。 パイプで繋げる考えをそのままにシェルスクリプトのようなものを進化させようとした Windows Power Shell を思い出しましたが、方針だけは評価できるものの、実際に出来上がったものは読みにくて書きにくい ガッカリ残念感の哀愁が漂っていたような印象を受けた覚えがあります1。 WPS と比較するのもアレなんですが、”顧客が本当に必要だったもの” は streem の方ですよね!

さて、そんな streem の lex.lparse.yMakefile をひっぱってきて、いろいろといじくり回しているうちに大体の記述は分かるようになってきました。まだパースだけで、式木の構築すらやっていないのですが、入れ子になった括弧を処理して、四則演算を行い、印字する程度のことはできました。

lex と yacc (正確にいうと flex と bison)を使ってなかった旧実装でも、関数定義式(defun)や制御式(if等)まで力技で実装していたので、それに比べたらまだまだラクガキレベルです。 ただ、やっぱり先人の知恵である解析器の便利さを実感することができました。

括弧

Clojure や Scheme を含む Lisp 系の言語は、S式を表現する際に括弧 () を多用します。Lisp 界隈のページを見ると、「括弧が書きづらさや読みにくさにつながって、初心者が寄りつかない」という記事をよく目にします。式木を表現したいだけであれば、Python のようにインデントでも可能ですので、そういう動きも以前からあったようです。

上記リンクでは、Scheme の ML のやりとりが紹介されています。自分も最初は、bison で () に加えてインデントによるネストをサポートしてみようかなと一瞬だけ考えましたが、電卓レベルと言えどちゃんと動いてる構文解析にこれから手を加えるのも億劫になるくらいなので、実用的に動いている実装のパーサから手を入れるのに及び腰になるのは理解できました。

ただ、コミュニティうんぬんの話は置いておいても、何十年も使い続けられている Lisp ですので時代ごとにこういう動きがあったと思いますが、未だに括弧を多用する構文が残っているということは、何か本質的な問題点があったのかもしれません。

supoo.rb

さて、そうは言ってもやっている事自体勉強&趣味なので、車輪の再発明上等でインデントでS式を書けるようにしてみました。

次のようなアプローチです。

  1. supoo はまだ電卓レベルなので parse.y を使いません。評価には GNU Common Lisp (clisp) を使います。
  2. 仮にちゃんと動いていても、yacc 構文では書きません。オレオレなインデント記法→ Lisp 記法を実現するフィルタとして実装します。

フィルタのスクリプト(supoo.rb)は次のようになりました。

中身はただのテキスト・プロセッサーです。要はインデントレベルを検知しながら、必要に応じて () を突っ込んでいるだけ。

次のように使います。

# 実行
ruby supoo.rb < indended-lisp.supoo | clisp -q -

# 通常の Lisp 構文に変換
ruby supoo.rb < indended-lisp.supoo > general-syntax.lisp

例えば、次のようなインデント記法のスクリプトがあったとします。

前置記法は変わらないですが、インデントによるS式のネスティングをしています。 必要以上に改行とインデントを入れなくても良いように、通常の () 構文も使えるようにしています2

このスクリプトを supoo.rb に食わせると

という Common Lisp なコードが印字されます。

clisp で実行すると

$ ruby supoo.rb < indended-lisp.supoo | clisp -q -
0 1 1 2 3
(1 . A)
--- TEST ---
1 2 3 4 5

のように正しく実行することができました。


  1. 結局 bash on Windows の提供という選択肢をとる Microsoft も最近では憎めないですが。 

  2. リストを各要素1行ずつに分けて書きたくないですよね。 

アスペクト比と面積から縦横の大きさを求めるRubyスクリプト

Imagemagick-logo

ImageMagick の montage コマンドを使うと複数の画像をタイル状に並べて、1枚のサムネイル集合写真のような画像を作成することができます。スマートフォンの「カメラロール」のようなものです。

tiled-thumbs

具体的には次のようなコマンドでサムネ画像を生成します。

montage -background black -tile 11x20 -geometry 48x48+0+0 *.jpg thumb.jpg

ここで問題となるのが -tile オプション。これは、「複数の入力画像を縦横何個ずつ並べるか」を指定するオプションです。入力画像ファイル数が少なかったり、出力画像のアスペクト比が 1:1 であれば単純に入力画像ファイル数の冪根を求めればいいだけなのですが、16:9 などといったスマホの画面で閲覧させるのを前提としたアスペクト比の場合、何個ずつ並べればいいかの計算がちょっと厄介になります。

入力画像が正方形の場合、入力総数を [math]n[/math], 求めたい縦横何個を [math]x, y[/math], 縦横比を [math]w, h[/math], アスペクト比を升目と考えた時の総数に対する比を [math]t[/math] とおくと、[math]n = xy[/math] で [math]xy \leq wt \cdot ht[/math] を満せばいいわけなので、[math]x, y[/math] それぞれについて解くと、

[math]x = w \sqrt{\mathstrut \frac{n}{wh} }, y = h \sqrt{\mathstrut \frac{n}{wh} }[/math]

という具合になります。

これを Ruby に書き直して計算結果を印字するようにしたものが次のコードです。

#!/usr/bin/env ruby

# 総数
n = 333

# 比率
w = 16
h = 9

def calc(n, w, h)
  t = Math.sqrt(n.to_f / (w * h).to_f)
  return (w * t).to_i, (h * t).to_i
end

x, y = calc(n, w, h)
if x * y < n
  x, y = calc(n + x, w, h)
end

puts "#{x}x#{y}"

calc の戻り値は個数を表すため必ず整数値になるわけですが、総数よりも小さい場合は1枚の画像に収まらないため1、再計算させています。 上の例のように、333 ファイルを 16:9 の比でタイル配置したい場合

25x14

という結果が得られます。


  1. montage コマンドはタイル総数よりも入力画像数が多い場合、複数の画像として出力してくれます。また、足りない場合は余白になります。 

最古の Ruby 実装をちょっとだけ覗いてみました

職場で定期的にやることになった Ruby 勉強会の資料作りをしていて、ブロック構文を投げてメソッド側から述語を呼ぶコールバック機能はいつから設計されているのかな、と気になって調べていました。

ドキュメントにも、以前から

(ブロック付きメソッドは)最初はループの抽象化のために用いられていたため、 特にイテレータと呼ばれることもあります。

メソッド呼び出し(super・ブロック付き・yield) (Ruby 2.3.0)

という記述があります。(現在では、File.open() 時の自動後処理や、Proclambda の述語的な記述用途でもあるので、必ずしもイテレータではないです)

いろんなページを見ていると、

私家版Ruby史

という Ruby の歴史を分かりやすくまとめたページを見つけました。

最古の Ruby

今から20年以上前、1994年初夏のバージョン 0.49 が最古のスナップショットらしいです。下記からダウンロードすることができます。 (それ以上古いスナップショットは、Matzさんの転職のどたばたとHDDのクラッシュによって失われてそうです。)

ftp://ftp.ruby-lang.org/pub/ruby/1.0/

each メソッドはイテレータとして説明があるものの、サンプルで使っているところを見つけることができませんでした。 (意外なことにサンプル中の繰り返しは for ... in 構文を多用してます)

以下、spec ファイルにあったイテレータの説明です。

** イテレータ

イテレータとは制御構造(特にループ)の抽象化のために用いられるメソッドの 一種である. イテレータの呼び出しは以下の構文で行なわれる.

do 文1 using 変数 文2 end [ do ]

「文2」をブロックとして設定し, 文1のメソッドをイテレータとして評価
する. 文1のトップレベルのメソッドだけがイテレータとして呼び出され, 
レシーバを表す式や, 引数の式はイテレータとしては呼び出されない. 文
1に複数の式があれば各々がイテレータとして順に呼ばれる.

イテレータ内でyield valueが実行されると, その値がdo文で指定された変数 に代入されブロックが実行される. ブロックの実行が終了するとその値は yield式の値として返される. あるメソッドがイテレータとして呼び出された かどうかは関数iterator_p()で知ることができる. 中にはEnumerableモジュー ルのgrepメソッドのようにイテレータとして呼ばれた時と普通のメソッドとし て呼ばれた時とで動作が異なるメソッドもある.

for 変数 in 式 文 end [ for ]

式の各要素に対し文を実行する. これは以下のdo文と等価である.

    do (式).each using 変数
      文
    end

よって式の値のオブジェクトがメソッドeachを持たない場合, forを実行
すると例外が発生する.

今の do |var| ... end 構文ではなく、do ... using var ... end という形をしてたんですねー、面白い。 例外処理も begin ... end ではなく protect ... end で囲んであって色々と興味深いです。

最近の構文しか知らない方は、ぜひダウンロードして読んでみることをオススメします。

また Ruby でイテレータを、という考え方は設計段階からあったようです。

ずいぶん長い間いろいろな言語をつまみぐいして, よさそうなものを拾い集めてきました.rubyはそういう概念の集合 でもあります.例えば例外とかイテレータとか.

[ruby-list:63] Why ruby

ちなみに C# は2005年暮れに発表された 2.0 まで、yield キーワードがなかったため、コルーチンすら 書けませんでした。こわい。

MLやCVSを詳しく追っていったわけではありませんが、ブロックを投げてなんでもやっちゃえる動きは、 その後の最適化、考え方の抽象化によって定着・進化していったのだと思いました。