第4回 慢性的CL勉強会[予習2]

6月 21st, 2008

THE POWER OF LISP MACROS
を読み終わったので適当にまとめておきます。
*全体として*
難易度は低めで、ソースコードが多いので、
英語が苦手な私でも結構読みやすかったです。
ただ、本当にソースだけが書いてあって、
その実行結果などが書いてなかったりするので、
少々不親切な感じはしました。
*疑問など*

(format t "Test ~A ~:[FAILED~;passed~].~%"
',name (progn ,@body))

formatが分かりません><

,@(when condition
`((,condition () (format t "Test ~A passed.~%"
',name))))

,@を使ってますが、内側はかならず1要素のリストなので、

,(when condition
`(,condition () (format t "Test ~A passed.~%"
',name)))

では駄目なんでしょうか。

`(format t " ~(~A~)~:[=\"~A\"~;~]"
',key (eq ,value t) ,value)

やっぱりformatが分かりません。

(define-compiler-macro add-vat (amount &key (tax-rate .19))
`(add-vat% ,amount ,tax-rate))

define-compiler-macroって何?

;;; 10. Don't try this at home
;;; --------------------------
;;;
;;; Macros for black belts
;;; - "On Lisp" 
;;; - Screamer 
;;; - Series 
;;; - Iterate 

家で勉強すると体に悪いんですね。分かります。
「black belts = 黒帯 = 上級者向け」ということでしょうか。
難しいから無理するなよってことですかね。
*感心したところ*

(defun new-double-quote-reader (stream macro-char)
(declare (ignore macro-char))
(with-output-to-string (out)
(loop for char = (read-char stream t nil t)
while (char/= char #\")
do (write-char
(cond ((char= char #\\)
(let ((next-char (read-char stream t nil t)))
(case next-char
(#\t #\Tab)
(#\n #\Newline)
(otherwise next-char))))
(t char))
out))))
(set-macro-character #\" 'new-double-quote-reader)

リーダーマクロはかっこよすぎると思います。

単純なマクロはすぐに分かるんですけど、バッククォートが入れ子になって、
「,’,hoge」とか「,@,foo」とかが出てくると少し理解に時間が掛かります。
こういうのはやっぱしソースを沢山見たり書いたりして慣れるしかないんでしょうかね。

第4回 慢性的CL勉強会[予習]

6月 20th, 2008

第4回 慢性的CL勉強会@Lingr 8時だョ!全員集合の資料の一つである
Common Lisp Pitfalls
を読み終わったので適当にまとめておきます。
*全体として*
Common Lispの勘違いしやすい点のまとめ。
既に知ってることから全く知らないことまで色々書いてました。
以前の慢性的CL勉強会ででてきた話なんかもありました(defvarとか)。
とりあえず、私には内容よりも英語の方が難しい(笑)。
*疑問など*

* Destructive functions that you think would modify CDRs might
modify CARs instead.  (Eg, NREVERSE.)

nreverseなどは一見CDRを書き換えてるように見えるけど、
実はCARを書き換えてるかもしれないといったことが書いてるんですが、
CARの書換でどうやってnreverseするのか分かりません。
– read-from-string, write-string, write-line, parse-namestring
どれ一つとして知りません(泣)
名前から想像すると、read-from-stringは文字列をストリームとして開くのでしょうか。
– EQUAL compares both vectors and structs with EQ.
equalって個々の要素の比較にはeqlを使ってたきがするんですが、eqと書いてあります。
手元のCLtL2(日本語版)によると
『数と文字はeqlによる比較を行い、他の全てのデータオブジェクトにはeqによる比較を行う』
ということをX3J13で決議したと書いてありました。
全部eqlでいいと思うんですが、なんでこんなややこしいことになってるんでしょうか。

* Some Common lisp operators use EQ, rather than the usual EQL,
in a way that cannot be overridden: CATCH, GET, GET-PROPERTIES,
GETF, REMF, REMPROP, and THROW.  See table 5-11 on p 5-57 of the
standard.

エイゴワカラナイヨ。

* The following trick for intercepting all unwinds is not portable
and might not be allowed at all:
(tagbody (unwind-protect (do-some-stuff)
;; Intercept all unwinds, ha ha!
(go where-I-say))
where-I-say
;; We always get here.
(do-some-more-stuff))
Informally: once an unwind to a certain point has begun, it must
always go at least that far.  [See CLtL pages 189-192.]  Similar
tricks using THROW or RETURN-FROM suffer the same fate.  I used
GO above because I think that makes the intention clearer.

こんなコードを書くなというのは分かったけど、
何で駄目なのかの説明がいまいち分かりませんでした。

* Remember that there are "potential numbers" and that they are
reserved tokens [CLtL pages 516-520].  Normally, only odd things
such as 1.2.3 or 3^4/5 are "potnums", but when *READ-BASE* is
greater than 10, many more tokens are effected.  (For real fun,
set your read base to 36.)

なにゆーてんの?

* You often have to declare the result type to get the most
efficient arithmetic.  Eg,
(the fixnum (+ (the fixnum e1) (the fixnum e2)))
rather than
(+ (the fixnum e1) (the fixnum e2))

(the fixnum e1)って一体何?
型宣言をその場でやってるってことでしょうか。
– proclaim, declaim
熱い説明が書いてあるけど、
どっちも知りません(泣)
– packages
これも前提知識がないのでさっぱり分かりません。
なんか大きく仕様が変更したということだけ聞いたことがあります。
– CLOS
これも前提知識がないのでさっぱり。
Flavorの説明なら少々読んだことがあるんですけど。
そういえば、CLOSの読みは「クロス」だと思ってたんですけど、
先日ある方に、「京都では”シーロス”って読むんだよ」と教えてもらいました。
*感心したところ*
– (let ((x 5)) (eq x x)) might return false
最適化の関係でletが消えてxが5に置き換わると、
nilになる可能性があるそうです。
– flet ((f …)) (eq #’f #’f)) can return false.
#’を使うと新しいオブジェクトが生成されるので、
関数の比較を行うのは難しいとか。

(let ((x 1))
(declare (fixnum x))
(let ((x ...))
;; This x must be a fixnum too!
...))

型宣言は変数(束縛)に対してではなく、名前に対して行われるため、
同じ名前で別の変数を作っても、宣言が有効になるらしいです。
ただし、これはCLtLの記述であり、ANSIでは変数に対して型宣言がなされ、
この場合は内側のxには型宣言は無視されるとか何とか。

6文字までのアトム

6月 18th, 2008

昨日、irc.freenode.net#Lisp_Schemeで、

17:40 (zick) 分かりにくい名前といえばrplacaなんか分かりにくいですよね。
(中略)
17:43 (g000001) 名前に6文字制限があったのでは?みたいな話も聞いたことがあるんですが、
17:43 (g000001) LISP1.5はユーザは、6文字以上の関数も定義できるし、
17:44 (g000001) 真相が知りたいと、思ってました、そういえば。

という会話があり、今日になって中西先生の「Lisp入門」に
LISP1.5のアトムの格納方法が書いてあったのを思い出し、
大学図書館に借りに行きました。
(これでこの本を借りるのはもう3、4回目です。いっそのこと
どこかで探して買ったほうがいいかも。)
それによると、
1)アトムの印字名はアトムの属性リストにPNAMEという属性として含まれている。
2)印字名は印字名リストによって構成される。
3)文字列はフルワード領域という異なる場所に格納される。
4)フルワード領域の一つの箱に収めることが出来る文字数は機械によって異なる。
とのようにあります。
36bitが1ワードの機械では恐らく1つの箱に6文字が格納されます。
たとえば、アトムAは
( APVAL Aの持つ値 PNAME (“A”))
のようなリストから構成されます。
ただし、はこのリストがアトムであることを示す特殊なデータ、
“A”は文字列Aを含むフルワード領域の箱への参照です。
この(“A”)の部分を印字名リストと呼びます。
同様に、アトム ABCXYZ123Q の印字名リストを考えると次のようになります。
(ABCXYZ 123Q??)
?は余ったところに入れる特殊な文字(NULL文字でしょうか?)を表します。
この図からわかるように、アトムの名前が6文字を超えると、
フルワード領域の箱、リスト領域の箱両方を余分に消費します。
これを考えるとアトムの名前をなるべく6文字以内に抑えたのも納得できます。

Common Lispの0次元配列

6月 17th, 2008

Common Lispではmake-arrayの引数にnilを指定すると、
0次元配列を作ることが出来ると知ったので試してみました。

>(setq a (make-array ()))
#0ANIL
>(setf (aref a) 99)
99
>(aref a)
99
>a
#0A99
>(type-of a)
(SIMPLE-ARRAY T NIL)

一体なんの役に立つのかは分かりませんが、
せっかく知ったので忘れないように書き残しておきます。
それはさておき、S式ベースC言語「SC」の開発者のサイトにいつのまにやら
『ニコ動でLisp』へのリンクを貼ってもらっていました。
ありがとうございます。
さらに笑ったのが、Scheme-user.jpの「定番サイト」に
はてなようせいとまなぶ Scheme の形式的意味論
はてなようせいとまなぶ R5RS の表示的意味論
が入っているんですけど、どのへんが定番なんですかwwww

話はさらに変わりますが先日あやしげなお店で、
「春日歩の通販生活final」
というソフトを見つけました。
パッケージ裏の説明を読んでもなんだか訳が分からなかったんですが、
その訳の分からなさに負けて思わず買ってしまいました(笑)
なんだかおまもりんごさんみたいだよなと思ってたら、
同じ人が作ってたみたいです。どおりで似てるわけです。
で、実際に起動してみたところ、
画面をひたすら眺めて、時々クリックをするだけなのに、
何故か結構面白くて妙な感じです。
なんだかエル・フィッシュを思い出しました。

第3回 慢性的CL勉強会[まとめ]

6月 15th, 2008

Lingrのログはこちら。
*疑問への回答*
今回も予習した内容に返答をもらえました。
ありがとうございます。
– loopマクロって多用するものなの?
多用するらしいです。
loopを使えてこそCLer?
– ignoreとignorable
この二つは変数を使わないときに出る警告を抑えるそうです。
Prologで言うところの、 _(anonymous variable) がignore、
_から始まる変数(underscore variable)がignorableのようです。
(パターンマッチをするわけではないので少々意味が違いますが。)
– #’car と ’car
#’の場合はコンパイル時にオブジェクトが決定されますが、
‘の場合は実行時にシンボルから関数を探すようです(だから#’の方が最適化される)。
fletでローカルな束縛を作っても’だと大域関数になるようです。
*その他の事項*
– lexprとかfexpr
「このあたりはzickさんが得意だと思うんですが」と書かれたので一応書いておきます。
昔のLispでは関数は次のように分類されたそうです。
+ subr : いわゆる組み込み関数。恐らくsubroutineの略。引数の数は固定
+ fsubr : 引数を評価しない組み込み関数。関数というよりむしろスペシャルフォーム。
+ lsubr : 引数の数が可変長の組み込み関数。引数は評価される。
+ expr : lambdaで作る関数。expressionの略という噂を聞いた。
+ fexpr : 引数を評価しないユーザ関数。マクロに近い。
+ lexpr : 引数の数が可変長のユーザ関数。引数は評価される。
fexprはnlambdaというスペシャルフォーム(fsubr?)で作ります。
lexprは確かlambdaで特殊な書き方をしたら作れたかと思います(これは自信がない)。
またexpr系統の関数をコンパイルすればsubr系統として扱われたかと思います。
このあたりの内容は
中西 正和 (著) 『Lisp入門―システムとプログラミング』
に全て書いてあります。興味がある方はこちらをご覧下さい。
ちなみに、私はこれを最初に学んだので、私の作った処理系(例えば地獄Scheme)
では、スペシャルフォームを作る関数がCreateFEXPRという名前だったり、
スペシャルフォームとしてnlambdaがあったりして、それで一部のスペシャルフォームを作っています。
*Lispマシンといえば*
今回の勉強会では話題に上がってなかったみたいですが、
Lispマシンといえば私はCDRコーディングを思いつきます。
CDRコーディングとはリストの要素がメモリ上で連続して配置してある際に、
メモリを節約する技術のことです。
(恐らく)多くのLispではCAR部とCDR部をセットにしたもの(ポインタ2つ分)をコンスセルとしますが、
CDRコーディングを用いる場合は、この半分のもの(ポインタ1つ分)を1つのセルとして扱います。
リストの要素がばらばらに配置されている場合はCAR部の次にはCDR部が配置されますが、
要素が連続している場合はCDR部ではなく次のコンスセルのCAR部を配置します。
こうすることによりメモリ使用量を減らせる上にキャッシュのヒット率も上がるという魂胆です。
これを実現するためには
「次のセルは現在のセルのCDR部(通常のコンスセル)」
「次のセルは次のセルのCAR部で、これが現在のセルのCDR部の参照先」
「現在のセルのCDR部はNIL」
「現在のセルは現在のセルが指す先にある(間接参照)」
の4つの状態を考える必要があるために2ビットを消費します。
(最後の一つはCAR部, CDR部を書き換えたときの対策です)
私もCで作った自作のLispインタプリタでCDRコーディングをやってみようかと思ったんですが、
CDRコーディングに2ビットもとられると1語32ビットに1つのセルを入れるのがなかなか辛くて、
どうしたものかと考えてみて、実際にCDRコーディングをしているSymbolic3600を見てみると、
このマシンは1語が36ビットあるから余裕だったようです。
これが……専用マシンか、と少し悔しくなりました(笑)

第3回 慢性的CL勉強会[予習]

6月 14th, 2008

第3回 慢性的CL勉強会@Lingr 8時だョ!全員集合の資料の一つである
「Common Lisp における例外処理 〜Condition System の活用〜」
を読み終わったので適当にまとめておきます。
*全体として*
CommonLispの例外処理の話。
例外を発生させてハンドルするというのは他の言語とそれほど変わらないと思いますが、
デバッガを立ち上げるかどうかを制御したり、デバッガをプログラマが定義したものに置き換えたり、
エラー発生後に行うこと(処理を止める、値を再入力させる)を
ユーザにインタラクティブに問い合わせる仕組みがあるのが
なんとも独特で凄い感じでした。
*疑問など*
例外処理自体の説明は非常に分かりやすかったのですが、
例外と関係ないところが分からなかったりしました(笑)
– loopマクロって多用するものなの?
なんだかサンプルのソースコードに山ほどloopマクロが出てきたんですが、
CommonLisp使いってloopマクロを多用するのでしょうか。
loopマクロってなんだかそれ自体が一つの言語に見えるくらい、
いろいろ出来るみたいで大変そうなんですけど。
– (lambda (cond) (declare (ignore cond)) …)
これは引数condは使用しないっていう意味の宣言なんでしょうか。
うまく最適化されるのかな。
– (let ((#:G1000 nil)) (declare (ignorable #:G1000)) …)
上と同じ解釈をすると、#:G1000は使うかもしれないし、
使わないかもしれないといった意味になるんでしょうか。
– #’car と ’car
#’carは(function car)、’carは(quote car)というのは分かるのですが、
関数が必要とされる箇所でこの二つって区別されましたっけ?
lambda式につける場合は#’でないと駄目なのは明らかなんですが、
シンボルに対してはどうだったっけ……。

「第2回 突発性CL勉強会@Lingr 8時だョ!全員集合」が終わって

6月 8th, 2008

事前に質問などを書いていたら、なんと答えてもらえました。わーい。
適当にまとめておきますが、詳細はLingrのログをご覧下さい。
> コンディション
> format
以下を参照
http://gigamonkeys.com/book/
> 型情報
clisp等はあまり警告してくれないが、
cmucl/sbclとかだと、型の指定をすると結構警告を細かく出してくれる。
> add-to-listの問題
pushnewをつかえばいい。
pushnewは追加する要素が既に存在しないときのみpushを行う。
> – 実行時の制御(しかしGensymはそれを行なった)
Gensymという会社があって、Real-time制御にLispを使って成功した。
http://portal.acm.org/citation.cfm?id=114669.114679
> – 小さなイメージのアプリケーションを配達する
巨大なイメージを抱えこんでいるので、
単体のexeファイルとして配布するのには都合がわるい。
> eval-when
eval-whenはコンパイル時とロード時と実行時の各々の状況で、
評価したりしなかったりを制御できる構文。
http://gigamonkeys.com/book/the-special-operators.html
> 逆に言えばチルダの後って大文字でも小文字でも大丈夫ってことでしょうか?
# ~aでも、~Aでも意味的には同じ。
~&は、先頭位置でなければ、改行するというもの。
> tconc
末尾へのポインタと、ヘッドへのポインタを保持するもので、
末尾への追加コストが低くできるという。
http://www.ece.uci.edu/eceware/cadence/sklanguser/chap8.htm

最後の方に

そうだ、zickさんはLISPの処理系つくるの大好きじゃないですか、
是非、教えてください!

なんて書かれました(そのとき席を離れてて返信できなくてごめんなさい)
NScripterかニコスクリプトでよければwwww
Lispインタプリタ作成に役立つ本としてSICPを挙げる人が非常に多いようですが、
私としては中西 正和 (著) 『Lisp入門―システムとプログラミング』を推奨します。
「Lispは入門できないが、Lispインタプリタは作れるようなる悪魔の本」
と勧められて読みましたが、確かにその通りでした(笑)
入手困難かもしれませんが、大学の図書館に行けば確実にあるかと思います。

「第2回 突発性CL勉強会@Lingr 8時だョ!全員集合」に向けて

6月 7th, 2008

本日午後8時よりLingr: Common Lisp部屋にて開かれる
「第2回 突発性CL勉強会@Lingr 8時だョ!全員集合」
楽しくROMできるように、そこで使われる題材
「良いLispプログラミングスタイル」
を少し予習をしておきました。
とはいえ、全体の半分くらいを適当に流し読みしただけですが(笑)
適当に、分からないこと、感心したことなどをまとめておきます。
*分からないところ*

悪い: 非慣習的
(defun add-to-list (elt lst)
(cond ((member elt lst) lst)
(t (cons elt lst))))
良い: 組み込み関数を用いる
(練習問題として残してある)

union使えばいいんでしょうか?

現在のLispの実装は、以下に対してはそれほど良くない:
- 小さなイメージのアプリケーションを配達する
- 実行時の制御(しかしGensymはそれを行なった)

この二つの意味がいまいち分かりません。
イメージや配達ってどういうこと?

EVAL-WHEN

eval-whenはよく取り上げられるけど名前しか知らない orz

Lispは以下に対して良い:
- 単一のプログラマ(または10人未満のチーム)のプロジェクト

これってLisp使える人が少ないだけ……というのとは違うんでしょうか。

コンディションとエラーの違いを理解する

CommonLispのエラーの仕組みというのを全く知らない。
R6RSの話を少し思い出した。

- 囲まれている小文字のテキストから目立たせるために、大文字でformat指示
子を置くことを考慮すること。
たとえば、"Foo: ~a"ではなく"Foo: ~A"とする。
- 有用なイディオムを学ぶこと。たとえば: ~{~A~^, ~}や~:pである。
- ~&と~%をいつ使うかを意識すること。
また、"~2%"や"~2&"も便利である。
単一の行を出力する多くのコードは~&で始まり、~%で終わるべきである。

逆に言えばチルダの後って大文字でも小文字でも大丈夫ってことでしょうか?
私は~Aと~%くらいしか知りません。「~&」って一体何者?

共通の抽象データ型からLispの実装へどのように対応づけるかを知る。
- キュー: tconc、(フィルポインタをもつ)ベクタ

tconcって何?

catchとthrowを使用に対するいくつかの忠告がある:
- マクロとしてより抽象的な制御構造を実装するときに副プリミティブとして
catchとthrowを用いること。通常のコードの中では用いないこと。
- ときどき、catchを設定するとき、プログラムはその存在をテストする必要
があるかもしれない。その場合、再起動がより適切かもしれない。

catch/throwはマクロで使う物であって通常のコードでは使うものではないってことでしょうか。
なんでこんなに嫌われて(?)いるんでしょうか?

悪い: インライン関数であるべき
(defmacro name-part-of (rule)
`(car ,rule))

インライン関数とマクロってどう使い分けるべきなんでしょうか。

- ジェネレーションスキャベンジングGCは、破壊的な更新によって遅くなり得
ることに注意すること。

世代別GCというやつのことでしょうか。
これだと作ってすぐに捨てるものが大量にあっても、あまり問題にならないというのは分かりますが、
これが破壊的な更新によって「遅くなり得る」とはどういうことでしょうか。

マルチタスクの環境ではしばしばもう少し注意深くなければな
らない。
(unwind-protect (progn form1 form2 ... formn)
(cleanup1 cleanup2 ... cleanupn))
- form1が実行されると仮定しないこと。
- formnが実行して完了することはないと仮定しないこと。

さっぱり分かりません orz
そういえば、マルチタスクのための構文ってCLに標準で入ってるんでしたっけ?
*感心したところ・驚いたところ*

型情報がわかっている場合、それを宣言すること。他の人々が行なうようなこ
とや、コンパイラが使うだろうとわかっているものだけを宣言するようなこと
を行なわないこと。コンパイラは変化し、プログラムが進行中の介在を必要と
せずにそれらの変更を自然に利用させたい。
また、宣言は人間の読み手とのコミュニケーションのためのものでもある -
単にコンパイラのためではない。

なんだかLispならではの意見ですよね。
型付けが動的なのに、型を宣言できるというのがなんとも。
型宣言があれば読みやすいというのには納得。

再ロード時に再初期化したくない物事に対してdefvarを使うこと。

defvarって再ロードしても値を書き換えなかったんですね。
(変数が未束縛のときのみ値を設定するらしい)

(catch 'robot-op
(let ((status (motor-status motor)))
(unwind-protect
(progn (turn-on-motor motor)
(manipulate motor))
(when (motor-on? motor)
(turn-off-motor motor))
(setf (motor-status motor) status))))

unwind-protectの引数のインデント。
第一引数だけインデントが少し深くなっていて、
なんかおかしい気もするけど、意味を考えたら物凄く読みやすい。
第一引数だけ特別な意味を持つような関数・スペシャルフォームには
こういったインデントを使うのがいい気がした。

クロージャをもつLispやその他の言語(たとえば、ML, Sather)は以下をサポー
トする:
- 制御抽象(イテレータやその他の新たな制御フロー構造を定義する)

高階関数を使うことだと思うけど、制御抽象って言い方はなんかカッコイイ。

(defstruct (rule (:type list))
name antecedent consequent)

こうすると構造体の実体がリストになるんでしたっけ。
なんかIRCの#Lisp_Schemeでこの話は聞いた覚えがあります。

再帰は再帰的なデータ構造に対して良い。多くの人々はリストを列として見る
ことを、そしてそれの上に繰り返しを用いることを好み、したがって、リスト
が先頭と残りに分割されるという実装の詳細を強調しないことを好む。

リストといえば、先頭と残りを分けるというイメージがありますが、
mapとかreduceとかの存在を考えると、やっぱり分割を強調しない方が
簡潔でいいということでしょうか。

表現力豊かなスタイルとして、末尾再帰がエレガントだとしばしば考えられて
いる。しかし、Common Lispは末尾再帰の除去を保証しないので、完全に移植
可能なコードの中では繰り返しの代替として使われるべきではない(Schemeの
中では問題ない)。

過去に末尾再帰をかなり使ったCommonLispのプログラムを
CLISPでコンパイルせずに動かそうとしたら、末尾再帰の最適化が成されてなかったみたいで、
実行時にスタックがあふれました。コンパイルしたらすんなり動きました。

つづきはWebで。

パーサのバグを修正

5月 30th, 2008

* *scrap*でニコ動Lispのバグを指摘されました。

(defun if0 (n a b) (if (eq 0 n) a b))
(if0 0 1 2) ;=> 2
((lambda (n) (if (eq 0 n) 1 2) 0) ;=> 2
((lambda (n) (if (eq n 0) 1 2) 0) ;=> 1

(下2行は括弧が足りませんが、ただの誤字でしょう。)
試してみると、たしかにその通り。
youzさん、報告ありがとうございます。
ソースを読んでみると、
数字→スペース→文字(数字以外)
の順番で文字を読み込んだときの処理というのがまるまる抜けていました。
(eq 0 n)がリードされると(eq 0)になっていた。これは酷い。
ついでにevalもつくっておきました。評価にはevalを呼んだときの環境が使われます。
面倒だったので、長時間版の方しか変更していませんが、いいですよね。

ニコ動Lispを公開してから、いくつかバグを見つけては修正しているんですが、
そのほとんどの原因がパーサだったりします。
パーサは最初に書き始めた箇所で、あまり何も考えずに
「動けばいいや」という精神で書いたんですが、
それが後になって苦労の種となってしまいました。
ソースが手抜きなのはともかく、せめて設計だけでも先に真面目にやってればよかった……

ニコ動Lisp 2.0[長時間版]

5月 30th, 2008

3分では短いとのことなので、9分6秒のものを作りました。

動画を作った後で気づいたんですが、
「ニコスクリプトは1000行まで」(ただし1行に複数命令を書ける)
という制約があり、インタプリタ本体で150行使用し、
「○分○秒に処理をする」というのには1行を使用するため、
0.5秒に1回処理をするように作っていたら、
7分5秒(+初期化分)しか処理が出来ないことになります。
処理を1秒に2回(同じ時刻に行うので1行で書ける)にすると、
倍の14分10秒まで処理を続けることが出来るので、この方法を採用しました。
しかし、同じ時刻に処理を行うと、表示が後からの処理の方しか画面に反映されず、
少々かっこ悪くなりました(特にパース時が顕著です)。
また、処理自体も重たくなった気がします(これは推測)。
そんな訳で、長時間処理を続けるインタプリタを
ニコ動上で動作させるのは色々と問題がありそうです。