hugs98

『人間に勝つコンピューター将棋の作り方』(技術評論社)なんて本を読んでみた。 大人向けの囲碁・将棋欄にあった唯一興味を引く本だったから。図書分類記号では、ちゃんと 分類番号796ってのを与えられている割には、この手の本がやけに少ないな。小供向けの コーナーの方が多数あったぞ。

それはさておき、技評が出してるのに驚いた。そして表紙でまた驚いた。アニメっぽい 女流棋士が描かれているんだもん。

そう、コンピューター将棋が女流棋士と対戦して、初めて勝った事を象徴してるんですかね? 欧米では早くからチェスを指すプログラムが研究されていたけど、将棋は余り進歩が無かった。 例の取った駒を有効活用するってのが、探索空間を拡げてしまい、どう対応したら良いか 、有効な方法が見出せなかったから。

ルール上ある局面での差し手の数は85手ぐらいあるそうな。その局面を130回ぐらい展開 して、ひと勝負つくとの事。すると、85の130乗という手が将棋にはある訳だ。 途方もない数ですよ。

自分がこう指したら、相手はこう指すだろう。その局面で自分はこう指す、、、、と相手は こう指す(かも知れない)って、深読みをせにゃ、人間には勝てん。コンピュータは馬鹿だ から言われた事しか出来ない。直感とか無いからね。

そこで、色々な人が、それぞれの考えで将棋を指すプログラムを作った。有名な所では、 Bonanzaとか激指とかYSSとかGPS将棋とかが有る。

人間に勝つには、それぞれのプログラムが協力したら、いいんでないかい。でも、それぞれには 主義主張が有るんでなぁ。ならば多数決でどうだ。

そんな方法でもいいから、取り合えず協力してやんべぇって、大学の先生方は考えた。 あから2010ってのがそれ。コンピュータ強調(協調)将棋。で、先生方がガウンを まとって、将棋連盟に果たし状を出しに行った。

それを受けて、将棋連盟の親分は、『いい度胸をしているとその不遜な態度に感服仕っ次第』 と受けて、対戦が決定。

大学の先生方には、将棋道場が有った。いや、作ったんだ。 コンピュータ将棋連続対局場所 これで、密かに自分のプログラムの腕を磨いていたのさ。

後、数年もすれば、コンピュータが人間を越える日がやってくるだろう。人口知能の勝利。 次なる手は、接待将棋の開発とか。

コンピュータが連戦したんじゃ、人間は気分が悪くなる。かと言って、コンピュータが あからさまに悪手を指して、人間に勝ちを譲ったんでは、これまた人間の気分を害する。

上手によいしょするプログラムを作りましょ。これ難しいーーーーぞ。頑張って!!

life

前回、ひょんな事からLifeゲームを思い出した。入れて楽しんでもいいんだけど、どうせ すぐ飽きる。簡易コードが無いかしらと思ってしばしば熟考。。思い出した。 プログラミングHaskellにコードが載っていた事を。

早速、hugsを入れたぞ。そして実行はどうやるんだったかな?

[sakae@fedora hugsBook]$ hugs
__   __ __  __  ____   ___      _________________________________________
||   || ||  || ||  || ||__      Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__||  __||     Copyright (c) 1994-2005
||---||         ___||           World Wide Web: http://haskell.org/hugs
||   ||                         Bugs: http://hackage.haskell.org/trac/hugs
||   || Version: September 2006 _________________________________________

Haskell 98 mode: Restart with command line option -98 to enable extensions

Type :? for help
Hugs> :l life.lhs
Main> main
ERROR - Undefined variable "main"
Main> life glider

これで、しばし観賞。確かrunhugsって即実行系も有ったな。

[sakae@fedora hugsBook]$ runhugs life.lhs
runhugs: Error occurred
ERROR - Undefined variable "main"

実行系の時は、mainが必要とな。main = life glider を追加してと、、、

[sakae@fedora hugsBook]$ runhugs life.lhs
   :
   O O
    OO
    Orunhugs: Error occurred

ERROR - Garbage collection fails to reclaim sufficient space

あれ? ガベコレが走るなんて、裏Lispですかい? メモリーを増やしてあげると、御利益が あるかな? その前に、何世代まで、実行してくれるか表示出来るようにしておこう。 まあ、こういうのHaskellのリハビリになるでしょう。

