Как вы используете Bounded typeclass в Haskell для определения типа с диапазоном с плавающей запятой?

Я ожидал, что следующий код завершится с ошибкой типа из-за нарушения minBound и maxBound. Но, как вы можете видеть, он проходит, не помещая ошибку.

{-# OPTIONS_GHC -XTypeSynonymInstances #-}
module Main where

type Probability = Float
instance Bounded Probability where
    minBound = 0.0
    maxBound = 1.0

testout :: Float -> Probability
testout xx = xx + 1.0

main = do
  putStrLn $ show $ testout 0.5
  putStrLn $ show $ testout (-1.5)
  putStrLn $ show $ testout 1.5

В прелюдии я получаю это

*Main> :type (testout 0.5)
(testout 0.5) :: Probability

И в подсказке я получаю следующее:

[~/test]$runhaskell demo.hs
1.5
-0.5
2.5

Ясно, что я не объявляю ограниченным образом, и я уверен, что я делаю что-то неправильно синтаксически. В Google не так много простых вещей, связанных с Bounded, так что любая помощь будет высоко оценена.

Ответы

Ответ 1

Это не то, для чего Bounded. Bounded a просто определяет функции minBound :: a и maxBound :: a. Это не вызывает никакой специальной проверки или чего-либо еще.

Вы можете определить ограниченный тип с помощью так называемого интеллектуального конструктора. То есть:

module Probability (Probability) where

newtype Probability = P { getP :: Float }
    deriving (Eq,Ord,Show)

mkP :: Float -> Probability
mkP x | 0 <= x && x <= 1 = P x
      | otherwise = error $ show x ++ " is not in [0,1]"

-- after this point, the Probability data constructor is not to be used

instance Num Probability where
    P x + P y = mkP (x + y)
    P x * P y = mkP (x * y)
    fromIntegral = mkP . fromIntegral
    ...

Таким образом, единственный способ сделать Probability - использовать функцию mkP в конце концов (это делается для вас, когда вы используете числовые операции с учетом нашего экземпляра Num), который проверяет, что аргумент находится в диапазоне. Из-за списка экспорта модулей за пределами этого модуля невозможно построить недопустимую вероятность.

Наверное, не тот двухлиний, который вы искали, но хорошо.

Для дополнительной компоновки вы можете отбросить эту функциональность, создав модуль BoundCheck вместо `Вероятность. Как и выше, кроме:

newtype BoundCheck a = BC { getBC :: a }
    deriving (Bounded,Eq,Ord,Show)

mkBC :: (Bounded a) => a -> BoundCheck a
mkBC x | minBound <= x && x <= maxBound = BC x
       | otherwise = error "..."

instance (Bounded a) => Num (BoundCheck a) where
    BC x + BC y = mkBC (x + y)
    ...

Таким образом, вы можете получить нужные вам функциональные возможности, когда вы задали вопрос.

Чтобы сделать это, вам понадобится расширение языка {-# LANGUAGE GeneralizedNewtypeDeriving #-}.