検証済みであることを型で明示する
検証の前後で型を変えることで、その値の検証が済んでいるかどうかを確認できる
後続する関数は、「検証済みの型」を引数に取るように定義する
Booleanは情報量として薄いので「検証の成功」を表すのに適切でない
何が嬉しいか?
検証作業の漏れが起きない
後続する関数を、「検証済みの型」を引数に取るように定義しておく
検証に成功していないと引数に渡せない
従って、検証することを強制でき、もし漏れていてもコンパイラが教えてくれる
型で意図を読み取れる
e.g. これは「ただのlistではなく、sort済みのlistを引数に取る」ことを想定して定義された関数ですよ、というのを利用者に伝えられる
実装ミスが減る
型に曖昧性が少なければ少ないほど、それに当てはまる実装が少なくなるので実装ミスは減る
ここで言う「検証」の例
文字列が空でないことを検証する
検証後にStringではなくNonEmpty Stringに変換する
リストをsortする
sort後に、[a]ではなくSortedList aに変換する
userがloginしていることを検証する例 ref if節でvalidationしてloginできるかどうかを判定している
code:hs
loginUser :: User -> Password -> PasswordHash -> IO Page
loginUser user pwd pwdHash =
if validateHash pwd pwdHash
then getUserPage user
else accessDenied user
validateHashの返り値はBool
この検証の前後でuserの型に変化がない
検証してようがしてまいが、同じものとしてuserを使えてしまう
だからelse節内で誤ってgetUserPageを呼ぶこともできてしまう
呼んだときに型エラーにならない
validateHashの実行を忘れて、直接getUserPage userを呼ぶこともできてしまう
検証後にUserAuthという型に変換するように書き換えると良い
code:hs
data UserAuth
validateHash :: Password -> PasswordHash -> Maybe UserAuth
validateHash = undefined
getUserPage :: UserAuth -> User -> IO Page
getUserPage = undefined
loginUser :: User -> Password -> PasswordHash -> IO Page
loginUser user pwd pwdHash = case validateHash pwd pwdHash of
Nothing -> accessDenied user
Just auth -> getUserPage auth user
検証後に型を変える
getUserPageは検証済みのuser(UserAuth)のみを引数に取るようにする
検証忘れが起きない
書き換え前のthen節とelse節を逆に書くようなこともできなくなる
Boolを返す関数で判定することで文脈が損失する例 ref code:hs
add :: (a -> Maybe Int) -> (a -> Maybe Int) -> a -> Maybe Int
add f g x =
if isNothing (f x) || isNothing (g x)
then Nothing
else Just (fromJust (f x) + fromJust (g x))
f, gは検証を行うような関数
isNothingというBoolを返す関数で判定しているため、検証の情報が伝達していない
if節とelse節の両方でf xを実行している
本来ならf xを1回実行すればJustかNothingかの情報が乗るので、それを上手く使えるはず
then節とelse節の中身を入れ替えても型エラーにならない
こう書き直せばいい
code:hs
add :: (a -> Maybe Int) -> (a -> Maybe Int) -> a -> Maybe Int
add f g x = do
a <- f x
b <- g x
pure $ a + b
filter :: (a -> Bool) -> [a] -> [a]
デフォルトのfilterは判定する関数がBoolなので分かりづらい
意図を真逆に解釈できてしまう
trueのものを残すのか、falseのものを残すのか、型から推測できない
ではどうするか?
より明示的な型でやる
code:1.hs
data Keep = Discard | Keep
filter1 :: (a -> Keep) -> a -> a maybeを使う
code:3.hs
filter3 :: (a -> Maybe b) -> a -> b 素数判定を行った後に、それを明示する型に変換する例 ref code:hs
newtype Prime = Prime Int
prime :: Int -> Maybe Prime
キーワードとしてはこの辺も近いっぽい