Haskell:如何将多个实例放在同一个模块中?

时间:2012-08-31 14:37:03

标签: haskell

假设我有以下代码:

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

那么,我的选择是什么?

This page提及“您可以通过将类型包装在新类型中并将所有必需实例提升到该新类型来实现此目的。”有人能给出一个例子吗?

有更好的方法吗?

4 个答案:

答案 0 :(得分:17)

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


更好的方法是使用sortBycompareon,以及检索名字和姓氏的功能。即。

sortFirstname = sortBy (compare `on` firstName)

(在这方面,可能值得使用Person的记录类型,即data Person = Person { firstName :: String, lastName :: String },甚至可以免费获取访问者功能。)

答案 1 :(得分: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) 

答案 2 :(得分:4)

页面提到newtype的原因是您不能为给定类型提供同一类的两个实例。好吧,你可以但不在同一范围内,这通常是一个非常糟糕的主意。有关详细信息,请参阅Orphan Instances(因为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将比较函数作为参数。

答案 3 :(得分:3)

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

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

如果我需要使用各种排序处理许多有序列表,而不是忙于newtype包装和解包 - 这是非常繁琐的 - 我会考虑使用数据类型来捆绑列表它的比较函数,然后定义使用“by”函数和相关比较的代理函数。