module Data.NamedRecord (
Property (get, set, upd),
New (new),
add,
(:=) (..),
(:+) (..),
record,
record',
extends,
has,
name, nameT, nameV, names
) where
import Control.Applicative
import Control.Monad
import Data.Binary hiding (get)
import qualified Data.Binary as B
import Data.Data
import Data.Function (on)
import Data.List
import qualified Data.Name
import Data.Name (name, nameT, nameV, names)
import Data.Typeable
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax (Lift (..))
data a := b = a := b deriving Show
infixr 3 :=
data a :+ b = a :+ b deriving Show
infixr 2 :+
add :: b -> a -> a :+ b
add = flip (:+)
infixl 1 `add`
class Property o n v | o n -> v where
get :: o -> n -> v
set :: o -> n := v -> o
upd :: o -> n := (v -> v) -> o
infixl 1 `set`
infixl 1 `get`
infixl 1 `upd`
instance Property (n := v) n v where
get (_ := v) _ = v
set _ v = v
upd (_ := v) (n := f) = (n := f v)
instance Property ((n := v) :+ b) n v where
get (a :+ b) n = get a n
set (a :+ b) p = (set a p) :+ b
upd (a :+ b) f = (upd a f) :+ b
instance Property b n v => Property (a :+ b) n v where
get (a :+ b) n = get b n
set (a :+ b) p = a :+ (set b p)
upd (a :+ b) f = a :+ (upd b f)
instance Binary v => Binary (n := v) where
put (_ := v) = put v
get = (_type :=) <$> B.get
instance (Binary v, Binary b) => Binary (v :+ b) where
put (v :+ b) = put v >> put b
get = liftM2 (:+) B.get B.get
class New o where
new :: o
instance New (a := b) where
new = _type := _value
instance (New a, New b) => New (a :+ b) where
new = new :+ new
class ToExp a where
toExp' :: a -> Q Exp
instance ToExp (Q Exp) where
toExp' = id
instance Lift a => ToExp a where
toExp' = lift
class Field a where
toExp :: a -> Maybe (Q Exp)
toType :: a -> Q Type
toExp _ = Nothing
instance ToExp e => Field (Q Type := e) where
toExp (_ := e) = Just $ toExp' e
toType (v := _) = v
instance ToExp e => Field (Name := e) where
toExp (_ := e) = Just $ toExp' e
toType (v := _) = return $ ConT v
instance Field Name where
toType = return . ConT
instance Field (Q Type) where
toType = id
instance (Data a, Typeable a) => Field a where
toExp = Just . dataToExpQ (const Nothing)
toType e = qType typeName
where
typeName = show (typeOf e)
qType typeName = case typeName of
"[Char]" -> return $ ConT ''String
('[':xs) -> do
t <- qType (init xs)
return $ AppT ListT t
('M':'a':'y':'b':'e':' ':xs) -> do
t <- qType xs
m <- [t| Maybe |]
return $ AppT m t
name -> lookupTypeName name >>= typeFor
typeFor (Just n) = return $ ConT n
typeFor Nothing = fail $ "The type \"" ++ typeName
++ "\" can not be handled by Data.NamedRecord TH sugar"
++ " (you might to import the module where it's from)."
class RecordTemplate a b c | a b -> c where
(~>) :: a -> b -> c
instance (Field v, Field w) => RecordTemplate
(String := v)
(String := w)
[(String, Q Type, Maybe (Q Exp))] where
(n := v) ~> (m := w) = [(n, toType v, toExp v),
(m, toType w, toExp w)]
instance Field v => RecordTemplate
(String := v)
[(String, Q Type, Maybe (Q Exp))]
[(String, Q Type, Maybe (Q Exp))] where
(n := v) ~> xs = (n, toType v, toExp v) : xs
instance Field v => RecordTemplate Record (String := v) (Q [Dec]) where
r ~> (n := v) = r ~> [(n, toType v, toExp v)]
instance RecordTemplate
Record [(String, Q Type, Maybe (Q Exp))] (Q [Dec]) where
Record name xs opts ~> fs = do
let typeD typ = TySynD (mkName name) [] typ
noValue = VarE '_value
normalize (n, v, d) = do
v' <- v
d' <- maybe (return noValue) id d
return (n, (v', d'))
nFields <- mapM normalize fs
let fs' = sortBy (compare `on` fst) (concat (nFields : xs))
unify sss@(s:ss) =
if all id (zipWith ((==) `on` (fst . snd)) ss sss)
then return $ select sss
else fail $ "Types for the named field \""
++ fst s ++ "\" could not be unified."
++ "\n The conflicting types are: \n"
++ unlines (map (show . fst . snd) sss)
select (x:[]) = x
select (x@(_, (_, d)) : xs)
| d == noValue = select xs
| otherwise = x
mkDef (n, (v, d)) = do
name <- nameT n
return (AppT (AppT (ConT ''(:=)) name) v, d)
nFields <- mapM unify (groupBy ((==) `on` fst) fs')
fields <- mapM mkDef nFields
rExp <- dataToExpQ (const Nothing) nFields
let reflD = ValD (VarP (mkName ("__" ++ name))) (NormalB rExp) []
syn = foldr (\(x, _) xs -> AppT (AppT (ConT ''(:+)) x) xs)
(fst $ last fields) (init fields)
cName = mkName ("new" ++ name)
sigD = SigD cName (ConT (mkName name))
funcD = ValD (VarP cName) (NormalB funcB) []
funcB = foldr join (field $ last fields) (init fields)
where
join x xs = InfixE (Just $ field x) (ConE '(:+)) (Just xs)
field (_, x) = InfixE (Just (VarE '_type))
(ConE '(:=))
(Just x)
declaration = [typeD syn, sigD, funcD, reflD]
if opts
then do
names <- mapM Data.Name.name (map fst nFields)
return (declaration ++ concat names)
else do
return declaration
_type = error $ "NamedRecord field type unwrapped!"
++ " You should never see this."
++ " Srsly, what did you do?"
_value = error "Data.NameRecord.undefined: No value set."
has :: RecordTemplate a b c => a -> b -> c
has = (~>)
infixr 1 ~>
infixr 1 `has`
class RecordExtends a where
(<:) :: Record -> a -> Record
instance RecordExtends [(String, (Type, Exp))] where
Record name xs opts <: x = Record name (x:xs) opts
extends :: RecordExtends a => Record -> a -> Record
extends = (<:)
infixl 2 <:
infixl 2 `extends`
data Record = Record String [[(String, (Type, Exp))]] Bool
record :: String -> Record
record name = Record name [] False
record' :: String -> Record
record' name = Record name [] True
fieldNames :: FieldNames a => a -> [String]
fieldNames = _fieldNames []
class FieldNames a where
_fieldNames :: [String] -> a -> [String]
instance (Show n, FieldNames r) =>
FieldNames (n := ns :+ r) where
_fieldNames xs (n := _ :+ r) = _fieldNames (show n : xs) r
instance (Show n) => FieldNames (n := ns) where
_fieldNames xs (n := _) = show n : xs