Just $ A sandbox

プログラミングと計算機科学とかわいさ

オートマトンで遊ぶやつを作った

この記事はHaskell Advent Calendar 2015 18日目の記事です.

Automatoy(オートマトン)で遊ぶやつを作ったので紹介します.

github.com

ブラウザで遊べる.
以下それっぽい解説.

Automatoyについて

http://myuon.github.io/automatoy/

"Def"タブでオートマトンを定義.
"Check"タブで与えられた文字列のacceptance checkができる.
"Conversion"タブでいくつかのサンプルオートマトンを読み込んだり, NFAをDFAに変換(いわゆるpowerset construction)できる.

また, 右上のImport/Exportでjsonに変換したり, jsonを読み込んだりできる.

もう少し扱えるオートマトンのバリエーションを増やしたり, acceptance checkにアニメーションを取り入れたり, アルゴリズムを増やしたり(complementとかそういうやつ)したら面白そう.
ものすごく気が向いたら頑張って開発する. (たぶんしない)

オートマトンの絵を書くのには, Cytoscapeを使った.
Haskellでコードを書いて, HasteJavascriptコンパイルしている.

HasteとTemplateHaskell(以下TH)

Hasteでは現在THがサポートされていないのでlensを含む, THが使われているライブラリをインストールして使うことができない.
lensの場合は, THを含まないより簡単なパッケージとしてlens-familyがあるのでこれを使った.

さて, lensには便利なmakeLensesという機能(TH)があるのでこれを使いたいのだが, lens-familyでこれをやるためには以下のコードを書く必要がある.

data Hoge = Hoge { _f1 :: k1, _f2 :: k2, .. , _fn :: kn }

f1 :: Lens' Hoge k1; f1 = lens _f1 (\p x -> p { _f1 = x })
.. -- フィールドの個数だけ繰り返す
fn :: Lens' Hoge kn; fn = lens _fn (\p x -> p { _fn = x })

これが最高に面倒くさい(特に, 各fiの型をそれぞれ宣言しなければならないことと, _fiとfiのいずれもexportしないといけないところ)のでどうにかしたかった.

これを解決する1つの方法として, Extensible Recordsを提供するextensibleというパッケージを使うというのがある.

extensibleはTHを使っているのでこれをそのままHaste環境にインストールはできないけれど, 少しアイデアを借りてそれらしいもの(もっと簡単なやつでよい)を実装することにした*1.

Union Type

結局以下のようなことができれば良い:

  • k1 .. kn型をまとめて管理するコンテナ(リストのようなもの)を用意する
  • コンテナへのアクセサとしてlensを提供する. getter "fi"のようにして値にアクセスできるようにする
  • アクセサはコンパイル時にvalidかどうかをチェックする. 存在しない名前などはコンパイルエラーにする. (コンパイル時にやる必要があるかどうかは趣味の問題だけれど, タイプミスなどはコンパイルエラーにしてくれた方が明らかに便利なので今回はコンパイル時にやる)

1番目はヘテロリスト(1-kind list)でよい.
2番目は型クラスを使って上手くやろう.
3番目は文字列の代わりにSymbolを使えば良い.

実装

