ファミコンエミュレータ for Java
http://d.hatena.ne.jp/tanakh/20050506#p2
のあたりで開発したファミコンエミュレータをJavaに移植しました。
ここで公開中
http://github.com/tanakh/bjne-java
jarもあります。
http://github.com/tanakh/bjne-java/downloads
オリジナルのC++版。
http://github.com/tanakh/bjne
セーブ機能意外はすべて移植しました。
マッパは#0〜4と、VRC6を実装しました。VRC6は趣味です。
割と動いてるんじゃないかなあと思います。
Javaで辛かったこと
- 符号なし整数型がない。
メモリは基本的にbyteの配列で持つことになりますが、そこから取り出すときに気をつけないと負の数になってしまったり、レジスタをbyteなどで持ちますがこれも気をつけないと負の数になったり、アドレスが16ビットだけど、これも気をつけなければ負の数になったりとか、とかく短い長さの符号なし整数を多用するエミュレータではしんどいです。気を付ける箇所が無数にあるのに、コンパイラは何も言ってくれないので、とても怖いです。
- プリプロセッサがない
CPUのエミュレーション部分など、似たような箇所が多いので、C++版ではマクロでまとめていたところがあまりうまく書けなかった。
- プリミティブ型の参照がない
書き換える変数をパラメータ化できない。load(regHoge, addr)とかみたいなのができないので、loadHoge(addr)とかをレジスタごとに作ったり…。
どうしたものか。
Haskellでメモ化を行うもう一つの方法
はじめに
Haskell で動的計画法を書くための3つの方針 - tosの日記
これを読んで、私もちょっと前にHaskellでメモ化をやる方法を考えていたことを思い出したので、書いてみることにします。
Haskellでのメモ化は、私のかなり昔の駄文(リアルにびっくりするほど駄文なのでご注意。メモ化の綴りも間違ってます)や、このあたりに日本語の文章があります。
これらのページでのメモ化実現方針は、1. 計算済み値を保持するテーブルをMapなどを用いて用意する 2. そのテーブルを副作用を用いて更新する、というものになっています。なるほど、これは手続き型言語との対比でとても直接的な実装です。しかし、テーブルを更新するために関数全体がモナドになってしまったりして、あまり使い勝手が良くなさそうです。モナドであることを悟らせないために、演算子をモナド化したり、あるいはモナドじゃなくするためにunsafePerformIOを使うなどのトリックも考えられますが、どちらもあまり綺麗には見えません。
メモ化においてやりたい事は、テーブルの作成でもテーブルの更新でも無くて、一回計算した値がもう一度計算されないようになっているということです。一度計算したものがもう一度計算されない―――おあつらえ向きの物がHaskellにはあります。そう、遅延評価です。
基本的な考え方
簡単のために、メモ化関数の引数は1つの自然数とします(この制約は後である程度ゆるくします)。無限のサイズのテーブルがあると考えます。そこに、その関数(fとしましょう)のすべての引数に対する値が格納されています。0番目の所にはf 0、1番目のところにはf 1、…というふうにすべて入っています。遅延評価を用いて、各々の値は参照されるまで計算されないようにします。f n を計算したければ、そのテーブルをルックアップするだけです。そのノードが参照されるのが初めてならその時点でf nが計算され、二回目以降ならすでに計算された値が返されます。問題は、そのテーブルをどうやって表現するかです。効率的にルックアップ出来なければいけませんし、計算されていないノードは存在しないようにしなりません。
無限リストからの類推で、無限ツリーというものを考えます。無限ツリーはノードが無限にあるツリーです。ここではリーフはないとします。つまり、ノードはすべて内部ノードで、子を持っています。その特殊な場合として、無限完全二分木を考えます。すべてのノードが2つの子をもち、無限に広がっているような木です。これにヒープよろしく、ルートには0を、その子には1と2、さらにその子には3と4と5と6…というように番号をふります。そして、その対応する番号を引数とする関数の値をノードに持たせます。これは完全にバランスした二分木なので、O(log n)でルックアップできます。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...
実装
メモ化のインターフェースは、fixと同じものを採用します(fixというのは、ここではControl.Monad.fixを指しています。これは一般には"不動点演算子"と呼ばれるものです。不動点演算子に関しては、私のブログの駄文や、Googleに聞いてみてください)。fixをこれから実装するmemofixに挿げ替えるだけで関数がメモ化されるという目論見です。利用例は次の様になります。
-- normal version fib = fix $ \f n -> if n<2 then n else f (n-1) + f (n-2) -- memoised version memofib = memofix $ \f n -> if n<2 then n else f (n-1) + f (n-2)
まずは無限完全二分木を定義します。これはリーフを考えなくていいので、とても簡単です。
data Tree a = Tree a (Tree a) (Tree a)
次に、このツリーのルックアップを定義します。添字の上位ビットからみて、ツリーを辿っていくだけです。
findTree :: Tree b -> Integer -> b findTree tree ix = f (bits $ ix + 1) tree where f [] (Tree v _ _) = v f (0:bs) (Tree _ l _) = f bs l f (_:bs) (Tree _ _ r) = f bs r bits = tail . reverse . map (`mod`2). takeWhile (>0) . iterate (`div`2)
次に、関数の値がすべて詰まった無限ツリーの生成です。ノードの値を計算するための関数を渡してもらって、適当に全部生成するだけです。遅延評価がすべて何とかしてくれます。
genTree :: (Integer -> b) -> Tree b genTree f = gen 0 where gen ix = Tree (f ix) (gen $ ix*2+1) (gen $ ix*2+2)
さて、あとはメモ化コードを書くだけです。これもとてもシンプルです。
memofix :: ((Integer -> b) -> (Integer -> b)) -> (Integer -> b) memofix f = memof where memof = f $ findTree tbl tbl = genTree memof
memofがメモ化versionのfで、tblがすべての答えの詰まった無限ツリーです。tblは先程のgenTreeにmemofを渡してやれば出来上がりで、memofはfにtblをルックアップする関数を渡してやればいいのです。簡単でしょ?
memofib = memofix $ \f n -> if n<2 then n else f (n-1) + f (n-2) main = print $ map memofib [(0 :: Integer)..100]
実行してみます。
$ ghc --make Main.hs && time ./Main [1 of 1] Compiling Main ( Main.hs, Main.o ) Linking Main ... [0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986,102334155,165580141,267914296,433494437,701408733,1134903170,1836311903,2971215073,4807526976,7778742049,12586269025,20365011074,32951280099,53316291173,86267571272,139583862445,225851433717,365435296162,591286729879,956722026041,1548008755920,2504730781961,4052739537881,6557470319842,10610209857723,17167680177565,27777890035288,44945570212853,72723460248141,117669030460994,190392490709135,308061521170129,498454011879264,806515533049393,1304969544928657,2111485077978050,3416454622906707,5527939700884757,8944394323791464,14472334024676221,23416728348467685,37889062373143906,61305790721611591,99194853094755497,160500643816367088,259695496911122585,420196140727489673,679891637638612258,1100087778366101931,1779979416004714189,2880067194370816120,4660046610375530309,7540113804746346429,12200160415121876738,19740274219868223167,31940434634990099905,51680708854858323072,83621143489848422977,135301852344706746049,218922995834555169026,354224848179261915075] real 0m0.012s user 0m0.010s sys 0m0.000s
動きました。メモ化しないと、
fib = fix $ \f n -> if n<2 then n else f (n-1) + f (n-2) main = print $ map fib [(0 :: Integer)..100]
$ ghc --make Main.hs && time ./Main [1 of 1] Compiling Main ( Main.hs, Main.o ) Linking Main ... ^C
帰って来ず。
添字の一般化
添字が自然数1つというのは嬉しくありません。自然数以外や二つ以上の引数を扱うことを考えます。無限完全二分木以外のデータ構造を考えるのが面倒なので、任意の引数を自然数に変換することを考えます。つまり、当該データ型のすべてのデータに番号を振ってやって、その番号によってツリーをルックアップするのです。もちろんこれはその型が可算無限集合でなければいけないのですが、可算無限でない引数をメモ化の引数にするのはここでは考えないことにします。複数の引数はそのまま扱うのはとても難しいので、タプルにするということで妥協します。
自然数に変換できるというクラスMemoIxを定義します。逆変換も後で必要になるので用意しておきます。
class MemoIx a where index :: a -> Integer unindex :: Integer -> a
ところで、Haskellの標準ライブラリにIxというクラスがあります。MemoIxと同様に値に対して整数を振るためのクラスですが、これを利用するには値の上限・下限が必要になります。遅延評価でせっかく無限のものを扱えるようになったのに、値の上限・下限を要求されるのは大変イケてないので、これをそのまま使うことは出来ませんでした。
具体的な型に対するインスタンスを書きます。まずはIntegerです。Integerは整数であって自然数ではないので、自然数に変換してやる必要があります。正の数を偶数に、負の数を奇数にエンコードすることにします。
instance MemoIx Integer where index n | n>=0 = n*2 | otherwise = -n*2-1 unindex n | n`mod`2==0 = n`div`2 | otherwise = -((n+1)`div`2)
次にタプルを考えます。タプルはペアで表現できるので、ペアのみを考えます。ペアにうまく番号を振るには、次のように斜めに振ってやれば良いです(方法は他にも色々考えられます)。
| 0 1 2 3 ... -+----------- 0| 0 2 5 9 ... 1| 1 4 8 ... 2| 3 7 ... 3| 6 ... .| ...
この番号を頑張って計算します。unindexで二次方程式を浮動小数を用いて解いているのですが、これのせいでIntegerのメリットが失われているのがあまり良くないです(何とかならないでしょうか)。
instance (MemoIx a, MemoIx b) => MemoIx (a, b) where index (a, b) = l*(l+1)`div`2 + ib where ia = index a ib = index b l = ia+ib unindex ix = (unindex ia, unindex ib) where l = floor ((-1 + sqrt (1 + 8 * fromIntegral ix))/2) ib = ix - l*(l+1)`div`2 ia = l-ib
ところでこのコードは自然数への変換の際にかなり数が大きくなります。これは大丈夫なのでしょうか?入力a, bに対して、出力の大きさはO(ab)程度です。ということは、これをルックアップするのはO(log(max(a, b)))程度ということになります。なので、大丈夫そうです。
文字列型など他の型のMemoIxのインスタンスも考えられますが、それぞれ皆様各自お考え下さいませ。
さて、後はツリーとメモ化コードをMemoIxに対応させれば完成です。
findTree :: MemoIx a => Tree b -> a -> b findTree tree ix = f (bits $ index ix + 1) tree where f [] (Tree v _ _) = v f (0:bs) (Tree _ l _) = f bs l f (_:bs) (Tree _ _ r) = f bs r bits = tail . reverse . map (`mod`2). takeWhile (>0) . iterate (`div`2) genTree :: MemoIx a => (a -> b) -> Tree b genTree f = gen 0 where gen ix = Tree (f $ unindex ix) (gen $ ix*2+1) (gen $ ix*2+2) memofix :: MemoIx a => ((a -> b) -> (a -> b)) -> (a -> b) memofix f = memof where memof = f $ findTree tbl tbl = genTree memof
適当な関数を書いてみます。
comb :: (Integer, Integer) -> Integer comb = memofix $ \f (i, j) -> if i==1||j==1 then 1 else f (i-1, j) + f (i, j-1) main = print $ comb (100,50)
実行してみます。
$ ghc --make Main.hs -O2 && time ./Main [1 of 1] Compiling Main ( Main.hs, Main.o ) Linking Main ... 4503056131931081050165600532646379362000 real 0m0.082s user 0m0.070s sys 0m0.010s
正しく動作しました。
インターフェースのバリエーション
fixのインターフェースではなくて、次のようなものも考えられます。テーブルを作ってルックアップする関数を返すだけのシンプルなものです。
memo :: MemoIx a => (a -> b) -> (a -> b) memo f = findTree (genTree f)
これを用いてfibを定義するとこうなります。
fib :: Integer -> Integer fib = memo $ \n -> if n<2 then n else fib (n-1) + fib (n-2)
fixのインターフェースよりもこちらの方が書きやすいかもしれません。
ソースコード
全体のソースコードを再掲しておきます。
class MemoIx a where index :: a -> Integer unindex :: Integer -> a instance MemoIx Integer where index n | n>=0 = n*2 | otherwise = -n*2-1 unindex n | n`mod`2==0 = n`div`2 | otherwise = -((n+1)`div`2) instance (MemoIx a, MemoIx b) => MemoIx (a, b) where index (a, b) = l*(l+1)`div`2 + ib where ia = index a ib = index b l = ia+ib unindex ix = (unindex ia, unindex ib) where l = floor ((-1 + sqrt (1 + 8 * fromIntegral ix))/2) ib = ix - l*(l+1)`div`2 ia = l-ib data Tree a = Tree a (Tree a) (Tree a) findTree :: MemoIx a => Tree b -> a -> b findTree tree ix = f (bits $ index ix + 1) tree where f [] (Tree v _ _) = v f (0:bs) (Tree _ l _) = f bs l f (_:bs) (Tree _ _ r) = f bs r bits = tail . reverse . map (`mod`2). takeWhile (>0) . iterate (`div`2) genTree :: MemoIx a => (a -> b) -> Tree b genTree f = gen 0 where gen ix = Tree (f $ unindex ix) (gen $ ix*2+1) (gen $ ix*2+2) memofix :: MemoIx a => ((a -> b) -> (a -> b)) -> (a -> b) memofix f = memof where memof = f $ findTree tbl tbl = genTree memof memo :: MemoIx a => (a -> b) -> (a -> b) memo f = findTree (genTree f)
とかいうことを
嬉々として書いていたら、
http://www.haskell.org/haskellwiki/Memoization
こんなのがあったのですよ…。ぶわっ。
こっちのほうがツリーのエンコード方法が上手いので、後でコード書きなおします。
という夢を見ました
エイプリルフールなので、普段できない、関数型言語を貶しまくるというのをやってみましたが、思った以上に普通になりました。嘘をつくのは自分の心だけにして、それ以外はなるべく説得力のある文章を心掛けましたが、いかがでしたでしょうか。なんだ、分かってるんじゃないか、というご意見も頂きました。ちなみに、嘘でも、Haskellが嫌いだとか嫌になったとかとは書けませんでした。
先日のスライドが、関数型の都合の悪いところを無視して良く見えるところだけ書いていたのに対して、関数型のいいところを全く無視して、いかにも駄目そうに書いてみたのが昨日のです。つまりこれらは表裏一体で、二つ揃って初めて世界の均衡が取れるという訳です(ほんとかよ)。
そういう訳で、私は本当に関数型言語に見切りをつけたわけではないので、悪しからず。今後ともより一層のご愛顧を賜りますよう、よろしくお願いいたします。
手続き型に改宗しました
つい先日にあのような資料をアップロードして心苦しいのですが、手続き型に改宗することにしました。誰よりも関数型言語を愛し、常日頃よりどうして関数型が流行らないのかを考え続けていた私ですが、10年余りを経てようやく気付きました。関数型がイケてないから流行らないのだと。
関数型の人たちは言います。immutableこそ正義であると。でもそうなんでしょうか?変数に代入できる方が書きやすいに決まっています。書きにくいと思いつつも、Haskellというのは曰く、素晴らしい言語なのだから、そんなことはないはずだと自分をごまかしごまかしこれまで生きてきましたが、そうじゃないんです。ようやく目が覚めました。変数に代入できる方が書きやすいのは当たり前なんです。
モナドなんてへんてこなものを当たり前のように持ち出さなければならない時点でおかしいことに気付くべきでした。なぜ私はコンソールから文字を入力したいだけなのに、そんな妙な数学の匂いのするものを利用しなければならないのでしょうか。うまく使えば便利だという話はそれはそれで尤もですが、殆どの場合にそんなものは必要ないでしょう。他の言語を見てご覧なさい。どれも困った様子はないでしょう?
型も良くないです。型があれば実行時にバグに悩まされないなどと仰いますが、結局のところ配列の添字がおかしいとか、パターンマッチ漏れとかはわからないんです。コンパイルが通ってそれがそのまま動作するなんて、ほとんど幻想です。それなのに、コンパイルの通りは著しく悪くなります。コンパイル通すだけで一苦労です。私なんて、コンパイルが通らなくてプログラムを完成させるのを諦めたこともしばしばです。それならいっそ、バグっててもいいから動いて欲しいと思います。
高階関数も良くないんです。人間というのは、あまり複雑なものを扱えるように出来ていません。関数の時点で難しいんです。それなのに、関数に関数を渡すとか、難しすぎるんです。さらにそれが許されると、関数を渡す関数を渡す関数なども考えなければならなくなります。とても人間の制御出来る範囲を超えています。関数がファーストクラスじゃないというのは、考えてみれば、人間の能力の限界にフィットしたとても現実的な制約だったんです。ループを再帰で表すというのもありますが、無茶もいいところです。再帰というのは難しいんです。私の周りでも、再帰が分からずに躓く人を大勢見てきました。どうしてfor文で簡単に扱えるものを再帰で書かなければならないんでしょう。人は制御の流れをイメージするのは得意だけど、再帰的なものをイメージするのは苦手なのです。
冷静になって考えてみれば、とてもシンプルな理由でした。「関数型は書きにくい」。初めて関数型に触ったときは今まで自分がやってきたプログラムとは全く違っていて、それはそれは素晴らしいものに見えました。でもそれはプログラマが罹るはしかのようなものだったのです。10年もの間熱に浮かされていましたが、ようやく目が覚めました。現実が見えてきました。私は手続き型に改宗します。
ついでに言いますと、オブジェクト指向もいらないです。純粋手続き型(?)でいいのです。オブジェクト指向でコンポーネント再利用できて生産性向上、というのが典型的なオブジェクト指向プログラマの言い分ですが、皆さん、クラスを作ってそれが他のプロジェクトで再利用できたためしなんてあるでしょうか?
デザインパターンなどというものもありますが、あれも必要ないです。ご存じの方はご理解いただけるかと思いますが、あれはとてもややこしいものです。やろうとしていることはどれもとても単純なことのはずなのに、クラス階層図は目を背けたくなるほどの複雑さです。そんな仰々しく天下り的なものを受け入れなければならない時点で、何かがおかしいと気付くべきです。
そうです。世の中にはC言語があればそれで十分なのでした。Cのプログラムやライブラリを見てみなさい。どれも何の過不足もなくうまく廻っているではありませんか。なぜ関数型などを持ち出す必要がありましょうか!そういうことなんです、関数型が一向に流行らないのは。こんな簡単なことに私もようやく気づきました。
これからはこのページは純粋手続き型雑記帳としてやっていこうと思います。以前の内容は、とてもお見苦しいものではありますが、若気の至りということで残しておくことにします。落胆された方も中にはおられるかと思いますが、必ずご理解いただけるものだと思っております。これからも変わらぬご愛顧をよろしくお願いいたします。
チームラボ天下一武道会
チームラボ天下一武道会 ~コードGolf & F1レース!~ : ATND
に参加してきました。普段Java使っていないのでどうなるかと思いましたが、なんとか優勝することができました。
問題は、ユーザデータと商品の購入データが与えられるので、全商品間のコサイン類似度を求めよというものでした。入力は
<ユーザID>,<商品ID>
のCSVで与えられ、出力は商品ID×商品IDの二次元配列をCSVで出します。購入データは1万件、ユーザ数は1000以下、商品数は500以下という制限です。
順位は、実行時間によるスコアとバイトコードのサイズによるスコアの和によって付けられます。それぞれ50点満点で、実行時間は
- 10秒 (50点)
- 60秒 (0点)
バイトコードのサイズは、
- 1Kbyte (50点)
- 6Kbyte (0点)
を基準として、線形補完されて決まります。50点満点ですが、基準値を超えた場合は60点まで加点されるとのことでした。
最初問題聴いたときは、効率のいい疎行列計算しろってことなのかなとか思ったのですが、入力データはそんなに疎ではないらしいし(と思ったけど、500*1000=5万と勘違いしてたようでした)、そもそも密行列で持ってばかな方法でも 500^5*1000 = 2500万 ぐらいの計算量で済むので、10秒どころか1秒もあれば余裕ではないのかと思いました。Rで計算すると60秒ほどかかるそうなのですが、それの6倍速が上限というのは緩かった気がします(しかしこれ以上短くすると、JVMの起動時間が無視できなくてあれかもしれない…)。そういうわけで、時間は多分緩いけど、バイトコードサイズ1KBは相当厳しそうに見えたので、純ゴルフ勝負になりそうな予感でした。
まずは適当に組んでみますと、実行速度は手元で2.5秒ほどで、やっぱりそんなにかからない。バイトコードサイズは2.8KBほどで、これをどこまで縮められるかという感じ(そもそも空のクラスでも300バイトぐらいあるんですよ)。まあしかし、普段Javaでコード書かないものですから、Javaの重箱仕様やら、ましてやJVMのことなんかよくわからない。どうやれば小さくなるかなんて皆目見当もつきませんでしたが、とりあえず小さくなりそうなことをひとつずつやっていきました。
- ローカル変数名の長さはバイトコードサイズとは無関係
- 使っているクラス数を減らすとガクッとサイズが減る(TreeMapはMapじゃなくてTreeMapで受けるべし、とか)
- 使っているメソッド数を減らすとサイズが減る(メソッドごとに文字列で参照情報が入るのだろうか?)
- 改行を消すと縮む場合がある(多分デバッグのための行番号の情報)
- メソッドの引数名の長さはバイトコードサイズに影響
- try catch より throwsのほうが短い
- 変数宣言はまとめたほうが短い
- forの宣言部などに入れられるものは入れたほうが短い
- 複雑な式を関数の引数に入れるとなぜかでかくなる
- importはバラでやったほうが若干短い
そういうわけで、こつこつ短くしていきました。最初はどんな入力がきても大丈夫なように作っていたのですが、入力が決まっているので、配列長決めうちにしてArrayListから配列に変えたり、ユーザIDと商品IDはぬけがある時のためにTreeMapを用いて内部IDに変換していたのをやめて、ちょっと計算は無駄になるけどぬけてるとこも計算しちゃうとか、出力は対象行列になるから上半分だけ計算していたのを、どうせ計算にはそんなに時間掛からないからすべて計算することにしたり(データを保存しておくための配列が要らなくなる)、入力を読みながら隣接行列構築するなど、許容される範囲で速度を遅くしつつ、コードを簡略化していくという作業を続けた結果、速度3秒(評価環境では5秒ぐらい?)、バイトコードサイズ1390バイトぐらいにまで縮みました。コードのサイズでいうと、最初は80行ぐらいあったのが、最終的には25行ぐらいになりました。
import java.io.FileReader; import java.io.BufferedReader; import java.io.PrintWriter; class cosine{ public static void main(String[] a) throws Exception{ BufferedReader br = new BufferedReader(new FileReader(a[0])); br.readLine(); PrintWriter pw = new PrintWriter("output.csv"); double[][] tbl = new double[512][1024]; for (String line;(line=br.readLine())!=null;) tbl[new Integer(line.split(",")[1])][new Integer(line.split(",")[0])]++; for (int i=0; i<512; i++){ double ni=0; for (int k=0; k<1024; k++) ni += tbl[i][k]*tbl[i][k]; if (ni>0){ int t=0; for (int j=0; j<512; j++){ double nj=0, nc=0; for (int k=0; k<1024; k++){ nj += tbl[j][k]*tbl[j][k]; nc += tbl[i][k]*tbl[j][k]; } if (nj>0){ if (t!=0) pw.printf(","); t=1; pw.printf("%.2f", nc/Math.sqrt(ni*nj)); } } pw.printf("\n"); } } pw.close(); } }
提出したコードからすぐ気付いた良くない個所が直してあるので、若干短くなっています。時間いっぱいまで頑張っていたので、まだまだ縮むとは思います。1KBを切りたかったのですが、これはちょっと難しいかもしれません。最初からこれぐらい割り切ったコードを書いていればよかったのかもしれませんが、Javaの速度感覚が無かったのが駄目でした。
バイトコードサイズでのゴルフはやったことがなかったのですが、なかなか楽しかったです。コードサイズは書いたままがスコアですけど、バイトコードサイズはコンパイラの気分次第で、コンパイルするまで縮むか分からないので、コンパイルが毎回どきどきです。
残り10分ぐらいの時に検証用データの最終版がもらえたのですが、なかなか手元で出力が合わなくて、うわあこれはだめぽ、みたいな状態でサブミットしたので、懇親会中は気が気ではなかったです。でも他の人たちもみんな合わないみたいなので少し安堵しつつも、やっぱり大丈夫なのかなという感じでやっぱり落ち着きませんでした。コンテスト中にあってるかどうか調べる手段は欲しいと思いました。出力がおかしくならないようにしながらコードをいじる作業が続くので、どこかでバグを入れてしまうと、どこからバグっているのか分からなくなって厳しいです。それと、スコアボードもやっぱり欲しいと思いました。周りがどれぐらい頑張っているのか分からないので、一人もくもくになってしまって、ちょっと辛かったです。あと、スコアボードがないと接戦になりにくい気がする。
そんなこんなですが、一位になれて嬉しかったです。またの機会があればぜひ参加させていただきたいと思います。チームラボの皆さまどうもありがとうございました。参加した皆さま、お疲れ様でした。
JOI 春合宿の講義資料
id:iwiwi さんからご紹介に与りまして、JOI春合宿にて講義をさせて頂きました。テーマはなんでも良いとのことでしたので、関数プログラミング入門ということで話させて頂きました。スライドを以下に公開しております。
聴いて頂いた皆さま、拙い講義ではありましたが、どうもありがとうございました。二時間も頂けるとのことだったので、あれもこれも話したいとなって、まとまりのない発表になってしまった感が否めませんが、少しでも関数プログラミングの魅力が伝われば幸いです。関数プログラミング入門ということで、関数プログラミングを全く知らない人をターゲットに作りましたが、少々無理があったかもしれません。私はネルー値が1を切らないとなかなか準備に取り掛かれなくて、当日は準備不足で資料のミスも目立ったし、資料の退屈さを紛らわすために適当に突っ込んだネタ画像も唐突過ぎていけない(その辺は大分修正しました。余計ひどくなっているかもしれませんが)。
とまあ、反省はこのぐらいにしまして、内容は関数プログラミングの基礎から並行プログラミングの話まで、なるべく関数プログラミングがおいしく見えるところをかいつまんで説明してあります。嘘も方便も多かろうと思いますが、識者の方は大目に見てやって下さい。こういうのは宗教論争だと言われたりもしますので(私は学術論争だと思っているのですが)、まさに布教でして、ならばこれは宗教の勧誘と変わらないわけでして、私の言うことを全面的には信用しないようにと念を押してはおきました。
で、内容の話ですね。いやあ、退屈な資料ですね。キャッチーな資料の作り方を誰か教えてください。まず、パワーポイントデフォルトのテーマ、これがいけない。白地にゴチック体、淡々と並べられるitemize。作り方が分からないので、アニメーションも一切なし、PDFに変換しても完全版が楽しめるという親切設計。うえうえ、3分で眠れる自信がありますよ!最近流行りといったらあれです、高橋メソッド。巨大な文字のインパクト。次々めくられるスライドによる圧倒的なスピード感。聴衆も話者の巧みなキーボード操作から目が離せない!?って、そうじゃないよ。古いよ。なんでなの、これ。高橋メソッドにしたらページ数1000超えるよ!誰かほんとにクールなスライドの作り方教えてください。
それで、内容の話ですね。うーん、内容はスライド観てください。
wafのユニットテストモジュールの改良
前回wafの紹介記事を書きましたが、あれからいくつかバージョンアップがあって、それに伴ってユニットテストモジュールの仕様が結構変わりました。変わったといいますか、かなり改悪されたように思います。
- --alltestオプションが常にTrueになるようになった(オプションは存在するが、デフォルトでTrueで指定してもTrueで、Falseにする術がなくなった。バグとしか思えない)
- サマリ表示でエラーの詳細がでなくなった
ちょっとこのまま使っていくには厳しい状況です。それに加えて、ユニットテストをコンパイル・実行しないというオプションがかねてから欲しいと思っていたので(ユニットテストライブラリがない環境でもインストールはできて欲しいものです)、これを機に自作することにしました。拡張しやすいこともwafのメリットなので、作業はとてもスムーズに進みました。
http://github.com/tanakh/waf-unittest
というわけで、作ったものをgithubに公開しました。
waf組み込みのユニットテストモジュールとの違いは、
- デフォルトではユニットテストをコンパイル・実行しない
- サマリの自動表示(add_post_funいらず)
- エラー詳細の表示
- gtestの組み込みサポート(オートチェック・オートリンク)
- 特定のテストだけ実行する機能
などです。詳しくはgithubのREADMEをご覧ください。