> showage                 :: Int -> IO ()
> showage n               =  do goto (width + 2, 0)
>                               putStr (show n)
>
> life                    :: Board -> Int -> IO ()
> life b n                =  do cls
>                               showcells b
>                               showage n
>                               wait 5000
>                               life (nextgen b) (n + 1)
>
> main                    =  life glider 0

世代を表示させるshowageを追加。lifeの引数を追加して、世代表示するようにした。 ここで、再帰してるんだな。では、実験。

[sakae@fedora ~]$ runhugs mylife.lhs
           47runhugs: Error occurred

ERROR - Garbage collection fails to reclaim sufficient space

ちょっと醜いけど、47世代まで行けた。メモリーを増やすのをmanで調べたよ。

[sakae@fedora ~]$ runhugs +h3m mylife.lhs
           146runhugs: Error occurred

ERROR - Garbage collection fails to reclaim sufficient space

初期値は256kらしいから、メモリーをどーんと増やしても、それに見合う御利益は得られないとな。 なお、hugsから起動して、エラーになった所で、強制的にgcすると

           48
ERROR - Garbage collection fails to reclaim sufficient space
Main> :gc
ERROR - Garbage collection fails to reclaim sufficient space
Main>

余裕の無い作りになってましたよ。普通にhugsを起動してどのぐらいcellが有るか調べると

Hugs> :gc
Garbage collection recovered 986940 cells

あっ、cellて普通の人は細胞と思うみたいですよ。Lisperはすぐにアレを思い出すな。 一体どういう構造になってる?

この先、資料を見るかも知れないので、hugs98のページを揚げておく。 Haskell 98 言語とライブラリ 改訂レポート こちらは入門編

なお、hugs98はその名前から分かるように昔の仕様。とっくに開発は終了してる。それに 変わって新しいHaskellが標準になった。そう、大英帝国はグラスゴー発のGHC。 仕様が安定せず追っかけが必須。haskellの今を知りたいならば、 あどけない話あたりが良いだろう。

そこにソースが有るぞ

で、中身見たーーい。そうなると、fedoraは不適でBSD系(と、勝手に思う)ですな。 ports一発、お取り寄せでコンパイルしてくれる。後はその残骸を漁るだけ。 で、注意しなければいけないのが、付属品をわんさかと要求される事だ。

FreeBSDでやろうとしたら、gccから始めないとだめだった。そなら、OpenBSDでは? Makefileを覗いたら、そこまでは無かったけど、

WANTLIB =               GL GLU X11 c m ncurses pthread readline

こういう物が必要との事。面倒嫌いって事で、NetBSDのそれを調べてみると、readlineだけ 必要との事。よって、NetBSDでやってみる。Makefileに面白い注意書きがあった。

# The default -O2 breaks the interpreter producing strange internal errors
# at run time.
.if !empty(CC_VERSION:Mgcc-4*)
CFLAGS+=    -O0
.endif

普通にコンパイルされてhugsが鎮座してくれた。けど、gdbに追うにはdebugオプションが 付いていないので、不適。srcの下にあるMakefileに手を入れる。

CFLAGS          = -gdwarf-2 -g3 -O0 -I/usr/include

これで再コンパイルすると、リンク段階でreadlineが無いと言う。はて、先ほどはちゃんと コンパイル出来ていたはずなんだけど。最上位でコンパイルしないと駄目? もう一度、Makefileを見直し。そしてライブラリィdirを強制指定。

LDFLAGS         = -L/usr/lib -L/usr/pkg/lib -Wl,-R/usr/lib -Wl,-R/usr/pkg/lib

これで無事にhugs類が出来上がった。やれやれ。

hugsを追う

hugsのソースは5万行を越えていて、取っ掛かりが無いので、例のごとく、どこら辺で 走っているかスパイしてみる。hugsを起動しておいて、いきなりすれにattachする。 これが対話プログラムを追う時の、上等もとえ常套手段だ。(オイラーが今決めた)

