トップ 最新 追記

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

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-01-01 [長年日記]

% [Python] やばい、この要素にはグッと来た

この前ネタを発展させて、入力がリストでもタプルでも大丈夫なのを書けないかなーと思っていろいろ試してみたり。 で、最初 cons 関数をこんな風にしてみる。

% cat cons.py
def cons(x, xs):
   t = type(xs)
   tmp = (x,) + tuple(xs)
   if t is tuple:
      return tuple(tmp)
   elif t is list:
      return list(tmp)
   else:
      raise TypeError
% python2.5 -i cons.py
>>> cons(1, (2,3))
(1, 2, 3)
>>> cons(1, [2,3])
[1, 2, 3]
>>> cons(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "cons.py", line 3, in cons
    tmp = (x,) + tuple(xs)
TypeError: 'int' object is not iterable

まあ、これでも動くには動く。 でも、この調子で他の関数にもちまちま if 文を足してくのって、なんか不毛じゃね?

んで、ここでふと思い立って実験。

In [1]: t = type([1,2])

In [2]: t
Out[2]: <type 'list'>

In [3]: t()
Out[3]: []

In [4]: t('12')
Out[4]: ['1', '2']

In [5]: t( (1,2) )
Out[5]: [1, 2]

キタコレ。 これを利用して cons もすっきり。

def cons(x, xs):
   t = type(xs)
   tmp = (x,) + tuple(xs)
   return t(tmp)

あとは、空のシーケンスは全部偽なのを利用して、

if l == []:

みたいなのを、

if not l:

て感じにして、list() や tuple() が空リストや空タプルを作るのを利用して、

return loop(0, llst, [])

とかを、

return loop(0, llst, t())

みたいにしてけば、めでたくシーケンス汎用なものができあがり。 以下、改変後のソースと実行例 (読み飛ばして結構)。

% cat christmas_gift_all_sequence.py
import random

def car(lst):
   return lst[0]
def cdr(lst):
   return lst[1:len(lst)]
def cons(x, xs):
   t = type(xs)
   tmp = (x,) + tuple(xs)
   return t(tmp)

def shuffle(lst):
   t = type(lst)
   tmp = list(lst)
   random.shuffle(tmp)
   return t(tmp)

def flatten(llst):
   t = type(llst)
   def loop(num, l, ret):
      if not l:
         return ret

      x, xs = car(l), cdr(l)
      def loop_(l_, ret_):
         if not l_:
            return ret_

         y, ys = car(l_), cdr(l_)
         return loop_(ys, cons((num, y), ret_))
      return loop(num + 1, xs, loop_(x, ret))
   return loop(0, llst, t())

def pairing_list(llst):
   t = type(llst)
   fl = flatten(llst)
   def loop():
      def fold_f((receivers, result), (id, name)):
         def pair(reserve, recs):
            if not recs:
               raise Exception('error')

            x, xs = car(recs), cdr(recs)
            rid, rname = x
            def merge(l1, l2):
               if not l1:
                  return l2
               y, ys = car(l1), cdr(l1)
               return merge(ys, cons(y, l2))
            if id == rid:
               return pair(cons(x, reserve), xs)
            else:
               return (merge(reserve, xs), cons((name, rname), result))
         return pair((), shuffle(receivers))
      try:
         _, ret = reduce(fold_f, shuffle(fl), (shuffle(fl), t()))
         return ret
      except Exception, e:
         if str(e) == "error":
            return loop()
         raise e
   return loop()
% python2.5 -i christmas_gift_all_sequence.py
>>> pairing_list( ((1,2), (3,), (4,)) )
((1, 4), (3, 2), (4, 1), (2, 3))
>>> pairing_list( [[1,2], [3,], [4,]] )
[(4, 1), (3, 2), (2, 4), (1, 3)]

% [Python] 三項演算子の代わり

if が値を返してくれればそれでも良いんだけど、とにかくそういうものが欲しい。 欲しいんだけど、とりあえず無いっぽい。 なので、何か方法を考える。

In [1]: def if_then_else(bl, th, el):
   ...:     if bl:
   ...:         return th
   ...:     else:
   ...:         return el
   ...: 

In [2]: s = 'hoge'

In [3]: if_then_else(len(s) == 4, 'ok', 'ng')
Out[3]: 'ok'

In [4]: if_then_else(len(s) == 0, 'ok', 'ng')
Out[4]: 'ng'

一瞬こんな組み込み関数があるんじゃないかと思って探してみたが……ねーよ。 用意されてるなら使っても良いけど、自分で定義してまではちょっとなあ。

結局、今のところ思いついてるので無難なのは、こんな↓かね。 イマイチ直感的じゃないつーか、とっさに書くと間違えそうではあるんだけど……

In [5]: len(s) == 4 and 'ok' or 'ng'
Out[5]: 'ok'

In [6]: len(s) == 0 and 'ok' or 'ng'
Out[6]: 'ng'

なんかこういう FAQ 的な小技って、どっかにまとまってそうなんだけど、どこにあるんかなあ?

% [Python] 続・三項演算子

も少し具体的に調べてから書けば良かった(苦笑

2.5 からはあるんじゃん (参考→PEP 308: Conditional Expressions)。 つーか基本的にわしが見てるドキュメントが 2.4 ベースなのが良くなかったなあ。 ってことで、

In [1]: s = 'hoge'

In [2]: 'ok' if len(s) == 0 else 'ng'
Out[2]: 'ng'

In [3]: 'ok' if len(s) == 4 else 'ng'
Out[3]: 'ok'

ちょっと気持ち悪いけど、and,or を使った書き方よりはよほどわかりやすい。

% [Python] functools -- 高階関数、callable object の操作

2.4 のリファレンスは一通り眺めたつもりなので、2.5 からの要素を追ってみたりする。

まず部分適用。

In [1]: import functools as ft

In [2]: def f(a,b,c):
   ...:     return "%d, %d, %d" % (a,b,c)
   ...: 

In [3]: f(1,2,3)
Out[3]: '1, 2, 3'

In [4]: g = ft.partial(f, 1, 2)

In [5]: g
Out[5]: <functools.partial object at 0x120e780>

In [6]: g(3)
Out[6]: '1, 2, 3'

Scheme の cut みたいなもんだけど、マクロじゃないんで (cut f <> 2 3) みたいなのはすんなり書けない。 そういうときはキーワード引数を使う。 キーワード引数ってのは、関数定義時の仮引数名が呼び出し時には引数のラベルになる仕組み。

In [7]: h = ft.partial(f, b=2, c=3)

In [8]: h(1)
Out[8]: '1, 2, 3'

update_wrapper 関数が何するものかイマイチよくわからんのだけど、ラップする関数 (wrapper) とそれにラップされる関数 (wrapped) がある時に、wrapper の属性 (既定だと __name__, __module__, __doc__) を wrapped のものに書き換えるらしい。 Python 歴が浅いので何がどう嬉しいのかイメージできないんだけど、デコレータを自作するときに使ったりするようだ。 wraps 関数は update_wrapper に部分適用して、wrapper だけを引数に取る関数を返すみたい。 wrapper を修飾するデコレータとして使うようだ。 ともかくよくわからん。

部分適用なんかは、わざわざこのモジュール使わなくても lambda なんかで代用が効くと思うけど、lambda は葬る方向らしいからこういうのが用意されてきてるのかね。

In [9]: g = lambda c: f(1,2,c)

In [10]: g(3)
Out[10]: '1, 2, 3'

In [11]: h = lambda a: f(a,2,3)

In [12]: h(1)
Out[12]: '1, 2, 3'

% [Python] PEP 341: Unified try/except/finally

2.5 からは try に except と finally を一緒に使えるようになりましたよーという話らしいが…… むしろ今まで一緒に使えなかったってのが驚きだよ。

% python2.3
Python 2.3.5 (#1, Mar 20 2005, 20:38:20) 
[GCC 3.3 20030304 (Apple Computer, Inc. build 1809)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...     raise Exception
... except:
...     print 'exception'
... finally:
  File "<stdin>", line 5
    finally:
          ^
SyntaxError: invalid syntax

あがー...orz こんな finally に何の意味がある。 昔の人はどうしてたんだ? こうですか?

>>> try:
...     try:
...             raise Exception
...     finally:
...             print 'finally'
... except:
...     print 'exception'
... 
finally
exception

……不毛。

% python2.5
Python 2.5 (r25:51908, Nov 24 2006, 10:19:07) 
[GCC 4.0.1 (Apple Computer, Inc. build 5363)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> try:
...     raise Exception
... except:
...     print 'exception'
... finally:
...     print 'finally'
... 
exception
finally

ほっとする。 三項演算子といい、これといい、とりあえずわしは 2.5 より前のバージョンは見なかったことにして生きていこうと思います。

% [Python] PEP 343: The 'with' statement

Ruby のプロック付きメソッドの便利さに対抗した要素だろうか。

with expression [as variable]:
    with-block

expression を評価した結果が context management protocol をサポートしたオブジェクトである場合、with-block の実行前に前処理、ブロックの実行後に後処理を自動的にやってくれるらしい。 context management protocol というのは、前処理用の __enter__() メソッドと後処理用の __exit__() メソッドを実装していることのようだ。 とりあえず file object には実装されてるみたいなんで試してみよう。

% cat /tmp/test.txt
hoge
fuga

こんなのがあったとして、

In [1]: from __future__ import with_statement

In [2]: with open('/tmp/test.txt', 'r') as fp:
   ...:     for line in fp:
   ...:         print line
   ...: 
hoge

fuga


In [3]: fp
Out[3]: <closed file '/tmp/test.txt', mode 'r' at 0x121cbf0>

fp はちゃんとクローズされている。

In [4]: fp.__enter__
Out[4]: <built-in method __enter__ of file object at 0x121cbf0>

In [5]: fp.__exit__
Out[5]: <built-in method __exit__ of file object at 0x121cbf0>

context management protocol を備えている。 もう少し具体的にわかるように実験してみよう。

In [21]: class C:
   ....:     def __enter__(self):
   ....:         print 'enter'
   ....:         return
   ....:     def __exit__(self, _type, _value, _traceback):
   ....:         if _traceback is None:
   ....:             print 'ok'
   ....:             return True
   ....:         else:
   ....:             print 'ng'
   ....:             print _type
   ....:             return True
   ....: 

In [23]: with C():
   ....:     print 'with-block'
   ....: 
enter
with-block
ok

In [24]: with C():
   ....:     raise Exception
   ....: 
enter
ng
<type 'exceptions.Exception'>

__exit__ の返り値が偽の値だと、捕捉された例外が再度 raise される。

もしかすると、Ruby のブロック付きメソッドより良いかもって気がする。 少なくとも汎用性はこっちの方が上だ。 2.6 からは標準になるみたいなんで、それに合わせてたくさんの関数が context management protocol に対応されれば、かなり良い感じになりそうな予感。

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

% ま。 [「汎用的」という観点からはループも要素選択も内包表記の代替もできるブロック付きメソッドの方が汎用的だと思いますが。 ..]

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


2007-01-02 [長年日記]

% [OCaml][Ruby][Python] 写経してみてようやく気付くこともある

この前、「何度試しても失敗することが無いのは何でだろう」と疑問に思っていた部分だが、自分で (Python でだけど) 鍋谷さんと同じものを書いてみてやっと意味がわかった。 find_exchange を再帰するところで、組合せに失敗して not_given に有効な送り先が残っていない場合、再帰前のループに戻ってきて一つ前の結果を無かったことにして進めるからなんだね。 言うなれば、天然バックトラック?

Ruby の配列操作はわりとがんがん新しいオブジェクトを作るけど、Python のリスト操作はほとんどが破壊的操作なんで気付いたっすよ。 こういうのもけがの功名って言うのかしら。 でも結局、exchinfo[ix] = exchinfo[ix].dup の意味がわかんなかったな。 別にこれはコピーしなくても動く気がするけど……また何か見落としてるかもしれない。 あるいは安全側に倒してるだけ?

ともあれ、やっぱりわしの実装がヘボかったのが証明されたわけだけど、じゃあ同じような動きになるように OCaml のを修正してみたくなるのが人情。 そんなこんなで以下↓……と思ったんだけど、非破壊的に、かつ、さりげなく書くのは難しいな (わしの脳がユルいだけだってのは置いとけ)。 後で書こう。

% [OCaml] あなたならどうお書きになります1.0 リベンジ (失敗)

というわけで書いた。

(追記) 思いっ切り間違ってたんで一旦削除...orz もう少しちゃんと考えてやり直すっす。

% [Ruby][Python] トップレベルに関数を書き散らせることの安心感

関数型脳になるとついついトップレベルに関数をぽいぽい書いてしまう (個人差があります)。 んで、まあファイル一個で終わるならそれで良いんだけど、Ruby で再利用の可能性を含みつつ何かを書く場合、それをやっちゃうと後で泣く可能性が結構あったりして鬱。

% cat a.rb
def f
   'hoge'
end
def hello
   p f
end
% cat b.rb
require 'a'
hello
def f
   'fuga'
end
hello
% ruby b.rb
"hoge"
"fuga"

こういうことね。Ruby のワナの一つだよな。 Python だとこういうことが無いんで、良いなあと思った。

% cat a.py
def f():
   return 'hoge'
def hello():
   print f()
% cat b.py
from a import *
hello()
def f():
   return 'fuga'
hello()
% python b.py
hoge
hoge

Ruby で再利用する可能性のあるものを書くときは、適当にモジュールやクラスに包んでやるのが基本だと思うけど、当初はその気が無くても後で利用したくなることってのはあるもんで、まあぶっちゃけ、Ruby ではトップレベルでメソッド定義すんなってことなのかもしれない。

ただ、もう少し言えば Ruby のクラス (モジュール含む) は名前空間がフラットなのもちょっと気になるんだよな。 それが問題になるほどの規模で、何かを書いたことがあるわけじゃないけど……

% cat c.rb
class C
   def f
      p 'hoge'
   end
end
def fact
   C.new
end
% cat d.rb
require 'c'
class C
   def f
      p 'fuga'
   end
end
fact.f
% ruby d.rb
"fuga"

ある程度の人数で開発するなら、なにかしら紳士協定が必要になるよね。 命名規則とか。 Python の場合、ファイルごとに名前空間が区切られるから、こういう問題はたぶん起こらない。 まあ、そもそもちゃんと意味のある名前を付けてれば、そんなそんな被ったりしないだろ、って話はあるけども。

% [Ruby][Python] Ruby ユーザのための Python 入門 (リスト編)

リスト編とか言っといて、他にもやるつもりがあるのかってーと微妙。 っていうか、要するにいつものように自分の脳味噌の整理だから。

Ruby にどっぷり漬かってる人が Python をいじり始めて、まず困惑するのが返り値の問題だろう。 Ruby はあらゆるところで値が返ってくるのが特徴で、たぶんみんなそれを無意識に使ってる。 代入だって、if 文だって、とにかく値を返す。

irb(main):001:0> l = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]

でも Python は逆に、驚くほどあらゆるものが値を返さない。 代入も if 文も、関数だって明示的に return しないかぎり値を返さない。 まあ、構文が値を返さないのは良いよ、むしろ Ruby が変態的かもしれない。 でも、インスタンスメソッドが値を返さないものばっかりなのは、ちょっとつらい気もするぞ。 どうも Python では破壊的操作をする関数は値を返さない (None を返す) というポリシーがあるっぽい。

Ruby だとこういうの↓普通に書くと思うんだけど、

irb(main):006:0> l.push(6).push(7)
=> [1, 2, 3, 4, 5, 6, 7]

Python では、

In [8]: l = [1,2,3,4,5]

In [9]: l.append(6).append(7)
---------------------------------------------------------------------------
<type 'exceptions.AttributeError'>        Traceback (most recent call last)

/Users/jijixi/<ipython console> in <module>()

<type 'exceptions.AttributeError'>: 'NoneType' object has no attribute 'append'

l.append(6) の返り値が None なのでこういうエラーになる。

まあ愚痴はこれくらいにして、Ruby でわりとよくある操作と同じことを Python でやるとどうなるかという比較をやってみようと思う。 Python に慣れてるわけじゃないので、もっとキレイにできるぞーとかってツッコミは歓迎。

# 配列リテラル
# Ruby
[1,2,3,4,5]
  ↓
# Python
同じ
# イテレーション
ary.each { |i| ... }
  ↓
for i in ary:
   ...
# 結合
ary + ary2
  ↓
同じ
# 文字列を連結
ary.join(',')
  ↓
','.join(ary)
ary << a << b
  ↓
ary.append(a)
ary.append(b)
ary.map { |i| ... }
  or
ary.collect { |i| ... }
  ↓
def proc(i):
   ...
map(proc, ary)
ary.inject(init) { |a,b| ... }
  ↓
def proc(a,b):
   ...
reduce(proc, ary, init)
ary.compact
  ↓
[x for x in ary if x is not None]
ary.reverse
  ↓
list(reversed(ary))
ary.reverse!
  ↓
ary.reverse()
ary.each_with_index { |item, idx| ... }
  ↓
def proc(item, idx):
   ...
[proc(ary[i], i) for i in range(len(ary))]  # もちろん proc の中身が短かければ直接書いても可
ary.dup
  ↓
list(ary)
ary.delete_at(pos)
  ↓
ary.pop(pos)
# 値を返さなくて良いなら del ary[pos] でも可
ary.flatten
  ↓
# 自分でそれっぽい関数書くしかなさそう?

Ruby の配列に比べると Python のリストは操作関数が少ない (と思う) んだけど、代わりに内包表記があるから大抵のことはそれで何とかなると思われる。 つーか内包表記が無かったら多分キレてるな、わし。

% [OCaml] あなたならどうお書きになります1.0 再リベンジ

今度こそ大丈夫だと思う。 さっきの失敗バージョンに比べると、ちょっと冗長な感じだけどまあ良いや。 どっちにしても、いじったのは pairing_list 関数だけ。

% cat christmas_gift_5.ml
module M = Map.Make (
struct
   type t = int
   let compare = Pervasives.compare
end)

let () = Random.self_init ()

let list_of_map m =
   M.fold (fun _ item seed -> item :: seed) m []

let shuffle lst =
   let rec loop l ret =
      match l with
      | [] -> ret
      | x::xs ->
           let rec make_id () =
              let tmp = Random.bits () in
              if M.mem tmp ret then make_id () else tmp
           in
           loop xs (M.add (make_id ()) x ret)
   in
   list_of_map (loop lst M.empty)

let flatten llst =
   let rec loop num l ret =
      match l with
      | [] -> ret
      | x::xs ->
           let rec loop' l' ret' =
              match l' with
              | [] -> ret'
              | y::ys ->
                   loop' ys ((num, y) :: ret')
           in
           loop (num + 1) xs (loop' x ret)
   in
   loop 0 llst []

let pairing_list llst =
   let fl = flatten llst in
   let fold f s l =
      let rec fold' s' l' =
         match l' with
         | [] -> s'
         | x::xs -> fold' (f s' x l) xs
      in
      fold' s l
   in
   let rec fold_f ret ((rid, rname) as r) l =
      match ret with
      | `Found _ -> ret
      | `Yet (senders, result) ->
           match senders with
           | [] -> failwith "unknown error!"
           | (id, name)::xs ->
                if id = rid
                then ret
                else begin
                   let new_result = (name, rname) :: result in
                   let new_recs = List.filter (fun a -> a <> r) l in
                   match new_recs with
                   | [] -> `Found new_result
                   | _ ->
                        let tmp =
                           fold fold_f (`Yet (xs, new_result)) (shuffle new_recs)
                        in
                        match tmp with
                        | `Found _ -> tmp
                        | `Yet _   -> ret
                end
   in
   let r = fold fold_f (`Yet (shuffle fl, [])) (shuffle fl) in
   match r with
   | `Found x -> Some (List.sort compare x)
   | _ -> None
% ocaml
# #use "christmas_gift_5.ml";;
# pairing_list [[1;2;3;4];[5;6;7];[8;9]];; 
- : (int * int) list option =
Some [(1, 9); (2, 7); (3, 8); (4, 6); (5, 1); (6, 4); (7, 3); (8, 5); (9, 2)]
# pairing_list [[1;2;3;4];[5;6;7];[8;9]];;
- : (int * int) list option =
Some [(1, 7); (2, 6); (3, 5); (4, 9); (5, 3); (6, 2); (7, 8); (8, 4); (9, 1)]
# pairing_list [[1;2;3;4];[5;6;7];[8;9]];;
- : (int * int) list option =
Some [(1, 9); (2, 7); (3, 6); (4, 8); (5, 3); (6, 2); (7, 1); (8, 4); (9, 5)]
# pairing_list [[1;2;3;4];[5;6;7]];;
- : (int * int) list option = None
# pairing_list [[1;2;3;4];[5;6;7]];;
- : (int * int) list option = None

型検査だけでは、脳の足りなさをごまかせません(苦笑

% [Python] Python の世界はプロトコルで回っている

ここで言うプロトコルってのはぶっちゃけメソッドのことで、必要なプロトコル (メソッド) が定義されていれば、それを必要としている操作に対して共通に使うことができるように Python の標準ライブラリは作られているようだ。 例えば callable オブジェクト。

In [1]: class Callable:
   ...:     def __call__(self):
   ...:         print 'call'
   ...: 

In [2]: o = Callable()

In [3]: o()
call

__call__ メソッドが定義されたオブジェクトは関数のように呼び出しができる。 他にも、__iter__ とかもろもろ定義されてると必要に応じて自動的に呼ばれるメソッドがたくさん存在する。(参考→特殊メソッド名)

In [4]: class Iterable:
   ...:     def __iter__(self):
   ...:         for i in range(10):
   ...:             yield i
   ...: 

In [5]: for i in Iterable():
   ...:     print i
   ...: 
0
1
2
3
4
5
6
7
8
9

Ruby にも自動で呼ばれるメソッドはあるけど (__str__ とか)、あれはどっちかと言うと自動キャストみたいな使い方がされてる感じがする。 Python の場合は総称関数を作るために使われてるような感じ。 Ruby だとそれぞれの型がメソッドを持つけど、Python ではメソッド (というか関数) が一つあって、その関数は必要なプロトコルが実装されてるオブジェクトなら何でも使えるってイメージ。 例にするとこんな↓

irb(main):001:0> class A
irb(main):002:1>   def bow
irb(main):003:2>     p 'bow'
irb(main):004:2>   end
irb(main):005:1> end
=> nil
irb(main):006:0> class B
irb(main):007:1>   def bow
irb(main):008:2>     p 'wow'
irb(main):009:2>   end
irb(main):010:1> end
=> nil
irb(main):011:0> A.new.bow
"bow"
=> nil
irb(main):012:0> B.new.bow
"wow"
=> nil

Ruby がこんなだとしたら Python は…

In [1]: class A:
   ...:     def __bow__(self):
   ...:         print 'bow'
   ...: 

In [2]: class B:
   ...:     def __bow__(self):
   ...:         print 'wow'
   ...: 

In [3]: def bow(obj):
   ...:     obj.__bow__()
   ...: 

In [4]: bow(A())
bow

In [5]: bow(B())
wow

こういう感じ。 これじゃわかりづらいな。 えーと、つまり……Ruby だと『A も B も同じように鳴かせたかったら、どちらにも bow メソッドを実装しましょう』で、Python だと『A も B も同じように bow 関数で使いたかったら、bow 関数に適したメソッド (__bow__) を定義しましょう』って感じ。 全然つまってませんか。 あーもー、ほんと説明ヘタ。

どっちにしても所詮ポリシーの問題でしかないと思うけど、Ruby はまずオブジェクトがあって、それにメソッドを合わせる。 Python はまず関数があって、それにオブジェクトを合わせる。 そういう感じ。 ものすごく適当な言い方をするなら、『Python は関数型言語に近い』と思う。 副作用だらけだし cons セルも無いけど、ライブラリの構築ポリシーとかは関数型っぽいんじゃないかなあ。

まあ、そんな感じで lisper が Ruby より Python に逃げ込む確率が高いらしいのも納得できるような気も。 や、ほんとに Python の方が人気あるのかどうかは知らんけど。


2007-01-03 [長年日記]

% [Python] 内包表記があれば良いや

リストを破壊的に操作するなんてのは、パフォーマンスチューニングの段階でやれば良いんであって、最初から副作用に怯えながらプログラムするなんてナンセンスですよ。 ……と関数型脳がささやいております。 とはいえ Python のリストは、インスタンスメソッドは破壊的なのばっかだし、そうじゃない関数はあんまり充実してるとも思えない。

これはもうリストをいじるときは、内包表記使いまくれってことですね? (ほんとか?)


2007-01-04 [長年日記]

% [Ruby][Python] Ruby の文法的欠陥

"欠陥" というと言いすぎのような気もするけど、この前書いた「Ruby のクラス (モジュール含む) は名前空間がフラットなのもちょっと気になる」という愚痴に関して、ちょっぴり誤解されてる気がしなくもないので補足とか。

わしがこの前愚痴ってたのは「クラスの定義が書き換えられるのがイヤ」と言ってるんではなくて、「本人にその気が無いのに、ついクラスの定義を書き換えてしまう可能性がある」ってことなのですよ。 既存のクラスを簡単に書き換えられることに文句を言うつもりはさらさら無いわけです。 実際、この前ちょろっと書いたネタでも使ってるわけで。 書き換えができること自体は便利だと思ってる。

じゃあ何がイヤなのかってーと、『新しいクラスを作ってるつもりで、知らないうちに既存のクラスを上書きする可能性がある』のと、それが『下手をすれば、広範囲に影響する可能性がある』ってこと。 たとえ一人でしかコードを書かないとしても、『過去の自分は他人』だからして、心配で心配でしかたがない。 だから、OCaml みたいにソースコードを書き換えなければ挙動が変わることは無い言語に安心を覚えてしまうんだろうなあ。

んでまあ、この事について具体的に考えると、たぶんクラスを拡張する構文が、新しくクラスを作る構文とまったく同じなのが問題なんじゃないかと。 例えば、クラスを拡張するときはこんな↓風に書くことにするとかすれば、結構安心な気がするんだけど。

class <<< C
  def hoge
    ...
  end
end

で、通常のクラス定義をするときにすでに同じ名前のクラスが存在したら、警告でも出せば良い。

ところで Python も (最近知ったばかりだけど) クラス定義はオープンで、

In [1]: class A:
   ...:     def a(self):
   ...:         print 'a'
   ...: 

In [2]: o = A()

In [3]: o.a()
a

In [4]: def f(self):
   ...:     print 'b'
   ...: 

In [5]: A.a = f

In [6]: o.a()
b

こんな風にできる。 ただし、ファイルごとに名前空間が別になるから、

In [16]: !cat a.py
class A:
   def a(self):
      print 'c'

In [17]: import a

In [22]: o2 = a.A()

In [23]: o2.a()
c

こうなるし、

In [24]: from a import A

In [25]: o3 = A()

In [26]: o3.a()
c

In [27]: o.a()
b

こうなる。さらに、

In [28]: a.A.a = f

In [29]: o3.a()
b

こう。 すごく安心。 あと、ここでは from a import A のせいでトップレベルの A は a.A に隠されてるけど、

In [30]: B = o.__class__

In [31]: o4 = B()

In [32]: isinstance(o4, A)
Out[32]: False

In [33]: isinstance(o, B)
Out[33]: True

こんなこともできたり。 ともあれ、Python でも既存のクラスをいじったりはできるけど、少なくとも Ruby みたいに "うっかり" いじっちゃうことはまず無いと思われる。 そんな意味で Python には Ruby に無い安心を感じるね。

それはそれとして、最近 Ruby にいろいろ文句をつけてる感じではあるけど、だからって Ruby を使うの止めるつもりなわけでもない。 ちょっとした日々のツールをでっちあげたりするのに、Ruby ほど適した言語はなかなか無いと思うんで。 Python は Ruby に比べると面倒なこと多すぎる。 ただ、少し大きめのものを作るとするなら Ruby と Python どっちを選ぶかって言うと Python かなあ…って気にはなってる感じ。

え? Perl? あれはほら、変数に付く "$" がどうしても好きになれなくて(苦笑

% [雑談] ところで誤解と言えば…

わしのハンドルネームは『jijixi』です。 『jiji-xi』ではないので『jiji』だと別人になります。

ちなみに読み方は、かな打ちの人にはなかなか理解してもらえないんですが、要するにローマ字かな変換で jijixi と打ってみればわかります。 というか、本来はひらがなで書くのが正式名だったんだけど、チャットかなんかで打ち間違えたのが始まりだった気がします。

% [Mac][Fink][Mono][Python] さようなら Fink、こんにちは MacPorts

Ruby の更新が遅いとか Gauche の更新が遅いとか Hugs が永遠に 2001 年版だとか、いろいろうんざりしてきたので、Fink はやめて DarwinPorts 改め MacPorts に移行してみたのであった。 ああ、良いわ、これ。 port コマンドのインタラクティブモードとか楽ちん。 ざっと見た感じ、Fink で入れてたもので MacPorts に無いものとかは無さそうなんで、Fink のディレクトリはごっそり消した。

で、消してから思ったんだけど、Gimp とか Mono とか新たにビルドすんのウゼー...orz

てことで、その辺はバイナリ拾ってきてごまかすことにした。Gimp.app とかね。 んで、Mono もオフィシャルサイトに取りに行ったら、あら 1.2 なんて出てたのね。 ともあれ dmg ファイル拾ってきてインストール。

……うほっ、Boo とか IronPython とか一緒に入ってる。 つーか Boo って今となってはイマイチ位置付けが微妙だよな。

ともあれ、いずれ IronPython は触ってみようと思ってたんで、さっそくいじる。

% ipy
IronPython 1.0 (1.0) on .NET 2.0.50727.42
Copyright (c) Microsoft Corporation. All rights reserved.
>>> 

……うーん、なんか行編集が効かない。つーかバックスペースすら効かないのはヤバイ。 どうなってんの? そもそも何で文字白いの? Ctrl-D が効かないし、いちいち import sys; sys.exit() ですか?

いや、まあ、最初っから ledit 使えってことよね。 ちなみに rlwrap だとなぜかうまく動かないのは何故だろう。 あと ledit を通すと文字の色が変わらなくなる (ま、それは別に問題無いが)。

% ledit ipy
IronPython 1.0 (1.0) on .NET 2.0.50727.42
Copyright (c) Microsoft Corporation. All rights reserved.
>>> import sys
>>> sys.getdefaultencoding()
'us_ascii'

あー、やっぱなあ。 あと、念の為確認してみよう。

>>> sys.setdefaultencoding
<built-in method setdefaultencoding of module object at 0x000000000000002B>

お、消えてない。

>>> sys.setdefaultencoding('euc-jp')
>>> s = unicode('\164\219\164\178')
Traceback (most recent call last):
UnicodeEncodeError: 'ascii' codec can't decode byte 0 in position 0: ordinal not in range

ありゃ、ledit を通すとダメってパターンか?

% ipy
IronPython 1.0 (1.0) on .NET 2.0.50727.42
Copyright (c) Microsoft Corporation. All rights reserved.
>>> import sys
>>> sys.setdefaultencoding('euc-jp')
>>> s = unicode('xxxxx')
>>> s
u'\u307b\u3052'

なんか入力文字の化け方が不可解だが (念の為上記は改変してあるよ)、とりあえずうまく行ってるみたいだな。 でも ledit 通さないとはっきり言って使い物にならんのだが……どうすりゃ良いんだ。 まあ、無理に対話モードで Unicode 文字列使う必要も無いんだが。

% [game] さようなら GBA SP

GBA SP のバッテリーが死亡した。 んでね、どうもこいつって……バッテリーが無いと起動できない……みたいなのね。 電源繋いでてもダメなの。 どっかの Let's n○te とかいうノートパソコンみたいにバッテリー外せば行けるかなと思ったんだけど、それもだめ。

……まあね、バッテリーに寿命があるのはしかたないですよ。 でもさ、バッテリーが死んだら金輪際使用不可ってのはどうよ。 持ち歩けなくても、紐付きで良いから使わせろよ。 つーか替えのバッテリーとか、どこで買えば良いの? そこら辺の店で注文できるの? つーかそこまでして延命させることもないか、最近は GBA のソフトも DS でやるのが普通だし。

ってことで、さようなら、さようなら GBA SP 。今までありがとう。

% [雑談] このところの雪の少なさは異常

何この春先みたいな景色。

% [Ruby][Python][雑談] リストの扱いあれこれ

ある配列の要素を取捨選択しつつ、必要に応じて処理しながら新しい配列を作りたいことって、よくあるでしょ。 あると思うんだ。 ……あるよね。

関数型脳に毒される前のわしは、こんな風に書いてた。

irb(main):001:0> ary = [1,4,7,2,5,8,3,6,9]
=> [1, 4, 7, 2, 5, 8, 3, 6, 9]
irb(main):002:0> result = []
=> []
irb(main):003:0> ary.each { |x|
irb(main):004:1*   if x > 5
irb(main):005:2>     result << x * 2
irb(main):006:2>   else
irb(main):007:2*     next
irb(main):008:2>   end
irb(main):009:1> }
=> [1, 4, 7, 2, 5, 8, 3, 6, 9]
irb(main):010:0> result
=> [14, 16, 12, 18]

Ruby に慣れていろいろなメソッドの存在を知ってくると、こんな風にも書くようになる。

irb(main):011:0> ary.select { |x| x > 5 }.map { |x| x * 2 }
=> [14, 16, 12, 18]

でも今では、どうしてもこんな風に書きたくなってしまう。

irb(main):026:0> ary.inject([]) { |a,b|
irb(main):027:1*   if b > 5 then a + [b * 2] else a end
irb(main):028:1> }
=> [14, 16, 12, 18]

わりと病気っぽい。 ちなみに三項演算子は字面があんまり好きくないので、一行にするときも if 使う派です、わし。 ところでこの例だと、どのパターンが一番効率が良いんだろう。 select して map じゃなく map! するのが無難かしら。 ただ select → map のコンボだともう少し複雑なことやろうとしたときに、困ったりする場合もあったりなかったり。 やっぱこれからの時代は inject ですよ (うそくさい)。 言語によって fold だったり fold_left だったり inject だったり reduce だったり、ややこしいけどな(笑

ああ、そうそう、こんなのもあるね。

irb(main):035:0> ary.map { |x|
irb(main):036:1*   if x > 5 then x * 2 else nil end
irb(main):037:1> }.compact
=> [14, 16, 12, 18]

ちなみに、こういうのを Python で書くならどうかなーと考えると……

In [1]: ary = [1,4,7,2,5,8,3,6,9]

In [2]: map(lambda x: x*2, filter(lambda x: x > 5, ary))
Out[2]: [14, 16, 12, 18]

とか、

In [3]: [ x * 2 for x in ary if x > 5 ]
Out[3]: [14, 16, 12, 18]

こう。あるいは…

In [5]: reduce(lambda a,b: a + [b * 2] if b > 5 else a, ary, [])
Out[5]: [14, 16, 12, 18]

こうとか。 つーか、こうして見ると内包表記の威力ってすごいな。 map いらねーじゃん。 内包表記と reduce があれば何でもできそうだ。

Ruby の場合は内包表記の代わりにがんがんメソッドチェーンしちゃうのが醍醐味かね。

In [9]: [ x + 2 for x in range(10) if x % 2 == 0 ]
Out[9]: [2, 4, 6, 8, 10]

Python でこんな感じのは、Ruby だと、

irb(main):003:0> (0...10).select{|x| x % 2 == 0}.map{|x| x + 2 }
=> [2, 4, 6, 8, 10]

こんなとか。 Python で内包表記がすごくありがたく感じるのは、こういう書き方できないからってのもあるな。 他がいろいろ不自由なだけに、その分自由を感じるというか(苦笑

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

% TrackBack [http://d.hatena.ne.jp/takatoh/20070117/modulename Haskell ..]


2007-01-05 [長年日記]

% [Python] PEP 342: New Generator Features

2.5 からの新要素。 今まで yield は単なる構文だったから値を返さなかったが、今度から式になって値を返すようになったという話。 具体的には next() メソッドが呼ばれたときは (for 文なんかでもこれが呼ばれる) None を返し、send(arg) メソッドが呼ばれたときは arg を返す。 それで何が嬉しいのかイマイチ思い付かないんだけど、とりあえず fold (Python でいう reduce) が作れそうだと思ったんで試してみた。 や、もちろん、ジェネレータなんぞ使わなくても作れるんだけど……

In [67]: !cat test.py
def fold(f, lst, init=None):
    if init is None:
        init = lst[0]
        lst  = lst[1:len(lst)]
    def gen():
        i = 0
        r = init
        yield r
        while i < len(lst):
            r = (yield f(r, lst[i]))
            i += 1
        yield r
    g = gen()
    try:
        r = g.next()
        while True:
            r = g.send(r)
    except StopIteration:
        return r
    else:
        raise Exception

In [68]: execfile('test.py')

In [69]: import operator as op

In [70]: fold(op.add, range(1,5))
Out[70]: 10

In [71]: fold(op.add, range(1,5), 2)
Out[71]: 12

In [72]: fold(op.add, range(1,5), 0)
Out[72]: 10

In [73]: fold(lambda a,b: a + [b], range(1,5), [])
Out[73]: [1, 2, 3, 4]

この仕様に合わせて for 文を拡張したりしないと、ちょっと使いづらい感じはする。 send() を呼んだ時点で次の yield まで行っちゃうのが良くない気が。 値を投入するだけにしとけば良かったのに。

他にも throw(type) でジェネレータ内部に type 型の例外を起こさせたり、close() で GeneratorExit 例外を起こさせたりできるらしい。 こいつらは、今のままの for 文でも使い道があるかもしれない。 単純なところだと…

In [74]: def gen():
   ....:     try:
   ....:         for i in range(10):
   ....:             yield i
   ....:     except:
   ....:         pass
   ....: 

In [76]: g = gen()

In [78]: for i in g:
   ....:     if i < 4:
   ....:         print i
   ....:     else:
   ....:         g.close()
   ....: 
0
1
2
3

こんなとか。 まあ、この例なら break で十分だが(苦笑)。 throw() を使えば、巻き戻し可能なジェネレータとか作れるんじゃないかな。 そういうのが必要なシチュエーションて、どんだけあるのか知らんけど。

% [OCaml][雑談] OCaml の好きなところ

前にも似たようなこと書いたことある気がするけど。 わりとネタっぽいけど、それなりに真実を含みつつある感じでもある。 では、以下。

  1. 強い静的型付けで、かつ型推論がある
  2. 関数オーバーロードが存在しない
  3. 暗黙のキャストが存在しない

型安全でプログラムの正当性を保証することはできないけど、型安全を保証することで頭のよくない人間の間違いを検知することは可能なのです。 型の不一致でコンパイルエラーが出るなら、それは書いてる人間が間違ってるのです。アホなのです。 どんな言語だって、今書いてる場所でどんなデータを扱ってるかを意識しないで書くことは不可能でしょう。 OCaml なら静的型付けで関数のオーバーロードも無い (整数と浮動小数点数がそれぞれ別の演算子を使う病的さ) ので、自分が今どの型を扱っているつもりなのか一目瞭然なのです。 そして、そのつもりが間違っていればコンパイル時にあらかじめわかるのです。

型推論のおかげで、よほどややこしい状況でない限り、型情報なんかはまったく書かずにコードを書き進められるにもかかわらず、その病的なまでの静的さでコードを読めば何の型を扱おうとしてるかわかるわけです。 「Lisp サイコー」とか臆面もなく言えちゃうような人は、スーパーハッカーか、もしくはよほどの身の程知らずじゃないかと思いますが、そうじゃない凡人は自分が今扱ってる値がほんとに思ったとおりの型なのか完璧に把握なんてできないのです。 そして間違って扱ってた場合に、実行してみるまで間違ってるかどうかわかんないなんて怖すぎるのです。 わしは Ruby で何か書くときも、いつもぶるぶる震えてるのです。

だからみんな TDD, TDD って言うんでしょ。 あれって結局、あらかじめ型の指定をして、それが正しいかしょっちゅう確かめましょうってことじゃないですか。 OCaml ならそんなテスト書かんでも良いのですよ。

……まあ、具体的なふるまいまでは感知できないから、それは結局テスト書かなきゃだめですがね。

暗黙のキャストが無いのも安心の理由の一つ。 関数オーバーロードも一種の暗黙のキャストじゃないかと思うんですが、そんなのも無いから心配ありません。 少なくとも、自分が何の型を扱ってるのかわかんなくする罠としては、両方似たようなもんです。 整数を扱ってるつもりなら、そこは絶対整数を扱おうとしてるのです (もちろん凡人は間違うものなので、実際にはそこは整数じゃないかも知れませんが)。 知らないうちに小数とかになってたりしちゃ困ります。 ハスなんとかって言語がはやったりしてますが、あれはだめです。

xxxx> 1 / 2
0.5
xxxx> (1 / 2) == 0
False
(一部伏せ字)

は? なんですかこれは。 1 / 2 は 0 でしょ。 何言ってるんでしょうね、まったく。 OCaml ならその辺ばっちりですよ。

# 1 / 2;;
- : int = 0
# 1. /. 2.;;
- : float = 0.5

この安心感がたまりませんね。 OCaml ほど安心感のある言語はなかなか無いですよ。

% [Erlang] Erlang デビューしてみた

なんかおもしろいもん無いかなーと port search でてけとーに探してたら Erlang があったんで、思わず入れてみたりしたのであった。 Fink には無かったし、FreeBSD に ports で入れようと思ったらなぜかビルドに失敗してそのままだったりしたのよね。 そんなわけで、k.inaba さんの残した足跡とか踏ませてもらいつつ遊ぶ。

基本的なところは ML とか Haskell とか触ったことあれば、迷うところはあんまり無さそう。 変数名が大文字かアンダースコアで始まらないといけないってのが、ちょっと変わってる感じ。 小文字から始まる識別子は atom と呼ばれていて、どうもシンボルみたいなものっぽい。

% erl
Erlang (BEAM) emulator version 5.5.2 [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.5.2  (abort with ^G)
1> is_atom(hoge).
true
2> is_atom(Hoge).
** 1: variable 'Hoge' is unbound **

モジュールや関数は atom に束縛されるみたいだけど、対話環境では関数定義ができないみたいで Hugs の悪夢が蘇える。 クロージャがあるんで変数にクロージャを束縛すれば良いんだけど、どうも再帰ができないみたいで、結局エディタで関数定義しないと遊びきれない予感。

3> F = fun (X) -> if X > 0 -> F(X-1); true -> X end end.
** 1: variable 'F' is unbound **

ともあれ、Erlang と言えば平行プログラミングだよなーってことで、とりあえずずっと前のお遊びコードを移植してみた。 なかなかライブラリのリファレンスが見つけられなくて (さっきようやく見つけた) どうでも良いものまで自前で書いてたりするけど、まあそこら辺は関数定義の練習になったから良し。 ほんとは結果を receive するプロセスを別に作ってやった方が平行っぷりが出て楽しいと思うんだけど、そのプロセスをキレイに止める方法がわかんないんでやめた。 なんかパッと見 Haskell のコードみたいに見えるけど、引数の数が同じ (でパターンが違う) 関数は ; で繋げてやらないといけないので、Haskell みたいな微妙な間違いはしなさそう。

% cat prime.erl
-module(prime).
-export([isPrime/1,
         proc/2,
         reverse/1,
         range/1, 
         range/2, 
         range/3, 
         deleteItem/2,
         primeList/1
         ]).

isPrime(Num,C) ->
   if C * C > Num -> Num;
      true ->
         if Num rem C == 0 -> false;
            true -> isPrime(Num,C+1)
         end
   end.
isPrime(0) -> false;
isPrime(1) -> false;
isPrime(Num) -> isPrime(Num,2).

proc(Pid, Num) ->
   Result = isPrime(Num),
   Pid ! {self(), Result}.

reverse([], Result) -> Result;
reverse([X|XS], Result) -> reverse(XS, [X|Result]).
reverse(List) -> reverse(List, []).

range(Start,Stop,Step,Result) ->
   if Start < Stop -> range(Start + Step, Stop, Step, [Start|Result]);
      true -> Result
   end.
range(Start, Stop, Step) -> range(Start, Stop, Step, []).
range(Start, Stop) -> range(Start, Stop, 1).
range(Stop) -> range(0, Stop, 1).

deleteItem(_, [], Result) -> Result;
deleteItem(Item, [X|XS], Result) ->
   NewResult = if X == Item -> Result;
                  true -> [X|Result]
               end,
   deleteItem(Item, XS, NewResult).
deleteItem(_, []) -> [];
deleteItem(Item, List) when is_list(List) -> deleteItem(Item, List, []).

primeList([], [], Result) -> Result;
primeList([], PIDs, Result) ->
   receive
      {Pid, false} ->
         primeList([], deleteItem(Pid, PIDs), Result);
      {Pid, Num} ->
         primeList([], deleteItem(Pid, PIDs), [Num|Result])
   end;
primeList([X|XS], PIDs, Result) ->
   Pid = spawn(prime, proc, [self(), X]),
   primeList(XS, [Pid|PIDs], Result).
primeList(Max) ->
   List = range(Max),
   case List of
   [] -> [];
   [_|_] -> primeList(List, [], [])
   end.
4> c(prime).
{ok,prime}
5> prime:primeList(100).
[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]

ソートしてるわけでもないのにキレイに順番に並んでて、ちっとも平行動作してる意味無いのバレバレ(笑

% [Erlang] 分散処理の片鱗を見てみたりするテスト

複数のノード (VM) で動いたりしてこそ Erlang ですよ (ほんとか?)。 ってことで簡単なテストとか。 まあ、簡単と言いつつ、こんだけやるのに一時間くらいかかったのは内緒だってば。

まずソースを用意。 メッセージを待って、飛んできたメッセージを表示する関数 wait() が定義してある。 間違った形式でメッセージが来た場合は error と表示してプロセス終了。

% cat hoge.erl
-module(hoge).
-export([wait/0]).

wait() ->
   receive
      { Node, Msg } ->
         io:fwrite("~s : ~s~n", [Node, Msg]),
         wait();
      _ -> io:fwrite('error~n')
   end.

で、これをまず一つ目の VM で読み込む。

% erl -sname hoge
(hoge@monolith-air)1> c(hoge).
{ok,hoge}

この場合、'hoge@monolith-air' というのがこの VM のノード名。 そして、別の端末でもう一つ VM を立ち上げる。

% erl -sname fuga
(fuga@monolith-air)1> Node = 'hoge@monolith-air'.
'hoge@monolith-air'
(fuga@monolith-air)2> net_adm:ping(Node).
pong

net_adm:ping 関数は Node が生きてる場合 pong を返す (死んでるときは pang)。 こちらでは hoge モジュールは読み込まないので、

(fuga@monolith-air)3> hoge:wait().

=ERROR REPORT==== 5-Jan-2007::22:23:19 ===
Error in process <0.35.0> on node 'fuga@monolith-air' with exit value:
   {undef,[{hoge,wait,[]},{erl_eval,do_apply,5},{shell,exprs,6},{shell,eval_loop,3}]}

** exited: {undef,[{hoge,wait,[]},
                   {erl_eval,do_apply,5},
                   {shell,exprs,6},
                   {shell,eval_loop,3}]} **

hoge:wait() を呼ぼうとしても、このようにエラーになる。 では、ノード hoge 上に fuga からプロセスを作成しよう。

(fuga@monolith-air)5> Pid = spawn(Node, hoge, wait, []).       
<4833.45.0>

これで、hoge 上でプロセスが動いてる状態になったはず。 確かめ方がまだよくわからないので、とりあえず "はず" ってことで。 次にこのプロセスにメッセージを送る。

(fuga@monolith-air)7> Pid ! {node(), 'Hello, World!'}.
{'fuga@monolith-air','Hello, World!'}
fuga@monolith-air : Hello, World!

2 行目は入力した式の返り値で、3 行目がプロセスが表示した結果。 多分、Term の指定とかをすれば向こう側の端末に表示したりもできるんじゃないかと思うけど、まだちょっとそこまで手が回らない。 適当に間違った形式のメッセージを送ってやれば、プロセスは終了する。

(fuga@monolith-air)9> Pid ! 'bye'.            
bye
error                  

基本的には IP ベースの仕組みらしいので、別のマシン同士でもネットワークの設定さえちゃんとしてやれば同じようにできるはず。 すんごく簡単。 やばいなー、おもしろいなー、Erlang 。 なんかこう…いろいろやってみたくなる誘惑がふつふつと……

% [Erlang] や、なんかスゴイっすよ、これ

さっきの素数を計算するアレ、OCaml で書いたのだと…

% time ./prime 10000
Fatal error: exception Sys_error("Thread.create: Resource temporarily unavailable")
./prime 10000  1.71s user 4.87s system 85% cpu 7.704 total

こんな感じなわけさ。 んで、さっきの prime.erl に、

main([Arg|_]) ->
   Max = list_to_integer(atom_to_list(Arg)),
   List = primeList(Max),
   io:fwrite('~s~n', [integer_to_list(lists:max(List))]).

こんな感じの定義を足して (export リストに main/1 ってのも追加して)、

% erl -compile prime
% time erl -noshell -s prime main 10000 -s init stop
9973
erl -noshell -s prime main 10000 -s init stop  5.94s user 0.34s system 79% cpu 7.861 total

余裕。さらに、

% time erl -noshell -s prime main 100000 -s init stop
99991
erl -noshell -s prime main 100000 -s init stop  612.76s user 20.79s system 90% cpu 11:41.34 total

かなり時間かかったけど、ちゃんと終わる。偉い。 だってこの実装だと、100,000 個のプロセス (他の言語で言うところのスレッド) をががーっと作ってるわけよね。 なんかもう無茶苦茶だな、この言語。良い意味で(笑

ちなみにアクティビティモニタで眺めてたところでは、メモリ使用量のピークは 13MB くらいだった。 んで、そっからじわじわと (たぶんプロセスがだんだん終了していって) 下がってくるのがおもしろかった。 なんだろね、GC が逐一回収してるんだろか。 GC の方式が何なのかわかんないけど、erl -man erlang で組み込み関数を見てみると、プロセスごとに GC を動かす仕組みがあるみたいだから、プロセスが終了するときにその都度動いてるのかも。


2007-01-06 [長年日記]

% [Erlang] 素数計算平行処理バージョン

昨日のを改良して、リストを複数に分割して一緒に走らせるようにしてみた。 最終的にリストに詰めて返すんだと見た目的につまんないんで、計算した素数をその都度表示していくタイプにした。 なんだかんだで、作るのに三時間くらいかかっちゃったんだけど、一旦感触が掴めてしまえばわりと簡単なのかもしれない。 この調子で各プロセスを別々のノードに振り分けるようにすれば、分散処理バージョンもできあがりじゃないかな。

% cat prime2.erl
-module(prime2).
-export([isPrime/1,
         calc/2,
         printer/0,
         printerMonitor/2,
         primeNumbers/2 ]).

isPrime(Num,C) ->
   if C * C > Num -> Num;
      true ->
         if Num rem C == 0 -> false;
            true -> isPrime(Num,C+1)
         end
   end.
isPrime(0) -> false;
isPrime(1) -> false;
isPrime(Num) -> isPrime(Num,2).

calc(Pid, Num) ->
   Pid ! isPrime(Num).

printer() ->
   receive
      quit ->
         io:fwrite('~n--> print process ~s was quit.~n',
                   [pid_to_list(self())]),
         exit('terminated.');
      Num when is_integer(Num) ->
         io:fwrite('~s, ', [integer_to_list(Num)]);
      _ -> nop
   end,
   printer().

printerMonitor(PrinterPid, PIDs) ->
   case PIDs of
      [] ->
         PrinterPid ! quit;
      [_|_] ->
         NewPIDs = receive
            quit -> exit('terminated.');
            {'DOWN', _, _, Pid, _} ->
               lists:delete(Pid, PIDs);
            _ -> PIDs
         end,
         printerMonitor(PrinterPid, NewPIDs)
   end.

primeNumbers(PPid, [], PIDs) ->
   printerMonitor(PPid, PIDs),
   ok;
primeNumbers(PPid, [X|XS], PIDs) ->
   {Pid, _} = erlang:spawn_monitor(prime2, calc, [PPid, X]),
   primeNumbers(PPid, XS, [Pid|PIDs]).
primeNumbers(Max, Distrib) ->
   List = [lists:seq(X,Max,Distrib) || X <- lists:seq(2,Distrib+1)],
   case List of
      [[_|_]|_] ->
         lists:map(fun(X) ->
            primeNumbers(spawn(prime2, printer, []), X, [])
         end, List),
         ok;
      _ -> ng
   end.
% erl
1> c(prime2).
{ok,prime2}
2> prime2:primeNumbers(100, 5).
2, 7, 3, 17, 13, 37, 19, 23, 47, 29, 43, 67, 5, 59, 53, 97, 
--> print process <0.99.0> was quit.
79, 73, 
--> print process <0.36.0> was quit.
11, 89, 83, 31, ok
--> print process <0.78.0> was quit.

--> print process <0.57.0> was quit.
41, 
61, 71,    
--> print process <0.120.0> was quit.
3> prime2:primeNumbers(1000, 10).
2, 
--> print process <0.141.0> was quit.
3, 13, 23, 43, 
--> print process <0.343.0> was quit.
53, 5, 73, 83, 
--> print process <0.444.0> was quit.
103, 113, 163, 
--> print process <0.545.0> was quit.
7, 173, 17, 193, 37, 223, 47, 233, 
--> print process <0.747.0> was quit.
67, 263, 19, 97, 283, 29, 107, 293, 59, 127, 313, 79, 137, 353, 
--> print process <0.949.0> was quit.
89, 157, 373, 11, 109, 167, 383, 31, 139, 197, 433, 41, ok149, 227, 443, 61, 
179, 257, 463, 71, 199, 277, 503, 101, 229, 307, 523, 131, 239, 317, 563, 151, 269, 337, 593,
181, 349, 347, 613, 191, 359, 367, 643, 211, 379, 397, 653, 241, 389, 457, 673, 251, 409, 467,
683, 271, 419, 487, 733, 281, 439, 547, 743, 311, 449, 557, 773, 331, 479, 577, 823, 401, 499,
587, 853, 421, 509, 607, 863, 431, 569, 617, 883, 461, 599, 647, 953, 491, 619, 677, 983, 521,
659, 727,    
--> print process <0.242.0> was quit.
541, 709, 757, 571, 719, 787, 601, 739, 797, 631, 769, 827, 641, 809, 857, 661, 829, 877, 691,
839, 887, 701, 859, 907, 751, 919, 937, 761, 929, 947, 811,    
--> print process <0.848.0> was quit.
967, 821, 977, 881, 997, 911,    
--> print process <0.646.0> was quit.
941, 971, 991,    
--> print process <0.1050.0> was quit.

結果のコピペには適当に改行を入れてある。 ok と出ているところで primeNumbers 関数は終了していたりする。

ソースの簡単な解説を少々。 erlang:spawn_monitor 関数を使うと monitor 登録された状態でプロセスが作成される。 monitor 登録されたプロセスは終了すると親プロセスに {'DOWN', Ref, process, Pid, Reason} という型のメッセージを送る。 ということで、printerMonitor 関数でそのメッセージを待ちつつ、最初に渡された Pid のリストを減らしていって、最後に Pid が無くなったら printer プロセスに quit とメッセージを送ってプロセスを終了させている。 quit だの nop だの ok だのってのは何か意味のあるものではなくて、単なるシンボル (atom) なんだけど、メッセージのやりとりにはこういうのを使うのがコツなんだとようやく気付いた。 とにかく、何かにつけて atom を使っときゃ何とかなるというか。

ちなみにシングルクォートで囲まれてるのも atom だ。 'DOWN' のように大文字で始まる atom が欲しかったり、'hoge@localhost' みたいに記号が含まれる atom が欲しいときに使う。 んで、ダブルクォートで囲んだのが文字列で、こいつは 8bit 文字のリストになっている。 だから、整数→文字列の変換関数が integer_to_list なんつー名前だったりする。

<0.646.0> とかいうのはプロセス ID 。 プロンプトにこのまま打ち込んでも理解してもらえないので、そういうときは list_to_pid("<0.646.0>") てな風にしてやらなきゃないみたい。 うっかり返り値を束縛せずに spawn しちゃったときなんかに使える。


2007-01-07 [長年日記]

% [Erlang] Erlang のちょっとした特徴

とりあえず Concurrent な部分は置いといて、普通にそこらの言語と比べてみた場合の話。 ぶっちゃけ大勢に影響しないような、どうでも良い話。

同じ名前でも引数の数が違うと別の関数

まあ、要するに関数のオーバーロードだから、Java とかから見ればめずらしいわけでもないかもしれないけど、関数型言語として見るとめずらしい感じ。 引数の数によってシグネチャが変わるから、それぞれに export するかどうか選べるのが肝。 とりあえず、関数内関数がうまく作れない (クロージャだと再帰できないし) ので、その代わりとして使ってるが、デフォルト値を持った関数を書く時なんかもすっきりする気がする。

制御構造の終わりは end

制御構造というかブロック的な要素全般だね。 if..end とか case..of..end とか try..catch..end とか begin..end とか。 こいつらがみんな値を返すことを考えると、その値を利用するときにカッコが無駄に増えないから助かる。

1> fun(X) -> X + 1 end(2).
3

こんな風に書けたり。 でも Ruby とかの流れでどうしても、

if ...
   ...
end

って書きたくなるんだけど、例えばこんな風に書いちゃうと

2> if false -> hoge;
2>    true -> fuga;
2> end.
** 3: syntax error before: 'end' **

怒られてしまうのが微妙。ちなみに正解はこう↓

2> if false -> hoge;
2>    true -> fuga  
2> end.
fuga

要するに最後に無駄なセミコロンを付けるとダメってことね。 これはちょっといただけないな。 リストなんかもそうだし。

3> [1,2,3,].
** 1: syntax error before: ']' **

(人間にとっての) 曖昧さを排除しようとする文法?

上記のセミコロン問題も含めて、そうなのかな? という気がする。 例えばタプルなんかも、他の言語なら丸カッコを使うけど、それって式のグルーピングや関数の呼び出しにも使うからパッと見区別が付きづらいこともあったりするわけで、その点 Erlang の場合はタプル (あるいはレコード) だってのが一目瞭然なのは結構良い感じ。 セミコロンは連続したパターンの区切りだし、ピリオドは関数定義の終わり、というように記号の類が複数の意味で使われているところがほとんど無いから、読んでてもそれが何を意味しているか間違えにくい気がする。 んで、そういう要素をパターンマッチを使って書いていくと、動的型付けのわりには何か妙な安心感があるんだよね。 複文は必ずカンマで繋がなきゃいけないから、何か変なこと書いちゃっても大抵読み込み時にエラーになるし。 なんつーか、書き手の間違いがあらかじめエラーになりやすい文法っていうか。

なんだかんだ言ってもパターンマッチがサイコー

やっぱパターンマッチが有ると無いとじゃ、流れの把握しやすさが段違いだと思う。 その事実は動的型付け言語でも変わることは無い。 Ruby とか Python もとっととパターンマッチを採用するべきだと思うよ。

% [Erlang] 対話的にプロセス通信のテスト

VM を二つ立ち上げてやっても良いけど、一つの VM でできる。

% erl
Erlang (BEAM) emulator version 5.5.2 [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.5.2  (abort with ^G)
1> self().
<0.29.0>
2> Wait = fun() ->
2>   receive      
2>     bye -> nop;
2>     Msg -> io:fwrite('~p~n', [Msg]), ok
2>   end
2> end.
#Fun<erl_eval.20.69967518>
3> Wait().

この時点でプロンプトは receive 待ちでロックするので、Ctrl+G を押す。

User switch command
 -->

すると、こういう↑プロンプトが出るんで、(? でヘルプ)

 --> s
 --> j 
   1  {shell,start,[init]}
   2* {shell,start,[]}
 --> c 2

s で新しいシェルを起動して (j はジョブリスト)、c # で新しいシェルに接続して、

Eshell V5.5.2  (abort with ^G)
1> list_to_pid("<0.29.0>") ! hello.
hello
2> 

最初のシェルの Pid に向けてメッセージを送ってやれば、

User switch command
 --> c 1

4> 

ちゃんと receive が終了してプロンプトが戻ってる (プロンプトを表示するには c 1 の後、一回 Enter を叩くこと)。 とまあ、こんな風にシェルを複数立ち上げて行き来することで、receive のようなブロックする操作でも対話的に試してみることができる。

ちなみに receive 式には after 節ってのがあって、タイムアウトする時間を指定できる。 こんな感じ↓

5> receive                         
5>   Msg -> io:fwrite(Msg)         
5> after                           
5>   10000 -> io:fwrite('timeout~n')
5> end.
timeout
ok

この場合、10 秒 (10,000 msec) でタイムアウトしている。

他には、プロセス ID を名前で引けるように登録することが可能。

1> register(prompt1, self()).
true
2> receive
2>   _ -> nop
2> end.

User switch command
 --> s
 --> j
   1  {shell,start,[init]}
   2* {shell,start,[]}
 --> c 2 
Eshell V5.5.2  (abort with ^G)
1> whereis(prompt1).
<0.29.0>
2> prompt1 ! bye.
bye
3> 
User switch command
 --> c 1

3> 

これはわりと便利な気がする。

% [Erlang] 別ノードのプロセスにメッセージ送信

さて、上記の register を利用すると別ノードのプロセスにメッセージを送れるようになる。 まず一つ目のノード。

% erl -sname fuga@localhost
(fuga@localhost)1> register(fuga, self()).
true
(fuga@localhost)2> receive
(fuga@localhost)2>   Msg -> io:fwrite('~p~n', [Msg])
(fuga@localhost)2> end.

ここまでで、シェルの Pid に fuga という名前を付けて、受信待ちでロックしてる状態。 んで、二つ目のノード。

% erl -sname hoge@localhost
(hoge@localhost)1> register(hoge, self()).
true
(hoge@localhost)2> { fuga, 'fuga@localhost' } ! hello.
hello

とりあえずこっちの register は無駄だけど一応ね。 そして Pid の登録名とノード名のタプルに対して ! を使っている。 すると fuga@localhost の方で、

hello
ok
(fuga@localhost)3> 

送ったメッセージが表示されて receive を抜ける。 io:fwrite なんかに別ノードの IoDevice を指定してやれば直接別ノードの端末に何か表示できるかと思ってたが、standard_io は IoDevice じゃなく特別に処理される単なる atom でしかないので、別ノードの standard_io を取得するのは不可能なのであった。 てことで、別ノードの端末に何かを表示したいときには、表示したいノード側で起動されたプロセスにメッセージを送ってよろしくさせるしか無いみたい。 まあ、当たり前と言えば当たり前なのかも。

% [Erlang] 別ノードの関数を呼ぶ

spawn するんじゃなく、単に関数を呼んで値を返してもらう方法。

% erl -sname fuga@localhost
(fuga@localhost)1> c(prime).
{ok,prime}

この前の prime モジュールを読み込んだ状態。 で、別のノードから、

% erl -sname hoge@localhost
(hoge@localhost)1> rpc:call('fuga@localhost', prime, primeList, [10]).
[7,5,3,2]

こんな感じ。 rpc:call には 5 引数のものもあって、5 番目の引数にタイムアウトの時間を指定できたりする。 rpc モジュールには他にも call の仲間がたくさんあっておもしろい。 parallel_eval なんて、すごくおもしろそう。 説明を読むかぎりだと、関数呼び出しのリストを渡すとネットワーク上の複数のノードに適当に処理を振り分けて、結果をリストに集めて戻してくれるみたい。 その特性を活かせるような環境を構築する方が難しそうだけど(苦笑

% [Erlang][Python] Py-Interface

Erlang のノードを Python で実装したもの。 かなり試行錯誤しながらも、ようやく使う目処が立ってきたんでメモ。 まずダウンロードしてきて解凍したら、erl*.py なファイルを Python のライブラリパスが通ったとこに放り込む。(なんやら configure とかいろいろ付いてるけど、わしの環境だと全く意味が無かった)

そんでまずは erl を起動しておく。 ほんとは後からでも良いんだけど、epmd というデーモンがあらかじめ起きてないと Python 側で問題あるみたいなんで念の為。

% erl -sname hoge@localhost -setcookie ''

-setcookie はどうしても必要みたいなんで付けとく。 この場合、cookie は空文字列。 そしたら次に Python を起動。(IPython 使てるけどな)

In [1]: import erl_node
In [2]: import erl_eventhandler
In [3]: node = erl_node.ErlNode('fuga@localhost')
In [4]: node.Publish()
In [5]: def callback(msg):
   ...:     print msg
   ...: 
In [6]: mbox = node.CreateMBox(callback)
In [7]: mbox.RegisterName('fuga')
In [8]: evhand = erl_eventhandler.GetEventHandler()
In [9]: evhand.Loop()

こんだけ一気に行きましょ。 3 行目でノードを作成して、4 行目で epmd にノードを登録している (はず)。 mbox というのはメールボックスで、Erlang でメッセージをやりとりするためのキューがそう呼ばれてる。 で、メッセージを受け取ったときに起動するコールバック関数を定義して登録している (まあ、送られてきたものをそのまま表示するだけだけど)。 メールボックスとプロセスはセットなんで、これで Python 側のノードにプロセスが一個できたことになっているはず。

んで、RegisterName がさっき紹介した register 関数と同じ役割で、ここでは fuga という名前でプロセスを登録している。 あとはイベントハンドラを取得して、イベント待ちループに突入。

次に Erlang 側に戻ってメッセージを送ってみる。

(hoge@localhost)1> Fuga = { fuga, 'fuga@localhost' }.
{fuga,fuga@localhost}
(hoge@localhost)2> Fuga ! hello.
hello
(hoge@localhost)3> Fuga ! 10.   
10
(hoge@localhost)4> Fuga ! "hello, world".
"hello, world"
(hoge@localhost)5> Fuga ! [1,2,3,4,5].   
[1,2,3,4,5]
(hoge@localhost)6> Fuga ! ["foo","bar"].
["foo","bar"]

こんな感じでいくと、Python 側では…

hello
10
hello, world

['foo', 'bar']

てな感じで表示されていく。 [1,2,3,4,5] がうまく表示されてないけど、これはたぶん Erlang 側の値と Python 側の値の変換に関する問題だと思う。 きっと erl_term.py 辺りをよく読めば対処できそうなんで後で読む。

さて、こうなると、Erlang 側ではできないマルチバイト文字列の処理なんかを Python にやらせるような仕組みとか作ってみたくなってくるよなー。 コールバック関数をうまく作ればなんとかなりそうではあるけど…

% [Erlang] 別ホスト上のノードとの接続

基本的にクッキーが一致しないと接続ができないらしい。 クッキーは指定しないとランダムに生成される (と書いてあるみたいだけど、何回か起動してみても同じだったりして何だかよくわかんない)。 ただし、同ホスト同ユーザのノード同士は、クッキーに関係無く接続可能ということみたい。

ということで、別ホストのノードと接続する際の流れをメモ。

% erl -sname fuga@monolith
(fuga@monolith)1> erlang:get_cookie().
'FFSHRKBPBTHZFUHDDGBE'

まずどちらかのノードで自分のクッキーを取得。 これを相手側のノードに登録してやる。 うちの場合は ssh で触ってるマシンなのでコピペ。 まあ、無理に自動生成されたのを使わなくても、パスワード的に決めたフレーズをお互いに使っても良いんだろうけど。

% erl -sname hoge@sharpy
(hoge@sharpy)1> Fuga = 'fuga@monolith'.
fuga@monolith
(hoge@sharpy)2> erlang:set_cookie(Fuga, 'FFSHRKBPBTHZFUHDDGBE').
true
(hoge@sharpy)3> net_adm:ping(Fuga).
pong

接続完了。 VM 起動前にクッキーがわかっているなら、コマンドラインオプションで -setcookie 'FFSHRKBPBTHZFUHDDGBE' みたいに指定しても良い。 その場合は起動後に erlang:set_cookie(node(), 'FFSHRKBPBTHZFUHDDGBE') としたのと同じことになると思われる。 set_cookie はリモートノードを指定すると、そのノードとの接続にのみ指定したクッキーを使うことになり、ローカルノードを指定すると全てのノードとの接続に指定したクッキーを使うことになるらしい。

(hoge@sharpy)5> receive
(hoge@sharpy)5>   Msg -> {main, Fuga} ! Msg
(hoge@sharpy)5> end.

送られたメッセージをそのまま Fuga に送り返すべく受信待ち。

(fuga@monolith)2> register(main,self()).
true
(fuga@monolith)3> Hoge = 'hoge@sharpy'.
hoge@sharpy
(fuga@monolith)4> {main,Hoge} ! hello.
hello

hello と送りつける。

hello
(hoge@sharpy)6> 

受信し終わったのでプロンプトが戻ってる。 hoge が送り返したメッセージが溜まってるはずなので受信してみる。

(fuga@monolith)5> receive
(fuga@monolith)5>   Msg -> io:fwrite('~p~n', [Msg])
(fuga@monolith)5> end.
hello
ok

めでたし。

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

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


2007-01-08 [長年日記]

% [PC][雑談] パターンマッチの話

えーと、何やら振られてしまいましたが…

パターンマッチを備えている言語を本腰入れて やったことが多分ない(あれ、Haskellは?)ので、一度やってみるべきか。 なにかこれがいいという言語ってありますか?

…ですか。 うーん、パターンマッチに狙いを絞るんであれば、パターンマッチの仕組みを持った言語の中から自分に合いそうなのを選ぶのが良いんじゃないでしょうかねえ。 わしが知ってる言語で言うと……

  • 静的型付け
    • OCaml
    • SML
    • Haskell
    • Clean
    • Scala
    • Nemerle
  • 動的型付け
    • Scheme
    • Erlang

これくらいかな。 世の中にはもっとあると思うけど、わしが触ったことがあるのはこんなもんかと。 C 系の言語に慣れてる人なら、この中では Nemerle が比較的取っつきやすいかも。 あれは関数型っぽく拡張した C# てな感じの触れ込みなんで。 Ruby や Python にパターンマッチを導入するときに、使えるパターン構成を参考にするなら Scheme が一番かも。

パターンマッチがやってることを Ruby で再現すると以下のような感じだと思いますが、かなり乱暴な例なので、ちゃんと知るにはやっぱり自分でいろいろ書いてみるしか無いと思いますです、はい。 (以下は木村さんがどの程度パターンマッチについて知っているのかわからないので、単にパターンマッチに馴染みの無い人向けのつもりで書いてます)

とりあえずこの前の Erlang のコードを引っぱってきて、ちょっと改変してお題にしましょう。

% erl
1> List = [[1,2,3],[4,5,6],[7,8,9]].
[[1,2,3],[4,5,6],[7,8,9]]
2> case List of
2>   [[X|XS]|_] -> 
2>     io:fwrite('~p~n~p~n', [X, XS]);
2>   _ ->
2>     io:fwrite('no match~n')
2> end.
1
[2,3]
ok

この例では、List という変数の中身を見て、[ [X|XS]|_] というパターンなら X 及び XS の値を、それ以外のパターンなら 'no match' を表示するという処理をしてます。 _ (アンダーバー一個) はワイルドカードのようなもので、どんなパターンにもマッチします。 Erlang のリストは他の関数型言語と同じように連結リスト的な構造なので、[最初の要素|残りの要素(リスト)] という形になっています。 つまり [ [X|XS]|_] というパターンは、(少なくとも最初の) 要素が (空ではない) リストであるリストという意味です。 そして、パターンに変数を使っている場合は、その変数にパターンに応じた値が束縛されます。 この場合、List の最初の要素 (リスト) の最初の要素が X に、最初の要素の残りの要素が XS に束縛されています。

余談 : ここで OCaml や Haskell なんかだとリストの要素は一つの型に制限されるので、最初の要素がリストであれば残りの要素もリストなんですが、Erlang のリストは何でも入るので残りの要素もリストとは限りません。

余談2 : Erlang の変数はちょっと他と扱いが違うので、場合によってはパターンの意味が変わったりしますが、とりあえず一般的ではないので無視します。

ではこれを Ruby で再現してみましょう。

% irb
irb(main):001:0> list = [[1,2,3],[4,5,6],[7,8,9]]
=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
irb(main):002:0> if list.is_a?(Array) && list[0].is_a?(Array) &&
irb(main):003:1*    list[0].length > 0 then
irb(main):004:1*   x = list[0][0]
irb(main):005:1>   xs = list[0][1..-1]
irb(main):006:1>   p x
irb(main):007:1>   p xs
irb(main):008:1> else
irb(main):009:1*   p 'no match'
irb(main):010:1> end
1
[2, 3]
=> nil

パターン一個分だけで、うんざりするような条件式ですね。 それに x や xs への値の代入には、間違って書く可能性を多分に含みます。 これに比べて Erlang の例は非常に簡潔です。

このようにデータ構造のパターンを記述して条件分岐をする仕組みを (主に関数型言語における) パターンマッチと言います。 記述スタイルにもよりますが、パターンマッチ機構を持つ言語なら、それを利用して書いた方がデータをどのように扱っているかコードから読み取りやすくなるので、活用すべきだと思います。

ちなみに今回のネタは Wiki 記法のワナに嵌ってしまってぐったりしましたよ(苦笑

% [雑談] 料理の必勝法

え、

「しょうゆかけて火を通せば、なんかできる」

[Jより引用]

それって必勝法ってか『男の料理』の基本なんじゃ? まあ、必勝法かどうかはともかく内容には激しく同意。しょうゆは偉大です。

% [Erlang] Erlang のパターンマッチは微妙に偉大

以前、こんな話とか関連してこんな話とかあったわけだが。

Erlang の変数は再利用ができない。 具体的には同一スコープで別の値を束縛しなおすことができない。 他の言語に慣れてると結構ビビる仕様で、ちょっと不便に思わないこともないんだが、そのおかげでパターンマッチに恩恵がもたらされているのであった。 というか、そのために再束縛禁止にしてるんじゃないかと思うが。

1> { Nop, Push, Pop, Acc, Add, Halt } = { 0, 1, 2, 3, 4, 5 }.
{0,1,2,3,4,5}
2> F =    
2> fun(Nop) -> nop;
2>    (Push) -> push;
2>    (Pop) -> pop;
2>    (Acc) -> acc;
2>    (Add) -> add;
2>    (Halt) -> halt 
2> end.
#Fun<erl_eval.6.56006484>
3> F(Nop).
nop
4> F(0).
nop
5> F(5).
nop
6> F(Pop).
nop

ほーらこれをごら…間違った。 もう一回。

1> OpCode = { 0, 1, 2, 3, 4, 5 }.
{0,1,2,3,4,5}
2> { Nop, Push, Pop, Acc, Add, Halt } = OpCode.
{0,1,2,3,4,5}
3> F = fun(X) ->
3>   { Nop, Push, Pop, Acc, Add, Halt } = OpCode,
3>   case X of
3>     Nop -> nop;
3>     Push -> push;
3>     Pop -> pop;
3>     Acc -> acc;
3>     Add -> add;
3>     Halt -> halt
3>   end
3> end.
#Fun<erl_eval.6.56006484>
4> F(Nop).
nop
5> F(Halt).
halt
6> F(3).
acc
7> F(2).
pop

ほーらこれをごらんなさい。 素敵にかっちょいいじゃあないですか。

% [Erlang] マクロ

さっきの話のついでだから調べてみた。 基本的には C のマクロみたいなもんかな。 コンパイル時 (読み込み時) に展開されるので、対話環境では試せない。 ので、ファイルに書いてみる。

% cat macro_test.erl
-module(macro_test).
-export([f/1, nop/0, push/0, pop/0, acc/0, add/0, halt/0]).

-define(NOP,  0).
-define(PUSH, 1).
-define(POP,  2).
-define(ACC,  3).
-define(ADD,  4).
-define(HALT, 5).

-define(ERROR(X), io:fwrite('~p, ~p, ~p~n', [?MODULE, ?LINE, X])).

f(?NOP)  -> nop;
f(?PUSH) -> push;
f(?POP)  -> pop;
f(?ACC)  -> acc;
f(?ADD)  -> add;
f(?HALT) -> halt;
f(_)     -> ?ERROR(f).

nop() -> ?NOP.
push() -> ?PUSH.
pop() -> ?POP.
acc() -> ?ACC.
add() -> ?ADD.
halt() -> ?HALT.
% erl
1> c(macro_test).
{ok,macro_test}
2> macro_test:f(macro_test:nop()).
nop
3> macro_test:f(0).               
nop
4> macro_test:f(3).
acc
5> macro_test:f(macro_test:halt()).
halt
6> macro_test:f(6).                
macro_test, 19, f
ok

モジュールごとに展開されるので、C のマクロのような極悪さは無いと思う。 普通に便利に使えるんじゃなかろうか。 マクロ関係のディレクティブには他に -undef() とか -ifdef() とかも用意されている。 あと、?MODULE とか ?LINE は組み込みで用意されているマクロで、要するにまあ __FILE__ とか __LINE__ とかそういう類のもの。

% [雑談] や、だって論理型言語なんて触れる機会が無いんですもの(苦笑

全然薄くないあろはさんとこのずっと下の方で言及されてますが。 って言うか、リンクしにくいから話題が変わるところは別エントリにしてもらえないかと思ったりします。 (fc2 ではそれができないってならしかたないけど)

わりとわしは言語マニアではあるけど、大学とかで専門的な勉強をしたわけではないし、どちらかと言うと『実用性 (主に実生活的な方向で) を重視』する傾向が多少なりともあるんで、論理型は (偏見かもしれないけど) 実用的じゃない (というか何ができるのかわかんない) 感じがして食指が動かないというか何というか。

ちなみに Scheme のパターンマッチは仕様に含まれてるわけじゃなくて、広く使われているマクロが存在するということです。(参考→Gauche ユーザリファレンス: 11.42 util.match - パターンマッチング

まあ、論理型もいずれはいじってみよう。 食わず嫌いはよくないし。

% [Erlang] Erlang と Prolog の関係

そう言えばどっかで見かけた気がするぞ…と思って探してみたら lethevert さんのとこだった。

Erlangは、リアルタイムでパラレルなPrologとして産声を上げた後、関数型から多くの機能を取り入れて、今では関数型に分類されるようになったのですが、もともとは論理型だったそうです。Erlangが静的型を持たないのは、Prologの伝統を受け継いでいるからだそうです。

なるほど。 よく見たらあろはさんもコメントしてるし。

% [Prolog][Erlang] SWI-Prolog というのを入れてみた

軽くググったいくつかのページでおすすめになってたんで。 あと MacPorts に入ってたのも理由だけど。

んで、いくつかのページ (Prolog入門お気楽 Prolog プログラミング入門) を斜め読みつついじってみたり。 あー、たしかに字面は Erlang そっくりだ (厳密には "Erlang が Prolog にそっくり" か)。 でもやっぱ考え方とかは全然違ってて、これはなんつーか『手続き型→関数型』の移行よりもさらに強力に思考の方向転換が必要な気がする。 ぶっちゃけさっぱりわかりません(苦笑

まあ、関数型言語だって最初はさっぱりわかんねーって言ってたんだし、いじってるうちに慣れるのかもしれないが…… でも、手続き型の関数 (サブルーチン) も関数型の関数も、いくつかの値を受け取って何かをした結果の値を返すっていう流れは同じだったし、そういう意味ではずっとずっと取っつきやすかったよ。

なんとなく、この前のクリスマスプレゼント問題で組み合わせを見つけるのが楽かと思って、ちょろっとやってみたんだけど挫折した。 つーか、まだ全然『プログラミングしてる』感覚が無くて、見たことも無い機械の "ボタンに似たもの" を押してみたり、"ダイヤルに似たもの" を回してみたりしたけど、実は全然用途が違うものだった…みたいな感覚(謎

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

% 青木 [Ruby版は多重代入を使うともっと簡単に書けますよ。 list = [[1, 2, 3], [4, 5, 6], ..]

% jijixi [お、なるほど。 > OOPL向きではないんじゃないか というのは確かにそうかもしれません。 再帰でループを回すような..]


2007-01-09 [長年日記]

% [Erlang] HiPE : High Performance Erlang

以前こういう話を読んだことがあって、そういうものがあるのは知っていたんだけど、リンクされてたスライドでは、対象が Sparc か x86 ってなってたんで PowerPC なわしとしては特に気にしてなかったのである。 でも、なんだかよく見ると……

% erl
Erlang (BEAM) emulator version 5.5.2 [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.5.2  (abort with ^G)
1> 

あれ? [hipe] って書いてますよ? じゃあ、試してみる。

% erlc +native prime.erl

普通にコンパイルした時に比べて、.beam ファイルのサイズがデカくなっている (1312 -> 4652) から、たしかにそれらしい操作が行なわれてはいる模様。 でも実行してみた感じでは目に見えるような違いは無いようなんだが…… とりあえずネイティブコンパイルしても、VM を通して実行するのは一緒だし、OCaml みたいな劇的な違いは無いのかも知れない。

ちなみに、

1> c(prime, {hipe, [o3]}).
{ok,prime}

とかやって .beam を作るとサイズが 1212 とかになってたが、やっぱり実行速度には違いが感じられなかった。 これって、どんなシチュエーションでパフォーマンスの差が出るんだろうなあ。

この辺を見ると

A "just-in-time" native code compiler for Erlang

とか書いてあるんで、ネイティブコンパイルしてると言うよりは、JIT 用に最適化したバイトコードを生成してるって感じなのかな?

% [Erlang] 続 HiPE

うーん、このページとか見ると、結構劇的な違いがあるように見えるんだが…… さっきテストに使ったコードが良くなかったかなあ。 プロセス作りまくって、ごにょごにょ…ってコードだし、プロセス作成のコストの方が計算部分より大きく出ちゃう例だったのかも知れない。

てことで、フィボナッチ数を計算する素朴なコードを書いてそれを使って試してみた。

% cat test.erl
-module(test).
-export([fib/1]).

fib(0) -> 1;
fib(1) -> 1;
fib(N) -> fib(N - 1) + fib(N - 2).
% erl -compile test
% erl test.beam
1> timer:tc(test, fib, [40]).
{19927950,165580141}
% erlc +native test.erl
% erl test.beam
1> timer:tc(test, fib, [40]).
{8442103,165580141}

返り値のタプルの一つ目の要素が、かかった時間 (msec)。 良かった…壊れてなかった(苦笑

% [Erlang] HiPE 追試

あれ、よく見るとさっきテストしたときのコピペは変だな。 c(prime, {hipe, [o3]}) じゃネイティブコードになってないだろ。 もしかして間違ってテストしてたのか?

もっかいやってみよう。

% erl -compile prime
% erl prime.beam
1> timer:tc(prime, primeList, [1000]).
{96134,
...
2> timer:tc(prime, primeList, [10000]).
{4282007,
...
% erlc +native prime.erl
% erl prime.beam
1> timer:tc(prime, primeList, [1000]).
{102385,
...
2> timer:tc(prime, primeList, [10000]).
{4260866,
...

うーん、やっぱ o3 オプションを試すときに間違っただけで、その前のテストは間違ってなかったらしい。 何でもかんでもネイティブコンパイルすれば速くなるってわけじゃないってことだね。


2007-01-10 [長年日記]

% [Mac][PC] iPhone

これはヤバいね。欲しすぎる。 Newton 新生か?

操作デモ見てるだけで涎が出そう(苦笑

% [Occam] 今日の言語 Occam

Erlang の個人的ランクが急上昇しつつある今、OCaml と名前がややこしいと有名(?)な Occam にも手を出してみるしかないと理由も無く思ったのですよ。 そんなわけで、KRoC という Occam のコンパイラをインストールしてみた。 Mac OS X には対応してないみたいなんで、FreeBSD にだけど。 Ports には無いけど、アーカイブを解凍して

% ./build --install-dir=/opt

とかでオッケーなんで簡単。 あとは、(上の例で言うと) /opt/bin にインストールされる setup.sh を読み込むなり、参考に自分で設定するなりすれば終了。 ビルドに Bash を要求するのがウゼーって感じだけど、なぜか入れた記憶が無いのに Bash が入ってたんで特に面倒も無くインストールできた。 まあ、Linux な人にはどうでも良い話かも知れないが。

んで、適当に入門文書を探してみたけど、あんまり良いのは見つからず。 とりあえず、こことかこのページからダウンロードできるリファレンスマニュアルの PDF とかを見ながら四苦八苦。

非常に大雑把に言って、IF とか WHILE とかの制御構造や SEQ (シーケンシャル) とか PAR (パラレル) というブロックが、それぞれプロセスになる…って感じみたい。たぶん。 Erlang と違って、こいつはバリバリ手続き型。 プロセスの生成をあまり意識しない代わりに、メッセージ通信用のチャンネルを意識しなきゃならない感じ。 あと静的型付け。 Erlang のメッセージ通信は Atom とパターンマッチで通信プロトコルを表現するけど、Occam ではチャンネルがそれぞれあらかじめ決めた通信プロトコルの型を持っている。 プログラマが指定するのは通信するプロセスじゃなくて、使うチャンネルになる。

その他、特徴としては……

  • Haskell や Python みたいにインデントで構造を表現するタイプ。
  • なぜか識別子にアンダースコアが使えなくてビビる。
    • 代わりに . を使うのがお作法みたい。
  • 予約語や組み込み関数が全部大文字なのがキモい。
    • 識別子に使う文字に大文字小文字の制限は無いみたいだけど、上記のような理由から自分で使うのは小文字にするのが普通らしい。
  • FUNCTION で値を返す方法がヘンテコ。
    • VALOF というブロックを作って RESULT で返すんだけど、RESULT が一回しか使えないから返り値用の変数を作らないとツラい。
  • 論理演算子とか比較演算子の優先度が変
    • 変というかそもそも優先度が無いのかも知れないが、なんか無駄にカッコが必要。
  • エントリポイントがよくわかんない。
    • とりあえずファイル一個の場合は、最後に定義されてるプロシージャ (PROC) が最初に実行されるみたいだけど、なんだかよくわかんない。
  • 名前空間がフラットくさい。
    • なんつーか、C レベル?
    • リファレンスを斜め読みした限りでは、モジュールシステムは無いように見えた。

まあ、なんつーか、あんまりそそられない。 つーか、もう今さら while でループとか書きたくねーっつかさ(苦笑

% [Occam] まあ、せっかくだし素数でも

そそられないけど、せっかくインストールしたし何か書いてみようかと。 0 から順番に素数かどうか判定して素数なら表示…って感じのコード。 とりあえず、どこをどう並列化したら良いのかわからんので、まずは動くようにってレベル。 というか、ここですでに力尽きた(苦笑

% cat prime.occ
#USE "course.lib"

BOOL FUNCTION is.prime (VAL INT n)
  BOOL r:
  VALOF
    IF
      (n = 0) OR (n = 1)
        r := FALSE
      TRUE
        INT c:
        BOOL continue:
        SEQ
          c := 2
          continue := TRUE
          r := TRUE
          WHILE ((c * c) <= n) AND continue
            IF
              (n REM c) = 0
                SEQ
                  r := FALSE
                  continue := FALSE
              TRUE
                c := c + 1
    RESULT r
:

PROC print.primes (VAL INT max, CHAN OF BYTE out)
  SEQ i = 0 FOR (max + 1)
    IF
      is.prime (i)
        SEQ
          out.int (i, 0, out)
          out.string (", ", 0, out)
      TRUE
        SKIP
:

PROC main (CHAN OF BYTE stdout)
  print.primes (1000, stdout)
:
% kroc prime.occ -lcourse
% ./prime
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307,
 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421,
 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547,
 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797,
 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929,
 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, %

すごく普通。 なんか最後に % が出てるのは、たぶん出力の最後に改行が無いせいでプロンプトが混っただけ。 まあ、どこも並列化してない (はず) から普通の結果になるのは当たり前。 でもあれだよ、きっとこうすれば並列化されんじゃね?

--- prime.occ.orig      Wed Jan 10 23:27:32 2007
+++ prime.occ   Wed Jan 10 23:27:42 2007
@@ -25,7 +25,7 @@
 :
 
 PROC print.primes (VAL INT max, CHAN OF BYTE out)
-  SEQ i = 0 FOR (max + 1)
+  PAR i = 0 FOR (max + 1)
     IF
       is.prime (i)
         SEQ

そんな感じするよね。 さあ、やってみよう。

% kroc prime.occ -lcourse
Warning-occ21-prime.occ(28)- not usage checking dynamic replicated PAR

ありゃ、なんか警告出たな。 とりあえず意味わかんないから実行。 (なんとなく予想としては、FOR ループの回数が静的に決まってないよ、という警告のような気がする)

% ./prime
29 71%

いやん。何これ。 バッファの呪いかな。 試しに flush を挟んでみたら…

Error-occ21-prime.occ(29)- parallel outputs on channel `out'

とか怒られた。 しかたないから、配列に格納して表示とか。

% cat prime2.occ
#USE "course.lib"

BOOL FUNCTION is.prime (VAL INT n)
  BOOL r:
  VALOF
    IF
      (n = 0) OR (n = 1)
        r := FALSE
      TRUE
        INT c:
        BOOL continue:
        SEQ
          c := 2
          continue := TRUE
          r := TRUE
          WHILE ((c * c) <= n) AND continue
            IF
              (n REM c) = 0
                SEQ
                  r := FALSE
                  continue := FALSE
              TRUE
                c := c + 1
    RESULT r
:

PROC print.primes (CHAN OF BYTE out)
  VAL INT max IS 1000:
  [max]INT ary:
  SEQ
    PAR i = 0 FOR max
      IF
        is.prime(i)
          ary[i] := i
        TRUE
          ary[i] := 0
    SEQ j = 0 FOR max
      IF
        ary[j] <> 0
          SEQ
            out.int (ary[j], 0, out)
            out.string (", ", 0, out)
        TRUE
          SKIP
    out.string("*n", 0, out)
:

なんか配列のサイズは動的に指定できないみたいなんで (どうしたら良いんだろ?) main 関数は捨てて max は print.primes の中で定数にしてしまった。 で、実行結果はこんな。

% kroc prime2.occ -lcourse
Warning-occ21-prime2.occ(39)- variable `ary[..]' is undefined here

なんか警告出てるけど無視。

% ./prime2
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307,
 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421,
 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547,
 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797,
 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929,
 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,

ほんとに並列処理されてるのかわかんないけど、端末出力が変になるのにこっちの結果はちゃんとしてるってことは、きっと並列処理されてるんでしょう。たぶん。おそらく。

% [Occam] 再帰……再帰って何だっけ?

なんか「もう良いや」と思いつつ、ついついいじってしまうのは、変な魅力があるからでしょうか?

でもさ、

% cat fib.occ
#USE "course.lib"

INT FUNCTION fib (VAL INT n)
  INT r:
  VALOF
    IF
      (n = 0) OR (n = 1)
        r := 1
      TRUE
        r := fib (n - 1) + fib (n - 2)
    RESULT r
:
% kroc fib.occ -lcourse
(略)
Error-occ21-fib.occ(10)- fib is not declared

おいぃ!! 世を儚んで死にたくなるぞ。 回避方法は……わかりません...orz

ということで、とりあえず Occam 探訪はしゅーりょー(疲


2007-01-11 [長年日記]

% [Python][Ruby][雑談] with 構文とブロック付きメソッド

ま。さんという人から (たぶん) with 構文のネタについてツッコミを受けたんだけど、

「汎用的」という観点からはループも要素選択も内包表記の代替もできるブロック付きメソッドの方が汎用的だと思いますが。

それが良いかとか分かりやすいかとかは別として。

まったくもってそのとおりですよね。 ……って、じゃあなんでわしは「汎用性はこっちが上」とか言ってんだ?

なんつーか、ぶっちゃけあんまり言葉の意味を吟味せずに、勢いだけで書いちゃってる部分もあるんで (特にこの日のようにエントリを量産してるときは)、後から読んだときに自分でも何が言いたかったのかわかんないことがあるんだけど、がんばって書いた当時のことを思い出してみよう。 っていうか、たかだか 10 日ほど前のことを "当時" なんて言ってしまうのは老化が進んでる証拠ではないのか。

ともあれ、当時の意図だけど。 とりあえず「汎用性」という言葉を使ったのは間違ってる気がする。 じゃあ、何て言えば良かったのかはよくわかんないんで、たぶんよくわかんなくてノリだけで「汎用性」って書いたんじゃないかって気がしなくもない(苦笑

次の日に書いたプロトコルの話にも関係すると思うんだけど、with 構文が使えるということは Context Management Protocol (以下、CMP) を実装していることと等価だというのが嬉しいところだと思う。

Ruby のブロック付きメソッドだと、それが単なるイテレータなのか、それとも後始末もきちんとやってくれてるものなのかわかんない。 もちろん組み込みのものは大丈夫だし、広く公開されてるものならきっと大丈夫だろうけど、自分でやっつけで書いたものなんかならそういう処理を忘れて (というか端折って) てもおかしくない。 で、後始末がされてないものを後始末がされてるつもりでうっかり使っちゃうと、いろいろ面倒なことになったりする可能性がある。 まあ、「ソース読め」というのはこの際置いておいて。

逆に Python の with 構文なら、そもそも CMP が実装されてないと with 構文は使えないんだから、後始末の処理を実装していない場合、何か面倒が起こる前に (with のところで) エラーで止まってくれる。 まあ、わざわざ空の __enter__ とか __exit__ を定義するおろかものがいればその限りではないが、そんなアホが周りにいるなら少し身の振り方を考えた方が良い。

とまあ、こんな感じのことを考えていて、それでどうして「汎用性」という言葉を使ったのかはやっぱりうまく説明できないけど、「前処理と後始末処理の統一的な扱い方として、安心して使える」くらいの意味があったんじゃないかと。 ……あんま汎用性と関係無いよな。やっぱノリだったんだよノリ(の

% [Erlang] 自分のノード上にあるモジュールを他のノードに転送

分散システムを作る上で、こういう機能は必須だろうと思ったんで、やり方を調べてみた。 code モジュールの get_object_code 関数のところに例が出ている。

% erl -sname hoge@localhost
(hoge@localhost)1> code:is_loaded(prime).
false

この時点で hoge ノードには prime モジュールは存在しない。

% erl -sname fuga@localhost
(fuga@localhost)1> c(prime).
{ok,prime}
(fuga@localhost)2> {Mod,Bin,FName} = code:get_object_code(prime).
{prime,<<70,79,82,49,0,0,4,156,66,69,65,77,65,116,111,109,0,0,0,179,0,0,0,24,5,112,114,...>>,
       "/Volumes/Sub/tmp/jijixi/erlang_test/prime_number/prime.beam"}
(fuga@localhost)3> rpc:call('hoge@localhost', code, load_binary, [Mod,FName,Bin]).
{module,prime}

fuga ノードで prime モジュールを読み込み、code:get_object_code でモジュールのバイナリを取得し、rpc:call を使って hoge 上で code:load_binary を動かす。

(hoge@localhost)2> code:is_loaded(prime).
{file,"/Volumes/Sub/tmp/jijixi/erlang_test/prime_number/prime.beam"}
(hoge@localhost)3> prime:isPrime(10).
false

…と、hoge で prime モジュールが読み込まれて、めでたく使用可能に。 ここでは同一ホスト上でテストしてるが、もちろん別ホスト上のノード同士でも大丈夫。 ちょっと不思議なのは、hipe を有効にしてコンパイルしたモジュールも、別アーキテクチャのノードに普通に送って普通に動かせるところ。 (monolith は MacOSX/ppc , sharpy は FreeBSD/i386)

(fuga@monolith)1> c(prime, [native, {hipe,[o3]}]).
{ok,prime}
(fuga@monolith)3> {Mod,Bin,FName} = code:get_object_code(prime).
{prime,<<70,79,82,49,0,0,17,188,66,69,65,77,65,116,111,109,0,0,0,179,0,0,0,24,5,112,114,...>>,
       "/Volumes/Sub/tmp/jijixi/erlang_test/prime_number/prime.beam"}
(fuga@monolith)4> rpc:call('hoge@sharpy', code, load_binary, [Mod,FName,Bin]).
{module,prime}
(hoge@sharpy)2> code:is_loaded(prime).
{file,"/Volumes/Sub/tmp/jijixi/erlang_test/prime_number/prime.beam"}
(hoge@sharpy)3> prime:isPrime(10).
false

試しにネイティブコンパイルした beam ファイルだけを別マシンに持ってって試してみると…

1> l(prime).
{module,prime}
2> prime:isPrime(100).
false
3> prime:primeList(100).
[97,89,83,79,73,71,67,61,59,53,47,43,41,37,31,29,23,19,17,13,11,7,5,3,2]

あんれま、普通に使えるよ。 なんだろ、これ。 あー、まあ単純に考えれば、ネイティブコンパイルしたときにはバイナリの中に一緒にバイトコードも含まれてるってことなのかね。

ともあれ、ノードさえ立ち上げてもらえれば、ソースコードが一箇所にしか無くてもガンガン分散システム構築できますよと。

% [Erlang] ノード間接続時のクッキーの取り扱いについて補足

この前書いた話に微妙に勘違いが混ってるみたいなんで補足とか。 英語苦手なくせに斜め読みしかしないから間違うんだ。 (や、苦手だからこそ斜め読みなんだけどさ)

最初に erl を起動したときにランダムに決められるクッキーは、$HOME/.erlang.cookie というファイルに保存されて、ローカルなノードには (特に指定しない限り) そのファイルを元にクッキーが設定される。 結果的にローカルノードは全て同じクッキーを持つことになるので、ローカルノード同士の接続は特に設定無しで可能になる。

まあ、結局目に見える状態としてはこの前書いたのと変わりは無いんで、あえて書くほどのことでも無かったか。 ともあれ、いちいちクッキーの設定なんかしないでリモートノード同士接続したい場合は、それぞれのホストに同じ .erlang.cookie ファイルを置いとけば良いということになるね。

% [clip] ETプログラミング入門テキスト(Dルール編)

ET SITE より。 なんか全然関係無い検索してるときにたまたま見つけたんでメモ。

後で読む。


2007-01-12 [長年日記]

% [Erlang] Occam の PAR .. FOR に対抗して(?)、rpc:pmap を使ってみる

pmap は parallel map の意味。文字通り並列的に map 処理をしてくれる。 Occam のようにプロセスに割り振るんではなくて、ノードに割り振るんだけど。 ともあれ。

まず、いくつかノードを立ち上げる。

% erl -sname foo@localhost
% erl -sname bar@localhost
% erl -sname baz@localhost

こいつらはもう触らないで放置。 次にごにょごにょやるためのノード。

% erl -sname main@localhost

これからの操作は全てこのノード上で行なう。 その前にモジュールのソースを用意。 適当に端末に表示させるため。

% cat print_prime
-module(print_prime).
-export([print/1]).
-import(prime, [isPrime/1]).

print(N) ->
   case prime:isPrime(N) of
      false -> nop;
      R ->
         io:fwrite('~p, ', [R])
   end.

prime モジュールは前に作ったやつ。 では開始。

(main@localhost)1> NodeList = net_adm:world_list([localhost]).
[foo@localhost,bar@localhost,baz@localhost,main@localhost]
(main@localhost)2> ForeignNodes = lists:delete(node(), NodeList).
[foo@localhost,bar@localhost,baz@localhost]

NodeList は localhost 上に存在するノードのリスト。 そこから自分自身を抜いたのが ForeignNodes 。

(main@localhost)3> c(print_prime).
{ok,print_prime}
(main@localhost)4> {Mod1,Bin1,FName1} = code:get_object_code(prime).      
{prime,<<70,79,82,49,0,0,4,156,66,69,65,77,65,116,111,109,0,0,0,179,0,0,0,24,5,112,114,...>>,
       "/Volumes/Sub/tmp/jijixi/erlang_test/prime_number/prime.beam"}
(main@localhost)5> {Mod2,Bin2,FName2} = code:get_object_code(print_prime).
{print_prime,<<70,79,82,49,0,0,2,32,66,69,65,77,65,116,111,109,0,0,0,96,0,0,0,12,11,112,114,...>>,
             "/Volumes/Sub/tmp/jijixi/erlang_test/prime_number/print_prime.beam"}
(main@localhost)6> rpc:multicall(ForeignNodes, code, load_binary, [Mod1,FName1,Bin1]).
{[{module,prime},{module,prime},{module,prime}],[]}
(main@localhost)7> rpc:multicall(ForeignNodes, code, load_binary, [Mod2,FName2,Bin2]).
{[{module,print_prime},{module,print_prime},{module,print_prime}],[]}
(main@localhost)8> nodes().
[foo@localhost,bar@localhost,baz@localhost]

print_prime モジュールを読み込んだあと、そのモジュールと依存する prime モジュールを自分以外のノードに転送。 最初に実験したとき、prime モジュールを転送しなかったせいで失敗しまくって悩んだりした。 モジュールの依存関係を調べる方法とかも何とかしたいところ。 ともあれ、これで用意ができたので本番。

(main@localhost)9> BaseList = lists:seq(0, 1000).
[0,
...(だらだらとリストが表示されてるので略)
(main@localhost)10> NewList = rpc:pmap({print_prime,print}, [], BaseList).
991, 983, 971, 967, 947, 919, 997, 977, 953, 941, 937, 929, 911, 907, 887, 883, 863, 859, 839,
 881, 877, 857, 853, 829, 827, 823, 811, 787, 821, 809, 797, 773, 751, 769, 761, 757, 743, 739,
 727, 719, 691, 683, 733, 709, 701, 677, 673, 661, 659, 647, 643, 631, 619, 607, 599, 653, 641,
 617, 613, 601, 587, 571, 563, 547, 523, 593, 577, 569, 557, 541, 521, 503, 499, 491, 487, 479,
 467, 463, 509, 461, 457, 443, 439, 431, 419, 383, 379, 449, 433, 421, 409, 401, 397, 389, 373,
 367, 359, 347, 331, 311, 307, 353, 349, 337, 317, 313, 283, 293, 281, 271, 263, 277, 269, 257,
 251, 239, 241, 227, 223, 233, 229, 211, 199, 197, 191, 179, 193, 181, 167, 163, 173, 157, 151,
 139, 149, 137, 131, 127, 107, 103, 113, 109, 101, 97, 83, 79, 89, 71, 67, 59, 73, 61, 47, 43, 
53, 41, 31, 37, 29, 23, 19, 11, 17, 13, 7, 3, 2, 5, [nop,
...(これまたリストが表示されてるので略)

いつものように結果には適当に改行が入れてある。 思ったほどバラけてはいないけど、それでもそれなりに順番無視で表示されてるのがわかる。 (まあ大きい方の数から表示されてる時点で、十分順番無視だとも言えるが)

% [Oz] 今日の言語 Oz/Mozart (序章)

あろはさんとこ経由、中村正三郎のホットコーナーより。 Oz という言語が存在することは知っていたけど、それがどんな言語なのかはまったく知らず、ましてや Mozart というのがその実装の一つだなんて露程も知らなかったわしがいますよ。 Mozart って Mozilla 絡みの何かだと思ってたヨ(笑

それはともかく、何やら Erlang 熱の高まる今まさにいじってみるべき言語であるっぽいのでさっそくインストール。 MacOSX 用のパッケージがあったんでラクチンだ!! ……と思ったら、oz コマンドを起動してみて激しくショックを受けたのであった。

対話環境が Emacs 使ってやがる...orz

何これ、わしに対する嫌がらせ? Emacs なんつー変態エディタなんて、全く使えないよ。 とりあえず、間違って起動しちゃったときのために、C-x C-c てのだけは憶えたけど、それ以外はさっぱりだ。 何で C-h でヘルプとか出んだよ、わけわかんねー。

……えー……という感じなので前途多難です。

% [Oz] まだまったく本題に入れていない

さて、C-h の件については向井さんが教えてくれたので良しとしよう (ツッコミ欄参照)。 だが、今度は実行の仕方がわからない。 どうも GUI 付きな Emacs ならメニューバーに Oz という項目があって、そこからいろいろできるようだが、わしの環境にはどうやらそんなものは無いので (や、よくわからんのだが)、端末表示のが立ち上がるわけだ。 んで、何やらその場合も上の方にメニューバー的な領域があるにはあるんだが、それを操作する方法がわからん。

とりあえずマニュアルのスクリーンショットを見ると、画面の下の方に menu-bar Oz Feed Line とか出てるので、そういう操作をすれば良いのであろう。 でもやり方がわからんので、しかたなくそのページの下の方に進んでいくと何やらキーバインディングが書いてある。これか!!

で、Feed current line という操作をすべく C-. と押すが Beep が鳴るのみで何も起きない。 何が悪いのかわしにはわかりません。 しかたなくまた色々さまよって、こっちのページにこんな↓記述を発見。

Generally, Oz-specific commands are made available both with C-. and C-c . as prefix. This manual always lists only the first of these. However, some terminals may not be able to generate C-.; this is why the second one is provided.

…えーと、C-. だと入力できない端末もあるから、C-c . でも同じように動作するよ。 みたいな話?

なら最初からそう書け、くそったれ。(だんだんやさぐれてきました)

ともあれ、ようやく Hello World が実行できましたよ。 C-c . e ってやると下側のウィンドウに実行結果が出てました。 わーい。

……お願いだから vim で書かせて。 それか、普通に readline な対話環境付けて。 だが、vim 用のプラグインは見当たらず。vim の配布物には含まれてないし、Mozart のものにも含まれてないっぽい。 いや、まあ、Emacs なんか使うよりは入力支援全く無しでも vim で書いた方が良いんで、もう少し長いものを書くようになったら vim で書くよ、きっと。

% [Oz][vim] oz.vim

お、あったあった。 さっそくいただいとく。 これで少しは気が楽になったな。

最終更新が古いのがちょっと気になるけど、無いよりはマシだろう。

% [Oz][Mac] まだまだ本題に入れず

今度は M-C-x の入力の仕方がわからないという。 Terminal.app の設定で、option キーが META に相当するらしいのはわかってるので、option+control+x と押してみるが、なぜか M-x になってしまう。

もう良い加減匙投げムード満点なんで、これはもう GUI な Emacs を調達する他あるまいってことにした。 幸い Mac OS X には Carbon Emacs というものがあるので、ありがたく頂戴する。

で、シェルの設定で、

export OZEMACS='/Applications/Emacs.app/Contents/MacOS/Emacs'
alias oz='oz &'

くらいにしとけば完了。 なんかよくわかんないけど、画面の色んなところがクリックできるようになってて、そっからコンパイルとか選べるから、これでもう大丈夫だろう。 つーか、最初っからこうすれば良かったのかもしれないな(疲

% [Oz] Oz 事始め

なんてこった、思いもかけず論理型の呪いが(苦笑)。 もう避けては通れないってことですか?

まだ全然いじれてないけど、とりあえず感じたことは

  • 論理型プログラミングもできるマルチパラダイム言語らしい。
    • プロシージャ (関数) の定義が関数型のように『引数→返り値』という形ではなく論理型のルールみたいな形。
    • つーか、proc で定義するとそうだけど、fun で定義すれば通常の関数みたいな形になる? なら気は楽だ。(Prolog のわけわからなさに打ちのめされて論理型恐怖症になっている模様)
    • パターンマッチが論理型のように (?) 双方向に作用する。(いまいちこの辺理解できてないんで適当)
  • また変数に大文字始まりか。
    • 小文字始まりなのは予約語以外は自動的に atom のようだ。
    • 変数が再束縛できないのも論理型の呪い? (というか、きっと並列処理に都合が良いんだろうなあ)
  • ブロックがみんな end で終わるんで、ちょっと Ruby っぽい。
    • と言いつつ、local..in..end というブロックは、なんとなく SML を思い出す。
  • タプルの書き方が変すぎる。
    • 1#2#3 とか。
    • '#'(1 2 3) とか。
  • リストは区切り文字使わない。
    • [1 2 3] とか。それ何てLis(ry
  • atom(要素) という形はレコードというらしい。
    • だからきっとタプルの '#'(1 2 3) てのもレコードの仲間。
  • case 式のパターンの区切りがキモい ( [] )。それ空リストにしか見えないよ。(空リストは nil)
  • 関数の適用は {Func Arg1 .. ArgN} という形。それ何てLi(ry

まだ全然並列処理とかのネタに入れない……

% [Oz][Erlang] Oz の並列処理

まだ、なかなか動かして確かめる域に達せずにいるけど、ドキュメントを眺めながら考えてみた。 チュートリアルの 8 Concurrency のページに A concurrent Map function としてこんな例が載っている。

fun {Map Xs F}
   case Xs
   of nil then nil
   [] X|Xr then thread {F X} end |{Map Xr F}
   end 
end

thread .. end というブロックが別のスレッドとして動くらしい。 さて、この例がほんとに並列処理として動くなら、thread {F X} end が値を返す前にその次の {Map Xr F} が呼び出されないといけないと思うんだが (というか実際そうなんだが)、どうもそれがイメージしづらいというか何というか。 つーか、これって Haskell の map の定義そっくりだよね。 ということはあれか、下手に末尾再帰とか意識しちゃうと、並列処理を有効に活用できないってことか。 「後で必要になったら計算する」じゃなくて「いずれ戻ってくるからほっといて次にいく」のは大きな違いみたいだけど、コードに落としてみるとそっくりってのもおもしろいな。

Oz の場合、スレッドに親子関係が存在するので、スレッドが値を返すことが普通にできちゃうけど、Erlang だと、プロセスには特に設定しないかぎり依存関係は無いので (だから普通に値を返したりはしない)、こういうのはすんなり書けないと思う。 まあ、生成するときに生成元のプロセス ID を教えておいて、最後にその ID にメッセージを送るようにしておくとかすれば、それっぽいことはできなくはないけども。 そうすると戻ってきたメッセージを集めるために receive でブロックするはめになるけど、Oz の例だって{F X} が全部戻ってくるまでブロックする (はず) んだから同じか。

なんつーか、Erlang は並列処理よりも分散処理の方に力を入れてる感じがあって、分散環境での並列処理に関してはかなり書きやすい気はするけど、(物理的には一つな) マルチコア CPU を活かすため…というお題目なら Oz や Occam みたいなアプローチの方が有効なのかな。 ほんとは、こんな風に自分で並列化するところを指定とかしなくて良いのが一番なんだろうけど、かと言って今のアーキテクチャのままだと並列化できるからといって、何でもかんでもすれば速くなるのか? って言うとそうでもない気がするし (メモリやバスの速度の問題とかね)、やっぱハードウェアが変わらないうちはこういうアプローチが無難かなーという気もする。

% [Oz] あーもー破滅的にサンプルコードが読みづらい

連続した式の区切りが空白しか無いくせに、LISP みたいに全部の式がカッコで囲まれてるわけじゃないから、どこで区切って良いのかわかりづらすぎる。 関数呼び出しも関数型方式のもあれば論理型方式のもあって、値が返ってきてるんだかそうでないんだかわかんねーし、あーもー。

とりあえずスレッド間通信にはポートを使うというのはわかった。 要するにキュー。 あとそのポートに飛ばす要素としてチャンクとセル。 チャンクは機能が限定されたレコードで、セルは OCaml のリファレンスみたいなもん。 ポートには何でも投げられるっぽいけど、まあプロトコル的なものは必要だろうから、そういう用途にチャンクを使うって感じか?

えーと……なんだか、まだ自分で何かを書ける気になれない……

% [Oz] オブジェクト指向

マニュアルの最初で長々とクラス及びオブジェクトの構造をまるで「自分でこうして作ってね」と言わんばかりに説明してて心底肝を冷やしたが、ちゃんとそれ用の文法が用意されてるので、普通はそっちを使えば良いのであった。 なんか説明する順番逆じゃねーのか。 まず普通の使い方から説明しろよ。

なんかクラスやメソッドの定義がそれぞれ end で終わるから、やっぱり Ruby っぽく見える。

declare Hoge in
class Hoge
   attr val
   meth foo
      {Browse @val}
   end
   meth bar(Num)
      val := Num
   end
   meth init
      val := 0
   end
end

local Obj in
   Obj = {New Hoge init}
   {Obj foo}
   {Obj bar(2)}
   {Obj foo}
end

インスタンスメソッドの呼び出し方が、ちょっぴり Objective-C っぽい。 どーでもいーけど、サンプルコードをそのままコピペしても実行できないのはいただけないよなー。 や、なんかクラス定義のときも declare で変数宣言しておかないといかんつーのが、マニュアルからじゃ読み取れないわけで。 この例をコンパイル通すのに、しばし悩んだのだよ。

継承は多重継承も可能らしい。 class Hoge from A B C ... みたいに書く模様。

クラスの要素としてメソッドと属性 (インスタンス変数) の他にフューチャー (Feature) とやらがあるんだけど、イマイチ何のためのものかわかんない。 インスタンス変数と何が違うんだ? 書き換え不可なのかな。

パラメタライズドクラス (カタカナで書くとキモいな) とか言う項目があって、まあ要するに Java の Generics みたいなことをしてるけど、そもそも動的型付けな言語なんだから、普通にダックタイピングできちゃうわけで、こんなわざわざ書くことあるのかしらん? 別にそのための文法が用意されてるわけじゃなくて、単に関数内で無名クラスを作ってそれを返すってだけの話なんだが。

あと self がキーワードになってるんで、自分自身として使えますよ、とか。 atom じゃなく変数でメソッド名を付けるとプライベートメソッドになりますよ、とか。 プライベートメソッドをインスタンス変数に入れといて、それを経由して呼び出すようにすれば protected みたいなことができますよ、とか。 って、なんかこれ微妙にバッドノウハウくさいな。

なんか他にも細々書いてあるけど、もう良いや。 そんなことよりも、オブジェクトは書き換え可能な変数を持ってるわけで、となれば当然排他制御の問題が出てくるよなーということで次のページではロックの話が出てきてるようだ。 ……が、そろそろ疲れてきた。 ってことで一旦締め。

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

% 向井 [(global-set-key "\C-h" 'delete-backward-char) は紳士の嗜みですよ(とか..]

% jijixi [おお、今まさにそれを探さねば Oz をいじるのは無理だと思ったところでしたよ。 ありがとうございます(涙]


2007-01-13 [長年日記]

% [Erlang] 並列処理版 map

昨日の Oz の例に対抗して、それっぽいものを書いてみた。 結局標準ライブラリの中にこの手のものは見つけられてないけど、この程度で書けるものだから無いのか、それともあんまり役に立たないから無いのか、はてさて。 まあ、メッセージパッシング式の並列処理は遅いってのが定説 (事実?) だから、所詮自己満足の産物でしかないわけだが。 純粋なマイクロカーネルな OS なんて全然普及してないもんなー。 WinNT にしても Mac OS X にしても、結局速度を稼ぐためにモノリシックな方向に寄ってしまっててロマンが足りないよ。 つーか、ロックしてどーのこーのなんてやるより、メッセージで協調した方がどう考えてもわかりやすいんだから、そういうのが速くなるようにハードウェアが進化すべきなんだよなー、ぐちぐち。 コンピュータ業界って、ちっとも昔夢見てたような方向に進まないよね……

……愚痴終了。 で、本題。

% cat parallel.erl
-module(parallel).
-export([func_wrapper/3,
         spawn_with_wrap/3,
         get_results/1,
         map/2]).
-define(REC_MSG, 'parallel:RETURN_VALUE').

func_wrapper(Pid, Func, Args) ->
   fun() ->
      R = apply(Func, Args),
      Pid ! {?REC_MSG, self(), R}
   end.

spawn_with_wrap(Pid, Func, Args) -> spawn(func_wrapper(Pid, Func, Args)).

get_results(KeyPIDs) ->
   get_results(KeyPIDs, []).

get_results([], Result) -> Result;
get_results([Pid|Keys], Result) ->
   receive
      {?REC_MSG, Pid, Val} -> ok
   end,
   get_results(Keys, [Val|Result]).

map(_, []) -> [];
map(Func, [X|XS]) ->
   F = fun(Arg) ->
      spawn_with_wrap(self(), Func, [Arg])
   end,
   PIDs = map(F, [X|XS], []),
   get_results(PIDs).

map(_, [], Result) -> Result;
map(Func, [X|XS], Result) -> map(Func, XS, [Func(X)|Result]).

一つの処理ごとにプロセスを作って、そいつには呼び出し元に結果をメッセージとして投げさせる。 あとは、それらのプロセス ID リストを利用して結果を receive してリストに詰め直して終わり。 receive 時に、定数を含むマッチパターンだけを指定すると、マッチしないメッセージがメールボックスに溜まっていても無視して (手を付けないで) マッチするメッセージを待つので、Pid を含むメッセージを投げさせておけば、順番に受信できるという寸法。 さらに万全を期すなら、Pid じゃなく何か別のユニークな値 (リファレンスとか) をキーにした方が良いのかも。 まあ、そんなそんな短かい時間の中で Pid が重複することは無いと思うんだけど (確信は無い)。 あと、メッセージに冗長な atom を割り当ててるのは、呼び出し元のプロセスが他にどんなメッセージを受けるようになってるかわからないので、うっかり関係無いメッセージを処理しちゃったりするのを防ぐため。 あ、それこそここでリファレンスを使うべきだったか。

あ、念の為書いておくと、一旦 Pid のリストにマップするときと get_results で一回ずつリストがひっくり返ってます。末尾再帰してるので。

% [Erlang] リファレンス

って GC されるのかな。 map 関数使うたびに量産したりすると、何かメモリの無駄な気もするんだが……

このページによれば、32bit アーキテクチャの場合 5 words か……え、20 bytes ってこと? デカいよ。 Pid なら 1 word でこれはどっちみち生成されるもんだしなあ。 つーか、いつも富豪富豪言ってるくせに、こんなときだけそんな貧乏くさいこと考えるなって話もあるんだが(苦笑

だいたいプロセスを生成してる時点で数百ワード使ってるわけだから、5 ワードなんて誤差っちゃー誤差だよな。

% [Erlang] リファレンス使った方式に直そうかなと思ったけど、結局やめた

なんか凝り始めるとキリが無いっつーか。

map 関数に渡す関数が間違って止まらなくなっちゃったりした時のために、結果を集める操作も別のプロセスにしといて、個々のプロセスと link させといて、いざとなったら結果集めプロセスを殺せば他も全滅してくれるようにしといた方が良いかな、とか、そういうのを延々と考えてしまう。

そもそも実用のために作ったんじゃなくて、単なるネタなんだから凝る必要無いんだよな。 少なくとも今は実用に供さないわけで、万が一実用にいたることがあれば、そのときに考えれば良いや。

% [Oz][Erlang] ロックロッカーロッケスト

題に特に意味は無い。

あー……ロック関係のところ読んで疲れてしまったよ。 あれだね、Oz は既存の (という言い方は変かな) マルチスレッドプログラミング (いわゆる共有メモリ方式) に慣れてる人には受けが良いのかもしれないが、わしのようにそれに馴染めない人間には結局ツラい代物でしか無いのかも。

メソッド呼び出し時に自動でロックする方法とかあるけど (Java の synchronized みたいなもんか)、そんなのデッドロック恐怖症のわしからすれば、デッドロックのスクツ (なぜか変換できない) としか思えないよ。 かと言って、自分でちまちまロックするなんてやってられん。 しかたないから、mutable なオブジェクトなんぞ使わずに、全部メッセージパッシングでやろうぜ…と思っても、例の Port とやらに書き込むときはロックが必要なのか、それとも勝手にやってくれてるのかわかんない。 ロックが必要だとしたらウザすぎだし。 Port も Port で結局単なるキューだから、Erlang のように必要なメッセージだけ受けとって、他は手を付けずに残しておく…みたいなことをやろうとし始めると、いろいろ面倒なことになる。

結局、いじればいじるほど Erlang の楽しさが浮き彫りになっていく気がして、にんともかんとも。 文法とかはそれほど嫌いじゃないし、ややこしくならない程度にスレッドを使うのは楽しげだけど、やっぱ色んな意味で Erlang の方が好みだなあ。

% [Erlang] オンラインパッチ

リファレンスマニュアルの Code Replacement という項に動作中のプロセスの関数を途中で差し換える例が載ってるので、試してみた。

基本として、Erlang の VM はオブジェクトコードを 2 バージョン保持していて、既存のモジュールと同じ名前のモジュールが新たに読み込まれた場合、それまでのコードを保持しつつ、次の呼び出しからは新しいモジュールを使用する。 二つ前や三つ前のコードはそれを使用しているプロセスが終了した時点で消える。

で、新たに関数を呼び出すプロセスは問題無いんだけど、古いモジュールの関数が無限ループしている場合、そのままだといつまでたっても古いコードのまま動き続けることになる。 そこで、ちょっと仕掛けをして途中から新しいコードにすげ換えてしまおうという話。

まずテスト用のコード。

% cat test.erl
-module(test).
-export([loop/0, test/1]).

loop() ->
   receive
      code_switch ->
         test:loop()
   after
      2000 ->
         test(old),
         loop()
   end.

test(X) ->
   io:fwrite('~p~n', [X]).

loop 関数は 2 秒ごとに old と端末に表示しながら無限ループしている。 で、これをまずどこかのノードで動かす。

% erl -sname remote@localhost
(remote@localhost)1> c(test).
{ok,test}
(remote@localhost)2> register(main, self()).
true
(remote@localhost)3> test:loop().
old
old
old
...(以下ずっと続く)

手間を省くために、こちら側で test モジュールを読み込ませてるけど、もちろん別ノードから読み込ませても良い。 次に、上記のコードを、

--- test.erl.orig       2007-01-14 01:20:59.000000000 +0900
+++ test.erl    2007-01-14 01:08:11.000000000 +0900
@@ -7,7 +7,7 @@
          test:loop()
    after
       2000 ->
-         test(old),
+         test(new),
          loop()
    end.

こんな風に書き換えて、別のノードで読み込み、そのオブジェクトコードをさっきのノードに転送してやる。

% erl -sname local@localhost
(local@localhost)1> c(test).
{ok,test}
(local@localhost)2> {Mod,Bin,FName} = code:get_object_code(test).
{test,<<70,79,82,49,0,0,2,52,66,69,65,77,65,116,111,109,0,0,0,80,0,0,0,10,4,116,101,...>>,
      "/Volumes/Sub/tmp/jijixi/erlang_test/online_patch/test.beam"}
(local@localhost)3> Remote = 'remote@localhost'.
remote@localhost
(local@localhost)4> rpc:call(Remote, code, load_binary, [Mod,FName,Bin]).
{module,test}

あとは、test:loop 関数を動かしているプロセスに、

(local@localhost)6> {main,Remote} ! code_switch.
code_switch

と、メッセージを送ってやると、

...
old
old
old
new
new
new
...

さっきのノードでうまいこと新しい test:loop 関数に乗り換えてくれた。

再帰するときに test:loop() のようにモジュール名込みで呼び出せば、その時点で有効なコードを呼び出してくれるってことなんだと思うけど、じゃあ、最初っからそう書いとけば? みたいなことを思ったが…… 逆に何かアクションを起こさないと新旧コードが切り替わらないようにしておいた方が、古いコードを動かしたまま新しいコードを本番環境でテストできたりして嬉しいってこともあるのかな。 その辺は必要に応じて選択できるのも、またありがたいのかもしれない。

% [Erlang] スレイブノードとプール

おら何だかわくわくしてきたぞ。

% touch ~/.hosts.erlang

空ファイルで良いから作っておかないと、エラーが出てうるさい。あとは…

% erl -sname main@localhost
(main@localhost)1> HostNameList = [foo, bar, baz].
[foo,bar,baz]
(main@localhost)2> lists:map(fun(X) -> slave:start(localhost,X) end, HostNameList).
[{ok,foo@localhost},{ok,bar@localhost},{ok,baz@localhost}]
(main@localhost)3> pool:start(node()).
[]
(main@localhost)4> lists:map(fun(X) -> pool:attach(X) end, nodes()).
[attached,attached,attached]

こんな感じにしておけば、spawn の代わりに pool:pspawn を使うことで、暇そうなノードを選んで処理を投げてくれるらしい。 んで、いらなくなったら…

(main@localhost)5> pool:stop().
stopped
(main@localhost)6> nodes().
[]

てな感じで、pool:stop を呼んでやるとプールに登録されているスレイブノードは勝手に死んでくれる。 まあ、ローカルでやる分には SMP とかじゃないと意味無さそうだけど、他のホストにノードを用意できるなら、そいつをプールに登録しておけば後は適当に負荷分散してくれるんだろう。 よくできてるなあ。こういう機能が簡単に使えるってのは楽しすぎるね。


2007-01-15 [長年日記]

% [独り言] なんか…

考えれば考えるほど、押してはいけないスイッチを押してしまった気がする。 とはいえ、押さなければ身動きがとれない状況に追い詰められつつあったのも事実で。

結局、「なるようになるさ」で行くしかないんだろうけど……


2007-01-16 [長年日記]

% [雑談] 『杜撰』の読み

れもん』は無いよ(笑

まあ、そういうわしも二十代半ばまで『もりせん』て読んでたけどな。 なんだそれ、意味わかんねーよ。 海千山千の仲間かよ。

いや、結構こういうのってあるよね。 だからこういうのとか流行ったりすんだよ。 わしもちょっと買いそうだったもの (結局買わなかったが)。

% [独り言] さて、棺桶に片足突っ込む準備は完了したわけだが

そして今まさに足首くらいは入れてみようかというところ。 ふむ、この後わしが採るべき行動は?

  1. 棺桶は満員です、と跳ね返されるのを祈る。
  2. 棺桶は棺桶でもわりとぬくくて、すごしやすい棺桶であることを祈る。
  3. 棺桶から出られなくなる前に逃げる。

……あー、本音で言えば 3 なわけだが……どっかから遺産とか転がりこんでこないもんかなあ。(夢見んな)

% [Fortress] Fortress

各所より。 (個人的な) 並列処理ブームに合わせて公開とはやってくれるじゃないか。 これはさっそくいじってみなければなるまい……と思ったんだが、とりあえず環境構築が面倒なので、language specification という PDF を眺めてみる。

で、25 ページの for ループの説明「For Loops Are Parallel by Default」キタコレ。 シーケンシャルなループは while で書けば良いのかな? あと、すぐ上に「タプルの各要素は並列に計算されるよ」などと書いてある。 並列に計算されるとまずい部分は atomic do .. end で囲むらしい。

ちょww

b = [3 4
     5 6]

これ二次元配列www

c = [1 2 
     3 4;; 5 6 
           7 8;; 9 10 
                11 12] 

三次元配列。テラワケワカラナス。 なんかセミコロン二つが三次元目の区切りになってるらしい。 四次元目ならセミコロン三つだとか。 脳内変換できねーよ。

さあ、おもしろくなってまいりました。

s = {0, 1, 2, 3, 4}

これがセットで、

l = <|0, 1, 2, 3, 4|>

これがリスト。で、

m = {'a' |-> 0, 'b' |-> 1, 'c' |-> 2}

これがマップ。 ほんとは <| とか |-> とかは一文字なんだけど、入力のしかたがわからんので代替表記 (こっちでも多分通る? のか?)。

% [Fortress][Mac] インストールメモ

Mac にインストールしたんで、そんときのメモとか。 なんかどっかに MacOSX で動作確認されてるとか書いてあった気がするんで、わりと気楽に行ってみたが、ほんとすんなり行って拍子抜け。 Windows な人はみずしまさんとことかを参考にどぞ。

とりあえず、僕は小学生なので Subversion が入ってませんでした。 中学生になるべくインストールします。

% port install subversion
% rehash

次にこのページの説明どおりゲストアカウントでチェックアウト。

% mkdir -p ~/tmp/build/fortress_via_svn
% cd ~/tmp/build/fortress_via_svn
% svn checkout http://fortress.sunsource.net/svn/fortress/trunk fortress --username guest
% cd fortress/ProjectFortress

ここで README を読むと、

  • J2SDK 1.5 or later.
  • Ant 1.6.5 or later.
  • JUnit 3.8.1 or later.
  • Bash version 2.5 or later, installed at /bin/bash.

これらが必要と書いてます。 JDK はすでにインストール済みなので問題無し。 インストールされてない場合は適当に Apple のサイトからゲットしてください (URL を示そうと思ったら見つけられなかったよ...orz)。 DeveloperTools をインストールしていると Ant は入ってます。 JUnit は入れてなかったんで支持通りダウンロードして、適当な場所に突っ込んで環境変数設定して終了。

export JUNIT_PATH=/usr/local/junit
export CLASSPATH=${JUNIT_PATH}/junit-4.2.jar:${JUNIT_PATH}

どこに入れるのがお作法なのかわからんかったので、ほんとに適当な場所に。 Bash は入ってるから問題無し。 てことでいよいよビルド。

./ant clean compile
java -cp "build:third_party/xtc/xtc.jar:third_party/FJ/concurrent.jar" \
com.sun.fortress.interpreter.drivers.fs hello.fss
(略)
Hello, World!

完了。 あとは fortress というファイル (シェルスクリプト) の作りがあんまりなので、

20c20,21
< java -cp "build:third_party/xtc/xtc.jar:third_party/FJ/concurrent.jar" \
---
> export FORTRESS_PATH=~/tmp/build/fortress_via_svn/fortress/ProjectFortress
> java -cp "${FORTRESS_PATH}/build:${FORTRESS_PATH}/third_party/xtc/xtc.jar:${FORTRESS_PATH}/third_party/FJ/concurrent.jar" \

こんな感じに直して適当にパスが通ったとこに放り込んで、

% rehash
% fortress hello.fss
(略)
Hello, World!

終了。 さて、これから色々いじろう。

% [Fortress] スレッド

PDF の 38 ページ辺り。 ざっと (インチキな) 抄訳。 訳が面倒なとことかよくわかんないところ、あるいは (個人的に) どーでもいーと思うようなことは飛ばしてるので、念の為。

Fortress のスレッドには「暗黙のスレッド (implicit threads)」と「明示的に生成されたスレッド(spawned threads or explicit threads)」があり、いくつかの構築子 (constructs) は暗黙にスレッドを生成する。

  • タプル
    • 各要素は暗黙のスレッドによって独立して計算される。
  • also do ブロック
    • also 節と呼ばれるブロックは暗黙のスレッドに以下略。
  • メソッド及び関数の呼び出し
    • 複数の引数があるとき、それらは暗黙のスレ以下略。
  • for ループ、内包表記、その他
    • ループ系の処理が並列処理されるかは、使用されるジェネレータによって決まる。
    • プログラマは「シーケンシャルジェネレータ」以外のジェネレータは、暗黙の以下略だと思っておくべき。
  • 極値式(どんなものかまだわからない)
    • 極値式のガード式はそれぞれ暗黙以下略。
  • テスト
    • それぞれのテストは暗以下略。

暗黙のスレッドは fork-join スタイルで動く (訳注: 生成元の環境を引き継ぎ、終了したら親スレッドに制御を戻す、くらいの意味か?)。 全てのスレッドは一緒に作られたグループに属し、グループ内の全てのスレッドが終了することで完全な終了となる。 プログラマがグループ内の個別のスレッドを制御する方法は無い。

あるグループのいくつかの暗黙スレッドが不正に (訳注: completes abruptly はスレッド状態の abruptly completed に相当する状態のことを指すと思われる) 終了するなら、そのグループ自体も同様に不正終了することになる。

明示的生成スレッド (Spawned thread) のオブジェクトは、四つのメソッド (val, wait, ready, そして stop) を持ったオブジェクトへの参照である。 これらのメソッドは引数を取らない。 val メソッドはスレッドが終了するのを待つ。 そして、スレッドが正常終了した場合はその結果を返す。

明示的生成スレッドはスコープの外を指すラベル (訳注:ラベルって何だ?) へ脱出 (exit) することを許さないが、例外の捕捉 (catch) に失敗した場合やスレッドが不正終了した場合はその限りではない。 それが (訳注: このようなスコープ外への脱出が) 起こったとき、例外は保留される。 そして、val メソッドの呼び出しによって保留された例外が投げられる。 (訳注: val メソッドを呼ばずに) 明示的生成スレッドオブジェクトが破棄された場合は、その例外は単に無視される。 特に、spawn 式が失敗すると、そこで生成されたスレッドの終了を検知したり、保留された例外から回復することは不可能である。(訳注: spawn 式なるものがよくわからんので、よくわからん)

wait メソッドはスレッドの終了を待って、空の値 (the void value) を返す。 ready メソッドは終了を待たずに、スレッドが終了しているか否かを bool 値として返す。 これらのメソッド呼び出しは、保留された例外には手をつけない。

明示的生成スレッドは val メソッドか wait メソッドが呼ばれたとき、あるいは ready メソッドが true を返したときに、「終了した」と見做すことができる。

あるスレッドを中断させるには三つの方法がある。

  1. そのスレッド内で暗黙のスレッドを生成する処理をした場合、それらのスレッドグループが終了するまで中断する。
  2. そのスレッドが開始させた明示的生成スレッドに対して、val または wait メソッドを呼んだ場合、そのスレッドが終了するまで中断する。
  3. atomic 式の中で abort 関数が呼ばれた場合、中断する。

Fortress プログラムにおけるスレッドは共有オブジェクトに対しても同時に操作が可能である。 その際、同期的にデータアクセスをさせる手段として、atomic 式が用意されている。 (訳注: atomic 式を使うと自動的に排他制御してくれるということか?)

有効な作業と、スレッド数との割合は parallel slack と呼ばれている (訳注:知りません。並列性のゆとり?とでも訳す?)。

(訳注:この後の文章がちょっと意味が取りづらいので、大雑把に意訳。全然見当はずれになってる可能性あり)

極軽量なスレッドと負荷の平衡処理が非常に多くの余裕をもたらし、余裕のある計算は使用可能なプロセッサの数の違いに対して容易に適応する。 余裕はとても有為な要素であり、Fortress プログラマはできるかぎり並列性を活用すべきである。

% [雑談] 慣れないことすると疲れる

なんとなく意味を取りつつ読み進めるだけならそうでもないけど、訳文としてテキストに起こすとなると大変だなあ。 こんなインチキ訳ですら(笑

翻訳プロジェクトとかやってる人って偉いよ。


2007-01-17 [長年日記]

% [Ruby][雑談] モジュールに関してもろもろ

なんか久々にでかいリファラが付いたなと思ったら、Matz さんにこの前のクラスのネタを取り上げられていた

とりあえず、最初に思った… 『クラスを定義する時にはスーパークラスを明示する』 に対する感想は、すでに同じネタでトラックバックが来ていた↓ので割愛。

クラスはよくてもモジュールは? (Haskell はスケるよ)

んで、『ちゃんと名前空間を分離する』に関して少し考えてみた。 名前空間を分離するのに使うのは基本的にモジュールだろう。 ただ、個人的な感想だと Ruby のモジュールって Mixin の道具としては使いやすいけど (クラスとほとんど同じ構造だから)、名前空間の分離をするための道具としてはちょっぴり使いづらい気がしてる。

名前空間を分離するにはトップレベルのモジュールだけでは不安だから、やっぱりネストしたモジュールを使いたくなるのが人情。 でも…

% irb
irb(main):001:0> module COM::AZITO::JIJIXI::TEST
irb(main):002:1> end
NameError: uninitialized constant COM
        from (irb):1

こういうことはできない (それぞれ上位のモジュールがすでに定義されてれば別だが)。 かと言って、

% cat com/azito/jijixi/test.rb
module COM
module AZITO
module JIJIXI
module TEST

   def self.foo
      p 'foo'
   end
   def self.bar
      p 'bar'
   end

end
end
end
end

こんなの書きたくないっす。 やっぱ例えば…

package COM::AZITO::JIJIXI
module TEST
   ...
end

こんな風に書けたりすれば嬉しいと思うなー。

ちなみに、現状でこんな感じの仕組みを作るなら……

% cat com/__init__.rb
module COM
end
% cat com/azito/__init__.rb
require 'com/__init__'
module COM::AZITO
end
% cat com/azito/jijixi/__init__.rb
require 'com/azito/__init__'
module COM::AZITO::JIJIXI
end
% cat com/azito/jijixi/test.rb
require 'com/azito/jijixi/__init__'
module COM::AZITO::JIJIXI::TEST
   def self.foo
      p 'foo'
   end
end
% irb
irb(main):001:0> require 'com/azito/jijixi/test'
=> true
irb(main):002:0> COM::AZITO::JIJIXI::TEST.foo
"foo"
=> nil

こんなとか。 手でやると面倒だけど、この程度なら自動生成も簡単でしょう。 まあ、処理系が支援してくれるのが一番楽で良いんだけど。


2007-01-18 [長年日記]

% [Fortress] label 式と exit 式

PDF の 99 ページ。13.13 Label and Exit より。

えーとこれは…何だっけ、どっかで見たことあるような機能だな。うーん、思い出せない。 ともあれ、名前付きのブロック (label block) というのがあって、その中で exit 式を使うことでブロックを脱出できるという機能。 exit にはラベル名を指定して任意のラベルブロックから脱出できるそうな。 試しにテストコードを書いてみた。

% cat labelAndExit.fss
component LabelAndExit
   export Executable
   run() = do
      label A
         label B
            print "in B\n"
            exit A
         end B
         print "in A\n"
      end A
      print "out of A\n"
   end
end

…書いてみたんだけど、こんな↓エラーが出て実行できず。

% fortress labelAndExit.fss
Parsing labelAndExit.fss with the Rats! parser: 810 milliseconds
Read /Volumes/Sub/tmp/jijixi/build/fortress_via_svn/fortress/ProjectFortress/FortressLibrary.jst: 704 milliseconds
com.sun.fortress.interpreter.evaluator.InterpreterError: com.sun.fortress.interpreter.evaluator.Evaluator.forLabel not implemented
        at com.sun.fortress.interpreter.evaluator.Evaluator.NI(Evaluator.java:123)
        at com.sun.fortress.interpreter.evaluator.Evaluator.forLabel(Evaluator.java:748)
        at com.sun.fortress.interpreter.evaluator.Evaluator.forLabel(Evaluator.java:72)
        at com.sun.fortress.interpreter.nodes.Label.accept(Label.java:40)
        at com.sun.fortress.interpreter.evaluator.Evaluator.evalExprList(Evaluator.java:281)
        at com.sun.fortress.interpreter.evaluator.Evaluator.forBlock(Evaluator.java:225)
        at com.sun.fortress.interpreter.evaluator.Evaluator.forBlock(Evaluator.java:72)
        at com.sun.fortress.interpreter.nodes.Block.accept(Block.java:27)
        at com.sun.fortress.interpreter.evaluator.values.Closure.applyInner(Closure.java:132)
        at com.sun.fortress.interpreter.evaluator.values.Fcn.apply(Fcn.java:44)
        at com.sun.fortress.interpreter.drivers.Driver.runProgramTask(Driver.java:243)
        at com.sun.fortress.interpreter.drivers.Driver$EvaluatorTask.run(Driver.java:274)
        at EDU.oswego.cs.dl.util.concurrent.FJTask.invoke(Unknown Source)
        at EDU.oswego.cs.dl.util.concurrent.FJTaskRunnerGroup$InvokableFJTask.run(Unknown Source)
        at EDU.oswego.cs.dl.util.concurrent.FJTaskRunner.scanWhileIdling(Unknown Source)
        at EDU.oswego.cs.dl.util.concurrent.FJTaskRunner.run(Unknown Source)

--------Fortress error appears below--------


com.sun.fortress.interpreter.evaluator.Evaluator.forLabel not implemented

わしのコードが間違ってるのか、実際にラベルの機能が未実装なのかははっきりしないが、ともかくこんな機能があるよということで。

% [Fortress] sequential generator

PDF 103P 辺り。 Fortress では基本的にジェネレータ (主に for ループや内包表記に使われる値の並び) は並列的に扱われるが、あるジェネレータ "g" に対して、"sequential(g)" とすることで逐次的に扱われるジェレネータを取得することができる。

for i <- sequential(n#m) do
   ...
end

"n#m" は n から n+m-1 の範囲を表わすレンジ式。 レンジ式はジェネレータのうちの一つ。 ちなみにレンジ式には "n:m" という形式もあることになっていて、こちらは n から m の範囲を表わすはずなんだけど、なぜか謎のシンタックスエラーが出てコンパイルできない。 サンプルコードなんかにはこっちの方が使われてるくせに、実際には使えないなんて切ないぞ。

% [game] 世界樹の迷宮買ってきた

さっそくキャラ作成。

ギルド名
Langs
  • 前衛
    • OCaml (パラディン)
    • Python (ソードマン)
  • 後衛
    • Ruby (アルケミスト)
    • Erlang (レンジャー)
    • Obj-C (メディック)

とりあえずこんな感じ。 名前と職業に特に因果関係は無いよ。 なんとなく予想としてレンジャーがパーティーの要になりそうな気がしたんで、こいつだけは今一番お気に入りの Erlang を指名したが、他は単に思い付いた順番に割り振っただけ。

つーか、絶対ある程度感触掴んでから作るキャラの方が強くなるはずなのに、いきなりこんなお気に入り満載の名前付けて良いのか? まあ、レベル下げてスキルポイントを振り直したりできるみたいだから、何とかなるかな。

さーて、こうやって遊んでられるのも今の内だから、目一杯楽しむかー。

最初の内はまだ作れないけど、カースメーカーの名前は Perl にするつもり。イメージ的に(笑

% [game] セカキュー日記、やっと二階に辿りついた編

あー、こんな地道なゲームやるの久し振りだなー。 たったワンフロア攻略するのに、何度街に帰ったことか(苦笑

ちょっと気を抜くと簡単に死ぬし、全体に漂う雰囲気から懐しさを感じられる類の人じゃないとダメかもしれない。 や、わしはその類の人だから、すげーニヤニヤしながらやってますが。

マップを自分で書くのは、実際やってみるまでは面倒かなーと思ってたが、やってみると何か妙に楽しい。 あと、メモを貼れるのが良い。 ものすごく手間をかけて、シャイニング&ザ・ダクネスのマップ作ってた思い出が甦るよ。

あせってどんどん先に進もうとするとゲームにならないので、まったり稼ぎながらちょっとずつ進めていくのが楽しむコツでしょうな。


2007-01-20 [長年日記]

% [game] セカキュー日記、もはや何時間プレイしたのかわからない編

現在 7F に差し掛かったくらい。 レベルは 22 といったところ。

ちなみに 6F 以降は第二階層になり景色が変わる。 5F の最後には (絶対逃げられないという意味で) 初のボス敵がいて、なかなか大変だった。 初の全滅も経験したし。 おそらくこれ以降も 5 フロアごと程度の頻度でこういうボスが出るパターンだと思われ。 あ、それから全滅すると最後にセーブしてからの経験値などはパーになるけど、マップデータだけは保存ができるので、全滅すること自体に心理的抵抗が無いなら、少し無理にでも進んで地形を把握してから再度挑むというやり方もありかもしれない。

あと、こっそり(?) 5F のボスの攻略法を書いとくと。 真っ向から勝負すると、あっという間に周りから援軍が到着するのでまず勝てない (や、もちろんレベルをありえないほど上げれば話は別だが)。 まずは巣の入口側にいる部下を先に片付けておいて (ここで一旦街に戻っても良い…というか戻った方が良いと思うが)、あとはボスを残りの部下から遠ざけるように誘導してから倒す。 わしの場合、倒した時点のレベルは 17 だった。

これまでの感想としては、パラディン重要。 というかパラディンが憶える防御スキル重要。 特に FOE との戦いでは、フロントガードやバックガードが無いと危険すぎる。 たぶんパラディンがいないパーティだと、全体的に通過レベルが上昇しちゃうんじゃないかな。

あとレンジャーもやっぱり地味に重要だと思う。 ザコ戦では先制ブーストや先制ブロックの効果が結構でかいし、FOE 戦では一発のダメージが命取りだからトリックステップの効果はあなどれない。

今のところの基本戦法は、パラディンがフロントガード使いまくり (後衛を攻撃してくる敵ならバックガード) で防御に徹しつつ、ソードマンとアルケミストが攻撃。 レンジャーは初めに一・二回トリックステップして、あとは適当に攻撃しつつ、アザーズステップでメディックに急いで回復スキル使わせる役。 地味だけどかなり重要。つーかそれくらいバランス調整がカツカツなんで、むしろレンジャーがいないパーティで始めちゃった人は大丈夫なのかと心配になるくらいだが(苦笑


2007-01-21 [長年日記]

% [game] セカキュー日記、自分でマッピングすることの喜び編

テレビ (あるいはモニタ) を前にゲームをしつつ、たまに手元のノート (や、その類。方眼紙とか) にマップを書き込んでいく楽しさってのは、たぶんやったことがある人じゃないとわからないし、やっても楽しくない人だって多いだろうが、ともかくわしは楽しくてしょうがない。

んで、開発陣は単純な思い付き (例えば、DS だから何かペンを使う操作が欲しいとかっていう簡単な理由) ではなくて、ちゃんとそのマッピングすることの楽しさをわかった上で作ってると思うのね。 だって、上の画面で何が起こってようと、下の画面では何事も無くマップが書けるのよ。 つまり、テレビとノートは別々に操作ができるわけ。

例えば歩いてて戦闘に入る。 そのときコマンド入力できるようになるまでには、多少の時間がかかる。 で、その間マップを書く。 あるいはコマンドを入力する。 しばらく戦闘のエフェクトが流れて、することが無いからマップをいじる。

そういう、まさに昔ノートを相手にやっていたことが、ノートに書くよりもずっと手軽に綺麗にできてしまうわけだ。

分岐路に差し掛かる。 周りを見渡して、通路が伸びている方向にはきっかけを書いておく。 宝箱を開ける。 中に入っていたものをメモする。

わかる人にはわかるこの喜び。 ぜひ『わかる人』には体験してもらいたいものだ。 昔 3D RPG をマッピングしながら楽しんだことがある人、絶対楽しいから世界樹の迷宮を買いましょう。

% [game] セカキュー日記、B10F 突破編

レベルは 36。 8F に 5 日間こもるクエストをこなして十分レベルが上がってたおかげか (3 くらい上がった気がする)、わりと楽勝でボスを倒せた。 それでも体力減ると使ってくるカウンターのせいで、二人くらい死にかけてたが。 あとレベル二つ三つ下だったら、即死してたかも。 死にそうになったのはレンジャーだったから、そうなってたら結構危なかったな。

ボスはザコを呼び寄せるんで、そいつらを効率的に始末するために全体攻撃系スキルを何か憶えさせておいた方が良い風味。 わしは大爆炎の術式があったから楽だった。 ボスに対しては雷属性がよく効く。

第三階層のイメージは蒼。 何で青いのかはよくわからん。 サンゴみたいなのとか生えてるし、凍ってるというよりは海の底って感じ?

% [game] セカキュー日記、ブシドー参入編

ようやくブシドーが作れるようになった。 11F のとある場所でレンからそのためのアイテムがもらえる。

試しに一人作ってみたが、なかなかおもしろいスキル構成なんで、ちょっと育ててみたくなるなあ。 まあ、強いメンバーの後ろに隠れさせてれば速成可能だから、ある程度まではすぐなんだけど、メインを張れるようにまでするにはストーリー進めるのを止めて稼がなきゃならんだろうし、うーん、悩む。 やっぱ、こういう趣味性の高いキャラは、一回クリアしてからの方が良いのかなーとも思うし……


2007-01-23 [長年日記]

% [game] セカキュー日記、豆知識編

『複眼』を手に入れるには氷の術式系が便利。 アイテムの説明を読めばヒントがあるけど、要するに傷がつかないような倒しかたをしないとダメってことね。 モンスター図鑑のアイテム欄で 3 番目に入ってるのはレアアイテムみたいで、ドロップ率が極端に低いか特殊な倒し方しないといけないもののようだ。

他に特殊な倒し方が必要なものと言うと、火喰い鳥の『赤玉石』なんかがあるね。 これは炎の術式系で倒せば手に入る。

% [game] セカキュー日記、アザーズステップが役に立ちすぎる編

やっぱレンジャー最高。 世の中にはレンジャーなんてアイテム拾いしか能が無いと思ってる人もいるみたいだが、もうアザーズステップ無しの生活なんて考えられない。

スキルレベルを 10 まで上げると、成功率 100% で消費 TP が 1 になるので、いくら追い詰められようがレンジャーが生きてさえいれば確実に回復魔法なりアイテムなりを誰かに使わせることができる。 他にも、全体攻撃魔法なんかは発動が遅いけど絶対最初に当てられるとか、便利すぎる。 その上消費 TP 1 だから TP の残りなんてほとんど気にしなくて良いし。

やっぱレンジャー重要っすよ。

% [game] セカキュー日記、休養するか否か編

休養するとレベルが 10 下がるかわりに、スキルポイントを最初から振り直せるのである。 ほとんどのキャラはそこそこ思い通りに成長させられたんだけど、約一名、ぶっちゃけメディックだけがちょっと無駄にポイントを浪費した部分があって (要するに意味の無いスキルに注ぎ込んじゃったと)、休養させるべきか悩んでいる。

えーと、まあ、すでに思い知ってる人も多いような気がするが、『キャンプ処置』は全く役に立たないです。 説明だけ読むとすごく便利そうに見えるんだけど、それは罠。 取得してみて役立たずさに気付いたものの、レベルを上げれば化けるんじゃないかと甘い期待をして 10 ポイント捨てたおろかものがここにいますよ...orz

……まあ、あれだよな、10 ポイント無駄にしたってことは、これまでの 10 レベル分が無駄だったようなもんだからして、レベルが 10 下がってもその分やり直せるなら僥倖なのかも知れないし……やっちゃうかー。

そんなこんなで悩み多き年頃。 現在到達階は B17F で、レベルは 49 (メディックだけは 48)。 メディックがレベル低いのは、そいつの代わりに初心者キャラを入れて何人か速成してたから。 一応、16F でカースメーカーが作成可能になるんで、一通りの職業を作ってみた感じ。

ブシドーとカースメーカーはわりと趣味的な能力してて、メインで使うにはちょっとアレかもという雰囲気。 バードはそれなりに役に立ちそうではあるが、入れるポジションが無いというか、入れるならレンジャーのところだけど、わしの場合レンジャーを外すなんて考えられないしなあ…という感じ。 ダークハンターは使い方によってはソードマンの位置に入れても良いのかもしれないが、どうなんだろうね、そこら辺は趣味で…って感じか。 やっぱ基本的には今の構成 (パラディン、ソードマン、アルケミスト、レンジャー、メディック) が安定だろうなあ。

% [雑談] あろはさんとこを見に行くと、なぜか OmniWeb が暴走するので直接コメント書きに行けないから、ここに書いたりする

livedoor Reader でしか読んでないから、どんなコメント付いてるとか全然知らないという。 わしはコメントも RSS で流してほしい派。 ともあれ、この辺に関して。

そのうちシーメーでもいかがでしょうか ?

現在、諸般の事情により「人としてどうか?」というくらい貧乏なもんで、何とかその危機を脱した頃にでも。 まあ、早くても数ヶ月後な気がしますが(苦笑

ちなみに住処は札幌ではなくて、札幌のお隣です。


2007-01-24 [長年日記]

% [Python] Stackless Python

なんか、ふとした拍子に見付けてしまったが。 ここで言う『Stackless』とは C のスタックを使わないという意味らしい。 もちろん、C で書いてるのに C のスタックを使わないなんて不可能なので、つまるところ Python の関数を呼ぶときに C のスタックが積まれないということだろう。

そんで、なんでわざわざ Stackless にしてるかというとスレッドサポートを充実させるためのようだ。 で、Stackless なのを利用して作られた stackless というモジュールが用意されていて、

  • 細粒度スレッド
    • タスクレット (Tasklet) と呼んでいる
  • チャンネル
    • タスクレット同士の通信
  • スケジューリング
    • スケジューラが組み込み済み
  • 直列化
    • タスクレットはシリアライズ可能

というような機能があるらしい。

そんなこんなで、これから "Introduction to Concurrent Programming with Stackless Python" てページでも読んでみようかというところ。 読んでみて、何か思うところがあれば、また後で書くかも。

% [雑談] フライング拍手

新聞を読んでいたら『フライング拍手』なる言葉を見かけたのである。 簡単に言うと、クラシック音楽のコンサートなどで、曲が完全に終わる前にフライングで拍手をすることらしい。

で、

この『フライング拍手』だが、またの名を『フライングブラボー』と言うそうな。 ・・・もちろんそこでわしの頭にはボルナレフのあの姿が浮かんだことは言うまでもない。 新聞読みながら一人吹き出すわし。 だが、何がおもしろかったのか家族には説明不能なので、ここに書いて同士を募る所存。

% [Dylan] 今日の言語 Dylan

S 式じゃない LISP として有名な(?)言語。 LISP かどうかは別として、元々は Apple が作ったってことで、それなりに古い Mac ユーザのわしとしてはわりと気にはなっていたんだが、何かこれまで縁が無くてスルーしてきた。 今日、某所で名前を見かけてふと思い出したんで、思い切ってインストールしてみたり。

いちおう MacPorts にも lang/gwydion-dylan というのがあるんだけど、なぜかビルドに失敗するので、しかたなく FreeBSD の方で lang/dylan を入れてみた (モノは同じはず)。

んで、ざっとチュートリアルを眺めてみると、「あー、たしかに LISP だ」と思う。 いちおう動的型付けなオブジェクト指向言語というのが基本的な位置付けみたいだけど、そのオブジェクト指向はまんま CLOS で、あとブール値が #t と #f だったり、識別子に結構自由に記号が使えたり、いろいろと LISP 的。 動的型付けではあるけど、変数の型を指定することができて、コンパイル時に型の不一致などは見つけてくれるらしい。 この辺は Common Lisp 的? あと、末尾再帰の最適化もやってくれるみたいなんで、関数型脳にも安心。

基本的にブロック構造や定義文は end で終わるんだけど、例えば

define method hello()
   ...
end;

という関数定義は、

define method hello()
   ...
end method;

とか、

define method hello()
   ...
end method hello;

と書くこともできて、end が何の終端なのか間違えにくくできるみたい。

まあ、ともあれまずは世界に挨拶から。

% cat hello.dylan
module: hello

define function hello()
   format-out("Hello, World!\n");
end function hello;

hello();

一行目はどうもファイルのメタ情報を書く特殊形式(?)みたいなものらしく、とりあえず module: だけは書かないとコンパイルできないみたい。 他に、synopsis: だの author: だのあるみたいだが、こいつらがどういう扱いをされるものなのかはよくわからん。 んで、コンパイル。 これがものすごく時間がかかる。 なんでかー!!ってくらいに。 Pen3/550MHz のマシンなんだが、ためしに time 付けてみると…

% time d2c hello.dylan
Entering single file mode.
Compiling library hello
(中略)
Parsing hello.dylan
seeding representations
Finalizing definitions
inheriting slots
inheriting overrides
laying out instances
Processing hello.dylan
....
Emitting Library Heap.
Emitting Global Heap.
Building inits.
gcc -o hello hello.o  /usr/local/lib/dylan/2.4.0/x86-freebsd-elf-gcc/libio-dylan.so (略)
Optimize called 112 times.
Compilation finished with 0 Warnings and 0 Errors
d2c hello.dylan  17.27s user 2.61s system 98% cpu 20.105 total

20 秒ってあんた。 あと、見てのとおり C のコードを吐いて gcc でコンパイルというのをやってるようだ。 ともあれ実行。

% ./hello
Hello, World!

ちなみにこのとき…

% ls -l
total 2914
-rwxr-xr-x  1 jijixi  jijixi   447964  1 24 19:45 hello
-rw-r--r--  1 jijixi  jijixi  2014449  1 24 19:44 hello.c
-rw-r--r--  1 jijixi  jijixi      103  1 24 19:37 hello.dylan
-rw-r--r--  1 jijixi  jijixi   448552  1 24 19:45 hello.o
% wc -l hello.c
   51285 hello.c

ぐへ……

それはそれとして、いちいち C のソースとかオブジェクトファイルがばら撒かれるのが汚ないので、make-dylan-app というコマンドで make プロジェクトの雛形を作って遊ぶのが吉っぽい。

% make-dylan-app app_test
% cd app_test
% ls -l
total 8
-rw-r--r--  1 jijixi  jijixi  144  1 24 20:04 Makefile
-rw-r--r--  1 jijixi  jijixi  161  1 24 20:04 app_test-exports.dylan
-rw-r--r--  1 jijixi  jijixi  246  1 24 20:04 app_test.dylan
-rw-r--r--  1 jijixi  jijixi   74  1 24 20:04 app_test.lid
% cat app_test.dylan
module: app_test
synopsis: 
author: 
copyright: 

define function main(name, arguments)
  format-out("Hello, world!\n");
  exit-application(0);
end function main;

// Invoke our main() function.
main(application-name(), application-arguments());

こんな感じで用意してくれるので、あとはこいつをちょこちょこいじって make でオッケーと。

では、これから適当にいじっていく予定。 日記にサマリを書くかどうかは気分次第。

% [Dylan] オブジェクト指向関係

gauche を勉強したときに憶えた CLOS 的アプローチを理解できてれば、まあわりとそのまんまなんで普通に理解できる感じ。 メソッドは総称関数として存在して、クラスやオブジェクトには属さない。 オブジェクトはスロット…要はインスタンス変数を持っていて、よくあるドット記法でアクセスできる。 あるいは getter や setter を使うこともできるけど。 メソッドは基本的には関数のように使うけど、引数が一つの場合 (要は self のみ) にはスロットアクセスのような書き方ができるみたい。

以下、サンプルコード。 make-dylan-app で作った雛形を使っている前提でよろしく。

% cat object_test.dylan
module: object_test

define class <hoge> (<object>)
   slot name,
      init-value: "hoge";
end;
define class <fuga> (<object>)
   slot name,
      init-value: "fuga";
   slot number,
      required-init-keyword: num:;
end;

define method my-name(obj :: <object>)
   obj.name;
end;

define method my-number(obj :: <hoge>)
   0;
end;
define method my-number(obj :: <fuga>)
   obj.number;
end;

define method set-my-number(obj :: <fuga>, num)
   obj.number := num;
end;

define function main(name, arguments)
   let o1 = make(<hoge>);
   let o2 = make(<fuga>, num: 200);
   format-out("o1.my-name => %s\n", o1.my-name);
   format-out("o2.my-name => %s\n", o2.my-name);
   format-out("o1.my-number => %d\n", o1.my-number);
   format-out("o2.my-number => %d\n", o2.my-number);
   set-my-number(o2, 10);
   format-out("o2.my-number => %d\n", o2.my-number);
   exit-application(0);
end function main;

// Invoke our main() function.
main(application-name(), application-arguments());
% make
% ./object_test
o1.my-name => hoge
o2.my-name => fuga
o1.my-number => 0
o2.my-number => 200
o2.my-number => 10

クラス定義のときに継承クラスの指定 (上の例で言えば (<object>)) は省けないようだ。 あとはまあ、これ以上何も言うことは無いというか、こういうもんです、としか言えないというか。

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

% no-name [はじめまして。Shiroさんのアンテナ経由で、時々読ませてもらってます。 ところで、Stackless Python..]


2007-01-25 [長年日記]

% [Scala] Version 2.3.2 が出てたので、久々に触ってみる

朝の 4 時に消防車がサイレンガンガン鳴らしながら走ってきましてね、目が覚めてしまったわけですよ。 おまえもう少し空気読めと、そう言いたい。 そんでまあ、それだけなら「うるせーなー」でそのまままた寝て終わりだったはずなんだけど、何かしらんがそのサイレンが我が家のすぐそばで止まりやがんの。 さすがにそれは気になりすぎるよね。 家のすぐそばで火が燃えてるかもしれないのに、おちおち寝てられない。 しかたなく起き出して様子を見に行ったり何だりしてるうちに、すっかり目は覚めてしまってもう寝れませんて。 幸い大事にはならずに済んだので良かったことは良かったんだが、ともかく何だ起きてしまったからにはパソコンいじったり何だりあーなにやってんだろわし...orz

愚痴終了。

そんなわけで、Scala の 2.3.2 だが。 まあ、これまでも新しいバージョンが出るたびにダウンロードして、新しい機能があればいじってみたりもしてるんだけど、今回のバージョンアップに関しては一言書かないと気が済まないので書く。 Change History より。

Added support multi-line expressions and definitions in the interpreter.

待ってました。 これで俄然遊びやすくなるぞ。

% scala
scala> def f(x:Int)(y:Int) = {
     |   x + y
     | }
f: (scala.Int)(scala.Int)scala.Int

ひゃっほー。

% [Dylan] マクロ

Dylan は羊の皮をかぶった LISP だからして、マクロも強力っぽい。 よくわからんが、define class とかの構文はマクロで書かれてるみたい (エラーメッセージでそんな感じのが出てた)。

残念ながらわしにとってマクロは超不得意分野なので、どんな感じなのかはこの辺を見て想像してちょうだい。

% [Dylan] Pitfalls for Lisp to Dylan Conversion

Common Lisp と Dylan の違いを並べた文書。 わしは Common Lisp はよく知らないが (だからって Scheme とかもよく知ってるわけじゃないけど) Dylan の特徴を知る上でもわりと参考になる感じ。 ちなみに関連して LTD: A Lisp To Dylan Conversion Tool なんてページもある。

なんか最初の方とかは、Common Lisp と Scheme の違いを並べてるように見えるな。

% [Python] Stackless Python と First-class Continuation

Stackless Python に関してツッコミをもらったのだが…

ところで、Stackless Pythonの特徴は、継続をサポートしていることだと聞いています。

とのことで、「ほうほう、でもチュートリアルには continuation なんて文字列は見当たらなかった気がするなあ」と思いつつちょっくら調べてみた。 で、端的に言って…

現在の Stackless Python には、First-class continuation は存在しない。

ということのようだ。 元々は VM を全部自前で作っていて、その頃には (ちょっとこの辺、厳密な歴史がわからないんだけど) continuation というモジュールが存在して、continuation.return_current() で現在の継続を取得することができたらしい。 で、それを利用してスレッドやコルーチンなんかを実装していた、と。 ところが、その後メンテナンスしきれなくなってきて、結局自前の VM はやめて本家の Python で動く拡張モジュール (それプラス、パッチセットもかな?) として再生した。 その時に First-class continuation は捨てて…と言うか本家の実装に合わせるには捨てざるを得なかったんだろうけど、ともかく捨てて、One-Shot continuation (一度使えばおしまいな継続) を使った実装に書き直されたということみたい。 ここで言う One-Shot continuation てのがどんな実装なのかはわからんけど、ユーザとしてはそれが取得できたからといって嬉しいことは無い (ジェネレータと変わらんでしょ) ので、それを操作するような API は用意されていない模様。

チュートリアルを眺めた時点で「なんか、ごくごく普通のスレッドライブラリだなー」と思ってたんだけど、今となっては正しくそういう意味しか無いのかもしれない。 つーか Python 標準のスレッドライブラリ (threading モジュール) が妙に貧弱 (だと思う) なせいで、「普通」でも十分便利に感じるというか。 やれることは、Ruby なんかで用意されてるものと大差無い感じじゃないかな。

そんな感じで、Stackless Python についてはちょっと残念な結果に終わりましたとさ。 正直、threading モジュールはあんまり使いたくないな…という気がするんだけど、かと言ってスレッドのためだけにわざわざ Stackless Python を導入するってのもどうなのよ? と思うね。 ソースのポータビリティが失なわれちゃうし。 いっそのこと本家にマージしてくれれば良いんだけど、なんか過去にいろいろあったみたいだし (初期の Stackless は本家に対しての提案だったんだけど、Guido に却下されてどーのこーのとかって話があるみたい) 難しいのかなあ……

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

% no-name [> 現在の Stackless Python には、First-class continuation は存在しない。..]


2007-01-26 [長年日記]

% [game] セカキュー、ネタバレしないように思わせぶりなことだけ書く日記

とうとう (おそらく最終の) 第五階層に到達。 そして思わず…

「そー来たかーー!!」

…と叫ぶわし。 やー、そーかー、アトラスだしなー、やり始める前はちょっぴりそういう方向性も考えなくはなかったよ、たしかに……でもやってるうちにそんな考えはどっかに行ってたよなー、だってそんな気配微塵も見えないんだもんさー。 そんな感じで、このゲームに関してはストーリーなんておまけみたいなもんで、わりあいどーでもいーと思ってたんだけど、にわかに行く末が気になってまいりましたよ。

それにしても、いまだにほとんどの階のマップに隙間が残ってるんだけど、そこら辺はいつになったら入れるんかなあ。 特に 15F なんてほとんど空だぞ。 一度クリアしても、いろいろやり込み要素たっぷりな予感……

% [Ruby] [ruby-list:43140] Array#[]とArray#at

あー、そういやわしも Ruby 始めたばっかりの頃は同じような勘違いしてたなー。 要は、Array#[]= てメソッドが Array#[] とは別に存在してるということを知らない (あるいは失念してる) んだろうね。 あたかも、Array#[] に対して = で代入してるように見えるから、Array#at に対しても同じように = で代入できるように感じちゃうんだ、きっと。 んでまた、こういう時って undefined method エラーになってくれないからわけがわかんないんだよ。

特に C とかその類の言語を経験してると、結構混乱する話かもなーという気がする。


2007-01-27 [長年日記]

% [game] セカキュー日記、とりあえずクリア編

いちおークリア。 ラスボスはわりと弱かったかな。 つーか、こっちのスキル構成がばっちりハマってたってことかも知れないけど、ともかく全く危げなく勝ってしまったよ。 まあ、厳しい戦いなら、多分これから先の方が厳しいことになると思われるし (アトラスのゲームの宿命だ)、ラスボスはあれくらいで良いんだよ、きっと。 クリアするとオプションの『BGM PLAY』と『PASSWORD』が解禁になる。 パスワードはなんか次回作のためとかってなってるけど、18 x 4 文字もあるんで書き写すのがツラいぞ。

何はともあれ、コストパフォーマンスの高い作品であった。 ……や、「あった」っていうか、まだまだ遊べる部分が残ってるんで全然終わらないんだが。

せっかくだから、ラスボス倒したときのスキル構成をメモ。

  • パラディン Lv.68
    • HP ブースト Lv.5
    • TP ブースト Lv.3
    • DEF ブースト Lv.10
    • 盾マスタリー Lv.10
    • 決死の覚悟 Lv.1
    • オートガード Lv.5
    • 挑発 Lv.1
    • 猛進逃走 Lv.1
    • パリング Lv.1
    • 渾身ディフェンス Lv.-
    • 防御陣形 Lv.5
    • フロントガード Lv.10
    • バックガード Lv.10
    • ファイアガード Lv.1
    • ショックガード Lv.1
    • フリーズガード Lv.1
    • シールドスマイト Lv.-
    • キュア Lv.1
    • キュアII Lv.1
    • 警戒歩行 Lv.1
    • 採掘 Lv.2
  • ソードマン Lv.69
    • HP ブースト Lv.1
    • TP ブースト Lv.5
    • ATC ブースト Lv.10
    • DEF ブースト Lv.1
    • 斧マスタリー Lv.10
    • 剣マスタリー Lv.10
    • ダブルアタック Lv.5
    • ウォークライ Lv.10
    • ヘルズクライ Lv.1
    • アームリカバリー Lv.1
    • 猛進逃走 Lv.-
    • レイジングエッジ Lv.1
    • トルネード Lv.1
    • ハヤブサ駆け Lv.1
    • チェイスファイア Lv.-
    • チェイスフリーズ Lv.-
    • チェイスショック Lv.-
    • パワークラッシュ Lv.1
    • スタンスマッシュ Lv.10
    • ヘッドバッシュ Lv.1
    • 採掘 Lv.2
  • アルケミスト Lv.68
    • TP ブースト Lv.10
    • 炎マスタリー Lv.10
    • 氷マスタリー Lv.5
    • 雷マスタリー Lv.5
    • 毒マスタリー Lv.1
    • 博識 Lv.1
    • TP リカバリー Lv.7
    • 炎の術式 Lv.6
    • 火炎の術式 Lv.10
    • 大爆炎の術式 Lv.1
    • 氷の術式 Lv.3
    • 氷結の術式 Lv.1
    • 大氷嵐の術式 Lv.1
    • 雷の術式 Lv.3
    • 電撃の術式 Lv.1
    • 大雷嵐の術式 Lv.1
    • 毒の術式 Lv.1
    • 毒霧の術式 Lv.-
    • 千里眼の術式 Lv.1
    • 帰還の術式 Lv.1
    • 伐採 Lv.1
  • レンジャー Lv.68
    • HP ブースト Lv.1
    • TP ブースト Lv.1
    • AGI ブースト Lv.5
    • 弓マスタリー Lv.5
    • 先制ブースト Lv.10
    • 先制ブロック Lv.10
    • アクトファースト Lv.1
    • トリックステップ Lv.5
    • ファストステップ Lv.3
    • シャドウエントリ Lv.-
    • 逃走準備 Lv.3
    • アザーズステップ Lv.10
    • パワーショット Lv.1
    • ダブルショット Lv.1
    • エイミングフット Lv.1
    • サジタリウスの矢 Lv.-
    • オウルアイ Lv.1
    • 警戒歩行 Lv.6
    • 伐採 Lv.2
    • 採掘 Lv.1
    • 採取 Lv.3
  • メディック Lv.64
    • HP ブースト Lv.1
    • TP ブースト Lv.10
    • ATC ブースト Lv.-
    • 回復マスタリー Lv.10
    • 戦後手当 Lv.-
    • 博識 Lv.-
    • TP リカバリー Lv.5
    • キュア Lv.3
    • キュアII Lv.3
    • キュアIII Lv.8
    • エリアキュア Lv.1
    • エリアキュアII Lv.10
    • リザレクション Lv.1
    • バインドリカバリ Lv.5
    • リフレッシュ Lv.8
    • 医術防御 Lv.-
    • 医術防御II Lv.-
    • リジェネレート Lv.-
    • ヘヴィストライク Lv.-
    • キャンプ処置 Lv.-
    • 伐採 Lv.-

メディックだけは一度休養してるので、スキルポイントの使い方に無駄が無いけど、他のキャラで Lv.1 なスキルは結構いらないもの多し。 ラスボス戦で重要だったスキルは、パラディンの防御陣形、ソードマンのスタンスマッシュ、アルケミストの火炎の術式、レンジャーのアザーズステップ辺り。 ぶっちゃけほとんどこれしか使ってない。 あとはエリアキュアII がそれなりに強化されてれば楽勝かと。 まあスタンはほとんど効かなかったんで (たしか一回だけ)、ソードマンの使うスキルは他のものでも良いけど、普段の戦闘でスタンスマッシュとアザーズステップの組合せは重宝するしお薦め。 メディックに医術防御を憶えさせてないのは、パラディンの (ファイア|ショック|フリーズ)ガードで十分だから。 スキルポイントに余裕があれば憶えさせても良いんだけど、キュア系鍛える方が先でしょ。

さて、それじゃ続きをやるとするか。 すでに新しいクエストとかも登録されてるしな。

% [game] セカキュー日記、まだまだ冒険は続く編

俺たちの冒険はまだまだこれからだ!!

って打ち切りマンガみたいだけど、ほんとに続くよ。 第六階層キタコレ。 そーかー、きっとここにいるんだな、例の赤い竜は。

しかし、どんだけコストパフォーマンス良いんだよ、このゲーム。

% [game] セカキュー日記、金色の龍編

ショックガード使っても、全体雷攻撃で 1,000 ダメージ。 それ軽く全滅ですから。

あー、これは医術防御とショックガードのレベル上げないとあかんなあ。 つーかこの調子だと赤い竜と青い竜のために、ファイアガードとフリーズガードも上げないとダメな予感。

% [game] セカキュー日記、これはマズい編

何かレベルが上がらなくなったなーと思ったら Lv.70 で打ち止めかよ!! ヤバいよ、99 まで行くつもりでスキル取得計画立ててたから、全然必要なもの足りてないって!!

あー……これはメディック以外全員休養しなきゃダメかなあ。 つーか、こんなに厳しいバランスだなんて...orz

うーん、引退して作ったキャラはレベルの上限が上がってるとかあるのかなあ。 もしそうならツラくても狙う価値あるんだが。 まあ、当面は休養してスキルポイント振り直しでがんばってみよう。 いくらなんでも、もう一度一から五人を Lv.70 まで育てるなんてツラすぎる。

しかし、こうなってくると、今度はソードマンを剣タイプにするか斧タイプにするか迷うところだなあ。 や、基本的には斧タイプで良いはずなんだけど、第五階層に入ってから雷しか効かない敵が出てきてて、こいつ倒すのにチェイスショックが無いとウザすぎんのよ。 あと、全体的に斧の方が攻撃力高い武器が出る可能性高いし。

パラディンは思い切ってフロントガードとバックガードは捨てるか。 今後の事を考えると属性系ガードの方が有用だ。

やー参ったなこれは。 カツカツのバランスの中でスキル構成考えるのは楽しいけどさー……迷ってしまって決められないよ。

% [game] セカキュー日記、属性系ガードすげえ編

おお、スキルポイント振り直してみて判明したが、パラディンの (ファイア|ショック|フリーズ)ガードは Lv.5 まで上げると完全無効にまでなる。 しかもそれ以降にまで上げると吸収効果まで付くとは。 うーん、理想的にはパラディン三人作って、各属性防御のエキスパートにしたいくらいだなあ。 まあ、無茶だから Lv.5 を三つにしとくけど……

なんか他にもレベル上げるとおもしろくなるスキルがあるかもしれないから、いろいろ試してみよう。


2007-01-28 [長年日記]

% [Mac] StopFold がどーのこーのな件

なんという適当なタイトル。

StopFold というのは、AppleMail の余計なお世話機能の一つ format=flowed を強制的に付けなくしてくれる機能拡張。 わしも愛用してるんだが、何か青木さんとこでちゃんと動いてないとかいう話を見て、実はウチも知らん間に無効になってたか? と思って確かめてみた。 ……が、普通に機能してるなあ。 PowerPC と Intel 版との違いだろうか。

よく見たらわしの使ってるのより新しいバージョンが出てたので、そっちに入れ換えてみたけど、やっぱりちゃんと動いてるみたい。


2007-01-29 [長年日記]

% [game] セカキュー日記、金の龍は倒したが編

休養明けのレベル低下を補填して、全員 Lv.70 になった時点で金の龍を倒しに。 ぶっちゃけ Lv.5 ショックガードがあれば楽勝だった。 あとまあ、通常攻撃で即死しない程度の体力が必要なんで、レベルはそれなりに高くないといけないが。

で、気を良くして青い竜 (竜の字は意識的に使い分けてるよ) を倒しにレッツゴー。 ……何こいつ堅すぎ。 特に防御魔法使われると数ターンの間、雷属性でしかダメージ与えられないうえに、高確率で一緒にリジェネレーションを使って毎ターン 1750 回復とか、アリエナス。 こっちは、雷マスタリー Lv.10 の雷撃の術式 Lv.1 で、それプラス、チェイスショック Lv.5 使ってもせいぜいダメージは 500 を越えるくらい。 もりもり回復しやがる...orz

さらにその防御状態のときには、かなりの確率で後列を狙った物理スキルを使ってきて、こいつがまあなんつーの後列の人は即死するような威力 (1,000 近いダメージくらう) なわけ。 ダメージ与えるにはアルケミストが必須なのに、速攻で死ぬ。 んで、生き返らせてる間にももりもり回復してるし。 かと言って、防御陣形で防御力上げようにも、下手にフリーズガードを途切らせると全体氷攻撃で全滅だし。 なすすべ無し。

とりあえず防御力の強化はアイテム買いこんで対処するにしても、リジェネのスピードに対抗できるだけの攻撃力が無いのはツラすぎる。 ってことで、ソードマンとアルケミストは二度目の休養を考えざるを得ない状況に……

なんかほんと、アルケミストとソードマンは各属性のエキスパートを一人ずつ作った方が早い気がしてきたぞ。 つか、属性攻撃じゃないとツラい敵って結構いると思うんだけど、属性攻撃はアルケミストがいないとできないっつーのはどうなのよ。 あ、一応ブシドーは単独で使える属性攻撃持ってるか。 だからってブシドー二人とかありえんしなあ。 アルケミストを抜いてソードマンとブシドーで行けるなら良いけど、ソードマンはアルケミストがいないとダメだし。 やっぱこれからはバードの時代なのかしら (バードは味方の攻撃に属性を付加するスキルを持っている)。 でもレンジャーをあきらめるのはツラいなー。 アルケミストのところにバードを入れるか? それはそれでツラい気がする。 あー、参った。

ともあれ、そろそろメインメンバー以外も育てて、戦術に幅を持たせる必要が出てきた気がするな。 ほんといつまでも遊べるゲームだよ、まったく(苦笑

ちなみに第六階層は、落とし穴だらけの階があって探索は遅々として進まず。 落とし穴を避けるためのヒントらしきものも見つけられなくて、今のところ絨毯爆撃しか方法が無いから能率悪すぎる。 警戒歩行 Lv.10 が無いとやってられん。 落ちた先はダメージ床の海だし、さすがにこれはいじわるすぎないか。

% [game] セカキュー日記、よし、まずはバードを育てよう編

ともあれ全ての職業をメインと同レベルまで引き上げよう。 そのためにはまずバードにホーリーギフト (パーティ全員の取得経験値にボーナスが付く常時発動スキル) を憶えさせねば。 そうすりゃ休養後の復帰もしやすくなるし、ホーリーギフト Lv.10 は目指しておいて損は無いだろう。

% [雑談][reddit] お前は submit するなと言われている気がする

なんかいつの頃からか reddit で submit するときに、例のアレ、何ていうんだっけ? 読みづらくした文字の画像が置いてあってそれを読んで打ち込まないとダメってヤツ、アレが付いたわけね。 まーなんつーのかな、reddit の趣旨から言っても、あーやって自動化を防止するのは良いと思うよ。 その事自体は何ら問題無いんだけどさ……

わしにはあの文字が読めない。

……のよ。 アレが付くようになってから、何度か submit してるけど、一度たりと自信を持って読めた試しが無い。 "たぶん" こうかな? って感じで半分ヤマカンで入力してるの。 一回間違えたこともあるし。 O と Q の区別が付かねーんだよー...orz

なんかこう……ものスゴいストレスを感じるんだよね、これ。 何とかならんもんかな。 わしの目が腐ってるだけ? 他の人は普通に読めてるのかな。 他のところで困ったことって無いんだけど、reddit のやつだけはどうにも読みづらいんだよな……

あ、ちなみに jijixi ってアカウントは結局ログインできなくなったままなんで、gotow ってアカウントを新たに取ってやってます。

% [雑談] アフタヌーンタワー

おお振りが始まってからというもの、単行本化される前の号は保存すべしというのが最近のわしの流儀なのである。 んでまあ、もっさりと積んであるんだが、7 巻も出たことだし整理しようと思って巻末の表示を見る。

『おおきく振りかぶって』第 7 巻は、アフタヌーン '05 年 11 月号から '06 年 1 月号に掲載された作品を収録しました。

たった三ヶ月分かよ!!

……えー、そんなわけで、わが家にはいまだ 13 冊のアフタヌーンがぬどーんと積まれているのでした。 さすがに方針を考え直した方が良いかもわからんね……

% [独り言] だめですた

なんつーか、嫌々さ加減がバレバレだったみたいだす(苦笑

さて、そんなこんなで、いよいよニート生活まっしぐらになりつつあるんだが……誰か仕事ください。 原稿書きます。プログラムも書きます。ゲームもします。それは仕事ではないな。

やっぱさー、何がダメって、残念より安堵の気持ちの方が強いってのがダメだよなー。

% [雑談] わしがダメな 10 の理由

  1. いつも眠そう
  2. いつもダルそう
  3. いつもめんどくさそう
  4. 勤勉? ナニソレ?
  5. 怠惰? 遅延評価はそれほど好きじゃないな
  6. 何かに没頭すると周りが見えなくなる
  7. 几帳面なところをカッコ悪いと思っている
  8. わりとアナーキー
  9. 超夜型
  10. トイレが長い

10 個も考えるのめんどくさいな。 1 個で良いよ 1 個で。

  1. 大した能力も無いくせに、態度だけはスーパーハッカー並

あ……なんか自己嫌悪...orz

% [clip] Yahoo!にバカにされたwwww (イミフwww)

Yahoo! クオリティ高いなー(笑

% [vim][Ruby] refe.vim

たしか otsune さんか誰かのブックマークから。

vim で編集中に refe を参照する vim スクリプト。 Vim ユーザで Ruby ユーザなら鼻血が出るほど便利な感じ。 ありがたく使わせていただきます。

マニュアルに書いてあるように lookupfile.vim を入れると更にステキっぽい。 lookupfile.vim には genutils.vim も必要みたいなんで注意。

% [独り言] 何週間かぶりに早寝早起きモード

…に突入したみたいで、まだ九時前だってのに超眠い。

良いや、寝ちゃえ。 今日はショックを受けたんだよ。 ……受けたはずなんだ、たぶん。 だからショックで早々に寝込んでしまうのもアリだと思うのよ。

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

Before...

% jijixi [> わたしだと出ないですけど……。 えー!? 差別イクない。]

% shiro [CAPTCHAってのは商標でしたっけ。簡単なやつは既に多くが自動認識されちゃってるみたいなんで、いたちごっこになって..]

% jijixi [> あと、私もCAPTCHAは出ないので、ユーザの何らかのアクティビティを見てはいるのでしょうねえ。 あうー、とり..]


2007-01-30 [長年日記]

% [reddit] 例のアレが出なくなっていた件

今 reddit に submit したんだけど、例の画像が出なかった。 やったー、やっと快適になったぞ。

さて、結局どういうルールで出たり出なかったりするんだろうか。 想像してみよう。

  • カルマの高低
  • submit の頻度
  • 新規アカウントの場合、最初のうち何度かは必須

出なくなったタイミング的に、どれも可能性があるんだけど……どれが正解だったんだろう。 もしまたいずれ出るようなら、一つ目か二つ目ってことになるんだろうけど。 まあ、今後の推移を見よう。

% [game] セカキュー日記、吟遊詩人あなどりがたし編

そんなわけで、前回の宣言どおりバードを育てているんだが、実は思った以上に役に立つキャラだった模様。

通常、一時的に能力をアップさせるスキル (パラディンの防御陣形や、ソードマンのウォークライとか) は 5 ターンほどで効力が切れるんだが、バードのスキルは切れない。 少なくとも 10 ターン以上保つことは確認した。 この差は地味にデカい。 ザコ戦では大して意味は無いけど、ボス戦だとどうしても長丁場になるし、防御陣形が切れる隙がすごく危険なんだよな。 なんか竜退治には頼れる存在になりそうな気がしてきた。

今はアルケミストのところにバードを入れて育ててるんだが、次はレンジャーのところにカースメーカーを入れてみるのもおもしろいかも知れないなあ。 バードがこれだけ使えるってことは、表裏一体の能力を持つカースメーカーも行ける可能性十分だ。 このゲームって弱体化スキルを失敗することって無いみたいだし、敵操作系のいかにも趣味的なスキルに手を出さなければ普通に使えるキャラかもって気はする。

ところで、期待していたバードの属性付加スキルは、通常攻撃にしか効かないので微妙にショボいかも。 物理スキルにも効いてくれれば、ソードマンのトルネードとかでウハウハなのに。 しかしこうなってくると、ソードマンにはダブルアタックが欲しくなるんだよなー。 でも、前回休養したときに剣系に完全に絞った影響で (ダブルアタックには斧マスタリーも必要) ダブルアタックは諦めてる状態なんだよね。 また休養させっかー? しかしなー…… 思い切ってもう一人ソードマン作っちゃうかなー。 あーほんとキリがねーや、このゲーム(苦笑


2007-01-31 [長年日記]

% [Dylan] 例外処理がちょっとおもしろい

よくある try..catch 方式ではなくて、割り込み処理のように例外ハンドラを定義することで例外処理をするようになっている。 まあ、block 構文には exception 節があって try..catch 式のやり方も可能だけど。 ハンドラ方式の場合は、signal 関数によるシグナルを同一スコープ内 (…に無ければ、たぶん直近上位のスコープ) のハンドラが処理する形になる。 以下のような例を書いてみた。

% cat exception_test.dylan
module: exception_test

define class <my-exception> (<condition>)
end;

define function test1()
   let handler <my-exception> =
      method (cond, next)
         format-out("on handler1\n");
      end method;
   block (exit)
      let handler <my-exception> =
         method (cond, next)
            format-out("on handler2\n");
            exit();
         end method;
      signal(make(<my-exception>));
   end block;
   signal(make(<my-exception>));
end;

define function test2()
   block (exit)
      signal(make(<my-exception>));
   exception (cond :: <my-exception>)
      format-out("on exception clause\n");
   end block;
end;

define function main(name, arguments)
   test1();
   test2();
   exit-application(0);
end function main;

// Invoke our main() function.
main(application-name(), application-arguments());

これを実行すると、

% ./exception_test
on handler2
on handler1
on exception clause

こんな感じで、test1 内では先に block 内のハンドラが呼ばれてるのがわかる。

signal 関数は起こしたシグナルに対するハンドラが値を返す場合には、その値を返す。 要するに他の言語で言う、throw や raise なんかとは振舞いが違って、値が戻ってくる関数だということ。 そうじゃなく戻ってこなくて良い場合 (あるいは戻ってほしくない場合) には error 関数を使う。

シグナルハンドラが定義されていない場合には、default-handler という総称関数が呼ばれることになっていて、逆に言えばこの関数に対して特定の型用のメソッドを追加しておけば大域的なハンドラとして使うことができるということだろう。 例外処理についても、UNIX 的なシグナル処理についても、一つの仕組みで統一的に扱えるという寸法だ。

実はハンドラに指定するメソッドの、第二引数に何が入ってくるのかがよくわからんのだが、上記のコードのハンドラ内で


format-out("%s\n", next);

とか書いてやると、

{an instance of <method-closure>}

って結果になる。 一体何が詰まっていることやら。 リファレンス読んでもよくわからんのだよな。 もしかして継続?

例外処理関係では他にリスタートとか、リカバリープロトコルとかいう要素があるんだけど、何かいまひとつピンと来ない。 …ので割愛。 後でゆっくり考える。

% [雑談] Dylan って結構良い言語なのに、何で流行らなかったんだろう

Matz LISP であるところの Ruby が流行るなら、Apple LISP であるところの Dylan だって流行っても良かったはずなんだがなあ……とかね。

まあ結局、LISP が流行らない理由と同じで C/C++ と違いすぎるってことなんだろうな。 移行の障壁が高ければ高いほど普及の妨げになるわけで、世の中の大多数のプログラマが C やそれに似た文法の言語を最初に憶えてる事実を考えれば、ある意味仕方のないことというか。 Java が流行ったのだって、結局突き詰めれば C++ に見た目が似てるからだと思うよ、わしは。 見た目が似てて、似たような書き方すればそれなりに動く…となれば、「ちょっとやってみるか」という気になりやすいもんね。

Ruby の場合はオブジェクトの扱いなんかは C の構造体や C++ のクラスを知ってれば、それなりに憶えやすい文法なわけだけど、Dylan はあまりにもまんま CLOS だからヘタに C++ とかでオブジェクト指向の経験があったりすると気持ち悪すぎるんだろう。

あとは流行る流行らないには、マクロの存在も関係するんじゃないかという気がしてる。 実際には LISP 的なマクロって C のマクロとは全然別物だと思うけど、それでもやっぱりマクロで苦労したことのある C/C++ プログラマは "マクロ" という言葉を聞いただけで一歩引いちゃうんじゃないかと。 逆に「Ruby にはマクロがありません」って言われるとホッとするんだよ、きっと。


トップ 最新 追記

日記ってのは本来、自分で読み返すためにあるもんだよなあ……
もしくは有名人になったら死後に本になったりとかか?

RSS はこちら

jijixi at azito.com