如何维护 Range 类型的类型不变性,其中下限始终小于或等于上限

rex*_*ghk 4 haskell

Range我正在尝试使用以下定义对泛型类型进行建模:

data Range a = Range
  { lower :: a
  , upper :: a }
Run Code Online (Sandbox Code Playgroud)

我添加了一个智能构造函数(并且仅在没有数据构造函数的情况下导出它),以便消费者不会意外创建一个Rangelower字段大于其upper字段的 a :

range :: Ord a => a -> a -> Range a
range lower upper =
  if lower > upper
  then Range upper lower
  else Range lower upper
Run Code Online (Sandbox Code Playgroud)

这一切都很好,我继续派生Functor它的一个实例:

instance Functor Range where
  fmap f (Range lower upper) = Range (f lower) (f upper)
Run Code Online (Sandbox Code Playgroud)

这变得有问题,因为我可以这样做:

main :: IO ()
main = do
  let r1 = range 1 2
      r2 = fmap negate r1
  return ()
  -- Now r2 has its upper field less than its lower field
Run Code Online (Sandbox Code Playgroud)

我天真的尝试是range在实现中使用智能构造函数fmap,例如:

instance Functor Range where
  fmap f (Range lower upper) = range (f lower) (f upper)
Run Code Online (Sandbox Code Playgroud)

但是,这不会编译为f lower并且f upper不限于具有Ord a实例。

lower我该如何解决这个问题,以便我可以保持始终小于或等于的类型不变性upper

Joe*_*Joe 9

正如评论中提到的,无法使其成为合法函子,因为即使结果类型没有实例,它也必须fmap f适用于所有函数。fOrd

\n

在我看来,你有两个不错的选择:

\n
\n

第一:不要定义函子约束,创建一个rangeMap具有更严格类型的新函数(这就是set 的做法)。

\n
data Range a = Range { lower :: a, higher :: a } deriving Show\n\nrange :: Ord a => a -> a -> Range a\nrange lower upper =\n  if lower > upper\n  then Range upper lower\n  else Range lower upper\n\nrangeMap :: Ord b => (a -> b) -> Range a -> Range b\nrangeMap f (Range x1 x2) = range (f x1) (f x2)\n
Run Code Online (Sandbox Code Playgroud)\n
rangeMap (negate) $ range 2 1 \nRange {lower = -2, higher = -1}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

其次,更改范围的表示形式,使其支持Functor实例,但将lower和实现higher为需要Ord约束的函数。

\n
data Range a = Range a a\n\ninstance Functor Range where\n  fmap f (Range x1 x2) = Range (f x1) (f x2)\n\nlower :: Ord a => Range a -> a\nlower (Range x1 x2) = min x1 x2\n\nhigher :: Ord a => Range a -> a\nhigher (Range x1 x2) = max x1 x2\n
Run Code Online (Sandbox Code Playgroud)\n
\xce\xbb:r = negate <$> Range 1 2\n\xce\xbb:lower r\n-2\n\xce\xbb:higher r\n-1\n
Run Code Online (Sandbox Code Playgroud)\n
\n

根据您的用例,这两种方法中的任何一种可能更可取。

\n

  • @乔对。但实际上 `instance Eq a =&gt; Eq (Range a)` 是可能的,而且效率可能不会更低!`范围 lo hi == 范围 lo'hi' = (lo == lo' &amp;&amp; hi == hi') || (lo == hi' &amp;&amp; hi == lo')` (2认同)