HaskellでReaderT
を使って組むとよい、という記事を見てちょっと試してみた感想。
The ReaderT Design Patternに、 アプリ全体で参照したい値や書き換えたい値をReaderTで扱うとよい、というようなことが書いてあった。 要点をよくわかってないけど試したところまでメモしておく。
ReaderT
の前に、関連するモナドの動作もよく理解してないので復習しつつ。
IORef
値を書き換えたい場合、IORefが使える:
import Data.IORef (IORef, newIORef, readIORef, writeIORef) |
扱うにはIOモナド内で行う必要がある。
Reader
値を参照したいだけの場合、Readerモナドが使える。 runReaderで走る内部に環境を暗黙的に引き回すことができて、 askで値の取得ができる:
import Control.Monad.Reader (Reader, runReader, ask) |
ReaderT
Reader
で渡す環境にIORef
を入れてもIOモナドじゃないので値を読んだり書いたりできない。
そこでReaderT
モナドを使用する。
ReaderT
モナド内部でliftIOでIOモナドを実行できる。
走らせるにはrunReaderT
を使う:
import Data.IORef (IORef, newIORef, readIORef, writeIORef) |
ReaderT Design Pattern
ReaderT
の場合と内容は変わらないけど、The ReaderT Design Patternの冒頭で書かれているようにEnv
やApp
を定義してみる:
import Data.IORef (IORef, newIORef, readIORef, writeIORef) |
Haskellで作られているNESエミュレータhnesもEmulator型をそのように使っているみたいだった (Nesがアプリ全体の情報を保持する環境か)。
考察
- 「Better globals」とのことで暗黙的に全体の設定を引き回すことができて必要な箇所で参照できる、というのが利点とのことで、
- 値を書き換えたい場合にも対応できて、
- こういう仕組みで作ればアプリ作れるよね
ということでいいかと思ったんだけど、
- IOモナドの代わりに
ReaderT
モナドになっただけで、全体をモナドとして書かなきゃいけないことは変わりない - 引き渡す環境がアプリ全体どこからでも参照・変更される可能性があるというのは、スパゲティになる可能性が高くてダメそう
- それを考慮して部分によって渡す環境を絞るのは何が必要か決めるのが難しいし面倒
というので問題があるんじゃないか、と思った。
まあ元記事もHaskell自体もよく理解してないのでなんともなんだけど…。