Haskell: How to put multiple instances in the same module?

前端 未结 4 2049
独厮守ぢ
独厮守ぢ 2021-01-13 21:23

Let\'s say that I have the following code:

import Data.List.Ordered

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

main :: IO ()
main = print          


        
相关标签:
4条回答
  • 2021-01-13 21:44

    The newtype method would be:

    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
    

    Then the sorting function would be something like:

    sortFirstname = map unByFirstname . sort . map ByFirstname
    

    and similarly for ByLastname.


    A better way is to use sortBy, compare and on, with functions for retrieving the first and last names. i.e.

    sortFirstname = sortBy (compare `on` firstName)
    

    (On that note, it might be worth using a record type for Person, i.e. data Person = Person { firstName :: String, lastName :: String }, and one even gets the accessor functions for free.)

    0 讨论(0)
  • 2021-01-13 21:50

    The reason that page mentions newtype is that you cannot have two instances of the same class for a given type. Well, you can but not in the same scope and it's generally a really bad idea. See Orphan Instances for more information (as C. A. McCann pointed out this isn't about orphan instances. I included the link because it has a good description of why duplicate instances, even in cases that superficially work, is a bad idea.).

    An example of newtype would be:

    
    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
    

    Then when you want to sort by first name you would have to do:

    sort (map SF persons)
    

    It's not very convenient so it may be better to use sortBy which takes an comparison function as an argument.

    0 讨论(0)
  • 2021-01-13 21:53

    You don't want to define multiple Ord instances just to sort by different orders. You just need to use the sortBy function, which takes an explicit comparison function as its argument.

    Neat trick: if you use record types to define your Person type, import Data.Function (which gives you the on function) and Data.Monoid, you can use some neat tricks to make this much briefer and easy:

    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) 
    
    0 讨论(0)
  • 2021-01-13 21:53

    It seems that all the functions in Data.List.Ordered have "by" variants that let you provide a comparison function rather than using an Ord instance. If there's no single obvious "standard" ordering, that's not a bad option.

    It then becomes your responsibility to ensure any given list is always used with a consistent comparison function, however, which is a potential source of all kinds of fun bugs.

    If I needed to work with many ordered lists using a variety of orderings, rather than fussing with newtype wrapping and unwrapping--which is dreadfully tedious--I'd consider a data type to bundle a list with its comparison function, then define proxy functions that use the "by" functions with the associated comparison.

    0 讨论(0)
提交回复
热议问题