正如您所指定的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}
您将无法在 thename
和ssn
字段上进行比较的版本。您可以通过将每个字段包装在 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
类会有用的实例,并且尝试<=
使用同一类型的多个版本重载它似乎在实践中会令人困惑。