定义其枚举子类型不是序数的序数类型

时间:2018-07-26 23:40:34

标签: haskell

我遇到了定义序数类型的问题,这些序数类型的值可能是也可能不是序数。

基本上我有两种类型,OrderedTypeUnorderedType

data OrderedType = One | Two | Three deriving (Eq, Ord, Show)
data UnorderedType = Red | Blue | Green deriving (Eq, Show)

我有一个类型,其值构造函数将其中一个作为参数:

data WrapperType = WrappedOne OrderedType
                   | WrappedTwo UnorderedType deriving (Eq, Ord, Show)

基本上,我想做的是订购WrapperType,而无需 必须为compareWrappedOne实现WrappedTwo函数。

当我尝试编译以上内容时,出现以下错误:

• No instance for (Ord UnorderedType)
arising from the first field of ‘WrappedTwo’ (type ‘UnorderedType’)
Possible fix:
use a standalone 'deriving instance' declaration,
  so you can specify the instance context yourself
• When deriving the instance for (Ord WrappedType)

这是有道理的,因为Ord的股票衍生WrappedType实例将尝试 比较WrappedTwo的所有值。

简而言之,我想做的是这样:

WrappedOne _ < WrappedTwo _ -- True

但是不必为每种类型编写一个Ord实例。

我该怎么做?

3 个答案:

答案 0 :(得分:3)

不清楚您想要什么;我想您想将所有用WrappedTwo构造的值都视为等效值吗?

newtype ForgetOrder a = ForgetOrder a

instance Eq (ForgetOrder a) where
    _ == _ = True

instance Ord (ForgetOrder a) where
    compare _ _ = EQ

然后您可以将类型定义为:

data WrapperType = WrappedOne OrderedType
                 | WrappedTwo (ForgetOrder UnorderedType) deriving (Eq, Ord, Show)

是的,必须包装和拆开新类型有点麻烦,并且编写模式同义词来避免它也很麻烦。 C'est la vie。

但是,我担心您也想要WrappedTwo Red /= WrappedTwo Green。在这一点上,我将不得不与Wagner先生一起赶上潮流,并说,无论您走过的思路是什么,都需要回头再找另一条路。 Haskell的最大乐趣之一就是人们关心法律 [1]。您会发现在Hackage上很少有定义违法实例的库。并且有充分的理由:例如,Data.Set期望Ord定义总订单并与Eq兼容。具有违法的Ord的类型会使Set的类型完全荒谬而无用。但是我自信地将各种东西放到Set上,而不必担心,因为在Haskell文化中,合法性非常普遍。

但是,如果您不认为...好。很抱歉传道。


[1]我很尴尬地发现预期的法律不是documented with the Ord class。无论如何,我对这些法律的常规理解如下:

  1. 等效符号:

    x < y   =  compare x y == LT
    x > y   =  compare x y == GT
    x == y  =  compare x y == EQ
    x <= y  =  x < y || x == y
    x >= y  =  x > y || x == y
    
  2. ==是等价关系:

    x == x
    If x == y then y == x
    If x == y and y == z then x == z
    
  3. ==的可扩展性 (这似乎比其他的宽松一些;例如,sort的文档以{{1 }}可能不是扩展性的):

    ==

    (对于从给定抽象外部可定义的f 边界; IOW相同值的不同表示形式是 只要用户无法区分它们就可以。)

  4. if x == y, then f x == f y 的总计:

    <

    (三尖瓣切开术是从“记号的等价性”与“比较”开始)

答案 1 :(得分:1)

出于我在注释中讨论的原因,我建议您不要这样做:您的Ord实例和Eq实例应该一致,而您的Eq实例应该仅等于行为相同。而是查看您的数据,其中仅包含您希望比较的信息。所以:

data Constructor = Lower | Higher deriving (Eq, Ord, Read, Show)
data Wrapper = WrappedOne Foo | WrappedTwo Bar deriving (Read, Show)

constructor :: Wrapper -> Constructor
constructor (WrappedOne _) = Lower
constructor (WrappedTwo _) = Higher

现在,您将在这里致电compare wrapperA wrapperB,而致电compare (constructor wrapperA) (constructor wrapperB)

答案 2 :(得分:0)

作为一种替代方法,如果您只对构造函数级别的比较感兴趣,而无需为有序组件类型提升底层的Ord实例,则可以使用Data.Data泛型来按构造函数索引排序:

{-# LANGUAGE DeriveDataTypeable #-}
import Data.Data
import Data.Ord

data OrderedType = One | Two | Three deriving (Eq, Ord, Show, Data)
data UnorderedType = Red | Blue | Green deriving (Eq, Show, Data)

data WrapperType = WrappedOne OrderedType
                   | WrappedTwo UnorderedType deriving (Eq, Show, Data)

compareCon :: (Data a) => a -> a -> Ordering
compareCon = comparing (constrIndex' . toConstr)
  where constrIndex' x = case constrRep x of
          AlgConstr i -> i
          _           -> 0

constrIndex模块中有一个Data.Data,但是当类型不是代数时会抛出错误,因此上面的constrIndex'更安全。

无论如何,有了这些定义,我们得到:

> compareCon (WrappedOne One) (WrappedTwo Red)
LT
> compareCon (WrappedOne One) (WrappedOne Two)
EQ
> compareCon (WrappedTwo Blue) (WrappedTwo Green)
EQ
> 

这似乎比滥用Ord安全得多。