Background
The general task is to dig into a list of (very complicated) records, reporting on what they contain. This means inventing lots of filters and maps over the list and reporting back.
The simplified problem looks something like this:
{-# LANGUAGE OverloadedStrings, DataKinds, ExistentialQuantification, GADTs #-}
import qualified Data.Map as Map
import Control.Monad (void)
data Rec = Rec
{ _rInt :: Int
, _rChar :: Char
} deriving (Show,Eq,Ord)
tRec = take 10 $ zipWith Rec [1..] ['a'..]
count :: (Ord a) => [b] -> (b -> a) -> (b -> Bool) -> Map.Map a Int
count list key pred' =
let fn a = Map.insertWith (+) (key a) 1 in
foldr fn Map.empty (filter pred' list)
report :: (Ord a, Show a) => [b] -> String -> (b -> a) -> (b -> Bool) -> IO ()
report list str key pred' = do
let c = count list key pred'
(putStrLn . (str ++) . show) c
For example:
λ: report tRec "count of characters with odd ints: " _rChar (odd . _rInt)
count of characters with odd ints: fromList [('a',1),('c',1),('e',1),('g',1),('i',1)]
Standard datatype wrapper
The various reports can be bundled up quite nicely (and ready for further refactoring) using a higher-order type wrapper like so:
data Wrap = WrapInt Int | WrapChar Char deriving (Show, Eq, Ord)
demoWrap = void $ sequence $
zipWith3
(report tRec)
["count of all ints: ","count of characters with odd ints: "]
[WrapInt . _rInt, WrapChar . _rChar]
[const True, odd . _rInt]
Which gives:
λ: demoWrap
count of all ints: fromList [(WrapInt 1,1),(WrapInt 2,1),(WrapInt 3,1),(WrapInt 4,1),(WrapInt 5,1),(WrapInt 6,1),(WrapInt 7,1),(WrapInt 8,1),(WrapInt 9,1),(WrapInt 10,1)]
count of characters with odd ints: fromList [(WrapChar 'a',1),(WrapChar 'c',1),( WrapChar 'e',1),(WrapChar 'g',1),(WrapChar 'i',1)]
GADT solution
In an attempt to remove the wrapper type ugliness I thought an ADT/GADT solution might help.
Here's my attempt:
-- GADTs attempt
data Useable where
MkUseable :: (Show a, Eq a, Ord a) => a -> Useable
wrap :: (Show a, Eq a, Ord a) => a -> Useable
wrap = MkUseable
instance Show Useable where
showsPrec p (MkUseable a) = showsPrec p a
-- this doesn't work
instance Eq Useable
-- where
-- (MkUseable a) == (MkUseable b) = a == b
instance Ord Useable
-- where
-- compare (MkUseable a) (MkUseable b) = compare a b
demoGADT = void $ sequence $
zipWith3
(report tRec)
["all ints:","odd chars:"]
[wrap . _rInt, wrap . _rChar]
[const True, odd . _rInt]
The compiler (quite rightly) barfs at Eq and Ord instances of Useable having potentially different types. But the intention isn't to ever compare Useable
s with different types - it's to more simply wrap any (Show a, Ord a) type so I can put them in a list.
So two questions:
How can types be wrapped using GADT in the spirit of the standard wrapper solution above?
What am I missing (more generally) - are there easier ways to functionally interrogate data?