4

假设我有以下代码:

import Data.List.Ordered

data Person = Person String String
     deriving (Show, Eq)

main :: IO ()
main = print . show . sort $ [(Person "Isaac" "Newton"), (Person "Johannes" "Kepler")]

在同一个模块中,我希望能够按名字和姓氏对列表进行排序。显然我不能这样做:

instance Ord Person where
         compare (Person _ xLast) (Person _ yLast) = compare xLast yLast

instance Ord Person where
         compare (Person xFirst _) (Person yFirst _) = compare xFirst yFirst

那么,我有哪些选择?

此页面提到“您可以通过将类型包装在新类型中并将所有必需的实例提升到该新类型来实现这一点。” 有人可以举个例子吗?

有没有更好的办法?

4

4 回答 4

18

newtype 方法是:

newtype ByFirstname = ByFirstname { unByFirstname :: Person }

instance Ord ByFirstname where
  -- pattern matching on (ByFirstname (Person xFirst _)) etc
  compare [...] = [...]

newtype ByLastname = ByLastname { unByLastname :: Person }

instance Ord ByLastname where
  -- as above

那么排序函数将类似于:

sortFirstname = map unByFirstname . sort . map ByFirstname

同样对于ByLastname.


更好的方法是使用sortBy,compareon, 用于检索名字和姓氏的函数。IE

sortFirstname = sortBy (compare `on` firstName)

(关于这一点,可能值得为Personie 使用记录类型,data Person = Person { firstName :: String, lastName :: String }甚至可以免费获得访问器函数。)

于 2012-08-31T14:49:11.097 回答
9

您不想定义多个Ord实例只是为了按不同的顺序排序。您只需要使用该sortBy函数,该函数将显式比较函数作为其参数。

巧妙的技巧:如果您使用记录类型来定义您的Person类型、导入Data.Function(它为您提供on功能)和Data.Monoid,您可以使用一些巧妙的技巧来使这更加简洁和容易:

import Data.Function (on)
import Data.Monoid (mappend)
import Data.List (sortBy)

data Person = Person { firstName :: String, lastName :: String }

instance Show Person where
    show p = firstName p ++ " " ++ lastName p

exampleData = [ Person "Mary" "Smith"
              , Person "Joe" "Smith"
              , Person "Anuq" "Katig"
              , Person "Estanislao" "Martínez"
              , Person "Barack" "Obama" ]
-- 
-- The "on" function allows you to construct comparison functions out of field
-- accessors:
--
--     compare `on` firstName :: Person -> Person -> Ordering
--     
-- That expression evaluates to something equivalent to this:
--
--    (\x y -> compare (firstName x) (firstName y))
--
sortedByFirstName = sortBy (compare `on` firstName) exampleData
sortedByLastName = sortBy (compare `on` lastName) exampleData

--
-- The "mappend" function allows you to combine comparison functions into a
-- composite one.  In this one, the comparison function sorts first by lastName,
-- then by firstName:
--
sortedByLastNameThenFirstName = sortBy lastNameFirstName exampleData
    where lastNameFirstName = 
              (compare `on` lastName) `mappend` (compare `on` firstName) 
于 2012-08-31T18:01:06.800 回答
4

该页面提到的原因newtype是,对于给定类型,您不能拥有同一类的两个实例。好吧,您可以但不在同一范围内,这通常是一个非常糟糕的主意。有关更多信息,请参阅孤立实例(正如 CA McCann 指出的那样,这与孤立实例无关。我包含该链接是因为它很好地描述了为什么重复实例(即使在表面上有效的情况下)是一个坏主意。)。

一个例子newtype是:


newtype SortFirst = SF Person

instance Ord Person where
         compare (Person _ xLast) (Person _ yLast) = compare xLast yLast

instance Ord SortFirst where
         compare (SF (Person xFirst _)) (SF (Person yFirst _)) = compare xFirst yFirst

然后,当您想按名字排序时,您必须这样做:

sort (map SF persons)

这不是很方便,因此最好使用sortBy将比较函数作为参数的。

于 2012-08-31T14:45:09.237 回答
3

似乎所有函数Data.List.Ordered都有“by”变体,让您提供比较函数而不是使用Ord实例。如果没有一个明显的“标准”排序,那不是一个糟糕的选择。

然后,您有责任确保任何给定的列表始终与一致的比较功能一起使用,但是,这是各种有趣错误的潜在来源。

如果我需要使用各种排序来处理许多有序列表,而不是大惊小怪地newtype包装和展开——这非常乏味——我会考虑使用一种数据类型来将列表与其比较函数捆绑在一起,然后定义代理函数使用带有相关比较的“by”函数。

于 2012-08-31T14:46:13.777 回答