{-# LANGUAGE ConstraintKinds, CPP, DataKinds, FlexibleContexts, FlexibleInstances, GADTs, KindSignatures, MultiParamTypeClasses, PatternSynonyms, RankNTypes, ScopedTypeVariables, TypeFamilies, TypeOperators, ViewPatterns #-} module Frames.RecF (V.rappend, V.rtraverse, rdel, CanDelete, frameCons, frameConsA, frameSnoc, pattern (:&), pattern Nil, AllCols, UnColumn, AsVinyl(..), mapMono, mapMethod, ShowRec, showRec, ColFun, ColumnHeaders, columnHeaders, reifyDict) where import Data.List (intercalate) import Data.Proxy import qualified Data.Vinyl as V import Data.Vinyl (Rec(RNil), RecApplicative(rpure)) import Data.Vinyl.Functor (Identity) import Data.Vinyl.TypeLevel import Frames.Col import Frames.TypeLevel import GHC.TypeLits (KnownSymbol, symbolVal) -- | Add a column to the head of a row. frameCons :: Functor f => f a -> V.Rec f rs -> V.Rec f (s :-> a ': rs) frameCons = (V.:&) . fmap Col {-# INLINE frameCons #-} -- | Add a pure column to the head of a row. frameConsA :: Applicative f => a -> V.Rec f rs -> V.Rec f (s :-> a ': rs) frameConsA = (V.:&) . fmap Col . pure {-# INLINE frameConsA #-} -- | Separate the first element of a row from the rest of the row. frameUncons :: Functor f => V.Rec f (s :-> r ': rs) -> (f r, V.Rec f rs) frameUncons (x V.:& xs) = (fmap getCol x, xs) {-# INLINE frameUncons #-} -- | Add a column to the tail of a row. Note that the supplied value -- should be a 'Col' to work with the @Frames@ tooling. frameSnoc :: V.Rec f rs -> f r -> V.Rec f (rs ++ '[r]) frameSnoc r x = V.rappend r (x V.:& RNil) {-# INLINE frameSnoc #-} -- Nil :: Rec f '[] pattern Nil = V.RNil -- (:&) :: Functor f => f r -> Rec f rs -> Rec f (r ': rs) pattern x :& xs <- (frameUncons -> (x, xs)) -- NOTE: A bidirectional pattern synonym would be great, but we'll -- have to wait for GHC 7.10 to gain wide acceptance before depending -- upon its availability. class ColumnHeaders (cs::[*]) where -- | Return the column names for a record. columnHeaders :: proxy (Rec f cs) -> [String] instance ColumnHeaders '[] where columnHeaders _ = [] instance forall cs s c. (ColumnHeaders cs, KnownSymbol s) => ColumnHeaders (s :-> c ': cs) where columnHeaders _ = symbolVal (Proxy::Proxy s) : columnHeaders (Proxy::Proxy (Rec f cs)) -- | A type function to convert a 'Record' to a 'Rec'. @ColFun f (Rec -- rs) = Rec f rs@. type family ColFun f x where ColFun f (Rec Identity rs) = Rec f rs -- | Strip the column information from each element of a list of -- types. type family UnColumn ts where UnColumn '[] = '[] UnColumn ((s :-> t) ': ts) = t ': UnColumn ts -- | Enforce a constraint on the payload type of each column. type AllCols c ts = LAll c (UnColumn ts) -- | Remove the column name phantom types from a record, leaving you -- with an unadorned Vinyl 'V.Rec'. class AsVinyl ts where toVinyl :: Functor f => Rec f ts -> V.Rec f (UnColumn ts) fromVinyl :: Functor f => V.Rec f (UnColumn ts) -> Rec f ts instance AsVinyl '[] where toVinyl _ = V.RNil fromVinyl _ = V.RNil instance AsVinyl ts => AsVinyl (s :-> t ': ts) where toVinyl (x V.:& xs) = fmap getCol x V.:& toVinyl xs fromVinyl (x V.:& xs) = fmap Col x V.:& fromVinyl xs #if __GLASGOW_HASKELL__ < 800 fromVinyl _ = error "GHC coverage checker isn't great" #endif -- | Map a function across a homogeneous, monomorphic 'V.Rec'. mapMonoV :: (Functor f, AllAre a ts) => (a -> a) -> V.Rec f ts -> V.Rec f ts mapMonoV _ V.RNil = V.RNil mapMonoV f (x V.:& xs) = fmap f x V.:& mapMonoV f xs -- | Map a function across a homogeneous, monomorphic 'Rec'. mapMono :: (AllAre a (UnColumn ts), Functor f, AsVinyl ts) => (a -> a) -> Rec f ts -> Rec f ts mapMono f = fromVinyl . mapMonoV f . toVinyl -- | Map a typeclass method across a 'V.Rec' each of whose fields -- have instances of the typeclass. mapMethodV :: forall c f ts. (Functor f, LAll c ts) => Proxy c -> (forall a. c a => a -> a) -> V.Rec f ts -> V.Rec f ts mapMethodV _ f = go where go :: LAll c ts' => V.Rec f ts' -> V.Rec f ts' go V.RNil = V.RNil go (x V.:& xs) = fmap f x V.:& go xs -- | Map a typeclass method across a 'Rec' each of whose fields -- has an instance of the typeclass. mapMethod :: forall f c ts. (Functor f, LAll c (UnColumn ts), AsVinyl ts) => Proxy c -> (forall a. c a => a -> a) -> Rec f ts -> Rec f ts mapMethod p f = fromVinyl . mapMethodV p f . toVinyl -- | A constraint that a field can be deleted from a record. type CanDelete r rs = (V.RElem r rs (RIndex r rs), RDelete r rs V.⊆ rs) -- | Delete a field from a record rdel :: CanDelete r rs => proxy r -> Rec f rs -> Rec f (RDelete r rs) rdel _ = V.rcast -- | The ability to pretty print a 'Rec''s fields. class Functor f => ShowRec f rs where showRec' :: Rec f rs -> [String] instance Functor f => ShowRec f '[] where showRec' _ = [] instance forall s f a rs. (KnownSymbol s, Show (f (Col' s a)), ShowRec f rs) => ShowRec f (s :-> a ': rs) where showRec' (x :& xs) = show (col' <$> x :: f (Col' s a)) : showRec' xs showRec' _ = error "GHC coverage error" -- | Pretty printing of 'Rec' values. showRec :: ShowRec f rs => Rec f rs -> String showRec r = "{" ++ intercalate ", " (showRec' r) ++ "}" -- | Build a record whose elements are derived solely from a -- constraint satisfied by each. reifyDict :: forall c f proxy ts. (LAll c ts, RecApplicative ts) => proxy c -> (forall a. c a => f a) -> Rec f ts reifyDict _ f = go (rpure Nothing) where go :: LAll c ts' => Rec Maybe ts' -> Rec f ts' go RNil = RNil go (_ V.:& xs) = f V.:& go xs