YiでEditorM aの状態を変更したい時はMonadEditor使えば

# YiでEditorM aの状態を変更したい時はMonadEditor使えば
　最近はYiコントリビューターになるために、`.vimrc`のキーマッピングを`yi.hs`に再実装している。
stackageのlts-7.4にあるYiはまだドキュメントが充実していないことがわかってきた。  
(ただしHaddockにあるHaskellの型シグネチャと実装読みによってだいたいは補える)

Yi布教を兼ねてメモしていく。

# YiでEditorM aの状態を変更したい時は、最悪MonadEditor m => m a使えばいい
YiのVimインターフェース
(まだこれに対する正確な用語を僕は持たないので、
 `Yi.Keymap.Vim`以下の関数や`defaultVimConfig`などによって提供される
 VimライクなYi環境をVimインターフェースと呼称している)  
で設定する際に困惑したのが、

- ウィンドウを閉じる
- バッファを破棄する
- バッファの指すファイルを保存する

といった操作を扱う型に`YiM a`, `EditorM a`という2つの型があったこと。  
これは多分…`YiM`がYi全体を覆う、状態と操作の、全ての包含であることに対して  
`EditorM`はVimインターフェースでいう、現在がNormalモードであるとか、バッファをいくつ持っているかどれを持っているか…だとかいう
まさにエディタとしての状態を`State`で持っているのかなと思う（そして操作も`State`によって提供されているのかな）。

- [`YiM`](https://www.stackage.org/haddock/lts-7.4/yi-0.12.6/Yi-Types.html#t:YiM)
    - `ReaderT Yi IO a`のnewtype
- [`EditorM`](https://www.stackage.org/haddock/lts-7.4/yi-0.12.6/Yi-Editor.html#t:EditorM)
    - `ReaderT Config (State Editor) a`のnewtype

## MonadEditorについて
`YiM`と`EditorM`は両方`MonadEditor`インスタンスとして定義されているので、この`implBinding`のようなアドホック多相な関数を作れる。

```haskell
-- The keymapping implementor for both of V.VimBindingY and V.VimBindingE
implBinding :: MonadEditor m => V.EventString -> m () -> V.EventString -> V.VimState -> V.MatchResult (m V.RepeatToken)
implBinding key context = \key' state ->
  case V.vsMode state of
    V.Insert _ -> (const $ context >> return V.Continue) <$> key' `V.matchesString` key
    _          -> V.NoMatch
```

これを使って、Vimインターフェースのキーマッピングを表す型であるVimBindingを得る。

- [VimBinding](https://www.stackage.org/haddock/lts-7.0/yi-0.12.6/Yi-Keymap-Vim.html#t:VimBinding)

`inoremapE`と`inoremapY`は、`EventString`と、`YiM ()`または`EditorM ()`を使ってVimBindingを作る関数。  
２つの関数に分岐してしまっているのは、VimBinding型の値構築子の定義の関係。

```haskell
-- Like inoremap of Vim from V.EventString for EditorM
-- for ∀a. V.Insert a
inoremapE :: V.EventString -> EditorM () -> V.VimBinding
inoremapE key x = V.VimBindingE $ implBinding key x

-- Like inoremap of Vim from V.EventString for YiM
-- for ∀a. V.Insert a
inoremapY :: V.EventString -> YiM () -> V.VimBinding
inoremapY key x = V.VimBindingY $ implBinding key x
```


## 本題: MonadEditorを使って、問題を解決する
　僕は、.vimrcにも定義してある、日常的に使っている
 「Insertモードで、カレントバッファの指すファイルを保存した後にNormalモードに戻る」というキーマッピングを定義しようとした。  
僕は`yiWrite`, `switchModeE Normal`という関数を見つけた。

- `viWrite :: YiM ()`
    - カレントバッファの指すファイルを保存する
- `switchModeE :: VimMode -> EditorM ()`
    - 現在のVimインターフェースのモード(`VimState`型の`vsMode`)を変更する
- `data VimMode = Normal | Insert Char | ...`

でもこれらの関数は型が違うので、同時に使用できない。

以下の要点と関数に注目して、`switchModeY :: VimMode -> YiM ()`を定義することで解決した。

- `MonadEditor`インスタンスは必ず`MonadState`である
    - [`MonadEditor`の定義](https://www.stackage.org/haddock/lts-7.4/yi-0.12.6/Yi-Editor.html#t:MonadEditor)による
- `getEditorDyn :: (MonadEditor m, YiVariable a) => m a` (いくつかの定義を省略している)
- `putEditorDyn :: (MonadEditor m, YiVariable a) => a -> m ()` (いくつかの定義を省略している)
- `instance YiVariable VimState`

- => `YiM`を`MonadState`として使えば`VimState`を変更できる

これを書いた。

```haskell
escVimInsWithSave = inoremapY' "<C-k><CR>" (viWrite >> switchModeY V.Normal)  -- Yi interpret <C-j> as <CR>
  where
    switchModeY :: V.VimMode -> YiM ()
    switchModeY mode = getEditorDyn >>= \s -> putEditorDyn s { V.vsMode = mode }
```

あとはこれをキーマッピングに追加すればいいだけだっ！

解決。
ヨッシャ。

## 結論
`VimState`を`MonadEditor`で使えば、Vimインターフェースの状態についてはなんとかなりそう。


## 余談: EventとEventStringについて
僕がYiの型を巡回して最初に迷ったのは、キーマッピングをされる対象のキーを表す型が、`Event`と`EventString`の2つあったことで、  
 これは`EventString`が`Yi.Keymap.EventUtils`以下に定義されていることから、
 「`EventString`はVimインターフェースのために用いられるものなのかな」と考えている。  
 以下の関数によって相互に変換ができる
 
- [`Yi.Keymap.EventUtils`](https://www.stackage.org/haddock/lts-7.4/yi-0.12.6/Yi-Keymap-Vim-EventUtils.html)
    - `stringToEvent :: String -> Event`
    - `eventToEventString :: Event -> EventString`
- [`Yi.Keymap.Vim.Common`](https://www.stackage.org/haddock/lts-7.4/yi-0.12.6/Yi-Keymap-Vim-Common.html#t:EventString)
    - `_unEv :: EventString -> Text`

- - -

この記事はYiによって書かれた。  
lts-7.4のYiにはまだMarkdownのためのModeがない。