3

是否有可能让一个类型类暗示 Haskell 中的另一个类型类?例如,假设有一堆可以按“属性”排序的“事物”:

data Person = Person { name :: String, age :: Int }

Person p1 <= Person p1 = (age p1) <= (age p2)

为了避免重复,可以定义一个“按键排序”类型类

class OrdByKey o where
  orderKey :: (Ord r) => o -> r
  x <= y = (orderKey x) <= (orderKey y)

然后实例声明Person可能看起来像这样

instance OrdByKey Person where
  orderKey Person p = age p

现在,由于多种原因,这显然不起作用。我想知道这是否可能?

4

2 回答 2

2

正如您所指定的OrdByKey,当您希望能够为记录类型中的每个字段声明一个实例时,该类的每种类型只能有一个实例。

为此,您还必须将字段类型放入类定义中。这使您可以执行以下操作:

{-# LANGUAGE MultiParamTypeClasses #-}

data Person = Person { name :: String, age :: Int }

class (Ord r) => OrdByKey o r where
   orderKey :: o -> r

instance OrdByKey Person Int where
  orderKey p = age p

x <=? y = (orderKey x :: Int) <= (orderKey y :: Int)

但是,每个字段类型只能有一个实例,因此如果您的 Person类型看起来像

data Person = Person { name :: String, age :: Int, ssn :: String}

您将无法在 thenamessn字段上进行比较的版本。您可以通过将每个字段包装在 a 中来解决此问题, newtype因此每个字段都具有唯一的类型。所以你的Person类型看起来像

data Person = Person { name :: Name, age :: Age, ssn :: SSN}

不过,这会导致很多newtypes浮动。

这样做的真正缺点是需要指定 orderKey函数的返回类型。我建议使用on函数 from Data.Function来编写适当的比较函数。我认为这样的功能

compareByKey :: (Ord b) => (a -> b) -> a -> a -> Bool
compareByKey = on (<=)

概括了您“可以通过某些键进行比较”的想法。您只需为其提供提取该键的函数Person,在这种情况下,这正是您的类型的访问器函数。

我想不出一个OrdByKey类会有用的实例,并且尝试<=使用同一类型的多个版本重载它似乎在实践中会令人困惑。

于 2013-02-08T17:04:36.730 回答
0

你可以这样做:

instance Ord Person where
    compare p1 p2 = compare (age p1) (age p2)

现在标准<=运算符将对Persons 工作并比较他们的年龄。

于 2013-02-08T16:58:34.310 回答