[Ruby] Ruby の for と each の違いは変数のスコープ

ハマったのでメモ。 for と each の違いです。

t = []
for i in [1,2,3]
# [1,2,3].each do |i|
  t << Thread.new do
    puts "start #{i}"
    sleep i
    p Thread.current
    puts "end #{i}"
  end
end

p t

while t[0].alive?
end

t[1].kill
t[2].kill

p t

こいつに thread.rb とか名前つけて、実行

$ ruby thread.rb
start 1
start 2
start 3
[#<Thread:0x100169be8 sleep>, #<Thread:0x100169ad0 sleep>, #<Thread:0x1001699b8 sleep>]
#<Thread:0x100169be8 run>
end 3
[#<Thread:0x100169be8 dead>, #<Thread:0x100169ad0 dead>, #<Thread:0x1001699b8 dead>]

アレ?ちゃんと t[0] が終わってるんだし、そこは end 1 って出てほしいんだけど。
そこで、 for のところを each に変えて実行。

$ ruby thread.rb
start 1
start 2
start 3
[#<Thread:0x100169b20 sleep>, #<Thread:0x1001699b8 sleep>, #<Thread:0x100169850 sleep>]
#<Thread:0x100169b20 run>
end 1
[#<Thread:0x100169b20 dead>, #<Thread:0x1001699b8 dead>, #<Thread:0x100169850 dead>]

こんどはちゃんと end 1 が出る。
あれーと悩んでいたら、次のページを発見。

for =>そのfor文が定義されているスコープに対して、iを定義する
each=>eachブロック内でのみ生存する新たなスコープを用意し、そこでiを定義する

http://ipcom.withgood.net/wordpress/?p=484

つまり、この場合は i がグローバルになっちゃうので、 for のループごとに i の値が書き変わっちゃうのが原因でした。
3つめのスレッドを作った時点で i の値は 3 になり、1つめのスレッドが終了したときその i の値を参照するので 3 が出力されると。
Ruby では基本的に for ではなく each を使うのがよさそうですね。。

[Emacs] Emacs の popup.el を使って、 MacOSX の Dictionary.app から辞書をひく

Mac の Dictionary.app 便利ですね。
http://sakito.jp/mac/dictionary.html
なんかに使い方が書いてあって、 Emacs との連携も書いてあるのですがいかんせん新しいウィンドウを作ってしまうのがスマートでない。
popup.el なんていう素晴しいインターフェイスもあることだし、ここはひとつツールチップの中で表示させてみてはどうか。
と思って書いたのが以下です。
上記ページの dict.py が必要です。

;; dict.py is from http://sakito.jp/mac/dictionary.html
(defun dictionary ()
  "dictionary.app"
  (interactive)
  (let ((word (if (and transient-mark-mode mark-active)
                  (buffer-substring-no-properties (region-beginning) (region-end))
                (read-string "Dictionary: ")))
        (cur-buffer (current-buffer))
        (tmpbuf " * dict-process *"))
    (set-buffer (get-buffer-create tmpbuf))
    (erase-buffer)
    (insert word "\n")
    (let ((coding-system-for-read 'utf-8-mac)
          (coding-system-for-write 'utf-8-mac))
      (call-process "~/scripts/dict.py" nil tmpbuf nil word) ;; specify full pass of dict.py
      (let ( (str (buffer-substring (point-min) (- (point-max) 2))))
        (set-buffer cur-buffer)
        (popup-tip str :scroll-bar t))
      )))
(global-set-key (kbd "C-M-d") 'dictionary)

だいたい思い通りに動いているんだけど、問題点が2つほど。

  • ツールチップに入りきらない場合を考えて popup-tip のスクロールバーを表示させているのだけど、実際にスクロールする方法がわからない。
  • dict.py から辞書をひくとき、日本語で辞書をひくと例文まで取得できるけど英語でひくと例文までは取得できない。

1つめはキーマップ定義したりしてなんとかなるんじゃないかなーとか思っているんだけど。
とりあえず応急処置としては、

(popup-tip str :scroll-bar t :height 30)

とかやれば広くなるので、表示量が増えます。デフォルトは 15 です。
2つめは Dictionary.app の API が悪くて、 dict.py 中の

DCSCopyTextDefinition(None, word, (0, len(word)))

という箇所。こいつの挙動がよくわからない。
他に API あるんじゃないのと思って調べてみたけどよくわからなかった。
なので、あくまでざっくりと調べる用で本当にきちんと調べたいときは Dictionary.app から調べましょうということで。

[Ruby] Ruby

訓練のためにと思って Ruby を使ってあるプログラムを書いています。
一応専門がプログラミング言語理論とか型理論とかその辺で、Javaに慣れた身としては戸惑うことがたくさんあります。
というか動的型付け言語全般の話かもしれません。

特に慣れないのが

  • メソッドや変数に型注釈を付けない

という点。
型注釈、 Type Annotation っていうのは最大のドキュメンテーションです。
これがないコードがいかに読みにくいか。
自分の卒論は型推論の実装でしたが、そもそも型推論って理論的には面白いけど、(Type Annotation を付けないという点で)実用上そこまで意味があると思えないんですよね。

あとは、まあ Ruby ってどうなのと思うものもいくつかありまして、

  • 後置 if 読みにくい
  • unless の存在が許せない
  • 抽象クラスがない
  • メソッド内で @変数名 を使うとそれがインスタンス変数になる(クラス宣言時に最初にインスタンス変数を列挙できない)
  • private :変数名 とか attr :メソッド名 とかキモイ
  • あとやっぱり end キモイ

とか、そのへんですかね

まともに書き出してまだ1週間くらいで、まだまだ慣れない。。

[メモ] gitサーバの構築

あるプロジェクトで複数人でgitを使って開発しようということになったので、gitサーバを構築したメモ。環境はさくらVPS上の CentOS 5.4 です(゚∀゚)

1. gitインストール

まずgitをインストールします。。rootで作業。

remote# vi /etc/yum.repos.d/CentOS-Base.repo

して、以下を追加

[dag]
name=Dag RPM Repository for Redhat EL5
baseurl=http://apt.sw.be/redhat/el$releasever/en/$basearch/dag
gpgcheck=1
enabled=0
gpgkey=http://dag.wieers.com/packages/RPM-GPG-KEY.dag.txt

あとは

remote# yum --enablerepo=dag -y install git

でおk。ソースとってきて make && make install でも簡単らしいです(´∞`)

2. リポジトリ作成

/var/git 以下に置くことにしました。

remote# mkdir -p /var/git
remote# cd /var/git
remote# mkdir test.git
remote# git --bare init --shared

で、空のリポジトリができました。

remote# groupadd dev
remote# usermod -G dev user
remote# chown root:dev -R .

として dev グループの人用に設定します。サーバ側の設定は以上

3. クライアントからリポジトリを登録

ローカルで適当なリポジトリを作ってサーバ側に登録します。

local$ mkdir test
local$ cd test
local$ git init

空のリポジトリができます。次にリモートリポジトリの情報を登録します。

local$ git remote add origin ssh://user@server/var/git/test.git

でおkです。あるいは、 ssh://user@server の部分は .ssh/config のホスト名を指定しても大丈夫っぽいです。

local$ git remote add origin myhost:/var/git/test

こんな感じ。

local$ echo hoge > test.txt
local$ git add test.txt
local$ git commit -m "First commit"

で、ローカルのリポジトリに test.txt をコミットしました。これをサーバに転送します。

local$ git push origin master

と打って origin に master を push します。

4. 確認

うまく行ってそうなら、豪快にディレクトリごと削除してリモートから clone してみます。

local$ cd ../
local$ rm -rf test
local$ git clone ssh://user@server/var/git/test.git

これで元通り。

[メモ] ssh でリモートログイン

ssh でリモートログインするときはパスワードを手打ちしていたけど、リモートでちょっと大規模なファイルの編集をする必要が出てきて、 Emacs の tramp をきちんと使いたくなったのでパスワードをいちいち打つのが面倒になってきた。
そこで、公開鍵と秘密鍵を設定してパスワードなしでログインできるようにした。
以下設定の手順:

1. 秘密鍵の作成

ローカルから

local$ ssh-keygen

と入力。

Enter file in which to save the key (/Users/dai/.ssh/id_rsa):

はそのままで Enter、

Enter passphrase (empty for no passphrase):
Enter same passphrase again:

パスフレーズを2度入力。これで秘密鍵 (と公開鍵) ができた。

2. リモート側に公開鍵を設定

scp でリモートに鍵を転送して、 authorized_keys を設定する。

local$ scp ~/.ssh/id_rsa.pub user@www.host.com:
local$ ssh user@www.host.com
remote$ mv id_rsa.pub .ssh/authorized_keys
remote$ chmod 600 .ssh/authorized_keys

でOK. ただし、 authorized_keys がすでに存在する場合 (すでに他のマシンからの公開鍵を設定している場合) は

remote$ cat id_rsa.pub >> ~/.ssh/authorized_keys
remote$ rm id_rsa.pub

と追記する。以上でとりあえず終了。

local$ ssh www.host.com

でパスワードなしでいけるようになる。あ、 Mac の場合はキーチェーンアクセスがどうとか出てきた。
ホストに別名を付けたい場合や、複数リモートの設定をする場合はさらに以下を。

3. 複数リモートの設定を書く

2. までの手順リモートマシンごとにやる。 id_rsa ファイルは上書きされないように適宜名前を変えておく。
ローカルの ~/.ssh/config を開いて、

Host host1
HostName www.host1.com
Port 22
User hoge
IdentityFile ~/.ssh/id_rsa_host1

Host host2
HostName www.host2.com
Port 22
User fuga
IdentityFile ~/.ssh/id_rsa_host2

と記入する。これで、

local$ ssh host1

とすれば hoge@host1 に、

local$ ssh host2

とすれば fuga@host2 に ssh できるようになる。

4. .zshrc にホスト名を記述

.zshrc に

zstyle ':completion:*' users-hosts host1 host2

などと記述しておけば

$ ssh <TAB>

で補完してくれる。 user@ を前に付けてもいける。

5. Emacs の tramp を使う

普通に find-file するとき、

/ssh:host1:/path/to/file

でリモートのファイルを開けるようになる。タブでの補完もきく。保存すれば自動でリモートに転送してくれる。
ただし、 recentf-mode を使っていると、 Emacs 起動時にいちいちリモートアクセスが発生して起動がかなり遅くなってしまうので

(setq recentf-exclude '("^/[^/:]+:"))

と書いておくとこれらのファイルをチェックしないようになる。