Skip to content

Instantly share code, notes, and snippets.

@purcell
Last active November 18, 2019 09:52
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save purcell/d5d9de9101a738b7f1fc97c511b1fb22 to your computer and use it in GitHub Desktop.
Save purcell/d5d9de9101a738b7f1fc97c511b1fb22 to your computer and use it in GitHub Desktop.
Multiple aggregates in a single pass, using Purescript
-- This is based on ideas from the excellent article "Beautiful Aggregations
-- with Haskell" by Evan Borden: https://tech.freckle.com/2017/09/22/aggregations/
module Aggregation where
import Prelude
import Data.Foldable (foldMap)
import Data.Monoid.Additive (Additive(..))
import Data.Newtype (un)
type Person = { name :: String, occupation :: String, age :: Int }
rows :: Array Person
rows = [ { name: "Bob", occupation: "Dentist", age: 34 }
, { name: "Tim", occupation: "Nurse", age: 25 }
, { name: "Ann", occupation: "Programmer", age: 47 }
, { name: "Mae", occupation: "Astronaut", age: 38 }
, { name: "Ann", occupation: "Truck driver", age: 55 }
]
-- | Produce some summary information from a given list of people in a single pass.
-- |
-- | > summary rows
-- | { annCount: 2, occupations: ["Dentist","Nurse","Programmer","Astronaut","Truck driver"], sumOfAges: 199 }
-- |
-- | Note that this could be written more concisely/generally, but I'm trying to be explicit.
summary :: Array Person -> { occupations :: Array String, sumOfAges :: Int, annCount :: Int }
summary people =
-- foldMap works like a map/reduce, where each value in a collection is put into a
-- monoidal value, and those "wrapped" values are combined together into a single
-- value by the particular monoid that is in use.
unpack (foldMap step people)
where
-- Return a monoidal result for each folded item. It just so happens
-- that a record whose fields are all monoids is also a monoid, so this function
-- just returns a record. These records get combined together into a single record
-- by having their corresponding field values successively combined monoidally.
step { name, occupation, age } =
{ occupations: pure occupation
, sumOfAges: pure age
, annCount: if name == "Ann" then pure 1 else mempty }
-- Unpack the combined monoidal accumulator, which just involves unpacking
-- individual fields
unpack { occupations, sumOfAges, annCount } =
{ occupations, sumOfAges: un Additive sumOfAges, annCount: un Additive annCount }
@sergeyklay
Copy link

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment