1

I'm fetching a list of records from a webservice and generating reports based on that data. I'm currently using code similar to this:

let sorted = sortBy (comparing field1 <> comparing field2) records
let grouped = groupBy cmp sorted
              where cmp x y =    (field1 x) == (field1 y)
                              && (field2 x) == (field2 y)
let subtotals = map subtot grouped
                where subtot = foldl1 recsum
                      recsum x y = x { field3 = (field3 x) + (field3 y) }

Is there a less verbose way to accomplish this? It gets a bit cluttery, especially when grouping by multiple fields. Even just having a single function for sort+group would help. I thought of writing a function taking a list of field deconstructors like

sortAndGroup :: Ord v => [r -> v] -> [r] -> [[r]]

but that won't work when the fields have different types. Any ideas?

4

1 回答 1

7

Instead of giving sortAndGroup a list of fields, let's give it a single function whose result will contain a tuple of those fields (or whatever else we need):

import Control.Arrow ((&&&))
import Data.Function (on)
import Data.Ord (comparing)
import Data.List (groupBy, sortBy)

sortAndGroup :: (Ord v) => (r -> v) -> [r] -> [[r]]
sortAndGroup cf = groupBy (on (==) cf) . sortBy (comparing cf)

Now we can easily sort and group a data type according to a set of fields:

data Foo = Foo { field1 :: Int, field2 :: Int, field3 :: Int }
  deriving (Show, Eq, Ord)

bar :: [Foo] -> [[Foo]]
bar = sortAndGroup (\f -> (field1 f, field2 f))

Moreover, we can combine record accessors using (&&&) from Control.Arrow, specialized for -> (which is an instance of Arrow):

-- Specialized for ->
(&&&) ::: (b -> c) -> (b -> c') -> (b -> (c, c'))

so bar can be shortened as

bar' :: [Foo] -> [[Foo]]
bar' = sortAndGroup (field1 &&& field2)
于 2013-09-23T13:58:25.210 回答