我正在寻找一种能够表示值的数字类型0.213123
or 0.0
, or 1.0
,但拒绝超出范围的值,例如-0.2123
and 1.2312
。是否存在适合该目的的特定类型,将数字限制在特定范围内的最佳通用方法是什么?
当然,想到的第一个答案是:只使用Double
,但被 Haskell 的类型系统宠坏了,我已经习惯于在类型级别上最大限度地保护程序。
我正在寻找一种能够表示值的数字类型0.213123
or 0.0
, or 1.0
,但拒绝超出范围的值,例如-0.2123
and 1.2312
。是否存在适合该目的的特定类型,将数字限制在特定范围内的最佳通用方法是什么?
当然,想到的第一个答案是:只使用Double
,但被 Haskell 的类型系统宠坏了,我已经习惯于在类型级别上最大限度地保护程序。
一个严肃的建议
您可以在适当位大小的单词周围使用新类型包装器(和智能构造函数):
newtype SmallFrac = SF Word64
-- Example conversion (You'd actually want to make
-- instances of common classes, I assume)
sfToDouble :: SmallFrac -> Double
sfToDouble (SF x) = fromIntegral x / fromIntegral (maxBound `asTypeOf` x)
instance Show SmallFrac where
show = show . sfToDouble
实现乘法和除法的成本可能比您想要的要高,但至少加法很容易(模防止上溢/下溢)并且您声称不需要任何更好的操作。
一个不太有用的建议
如果您只需要一个表示值介于 1 和 0 之间的符号,那么请采用 dave4420 的建议,只需一个单位类型:
newtype SmallFrac = SF ()
这种类型没有任何操作,甚至没有转换为其他类型的兴趣,例如Double
,但这符合所述的要求。
不标准。你必须做一个——我建议一个聪明的构造函数。请记住,尽管这样的类型支持很少的数字操作——你不能添加它们并将它们保留在集合中,也不能否定它们,所以我建议不要使用Num
实例。Monoid
关于乘法的 A是合理的。
newtype Rep1 = Rep1 Double
checkRange :: Double -> Maybe Double
checkRange x
| 0 < x && x < 1 = Just x
| otherwise = Nothing
toRep1 :: Double -> Maybe Rep1
toRep1 x = Rep1 . (\x -> tan $ (x-0.5) * pi) <$> checkRange x
fromRep1 :: Rep1 -> Double
fromRep1 (Rep1 x) = atan x / pi + 0.5
data Rep2 = Rep2 Integer Integer
fromRep2 :: Rep2 -> Double
fromRep2 (Rep2 a b) = fromIntegral (abs a) / fromIntegral (abs a + abs b + 1)
toRep2 :: Double -> Maybe Rep2
toRep2 = error "left to the reader"
智能构造器模式的一种变体。
这可能是矫枉过正。
{-# LANGUAGE TemplateHaskell #-}
module Foo (Foo(), doubleFromFoo,
maybeFooFromDouble, unsafeFooFromDouble, thFooFromDouble)
where
import Language.Haskell.TH
总之,标准newtype
...
newtype Foo = Foo Double
出去Double
很容易...
doubleFromFoo :: Foo -> Double
doubleFromFoo (Foo x) = x
在运行时放入aDouble
会导致运行时检查,无法绕过...
maybeFooFromDouble :: Double -> Maybe Foo
maybeFooFromDouble x
| 0 <= x && x <= 1 = Just (Foo x)
| otherwise = Nothing
...除非您很高兴不安全(并且有一些社会手段来强制执行所有使用unsafeFooFromDouble
实际上都是安全的)...
unsafeFooFromDouble :: Double -> Foo
unsafeFooFromDouble = Foo
但如果它是编译时常量,您可以在编译时进行检查,而无需运行时开销:
thFooFromDouble :: (Real a, Show a) => a -> Q Exp
thFooFromDouble x
| 0 <= x && x <= 1 = return $ AppE (VarE 'unsafeFooFromDouble)
(LitE (RationalL (toRational x)))
| otherwise = fail $ show x ++ " is not between 0 and 1"
这就是您使用最后一个功能的方式:
$(thFooFromDouble 0.3)
$
记住不要在和(
!之间放置任何空格。