検証済みであることを型で明示する
検証の前後で型を変えることで、その値の検証が済んでいるかどうかを確認できる
後続する関数は、「検証済みの型」を引数に取るように定義する
Booleanは情報量として薄いので「検証の成功」を表すのに適切でない
何が嬉しいか?
検証作業の漏れが起きない
後続する関数を、「検証済みの型」を引数に取るように定義しておく
検証に成功していないと引数に渡せない
従って、検証することを強制でき、もし漏れていてもコンパイラが教えてくれる
型で意図を読み取れる
e.g. これは「ただのlistではなく、sort済みのlistを引数に取る」ことを想定して定義された関数ですよ、というのを利用者に伝えられる
実装ミスが減る
型に曖昧性が少なければ少ないほど、それに当てはまる実装が少なくなるので実装ミスは減る
ここで言う「検証」の例
文字列が空でないことを検証する
検証後に String
ではなく NonEmpty String
に変換する
リストをsortする
sort後に、 [a]
ではなく SortedList a
に変換する
userがloginしていることを検証する例
refif節でvalidationしてloginできるかどうかを判定している
hsloginUser :: 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
という型に変換するように書き換えると良い
hsdata 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
を返す関数で判定することで文脈が損失する例
refhsadd :: (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
節の中身を入れ替えても型エラーにならない
こう書き直せばいい
hsadd :: (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のものを残すのか、型から推測できない
ではどうするか?
より明示的な型でやる
1.hsdata Keep = Discard | Keep
filter1 :: (a -> Keep) -> [a] -> [a]
maybeを使う
3.hsfilter3 :: (a -> Maybe b) -> [a] -> [b]
素数判定を行った後に、それを明示する型に変換する例
refhsnewtype Prime = Prime Int
prime :: Int -> Maybe Prime
キーワードとしてはこの辺も近いっぽい