概要
型安全プログラミングがどのように役に立つかを Haskell を使った和暦(元号)の実装を例に紹介します。
実装は下記の repository にあります。
https://github.com/algas/haskell-example-date
対象読者
Haskell の最低限の知識と haskell stack の使用経験があることを前提とします。
基本的な文法や言語構文の説明は入門書に任せることにします。
使い方
以下の単体テストを動作させることができます。
git clone git@github.com:algas/haskell-example-date.git
stack build
stack test
実装の概要
Data.Time.Wareki
で公開している関数を説明します。
data Gengoh = Meiji | Taisho | Showa | Heisei -- 元号(簡単のため明治以降のみ)
data Wareki = Wareki Gengoh Integer Int Int -- 和暦(コンストラクタは非公開)
fromGregorian :: Integer -> Int -> Int -> Maybe Wareki -- 西暦から和暦
toGregorian :: Wareki -> (Integer, Int, Int) -- 和暦から西暦
fromDay :: Day -> Maybe Wareki -- 西暦から和暦
toDay :: Wareki -> Day -- 和暦から西暦
toText :: Wareki -> Text -- 和暦から文字列
addDays :: Integer -> Wareki -> Maybe Wareki -- 日を足す
diffDays :: Wareki -> Wareki -> Integer -- 日の差分
関数の使用方法についてはテストコード test/WarekiSpec.hs
をご参照ください。
何が安全なのか?
正しくない和暦を入力できないように型で守っています。
独自型の定義
Wareki
(和暦)型を独自に定義していて、整数型(Int,Integer)や日付型(Day)などとは型で区別されているので混同することはありません。型の変換は定義した関数を通じて行うしかなく暗黙的に意図しない型変換が行われることはありません。
コンストラクタ非公開
Data.Time.Wareki
では Wareki
のコンストラクタを公開していません。したがって Wareki
を直接生成することはできません。Wareki を作るには fromGregorian
か fromDay
かいずれかの関数を使うしかありません。
warekiExample = Wareki Heisei 13 3 11 -- コンストラクタが公開されていないのでコンパイルできない
fromGregorian 2017 3 5 -- Just (Wareki Heisei 29 3 5)
定義域の設定
本実装では昭和1年(西暦1868年)1月25日を和暦の始まりとしていて、それよりも前の日は和暦として未定義としています。ただし和暦の終わりは制限していないので未来の和暦はすべて「平成」として定義されます。
fromGregorian 1868 1 24 -- Nothing
(toText . fromJust) (fromGregorian 2100 1 25) -- "平成112年1月25日"
本実装のポイント
公開する関数の制限
Data.Time.Wareki
で公開する関数を制限しています。上述のとおり型安全を実現するために Wareki
のコンストラクタを非公開にしています。
Internal の実装
和暦の中身は Data.Time.Wareki.Internal
(src/Data/Time/Wareki/Internal.hs) に実装して、Data.Time.Wareki
で公開範囲の制限しています。 Internal と公開モジュールを分離することで単体テストを実現しています。テスト側で Internal モジュールを直接参照することで非公開の関数もテストできます。
Calendar モジュールの利用
日付の基本的な操作は time ライブラリの Data.Time.Calendar
の実装に依存しています。
注意
- 本記事の実装は完全&最善ではなく、あくまでも型安全のサンプルとして平易な記述で作成したものだという点をご理解ください。
- 型安全といっても変換関数の実装が間違っている場合には正しくない結果が出力されます。テストが全く不要になるわけではありません。本実装はテストによって正常な動作を確認していますが、もし不具合を発見した場合にはお知らせください。
雑感
- 実装してみたら当初に想定したほどシンプルじゃなかった。型安全の例としては最適ではなかったかも。
-
Read
も実装しないとやや実用性に欠けるか。 - Haskell 自体の説明を省いてどれだけの人に意図を理解してもらえるのか不安。