-- |Record combinators built on top of the record core that "Data.Record" provides. module Data.Record.Combinators ( -- * Record styles withStyle, -- * Field operations (!!!), (\\\), -- * Catenation Cat, cat, -- * Applicative functor operations repeat, (<<*>>), map, zipWith, -- * Modification of fields {-FIXME: Actually, all applicative functor operations except repeat are also about modification of fields. -} modify, (///), -- * Conversion -- FIXME: maybe don’t use the term “conversion” because of “record conversion” toList ) where -- Prelude import Prelude hiding (repeat, map, zipWith) import qualified Prelude -- only for documentation -- Data import Data.Kind as Kind import Data.TypeFun as TypeFun import Data.Record as Record -- Control import Control.Applicative as Applicative hiding (Const) -- only for documentation -- * Record styles infixl 2 `withStyle` --FIXME: There is punctuation missing at the end of certain code blocks. {-| Fixes the style of a record. When a record is constructed using @X@, @(:&)@, and @(:=)@, the style of this record is not fixed. For example, the most general type of the record @ X :& Surname := \"Jeltsch\" :& Age := 33 :& Room := \"HG/2.39\" @ is @ ('App' style sortSurname ~ String, Num ('App' style sortAge), 'App' style sortRoom ~ String) => (X :& Surname ::: sortSurname :& Age ::: sortAge :& Room ::: sortRoom) style @ We can fix the style of that record using the expression @ X :& Surname := \"Jeltsch\" :& Age := 33 :& Room := \"HG/2.39\" \`withStyle\` 'Id' 'KindStar' @ which has the most general type @ (Num age) => (X :& Surname ::: String :& Age ::: age :& Room ::: String) ('Id' 'KindStar') @ The @withStyle@ combinator is similar to 'asTypeOf'. -} withStyle :: (Record (Domain style) rec) => rec style -> style -> rec style withStyle = const -- * Field operations infixl 2 !!!, \\\ -- |Looks up the value of a record field. (!!!) :: (Separation rec remain sepName sepSort) => rec style -> sepName -> App style sepSort rec !!! name = let _ := sepVal = snd (separate rec) `asTypeOf` (name := undefined) in sepVal -- |Removes a record field. (\\\) :: (Separation rec remain sepName sepSort) => rec style -> sepName -> remain style rec \\\ name = let (remain,_) = separate rec `asTypeOf` (undefined,name := undefined) in remain -- * Catenation -- |Catenation of two record schemes. type family Cat (rec1 :: * -> *) (rec2 :: * -> *) :: * -> * type instance Cat rec1 X = rec1 type instance Cat rec1 (rec2 :& name2 ::: sort2) = Cat rec1 rec2 :& name2 ::: sort2 -- |Catenation of two records. cat :: (TypeFun style, Record (Domain style) rec1, Record (Domain style) rec2) => rec1 style -- ^ -> rec2 style -- ^ -> Cat rec1 rec2 style -- ^ cat = let CatThing cat = fold catNilAlt catExpander in cat newtype CatThing style rec1 rec2 = CatThing (rec1 style -> rec2 style -> Cat rec1 rec2 style) catNilAlt:: (TypeFun style, Record (Domain style) rec1) => CatThing style rec1 X catNilAlt = CatThing nilCat where nilCat rec1 X = rec1 catSnocAlt :: (TypeFun style, Record (Domain style) rec1, Record (Domain style) rec2, Name name, Inhabitant (Domain style) sort) => CatThing style rec1 rec2 -> CatThing style rec1 (rec2 :& name ::: sort) catSnocAlt (CatThing cat) = CatThing snocCat where snocCat rec1 (rec2 :& field2) = cat rec1 rec2 :& field2 catExpander :: (TypeFun style, Record (Domain style) rec1, Record (Domain style) rec2, Name name) => All (Domain style) (Expander (CatThing style rec1) rec2 name) catExpander = closed (Expander catSnocAlt) -- * Record schemes as a kind of applicative functor {-| Generates a record whose fields all contain the same value. In contrast to the 'Prelude.repeat' function from the Prelude, this function generates a finite data structure. Thereby, the size of the generated record is determined by its type. @repeat@ is almost a proper implementation of 'pure' from the 'Applicative' class. -} repeat :: (TypeFun style, Record (Domain style) rec) => Universal style -- ^ -> rec style -- ^ repeat = let RepeatThing repeat = fold repeatNilAlt repeatExpander in repeat newtype RepeatThing style rec = RepeatThing (Universal style -> rec style) repeatNilAlt :: (TypeFun style) => RepeatThing style X repeatNilAlt = RepeatThing nilRepeat where nilRepeat _ = X repeatSnocAlt :: forall style rec name sort. (TypeFun style, Record (Domain style) rec, Name name, Inhabitant (Domain style) sort) => RepeatThing style rec -> RepeatThing style (rec :& name ::: sort) repeatSnocAlt (RepeatThing repeat) = RepeatThing snocRepeat where snocRepeat :: Universal style -> (rec :& name ::: sort) style snocRepeat wrappedVal = repeat wrappedVal :& name := unwrapApp (wrappedVal :: WrappedApp style sort) repeatExpander :: (TypeFun style, Record (Domain style) rec, Name name) => All (Domain style) (Expander (RepeatThing style) rec name) repeatExpander = closed (Expander repeatSnocAlt) zipWithApp :: (TypeFun style, TypeFun style', Domain style ~ Domain style', Record (Domain (style :-> style')) rec) => rec (style :-> style') -> rec style -> rec style' zipWithApp = let ZipWithAppThing zipWithApp = fold zipWithAppNilAlt zipWithAppExpander in zipWithApp newtype ZipWithAppThing style style' rec = ZipWithAppThing (rec (style :-> style') -> rec style -> rec style') zipWithAppNilAlt :: (TypeFun style, TypeFun style', Domain style ~ Domain style') => ZipWithAppThing style style' X zipWithAppNilAlt = ZipWithAppThing nilZipWithApp where nilZipWithApp X X = X zipWithAppSnocAlt :: (TypeFun style, TypeFun style', Domain style ~ Domain style', Record (Domain (style :-> style')) rec, Name name, Inhabitant (Domain style) sort) => ZipWithAppThing style style' rec -> ZipWithAppThing style style' (rec :& name ::: sort) zipWithAppSnocAlt (ZipWithAppThing zipWithApp) = ZipWithAppThing snocZipWithApp where snocZipWithApp (funRec :& name := fun) (argRec :& _ := arg) = zipWithApp funRec argRec :& name := fun arg zipWithAppExpander :: (TypeFun style, TypeFun style', Domain style ~ Domain style', Record (Domain (style :-> style')) rec, Name name) => All (Domain style) (Expander (ZipWithAppThing style style') rec name) zipWithAppExpander = closed (Expander zipWithAppSnocAlt) infixl 4 <<*>> {-| Merges a record of functions and a record of arguments by applying the functions to the corresponding arguments. The @(\<\<*\>\>)@ function is almost a proper implementation of @(\<*\>)@ from the 'Applicative' class. -} (<<*>>) :: (TypeFun style, TypeFun style', Domain style ~ Domain style', Record (Domain (style :-> style')) rec) => rec (style :-> style') -- ^ -> rec style -- ^ -> rec style' -- ^ (<<*>>) = zipWithApp -- ** Derived combinators -- |Transforms a record by applying a function to all its field values. map :: (TypeFun style, TypeFun style', Domain style ~ Domain style', Record (Domain (style :-> style')) rec) => Universal (style :-> style') -- ^ -> rec style -- ^ -> rec style' -- ^ map fun argRec = repeat fun <<*>> argRec -- |Merges two records by applying a function to each pair of corresponding field values. zipWith :: (TypeFun style1, TypeFun style2, TypeFun style', Domain style1 ~ Domain style2, Domain style2 ~ Domain style', Record (Domain (style1 :-> style2 :-> style')) rec) => Universal (style1 :-> style2 :-> style') -- ^ -> rec style1 -- ^ -> rec style2 -- ^ -> rec style' -- ^ zipWith fun argRec1 argRec2 = repeat fun <<*>> argRec1 <<*>> argRec2 -- * Modification of multiple fields infixl 1 /// {-| Modifies a record by changing some of its field values. The first argument of @modify@ is called the modification record, and the second argument is called the data record. The result is formed by applying each field value of the modification record to the corresponding field value of the data record and replacing the latter by the result of the application. Data record fields that have no corresponding field in the modification record are left unchanged. -} modify :: (TypeFun style, Record (Domain style) rec, Record (Domain style) modRec, Convertible rec modRec) => modRec (style :-> style) -- ^ -> rec style -- ^ -> rec style -- ^ modify modRec = foldr (.) id $ toList (convert updateFuns <<*>> modRec) type UpdateFunStyle rec style = (style :-> style) :-> Const (Domain style) (rec style -> rec style) updateFuns :: (TypeFun style, Record (Domain style) rec) => rec (UpdateFunStyle rec style) updateFuns = let UpdateFunsThing updateFuns = fold updateFunsNilAlt updateFunsExpander in updateFuns newtype UpdateFunsThing style rec = UpdateFunsThing (rec (UpdateFunStyle rec style)) updateFunsNilAlt :: (TypeFun style) => UpdateFunsThing style X updateFunsNilAlt = UpdateFunsThing nilUpdateFuns where nilUpdateFuns = X updateFunsSnocAlt :: (TypeFun style, Record (Domain style) rec, Name name, Inhabitant (Domain style) sort) => UpdateFunsThing style rec -> UpdateFunsThing style (rec :& name ::: sort) updateFunsSnocAlt (UpdateFunsThing updateFuns) = UpdateFunsThing snocUpdateFuns where snocUpdateFuns = map (WrapApp (onInit .)) updateFuns :& name := updateFun updateFun mod (rec :& name := val) = rec :& name := mod val updateFunsExpander :: (TypeFun style, Record (Domain style) rec, Name name) => All (Domain style) (Expander (UpdateFunsThing style) rec name) updateFunsExpander = closed (Expander updateFunsSnocAlt) onInit :: (rec style -> rec style) -> ((rec :& name ::: sort) style -> (rec :& name ::: sort) style) onInit fun (rec :& field) = fun rec :& field {-| Overwrites the values of multiple record fields. The first argument is the source record, and the second argument lists the names of the fields to be modified together with their new values. -} (///) :: (TypeFun style, Record (Domain style) rec, Record (Domain style) replRec, Convertible rec replRec) => rec style -- ^ -> replRec style -- ^ -> rec style -- ^ rec /// replRec = modify (map (WrapApp const) replRec) rec -- * Conversion -- |Converts a record whose style is a constant function into the list of its field values. toList :: (Kind kind, Record kind rec) => rec (Const kind val) -> [val] toList = reverse . toRevList toRevList :: (Kind kind, Record kind rec) => rec (Const kind val) -> [val] toRevList = let ToRevListThing toRevList = fold toRevListNilAlt toRevListExpander in toRevList newtype ToRevListThing kind val rec = ToRevListThing (rec (Const kind val) -> [val]) toRevListNilAlt :: (Kind kind) => ToRevListThing kind val X toRevListNilAlt = ToRevListThing nilToRevList where nilToRevList X = [] toRevListSnocAlt :: (Kind kind, Record kind rec, Name name, Inhabitant kind sort) => ToRevListThing kind val rec -> ToRevListThing kind val (rec :& name ::: sort) toRevListSnocAlt (ToRevListThing toRevList) = ToRevListThing snocToRevList where snocToRevList (rec :& _ := val) = val : toRevList rec toRevListExpander :: (Kind kind, Record kind rec, Name name) => All kind (Expander (ToRevListThing kind val) rec name) toRevListExpander = closed (Expander toRevListSnocAlt)