

Haskellにて範囲を制限してくれるようなIntのような型を作ってみたいのですが、  
以下のようにBounded型クラスのインスタンスにしても制限されません。  
どうすれば範囲制限付きのIntのような型を作成できますか？

[code]

    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    
    newtype Century = Century Int
       deriving (Num,Eq,Ord,Show)
    
    instance Bounded Century where
        minBound = 0
        maxBound = 100
    
[/code]


CC BY-SA 3.0
2016-11-18T12:16:51.440
30511
2016-11-24T13:24:41.920
2016-11-18T12:25:43.490
19620
19620
question
haskell
範囲制限付きの数値型を作成するにはどうすればどうすればいいですか？


一言でいうとできないのですが、型の定義をモジュールの中に隠して、その型の値を生成して返す関数をエクスポートすることで、似たようなことができます。

例えば、下のような内容の Century.hs を作成します。

[code]

    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    
    module Century (makeCentury) where
    
    newtype Century = Century Int
        deriving (Num,Eq,Ord,Show)
    
    makeCentury :: Int -> Century
    makeCentury n
        | n < 0 || n > 100 = error "Out of range"
        | otherwise        = Century n
    
[/code]

これをコンパイルしてインポートすると、Century はエクスポートしていないので、データコンストラクタは使えません。

[code]

    import Century
    
    main :: IO ()
    main = do
        putStrLn $ show (makeCentury 3)   -- 'Century 3' と出力
        putStrLn $ show (makeCentury 101) -- 実行時エラー
        -- putStrLn $ show (Century 1010) コンパイルできない
    
[/code]


CC BY-SA 3.0
2016-11-19T01:47:38.983
30522
2016-11-19T01:47:38.983
3605
30511
answer


[`Num`クラスの`fromInteger`メソッドを自前で書くこと](https://www.haskell.org/tutorial/numbers.html)で、
数値リテラルを使うことが出来ます。（ベースはHidekiさんの回答と同様です）

[code]

    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    
    module Century (Century, makeCentury) where
    
    newtype Century = Century Int
        deriving (Eq,Ord,Show)
    
    makeCentury :: Int -> Century
    makeCentury n
        | n < 0 || n > 100 = error "Out of range"
        | otherwise        = Century n
    
    instance Num Century where
      Century a + Century b = makeCentury $ a + b
      Century a * Century b = makeCentury $ a * b
      abs (Century a) = makeCentury $ abs a
      signum (Century a) = makeCentury $ signum a
      fromInteger = makeCentury . fromIntegral
      negate (Century a) = makeCentury $ negate a
    
[/code]

[code]

    *Century> 1 :: Century
    Century 1
    *Century> 100 :: Century
    Century 100
    *Century> 10 * 100 :: Century
    Century *** Exception: Out of range
    
[/code]

但し、このコードだとラップアラウンドを警告してくれない点に注意してください。


CC BY-SA 3.0
2016-11-24T13:24:41.920
30655
2016-11-24T13:24:41.920
13199
30511
answer