Haskell -- brick を使ってみる 2

今回試すのは CustomEventDemo.hs といふ brick のデモだ
前回と同様に stack を使って 走らせてみた

CustomEventDemo

コードの全貌は以下

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Main where

import Lens.Micro ((^.))
import Lens.Micro.TH (makeLenses)
import Lens.Micro.Mtl
import Control.Monad (void, forever)
import Control.Concurrent (threadDelay, forkIO)
import qualified Graphics.Vty as V

import Brick.BChan
import Brick.Main
  ( App(..)
  , showFirstCursor
  , customMain
  , halt
  )
import Brick.AttrMap
  ( attrMap
  )
import Brick.Types
  ( Widget
  , EventM
  , BrickEvent(..)
  )
import Brick.Widgets.Core
  ( (<=>)
  , str
  )

data CustomEvent = Counter deriving Show

data St =
    St { _stLastBrickEvent :: Maybe (BrickEvent () CustomEvent)
       , _stCounter :: Int
       }

makeLenses ''St

drawUI :: St -> [Widget ()]
drawUI st = [a]
    where
        a = (str $ "Last event: " <> (show $ st^.stLastBrickEvent))
            <=>
            (str $ "Counter value is: " <> (show $ st^.stCounter))

appEvent :: BrickEvent () CustomEvent -> EventM () St ()
appEvent e =
    case e of
        VtyEvent (V.EvKey V.KEsc []) -> halt
        VtyEvent _ -> stLastBrickEvent .= (Just e)
        AppEvent Counter -> do
            stCounter %= (+1)
            stLastBrickEvent .= (Just e)
        _ -> return ()

initialState :: St
initialState =
    St { _stLastBrickEvent = Nothing
       , _stCounter = 0
       }

theApp :: App St CustomEvent ()
theApp =
    App { appDraw = drawUI
        , appChooseCursor = showFirstCursor
        , appHandleEvent = appEvent
        , appStartEvent = return ()
        , appAttrMap = const $ attrMap V.defAttr []
        }

main :: IO ()
main = do
    chan <- newBChan 10

    void $ forkIO $ forever $ do
        writeBChan chan Counter
        threadDelay 1000000

    let buildVty = V.mkVty V.defaultConfig
    initialVty <- buildVty
    void $ customMain initialVty buildVty (Just chan) theApp initialState

元の リポジトリにあるコードを ちょっと改變した (バージョンが十分新しいので cppを使ふ部分は なくて良いと判断した)
とてもシンプルな デモ画面だが Counter value is:
の數字が 一秒ごとに 1ずつ 増へていく

data CustomEvent = Counter deriving Show

これは それらしく言へば CustomEvent型の データコンストラクタ が Counter といふ名前で定義されてゐる といふことでいいのかな

data St =
    St { _stLastBrickEvent :: Maybe (BrickEvent () CustomEvent)
       , _stCounter :: Int
       }

makeLenses ''St

状態を表すStは 要素が二つあって ひとつは明らかに カウントを数えるものだが 最初の要素は 「最後のbrickイベント」といふ名前になってゐる
これが イベントを受けとったりするやつなのだらうか
makeLenses のところの理解は 後回しにしやう〜(前回も出てきたけど)
まあ ちょっと 軽く考へておくと Lens(讀みは レンズ でいいのかな) といふのは
Haskell入門」の267ページによると
「複雑なデータ構造への効率的なアクセス」
をするためのものーーださうだ
よし! あとで 勉強しよー
といふわけで 今は無視

drawUI :: St -> [Widget ()]
drawUI st = [a]
    where
        a = (str $ "Last event: " <> (show $ st^.stLastBrickEvent))
            <=>
            (str $ "Counter value is: " <> (show $ st^.stCounter))

str といふのは 前回も出てきたが スルーしてゐた
まあ 文字表示に關するものだらうことは予想できるのだが
ちょっと 見ておくか
import文から分かるやうに str は
import Brick.Widgets.Core モジュールの函數のはずだ
Hackage で確認する

