キーと値からなる設定ファイルをパースするとします。設定ファイルはこんな感じです。
name=Test value=5
マップなどで値を持つという方法もありますが、ちゃんと型付けするために専用の型を用意することにします。そうするとパースするコードはこんな感じでしょうか。
import Control.Monad import Data.Maybe data Config = Config { name :: !String, value :: !Int } deriving Show parse :: String -> Config parse s = let values = catMaybes $ map parseLine $ lines s in Config (fromMaybe "" $ lookup "name" values) (fromMaybe 0 $ lookup "value" values >>= maybeRead) parseLine :: String -> Maybe (String, String) parseLine s = case break (== '=') s of ("", _) -> Nothing (n, v) -> Just (n, tail v) maybeRead :: Read a => String -> Maybe a maybeRead s = case reads s of [(v, [])] -> Just v _ -> Nothing
設定項目が増えたら、コンストラクタの引数を増やして、parse関数を書き換えます。しかし、この二つが離れていると分かりにくいし、名前や順番を間違っていても気づかなそうです。そこで、Template Haskellを使って、設定をまとめられるようにしてみます。
{-# LANGUAGE TemplateHaskell #-} module Key where import Data.List.Split import Data.Maybe import Language.Haskell.TH keys :: [(String, TypeQ, ExpQ, ExpQ)] keys = [("name", [t|String|], [|Just . id|], [|""|] ), ("value", [t|Int|], [|maybeRead|], [|0|] ), ("list", [t|[String]|], [|Just . sepBy "," |], [|[]|] ), ("tuple", [t|(String, Int)|], [|maybeRead|], [|("", 0)|])] maybeRead :: Read a => String -> Maybe a maybeRead s = case reads s of [(v, [])] -> Just v _ -> Nothing
keysは、設定項目のリストで、名前、型、パースするための関数、デフォルトの値を指定します。設定項目を二つほど増やしてみました。ここから、Config型とパースするための関数を生成するのが以下のコードです。
{-# LANGUAGE TemplateHaskell #-} import Control.Monad import Data.Maybe import Language.Haskell.TH import Key $(sequence [dataD (cxt []) (mkName "Config") [] [recC (mkName "Config") (map (\(n, t, _, _) -> varStrictType (mkName n) (strictType isStrict t)) keys)] [''Show]]) parse :: String -> Config parse s = let values = catMaybes $ map parseLine $ lines s in $(foldl appE (conE (mkName "Config")) (map (\(n, _, f, d) -> [|fromMaybe $d $ lookup n values >>= $f|]) keys)) parseLine :: String -> Maybe (String, String) parseLine s = case break (== '=') s of ("", _) -> Nothing (n, v) -> Just (n, tail v)
設定項目が変わるときのメンテナンス性は上がりましたが、コード自体が複雑になって面倒くさくなったような気もします。微妙。