data HList (xs :: [*]) where
  HNil :: HList '[]
  HCons :: x -> HList xs -> HList (x ': xs)

data Union (xs :: [*]) = Union (HList xs)

data (:<) (s :: Symbol) a = Tag a
data Name (s :: Symbol) = Name

実際にUnionを使うときは, Union <("intValue", 20), ("charValue", 'a')> のように, Symbolと値をペアにして使う.
これを(s :: Symbol) :< (a :: *)でペアを表すことにしている.
Nameは単にSymbolのみを型としてつかえるようにするもの.

class MakeLense (xs :: [*]) (s :: Symbol) out | xs s -> out where
  getter' :: Name (s :: Symbol) -> HList xs -> out
  setter' :: Name (s :: Symbol) -> (out -> out) -> HList xs -> HList xs

instance MakeLense ((k :< v) ': xs) k v where
  getter' _ (HCons (Tag v) _) = v
  setter' _ f (HCons (Tag v) xs) = HCons (Tag $ f v) xs

instance {-# OVERLAPPABLE #-} MakeLense xs syb out => MakeLense ((k :< v) ': xs) syb out where
  getter' k (HCons _ xs) = getter' k xs
  setter' k f (HCons x xs) = HCons x $ setter' k f xs

getter :: (MakeLense xs s out) => Name (s :: Symbol) -> Getter' (Union xs) out
getter syb = to $ \(Union hl) -> getter' syb hl

setter :: (MakeLense xs s out) => Name (s :: Symbol) -> Setter' (Union xs) out
setter syb = setting $ \f (Union hl) -> Union $ setter' syb f hl

lenses :: (MakeLense xs s out) => Name (s :: Symbol) -> Lens' (Union xs) out
lenses syb = lens (^. getter syb) (\u x -> set (setter syb) x u)

以下のような感じで使う.

data Flag = A | B | C deriving (Show)
data Gender = F | M deriving (Show)
type Ext = Union ["flag" :< Flag, "money" :< Int, "gender" :< Gender]

runExt :: StateT Ext IO ()
runExt = do
  lenses (Name :: Name "flag") .= B
  lenses (Name :: Name "money") += 30
  g <- use (getter (Name :: Name "gender"))
  case g of
    F -> lenses (Name :: Name "gender") .= M
    M -> lenses (Name :: Name "gender") .= F

main = do
  print "hoge"

  let iniExt = Union $ HCons (Tag A) (HCons (Tag 0) (HCons (Tag F) HNil)) :: Ext
  print $ iniExt ^. getter (Name :: Name "flag")
  print $ iniExt ^. getter (Name :: Name "money")

  ext' <- execStateT runExt iniExt
  print $ ext' ^. getter (Name :: Name "flag")
  print $ ext' ^. getter (Name :: Name "money")

Name :: Name "hoge"などと書かなければいけないのは面倒だけれど, 最初に書いたような面倒は多少軽減されたはず.

課題とか

このままだとUnion型の値を定義するのが面倒とか, 存在しないキーがあるときのエラーメッセージがあまりユーザーフレンドリーでないなどの問題がある.
ので, そのへんはextensibleの中では結構上手く解決されているので興味がある人はData.Extensible.Fieldなどを読むとよいでしょう.
自分は下のコードで大体満足しているけれど.

全体

MakeLense.hs(ちょっとだけ改良版)のコードは以下.

MakeLense.hs · GitHub

*1:正確に言えばここからアイデアを借りたわけではなくて, 以下のような実装を考えていたら「これは全く同じことがすでにextensibleで実装されているな」と気がついたわけです. 余談.

『ふたりのハードプロブレム』 感想

ncode.syosetu.com

以下, ネタバレ注意.

ジャンルとしてはSFでしょうが, 話自体は人間ドラマに重きが置かれているという印象でした.
登場人物がみんな感情豊かで人間臭いのがまた良いところ.
あまりあらすじ等を確認していなかったので, 前情報なしだといい意味で期待を裏切られて, 読んでいて楽しかったです.

例えば希海は自堕落でコミュニケーション能力が低くて, さらに自己評価も低いという負の側面が強い人間ですが, ルーミングの時はちゃんと相手に向き合って能力を発揮したり, リコには気を遣ってあげたりという優しい一面も見えます.
正負の側面がキチンと見えるので, 登場人物がちゃんと活きていて, 感情移入しやすいのはそれだけで良いところでしょう.
登場人物達は物語が進むと, ちゃんといいところだけでなく悪いところも(希海の場合は逆ですが)見えるので, みんなが人間臭く感じられるのが良いです.
個人的には, それまで掴みどころがなかった東さんの, ラスト間際で希海との会話の中で発している『私の若い頃』という言葉の重みがすごく好きです.
ここの会話は東さんが暗い過去を背負っていることがしっかり反映されているセリフで, さり気ないしあからさまではないのにその重みが感じられるところが好きです.

前半ではリコの性格が希海とは対照的に, 特に記憶がないだけ純粋で無邪気な世間知らずのアンドロイドといった印象な分, 後半の記憶を取り戻してからの希海への物言いと閉律症になってからの性格の変化には驚かされます.
リコには終始アンドロイドという記号が与えられ作中でもそれに振り回されるという雰囲気こそありますが, 後半のリコの変化には確かに人間臭さがあり, 確かにリコも人間として描かれているのだと思えます.
そのためにリコの心についての悩みは余計ややこしいし本人もすごく悩んでいるようですが, リコの描かれ方を見ているとその答えはもう出ているのではないかとも思います.

少し話は変わりますが, 希海はずっと『機械になりたい』と繰り返していて, 本人の目の前でなんてことを, と読者としては思います.
ここでの機械というのが何を指しているのかは少し曖昧で, 希海の言い方的には何か産業ロボットに対応するものを指しているのかなとも思いますが作中に出てくる機械はアンドロイドくらいで, しかもリコも自分のことをはっきりと機械と言います.
機械のリコがこんなに悩み苦しんでいるのに機械になれば救われると考えているのはなぜだろう?という疑問が残りました.
でも逆に希海にそういう思いがあるからこそ, リコが自分は機械だから苦しくないはずみたいな(正確なセリフを忘れたので曖昧)強がりを言うシーンでは切なさを感じてしまうのかもしれません.

アンドロイドだけかと思ったら精神世界のアクロバティックバトルが描かれたり, キャラクターの印象がガラリと変わって裏の面が見えたり, そういう意味で期待を裏切られたので読んでてとても楽しかったです.
どこかから借りてきたような不思議語彙の数々も相まって硬派なSFの印象ですが, 希海とリコが成長し, 二人で助けあって苦難を乗り越えるドラマはとても純粋で心を動かされました.
続編が出ればまた読みたいです.

『Normalize Human Communication のまひゅ』 感想

http://mukiryokukan.sakura.ne.jp/NormalizeHumanCommunication.htm

以下, ネタバレ注意.

ずっと前から友人から勧められていたのでやりました.
プレイしてから多少時間が空いてしまったので記憶が少し曖昧ですが感想を述べることに.
一言で言うと「緩やかな死(色んな意味で)」って感じの作品です.

まずストーリーについて.
話が全体的に緩やかに死に向かって進んでいて, 終盤にどんでん返しとかビックリ展開がない分, 作者の意図というか作品のメッセージ性がより浮き彫りになっているのが良いです.
逆にアレでほんわかハッピーエンドだったら戦犯ものだけれど.
じわじわ死に向かって進むのがなんとも言えない心持ちにさせられますが, 不思議と冗長さはあまりなかったです.

次にキャラクターについて.
ヒロインの鈴乃というキャラだけ良くも悪くも目立ちます.
全キャラで一番冷めているというか, 他の人たちのようなフィクションのキャラクターという雰囲気が一切ない.
主人公がよくいる判子ノベルゲーム主人公的思考で, 芝居がかったこととか希望をもてそうなセリフを吐くくせに, 鈴乃がそれに全く釣られないところや, 逆に主人公に現実の厳しさを突きつけるようなセリフがたくさん.
そういう対比された二人の会話はすごく惹かれるものがありました.
鈴乃のセリフは考えさせられるものが多いですが, つらい感じではなく, かといって諦めきった人の発言とも思えないなんとも言えない感じがあります.
それだけに, 話がだんだん進んでいくに連れて発言の重みが増すところはよくできています.
最後の最後になって, 日記とモノローグ(というか走馬灯?)で初めて鈴乃の本心が分かるという演出も好きです.
最後まで読み進めて初めて, 彼女のつらいとか悲しいみたいな歳相応の弱い部分が垣間見えて, そういう苦しみに耐えてきた, しかもそれを主人公に伝えることはしなかったという強さには胸を打たれるものがありました.

彼女とは対照的にお父さんは変わっているけど裏がなさそうな感じが好印象でしたね.
個人的に, 初佳さんの立ち位置はよく分かりませんでした.
鈴乃の本心をある程度汲み取れる程度には親しい立場だったはずなのに彼女の思いとは全然違う反応ばかりしているような気がして(鈴乃が死んでから泣いてるところとか, お墓のところで未来に向けてみたいなことを言うところとか), その割に主人公とは違って心情が細かく描写されることがなかったので.
サイトのシナリオ裏話にあった, 初佳さんのアイスピックヤンデレエピソードは普通に入れてよかったんじゃないかなと.
その方が彼女の感情は理解できる気がするし, あと単純に見てみたかったし.

最後に形式的なことを.
操作性は少し難ありで, 個人的には文字送りの時にクリックすると「ページ末まで表示」ではなく「次ページに進んでしまう」のがつらかったです.
ふとしたところの演出がとても丁寧に作られていたので没入しやすかったのはとてもよかったと思います.
アプリ化したとき(?)にキャラクター絵が一新されたみたいで, ググるとめっちゃ可愛い絵がヒットしますが原作プレイした身から言うと原作絵の方がこの作品には合っていると感じました.

プレイしているときはもっと色々感じるところがあったはずだけど, 時間が経ったので忘れました.
また再プレイして, 感想もついでに書き直したい.