Elmで読み取りのみ可能なレコードを作りたい

# 読み取り可能なレコードを作りたい

気持ちとしては、そのデータ構造にレコードとして.hogehogeってアクセスしたいけど、不正な値へのマッピングを許したくないのでタイトルのような気持ちになった


## 問題の実装

```elm
type alias MyTime = { hours : Int, minutes : Int }
```

という型を作ったのだが、いけないことができてしまう

```elm
aho : MyTime 
aho = MyTime 0 1234567 -- 60分以上になってるのや、24時間をこえるものは作れないようにしたい
```


## コンストラクタを作って、それを公開しないようにする


結局こうなった。

```elm
module MyTime exposing (MyTime, MyValidTime, fromMyTime, myTime)


type MyValidTime
    = MyValidTime MyTime


type alias MyTime =
    { hours : Int
    , minutes : Int
    }


fromMyTime : MyTime -> Maybe MyValidTime
fromMyTime { hours, minutes } =
    let
        h =
            hours + (minutes // 60)

        m =
            modBy 60 minutes
    in
    if h >= 24 then
        Nothing

    else
        Just << MyValidTime <| MyTime h m


myTime : MyValidTime -> MyTime
myTime (MyValidTime t) =
    t

```

## この実装のありがたみ

これの何がうれしいかというと

```elm
module Foo exposing (..)

import MyTime exposing (MyTime, MyValidTime, myTime, fromMyTime)

a : MyTime
a = MyTime 0 123


{- MyValidTimeコンストラクタがないってエラーになる
   a : MyValidTime
   a = MyValidTime (MyTime 0 1234567) -}


{- fromMyTime関数を通さないとMyValidTimeが作れないので、
   MyValidTimeは必ずfromMyTime関数を使うことになる -}
validA : Maybe MyValidTime
validA = fromMyTime a -- Just (MyValidTime { hours = 2, minutes = 3 }) になる



{- MyTimeという名前が付いてても別にminutesはIntなので、
   不正な値が入っているかどうかはMyTime型からわからない -}
aMinutes : Int
aMinutes = a.minutes 


{- しかし、MyValidTimeはfromMyTimeからしか作ることができず、
   その実装からminutesが確実に60未満になることがわかる -}
validAMinutes : Int
validAMinutes = validA
    |> Maybe.map (myTime >> .minutes)
    |> Maybe.withDefault 0


-- ユーザが勝手にこういう実装をしてしまうことも
add100minutes : MyTime -> MyTime
add100minutes v =
    { v | minutes = v.minutes + 100 }


-- MyValidTime型ならユーザがどんな実装をしても24時間超えたり、分が60分を絶対に超えない
validAdd100minutes : MyValidTime -> Maybe MyValidTime
validAdd100minutes v =
    Debug.todo "破れるもんならやってみろ！"
```



いちいちmyTimeっていう変換をかける手間は惜しいけど、これで一応読み取り可能なレコードができた。

`MyValidTime`のデフォルトのコンストラクタを隠蔽して、fromMyTimeという別のコンストラクタ関数を作成することによって`MyValidTime`型にfromMyTimeの実装である**必ず24時間以内であって、minutesも60未満であることが保証されている**ということが型からわかるようになる。それはコンストラクタによって与えられてる制約で、その関数が一つしかないからである。（もしデフォルトコンストラクタもexportしていたらライブラリユーザが好き勝手中身を弄れることになるので、型からわからなくなる）。

そして、そのMyValidTimeというデータの中身を読み取るために`myTime`関数をつかい、中身をレコードの形式で取り出すことができるようになったので、これで読み取り可能なレコードとしての機能が出来上がった。


## 【追記】 もうちょいレコードっぽく！

命名をもうちょい考え直すことにした。MyValidTimeもじゅーるは内部表現で使ってたMyTimeといったレコード型をエクスポートせずに、レコードコンストラクタっぽい関数を用意してあげることにした。そして、それぞれのレコードにアクセスするような関数を実装すると

```elm
module MyTime exposing (MyValidTime, MyValidTime, hour, minute, toRecord)


type MyValidTime
    = MyValidTime Int Int


myValidTime : Int -> Int -> Maybe MyValidTime
myValidTime hours minutes =
    let
        h =
            hours + (minutes // 60)

        m =
            modBy 60 minutes
    in
    if h >= 24 then
        Nothing

    else
        Just (MyValidTime h m)


hours : MyValidTime -> Int
hours (MyValidTime h _) = h


minutes : MyValidTime -> Int
minutes (MyValidTime _ m) = m


toRecord :: MyValidTime -> { hour : Int, minute : Int }
toRecord (MyValidTime h m =
    { hour = h
    , minute = m
    }
```

結局、`MyValidTime : Int -> Int -> MyValidTime`というコンストラクタの代わりにラップした`myValidTime : Int -> Int -> MyValidTime`といったコンストラクタを作成し、それぞれのアクセサを明示的に実装しただけになってしまった。いや、本来こうあるべきだったのだろう...読み取り可能なレコードがやっと作れた。