Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

値が制約を満たしていることを型で保証する

URLをあらわす文字列であるとか、0以上であることが要求される整数であるとか、値が何らかの制約を満たすことを要求される場面は多くあります。

たとえば、男女2頭のヤギさんの角の本数を引数にとって、その2頭のヤギさんが愛し合って交尾して子どもを作っても問題ないかを判断する関数を定義します。

canMakeLove : Int -> Int -> Bool

それぞれの角の数を引数にとって Bool型を返す自然な設計ですね?

でも、ちょっとまってください!
有角のヤギさんは2本しか角を持っていません。
そのため、ヤギさんのとりうる角の数は以下のいずれかしかありえないはずです。

  • 0本: 生まれつき無角のヤギさんか、有角の仔ヤギちゃんの角芽を焼きごてで焼いた(断末魔のような悲鳴がする)
  • 1本: 有角の仔ヤギちゃんの角芽を焼いたけど不十分で1つだけ残った(断末魔の悲鳴が無駄になった😱)
  • 2本: 生まれつき有角
  • 3本以上: 染色体に異常があるので、ここではヤギとは見なさない

つまり、canMakeLove の引数に 3以上の整数を与えると動作が保証されなくなってしまいます。

でも、関数を使う側が「気をつけて使う」なんて、現代のプログラミングではありえないですよね?
ちゃんと技術を使って解決しましょう!

sakura0.jpg

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 = ...

まず注目していただきたいのは、HornOpaque 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 ->
                    "ふたりの愛をはばむものは何もありません"
        ]

家族がふえるよ! やったね、ヤギさん

さくらちゃんのツイッターをフォローする
他の記事を見る
さくらちゃんの写真集を手に入れる
sakura.jpg

arowM
ヤギさんとして自由に生きてるよ さくらちゃんはアーティストだから世の理不尽には頭突きしちゃうよ フリーランスUXハッカー・プログラマー(Elm, Haskell)・技術翻訳・ヤギ語翻訳 ARoW代表 http://arow.info /気吹堂(出版)代表/UZUZ CXO http://github.com/arow
https://arow.info
arow-oss
もともと法人だったけど潰しちゃったよ c.f., https://qiita.com/arowM/items/9eddd10d531154cbc065
https://arow.info
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away