str :: String -> Widget n

となってゐる
やはり 文字列を取って ウイジットなるものを返す函數だ
確か brickの説明にちょろっと 書いてあったと思ふのだが
この brickといふやつは ウイジットといふものの集合體で アプリを實行してゐる といふことらしいから
brickを使ふからには この ウイジットヘの理解が必須なのだらう (まだ全然理解してないけど)
デモ画面を見ると
Last event: Just (AppEvent Counter)
となってゐる
drawUI によれば この Just (AppEvent Counter)
といふのは st^.stLastBrickEvent つまり 状態Stの中の stLastBrickEvent の値を表してゐる
ふーん AppEvent といふ ブリックのイベント があって それが Counter っていふ名前なのね〜 ていふ理解かな 今は

appEvent :: BrickEvent () CustomEvent -> EventM () St ()
appEvent e =
    case e of
        VtyEvent (V.EvKey V.KEsc []) -> halt
        VtyEvent _ -> stLastBrickEvent .= (Just e)
        AppEvent Counter -> do
            stCounter %= (+1)
            stLastBrickEvent .= (Just e)
        _ -> return ()

前回もあった appEvent函數だ
case文の中にある最初の行は Escキーを押すイベントだ
haltは中断ってことだろう (前回もさうだったと思ふ) ちなみに %= とか .= とかの謎オペレータは
import Lens.Micro.Mtl
でインポートされたやうだ
まあ 明らかに %= (+1) は stCounterに1を加えて代入
.=は 單純に置き換へ といふのが見てとれる
しかし あれだな〜 ここで 状態變數(?)を操作できるんだな〜

initialState :: St
initialState =
    St { _stLastBrickEvent = Nothing
       , _stCounter = 0
       }

theApp :: App St CustomEvent ()
theApp =
    App { appDraw = drawUI
        , appChooseCursor = showFirstCursor
        , appHandleEvent = appEvent
        , appStartEvent = return ()
        , appAttrMap = const $ attrMap V.defAttr []
        }

initialStateについては 明解だらう
theApp の カーソルの定義らしきものが showFirstCursor になってゐる
前回は 何だっけ appCursor といふのになってゐて
それが フォーカスリングカーソル みたいなものによって定義されてたな〜
たぶん今回は 「普通の」カーソルっていふことか?
デモ画面には カーソルらしきものは表示されてゐないが・・・
AttrMapの部分は attrMapといふ函數を使ってゐる (Brick.AttrMap をインポートしてゐる)
今は ここは深入りしない

main :: IO ()
main = do
    chan <- newBChan 10

    void $ forkIO $ forever $ do
        writeBChan chan Counter
        threadDelay 1000000

    let buildVty = V.mkVty V.defaultConfig
    initialVty <- buildVty
    void $ customMain initialVty buildVty (Just chan) theApp initialState

さすがに 最初の newBChan 10 は氣になるので 少し調べた
モジュール Brick.BChan
BChan といふデータは
BChan is an abstract type representing a bounded FIFO channel.
と書かれてゐる
有限の FIFOチャネルを表す?
FIFO って First In First Out ってこと? 情報技術者試験の勉強で出てきたやうな・・・
スタックみたいなもんだと 考へていいのかな・・・
んで newBChan 10 といふのは そのチャネルを10個まで持てるやうなデータ BChanをつくるってこと?

void や forever は Control.Monad モジュールから來てゐる
void は 結果を返さないよってことだらう
forever は その名の通り ずっと繰り返せってこと だと思ふ
threadDelay の百万が 1秒にあたるんだ〜
てことは threadDelay の 1 は 百万分の1秒! すごいな〜

前回のやつと 今回のやつを うまく組み合はせて 例へば stLastBrickEvent の値を Input2に stCounter の値を Input1に表示したりできるかな〜
今度それに挑戦してみやうかな