忍受我...这个问题需要一些解释,但我认为这是一个有趣的问题,我认为其他人已经面对它了.
我想要一个我知道的类型总是在0到1之间的值,包括0和1.这很容易做到,我可以创建一个类型UnitInterval
,只暴露我的智能构造函数toUnitInterval
和解构函数fromUnitInterval
.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | A number on the unit interval 0 to 1, inclusive.
newtype UnitInterval = UnitInterval Double
deriving (Show, Eq, Ord, Fractional, Floating)
-- | Convert a value to a @UnitInterval@. The value will be capped to
-- the unit interval.
toUnitInterval :: Double -> UnitInterval
toUnitInterval = UnitInterval . max 0 . min 1
fromUnitInterval :: UnitInterval -> Double
fromUnitInterval (UnitInterval x) = x
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好.但是我的模块的用户会发现UnitInterval
s和Double
s的算术很混乱.例如,
?> let a = toUnitInterval 0.5
?> let b = 0.25 :: Double
?> toUnitInterval $ (fromUnitInterval a) * b
UnitInterval 0.125
Run Code Online (Sandbox Code Playgroud)
当然我可以做UnitInterval
一个派生的实例Num
,所以只要我坚持UnitInterval
s ,我就可以很容易地做算术.
?> a*a
UnitInterval 0.25
?> a+a+a
UnitInterval 1.5 -- Oops! out of range
Run Code Online (Sandbox Code Playgroud)
但我可以Num
为UnitInterval
s 编写一个自定义实现,其中的操作就像+
边界检查一样.但是我的模块的用户需要进行复杂的计算,其中部分结果不在范围内.因此,他们必须将所有内容转换为Double
s,进行计算,最后转换回UnitInterval
s.
但是等等......也许有更好的方法.我可以做UnitInterval
一个算子!表达式fmap (\x -> x * exp x) a
应该给出结果
UnitInterval 0.8243606353500641
.很好,干净的代码.现在,Functor
有种(* ? *)
,UnitInterval
有种*
.但我可以改变这一点,就像这样......
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | A number on the unit interval 0 to 1, inclusive.
newtype UnitInterval a = UnitInterval a
deriving (Show, Eq, Ord, Num, Fractional, Floating)
-- | Convert a value to a @UnitInterval@. The value will be capped to
-- the unit interval.
toUnitInterval :: (Num a, Ord a) => a -> UnitInterval a
toUnitInterval = UnitInterval . max 0 . min 1
fromUnitInterval :: UnitInterval a -> a
fromUnitInterval (UnitInterval x) = x
instance Functor UnitInterval where
fmap f (UnitInterval x) = toUnitInterval (f x) -- line 16
Run Code Online (Sandbox Code Playgroud)
但那不编译.事后来看,我看到了这是因为我需要约束结果fmap
,这会给它一个不同的类型签名Functor
.
amy.hs:16:29:
No instance for (Num b) arising from a use of ‘toUnitInterval’
Possible fix:
add (Num b) to the context of
the type signature for
fmap ? (a ? b) ? UnitInterval a ? UnitInterval b
In the expression: toUnitInterval (f x)
In an equation for ‘fmap’:
fmap f (UnitInterval x) = toUnitInterval (f x)
In the instance declaration for ‘Functor UnitInterval’
Run Code Online (Sandbox Code Playgroud)
叹了闻......回到第一个版本,带着难看的算术.有没有人有更好的解决方案?
你会面临一些困难,你问什么,因为在[0,1]的数字不封闭下(+)
运行.换句话说,"[0,1]"内的保证不会通过添加来保留.
所以有几种方法可以解释你想要的东西.一个是你可能正在寻找每个你重新约束值的操作"阶段"[0,1]
mapConstrain :: (Num a, Ord a) => (a -> a) -> (UnitInterval a -> UnitInterval a)
mapConstrain f (UnitInterval val) = UnitInterval (max 0 (min 1 (f val)))
Run Code Online (Sandbox Code Playgroud)
像这样的操作将限制你会发现,因为很难写出类似的东西
a :: UnitInterval Double
b :: UnitInterval Double
a + b
Run Code Online (Sandbox Code Playgroud)
使用mapConstrain
.然而,Applicative
类型类提出了解决这个问题的机制.
另一种方法是在每次操作后进行约束.然后我们可以实例化Num
newtype UnitInterval a = UI a
constrain :: (Num a, Ord a) => a -> a
constrain = max 0 . min 1
instance Num a => Num (UnitInterval a) where
UI a + UI b = UI (constrain $ a + b)
UI a * UI b = UI (constrain $ a * b) -- not technically needed!
abs (UI a) = UI a
signum (UI a) = UI (signum a)
...
Run Code Online (Sandbox Code Playgroud)
最后的方法是允许无限制的操作,但只允许用户"查看" UnitInterval
有效的值.这可能是最简单的实现,因为您可以自动派生Num
newtype UnitInterval a = UI a deriving Num
getUI :: (Num a, Ord a) => UnitInterval a -> Maybe a
getUI (UI a) = if (a <= 1 && a >= 0) then Just a else Nothing
Run Code Online (Sandbox Code Playgroud)
或者,你可以用一个决赛击中它constrain
.当然,这种操作模式允许UnitInterval
值超出[0,1],只要它们在被查看之前返回那里.
归档时间: |
|
查看次数: |
83 次 |
最近记录: |