我正在寻找能够代表价值0.213123
或0.0
或1.0
的数字类型,但拒绝超出范围值,例如{{ 1}}和-0.2123
。是否存在符合该目的的特定类型,以及将数字限制在特定范围内的最佳一般方法是什么?
当然,首先想到的答案是:只使用1.2312
,但是被Haskell的类型系统破坏了,我已经习惯了在类型级别上最大限度地保护程序。
答案 0 :(得分:5)
严肃的建议
您可以在正确的位大小的单词周围使用newtype包装器(和智能构造函数):
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
实现乘法和除法可能比您想要的更昂贵,但至少添加很容易(模数防止上溢/下溢)并且您声称不需要任何操作,所以更好。
不太有用的建议
如果你只需要一个代表一个和零之间的值的符号,那么请采用dave4420的建议,只需要一个单位类型:
newtype SmallFrac = SF ()
此类型没有任何操作,甚至没有转换为其他类型的兴趣,例如Double
,但这符合所述的请求。
答案 1 :(得分:4)
不标准。你必须做一个 - 我建议smart constructor。请记住,虽然这样的类型支持非常少的数字操作 - 你不能添加它们并将它们保存在集合中,也不能否定它们,所以我建议不要使用Num
实例。乘法Monoid
是合理的。
答案 2 :(得分:3)
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"
答案 3 :(得分:2)
智能构造函数模式的变体。
这可能有点矫枉过正。
{-# 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
在运行时放入Double
会导致运行时检查,而不是围绕...
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)
切记不要在$
和(
之间添加任何空格!