長さの情報を持つ配列型とか

1月 16th, 2011

Common Lispでは変数の型を明示的に指定することができます。

(defun a3 (x)
  (declare (type (array * (3)) x))
  x)

こう書くと、関数a3の引数xの型は「長さ3の配列」という意味なります。
これをSBCLで動かしてやると

(a3 123)  ;=> ERROR
; The value 123 is not of type (VECTOR * 3).

このように型の違うものでa3を呼び出すとエラーを検出できます。
このエラーの検出は実行時に行われています。
そのため、関数を挟むと、その関数を呼び出すまでエラーを検出できません。

;;; a3に整数を渡す関数
(defun f1 (x)
  (declare (type integer x))
  (a3 x))  ;=> 問題なく動作 (エラーを検出できない)

こうなってしまう原因は「xの型は長さ3の配列である」という情報が、
関数a3の内側にしか伝えられないためです。
declaimを使ってa3の型を外側に伝えてやることで、この問題は解決できます。

(declaim (ftype (function ((array * (3))) t) a3)
(defun a3 (x)
  (declare (type (array * (3)) x))
  x)

(defun f1 (x)
  (declare (type integer x))
  (a3 x))  ;=>
; caught WARNING:
;   Asserted type (VECTOR * 3) conflicts with derived type
;   (VALUES INTEGER &OPTIONAL).

コンパイラが型がおかしいことをちゃんと伝えてくれました。
これで「コンパイル時に型チェックが入る言語以外でプログラムを書きたくない」
という人も安心してCommon Lispが使えます。

安心したところでもう少しプログラムを書いてみます。

(declaim (ftype (function (array) t) a*))
(defun a* (x)
  (declare (type array x))
  (a3 x))

引数に配列をとる関数a*を定義しました。
a*の引数は「配列」というだけで、長さは指定していません。
しかし、a3の呼び出しに対して(SBCLでは)警告が出されることはありません。
それでは、a*を使ってみます。

(a* (make-array 3 :initial-element 999))  ;=> #(999 999 999)
(a* 123)  ;=> ERROR
; The value 123 is not of type ARRAY.
(a* (make-array 4 :initial-element 999))  ;=> ERROR
; The value #(999 999 999 999) is not of type (VECTOR * 3).

a*に配列以外を渡すと、a*に怒られ、
a*に長さ3以外の配列を渡すと、a3に怒られます。
しかし、(SBCLでは)コンパイル時に前者しか検出できません。

;;; a*に整数を渡す関数
(defun f2 (x)
  (declare (type integer x))
  (a* x))  ;=>
; caught WARNING:
;   Asserted type ARRAY conflicts with derived type (VALUES INTEGER &OPTIONAL).

;;; a*に長さ4の配列を渡す関数
(defun f3 (x)
  (declare (type (array * (4)) x))
  (a* x))  ;=> 問題なく動作 (エラーを検出できない)

なんということでしょう。ちょとガッカリな結果です。
この結果を利用して、非常に残念な関数を定義できます。

(declaim (ftype (function ((array * (4))) t) a4)
(defun a4 (x)
  (declare (type (array * (4)) x))
  (a* x))

この関数a4は「長さ4の配列」を受け取る関数なので、
長さ4の配列以外を渡すと怒られます。
しかし、a4は最終的にa3を呼び出すので、
長さ3の配列を渡さないと怒られます。
つまり、どんなものを渡しても怒られるというわけです。

C言語(GCC)も似たような動きをします。

int a[3], (*p)[3], (*q)[], (*r)[4];
p = &a;
r = q = p;

長さ不定の配列へのポインタを中継することで、
長さ3の配列のアドレスを長さ4の配列へのポインタに代入できてしまいます。
ちなみに、C++(G++)だとコンパイルエラーになります。
G++といえば、

class C1 {} c1;
class C2 { int a[0]; } c2;

このようなクラスを作ったときに、
sizeof(c1) > sizeof(c2)が成立するおもしろ言語なのに生意気です。

今年1年を振り返る 2010

12月 31st, 2010

適当に今年の出来事を10個集めてみました。
リンク先には一部、(あまり)関係の無いものが含まれています。

マンガで分かるLispがたくさん
今年の更新の半分以上がこれな気がします。

自転車でまた琵琶湖一周
去年の「次走るときはもう少し良い自転車で走りたい。」という願望が実現。クロスバイクを購入し、今年も琵琶湖を回りました。ママチャリと比べて非常に楽です。

新しい研究室に入った
全国有数の「Lispな研究室」に入りました。楽しいです。

COMFRK vol. 1に記事を寄稿した
Prolog楽しいです。

リリカル Lispのソースをgithubで公開
流行りに乗ってgitとか使ってみたものの、さっぱり分かりません。

Googleのサマーインターンに参加
2ヶ月で体重が4kgも増えました。Google怖い。東京怖い。

ILC 2010で発表
英語が全く話せないのにアメリカに行きました。

またBiwaSchemeでゲーム作った
アクションゲームも作れるBiwaSchemeはすごい。

よくみると出来事が10個集まってない
ブログエントリを全然書かなかったせいで、ネタが10個集まりませんでした。来年はもう少し頑張ります。

またBiwaSchemeでゲーム作った

12月 21st, 2010

またBiwaSchemeでゲームを作りました (以前作ったのはこれ) 。
Internet Explorer以外のまともなブラウザなら多分動作します。

Hockey in BiwaScheme

画面右の “open” をクリックすると、BiwaSchemeの対話環境が表示され、
そこに式を打ち込んで “eval” を押すことで変数の値を書き換えたり、
手続きの定義を書き換えることができます。
最初は、テキストボックスに “(set! ball-vy -10)” が入っており、
この状態で “eval” をクリックすると、ボールが勢い良く上に飛んでいきます。

実はこれ、以前Erlangで作ったゲームをそのまま移植しただけです。
Erlangで書いたときは通信対戦をできるようにしていたので、
今回のBiwaScheme版でもTupleSpaceを使って何とかできないか、
と挑戦はしてみたものの、うまくいかなかったため、
泣く泣く通信対戦の機能は削ってしまいました。

ILC 2010行ってきました

10月 25th, 2010

カタカナ英語の発音でもそれなりに通じたようです。
(スライドにちゃんと文字が書いてるし。)
質問は、それなりに聞き取れました。多分。
でも、返答を考えながら英語を話そうとした結果、
説明と英語の両方が崩壊し、訳が分からないことに。
質問された方は苦笑しながら”OK.”といってくれました。

大体そんな感じです。

ILC 2010行ってきます

10月 15th, 2010

ILC 2010で発表をすることになりました。
英語は全く聞き取れず、発音はカタカナ英語の発音という、
素晴らしい英語力の私が、国際会議で発表を行うというのは非常に迷惑な話なのですが、
ここは人の迷惑を顧みないことにします。

次世代のリリカルLispを作るのはあなたです

8月 22nd, 2010

先日、COMFRK vol. 1を買いに来た方が
「リリカルLispは拡張したりしないんですか」
と質問されたそうです。

私にはもうリリカルLispのソースを触る元気は残っていません。
githubにソースを置いておいたので、自由に改造して下さい。

http://github.com/zick/Magical-Language-Lyrical-Lisp

流行に乗ってgitとか使ってみたけど、さっぱりわかない。
commitとpushってどう違うんだ。

[PrologでSchemeの操作的意味論を実装]Errata

8月 15th, 2010

昨日、無事完売しましたCOMFRK vol. 1の私の記事で
誤字を見つけたので書いておきます。
他に誤字を見つけた方がいましたら、コメントを残してもらえると助かります。

(5ページ目 右側 [おわりに])
– 誤: x=a+b
– 正: x=a-b

[宣伝]COMFRK vol. 1

8月 14th, 2010

今更ながら、宣伝。

名前: COMFRK
日時: 8/14(土) 二日目
場所: 東ア47a
内容: 雑誌1部 COMFRK vol. 1

1. 夏休み子供λ相談室 by ranha
2. Haskellコミュニティ探訪 – 処理系とライブラリを中心にして – by shelarcy
3. 差分のアルゴリズム by cubicdaiya
4. メインメモリアクセスマニュアル by nish
5. C++0xの空、Variadic Templatesの夏 by lyrical logical
6. PrologでSchemeの操作的意味論を実装 by zick
7. ゲームオーバーのすゝめ by mascalade
8. ???

(埒を明ける日々)

という訳で
「PrologでSchemeの操作的意味論を実装」
という記事を書かせていただきました。
本来ははてなようせいとまなぶ Schemeの形式的意味論に載せるつもりだった、
Prologのソースコードの解説です。
大体こんな感じの内容です。

式Mのxをyに置き換える

7月 17th, 2010

λ計算だとか、数学よりの話をするときはよく、

式Mのxをyに置き換えたものを
M[x:=y]
で表す.

なんて定義が出てきます。
本によっては、まったく同じことを表すのに、
[y/x]M
だとか、
M[y/x]
といった記法も使います。

本日、このM, x, yの順番に、意味がある(かもしれない)ことに唐突に気づきました。

Mxyに置き換える
M[x:=y]

M[x:=y]は日本語表記と完全に一致するじゃないですか、
なんか読み易いと思ったらそういうことか。

それから、[y/x]Mは英語だと意味をなします。

Substitute y for all x in M.
[y/x]M

完全に一致。

しかし、そうなるとM[y/x]が浮いてしまうんですが、
これには何か意味があるんでしょうか。
謎です。

#+のちょっといい話

7月 13th, 2010

Lispでは式1個だけをコメントアウトしたいことがよくあります。

(list A B C)

このようなプログラムで、Bをコメントアウトするときに、
行コメントをつかうと非常に不格好になります。

(list A ;B
  C)

ブロックコメントコメント #| … |# を使えばもう少し奇麗に書けますが、面倒です。

(list A #|B|# C)

Schemeの場合、R6RSで式コメントが書けるようになりました。

(list A #;B C)

非常に簡潔です。素晴らしい。

Common Lispで同じことをする場合、#+を使います。
#+はCで言うところの#ifdefであり、環境に依存するコードを書くときによく使います。

(defun do-something-quickly()
  #+allegro ACL専用の処理
  #+sbcl SBCL専用の処理
  #+clisp CLISP専用の処理
  #-(and allegro sbcl clisp) (error "Use ACL, SBCL or CLISP.")
)

#+X Yと書くと、(member X *features*)が真であれば、Yがリードされ、
偽であればYは読み飛ばされます。
*feature*に含まれないシンボルXをわざと書けば、式コメントが実現できます。

;; 例1
(list A #+nil B C)
;; 例2
(list A #+ignore B C)
;; 例3
(list A #+comment B C)

多くの場合、上の3つの例はうまく動いてくれますが、
万が一、*feature*にnilやignoreやcommentが含まれている場合、Bがコメントアウトされません。

どうしたものかと思いつつ、ずっと #+nil を使い続けていたんですが、
どんな環境でも確実にうまくいく方法を見つけました。

(list A #+(or)B C)

#+(or)です。
#+(or X Y …) Zと書くと、
(or (member X *features*) (member Y *features*) …)
が真のときのみ、Zがリードされます。
つまり、orに引数を与えなければZは常にリードされません。
これで、*features*にnilが含まれていないか心配で眠れない夜ともおさらばです。