(gdb) bt
#0  0xbba2a8b7 in read () from /usr/lib/libc.so.12
#1  0xbbbcad7c in rl_getc () from /usr/pkg/lib/libreadline.so.6
#2  0xbbbcb5df in rl_read_key () from /usr/pkg/lib/libreadline.so.6
#3  0xbbbb5bd1 in readline_internal_char () from /usr/pkg/lib/libreadline.so.6
#4  0xbbbb6284 in readline () from /usr/pkg/lib/libreadline.so.6
#5  0x0806e829 in consoleInput (prompt=0xbfbfe3e4 "Hugs> ") at input.c:272
#6  0x0804b9af in promptForInput (moduleName=0x80f9bfc <text+1020> "Hugs") at hugs.c:937
#7  0x0804ba6a in initInterpreter () at hugs.c:1038
#8  0x0804ba09 in interpreter (argc=1, argv=0xbfbfe84c) at hugs.c:982
#9  0x08049fca in main (argc=1, argv=0xbfbfe84c) at hugs.c:220

先ほどプチ苦労したreadlineはlibcの上に胡坐をかいているのね。その上にいるのは、 フレーム番号5番さん。それらしい名前になってるな。8番さんの下働きとして、7番に、 initInterpreterが居るけど、initって名前がちと気になるな。

1031static Void local initInterpreter()
1032{
1033    everybody(RESET);               /* reset to sensible initial state */
1034    dropScriptsFrom(numLoadedScripts()-1);
1035                                    /* remove partially loaded scripts */
1036                                    /* not counting prelude as a script*/
1037
1038=>  promptForInput(textToStr(module(findEvalModule()).text));
1039}

更にフレームをもう一段上がって、interpreterを見渡してみれば、起動時の初期処理の後

 981    for (;;) {
 982=>      initInterpreter();
 983        if (doCommand())
 984            break;
 985    }

このようなループをぐるぐる回るとな。核は、doCommandだな。これはインタープリタで 受け付ける、コロンコマンドを処理してる(だろうね)。

doCommandでは、入力を解析して、switch文で振り分けているんか。例えば、:? なんて やると、

1091            case NAMES  :
1092#if HUGS_FOR_WINDOWS...
1094#endif
1095                          listNames();
1096                          break;
1097=>          case HELP   : menu();
1098                          break;
1099            case BADCMD : guidance();
1100                          break;
1101            case SET    : setOptions();
1102                          break;

menu()が実行されるとな。HELPなんてのは、 人間が理解し易いようにそれなりの単語を使っているけど、実態は

(gdb) info macro HELP
Defined at /usr/pkgsrc/lang/hugs/work/hugs98-Sep2006/src/command.h:32
  included at /usr/pkgsrc/lang/hugs/work/hugs98-Sep2006/src/hugs.c:16
#define HELP 7

こんな風になってるのね。そんじゃ、簡単なスクリプトをロードしてみる。:load t.hs

 364static Void local load() {           /* read filenames from command line   */
 365    String s;                        /* and add to list of scripts waiting */
 366                                     /* to be read                         */
 367    clearEvalModule();
 368    while ((s=readFilename())!=0) {
 369#if HUGS_FOR_WINDOWS...
 371#endif
 372=>      addScriptName(s,TRUE);
 373    }
 374    readScripts(1);
 375}

いちどに幾つものスクリプトをロード出来るのね。今回は、addScriptNameの引数は、 勿論、指定したt,hsになってた。

addScriptNameの先を追って行くと、scriptTableにファイル名等を登録してた。

$7 = {{
    fileName = 0xbb90e040 "/usr/pkg/lib/hugs/packages/hugsbase/Hugs/Prelude.hs",
    realName = 0xbb90e080 "/usr/pkg/lib/hugs/packages/hugsbase/Hugs/Prelude.hs",
    directory = 0x0,
    lastChange = 1444075489,
    postponed = 0,
    chased = 1
  }, {
    fileName = 0xbb90f040 "/usr/pkg/lib/hugs/packages/base/Prelude.hs",
    realName = 0xbb90f070 "/usr/pkg/lib/hugs/packages/base/Prelude.hs",
    directory = 0x0,
    lastChange = 1444075492,
    postponed = 0,
    chased = 1
  }, {
    fileName = 0xbb90f0d0 "/usr/pkg/lib/hugs/packages/hugsbase/Hugs.hs",
    realName = 0xbb90f100 "/usr/pkg/lib/hugs/packages/hugsbase/Hugs.hs",
    directory = 0x0,
    lastChange = 1444075489,
    postponed = 0,
    chased = 0
  }, {
    fileName = 0xbb90d4a0 "t.hs",
    realName = 0x0,
    directory = 0xbb90c0a4 ".",
    lastChange = 0,
    postponed = 0,
    chased = 0
  } <repeats 796 times>}

