superrecord-0.5.1.0: src/SuperRecord.hs
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE MagicHash #-}
#ifdef JS_RECORD
{-# LANGUAGE JavaScriptFFI #-}
#endif
module SuperRecord
( -- * Basics
(:=)(..)
, Record, rnil, rcons, (&)
, fld
, Has, HasOf
, get, (&.)
, set
, modify
, getPath, setPath, modifyPath, RecApplyPath, (:&), (&:), (&:-)
, combine, (++:), RecAppend
-- * Reflection
, reflectRec, reflectRecFold, RecApply(..)
-- * Native type interop
, FromNative, fromNative
, ToNative, toNative
-- * MTL interop
, asksR, asksRP
, getsR, setsR, modifiesR
, getsRP, setsRP, modifiesRP
-- * Lens interop
, lens
-- * Machinery
, Rec
, RecCopy
, RecTyIdxH
, showRec, RecKeys(..), recKeys
, RecEq(..)
, recToValue, recToEncoding
, recJsonParser, RecJsonParse(..)
, RecNfData(..)
, RecVecIdxPos
, RecSize, RemoveAccessTo
, FldProxy(..), RecDeepTy
, RecAll
, KeyDoesNotExist
, Sort
-- * Unsafe operations
, unsafeRNil
, unsafeRCons
)
where
import SuperRecord.Field
import SuperRecord.Sort
import Control.DeepSeq
import Control.Monad.Reader
import Data.Aeson
import Data.Aeson.Types (Parser)
import Data.Constraint
import Data.Proxy
import GHC.Base (Int(..), Any)
import GHC.Generics
import GHC.Prim
import GHC.TypeLits
import qualified Control.Monad.State as S
import qualified Data.Text as T
#ifdef JS_RECORD
import GHCJS.Marshal
import System.IO.Unsafe (unsafePerformIO)
import qualified Data.JSString as JSS
import qualified JavaScript.Object.Internal as JS
#else
import GHC.ST ( ST(..) , runST)
#endif
-- | Sort a list of fields using merge sort, alias to 'FieldListSort'
type Sort xs = FieldListSort xs
-- | The core record type. Prefer this type when manually writing type
-- signatures
type Record lts = Rec (Sort lts)
-- | Internal record type. When manually writing an explicit type signature for
-- a record, use 'Record' instead. For abstract type signatures 'Rec' will work
-- well.
data Rec (lts :: [*])
= Rec
{
#ifndef JS_RECORD
_unRec :: SmallArray# Any -- Note that the values are physically in reverse order
#else
_unRec :: !JS.Object
#endif
}
type role Rec representational
#ifdef JS_RECORD
copyObject :: JS.Object -> IO JS.Object
copyObject obj =
do objNew <- JS.create
props <- JS.listProps obj
forM_ props $ \prop ->
do val <- JS.unsafeGetProp prop obj
JS.unsafeSetProp prop val objNew
pure objNew
#endif
instance (RecApply lts lts Show) => Show (Rec lts) where
show = show . showRec
instance RecEq lts lts => Eq (Rec lts) where
(==) (a :: Rec lts) (b :: Rec lts) = recEq a b (Proxy :: Proxy lts)
{-# INLINE (==) #-}
instance
( RecApply lts lts ToJSON
) => ToJSON (Rec lts) where
toJSON = recToValue
toEncoding = recToEncoding
instance (RecSize lts ~ s, KnownNat s, RecJsonParse lts) => FromJSON (Rec lts) where
parseJSON = recJsonParser
instance RecNfData lts lts => NFData (Rec lts) where
rnf = recNfData (Proxy :: Proxy lts)
-- Hack needed because $! doesn't have the same special treatment $ does to work with ST yet
#ifndef JS_RECORD
runST' :: (forall s. ST s a) -> a
runST' !s = runST s
#endif
-- | An empty record
rnil :: Rec '[]
rnil = unsafeRNil 0
{-# INLINE rnil #-}
-- | An empty record with an initial size for the record
unsafeRNil :: Int -> Rec '[]
#ifndef JS_RECORD
unsafeRNil (I# n#) =
runST' $ ST $ \s# ->
case newSmallArray# n# (error "No Value") s# of
(# s'#, arr# #) ->
case unsafeFreezeSmallArray# arr# s'# of
(# s''#, a# #) -> (# s''# , Rec a# #)
#else
unsafeRNil _ =
unsafePerformIO $! Rec <$> JS.create
#endif
{-# INLINE unsafeRNil #-}
-- | Prepend a record entry to a record 'Rec'
rcons ::
forall l t lts s sortedLts.
( RecSize lts ~ s
, sortedLts ~ Sort (l := t ': lts)
, KnownNat s
, KnownNat (RecVecIdxPos l sortedLts)
, KeyDoesNotExist l lts
, RecCopy lts lts sortedLts
#ifdef JS_RECORD
, ToJSVal t
#endif
)
=> l := t -> Rec lts -> Rec sortedLts
#ifndef JS_RECORD
rcons (_ := val) lts =
runST' $ ST $ \s# ->
case newSmallArray# newSize# (error "No value") s# of
(# s'#, arr# #) ->
case recCopyInto (Proxy :: Proxy lts) lts (Proxy :: Proxy sortedLts) arr# s'# of
s''# ->
case writeSmallArray# arr# setAt# (unsafeCoerce# val) s''# of
s'''# ->
case unsafeFreezeSmallArray# arr# s'''# of
(# s''''#, a# #) -> (# s''''#, Rec a# #)
where
!(I# setAt#) =
fromIntegral (natVal' (proxy# :: Proxy# (RecVecIdxPos l sortedLts)))
newSize# = size# +# 1#
!(I# size#) = fromIntegral $ natVal' (proxy# :: Proxy# s)
#else
rcons (lbl := val) (Rec obj) =
Rec $! unsafePerformIO $!
do obj' <- copyObject obj
val' <- toJSVal val
JS.unsafeSetProp (JSS.pack $ symbolVal lbl) val' obj'
pure obj'
#endif
{-# INLINE rcons #-}
class RecCopy (pts :: [*]) (lts :: [*]) (rts :: [*]) where
recCopyInto ::
Proxy pts -> Rec lts -> Proxy rts
-> SmallMutableArray# s Any
-> State# s
-> State# s
instance RecCopy '[] lts rts where
recCopyInto _ _ _ _ s# = s#
instance
( Has l rts t
, Has l lts t
, RecCopy (RemoveAccessTo l (l := t ': pts)) lts rts
) => RecCopy (l := t ': pts) lts rts where
recCopyInto _ lts prxy tgt# s# =
let lbl :: FldProxy l
lbl = FldProxy
val = get lbl lts
pNext :: Proxy (RemoveAccessTo l (l := t ': pts))
pNext = Proxy
!(I# setAt#) =
fromIntegral (natVal' (proxy# :: Proxy# (RecVecIdxPos l rts)))
in case writeSmallArray# tgt# setAt# (unsafeCoerce# val) s# of
s'# -> recCopyInto pNext lts prxy tgt# s'#
-- | Prepend a record entry to a record 'Rec'. Assumes that the record was created with
-- 'unsafeRNil' and still has enough free slots, mutates the original 'Rec' which should
-- not be reused after
unsafeRCons ::
forall l t lts s.
( RecSize lts ~ s
, KnownNat s
, KeyDoesNotExist l lts
#ifdef JS_RECORD
, ToJSVal t
#endif
)
=> l := t -> Rec lts -> Rec (l := t ': lts)
#ifndef JS_RECORD
unsafeRCons (_ := val) (Rec vec#) =
runST' $ ST $ \s# ->
case unsafeThawSmallArray# vec# s# of
(# s'#, arr# #) ->
case writeSmallArray# arr# size# (unsafeCoerce# val) s'# of
s''# ->
case unsafeFreezeSmallArray# arr# s''# of
(# s'''#, a# #) -> (# s'''#, Rec a# #)
where
!(I# size#) = fromIntegral $ natVal' (proxy# :: Proxy# s)
#else
unsafeRCons (lbl := val) (Rec obj) =
Rec $! unsafePerformIO $!
do val' <- toJSVal val
JS.unsafeSetProp (JSS.pack $ symbolVal lbl) val' obj
pure obj
#endif
{-# INLINE unsafeRCons #-}
-- | Alias for 'rcons'
(&) ::
forall l t lts s sortedLts.
( RecSize lts ~ s
, sortedLts ~ Sort (l := t ': lts)
, KnownNat s
, KnownNat (RecVecIdxPos l sortedLts)
, KeyDoesNotExist l lts
, RecCopy lts lts sortedLts
#ifdef JS_RECORD
, ToJSVal t
#endif
)
=> l := t -> Rec lts -> Rec sortedLts
(&) = rcons
{-# INLINE (&) #-}
infixr 5 &
type family RecAll (c :: u -> Constraint) (rs :: [u]) :: Constraint where
RecAll c '[] = ()
RecAll c (r ': rs) = (c r, RecAll c rs)
type family KeyDoesNotExist (l :: Symbol) (lts :: [*]) :: Constraint where
KeyDoesNotExist l '[] = 'True ~ 'True
KeyDoesNotExist l (l := t ': lts) =
TypeError
( 'Text "Duplicate key " ':<>: 'Text l
)
KeyDoesNotExist q (l := t ': lts) = KeyDoesNotExist q lts
type RecAppend lhs rhs = RecAppendH lhs rhs rhs '[]
type family ListConcat (xs :: [*]) (ys :: [*]) :: [*] where
ListConcat '[] ys = ys
ListConcat xs '[] = xs
ListConcat (x ': xs) ys = x ': (ListConcat xs ys)
type family ListReverse (xs :: [*]) :: [*] where
ListReverse (x ': xs) = ListConcat (ListReverse xs) '[x]
ListReverse '[] = '[]
type family RecAppendH (lhs ::[*]) (rhs :: [*]) (rhsall :: [*]) (accum :: [*]) :: [*] where
RecAppendH (l := t ': lhs) (m := u ': rhs) rhsall acc = RecAppendH (l := t ': lhs) rhs rhsall acc
RecAppendH (l := t ': lhs) '[] rhsall acc = RecAppendH lhs rhsall rhsall (l := t ': acc)
RecAppendH '[] rhs rhsall acc = ListConcat (ListReverse acc) rhsall
type family RecSize (lts :: [*]) :: Nat where
RecSize '[] = 0
RecSize (l := t ': lts) = 1 + RecSize lts
type RecVecIdxPos l lts = RecSize lts - RecTyIdxH 0 l lts - 1
type family RecTyIdxH (i :: Nat) (l :: Symbol) (lts :: [*]) :: Nat where
RecTyIdxH idx l (l := t ': lts) = idx
RecTyIdxH idx m (l := t ': lts) = RecTyIdxH (1 + idx) m lts
RecTyIdxH idx m '[] =
TypeError
( 'Text "Could not find label "
':<>: 'Text m
)
type family RecTy (l :: Symbol) (lts :: [*]) :: k where
RecTy l (l := t ': lts) = t
RecTy q (l := t ': lts) = RecTy q lts
-- | Require a record to contain at least the listed labels
type family HasOf (req :: [*]) (lts :: [*]) :: Constraint where
HasOf (l := t ': req) lts = (Has l lts t, HasOf req lts)
HasOf '[] lts = 'True ~ 'True
-- | Require a record to contain a label
type Has l lts v =
( RecTy l lts ~ v
, KnownNat (RecSize lts)
, KnownNat (RecVecIdxPos l lts)
#ifdef JS_RECORD
, KnownSymbol l, FromJSVal v, ToJSVal v
#endif
)
-- | Get an existing record field
get ::
forall l v lts.
( Has l lts v
)
=> FldProxy l -> Rec lts -> v
#ifndef JS_RECORD
get _ (Rec vec#) =
let !(I# readAt#) =
fromIntegral (natVal' (proxy# :: Proxy# (RecVecIdxPos l lts)))
anyVal :: Any
anyVal =
case indexSmallArray# vec# readAt# of
(# a# #) -> a#
in unsafeCoerce# anyVal
#else
get lbl (Rec obj) =
unsafePerformIO $!
do r <- JS.unsafeGetProp (JSS.pack $ symbolVal lbl) obj
fromJSValUnchecked r
#endif
{-# INLINE get #-}
-- | Alias for 'get'
(&.) :: forall l v lts. (Has l lts v) => Rec lts -> FldProxy l -> v
(&.) = flip get
infixl 3 &.
-- | Update an existing record field
set ::
forall l v lts.
(Has l lts v)
=> FldProxy l -> v -> Rec lts -> Rec lts
#ifndef JS_RECORD
set _ !val (Rec vec#) =
let !(I# size#) = fromIntegral $ natVal' (proxy# :: Proxy# (RecSize lts))
!(I# setAt#) = fromIntegral (natVal' (proxy# :: Proxy# (RecVecIdxPos l lts)))
dynVal :: Any
!dynVal = unsafeCoerce# val
r2 =
runST' $ ST $ \s# ->
case newSmallArray# size# (error "No value") s# of
(# s'#, arr# #) ->
case copySmallArray# vec# 0# arr# 0# size# s'# of
s''# ->
case writeSmallArray# arr# setAt# dynVal s''# of
s'''# ->
case unsafeFreezeSmallArray# arr# s'''# of
(# s''''#, a# #) -> (# s''''#, Rec a# #)
in r2
#else
set lbl !val (Rec obj) =
unsafePerformIO $!
do newObj <- copyObject obj
val' <- toJSVal val
JS.unsafeSetProp (JSS.pack $ symbolVal lbl) val' newObj
pure $ Rec newObj
#endif
{-# INLINE set #-}
-- | Update an existing record field
modify ::
forall l v lts.
(Has l lts v)
=> FldProxy l -> (v -> v) -> Rec lts -> Rec lts
modify lbl fun r = set lbl (fun $ get lbl r) r
{-# INLINE modify #-}
-- | Constructor for field accessor paths
data lbl :& more = FldProxy lbl :& more
infixr 8 :&
-- | Constructor for field accessor paths
(&:) :: FldProxy q -> more -> q :& more
(&:) = (:&)
{-# INLINE (&:) #-}
infixr 8 &:
-- | Specialized version of (&:) to help writing the last piece of the path w/o
-- confusing the type checker
(&:-) :: FldProxy q -> FldProxy r -> q :& FldProxy r
(&:-) = (:&)
{-# INLINE (&:-) #-}
infixr 8 &:-
-- | Helper function to allow to clearing specify unknown 'IsLabel' cases
fld :: FldProxy l -> FldProxy l
fld = id
type family RecDeepTy (ps :: r) (lts :: [*]) :: * where
RecDeepTy (l :& more) (l := Rec t ': lts) = RecDeepTy more t
RecDeepTy (l :& more) (l := t ': lts) = t
RecDeepTy (l :& more) (q := t ': lts) = RecDeepTy (l :& more) lts
RecDeepTy (FldProxy l) '[l := t] = t
RecDeepTy l '[l := t] = t
class RecApplyPath p x where
-- | Perform a deep update, setting the key along the path to the
-- desired value
setPath' :: p -> (RecDeepTy p x -> RecDeepTy p x) -> Rec x -> Rec x
-- | Perform a deep read
getPath' :: p -> Rec x -> RecDeepTy p x
instance (Has l lts t, t ~ RecDeepTy (FldProxy l) lts) => RecApplyPath (FldProxy l) lts where
setPath' = modify
{-# INLINE setPath' #-}
getPath' = get
{-# INLINE getPath' #-}
instance
( RecDeepTy (l :& more) lts ~ RecDeepTy more rts
, RecTy l lts ~ Rec rts
, Has l lts v
, v ~ Rec rts
, RecApplyPath more rts
) => RecApplyPath (l :& more) lts where
setPath' (x :& more) v r =
let innerVal :: Rec rts
innerVal = get x r
in set x (setPath' more v innerVal) r
{-# INLINE setPath' #-}
getPath' (x :& more) r = getPath' more (get x r)
{-# INLINE getPath' #-}
-- | Perform a deep update, setting the key along the path to the
-- desired value
setPath :: RecApplyPath k x => k -> RecDeepTy k x -> Rec x -> Rec x
setPath s v = setPath' s (const v)
{-# INLINE setPath #-}
-- | Perform a deep update, transforming the value at the final key
modifyPath :: RecApplyPath k x => k -> (RecDeepTy k x -> RecDeepTy k x) -> Rec x -> Rec x
modifyPath = setPath'
{-# INLINE modifyPath #-}
-- | Perform a deep read. This is somewhat similar to using (&.), but is useful
-- when you want to share a 'RecPath' between 'getPath', 'modifyPath' and/or 'setPath'
getPath :: RecApplyPath k x => k -> Rec x -> RecDeepTy k x
getPath = getPath'
{-# INLINE getPath #-}
-- | Combine two records
combine ::
forall lhs rhs sortRes.
( KnownNat (RecSize lhs)
, KnownNat (RecSize rhs)
, KnownNat (RecSize lhs + RecSize rhs)
, sortRes ~ Sort (RecAppend lhs rhs)
, RecCopy lhs lhs sortRes
, RecCopy rhs rhs sortRes
)
=> Rec lhs
-> Rec rhs
-> Rec sortRes
#ifndef JS_RECORD
combine lts rts =
let !(I# size#) =
fromIntegral $ natVal' (proxy# :: Proxy# (RecSize lhs + RecSize rhs))
in runST' $ ST $ \s# ->
case newSmallArray# size# (error "No value") s# of
(# s'#, arr# #) ->
case recCopyInto (Proxy :: Proxy lhs) lts (Proxy :: Proxy sortRes) arr# s'# of
s''# ->
case recCopyInto (Proxy :: Proxy rhs) rts (Proxy :: Proxy sortRes) arr# s''# of
s'''# ->
case unsafeFreezeSmallArray# arr# s'''# of
(# s''''#, a# #) -> (# s''''#, Rec a# #)
#else
combine (Rec o1) (Rec o2) =
unsafePerformIO $
Rec <$> mergeObjs o1 o2
#endif
{-# INLINE combine #-}
-- | Alias for 'combine'
(++:) ::
forall lhs rhs sortRes.
( KnownNat (RecSize lhs)
, KnownNat (RecSize rhs)
, KnownNat (RecSize lhs + RecSize rhs)
, sortRes ~ Sort (RecAppend lhs rhs)
, RecCopy lhs lhs sortRes
, RecCopy rhs rhs sortRes
)
=> Rec lhs
-> Rec rhs
-> Rec sortRes
(++:) = combine
{-# INLINE (++:) #-}
data RecFields (flds :: [Symbol]) where
RFNil :: RecFields '[]
RFCons :: KnownSymbol f => FldProxy f -> RecFields xs -> RecFields (f ': xs)
recKeys :: forall t (lts :: [*]). RecKeys lts => t lts -> [String]
recKeys = recKeys' . recFields
recKeys' :: RecFields lts -> [String]
recKeys' x =
case x of
RFNil -> []
RFCons q qs -> symbolVal q : recKeys' qs
-- | Get keys of a record on value and type level
class RecKeys (lts :: [*]) where
type RecKeysT lts :: [Symbol]
recFields :: t lts -> RecFields (RecKeysT lts)
instance RecKeys '[] where
type RecKeysT '[] = '[]
recFields _ = RFNil
instance (KnownSymbol l, RecKeys lts) => RecKeys (l := t ': lts) where
type RecKeysT (l := t ': lts) = (l ': RecKeysT lts)
recFields (_ :: f (l := t ': lts)) =
let lbl :: FldProxy l
lbl = FldProxy
more :: Proxy lts
more = Proxy
in (lbl `RFCons` recFields more)
-- | Apply a function to each key element pair for a record
reflectRec ::
forall c r lts. (RecApply lts lts c)
=> Proxy c
-> (forall a. c a => String -> a -> r)
-> Rec lts
-> [r]
reflectRec _ f r =
reverse $
recApply (\(Dict :: Dict (c a)) s v xs -> (f s v : xs)) r (Proxy :: Proxy lts) []
{-# INLINE reflectRec #-}
-- | Fold over all elements of a record
reflectRecFold ::
forall c r lts. (RecApply lts lts c)
=> Proxy c
-> (forall a. c a => String -> a -> r -> r)
-> Rec lts
-> r
-> r
reflectRecFold _ f r =
recApply (\(Dict :: Dict (c a)) s v x -> f s v x) r (Proxy :: Proxy lts)
{-# INLINE reflectRecFold #-}
-- | Convert all elements of a record to a 'String'
showRec :: forall lts. (RecApply lts lts Show) => Rec lts -> [(String, String)]
showRec = reflectRec @Show Proxy (\k v -> (k, show v))
recToValue :: forall lts. (RecApply lts lts ToJSON) => Rec lts -> Value
recToValue r = object $ reflectRec @ToJSON Proxy (\k v -> (T.pack k, toJSON v)) r
recToEncoding :: forall lts. (RecApply lts lts ToJSON) => Rec lts -> Encoding
recToEncoding r = pairs $ mconcat $ reflectRec @ToJSON Proxy (\k v -> (T.pack k .= v)) r
recJsonParser :: forall lts s. (RecSize lts ~ s, KnownNat s, RecJsonParse lts) => Value -> Parser (Rec lts)
recJsonParser =
withObject "Record" $ \o ->
recJsonParse initSize o
where
initSize = fromIntegral $ natVal' (proxy# :: Proxy# s)
-- | Machinery needed to implement 'reflectRec'
class RecApply (rts :: [*]) (lts :: [*]) c where
recApply :: (forall a. Dict (c a) -> String -> a -> b -> b) -> Rec rts -> Proxy lts -> b -> b
instance RecApply rts '[] c where
recApply _ _ _ b = b
instance
( KnownSymbol l
, RecApply rts (RemoveAccessTo l lts) c
, Has l rts v
, c v
) => RecApply rts (l := t ': lts) c where
recApply f r (_ :: Proxy (l := t ': lts)) b =
let lbl :: FldProxy l
lbl = FldProxy
val = get lbl r
res = f Dict (symbolVal lbl) val b
pNext :: Proxy (RemoveAccessTo l (l := t ': lts))
pNext = Proxy
in recApply f r pNext res
-- | Machinery to implement equality
class RecEq (rts :: [*]) (lts :: [*]) where
recEq :: Rec rts -> Rec rts -> Proxy lts -> Bool
instance RecEq rts '[] where
recEq _ _ _ = True
instance
( RecEq rts (RemoveAccessTo l lts)
, Has l rts v
, Eq v
) => RecEq rts (l := t ': lts) where
recEq r1 r2 (_ :: Proxy (l := t ': lts)) =
let lbl :: FldProxy l
lbl = FldProxy
val = get lbl r1
val2 = get lbl r2
res = val == val2
pNext :: Proxy (RemoveAccessTo l (l := t ': lts))
pNext = Proxy
in res && recEq r1 r2 pNext
type family RemoveAccessTo (l :: Symbol) (lts :: [*]) :: [*] where
RemoveAccessTo l (l := t ': lts) = RemoveAccessTo l lts
RemoveAccessTo q (l := t ': lts) = (l := t ': RemoveAccessTo l lts)
RemoveAccessTo q '[] = '[]
-- | Machinery to implement parseJSON
class RecJsonParse (lts :: [*]) where
recJsonParse :: Int -> Object -> Parser (Rec lts)
instance RecJsonParse '[] where
recJsonParse initSize _ = pure (unsafeRNil initSize)
instance
( KnownSymbol l, FromJSON t, RecJsonParse lts
, RecSize lts ~ s, KnownNat s, KeyDoesNotExist l lts
#ifdef JS_RECORD
, ToJSVal t
#endif
) => RecJsonParse (l := t ': lts) where
recJsonParse initSize obj =
do let lbl :: FldProxy l
lbl = FldProxy
rest <- recJsonParse initSize obj
(v :: t) <- obj .: T.pack (symbolVal lbl)
pure $ unsafeRCons (lbl := v) rest
-- | Machinery for NFData
class RecNfData (lts :: [*]) (rts :: [*]) where
recNfData :: Proxy lts -> Rec rts -> ()
instance RecNfData '[] rts where
recNfData _ _ = ()
instance
( Has l rts v
, NFData v
, RecNfData (RemoveAccessTo l lts) rts
) => RecNfData (l := t ': lts) rts where
recNfData (_ :: (Proxy (l := t ': lts))) r =
let !v = get (FldProxy :: FldProxy l) r
pNext :: Proxy (RemoveAccessTo l (l := t ': lts))
pNext = Proxy
in deepseq v (recNfData pNext r)
-- | Conversion helper to bring a Haskell type to a record. Note that the
-- native Haskell type must be an instance of 'Generic'
class FromNative a lts | a -> lts where
fromNative' :: a x -> Rec lts
instance FromNative cs lts => FromNative (D1 m cs) lts where
fromNative' (M1 xs) = fromNative' xs
instance FromNative cs lts => FromNative (C1 m cs) lts where
fromNative' (M1 xs) = fromNative' xs
instance
( KnownSymbol name
#ifdef JS_RECORD
, ToJSVal t
#endif
)
=> FromNative (S1 ('MetaSel ('Just name) p s l) (Rec0 t)) '[name := t]
where
fromNative' (M1 (K1 t)) = (FldProxy :: FldProxy name) := t & rnil
instance
( FromNative l lhs
, FromNative r rhs
, lts ~ Sort (RecAppend lhs rhs)
, RecCopy lhs lhs lts
, RecCopy rhs rhs lts
, KnownNat (RecSize lhs)
, KnownNat (RecSize rhs)
, KnownNat (RecSize lhs + RecSize rhs)
)
=> FromNative (l :*: r) lts where
fromNative' (l :*: r) = fromNative' l ++: fromNative' r
-- | Convert a native Haskell type to a record
fromNative :: (Generic a, FromNative (Rep a) lts) => a -> Rec lts
fromNative = fromNative' . from
{-# INLINE fromNative #-}
-- | Conversion helper to bring a record back into a Haskell type. Note that the
-- native Haskell type must be an instance of 'Generic'
#if MIN_VERSION_base(4, 10, 0)
class ToNative a lts where
toNative' :: Rec lts -> a x
#else
class ToNative a lts | a -> lts where
toNative' :: Rec lts -> a x
#endif
instance ToNative cs lts => ToNative (D1 m cs) lts where
toNative' xs = M1 $ toNative' xs
instance ToNative cs lts => ToNative (C1 m cs) lts where
toNative' xs = M1 $ toNative' xs
instance
(Has name lts t)
=> ToNative (S1 ('MetaSel ('Just name) p s l) (Rec0 t)) lts
where
toNative' r =
M1 $ K1 (get (FldProxy :: FldProxy name) r)
instance
( ToNative l lts
, ToNative r lts
)
=> ToNative (l :*: r) lts where
toNative' r = toNative' r :*: toNative' r
#ifdef JS_RECORD
instance ToJSVal (Rec x) where
toJSVal (Rec (JS.Object obj)) = pure obj
instance FromJSVal (Rec x) where
fromJSVal jv = pure (Just $ Rec $ JS.Object jv) -- TODO: implement checking!!
#endif
-- | Convert a record to a native Haskell type
toNative :: (Generic a, ToNative (Rep a) lts) => Rec lts -> a
toNative = to . toNative'
{-# INLINE toNative #-}
-- | Like 'asks' for 'MonadReader', but you provide a record field you would like
-- to read from your environment
asksR :: (Has lbl lts v, MonadReader (Rec lts) m) => FldProxy lbl -> m v
asksR f = asks (get f)
{-# INLINE asksR #-}
-- | Like 'asks' for 'MonadReader', but you provide a record field you would like
-- to read from your environment
asksRP :: (RecApplyPath k x, MonadReader (Rec x) m) => k -> m (RecDeepTy k x)
asksRP p = asks (getPath p)
{-# INLINE asksRP #-}
-- | Like 'gets' for 'MonadState', but you provide a record field you would like
-- to read from your environment
getsR :: (Has lbl lts v, S.MonadState (Rec lts) m) => FldProxy lbl -> m v
getsR f = S.gets (get f)
{-# INLINE getsR #-}
-- | Similar to 'put' for 'MonadState', but you only set a single record field
setsR :: (Has lbl lts v, S.MonadState (Rec lts) m) => FldProxy lbl -> v -> m ()
setsR f v = S.modify (set f v)
{-# INLINE setsR #-}
-- | Similar to 'modify' for 'MonadState', but you update a single record field
modifiesR :: (Has lbl lts v, S.MonadState (Rec lts) m) => FldProxy lbl -> (v -> v) -> m ()
modifiesR f go = S.modify (modify f go)
{-# INLINE modifiesR #-}
-- | Similar to 'gets' for 'MonadState', but allows getting a value along a 'RecPath'
getsRP :: (RecApplyPath k x, S.MonadState (Rec x) m) => k -> m (RecDeepTy k x)
getsRP p = S.gets (getPath p)
{-# INLINE getsRP #-}
-- | Similar to 'put' for 'MonadState', but you only set a single record field
setsRP :: (RecApplyPath k x, S.MonadState (Rec x) m) => k -> RecDeepTy k x -> m ()
setsRP p v = S.modify (setPath p v)
{-# INLINE setsRP #-}
-- | Similar to 'modify' for 'MonadState', but you update a single record field
modifiesRP ::(RecApplyPath k x, S.MonadState (Rec x) m) => k -> (RecDeepTy k x -> RecDeepTy k x) -> m ()
modifiesRP p go = S.modify (modifyPath p go)
{-# INLINE modifiesRP #-}
type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)
-- | Convert a field label to a lens
lens ::
Has l lts v => FldProxy l -> Lens (Rec lts) (Rec lts) v v
lens lbl f r =
fmap (\v -> set lbl v r) (f (get lbl r))
{-# INLINE lens #-}
#ifdef JS_RECORD
foreign import javascript unsafe "Object.assign({}, $1, $2)" mergeObjs ::
JS.Object -> JS.Object -> IO JS.Object
#endif