Haskell: 类型类意味着其他类型类。

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

现在这个明显有很多问题,我想知道它是否可能?


2
如果您的目标只是基于记录中的特定字段进行排序,那么您可以为 Person 定义 Ord 实例来比较该特定字段。 - sabauma
@sabauma 是的,但我正在寻找那个“可以通过某些关键进行比较”的概括。 - Jonas H.
针对这个特定的例子,看一下比较函数:comparing age you me - Michael Steele
对于更一般的运算符,您可以查看 on。它的类型为 (b -> b -> c) -> (a -> b) -> a -> a -> c,因此 comparing age = on compare age,但您也可以使用 on (==) f 在应用 f 后测试两个内容是否相等。 - Laar
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}

你将无法在namessn字段上进行版本比较。您可以通过将每个字段包装在newtype中来解决这个问题,以便每个字段都具有唯一的类型。因此,你的Person类型将如下所示:

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

这将导致许多新类型的出现。

真正的缺点是需要为orderKey函数指定返回类型。我建议使用Data.Function中的on函数编写适当的比较函数。我认为一个像

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

这个类可以概括出"可以通过某个关键字进行比较"的想法。您只需要提供提取该关键字的函数,这在本例中将完全是Person类型的访问器函数。

我无法想象在什么情况下会有用到OrdByKey类,并且尝试为同一类型重载多个版本的<=似乎在实践中会非常混乱。


看起来使用多参数类型类是一种干净的方法。谢谢! - Jonas H.

0
你可以这样做:
instance Ord Person where
    compare p1 p2 = compare (age p1) (age p2)

现在标准的<=运算符将适用于Person,并比较他们的年龄。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接