先頭の3つは、システム備え付けって事で、削除不可になってた。このテーブル配列数から すると、とんでもない数のスクリプトを許容してるんだな。登録が終わった時点で、lastChangeが設定 されてた。:edit で編集すると、これを見て、再ロードするんだな。ロード時に評価 されるのだろうか? そんな事より、先へ行こう。

いよいよ実行。main ってやると、EVALってラベルの所で、evaluator(findEvalModule()); を 実行しようとする。

取りあえず、evaluatorで待ち構えると、

Breakpoint 2, evaluator (m=2628) at evaluator.c:64
64          Kinds ks   = NIL;

謎の番号2628って何だ? コードを見ると分かるかな?

 61Void evaluator(m)
 62Module m; {        /* evaluate expr and print value    */
 63    Type  type, bd, t;
 64=>  Kinds ks   = NIL;
 65    Cell  temp = NIL;
 66
 67    setCurrModule(m);
 68    scriptFile = 0;
 69    startNewScript(0);                 /* Enables recovery of storage      */
 70                                       /* allocated during evaluation      */
 71    parseExp();
 72    checkExp();
 73    defaultDefns = evalDefaults;
 74    type         = typeCheckExp(TRUE);
 75    if (isPolyType(type)) {
 76        ks = polySigOf(type);
 77        bd = monotypeOf(type);
 78    }
  :

setCurrModuleは、利便性の為に用意したとな。で、そこの出口では、

(gdb) p/x module(m)
$17 = {
  text = 0x123,
  tycons = 0xfff0c0a4,
  names = 0xfff0c0bb,
  classes = 0xfff0c076,
  exports = 0xfff0c0bf,
  modAliases = 0xfff0bdd7,
  qualImports = 0x0,
  modImports = 0xfff0c0b0
}

続けて追って行くと、machine.cの中のrunに実行が移る。

Void run(start,root)                   /* execute code beginning at given  */
Addr     start;                        /* address with local stack starting*/
StackPtr root; {                       /* at given root offset             */
    register Memory pc = memory+start;

#if     !DEBUG_CODE && HAVE_LABELS_AS_VALUES...
#else
#if DEBUG_CODE...
#else
#define Dispatch        for (;;) { switch((pc++)->instr) {
#endif
#define Case(x)         case x
#define Continue        continue
#define EndDispatch     default : internal("illegal instruction"); \
                                  break;                           \
                        }}
#endif

=>  Dispatch

        Case(iLOAD)   : push(stack(root+pc->mint));      /* load from stack*/
                        pc++;
                        Continue;

        Case(iCELL)   : push(pc->cell);                  /* load const Cell*/
                        pc++;
                        Continue;

独自定義のstackマシンだな。pcがどんな構造かと言うと

(gdb) p pc
$25 = (Memory) 0xbac22494
(gdb) p *$
$26 = {
  mint = 1,
  mfloat = 1.40129846e-45,
  cell = 1,
  text = 1,
  addr = 1,
  instr = iCELL,
  lab = 1
}

後は自由気ままにコードの中をさ迷うと、evaluator内で、パースしてコンパイルして、 それをアセンブルするって趣きなんだけど、どんなアセンブリコードになるかと言うと、

