状態遷移

eskkに大量にコミットしたのでバグ潰しがてら普通の日記でも書く。
感覚で適当なこと書くので突っ込まれるだろうけど気にしない。
というか突っ込まれるのを気にしないために適当なこと書く。


モードというか状態遷移を実装する時はなるべくフラットにする、というのが最近自分が心がけていることの一つだったりする。
つまり関数を呼び出してまたそこから新しいモードに入るので関数を呼び出し、モードが終わったら戻ってくる、というのはダメだと思う。
関数を呼び出したらreturnしてまた別のモードの関数を呼び出す形にするのがいいと思う。
呼び出しのネストが深いというのは、よくないことの予兆の一つだと思っている。
Cではスタックを消費するから〜とかいう理由でもなくCに限ったことじゃないと思っている。

上位と下位のコードで値を受け渡ししにくくなる = 修正が難しいプログラム

だと思う。


Vimソースコードは悪い例だと思っている。
とかdisれるレベルじゃないのですが(ry
例えばノーマルモードでiが押された時、
normal_cmd() (in normal.c)から(直接じゃないけど) edit() (in edit.c)を呼び出し、
インサートモードが終わるとedit()からreturnする。
そういう形じゃなくこういう形だと良いと思った。*1

MODE_NORMAL = 'n'
MODE_INSERT = 'i'

def do_normal(c, context)
    case c
    when 'i'
        ...
        context.mode = MODE_INSERT
    when 'a'
        ...
        context.mode = MODE_INSERT
    end
end
def do_insert(c, context)
    ...
end

mode_func_table = {
    MODE_NORMAL => do_normal,
    MODE_INSERT => do_insert,
}

def main_loop
    context = {
        col => 1,
        lnum => 1,
        mode => MODE_NORMAL,
        ...,
    }
    mode_func_table[context.mode](getchar(), context)
end

main_loop

VMみたいな形とも言える。
main_loopを

mode = mode_func_table[mode](c)

とかするとさらにそれっぽい。
それか継続で言うトランポリンとかいうアレかも。
スタックって言うから複数状態遷移のフローをpushできるみたいだけど。


Vimソースコードグローバル変数が多いのも関数がネストしまくりで
グローバル変数経由で渡すしかないとかそんな理由な気がする。気がするだけ。
あとGNUソースコード*2とかもグローバル変数使いまくりで
「プロセス分かれてるからええやろガハハ」な印象を受ける。
そこらへんはGNUとか関係なくて昔に書き始めたソースコードだからってだけだろう。多分。


まぁVimのfコマンドとかrコマンドとか入力を受け付けるのもあるし、
(別バッファにユーザからの入力を入れればいいという策もあるにしろ)
VimにしろVMにしろ、結局は「どこまでの操作を最小とするか」なんだと思う。
Vimのoperatorとtextobjのようにかけ算的に働かせるというアプローチはあるけど
それぞれどのように折り合いをつけるかが悩ましい。
operatorとtextobjの場合バッファ(文字列)の操作に特化してるから
そんなにややこしくはない気がする。
じゃあどんな操作が絡むとややこしいのか。
同期的な操作だろうか。I/Oみたいに、順序が変わってはいけない操作とか。
時間って厄介で何もしてなくてもパラメータが変わってくんですよ。やだー。
時間って何?

*1:最近pacman.vim書いてこんな形になった

*2:coreutilsとか