入力の型を厳格にすることでhandlingを避ける
Maybe aとNonEmpty aの違いの説明にもなっている
除算をする2つの関数
code:maybe.hs
safeDivide :: Int -> Int -> Maybe Int
safeDivide i 0 = Nothing
safeDivide i j = Just (i div j)
code:notempty.hs
safeDivide :: Int -> NonZero Int -> Int
safeDivide i (NonZero j) = i div j
どちらも型安全に除算を行う
この関数は実行時エラーは起こさない
型安全と言える
誰がNothingをhandlingするか?が異なる
maybe.hsの場合、呼び出し側は、
好きにIntを突っ込んで、
結果を利用するためにhandling(null check的)する必要がある
notempty.hsの場合、呼び出し側は、
Intより制限のあるNonZero Intを突っ込んで
結果の利用時にはhandlingの必要はない
NonZero Intを作るためには何らかの処理を噛ませないといけないので、この2つだけ比較すればどちらも嬉しさは変わらないように見える
Entityを定義するときに差が出る
code:hs
data Order = Order { items :: Item }
code:hs
data Order = Order { items :: NonEmpty Item }
前者を選んだ場合、
プログラム全体に、Maybeが現れる
それを扱う関数の殆どが返り値の型がMaybeになる
誰かが自分の中でhandlingしないといけない
引数の制限が緩いので、1つ1つ関数の責務も大きくなる
関数は、その引数の型が取りうる値を全てサポートする責任がある。
safeDivideなら、引数はIntなので「0で割る」こともサポートする必要がある
後者を選んだ場合、
型の定義に嘘がない
型は仕様を実践できている
プログラム内部ではhandlingが一切不要になる
handling的なことが必要なのは外部と接続する箇所だけ
validationするのは、外部と接続する箇所だけになるから
Functional Core, Imperative Shell的な
layered architectureで言う、最も外側でvalidationさえすれば、
内側では常にNotEempty Itemを持ち回すことができる
その中でいちいちnull checkのようなことをする必要がない
返り値がMaybeだと、明らかに正しい文脈でもhandlingが必要になる
例 ref
code:hs
getConfigurationDirectories :: IO FilePath
getConfigurationDirectories = do
configDirsString <- getEnv "CONFIG_DIRS"
let configDirsList = split ',' configDirsString
when (null configDirsList) $
throwIO $ userError "CONFIG_DIRS cannot be empty"
pure configDirsList
main :: IO ()
main = do
configDirs <- getConfigurationDirectories
case head configDirs of
Just cacheDir -> initializeCache cacheDir
Nothing -> error "should never happen; already checked configDirs is non-empty"
getConfigurationDirectoriesは、[FilePath]を返す
この関数内ではenvから文字列を読み込んで、空でないかチェックした後に、[FilePath]を返している
main内のconfigDirsは必ず空にはならないため、head configDirsは必ずJust FilePathを返す
が、Maybeなので条件分岐する必要がある
問題としては大きく2つある(記事内では3つ挙げられている)
絶対にNothingにならないのにhandlingが必要(このノートの趣旨)
getConfigurationDirectoriesでcheckされているかどうかがデータ構造から読み取れない
ref Parse, don’t validate
関連
篩型
Quotient Types
参考
Parse, don’t validate
Type Safety Back and Forth
良い記事
だけど、forwardとbackの用語の使い方がいまいちピンと来ていないmrsekut.icon
Maybeの方を「pushing responsiblity forward」
NonEmptyの方を「pushing responsibility back」
と呼んでいる
時系列で見れば逆じゃない?という気もする
とにかく、これを自分の中で説明できるまではこの用語は使わないほうが良さそう
実際Parse, don’t validateでは、NonEmptyの方をforwardと呼んでるmrsekut.icon
Keep your types small...
上の記事の次の記事
『Simple Made Easy』に繋がる
予定の文脈で
push forwardが、「前倒し」
push backが「先延ばし」