こんにちは,夜間ならこんばんは.ゆきくらげです.最近,PureScriptでの開発にハマっているのですが,それにあたって大いに参考になっているのが halogen-realworld というプロジェクトです.
このプロジェクトは実用で使えそうな技術をギュッとまとめたすばらしい物であるのですが,今回はその中でも Capability というモジュール群と AppM というモジュールが,どのような設計思想に基づいて作られているのか考察していきたいと思います.実際のところ全部コメントで書かれてますが.
短い記事です.あと衝動で書いているのでコードが間違っているかもしれません.
型クラスの利点
例えば次のような型クラスを作ります.
class C a where
func1 :: a -> Int
このような型クラスがあれば,次のように具体的な型がわからなくてもfunc1
を利用する関数が型の整合性を保ったまま書けます.次に示すのは,クラスC
のインスタンスである型の値を受け取り,それをfunc1
に通して得た値をshow
する関数です.
f :: forall a. C a => a -> String
f = show <<< func1
C
を持つクラスとして,String
があるでしょう.文字列の長さを返します.
instance C String where
func1 str = length str
newtype
で型を作ってもいいですね.
newtype T = Cons String
instance C T where
func1 (Cons str) = length str
さて,型クラスの利点として,具体的な実装とその利用が切り離せるという点があります.すなわち,f
では利用,具体的な実装はインスタンス定義の際に行うということです.
さらにこの仕組みを何重にも重ねられるでしょう.すなわち利用が新たな実装となって他で利用するという感じです.
このように,型クラスを中間地点のようにして,実装を同時に型の整合性を保ったまま進めることができます.良いですね.
どうも,モナドです
おはようございます.やはりモナドです.とは言っても上の実装をそのままモナドに当てはめるだけです.次のような実装ができます.
class Monad m <= C m where
func2 :: Int -> m Int
こうすることでどのような利点があるでしょうか.
先程の実装ではある意味,型変数a
に囚われています.(func1
の型はa -> Int
でした.)a
が直接入らない機能は制約上導入することができません.この問題は上のm
のように型変数を型構築子を表すものとして導入することで解決できます.
次に,モナドの表現力の高さです.世の中には様々な人が開発した様々なモナドがあります.これらをモナド変換子を使って合成することで,僕の考える最強のモナドを作ることができたりします.その僕の考える最強のモナドを使ってインスタンスの実装をすれば,僕の考える最強の実装が可能になります.良いですね.
利用の際は次のようにします.こちらはInt
型の値を受け取り,func2
で何らかの変換をして,show
する関数です.
f :: forall m. C m => Int -> m String
f x = do
y <- func1 x
pure $ show y
func2
の実装は様々です.簡単なエラー処理をしたい文脈でMaybe
を使用したいとします.Int
型の値を受け取り,0
だったならNothing
,それ以外なら100÷その値を返します.
instance C Maybe where
func1 0 = Nothing
func1 n = Just $ 100 `div` n
ランダム生成もできます.モナドなので.
どんな値を与えてもそれを無視して0から100までのランダムな値を返します.
instance C Effect where
func1 _ = randomInt 0 100
組み合わせることもできます.モナドなので.
Int
型の値を受け取り,0
だったならNothing
,それ以外ならランダムな値÷その値を返します.
instance C (MaybeT Effect) where
func1 0 = MaybeT $ pure Nothing
func1 n = do
m <- lift $ randomInt 0 100
pure $ m `div` n
newtype
で型を作ってもいいですね.
newtype T = Cons (MaybeT Effect)
instance C T where
func1 0 = Cons $ MaybeT $ pure Nothing
func1 n = Cons $ do
m <- lift $ randomInt 0 100
pure $ m `div` n
良いですね.
ずばり,コレが冒頭の Capability というモジュール群と AppM というモジュールがやっていることです.Capability ではクラス定義を行って,AppM では実装を行っています.