トップ 最新 追記

じじぃの日記、ツッコミ可

Twitter: @jijixi_org
Xbox Live: jijixi

初心者が書いた OCaml 入門
Spotlight tips サイト内リンク集
1970|01|02|
2003|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|11|
2011|05|
2012|01|

2007-06-01 [長年日記]

% [Erlang] 依存するモジュールを調べる

とりあえずデバッグ情報有りでコンパイルされているモジュールなら、依存するモジュールを調べられることがわかったんでメモ。

例えば、

% cat a.erl
-module(a).
-compile(export_all).
f() -> io:format("hoge\n").
% cat b.erl
-module(b).
-compile(export_all).
g() -> a:f().
% erl -sname hoge@localhost
% erl -sname fuga@localhost

てな状態で、

(hoge@localhost)1> c(a).
{ok,a}
(hoge@localhost)2> c(b).
{ok,b}
(hoge@localhost)3> b:g().
hoge
ok
(hoge@localhost)4> {Mod,Bin,FName} = code:get_object_code(b).
{b,<<70,79,82,49,0,0,1,220,66,69,65,77,65,116,111,109,0,0,0,47,0,0,0,7,1,98,1,
     ...>>,
   "/Volumes/Sub/tmp/jijixi/erlang_test/xref_test/b.beam"}
(hoge@localhost)5> rpc:call(fuga@localhost, code, load_binary, [Mod,FName,Bin]).
{module,b}
(hoge@localhost)6> rpc:call(fuga@localhost, b, g, []).
{badrpc,{'EXIT',{undef,[{a,f,[]},{rpc,'-handle_call/3-fun-0-',5}]}}}

こんなことになってショボーンという場合。 これは要するに、モジュール b の g という関数内で a:f() という呼び出しがあるにも関わらず、fuga@localhost の方には a というモジュールがロードされていないために、hoge@localhost では (a がロードされているので) ちゃんと動くけど fuga の方では動かないという状態。 解決するには、つまるところモジュール a も fuga の方に読み込ませてやれば良いんだけど、エラーが起こってから調べるんじゃなく b を読み込ませる時点で、依存するモジュールをあらかじめ知ることはできないのか…という話。

んでまあ、冒頭にも書いたとおり、b がデバッグ情報有りでコンパイルされていれば、調べる方法はある。

(hoge@localhost)7> c(b,[debug_info]).
{ok,b}

まず、b をデバッグ情報有りでコンパイルし直す。

(hoge@localhost)8> xref:start(foo).
{ok,<0.62.0>}

クロスリファレンスツールを起動。foo は単なる識別名だから何でも良い。

(hoge@localhost)9> xref:add_module(foo, b).
{ok,b}

調べたいモジュールを foo に追加。 マニュアルには第二引数の型は string() だと書いてあるけど、atom() でも通るみたい。

(hoge@localhost)10> xref:analyze(foo, {module_call, b}).
{ok,[a]}

解析。詳しくはマニュアル参照だけど、module_call は指定したモジュールから呼ばれる可能性のあるモジュールのリストを返す。 あとは、このリストを利用して、依存するモジュール全て転送してしまえば問題は解決という寸法。

% [clip] バナナ考えたヤツ天才じゃね? (ネコプロトコル)

ワロタ。

% [雑談] 立ち上がるときに、うっかり「よっこいしょーいち」と言いそうになってしまったのは色んな意味でマズいと思った

って言うか (何が?)、某つかさって原作だとわりと地味な扱いだと思うんだけど、アニメでは結構優遇されてるよね。 (何を言いたいのか、自分でもよくわかんない)


2007-06-02 [長年日記]

% [雑談] どーでもいー話だけど

黒の契約者のオープニングで、最後の方に出てくる黄緑色の髪の人物が、コードギアスに出てきたあの人物に見えてしかたがない。

% [Gauche][OCaml] reduce を util.match を使って

