packages feed

Frames-0.2.0: demo/MissingData.hs

{-# LANGUAGE DataKinds, FlexibleContexts, FlexibleInstances,
             MultiParamTypeClasses, QuasiQuotes, TemplateHaskell,
             TypeOperators, UndecidableInstances #-}
-- | An example of dealing with rows that contain missing data. We may
-- want to fill in the gaps with default values.
import Data.Monoid ((<>), First(..))
import Data.Vinyl (Rec(..), rmap, RecApplicative, rapply)
import Data.Vinyl.Functor (Lift(..))
import Frames hiding ((:&))
import Pipes (cat, Producer, (>->))
import Pipes.Prelude as P

-- An en passant Default class
class Default a where
  def :: a

type MyInt = "int" :-> Int
type MyString = "string" :-> String
type MyBool = "bool" :-> Bool

-- Note that we define instances for column types. This lets us have
-- different defaults for different column names.
instance Default MyInt where def = Col 0
instance Default MyString where def = Col ""
instance Default MyBool where def = Col False

-- We can write instances for /all/ 'Rec' values.
instance (Applicative f, AllConstrained Default ts, RecApplicative ts)
  => Default (Rec f ts) where
  def = reifyDict [pr|Default|] (pure def)

-- Just to try it out at the 'Identity' functor.
defRec :: Record '[MyString, MyInt, MyBool]
defRec = def

-- A default record at a more interesting 'Functor'.
defFirst :: Rec First '[MyString, MyInt, MyBool]
defFirst = def

-- Real data often has holes. Here we have the 'MyString' column, but
-- not the others.
holyRow :: Rec First '[MyString, MyInt, MyBool]
holyRow = rmap First $ pure (Col "joe") :& Nothing :& Nothing :& RNil

-- We can fill in the holes with our default record.
unholy :: Maybe (Record '[MyString, MyInt, MyBool])
unholy = recMaybe . rmap getFirst $ rapply (rmap (Lift . flip (<>)) def) holyRow

-- * Reading a CSV file with missing data

instance Default ("col_a" :-> Int) where def = Col 0
instance Default ("col_b" :-> Text) where def = Col mempty

tableTypes "Row" "data/missing.csv"

-- | Fill in missing columns with a default 'Row' value synthesized
-- from 'Default' instances.
holesFilled :: Producer Row IO ()
holesFilled = readTableMaybe "data/missing.csv" >-> P.map (fromJust . holeFiller)
  where holeFiller :: Rec Maybe (RecordColumns Row) -> Maybe Row
        holeFiller = recMaybe
                   . rmap getFirst
                   . rapply (rmap (Lift . flip (<>)) def)
                   . rmap First
        fromJust = maybe (error "Frames holesFilled failure") id

showFilledHoles :: IO ()
showFilledHoles = pipePreview holesFilled 10 cat

main :: IO ()
main = return ()