static Addr local dissInstr(pc)       /* print dissassembly of instruction */
Addr pc; {
    switch (instrAt(pc)) {
        case iLOAD   : pc = dissInt(pc,"LOAD");      break;
        case iLEVAL  : pc = dissInt(pc,"LEVAL");     break;
        case iCELL   : pc = dissCell(pc,"CELL");     break;
        case iCHAR   : pc = dissInt(pc,"CHAR");      break;
        case iINT    : pc = dissInt(pc,"INT");       break;
        case iDOUBLE : pc = dissDouble(pc,"DOUBLE"); break;
        case iSTRING : pc = dissText(pc,"STRING");   break;
        case iMKAP   : pc = dissInt(pc,"MKAP");      break;
        case iUPDATE : pc = dissInt(pc,"UPDATE");    break;
        case iRUPDATE: pc = dissNone(pc,"RUPDATE");  break;
        case iUPDAP  : pc = dissInt(pc,"UPDAP");     break;
        case iRUPDAP : pc = dissNone(pc,"RUPDAP");   break;
        case iEVAL   : pc = dissNone(pc,"EVAL");     break;
        case iSTAP   : pc = dissNone(pc,"STAP");     break;
        case iRETURN : pc = dissNone(pc,"RETURN");   break;
        case iTEST   : pc = dissCellAddr(pc,"TEST"); break;
        case iGOTO   : pc = dissAddr(pc,"GOTO");     break;
        case iSETSTK : pc = dissInt(pc,"SETSTK");    break;
        case iALLOC  : pc = dissInt(pc,"ALLOC");     break;
        case iSLIDE  : pc = dissInt(pc,"SLIDE");     break;
        case iROOT   : pc = dissInt(pc,"ROOT");      break;
        case iFAIL   : pc = dissNone(pc,"FAIL");     break;
        case iTABLE  : pc = dissNone(pc,"TABLE");
                       pc+= intAt(pc)+1;
                       break;
        default  : internal("unknown instruction");
    }

この辺になると、rubyもPythonもgaucheもJavaも、オレオレCPUの世界だな。きっと、 あの人とかこの人が得意の分野だろう。machine.cを良く見ておけって結論かな。 後は、補助として、DEBUG_CODEをtrueにしとくとICEの代わりになるかな?

Hugs> :set +D
Hugs> :load t.hs
------------------
name=main
Arity   = 0
codeGen = print "Hellow Haskell"
name=main
0x8917  STRING  Hellow Haskell
0x8919  CELL    v16
0x891B  CELL    v25
0x891D  MKAP    1
0x891F  CELL    print
0x8921  MKAP    2
0x8923  RETURN
------------------

ふむ、hugs版のハロワは、上記のようなコードになるとな。で、それを実行すると、

Main> main
------------------
Arity   = 0
codeGen = hugsIORun main
0x8925  CELL    main
0x8927  CELL    hugsIORun
0x8929  MKAP    1
0x892B  RETURN
------------------

main :: IO () が効いて、外界表示用の特殊機能マシンを作って、そいつにmainを喰わせる って寸法かな。続いて、だらだらと、実行過程が表示される。

evaluator() builds: hugsIORun main
 Entering name(73): hugsIORun
0x2D01: LOAD    1
0x2D03: CELL    v1097
0x2D05: MKAP    1
0x2D07: CELL    basicIORun
0x2D09: MKAP    1
0x2D0B: CELL    v1098
0x2D0D: RUPDAP
 Leaving name(73): hugsIORun
 Entering name(889): v1098
0x2CEE: LEVAL   1
 Entering name(598): basicIORun
  :
             Entering name(579): putChar
                0x28EC: CELL    stdout
                0x28EE: CELL    hPutChar
                0x28F0: MKAP    1
                0x28F2: RETURN
             Leaving name(579): putChar
   :
 Entering name(77): Finished_Return
0x2CF0: TEST    Finished_ExitWith       0x2CF8
0x2CF8: TEST    Finished_Return 0x0000
0x2CFB: LOAD    2
0x2CFD: CELL    Right
0x2CFF: RUPDAP
 Leaving name(889): v1098
 Entering name(149): Right

Main>

この長いtraceを解析すれば、何が起こっているか分かるな。

そんじゃ、もう一例。与えられた整数をインクリメントする関数を定義して実行。

Main> incN 123
------------------
Arity   = 0
codeGen = hugsIORun (putStr (showsPrec 0 (incN 123) []))
0x8937  CELL    []
0x8939  INT     123
0x893B  CELL    v32
0x893D  CELL    fromInt
0x893F  MKAP    2
0x8941  CELL    v32
0x8943  CELL    incN
0x8945  MKAP    2
0x8947  INT     0
0x8949  CELL    v44
0x894B  CELL    showsPrec
0x894D  MKAP    4
0x894F  CELL    putStr
0x8951  MKAP    1
0x8953  CELL    hugsIORun
0x8955  MKAP    1
0x8957  RETURN
------------------

ほー、結果の表示の為、適切なコードが挿入されるとな。実際のincNは

                     Entering name(1180): incN
                      0x8917: INT       1
                      0x8919: LOAD      2
                      0x891B: CELL      fromInt
                      0x891D: MKAP      2
                      0x891F: LOAD      1
                      0x8921: LOAD      2
                      0x8923: CELL      +
                      0x8925: MKAP      2
                      0x8927: RUPDAP
                     Leaving name(1180): incN

上記で定義したincNはジェネリック型だったけど、Intを受けてIntを返すように宣言すると

                     Entering name(1180): incN
                      0x8917: INT       1
                      0x8919: CELL      v30
                      0x891B: CELL      fromInt
                      0x891D: MKAP      2
                      0x891F: LOAD      1
                      0x8921: CELL      v30
                      0x8923: CELL      +
                      0x8925: MKAP      2
                      0x8927: RUPDAP
                     Leaving name(1180): incN

出て来るコードが微妙に違うな。ここまではgaucheみたいな言語でも普通にやっている事。 HaskellをHaskellたらしめている型チェックはどうしてるんだろうか?

こういうのを調べるのの鉄則は、エラーを起して状態を観察すれば良い。例えばこんなの。

-- test for hugs --
incN   :: Int -> Int
incN n = n + 1.0

整数と浮動小数点の演算を混ぜこぜにした。hugsからロードしてみると

ERROR "t.hs":3 - Instance of Fractional Int required for definition of incN

期待通り? エラー発生。後は、このエラーを出してる所をソースからgrep。但し、Intとか の字句は埋め込みの可能性があるので、何度か字句の組み合わせを変えて試すと良い。 こういう時は、取替え可能な字句がどれか、根幹の字句はどれかが分かれば、手っ取り早い。 おいらは、英語力低下の為、何度かTryしたよ。

そして見つかったのは、

2874static Void local typeDefnGroup(bs)     /* type check group of value defns */
2875List bs; {                              /* (one top level scc)             */
2876    List as;
2877
2878=>  emptySubstitution();
2879    hd(defnBounds) = NIL;
2880    preds          = NIL;
2881    setTypeIns(bs);
2882    typeBindings(bs);                   /* find types for vars in bindings */
2883
2884    if (nonNull(preds)) {
2885        Cell v = fst(hd(hd(varsBounds)));
2886        Name n = findName(textOf(v));
2887        Int  l = nonNull(n) ? name(n).line : 0;
2888        preds  = scSimplify(preds);
2889        ERRMSG(l) "Instance%s of ", (length(preds)==1 ? "" : "s") ETHEN
2890        ERRCONTEXT(copyPreds(preds));
2891        ERRTEXT   " required for definition of " ETHEN
2892        ERREXPR(nonNull(n)?n:v);
2893        ERRTEXT   "\n"
2894        EEND;
2895    }

後は、この関数にBPを置いて走らせる、簡単なお仕事です。

(gdb) bt
#0  typeDefnGroup (bs=-141275) at type.c:2878
#1  0x080c08e1 in typeCheckDefns () at type.c:2853
#2  0x0808912f in addScript (fname=0xbb90d4b0 "t.hs", len=103) at script.c:203
#3  0x08089793 in readScripts (n=800) at script.c:360
#4  0x0804a2d6 in load () at hugs.c:374
#5  0x0804bac8 in doCommand () at hugs.c:1058
#6  0x0804ba0e in interpreter (argc=1, argv=0xbfbfed68) at hugs.c:983
#7  0x08049fca in main (argc=1, argv=0xbfbfed68) at hugs.c:220

それにしても、ここから先、意味を理解しながら追って行くのは大変だぞ。

etc

季節柄、 詰碁を解く Haskell プログラム なんてのが見つかって、嬉しいな。

それにしてもNet上にある詰め碁のサイトは、揃いも揃って、フラッシュを要求して くるなあ。代替品で賄っている所は無いのか。プンプン。