URLをあらわす文字列であるとか、0以上であることが要求される整数であるとか、値が何らかの制約を満たすことを要求される場面は多くあります。
たとえば、男女2頭のヤギさんの角の本数を引数にとって、その2頭のヤギさんが愛し合って交尾して子どもを作っても問題ないかを判断する関数を定義します。
canMakeLove : Int -> Int -> Bool
それぞれの角の数を引数にとって Bool型を返す自然な設計ですね?
でも、ちょっとまってください!
有角のヤギさんは2本しか角を持っていません。
そのため、ヤギさんのとりうる角の数は以下のいずれかしかありえないはずです。
- 0本: 生まれつき無角のヤギさんか、有角の仔ヤギちゃんの角芽を焼きごてで焼いた(断末魔のような悲鳴がする)
- 1本: 有角の仔ヤギちゃんの角芽を焼いたけど不十分で1つだけ残った(断末魔の悲鳴が無駄になった😱)
- 2本: 生まれつき有角
- 3本以上: 染色体に異常があるので、ここではヤギとは見なさない
つまり、canMakeLove
の引数に 3以上の整数を与えると動作が保証されなくなってしまいます。
でも、関数を使う側が「気をつけて使う」なんて、現代のプログラミングではありえないですよね?
ちゃんと技術を使って解決しましょう!
Horn型の導入
このように、何らかの制約を持つことが値に要求される場面でよく使われるテクニックがあります。
型によって値が制約を持つことを保証してしまうのです。
さっそくやってみましょう!
module Horn
exposing
( Horn
, fromInt
, toInt
)
{-| 角の本数をあらわす専用の型
-}
type Horn =
Horn Int
{-| `Horn` 型の値を作成する唯一の方法
fromInt 3
--> Nothing
fromInt 2
--> Just (Horn 2)
-}
fromInt : Int -> Maybe Horn
fromInt n =
if (0 <= n && n <= 2)
then
Just (Horn n)
else
Nothing
{-| `Horn` 型の値を整数に変換する唯一の方法
-}
toInt : Horn -> Int
toInt (Horn n) = n
{-| オスとメス(順不同)の角の本数から、その2頭が交尾しても問題ないかどうかどうかを判定する関数
-}
canMakeLove : Horn -> Horn -> Bool
canMakeLove = ...
まず注目していただきたいのは、Horn
が Opaque type になっていることです。
module Horn
exposing
( Horn(..)
, ...
と記述すれば
Horn : Int -> Horn
というコンストラクタもこのモジュールの外で使えますが、ここでは Horn
という型のみを公開しているため、このコンストラクタはこのモジュール内部でしか使えません。
すると必然的に fromInt
以外に Horn
型を作成する手段がありませんから、
「Horn
型の値は必ずヤギさんの角の本数としてValidである」と保証されます。
では、実際に canMakeLove
関数を作ってみましょう!
{-| オスとメス(順不同)の角の本数から、その2頭が交尾しても問題ないかどうかどうかを判定する関数
-}
canMakeLove : Horn -> Horn -> Bool
canMakeLove (Horn n) (Horn m) =
-- 無角同士の場合、無性のヤギが生まれてしまう可能性があるため交尾させない方が無難
not (n == 0 && m == 0)
このライブラリを使う際にはこんな感じになります。
import Horn exposing (Horn)
type alias Model =
{ horn1 : Int
, horn2 : Int
}
message : Model -> Html Msg
message model =
let
horn1 : Maybe Horn
horn1 = Horn.fromInt model.horn1
horn2 : Maybe Horn
horn2 = Horn.fromInt model.horn2
mresult : Maybe Bool
mresult = Maybe.map2 canMakeLove horn1 horn2
in
div
[]
[ text <|
case mresult of
Nothing ->
"入力値が不正です"
Just False ->
"無性のヤギが生まれる可能性があり、避けたほうが良いでしょう"
Just True ->
"ふたりの愛をはばむものは何もありません"
]
家族がふえるよ! やったね、ヤギさん