OCaml やら Erlang で書いてもつまんないし、久しぶりに Scheme で何か書いてみようと思った次第。 でもやっぱ car とか cdr とか書いてらんないよね(笑

% cat reduce.scm
(use util.match)
(define (reducel f lst)
  (letrec ((reduce-inner
             (lambda (result l)
               (match l
                (() result)
                ((hd . tl)
                 (reduce-inner (f result hd) tl))))))
    (match-let1
      (x y . tl) lst
      (reduce-inner (f x y) tl))))

(define (reducer f lst)
  (reducel (lambda (x y) (f y x)) (reverse lst)))
% gosh
gosh> (load "./reduce.scm")
#t
gosh> (reducel (lambda (x y) (string-append "(" x "#" y ")")) (list "1" "2" "3" "4"))
"(((1#2)#3)#4)"
gosh> (reducer (lambda (x y) (string-append "(" x "#" y ")")) (list "1" "2" "3" "4"))
"(1#(2#(3#4)))"

ぶっちゃけ OCaml で頭に浮かんだコードを、そのまま Scheme に変換しただけだったり。 せっかくだから、対応する OCaml のコードも書いとくか。

% cat reduce.ml
let reducel f lst =
   let rec reduce_inner result l =
      match l with
      | [] -> result
      | hd :: tl ->
           reduce_inner (f result hd) tl
   in
   match lst with
   | x :: y :: tl ->
        reduce_inner (f x y) tl
   | _ -> failwith "invalid argument"

2007-06-03 [長年日記]

% [雑談][OCaml][Erlang] パターンマッチのコスト?

OCaml でリストを扱う場合の定番の書き方として、

match lst with
  [] -> ...
| x::xs -> ...

こういうのがあって、わしもまあ、この書き方にすっかり慣れちゃってるんだけど、もしかすると Erlang ではあまりよろしくないのかなという気がしている。 それというのも、Erlang のライブラリのソースを見ると、これと同じシチュエーションでも徹底して以下のように書いてあるからだ。

hoge([H|T]) -> ...
hoge([]) -> ...

たぶん、こういうのは (特に再帰するような関数だと) [H|T] というパターンの方が圧倒的に多い回数マッチするから (なので、[] とのマッチ検査が無駄) じゃないかと思うんだけど、そうすると逆に OCaml で [] というパターンを先にするのは大丈夫なのかと多少心配になってくる。 ……まあ、OCaml のソース見ても、上のように書いてるわけだから、きっと無視できるような問題なんだとは思うけど。

ちょっと想像してみると、OCaml の場合は静的型付けなので、コンパイルした時点でそこにリストしか入ってこないことは確定するわけで、それだけでも、まずリストかどうかを調べなきゃならない (であろう) Erlang よりもコストは低い。 それに加えて、空リストの実体は一つしかないような構造にしてしまえば、単純にポインタの比較で済んでしまうので、それくらいになると、もうほとんど無視できるコストだろう。 逆に Erlang は単なる [] というパターンでも、まずリストかどうか調べて、次に空リストかどうか調べるという手続きが必要なわけで (分散環境でのポータビリティを考えると、おそらく空リストを単一のポインタレベルに落とし込むのは無理だと思う)、これを再帰の時に毎度毎度繰り返せば結構目に見える無駄になりそう。

まあ、コードの流れとしては、通常空リストが終了条件になることが多いはずなので、空リストのパターンを先に書いた方が読みやすいとは思うし、Erlang でもチューニングが必要になるまでは空リストが先で良いんじゃないかなーと思わなくもないけど。

% [Gauche][OCaml] reducer もちゃんと書こう

昨日の reducel を引っくり返して適用は、あまりに手抜きだと思ったんで、ちゃんとしたのも書いとく。

(use util.match)
(define (reducer f lst)
  (match lst
   ((x y . ()) (f x y))
   ((hd . tl)
    (f hd (reducer f tl)))))
   

あと OCaml 版。

let rec reducer f lst =
   match lst with
   | x::y::[] -> f x y
   | hd::tl ->
        f hd (reducer f tl)
   | _ -> failwith "invalid argument"

一番下のパターンが無い場合に出る警告を安心だと思うか、ウザいと思うかが OCaml ユーザと Haskell ユーザの違いではなかろうか……などと思ったり思わなかったり。

% [雑談][Gauche][Scheme] マクロのありがたみ

このところ lazy array だの lazy list だの作って遊んでた関係で、何となく Gauche の util.stream モジュールのソースを眺めたり。

正直、いまだにマクロがどれほど役に立つものか実感できないでいるんだけど、少なくともこういう Lazy なものを作るときには自然に書けて良いよなあと感じる。 マクロが無いと、どうしても Eager なのと Lazy なのでは使うときに違った書き方を強制する必要があるもんな。

本日のツッコミ(全2件) [ツッコミを入れる]

% shiro [仕様で許されているのかどうかわかりませんが、コンパイル時点でマッチに重複が無いことがわかるわけですから、どちらの順序..]

% jijixi [ああ、なるほど、そういうこともあるかも知れませんね。]


2007-06-04 [長年日記]

% [Ruby] でっちあげシリーズ、遅延リスト修正版

前回のものは、あまりにもいきばたな作りだったんで、リファクタリングしてみた。 行数はほぼ変わらないので、また別ページに→lazy_list.rb

前回のものとは多少構成を変えたので、また使い方書いとくと、

  • list() で空リスト。
    • :null は廃止。代わりに引数無しの list() の別名で null() を追加。
    • 相変らず空リストはシングルトンじゃないんで色々無駄が多いけど、まあそこら辺は勘弁。
  • list(1,2,3,4) で [1,2,3,4] に相当するリスト
    • 要はブロックが無い場合、可変長引数をそのまま各要素として持つリストになる。
    • Array クラスに to_l メソッドを追加した。ブロックも使える。list(1,2,3,4) は [1,2,3,4].to_l とほぼ同等。
  • list(1,10,2){|x| x} で 1 から始まって 10 を越えると終了する 2 ずつ変化するリスト。
    • 無限リストにするときは、第二引数を nil に。
    • Range クラスにも to_l を追加したので、(1..10).to_l で 1 から 9 のリストになったりする。
  • list{ ... } は変わらず、遅延評価用のラッパー。
  • decons メソッド (head と tail に分ける) の別名として to_ary を定義したので、hd, tl = lst と書けば普通に head と tail が取り出せるようになった。ちょっぴりパターンマッチっぽくて、お気に入り。

以下、使用例。

irb(main):001:0> require 'lazy_list'
=> true
irb(main):002:0> l1 = list
=> #<List: null>
irb(main):003:0> l2 = null
=> #<List: null>
irb(main):004:0> l3 = list(1,2,3,4)
=> #<List: head = 1, tail = lazy>
irb(main):005:0> l3.to_a
=> [1, 2, 3, 4]
irb(main):006:0> l4 = list(1,10,2){|x| x}
=> #<List: head = 1, tail = lazy>
irb(main):007:0> l4.to_a
=> [1, 3, 5, 7, 9]
irb(main):008:0> l5 = list(1,nil,3){|x| x}
=> #<List: head = 1, tail = lazy>
irb(main):009:0> l5.take(10).to_a
=> [1, 4, 7, 10, 13, 16, 19, 22, 25, 28]
irb(main):010:0> l6 = (1..10).to_l
=> #<List: head = 1, tail = lazy>
irb(main):011:0> l6.to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
irb(main):012:0> l7 = list{ l3 + l4 }
=> #<List is lazy: head = ???, tail = ???>
irb(main):013:0> hd, tl = l7
=> [1, #<List is lazy: head = ???, tail = ???>]
irb(main):014:0> hd
=> 1
irb(main):015:0> tl
=> #<List is lazy: head = ???, tail = ???>

% [JoCaml] ようやくソースが手に入ったので

さっそくインストールしてみる。 OCaml 3.10 がベースみたいだから当然のように ./configure -cc 'gcc -arch ppc64' で。

……なんかごくごく普通にインストール完了したので、特に書くこと無し。

とりあえず、この前の妄想 map 関数を試してみたが……動かなかった。つーか戻って来なかった。もう少し勉強します。

% [JoCaml] 並列 map 関数 (試作品)

なかなか感触が掴みにくい。 なんとなく、Haskell のモナドのように「一度値を (並行計算の) 中に入れてしまうと、値を取り出すのが難しい」という感触。 とりあえず、試行錯誤の結果、たぶん並行動作しているだろう…と思えるような結果は出せたので、一応晒す。

% cat par_map.ml
let rec fib n =
   match n with
   | 0 | 1 -> 1
   | n -> fib (n-1) + fib (n-2)

def map(_, []) =
   reply [] to map
or  map(f, x::xs) =
   def hd(y) & tl(ys) & get() = reply y::ys to get in
   (let h = f x in hd(h)) &
   (let t = map(f, xs) in tl(t)) &
   reply get() to map

let test () =
   let lst = [32; 2; 10; 30; 20] in
   let f x = let r = fib x in Printf.printf "%d : %d\n" x r; r in
   map(f, lst)
% jocaml
        JoCaml version 3.10.0

# #use "par_map.ml";;
val fib : int -> int = <fun>
val map : ('a -> 'b) * 'a list -> 'b list = <fun>
val test : unit -> int list = <fun>
# test();;
2 : 2
10 : 89
20 : 10946
30 : 1346269
32 : 3524578
- : int list = [3524578; 2; 89; 1346269; 10946]

各計算結果が逐次出力されていくんじゃなくて、最後にドバっと一気に表示されるのが多少不安だけど、たぶんそこら辺はバッファの都合だろう。

map 関数でどんなことをしているか説明すると、基本的には…

let rec map f = function
| [] -> []
| x::xs ->
   f x :: map f xs

こういうのをベースにして作っている。 んで、f x と map f xs の部分を並行動作させるのが目的になるんだけど、値を返すチャンネル (reply to を使っているチャンネル) は値が返るまでブロックするので並行動作には向かない。 なので、値を集めるチャンネル (それが hd(y) & tl(ys) & get() ) を用意しておいて、値を計算したらそのチャンネルに結果を投げるだけのプロセスを並行的に動かす。

hd(y) & tl(ys) & get() というチャンネルは、hd, tl, get という三つのチャンネルにそれぞれ引数パターンにマッチしたメッセージが投げられた時点で始めて動作するようになっている。 hd と tl には値が戻らないのでブロックしないが、get だけは値が戻るのでブロックする。

なんかうまく説明できてない気がするけど、ともあれ、何かを並列に計算して結果を受け取るような処理をする場合は、こういう感じが基本パターンになるんじゃないかと思う。たぶん。

% [JoCaml] Join pattern と Synchronous channel のワナ

さっきの map 関数で使ったような、

def hd(y) & tl(ys) & get() = ...

という定義を Join pattern という。 で、

def f(x) = reply x to f

のように、reply to で値を返すチャンネルを Synchronous channel (同期チャンネル) と呼ぶ。

同期チャンネルは値が返るまでブロックするという点で、通常の関数とほとんど違わない。 実際、上記のような定義は、

# def f(x) = reply x to f;;
val f : 'a -> 'a = <fun>

というように、関数型として扱われる。 関数型として扱われるということは、このチャンネルを呼び出す動作はプロセスではなく式と見做されるということだ。 そして、プロセス扱いされないということは、並行動作させるための文法である process & process が (直接は) 使えないということになる。

それを踏まえて、以下の定義を見てみよう。

# def f(x) & g(y) = reply x to f & reply y to g;;
val f : 'a -> 'a = <fun>
val g : 'a -> 'a = <fun>

これは、チャンネル f と g にそれぞれメッセージが送られると、f と g の呼び出し元にそれぞれそのまま値を返すという動作をする。 ここで気を付けなくてはいけないのが、f も g も関数扱いのチャンネルだということ。 つまり、ここでうっかり

# f(1);;

などとやってしまうと、事実上デッドロックである。 f と同時に g にもメッセージが送られないかぎり、f は決して値を返さないからだ。 そして、f が値を返さないかぎり、このブロックは解除されない。 もちろん、あらかじめ g にメッセージを投げる別のプロセスも用意されているならデッドロックはしないが、基本的には Join パターンで複数チャンネルを定義する場合には値を返す (同期的な) チャンネルは一つだけにしておいて、最後に呼ぶようにすると失敗が少なくなると思う (少なくとも慣れるまでは、そうするのが良さそう)。

まあ、とにかくブロックする操作を使うときは気を付けようという話だね。 特にこういう複数種のブロックする操作を組み合わせる際には、細心の注意が必要だ。

% [JoCaml] チャンネル定義の謎

先の map 関数の定義でもわかるように、Join パターンで or を利用すると、Haskell や Erlang のような関数の定義の仕方が可能になる。 ……なるはずだと思うんだ。 少なくとも map はうまく動いたし。 でもね……

# def fib1(n) =
  match n with
  | 0 | 1 -> reply 1 to fib1
  | n -> reply fib1 (n-1) + fib1 (n-2) to fib1;;
val fib1 : int -> int = <fun>
# def fib2(0) = reply 1 to fib2
  or  fib2(1) = reply 1 to fib2
  or  fib2(n) = reply fib2 (n-1) + fib2 (n-2) to fib2;;
val fib2 : int -> int = <fun>

この二つの関数って、同じことを表わしていると思うんだけど……

# fib1(10);;
- : int = 89
# fib2(10);;
Stack overflow during evaluation (looping recursion?).

こんなことに。 正直事情が飲み込めない。

試しに map 関数を、

def map(f, lst) =
   match lst with
   | [] -> reply [] to map
   | x::xs ->
      def hd(y) & tl(ys) & get() = reply y::ys to get in
      (let h = f x in hd(h)) &
      (let t = map(f, xs) in tl(t)) &
      reply get() to map

こんな風に書き換えてみたら……

# test();;
20 : 10946
10 : 89
2 : 2
30 : 1346269
32 : 3524578
- : int list = [3524578; 2; 89; 1346269; 10946]

結果がこんな風になった。 どうも Join パターンで分岐するのと match で分岐するのでは何かしら違いがあるのは間違いなさそうだ。 当面、必要も無いのに Join パターンでパターンマッチするのは避けておいた方が良いかもしれない。

% [JoCaml] これはバグでしょうか仕様でしょうか

さっきのパターンマッチの件について、どういう問題かはっきりする例を発見できたのでメモ。

# def f(0) = reply "zero" to f
  or  f(1) = reply "one" to f
  or  f(n) = reply string_of_int n to f;;
val f : int -> string = <fun>
# f 0;;
- : string = "0"
# f 1;;
- : string = "1"

このとおり、f 0 という呼び出しが f(0) じゃなく f(n) というパターンの方にマッチしちゃってる。 これじゃ、さっきの fib2 みたいなのが無限ループになるのも仕方がない。 map でうまく行ってたのは、[] と x::xs という絶対に重複しないパターンだったからだな。

さて、問題は、これが意図された動きなのかどうかってことだよな。 試しに、↓こういうのを書いてみると…

# def g(n) = reply string_of_int n to g
  or  g(1) = reply "one" to g
  or  g(0) = reply "zero" to g;;
val g : int -> string = <fun>
# g(0);;
- : string = "zero"
# g(1);;
- : string = "one"

こうなるんだよね。 これが果して、「下から順にマッチ」してるのか、それとも「順不動でマッチ (してて、この例ではたまたま下からマッチした)」してるのか、どっちなんだろう。 今のところ、この下から順にマッチしているっぽい挙動をあてにして良いものかどうかわからんから、やっぱなるべく使わないようにしといた方が良さそうかな。 つーか、下からマッチしてるんだとしても、それはそれで直感に反する変な仕様だよなあ。

とりあえず、さっきの fib に関しては、↓こうすれば、ちゃんと動くようになった。

# def fib(n) = reply fib(n-1) + fib(n-2) to fib
  or  fib(1) = reply 1 to fib
  or  fib(0) = reply 1 to fib;;
val fib : int -> int = <fun>
# fib 10;;
- : int = 89

一応 JoCaml のメーリングリストには登録したけど、うまく質問する自身が無い...orz

% [JoCaml] 分散処理、事始め

まあ、簡単っちゃー簡単なのかも。 でも、結局どこに何を置いてどこの何を使う…みたいなことを全部自前でやらなきゃならない点で、Erlang ほどの気軽さは無いよな。

% jocaml
# let here = Unix.ADDR_UNIX "/tmp/hoge";;
val here : Unix.sockaddr = Unix.ADDR_UNIX "/tmp/hoge"
# Join.Site.listen here;;
- : unit = ()
# def f(x) = Printf.printf "%d\n" x; 0;;
val f : int Join.chan = <abstr>
# Join.Ns.register Join.Ns.here "f" f;;
- : unit = ()

まず準備。手を抜いて unix domain socket 使ってるけど、IP 使うときでも大して変わらんはず。 アドレスがどうのこうのは OCaml の Unix モジュールのマニュアル参照ってことで。 要は Join.Site.listen に、どのアドレス (の、どのポート) で接続を受け付けるか指定する。 んで、ここでは f というチャンネルを定義して、自分とこ (Join.Ns.here) のネームサーバに "f" という名前で登録している。

次に別のノードを立ち上げる。

% jocaml
# let there_addr = Unix.ADDR_UNIX "/tmp/hoge";;
val there_addr : Unix.sockaddr = Unix.ADDR_UNIX "/tmp/hoge"
# let there = Join.Ns.there there_addr;;
val there : Join.Ns.t = <abstr>

接続先のアドレスを使って Join.Ns.t の値 (there) を取得。

# let g : int Join.chan = Join.Ns.lookup there "f";;
val g : int Join.chan = <abstr>

Join.Ns.lookup 関数で、向こうのネームサーバ (there) から "f" という名前の値を検索して、g に束縛。 型情報が無いので、ちゃんと指定してやる。 指定しないで使おうとすると死。 まあ、正しく使えば、たぶん死なないけど。

# spawn g(1);;
- : unit = ()
# 

1 が表示される……と思ったんだけど表示されなかった。 と言うか、向こうのノードで表示されてた。 Erlang とは違って、stdio はあくまでも向こうのものが使われる模様。

ちなみにサーバ側のノードを終了しても、ソケットファイルは掃除してくれないので、適当に自分で処理しよう。

本日のツッコミ(全4件) [ツッコミを入れる]

Before...

% jijixi [ふむふむ、そういう概念って元々あるんですね。 とすると、JoCaml もそうである可能性は結構ありそう。 やっぱり、..]

% shelarcy [> 並列 map 関数 (試作品) データ並列化ですね。並列処理の一般的な手法については、今月の記事や元ネタの "..]

% jijixi [情報ありがとうございます。参考にさせてもらいます。]


2007-06-05 [長年日記]

% [JoCaml] JoCaml のプロセスはマイクロスレッドの類かと思ったんだけど、どうも違うような気もする件

どうも top を眺めながら適当にプロセスを並列起動させても、プロセスの数とスレッドの数が合わない (プロセスの数よりスレッドが少ないことがある) ので、JoCaml のプロセスってマイクロスレッドとかコルーチンとかいう類なのかと思って、ちょっとソース漁ってみたんだけど、どうにもよくわからない。

とりあえず、そういうことをサポートしてるなら VM に命令が増えてそうだよなーと思って、byterun/instruct.h の diff を (OCaml 3.10.0 との間で) 取ってみたけど、何も違ってないみたい。 んで、otherlibs/threads に大量に増えている join_ なんちゃらというファイルをいろいろ眺めてみた感じだと、どうも (システムスレッドの) スレッドプールを作って、プロセス作成時に適当に各スレッドに割り振ってるだけのような気がする。 や、実際のとこ、よくわかってはいないけども。

いろいろ適当にいじってみると、& で繋げて並列起動しても、それぞれが違うスレッド上で動くとは限らなくて、同じスレッドに複数のプロセスが配置されることもあるみたいなのがわかってきた。 で、思い立って、こういう実験をしてみたのだが……

# let p () = Join.debug "" "";;
val p : unit -> unit = <fun>

Join.debug という関数は、第一引数 (文字列) に、呼び出された時点でのスレッド ID を付加して、さらに残りの引数によるフォーマット文字列を印字するもの。 この例の場合、何も文字列を指定していないので、スレッド ID だけ表示される。

# p ();;
[0]: 
- : unit = ()

そんでもって、↓こんなチャンネルを用意。

# def f(sec) = Unix.sleep sec; p (); 0;;
val f : int Join.chan = <abstr>

これを同じ秒数を指定して、複数個同時に起動すると……

# spawn f(10) & f(10) & f(10) & f(10) & f(10);;
- : unit = ()
# [1]: 
[2]: 
[1]: 
[2]: 
[1]: 
# spawn f(10) & f(10) & f(10) & f(10) & f(10);;
- : unit = ()
# [2]: 
[1]: 
[3]: 
[2]: 
[1]: 

こんな感じで、同じスレッドで動いているものがあるとわかるだろう。

でだ。 この結果だけ見てもわからないと思うけど、この結果が表示されるタイミングには重要な特徴がある。 これが実際にどういう風に表示されたか説明すると、まず一度目の場合、

  • spawn して 10 秒後、[1]: [2]: がほぼ同時に表示され、
  • さらに 10 秒後、[1]: [2]: が同時に表示され、
  • またさらに 10 秒後 [1]: が表示された

二度目の場合は、

  • spawn して 10 秒後、[2]: [1]: [3]: がほぼ同時に表示され、
  • さらに 10 秒後、[2]: [1]: が表示された

こういう具合だ。 本来この処理は、spawn して 10 秒後に、5 つの出力が一気に表示されてほしいわけだけど、そうはなっていないわけだね。 並行処理させるつもりで起動したプロセスが、実はその内のいくつかが同じスレッド上に配置されて、実質並行動作しないかも知れないってのは、どうなんだろうな (まあ、そもそもスレッド絡みな処理で Unix.sleep 使うなんてどうなのよ…という話はあるけど、実は Thread.delay を使っても同じことが起こるので気にせずに)。 たまたまブロックして戻ってこないようなプロセスが、同じスレッドで動いていて、他のプロセスが詰まる可能性とかあるんじゃないのかね。 それとも、ブロックするようなプロセスは常に別のスレッドに配置する、みたいな解析とかしてんのかな。

ともあれ、現状ではマイクロスレッドのようなものが存在するようには見えないので、システム側のスレッド数よりも多くのプロセスを起動するようなシチュエーションになった場合、想定したようには動かない可能性もあると思っておいた方が良さそう。 どうも、スレッドプールが拡張されるタイミングがよくわからなくて、実際、上の例を見てもわかるようにプロセス数に足りないからといって、すぐにスレッドを増やそうとするわけでもなさそうなんだよね。 スレッド数が 10 を越える程度になると、あとはわりとどんどん増やす (し、どんどん減らす) アルゴリズムになってるみたいだけど、そこら辺まで増えるまでの間の動きのニブさが微妙に気になるというか。

まあ、どっちにしても、(少なくとも今のところは) JoCaml のプロセスはほぼ pthread と 1:1 で対応するような実装みたいなので、Erlang とかみたいにありえないほどのプロセスを起動して云々ということはできそうにない。 つーか、プロセスの仕組み的に、あまり長生きするようなプロセスが作られることって無さそうだし、よほど意識してやらないかぎりは、pthread の制限に引っかかるほど大量のプロセスが作られることって無いのかもしれないが。 あと、実際にスレッドが足りなくなったら、たぶん既存のスレッドのキューにプロセスを追加していくようになると思うんで、見た目だけでなら pthread の制限以上にプロセスを起動することはできるはず (もちろんその場合は、厳密には並行動作していないことになるけど)。

% [JoCaml] いじめてみるテスト

試しにたくさんプロセス起動してみた。

# def f() = Thread.delay 60.; Join.debug "" ""; 0;;
val f : unit Join.chan = <abstr>
# for i = 0 to 10000 do
    spawn f()
  done;;
- : unit = ()

すると、スレッド数は最高で 2,560 までしか増えなかった。 でも、何も表示されないうちに (60 秒経つ前に) プロンプトが戻ってきたので、一応見かけ上は 10000 個 (あれ? 10001 個だっけ?) のプロセスが起動されたことにはなっているようだ。たぶん。 ……や、なんか表示されたデバッグプリントの数が少ないような気がするが……まあ、気にしないでおこう(苦笑


2007-06-06 [長年日記]

% [clip][Ruby] もんだい (まめめも)

インチキな答として、class_eval とかで inspect やら = = やら * やらを上書きする方法は思い付く。

マジメな答は……わからん。どっかで見たことあるような気もするんだけど。 最初の値が Bignum だから、結構何とでもなりそうな気はするけど、詳しい構造とか知らないからすぐにはわかんないな。

% [雑談][Ruby] 問題の条件からは外れるけど

どうしても mutable な整数が欲しいなら、こんな感じとかね。 (普通はこんなもん必要無いと思うが)

irb(main):001:0> class MutableInteger
irb(main):002:1>   def initialize(n)
irb(main):003:2>     @val = n
irb(main):004:2>   end
irb(main):005:1>   def method_missing(sym,*args)
irb(main):006:2>     @val = @val.send(sym,*args)
irb(main):007:2>   end
irb(main):008:1>   def ==(x)
irb(main):009:2>     @val == x
irb(main):010:2>   end
irb(main):011:1>   def inspect
irb(main):012:2>     @val.inspect
irb(main):013:2>   end
irb(main):014:1> end
=> nil
irb(main):015:0> i = MutableInteger.new(12345678987654321)
=> 12345678987654321
irb(main):016:0> i / 111111111
=> 111111111
irb(main):017:0> p i
111111111
=> nil
irb(main):018:0> p i == 111111111
true
=> nil
irb(main):019:0> p i * 111111111
12345678987654321

まあ、これは非常に手抜きな実装だけど。 まじめにやるなら、メソッドが Integer 以外を返してきたときは @val に代入しないとか、逆に Integer を返してきたときは、代入しつつ self を返すとか、いろいろ細かいこと考えなきゃだめだろうね。 あと、Object が持ってるメソッドは適当に上書きしてやるとか (特に to_* 系)。

% [JoCaml] システムスレッドとプロセスの関係

もう少しまじめにソースを読んでみた。otherlibs/threads/join_scheduler.ml 辺り。 実際に JoCaml で使われているのは threads じゃなく systhreads の方だと思うけど、join_scheduler.ml は同じものなので気にせずに。

完全に把握できたわけじゃないけど、大体の流れとしては…

  • プロセス作成 (create_process)
    • プロセスで実行される関数に終了時の処理を付加して、システムスレッド (以下スレッド) を起動 (really_create_thread)
      • スレッドが起動できれば、そのまま
      • 起動できなければ、起動待ちプール (pool_kont) に関数を登録 (後で起動する)
      • in_pool の値が 0 じゃない場合は無条件でプールに登録 (in_pool の増減の流れが追いきれてなくて、よくわからないが、ある程度スレッドが増えたら 0 じゃなくなるんだと思う)
  • プロセス終了 (exit_thread)
    • プロセス作成時に付加されるので、終了時に自動的に呼ばれる
      • in_pool が pool_size (最低限残しておくスレッド数。デフォルトは 10 で環境変数 JOPOOLSIZE で変更できるみたい) 以上で、起動待ち関数の数 (pool_konts) が現在動いているスレッドより少ない場合は、実際にスレッドを終了
      • そうでない場合は pool_enter から起動待ち関数を処理
      • 起動待ち関数が無い場合は、do_pool 関数へ

なんか最初はじわじわとスレッド数が増えつつ pool_size に達した頃に、生き残ってるスレッドは do_pool 関数でのループに移行するという感じじゃないかと思う。 この "じわじわ" の感じがうまく流れが掴めなくてよくわかんないんだけど、do_pool (do_loop の typo ではないけど実質的に無限ループ) に入ってからは、create_process によって起動待ちプールへ関数が登録されるのを待ち、登録されたらそれを実行したり、自分だけで処理できないほどたくさん登録されてるようなら fork_for_pool でスレッドを増やしたりする。

結局、当初想像してたような管理スレッドのようなものは無いみたいで (だから昨日書いたようなスレッドプールという言い方は何か違う感じ)、生き残ってるスレッドがそれぞれ自律的に起動待ち関数プール (pool_kont) を監視しつつ、よろしくやるみたいな構成だと思う。

ともあれ、最初のうちのじわじわっぷりはともかく、ある程度軌道に乗ってからは、プロセスとスレッドは (少なくともスレッドが生成に失敗するようになるまでは) 一対一で対応すると思って良さそう。 つーか、この最初のじわじわは必要なのかねえ。 とっとと、pool_size の分だけスレッド起動して do_pool を動かしてしまえば良いんじゃないのかな。

% [JoCaml] 続き (in_pool の動き)

do_pool 関数で最初に incl in_pool して、Condition.wait でブロックしてるので (で、このブロックが解除されると decl in_pool する)、このブロック状態のスレッドが存在していると in_pool が 0 ではない状態になるんだろう。 ここで待っている Condition.t の値には、pool_kont に関数が追加されたとき (put_pool_locked) や、同期チャンネルがブロックする際に他のスレッドに制御をまわすとき (inform_suspend) などに signal が送られるので (そうすると結果 wait が解除されて in_pool の値も減る)、結局のところ in_pool の値は、

やる事が無くて暇を持てあましているスレッドの数

…だと言えそう。 でも、それが判っても、起動したての頃になかなかスレッドが増えない挙動の説明が付かないなあ。 どうなってんだろ?

あ、そういや、jocaml を起動した時点でスレッド数が 3 になってたっけ。 そうすると、昨日の例で最初に 5 つのプロセスを起動したときに、まず 2 つのプロセスが使われるのもわかるな。

最初からあるスレッドの内の 1 つは対話環境を受け持ってるはずだから、残りは 2 つ。 その 2 つがすでに do_pool の状態だとすると、create_process で 5 つのプロセスが起動される場合、どれもすぐに終わるわけじゃないので全て pool_kont に登録されるはずだ。

do_pool では pool_kont から関数を引き出す際に、残りのリストが空じゃない場合には fork_for_pool を呼び出してスレッドを増やす。 でも、たぶん 5 つの関数が登録されるのよりも 1 つ登録された時点で先に動いちゃう方が早いせいで、

  • 5 つの内の最初の 1 つが登録される
  • 2 つ目が登録される前に pool_kont の残りが空かどうか判断してしまって (つまり空なので) スレッドを増やさずに最初のプロセスを動かしてしまう (Condition.wait が解除された時点で pool_mutex をロックしちゃうので、そのロックを必要とする put_pool_locked は動けないのだ)
  • それがたまたま 10 秒間待つような変なプロセスだった
  • 2 つ目が登録された時点で、また同じことが起きるので、まだスレッドは増えない
  • スレッドは使い果たされたので、10 秒間そのまま

さて、ここで 10 秒間待ってる間に残りの 3 つが pool_kont に登録されててくれれば、少なくとも最初のプロセスが終了した時点で残りは全部別のスレッドで動いてくれそうだが、どうもそうはなっていない。 なぜかと言うと、動いている 2 つのスレッドが終了した時点では、すぐに do_loop に戻るわけじゃなくて、まず pool_enter が実行されるから。 で、こいつは do_loop のように pool_kont の残りを見て空じゃなければ fork_for_pool を呼ぶ…みたいなことはせずに、単純に pool_kont にある関数を実行するだけなのだ。 つまり、最初に動いた 2 つのスレッドは、終了した時点で問答無用に残り 3 つのうちの 2 つを実行する。 スレッドを増やしたりはせずに。 もちろん、最後に残った 1 つも同様に処理される。 結果スレッドは増えないまま……

のはずなんだけど、なぜか次に同じく 5 つのプロセスを動かすときには空きスレッドが 3 つに増えている。 その理由については憶測なんだけど、スレッドが 1 つ増えるタイミングを見ていると、最初の 5 つを動かした瞬間に実はすでに増えているので、spawn 自体が一つスレッドを使ってるのかも知れない。 で、起動した 5 つのプロセスが終わるまでは spawn が使ってるスレッドが空かないんだとすれば、辻褄が合う気がする。 んで、次にまた 5 つのプロセスを起動するごとに、その都度 spawn の分だけスレッドが増えていく感じ。

なんだか、こう考えると、この最初のじわじわって意図的なのかどうなのか微妙なところって気がするなあ。 do_pool では pool_kont にたくさん入ってるようなら fork_for_pool することになってるんだし、だとしたら pool_enter でも同じようにした方が良いんじゃないのかねえ。

(追記)

どうやらこれについては、想定通り動いていない (微妙にバグ?) 可能性が高い。 その辺の話はトラックバック参照。

それはともかく、このエントリ書いた後、布団に入ってから気付いたんだけど、上記の流れは間違っている。 2 つ目のプロセスが動きだした時点で、(もし、ちゃんと動いているならば) in_pool は 0 になっているはずなので、それ以降の 3 つのプロセスは create_process の時点で pool_kont に追加されるのではなく really_create_process によって新しいスレッドで動き始めないとおかしい。 おかしいんだけど、何らかの原因で (対話環境が怪しい) そのようにならないようだ。

本日のツッコミ(全2件) [ツッコミを入れる]

% TrackBack [http://jijixi.azito.com/cgi-bin/diary/index.rb?date=200706..]

% TrackBack [http://d.hatena.ne.jp/ku-ma-me/20070607/p1 まめめも もんだいの解答 ..]


2007-06-07 [長年日記]

% [JoCaml] じわじわ問題は、やっぱよくわかんないけど、とりあえず引き合いに出した例がむしろ特殊なケースだって気もしてきた

つーか、spawn の動作に何か変なところがあるんじゃないかという気もしてきてるんだけど、spawn のときに何がどう動いてるのかは確認できてないんで、そこは置いといて単に事実だけ書くと、

# def f() = Unix.sleep 10; Join.debug "" ""; 0;;
val f : unit Join.chan = <abstr>
# def g() = f()&f()&f()&f()&f();;
val g : unit Join.chan = <abstr>
# spawn g();;
- : unit = ()
# [1]: 
[2]: 
[3]: 
[4]: 
[5]: 

このように、spawn じゃなく複数起動のために別のチャンネルを作ってやると、想定通り動いてくれるんだよね。 なんか対話環境でやってるのも関係してるのかな。 ためしに、

% cat test.ml
def f() = Unix.sleep 10; Join.debug "" ""; 0
let _ = spawn f() & f() & f() & f() & f(); Unix.sleep 30

こんなのでテストしてみると、(sleep 30 を付けてるのは、これが無いと何も表示しないうちに終了しちゃうから)

% jocamlc test.ml
% ./a.out
[1]: 
[2]: 
[3]: 
[4]: 
[5]: 
^C

うまく行く。 ってことで、たぶん…

  • 対話環境で
  • スレッド数が少ない状態のとき
  • spawn を使って並行プロセス起動
  • 待機状態のスレッド (in_pool) より多くのプロセスを並行起動

したときだけ、何か変な動きをしているっぽい……というのが今のところの結論。 てことはまあ、実用上はあまり問題無さそうなんで何より。 問題有りそうな気がしてきたんで、要調査。

(追記)

どうも spawn に限らず for とか使うときもダメっぽい (for も一応並行動作することになっている)。 で、やっぱり対話環境を使わずに試すとちゃんと動くので、対話環境が全般的に鬼門のようだ。 toplevel のソースで ocaml3.10 と diff 取ってみたけど、別に大したことは追加されてないみたいなんだよな。 せいぜい、ライブラリパスを増やしてるとか、その程度。 まあ、とりあえず、対話環境での実験結果は鵜呑みにしないようにした方が無難そう。

(さらに追記)

むしろ対話環境云々より、スレッド絡みの環境依存の可能性が出てきた。 詳細は二つ下のエントリで。

% [JoCaml] JoCaml でπ計算

まあ、マニュアルの 1.4.1 の Bi-directional channels ってとこでπ計算のチャンネルを再現する例が出てるので、それで終了なんだけど一応自分でも書いてみた。

% cat pi.ml
type 'a pi_calculus_channel = { send : 'a Join.chan; recv : unit -> 'a }

let new_chan () =
   def send(x) & receive() = reply x to receive in
   { send = send; recv = receive }

def send(ch,x) = ch.send x

def recv(ch,f) =
   let x = ch.recv () in
   f x; 0

この前の Erlang 版に比べると異様にシンプル。 つーか、そもそも計算モデルが似てるから。 あと replication は省略。 普通に join のチャンネル作れば、それが replication みたいなもんだし。

で、実行例。 今回も同じお題で。

# #use "pi.ml";;
type 'a pi_calculus_channel = { send : 'a Join.chan; recv : unit -> 'a; }
val new_chan : unit -> 'a pi_calculus_channel = <fun>
val send : ('a pi_calculus_channel * 'a) Join.chan = <abstr>
val recv : ('a pi_calculus_channel * ('a -> unit)) Join.chan = <abstr>
# let x = new_chan ();;
val x : '_a pi_calculus_channel = {send = <abstr>; recv = <fun>}
# spawn send(x,123) & recv(x,fun v -> print_int v; print_newline ());;
- : unit = ()
# 123
# let x, c = new_chan (), new_chan ();;
val x : '_a pi_calculus_channel = {send = <abstr>; recv = <fun>}
val c : '_a pi_calculus_channel = {send = <abstr>; recv = <fun>}
# spawn send(x,c)
      & recv(c,fun v -> print_endline v)
      & recv(x,fun d -> spawn send(d,"abc"));;
- : unit = ()
# abc

あと、fact は

# def fact(n,r) =
    if n = 0 then send(r,1)
    else let c = new_chan () in
         fact(n-1,c)
       & recv(c, fun x -> spawn send(r,n*x));;
val fact : (int * int pi_calculus_channel) Join.chan = <abstr>
# let result = new_chan ();;
val result : '_a pi_calculus_channel = {send = <abstr>; recv = <fun>}
# spawn fact(10,result);;
- : unit = ()
# result.recv ();;
- : int = 3628800

こんな感じでひとつ。

% [JoCaml] デバッグメッセージを表示させてみた

環境変数 JOVERBOSE に正の整数を設定することで、デバッグメッセージが表示できるのであった。 もっと早く気付けよな、わしも。ソース読んでるくせに...orz

で、例のやつを、まずは対話環境で。

% JOVERBOSE=3 jocaml
REGISTER_SERVICE[0]: joroute
JOIN[0]: init
REGISTER_SERVICE[0]: name_youhou
SEND ALONE[0]: readers_348
CREATE_PROCESS[0]: active=1, nthread=1 suspended=0[0, 0]
TAIL_ASYNC[1]: channel readers_354, status=00000000
ATTEMPT FAILED[1]: readers_354 00000001
EXIT THREAD[1]: active=2, nthread=2 suspended=0[0, 0]
CHECK[1]: active=1, nthread=2 suspended=0[0, 0]
POOL SLEEP[1]: active=1, nthread=2 suspended=1[1, 0]
REAL FORK[0]: 1 active=2, nthread=2 suspended=0[0, 0]
REGISTER_SERVICE[0]: name_zorglub
        JoCaml version 3.10.0

# def f() = Thread.delay 5.; Join.debug "" ""; 0;;
val f : unit Join.chan = <abstr>

起動して、チャンネルを定義するまで。 これから主に注目すべきは CREATE_PROCESS という出力。 表示される値は、active, nthread はそれぞれ (ほぼ) 同名の変数の値、suspended は suspended[in_pool, pool_konts] という感じ。

# spawn f() & f() & f() & f() & f();;
SEND ALONE[0]: f_66
CREATE_PROCESS[0]: active=1, nthread=2 suspended=1[1, 0]
PUT POOL[0]: active=2, nthread=2 suspended=1[1, 1]

f_66 というのはおそらく f のこと。 CREATE_PROCESS の出力は create_process 関数に入ってすぐなので、ここで表示されているのはプロセス起動前の状態。 事前の予想に反して、この時点での in_pool は 2 じゃなくて 1 だが、そもそも nthread が 2 になっているので、大元である起動時のスレッドはここに勘定されていなくて、対話環境もそのスレッドではなく、こちらの二つのうちの一つで動いているということか (active が 1 なのがそれ?)。 PUT POOL は put_pool_locked 関数内での表示で、pool_kont にプロセス用関数を登録して、 incr pool_konts した後である。 で……

SEND ALONE[0]: f_66
CREATE_PROCESS[0]: active=2, nthread=2 suspended=1[1, 1]
PUT POOL[0]: active=3, nthread=2 suspended=1[1, 2]
SEND ALONE[0]: f_66
CREATE_PROCESS[0]: active=3, nthread=2 suspended=1[1, 2]
PUT POOL[0]: active=4, nthread=2 suspended=1[1, 3]
SEND ALONE[0]: f_66
CREATE_PROCESS[0]: active=4, nthread=2 suspended=1[1, 3]
PUT POOL[0]: active=5, nthread=2 suspended=1[1, 4]
SEND ALONE[0]: f_66
CREATE_PROCESS[0]: active=5, nthread=2 suspended=1[1, 4]
PUT POOL[0]: active=6, nthread=2 suspended=1[1, 5]

思ってたのとは違って、一気に五つのプロセスが pool_kont に登録されてしまった。

POOL AWAKE[1]: active=6, nthread=2 suspended=0[1, 5]

そして、ここで初めて待機していたスレッドが動き出す (in_pool の分)。

POOL RUN[1]: 1
REAL FORK[1]: 2 active=6, nthread=3 suspended=0[0, 3]
- : unit = ()

そして、pool_kont には関数が溜まってるので、fork_for_pool 関数が呼ばれる。 REAL FORK はその時点での出力 (出力しているのは really_create_pool)。 fork_for_pool から really_create_pool が呼ばれる時点で pool_kont から一つ取り出しているので、POOL AWAKE の時点から計二つが減って、残りは 3 になっている。 スレッド数も一つ増えて 3 に。

# [1]: 
EXIT THREAD[1]: active=6, nthread=3 suspended=0[0, 3]
CHECK[1]: active=5, nthread=3 suspended=0[0, 3]
POOL FIRST RUN[1]: 1

一つ目のスレッドが終了。active が 0 以下かチェックして (CHECK) そうでなければ pool_enter へ。 pool_enter では pool_kont が空でなければ、そのまま中身を処理しにいく (POOL FIRST RUN)。

[2]: 
EXIT THREAD[2]: active=5, nthread=3 suspended=0[0, 2]
CHECK[2]: active=4, nthread=3 suspended=0[0, 2]
POOL FIRST RUN[2]: 2

二つ目の (さっき起動された) スレッドが終了。同じく active が 0 以下じゃなく pool_kont も空じゃないので、そのまま中身を処理。

[1]: 
EXIT THREAD[1]: active=4, nthread=3 suspended=0[0, 1]
CHECK[1]: active=3, nthread=3 suspended=0[0, 1]
POOL FIRST RUN[1]: 1

一つ目のスレッドが終了して、以下略。

[2]: 
EXIT THREAD[2]: active=3, nthread=3 suspended=0[0, 0]
CHECK[2]: active=2, nthread=3 suspended=0[0, 0]
POOL SLEEP[2]: active=2, nthread=3 suspended=1[1, 0]

二つ目のスレッドが終了。これで四つのプロセスが終了したことになる。 残りは今一つ目で動いている一つのみ。 pool_kont が空なので、このスレッドはサスペンド状態に以降 (do_pool での Condition.wait)。

[1]: 
EXIT THREAD[1]: active=2, nthread=3 suspended=1[1, 0]
CHECK[1]: active=1, nthread=3 suspended=1[1, 0]
POOL SLEEP[1]: active=1, nthread=3 suspended=2[2, 0]

一つ目のスレッドが終了。同じくサスペンド状態へ。

これで最終的に待機 (サスペンド) 状態のスレッド (in_pool) が二つに増えたので、次に同じことをすると、この二つを使って同じことが行なわれる。

さて、これはこれで辻褄が合っているような気もするんだけど、どうも spawn した時点で一気に五つのプロセスが pool_kont に登録されてしまうのが微妙に納得が行かない。 というのも、pool_kont に登録する処理である put_pool_locked で pool_condition に signal を送っているわけだから、その時点で do_pool で待っているスレッドが動き始めても良さそうなものなのだ。 もしそこで動き始めていれば、上記のような動きにはならないはず。 なんだかよくわからん。

では、対話環境を使わずに同じことをやるとどうなるだろう。

% cat test.ml
def f() = Thread.delay 5.; Join.debug "" ""; 0
let _ = spawn f() & f() & f() & f() & f(); Unix.sleep 20
% jocamlc test.ml
% JOVERBOSE=3 ./a.out
REGISTER_SERVICE[0]: joroute
JOIN[0]: init
REGISTER_SERVICE[0]: name_youhou
SEND ALONE[0]: readers_348
CREATE_PROCESS[0]: active=1, nthread=1 suspended=0[0, 0]
REAL FORK[0]: 1 active=2, nthread=2 suspended=0[0, 0]
REGISTER_SERVICE[0]: name_zorglub

どうやら、対話環境という余計な人がいないせいか、in_pool が 0 だ。

SEND ALONE[0]: f_58
CREATE_PROCESS[0]: active=2, nthread=2 suspended=0[0, 0]
REAL FORK[0]: 2 active=3, nthread=3 suspended=0[0, 0]

in_pool が 0 なので、当然のように really_create_pool が呼ばれてスレッドが増える。

SEND ALONE[0]: f_58
CREATE_PROCESS[0]: active=3, nthread=3 suspended=0[0, 0]
REAL FORK[0]: 3 active=4, nthread=4 suspended=0[0, 0]
SEND ALONE[0]: f_58
CREATE_PROCESS[0]: active=4, nthread=4 suspended=0[0, 0]
TAIL_ASYNC[1]: channel readers_354, status=00000000
REAL FORK[0]: 4 active=5, nthread=5 suspended=0[0, 0]
ATTEMPT FAILED[1]: readers_354 00000001
SEND ALONE[0]: f_58
EXIT THREAD[1]: active=5, nthread=5 suspended=0[0, 0]

この EXIT THREAD は裏で何かやってる人の話なので、f_58 とは関係無い (はず)。

CREATE_PROCESS[0]: active=5, nthread=5 suspended=0[0, 0]
CHECK[1]: active=4, nthread=5 suspended=0[0, 0]
REAL FORK[0]: 5 active=5, nthread=6 suspended=0[0, 0]
POOL SLEEP[1]: active=5, nthread=6 suspended=1[1, 0]

ここまでで四つのプロセスが起動。スレッドも四つ増えた。 で、裏で何かやってた人が終了して in_pool に入ったので…

SEND ALONE[0]: f_58
CREATE_PROCESS[0]: active=5, nthread=6 suspended=1[1, 0]
PUT POOL[0]: active=6, nthread=6 suspended=1[1, 1]
POOL AWAKE[1]: active=6, nthread=6 suspended=0[1, 1]
POOL RUN[1]: 1

五つ目のプロセスは待機スレッドから起動。

[2]: 
EXIT THREAD[2]: active=6, nthread=6 suspended=0[0, 0]
CHECK[2]: active=5, nthread=6 suspended=0[0, 0]
POOL SLEEP[2]: active=5, nthread=6 suspended=1[1, 0]
[3]: 
EXIT THREAD[3]: active=5, nthread=6 suspended=1[1, 0]
CHECK[3]: active=4, nthread=6 suspended=1[1, 0]
POOL SLEEP[3]: active=4, nthread=6 suspended=2[2, 0]
[4]: 
EXIT THREAD[4]: active=4, nthread=6 suspended=2[2, 0]
CHECK[4]: active=3, nthread=6 suspended=2[2, 0]
POOL SLEEP[4]: active=3, nthread=6 suspended=3[3, 0]
[5]: 
EXIT THREAD[5]: active=3, nthread=6 suspended=3[3, 0]
CHECK[5]: active=2, nthread=6 suspended=3[3, 0]
POOL SLEEP[5]: active=2, nthread=6 suspended=4[4, 0]
[1]: 
EXIT THREAD[1]: active=2, nthread=6 suspended=4[4, 0]
CHECK[1]: active=1, nthread=6 suspended=4[4, 0]
POOL SLEEP[1]: active=1, nthread=6 suspended=5[5, 0]

あとは順次プロセスが終了して、待機状態に移行していく。

……うーん、これってどうなんだ? 対話環境が悪いと思ってたけど、むしろ「対話環境じゃない時にはたまたま最初に in_pool が 0 なので、うまく行ってる」だけって気がしなくもないな。 どっちにしても、in_pool が 0 ではなくて、かつ、in_pool より多い数のプロセスを並行起動しようとすると、同じように詰まるってことじゃないのかな。 (…と思って試してみたが、やっぱりそうなるっぽい)

put_pool_locked で Condition.signal が使われた時に、ちゃんと Condition.wait で待っているスレッドが動き出してくれれば、問題無いと思うんだけど……これって何が悪いんだ? もしかすると、環境依存の問題って可能性も無いとは言えないよな……うげぇ、調べるのめんどいぞ。

% [JoCaml] とりあえず FreeBSD 6.2R / i386 でも試してみた

……が、状況は同じみたいだなあ。 うーん、BSD 系だけの問題って可能性もアリか? Mac OS X の pthread 実装って FreeBSD 由来だっけ?

まあ、そもそも OCaml のスレッドライブラリの実装がそうだって可能性もあるけどなあ。 diff 取ったけど、元からあるスレッド関係のモジュールには、手が入ってないみたいだし。

% [JoCaml] テストコードを jocamlc -vmthread でコンパイルしてみたが…

やっぱり同じ状態。

とすると、結局 JoCaml の join_schedule.ml 辺りか、あるいは大元の OCaml のスレッドライブラリに問題がある可能性が高いわけだが……うーん。 わしが Condition モジュールの (想定されている) 動作を勘違いしているのでなければ、JoCaml のコードは正しいと思うんだけど……自信無いなあ。

% [JoCaml] こりゃ pthread の勉強しなきゃだめか…と思ったんだけど

なんかスレッド関係のソース (othrelibs/systhreads/posix.c 辺り) を眺めてみたけど、普通に pthread 関係の関数をラッピングしてるだけっぽいよなあ。 やっぱり、わし、なんか勘違いしてるかなあ。

でも、pthread_cond の man ページとか読んでも、勘違いしてるようには思えないんだが……つーか、それよりも、このページの『著者』のとこの名前見て、ちょっとびっくらこいただよ(苦笑

% [JoCaml] pthread ってそういうもんなのかなあ……

ちょっと実験してみたんだけども。

% cat cond_test.ml
let c, m = Condition.create (), Mutex.create ()

let _ = Thread.create begin fun () ->
   print_endline "start";
   Condition.wait c m;
   print_endline "wait done"
end ()

let _ = Thread.create begin fun () ->
   Mutex.lock m;
   print_endline "lock";
   Condition.signal c;
   Thread.yield ();
   print_endline "signal";
   Mutex.unlock m;
   print_endline "unlock"
end ()

let _ =
   let rec loop () = loop () in
   loop ()
% jocamlc cond_test.ml
% ./a.out
start
lock
signal
unlock
wait done
^C

はあ……そういうもんなのか。 Thread.yield とか全然関係無く、Condition.signal 送った後、そのスレッドが止まるまでは wait してたスレッドが動いたりしないんだな。 C でも同等品を書いて試してみたけど、同じ結果だったし。 せめて Mutex.unlock した時点でスイッチしてくれると思ったのに……

ちなみに、試しに yield のところを join にして無理矢理 wait してるスレッドに飛び込ませてやったら……デッドロックした...orz

これって、こういうもんだと割り切って使うしか無いんだろうか……

% [JoCaml] サンタクロース問題

sumim さんとこより。 なんか join パターン使えば超簡単に書けそうな気がしたんでやってみた。 まあ、凝ったことしてないので、見栄えは良くないけども。

% cat santa.ml
let () = Random.self_init ()
let wait () = Thread.delay (Random.float 5.)

type someone = Reindeer | Dwarf

def return() & sleep() = reply Reindeer to sleep
 or meeting() & sleep() = reply Dwarf to sleep

def reindeer() = wait (); print_endline "reindeer was returned."; return()
def dwarf() = wait (); print_endline "dwarf was visited."; meeting()
def reindeers() = for i = 1 to 9 do spawn reindeer() done; 0
def santa(r,d) =
   if r = 9 then begin
      print_endline "delivery...";
      santa(0, d) & reindeers() 
   end else
   if d >= 3 then begin
      print_endline "meeting...";
      santa(r, d-3) & (for i = 1 to 3 do spawn dwarf() done; 0)
   end else
   match sleep() with
   | Reindeer -> santa(succ r, d)
   | Dwarf -> santa(r, succ d)

let () = spawn santa(0, 0)
             & reindeers()
             & (for i = 1 to 10 do spawn dwarf() done; 0);
         let rec loop () = loop () in loop ()

28 行。Erlang なんて目じゃないぜ。 つか、どこまでやれば良いのか、わかんないから、行数言われたって比べようが無いよなあ、実際は(苦笑

% jocamlc santa.ml
% ./a.out
dwarf was visited.
reindeer was returned.
dwarf was visited.
reindeer was returned.
dwarf was visited.
meeting...
dwarf was visited.
dwarf was visited.
reindeer was returned.
dwarf was visited.
meeting...
dwarf was visited.
dwarf was visited.
dwarf was visited.
meeting...
reindeer was returned.
dwarf was visited.
dwarf was visited.
dwarf was visited.
reindeer was returned.
meeting...
reindeer was returned.
reindeer was returned.
reindeer was returned.
dwarf was visited.
dwarf was visited.
dwarf was visited.
meeting...
dwarf was visited.
reindeer was returned.
delivery...
^C

ちゃんと動いてるのかどうかは知らん(ぉぃ

本日のツッコミ(全1件) [ツッコミを入れる]

% TrackBack [http://jijixi.azito.com/cgi-bin/diary/index.rb?date=200706..]


2007-06-08 [長年日記]

% [JoCaml] サンタ問題再掲

昨日のは正味 5 分かそこらで書いたんで (問題を理解するための時間は除く)、いろいろとやっつけだった。 今朝になって、ちょっと気になったんで、もう少しすっきりさせたのを再度載せる。

% cat santa.ml
let () = Random.self_init ()
let wait () = Thread.delay (Random.float 5.)

def meeting() & sleep() = reply `Dwarf to sleep
 or return()  & sleep() = reply `Reindeer to sleep

def reindeer() = wait (); print_endline "reindeer was returned."; return ()
def dwarf()    = wait (); print_endline "dwarf was visited."; meeting ()
def reindeers() = for i = 1 to 9 do reindeer () done
def dwarfs(n)   = for i = 1 to n do dwarf () done

let rec santa r d =
   match r, d with
   | 9, _ ->
        print_endline "delivery...";
        spawn reindeers ();
        santa 0 d
   | _, _ when d >= 3 ->
        print_endline "meeting...";
        spawn dwarfs 3;
        santa r (d-3)
   | _ ->
        match sleep () with
        | `Reindeer -> santa (succ r) d
        | `Dwarf    -> santa r (succ d)

let () = spawn reindeers () & dwarfs 10;
         santa 0 0

まだちょっと、チャンネルの定義や呼び出し (メッセージ送信) のときの書き方に迷いがあるんだよな。 OCaml 的には、関数名と引数の間にスペース入れるのが主流だと思うんだけど、引数無しのチャンネルならまだしも、引数一つのものだとカッコが省略できないせいで、なんか異様に間の抜けた感じになっちゃう。 てことで、今のところ def での定義は関数名と引数をくっ付けて書くようにしてる。(まあ、マニュアルとかでもそうだから、それが正解かもしれないが)

逆に、呼び出しのときにはカッコを省略できたりするから困るんだよな。 普通の関数呼び出しと同じように書けちゃう。 書けちゃうんだけど、ほんとにそれで良いのか?という疑問は付きまとうわけで……いやまあ、今のところは、そう書いちゃってるけども。

% [JoCaml] プロセスというもののせいで、二つの文脈が存在することになってしまっているのが微妙にウザい

OCaml では全ての要素が式であって値を返す。全ての式は他の式の構成要素に成り得る。 関数型の言語は大抵そうだし、Ruby なんかもそうだろう。 ともあれ、この事実は非常に直感的というか、迷いが無くてとても良い。

ところが、JoCaml では通常の値を返す式の他に、"プロセス" を返す式が増えてしまった。 しかも、この "プロセス" という値は、通常の値のようには扱えないのだ。 そのため、プロセスを返す式と通常の値を返す式は、別々の要素として扱われるようになった。

結果として、今までは一つだった文脈が二つに分かれてしまった。 これを便宜上、プロセスコンテキストと式コンテキストと呼ぼう。 実は、たまにエラーメッセージとかで、そんな感じの言葉が出てくることもある。

しかし、何と言うか、この文脈とかコンテキストとか言うやつは色々とアレなものである。 正直、ムカツク。 例えば、こんな定義を用意したとしよう。

# def ch() = print_endline "process"; 0;;
val ch : unit Join.chan = <abstr>
# let ex() = print_endline "expression";;
val ex : unit -> unit = <fun>

ch はチャンネルなので、プロセスコンテキストで使われ、ex は関数なので式コンテキストで使われる。 それぞれを、もう一方のコンテキストに変換する一番単純な方法は、

# let ex_ch() = spawn ch();;
val ex_ch : unit -> unit = <fun>
# def ch_ex() = ex(); 0;;
val ch_ex : unit Join.chan = <abstr>

こんな感じだ。 つまり、プロセスは spawn を通じて式に変換され、式は何もしないプロセス 0 を後ろに繋げることでプロセスに変換される。 def の右辺はプロセスコンテキスト、let の右辺は式コンテキストとなっているので、

# let ex_ch() = ch();;
Characters 14-16:
  let ex_ch() = ch();;
                ^^
This expression is not a function, it cannot be applied
# def ch_ex() = ex();;
Characters 14-16:
  def ch_ex() = ex();;
                ^^
This expression is not a channel

こうするとエラーになる。 でもまあ、ここまでは良い。def はプロセスコンテキストで let は式コンテキストであることは、いつも変わらないから。

ところが、if や for, match といった構造が色々とクセモノなのだ。 これらの制御構造は、プロセスコンテキストで使われればプロセスとして、式コンテキストで使われれば式として扱われる。 なので、

# def ch_f(x) =
    match x with
    | 0 -> ch()
    | _ -> ch_ex();;
val ch_f : int Join.chan = <abstr>
# let ex_f(x) =
    match x with
    | 0 -> ex_ch()
    | _ -> ex();;
val ex_f : int -> unit = <fun>

こうなる。つまり、match ならパターンマッチ後の処理がプロセスになったり式になったりするわけだ。 当然コンテキストが合わなければ…

# def ch_f(x) =
    match x with
    | 0 -> ex_ch()
    | _ -> ch_ex();;
Characters 38-43:
    | 0 -> ex_ch()
           ^^^^^
This expression is not a channel

…怒られる。 OCaml に慣れきっていると、どうにもこの "文脈によって" 扱いが違うということに戸惑いを覚えるよ。

ちなみに文脈によって扱いが変わるものには、以下のようなものがある。( ... の部分がプロセス扱いだったり式扱いだったりする)

if 式 then ... else ...
match 式 with パターン -> ... | パターン -> ...
let 束縛式 in ...
def チャンネル定義 in ...
for 変数 = 式 to 式 do ... done
begin ... end
( ... )

あと、0 という定数も、文脈によって式 (というか整数値の 0) かプロセス (何もしないプロセス) のどちらかで扱われる。

そんなわけで、プロセスコンテキストか式コンテキストかを意識しながら書かないと、いろいろ苦労するよ、というお話。 まあ、ウザいことはウザいけど、Perl みたいに勝手に何か別の値に変化したりはしないので、しょっちゅうコンパイルしてエラーをつぶしていけば済む問題でもある。

% [clip] 知らないほうが幸せだった雑学 (アルファルファモザイク)

いつも思うけど、この手のものって、どこまでほんとなのか判断つかなくて困る。 まあ、基本的にはネタとして見てるだけだけど。

67 おさかなくわえた名無しさん :2007/05/18(金) 01:24:27 ID:rRvZBWNa?n宇宙空間 が 絶対零度 -273℃ っておかしくねぇ?俺ら騙されてねぇ??n?nだって地球って太陽の熱でめっちゃ暑いじゃん?n夏なんか太陽近いからさらに暑いじゃん?nつーことわさ?nその間の宇宙は少なくとも40度くらいはないとおかしいじゃん?n空気出しても凍らなくねぇ?俺地球にもどれるっぽくねぇ?

444 おさかなくわえた名無しさん :2007/05/28(月) 07:00:24 ID:6yGwnM3U?n>>67?nカーズ様乙

カーズ様ガンガレw

% [雑談] 日本ハム勝ちすぎ

セ・リーグのみなさん、勢いに乗せてくれてありがとう……とか思ってたけど、そろそろ逆に言い知れぬ不安を覚えるようになってきたな。 今日なんか天候まで味方につけちゃって。 なんかこう……よくわかんないけど、大丈夫なのか?……みたいな、ねえ。 ともあれ、今年はコンサも強いし、北海道勢は景気が良いね。

とりあえず、今後の予想としては連勝ストッパーはダル君だな。8 割以上の確率で。 やつは、こういう流れのときに、ころっと負けるタイプだ (偏見です)。

本日のツッコミ(全2件) [ツッコミを入れる]

% きむら(K) [予言80%的中ですか?]

% jijixi [あはは、ほんとにダル君絡んでましたね。]


2007-06-11 [長年日記]

% [雑談] あー、暑くてだりぃ

何もする気が起きん...orz

% [JoCaml] わしの貧弱すぎる英語力では意図を伝えられないと思いつつも、気付いてて放置するのもアレだという気がしたので、思い切ってメーリングリストに投げてみた件

例のじわじわ問題 (思ったようにスレッドが増えてくれない問題) の話ね。 一応、問題になる例と、それをとりあえず解決するパッチも付けといたんで、英語が意味不明でも何とか意を汲んでくれることを期待しとこう(苦笑

念の為、日本語でまとめておくと、

  1. otherlibs/threads/join_scheduler.ml 内において
  2. in_pool が 0 でないとき
  3. in_pool より大きい数のプロセスを作成しようとした場合
  4. うまく必要な数までシステムスレッドが増えずに、結果プロセスが並行的に動いてくれない

という問題があるわけだ。 んで、とりあえず (他への影響が不明なものの) この問題を解決するパッチが以下↓。

% cat join_scheduler.diff
--- jocaml-3.10.0/otherlibs/threads/join_scheduler.ml.orig	2007-06-11 16:00:16.000000000 +0900
+++ jocaml-3.10.0/otherlibs/threads/join_scheduler.ml	2007-06-11 16:19:58.000000000 +0900
@@ -122,7 +122,7 @@
 and pool_mutex = Mutex.create ()
 and pool_kont = ref [] 
 
-let fork_for_pool () = match !pool_kont with
+let rec fork_for_pool () = match !pool_kont with
 | f::rem ->
     pool_kont := rem ; decr pool_konts ;
     Mutex.unlock pool_mutex ;
@@ -131,6 +131,10 @@
       pool_kont := f :: !pool_kont ; incr pool_konts ;
       Mutex.unlock pool_mutex ;
 (*DEBUG*)debug1 "FORK" "DELAYED" ;
+    end else
+    if rem <> [] && !in_pool = 0 then begin
+      Mutex.lock pool_mutex ;
+      fork_for_pool ()
     end
 | [] ->
     Mutex.unlock pool_mutex

単純に言うと、fork_for_pool という関数を pool_kont (起動待ちのプロセス) が空になるまで再帰させるようにしてある。 これによって、

# def f() = Thread.delay 5.; Join.debug "" ""; 0;;
val f : unit Join.chan = <abstr>
# spawn for i = 1 to 5 do f() done;;
- : unit = ()
# [1]:
[2]:
[1]:
[2]:
[1]:

こんな風になっていたものが、(二個ずつが 5 秒間隔で動くので、計 15 秒ほど時間がかかる)

# def f() = Thread.delay 5.; Join.debug "" ""; 0;;
val f : unit Join.chan = <abstr>
# spawn for i = 1 to 5 do f() done;;
- : unit = ()
# [1]:
[2]:
[3]:
[4]:
[5]:

このように、ちゃんと必要な分スレッドが起動されるようになって (五個が一斉に動くので 5 秒で終了)、めでたしめでたし。

% [JoCaml] 案の定わかってもらえなくて、「再現しないょ」と言われた件

や、まあ確かに対話環境でしか再現しない例を挙げたわしも悪いんだが。 とりあえず、対話環境じゃなくても再現するコードを投げておいたんで、今度こそだいじょぶだろう。 ……たぶん。 問題さえ認識してもらえば、あとは向こうでうまいことやってくれるだろう。

……つーか、これ以上細かい話になっても、フォローできる自信が無い (語学力的に)。

% [雑談] サンタクロース問題の『こびとさん』って……

もしかして、原文だと elf なの? や、なんか結城さんあたりが elf でやってるんで。 つーか、わし原文読んでないんだよな(苦笑

しかし、エルフなら訳語はどっちかと言うと『妖精さん』じゃないんだろうか。 『こびとさん』というと、白雪姫とかのアレを思い出すけど、アレってドワーフだよね。

% [Ruby][JoCaml][OCaml] pthread でコンテキストスイッチするタイミングがまとまってるページとか無いんかな

というか、コンテキストスイッチしないタイミングの方がまとめやすいのかも知れないけど、まあ、とにかくそういうの。 結局、例のじわじわ問題は、pthread におけるスイッチタイミングの粒度が荒いせいじゃないかと思うんだが、それはまあ今さらどうしようもないとしても、どのくらいのタイミングで切り替わってくれるか、あてにできる情報が欲しいよね、みたいなね。 なんだか、最近じゃあ主だったどの言語も pthread でスレッドライブラリが作られてるという現状を考えると、そういうのを憶えておいても損は無いんじゃないかと思ったりしたわけ。

ちなみに OCaml の場合は、pthread でも vmthread でも同じタイミングっぽい。 というか、pthread と同じになるように vmthread を作ってるようなフシがある。 まあ、同じインターフェイスを使うわけだし、あんまり挙動が違うようでも困るっちゃー困る。

で、OCaml の他にも、pthread といわゆるグリーンスレッドを比べることができる言語って無かったかなーと考えたら、ちょうど今 ruby 1.9 が手元にあることに気付いたので、ちょっくら 1.8 と 1.9 でどう変わるか試してみた。 お題は、この前 JoCaml 絡みで試したやつ。 ConditionVariable が、wait する前に必ずロックを取得しておかないと動かないので (まあ、たしかにそれがお作法だから。前回のアレが手抜きすぎたというのもある)、その分を足した以外は、やってること同じ。 ちなみに言い訳しとくと、Mutex#synchronize を知らないわけじゃなくて、元にしたコードがアレだからまんまになっちゃったというか、別に良いじゃんこれで (逆ギレです)。

% cat cond_test.rb
require 'thread'

c,m = ConditionVariable.new, Mutex.new

id1 = Thread.new do
   m.lock
   p 'start'
   c.wait m
   p 'wait done'
   m.unlock
end

sleep 1

id2 = Thread.new do
   m.lock
   p 'lock'
   c.signal
   p 'signal'
   m.unlock
   p 'unlock'
end

while true do
   # nop
end
% ruby cond_test.rb
"start"
"lock"
"signal"
"wait done"
"unlock"

えーと、sleep 1 みたいなおまじないが無いとデッドロックしちゃうようなアレがアレなんで、わしは Ruby のスレッドはあんまし使いたいと思わないんだけども、ともかく。 これだ、わしが期待しちゃうのは、この挙動だよ。 しかし、これが 1.9 になると…

% ruby19 cond_test.rb
"start"
"lock"
"signal"
"unlock"
"wait done"

この前と同じ結果に。 まあ、どっちも pthread 使ってるわけだから、あたりまえと言えばあたりまえか。 Fiber は……使い方わかんないので無し。

あと他に比較できそうな言語ってあったっけなあ。 Java は……もうグリーンスレッドって無いんだっけ?

% [雑談] なんか消防車がたくさんやって来たなあ……と思ったらえらい間近でサイレンが止まった件

なんぞこれ!と思って窓を覗いてみたら、家の前の道路を挟んだ向かい側でぼんぼん燃えてますわ。 つーか、あそこって何がある所だったかな。 かなり近くなんで、ちょっと怖い。

% [雑談] つーか、すげえ燃えてるんすけど

マジ怖い。 幸い風は無いんで、さすがにこっちに飛び火してくる可能性は低いだろうけど。

それはそれとして、野次馬どもの車が続々家の前の道路に集結してきててウザい(笑

% [雑談] うーん、目の前の火事は鎮火したようなんだが、なぜかまた消防車が

別のとこでも火事か? こんな田舎で、ほぼ同じ時間にすぐ近くで連続して火事が起きるなんて、なんかヤだな。 放火とかじゃないだろうな。

% [雑談] らき☆すた観賞中

ちょwww アニメ店長www

本日のツッコミ(全2件) [ツッコミを入れる]

% 向井 [つ http://www.lingr.com/room/gauche/archives/2007/06/06#msg..]

% jijixi [Oh!! ナカーマ :-) ファンタジー系の RPG とかやってると、ドワーフがこびとさんだとは思えないワナとかある..]


2007-06-12 [長年日記]

% [雑談] まあ、とりあえず今朝のトピックは Safari for Windows だよなー、と

Mac 版の 3 beta は、もし SafariStand が使えなくなると困るんで、しばらく情報待ち。

で、何はともあれ Windows 版。 とりあえず Firefox のブックマークファイルをインポートしてみる……死んだ。 ひどすぎる。 つーか、Mac の方で使ってる Safari のブックマークでも死ぬので、もしかするとマルチバイト文字が入ってると…とかって話かもしれん。

しかたないのでインポートは後回しにして、ここを参考に、MS UI Gothic を指定したり、適当に名前に日本語含まないフォント (M+1P+Sazanami とか konatu とか Osaka とか) を突っ込んでみたけど……うまくいかないなあ。 Vista と XP で事情が違ったりすんのかね。

……なんかめんどくさくなったんで終了。 つーか、別にわしゃ Windows で暮してるわけじゃないし、ぶっちゃけどうでも良い。 Web アプリとかの開発者なら注目すべきなのかもしれないが、今のところわしはそっちには縁が無い。 だいたい、SafariStand の無い Safari なんて……


2007-06-13 [長年日記]

% [雑談] きゅうりペプシ飲んでみたが…

なんか普通に飲めるじゃん、これ。 ちぇ、ネタにもなりゃしねえ。 これなら素直に NEX 飲んでりゃよかった。


2007-06-14 [長年日記]

% [独り言] あー……あかん、あかんわ……

手応え薄っ。 自分の自己アピール下手には嫌気がさすわ...orz

% [Ruby] 『クロージャによる超軽量並行プロセス』を Ruby で

元ネタはここ

% cat picalc.rb
class Chan
   attr_accessor :state, :val
   def initialize
      @state = :sender
      @val = []
   end
end

def send(y,x)
   case y.state
   when :sender
      y.val << x
   when :receiver
      if y.val.empty?
         y.state = :sender
         y.val = [x]
      else
         f, *rs = y.val
         y.val = rs
         f.call(x)
      end
   else
      raise
   end
end

def recv(y,f)
   case y.state
   when :receiver
      y.val << f
   when :sender
      if y.val.empty?
         y.state = :receiver
         y.val = [f]
      else
         x, *ss = y.val
         y.val = ss
         f.call(x)
      end
   else
      raise
   end
end
irb(main):001:0> require 'picalc'
=> true
irb(main):002:0> x = Chan.new
=> #<Chan:0x41fc8 @state=:sender, @val=[]>
irb(main):003:0> send x, 3
=> [3]
irb(main):004:0> recv x, lambda{|y| p y}
3
=> nil
irb(main):005:0> c = Chan.new
=> #<Chan:0x7b5c0 @state=:sender, @val=[]>
irb(main):006:0> repeat = lambda {
irb(main):007:1*   recv c, lambda{|i| p i; repeat.call}
irb(main):008:1> }
=> #<Proc:0x0004abb4@(irb):6>
irb(main):009:0> repeat.call
=> [#<Proc:0x0004ac2c@(irb):7>]
irb(main):010:0> send c, 1
1
=> [#<Proc:0x0004ac2c@(irb):7>]
irb(main):011:0> send c, 2
2
=> [#<Proc:0x0004ac2c@(irb):7>]
irb(main):012:0> send c, 3
3
=> [#<Proc:0x0004ac2c@(irb):7>]

ここで、ちょっとヤっちまった(謎)ので、仕切り直し。

irb(main):001:0> require 'picalc'
=> true
irb(main):002:0> servc = Chan.new
=> #<Chan:0x41c30 @state=:sender, @val=[]>
irb(main):003:0> serv = Proc.new {
irb(main):004:1*   recv servc, Proc.new{|i, repc|
irb(main):005:2*     send repc, (i * i)
irb(main):006:2>     serv.call
irb(main):007:2>   }
irb(main):008:1> }
=> #<Proc:0x00056cd4@(irb):3>
irb(main):009:0> serv.call
=> [#<Proc:0x00056db0@(irb):4>]
irb(main):010:0> r = Chan.new
=> #<Chan:0x4a114 @state=:sender, @val=[]>
irb(main):011:0> send servc, [123,r]
=> [#<Proc:0x00056db0@(irb):4>]
irb(main):012:0> recv r, Proc.new{|j| p j}
15129
=> nil
irb(main):013:0> send servc, [45,r]
=> [#<Proc:0x00056db0@(irb):4>]
irb(main):014:0> recv r, Proc.new{|j| p j}
2025
=> nil

さっきまで lambda 使ってたのに、突然 Proc.new に変わったのは察してもらいたい。 元のコードに似せようという、涙ぐましい努力なんだよぅ。 最後に fib。

irb(main):015:0> fibc = Chan.new
=> #<Chan:0x54650 @state=:sender, @val=[]>
irb(main):016:0> fib = Proc.new {
irb(main):017:1*   recv fibc, Proc.new{|n, repc|
irb(main):018:2*     fib.call
irb(main):019:2>     if n <= 1
irb(main):020:3>       send repc, n
irb(main):021:3>     else
irb(main):022:3*       repc1 = Chan.new
irb(main):023:3>       repc2 = Chan.new
irb(main):024:3>       send fibc, [n - 1, repc1]
irb(main):025:3>       send fibc, [n - 2, repc2]
irb(main):026:3>       recv repc1, Proc.new{|rep1|
irb(main):027:4*         recv repc2, Proc.new{|rep2|
irb(main):028:5*           send repc, (rep1 + rep2)
irb(main):029:5>         }
irb(main):030:4>       }
irb(main):031:3>     end
irb(main):032:2>   }
irb(main):033:1> }
=> #<Proc:0x000532f0@(irb):16>
irb(main):034:0> fib.call
=> [#<Proc:0x00053368@(irb):17>]
irb(main):035:0> r = Chan.new
=> #<Chan:0x467f8 @state=:sender, @val=[]>
irb(main):036:0> send fibc, [10,r]
=> [55]
irb(main):037:0> recv r, Proc.new{|m| printf "fib(10) = %d\n", 55}
fib(10) = 55
=> nil

send 関数で f.call したときとかの返り値は隠蔽するべきだったと反省した。 この状態だと、recv するまでもなく結果が見えてしまって、ちょっともったいない(苦笑

% [Mac] 思い切って Safari 3 Beta を入れてみた

どうも 2ch の Safari スレとか見るかぎり、SafariStand は微妙っぽい (大丈夫だったりそうでなかったり。有効にしてる機能にもよる?) ので今のところ外した状態。

とりあえず、わしにとって SafariStand で一番重要な機能は、ブックマーク全体のインクリメンタルサーチとブランクリンクをタブに開くことだけど、ブックマークは素でそれができるようになってるので問題無し (キーボードで選択できないから微妙に不便ではあるけど)。 タブの問題は……しばらく我慢。 他の機能は、まあ、無きゃ無いで良いか……くらいのもんなので、当面は気にしない方向で。

2ch では軽くなったっていう意見が大半なんだけど、たしかに軽くなってる気はする。 しばらく、これで過してみる予定。

% [Ruby] 上のネタに関して

なんかちょっと期待されてるみたいなので、わかる範囲で。

とりあえず、上のは、基本的に OCaml の例でやってることを、そのまま書くという意図だったので、あんまり Ruby っぽい書き方じゃないです。 recv の定義をブロックを受け取る形にするなら、こう↓するだけでオッケー。

def recv(y, &f)
   ...

Proc.new と proc と lambda が微妙に違うのがナゼか……は、知りません (駄目だ)。 なんかメーリングリストか何かで見た憶えが微かにあるので、たぶん誰か物覚えの良い人が教えてくれるでしょう (丸投げメソッド)。

% [Ruby] Proc に関するもろもろ

個人的には、

irb(main):001:0> f = Proc.new do |x,y| p x, y end
=> #<Proc:0x0003eae4@(irb):1>
irb(main):002:0> g = proc do |x,y| p x, y end
=> #<Proc:0x0008b3bc@(irb):2>
irb(main):003:0> f.call [1,2]
1
2
=> nil
irb(main):004:0> g.call [1,2]
ArgumentError: wrong number of arguments (1 for 2)
        from (irb):2
        from (irb):4:in `call'
        from (irb):4

こういう二種類の挙動が存在すること自体は悪くないと思う。 で、1.9 でここら辺がいろいろ変わったのは、わかりづらいから…とかじゃなかったかな。 それで、アリティチェックが厳しい Proc#call と、緩い Proc#yield に分かれた……みたいな話だったような気もするけど、記憶が曖昧。 試してみよう。

% irb19
irb(main):001:0> f = Proc.new do |x,y| p x,y end
=> #<Proc:4d4ba8@(irb):1>
irb(main):002:0> g = proc do |x,y| p x,y end
=> #<Proc:4cd650@(irb):2>
irb(main):003:0> f.call [1,2]
1
2
=> nil
irb(main):004:0> g.call [1,2]
1
2
=> nil
irb(main):005:0> f.yield [1,2]
1
2
=> nil
irb(main):006:0> g.yield [1,2]
1
2
=> nil

……あり?

irb(main):007:0> h = lambda do |x,y| p x,y end
=> #<Proc:4b9ba0@(irb):7>
irb(main):008:0> h.call [1,2]
[1, 2]
nil
=> nil
irb(main):009:0> h.yield [1,2]
[1, 2]
nil
=> nil

……なんじゃこりゃ。こんな話だっけ?

irb(main):010:0> f.call 1,2,3
1
2
=> nil
irb(main):011:0> f.yield 1,2,3
1
2
=> nil
irb(main):012:0> g.call 1,2,3
1
2
=> nil
irb(main):013:0> g.yield 1,2,3
1
2
=> nil
irb(main):014:0> h.call 1,2,3
ArgumentError: wrong number of arguments (3 for 2)
        from (irb):14:in `call'
        from (irb):14
        from /usr/local/lib/ruby/1.9/irb.rb:150:in `block (2 levels) in eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:259:in `signal_status'
        from /usr/local/lib/ruby/1.9/irb.rb:147:in `block in eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:146:in `eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:70:in `block in start'
        from /usr/local/lib/ruby/1.9/irb.rb:69:in `catch'
        from /usr/local/lib/ruby/1.9/irb.rb:69:in `start'
        from /usr/local/bin/irb19:13:in `<main>'
irb(main):015:0> h.yield 1,2,3
ArgumentError: wrong number of arguments (3 for 2)
        from (irb):15:in `yield'
        from (irb):15
        from /usr/local/lib/ruby/1.9/irb.rb:150:in `block (2 levels) in eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:259:in `signal_status'
        from /usr/local/lib/ruby/1.9/irb.rb:147:in `block in eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:146:in `eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:70:in `block in start'
        from /usr/local/lib/ruby/1.9/irb.rb:69:in `catch'
        from /usr/local/lib/ruby/1.9/irb.rb:69:in `start'
        from /usr/local/bin/irb19:13:in `<main>'

謎すぎる。これじゃ Proc#yield の存在意義って何さ。 うちの ruby 1.9 がおかしいんかなあ。

ところで、マニュアルの Proc のページで、yield のところに 1.7 feature って書いてあるけど 1.9 の間違いだよね?

% [Mac] Safari 3 のページ内サーチが可愛いすぎる件

基本的には最近の Firefox に付いてるのと同じようなもんなんだけど、マッチした対象の表示のされ方がプリチーすぎる。 これはもう Safari 2 には戻れない(笑

% [Mac] WebKit がバージョンアップしたからといって、システム全体が快適になるなんてことがあり得るだろうか?

や、気のせいかも知んないけど、そんな感じすんのよね。

つーかね、そんなことよりもあれですよ、とうとうバックスラッシュ(もしくは円記号)が化けなくなったみたいよ。

print "hello world\n"

これで、うっかり Safari から tDiary の設定いじっちゃって、正規表現がメタメタになるという悲しい現象からおさらばだ(なんか不毛だな)。

本日のツッコミ(全2件) [ツッコミを入れる]

% TrackBack [http://d.hatena.ne.jp/sukesam/20070615/1181839050 jiroの日記 ..]

% TrackBack [http://jijixi.azito.com/cgi-bin/diary/index.rb?date=200706..]


2007-06-15 [長年日記]

% [Ruby] 『『クロージャによる超軽量並行プロセス』を Ruby で』をもう少し Ruby っぽく

昨日の続き。

% cat picalc2.rb
class Chan
   def initialize
      @receive = false
      @val = []
   end

   def <<(x)
      unless @receive
         @val << x
      else
         if @val.empty?
            @receive = !@receive
            @val = [x]
         else
            f, *rs = @val
            @val = rs
            f.call(x)
         end
      end
      self
   end
   alias :send :<<

   def >>(f)
      if @receive
         @val << f
      else
         if @val.empty?
            @receive = !@receive
            @val = [f]
         else
            x, *ss = @val
            @val = ss
            f.call(x)
         end
      end
      self
   end

   def recv(&f)
      self >> f
   end
end

ほんとは @receive って変数名を、もっと別のにしたかったんだけど、ちょっと良いのが思い付かんかった。 あとはこんな感じで。

irb(main):001:0> require 'picalc2'
=> true
irb(main):002:0> x = Chan.new
=> #<Chan:0x42090 @val=[], @receive=false>
irb(main):003:0> x << 3
=> #<Chan:0x42090 @val=[3], @receive=false>
irb(main):004:0> x >> method(:p)
3
=> #<Chan:0x42090 @val=[], @receive=false>
irb(main):005:0> x << 5 >> lambda do |y| p y end
5
=> #<Chan:0x42090 @val=[], @receive=false>
irb(main):006:0> c = Chan.new
=> #<Chan:0x5abcc @val=[], @receive=false>
irb(main):007:0> repeat = proc { c >> proc {|i| p i; repeat.call} }
=> #<Proc:0x000434e0@(irb):7>
irb(main):008:0> repeat.call
=> #<Chan:0x5abcc @val=[#<Proc:0x00043558@(irb):7>], @receive=true>
irb(main):009:0> c << 1
1
=> #<Chan:0x5abcc @val=[#<Proc:0x00043558@(irb):7>], @receive=true>
irb(main):010:0> c << 2 << 3
2
3
=> #<Chan:0x5abcc @val=[#<Proc:0x00043558@(irb):7>], @receive=true>

あとはまあ、repeat のとことかが末尾呼び出しで終わってるのをジャンプに書き換えたかったんだけど、考えるのめんどくさなったので止めた。 つーか、上記のように書いてても、ちっとも「スタック溢れたらどうしよう」とか思わない (つまり当たり前のように末尾呼び出しの最適化を期待している) のは、ちょっとヤバいかもと思った(苦笑

(追記) ん、待てよ? repeat.call で処理がブロックしてるわけじゃないから、別にスタックは溢れないか。 単にチャンネルの @val に Proc オブジェクトが積まれていくだけだもんな。

% [Ruby][雑談] きむらさんが丸投げを受けてくれたようだが…

なんかちょっと思ってたのと違う気が。 もっとこう、1.8 の挙動がまとめてあって、これはこういう理由だったんだけど、1.9 では云々…みたいな話があったような気がするんだけど、メールじゃなく誰かの日記とかだったかなあ。

ところで全然関係無い話。 今、ふと思って試してみたら、Method クラスには yield メソッドが無いみたいなんだけど、良いのかなこれで。

% irb19
irb(main):001:0> method(:p).yield(1)
NoMethodError: undefined method `yield' for #<Method: Object(Kernel)#p>
        from (irb):1:in `method_missing'
        from (irb):1
        from /usr/local/lib/ruby/1.9/irb.rb:150:in `block (2 levels) in eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:259:in `signal_status'
        from /usr/local/lib/ruby/1.9/irb.rb:147:in `block in eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:146:in `eval_input'
        from /usr/local/lib/ruby/1.9/irb.rb:70:in `block in start'
        from /usr/local/lib/ruby/1.9/irb.rb:69:in `catch'
        from /usr/local/lib/ruby/1.9/irb.rb:69:in `start'
        from /usr/local/bin/irb19:13:in `<main>'

% [Ruby][雑談] ブロック無しの Proc.new, proc, lambda

せっかくなのでこの辺も、わかる範囲で。

ブロック無しの Proc.new, proc, lambda は、それが使われてるメソッドが持っているブロックを暗黙のブロックとして Proc オブジェクトを作るものです。 で、proc と lambda で警告が出るのは非推奨だから (要するに、それをやるなら Proc.new 使いましょうということです)。

&block という仮引数は、ブロックを Proc オブジェクトにして block という変数に束縛する、というのと同義なので、

def a(&b)
  b.call
end

は、

def a
  b = Proc.new
  b.call
end

と同じような意味です。 で、ここからは個人的な趣味の話。

上の例からもわかるように、ブロックに名前を付ける必要が無い場合は、ブロックを実行するなら yield、Proc にして何かするなら Proc.new を使えば良いので、仮引数すら必要無いわけだけど、これがもう、なんつーか個人的には我慢ならん。 だって、それじゃ def の行だけ見ても、それがブロックを受け取るメソッドなのか、そうでないのか判断できないじゃん。 &block って仮引数があれば、「ああ、これはブロックを受け取るメソッドなんだな」って一目で判る。

そんなわけで、わしは yield も引数無しの Proc.new も大嫌いなので、ブロック使うメソッド書くときは必ず、仮引数で受け取って、そいつに call メソッドを使うようにしてるのであった。

あー、でも、パフォーマンスのことを考えると、ブロックをそのまま実行するだけなら仮引数無しで yield した方が良いんだっけ? それとも、その辺はうまいこと最適化してくれてるんかな。

% [雑談][Ruby][JoCaml] ちょっと知ったかぶりしてみよう

なんかトラックバック来てたんだけど、

ちゃんとプロセスを並行に動作させるにはどうしたらいいんでしょうか。スレッドプール?

ここで言われてる『並行』がどの程度のものをイメージしてるかはともかく、例のアレも一応ちゃんと並行 (concurrent) に動いていると言えると思う。 ただ、コンテキストスイッチが起きるのが『関数が終わったとき』だけなんで、粒度が粗いってだけで。

あとはまあ、あるプロセスが無限ループしたりして戻ってこない場合、全体がそこでストップしてしまうのを何とかしたいとか、並列 (parallel) プロセス (CPU が複数あるときに、本当に複数同時に動くようなプロセス) を作りたいとか、って話になると、通常の言語の機能だけでは辛くなってくるはず。 とりあえず Ruby の場合なら言語自体がスレッドの機能を持ってるので、それを利用してスケジューラを構築すればできるようになると思う (並列プロセスは利用するスレッドが並列駆動に対応してないと無理だけど)。 あとはスレッドプールなり何なりを作って、そこに各プロセスを割り振ってやる。 ちなみに、JoCaml は正にそういうことをやってるはず。

じゃあ、言語自体がスレッドの機能を持っていない C とかは、どうやって pthread みたいなスレッドライブラリを作ってんの?というと、たぶんハードウェアのタイマー割り込みを使ってると思う (確かめないで書いてるけど)。 結局、非同期プロセス (スレッド) とか、そいつらのタイムスケジューリングを実装するためには、

  • 通常の処理の流れとは独立して動くタイマー (と割り込み)
  • タイマー割り込みに対応して、何か処理をするためのハンドラ

くらいが最低限必要なんじゃないかな。 スレッドライブラリを持っている言語なら、この二つは作ることができるはず。

……ほんとか?

本日のツッコミ(全1件) [ツッコミを入れる]

% TrackBack [http://d.hatena.ne.jp/ytakamiya/20070615#1181897876 たかみやの日..]


2007-06-17 [長年日記]

% [Python] ジェネレータを使った Fiber もどき

Fiber というものに一般的に求められる要件がよくわかってないんだけど、とりあえず Ruby1.9 に入ったものに関しては、この説明を読む限り、明示的に次の対象を指定したときだけ切り替わる…ってことみたいなんで、そういうのならジェネレータ使って作れそうだよなーと思って試しにやってみた。

% cat fiber.py
import threading 

global manager

class Manager(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.setDaemon(True)
        self.__fibers__ = []
        self.lock = threading.RLock()
        self.cond = threading.Condition(self.lock)
    def put(self, fiber):
        self.lock.acquire()
        self.__fibers__.append(fiber)
        self.cond.notify()
        self.lock.release()
    def run(self):
        next = None
        while True:
            self.lock.acquire()
            if self.__fibers__:
                if next is None:
                    fiber = self.__fibers__.pop(0)
                else:
                    try:
                        idx = self.__fibers__.index(next)
                    except ValueError:
                        self.cond.wait()
                        self.lock.release()
                        continue
                    fiber = self.__fibers__.pop(idx)
                try:
                    next = fiber.__runner__.next()
                    if next is not None and not isinstance(next,Fiber):
                        next = None
                except StopIteration:
                    next = None
                else:
                    self.__fibers__.append(fiber)
            else:
                self.cond.wait()
            self.lock.release()
        
class FiberError(Exception): pass

class Fiber:
    def __init__(self, f=None):
        self.running = False
        self.__runner__ = None
        if f is not None:
            self.set(f)
    def set(self, f):
        if callable(f):
            f = f()
        if hasattr(f,'__iter__') and hasattr(f,'next'):
            self.__runner__ = f
        else:
            raise TypeError('iterable or callable(return iterable) required.')
    def start(self):
        if self.__runner__:
            manager.put(self)
            self.running = True
        else:
            raise FiberError('did not set function.')

manager = Manager()
manager.start()

しかしまあ、なんという self 地獄(苦笑

とりあえず以下のテストコードが動いた時点で、モチベーションが下がって手を止めてしまったので、まだきっとバグがある。 使い方としては、見ればわかると思うけど、yield で返した Fiber オブジェクトが次に動くようになってるので、そういう風に書いたジェネレータ (か、それを返す関数) を Fiber に set してやれば良い。

なんつーか、ほんとにおざなりなんで、まともに使えるようなものじゃなくて、単なるちょっとしたネタだから、そこら辺はご理解いただきたい。

% cat test.py
import fiber

f1 = fiber.Fiber()
f2 = fiber.Fiber()

def f():
    print 'f1 start'
    yield f2
    print 'hoge'
    yield f2

def g():
    print 'f2 start'
    yield f1
    print 'fuga'

f1.set(f)
f2.set(g)

f1.start()
f2.start()
% python2.5 test.py
f1 start
f2 start
hoge
fuga
本日のツッコミ(全1件) [ツッコミを入れる]

% TrackBack [http://jijixi.azito.com/cgi-bin/diary/index.rb?date=200706..]


2007-06-18 [長年日記]

% [Python] ジェネレータで Fiber もどき、その2

ついうっかり、前回のを改良してしまった。 Fiber のプールをグローバルにして、複数の Manager がよってたかってそいつらを処理する形 (JoCaml 方式) に変更。 まあ、JoCaml みたいに自動的にスレッド (Manager) が増えたりはしないけど。 つーか、勢い余ってそこまでやりそうになったけど、すんでのところで自重した(苦笑

% cat fiber.py
import threading 

wait_pool = []
lock = threading.RLock()
cond = threading.Condition(lock)

def add_wait_pool(obj):
    lock.acquire()
    wait_pool.append(obj)
    cond.notify()
    lock.release()

class Manager(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.setDaemon(True)
    def run(self):
        while True:
            lock.acquire()
            if wait_pool:
                fiber = wait_pool.pop(0)
                lock.release()
                try:
                    next = fiber.__runner__.next()
                except StopIteration:
                    next = None
                    fiber.__alive__ = False
                else:
                    if isinstance(next, Fiber) and next.__alive__:
                        add_wait_pool(next)
            else:
                cond.wait()
                lock.release()

class FiberError(Exception): pass

class Fiber:
    def __init__(self, f=None):
        self.__alive__ = False
        self.__runner__ = None
        if f is not None:
            self.set(f)
    def set(self, f):
        if callable(f):
            f = f()
        if hasattr(f,'__iter__') and hasattr(f,'next'):
            self.__runner__ = f
            self.__alive__ = True
        else:
            raise TypeError('iterable or callable(return iterable) required.')
    def start(self):
        if self.__alive__:
            add_wait_pool(self)
        else:
            raise FiberError('did not set function.')

def runnable(f):
    return lambda: Fiber(f)

default_manager = Manager()
default_manager.start()

前回と比べると、Fiber.start で登録というのをやめて、start はほんとにその Fiber を動かすためのものにした。 start してない Fiber は、別の Fiber から呼ばれたら動く。 あと、runnable というデコレータを追加。 これで修飾した関数は、実行するとその関数を本体にした Fiber オブジェクトを返すようになる。

% cat test.py
import fiber

f1 = fiber.Fiber()
f2 = fiber.Fiber()

def f():
    print 'f1 start'
    yield f2
    print 'hoge'
    yield f2

def g():
    print 'f2 start'
    yield f1
    print 'fuga'

f1.set(f)
f2.set(g)

f1.start()
raw_input()
% python2.5 test.py
f1 start
f2 start
hoge
fuga

前回とほぼ同じテストコード。 f1 しか start してない辺りが違う。 raw_input は単なる時間稼ぎ。Manager スレッドは setDaemon(True) してるので、メインスレッドが終了すると一緒に死んじゃうから。

% cat test2.py
from fiber import runnable

fs = {}

@runnable
def f():
    print 'f1 start'
    yield fs[2]
    print 'hoge'
    yield fs[2]

@runnable
def g():
    print 'f2 start'
    yield fs[1]
    print 'fuga'

fs[1] = f()
fs[2] = g()

fs[1].start()
raw_input()

デコレータを使った例。 実行結果は最初のと変わらない。

% cat test3.py
import fiber
import time
import threading

ms = [fiber.Manager() for _ in range(5)]
for m in ms:
    m.start()

@fiber.runnable
def f():
    time.sleep(5)
    print `threading.currentThread()`
    yield

fs = [f().start() for _ in range(5)]

raw_input()

スレッドプールを増やす例。 Ruby でこういう感じで動くような Manager クラスを作ろうと思うと、クラス変数使ったりとか色々めんどくさい気がするけど、Python だと単純にモジュールのスコープ利用すれば良いだけだから楽。 とにかく、適当に Manager クラスのオブジェクトを作って start させておけば、勝手に溜まってる Fiber を処理してくれる。 実行結果はこんな。

% python2.5 test3.py
<Manager(Thread-1, started daemon)>
<Manager(Thread-2, started daemon)>
<Manager(Thread-3, started daemon)>
<Manager(Thread-4, started daemon)>
<Manager(Thread-5, started daemon)>

% [Python] サンタクロース問題を Fiber もどきで

そう言えば、ささださんが Fiber でやってるのがあったっけなあ、と思い出したのでパクってみた。 とりあえず、Fiber.current に相当するものが無えー!!と、慌てて追加。

--- fiber.py.orig	2007-06-18 09:38:09.000000000 +0900
+++ fiber.py	2007-06-18 09:18:48.000000000 +0900
@@ -14,11 +14,13 @@
     def __init__(self):
         threading.Thread.__init__(self)
         self.setDaemon(True)
+        self.__current__ = None
     def run(self):
         while True:
             lock.acquire()
             if wait_pool:
                 fiber = wait_pool.pop(0)
+                self.__current__ = fiber
                 lock.release()
                 try:
                     next = fiber.__runner__.next()
@@ -57,5 +59,8 @@
 def runnable(f):
     return lambda: Fiber(f)
 
+def current():
+    return threading.currentThread().__current__
+
 default_manager = Manager()
 default_manager.start()

一応、Manager を複数起動しなければ排他処理とかは必要無いので (厳密には Manager の親スレッドが何もしないことが前提)、あとはそのままベタ移植。

% cat santa.py
import fiber
import random

wait_tonakais = []
wait_kobitos = []
global players
players = []

def next_fiber():
    f = players.pop(0)
    players.append(f)
    return f

@fiber.runnable
def tonakai():
    global wait_tonakais
    while True:
        while random.randint(0,5) % 2 == 0:
            yield next_fiber()
        wait_tonakais.append(fiber.current())
        print "Tonakai (" + `len(wait_tonakais)` + ") reached."
        if len(wait_tonakais) == 9:
            wait_tonakais = []
            print "9 Tonakais are reached."
        while fiber.current() in wait_tonakais:
            yield next_fiber()

for i in range(9):
    players.append(tonakai())

@fiber.runnable
def kobito():
    global wait_kobitos
    while True:
        while random.randint(0,5) % 2 == 0:
            yield next_fiber()
        wait_kobitos.append(fiber.current())
        print "Kobito (" + `len(wait_kobitos)` + ") reached."
        if len(wait_kobitos) == 3:
            wait_kobitos = []
            print "3 Kobitos are reached."
        while fiber.current() in wait_kobitos:
            yield next_fiber()

for i in range(10):
    players.append(kobito())

next_fiber().start()
raw_input()

もろもろの事情で少し違ってる部分もあるけど、概ねそのまんま。

% python2.5 santa.py
Tonakai (1) reached.
Tonakai (2) reached.
Tonakai (3) reached.
Tonakai (4) reached.
Tonakai (5) reached.
Tonakai (6) reached.
Tonakai (7) reached.
Tonakai (8) reached.
Kobito (1) reached.
Kobito (2) reached.
Kobito (3) reached.
3 Kobitos are reached.
Kobito (1) reached.
Kobito (2) reached.
Tonakai (9) reached.
9 Tonakais are reached.
Tonakai (1) reached.
Tonakai (2) reached.
Tonakai (3) reached.
Kobito (3) reached.
3 Kobitos are reached.

なんか適当にノリで作ったわりには、結構遊べるなあ、これ。

% [雑談] 微妙に話題の心理テストは…

+0.29.. くらいだった。 個人的には Python はどっちかと言うと関数型と見做してるので、それが手続き型の方に分類されてるせいでストレスを感じたなあ。

% [Python] ジェネレータで Fiber もどき、その3

Manager (スレッド) を複数起動可能にした関係で (つーか、元のやつも複数起動は可能だったけど、Fiber が単一の Manager に属するようになってた)、まじめにやるなら Fiber クラスにも排他制御は必要だよなーと思ってやってみたんだが……なんて不毛なんだ...orz

self.lock.acruire() と self.lock.release() の山。 self 地獄に lock 地獄か。泣けてくる。 まあ、threading.RLock の場合、すでにロックされてるかどうか気にせずに数の辻褄さえ合わせとけば動くから、まだマシだけども。 二重ロックのことまで気にかけないとならなくなったら、死ねる。

前回のバージョンだと、ある Fiber が別の Fiber を指定したときに、そいつがまだ関数を set されてないと、そこで処理がプッツリ切れちゃってたんだけど、今回のものでは、そういう状態のときには関数が実際に set された時点で自動的に start するようにフラグを立てるようにした。 まあ、基本的には必要な Fiber を全て用意してから、最初の Fiber を start するのがお作法っぽいんで、あんまり必要無さそうな機能ではあるけど。

あと、途中でめんどくさくなってボツにしたネタだと、threading.Thread クラスのように Fiber を継承して run メソッドをオーバーライド……っていう方式にしようか…ってのが。 つーか、Python の特徴を考えると、その方が Fiber ローカルな変数 (つか、インスタンス変数だけど) を扱いやすかったりするんで、ほんとにちゃんと使い物にしようと思ったら、そうした方が良いのかも。 でもまあ、とりあえず、そこまでは良いや…とね。

んで、まあソースはロック絡みで無駄に嵩が増えたのと、実質的な中身はあんまり前回と変わってないのとで、直接貼らずに別ページに。→ fiber.py

(追記) ふと思い出して with 構文 (2.5 future) を使ってみたら、異様にすっきりしたのでそっちに差し換えた。 今までは多分、2.4 でも動いたと思うけど、今回から 2.5 以降じゃないと動かないのであしからず。 (一応、with 使ってない方も名前変えて置いとくけど → fiber_no_with.py)


2007-06-19 [長年日記]

% [Python][Mac] MacPorts の python25 が腐ってる件

しばらく前から MacPorts で入れている python 2.5 が変で、ipython が動かないという問題に悩まされていたんだけど、いつまで経っても直らないんで、あきらめて Python は野良ビルドして入れることにした。

どうも lib-dynload/_hashlib.so とか、_sha* とか、その辺りを作るのに失敗してるみたいで、こいつらは openssl 絡みみたいなんだけど、なぜ失敗するかまで追求するのはめんどくさいので放置。 つーか、readline.so とかも作られてなくて、なんだかわけわからん。 きっと Portfile が腐ってるに違いない。

で、結局野良ビルドすることにしたわけだけど、単に ./configure && make だと readline.so ができないので (configure では見付かるんだけど、/usr にあるやつは機能が足りないらしくて、コンパイル時に失敗してるみたい)、openssl 関係が不安だけどこんな感じ↓でやってみた。

CPPFLAGS=-I/opt/local/include LDFLAGS=-L/opt/local/lib ./configure && make

結果、readline.so がうまくできたのはもちろん、_hashlib.so 辺りも普通にできあがってて、じゃあ何で MacPorts では失敗してんだよと。どんだけ?

ともあれ、ここ数日久しぶりに Python をいじってたにもかかわらず、ipython が使えなくて不便な思いをしていたのが解決されてめでたしめでたし。 ……しかし、もう Python ネタは無いので、またしばらく使う必要が無いという (空回り人生)。

% [Python] 暇なのでここのお題を借りて書いてみる

とりあえず Python 慣れしてなくても、すぐ書けそうなお題4をば。 添削ページに投稿したりはしないけど、添削を拒むものではないので、したい方はどうぞよろしく。

% cat practice4.py
def flatten(lst):
    def f(acc,x):
        acc.extend(flatten(x) if isinstance(x,list) else [x])
        return acc
    return reduce(f, lst, [])

def display(lst):
    for i in flatten(lst):
        print i

def check(lst):
    return not [x for x in flatten(lst) if not isinstance(x, (int,long))]

def test():
    l1 = [1, [2, 3, 4], 5, [[[6], [7, 8], 9], 10]]
    l2 = [1, [2, '3', 4], 5, [[[6], [7, 8], 9], 10]]
    print 'test display()'
    display(l1)
    print 'test check()'
    print check(l1)
    print check(l2)
if __name__ == '__main__':
    test()
% python practice4.py
test display()
1
2
3
4
5
6
7
8
9
10
test check()
True
False

それにしても、List.for_all みたいな関数は無いんだろうか。 あと、int と long が共通の親を持ってないとか、微妙にやりづらい。

% [Python] お題6:名簿の並び替え

すでに、飽きてきている気がするが(苦笑

ちなみにお題5:行列の回転は、問題のページ開いた瞬間に、あまりに簡潔すぎる回答が目に入ってしまって、それ以外の方法を考えるのがバカバカしくなったのでスルーした。

% cat practice6.py
from __future__ import with_statement

def transform(name):
    last,first = name.split()
    return '%s.%s\n' % (last[0],first)

def make_list(orig):
    return map(transform, orig)

def make_sorted_list(orig):
    tmp = make_list(orig)
    tmp.sort()
    return tmp

def transform_file(src_name,dst_name):
    with open(src_name) as ifp:
        with open(dst_name,'w') as ofp:
            ofp.writelines(make_list(ifp.readlines()))

def transform_file_incrementally(src_name,dst_name):
    with open(src_name) as ifp:
        with open(dst_name,'w') as ofp:
            for line in ifp:
                ofp.write(transform(line))

def transform_sorted_file(src_name,dst_name):
    with open(src_name) as ifp:
        with open(dst_name,'w') as ofp:
            ofp.writelines(make_sorted_list(ifp.readlines()))

def test():
    src = 'test/src.txt'
    dst1 = 'test/dst1.txt'
    dst2 = 'test/dst2.txt'
    dst3 = 'test/dst3.txt'
    transform_file(src,dst1)
    transform_file_incrementally(src,dst2)
    transform_sorted_file(src,dst3)
if __name__ == '__main__':
    test()

test/src.txt の中身は、とりあえずお題のページに挙がってた三行だけだけど、別に一万行くらいなら普通に動くんじゃねえ? つーか、単に変換するだけのやつはともかく、ソートするのは一旦全部メモリに抱え込まないと無理だよね。

しかし、一旦 with 構文の便利さを体験しちゃうと、もう普通になんて書いてらんないな。

ファイル IO は、言語によって出力時に改行を補ったり補わなかったり憶えるのが大変。 Python の場合は、write も writelines も改行は付加しないようだ。

% [Python] お題7:整数とビット列の相互変換

他の問題含めて全体的に言えることだけど、仕様の定義があいまいすぎる。 整数はまだしも、ビット列ってどんなのよ。 '0b111111' こういうの? [1,1,1,1,1,1] こういうの? 63 だってビット列だって言やあビット列でしょ。

……とか考えた辺りで、かったるくなったんで、簡単にフォーマット文字列と eval でごまかそう……と思ったんだけど、フォーマット文字列に 2 進数用の型指定子が無い...orz Ruby 万歳。 しかたないので、各ビットを要素としたリストを作ることにする。

% cat practice7.py
def integer_to_bits(num):
    max_column = 0
    while True:
        if 1 << max_column > num:
            break
        max_column += 1
    return [1 if num & 1 << i else 0
            for i in reversed(range(max_column))]

def bits_to_integer(bits):
    return reduce(lambda acc,(i,col): acc + (i << col),
                  zip(reversed(bits),range(len(bits))), 0)

def test():
    itob = [integer_to_bits(x) for x in [1,33,654,7843]]
    print itob
    btoi = [bits_to_integer(x) for x in itob]
    print btoi
if __name__ == '__main__':
    test()

負数には対応しておりません。 つーか、ビット数に上限が無い言語じゃあ、負数は完全に別の処理しなきゃならんからめんどい。

本日のツッコミ(全2件) [ツッコミを入れる]

% TrackBack [http://jijixi.azito.com/cgi-bin/diary/index.rb?date=200706..]

% TrackBack [http://jijixi.azito.com/cgi-bin/diary/index.rb?date=200706..]


2007-06-20 [長年日記]

% [Python] 内包表記を使うときの元になるシーケンスは、なるべくジェネレータを使った方が良い?

……はずだと思うんだ。まあ場合によるとは思うけど (その辺の話は下の方で書く)。 で、何度もそう思ってきたはずなんだけど、どうにもいざ書く場面でそれがとっさに出てこない。 あ、ここではメモリ使用量辺りの話をしてるので、ジェネレータには xrange なんかも含む。

どういうシチュエーションを想像してるかと言うと、内包表記の for の部分に、さらに内包表記が出てくるような場合ね。

[x*2 for x in [x for x in range(100) if x%2 == 0]]

いかにも作った例だけど、まあこういうの。 ここでは 100 未満の 2 の倍数のリストから、その各要素を 2 倍したリストを作ってるわけだけど (細かい話はスルーするように)、この場合、最終的に必要なのは一番外側の部分、要は x*2 した後のリストだけなわけ。 そうすると、range(100) で要素数 100 個のリストを作るのも、それから 2 の倍数のリストを作るのも、わりとメモリの無駄っぽい。

で、こういう場合は、range が xrange (実際にメモリを確保しないリスト…のようなもの) と書けるのは当然 (昔から) として、Python 2.4 から (だったはず) はジェネレータ式というものが存在するので、内側の内包表記をそのままジェネレータにしてしまえる。 こんな↓感じ。

[x*2 for x in (x for x in xrange(100) if x%2 == 0)]

要するに、内包表記と同じ書き方で、角カッコで囲む代わりに丸カッコで囲むとリストじゃなくジェネレータが生成されるという寸法。

……でだ。

range より xrange の方が明らかにメモリ効率が良さそうだというのはわかるんだけど、ジェネレータの場合はどうなのよ?と思うわけ。 つまり、ある程度より大きなリストを作るのに比べれば、ジェネレータの方がメモリ効率が良いのは間違いない (ジェネレータは最終的に作成されるシーケンスの長さに影響されないはずだから) けど、"ある程度" のしきい値ってどの辺なの?という疑問があるわけだ。 それにジェネレータってリスト (配列) をインデックスで手繰ってくよりも、それなりに遅いだろうというイメージもあるし、よほどメモリ使用量に差が出るようなシチュエーションじゃない限りは、ジェネレータじゃなく直接リスト作っちゃった方が良いのかも、って気もする。

というわけで、その辺りの損益分布(?)みたいなものの情報をお持ちの方はご一報を。

% [Python][Mac] 続 MacPorts の python25 が腐ってる件

昨日の続き。 なんか『日記に書くと追求しなきゃ気が済まなくなる症候群』によって、つい Portfile その他を見てしまったのだが……恐るべきことが判明した。 つーか、マジで腐ってやがった。

% cat python25/Portfile
(抜粋)
configure.args  --enable-shared \
                        --mandir=${prefix}/share/man \
                        --bindir=${prefix}/bin \
                        --libdir=${prefix}/lib \
                        --without-readline \
                        --enable-ipv6 \
                        --disable-tk \
                        --disable-framework
(/抜粋)

おい、なんで --without-readline とか付いてんだよ。バカか?アホか?

しかも、これだけじゃない。

% cat python25/files/patch-setup.py 
--- setup.py    2006-08-10 01:42:18.000000000 +0200
+++ setup.py    2007-02-17 16:57:24.000000000 +0100
@@ -15,7 +15,7 @@
 from distutils.command.install_lib import install_lib
 
 # This global variable is used to hold the list of modules to be disabled.
-disabled_module_list = []
+disabled_module_list = ["zlib","_hashlib","_ssl","_bsddb","_sqlite3","_tkinter","bz2","gdbm","readline","_curses","_curses_panel"]
 
 def add_dir_to_list(dirlist, dir):
     """Add the directory 'dir' to the list 'dirlist' (at the front) if

_hashlib とか _ssl とか思いっ切り無効化してやがる...orz

おーーい、誰だ、これコミットしたヤツ。喧嘩売ってんのか。 Variant でこいつらを有効にできるってんならまだしも、そこら辺決め打ちのクセにこの仕打ちはひどすぎるだろ。 依存する port を減らしたい (んだろ?) って気持ちは理解できなくもないが、この対応は無いよ。

……えー、というわけで Mac OS X で、ちゃんとした Python 2.5 を使いたい人は MacPorts なんかに頼ったらダメです。 自分でビルドするか、バイナリパッケージを入れましょう(疲

% [clip] google-perftools

via Matzにっき。 個人的に今必要なわけではないが、存在を憶えておくといつか助かるような気もするのでメモ。

それはそれとして、Matzにっきからリンクされている解説ページで、プロンプトが $ になってていかにも sh 系に見えるのに、コマンド例が csh 系だったりするのが違和感バリバリ。 zsh なのか?

まあ、うちの日記もプロンプト記号は % にしておいてコマンド例が sh 系 (zsh だから) で書いてたりするから、あんまり人の事は言えないんだが(苦笑

ちなみに zsh には csh 系からの移民を支援するために setenv とかの csh 系コマンドが用意されているのだ。 と言っても、全部ってわけでもないし、挙動が微妙に違ったりってのもあって、とっとと sh 系のやり方に慣れてしまった方が幸せって感じでもあるけど。

% [Python] お題1:ファイルの同期

書いてみたは良いが、テストデータ用意するのがめんどくさすぎて全くテストしていない。 動的型言語で、一度も動かしていないということは、つまりデバッグもしていない。 きっと一発じゃ動かないと思うけど、まあ、方向性としてはこんな感じでってことで。

% cat practice1.py
import filecmp
from os.path import getmtime, join
import shutil

def compare(left_dir, right_dir):
    dcmp = filecmp.dircmp(left_dir,right_dir,[])
    return (dcpm.common_files, dcmp.left_only, dcmp.right_only, dcmp.subdirs)

def compare_rec(left_dir, right_dir):
    common,lonly,ronly,subs = compare(left_dir,right_dir)
    if subs:
        for sub in subs.values():
            c,l,r = compare_rec(sub.left,sub.right)
            common.extend(c)
            lonly.extend(l)
            ronly.extend(r)
    return (common, lonly, ronly)

def merge(left_dir, right_dir):
    common,lonly,ronly = compare_rec(left_dir,right_dir)
    _,mismatch,_ = filecmp.cmpfiles(left_dir,right_dir,common,True)
    if mismatch:
        for file in mismatch:
            l,r = [getmtime(join(s,file))
                   for s in [left_dir,right_dir]]
            if l > r:
                shutil.copy2(l,r)
            elif r > l:
                shutil.copy2(r,l)
            else:
                raise RuntimeError
    only = [(join(left_dir,name),join(right_dir,name)) for name in lonly]
    only.extend(((join(right_dir,name),join(left_dir,name)) for name in ronly))
    for src,dst in only:
        shutil.copy2(src,dst)

サブディレクトリに対して、再帰的に処理してる辺りが特に怪しい。 絶対に一発じゃ動かないにおいがプンプンする(苦笑

% [Python] お題2:単語数カウント

なんだかなー……何やってんだろ、わし。何かもっと他にやること無いのかよ。 や、なんかまあ、なんでもいーから Python で何か書きたいって気持ちがあるのも確かなんだが…… それにしても...orz

% cat practice2.py
import string

seps = [c for c in string.punctuation if c != "'"]

def count_words(s):
    words = reduce(lambda result,c: result.replace(c,' '),seps,s).lower().split()
    dict = {}
    for word in words:
        if word in dict:
            dict[word] += 1
        else:
            dict[word] = 1
    return dict

def display(dic):
    tmp = [(word, str(num)) for (word,num) in
           sorted(dic.iteritems(), key=lambda (k,v): (-v,k))]
    word_len = max((len(w) for (w,_) in tmp))
    num_len  = max((len(n) for (_,n) in tmp))
    for word,num in tmp:
        print word.ljust(word_len) + ': ' + num.rjust(num_len)

def test():
    s = "It's fine day, isn't it? Yes, it is!"
    display(count_words(s))
if __name__ == '__main__':
    test()
% python practice2.py
it   : 2
day  : 1
fine : 1
is   : 1
isn't: 1
it's : 1
yes  : 1

% [Python] お題3:シングルトン

こんな感じかなあ。

% cat practice3.py
class Hoge:
    def foo(s):
        print 'foo'
hoge = Hoge()

def Hoge():
    return hoge

最初、Hoge.__init__ を削除しちゃえば…と思ったんだけど、それだと親クラスの __init__ が呼ばれるだけだからダメ。 Hoge に __call__ を持たせたら…というのも __init__ が優先されるんでダメ。 かと言って __init__ で例外挙げるとかだとダセーよなーと思って、考えた結果がこれ。 インチキくさいような、クールなような、微妙な線?

% [雑談] ちょwww見抜かれすぎwww

jijixi の脳内イメージ。

うそこメーカー
jijixiの脳内イメージ

一体どんな仕組みなんだw

うまく表示されないとき用のバックアップ→jijixi_maker_usoko_net.gif

% [Python][Mac] なんか勢いでバイナリパッケージをインストールしてしまったので、Mac 固有のモジュール探検

まあ、自分でビルドする場合でも、ちゃんと configuire に適当なオプション付けてあげれば、これらのモジュールも作られるはずなんだけども、「どうせ使わないだろうし、めんどくせ」てな感じで放置していたわけ。 でも、バイナリで入れると、最初から用意されてるわけで、まあなんつーか、あるなら使ってみようかな…という気になるのも人情。 とりあえず、

import findertools
findertools.sleep()

でスリープしてくれるのがわかって、「ぐあっ、AppleScript とか意味ネー」(その程度にしか AppleScript を使ってない人) ともんどり打った。

% [Python][Mac] EasyDialogs モジュール

なんか、これだけあれば、ちょっとファイルを選ばせて、何かして、どっか別のファイルに保存……みたいなのは、すぐにできそうな予感。

非常に簡単な Hello World.

% cat hello_mac.py
import EasyDialogs

ret = EasyDialogs.AskYesNoCancel('Push Yes!',cancel='',default=1)
if ret:
    msg = 'Hello World!'
else:
    msg = 'Goodbye World.'
EasyDialogs.Message(msg)

実行はお近くの Mac で。 まあ、要するに、

┌────────────────┐
│Push Yes!                       │
│                                │
│[  No  ]                 [ Yes ]│
└────────────────┘

こんな感じのダイアログが出て、ボタンを押すと、

┌────────────────┐
│Hello World!                    │
│                                │
│                         [ Yes ]│
└────────────────┘

とか出るという感じ。

% [Mac] SafariStand 3.0a1 (3 beta 専用) キタよー

hetima さんありがとー。 まだ完全対応とは行かないみたいだけど、_blank リンクをタブで開く機能が使えるだけでも価値がある。 (ただし、JavaScript でのポップアップには効かなくなっちゃったみたい。この点は今後に期待)

% [雑談] プロ野球速報ページ見てたんだけど…

なんか、9 回に入ってからハムがフィーバーしとるぞ(笑

ハムが勝って、ロッテと巨人が引き分けると、ハムの交流戦優勝が決まるらしいけど、さすがにそれは無さそうだ。

% [OCaml] GODI 豆知識

GODI で OCaml のビルド時に特定の configure 引数を与えたいときは、godi/etc/godi.conf に、

OCAML_CONF_ARGS=-cc 'gcc -arch ppc64'

こんな感じで書いておくと良い。 最初、godi/build/mk 内のファイルとか、godi/build/godi/godi-ocaml/Makefile とかを一生懸命探してウンウン唸ってたんだけど、実は GODI では、どういうわけか OCaml の configure は godi-ocaml-src をインストールするときに行われるので (そして、configure 後のソースを固めて保存しといて、ビルド時にはこれを使う)、godi/build/godi/godi-ocaml-src/Makefile に正解が書いてあった。

そういうわけで、上記のように OCAML_CONF_ARGS を追加してビルドし直したい場合は、godi-ocaml-src も入れ直さないと設定が有効にならないので注意。

(追記) ごふっ!……この設定って他のパッケージにも渡るのか。 そのせいで、camomile の configure 時に「-cc なんてオプション無いよ」と怒られてビルドに失敗する。 しかたないから、godi_make configure までで止めて、適当に configure いじって -cc オプションをダミーで足して (どうせ camomile では関係無いから)、それから続き (godi_make install) をやってごまかした。 godi_console でやると、clean して最初から始めてしまうんで、手動で godi_make 推奨。

うーん、この辺、Ports に慣れてる人なら問題無いだろうけど、そうでない人にはツラいかもなあ。 OCAML_CONF_ARGS を設定する場合は、自己責任で……ってことでよろしく(苦笑

本日のツッコミ(全4件) [ツッコミを入れる]

Before...

% odz [> 損益分岐 この辺でしょうか。 http://tabesugi.net/memo/2006/b4.html#271..]

% jijixi [ありがとうございます、odz さん。 内包表記とジェネレータの速度差が、思ったほど無いみたいなのは意外でした。 私が..]

% TrackBack [http://jijixi.azito.com/cgi-bin/diary/index.rb?date=200706..]


2007-06-21 [長年日記]

% [Python][Mac] findertools.sleep() で寝てくれなかった件 (ショボ〜ン

こういうの↓が、

% cat goto_sleep
#!/usr/local/bin/pythonw
import findertools
findertools.sleep()
% ./goto_sleep
/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/plat-mac/lib-scriptpackages/
Finder/Legacy_suite.py:67: DeprecationWarning: raising a string exception is deprecated
  raise aetools.Error, aetools.decodeerror(_arguments)
Traceback (most recent call last):
  File "./goto_sleep", line 3, in <module>
    findertools.sleep()
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/plat-mac/findertools.py",
  line 80, in sleep
    finder.sleep()
  File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/plat-mac/lib-scriptpackages/
  Finder/Legacy_suite.py", line 67, in sleep
    raise aetools.Error, aetools.decodeerror(_arguments)
aetools.Error: (-1708, 'the AppleEvent was not handled by any handler', None)

こんな感じでエラー。 ざっと pdb で舐めてみたけど、肝心の部分が C で書かれたところみたいで、原因は特定できず。 さすがにそこから先まで追う気力は無い。

まあ、sleep() はともかく、emptytrash() とか launch('.') みたいなのは動くんで、スリープに関する何かが以前と変わってて、それに追従できてないってことかなあ。

……っていうか、よく考えたら、うちの iMac はしばらく前からスリープからうまく復帰できなくなってたので、スリープ機能は必要無いのであった。 「そういや AppleScript を使う方法だとどうなってんだ?」とか思ってうっかり確かめてしまって (すんなりスリープした)、やっぱり復帰できなくてぐったり...orz

ちなみに AppleScript を使ってコマンドラインからスリープさせるには、

#!/bin/sh
osascript -e 'tell app "Finder" to sleep'

こんな感じで。

% [Scala] 最近の Scala

なんか最近微妙に Scala が盛り上がりつつあるような気配なんで、ついつられてまた調べてみたりしてるんだけど、2.6 で予定されている変更点が結構楽しみ。

OCaml のオブジェクト大好きなわしとしては、Structural subtyping の導入は嬉しい。 できれば型推論は OCaml 並にがんばってほしいところではあるけど。

あと、最近はあんまり詳しく変更点を追ってなかったんで気付いてなかったんだけど、結構色々文法が変わってるね。 内包表記は以前だと、

scala> val l = for (val x <- List.range(0,100); x < 10) yield x
l: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

こうだったのが、(今はまだ使えるけどいずれ廃止予定らしい)

scala> val l = for (x <- List.range(0,100) if x < 10) yield x
l: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

こうなって、少しわかりやすくなってる風味。 他にもタプルが、

Tuple(1,2)

と書かなきゃならなかったのが、

{1,2}

になって、結局今は、

(1,2)

になっている。 現状だと、タプルを持ってる普通の言語のように、

scala> val (x,y) = (1,2)
x: Int = 1
y: Int = 2

こういうのが書けるようになってて、すっきり。 あと、関数をカリー化する方法が、

scala> def f(x:Int)(y:Int) = x + y
f: (Int)(Int)Int
scala> &f
unnamed18: (Int) => (Int) => Int = <function>

こうだったのが、(これもまだ使える)

scala> f _
unnamed19: (Int) => (Int) => Int = <function>

こうなってたり。 ただ、

scala> &f(1)
unnamed26: (Int) => Int = <function>

こういう部分適用の代替表記がわからん。

scala> f _(1)
error: That kind of statement combination is not supported by the interpreter.

scala> (f _)(1)
<console>:5: error: type mismatch;
 found   : (Int)(Int)Int
 required: (?) => ?
 possible cause: missing arguments for method or constructor
  val unnamed27 = (f _)(1)
                   ^

一応、一旦変数に束縛すれば大丈夫みたいではあるが。

scala> def g = f _
g: (Int) => (Int) => Int

scala> g(1)
unnamed28: (Int) => Int = <function>

あ、わかったわかった、こうだ。

scala> f(1) _
unnamed32: (Int) => Int = <function>

省略する部分を _ にするわけか。 これはこれで、わかりやすいかな。

本日のツッコミ(全1件) [ツッコミを入れる]

% みずしま [お。Structural Subtyping導入されるんですね。それは確かに 楽しみ。]


2007-06-22 [長年日記]

% [Scala] Actor 初体験

ふとしたきっかけでサンタクロース問題を書いてる人を見付けて、つい対抗意識(?)を燃やしてしまった。 や、単に Actor 使って何か書く口実が欲しかっただけだけど(苦笑

ともあれ、この前リングベンチマークのコードを読んでるときにも色々疑問があったりしたんで (receive と react の違いって何?とか)、チュートリアルも眺めつつ。

% cat santa.scala
import scala.actors._
import java.lang.Thread
import java.lang.Math

object Santa extends Application {
   def waiting() = Thread.sleep((Math.random() * 5000).toLong)
   def reindeer(santa:Actor):Unit = {
      waiting()
      Console.println("reindeer was returned.")
      santa ! 'return
      Actor.loop {
         Actor.react {
            case 'play => reindeer(santa)
         }
      }
   }
   def elf(santa:Actor):Unit = {
      waiting()
      Console.println("elf was visited.")
      santa ! 'hello
      Actor.loop {
         Actor.react {
            case 'bye => elf(santa)
         }
      }
   }
   def santa(reindeers:List[Actor])(elves:List[Actor]):Unit = {
      (reindeers.length, elves.length) match {
         case (9, _) =>
            Console.println("delivery...")
            reindeers.foreach(_ ! 'play)
            santa(List())(elves)
         case (_, n) if n >= 3 =>
            Console.println("meeting...")
            elves.takeRight(3).foreach(_ ! 'bye)
            santa(reindeers)(elves.take(n-3))
         case (_, _) => Actor.loop {
            Actor.react {
               case 'return => santa(Actor.sender::reindeers)(elves)
               case 'hello  => santa(reindeers)(Actor.sender::elves)
            }
         }
      }
   }

   val s = Actor.link { santa(List())(List()) }
   for (x <- List.range(0,9))  Actor.link { reindeer(s) }
   for (x <- List.range(0,10)) Actor.link { elf(s) }
}

本来は import scala.actors.Actor._ してやるのがお作法っぽいけど、慣れてないとどれがどのパッケージの関数なのかさっぱりわからんので、あえてインポートせずに書いている。 あと、Scala 的なスリープの仕方がわからなかったので、安易に Thread.sleep とか使ってたり。 ただ、これは後で書くけど、たぶんあんまり良くない。

基本的な作りはこの前書いた JoCaml 版と同じ。 違うのは、JoCaml の方ではトナカイやこびとのプロセスはサンタのところに来ると終了していて、事が済んだときにサンタが新たに減った分を補充していたのが、こっちでは各プロセス (アクター) は終了せずに、サンタが (事が済んだときに) メッセージを送ってくるのを待つようになっている点。 まあ、完全に JoCaml 版と同じにしても良かったんだけど、それはそれでつまらないので。 こっちの方が Erlang (アクターモデル) っぽいし。

実行するとこんな↓感じ。 コンパイルすると大量の class ファイルができるので、試してみるなら適当にディレクトリを用意した方が良いと思う。

% scalac santa.scala
% scala -cp . Santa
reindeer was returned.
reindeer was returned.
reindeer was returned.
reindeer was returned.
reindeer was returned.
reindeer was returned.
reindeer was returned.
elf was visited.
reindeer was returned.
reindeer was returned.
delivery...
elf was visited.
reindeer was returned.
elf was visited.
meeting...
reindeer was returned.
elf was visited.
reindeer was returned.
reindeer was returned.
elf was visited.
elf was visited.
meeting...
reindeer was returned.
reindeer was returned.
reindeer was returned.
reindeer was returned.
elf was visited.
^C

てなところで一旦終了。

% [Scala][JoCaml] Actor の簡単なまとめ

やっぱり自分で何か書いてみないとわからないこともある。 てなわけで、サンタ問題を書いたおかげで、いろいろわかったので簡単にまとめ。

  • スレッド (Java のスレッド) とアクター (Scala のスレッド) は 1:1 の関係ではない。
    • スケジューラが、あらかじめ用意された WorkerThread (デフォルトでは 4 個) にアクターを割り振る。
      • 実装を見たわけじゃないけど、きっとアクターそのものじゃなく、適当なところで分割されて関数化されたものを割り振るんじゃなかろうか。
    • WorkerThread は、全てのスレッドがブロックして、かつ、処理すべきものが残っている場合に増える (らしい)。
      • 基本的には JoCaml と似ている。(ただし、JoCaml はブロックしていなくても増える)
    • Actor を使ったときには 5 つのスレッドが増えるので、ワーカが 4 個だとすれば、もう 1 個はスケジューラか?
      • つーか、Scala のランタイム自体が 10 個もスレッド使ってるんで、4 個というのはどうもケチくささを感じるが……
  • receive はスレッドをブロックする。react はブロックしない。
    • 上記のとおり、スレッドとアクターは 1:1 で対応しないので、安易に receive でブロックしてしまうと面倒なことになりうる。
    • そのために用意されているのが react 。
    • ただ実際には Erlang の receive のようにブロックしてくれないと困るケースは多い。
    • そういうときは loop { react { ... } } を使う。react はメッセージが無いときは何もしないので、これで見た目上は receive でブロックしてるのと同じ状態に。
      • これも実装を見たわけじゃないから想像だけど、loop はブロック内のコードを一回分ずつワーカに渡すようになってるんだと思われ。

スレッドとアクターが 1:1 対応じゃないということで、JoCaml でじわじわ問題が出るシチュエーションと同じ懸念 (たまたま全てのスレッドで無限ループになったりすると、残りの処理が動かなくなる可能性がある) があるわけだけど、最初からスレッドが 4 つ用意されるので、そういう問題に遭遇する可能性は低そうではある。 JoCaml と違って、元々 1:1 で対応させる気が無いようだから、これはこれで良いんだろう。

んで、さっき書いた Thread.sleep を使うのはマズいという話だけど、まさにこの事実が関係する。 つまり、あの場面ではアクターをスリープさせたいのに、Thread.sleep を使うと実質そのアクターだけじゃなく、次に同じスレッドで動くはずのアクターまで一緒にスリープしてしまうことになるわけだ。 例えば 8 個のアクターが一律 5 秒で Thread.sleep を使ったとしても、5 秒寝るものもあれば 10 秒寝るものもあるってこと。 それはちょっとがっかりだ。 とりあえず、ざっとリファレンスを眺めた限りでは、そこを何とかするようなものは用意されていないっぽい。 いろいろ試してみたけど、結局うまい方法は見つけられなかった。

reactWithin(5000) {
  case TIMEOUT => ()
}

とかで行けるかなーと思ったんだけど、なんか scala.actors.SuspendActorException とかいう例外が出てダメ。 まあ、単純にスリープして時間稼ぎたいなんてシチュエーションは、実用的にはむしろレアケースのような気もするし、気にするだけ無駄かなあ。 JoCaml の場合は、基本的にプロセスとスレッドを 1:1 で対応付けるポリシーのように見えたんで色々手を尽くしたけど、Scala はそうじゃないっぽいし。

あと、JoCaml であの問題にこだわったのは、& で繋いでプロセスを並行起動っていう文法があるから、その分くらいは全部別スレッドで動いて欲しいというのもあった。 Scala の場合はそういうの無いしね。

% [Python] all, any とか doctest とか

odz さんの『入れ子リストの中身を順に表示』を見てたら、いろいろ知らないものが。

おぉ、all なんて関数があったのか。 日本語訳のマニュアルには見当たらなかったが……また 2.5 future か。 any ってのもあるみたいだ。 いい加減、日本語マニュアルに頼るの止めた方が良いんだろうか。 いつまで経っても 2.5 にならんし。

んで、なんか doctest ってモジュールがおもしろい。 これだけで、ちょっとしたテストフレームワークになってるし、doctest 形式のテストを unittest 用に変換したりもできるみたい。 提言のページが、なかなか含蓄がある。

よい例がたくさんの言葉に値することは多々あります。

まったくもって、そのとおりだと思う。 常々「ちゃんと動くサンプルコード重要」と思っているわしとしては、このモジュールはきっちり使いこなすべきものかもしれない。

% [Scala] サンタクロース問題改訂版

さっきのバージョンは誰がサンタのところに来るかに明らかな偏りがあったので (最初に 4 匹トナカイが帰ってくるのは、ほぼ確定)、全員がそれぞれ独自に待ち時間を決めるのはやめて、神のサイコロによって「働け」と天啓が下るようにしてみた。 ちょっと手を抜いてて、相手が何をしてるかなんておかまいなしでメッセージを送るので、仕事して戻ってみたらすでにまた「働け」コールが来ていて、とんぼ返りでサンタのところに引き返さなきゃならない…みたいな連中も出るっぽい(苦笑

% cat santa.scala
import scala.actors._
import scala.actors.Actor._

object Santa extends Application {
   val rand = new java.util.Random(System.currentTimeMillis())
   def wheelOfFortune(members:Array[Actor]):Unit = {
      val delay = rand.nextInt(2000)
      val num = rand.nextInt(members.length)
      java.lang.Thread.sleep(delay)
      members.apply(num) ! 'work
      wheelOfFortune(members)
   }
   def reindeer(santa:Actor):Unit = {
      loop { react {
         case 'work =>
            Console.println("reindeer was returned.")
            santa ! 'return
            loop { react {
               case 'play => reindeer(santa)
            }}
      }}
   }
   def elf(santa:Actor):Unit = {
      loop { react {
         case 'work =>
            Console.println("elf was visited.")
            santa ! 'hello
            loop { react {
               case 'bye => elf(santa)
            }}
      }}
   }
   def santa(reindeers:List[Actor])(elves:List[Actor]):Unit = {
      (reindeers.length, elves.length) match {
         case (9, _) =>
            Console.println("delivery...")
            reindeers.foreach(_ ! 'play)
            santa(Nil)(elves)
         case (_, n) if n >= 3 =>
            Console.println("meeting...")
            elves.takeRight(3).foreach(_ ! 'bye)
            santa(reindeers)(elves.take(n-3))
         case (_, _) =>
            loop { react {
               case 'return => santa(sender::reindeers)(elves)
               case 'hello  => santa(reindeers)(sender::elves)
            }}
      }
   }

   val s = link { santa(Nil)(Nil) }
   val rs = for (x <- List.range(0,9))  yield link { reindeer(s) }
   val es = for (x <- List.range(0,10)) yield link { elf(s) }
   link { wheelOfFortune(rs.toArray ++ es) }
}

loop と react の組み合わせは、ある意味慣用句なので、ちょっと書き方を工夫してみた。 っていうか、行数ばっか嵩んで、たまったもんじゃないし。 あと、空リストとしての List() を、存在忘れてた Nil に直したり。

% [Erlang][Scala] やっぱり Erlang の熟成度はたいしたもんだなあ

Scala の Actor では、思ったように動いてくれない以下のようなケース。

scala> import scala.actors.Actor._
import scala.actors.Actor._

scala> for (x <- List.range(0,10)) actor{Thread.sleep(5000);println(self)}
unnamed0: Unit = ()

scala> scala.actors.Actor$$anon$0@a2bb54
scala.actors.Actor$$anon$0@c76b4c
scala.actors.Actor$$anon$0@5555bf
scala.actors.Actor$$anon$0@709b64
scala.actors.Actor$$anon$0@8e7b84
scala.actors.Actor$$anon$0@4f53eb
scala.actors.Actor$$anon$0@e6b82
scala.actors.Actor$$anon$0@b45f54
scala.actors.Actor$$anon$0@46101f
scala.actors.Actor$$anon$0@cc88ce

やってみればわかると思うけど、5 秒間隔で、4 個 → 4 個 → 2 個と表示される。 主に、Actor ローカルな sleep 関数が用意されていないのが原因。 いろいろと試してみた感じだと、現状だとなかなか自前で (自前じゃなくても?) それを作るのは難しい。 4 個ずつなのは、実際に処理するのが 4 つのワーカスレッドだから。

で、同じことを Erlang でやると、

Eshell V5.5.4  (abort with ^G)
1> F = fun(_) -> spawn(fun() -> timer:sleep(5000),io:format('~p~n',[self()]) end) end.
#Fun<erl_eval.6.72228031>
2> lists:foreach(F,lists:seq(1,10)).
ok
<0.41.0>
<0.40.0>
<0.39.0>
<0.38.0>
<0.37.0>
<0.36.0>
<0.35.0>
<0.34.0>
<0.33.0>
<0.32.0>

きっちり 5 秒後に全部表示される (もちろん多少の誤差はあるけど)。 別にシステムスレッドが 10 個使われてたりもしない。 まあ、こういうのができるのは、たぶん VM まで自前だからこそのような気もする。 Scala はどうしても JavaVM の制約に縛られるんだろうし。

ちなみに、timer:sleep() の実装は、↓こんな(苦笑

sleep(T) ->
    receive
    after T -> ok
    end.

これって、まさしくさっき Scala で試した、

reactWithin(5000) {
  case TIMEOUT => ()
}

と同じようなものだと思うんだけど、こっちはうまく動いてくれないんだよなー。 かといって、receiveWithin を使っちゃうと Thread.sleep 使うのと変わんないし。

% [Scala] ちょっと不思議な def

基本的には def は関数定義で val は値の束縛なんだけど、実はどうやら、

  • val (var も同様) の場合、右辺を評価した上で束縛する
  • def の場合、右辺は評価せずに束縛する

という特徴があるらしい。 例を挙げるとこういうこと。

scala> val a = { println("hoge"); 1 }
hoge
a: Int = 1

scala> def b = { println("fuga"); 2 }
b: Int

要するに、def を使うとブロックそのものを束縛できる。 んで、こいつは使う度に (評価する度に) ブロック内が評価されるので、

scala> for (x <- List(1,2,3)) println(x+b)
fuga
3
fuga
4
fuga
5
unnamed0: Unit = ()

こんなことになる。キモい。 なんか使い道無いかと考えたけど、思い浮かばなかった。

ちなみに、関数の引数としてブロックを受け取る方法もあって、このページによれば、

scala> def f(x : => int) = x + 1
f: (=> int)Int

scala> f { println("piyo"); 2 }
piyo
unnamed2: Int = 3

こんな感じのようだ。 うーん、何かできそうだよなー。

scala> def myIf[T](bl: =>Boolean)(th: =>T)(el: =>T) = {
     |   bl match {
     |     case true => th
     |     case false => el
     |   }
     | }
myIf: [T >: ? <: ?](=> Boolean)(=> T)(=> T)T

scala> myIf { 1 > 0 } { 'ok } { 'ng }
unnamed4: Symbol = 'ok

うひ、ちょっとおもろい。


2007-06-23 [長年日記]

% [雑談] 来週の OSC2007 Hokkaido は…

一応、今のところわりと行くつもり。 とか言いつつ、タイムテーブルとか全然チェックしてないけど。

たぶん昼ちょい前くらいを目がけて、まったり出動することになると思われる。 まあ、当日になってみないとわからんけど。

ちなみに、いつものように格好はジーパンに T シャツだろう (気候によって袖が長かったり短かったりはあると思うが)。 んで、坊主頭でメガネで無精髭。 一番の特徴はデカイこと。あと猫背。

不機嫌そうに見えても、そう見えるだけで、たぶん実際はそんなことないので気軽に声かけてください。 (つーか、ほんとに不機嫌だったら、きっとそこにはいないでしょう)

% [Scala] クロージャによる (以下略) を Scala で

実は、scala.actors パッケージには Channel というクラスがあって、それがほとんどそのままπ計算に使えそうなんだけども、ここは敢えてそれを使わずに。

% cat Picalc.scala
object Picalc {
   abstract class ChanType[T]
   case class Senders[T](messages:List[T]) extends ChanType[T]
   case class Receivers[T](processes:List[T=>Unit]) extends ChanType[T]

   class Chan[T] {
      var value:ChanType[T] = Senders(Nil)

      def <<(x:T) = { value match {
         case Senders(ss) =>
            value = Senders(ss ::: List(x))
         case Receivers(Nil) =>
            value = Senders(List(x))
         case Receivers(f::rs) =>
            value = Receivers(rs)
            f(x)
      }; this}

      def >>(f:T=>Unit) = { value match {
         case Receivers(rs) =>
            value = Receivers(rs ::: List(f))
         case Senders(Nil) =>
            value = Receivers(List(f))
         case Senders(x::ss) =>
            value = Senders(ss)
            f(x)
      }; this}
   }
}
% scalac Picalc.scala
% scala -cp .

scala> import Picalc._
import Picalc._

scala> val x = new Chan[Int]
x: Picalc.Picalc$Chan[Int] = Picalc$Chan@2dc320

scala> x << 3
unnamed0: Unit = ()

scala> x >> { println(_) }
3
unnamed1: Unit = ()

new Chan のときに型を指定しないと、Chan[Nothing] 型になっちゃうので注意。 ただし、型推論のヒントがあるようなシチュエーションならいらない。↓こんなとか。

scala> new Chan << 1 >> {println(_)}
1
unnamed2: Picalc.Picalc$Chan[Int] = Picalc$Chan@3eea2f

次、repeat の例。

scala> val c = new Chan[Int]
c: Picalc.Picalc$Chan[Int] = Picalc$Chan@2e0066

scala> def repeat():Unit = {
     |   c >> { println(_); repeat() }
     | }
<console>:8: error: missing parameter type
  c >> { println(_); repeat() }
                 ^

ありゃ、暗黙の変数使うときは、式は一つじゃないとダメっぽい。 あと、再帰的な関数を書く場合は返り値の型も指定しないとダメなのも注意。

scala> def repeat():Unit = {
     |   c >> { i => println(i); repeat() }
     | }
repeat: ()Unit

scala> repeat()
unnamed0: Unit = ()

scala> c << 1
1
unnamed1: Picalc.Picalc$Chan[Int] = Picalc$Chan@2e0066

scala> c << 2
2
unnamed2: Picalc.Picalc$Chan[Int] = Picalc$Chan@2e0066

scala> c << 3
3
unnamed3: Picalc.Picalc$Chan[Int] = Picalc$Chan@2e0066

あとはまあ、書くまでも無いかな。

% [Scala] scala.concurrent パッケージが地味に熱い

てっきり、scala.actors ができたからパッケージごと deprecated なんだと思い込んでたんだけど、よくよく見ると jolib とか pilib というステキちっくなモジュール (object) が。 名前から想像がつくように、jolib は join-calculus を、pilib は pi-calculus を再現するためのものだ。

ということで、とりあえず pilib を使ってみる。 書き方の感じとしては、Wikipedia の Pi-caluculus に書いてあるものに近いようだ。

scala> import scala.concurrent.pilib._
import scala.concurrent.pilib._

scala> val c = new Chan[Int]
c: concurrent.pilib.pilib$Chan[Int] = <function>

まずチャンネルを作って、

scala> c(3) * {}
unnamed0: concurrent.pilib.pilib$GP[Unit] = scala.concurrent.pilib$GP@e9d469

メッセージの送信。 チャンネル c に 3 というメッセージを送って、空のプロセス { } を実行している。 Wikipedia の例で行くなら、c(x).P と表記されているもの。 送信だけという操作はできなくて、送信して何かのプロセスを実行という形しか無いので注意。

scala> c * { x => println(x) }
unnamed1: concurrent.pilib.pilib$GP[Unit] = scala.concurrent.pilib$GP@1ae4cc

これで c からメッセージを受信して x に束縛し、続くプロセスを実行する。 ……はずなんだけど、ここでは何も表示されない。 なぜかと言うと、これらの例で返されている GP というクラスのインスタンス (Guarded Process) は、choice することで始めて実際に評価されるため。

ただし、ここでうっかり、

scala> choice( ch(3) * {} )

とかやっちゃうとブロックして戻ってこなくなるので注意。 はっきりした原因は不明だけど、たぶん Chan クラスが実際に送信受信する際、双方向のリクエストが無いとブロックするせいじゃないかと思う。 なので、正しく実行させるためには、送信のプロセスと受信のプロセスを spawn を使って並行起動する。

scala> spawn < choice(c(3)*{}) | choice(c*{x=>println(x)}) >
unnamed2: unit = ()

scala> 3

この例だと、3 が表示されるよりもプロンプトが戻ってくるのが早かったので、変なところに表示されているけど、ともあれ、想定どおりに動いてくれたのがわかる。 spawn は見てのとおり、< > で囲んで | で区切ったものを並行に計算するもの。 実は < も > も | も全部 Spawn クラスのメソッドだというのがおもしろい。 Scala の DSL 構築能力は異常だな。マクロ使ってるわけでもないのに。

……えーと、replication の表現方法がわからないので、とりあえずここまで。

本日のツッコミ(全1件) [ツッコミを入れる]

% TrackBack [http://jijixi.azito.com/cgi-bin/diary/index.rb?date=200706..]


2007-06-24 [長年日記]

% [Scala] pilib の続きと、scala.concurrent のその他の要素

うーん、それほど難しく考える必要は無かったか。 replication はこんな感じで。

scala> import scala.concurrent.pilib._
import scala.concurrent.pilib._

scala> val v = new Chan[Int]
v: concurrent.pilib.pilib$Chan[Int] = <function>

scala> def repeat():Unit = {
     |   choice(c * { i => println(i); repeat() })
     | }
repeat: ()Unit

scala> spawn < repeat() >
unnamed0: unit = ()

scala> spawn < choice(c(1)*{}) >
1
unnamed1: unit = ()

scala> spawn < choice(c(2)*{}) >
2
unnamed2: unit = ()

scala> spawn < choice(c(3)*{}) >
3
unnamed3: unit = ()

ところで、pilib.spawn 関数 (厳密には pilib.Spawn クラス) は scala.concurrent.ops.spawn という関数を利用して作られている。 ops モジュールには spawn, future, par, replicate という関数が定義されていて、それぞれ、

  • spawn(p : => Unit) : Unit
    • ブロック p を run メソッドの実体として指定した java.lang.Thread クラスを作成して start する
  • future[a](p : => a) : () => a
    • a 型の値を返すブロック p を別スレッドで実行し、その値を返す関数を返す。返された関数を実行したときに、まだ計算が終わっていない場合はブロックする。
  • par[a,b](xp : => a, yp : => b) : (a,b)
    • xp, yp 双方のブロックを並行的に実行し、値をタプルにまとめて返す。
    • 実装的には xp は普通に実行しつつ、yp を future で実行している。
  • replication(start : Int, end : Int)(p : Int => Unit) : Unit
    • ブロック p を start から end-1 までの通し番号を渡して spawn する。

というような感じになっている。 scala.concurrent パッケージには、他に MailBox や Channel というスレッド間通信に使えるクラスがあるので、ops モジュールとそれらを利用すれば比較的手軽に Java のスレッドを利用できる。 Actor だと微妙に困る (ちゃんとスレッドに分かれてもらわないと…とか) ような場面で使えそう。

ちなみに MainBox は Actor (や、Erlang のプロセス) が持っているものとほぼ同じもので、溜まっているメッセージの中から必要なものだけを取り出すことができる。 Channel はスレッドセーフなキュー。

scala> import scala.concurrent._
import scala.concurrent._

scala> val mbox = new MailBox
mbox: scala.concurrent.MailBox = scala.concurrent.MailBox@fdc6f6

scala> mbox.send(3.asInstanceOf[mbox.Message])
unnamed0: unit = ()

scala> mbox.send("hoge".asInstanceOf[mbox.Message])
unnamed1: unit = ()

scala> mbox.receive { case s if s.isInstanceOf[String] => s.asInstanceOf[String] }
unnamed2: String = hoge

scala> mbox.receive { case i if i.isInstanceOf[Int] => i.asInstanceOf[Int] }
unnamed3: Int = 3

receive 時に case でマッチしなかったメッセージは処理されずに残る。 パラメタライズされていなくて、メッセージの型は MailBox.Mesage (実体は AnyRef) に固定されているので、適宜キャストしてやらなきゃいけないのが面倒だけど、Channel より柔軟さがある。 逆に Channel はパラメタライズされているので、

scala> val ch = new Channel[Int]
ch: scala.concurrent.Channel[Int] = scala.concurrent.Channel@f5e794

scala> ch.write(10)
unnamed4: Unit = ()

scala> ch.read
unnamed5: Int = 10

こんな風に使える。 ただし、キュー (FIFO) なので、入れた順番に値が出てくることになる。

% [雑談] 壮絶に寝違えて首から肩にかけて、むちゃくちゃ痛い

楽な体勢を模索してるんだが、なかなか見つからない。 つ、つらい……

% [Scala] scala.collection.jcl パッケージ

Scala から Java のクラスを簡単に利用できるとは言っても、両者の Generics には互換性が無いので、Java のコレクションクラスを使おうと思うと、昔ながらのキャスト地獄に陥いってしまう。

scala> import java.util.ArrayList
import java.util.ArrayList

scala> val l = new ArrayList[Int]
<console>:5: error: java.util.ArrayList does not take type parameters
val l = new ArrayList[Int]
            ^

とは言え、それはさすがに寂しすぎるので、Scala の Generics を利用できるようにしたラッパークラスが用意されている。

scala> import scala.collection.jcl.ArrayList
import scala.collection.jcl.ArrayList

scala> val l = new ArrayList[Int]
l: scala.collection.jcl.ArrayList[Int] = []

これで安心。

% [Scala] PartialFunction の秘密

Actor.react などの関数は、

react(f : PartialFunction[Any, Unit]) : Nothing

こんな感じで PartialFunction という型の引数を取ることになっている。 そして、これまでの例を見ればわかるように、これは、

react {
  case 'hoge => ...
  case 'fuga => ...
}

というような形で使われる。 つまり、このいきなり case で始まるブロックが PartialFunction なわけだ。 PartialFunction[A,B] は A という型の引数を一つ取り、B という型の値を返す関数を意味していて、基本的には通常の関数と同様に使用できる。

通常の関数との大きな違いは isDefinedAt(x) というメソッドを持っていることだ。 これは何かと言うと、x の値が PartialFunction 内の case にマッチするかどうかを、実際に関数を実行する前に確認するためのもの。 多分、サンプルコードを見た方がわかりやすい。

scala> def f(arg : int)(block : PartialFunction[int,int]) = { block.isDefinedAt(arg) }
f: (int)(PartialFunction[int,int])Boolean

scala> f(1) { case 1 => println('hoge); 1 }
unnamed0: Boolean = true

scala> f(1) { case 2 => println('hoge); 1 }
unnamed1: Boolean = false

こんな感じ。 case 1 の場合、一つ目の引数で指定した 1 がマッチするので true が返り、case 2 の場合はマッチしないので false が返っている。 どちらも実際に関数 (block) は実行されていないので 'hoge は表示されていない。

Actor の MailBox はこれを利用して実装されている。 つまり、溜まっているメッセージの中から、receive (あるいは react) に与えられた PartialFunction にマッチするものを探してから、実際に実行するという手法だ。

PartialFunction を利用することで、マクロ無しでも相当柔軟なメタプログラミングができそうだ。 ちなみに、orElse メソッドというのもあって、これは事実上複数の PartialFunction を並列に繋ぐもの。 例としてはこんな感じ。

scala> type PF[T] = PartialFunction[T,T]
defined type alias PF

scala> def f(arg:int)(b1:PF[int])(b2:PF[int])(b3:PF[int]) = {
     |   (b1 orElse b2 orElse b3)(arg)
     | }
f: (int)(PF[int])(PF[int])(PF[int])int

scala> f(1) { case 1 => 2 } { case 2 => 4 } { case 3 => 6 }
unnamed3: int = 2

scala> f(2) { case 1 => 2 } { case 2 => 4 } { case 3 => 6 }
unnamed4: int = 4

scala> f(3) { case 1 => 2 } { case 2 => 4 } { case 3 => 6 }
unnamed5: int = 6

まあ、こんな関数を作ることは無いと思うが、言語内言語を構築しようとするときには何かしら使い出があるんじゃなかろうか。

あと、ついでだから紹介しておくと、引数が一つの関数 (Scala では通常の関数も実際はオブジェクト) には andThen というメソッドがあって、これは関数合成を意味する。

scala> ({ x:int => x + 1 } andThen { x:int => x * 2 })(3)
unnamed6: Int = 8

引数 3 に左の関数 (x + 1) を適用して、その結果に右の関数 (x * 2) を適用している。 つまり (f andThen g)(x) = g(f(x)) というやつだ。 def で定義した関数を使う場合は、こんな↓感じかな。

scala> def f(x:int) = x + 1
f: (int)Int

scala> def g(x:int) = x * 2
g: (int)Int

scala> (f _ andThen g _)(3)
unnamed10: Int = 8

% [Scala] apply と unapply

Scala では関数もオブジェクトである。 そして、オブジェクトを関数たらしめているのが apply メソッドだ。 Python で言えば __call__ メソッドのようなもの。

scala> object Hoge {
     |   def apply(s:String) = println(s)
     | }
defined module Hoge

scala> Hoge("hoge")
hoge
unnamed0: Unit = ()

このように、どんなオブジェクトでも apply メソッドが定義されていれば、あたかも関数のように使うことができる。

そして、apply にある意味対応するものとして、unapply メソッドがある。 これは、パターンマッチ時のマッチケースとして使われる。

scala> object Hoge {
     |   def unapply(arg:(int,int)) = Some(arg)
     | }
defined module Hoge

scala> def f(x:(int,int)) = x match {
     |   case Hoge(1,2) => 'ok
     |   case Hoge(_,_) => 'ng
     | }
f: ((int, int))Symbol

scala> f((1,2))
unnamed0: Symbol = 'ok

scala> f((0,2))
unnamed1: Symbol = 'ng

こんな感じ。 case class は、実質 apply と unapply が自動的に定義されたオブジェクトと同等だと言えるだろう。 つまり、

scala> case class Hoge(n:int,m:int)
defined class Hoge

これは、

scala> object Hoge {
     |   class Hoge(n:int,m:int) { val value = (n,m) }
     |   def apply(n:int,m:int) = new Hoge(n,m)
     |   def unapply(arg:Hoge) = Some(arg.value)
     | }
defined module Hoge

こういう定義とほぼ等しい。

scala> Hoge(1,2) match { case Hoge(n,m) => (n,m) }
unnamed0: (int, int) = (1,2)

% [雑談] マジで脳内メーカーが恐しい

あろはさんとこに本名でやった脳内が貼ってあって、「そうか本名でやれば、また違った結果が出るかも」と思ってやってみたら……

某の脳内

多少マジメになったけど、やっぱり『遊』からは離れられないらしい(苦笑)。 真ん中の『忘』がちょっと怖い。

ちなみに、わしは本名を知られたくないということは無くて、と言うか別に隠す努力もしてないし、ある種公然の秘密のようなものなんだけど、かと言ってはっきり書いたりしないのは要するにアレさ、グレートゼブラの中の人なんて、わかる人にはバレバレだけど、そこは敢えて「いったい誰なんだ!」とか言うのがお約束でしょ…みたいな、そういうノリ。 え、例えが古い? (あと、そういうネタは中の人が有名人じゃないとつまんないよ、というツッコミは却下w)

まあ、結局のところ、わしの個人情報なんて知りたいと思う人だけが知れば良い話で、特に興味を抱かない人にまで公言する必要は無いでしょう、と、そういう事。(なんか前にも、こんなこと書いた気がするな)

本日のツッコミ(全3件) [ツッコミを入れる]

% TrackBack [http://jijixi.azito.com/cgi-bin/diary/index.rb?date=200706..]

% みずしま [> PartialFunction おお。こんな変な?機能もあるのですね。参考になります。 いやはい、Scalaは面..]

% jijixi [なんか、マクロが無くたってここまでできるんだぞ!っていう限界に挑戦してるような気がしますよね(笑]


2007-06-25 [長年日記]

% [Scala] unapplySeq でシーケンスパターンの定義 (と、unapply に関する補足)

昨日の apply, unapply ネタの続き。 って言うか、ほんとは昨日の時点でこれも一緒に書くつもりだったのに、何かいろいろやりながら書いてたらすっぽり忘れてたという。

シーケンス型のオブジェクトに対しては、パターンマッチ時にシーケンスパターンというのが使える。 どういうものかと言うと、一言で言えば『長さが可変のパターン』かな。 パターンマッチを持っている言語なら、大抵はプリミティブなシーケンス型 (通常はリストだろう) に対して同様のものが使えて、例えば OCaml なら、

# match [1;2;3;4;5] with
  | x::y::z::rest -> Printf.printf "%d %d %d\n" x y z
  | _ -> ();;
1 2 3
- : unit = ()

こんな風に書けたりする。 ただ、普通こういう書き方は、あらかじめ言語側で用意されている型にしか使えなくて、自分で何か新しく型を作った場合には使えない。 unapplySeq はそれを可能にしてくれるものだ。

ちなみに、Scala で上記のものと同様のことをしたとすると、こんな感じ。

scala> List(1,2,3,4,5) match {
     |   case List(x,y,z,_*) => printf("{0} {1} {2}\n",x,y,z)
     | }
1 2 3
unnamed0: Unit = ()

_* は変数名じゃなく、『残り全部』を表わす特別なパターン。 これを変数に束縛したい場合は name @ _* のように書く。 @ はパターンの塊に名前を付けるもので (OCaml で言えば as)、次のような例にも使える。

scala> (1,2) match {
     |   case tup @ (x,y) => {printf("{0}\n{1}\n{2}\n",tup,x,y)}
     | }
(1,2)
1
2
unnamed1: Unit = ()

さて、それじゃあ unapplySeq の使い方だけど、基本的には unapply と何ら変わりは無い。 単に、シーケンス型の値を返せば良いだけだ。

scala> object MyList {
     |   class MyList[T](l:List[T]) { val value = l }
     |   def apply[T](xs:T*) = new MyList(xs.toList)
     |   def unapplySeq[T](x:MyList[T]) = Some(x.value)
     | }
defined module MyList

scala> MyList(1,2,3,4,5) match {
     |   case MyList(x,y,z,rest @ _*) => printf("{0} {1} {2} {3}\n",x,y,z,rest)
     | }
1 2 3 List(4, 5)
unnamed7: Unit = ()

ちなみに、unapply でも unapplySeq でも、Option 型を返すことになっているけど、これは『マッチしない』ことを表わすために None を返せるようにということ。 逆に言えば、上記の例のように Some の値だけを返す定義なら、MyList(...) というパターンを使ったときに、unapplySeq の引数である MyList 型の値が来れば、必ずマッチするということでもある。 (追記)微妙に嘘だな、これ。unapply/unapplySeq によって分解されたあとの値がマッチするかどうかも当然関係する。例えばパターンに定数を指定していれば、そこで一致しなきゃマッチしない。

マッチしないときに None を返すというのを利用すれば、複雑なガード条件を一つのパターンとしてまとめてしまうこともできる。 例えば、

scala> def f(x:int) = x match {
     |   case y if y > 0 && y % 2 == 0 => 'ok
     |   case _ => 'ng
     | }
f: (int)Symbol

こういう関数があったとして (x が 0 より大きくて、2 で割り切れる場合 'ok を返す)、この 'ok を返すようなパターンを何度も利用したいなら、

scala> object PositiveEven {
     |   def unapply(x:int) = if (x > 0 && x % 2 == 0) Some(x) else None
     | }
defined module PositiveEven

こんなのを定義しておくと、

scala> def g(x:int) = x match {
     |   case PositiveEven(_) => 'ok
     |   case _ => 'ng
     | }
g: (int)Symbol

scala> g(10)
unnamed9: Symbol = 'ok

scala> g(-10)
unnamed10: Symbol = 'ng

scala> g(3)
unnamed11: Symbol = 'ng

こんな風に書けるようになる。

% [Scala] Stream の微妙な不具合

以前話題にした件について、どうなったかなーと思って調べてみたんだけど、やっぱり同じように無限ループする。

scala> val infs = Stream.from(0)
infs: Stream[Int] = Stream(0, ?)

scala> val filts = infs.filter(_ < 10)
filts: Stream[Int] = Stream(0, ?)

scala> val takes = filts.take(10)
takes: Stream[Int] = Stream(0, ?)

scala> takes.length
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

どうも、こういう境界バグは見つけてしまうと何とかせずにいられないというか、前回はソースを展開する前にスルーしたから良かった(?)んだけど、今回は concurrent パッケージを調べたせいで、すでにソースが展開されている状況で、ついついソース読んじゃったんだよ...orz

ちなみに Stream クラスは (というか、実際には Stream のインスタンスを作成する Stream.cons.apply 関数だが) 例のすぐに評価されないブロックを利用して遅延評価な仕組みを作っている。 型で書くと、こう↓

apply[A](hd: A, tl: => Stream[A]) : Stream[A]

で、tl のブロック (特別 { } で囲まなくても、式一つでも同じ扱いだけど) は、Stream クラスの tail メソッドが呼ばれたときに始めて評価される。

そんな感じで、ざっとソースを眺めていくと、結局のところ take メソッドがよろしくないっぽい。 take の実装は↓こんな。

% cat scala/Stream.scala
...
245   override def take(n: Int): Stream[A] =
246     if (n == 0) Stream.empty
247     else Stream.cons(head, tail.take(n-1))
...

これだと、filter されたリストの最後の要素で、触らなくても良い tail を触ってしまう。 結果、その tail は (もう絶対にマッチしない条件だから) 無限ループなので、length で最後の要素まで実際に計算されると、無限ループになる。 ……うまく説明できないな。 単純化した例で書けば、

scala> val s = Stream.from(0).filter(_ == 0)
s: Stream[Int] = Stream(0, ?)

まず、こういうリストがあるとしよう。 これは、0 から始まる無限リストを、要素が 0 のものだけフィルタリングしたものなので、構造としては一番頭の要素が 0 で、それ以降のリスト (tail) は値が無い (計算しようとすると無限ループする) リストになっている。 ただ、この時点では tail の部分の評価は遅延されているので、何も起こらない。 そして、これを take(1) で切り取ってみる。

scala> val t = s.take(1)
t: Stream[Int] = Stream(0, ?)

これが現在の実装だと、どのような構造になっているか考えると、

Stream.cons(0, tail.take(0))

こうで、このリストに length を使えば、(empty が出るまで再帰するので) 当然 tail.take(0) を評価することになって、結果 (tail は無限ループするので) 無限ループになってしまうというわけ。

そんでまあ、結局この問題を解決するには take の実装を↓こんな風に変えれば良いと思う。

scala> def take[T](self:Stream[T])(n:int):Stream[T] = {
     |   n match {
     |     case 0 => Stream.empty
     |     case 1 => Stream.cons(self.head, Stream.empty)
     |     case _ => Stream.cons(self.head, take(self.tail)(n-1))
     |   }
     | }
take: [T >: ? <: ?](Stream[T])(int)Stream[T]

scala> val s = Stream.from(0).filter(_ < 10)
s: Stream[Int] = Stream(0, ?)

scala> take(s)(10).length
unnamed0: Int = 10

実際はインタンスメソッドとして実装するわけだから、その辺はそのように直すにしても、ともあれこれで例の問題は解決する。 得意の一行パッチを書けば、こんな感じか。

--- Stream.scala.orig	2007-06-25 17:06:09.000000000 +0900
+++ Stream.scala	2007-06-25 17:06:53.000000000 +0900
@@ -244,6 +244,7 @@
    */
   override def take(n: Int): Stream[A] =
     if (n == 0) Stream.empty
+    else if (n == 1) Stream.cons(head, Stream.empty)
     else Stream.cons(head, tail.take(n-1))
 
   /** Returns the stream without its <code>n</code> first elements.

つーかねー、実のところ、これでもまだ不満があって、空リストに対して take を呼んだときの対処がされてないのが気持ち悪いんだよなー。

scala> val s = Stream.range(0,5).take(10)
s: Stream[Int] = Stream(0, ?)

scala> s.length
java.util.NoSuchElementException: head of empty stream
        at scala.Stream$$anon$0.head(Stream.scala:29)
        at scala.Stream$$anon$0.head(Stream.scala:27)
        at scala.Stream$class.take(Stream.scala:247)
        at scala.Stream$$anon$0.take(Stream.scala:27)
        at scala.Stream$$anonfun$9.apply(Stream.scala:247)
        at scala.Stream$$anonfun$9.apply(Stream.scala:247)
        at scala.Stream$cons$$anon$1.tail(Stream...

そりゃねーよ、と。 ちなみに Haskell なら、こう↓ね。

Hugs> take 10 [0..4]
[0,1,2,3,4]
Hugs> length (take 10 [0..4])
5

いや、例えばこの NoSuchElementException とやらを for 文が配慮してくれるなら良いよ?

scala> for (x <- Stream.range(0,5).take(10)) println(x)
0
1
2
3
4
java.util.NoSuchElementException: head of empty stream
        at scala.Stream$$anon$0.head(Stream.scala:29)
        at scala.Stream$$anon$0.head(Stream.scala:27)
        at scala.Stream$class.take(Stream.scala:247)
        at scala.Stream$$anon$0.take(Stream.scala:27)
        at scala.Stream$$anonfun$9.apply(Stream.scala:247)
        at scala.Stream$$anonfun$9.apply(Stream.scala:247)
        at scala.Stream$cons$$anon$1.tail(Stream...

してくれないじゃない。 こんなんじゃ、安心して使えないよね。 どうしたって遅延リストと言ったら Haskell のものをイメージしちゃうわけだし、同じように使えないのはツライ。

しかし、この現在の状態って単に手抜きなのか、何かしらポリシーがあるのか、どっちなんだろうなあ。 空リスト云々なんて、単に空リストなら空リストを返すっていうパターンを入れれば良いだけなんだから、手を付けてないってことは、敢えてそうしてるって気がしなくもないんだけど、そうだとして、そうする根拠って何だろう。 うーん、わからん。

% [独り言] もうダメポ...orz

本気で、もうどうしたものやら。

% [Mac] Spotlight TIPS

向井さんとこ読んで、「待ってくれー!!」と思ったので書いとく。 それ Spotlight ならできるヨ。

一部のディレクトリはインデックスにつっこんでほしくない、

/.Spotlight/_rules.plist を編集しませう。以下例。

...(略)
<dict>
        <key>EXCLUDE</key>
        <array>
          <string>/opt/local/var/db</string>
        </array>
        ...(略)
</dict>
...(略)

うちの過去記事も併せてどぞ。

パスといえば、特定のパスの下だけ検索したいケースというのもあると思うけど、

『場所:/hoge fuga』とか。 あるいは Finder で目当てのフォルダを開いて、Finder ウインドウの検索欄を使用 (もしかしてデフォだと出てなかったかも知れないけど、その場合は速攻で『ツールバーをカスタマイズ...』推奨)。 『場所』とか打つのダセー (or ダルー) と思う場合は、『場所』の代わりに『where』でも可 (もしかすると、Unlocalize.mdimporterが必要だったかも)。 コマンドラインで mdfind 使うなら -onlyin オプションが便利。(see 'man mdfind')

% [clip][Mac] MacOS Xの言語モード (shiology)

via del.icio.us/otsune.

英語モードにすると快適だよ…という話。 実は、前々からなんとなくそんな気はしてたんだけど、実際に試したことはなかった。 やってみようかな。

本日のツッコミ(全2件) [ツッコミを入れる]

% 向井 [あーなるほど、 Finder の検索ウィンドウが使えるのですね。それは知りませんでした。 あと、設定ファイルの類は覚..]

% kzys [Unlocalize はそこには影響していないと思います。あとインデックスから外すのはシステム環境設定 > Spot..]


2007-06-26 [長年日記]

% [Scala] for .. yield は、"List" comprehension ではなかった

正直、この辺の説明では気付きにくいと思うんだけど、ともあれ、for .. yield によって返ってくるのは、元になったシーケンスの型と同じになる。 つまり、

scala> for (i <- List.range(0,10)) yield i
unnamed0: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> for (i <- Array.range(0,10)) yield i
unnamed1: Array[Int] = [I@dcda91

scala> for (i <- Iterator.range(0,10)) yield i
unnamed2: Iterator[Int] = has

scala> for (i <- Stream.range(0,10)) yield i
unnamed3: Stream[Int] = Stream(0, ?)

こういうこと。 これなら、Iterator や Stream を使った無限列も内包表記でさらっと書ける。 ステキだ。

scala> for (i <- Stream.from(0)) yield i
unnamed4: Stream[Int] = Stream(0, ?)

でもこれ、どういう仕組みなんだろ。 何か必要なプロトコルを備えてクラスを作れば、自動的に for の方で対応してくれたりすんのかな。

% [Scala] Proxy クラスと、その仲間

あーもー、Scala おもしろすぎる。

Proxy クラスというのはデリゲータを作るための基礎クラスである。

scala> val myZero = new Proxy {
     |   def self : int = 0
     | }
myZero: AnyRef with Proxy{def self(): Int} = 0

scala> myZero == 0
unnamed17: Boolean = true

scala> myZero == 1
unnamed18: Boolean = false

scala> myZero.toString
unnamed19: String = 0

self メソッドだけが abstract の状態で残されているので、それを定義してやると、あたかも self に設定した 0 と同じように振る舞ってくれる。 まあ、Proxy には equals, hashCode, toString くらいしか定義されていないので、実際にそうしてくれるのは上の例でやったことくらいのものだけど。

ちなみに、シーケンスやコレクションクラスには Proxy を継承した SeqProxy や MapProxy などが用意されていて、簡単にデリゲートクラスを作ることができる。 ……のだが、これは全然本題ではなくて。

Proxy のサブクラスには scala.runtime.Rich* というものがあって、実は Scala 内でも Java のプリミティブ型のように見えている String 型なんかも、実際には RichString 型だったりする。

scala> "hoge\n"
unnamed26: java.lang.String = hoge

こんな風に見えるが、

scala> "hoge\n".stripLineEnd
unnamed27: String = hoge

このように、java.lang.String が持っていないメソッドを持っている。 そんな感じで、scala.runtime パッケージの Rich なんちゃらクラスのメソッドは確認しておいて損は無いんじゃないだろうか。 ちなみに RichInt には Range クラスを生成するメソッドがあって、

scala> for (i <- 0 to 5) println(i)
0
1
2
3
4
5
unnamed41: Unit = ()

scala> for (i <- 0 until 5) println(i)
0
1
2
3
4
unnamed42: Unit = ()

こんな感じの書き方ができる。 見てわかるように to は終端を含んで、until は含まない。 あと until には、こういうのもある。

scala> for (i <- 0 until(10,2)) println(i)
0
2
4
6
8
unnamed43: Unit = ()

なんか、毎回毎回 List.range(n,m) みたいに書いてたのがばかばかしくなるね。 ただし、Range クラスは Iterator の子孫なので、yield で値を返すと結果は Iterator になる。 List が欲しいなら toList しよう。

scala> for (i <- 0 until(10,2)) yield i
unnamed44: Iterator[Int] = has

scala> (for (i <- 0 until(10,2)) yield i).toList
unnamed45: List[Int] = List(0, 2, 4, 6, 8)

% [雑談] なんとなく『あわせて読みたい』を利用してみようと思ったんだが…

不可思議なことにタイトルがなぜか『はてなアンテナ - ○○のアンテナ』(プライバシー保護?のため一部伏字) になってしまうのが、どうにも気持ち悪くて (けして、このアンテナの持ち主さんが気持ち悪いということではない) 結局止めてしまった。

なんかね、こういう意味不明な変な状態を見せられると、ちょっと安心できないというか。

試しにフィードメーターの方も試してみたら、こっちは『jijixi's diary』になってるなあ。 まあ良いか。ぶっちゃけどうでも良い。ふと思い付いただけで、どうしてもやりたいってわけでもないし。 またいつか機会があれば、ということで、今回は終了。

% [clip] 水がちょうど0℃で凍って100℃で沸騰するってすごくね? (2ちゃんねるレスブック)

こういうアホなネタ好きだなあ。

はたして、まとめられるときに切り捨てられたレスの中には、いくつ空気読めないマジレスがあったのか。


2007-06-28 [長年日記]

% [告知] 仕事探してます

コンピュータ関係なら何でもやります。 アルバイトでも契約社員でも何でもかまいません。 札幌近郊で人手をお探しの方、ご連絡ください。 遠方でもネットワーク経由で仕事が可能なら大丈夫です。

% [雑談] てなことを、この日記に書きたくもなるほどヤバい

マジでヤバい。 どれほどヤバいかと言うと、今日発売のスパロボ OG をスルーしたほどヤバい。 本気で、夜中の警備員のバイトとかコンビニのバイトとか視野に入れ始めてるんですが。

あ、ちょっとチャカした書き方してますが、それはこの日記の性格上不可避なキャラ作りでして、仕事の方はわりと本気で募集してますのでよろしくお願いします。 メールアドレスは、しばらく前からこの日記の一番下の方に表示するようにしました。 本名の方のアドレスをご存知の方は、そちらへ連絡していただいてもかまいません。

本日のツッコミ(全3件) [ツッコミを入れる]

% FV [VB、C、C++、Oracle、SQLのいずれかを使える人の 募集をしております。転職、、、されるのですか?]

% jijixi [えーと、転職と言いますか、フリーで細々とやってきたんですけど、最近あまりにも仕事が無いんでバイトなり就職なり考えなき..]

% TrackBack [http://jijixi.azito.com/cgi-bin/diary/index.rb?date=200708..]


2007-06-29 [長年日記]

% [Python][Mac] どうやら、python25 で無効にされてるモジュールは py25-なんちゃらというパッケージに細かく分かれて存在しているらしい件

例の hashlib なんかは py25-hashlib とか、tkinter なら py25-tkinter とか。 そら port search python とかやっても出て来ねーわな。

まあなんつーの、自分にとって何が必要で何が必要でないかわかってる人なら、こういう構成もアリなのかもね。 わしのような素人には、余計なお世話だが。 つーか、よくわかんないから、とりあえず全部入れときたいっつー人のために、メタパッケージくらい作れよ。

ともあれ、python25 が腐ってるという件は、微妙に嘘だったってことになるが、パッケージ名のセンス無さとかぐったりなんで (python25 の付属モジュールなんだから、python25-hashlib とか、そういう python25 で検索したときに一緒に出てくるような名前にしろよと小一時間問い詰めたい)、やっぱりもう MacPorts で Python 入れることは無い気がする。

% [雑談] えーと、そんな大層な人間じゃないので、期待しないでくださいね(ボソ

分不相応な評価が独り歩きすると、ちょっと怖いものがあります(苦笑

% [Scala] ちょっと変わった型制限

Predef.scala というファイルを眺めてたら、見たこと無い型表記を見かけたんで、ちょっと調べてみた。 ちなみに、このファイルにはデフォルトでトップレベルに定義されている型が色々列記されている。

んで、件の型はこんなの (例はでっちあげ)

scala> def f[A <% Long](x:A) = x
f: [A >: ? <: ?](A)(implicit (A) => Long)A

型引数の部分の <% は、どうやら『A <% B』と書けば『B への暗黙の変換 (View) が可能な A』という意味っぽい。 つまり、上記の例で言えば、f は Long に暗黙的に変換される得る型のみを引数に取る

scala> f(10L)
unnamed0: Long = 10

scala> f(10)
unnamed1: Int = 10

scala> f((10:Short))
unnamed2: Short = 10

scala> f("10")
<console>:5: error: no implicit argument matching parameter type (java.lang.String) => Long was found.
  val unnamed3 = f("10")
                  ^

scala> f(10.0)
<console>:5: error: no implicit argument matching parameter type (Double) => Long was found.
  val unnamed4 = f(10.0)
                  ^

正直、暗黙の変換にはちょっと忌避感があるんで、Views についてはあんまり調べてなかったんだけど、これを使うと、型の親子関係に縛られずに、かつ、きちんと型を制約した関数なんかが書けそうだね。 と言っても、わしの頭じゃ、ドロナーワな使用法しか思い付かないけど。 例えば、適当に設計したせいで、何の継承関係も無く量産されてしまった複数の型を一緒くたに扱いたいとか、そういうの。

scala> case class Point(x:int,y:int,start:int,end:int)
defined class Point

scala> case class Circle(x:int,y:int,radius:int)
defined class Circle

scala> case class Square(x:int,y:int,height:int,width:int)
defined class Square

これはヒドい(苦笑)。 でもまあ、全部 x 座標と y 座標持ってるんで、一つの関数で扱えたらなあなどと思う。 だからって、

scala> def pos(x:Any) = {
     | x match {
     |   case Point(x,y,_,_) => (x,y)
     |   case Circle(x,y,_) => (x,y)
     |   case Square(x,y,_,_) => (x,y)
     | }}
pos: (Any)(int, int)

さすがに、こんなの書いて、

scala> pos("hoge")
scala.MatchError: hoge
        at .pos(<console>:12)
        at .<init>(<console>:6)
        at .<clinit>(<console>)
        at RequestResult$.<init>(<console>:3)
        at RequestResult$.<clinit>(<console>)
        at RequestResult$result(<console>)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMetho...

実行時エラーとかは寂しかろう。 そんなわけで普通は結局クラス構成を見直したくなるんじゃないかと思うが、諸々の問題でそれが無理だったり現実的じゃなかったりすることもあるかもしれない。 しかし、そんなときでも、Views を使えばそれを泥縄に解決できる。

scala> implicit def point2xy(p:Point) = p match { case Point(x,y,_,_) => (x,y) }
point2xy: (Point)(int, int)

scala> implicit def circle2xy(c:Circle) = c match { case Circle(x,y,_) => (x,y) }
circle2xy: (Circle)(int, int)

scala> implicit def Square2xy(s:Square) = s match { case Square(x,y,_,_) => (x,y) }
Square2xy: (Square)(int, int)

こんな感じで暗黙の変換関数を用意して、

scala> def pos[A <% (int,int)](a:A) = (a:(int,int))
pos: [A >: ? <: ?](A)(implicit (A) => (int, int))(int, int)

scala> pos(Point(1,2,3,4))
unnamed8: (int, int) = (1,2)

scala> pos(Circle(1,2,3))
unnamed9: (int, int) = (1,2)

scala> pos(Square(1,2,3,4))
unnamed10: (int, int) = (1,2)

こんな感じで。

scala> pos("hoge")
<console>:9: error: no implicit argument matching parameter type (java.lang.String) => (int, int) was found.
  val unnamed11 = pos("hoge")
                     ^

これはコンパイル時のエラーね。 後で扱う型が増えても、

scala> case class Triangle(x:int,y:int,p1:int,p2:int,p3:int)
defined class Triangle

scala> implicit def Triangle2xy(t:Triangle) = t match { case Triangle(x,y,_,_,_) => (x,y) }
Triangle2xy: (Triangle)(int, int)

scala> pos(Triangle(1,2,3,4,5))
unnamed12: (int, int) = (1,2)

暗黙変換関数を追加してやれば、pos 関数を変更する必要もない。

まあ、この例みたいに変換先の型が (int,int) みたいなありふれたものだと、たまたま型が一致しちゃうものとかあるかも知れないから、ちゃんとやるならそのための型を専用に用意した方が良いと思うけど。


2007-06-30 [長年日記]

% [雑談] OSC2007 Hokkaido 行ってきた

腰が痛い。ちょっと歩いただけなのに、どんだけ運動不足なんだよ。

あろはさんとか池上さんとかに会ったよ。 空気読みすぎて池上さんに名刺渡しそこねたけど。(や、なんかずっと忙しそうにしてたからタイミングが掴めなくて…)

せっかくだから、見てきたプログラムのまとめ。

  1. 一コマ目は間に合わず
  2. 地球流体電脳倶楽部の紹介
    • 地球流体研究のソフト作ってます
    • 東大で作ったソフトは東大でしか使わせない、とか言うから自分達で作った
    • 学校で使ったソフト、憶えたテクニックを就職してからも活かせなかったら詐欺でしょ
    • だから全部オープンソースです
    • 志は高いけど、マンパワーが足りないので大学関係者以外の人でも助っ人歓迎
    • 実はこのコマで隣に座ってた人 (空席挟んでだけど) が池上さんだったんだけど、最後の質問・意見のときに「池上です」と名乗ったときには ikegami さん (たしかネット上ではこの表記しか見たことが無い) と結びつかなかったのがダメすぎる (や、だってブログ読んでて名前とか音読しないし、ただ字面で記憶してるだけだからさー)
  3. Ruby勉強会@札幌
    • 人がたくさんで入りそこねたから、実は見てない
  4. いまどきのマイクロソフト
    • たぶん Silverlight の話だろうと期待してチャレンジ
    • なぜか前半はマイクロソフトの歴史 (寝そうだった)
    • Windows Vista には Service for UNIX のサブセットが組み込まれてますよ
    • 他にも Windows Power Shell とかあるんで、色んなことがコマンドラインでできるようになってます
    • ようやく Silverlight の紹介
      • DLR (Dynamic Language Runtime) を利用して Python だけじゃなく Ruby とか Visual Basic とか用意する予定
      • VB は .NET になってから大きく変わってしまって結構不評だったらしい
      • 元々 VB.NET も以前の VB のようなものにしたかったけど、CLR では難しかった
      • え、わしはわりと好きだけどね VB.NET
    • Microsoft Robotics Studio の紹介
      • マイクロソフトでは実験的な技術は、自分たちがシェアを持ってないところに突っ込む風習がある
      • CCR (Concurrency and Coordination Runtime) というのを作ってて、その応用としてロボットを動かすことをやっている
  5. Haskell 紹介
    • 最後のエルは二つです
    • H は北海道の H
    • 北海道はでっかいどう
    • オキャムラーとして対決すべきでしたか?(そんな度胸ありません)
    • 定員 12 とか書いてる部屋なのに、20 人くらいいなかった?
  6. Haskell デモ
    • Haskell で書かれたゲームの紹介
      • monadius
      • samegame
      • なんかインベーダーだかなんだか
  7. 次のコマは、いまいち心惹かれるのが無かったんで爆睡
  8. 風呂で作るマッシュアップサービス
    • マッシュアップとは、ぐちゃぐちゃに混ぜること
    • じゃらんと Google Maps をマッシュアップして温泉マップを作るデモ
    • ネットに繋がらなくて大ピンチ
    • Ruby on Rails なら、そこそこ簡単に Web API を利用したマッシュアップができます

個人的に一番の収穫は Robotics Studio の存在を知ったことな気が。 ……良いのか、それで?……と思わなくもないが。


トップ 最新 追記

日記ってのは本来、自分で読み返すためにあるもんだよなあ……
もしくは有名人になったら死後に本になったりとかか?

RSS はこちら

jijixi at azito.com