{-# Language AllowAmbiguousTypes #-} {-# Language DataKinds #-} {-# Language FlexibleContexts #-} {-# Language FlexibleInstances #-} {-# Language MultiParamTypeClasses #-} {-# Language ScopedTypeVariables #-} {-# Language TypeApplications #-} {-# Language TypeFamilies #-} {-# Language TypeOperators #-} {-# Language UndecidableInstances #-} {-# OPTIONS_GHC -Wno-redundant-constraints #-} -- UnifyShape module Data.Ruin.Fieldwise where import Data.Functor.Compose import Data.Functor.Identity import Data.Semigroup (Semigroup,(<>)) import GHC.TypeLits (Symbol) import Data.Ruin.All import Data.Ruin.Eval import Data.Ruin.Internal -- | How to create a field @s@ of type @b@ from a value of @a@. class FPure a (s :: Symbol) b where fpure :: a -> b -- | Same as 'rmonopure'. instance (b ~ (dom -> cod)) => FPure (dom -> cod) s b where {-# INLINE fpure #-} fpure = id -- | An implementation detail of 'rpure'. newtype RPure a = MkRPure a -- | Defer to 'FPure'. instance (Applicative i,FPure a s b) => Gives s b i (RPure a) where {-# INLINE get #-} get = \(MkRPure a) -> pure $ fpure @a @s a -- | A record where the value of field @s@ is @'fpure' \@a \@s a@, for -- the given @a@. -- -- @ -- > :t 'Data.Ruin.Hoid.hoid' \@((':@') "x") . 'rpure' -- 'Data.Ruin.Hoid.hoid' \@((':@') "x") . 'rpure' :: 'FPure' a "x" t => a -> "x" ':@' t -- @ rpure :: (Build t,GivesThese (Fields t) Identity (RPure a)) => a -> t {-# INLINE rpure #-} rpure = runCEI . build . MkRPure ----- -- | An implementation detail of 'rmonopure'. newtype RMonoPure a = MkRMonoPure a instance (a ~ b) => FPure (RMonoPure a) s b where {-# INLINE fpure #-} fpure = \(MkRMonoPure a) -> a -- | A record where every field is a given monomorphic value. -- -- @ -- > :t 'Data.Ruin.Hoid.hoid' \@((':@') "x") . 'rmonopure' -- 'Data.Ruin.Hoid.hoid' \@((':@') "x") . 'rmonopure' :: t -> "x" ':@' t -- @ rmonopure :: (Build t,GivesThese (Fields t) Identity (RPure (RMonoPure a))) => a -> t {-# INLINE rmonopure #-} rmonopure = rpure . MkRMonoPure -- | Alias for 'rpure', symmetric with 'rmonopure'. rpolypure :: (Build t,GivesThese (Fields t) Identity (RPure a)) => a -> t {-# INLINE rpolypure #-} rpolypure = rpure ----- -- | An implementation detail of 'rmempty'. data RMEmpty = MkRMEmpty instance Monoid a => FPure RMEmpty s a where {-# INLINE fpure #-} fpure = \_ -> mempty -- | A record where every field is 'mempty'. -- -- @ -- > :t 'Data.Ruin.Hoid.hoid' \@((':@') "x") 'rmempty' -- 'Data.Ruin.Hoid.hoid' \@((':@') "x") 'rmempty' :: Monoid t => "x" ':@' t -- @ rmempty :: (Build t,GivesThese (Fields t) Identity (RPure RMEmpty)) => t {-# INLINE rmempty #-} rmempty = rpure MkRMEmpty -- | An implementation detail of 'rmappend'. data RMAppend l r = MkRMAppend l r instance ( Applicative i , a ~ FieldType s l , a ~ FieldType s r , Has s l , Has s r , Monoid a ) => Gives s a i (RMAppend l r) where {-# INLINE get #-} get = \(MkRMAppend l r) -> Compose $ (\a b -> pure (mappend a b)) <$> extricate1 s l <*> extricate1 s r where s = mkLabel @s -- | Combine two records if all of the fields are 'Monoid's. -- -- @ -- > :t \\l r -> 'Data.Ruin.Hoid.hoid' \@((':@') "x") $ 'rmappend' l r -- \\l r -> 'Data.Ruin.Hoid.hoid' \@((':@') "x") $ 'rmappend' l r -- :: Monoid t => "x" ':@' t -> "x" ':@' t -> "x" ':@' t -- @ rmappend :: ( Build t , GivesThese (Fields t) Identity (RMAppend t t) ) => t -> t -> t {-# INLINE rmappend #-} rmappend = \l r -> runCEI $ build $ MkRMAppend l r -- | An implementation detail of 'rmappend'. data RSAppend l r = MkRSAppend l r instance ( Applicative i , a ~ FieldType s l , a ~ FieldType s r , Has s l , Has s r , Semigroup a ) => Gives s a i (RSAppend l r) where {-# INLINE get #-} get = \(MkRSAppend l r) -> Compose $ (\a b -> pure (a <> b)) <$> extricate1 s l <*> extricate1 s r where s = mkLabel @s -- | Combine two records if all of the fields are 'Semigroups's. -- -- @ -- > :t \\l r -> 'Data.Ruin.Hoid.hoid' \@((':@') "x") $ 'rsappend' l r -- \\l r -> 'Data.Ruin.Hoid.hoid' \@((':@') "x") $ 'rsappend' l r -- :: Semigroup t => "x" ':@' t -> "x" ':@' t -> "x" ':@' t -- @ rsappend :: ( Build t , GivesThese (Fields t) Identity (RSAppend t t) ) => t -> t -> t {-# INLINE rsappend #-} rsappend = \l r -> runCEI $ build $ MkRSAppend l r ----- -- | An implementation detail of 'rlabel'. data RLabel = MkRLabel instance (a ~ Label s) => FPure RLabel s a where {-# INLINE fpure #-} fpure = \_ -> mkLabel -- | The record where the type of field @s@ is @Label s@. -- -- @ -- > :t 'Data.Ruin.Hoid.hoid' \@((':@') "x") 'rlabel' -- 'Data.Ruin.Hoid.hoid' \@((':@') "x") 'rlabel' :: "x" ':@' 'Label' "x" -- @ rlabel :: (Build t,GivesThese (Fields t) Identity (RPure RLabel)) => t {-# INLINE rlabel #-} rlabel = rpure MkRLabel ----- infixl 4 `rmap` -- | If the following constraint holds for every field @s@ in @t@, -- then @fun@ can map @rc@ to @t@. -- -- @ -- 'FPure' fun s ('FieldType' s rc -> 'FieldType' s t) -- @ -- -- @ -- > :t \\fun -> 'rmap' fun . 'Data.Ruin.Hoid.hoid' \@((':@') "x") -- \\fun -> 'rmap' fun . 'Data.Ruin.Hoid.hoid' \@((':@') "x") -- :: 'FPure' fun "x" (t -> t1) => fun -> "x" ':@' t -> "x" ':@' t1 -- @ rmap :: forall fun rc rfun t. ( Build rfun , Build t , GivesThese (Fields rfun) Identity (RPure fun) , GivesThese (Fields t) Identity (RSplat rfun rc) , UnifyShape rfun t , UnifyShape rc t ) => fun -> rc -> t {-# INLINE rmap #-} rmap = \fun rc -> (rpure fun :: rfun) `rsplat` rc ----- infixl 4 `rmapA` -- | If the following constraint holds for every field @s@ in @t@, -- then @fun@ can map @rc@ to @t@ within an 'Applicative' functor @i@. -- -- @ -- 'FPure' fun s ('FieldType' s rc -> i ('FieldType' s t)) -- @ -- -- @ -- > :t \\fun -> 'rmapA' fun . 'Data.Ruin.Hoid.hoid' \@((':@') "x") -- \\fun -> 'rmapA' fun . 'Data.Ruin.Hoid.hoid' \@((':@') "x") -- :: ('FPure' fun "x" (t -> i t1), Applicative i) => -- fun -> "x" ':@' t -> i ("x" ':@' t1) -- @ rmapA :: forall fun i rc rfun t. ( Applicative i , Build rfun , Build t , GivesThese (Fields rfun) Identity (RPure fun) , GivesThese (Fields t) i (RSplatA rfun rc) , UnifyShape rfun t , UnifyShape rc t ) => fun -> rc -> i t {-# INLINE rmapA #-} rmapA = \fun rc -> (rpure fun :: rfun) `rsplatA` rc ----- -- | An implementation detail of 'rsplat'. data RSplat rfun rc = MkRSplat rfun rc instance ( Applicative i , FieldType s rfun ~ (FieldType s rc -> b) , Has s rfun , Has s rc ) => Gives s b i (RSplat rfun rc) where {-# INLINE get #-} get = \(MkRSplat rfun rc) -> Compose $ fmap @Eval pure $ extricate1 s rfun <*> extricate1 s rc where s = mkLabel @s infixl 4 `rsplat` -- | A record where the value of field @s@ is @'Data.Eval.runEval' -- ('Data.Ruin.Deep.extricate' \#s rfun \<*> -- 'Data.Ruin.Deep.extricate' \#s rc)@. -- -- Compare to \"zippy\" instances of '<*>'. -- -- @ -- > :t 'rsplat' . 'Data.Ruin.Hoid.hoid' \@((':@') "x") -- 'rsplat' . 'Data.Ruin.Hoid.hoid' \@((':@') "x") -- :: "x" ':@' (t1 -> t) -> "x" ':@' t1 -> "x" ':@' t -- @ rsplat :: ( Build t , GivesThese (Fields t) Identity (RSplat rfun rc) , UnifyShape rc t , UnifyShape rfun t ) => rfun -> rc -> t {-# INLINE rsplat #-} rsplat = \rfun rc -> runCEI $ build $ MkRSplat rfun rc ----- -- | An implementation detail of 'rsplatA'. data RSplatA rfun rc = MkRSplatA rfun rc instance ( Applicative i , FieldType s rfun ~ (FieldType s rc -> i b) , Has s rfun , Has s rc ) => Gives s b i (RSplatA rfun rc) where {-# INLINE get #-} get = \(MkRSplatA rfun rc) -> Compose $ extricate1 s rfun <*> extricate1 s rc where s = mkLabel @s infixl 4 `rsplatA` -- | Like 'rsplat', but in an 'Applicative' functor. Note that every -- field in @rfun@ must be a function with an @i@-structured codomain. -- -- @ -- > :t 'rsplatA' . 'Data.Ruin.Hoid.hoid' \@((':@') "x") -- 'rsplatA' . 'Data.Ruin.Hoid.hoid' \@((':@') "x") -- Applicative i => :: "x" ':@' (t1 -> i t) -> "x" ':@' t1 -> i ("x" ':@' t) -- @ rsplatA :: ( Applicative i , Build t , GivesThese (Fields t) i (RSplatA rfun rc) , UnifyShape rc t , UnifyShape rfun t ) => rfun -> rc -> i t {-# INLINE rsplatA #-} rsplatA = \rfun rc -> runEval $ getCompose $ build $ MkRSplatA rfun rc