vinyl 0.5.1 → 0.14.3
raw patch · 33 files changed
Files
- CHANGELOG.md +99/−6
- Data/Vinyl.hs +16/−1
- Data/Vinyl/ARec.hs +20/−0
- Data/Vinyl/ARec/Internal.hs +303/−0
- Data/Vinyl/ARec/Internal/SmallArray.hs +56/−0
- Data/Vinyl/Class/Method.hs +325/−0
- Data/Vinyl/CoRec.hs +295/−0
- Data/Vinyl/Core.hs +356/−45
- Data/Vinyl/Curry.hs +188/−0
- Data/Vinyl/Derived.hs +187/−20
- Data/Vinyl/FromTuple.hs +172/−0
- Data/Vinyl/Functor.hs +235/−5
- Data/Vinyl/Lens.hs +163/−57
- Data/Vinyl/Recursive.hs +165/−0
- Data/Vinyl/SRec.hs +411/−0
- Data/Vinyl/Syntax.hs +45/−0
- Data/Vinyl/Tutorial/Overview.hs +297/−0
- Data/Vinyl/TypeLevel.hs +93/−3
- Data/Vinyl/XRec.hs +201/−0
- benchmarks/AccessorsBench.hs +206/−0
- benchmarks/AsABench.hs +14/−0
- benchmarks/Bench/ARec.hs +39/−0
- benchmarks/Bench/Rec.hs +37/−0
- benchmarks/Bench/SRec.hs +34/−0
- benchmarks/EqualityBench.hs +19/−0
- benchmarks/StorableBench.hs +43/−32
- tests/Aeson.hs +282/−0
- tests/CoRecSpec.hs +50/−0
- tests/Intro.lhs +0/−260
- tests/Spec.hs +77/−0
- tests/Test/ARec.hs +101/−0
- tests/XRecSpec.hs +45/−0
- vinyl.cabal +93/−11
CHANGELOG.md view
@@ -1,10 +1,104 @@-0.5.1----------+# 0.14.3+- Compatibility with `lens-aeson` > 1.2 +# 0.14.2+- Export the `ToARec` class++# 0.14.1+- Compatibility with `aeson` > 2.0++# 0.14.0+- `ARec` efficiency improvements (@Philonous)+- Make `ElField` a newtype (@Philonous)++The `ElField` change brings more opportunities for the optimizer, but can result in longer compile times.++# 0.13.3+- Fixed CHANGELOG entry for 0.13.2: it referred to version 0.14.0+- Relax bounds on `hspec`++# 0.13.2+- Removed aput and alens from Data.Vinyl.ARec. They were used internally, but their type is unsound.++# 0.13.1+- GHC 9.0.1 support++# 0.13.0+- GHC 8.10.1 support fix. A fix for the previous attempt at 8.10 support involves a backwards incompatible change.++# 0.12.2++- GHC 8.10.1 support++# 0.12.0++- GHC 8.8.1 support. Class type signatures were changed to remove explicit kind variables. This is to simplify the use of `TypeApplications` which changed with GHC 8.8.1 to require explicit application to those kind variables. Leaving them out of the class definitions preserves existing usage of `TypeApplications`. Thanks to Justin Le (@mstksg).++# 0.11.0++- Changed the `Show` instance of `CoRec`+- Added the `corec` helper that specifically helps type inference when+ constructing `CoRec ElField` values.++# 0.10.0++- Changed the types of `Data.Vinyl.CoRec.onCoRec` and `Data.Vinyl.CoRec.onField`. This was pushing through the changes to drop the use of `Proxy` arguments, relying instead on `TypeApplications`. Also added `onCoRec1` and `onField` to work with functions relying on a single type class.++- Faster `asA` and `asA'`. These implementations utilize `unsafeCoerce` in their implementations after we have performed a runtime check that proves (to us) that the types match up. The old implementations are still available as `asASafe` and `asA'Safe`. While both implementations can run in constant time if the compiler optimizes everything successfully, the faster variants are a bit more than 3x faster in a trivial benchmark.++- Add a `Generic` instance for `Rec` and common functors.++- Add a variety of `ToJSON` implementations as a test case. One or all of these should probably exist as a separate package to avoid `vinyl` depending on `aeson`, but their content may be of interest.++# 0.9.2++- Add `runcurryX` for applying an uncurried function to a `Rec` passing through the `XRec` machinery to strip out syntactic noise.++# 0.9.0++- A new `SRec` type for constant time field access for records with densely packed `Storable` fields. Conversion from `Rec` is accomplished with `toSRec`, while `fromSRec` takes you back to `Rec`. Record updates are fairly slow compared to native Haskell records and even `Rec`, but reading a field is as fast as anything.++- Concise record construction syntax from tuples. Construct a `FieldRec` with `fieldRec (#x =: True, #y =: 'b')` and have the type inferred as `Rec ElField '[ '("x", Bool), '("y", Char) ]`. Or use `record` to build records of any functor. Thanks to @heptahedron on GitHub for prompting this feature, and @sboosali for thinking through various approaches.++- Optional concise record field lens syntax. This uses an orphan `IsLabel` instance for all function types, so will conflict with any other library that does the same. Thus it is entirely opt-in: to enable this syntax, you must explicitly `import Data.Vinyl.Syntax`. This enables the use of labels as lenses. For example, `myRec & #name %~ map toUpper` to apply `map toUpper` to the `#name` field of the record value `myRec`. This technique is thanks to Tikhon Jelvis who shared it on the Haskell-Cafe mailing list.++- Field lenses can now change the type of a record. Thanks to @heptahedron on GitHub for exploring this feature. Using the above-mentioned features, one might now write something like `myRec & #name %~ length` to produce a record whose `#name` field is the length of the`String` `#name` field of some record value, `myRec`.++- Changed the type of `=:=` again to work directly with `Label`s as this is the most convenient usage.++- Definitions in `Data.Vinyl.Core` are now consistently in terms of type classes. This permits inlining and specialization to a user's record types. In the case where the record type is known, call sites do not change. But for functions polymorphic in the record's fields, a constraint will be required. If those constraints are a nuisance, or compile times increase beyond comfort, users should use definitions from the `Data.Vinyl.Recursive` that are written in a recursive style (as in previous versions of the `vinyl` package), treating the record as a list of fields.++- Added `restrictCoRec` and `weakenCoRec` suggested by @ElvishJerricco++# 0.8.0++- Overhaul of `FieldRec`: records with named fields. We now take advantage of the `-XOverloadedLabels` extension to support referring to record fields by names such a `#myField`.++- A new `ARec` type for constant-time field access. You can convert a classic, HList-like `Rec` into an `ARec` with `toARec`, or back the other way with `fromARec`. An `ARec` uses an `Array` to store record fields, so the usual trade-offs between lists and arrays apply: lists are cheap to construct by adding an element to the head, but slow to access; it is expensive to modify the shape of an array, but element lookup is constant-time.++**Compatibility Break**: The operator `=:` for constructing a record with a single field has changed. That operation is now known as `=:=`, while `=:` is now used to construct an `ElField`. It was decided that single-field record construction was not a common use-case, so the shorter name could be used for the more common operation. Apologies for making the upgrade a bit bumpy.++# 0.7.0+- Simplified `match`+- Added `Data.Vinyl.Curry`++# 0.6.0++Added a `CoRec` (co-record) type constructed in the same style as the existing `Rec` type for records. A `CoRec` is an open sum type: a value of `CoRec [a,b,c]` is either an `a`, a `b`, *or* a `c`. In contrast a `Rec [a,b,c]` includes an `a`, a `b`, *and*, a `c`.++# 0.5.3++Added a concise `Show` instance for `Const`.++# 0.5.2++Ported the tutorial to haddocks (andrewthad)++# 0.5.1+ Added utilities for working with the `FieldRec` type. -Vinyl 0.5-=========+# 0.5 Vinyl 0.5 combines the generality of Vinyl 0.4 with the ease-of-use of previous versions by eschewing the defunctionalized type families and just using plain@@ -14,8 +108,7 @@ Also new in 0.5 is a unified lens-based approach to subtyping, coercion and projection. -Vinyl 0.4-=========+# 0.4 Vinyl 0.4 is a big departure from previous versions, in that it introduces a universe encoding as a means to generalize the space of keys from strings to
Data/Vinyl.hs view
@@ -1,10 +1,25 @@+{-# LANGUAGE PatternSynonyms #-} module Data.Vinyl ( module Data.Vinyl.Core+ , module Data.Vinyl.Class.Method+ , module Data.Vinyl.ARec , module Data.Vinyl.Derived+ , module Data.Vinyl.FromTuple+ , module Data.Vinyl.Functor , module Data.Vinyl.Lens+ , module Data.Vinyl.SRec+ , module Data.Vinyl.XRec ) where import Data.Vinyl.Core+import Data.Vinyl.Class.Method (RecMapMethod(..), RecPointed(..))+import Data.Vinyl.Class.Method (rmapMethodF, mapFields)+import Data.Vinyl.Class.Method (rtraverseInMethod, rsequenceInFields)+import Data.Vinyl.ARec (ARec, toARec, fromARec) import Data.Vinyl.Derived+import Data.Vinyl.FromTuple (record, fieldRec, ruple, xrec, xrecX, xrecTuple)+import Data.Vinyl.Functor (ElField(..)) import Data.Vinyl.Lens-+import Data.Vinyl.SRec (SRec, toSRec, fromSRec)+import Data.Vinyl.XRec (XRec, pattern (::&), pattern XRNil, IsoXRec(..))+import Data.Vinyl.XRec (xrmap, xrapply, rmapX, XRMap, XRApply)
+ Data/Vinyl/ARec.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE Trustworthy #-}++-- | Constant-time field accessors for extensible records. The+-- trade-off is the usual lists vs arrays one: it is fast to add an+-- element to the head of a list, but element access is linear time;+-- array access time is uniform, but extending the array is more+-- slower.+module Data.Vinyl.ARec+ ( ARec -- Exported abstractly+ , IndexableField+ , ToARec+ , toARec+ , fromARec+ , aget+ , arecGetSubset+ , arecSetSubset+ , arecRepsMatchCoercion+ , arecConsMatchCoercion+ ) where+import Data.Vinyl.ARec.Internal
+ Data/Vinyl/ARec/Internal.hs view
@@ -0,0 +1,303 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+#if __GLASGOW_HASKELL__ >= 806+{-# LANGUAGE QuantifiedConstraints #-}+{-# LANGUAGE RankNTypes #-}+#endif+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+-- | Constant-time field accessors for extensible records. The+-- trade-off is the usual lists vs arrays one: it is fast to add an+-- element to the head of a list, but element access is linear time;+-- array access time is uniform, but extending the array is more+-- slower.+--+-- Tradeoffs:+--+-- * No sharing of the spine (i.e. when you change elements in the front of the+-- record the tail can't be re-used)+-- * ARec requires (4 + n) words + size of the fields+-- * 1 for the ARec constructor+-- * 1 for the pointer to the SmallArray#+-- * The SmallArray# has 2 words as header (1 for GC, 1 for number of elements)+-- * 1 pointer per element to the actual data+-- * Rec requires (2n) words + size of Fields+-- * 1 word per (:&) constructor+-- * 1 word for the pointer to the element+module Data.Vinyl.ARec.Internal+ ( ARec (..)+ , ToARec+ , IndexableField+ , arec+ , ARecBuilder (..)+ , arcons+ , arnil+ , toARec+ , fromARec+ , aget+ , unsafeAput+ , unsafeAlens+ , arecGetSubset+ , arecSetSubset+ , arecRepsMatchCoercion+ , arecConsMatchCoercion+ ) where+import Data.Vinyl.Core+import Data.Vinyl.Lens (RecElem(..), RecSubset(..))+import Data.Vinyl.TypeLevel+import Data.Vinyl.ARec.Internal.SmallArray+import Control.Monad.ST++import Unsafe.Coerce+#if __GLASGOW_HASKELL__ < 806+import Data.Constraint.Forall (Forall)+#endif+import Data.Type.Coercion (Coercion (..))+import GHC.Types++-- | An array-backed extensible record with constant-time field+-- access.+newtype ARec (f :: k -> *) (ts :: [k]) = ARec SmallArray+type role ARec representational nominal++-- | Get the ith element from the ARec+unsafeIxARec+ :: forall a k (f :: k -> *) (ts :: [k]).+ ARec f ts+ -> Int+ -> a+unsafeIxARec (ARec ar) ix = indexSmallArray ar ix+{-# INLINE unsafeIxARec #-}++-- | Given that @xs@ and @ys@ have the same length, and mapping+-- @f@ over @xs@ and @g@ over @ys@ produces lists whose elements+-- are pairwise 'Coercible', @ARec f xs@ and @ARec g ys@ are+-- 'Coercible'.+arecRepsMatchCoercion :: AllRepsMatch f xs g ys => Coercion (ARec f xs) (ARec g ys)+arecRepsMatchCoercion = unsafeCoerce (Coercion :: Coercion () ())++-- | Given that @forall x. Coercible (f x) (g x)@, produce a coercion from+-- @ARec f xs@ to @ARec g xs@. While the constraint looks a lot like+-- @Coercible f g@, it is actually weaker.++#if __GLASGOW_HASKELL__ >= 806+arecConsMatchCoercion ::+ (forall (x :: k). Coercible (f x) (g x)) => Coercion (ARec f xs) (ARec g xs)+arecConsMatchCoercion = unsafeCoerce (Coercion :: Coercion () ())+#else+arecConsMatchCoercion :: forall k (f :: k -> *) (g :: k -> *) (xs :: [k]).+ Forall (Similar f g) => Coercion (Rec f xs) (Rec g xs)+-- Why do we need this? No idea, really. I guess some change in+-- newtype handling for Coercible in 8.6?+arecConsMatchCoercion = unsafeCoerce (Coercion :: Coercion (Rec f xs) (Rec f xs))+#endif++-- Using a class instead of a recursive function allows aRecValues to be+-- completely inlined+class ToARec (us :: [k]) where+ aRecValues :: Rec f us -> ARecBuilder f us++instance ToARec '[] where+ aRecValues RNil = arnil+ {-# INLINE aRecValues #-}++instance ToARec us => ToARec (u ': us) where+ aRecValues (x :& xs) = x `arcons` aRecValues xs+ {-# INLINE aRecValues #-}++-- | Convert a 'Rec' into an 'ARec' for constant-time field access.+toARec+ :: forall f ts.+ (NatToInt (RLength ts), ToARec ts)+ => Rec f ts+ -> ARec f ts+toARec rs = arec (aRecValues rs)+{-# INLINE toARec #-}++{-+-- This is sensible, but the ergonomics are likely quite bad thanks to the+-- interaction between Coercible resolution and resolution in the presence of+-- quantified constraints. Is there a good way to do this?++arecConsMatchCoercible :: forall k f g rep (r :: TYPE rep).+ (forall (x :: k). Coercible (f x) (g x))+ => ((forall (xs :: [k]). Coercible (ARec f xs) (ARec g xs)) => r) -> r+arecConsMatchCoercible f = f+-}++-- | An efficient builder for ARec values+--+-- Use the pseudo-constructors 'arcons' and 'arnil' to construct an+-- 'ARecBuilder' and then turn it into an 'ARec' with 'arec'+--+-- Example: (requires -XOverloadedLabels and )+--+-- > user :: ARec ElField '[ "name" ::: String+-- > , "age" ::: Int+-- > , "active" ::: Bool]+-- > user = arec ( #name =: "Peter"+-- > `arcons` #age =: 4+-- > `arcons` #active =: True+-- > `arcons` arnil+-- > )+newtype ARecBuilder f us =+ -- A function that writes values to the correct position in the underlying array+ -- Takes the current index+ ARecBuilder ( forall s.+ Int -- Index to write to+ -> SmallMutableArray s -- Arrray to write to+ -> ST s ()+ )++infixr 1 `arcons`+-- | Pseudo-constructor for an ARecBuilder+--+-- "Cons" a field to an ARec under construction+--+-- See 'ARecBuilder'+arcons :: f u -> ARecBuilder f us -> ARecBuilder f (u ': us)+arcons !v (ARecBuilder fvs) = ARecBuilder $ \i mArr -> do+ writeSmallArray mArr i v+ fvs (i+1) mArr+{-# INLINE arcons #-}++-- | Pseudo-constructor for 'ARecBuilder'+--+-- Build an ARec without fields+--+-- See 'ARecBuilder'+arnil :: ARecBuilder f '[]+arnil = ARecBuilder $ \_i _arr -> return ()+{-# INLINE arnil #-}++-- | Turn an ARecBuilder into an ARec+--+-- See 'ARecBuilder'+arec+ :: forall k (us :: [k] ) f+ . (NatToInt (RLength us)) =>+ ARecBuilder f us+ -> ARec f us+arec (ARecBuilder fillArray) = ARec $+ runST $ withNewSmallArray (natToInt @(RLength us))+ $ fillArray 0+{-# INLINE arec #-}++-- | Defines a constraint that lets us index into an 'ARec' in order+-- to produce a 'Rec' using 'fromARec'.+class (NatToInt (RIndex t ts)) => IndexableField ts t where+instance (NatToInt (RIndex t ts)) => IndexableField ts t where++-- | Convert an 'ARec' into a 'Rec'.+fromARec :: forall f ts.+ (RecApplicative ts, RPureConstrained (IndexableField ts) ts)+ => ARec f ts -> Rec f ts+fromARec ar = rpureConstrained @(IndexableField ts) aux+ where aux :: forall t. NatToInt (RIndex t ts) => f t+ aux = unsafeIxARec ar (natToInt @(RIndex t ts))+{-# INLINE fromARec #-}++-- | Get a field from an 'ARec'.+aget :: forall t f ts. (NatToInt (RIndex t ts)) => ARec f ts -> f t+aget ar = unsafeIxARec ar (natToInt @(RIndex t ts))+{-# INLINE aget #-}++-- | Set a field in an 'ARec'.+unsafeAput :: forall t t' f ts ts'. (NatToInt (RIndex t ts))+ => f t' -> ARec f ts -> ARec f ts'+unsafeAput x (ARec arr) = ARec $ runST $+ withThawedSmallArray arr $ \mArr ->+ writeSmallArray mArr (natToInt @(RIndex t ts)) x+{-# INLINE unsafeAput #-}++-- | Define a lens for a field of an 'ARec'.+unsafeAlens :: forall f g t t' ts ts'. (Functor g, NatToInt (RIndex t ts))+ => (f t -> g (f t')) -> ARec f ts -> g (ARec f ts')+unsafeAlens f ar = fmap (flip (unsafeAput @t) ar) (f (aget ar))+{-# INLINE unsafeAlens #-}++-- instance (i ~ RIndex t ts, i ~ RIndex t' ts', NatToInt (RIndex t ts)) => RecElem ARec t t' ts ts' i where+-- rlens = alens+-- rget = aget+-- rput = aput++instance RecElem ARec t t' (t ': ts) (t' ': ts) 'Z where+ rlensC = unsafeAlens+ {-# INLINE rlensC #-}+ rgetC = aget+ {-# INLINE rgetC #-}+ rputC = unsafeAput @t+ {-# INLINE rputC #-}++instance (RIndex t (s ': ts) ~ 'S i, NatToInt i, RecElem ARec t t' ts ts' i)+ => RecElem ARec t t' (s ': ts) (s ': ts') ('S i) where+ rlensC = unsafeAlens+ {-# INLINE rlensC #-}+ rgetC = aget+ {-# INLINE rgetC #-}+ rputC = unsafeAput @t+ {-# INLINE rputC #-}++-- | Get a subset of a record's fields.+arecGetSubset :: forall rs ss f.+ (IndexWitnesses (RImage rs ss), NatToInt (RLength rs))+ => ARec f ss -> ARec f rs+arecGetSubset (ARec arr) =+ ARec $ runST $+ withNewSmallArray (natToInt @(RLength rs)) $ \mArr ->+ go mArr 0 (indexWitnesses @(RImage rs ss))+ where+ go :: SmallMutableArray s -> Int -> [Int] -> ST s ()+ go _mArr _to [] = return ()+ go mArr to (from : froms) = do+ writeSmallArray mArr to (indexSmallArray arr from :: Any)+ go mArr (to + 1) froms+{-# INLINE arecGetSubset #-}++-- | Set a subset of a larger record's fields to all of the fields of+-- a smaller record.+arecSetSubset :: forall rs ss f. (IndexWitnesses (RImage rs ss))+ => ARec f ss -> ARec f rs -> ARec f ss+arecSetSubset (ARec arrBig) (ARec arrSmall) = ARec $ runST $+ withThawedSmallArray arrBig $ \mArr -> do+ go mArr 0 (indexWitnesses @(RImage rs ss))+ where+ go :: SmallMutableArray s -> Int -> [Int] -> ST s ()+ go _mArr _ [] = return ()+ go mArr from (to : tos) = do+ writeSmallArray mArr to (indexSmallArray arrSmall from)+ go mArr (from + 1) tos+{-# INLINE arecSetSubset #-}++instance (is ~ RImage rs ss, IndexWitnesses is, NatToInt (RLength rs))+ => RecSubset ARec rs ss is where+ rsubsetC f big = fmap (arecSetSubset big) (f (arecGetSubset big))+ {-# INLINE rsubsetC #-}++instance (RPureConstrained (IndexableField rs) rs,+ RecApplicative rs,+ Show (Rec f rs)) => Show (ARec f rs) where+ show = show . fromARec++instance (RPureConstrained (IndexableField rs) rs,+ RecApplicative rs,+ Eq (Rec f rs)) => Eq (ARec f rs) where+ x == y = fromARec x == fromARec y++instance (RPureConstrained (IndexableField rs) rs,+ RecApplicative rs,+ Ord (Rec f rs)) => Ord (ARec f rs) where+ compare x y = compare (fromARec x) (fromARec y)
+ Data/Vinyl/ARec/Internal/SmallArray.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE BangPatterns #-}++-- | Helper functions for SmallArray#+--+-- This module exposes _unsafe_ functions to work with SmallArrays. That means+-- that specifically neither index bounds nor element types are checked So this+-- functionality should only be used in a context that enforces them by some+-- other means, e.g. ARec's type index++module Data.Vinyl.ARec.Internal.SmallArray where++import GHC.Prim+import GHC.Types+import Unsafe.Coerce+import GHC.ST++data SmallArray = SmallArray !(SmallArray# Any)+data SmallMutableArray s = SmallMutableArray !(SmallMutableArray# s Any)++indexSmallArray :: SmallArray -> Int -> a+indexSmallArray (SmallArray arr) (I# ix) =+ case indexSmallArray# arr ix of+ (# v #) -> unsafeCoerce v+{-# INLINE indexSmallArray #-}++withNewSmallArray :: Int -> (SmallMutableArray s -> ST s ()) -> ST s SmallArray+withNewSmallArray (I# len#) f =+ ST $ \s0 -> case newSmallArray# len# (error "withNewSmallArray exploded") s0 of+ (# s1, mArr #) ->+ case f (SmallMutableArray mArr) of+ ST st -> case st s1 of+ (# s2, () #) -> case unsafeFreezeSmallArray# mArr s2 of+ (# s3, ar #) -> (# s3, SmallArray ar #)+{-# INLINE withNewSmallArray #-}++writeSmallArray :: SmallMutableArray s -> Int -> a -> ST s ()+writeSmallArray (SmallMutableArray mArr) (I# n#) x = ST $ \s ->+ case writeSmallArray# mArr n# (unsafeCoerce x) s of+ s' -> (# s', () #)+{-# INLINE writeSmallArray #-}++withThawedSmallArray :: SmallArray+ -> (SmallMutableArray s -> ST s ())+ -> ST s SmallArray+withThawedSmallArray (SmallArray arr) f = ST $ \s0 ->+ let !(I# z#) = 0+ in case thawSmallArray# arr z# (sizeofSmallArray# arr) s0 of+ (# s1, mArr #) ->+ case f (SmallMutableArray mArr) of+ ST st -> case st s1 of+ (# s2, () #) -> case unsafeFreezeSmallArray# mArr s2 of+ (# s3, ar #) -> (# s3, SmallArray ar #)+{-# INLINE withThawedSmallArray #-}
+ Data/Vinyl/Class/Method.hs view
@@ -0,0 +1,325 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++{-| This module uses 'RecAll' to extend common typeclass methods to records.+ Generally, it is preferable to use the original typeclass methods to these+ variants. For example, in most places where 'recCompare' could be used,+ you could use 'compare' instead. They are useful in scenarios+ that involve working on unknown subsets of a record's fields+ because 'RecAll' constraints can easily be weakened. An example of this+ is given at the bottom of this page.+-}++module Data.Vinyl.Class.Method+ ( -- * Mapping methods over records+ RecMapMethod(..)+ , rmapMethodF+ , mapFields+ , RecMapMethod1(..)+ , RecPointed(..)+ , rtraverseInMethod+ , rsequenceInFields+ -- * Support for 'RecMapMethod'+ , FieldTyper, ApplyFieldTyper, PayloadType+ -- * Eq Functions+ , recEq+ -- * Ord Functions+ , recCompare+ -- * Monoid Functions+ , recMempty+ , recMappend+ , recMconcat+ -- * Num Functions+ , recAdd+ , recSubtract+ , recMultiply+ , recAbs+ , recSignum+ , recNegate+ -- * Bounded Functions+ , recMinBound+ , recMaxBound+ -- * Example+ -- $example+ ) where+import Data.Functor.Product (Product(Pair))+import Data.Vinyl.Core+import Data.Vinyl.Derived (KnownField, AllFields, FieldRec, traverseField)+import Data.Vinyl.Functor ((:.), getCompose, ElField(..))+import Data.Vinyl.TypeLevel+#if __GLASGOW_HASKELL__ < 804+import Data.Monoid+#endif++recEq :: RecAll f rs Eq => Rec f rs -> Rec f rs -> Bool+recEq RNil RNil = True+recEq (a :& as) (b :& bs) = a == b && recEq as bs++recCompare :: RecAll f rs Ord => Rec f rs -> Rec f rs -> Ordering+recCompare RNil RNil = EQ+recCompare (a :& as) (b :& bs) = compare a b <> recCompare as bs++-- | This function differs from the original 'mempty' in that+-- it takes an argument. In some cases, you will already+-- have a record of the type you are interested in, and+-- that can be passed an the argument. In other situations+-- where this is not the case, you may need the+-- interpretation function of the argument record to be+-- @Const ()@ or @Proxy@ so the you can generate the+-- argument with 'rpure'.+recMempty :: RecAll f rs Monoid => Rec proxy rs -> Rec f rs+recMempty RNil = RNil+recMempty (_ :& rs) = mempty :& recMempty rs++recMappend :: RecAll f rs Monoid => Rec f rs -> Rec f rs -> Rec f rs+recMappend RNil RNil = RNil+recMappend (a :& as) (b :& bs) = mappend a b :& recMappend as bs++-- | This function differs from the original 'mconcat'.+-- See 'recMempty'.+recMconcat :: RecAll f rs Monoid => Rec proxy rs -> [Rec f rs] -> Rec f rs+recMconcat p [] = recMempty p+recMconcat p (rec : recs) = recMappend rec (recMconcat p recs)++recAdd :: RecAll f rs Num => Rec f rs -> Rec f rs -> Rec f rs+recAdd RNil RNil = RNil+recAdd (a :& as) (b :& bs) = (a + b) :& recAdd as bs++recSubtract :: RecAll f rs Num => Rec f rs -> Rec f rs -> Rec f rs+recSubtract RNil RNil = RNil+recSubtract (a :& as) (b :& bs) = (a - b) :& recSubtract as bs++recMultiply :: RecAll f rs Num => Rec f rs -> Rec f rs -> Rec f rs+recMultiply RNil RNil = RNil+recMultiply (a :& as) (b :& bs) = (a * b) :& recSubtract as bs++recAbs :: RecAll f rs Num => Rec f rs -> Rec f rs+recAbs RNil = RNil+recAbs (a :& as) = abs a :& recAbs as++recSignum :: RecAll f rs Num => Rec f rs -> Rec f rs+recSignum RNil = RNil+recSignum (a :& as) = signum a :& recAbs as++recNegate :: RecAll f rs Num => Rec f rs -> Rec f rs+recNegate RNil = RNil+recNegate (a :& as) = negate a :& recAbs as++-- | This function differs from the original 'minBound'.+-- See 'recMempty'.+recMinBound :: RecAll f rs Bounded => Rec proxy rs -> Rec f rs+recMinBound RNil = RNil+recMinBound (_ :& rs) = minBound :& recMinBound rs++-- | This function differs from the original 'maxBound'.+-- See 'recMempty'.+recMaxBound :: RecAll f rs Bounded => Rec proxy rs -> Rec f rs+recMaxBound RNil = RNil+recMaxBound (_ :& rs) = maxBound :& recMaxBound rs++-- | When we wish to apply a typeclass method to each field of a+-- 'Rec', we typically care about typeclass instances of the record+-- field types irrespective of the record's functor context. To expose+-- the field types themselves, we utilize a constraint built from a+-- defunctionalized type family in the 'rmapMethod' method. The+-- symbols of the function space are defined by this data type.+data FieldTyper = FieldId | FieldSnd++-- | The interpretation function of the 'FieldTyper' symbols.+type family ApplyFieldTyper (f :: FieldTyper) (a :: k) :: * where+ ApplyFieldTyper 'FieldId a = a+ ApplyFieldTyper 'FieldSnd a = Snd a++-- | A mapping of record contexts into the 'FieldTyper' function+-- space. We explicitly match on 'ElField' to pick out the payload+-- type, and 'Compose' to pick out the inner-most context. All other+-- type constructor contexts are understood to not perform any+-- computation on their arguments.+type family FieldPayload (f :: u -> *) :: FieldTyper where+ FieldPayload ElField = 'FieldSnd+ FieldPayload (f :. g) = FieldPayload g+ FieldPayload f = 'FieldId++-- | Shorthand for combining 'ApplyFieldTyper' and 'FieldPayload'.+type family PayloadType f (a :: u) :: * where+ PayloadType f a = ApplyFieldTyper (FieldPayload f) a++-- | Generate a record from fields derived from type class+-- instances.+class RecPointed c f ts where+ rpointMethod :: (forall a. c (f a) => f a) -> Rec f ts++instance RecPointed c f '[] where+ rpointMethod _ = RNil+ {-# INLINE rpointMethod #-}++instance (c (f t), RecPointed c f ts)+ => RecPointed c f (t ': ts) where+ rpointMethod f = f :& rpointMethod @c f+ {-# INLINE rpointMethod #-}++-- | Apply a typeclass method to each field of a 'Rec' where the class+-- constrains the index of the field, but not its interpretation+-- functor.+class RecMapMethod c f ts where+ rmapMethod :: (forall a. c (PayloadType f a) => f a -> g a)+ -> Rec f ts -> Rec g ts++-- | Apply a typeclass method to each field of a 'Rec' where the class+-- constrains the field when considered as a value interpreted by the+-- record's interpretation functor.+class RecMapMethod1 c f ts where+ rmapMethod1 :: (forall a. c (f a) => f a -> g a)+ -> Rec f ts -> Rec g ts++instance RecMapMethod c f '[] where+ rmapMethod _ RNil = RNil+ {-# INLINE rmapMethod #-}++instance RecMapMethod1 c f '[] where+ rmapMethod1 _ RNil = RNil+ {-# INLINE rmapMethod1 #-}++instance (c (PayloadType f t), RecMapMethod c f ts)+ => RecMapMethod c f (t ': ts) where+ rmapMethod f (x :& xs) = f x :& rmapMethod @c f xs+ {-# INLINE rmapMethod #-}++instance (c (f t), RecMapMethod1 c f ts) => RecMapMethod1 c f (t ': ts) where+ rmapMethod1 f (x :& xs) = f x :& rmapMethod1 @c f xs+ {-# INLINE rmapMethod1 #-}++-- | Apply a typeclass method to each field of a @Rec f ts@ using the+-- 'Functor' instance for @f@ to lift the function into the+-- functor. This is a commonly-used specialization of 'rmapMethod'+-- composed with 'fmap'.+rmapMethodF :: forall c f ts. (Functor f, FieldPayload f ~ 'FieldId, RecMapMethod c f ts)+ => (forall a. c a => a -> a) -> Rec f ts -> Rec f ts+rmapMethodF f = rmapMethod @c (fmap f)+{-# INLINE rmapMethodF #-}++-- | Apply a typeclass method to each field of a 'FieldRec'. This is a+-- specialization of 'rmapMethod'.+mapFields :: forall c ts. RecMapMethod c ElField ts+ => (forall a. c a => a -> a) -> FieldRec ts -> FieldRec ts+mapFields f = rmapMethod @c g+ where g :: c (PayloadType ElField t) => ElField t -> ElField t+ g (Field x) = Field (f x)+{-# INLINE mapFields #-}++-- | Like 'rtraverseIn', but the function between functors may be+-- constrained.+rtraverseInMethod :: forall c h f g rs.+ (RMap rs, RPureConstrained c rs, RApply rs)+ => (forall a. c a => f a -> g (ApplyToField h a))+ -> Rec f rs+ -> Rec g (MapTyCon h rs)+rtraverseInMethod f = rtraverseIn @h (withPairedDict @c f)+ . rzipWith Pair (rpureConstrained @c aux)+ where aux :: c b => DictOnly c b+ aux = DictOnly++-- Note: rtraverseInMethod is written with that `aux` helper in order+-- to work around compatibility with GHC < 8.4. Write it more+-- naturally as `DictOnly @c` does not work with older compilers.++-- | Push an outer layer of interpretation functor into each named field.+rsequenceInFields :: forall f rs. (Functor f, AllFields rs, RMap rs)+ => Rec (f :. ElField) rs -> Rec ElField (MapTyCon f rs)+rsequenceInFields = rtraverseInMethod @KnownField (traverseField id . getCompose)+++{- $example+ This module provides variants of typeclass methods that have+ a 'RecAll' constraint instead of the normal typeclass+ constraint. For example, a type-specialized 'compare' would+ look like this:++> compare :: Ord (Rec f rs) => Rec f rs -> Rec f rs -> Ordering++ The 'recCompare' function looks like this:++> recCompare :: RecAll f rs Ord => Rec f rs -> Rec f rs -> Ordering++ The only difference is the constraint. Let's look at a potential+ use case for these functions.++ Let's write a function that projects out a subrecord from two records and+ then compares those for equality. We can write this with+ the '<:' operator from @Data.Vinyl.Lens@ and the normal 'compare'+ function. We don't need 'recCompare':++> -- This needs ScopedTypeVariables+> projectAndCompare :: forall super sub f. (super <: sub, Ord (Rec f sub))+> => Proxy sub -> Rec f super -> Rec f super -> Ordering+> projectAndCompare _ a b = compare (rcast a :: Rec f sub) (rcast b :: Rec f sub)++ That works fine for the majority of use cases, and it is probably how you should+ write the function if it does everything you need. However, let's consider+ a somewhat more complicated case.++ What if the exact subrecord we were projecting couldn't be+ known at compile time? Assume that the end user was allowd to+ choose the fields on which he or she wanted to compare records.+ The @projectAndCompare@ function cannot handle this because of the+ @Ord (Rec f sub)@ constraint. Even if we amend the constraint to+ read @Ord (Rec f super)@ instead, we cannot use this information+ to recover the @Ord (Rec f sub)@ constraint that we need. Let's+ try another approach.++ We can use the following GADT to prove subsethood:++> data Sublist (super :: [k]) (sub :: [k]) where+> SublistNil :: Sublist '[]+> SublistSuper :: Proxy r -> Sublist super sub -> Sublist (r ': super) sub+> SublistBoth :: Proxy r -> Sublist super sub -> Sublist (r ': super) (r ': sub)+>+> projectRec :: Sublist super sub -> Rec f super -> Rec f sub+> projectRec s r = case s of+> SublistNil -> RNil+> SublistBoth n snext -> case r of+> rhead :& rtail -> rhead :& projectRec snext rtail+> SublistSuper n snext -> case r of+> rhead :& rtail -> projectRec snext rtail++ It is also possible to write a typeclass to generate @Sublist@s+ implicitly, but that is beyond the scope of this example. Let's+ now write a function to use @Sublist@ to weaken a 'RecAll'+ constraint:++> import Data.Vinyl.Core hiding (Dict)+> import Data.Constraint+>+> weakenRecAll :: Proxy f -> Proxy c -> Sublist super sub -> RecAll f super c :- RecAll f sub c+> weakenRecAll f c s = case s of+> SublistNil -> Sub Dict+> SublistSuper _ snext -> Sub $ case weakenRecAll f c snext of+> Sub Dict -> Dict+> SublistBoth _ snext -> Sub $ case weakenRecAll f c snext of+> Sub Dict -> Dict++ Now we can write a different version of our original function:++> -- This needs ScopedTypeVariables+> projectAndCompare2 :: forall super sub f. (RecAll f super Ord)+> => Sublist super sub -> Rec f super -> Rec f super -> Ordering+> projectAndCompare2 s a b = case weakenRecAll (Proxy :: Proxy f) (Proxy :: Proxy Ord) s of+> Sub Dict -> recCompare (projectRec s a) (projectRec s b)++ Notice that in this case, the 'Ord' constraint applies to the full set of fields+ and is then weakened to target a subset of them instead.+-}
+ Data/Vinyl/CoRec.hs view
@@ -0,0 +1,295 @@+{-# LANGUAGE AllowAmbiguousTypes, BangPatterns, CPP, ConstraintKinds,+ DataKinds, EmptyCase, FlexibleContexts,+ FlexibleInstances, GADTs, KindSignatures,+ MultiParamTypeClasses, PolyKinds, RankNTypes,+ ScopedTypeVariables, TypeApplications, TypeOperators,+ UndecidableInstances #-}+-- | Co-records: open sum types.+--+-- Consider a record with three fields @A@, @B@, and @C@. A record is+-- a product of its fields; that is, it contains all of them: @A@,+-- @B@, /and/ @C@. If we want to talk about a value whose type is one+-- of those three types, it is /any one/ of type @A@, @B@, /or/+-- @C@. The type @CoRec '[A,B,C]@ corresponds to this sum type.+module Data.Vinyl.CoRec where+import Data.Maybe(fromJust)+import Data.Vinyl.Core+import Data.Vinyl.Lens (RElem, rget, rput, type (∈))+import Data.Vinyl.Functor (Compose(..), (:.), Identity(..), Const(..))+import Data.Vinyl.TypeLevel+import Data.Vinyl.Derived (FieldType, (:::))+import GHC.TypeLits (Symbol, KnownSymbol)+import GHC.Types (type Type)++import Unsafe.Coerce (unsafeCoerce)++-- | Generalize algebraic sum types.+data CoRec :: (k -> *) -> [k] -> * where+ CoRec :: RElem a ts (RIndex a ts) => !(f a) -> CoRec f ts++-- | A 'CoRec' constructor with better inference. If you have a label+-- that should pick out a type from the list of types that index a+-- 'CoRec', this function will help you more so than the raw 'CoRec'+-- data constructor.+corec :: forall (l :: Symbol)+ (ts :: [(Symbol,Type)])+ (f :: (Symbol,Type) -> Type).+ (KnownSymbol l, (l ::: FieldType l ts) ∈ ts)+ => f (l ::: FieldType l ts) -> CoRec f ts+corec x = CoRec x++-- | Apply a function to a 'CoRec' value. The function must accept+-- /any/ variant.+foldCoRec :: (forall a. RElem a ts (RIndex a ts) => f a -> b) -> CoRec f ts -> b+foldCoRec f (CoRec x) = f x++-- | A Field of a 'Rec' 'Identity' is a 'CoRec' 'Identity'.+type Field = CoRec Identity++-- | A function type constructor that takes its arguments in the+-- reverse order.+newtype Op b a = Op { runOp :: a -> b }++-- | Helper for writing a 'Show' instance for 'CoRec'. This lets us+-- ask for a 'Show' constraint on the type formed by applying a type+-- constructor to a type index.+class ShowF f a where+ showf :: f a -> String++instance Show (f a) => ShowF f a where+ showf = show++instance forall f ts. RPureConstrained (ShowF f) ts => Show (CoRec f ts) where+ show x = "{|" ++ onCoRec @(ShowF f) showf x ++ "|}"++instance forall ts. (RecApplicative ts, RecordToList ts,+ RApply ts, ReifyConstraint Eq Maybe ts, RMap ts)+ => Eq (CoRec Identity ts) where+ crA == crB = and . recordToList+ $ rzipWith f (toRec crA) (coRecToRec' crB)+ where+ f :: forall a. (Dict Eq :. Maybe) a -> Maybe a -> Const Bool a+ f (Compose (Dict a)) b = Const $ a == b+ toRec = reifyConstraint @Eq . coRecToRec'++-- | We can inject a a 'CoRec' into a 'Rec' where every field of the+-- 'Rec' is 'Nothing' except for the one whose type corresponds to the+-- type of the given 'CoRec' variant.+coRecToRec :: forall f ts. RecApplicative ts+ => CoRec f ts -> Rec (Maybe :. f) ts+coRecToRec (CoRec x) = rput (Compose (Just x)) (rpure (Compose Nothing))++-- | Shorthand for applying 'coRecToRec' with common functors.+coRecToRec' :: (RecApplicative ts, RMap ts)+ => CoRec Identity ts -> Rec Maybe ts+coRecToRec' = rmap (fmap getIdentity . getCompose) . coRecToRec++-- | Fold a field selection function over a 'Rec'.+class FoldRec ss ts where+ foldRec :: (CoRec f ss -> CoRec f ss -> CoRec f ss)+ -> CoRec f ss+ -> Rec f ts+ -> CoRec f ss++instance FoldRec ss '[] where foldRec _ z _ = z++instance (t ∈ ss, FoldRec ss ts) => FoldRec ss (t ': ts) where+ foldRec f z (x :& xs) = foldRec f (f z (CoRec x)) xs++-- | Apply a natural transformation to a variant.+coRecMap :: (forall x. f x -> g x) -> CoRec f ts -> CoRec g ts+coRecMap nt (CoRec x) = CoRec (nt x)++-- | Get a 'DictOnly' from an 'RPureConstrained' instance.+getDict :: forall c ts a proxy. (a ∈ ts, RPureConstrained c ts)+ => proxy a -> DictOnly c a+getDict _ = rget @a (rpureConstrained @c @ts DictOnly)++-- | Like 'coRecMap', but the function mapped over the 'CoRec' can+-- have a constraint.+coRecMapC :: forall c ts f g.+ (RPureConstrained c ts)+ => (forall x. (x ∈ ts, c x) => f x -> g x)+ -> CoRec f ts+ -> CoRec g ts+coRecMapC nt (CoRec x) = case getDict @c @ts x of+ DictOnly -> CoRec (nt x)++-- | This can be used to pull effects out of a 'CoRec'.+coRecTraverse :: Functor h+ => (forall x. f x -> h (g x)) -> CoRec f ts -> h (CoRec g ts)+coRecTraverse f (CoRec x) = fmap CoRec (f x)++-- | Fold a field selection function over a non-empty 'Rec'.+foldRec1 :: FoldRec (t ': ts) ts+ => (CoRec f (t ': ts) -> CoRec f (t ': ts) -> CoRec f (t ': ts))+ -> Rec f (t ': ts)+ -> CoRec f (t ': ts)+foldRec1 f (x :& xs) = foldRec f (CoRec x) xs++-- | Similar to 'Data.Monoid.First': find the first field that is not+-- 'Nothing'.+firstField :: FoldRec ts ts+ => Rec (Maybe :. f) ts -> Maybe (CoRec f ts)+firstField RNil = Nothing+firstField v@(x :& _) = coRecTraverse getCompose $ foldRec aux (CoRec x) v+ where aux :: CoRec (Maybe :. f) (t ': ts)+ -> CoRec (Maybe :. f) (t ': ts)+ -> CoRec (Maybe :. f) (t ': ts)+ aux c@(CoRec (Compose (Just _))) _ = c+ aux _ c = c++-- | Similar to 'Data.Monoid.Last': find the last field that is not+-- 'Nothing'.+lastField :: FoldRec ts ts+ => Rec (Maybe :. f) ts -> Maybe (CoRec f ts)+lastField RNil = Nothing+lastField v@(x :& _) = coRecTraverse getCompose $ foldRec aux (CoRec x) v+ where aux :: CoRec (Maybe :. f) (t ': ts)+ -> CoRec (Maybe :. f) (t ': ts)+ -> CoRec (Maybe :. f) (t ': ts)+ aux _ c@(CoRec (Compose (Just _))) = c+ aux c _ = c++-- | Apply methods from a type class to a 'CoRec'. Intended for use+-- with @TypeApplications@, e.g. @onCoRec \@Show show r@+onCoRec :: forall c f ts b g. (RPureConstrained c ts)+ => (forall a. (a ∈ ts, c a) => f a -> g b)+ -> CoRec f ts -> g b+onCoRec f (CoRec x) = case getDict @c @ts x of+ DictOnly -> f x+{-# INLINE onCoRec #-}++-- | Apply a type class method to a 'Field'. Intended for use with+-- @TypeApplications@, e.g. @onField \@Show show r@.+onField :: forall c ts b. (RPureConstrained c ts)+ => (forall a. (a ∈ ts, c a) => a -> b)+ -> Field ts -> b+onField f x = getIdentity (onCoRec @c (fmap f) x)+{-# INLINE onField #-}++-- * Extracting values from a CoRec/Pattern matching on a CoRec++-- | Compute a runtime 'Int' index identifying the position of the+-- variant held by a @CoRec f ts@ in the type-level list @ts@.+variantIndexOf :: forall f ts. CoRec f ts -> Int+variantIndexOf (CoRec x) = aux x+ where aux :: forall a. NatToInt (RIndex a ts) => f a -> Int+ aux _ = natToInt @(RIndex a ts)+{-# INLINE variantIndexOf #-}++-- [NOTE: asA] We want to say that if @NatToInt (RIndex a ts) ~+-- NatToInt (RIndex b ts)@ then @a ~ b@ by relying on an injectivity+-- property of 'RIndex'. However, we are checking the variant index of+-- the argument at runtime, so we do not statically know that+-- extracting the variant at a particular type is safe at compile+-- time.++-- | If a 'CoRec' is a variant of the requested type, return 'Just'+-- that value; otherwise return 'Nothing'.+asA :: NatToInt (RIndex t ts) => CoRec Identity ts -> Maybe t+asA = fmap getIdentity . asA'+{-# INLINE asA #-}++-- | Like 'asA', but for any interpretation functor.+asA' :: forall t ts f. (NatToInt (RIndex t ts))+ => CoRec f ts -> Maybe (f t)+asA' f@(CoRec x)+ | variantIndexOf f == natToInt @(RIndex t ts) = Just (unsafeCoerce x)+ | otherwise = Nothing+{-# INLINE asA' #-}++-- | Pattern match on a CoRec by specifying handlers for each case. Note that+-- the order of the Handlers has to match the type level list (t:ts).+--+-- >>> :{+-- let testCoRec = Col (Identity False) :: CoRec Identity [Int, String, Bool] in+-- match testCoRec $+-- (H $ \i -> "my Int is the successor of " ++ show (i - 1))+-- :& (H $ \s -> "my String is: " ++ s)+-- :& (H $ \b -> "my Bool is not: " ++ show (not b) ++ " thus it is " ++ show b)+-- :& RNil+-- :}+-- "my Bool is not: True thus it is False"+match :: forall ts b. CoRec Identity ts -> Handlers ts b -> b+match (CoRec (Identity t)) hs = aux t+ where aux :: forall a. RElem a ts (RIndex a ts) => a -> b+ aux x = case rget @a hs of+ H f -> f x++-- | Helper for handling a variant of a 'CoRec': either the function+-- is applied to the variant or the type of the 'CoRec' is refined to+-- reflect the fact that the variant is /not/ compatible with the type+-- of the would-be handler.+class RIndex t ts ~ i => Match1 t ts i where+ match1' :: Handler r t -> Rec Maybe ts -> Either r (Rec Maybe (RDelete t ts))++instance Match1 t (t ': ts) 'Z where+ match1' _ (Nothing :& xs) = Right xs+ match1' (H h) (Just x :& _) = Left (h x)++instance (Match1 t ts i, RIndex t (s ': ts) ~ 'S i,+ RDelete t (s ': ts) ~ (s ': RDelete t ts))+ => Match1 t (s ': ts) ('S i) where+ match1' h (x :& xs) = (x :&) <$> match1' h xs++-- | Handle a single variant of a 'CoRec': either the function is+-- applied to the variant or the type of the 'CoRec' is refined to+-- reflect the fact that the variant is /not/ compatible with the type+-- of the would-be handler+match1 :: (Match1 t ts (RIndex t ts),+ RecApplicative ts,+ RMap ts, RMap (RDelete t ts),+ FoldRec (RDelete t ts) (RDelete t ts))+ => Handler r t+ -> CoRec Identity ts+ -> Either r (CoRec Identity (RDelete t ts))+match1 h = fmap (fromJust . firstField . rmap (Compose . fmap Identity))+ . match1' h+ . coRecToRec'++matchNil :: CoRec f '[] -> r+matchNil (CoRec x) = case x of _ -> error "matchNil: impossible"++-- | Newtype around functions for a to b+newtype Handler b a = H (a -> b)++-- | 'Handlers ts b', is essentially a list of functions, one for each type in+-- ts. All functions produce a value of type 'b'. Hence, 'Handlers ts b' would+-- represent something like the type-level list: [t -> b | t \in ts ]+type Handlers ts b = Rec (Handler b) ts++-- | A 'CoRec' is either the first possible variant indicated by its+-- type, or a 'CoRec' that must be one of the remaining types.+restrictCoRec :: forall t ts f. (RecApplicative ts, FoldRec ts ts)+ => CoRec f (t ': ts) -> Either (f t) (CoRec f ts)+restrictCoRec r = maybe (Right (unsafeCoerce r)) Left (asA' @t r)+{-# INLINE restrictCoRec #-}++-- | A 'CoRec' whose possible types are @ts@ may be used at a type of+-- 'CoRec' whose possible types are @t:ts@.+weakenCoRec :: (RecApplicative ts, FoldRec (t ': ts) (t ': ts))+ => CoRec f ts -> CoRec f (t ': ts)+weakenCoRec = fromJust . firstField . (Compose Nothing :&) . coRecToRec++-- * Safe Variants++-- | A 'CoRec' is either the first possible variant indicated by its+-- type, or a 'CoRec' that must be one of the remaining types. The+-- safety is related to that of 'asASafe'.+restrictCoRecSafe :: forall t ts f. (RecApplicative ts, FoldRec ts ts)+ => CoRec f (t ': ts) -> Either (f t) (CoRec f ts)+restrictCoRecSafe = go . coRecToRec+ where go :: Rec (Maybe :. f) (t ': ts) -> Either (f t) (CoRec f ts)+ go (Compose Nothing :& xs) = Right (fromJust (firstField xs))+ go (Compose (Just x) :& _) = Left x++-- | Like 'asA', but implemented more safely and typically slower.+asASafe :: (t ∈ ts, RecApplicative ts, RMap ts)+ => CoRec Identity ts -> Maybe t+asASafe c@(CoRec _) = rget $ coRecToRec' c++-- | Like 'asASafe', but for any interpretation functor.+asA'Safe :: (t ∈ ts, RecApplicative ts, RMap ts)+ => CoRec f ts -> (Maybe :. f) t+asA'Safe c@(CoRec _) = rget $ coRecToRec c
Data/Vinyl/Core.hs view
@@ -1,5 +1,7 @@+{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}@@ -8,20 +10,52 @@ {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE Trustworthy #-}+{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-}+#if __GLASGOW_HASKELL__ >= 806+{-# LANGUAGE QuantifiedConstraints #-}+#endif {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} +-- | Core vinyl definitions. The 'Rec' data type is defined here, but+-- also of interest are definitions commonly used functions like+-- 'rmap', 'rapply', and 'rtraverse'.+--+-- The definitions in this module are written in terms of type classes+-- so that the definitions may be specialized to each record type at+-- which they are used. This usually helps with runtime performance,+-- but can slow down compilation time. If you are experiencing poor+-- compile times, you may wish to try the semantically equivalent+-- definitions in the "Data.Vinyl.Recursive" module: they should+-- produce the same results given the same inputs as functions defined+-- in this module, but they will not be specialized to your record+-- type. Instead, they treat the record as a list of fields, so will+-- have performance linear in the size of the record. module Data.Vinyl.Core where--import Data.Monoid+import Data.Coerce (Coercible)+#if __GLASGOW_HASKELL__ < 808+import Data.Monoid (Monoid)+#endif+#if __GLASGOW_HASKELL__ < 804+import Data.Semigroup (Semigroup(..))+#endif import Foreign.Ptr (castPtr, plusPtr) import Foreign.Storable (Storable(..))-import Data.Vinyl.Functor-import Control.Applicative hiding (Const(..))-import Data.Typeable (Proxy(..))+import Data.Functor.Product (Product(Pair)) import Data.List (intercalate)+import Data.Vinyl.Functor import Data.Vinyl.TypeLevel+import Data.Type.Equality (TestEquality (..), (:~:) (..))+import Data.Type.Coercion (TestCoercion (..), Coercion (..))+import GHC.Generics+import GHC.Types (Constraint, Type)+import Unsafe.Coerce (unsafeCoerce)+import Control.DeepSeq (NFData, rnf)+#if __GLASGOW_HASKELL__ < 806+import Data.Constraint.Forall (Forall)+#endif -- | A record is parameterized by a universe @u@, an interpretation @f@ and a -- list of rows @rs@. The labels or indices of the record are given by@@ -31,11 +65,27 @@ RNil :: Rec f '[] (:&) :: !(f r) -> !(Rec f rs) -> Rec f (r ': rs) -infixr :&+infixr 7 :& infixr 5 <+> infixl 8 <<$>> infixl 8 <<*>> +instance TestEquality f => TestEquality (Rec f) where+ testEquality RNil RNil = Just Refl+ testEquality (x :& xs) (y :& ys) = do+ Refl <- testEquality x y+ Refl <- testEquality xs ys+ Just Refl+ testEquality _ _ = Nothing++instance TestCoercion f => TestCoercion (Rec f) where+ testCoercion RNil RNil = Just Coercion+ testCoercion (x :& xs) (y :& ys) = do+ Coercion <- testCoercion x y+ Coercion <- testCoercion xs ys+ Just Coercion+ testCoercion _ _ = Nothing+ -- | Two records may be pasted together. rappend :: Rec f as@@ -51,20 +101,41 @@ -> Rec f (as ++ bs) (<+>) = rappend +-- | Combine two records by combining their fields using the given+-- function. The first argument is a binary operation for combining+-- two values (e.g. '(<>)'), the second argument takes a record field+-- into the type equipped with the desired operation, the third+-- argument takes the combined value back to a result type.+rcombine :: (RMap rs, RApply rs)+ => (forall a. m a -> m a -> m a)+ -> (forall a. f a -> m a)+ -> (forall a. m a -> g a)+ -> Rec f rs+ -> Rec f rs+ -> Rec g rs+rcombine smash toM fromM x y =+ rmap fromM (rapply (rmap (Lift . smash) x') y')+ where x' = rmap toM x+ y' = rmap toM y+ -- | 'Rec' @_ rs@ with labels in kind @u@ gives rise to a functor @Hask^u -> -- Hask@; that is, a natural transformation between two interpretation functors -- @f,g@ may be used to transport a value from 'Rec' @f rs@ to 'Rec' @g rs@.-rmap- :: (forall x. f x -> g x)- -> Rec f rs- -> Rec g rs-rmap _ RNil = RNil-rmap η (x :& xs) = η x :& (η `rmap` xs)-{-# INLINE rmap #-}+class RMap rs where+ rmap :: (forall x. f x -> g x) -> Rec f rs -> Rec g rs +instance RMap '[] where+ rmap _ RNil = RNil+ {-# INLINE rmap #-}++instance RMap xs => RMap (x ': xs) where+ rmap f (x :& xs) = f x :& rmap f xs+ {-# INLINE rmap #-}+ -- | A shorthand for 'rmap'. (<<$>>)- :: (forall x. f x -> g x)+ :: RMap rs+ => (forall x. f x -> g x) -> Rec f rs -> Rec g rs (<<$>>) = rmap@@ -72,7 +143,8 @@ -- | An inverted shorthand for 'rmap'. (<<&>>)- :: Rec f rs+ :: RMap rs+ => Rec f rs -> (forall x. f x -> g x) -> Rec g rs xs <<&>> f = rmap f xs@@ -80,17 +152,23 @@ -- | A record of components @f r -> g r@ may be applied to a record of @f@ to -- get a record of @g@.-rapply- :: Rec (Lift (->) f g) rs- -> Rec f rs- -> Rec g rs-rapply RNil RNil = RNil-rapply (f :& fs) (x :& xs) = getLift f x :& (fs `rapply` xs)-{-# INLINE rapply #-}+class RApply rs where+ rapply :: Rec (Lift (->) f g) rs+ -> Rec f rs+ -> Rec g rs +instance RApply '[] where+ rapply _ RNil = RNil+ {-# INLINE rapply #-}++instance RApply xs => RApply (x ': xs) where+ rapply (f :& fs) (x :& xs) = getLift f x :& (fs `rapply` xs)+ {-# INLINE rapply #-}+ -- | A shorthand for 'rapply'. (<<*>>)- :: Rec (Lift (->) f g) rs+ :: RApply rs+ => Rec (Lift (->) f g) rs -> Rec f rs -> Rec g rs (<<*>>) = rapply@@ -121,13 +199,68 @@ rtraverse f (x :& xs) = (:&) <$> f x <*> rtraverse f xs {-# INLINABLE rtraverse #-} +-- | While 'rtraverse' pulls the interpretation functor out of the+-- record, 'rtraverseIn' pushes the interpretation functor in to each+-- field type. This is particularly useful when you wish to discharge+-- that interpretation on a per-field basis. For instance, rather than+-- a @Rec IO '[a,b]@, you may wish to have a @Rec Identity '[IO a, IO+-- b]@ so that you can evaluate a single field to obtain a value of+-- type @Rec Identity '[a, IO b]@.+rtraverseIn :: forall h f g rs.+ (forall a. f a -> g (ApplyToField h a))+ -> Rec f rs+ -> Rec g (MapTyCon h rs)+rtraverseIn _ RNil = RNil+rtraverseIn f (x :& xs) = f x :& rtraverseIn f xs+{-# INLINABLE rtraverseIn #-}++-- | Push an outer layer of interpretation functor into each field.+rsequenceIn :: forall f g (rs :: [Type]). (Traversable f, Applicative g)+ => Rec (f :. g) rs -> Rec g (MapTyCon f rs)+rsequenceIn = rtraverseIn @f (sequenceA . getCompose)+{-# INLINABLE rsequenceIn #-}++-- | Given a natural transformation from the product of @f@ and @g@ to @h@, we+-- have a natural transformation from the product of @'Rec' f@ and @'Rec' g@ to+-- @'Rec' h@. You can also think about this operation as zipping two records+-- with the same element types but different interpretations.+rzipWith :: (RMap xs, RApply xs)+ => (forall x. f x -> g x -> h x) -> Rec f xs -> Rec g xs -> Rec h xs+rzipWith f = rapply . rmap (Lift . f)++-- | Map each element of a record to a monoid and combine the results.+class RFoldMap rs where+ rfoldMapAux :: Monoid m+ => (forall x. f x -> m)+ -> m+ -> Rec f rs+ -> m++instance RFoldMap '[] where+ rfoldMapAux _ m RNil = m+ {-# INLINE rfoldMapAux #-}++instance RFoldMap xs => RFoldMap (x ': xs) where+ rfoldMapAux f m (r :& rs) = rfoldMapAux f (mappend m (f r)) rs+ {-# INLINE rfoldMapAux #-}++rfoldMap :: forall rs m f. (Monoid m, RFoldMap rs)+ => (forall x. f x -> m) -> Rec f rs -> m+rfoldMap f = rfoldMapAux f mempty+{-# INLINE rfoldMap #-}+ -- | A record with uniform fields may be turned into a list.-recordToList- :: Rec (Const a) rs- -> [a]-recordToList RNil = []-recordToList (x :& xs) = getConst x : recordToList xs+class RecordToList rs where+ recordToList :: Rec (Const a) rs -> [a] +instance RecordToList '[] where+ recordToList RNil = []+ {-# INLINE recordToList #-}++instance RecordToList xs => RecordToList (x ': xs) where+ recordToList (x :& xs) = getConst x : recordToList xs+ {-# INLINE recordToList #-}+ -- | Wrap up a value with a capability given by its type data Dict c a where Dict@@ -140,53 +273,231 @@ -- Surely given @∀x:u.φ(x)@ we should be able to recover @x:u ⊢ φ(x)@! Sadly, -- the constraint solver is not quite smart enough to realize this and we must -- make it patently obvious by reifying the constraint pointwise with proof.-reifyConstraint- :: RecAll f rs c- => proxy c- -> Rec f rs- -> Rec (Dict c :. f) rs-reifyConstraint prx rec =- case rec of- RNil -> RNil- (x :& xs) -> Compose (Dict x) :& reifyConstraint prx xs+class ReifyConstraint c f rs where+ reifyConstraint+ :: Rec f rs+ -> Rec (Dict c :. f) rs +instance ReifyConstraint c f '[] where+ reifyConstraint RNil = RNil+ {-# INLINE reifyConstraint #-}++instance (c (f x), ReifyConstraint c f xs)+ => ReifyConstraint c f (x ': xs) where+ reifyConstraint (x :& xs) = Compose (Dict x) :& reifyConstraint xs+ {-# INLINE reifyConstraint #-}++-- | Build a record whose elements are derived solely from a+-- constraint satisfied by each.+class RPureConstrained c ts where+ rpureConstrained :: (forall a. c a => f a) -> Rec f ts++instance RPureConstrained c '[] where+ rpureConstrained _ = RNil+ {-# INLINE rpureConstrained #-}++instance (c x, RPureConstrained c xs) => RPureConstrained c (x ': xs) where+ rpureConstrained f = f :& rpureConstrained @c @xs f+ {-# INLINE rpureConstrained #-}++-- | Capture a type class instance dictionary. See+-- 'Data.Vinyl.Lens.getDict' for a way to obtain a 'DictOnly' value+-- from an 'RPureConstrained' constraint.+data DictOnly (c :: k -> Constraint) a where+ DictOnly :: forall c a. c a => DictOnly c a++-- | A useful technique is to use 'rmap (Pair (DictOnly @MyClass))' on+-- a 'Rec' to pair each field with a type class dictionary for+-- @MyClass@. This helper can then be used to eliminate the original.+withPairedDict :: (c a => f a -> r) -> Product (DictOnly c) f a -> r+withPairedDict f (Pair DictOnly x) = f x++-- | Build a record whose elements are derived solely from a+-- list of constraint constructors satisfied by each.+class RPureConstraints cs ts where+ rpureConstraints :: (forall a. AllSatisfied cs a => f a) -> Rec f ts++instance RPureConstraints cs '[] where+ rpureConstraints _ = RNil+ {-# INLINE rpureConstraints #-}++instance (AllSatisfied cs t, RPureConstraints cs ts)+ => RPureConstraints cs (t ': ts) where+ rpureConstraints f = f :& rpureConstraints @cs @ts f+ {-# INLINE rpureConstraints #-}+ -- | Records may be shown insofar as their points may be shown. -- 'reifyConstraint' is used to great effect here.-instance RecAll f rs Show => Show (Rec f rs) where+instance (RMap rs, ReifyConstraint Show f rs, RecordToList rs)+ => Show (Rec f rs) where show xs = (\str -> "{" <> str <> "}") . intercalate ", " . recordToList . rmap (\(Compose (Dict x)) -> Const $ show x)- $ reifyConstraint (Proxy :: Proxy Show) xs+ $ reifyConstraint @Show xs +instance Semigroup (Rec f '[]) where+ RNil <> RNil = RNil++instance (Semigroup (f r), Semigroup (Rec f rs))+ => Semigroup (Rec f (r ': rs)) where+ (x :& xs) <> (y :& ys) = (x <> y) :& (xs <> ys)+ instance Monoid (Rec f '[]) where mempty = RNil RNil `mappend` RNil = RNil instance (Monoid (f r), Monoid (Rec f rs)) => Monoid (Rec f (r ': rs)) where mempty = mempty :& mempty- (x :& xs) `mappend` (y :& ys) = (x <> y) :& (xs <> ys)+ (x :& xs) `mappend` (y :& ys) = (mappend x y) :& (mappend xs ys) instance Eq (Rec f '[]) where _ == _ = True instance (Eq (f r), Eq (Rec f rs)) => Eq (Rec f (r ': rs)) where (x :& xs) == (y :& ys) = (x == y) && (xs == ys) +instance Ord (Rec f '[]) where+ compare _ _ = EQ+instance (Ord (f r), Ord (Rec f rs)) => Ord (Rec f (r ': rs)) where+ compare (x :& xs) (y :& ys) = mappend (compare x y) (compare xs ys)+ instance Storable (Rec f '[]) where sizeOf _ = 0 alignment _ = 0 peek _ = return RNil poke _ RNil = return () -instance (Storable (f r), Storable (Rec f rs)) => Storable (Rec f (r ': rs)) where+instance (Storable (f r), Storable (Rec f rs))+ => Storable (Rec f (r ': rs)) where sizeOf _ = sizeOf (undefined :: f r) + sizeOf (undefined :: Rec f rs)- {-# INLINABLE sizeOf #-}+ {-# INLINE sizeOf #-} alignment _ = alignment (undefined :: f r)- {-# INLINABLE alignment #-}+ {-# INLINE alignment #-} peek ptr = do !x <- peek (castPtr ptr) !xs <- peek (ptr `plusPtr` sizeOf (undefined :: f r)) return $ x :& xs- {-# INLINABLE peek #-}+ {-# INLINE peek #-} poke ptr (!x :& xs) = poke (castPtr ptr) x >> poke (ptr `plusPtr` sizeOf (undefined :: f r)) xs- {-# INLINEABLE poke #-}+ {-# INLINE poke #-}++instance Generic (Rec f '[]) where+ type Rep (Rec f '[]) =+ C1 ('MetaCons "RNil" 'PrefixI 'False)+ (S1 ('MetaSel 'Nothing+ 'NoSourceUnpackedness+ 'NoSourceStrictness+ 'DecidedLazy) U1)+ from RNil = M1 (M1 U1)+ to (M1 (M1 U1)) = RNil++instance (Generic (Rec f rs)) => Generic (Rec f (r ': rs)) where+ type Rep (Rec f (r ': rs)) =+ C1 ('MetaCons ":&" ('InfixI 'RightAssociative 7) 'False)+ (S1 ('MetaSel 'Nothing+ 'NoSourceUnpackedness+ 'SourceStrict+ 'DecidedStrict)+ (Rec0 (f r))+ :*:+ S1 ('MetaSel 'Nothing+ 'NoSourceUnpackedness+ 'NoSourceStrictness+ 'DecidedLazy)+ (Rep (Rec f rs)))+ from (x :& xs) = M1 (M1 (K1 x) :*: M1 (from xs))+ to (M1 (M1 (K1 x) :*: M1 xs)) = x :& to xs++instance ReifyConstraint NFData f xs => NFData (Rec f xs) where+ rnf = go . reifyConstraint @NFData+ where+ go :: forall elems. Rec (Dict NFData :. f) elems -> ()+ go RNil = ()+ go (Compose (Dict x) :& xs) = rnf x `seq` go xs++type family Head xs where+ Head (x ': _) = x+type family Tail xs where+ Tail (_ ': xs) = xs++type family AllRepsMatch_ (f :: j -> *) (xs :: [j]) (g :: k -> *) (ys :: [k]) :: Constraint where+ AllRepsMatch_ f (x ': xs) g ys =+ ( ys ~ (Head ys ': Tail ys)+ , Coercible (f x) (g (Head ys))+ , AllRepsMatch_ f xs g (Tail ys) )+ AllRepsMatch_ _ '[] _ ys = ys ~ '[]++-- | @AllRepsMatch f xs g ys@ means that @xs@ and @ys@ have the+-- same lengths, and that mapping @f@ over @xs@ and @g@ over @ys@+-- produces lists whose corresponding elements are 'Coercible' with+-- each other. For example, the following hold:+--+-- @AllRepsMatch Proxy '[1,2,3] Proxy '[4,5,6]@+-- @AllRepsMatch Sum '[Int,Word] Identity '[Min Int, Max Word]@+type AllRepsMatch f xs g ys = (AllRepsMatch_ f xs g ys, AllRepsMatch_ g ys f xs)++-- This two-sided approach means that the *length* of each list+-- can be inferred from the length of the other. I don't know how+-- useful that is in practice, but we get it almost for free.++-- | Given that for each element @x@ in the list @xs@,+repsMatchCoercion :: AllRepsMatch f xs g ys => Coercion (Rec f xs) (Rec g ys)+repsMatchCoercion = unsafeCoerce (Coercion :: Coercion () ())++{-+-- "Proof" that repsMatchCoercion is sensible.+repsMatchConvert :: AllRepsMatch f xs g ys => Rec f xs -> Rec g ys+repsMatchConvert RNil = RNil+repsMatchConvert (x :& xs) = coerce x :& repsMatchConvert xs+-}++#if __GLASGOW_HASKELL__ >= 806+consMatchCoercion ::+ (forall (x :: k). Coercible (f x) (g x)) => Coercion (Rec f xs) (Rec g xs)+#else+consMatchCoercion :: forall k (f :: k -> *) (g :: k -> *) (xs :: [k]).+ Forall (Similar f g) => Coercion (Rec f xs) (Rec g xs)+#endif+consMatchCoercion = unsafeCoerce (Coercion :: Coercion () ())+{-+-- "Proof" that consMatchCoercion is sensible.+consMatchConvert ::+ (forall (x :: k). Coercible (f x) (g x)) => Rec f xs -> Rec g xs+consMatchConvert RNil = RNil+consMatchConvert (x :& xs) = coerce x :& consMatchConvert xs++-- And for old GHC.+consMatchConvert' :: forall k (f :: k -> *) (g :: k -> *) (xs :: [k]).+ Forall (Similar f g) => Rec f xs -> Rec g xs+consMatchConvert' RNil = RNil+consMatchConvert' ((x :: f x) :& xs) =+ case inst :: Forall (Similar f g) DC.:- Similar f g x of+ DC.Sub DC.Dict -> coerce x :& consMatchConvert' xs+-}++{-+-- This is sensible, but I suspect the ergonomics will be awful+-- thanks to the interaction between Coercible constraint resolution+-- and constraint resolution with quantified constraints. Is there+-- a good way to accomplish it?++-- | Given+--+-- @+-- forall x. Coercible (f x) (g x)+-- @+--+-- provide the constraint+--+-- @+-- forall xs. Coercible (Rec f xs) (Rec g xs)+-- @+consMatchCoercible :: forall k f g rep (r :: TYPE rep).+ (forall (x :: k). Coercible (f x) (g x))+ => ((forall (xs :: [k]). Coercible (Rec f xs) (Rec g xs)) => r) -> r+consMatchCoercible f = case unsafeCoerce @(Zouch f f) @(Zouch f g) (Zouch $ \r -> r) of+ Zouch q -> q f++newtype Zouch (f :: k -> *) (g :: k -> *) =+ Zouch (forall rep (r :: TYPE rep). ((forall (xs :: [k]). Coercible (Rec f xs) (Rec g xs)) => r) -> r)+-}
+ Data/Vinyl/Curry.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++{-|++Provides combinators for currying and uncurrying functions over arbitrary vinyl+records.++-}+module Data.Vinyl.Curry where+import Data.Kind (Type)+import Data.Vinyl+import Data.Vinyl.Functor+import Data.Vinyl.XRec++-- * Currying++class RecordCurry ts where+ {-|+ N-ary version of 'curry' over functorial records.++ Example specialized signatures:++ @+ rcurry :: (Rec Maybe '[Int, Double] -> Bool) -> Maybe Int -> Maybe Double -> Bool+ rcurry :: (Rec (Either Int) '[Double, String, ()] -> Int) -> Either Int Double -> Either Int String -> Either Int () -> Int+ rcurry :: (Rec f '[] -> Bool) -> Bool+ @++ -}+ rcurry :: (Rec f ts -> a) -> CurriedF f ts a++class RecordCurry' ts where+ {-|+ N-ary version of 'curry' over pure records.++ Example specialized signatures:++ @+ rcurry' :: (Rec Identity '[Int, Double] -> Bool) -> Int -> Double -> Bool+ rcurry' :: (Rec Identity '[Double, String, ()] -> Int) -> Double -> String -> () -> Int+ rcurry' :: (Rec Identity '[] -> Bool) -> Bool+ @++ -}+ rcurry' :: (Rec Identity ts -> a) -> Curried ts a+++instance RecordCurry '[] where+ rcurry f = f RNil+ {-# INLINABLE rcurry #-}+instance RecordCurry' '[] where+ rcurry' f = f RNil+ {-# INLINABLE rcurry' #-}++instance RecordCurry ts => RecordCurry (t ': ts) where+ rcurry f x = rcurry (\xs -> f (x :& xs))+ {-# INLINABLE rcurry #-}+instance RecordCurry' ts => RecordCurry' (t ': ts) where+ rcurry' f x = rcurry' (\xs -> f (Identity x :& xs))+ {-# INLINABLE rcurry' #-}++-- * Uncurrying++{-|+N-ary version of 'uncurry' over functorial records.++Example specialized signatures:++@+runcurry :: (Maybe Int -> Maybe Double -> String) -> Rec Maybe '[Int, Double] -> String+runcurry :: (IO FilePath -> String) -> Rec IO '[FilePath] -> String+runcurry :: Int -> Rec f '[] -> Int+@+-}+runcurry :: CurriedF f ts a -> Rec f ts -> a+runcurry x RNil = x+runcurry f (x :& xs) = runcurry (f x) xs+{-# INLINABLE runcurry #-}+++{-|+N-ary version of 'uncurry' over pure records.++Example specialized signatures:++@+runcurry' :: (Int -> Double -> String) -> Rec Identity '[Int, Double] -> String+runcurry' :: Int -> Rec Identity '[] -> Int+@++Example usage:++@+f :: Rec Identity '[Bool, Int, Double] -> Either Int Double+f = runcurry' $ \b x y -> if b then Left x else Right y+@+-}+runcurry' :: Curried ts a -> Rec Identity ts -> a+runcurry' x RNil = x+runcurry' f (Identity x :& xs) = runcurry' (f x) xs+{-# INLINABLE runcurry' #-}++-- | Apply an uncurried function to an 'XRec'.+xruncurry :: CurriedX f ts a -> XRec f ts -> a+xruncurry x RNil = x+xruncurry f (x :& xs) = xruncurry (f (unX x)) xs+{-# INLINABLE xruncurry #-}++-- | Apply an uncurried function to a 'Rec' like 'runcurry' except the+-- function enjoys a type simplified by the 'XData' machinery that+-- strips away type-induced noise like 'Identity', 'Compose', and+-- 'ElField'.+runcurryX :: IsoXRec f ts => CurriedX f ts a -> Rec f ts -> a+runcurryX f = xruncurry f . toXRec+{-# INLINE runcurryX #-}++-- * Applicative Combinators++{-|+Lift an N-ary function to work over a record of 'Applicative' computations.++>>> runcurryA' (+) (Just 2 :& Just 3 :& RNil)+Just 5++>>> runcurryA' (+) (Nothing :& Just 3 :& RNil)+Nothing+-}+runcurryA' :: (Applicative f) => Curried ts a -> Rec f ts -> f a+runcurryA' f = fmap (runcurry' f) . rtraverse (fmap Identity)+{-# INLINE runcurryA' #-}++{-|+Lift an N-ary function over types in @g@ to work over a record of 'Compose'd+'Applicative' computations. A more general version of 'runcurryA''.++Example specialized signatures:++@+runcurryA :: (g x -> g y -> a) -> Rec (Compose Maybe g) '[x, y] -> Maybe a+@+-}+runcurryA :: (Applicative f) => CurriedF g ts a -> Rec (Compose f g) ts -> f a+runcurryA f = fmap (runcurry f) . rtraverse getCompose+{-# INLINE runcurryA #-}++-- * Curried Function Types++{-|+For the list of types @ts@, @'Curried' ts a@ is a curried function type from+arguments of types in @ts@ to a result of type @a@.++>>> :kind! Curried '[Int, Bool, String] Int+Curried '[Int, Bool, String] Int :: *+= Int -> Bool -> [Char] -> Int+-}+type family Curried ts a where+ Curried '[] a = a+ Curried (t ': ts) a = t -> Curried ts a+++{-|+For the type-level list @ts@, @'CurriedF' f ts a@ is a curried function type+from arguments of type @f t@ for @t@ in @ts@, to a result of type @a@.++>>> :kind! CurriedF Maybe '[Int, Bool, String] Int+CurriedF Maybe '[Int, Bool, String] Int :: *+= Maybe Int -> Maybe Bool -> Maybe [Char] -> Int+-}+type family CurriedF (f :: u -> Type) (ts :: [u]) a where+ CurriedF f '[] a = a+ CurriedF f (t ': ts) a = f t -> CurriedF f ts a++{-|+For the type-level list @ts@, @'CurriedX' f ts a@ is a curried function type+from arguments of type @HKD f t@ for @t@ in @ts@, to a result of type @a@.++>>> :set -XTypeOperators+>>> :kind! CurriedX (Maybe :. Identity) '[Int, Bool, String] Int+CurriedX (Maybe :. Identity) '[Int, Bool, String] Int :: *+= Maybe Int -> Maybe Bool -> Maybe [Char] -> Int+-}+type family CurriedX (f :: u -> Type) (ts :: [u]) a where+ CurriedX f '[] a = a+ CurriedX f (t ': ts) a = HKD f t -> CurriedX f ts a
Data/Vinyl/Derived.hs view
@@ -1,51 +1,141 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-}-+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeApplications #-}+-- | Commonly used 'Rec' instantiations. module Data.Vinyl.Derived where import Data.Proxy+import Data.Vinyl.ARec import Data.Vinyl.Core import Data.Vinyl.Functor-import Foreign.Ptr (castPtr)-import Foreign.Storable+import Data.Vinyl.Lens+import Data.Vinyl.TypeLevel (Fst, Snd, RIndex)+import GHC.OverloadedLabels import GHC.TypeLits -data ElField (field :: (Symbol, *)) where- Field :: KnownSymbol s => !t -> ElField '(s,t)+-- | Alias for Field spec+type a ::: b = '(a, b) +-- | A record of named fields. type FieldRec = Rec ElField-type HList = Rec Identity-type LazyHList = Rec Thunk -deriving instance Eq t => Eq (ElField '(s,t))-deriving instance Ord t => Ord (ElField '(s,t))+-- | An 'ARec' of named fields to provide constant-time field access.+type AFieldRec ts = ARec ElField ts -instance Show t => Show (ElField '(s,t)) where- show (Field x) = (symbolVal (Proxy::Proxy s))++" :-> "++show x+-- | Heterogeneous list whose elements are evaluated during list+-- construction.+type HList = Rec Identity +-- | Heterogeneous list whose elements are left as-is during list+-- construction (cf. 'HList').+type LazyHList = Rec Thunk+ -- | Get the data payload of an 'ElField'. getField :: ElField '(s,t) -> t getField (Field x) = x +-- | Get the label name of an 'ElField'.+getLabel :: forall s t. KnownSymbol s => ElField '(s,t) -> String+getLabel (Field _) = symbolVal (Proxy::Proxy s)+ -- | 'ElField' is isomorphic to a functor something like @Compose -- ElField ('(,) s)@. fieldMap :: (a -> b) -> ElField '(s,a) -> ElField '(s,b) fieldMap f (Field x) = Field (f x) {-# INLINE fieldMap #-} +-- | Something in the spirit of 'traverse' for 'ElField' whose kind+-- fights the standard library.+traverseField :: (KnownSymbol s, Functor f)+ => (a -> b) -> f (ElField '(s,a)) -> ElField '(s, f b)+traverseField f t = Field (fmap (f . getField) t)+ -- | Lens for an 'ElField''s data payload. rfield :: Functor f => (a -> f b) -> ElField '(s,a) -> f (ElField '(s,b)) rfield f (Field x) = fmap Field (f x) {-# INLINE rfield #-} +infix 8 =:++-- | Operator for creating an 'ElField'. With the @-XOverloadedLabels@+-- extension, this permits usage such as, @#foo =: 23@ to produce a+-- value of type @ElField ("foo" ::: Int)@.+(=:) :: KnownSymbol l => Label (l :: Symbol) -> (v :: *) -> ElField (l ::: v)+_ =: v = Field v++-- | Get a named field from a record.+rgetf+ :: forall l f v record us.+ (HasField record l us us v v, RecElemFCtx record f)+ => Label l -> record f us -> f (l ::: v)+rgetf _ = rget @(l ::: v)++-- | Get the value associated with a named field from a record.+rvalf+ :: (HasField record l us us v v, RecElemFCtx record ElField)+ => Label l -> record ElField us -> v+rvalf x = getField . rgetf x++-- | Set a named field. @rputf' #foo 23@ sets the field named @#foo@ to+-- @23@.+rputf' :: forall l v v' record us us'.+ (HasField record l us us' v v', KnownSymbol l, RecElemFCtx record ElField)+ => Label l -> v' -> record ElField us -> record ElField us'+rputf' _ = rput' @_ @(l:::v) . (Field :: v' -> ElField '(l,v'))++-- | Set a named field without changing its type. @rputf #foo 23@ sets+-- the field named @#foo@ to @23@.+rputf :: forall l v record us.+ (HasField record l us us v v, KnownSymbol l, RecElemFCtx record ElField)+ => Label l -> v -> record ElField us -> record ElField us+rputf _ = rput @_ @(l:::v) . Field++-- | A lens into a 'Rec' identified by a 'Label'.+rlensfL' :: forall l v v' record g f us us'.+ (Functor g, HasField record l us us' v v', RecElemFCtx record f)+ => Label l+ -> (f (l ::: v) -> g (f (l ::: v')))+ -> record f us+ -> g (record f us')+rlensfL' _ f = rlens' @(l ::: v) f++-- | A type-preserving lens into a 'Rec' identified by a 'Label'.+rlensfL :: forall l v record g f us.+ (Functor g, HasField record l us us v v, RecElemFCtx record f)+ => Label l+ -> (f (l ::: v) -> g (f (l ::: v)))+ -> record f us+ -> g (record f us)+rlensfL _ f = rlens' @(l ::: v) f++-- | A lens into the payload value of a 'Rec' field identified by a+-- 'Label'.+rlensf' :: forall l v v' record g us us'.+ (Functor g, HasField record l us us' v v', RecElemFCtx record ElField)+ => Label l -> (v -> g v') -> record ElField us -> g (record ElField us')+rlensf' _ f = rlens' @(l ::: v) (rfield f)++-- | A type-preserving lens into the payload value of a 'Rec' field+-- identified by a 'Label'.+rlensf :: forall l v record g us.+ (Functor g, HasField record l us us v v, RecElemFCtx record ElField)+ => Label l -> (v -> g v) -> record ElField us -> g (record ElField us)+rlensf _ f = rlens @(l ::: v) (rfield f)+ -- | Shorthand for a 'FieldRec' with a single field.-(=:) :: KnownSymbol s => proxy '(s,a) -> a -> FieldRec '[ '(s,a) ]-(=:) _ x = Field x :& RNil+(=:=) :: KnownSymbol s => Label (s :: Symbol) -> a -> FieldRec '[ '(s,a) ]+(=:=) _ x = Field x :& RNil -- | A proxy for field types. data SField (field :: k) = SField@@ -55,9 +145,86 @@ instance KnownSymbol s => Show (SField '(s,t)) where show _ = "SField "++symbolVal (Proxy::Proxy s) -instance forall s t. (KnownSymbol s, Storable t)- => Storable (ElField '(s,t)) where- sizeOf _ = sizeOf (undefined::t)- alignment _ = alignment (undefined::t)- peek ptr = Field `fmap` peek (castPtr ptr)- poke ptr (Field x) = poke (castPtr ptr) x+type family FieldType l fs where+ FieldType l '[] = TypeError ('Text "Cannot find label "+ ':<>: 'ShowType l+ ':<>: 'Text " in fields")+ FieldType l ((l ::: v) ': fs) = v+ FieldType l ((l' ::: v') ': fs) = FieldType l fs++-- | Constraint that a label is associated with a particular type in a+-- record.+type HasField record l fs fs' v v' =+ (RecElem record (l ::: v) (l ::: v') fs fs' (RIndex (l ::: v) fs), FieldType l fs ~ v, FieldType l fs' ~ v')++-- | Proxy for label type+data Label (a :: Symbol) = Label+ deriving (Eq, Show)++instance s ~ s' => IsLabel s (Label s') where+#if __GLASGOW_HASKELL__ < 802+ fromLabel _ = Label+#else+ fromLabel = Label+#endif++-- | Defines a constraint that lets us extract the label from an+-- 'ElField'. Used in 'rmapf' and 'rpuref'.+class (KnownSymbol (Fst a), a ~ '(Fst a, Snd a)) => KnownField a where+instance KnownSymbol l => KnownField (l ::: v) where++-- | Shorthand for working with records of fields as in 'rmapf' and+-- 'rpuref'.+type AllFields fs = (RPureConstrained KnownField fs, RecApplicative fs, RApply fs)++-- | Map a function between functors across a 'Rec' taking advantage+-- of knowledge that each element is an 'ElField'.+rmapf :: AllFields fs+ => (forall a. KnownField a => f a -> g a)+ -> Rec f fs -> Rec g fs+rmapf f = (rpureConstrained @KnownField (Lift f) <<*>>)++-- | Remove the first component (e.g. the label) from a type-level+-- list of pairs.+type family Unlabeled ts where+ Unlabeled '[] = '[]+ Unlabeled ('(s,x) ': xs) = x ': Unlabeled xs++-- | Facilities for removing and replacing the type-level label, or+-- column name, part of a record.+class StripFieldNames ts where+ stripNames :: Rec ElField ts -> Rec Identity (Unlabeled ts)+ stripNames' :: Functor f => Rec (f :. ElField) ts -> Rec f (Unlabeled ts)+ withNames :: Rec Identity (Unlabeled ts) -> Rec ElField ts+ withNames' :: Functor f => Rec f (Unlabeled ts) -> Rec (f :. ElField) ts++instance StripFieldNames '[] where+ stripNames RNil = RNil+ stripNames' RNil = RNil+ withNames RNil = RNil+ withNames' RNil = RNil++instance (KnownSymbol s, StripFieldNames ts) => StripFieldNames ('(s,t) ': ts) where+ stripNames (Field x :& xs) = pure x :& stripNames xs+ stripNames' (Compose x :& xs) = fmap getField x :& stripNames' xs+ withNames (Identity x :& xs) = Field x :& withNames xs+ withNames' (x :& xs) = Compose (fmap Field x) :& withNames' xs++-- | Construct a 'Rec' with 'ElField' elements.+rpuref :: AllFields fs => (forall a. KnownField a => f a) -> Rec f fs+rpuref f = rpureConstrained @KnownField f++-- | Operator synonym for 'rmapf'.+(<<$$>>)+ :: AllFields fs+ => (forall a. KnownField a => f a -> g a) -> Rec f fs -> Rec g fs+(<<$$>>) = rmapf++-- | Produce a 'Rec' of the labels of a 'Rec' of 'ElField's.+rlabels :: AllFields fs => Rec (Const String) fs+rlabels = rpuref getLabel'+ where getLabel' :: forall l v. KnownSymbol l+ => Const String (l ::: v)+ getLabel' = Const (symbolVal (Proxy::Proxy l))++-- * Specializations for working with an 'ARec' of named fields.
+ Data/Vinyl/FromTuple.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilyDependencies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+-- | Concise vinyl record construction from tuples up to size 8. An+-- example record construction using 'ElField' for named fields:+-- @fieldRec (#x =: True, #y =: 'b') :: FieldRec '[ '("x", Bool), '("y", Char) ]@+module Data.Vinyl.FromTuple where+import Data.Kind (Type)+import Data.Monoid (First(..))+#if __GLASGOW_HASKELL__ < 804+import Data.Semigroup (Semigroup(..))+#endif+import Data.Vinyl.Core (RApply, RMap, RecApplicative, rcombine, rmap, rtraverse, Rec(..))+import Data.Vinyl.Functor (onCompose, Compose(..), getCompose, ElField)+import Data.Vinyl.Lens (RecSubset, RecSubsetFCtx, rcast, rdowncast, type (⊆))+import Data.Vinyl.TypeLevel (RImage, Snd)+import Data.Vinyl.XRec (XRec, pattern (::&), pattern XRNil, IsoXRec(..), HKD)+import GHC.TypeLits (TypeError, ErrorMessage(Text))++-- | Convert a tuple of types formed by the application of a common+-- type constructor to a tuple of the common type constructor and a+-- list of the types to which it is applied in the original+-- tuple. E.g. @TupleToRecArgs f (f a, f b) ~ (f, [a,b])@.+type family TupleToRecArgs f t = (r :: (u -> Type, [u])) | r -> t where+ TupleToRecArgs f (f a, f b, f c, f d, f e, f z, f g, f h) =+ '(f, [a,b,c,d,e,z,g,h])+ TupleToRecArgs f (f a, f b, f c, f d, f e, f z, f g) = '(f, [a,b,c,d,e,z,g])+ TupleToRecArgs f (f a, f b, f c, f d, f e, f z) = '(f, [a,b,c,d,e,z])+ TupleToRecArgs f (f a, f b, f c, f d, f e) = '(f, [a,b,c,d,e])+ TupleToRecArgs f (f a, f b, f c, f d) = '(f, [a,b,c,d])+ TupleToRecArgs f (f a, f b, f c) = '(f, [a,b,c])+ TupleToRecArgs f (f a, f b) = '(f, [a,b])+ TupleToRecArgs f () = '(f , '[])++-- | Apply the 'Rec' type constructor to a type-level tuple of its+-- arguments.+type family UncurriedRec (t :: (u -> Type, [u])) = r | r -> t where+ UncurriedRec '(f, ts) = Rec f ts++-- | Apply the 'XRec' type constructor to a type-level tuple of its+-- arguments.+type family UncurriedXRec (t :: (u -> Type, [u])) = r | r -> t where+ UncurriedXRec '(f, ts) = XRec f ts++-- | Convert between an 'XRec' and an isomorphic tuple.+class TupleXRec f t where+ -- | Convert an 'XRec' to a tuple. Useful for pattern matching on an+ -- entire record.+ xrecTuple :: XRec f t -> ListToHKDTuple f t+ -- | Build an 'XRec' from a tuple.+ xrecX :: ListToHKDTuple f t -> XRec f t++instance TupleXRec f '[a,b] where+ xrecTuple (a ::& b ::& XRNil) = (a, b)+ xrecX (a, b) = a ::& b ::& XRNil++instance TupleXRec f '[a,b,c] where+ xrecTuple (a ::& b ::& c ::& XRNil) = (a, b, c)+ xrecX (a, b, c) = a ::& b ::& c ::& XRNil++instance TupleXRec f '[a,b,c,d] where+ xrecTuple (a ::& b ::& c ::& d ::& XRNil) = (a, b, c, d)+ xrecX (a, b, c, d) = a ::& b ::& c ::& d ::& XRNil++instance TupleXRec f '[a,b,c,d,e] where+ xrecTuple (a ::& b ::& c ::& d ::& e ::& XRNil) =+ (a, b, c, d, e)+ xrecX (a, b, c, d, e) = a ::& b ::& c ::& d ::& e ::& XRNil++instance TupleXRec f '[a,b,c,d,e,z] where+ xrecTuple (a ::& b ::& c ::& d ::& e ::& z ::& XRNil) =+ (a, b, c, d, e, z)+ xrecX (a, b, c, d, e, z) = a ::& b ::& c ::& d ::& e ::& z ::& XRNil++instance TupleXRec f '[a,b,c,d,e,z,g] where+ xrecTuple (a ::& b ::& c ::& d ::& e ::& z ::& g ::& XRNil) =+ (a, b, c, d, e, z, g)+ xrecX (a, b, c, d, e, z, g) = a ::& b ::& c ::& d ::& e ::& z ::& g ::& XRNil++instance TupleXRec f '[a,b,c,d,e,z,g,h] where+ xrecTuple (a ::& b ::& c ::& d ::& e ::& z ::& g ::& h ::& XRNil) =+ (a, b, c, d, e, z, g, h)+ xrecX (a, b, c, d, e, z, g, h) = a ::& b ::& c ::& d ::& e ::& z ::& g ::& h ::& XRNil++type family ListToHKDTuple (f :: u -> Type) (ts :: [u]) :: Type where+ ListToHKDTuple f '[] = HKD f ()+ ListToHKDTuple f '[a,b] = (HKD f a, HKD f b)+ ListToHKDTuple f '[a,b,c] = (HKD f a, HKD f b, HKD f c)+ ListToHKDTuple f '[a,b,c,d] = (HKD f a, HKD f b, HKD f c, HKD f d)+ ListToHKDTuple f '[a,b,c,d,e] = (HKD f a, HKD f b, HKD f c, HKD f d, HKD f e)+ ListToHKDTuple f '[a,b,c,d,e,z] = (HKD f a, HKD f b, HKD f c, HKD f d, HKD f e, HKD f z)+ ListToHKDTuple f '[a,b,c,d,e,z,g] = (HKD f a, HKD f b, HKD f c, HKD f d, HKD f e, HKD f z, HKD f g)+ ListToHKDTuple f '[a,b,c,d,e,z,g,h] = (HKD f a, HKD f b, HKD f c, HKD f d, HKD f e, HKD f z, HKD f g, HKD f h)+ ListToHKDTuple f x = TypeError ('Text "Tuples are only supported up to size 8")++-- | Convert a 'Rec' to a tuple going through 'HKD' to reduce+-- syntactic noise. Useful for pattern matching on an entire 'Rec'.+ruple :: (IsoXRec f ts, TupleXRec f ts)+ => Rec f ts -> ListToHKDTuple f ts+ruple = xrecTuple . toXRec++-- | Build a 'Rec' from a tuple passing through 'XRec'. This admits+-- the most concise syntax for building a 'Rec'. For example, @xrec+-- ("joe", 23) :: Rec Identity '[String, Int]@.+xrec :: (IsoXRec f t, TupleXRec f t) => ListToHKDTuple f t -> Rec f t+xrec = fromXRec . xrecX++-- | Build a 'Rec' from a tuple. An example would be building a value+-- of type @Rec f '[a,b]@ from a tuple of values with type @'(f a, f+-- b)@.+class TupleRec f t where+ record :: t -> UncurriedRec (TupleToRecArgs f t)++instance TupleRec f () where+ record () = RNil++instance TupleRec f (f a, f b) where+ record (a,b) = a :& b :& RNil++instance TupleRec f (f a, f b, f c) where+ record (a,b,c) = a :& b :& c :& RNil++instance TupleRec f (f a, f b, f c, f d) where+ record (a,b,c,d) = a :& b :& c :& d :& RNil++instance TupleRec f (f a, f b, f c, f d, f e) where+ record (a,b,c,d,e) = a :& b :& c :& d :& e :& RNil++instance TupleRec f (f a, f b, f c, f d, f e, f z) where+ record (a,b,c,d,e,z) = a :& b :& c :& d :& e :& z :& RNil++instance TupleRec f (f a, f b, f c, f d, f e, f z, f g) where+ record (a,b,c,d,e,z,g) = a :& b :& c :& d :& e :& z :& g :& RNil++instance TupleRec f (f a, f b, f c, f d, f e, f z, f g, f h) where+ record (a,b,c,d,e,z,g,h) = a :& b :& c :& d :& e :& z :& g :& h :& RNil++-- | Build a 'FieldRec' from a tuple of 'ElField' values.+fieldRec :: TupleRec ElField t => t -> UncurriedRec (TupleToRecArgs ElField t)+fieldRec = record @ElField++-- | Build a 'FieldRec' from a tuple and 'rcast' it to another record+-- type that is a subset of the constructed record. This is useful for+-- re-ordering fields. For example, @namedArgs (#name =: "joe", #age+-- =: 23)@ can supply arguments for a function expecting a record of+-- arguments with its fields in the opposite order.+namedArgs :: (TupleRec ElField t,+ ss ~ Snd (TupleToRecArgs ElField t),+ RecSubset Rec rs (Snd (TupleToRecArgs ElField t)) (RImage rs ss),+ UncurriedRec (TupleToRecArgs ElField t) ~ Rec ElField ss,+ RecSubsetFCtx Rec ElField)+ => t -> Rec ElField rs+namedArgs = rcast . fieldRec++-- | Override a record with fields from a possibly narrower record. A+-- typical use is to supply default values as the first argument, and+-- overrides for those defaults as the second.+withDefaults :: (RMap rs, RApply rs, ss ⊆ rs, RMap ss, RecApplicative rs)+ => Rec f rs -> Rec f ss -> Rec f rs+withDefaults defs = fin . rtraverse getCompose . flip rfirst defs' . rdowncast+ where fin = maybe (error "Impossible: withDefaults failed") id+ defs' = rmap (Compose . Just) defs+ rfirst = rcombine (<>) (onCompose First) (onCompose getFirst)
Data/Vinyl/Functor.hs view
@@ -1,26 +1,71 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} -module Data.Vinyl.Functor where+module Data.Vinyl.Functor+ ( -- * Introduction+ -- $introduction+ -- * Data Types+ Identity(..)+ , Thunk(..)+ , Lift(..)+ , ElField(..)+ , Compose(..), onCompose+ , (:.)+ , Const(..)+ -- * Discussion -import Control.Applicative-import Data.Foldable-import Data.Traversable+ -- ** Example+ -- $example++ -- ** Ecosystem+ -- $ecosystem+ ) where++import Data.Proxy+#if __GLASGOW_HASKELL__ < 804+import Data.Semigroup+#endif+import Foreign.Ptr (castPtr) import Foreign.Storable+import GHC.Generics+import GHC.TypeLits+import GHC.Types (Type)+import Data.Vinyl.TypeLevel (Snd) +{- $introduction+ This module provides functors and functor compositions+ that can be used as the interpretation function for a+ 'Rec'. For a more full discussion of this, scroll down+ to the bottom.+-}++-- | This is identical to the "Identity" from "Data.Functor.Identity"+-- in "base" except for its 'Show' instance. newtype Identity a = Identity { getIdentity :: a } deriving ( Functor , Foldable , Traversable , Storable+ , Eq+ , Ord+ , Generic ) +-- | Used this instead of 'Identity' to make a record+-- lazy in its fields. data Thunk a = Thunk { getThunk :: a } deriving ( Functor@@ -33,9 +78,23 @@ newtype Compose (f :: l -> *) (g :: k -> l) (x :: k) = Compose { getCompose :: f (g x) }- deriving (Storable)+ deriving (Storable, Generic) +instance Semigroup (f (g a)) => Semigroup (Compose f g a) where+ Compose x <> Compose y = Compose (x <> y)++instance Monoid (f (g a)) => Monoid (Compose f g a) where+ mempty = Compose mempty+ mappend (Compose x) (Compose y) = Compose (mappend x y)++-- | Apply a function to a value whose type is the application of the+-- 'Compose' type constructor. This works under the 'Compose' newtype+-- wrapper.+onCompose :: (f (g a) -> h (k a)) -> (f :. g) a -> (h :. k) a+onCompose f = Compose . f . getCompose+ type f :. g = Compose f g+infixr 9 :. newtype Const (a :: *) (b :: k) = Const { getConst :: a }@@ -43,8 +102,80 @@ , Foldable , Traversable , Storable+ , Generic ) +-- | A value with a phantom 'Symbol' label. It is not a+-- Haskell 'Functor', but it is used in many of the same places a+-- 'Functor' is used in vinyl.+--+-- Morally: newtype ElField (s, t) = Field t+-- But GHC doesn't allow that+newtype ElField (t :: (Symbol, Type)) = Field (Snd t)++deriving instance Eq t => Eq (ElField '(s,t))+deriving instance Ord t => Ord (ElField '(s,t))++instance KnownSymbol s => Generic (ElField '(s,a)) where+ type Rep (ElField '(s,a)) = C1 ('MetaCons s 'PrefixI 'False) (Rec0 a)+ from (Field x) = M1 (K1 x)+ to (M1 (K1 x)) = Field x++instance (Num t, KnownSymbol s) => Num (ElField '(s,t)) where+ Field x + Field y = Field (x+y)+ Field x * Field y = Field (x*y)+ abs (Field x) = Field (abs x)+ signum (Field x) = Field (signum x)+ fromInteger = Field . fromInteger+ negate (Field x) = Field (negate x)++instance Semigroup t => Semigroup (ElField '(s,t)) where+ Field x <> Field y = Field (x <> y)++instance (KnownSymbol s, Monoid t) => Monoid (ElField '(s,t)) where+ mempty = Field mempty+ mappend (Field x) (Field y) = Field (mappend x y)++instance (Real t, KnownSymbol s) => Real (ElField '(s,t)) where+ toRational (Field x) = toRational x++instance (Fractional t, KnownSymbol s) => Fractional (ElField '(s,t)) where+ fromRational = Field . fromRational+ Field x / Field y = Field (x / y)++instance (Floating t, KnownSymbol s) => Floating (ElField '(s,t)) where+ pi = Field pi+ exp (Field x) = Field (exp x)+ log (Field x) = Field (log x)+ sin (Field x) = Field (sin x)+ cos (Field x) = Field (cos x)+ asin (Field x) = Field (asin x)+ acos (Field x) = Field (acos x)+ atan (Field x) = Field (atan x)+ sinh (Field x) = Field (sinh x)+ cosh (Field x) = Field (cosh x)+ asinh (Field x) = Field (asinh x)+ acosh (Field x) = Field (acosh x)+ atanh (Field x) = Field (atanh x)++instance (RealFrac t, KnownSymbol s) => RealFrac (ElField '(s,t)) where+ properFraction (Field x) = fmap Field (properFraction x)++instance (Show t, KnownSymbol s) => Show (ElField '(s,t)) where+ show (Field x) = symbolVal (Proxy::Proxy s) ++" :-> "++show x++instance forall s t. (KnownSymbol s, Storable t)+ => Storable (ElField '(s,t)) where+ sizeOf _ = sizeOf (undefined::t)+ alignment _ = alignment (undefined::t)+ peek ptr = Field `fmap` peek (castPtr ptr)+ poke ptr (Field x) = poke (castPtr ptr) x+instance Show a => Show (Const a b) where+ show (Const x) = "(Const "++show x ++")"++instance Eq a => Eq (Const a b) where+ Const x == Const y = x == y+ instance (Functor f, Functor g) => Functor (Compose f g) where fmap f (Compose x) = Compose (fmap (fmap f) x) @@ -58,6 +189,9 @@ pure x = Compose (pure (pure x)) Compose f <*> Compose x = Compose ((<*>) <$> f <*> x) +instance Show (f (g a)) => Show (Compose f g a) where+ show (Compose x) = show x+ instance Applicative Identity where pure = Identity Identity f <*> Identity x = Identity (f x)@@ -91,3 +225,99 @@ pure x = Lift (pure x, pure x) Lift (f, g) <*> Lift (x, y) = Lift (f <*> x, g <*> y) +-- $setup+-- >>> import Data.Vinyl.Core+-- >>> :set -XDataKinds+--++{- $example+ The data types in this module are used to build interpretation+ fuctions for a 'Rec'. To build a 'Rec' that is simply a heterogeneous+ list, use 'Identity':++>>> :{+let myRec1 :: Rec Identity '[Int,Bool,Char]+ myRec1 = Identity 4 :& Identity True :& Identity 'c' :& RNil+:}++ For a record in which the fields are optional, you could alternatively+ write:++>>> :{+let myRec2 :: Rec Maybe '[Int,Bool,Char]+ myRec2 = Just 4 :& Nothing :& Nothing :& RNil+:}++ And we can gather all of the effects with 'rtraverse':++>>> let r2 = rtraverse (fmap Identity) myRec2+>>> :t r2+r2 :: Maybe (Rec Identity '[Int, Bool, Char])+>>> r2+Nothing++ If the fields only exist once an environment is provided, you can+ build the record as follows:++>>> :{+let myRec3 :: Rec ((->) Int) '[Int,Bool,Char]+ myRec3 = (+5) :& (const True) :& (head . show) :& RNil+:}++ And again, we can collect these effects with "rtraverse":++>>> (rtraverse (fmap Identity) myRec3) 8+{13, True, '8'}++ If you want the composition of these two effects, you can use "Compose":++>>> import Data.Char (chr)+>>> :{+let safeDiv a b = if b == 0 then Nothing else Just (div a b)+ safeChr i = if i >= 32 && i <= 126 then Just (chr i) else Nothing+ myRec4 :: Rec (Compose ((->) Int) Maybe) '[Int,Char]+ myRec4 = (Compose $ safeDiv 42) :& (Compose safeChr) :& RNil+:}++-}++{- $ecosystem+ Of the five data types provided by this modules, three can+ be found in others places: "Identity", "Compose", and "Const".+ They are included with "vinyl" to help keep the dependency+ list small. The differences will be discussed here.++ The "Data.Functor.Identity" module was originally provided+ by "transformers". When GHC 7.10 was released, it was moved+ into "base-4.8". The "Identity" data type provided by that+ module is well recognized across the haskell ecosystem+ and has typeclass instances for lots of common typeclasses.+ The significant difference between it and the copy of+ it provided here is that this one has a different 'Show'+ instance. This is illustrated below:++>>> Identity "hello"+"hello"++ But, when using "Identity" from "base":++>>> import qualified Data.Functor.Identity as Base+>>> Base.Identity "hello"+Identity "hello"++ This 'Show' instance makes records look nicer in GHCi.+ Feel free to use "Data.Functor.Identity" if you do not+ need the prettier output or if you need the many additional+ typeclass instances that are provided for the standard+ "Identity".++ The story with "Compose" and "Const" is much more simple.+ These also exist in "transformers", although "Const"+ is named "Constant" there. Prior to the release of+ "transformers-0.5", they were not polykinded, making+ them unusable for certain universes. However, in+ "transformers-0.5" and forward, they have been made+ polykinded. This means that they are just as usable with 'Rec'+ as the vinyl equivalents but with many more typeclass+ instances such as 'Ord' and 'Show'.+-}
Data/Vinyl/Lens.hs view
@@ -1,3 +1,7 @@+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-}@@ -7,10 +11,20 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ < 806+{-# LANGUAGE TypeInType #-}+#endif +-- | Lenses into record fields. module Data.Vinyl.Lens- ( RElem(..)- , RSubset(..)+ ( RecElem(..)+ , rget, rput, rput', rlens, rlens'+ , RElem+ , RecSubset(..)+ , rsubset, rcast, rreplace+ , rdowncast+ , RSubset , REquivalent , type (∈) , type (⊆)@@ -19,105 +33,197 @@ , type (:~:) ) where +import Data.Kind (Constraint) import Data.Vinyl.Core import Data.Vinyl.Functor import Data.Vinyl.TypeLevel-import Data.Typeable (Proxy(..))+#if __GLASGOW_HASKELL__ < 806+import Data.Kind+#endif --- | The presence of a field in a record is witnessed by a lens into its value.--- The third parameter to 'RElem', @i@, is there to help the constraint solver--- realize that this is a decidable predicate with respect to the judgemental--- equality in @k@.-class i ~ RIndex r rs => RElem (r :: k) (rs :: [k]) (i :: Nat) where+-- | The presence of a field in a record is witnessed by a lens into+-- its value. The fifth parameter to 'RecElem', @i@, is there to help+-- the constraint solver realize that this is a decidable predicate+-- with respect to the judgemental equality in @k@.+class (i ~ RIndex r rs, NatToInt i)+ => RecElem (record :: (k -> *) -> [k] -> *) (r :: k) (r' :: k) (rs :: [k]) (rs' :: [k]) (i :: Nat) | r r' rs i -> rs' where+ -- | An opportunity for instances to generate constraints based on+ -- the functor parameter of records passed to class methods.+ type RecElemFCtx record (f :: k -> *) :: Constraint+ type RecElemFCtx record f = () -- | We can get a lens for getting and setting the value of a field which is -- in a record. As a convenience, we take a proxy argument to fix the -- particular field being viewed. These lenses are compatible with the @lens@ -- library. Morally: --- -- > rlens :: sing r => Lens' (Rec f rs) (f r)- rlens- :: Functor g- => sing r- -> (f r -> g (f r))- -> Rec f rs- -> g (Rec f rs)+ -- > rlensC :: Lens' (Rec f rs) (Rec f rs') (f r) (f r')+ rlensC+ :: (Functor g, RecElemFCtx record f)+ => (f r -> g (f r'))+ -> record f rs+ -> g (record f rs') -- | For Vinyl users who are not using the @lens@ package, we provide a getter.- rget- :: sing r- -> Rec f rs+ rgetC+ :: (RecElemFCtx record f, r ~ r')+ => record f rs -> f r- rget k = getConst . rlens k Const -- | For Vinyl users who are not using the @lens@ package, we also provide a -- setter. In general, it will be unambiguous what field is being written to, -- and so we do not take a proxy argument here.- rput- :: f r- -> Rec f rs- -> Rec f rs- rput y = getIdentity . rlens Proxy (\_ -> Identity y)+ rputC+ :: RecElemFCtx record f+ => f r'+ -> record f rs+ -> record f rs' +-- | 'RecElem' for classic vinyl 'Rec' types.+type RElem x rs = RecElem Rec x x rs rs+ -- This is an internal convenience stolen from the @lens@ library. lens :: Functor f- => (t -> s)- -> (t -> a -> b)- -> (s -> f a)- -> t- -> f b+ => (s -> a)+ -> (s -> b -> t)+ -> (a -> f b)+ -> s+ -> f t lens sa sbt afb s = fmap (sbt s) $ afb (sa s) {-# INLINE lens #-} -instance RElem r (r ': rs) Z where- rlens _ f (x :& xs) = fmap (:& xs) (f x)- {-# INLINE rlens #-}+instance RecElem Rec r r' (r ': rs) (r' ': rs) 'Z where+ rlensC f (x :& xs) = fmap (:& xs) (f x)+ {-# INLINE rlensC #-}+ rgetC = getConst . rlensC Const+ {-# INLINE rgetC #-}+ rputC y = getIdentity . rlensC @_ @_ @r (\_ -> Identity y)+ {-# INLINE rputC #-} -instance (RIndex r (s ': rs) ~ S i, RElem r rs i) => RElem r (s ': rs) (S i) where- rlens p f (x :& xs) = fmap (x :&) (rlens p f xs)- {-# INLINE rlens #-}+instance (RIndex r (s ': rs) ~ 'S i, RecElem Rec r r' rs rs' i)+ => RecElem Rec r r' (s ': rs) (s ': rs') ('S i) where+ rlensC f (x :& xs) = fmap (x :&) (rlensC f xs)+ {-# INLINE rlensC #-}+ rgetC = getConst . rlensC @_ @_ @r @r' Const+ {-# INLINE rgetC #-}+ rputC y = getIdentity . rlensC @_ @_ @r (\_ -> Identity y)+ {-# INLINE rputC #-} +-- | The 'rgetC' field getter with the type arguments re-ordered for+-- more convenient usage with @TypeApplications@.+rget :: forall r rs f record.+ (RecElem record r r rs rs (RIndex r rs), RecElemFCtx record f)+ => record f rs -> f r+rget = rgetC++-- | The type-changing field setter 'rputC' with the type arguments+-- re-ordered for more convenient usage with @TypeApplications@.+rput' :: forall k (r :: k) (r' :: k) (rs :: [k]) (rs' :: [k]) record f+ . (RecElem record r r' rs rs' (RIndex r rs), RecElemFCtx record f)+ => f r' -> record f rs -> record f rs'+rput' = rputC @k @record @r @r' @rs @rs'++-- | Type-preserving field setter. This type is simpler to work with+-- than that of 'rput''.+rput :: forall k (r :: k) rs record f. (RecElem record r r rs rs (RIndex r rs), RecElemFCtx record f)+ => f r -> record f rs -> record f rs+rput = rput' @_ @r @r @rs @rs @record++-- | Type-changing field lens 'rlensC' with the type arguments+-- re-ordered for more convenient usage with @TypeApplications@.+rlens' :: forall r r' record rs rs' f g.+ (RecElem record r r' rs rs' (RIndex r rs), RecElemFCtx record f, Functor g)+ => (f r -> g (f r')) -> record f rs -> g (record f rs')+rlens' = rlensC++-- | Type-preserving field lens. This type is simpler to work with+-- than that of 'rlens''.+rlens :: forall r record rs f g.+ (RecElem record r r rs rs (RIndex r rs), RecElemFCtx record f, Functor g)+ => (f r -> g (f r)) -> record f rs -> g (record f rs)+rlens = rlensC+ -- | If one field set is a subset another, then a lens of from the latter's -- record to the former's is evident. That is, we can either cast a larger -- record to a smaller one, or we may replace the values in a slice of a -- record.-class is ~ RImage rs ss => RSubset (rs :: [k]) (ss :: [k]) is where+class is ~ RImage rs ss => RecSubset record rs ss is where+ -- | An opportunity for instances to generate constraints based on+ -- the functor parameter of records passed to class methods.+ type RecSubsetFCtx record (f :: k -> *) :: Constraint+ type RecSubsetFCtx record f = () -- | This is a lens into a slice of the larger record. Morally, we have: -- -- > rsubset :: Lens' (Rec f ss) (Rec f rs)- rsubset- :: Functor g- => (Rec f rs -> g (Rec f rs))- -> Rec f ss- -> g (Rec f ss)+ rsubsetC+ :: (Functor g, RecSubsetFCtx record f)+ => (record f rs -> g (record f rs))+ -> record f ss+ -> g (record f ss) -- | The getter of the 'rsubset' lens is 'rcast', which takes a larger record -- to a smaller one by forgetting fields.- rcast- :: Rec f ss- -> Rec f rs- rcast = getConst . rsubset Const- {-# INLINE rcast #-}+ rcastC+ :: RecSubsetFCtx record f+ => record f ss+ -> record f rs+ rcastC = getConst . rsubsetC Const+ {-# INLINE rcastC #-} -- | The setter of the 'rsubset' lens is 'rreplace', which allows a slice of -- a record to be replaced with different values.- rreplace- :: Rec f rs- -> Rec f ss- -> Rec f ss- rreplace rs = getIdentity . rsubset (\_ -> Identity rs)- {-# INLINE rreplace #-}+ rreplaceC+ :: RecSubsetFCtx record f+ => record f rs+ -> record f ss+ -> record f ss+ rreplaceC rs = getIdentity . rsubsetC (\_ -> Identity rs)+ {-# INLINE rreplaceC #-} -instance RSubset '[] ss '[] where- rsubset = lens (const RNil) const+-- | A lens into a slice of the larger record. This is 'rsubsetC' with+-- the type arguments reordered for more convenient usage with+-- @TypeApplications@.+rsubset :: forall k rs ss f g record is.+ (RecSubset record (rs :: [k]) (ss :: [k]) is,+ Functor g, RecSubsetFCtx record f)+ => (record f rs -> g (record f rs)) -> record f ss -> g (record f ss)+rsubset = rsubsetC -instance (RElem r ss i , RSubset rs ss is) => RSubset (r ': rs) ss (i ': is) where- rsubset = lens (\ss -> rget Proxy ss :& rcast ss) set+-- | Takes a larger record to a smaller one by forgetting fields. This+-- is 'rcastC' with the type arguments reordered for more convenient+-- usage with @TypeApplications@.+rcast :: forall rs ss f record is.+ (RecSubset record rs ss is, RecSubsetFCtx record f)+ => record f ss -> record f rs+rcast = rcastC++-- | Allows a slice of a record to be replaced with different+-- values. This is 'rreplaceC' with the type arguments reordered for+-- more convenient usage with @TypeApplications@.+rreplace :: forall rs ss f record is.+ (RecSubset record rs ss is, RecSubsetFCtx record f)+ => record f rs -> record f ss -> record f ss+rreplace = rreplaceC++-- | Takes a smaller record to a larger one, a /downcast/, by layering a+-- 'Maybe' interpretation that lets us use 'Nothing' for the fields+-- not present in the smaller record.+rdowncast :: (RecApplicative ss, RMap rs, rs ⊆ ss)+ => Rec f rs -> Rec (Maybe :. f) ss+rdowncast = flip rreplace (rpure (Compose Nothing)) . rmap (Compose . Just)++type RSubset = RecSubset Rec++instance RecSubset Rec '[] ss '[] where+ rsubsetC = lens (const RNil) const++instance (RElem r ss i , RSubset rs ss is) => RecSubset Rec (r ': rs) ss (i ': is) where+ rsubsetC = lens (\ss -> rget ss :& rcastC ss) set where set :: Rec f ss -> Rec f (r ': rs) -> Rec f ss- set ss (r :& rs) = rput r $ rreplace rs ss+ set ss (r :& rs) = rput r $ rreplaceC rs ss -- | Two record types are equivalent when they are subtypes of each other. type REquivalent rs ss is js = (RSubset rs ss is, RSubset ss rs js)
+ Data/Vinyl/Recursive.hs view
@@ -0,0 +1,165 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ < 806+{-# LANGUAGE TypeInType #-}+#endif++-- | Recursive definitions of various core vinyl functions. These are+-- simple definitions that put less strain on the compiler. They are+-- expected to have slower run times, but faster compile times than+-- the definitions in "Data.Vinyl.Core".+module Data.Vinyl.Recursive where+#if __GLASGOW_HASKELL__ < 806+import Data.Kind+#endif+import Data.Proxy (Proxy(..))+import Data.Vinyl.Core (rpure, RecApplicative, Rec(..), Dict(..))+import Data.Vinyl.Functor (Compose(..), (:.), Lift(..), Const(..))+import Data.Vinyl.TypeLevel++-- | Two records may be pasted together.+rappend+ :: Rec f as+ -> Rec f bs+ -> Rec f (as ++ bs)+rappend RNil ys = ys+rappend (x :& xs) ys = x :& (xs `rappend` ys)++-- | A shorthand for 'rappend'.+(<+>)+ :: Rec f as+ -> Rec f bs+ -> Rec f (as ++ bs)+(<+>) = rappend++-- | 'Rec' @_ rs@ with labels in kind @u@ gives rise to a functor @Hask^u ->+-- Hask@; that is, a natural transformation between two interpretation functors+-- @f,g@ may be used to transport a value from 'Rec' @f rs@ to 'Rec' @g rs@.+rmap+ :: (forall x. f x -> g x)+ -> Rec f rs+ -> Rec g rs+rmap _ RNil = RNil+rmap η (x :& xs) = η x :& (η `rmap` xs)+{-# INLINE rmap #-}++-- | A shorthand for 'rmap'.+(<<$>>)+ :: (forall x. f x -> g x)+ -> Rec f rs+ -> Rec g rs+(<<$>>) = rmap+{-# INLINE (<<$>>) #-}++-- | An inverted shorthand for 'rmap'.+(<<&>>)+ :: Rec f rs+ -> (forall x. f x -> g x)+ -> Rec g rs+xs <<&>> f = rmap f xs+{-# INLINE (<<&>>) #-}++-- | A record of components @f r -> g r@ may be applied to a record of @f@ to+-- get a record of @g@.+rapply+ :: Rec (Lift (->) f g) rs+ -> Rec f rs+ -> Rec g rs+rapply RNil RNil = RNil+rapply (f :& fs) (x :& xs) = getLift f x :& (fs `rapply` xs)+{-# INLINE rapply #-}++-- | A shorthand for 'rapply'.+(<<*>>)+ :: Rec (Lift (->) f g) rs+ -> Rec f rs+ -> Rec g rs+(<<*>>) = rapply+{-# INLINE (<<*>>) #-}++-- | A record may be traversed with respect to its interpretation functor. This+-- can be used to yank (some or all) effects from the fields of the record to+-- the outside of the record.+rtraverse+ :: Applicative h+ => (forall x. f x -> h (g x))+ -> Rec f rs+ -> h (Rec g rs)+rtraverse _ RNil = pure RNil+rtraverse f (x :& xs) = (:&) <$> f x <*> rtraverse f xs+{-# INLINABLE rtraverse #-}++-- | Given a natural transformation from the product of @f@ and @g@ to @h@, we+-- have a natural transformation from the product of @'Rec' f@ and @'Rec' g@ to+-- @'Rec' h@. You can also think about this operation as zipping two records+-- with the same element types but different interpretations.+rzipWith+ :: (forall x . f x -> g x -> h x)+ -> (forall xs . Rec f xs -> Rec g xs -> Rec h xs)+rzipWith m = \r -> case r of+ RNil -> \RNil -> RNil+ (fa :& fas) -> \(ga :& gas) -> m fa ga :& rzipWith m fas gas++-- | Map each element of a record to a monoid and combine the results.+rfoldMap :: forall f m rs.+ Monoid m+ => (forall x. f x -> m)+ -> Rec f rs+ -> m+rfoldMap f = go mempty+ where+ go :: forall ss. m -> Rec f ss -> m+ go !m record = case record of+ RNil -> m+ r :& rs -> go (mappend m (f r)) rs+ {-# INLINABLE go #-}+{-# INLINE rfoldMap #-}++-- | A record with uniform fields may be turned into a list.+recordToList+ :: Rec (Const a) rs+ -> [a]+recordToList RNil = []+recordToList (x :& xs) = getConst x : recordToList xs+++-- | Sometimes we may know something for /all/ fields of a record, but when+-- you expect to be able to /each/ of the fields, you are then out of luck.+-- Surely given @∀x:u.φ(x)@ we should be able to recover @x:u ⊢ φ(x)@! Sadly,+-- the constraint solver is not quite smart enough to realize this and we must+-- make it patently obvious by reifying the constraint pointwise with proof.+reifyConstraint+ :: RecAll f rs c+ => proxy c+ -> Rec f rs+ -> Rec (Dict c :. f) rs+reifyConstraint prx rec =+ case rec of+ RNil -> RNil+ (x :& xs) -> Compose (Dict x) :& reifyConstraint prx xs++-- | Build a record whose elements are derived solely from a+-- constraint satisfied by each.+rpureConstrained :: forall u c (f :: u -> *) proxy ts.+ (AllConstrained c ts, RecApplicative ts)+ => proxy c -> (forall a. c a => f a) -> Rec f ts+rpureConstrained _ f = go (rpure Proxy)+ where go :: AllConstrained c ts' => Rec Proxy ts' -> Rec f ts'+ go RNil = RNil+ go (_ :& xs) = f :& go xs++-- | Build a record whose elements are derived solely from a+-- list of constraint constructors satisfied by each.+rpureConstraints :: forall cs (f :: * -> *) proxy ts. (AllAllSat cs ts, RecApplicative ts)+ => proxy cs -> (forall a. AllSatisfied cs a => f a) -> Rec f ts+rpureConstraints _ f = go (rpure Nothing)+ where go :: AllAllSat cs ts' => Rec Maybe ts' -> Rec f ts'+ go RNil = RNil+ go (_ :& xs) = f :& go xs
+ Data/Vinyl/SRec.hs view
@@ -0,0 +1,411 @@+-- | 'Storable' records offer an efficient flat, packed representation+-- in memory. In particular, field access is constant time (i.e. it+-- doesn't depend on where in the record the field is) and as fast as+-- possible, but updating fields may not be as efficient. The+-- requirement is that all fields of a record have 'Storable'+-- instances.+--+-- The implementation leaks into the usual vinyl lens API: the+-- requirement of 'Storable' instances necessitates specialization on+-- the functor argument of the record so that GHC can find all+-- required instances at compile time (this is required for+-- constant-time field access). What we do is allow ourselves to write+-- instances of the 'RecElem' and 'RecSubset' classes (that provide+-- the main vinyl lens API) that are restricted to particular choices+-- of the record functor. This is why the 'SRec2' type that implements+-- records here takes two functor arguments: they will usually be the+-- same; we fix one when writing instances and write instance contexts+-- that reference that type, and then require that the methods+-- (e.g. 'rget') are called on records whose functor argument is equal+-- to the one we picked. For usability, we provide an 'SRec' type+-- whose lens API is fixed to 'ElField' as the functor. Other+-- specializations are possible, and the work of those instances can+-- always be passed along to the 'SRec2' functions.+--+-- Note that the lens field accessors for 'SRec' do not support+-- changing the types of the fields as they do for 'Rec' and+-- 'ARec'.+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE UndecidableSuperClasses #-}+#if __GLASGOW_HASKELL__ < 806+{-# LANGUAGE TypeInType #-}+#endif++-- We get warnings about incomplete patterns on various class+-- instances.+{-# OPTIONS_GHC -Wno-incomplete-patterns #-}+module Data.Vinyl.SRec (+ -- * Main record lens API+ SRec(..), toSRec, fromSRec+ -- * Lens API specialized to 'SRec2'+ , sget, sput, slens+ , srecGetSubset, srecSetSubset+ -- * Internals+ , toSRec2, fromSRec2, SRec2(..)+ , FieldOffset, FieldOffsetAux(..), StorableAt(..)+ , peekField, pokeField+) where+import Data.Coerce (coerce)+#if __GLASGOW_HASKELL__ < 806+import Data.Kind+#endif+import Data.Vinyl.Core+import Data.Vinyl.Functor (Lift(..), Compose(..), type (:.), ElField)+import Data.Vinyl.Lens (RecElem(..), RecSubset(..), type (⊆), RecElemFCtx)+import Data.Vinyl.TypeLevel (NatToInt, RImage, RIndex, Nat(..), RecAll, AllConstrained)+import Foreign.Marshal.Utils (copyBytes)+import Foreign.Ptr (Ptr)+import Foreign.Storable (Storable(..))+import System.IO.Unsafe (unsafePerformIO, unsafeDupablePerformIO)+#if __GLASGOW_HASKELL__ >= 900+import Unsafe.Coerce (unsafeCoerce#)+import GHC.Prim (touch#, RealWorld)+#else+import GHC.Prim (touch#, unsafeCoerce#, RealWorld)+#endif++import GHC.IO (IO(IO))+import GHC.Base (realWorld#)+import GHC.TypeLits (Symbol)+import GHC.Prim (MutableByteArray#, newAlignedPinnedByteArray#, byteArrayContents#)+import GHC.Ptr (Ptr(..))+import GHC.Types (Int(..))++-- * Byte array code adapted from the `memory` package.++data Bytes = Bytes (MutableByteArray# RealWorld)++newBytes :: Int -> IO Bytes+newBytes (I# n) = IO $ \s ->+ case newAlignedPinnedByteArray# n 8# s of+ (# s', mbarr #) -> (# s', Bytes mbarr #)++touchBytes :: Bytes -> IO ()+touchBytes (Bytes mbarr) = IO $ \s -> case touch# mbarr s of s' -> (# s', () #)+{-# INLINE touchBytes #-}++withBytesPtr :: Bytes -> (Ptr a -> IO r) -> IO r+withBytesPtr b@(Bytes mbarr) f = do+ f (Ptr (byteArrayContents# (unsafeCoerce# mbarr))) <* touchBytes b+{-# INLINE withBytesPtr #-}++-- * Pun ForeignPtr names to ease refactoring++newtype ForeignPtr (a :: k) = ForeignPtr Bytes++withForeignPtr :: ForeignPtr a -> (Ptr b -> IO r) -> IO r+withForeignPtr (ForeignPtr b) = withBytesPtr b+{-# INLINE withForeignPtr #-}++mallocForeignPtrBytes :: Int -> IO (ForeignPtr a)+mallocForeignPtrBytes = fmap ForeignPtr . newBytes+{-# INLINE mallocForeignPtrBytes #-}++-- * The SRec types++-- | A 'Storable'-backed 'Rec'. Each field of such a value has+-- statically known size, allowing for a very efficient representation+-- and very fast field access. The @2@ suffix is due to apparently+-- taking /two/ functor arguments, but the first type parameter is+-- phantom and exists so that we can write multiple instances of+-- 'RecElem' and 'RecSubset' for different functors. The first functor+-- argument will typically be identical to the second argument. We+-- currently provide instances for the 'ElField' functor; if you wish+-- to use it at a different type, consider using 'sget', 'sput', and+-- 'slens' which work with any functor given that the necessary+-- 'Storable' instances exist.+newtype SRec2 (g :: k -> *) (f :: k -> *) (ts :: [k]) =+ SRec2 (ForeignPtr (Rec f ts))++-- | A simpler type for 'SRec2' whose 'RecElem' and 'RecSubset'+-- instances are specialized to the 'ElField' functor.+newtype SRec f ts = SRecNT { getSRecNT :: SRec2 f f ts }++-- | Create an 'SRec2' from a 'Rec'.+toSRec2 :: forall f ts. Storable (Rec f ts) => Rec f ts -> SRec2 f f ts+toSRec2 x = unsafePerformIO $ do+ ptr <- mallocForeignPtrBytes (sizeOf (undefined :: Rec f ts))+ SRec2 ptr <$ (withForeignPtr ptr (flip poke x))+{-# NOINLINE toSRec2 #-}++-- | Create an 'SRec' from a 'Rec'. This should offer very fast field+-- access, but note that its lens API (via 'RecElem' and 'RecSubset')+-- is restricted to the 'ElField' functor.+toSRec :: Storable (Rec f ts) => Rec f ts -> SRec f ts+toSRec = SRecNT . toSRec2+{-# INLINE toSRec #-}++-- | Create a 'Rec' from an 'SRec2'.+fromSRec2 :: Storable (Rec f ts) => SRec2 g f ts -> Rec f ts+fromSRec2 (SRec2 ptr) = inlinePerformIO (withForeignPtr ptr peek)+{-# INLINE fromSRec2 #-}++-- | Create a 'Rec' from an 'SRec'.+fromSRec :: Storable (Rec f ts) => SRec f ts -> Rec f ts+fromSRec (SRecNT s) = fromSRec2 s+{-# INLINE fromSRec #-}++-- | Just like unsafePerformIO, but we inline it. Big performance gains as+-- it exposes lots of things to further inlining. /Very unsafe/. In+-- particular, you should do no memory allocation inside an+-- 'inlinePerformIO' block. On Hugs this is just @unsafePerformIO@.+--+-- Copied from the @text@ package+{-# INLINE inlinePerformIO #-}+inlinePerformIO :: IO a -> a+inlinePerformIO (IO m) = case m realWorld# of (# _, r #) -> r++-- | Capture a 'Storable' dictionary along with a byte offset from+-- some origin address.+data StorableAt f a where+ StorableAt :: Storable (f a) => {-# UNPACK #-} !Int -> StorableAt f a++-- | The ability to work with a particular field of a 'Rec' stored at+-- a 'Ptr'.+class (RIndex t ts ~ i, RecAll f ts Storable) => FieldOffsetAux f ts t i where+ -- | Get the byte offset of a field from the given origin and the+ -- 'Storable' dictionary needed to work with that field.+ fieldOffset :: Int -> StorableAt f t++-- | A more concise constraint equivalent to 'FieldOffsetAux'.+class FieldOffsetAux f ts t (RIndex t ts) => FieldOffset f ts t where+instance FieldOffsetAux f ts t (RIndex t ts) => FieldOffset f ts t where++instance (RecAll f (t ': ts) Storable) => FieldOffsetAux f (t ': ts) t 'Z where+ fieldOffset !n = StorableAt n+ {-# INLINE fieldOffset #-}++instance (RIndex t (s ': ts) ~ 'S i,+ FieldOffsetAux f ts t i,+ RecAll f (s ': ts) Storable)+ => FieldOffsetAux f (s ': ts) t ('S i) where+ fieldOffset !n = fieldOffset @f @ts @t @i (n + sizeOf (undefined :: f s))+ {-# INLINE fieldOffset #-}++-- | Set a field in a record stored at a 'ForeignPtr'.+pokeField :: forall f t ts. FieldOffset f ts t+ => ForeignPtr (Rec f ts) -> f t -> IO ()+pokeField fptr x = case fieldOffset @f @ts @t 0 of+ StorableAt i -> withForeignPtr fptr $ \ptr ->+ pokeByteOff ptr i x+{-# INLINE pokeField #-}++-- | Get a field in a record stored at a 'ForeignPtr'.+peekField :: forall f t ts. FieldOffset f ts t+ => ForeignPtr (Rec f ts) -> IO (f t)+peekField fptr = case fieldOffset @f @ts @t 0 of+ StorableAt i -> withForeignPtr fptr $ \ptr ->+ peekByteOff ptr i+{-# INLINE peekField #-}++-- | Get a field from an 'SRec'.+sget :: forall f t ts. FieldOffset f ts t+ => SRec2 f f ts -> f t+sget (SRec2 ptr) = inlinePerformIO (peekField ptr)+{-# INLINE sget #-}++mallocAndCopy :: ForeignPtr a -> Int -> IO (ForeignPtr a)+mallocAndCopy src n = do+ dst <- mallocForeignPtrBytes n+ withForeignPtr src $ \src' ->+ withForeignPtr dst $ \dst' ->+ dst <$ copyBytes dst' src' n++-- | Set a field.+sput :: forall u (f :: u -> *) (t :: u) (ts :: [u]).+ ( FieldOffset f ts t+ , Storable (Rec f ts)+ , AllConstrained (FieldOffset f ts) ts)+ => f t -> SRec2 f f ts -> SRec2 f f ts+sput !x (SRec2 src) = unsafePerformIO $ do+ let !n = sizeOf (undefined :: Rec f ts)+ dst <- mallocAndCopy src n+ SRec2 dst <$ pokeField dst x+{-# INLINE [1] sput #-}++pokeFieldUnsafe :: forall f t ts. FieldOffset f ts t+ => f t -> SRec2 f f ts -> SRec2 f f ts+pokeFieldUnsafe x y@(SRec2 ptr) = unsafeDupablePerformIO (y <$ pokeField ptr x)+{-# INLINE [1] pokeFieldUnsafe #-}++{-# RULES+"sput" forall x y z. sput x (sput y z) = pokeFieldUnsafe x (sput y z)+"sputUnsafe" forall x y z. sput x (pokeFieldUnsafe y z) = pokeFieldUnsafe x (pokeFieldUnsafe y z)+ #-}++-- | A lens for a field of an 'SRec2'.+slens :: ( Functor g+ , FieldOffset f ts t+ , Storable (Rec f ts)+ , AllConstrained (FieldOffset f ts) ts)+ => (f t -> g (f t)) -> SRec2 f f ts -> g (SRec2 f f ts)+slens f sr = fmap (flip sput sr) (f (sget sr))+{-# INLINE slens #-}++-- Note: we need the functor to appear in the instance head so that we+-- can demand the needed 'Storable' instances. We do this by giving+-- 'SRec2' a phantom tag that duplicates the "real" functor parameter,+-- and define a constraint that the real argument is in fact+-- 'ElField'. This lets us write instances for different applications+-- of @SRec2@ (e.g. instance for @SRec2 Foo@ for records of type+-- @SRec2 Foo Foo ts@, and an instance for @SRec2 Bar@ for records of+-- type @SRec2 Bar Bar ts@).++-- | Field accessors for 'SRec2' specialized to 'ElField' as the+-- functor.+instance ( i ~ RIndex t ts+ , NatToInt i+ , FieldOffset ElField ts t+ , Storable (Rec ElField ts)+ , AllConstrained (FieldOffset ElField ts) ts)+ => RecElem (SRec2 ElField) t t ts ts i where+ type RecElemFCtx (SRec2 ElField) f = f ~ ElField+ rlensC = slens+ {-# INLINE rlensC #-}+ rgetC = sget+ {-# INLINE rgetC #-}+ rputC = sput+ {-# INLINE rputC #-}+++coerceSRec1to2 :: SRec f ts -> SRec2 f f ts+coerceSRec1to2 = coerce++coerceSRec2to1 :: SRec2 f f ts -> SRec f ts+coerceSRec2to1 = coerce++instance ( i ~ RIndex (t :: (Symbol,*)) (ts :: [(Symbol,*)])+ , NatToInt i+ , FieldOffset ElField ts t+ , Storable (Rec ElField ts)+ , AllConstrained (FieldOffset ElField ts) ts)+ => RecElem SRec (t :: (Symbol,*)) t (ts :: [(Symbol,*)]) ts i where+ type RecElemFCtx SRec f = f ~ ElField+ rlensC f = fmap coerceSRec2to1 . slens f . coerceSRec1to2+ {-# INLINE rlensC #-}+ rgetC = sget . coerceSRec1to2+ {-# INLINE rgetC #-}+ rputC x = coerceSRec2to1 . sput x . coerceSRec1to2+ {-# INLINE rputC #-}++-- | Get a subset of a record's fields.+srecGetSubset :: forall u (ss :: [u]) (rs :: [u]) (f :: u -> *).+ (RPureConstrained (FieldOffset f ss) rs,+ RPureConstrained (FieldOffset f rs) rs,+ RFoldMap rs, RMap rs, RApply rs,+ Storable (Rec f rs))+ => SRec2 f f ss -> SRec2 f f rs+srecGetSubset (SRec2 ptr) = unsafeDupablePerformIO $ do+ dst <- mallocForeignPtrBytes (sizeOf (undefined :: Rec f rs))+ SRec2 dst <$ (withForeignPtr dst $ \dst' ->+ rfoldMap @rs unTagIO (peekSmallPokeBig dst'))+ where peekers :: Rec (IO :. f) rs+ peekers = rpureConstrained @(FieldOffset f ss) mkPeeker+ {-# INLINE peekers #-}+ mkPeeker :: FieldOffset f ss t => (IO :. f) t+ mkPeeker = Compose (peekField ptr)+ {-# INLINE mkPeeker #-}+ pokers :: Ptr (Rec f rs) -> Rec (Poker f) rs+ pokers dst = rpureConstrained @(FieldOffset f rs) (mkPoker dst)+ {-# INLINE pokers #-}+ mkPoker :: forall t. Ptr (Rec f rs) -> FieldOffset f rs t => Poker f t+ mkPoker dst = case fieldOffset @f @rs @t 0 of+ StorableAt i -> Lift (TaggedIO . pokeByteOff dst i)+ {-# INLINE mkPoker #-}+ peekNPoke :: (IO :. f) t -> Poker f t -> TaggedIO t+ peekNPoke (Compose m) (Lift f) = TaggedIO (m >>= unTagIO . f)+ {-# INLINE peekNPoke #-}+ peekSmallPokeBig :: Ptr (Rec f rs) -> Rec TaggedIO rs+ peekSmallPokeBig dst' = Lift . peekNPoke <<$>> peekers <<*>> pokers dst'+{-# INLINE srecGetSubset #-}++-- | Phantom tagged 'IO ()' value. Used to work with vinyl's 'Lift'+-- that wants @forall a. f a -> g a@.+newtype TaggedIO a = TaggedIO { unTagIO :: IO () }++-- | A dressed up function of type @f a -> IO ()@+type Poker f = Lift (->) f TaggedIO++-- | Set a subset of a record's fields.+srecSetSubset :: forall u (f :: u -> *) (ss :: [u]) (rs :: [u]).+ (rs ⊆ ss,+ RPureConstrained (FieldOffset f ss) rs,+ RPureConstrained (FieldOffset f rs) rs,+ RFoldMap rs, RMap rs, RApply rs,+ Storable (Rec f ss))+ => SRec2 f f ss -> SRec2 f f rs -> SRec2 f f ss+srecSetSubset (SRec2 srcBig) (SRec2 srcSmall) = unsafeDupablePerformIO $ do+ let n = sizeOf (undefined :: Rec f ss)+ dst <- mallocForeignPtrBytes n+ withForeignPtr srcBig $ \srcBig' ->+ withForeignPtr dst $ \dst' ->+ copyBytes dst' srcBig' n+ SRec2 dst <$ (withForeignPtr dst $ \dst' ->+ rfoldMap @rs unTagIO+ (Lift . peekNPoke <<$>> peekers <<*>> pokers dst'))+ where peekers :: Rec (IO :. f) rs+ peekers = rpureConstrained @(FieldOffset f rs) mkPeeker+ {-# INLINE peekers #-}+ mkPeeker :: FieldOffset f rs t => (IO :. f) t+ mkPeeker = Compose (peekField srcSmall)++ pokers :: Ptr (Rec f ss) -> Rec (Poker f) rs+ pokers dst = rpureConstrained @(FieldOffset f ss) (mkPoker dst)+ {-# INLINE pokers #-}+ mkPoker :: forall t. FieldOffset f ss t => Ptr (Rec f ss) -> Poker f t+ mkPoker dst = case fieldOffset @f @ss @t 0 of+ StorableAt i -> Lift (TaggedIO . pokeByteOff dst i)+ {-# INLINE mkPoker #-}+ peekNPoke :: (IO :. f) t -> Poker f t -> TaggedIO t+ peekNPoke (Compose m) (Lift f) = TaggedIO (m >>= unTagIO . f)+ {-# INLINE peekNPoke #-}+{-# INLINE srecSetSubset #-}++instance (is ~ RImage rs ss,+ RecSubset Rec rs ss is,+ Storable (Rec ElField rs),+ Storable (Rec ElField ss),+ RPureConstrained (FieldOffset ElField ss) rs,+ RPureConstrained (FieldOffset ElField rs) rs,+ RFoldMap rs, RMap rs, RApply rs)+ => RecSubset (SRec2 ElField) rs ss is where+ type RecSubsetFCtx (SRec2 ElField) f = f ~ ElField+ rsubsetC :: forall g. Functor g+ => (SRec2 ElField ElField rs -> g (SRec2 ElField ElField rs))+ -> SRec2 ElField ElField ss+ -> g (SRec2 ElField ElField ss)+ rsubsetC f big@(SRec2 _) = fmap (srecSetSubset big) (f smallRec)+ where smallRec :: SRec2 ElField ElField rs+ smallRec = srecGetSubset big+ {-# INLINE smallRec #-}+ {-# INLINE rsubsetC #-}++instance (is ~ RImage rs ss,+ RecSubset Rec rs ss is,+ Storable (Rec ElField rs),+ Storable (Rec ElField ss),+ RPureConstrained (FieldOffset ElField ss) rs,+ RPureConstrained (FieldOffset ElField rs) rs,+ RFoldMap rs, RMap rs, RApply rs)+ => RecSubset SRec rs ss is where+ type RecSubsetFCtx SRec f = f ~ ElField+ rsubsetC f (SRecNT s) = SRecNT <$> rsubsetC (fmap getSRecNT . f . SRecNT) s+ {-# INLINE rsubsetC #-}
+ Data/Vinyl/Syntax.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE CPP, FlexibleContexts, FlexibleInstances, InstanceSigs,+ MultiParamTypeClasses, ScopedTypeVariables,+ TypeApplications, TypeFamilies, TypeOperators,+ UndecidableInstances #-}+{-# OPTIONS_GHC -Wno-orphans #-}+-- | Concise vinyl record field lens syntax. This module exports an+-- orphan instance to make working with labels a bit more powerful. It+-- will conflict with other libraries that provide special syntax for+-- labels (i.e. placing a label in function application position, as+-- in @#age 23@, or using a label as a lens).+--+-- Example:+-- @fieldRec (#x =: True, #y =: 'b') :: FieldRec '[ '("x", Bool), '("y", Char) ]@+-- @fieldRec (#x =: True, #y =: 'b') & #x %~ not@+module Data.Vinyl.Syntax where+import Data.Vinyl.Derived (HasField, (:::), rfield)+import Data.Vinyl.Functor (ElField)+import Data.Vinyl.Lens (RecElemFCtx, rlens')+import GHC.OverloadedLabels (IsLabel(..))+-- import GHC.TypeLits (KnownSymbol)++-- | Concise record construction syntax. Example: @record (#name "Joe", #age 23)@.+-- instance forall s a b. (KnownSymbol s, b ~ ElField '(s,a))+-- => IsLabel s (a -> b) where+-- #if __GLASGOW_HASKELL__ < 802+-- fromLabel _ = Field @s @a+-- #else+-- fromLabel = Field @s @a+-- #endif++-- | Concise 'ElField' lenses. Example @myRec & #name %~ map+-- toUpper@.+--+-- Credit to Tikhon Jelvis who shared this technique on the+-- Haskell-Cafe mailing list on December 23, 2017.+instance forall s t t' ts ts' f record a' b'.+ (HasField record s ts ts' t t', Functor f, RecElemFCtx record ElField,+ a' ~ (t -> f t'), b' ~ (record ElField ts -> f (record ElField ts')))+ => IsLabel s (a' -> b') where+#if __GLASGOW_HASKELL__ < 802+ fromLabel _ = rlens' @(s ::: t) . rfield+#else+ fromLabel :: (t -> f t') -> (record ElField ts -> f (record ElField ts'))+ fromLabel = rlens' @(s ::: t) . rfield+#endif
+ Data/Vinyl/Tutorial/Overview.hs view
@@ -0,0 +1,297 @@+{-|++ Vinyl is a general solution to the records problem in Haskell using+ type level strings and other modern GHC features, featuring static+ structural typing (with a subtyping relation), and automatic+ row-polymorphic lenses. All this is possible without Template Haskell.++ Let's work through a quick example. We'll need to enable some language+ extensions first:++>>> :set -XDataKinds+>>> :set -XPolyKinds+>>> :set -XTypeApplications+>>> :set -XTypeOperators+>>> :set -XTypeFamilies+>>> :set -XFlexibleContexts+>>> :set -XFlexibleInstances+>>> :set -XNoMonomorphismRestriction+>>> :set -XGADTs+>>> :set -XTypeSynonymInstances+>>> :set -XTemplateHaskell+>>> :set -XStandaloneDeriving++>>> import Data.Vinyl+>>> import Data.Vinyl.Functor+>>> import Control.Applicative+>>> import Control.Lens hiding (Identity)+>>> import Control.Lens.TH+>>> import Data.Char+>>> import Test.DocTest+>>> import Data.Singletons.TH (genSingletons)+>>> import Data.Maybe++ Let's define a universe of fields which we want to use.++ First of all, we need a data type defining the field labels:++>>> data Fields = Name | Age | Sleeping | Master deriving Show++ Any record can be now described by a type-level list of these labels.+ The @DataKinds@ extension must be enabled to automatically turn all the+ constructors of the @Field@ type into types.++>>> type LifeForm = [Name, Age, Sleeping]++ Now, we need a way to map our labels to concrete types. We use a type+ family for this purpose. Unfortunately, type families aren't first class in Haskell. That's+ why we also need a data type, with which we will parametrise 'Rec'.+ We also generate the necessary singletons for each field label using+ Template Haskell.++>>> :{+type family ElF (f :: Fields) :: * where+ ElF Name = String+ ElF Age = Int+ ElF Sleeping = Bool+ ElF Master = Rec Attr LifeForm+newtype Attr f = Attr { _unAttr :: ElF f }+makeLenses ''Attr+genSingletons [ ''Fields ]+instance Show (Attr Name) where show (Attr x) = "name: " ++ show x+instance Show (Attr Age) where show (Attr x) = "age: " ++ show x+instance Show (Attr Sleeping) where show (Attr x) = "sleeping: " ++ show x+instance Show (Attr Master) where show (Attr x) = "master: " ++ show x+:}++ To make field construction easier, we define an operator. The first+ argument of this operator is a singleton - a constructor bringing the+ data-kinded field label type into the data level. It's needed because+ there can be multiple labels with the same field type, so by just+ supplying a value of type @ElF f@ there would be no way to deduce the+ correct "f".++>>> :{+let (=::) :: sing f -> ElF f -> Attr f+ _ =:: x = Attr x+:}++ Now, let's try to make an entity that represents a human:++>>> :{+let jon = (SName =:: "jon")+ :& (SAge =:: 23)+ :& (SSleeping =:: False)+ :& RNil+:}++ Automatically, we can show the record:++>>> print jon+{name: "jon", age: 23, sleeping: False}++And its types are all inferred with no problem. Now, make a dog! Dogs+are life-forms, but unlike humans, they have masters. So, let’s build+my dog:++>>> :{+let tucker = (SName =:: "tucker")+ :& (SAge =:: 9)+ :& (SSleeping =:: True)+ :& (SMaster =:: jon)+ :& RNil+:}++Now, if we want to wake entities up, we don\'t want to have to write a+separate wake-up function for both dogs and humans (even though they+are of different type). Luckily, we can use the built-in lenses to+focus on a particular field in the record for access and update,+without losing additional information:++>>> :{+let wakeUp :: (Sleeping ∈ fields) => Rec Attr fields -> Rec Attr fields+ wakeUp = rput $ SSleeping =:: False+:}++Now, the type annotation on @wakeUp@ was not necessary; I just wanted+to show how intuitive the type is. Basically, it takes as an input+any record that has a 'Bool' field labelled @sleeping@, and modifies+that specific field in the record accordingly.++>>> let tucker' = wakeUp tucker+>>> let jon' = wakeUp jon++>>> tucker' ^. rlens @Sleeping+sleeping: False++>>> tucker ^. rlens @Sleeping+sleeping: True++>>> jon' ^. rlens @Sleeping+sleeping: False++We can also access the entire lens for a field using the rLens+function; since lenses are composable, it’s super easy to do deep+update on a record:++>>> let masterSleeping = rlens @Master . unAttr . rlens @Sleeping+>>> let tucker'' = masterSleeping .~ (SSleeping =:: True) $ tucker'++>>> tucker'' ^. masterSleeping+sleeping: True++A record @Rec f xs@ is a subtype of a record @Rec f ys@ if @ys ⊆ xs@;+that is to say, if one record can do everything that another record+can, the former is a subtype of the latter. As such, we should be able+to provide an upcast operator which "forgets" whatever makes one+record different from another (whether it be extra data, or different+order).++Therefore, the following works:++>>> :{+let upcastedTucker :: Rec Attr LifeForm+ upcastedTucker = rcast tucker+:}++The subtyping relationship between record types is expressed with the+'<:' constraint; so, 'rcast' is of the following type:++> rcast :: r1 <: r2 => Rec f r2 -> Rec f r1++Also provided is a "≅" constraint which indicates record congruence+(that is, two record types differ only in the order of their fields).++In fact, 'rcast' is actually given as a special case of the lens 'rsubset',+which lets you modify entire (possibly non-contiguous) slices of a record!++Consider the following declaration:++> data Rec :: (u -> *) -> [u] -> * where+> RNil :: Rec f '[]+> (:&) :: f r -> Rec f rs -> Rec f (r ': rs)++Records are implicitly parameterized over a kind @u@, which stands for the+"universe" or key space. Keys (inhabitants of @u@) are then interpreted into+the types of their values by the first parameter to 'Rec', @f@. An extremely+powerful aspect of Vinyl records is that you can construct natural+transformations between different interpretation functors @f,g@, or postcompose+some other functor onto the stack. This can be used to immerse each field of a+record in some particular effect modality, and then the library functions can+be used to traverse and accumulate these effects.++Let\'s imagine that we want to do validation on a record that+represents a name and an age:++>>> type Person = [Name, Age]++We\'ve decided that names must be alphabetic, and ages must be positive. For+validation, we\'ll use 'Maybe' for now, though you should use a+left-accumulating @Validation@ type (the module @Data.Either.Validation@+from the @either@ package provides such a type, though we do not+cover it here).++>>> :{+let goodPerson :: Rec Attr Person+ goodPerson = (SName =:: "Jon")+ :& (SAge =:: 20)+ :& RNil+:}++>>> :{+let badPerson = (SName =:: "J#@#$on")+ :& (SAge =:: 20)+ :& RNil+:}++We\'ll give validation a (rather poor) shot.++>>> :{+let+ validatePerson :: Rec Attr Person -> Maybe (Rec Attr Person)+ validatePerson p = (\n a -> (SName =:: n) :& (SAge =:: a) :& RNil) <$> vName <*> vAge+ where+ vName = validateName $ p ^. rlens @Name . unAttr+ vAge = validateAge $ p ^. rlens @Age . unAttr+ validateName str | all isAlpha str = Just str+ validateName _ = Nothing+ validateAge i | i >= 0 = Just i+ validateAge _ = Nothing+:}++Let\'s try it out:++>>> isJust $ validatePerson goodPerson+True++>>> isJust $ validatePerson badPerson+False++The results are as expected (@Just@ for @goodPerson@, and a @Nothing@ for+@badPerson@); but this was not very fun to build.++Further, it would be nice to have some notion of a partial record;+that is, if part of it can\'t be validated, it would still be nice to+be able to access the rest. What if we could make a version of this+record where the elements themselves were validation functions, and+then that record could be applied to a plain one, to get a record of+validated fields? That\'s what we’re going to do.++>>> type Validator f = Lift (->) f (Maybe :. f)++Let\'s parameterize a record by it: when we do, then an element of type+@a@ should be a function @Identity a -> Result e a@:++>>> :{+let lift f = Lift $ Compose . f+ validateName (Attr str) | all isAlpha str = Just (Attr str)+ validateName _ = Nothing+ validateAge (Attr i) | i >= 0 = Just (Attr i)+ validateAge _ = Nothing+ vperson :: Rec (Validator Attr) Person+ vperson = lift validateName :& lift validateAge :& RNil+:}++And we can use the special application operator '<<*>>' (which is+analogous to '<*>', but generalized a bit) to use this to validate a+record:++>>> let goodPersonResult = vperson <<*>> goodPerson+>>> let badPersonResult = vperson <<*>> badPerson++>>> isJust . getCompose $ goodPersonResult ^. rlens @Name+True++>>> isJust . getCompose $ goodPersonResult ^. rlens @Age+True++>>> isJust . getCompose $ badPersonResult ^. rlens @Name+False++>>> isJust . getCompose $ badPersonResult ^. rlens @Age+True++So now we have a partial record, and we can still do stuff with its contents.+Next, we can even recover the original behavior of the validator (that is, to+give us a value of type @Maybe (Rec Attr Person)@) using `rtraverse`:++>>> :{+let mgoodPerson :: Maybe (Rec Attr Person)+ mgoodPerson = rtraverse getCompose goodPersonResult+:}++>>> let mbadPerson = rtraverse getCompose badPersonResult++>>> isJust mgoodPerson+True++>>> isJust mbadPerson+False++-}+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+module Data.Vinyl.Tutorial.Overview where++import Data.Vinyl.Core+import Data.Vinyl.Functor+import Data.Vinyl.Lens
Data/Vinyl/TypeLevel.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-}@@ -6,27 +7,77 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeFamilyDependencies #-} {-# LANGUAGE TypeOperators #-}+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ < 806+{-# LANGUAGE TypeInType #-}+#endif+#if __GLASGOW_HASKELL__ >= 810+{-# LANGUAGE UndecidableInstances #-}+#endif module Data.Vinyl.TypeLevel where -import GHC.Exts+import Data.Coerce+import Data.Kind -- | A mere approximation of the natural numbers. And their image as lifted by -- @-XDataKinds@ corresponds to the actual natural numbers. data Nat = Z | S !Nat +-- | Produce a runtime 'Int' value corresponding to a 'Nat' type.+class NatToInt (n :: Nat) where+ natToInt :: Int++instance NatToInt 'Z where+ natToInt = 0+ {-# INLINE natToInt #-}++instance NatToInt n => NatToInt ('S n) where+ natToInt = 1 + natToInt @n+ {-# INLINE natToInt #-}++-- | Reify a list of type-level natural number indices as runtime+-- 'Int's relying on instances of 'NatToInt'.+class IndexWitnesses (is :: [Nat]) where+ indexWitnesses :: [Int]++instance IndexWitnesses '[] where+ indexWitnesses = []+ {-# INLINE indexWitnesses #-}++instance (IndexWitnesses is, NatToInt i) => IndexWitnesses (i ': is) where+ indexWitnesses = natToInt @i : indexWitnesses @is+ {-# INLINE indexWitnesses #-}++-- | Project the first component of a type-level tuple.+type family Fst (a :: (k1,k2)) where Fst '(x,y) = x++-- | Project the second component of a type-level tuple.+type family Snd (a :: (k1,k2)) where Snd '(x,y) = y++type family RLength xs where+ RLength '[] = 'Z+ RLength (x ': xs) = 'S (RLength xs)+ -- | A partial relation that gives the index of a value in a list. type family RIndex (r :: k) (rs :: [k]) :: Nat where- RIndex r (r ': rs) = Z- RIndex r (s ': rs) = S (RIndex r rs)+ RIndex r (r ': rs) = 'Z+ RIndex r (s ': rs) = 'S (RIndex r rs) -- | A partial relation that gives the indices of a sublist in a larger list. type family RImage (rs :: [k]) (ss :: [k]) :: [Nat] where RImage '[] ss = '[] RImage (r ': rs) ss = RIndex r ss ': RImage rs ss +-- | Remove the first occurrence of a type from a type-level list.+type family RDelete r rs where+ RDelete r (r ': rs) = rs+ RDelete r (s ': rs) = s ': RDelete r rs+ -- | A constraint-former which applies to every field in a record. type family RecAll (f :: u -> *) (rs :: [u]) (c :: * -> Constraint) :: Constraint where RecAll f '[] c = ()@@ -37,3 +88,42 @@ '[] ++ bs = bs (a ': as) ++ bs = a ': (as ++ bs) +-- | Constraint that all types in a type-level list satisfy a+-- constraint.+type family AllConstrained (c :: u -> Constraint) (ts :: [u]) :: Constraint where+ AllConstrained c '[] = ()+ AllConstrained c (t ': ts) = (c t, AllConstrained c ts)++-- | Constraint that each Constraint in a type-level list is satisfied+-- by a particular type.+class AllSatisfied cs t where+instance AllSatisfied '[] t where+instance (c t, AllSatisfied cs t) => AllSatisfied (c ': cs) t where++-- | Constraint that all types in a type-level list satisfy each+-- constraint from a list of constraints.+--+-- @AllAllSat cs ts@ should be equivalent to @AllConstrained+-- (AllSatisfied cs) ts@ if partial application of type families were+-- legal.+type family AllAllSat cs ts :: Constraint where+ AllAllSat cs '[] = ()+ AllAllSat cs (t ': ts) = (AllSatisfied cs t, AllAllSat cs ts)++-- | Apply a type constructor to a record index. Record indexes are+-- either 'Type' or @('Symbol', 'Type')@. In the latter case, the type+-- constructor is applied to the second component of the tuple.+type family ApplyToField (t :: Type -> Type) (a :: k1) = (r :: k1) | r -> t a where+ ApplyToField t '(s,x) = '(s, t x)+ ApplyToField t x = t x++-- | Apply a type constructor to each element of a type level list+-- using 'ApplyOn'.+type family MapTyCon t xs = r | r -> xs where+ MapTyCon t '[] = '[]+ MapTyCon t (x ': xs) = ApplyToField t x ': MapTyCon t xs++-- | This class is used for `consMatchCoercion` with older versions+-- of GHC.+class Coercible (f x) (g x) => Similar f g (x :: k)+instance Coercible (f x) (g x) => Similar f g (x :: k)
+ Data/Vinyl/XRec.hs view
@@ -0,0 +1,201 @@+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+-- | A variant of 'Rec' whose values have eliminated common syntactic+-- clutter due to 'Identity', 'Compose', and 'ElField' type+-- constructors.+--+-- A common pain point with using 'Rec' is the mandatory /context/ of+-- each value. A basic record might look like this, @Identity "joe" :&+-- Identity 23 :& RNil :: Rec Identity '[String, Int]@. The 'Identity'+-- constructors are a nuisance, so we offer a way of avoiding them:+-- @"joe" ::& 23 ::& XRNil :: XRec Identity '[String,Int]@. Facilities+-- are provided for converting between 'XRec' and 'Rec' so that the+-- 'Rec' API is available even if you choose to use 'XRec' for+-- construction or pattern matching.+module Data.Vinyl.XRec where+import Data.Vinyl.Core (Rec(..))+import Data.Vinyl.Functor+import Data.Vinyl.Lens (RecElem, RecElemFCtx, rgetC)+import Data.Vinyl.TypeLevel (RIndex)+import Data.Monoid+import GHC.TypeLits (KnownSymbol)++type XRec f = Rec (XData f)+pattern (::&) :: HKD f r -> XRec f rs -> XRec f (r ': rs)+pattern x ::& xs = XData x :& xs+{-# COMPLETE (::&) #-}++infixr 7 ::&++pattern XRNil :: XRec f '[]+pattern XRNil = RNil+{-# COMPLETE XRNil #-}++-- | Like 'rmap', but the supplied function is written against the+-- 'HKD'-simplified types. This is 'xrmap' sandwiched in between+-- 'fromXRec' and 'toXRec'.+rmapX :: forall f g rs. (XRMap f g rs, IsoXRec f rs, IsoXRec g rs)+ => (forall a. HKD f a -> HKD g a) -> Rec f rs -> Rec g rs+rmapX f = fromXRec . xrmapAux aux . toXRec+ where aux :: forall a. XData f a -> XData g a+ aux = XData . f @a . unX++-- | This is 'rmapX' specialized to a type at which it does not change+-- interpretation functor. This can help with type inference.+rmapXEndo :: forall f rs. (XRMap f f rs, IsoXRec f rs)+ => (forall a. HKD f a -> HKD f a) -> Rec f rs -> Rec f rs+rmapXEndo f = fromXRec . xrmapAux aux . toXRec+ where aux :: forall a. XData f a -> XData f a+ aux = XData . f @a . unX++-- | This is 'rmap' for 'XRec'. We apply a natural transformation+-- between interpretation functors to transport a record value between+-- interpretations.+xrmap :: forall f g rs. XRMap f g rs+ => (forall a. HKD f a -> HKD g a) -> XRec f rs -> XRec g rs+xrmap f = xrmapAux aux+ where aux :: forall a. XData f a -> XData g a+ aux = XData . f @a . unX++-- | A wrapper for an 'HKD'-simplified value. That is, noisy value+-- constructors like 'Identity' and 'Compose' are ellided. This is+-- used in the 'xrmapAux' type class method, but may be ignored by+-- users whose needs are met by 'xrmap' and 'rmapX'.+newtype XData t a = XData { unX :: HKD t a }++-- | The implementation of 'xrmap' is broken into a type class to+-- permit unrolling of the recursion across a record. The function+-- mapped across the vector hides the 'HKD' type family under a newtype+-- constructor to help the type checker.+class XRMap f g rs where+ xrmapAux :: (forall a . XData f a -> XData g a) -> XRec f rs -> XRec g rs++instance XRMap f g '[] where+ xrmapAux _ RNil = RNil++instance forall f g r rs. (XRMap f g rs, IsoHKD f r, IsoHKD g r)+ => XRMap f g (r ': rs) where+ xrmapAux f (x :& xs) = f x :& xrmapAux f xs++-- | Like 'rapply': record of components @f r -> g r@ may be applied+-- to a record of @f@ to get a record of @g@.+class XRApply f g rs where+ xrapply :: XRec (Lift (->) f g) rs -> XRec f rs -> XRec g rs++instance XRApply f g '[] where+ xrapply RNil RNil = RNil++instance XRApply f g rs => XRApply f g (r ': rs) where+ xrapply (XData f :& fs) (XData x :& xs) = XData (f x) :& xrapply fs xs++-- | Conversion between 'XRec' and 'Rec'. It is convenient to build+-- and consume 'XRec' values to reduce syntactic noise, but 'Rec' has+-- a richer API that is difficult to build around the 'HKD' type+-- family.+class IsoXRec f ts where+ fromXRec :: XRec f ts -> Rec f ts+ toXRec :: Rec f ts -> XRec f ts++instance IsoXRec f '[] where+ fromXRec RNil = RNil+ toXRec RNil = XRNil++instance (IsoXRec f ts, IsoHKD f t) => IsoXRec f (t ': ts) where+ fromXRec (x ::& xs) = unHKD x :& fromXRec xs+ toXRec (x :& xs) = toHKD x ::& toXRec xs++-- | Isomorphism between a syntactically noisy value and a concise+-- one. For types like, 'Identity', we prefer to work with values of+-- the underlying type without writing out the 'Identity'+-- constructor. For @'Compose' f g a@, aka @(f :. g) a@, we prefer to+-- work directly with values of type @f (g a)@.+--+-- This involves the so-called /higher-kinded data/ type family. See+-- <http://reasonablypolymorphic.com/blog/higher-kinded-data> for more+-- discussion.+class IsoHKD f a where+ type HKD f a+ type HKD f a = f a+ unHKD :: HKD f a -> f a+ default unHKD :: HKD f a ~ f a => HKD f a -> f a+ unHKD = id+ toHKD :: f a -> HKD f a+ default toHKD :: (HKD f a ~ f a) => f a -> HKD f a+ toHKD = id++-- | Work with values of type 'Identity' @a@ as if they were simple of+-- type @a@.+instance IsoHKD Identity a where+ type HKD Identity a = a+ unHKD = Identity+ toHKD (Identity x) = x++-- | Work with values of type 'ElField' @'(s,a)@ as if they were of+-- type @a@.+instance KnownSymbol s => IsoHKD ElField '(s,a) where+ type HKD ElField '(s,a) = a+ unHKD = Field+ toHKD (Field x) = x++-- | Work with values of type 'Compose' @f g a@ as if they were of+-- type @f (g a)@.+instance (IsoHKD f (HKD g a), IsoHKD g a, Functor f) => IsoHKD (Compose f g) a where+ type HKD (Compose f g) a = HKD f (HKD g a)+ unHKD x = Compose (unHKD <$> unHKD x)+ toHKD (Compose fgx) = toHKD (toHKD <$> fgx)++-- | Work with values of type 'Lift' @(->) f g a@ as if they were of+-- type @f a -> g a@.+instance (IsoHKD f a, IsoHKD g a) => IsoHKD (Lift (->) f g) a where+ type HKD (Lift (->) f g) a = HKD f a -> HKD g a+ unHKD x = Lift (unHKD . x . toHKD)+ toHKD (Lift x) = toHKD . x . unHKD++instance IsoHKD IO a where+instance IsoHKD (Either a) b where+instance IsoHKD Maybe a where+instance IsoHKD First a where+instance IsoHKD Last a where+instance IsoHKD ((,) a) b where++-- | Work with values of type 'Sum' @a@ as if they were of type @a@.+instance IsoHKD Sum a where+ type HKD Sum a = a+ unHKD = Sum+ toHKD (Sum x) = x++-- | Work with values of type 'Product' @a@ as if they were of type @a@.+instance IsoHKD Product a where+ type HKD Product a = a+ unHKD = Product+ toHKD (Product x) = x++-- | Record field getter that pipes the field value through 'HKD' to+-- eliminate redundant newtype wrappings. Usage will typically involve+-- a visible type application to the field type. The definition is+-- similar to, @getHKD = toHKD . rget@.+rgetX :: forall a record f rs.+ (RecElem record a a rs rs (RIndex a rs),+ RecElemFCtx record f,+ IsoHKD f a)+ => record f rs -> HKD f a+rgetX = toHKD . rgetAux @a+ where rgetAux :: forall r.+ (RecElem record r r rs rs (RIndex r rs),+ RecElemFCtx record f)+ => record f rs -> f r+ rgetAux = rgetC
+ benchmarks/AccessorsBench.hs view
@@ -0,0 +1,206 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE ScopedTypeVariables #-}++import Control.Monad (unless)+import Criterion.Main+import Data.Monoid (Endo(..))+import Data.Vinyl+import Data.Vinyl.Syntax ()+import Lens.Micro ((%~), (&))+import System.Exit (exitFailure)++import Bench.ARec+import Bench.SRec+import Bench.Rec++data HaskRec = HaskRec {+ a0 :: Int,+ a1 :: Int,+ a2 :: Int,+ a3 :: Int,+ a4 :: Int,+ a5 :: Int,+ a6 :: Int,+ a7 :: Int,+ a8 :: Int,+ a9 :: Int,+ a10 :: Int,+ a11 :: Int,+ a12 :: Int,+ a13 :: Int,+ a14 :: Int,+ a15 :: Int } deriving Show++haskRec :: HaskRec+haskRec = HaskRec 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 99++sumHaskRec r =+ a0 r + a1 r + a2 r + a3 r + a4 r + a5 r + a6 r + a7 r + a8 r + a9 r+ + a10 r + a11 r + a12 r + a13 r + a14 r + a15 r++data StrictHaskRec = StrictHaskRec {+ sa0 :: !Int,+ sa1 :: !Int,+ sa2 :: !Int,+ sa3 :: !Int,+ sa4 :: !Int,+ sa5 :: !Int,+ sa6 :: !Int,+ sa7 :: !Int,+ sa8 :: !Int,+ sa9 :: !Int,+ sa10 :: !Int,+ sa11 :: !Int,+ sa12 :: !Int,+ sa13 :: !Int,+ sa14 :: !Int,+ sa15 :: !Int }++shaskRec :: StrictHaskRec+shaskRec = StrictHaskRec 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 99++sumSHaskRec r =+ sa0 r + sa1 r + sa2 r + sa3 r + sa4 r + sa5 r + sa6 r + sa7 r + sa8 r + sa9 r+ + sa10 r + sa11 r + sa12 r + sa13 r + sa14 r + sa15 r++data UStrictHaskRec = UStrictHaskRec {+ usa0 :: {-# UNPACK #-} !Int,+ usa1 :: {-# UNPACK #-} !Int,+ usa2 :: {-# UNPACK #-} !Int,+ usa3 :: {-# UNPACK #-} !Int,+ usa4 :: {-# UNPACK #-} !Int,+ usa5 :: {-# UNPACK #-} !Int,+ usa6 :: {-# UNPACK #-} !Int,+ usa7 :: {-# UNPACK #-} !Int,+ usa8 :: {-# UNPACK #-} !Int,+ usa9 :: {-# UNPACK #-} !Int,+ usa10 :: {-# UNPACK #-} !Int,+ usa11 :: {-# UNPACK #-} !Int,+ usa12 :: {-# UNPACK #-} !Int,+ usa13 :: {-# UNPACK #-} !Int,+ usa14 :: {-# UNPACK #-} !Int,+ usa15 :: {-# UNPACK #-} !Int }++ushaskRec :: UStrictHaskRec+ushaskRec = UStrictHaskRec 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 99++sumUSHaskRec r =+ usa0 r + usa1 r + usa2 r + usa3 r + usa4 r + usa5 r + usa6 r + usa7 r + usa8 r+ + usa9 r + usa10 r + usa11 r + usa12 r + usa13 r + usa14 r + usa15 r++type SubFields = '[ '("a0", Int), '("a8", Int), '("a15", Int)]++-- updateSRec :: forall record. RecordSubset record ElField SubFields Fields+-- => record ElField Fields -> record ElField Fields+updateSRec :: SRec ElField Fields -> SRec ElField Fields+updateSRec = rsubset %~ appEndo aux+ where aux :: Endo (SRec ElField SubFields)+ aux = Endo (\r -> r & #a15 %~ (+ 2) & #a8 %~ (+ 3) & #a0 %~ (+ 4))++updateARec :: ARec ElField Fields -> ARec ElField Fields+updateARec = rsubset %~ appEndo aux+ where aux :: Endo (ARec ElField SubFields)+ aux = Endo (\r -> r & #a15 %~ (+ 2) & #a8 %~ (+ 3) & #a0 %~ (+ 4))++updateRec :: Rec ElField Fields -> Rec ElField Fields+updateRec = rsubset %~ appEndo aux+ where aux :: Endo (Rec ElField SubFields)+ aux = Endo (\r -> r & #a15 %~ (+ 2) & #a8 %~ (+ 3) & #a0 %~ (+ 4))++data SubRec = SubRec { suba0 :: Int, suba8 :: Int, suba15 :: Int }++updateHaskRec :: HaskRec -> HaskRec+updateHaskRec r = r { a0 = suba0 s, a8 = suba8 s, a15 = suba15 s }+ where s = aux (SubRec (a0 r) (a8 r) (a15 r))+ aux r' = r' { suba0 = suba0 r' + 4, suba8 = suba8 r' + 3, suba15 = suba15 r' + 2 }++main :: IO ()+main =+ do let newF = mkRec 0+ arec = toARec newF+ srec = toSRec newF+ unless (rvalf #a15 arec == rvalf #a15 newF)+ (do putStrLn "AFieldRec accessor disagrees with rvalf"+ exitFailure)+ unless (rvalf #a15 srec == rvalf #a15 newF)+ (do putStrLn "SFieldRec accessor disagrees with rvalf"+ exitFailure)+ let srec' = updateSRec srec+ haskRec' = updateHaskRec haskRec+ arec' = updateARec arec+ unless (rvalf #a0 srec' == a0 haskRec' && a0 haskRec' == 4 &&+ rvalf #a8 srec' == a8 haskRec' && a8 haskRec' == 3 &&+ rvalf #a15 srec' == a15 haskRec' && a15 haskRec' == 101)+ (do putStrLn "SRec and Haskell Record updates disagree"+ exitFailure)+ unless (rvalf #a0 arec' == 4 && rvalf #a8 arec' == 3 &&+ rvalf #a15 arec' == 101)+ (do putStrLn "ARec record updates are inconsistent"+ exitFailure)+ defaultMain+ [ bgroup "Update"+ [ bench "Haskell Record" $ nf (a15 . updateHaskRec) haskRec+ , bench "Rec" $ nf (rvalf #a15 . updateRec) newF+ , bench "ARec" $ nf (rvalf #a15 . updateARec) arec+ , bench "SRec" $ nf (rvalf #a15 . updateSRec) srec+ ]+ ,+ bgroup "creating"+ [ bench "vinyl record" $ whnf mkRec 0+ , bench "toSRec" $ whnf mkToSRec 0+ , bench "New style ARec with toARec " $ whnf mkToARec 0+ , bench "New style ARec with arec " $ whnf mkARec 0+ ]+ ,bgroup "sums"+ [ bench "haskell record" $ nf sumHaskRec haskRec+ , bench "strict haskell record" $ whnf sumSHaskRec shaskRec+ , bench "unboxed strict haskell record" $ whnf sumUSHaskRec ushaskRec+ , bench "vinyl SRec" $ nf sumSRec srec+ , bench "vinyl Rec" $ nf sumRec newF+ , bench "vinyl ARec" $ nf sumARec arec+ ]+ , bgroup "FieldRec"+ [ bench "a0" $ nf (rvalf #a0) newF+ , bench "a4" $ nf (rvalf #a4) newF+ , bench "a8" $ nf (rvalf #a8) newF+ , bench "a12" $ nf (rvalf #a12) newF+ , bench "a15" $ nf (rvalf #a15) newF+ ]+ , bgroup "AFieldRec"+ [ bench "a0" $ nf (rvalf #a0) arec+ -- , bench "a4" $ nf (rvalf #a4) arec+ -- , bench "a8" $ nf (rvalf #a8) arec+ -- , bench "a12" $ nf (rvalf #a12) arec+ , bench "a15" $ nf (rvalf #a15) arec+ ]+ , bgroup "SFieldRec"+ [ bench "a0" $ nf (rvalf #a0) srec+ -- , bench "a4" $ nf (rvalf #a4) srec+ -- , bench "a8" $ nf (rvalf #a8) srec+ -- , bench "a12" $ nf (rvalf #a12) srec+ , bench "a15" $ nf (rvalf #a15) srec+ ]+ , bgroup "Haskell Record"+ [ bench "a0" $ nf a0 haskRec+ -- , bench "a4" $ nf a4 haskRec+ -- , bench "a8" $ nf a8 haskRec+ -- , bench "a12" $ nf a12 haskRec+ , bench "a15" $ nf a15 haskRec+ ]+ , bgroup "Strict Haskell Record"+ [ bench "a0" $ nf sa0 shaskRec+ -- , bench "a4" $ nf sa4 shaskRec+ -- , bench "a8" $ nf sa8 shaskRec+ -- , bench "a12" $ nf sa12 shaskRec+ , bench "a15" $ nf sa15 shaskRec+ ]+ , bgroup "Unpacked Strict Haskell Record"+ [ bench "a0" $ nf usa0 ushaskRec+ -- , bench "a4" $ nf usa4 ushaskRec+ -- , bench "a8" $ nf usa8 ushaskRec+ -- , bench "a12" $ nf usa12 ushaskRec+ , bench "a15" $ nf usa15 ushaskRec+ ]+ ]
+ benchmarks/AsABench.hs view
@@ -0,0 +1,14 @@+{-# language DataKinds, TypeOperators, TypeApplications #-}+import Data.Vinyl.CoRec+import Data.Vinyl.Functor (Identity(..))+import Criterion.Main++main :: IO ()+main = let x1 :: CoRec Identity '[Int,Bool,Char,Double,(),Float]+ x1 = CoRec (Identity (23::Int))+ x5 :: CoRec Identity '[Bool,Char,Double,(),Int,Float]+ x5 = CoRec (Identity (23::Int))+ in defaultMain [ bench "asASafe1" $ whnf (asASafe @Int) x1+ , bench "asA1" $ whnf (asA @Int) x1+ , bench "asASafe5" $ whnf (asASafe @Int) x5+ , bench "asA5" $ whnf (asA @Int) x5 ]
+ benchmarks/Bench/ARec.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE GADTs #-}++module Bench.ARec where++import Data.Vinyl+import Data.Vinyl.ARec.Internal+import Data.Vinyl.Syntax ()++import Bench.Rec++mkARec :: Int -> ARec ElField Fields+mkARec i= arec (Field i `arcons` Field i `arcons` Field i `arcons` Field i `arcons`+ Field i `arcons` Field i `arcons` Field i `arcons` Field i `arcons`+ Field i `arcons` Field i `arcons` Field i `arcons` Field i `arcons`+ Field i `arcons` Field i `arcons` Field i `arcons` Field 99 `arcons`+ arnil)+++mkToARec :: Int -> ARec ElField Fields+mkToARec i= toARec (Field i :& Field i :& Field i :& Field i :&+ Field i :& Field i :& Field i :& Field i :&+ Field i :& Field i :& Field i :& Field i :&+ Field i :& Field i :& Field i :& Field 99 :&+ RNil)++sumARec :: ARec ElField Fields -> Int+sumARec str =+ get #a0 str + get #a1 str + get #a2 str + get #a3 str + get #a4 str+ + get #a5 str + get #a6 str + get #a7 str + get #a8 str+ + get #a9 str + get #a10 str + get #a11 str + get #a12 str+ + get #a13 str + get #a14 str + get #a15 str+ where+ get label r = rvalf label r+ {-# INLINE get #-}
+ benchmarks/Bench/Rec.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE GADTs #-}++module Bench.Rec where++import Data.Vinyl+import Data.Vinyl.Syntax ()+++type Fields = '[ '( "a0", Int ), '( "a1", Int ), '( "a2", Int ), '( "a3", Int )+ , '( "a4", Int ), '( "a5", Int ), '( "a6", Int ), '( "a7", Int )+ , '( "a8", Int ), '( "a9", Int ), '( "a10", Int ), '( "a11", Int )+ , '( "a12", Int ), '( "a13", Int ), '( "a14", Int ), '( "a15", Int )+ ]++mkRec :: Int -> Rec ElField Fields+mkRec i= Field i :& Field i :& Field i :& Field i :&+ Field i :& Field i :& Field i :& Field i :&+ Field i :& Field i :& Field i :& Field i :&+ Field i :& Field i :& Field i :& Field 99 :&+ RNil++sumRec :: Rec ElField Fields -> Int+sumRec str =+ get #a0 str + get #a1 str + get #a2 str + get #a3 str + get #a4 str+ + get #a5 str + get #a6 str + get #a7 str + get #a8 str+ + get #a9 str + get #a10 str + get #a11 str + get #a12 str+ + get #a13 str + get #a14 str + get #a15 str+ where+ get (_label :: Label s) r =+ let (Field v) = rget @'(s, _) r+ in v+ {-# INLINE get #-}
+ benchmarks/Bench/SRec.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedLabels #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE GADTs #-}++module Bench.SRec where++import Data.Vinyl.SRec+import Data.Vinyl++import Bench.Rec (Fields)+++mkToSRec :: Int -> SRec ElField Fields+mkToSRec i= toSRec (Field i :& Field i :& Field i :& Field i :&+ Field i :& Field i :& Field i :& Field i :&+ Field i :& Field i :& Field i :& Field i :&+ Field i :& Field i :& Field i :& Field 99 :&+ RNil)+++sumSRec :: SRec ElField Fields -> Int+sumSRec str =+ get #a0 str + get #a1 str + get #a2 str + get #a3 str + get #a4 str+ + get #a5 str + get #a6 str + get #a7 str + get #a8 str+ + get #a9 str + get #a10 str + get #a11 str + get #a12 str+ + get #a13 str + get #a14 str + get #a15 str+ where+ get (label :: Label s) r =+ case rget @'(s, Int) r of+ Field v -> v+ {-# INLINE get #-}
+ benchmarks/EqualityBench.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE DataKinds, TypeFamilies, UndecidableInstances #-}+import Control.Monad (join)+import Criterion.Main+import Data.Functor.Identity+import Data.Vinyl+import Data.Vinyl.TypeLevel++class Eq2 a where+ eq2 :: a -> a -> Bool++instance RecAll f rs Eq => Eq2 (Rec f rs) where+ eq2 RNil RNil = True+ eq2 (a :& as) (b :& bs) = a == b && eq2 as bs++main :: IO ()+main = defaultMain [+ bench "Eq" $ whnf (join (==)) r1+ , bench "Eq2" $ whnf (join eq2) r1 ]+ where r1 = pure 23 :& pure 'b' :& pure 3.14 :& RNil :: Rec Identity '[Int, Char, Double]
benchmarks/StorableBench.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE DataKinds, ScopedTypeVariables, TypeOperators #-}+{-# LANGUAGE DataKinds, GADTs, OverloadedLabels, ScopedTypeVariables,+ TypeOperators #-} -- A benchmark where we initialize a 'V.Vector' of random vertices, -- each carrying 3D position, 2D texture coordinates, and a 3D normal -- vector. A calculation is carried out where we multiply the y@@ -7,11 +8,10 @@ -- by interfacing the vertex data as a flat record, a traditional -- record of "Linear" finite dimensional vector types, and a vinyl -- record of linear fields.-import Control.Applicative-import Control.Lens+import Lens.Micro+import Lens.Micro.Extras (view) import Control.Monad (when) import qualified Data.Foldable as F-import Data.Proxy import qualified Data.Vector.Storable as V import qualified Data.Vector.Storable.Mutable as VM import Data.Vinyl@@ -28,40 +28,51 @@ randVecStd :: (Storable a, Variate a) => Int -> IO (V.Vector a) randVecStd = withSystemRandom . randVec -vNorm :: Proxy '("normal", V3 a)-vNorm = Proxy- type MyFields a = [ '("pos", V3 a), '("tex", V2 a), '("normal", V3 a) ] type MyVertex a = FieldRec (MyFields a) -doubleNviL :: V.Vector (MyVertex Float) -> V.Vector (MyVertex Float)-doubleNviL = V.map (rlens vNorm . rfield . _y *~ (2::Float))+(*~) :: Num a => ASetter s t a a -> a -> s -> t+l *~ x = l %~ (* x)+infixr 4 *~ -vinylNSumL :: (Num a, Storable a) => V.Vector (MyVertex a) -> a-vinylNSumL = V.sum . V.map (F.sum . view (rlens vNorm . rfield))+vinylNormSumLens :: (Num a, Storable a) => V.Vector (MyVertex a) -> a+vinylNormSumLens = V.sum . V.map (F.sum . view (rlensf #normal)) -doubleNvi :: V.Vector (MyVertex Float) -> V.Vector (MyVertex Float)-doubleNvi = V.map (rlens vNorm . rfield . _y *~ (2::Float))+vinylNormSumLabel :: (Num a, Storable a) => V.Vector (MyVertex a) -> a+vinylNormSumLabel = V.sum . V.map (F.sum . rvalf #normal) -vinylNSum :: (Num a, Storable a) => V.Vector (MyVertex a) -> a-vinylNSum = V.sum . V.map (F.sum . view rfield . rget vNorm)+doubleNormYLens :: V.Vector (MyVertex Float) -> V.Vector (MyVertex Float)+doubleNormYLens = V.map (rlensf #normal . _y *~ (2::Float)) +doubleNormY :: V.Vector (MyVertex Float) -> V.Vector (MyVertex Float)+doubleNormY = V.map (\(p :& t :& Field n :& RNil) ->+ p :& t :& Field (_y *~ (2::Float) $ n) :& RNil)++vinylNormSum :: (Num a, Storable a) => V.Vector (MyVertex a) -> a+vinylNormSum = V.sum . V.map (F.sum . (\(_ :& _ :& Field vn :& RNil) -> vn))+ main :: IO () main = do vals <- randVecStd $ n * 8 :: IO (V.Vector Float) let vinylVerts = V.unsafeCast vals :: V.Vector (MyVertex Float) flatVerts = V.unsafeCast vals reasVerts = V.unsafeCast vals- vinylAns = vinylNSum $ doubleNvi vinylVerts- vinylLans = vinylNSumL $ doubleNviL vinylVerts- flatAns = flatNSum $ doubleNfl flatVerts- reasAns = reasNSum $ doubleNre reasVerts- when (any (/= vinylAns) [vinylLans, flatAns, reasAns])+ vinylAns = vinylNormSum $ doubleNormY vinylVerts+ vinylLans = vinylNormSumLens $ doubleNormYLens vinylVerts+ vinylLabAns = vinylNormSumLabel $ doubleNormYLens vinylVerts+ flatAns = flatNormSum $ doubleNormFlat flatVerts+ reasAns = reasNormSum $ doubleNormReas reasVerts+ when (any (/= vinylAns) [ vinylLans, flatAns, reasAns, vinylLabAns ]) (error "Not all versions compute the same answer")- defaultMain [ bench "flat" $ whnf (flatNSum . doubleNfl) flatVerts- , bench "vinyl" $ whnf (vinylNSum . doubleNvi) vinylVerts- , bench "vinyl-lens" $ whnf (vinylNSumL . doubleNviL) vinylVerts+ defaultMain [ bench "flat" $+ whnf (flatNormSum . doubleNormFlat) flatVerts+ , bench "vinyl" $+ whnf (vinylNormSum . doubleNormY) vinylVerts+ , bench "vinyl-lens" $+ whnf (vinylNormSumLens . doubleNormYLens) vinylVerts+ , bench "vinyl-label" $+ whnf (vinylNormSumLabel . doubleNormYLens) vinylVerts , bench "reasonable" $- whnf (reasNSum . doubleNre) reasVerts ]+ whnf (reasNormSum . doubleNormReas) reasVerts ] where n = 1000 --------------------------------------------------------------------------------@@ -96,11 +107,11 @@ pokeElemOff ptr' 7 nz' where ptr' = castPtr ptr -flatNSum :: (Num a, Storable a) => V.Vector (TotallyFlat a) -> a-flatNSum = V.sum . V.map (\v -> nx v + ny v + nz v)+flatNormSum :: (Num a, Storable a) => V.Vector (TotallyFlat a) -> a+flatNormSum = V.sum . V.map (\v -> nx v + ny v + nz v) -doubleNfl :: V.Vector (TotallyFlat Float) -> V.Vector (TotallyFlat Float)-doubleNfl = V.map (\v -> v { ny = ny v * 2 })+doubleNormFlat :: V.Vector (TotallyFlat Float) -> V.Vector (TotallyFlat Float)+doubleNormFlat = V.map (\v -> v { ny = ny v * 2 }) -- A more reasonable approach to a vertex record. data Reasonable a = Reasonable { rPos :: V3 a@@ -121,8 +132,8 @@ where szx = sizeOf (undefined::V3 a) szy = sizeOf (undefined::V2 a) -reasNSum :: (Num a, Storable a) => V.Vector (Reasonable a) -> a-reasNSum = V.sum . V.map (F.sum . rNorm)+reasNormSum :: (Num a, Storable a) => V.Vector (Reasonable a) -> a+reasNormSum = V.sum . V.map (F.sum . rNorm) -doubleNre :: V.Vector (Reasonable Float) -> V.Vector (Reasonable Float)-doubleNre = V.map (\v -> v { rNorm = (_y *~ 2) $ rNorm v })+doubleNormReas :: V.Vector (Reasonable Float) -> V.Vector (Reasonable Float)+doubleNormReas = V.map (\v -> v { rNorm = (_y *~ 2) $ rNorm v })
+ tests/Aeson.hs view
@@ -0,0 +1,282 @@+{-# LANGUAGE CPP, DataKinds, DeriveGeneric, FlexibleContexts,+ FlexibleInstances, GADTs, OverloadedStrings, PolyKinds,+ ScopedTypeVariables, TypeApplications, TypeOperators,+ ViewPatterns #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- | Demonstrate encoding a 'Rec' to JSON. Three approaches are shown:+-- the first utilizes 'ToJSON' instances for the record's+-- interpretation type constructor applied to each of its fields. This+-- has the advantage of being concise by virtue of re-using a lot of+-- existing pieces. The downside to relying on existing 'ToJSON'+-- instances is that they encode self-contained JSON values, when what+-- we want to do is construct a single JSON object encompassing each+-- record field as a named field of that JSON object. We can do this+-- by inspecting the JSON serialization of each field, and extracting+-- it as a key-value pair if it was serialized as a JSON object with a+-- single named field. This works, but means that the types do not+-- guarantee correctness (i.e. if a record field is serialized as a+-- 'Number', we won't be able to include it in the serialization of+-- the 'Rec').+--+-- The second approach uses a bit of @aeson@ internals to instead+-- serialize each 'Rec' field as a key-value pair with no additional+-- decoration. This should be faster as well as more tightly typed+-- since we do not need to undo any 'Value' wrapping of the individual+-- record fields.+--+-- The third approach uses aeson's built-in functions for working with+-- 'Generic'. This requires some post-processing to address precisely+-- the above problem: the function based on 'Generic' ends up+-- producing a self-contained 'Value' for each field of the+-- record. Specifically, each field becomes an 'Array' that is either+-- empty or contains an 'Object' with a single field as well as+-- another, nested, 'Array' for the rest of the record. We include+-- here a function to flatten that recursive structure into the+-- 'Object' shape we want.+module Main where+import Control.Lens (view, deep)+import Control.Monad.State.Strict+import qualified Data.HashMap.Strict as H+#if __GLASGOW_HASKELL__ < 804+import Data.Semigroup ((<>))+#endif+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Vector as V+import Data.Vinyl+import Data.Vinyl.Class.Method (RecMapMethod1(..))+import Data.Vinyl.Functor (Compose(..), (:.), Identity(..), Const(..))+import Data.Aeson+import Data.Aeson.Encoding.Internal (wrapObject, pair)+#if MIN_VERSION_aeson(2,0,0)+import Control.Lens (_1, (%~))+import qualified Data.Aeson.Key as Key+import qualified Data.Aeson.KeyMap as KeyMap+#endif+import Data.Aeson.Lens (_Object)+import GHC.Generics (Generic, Rep)+import GHC.TypeLits (KnownSymbol)+import Test.Hspec++-- * Compatibility with aeson < 2+#if MIN_VERSION_aeson(2,0,0)+type KeyMap = KeyMap.KeyMap Value++keyFromString :: String -> Key+keyFromString = Key.fromString++keyFromText :: Text -> Key+keyFromText = Key.fromText++keyMapToList :: KeyMap -> [(Key,Value)]+keyMapToList = KeyMap.toList+#else+type Key = Text+type KeyMap = H.HashMap Text Value++keyFromString :: String -> Key+keyFromString = T.pack++keyFromText :: Text -> Key+keyFromText = id++keyMapToList :: KeyMap -> [(Key,Value)]+keyMapToList = H.toList+#endif++-- * Implementing 'ToJSON' for 'Rec'++-- | An 'Identity' functor is not reflected in a value's JSON+-- serialization.+instance ToJSON a => ToJSON (Identity a) where+ toJSON (Identity x) = toJSON x++-- | A named field serializes to a JSON object with a single named+-- field.+instance (KnownSymbol s, ToJSON a) => ToJSON (ElField '(s,a)) where+ toJSON x = object [(keyFromString (getLabel x), toJSON (getField x))]++-- | A @((Text,) :. f) a@ value maps to a JSON field whose name is the+-- 'Text' value, and whose value has type @f a@.+instance ToJSON (f a) => ToJSON ((((,) Text) :. f) a) where+ toJSON (Compose (name, x)) = object [(keyFromText name, toJSON x)]++-- | Replace each field of a record with the result of serializing it+-- to a JSON 'Value', and then extracting that 'Value''s single named+-- field. If the serialization is not in the form of an object with a+-- single field, the conversion fails with a 'Nothing'.+fieldsToJSON :: (RecMapMethod1 ToJSON f rs)+ => Rec f rs -> Rec (Maybe :. Const (Key,Value)) rs+fieldsToJSON = rmapMethod1 @ToJSON (Compose . aux)+ where aux x = case toJSON x of+ Object (keyMapToList -> [field]) -> Just (Const field)+ _ -> Nothing++-- | Convert a homogeneous record to a list factored through an outer+-- functor. A useful specialization is when the outer functor is+-- 'Maybe': if any field is 'Nothing', then the result of this+-- function is 'Nothing'.+recToListF :: (Applicative f, RFoldMap rs) => Rec (f :. Const a) rs -> f [a]+recToListF = fmap (rfoldMap (pure . getConst)) . rtraverse getCompose++instance (RFoldMap rs, RecMapMethod1 ToJSON f rs)+ => ToJSON (Rec f rs) where+ toJSON = maybe err object . recToListF . fieldsToJSON+ where err = error (unlines [ "The interpretation functor of this "+ , "record did not produce a named field "+ , "for at least one of its fields." ])++-- * Naming anonymous fields++-- | Pair each record field with its position.+recIndexed :: Rec f rs -> Rec ((,) Int :. f) rs+recIndexed = flip evalState 1 . rtraverse aux+ where aux x = do i <- get+ Compose (i,x) <$ put (i+1)++-- | A helper to pair each field of a record with a name derived from+-- its position in the record. This reflects the implicit ordering of+-- the type-level list of the record's fields.+nameFields :: RMap rs => Rec f rs -> Rec ((,) Text :. f) rs+nameFields = rmap aux . recIndexed+ where aux (Compose (i,x)) = Compose ("field"<>T.pack (show i), x)++-- * Test Cases++r1 :: Rec ElField '[("age" ::: Int), ("iscool" ::: Bool), ("yearbook" ::: Text)]+r1 = xrec (23, True, "You spin me right round")++r1JSON :: Value+r1JSON = object [ "age" .= (23 :: Int)+ , "iscool" .= True+ , "yearbook" .= ("You spin me right round" :: Text) ]++r2 :: Rec Identity '[Int,Bool,Text]+r2 = xrec (23, True, "You spin me right round")++r2JSON :: Value+r2JSON = object [ "field1" .= (23 :: Int)+ , "field2" .= True+ , "field3" .= ("You spin me right round" :: Text) ]++-- | A type with its own JSON Object encoding+data MyType = MyType { bike :: Bool, skateboard :: Bool } deriving Generic+instance ToJSON MyType++r3 :: Rec ElField '[ "age" ::: Int+ , "iscool" ::: Bool+ , "yearbook" ::: Text+ , "hobbies" ::: MyType ]+r3 = xrec (23, True, "You spin me right round", MyType True True)++r3JSON :: Value+r3JSON = object [ "age" .= (23 :: Int)+ , "iscool" .= True+ , "yearbook" .= ("You spin me right round" :: Text)+ , "hobbies" .= object ["bike" .= True, "skateboard" .= True] ]++main :: IO ()+main = hspec $ do+ describe "Simple Rec to JSON" $ do+ it "Named fields" $+ toJSON r1 `shouldBe` r1JSON+ it "Anonymous fields" $+ toJSON (nameFields r2) `shouldBe` r2JSON+ it "Nested objects" $+ toJSON r3 `shouldBe` r3JSON+ describe "Type-safe Rec to JSON" $ do+ it "Named fields" $+ recToJSON r1 `shouldBe` r1JSON+ it "Anonymous fields" $+ recToJSON (nameFields r2) `shouldBe` r2JSON+ it "Nested objects" $+ recToJSON r3 `shouldBe` r3JSON+ describe "Via Generics" $ do+ it "Named fields" $+ grecToJSON r1 `shouldBe` r1JSON+ it "Anonymous fields" $+ grecToJSON (nameFields r2) `shouldBe` r2JSON+ it "Nested objects" $+ grecToJSON r3 `shouldBe` r3JSON++-- * More type safe and efficient++-- | Produce a JSON key-value pair from a Haskell value. This is what+-- we want from each field of our records. The simple encoding above+-- that treats each record field as a self-contained JSON 'Value'+-- loses precision in the type.+class ToJSONField a where+ encodeJSONField :: a -> Series+ toJSONField :: a -> (Key,Value)++-- | An @ElField '(s,a)@ value maps to a JSON field with name @s@ and+-- value @a@.+instance (ToJSON a, KnownSymbol s) => ToJSONField (ElField '(s,a)) where+ encodeJSONField x = pair (keyFromString (getLabel x))+ (toEncoding (getField x))+ toJSONField x = (keyFromString (getLabel x), toJSON (getField x))++-- | A @((Text,) :. f) a@ value maps to a JSON field whose name is the+-- 'Text' value, and whose value has type @f a@.+instance ToJSON (f a) => ToJSONField (((,) Text :. f) a) where+ encodeJSONField (Compose (name,val)) =+ pair (keyFromText name) (toEncoding val)+ toJSONField (Compose (name,val)) = (keyFromText name, toJSON val)++encodeRec :: (RFoldMap rs, RecMapMethod1 ToJSONField f rs)+ => Rec f rs -> Encoding+encodeRec = wrapObject+ . pairs+ . rfoldMap getConst+ . rmapMethod1 @ToJSONField (Const . encodeJSONField)++recToJSON :: (RFoldMap rs, RecMapMethod1 ToJSONField f rs)+ => Rec f rs -> Value+recToJSON = object+ . rfoldMap ((:[]) . getConst)+ . rmapMethod1 @ToJSONField (Const . toJSONField)++-- * Generically++-- | If a 'Value' is a nested 'Array' of 'Object's, extract the+-- collection of key-value pairs from the entire recursive structure.+allAesonFields :: Value -> Maybe Object+allAesonFields (Array arr) =+ case V.toList arr of+ [] -> Just mempty+ [Object field, objTail] -> fmap (field <>) (allAesonFields objTail)+ _ -> Nothing+allAesonFields _ = Nothing++-- | Try un-nesting a recursive 'Array' of fields. That is, if a+-- 'Value' is laid out as @Array [Object [(key1,value1)], Array+-- [Object [(key2, value2)], ...]]@ we extract all the key-value+-- pairs, @[(key1,value1), (key2, value2), ...]@.+unnestFields :: Value -> Value+unnestFields v = maybe v Object (allAesonFields v)++-- | A lens implementation of something a bit looser than+-- 'unnestFields'.+allFields :: Value -> Object+#if MIN_VERSION_aeson(2,0,0)+allFields = KeyMap.fromList+#if MIN_VERSION_lens_aeson(1,2,0)+ . keyMapToList+#else+ . map (_1 %~ Key.fromText)+ . H.toList+#endif+ . view (deep _Object)+#else+allFields = view (deep _Object)+#endif++-- | The generic 'ToJSON' instance is not quite right since we use the+-- record's interpretation type constructor to define serialization,+-- resulting in each record field being treated as a self-contained+-- JSON object. What we want is for each record field to become a+-- named field of a single JSON object, so we must post-process the+-- result of the function defined on 'Generic'.+grecToJSON :: (Generic (Rec f rs), GToJSON Zero (Rep (Rec f rs)))+ => Rec f rs -> Value+grecToJSON = Object . allFields . genericToJSON defaultOptions
+ tests/CoRecSpec.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE CPP, DataKinds, FlexibleContexts, ScopedTypeVariables,+ TypeApplications, TypeOperators #-}+{-# OPTIONS_GHC -fdefer-type-errors #-}+module CoRecSpec (spec) where+import Control.Monad ((>=>))+import Data.Proxy+import Data.Vinyl+import Data.Vinyl.CoRec+import Data.Vinyl.Functor (Identity(..))++import Test.Hspec+import Test.ShouldNotTypecheck++-- Custom error types+data TooBig = TooBig+data Even = Even+data Not7 = Not7++-- Functions that might return an error value+fun1 :: (TooBig ∈ rs) => Int -> Either (CoRec Identity rs) ()+fun1 x = if x < 10 then Right () else Left (CoRec (pure TooBig))++fun2 :: (Even ∈ rs) => Int -> Either (CoRec Identity rs) ()+fun2 x = if odd x then Right () else Left (CoRec (pure Even))++fun3 :: (Not7 ∈ rs) => Int -> Either (CoRec Identity rs) ()+fun3 x = if x == 7 then Right () else Left (CoRec (pure Not7))++spec :: SpecWith ()+spec =+ describe "CoRecs" $ do+ let x = CoRec (pure True) :: Field '[Int,Bool,()]+ it "Can be cast successfully" $+ asA @Bool x `shouldBe` Just True+ it "Can fail to cast" $+ asA @Int x `shouldBe` Nothing+ it "Can be handled all at once" $+ match x (H (\y -> "Int")+ :& H (\y -> "Bool")+ :& H (\y -> "Unit")+ :& RNil) `shouldBe` "Bool"+ it "Can be handled piece by piece, out of order" $+ let handlers = match1 (H (\(u :: ()) -> "unit"))+ >=> match1 (H (\(b :: Bool) -> "bool "++show b))+ >=> match1 (H (\(i :: Int) -> "int "++show i))+ in either id matchNil (handlers x) `shouldBe` "bool True"+ it "Can detect partial pattern matches" $+ let handlers = match1 (H (\(u :: ()) -> "unit"))+ >=> match1 (H (\(b :: Bool) -> "bool "++show b))+ in shouldNotTypecheck (either id matchNil (handlers x))
− tests/Intro.lhs
@@ -1,260 +0,0 @@-This introduction was originally published at-<http://www.jonmsterling.com/posts/2013-04-06-vinyl-modern-records-for-haskell.html>--Vinyl: Modern Records for Haskell-=================================--Vinyl is a general solution to the records problem in Haskell using-type level strings and other modern GHC features, featuring static-structural typing (with a subtyping relation), and automatic-row-polymorphic lenses. All this is possible without Template Haskell.--First, install Vinyl from Hackage:--< cabal update-< cabal install vinyl--Let’s work through a quick example. We’ll need to enable some language-extensions first:--> {-# LANGUAGE DataKinds, PolyKinds, TypeOperators, TypeFamilies #-}-> {-# LANGUAGE FlexibleContexts, FlexibleInstances, NoMonomorphismRestriction #-}-> {-# LANGUAGE GADTs, TypeSynonymInstances, TemplateHaskell, StandaloneDeriving #-}-> import Data.Vinyl-> import Data.Vinyl.Functor-> import Control.Applicative-> import Control.Lens hiding (Identity)-> import Control.Lens.TH-> import Data.Char-> import Test.DocTest-> import Data.Singletons.TH--Let’s define a universe of fields which we want to use:--> data Fields = Name | Age | Sleeping | Master deriving Show-> type LifeForm = [Name, Age, Sleeping]--> type family ElF (f :: Fields) :: * where-> ElF Name = String-> ElF Age = Int-> ElF Sleeping = Bool-> ElF Master = Rec Attr LifeForm--> newtype Attr f = Attr { _unAttr :: ElF f }-> makeLenses ''Attr-> instance Show (Attr Name) where show (Attr x) = "name: " ++ show x-> instance Show (Attr Age) where show (Attr x) = "age: " ++ show x-> instance Show (Attr Sleeping) where show (Attr x) = "sleeping: " ++ show x-> instance Show (Attr Master) where show (Attr x) = "master: " ++ show x--> (=::) :: sing f -> ElF f -> Attr f-> _ =:: x = Attr x--> genSingletons [ ''Fields ]--Now, let’s try to make an entity that represents a man:--> jon = (SName =:: "jon")-> :& (SAge =:: 23)-> :& (SSleeping =:: False)-> :& RNil--Automatically, we can show the record:--> -- |-> -- >>> show jon-> -- "{name: \"jon\", age: 23, sleeping: False}"--And its types are all inferred with no problem. Now, make a dog! Dogs are-life-forms, but unlike men, they have masters. So, let’s build my dog:--> tucker = (SName =:: "tucker")-> :& (SAge =:: 9)-> :& (SSleeping =:: True)-> :& (SMaster =:: jon)-> :& RNil--Using Lenses---------------Now, if we want to wake entities up, we don’t want to have to write a-separate wake-up function for both dogs and men (even though they are-of different type). Luckily, we can use the built-in lenses to focus-on a particular field in the record for access and update, without-losing additional information:---> wakeUp :: (Sleeping ∈ fields) => Rec Attr fields -> Rec Attr fields-> wakeUp = rput $ SSleeping =:: False--Now, the type annotation on wakeUp was not necessary; I just wanted to-show how intuitive the type is. Basically, it takes as an input any-record that has a `Bool` field labelled `sleeping`, and modifies that-specific field in the record accordingly.--> tucker' = wakeUp tucker-> jon' = wakeUp jon--> -- |-> -- >>> tucker' ^. rlens SSleeping-> -- sleeping: False-> ---> -- >>> tucker ^. rlens SSleeping-> -- sleeping: True-> ---> -- >>> jon' ^. rlens SSleeping-> -- sleeping: False--We can also access the entire lens for a field using the rLens-function; since lenses are composable, it’s super easy to do deep-update on a record:--> masterSleeping = rlens SMaster . unAttr . rlens SSleeping-> tucker'' = masterSleeping .~ (SSleeping =:: True) $ tucker'--> -- | >>> tucker'' ^. masterSleeping-> -- sleeping: True--Subtyping Relation and Coercion----------------------------------A record `Rec f xs` is a subtype of a record `Rec f ys` if `ys ⊆ xs`;-that is to say, if one record can do everything that another record-can, the former is a subtype of the latter. As such, we should be able-to provide an upcast operator which “forgets” whatever makes one-record different from another (whether it be extra data, or different-order).--Therefore, the following works:--> upcastedTucker :: Rec Attr LifeForm-> upcastedTucker = rcast tucker--The subtyping relationship between record types is expressed with the-`(<:)` constraint; so, cast is of the following type:--< rcast :: r1 <: r2 => Rec f r1 -> Rec f r2--Also provided is a `(≅)` constraint which indicates record congruence-(that is, two record types differ only in the order of their fields).--In fact, `rcast` is actually given as a special case of the lens `rsubset`,-which lets you modify entire (possibly non-contiguous) slices of a record!--Records are polymorphic over functors----------------------------------------Consider the following declaration:--< data Rec :: (u -> *) -> [u] -> * where-< RNil :: Rec f '[]-< (:&) :: f r -> Rec f rs -> Rec f (r ': rs)--Records are implicitly parameterized over a kind `u`, which stands for the-"universe" or key space. Keys (inhabitants of `u`) are then interpreted into-the types of their values by the first parameter to `Rec`, `f`. An extremely-powerful aspect of Vinyl records is that you can construct natural-transformations between different interpretation functors `f,g`, or postcompose-some other functor onto the stack. This can be used to immerse each field of a-record in some particular effect modality, and then the library functions can-be used to traverse and accumulate these effects.--Let’s imagine that we want to do validation on a record that-represents a name and an age:--> type Person = [Name, Age]--We’ve decided that names must be alphabetic, and ages must be positive. For-validation, we’ll use `Maybe` for now, though you should use a-left-accumulating `Validation` type.--> goodPerson :: Rec Attr Person-> goodPerson = (SName =:: "Jon")-> :& (SAge =:: 20)-> :& RNil--> badPerson = (SName =:: "J#@#$on")-> :& (SAge =:: 20)-> :& RNil--We'll give validation a (rather poor) shot.--> validatePerson :: Rec Attr Person -> Maybe (Rec Attr Person)-> validatePerson p = (\n a -> (SName =:: n) :& (SAge =:: a) :& RNil) <$> vName <*> vAge where-> vName = validateName $ p ^. rlens SName . unAttr-> vAge = validateAge $ p ^. rlens SAge . unAttr->-> validateName str | all isAlpha str = Just str-> validateName _ = Nothing-> validateAge i | i >= 0 = Just i-> validateAge _ = Nothing--> -- $setup-> -- >>> let isJust (Just _) = True; isJust _ = False--> -- |-> -- >>> isJust $ validatePerson goodPerson-> -- True-> ---> -- >>> isJust $ validatePerson badPerson-> -- False--The results are as expected (`Just` for `goodPerson`, and a `Nothing` for-`badPerson`); but this was not very fun to build.--Further, it would be nice to have some notion of a partial record;-that is, if part of it can’t be validated, it would still be nice to-be able to access the rest. What if we could make a version of this-record where the elements themselves were validation functions, and-then that record could be applied to a plain one, to get a record of-validated fields? That’s what we’re going to do.--> type Validator f = Lift (->) f (Maybe :. f)--Let’s parameterize a record by it: when we do, then an element of type-`a` should be a function `Identity a -> Result e a`:--> vperson :: Rec (Validator Attr) Person-> vperson = lift validateName :& lift validateAge :& RNil-> where-> lift f = Lift $ Compose . f-> validateName (Attr str) | all isAlpha str = Just (Attr str)-> validateName _ = Nothing-> validateAge (Attr i) | i >= 0 = Just (Attr i)-> validateAge _ = Nothing--And we can use the special application operator `<<*>>` (which is-analogous to `<*>`, but generalized a bit) to use this to validate a-record:--> goodPersonResult = vperson <<*>> goodPerson-> badPersonResult = vperson <<*>> badPerson--> -- |-> -- >>> isJust . getCompose $ goodPersonResult ^. rlens SName-> -- True-> -- >>> isJust . getCompose $ goodPersonResult ^. rlens SAge-> -- True-> -- >>> isJust . getCompose $ badPersonResult ^. rlens SName-> -- False-> -- >>> isJust . getCompose $ badPersonResult ^. rlens SAge-> -- True---So now we have a partial record, and we can still do stuff with its contents.-Next, we can even recover the original behavior of the validator (that is, to-give us a value of type `Maybe (Rec Attr Person)`) using `rtraverse`:--> mgoodPerson :: Maybe (Rec Attr Person)-> mgoodPerson = rtraverse getCompose goodPersonResult--> mbadPerson = rtraverse getCompose badPersonResult--> -- |-> -- >>> isJust mgoodPerson-> -- True-> -- >>> isJust mbadPerson-> -- False--> main :: IO ()-> main = doctest ["tests/Intro.lhs"]
+ tests/Spec.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE DataKinds, FlexibleContexts, GADTs,+ NoMonomorphismRestriction, OverloadedLabels,+ ScopedTypeVariables, TypeApplications, TypeOperators #-}+{-# OPTIONS_GHC -Wall -Wno-type-defaults #-}+import Data.Vinyl+import Data.Vinyl.Functor (Lift(..), Const(..), Compose(..), (:.))+import Lens.Micro+import Test.Hspec+import Data.Vinyl.Syntax ()++import qualified CoRecSpec as C+import qualified XRecSpec as X++import qualified Test.ARec as ARec++-- d1 :: FieldRec '[ '("X",String), '("Y", String) ]+-- d1 = Field @"X" "5" :& Field @"Y" "Hi" :& RNil++-- d2 :: FieldRec '[ '("X", String -> Int), '("Y", String -> String) ]+-- d2 = Field @"X" (read :: String -> Int)+-- :& Field @"Y" (id :: String -> String)+-- :& RNil++d1' :: Rec (Const String) '[ '("x", Int), '("y", String) ]+d1' = Const "5" :& Const "Hi" :& RNil++d2' :: Rec ((->) String :. ElField) '[ '("x", Int), '("y", String) ]+d2' = Compose (Field . read) :& Compose (Field . id) :& RNil++d3 :: Rec ElField '[ '("x", Int), '("y", String) ]+d3 = rmap (\(Compose f) -> Lift (f . getConst)) d2' <<*>> d1'++main :: IO ()+main = hspec $ do+ C.spec+ X.spec+ describe "Rec is like an Applicative" $ do+ it "Can apply parsing functions" $ d3 `shouldBe` Field 5 :& Field "Hi" :& RNil+ describe "Fields may be accessed by overloaded labels" $ do+ it "Can get field X" $ rvalf #x d3 `shouldBe` 5+ it "Can get field Y" $ rvalf #y d3 `shouldBe` "Hi"+ describe "ARec provides field accessors" $ do+ it "Can get field Y" $ rvalf #y (toARec d3) `shouldBe` "Hi"+ it "Can set field X" $ rvalf #x (rputf #x 7 (toARec d3)) `shouldBe` 7+ describe "Converting between Rec and ARec" $ do+ it "Can go back and forth" $+ rvalf #y (toARec (#y %~ (show . length) $+ fromARec (rputf #x 7 (toARec d3))))+ `shouldBe` "2"+ describe "Converting between Rec and SRec" $ do+ it "Can go back and forth" $+ let d4 = #x =:= 5 <+> #y =:= 4 :: FieldRec '[ '("x",Int), '("y",Int)]+ isqrt = floor . (sqrt :: Double -> Double) . fromIntegral :: Int -> Int+ in rvalf #y (toSRec (#y %~ isqrt $+ fromSRec (rputf #x 7 (toSRec d4))))+ `shouldBe` 2++ describe "Produces field lenses from overloaded labels" $ do+ it "Can invert a boolean field" $ do+ (fieldRec (#x =: True, #y =: 'b') & #x %~ not)+ `shouldBe` fieldRec (#x =: False, #y =: 'b')+ describe "Supports tuple construction" $ do+ it "Can build ElField records from tuples" $+ fieldRec (#x =: 5, #y =: "Hi") `shouldBe` d3+ it "Can build Recs of Maybe values" $+ record @Maybe (Just True, Just 'a') `shouldBe` Just True :& Just 'a' :& RNil+ it "Can build Recs of Const values" $+ record @(Const String) ( Const "howdy" :: Const String Int+ , Const "folks" :: Const String Double)+ `shouldBe` Const "howdy" :& Const "folks" :& RNil+ describe "Can change the types of individual fields" $ do+ it "Can set a field with a different type" $+ (#x .~ 2.1) d3 `shouldBe` fieldRec (#x =: 2.1, #y =: "Hi")+ it "Can change a field's type" $+ (d3 & #y %~ length) `shouldBe` fieldRec (#x =: 5, #y =: 2)++ ARec.spec
+ tests/Test/ARec.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE DataKinds, FlexibleContexts, GADTs,+ NoMonomorphismRestriction, OverloadedLabels,+ ScopedTypeVariables, TypeApplications, TypeOperators #-}+{-# OPTIONS_GHC -Wall -Wno-type-defaults #-}++module Test.ARec where++import Data.Vinyl.ARec+import Data.Vinyl+import Test.Hspec++import Data.Vinyl.Syntax ()++type FullARec = ARec ElField '[ "f0" ::: Int , "f1" ::: Bool , "f2" ::: String+ , "f3" ::: Double, "f4" ::: Integer+ , "f2" ::: Int -- intentionally duplicate field name+ ]++type SubARecPre = ARec ElField '[ "f0" ::: Int , "f1" ::: Bool , "f2" ::: String ]++type SubARecDupes = ARec ElField '[ "f2" ::: String, "f2" ::: String+ , "f2" ::: Int, "f2" ::: String+ ]+++fullARec :: FullARec+fullARec = toARec ( #f0 =: 1 :& #f1 =: False :& #f2 =: "field2"+ :& #f3 =: 3.1415 :& #f4 =: 4444+ :& #f2 =: 666+ :& RNil+ )++-- For arecGetSubset -----------------------------------------------------------++subARecPre :: SubARecPre+subARecPre = toARec ( #f0 =: 1 :& #f1 =: False :& #f2 =: "field2" :& RNil)++subARecDupes :: SubARecDupes+subARecDupes = toARec ( #f2 =: "field2" :& #f2 =: "field2"+ :& #f2 =: 666 :& #f2 =: "field2"+ :& RNil+ )++arecWithDupes :: ARec ElField '[ "f" ::: Int, "f" ::: Int]+arecWithDupes = toARec (#f =: 1 :& #f =: 2 :& RNil)++-- For arecSetSubset -----------------------------------------------------------++subARecPreSet :: SubARecPre+subARecPreSet = toARec ( #f0 =: 11 :& #f1 =: True :& #f2 =: "field2-updated" :& RNil)++fullARecUpdated :: FullARec+fullARecUpdated = toARec ( #f0 =: 11 :& #f1 =: True :& #f2 =: "field2-updated"+ :& #f3 =: 3.1415 :& #f4 =: 4444+ :& #f2 =: 666+ :& RNil+ )++updateARecWithDupes :: ARec ElField '[ '("f0", Int), '("f0", Int), '("f0", Int)]+updateARecWithDupes = toARec (#f0 =: 3 :& #f0 =: 66 :& #f0 =: 1 :&RNil)++subARecDupesUpdated :: SubARecDupes+subARecDupesUpdated = toARec ( #f2 =: "updated" :& #f2 =: "field2"+ :& #f2 =: 666 :& #f2 =: "field2"+ :& RNil+ )++++spec :: SpecWith ()+spec = describe "ARec" $ do+ describe "arecGetSubset" $ do+ it "retrieves a prefix ARec" $+ -- The part to be retrieved is type-directed+ arecGetSubset fullARec `shouldBe` subARecPre+ it "retrieves the full ARec" $ do+ -- Should catch off-by-one errors that lead to overflow+ arecGetSubset fullARec `shouldBe` fullARec+ it "handles an empty subARec correctly" $+ arecGetSubset fullARec `shouldBe` toARec RNil+ it "handles duplicate field names correctly in the sub arec" $+ arecGetSubset fullARec `shouldBe` subARecDupes+ it "handles duplicate field names correctly in the source arec" $+ -- When both the name and the type of the field match we retrieve from the+ -- first field+ arecGetSubset arecWithDupes `shouldBe` toARec (#f =: (1 :: Int) :& RNil)+ describe "arecSetSubset" $ do+ it "sets a subset of fields" $ do+ arecSetSubset fullARec subARecPreSet `shouldBe` fullARecUpdated+ it "handles updates to every field" $ do+ -- Should catch off-by-one errors that lead to overflow+ arecSetSubset fullARec fullARec `shouldBe` fullARec+ it "handles an empty subset" $ do+ arecSetSubset fullARec (toARec RNil) `shouldBe` fullARec+ it "handles duplicates in the updating ARec" $ do+ -- The behaviour here should be that the _last_ updating field prevails+ arecSetSubset fullARec updateARecWithDupes `shouldBe` fullARec+ it "handles updatees with duplicate fields" $ do+ -- Here, only the _first_ field should be updated+ arecSetSubset subARecDupes (toARec (#f2 =: "updated" :& RNil))+ `shouldBe` subARecDupesUpdated
+ tests/XRecSpec.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE DataKinds, FlexibleContexts, OverloadedLabels,+ TypeApplications, TypeOperators, ViewPatterns #-}+module XRecSpec (spec) where+import Data.Vinyl+import Data.Vinyl.FromTuple (namedArgs, ruple, xrec, fieldRec, withDefaults)+import Data.Vinyl.Functor ((:.))+import Data.Vinyl.XRec (rgetX)+import Test.Hspec (SpecWith, describe, it, shouldBe)++-- | A function that takes named parameters.+foo :: FieldRec '["name" ::: String, "age" ::: Int] -> Int+foo (ruple -> (name, age)) = length name + age++-- | Like 'foo', but has default values for each parameter.+foo' :: (RMap ss, ss ⊆ '["name" ::: String, "age" ::: Int])+ => Rec ElField ss -> Int+foo' = foo . withDefaults defs+ where defs = fieldRec (#name =: "roberta", #age =: (48 :: Int))++spec :: SpecWith ()+spec = do+ describe "Named Arguments" $ do+ it "Can re-order arguments" $+ foo (namedArgs (#age =: (23 :: Int), #name =: "Joe")) `shouldBe` 26+ it "Can pass too many arguments" $+ foo (namedArgs ( #age =: (23 :: Int)+ , #isAwesome =: True+ , #name =: "Cheryl"))+ `shouldBe` 29++ describe "Default arguments" $ do+ it "Can take zero arguments" $+ foo' (fieldRec ()) `shouldBe` 55+ it "Can take a subset of named arguments (1)" $+ foo' (#age =:= (39::Int)) `shouldBe` 46+ it "Can take a subset of named arguments (2)" $+ foo' (#name =:= "Jerome") `shouldBe` 54+ it "Can take all arguments" $+ foo' (fieldRec (#age =: (36::Int), #name =: "Jerome")) `shouldBe` 42++ describe "Can get fields through HKD" $ do+ let myRec :: Rec (Maybe :. ElField) ["name" ::: String, "age" ::: Int]+ myRec = xrec (Just "joe", Just 23)+ it "Can eliminate Compose newtype wrappers" $ do+ rgetX @("age" ::: Int) myRec `shouldBe` Just 23
vinyl.cabal view
@@ -1,19 +1,20 @@ name: vinyl-version: 0.5.1+version: 0.14.3 synopsis: Extensible Records -- description: license: MIT license-file: LICENSE author: Jonathan Sterling-maintainer: jonsterling@me.com+maintainer: acowley@gmail.com -- copyright: category: Records stability: Experimental build-type: Simple cabal-version: >=1.10 extra-source-files: CHANGELOG.md+tested-with: GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.4, GHC == 8.10.4, GHC == 9.0.1, GHC == 9.2.1 -description: Extensible records for Haskell with lenses using modern GHC features.+description: Extensible records for Haskell with lenses. source-repository head type: git@@ -21,27 +22,108 @@ library exposed-modules: Data.Vinyl+ , Data.Vinyl.ARec+ , Data.Vinyl.ARec.Internal+ , Data.Vinyl.ARec.Internal.SmallArray+ , Data.Vinyl.Class.Method , Data.Vinyl.Core+ , Data.Vinyl.CoRec+ , Data.Vinyl.Curry+ , Data.Vinyl.FromTuple , Data.Vinyl.Lens , Data.Vinyl.Derived , Data.Vinyl.TypeLevel , Data.Vinyl.Functor , Data.Vinyl.Notation- build-depends: base >=4.7 && <= 5, ghc-prim+ , Data.Vinyl.Recursive+ , Data.Vinyl.SRec+ , Data.Vinyl.Syntax+ , Data.Vinyl.Tutorial.Overview+ , Data.Vinyl.XRec+ build-depends: base >= 4.11 && <= 5,+ ghc-prim,+ deepseq,+ array+ if impl (ghc < 8.6.0)+ build-depends: constraints >= 0.6.1 default-language: Haskell2010- ghc-options: -fwarn-dodgy-exports -fwarn-dodgy-imports -fwarn-unused-matches -fwarn-unused-imports -fwarn-unused-binds -fwarn-incomplete-record-updates -fwarn-missing-signatures -fwarn-name-shadowing -fwarn-orphans -fwarn-overlapping-patterns -fwarn-tabs -fwarn-type-defaults+ ghc-options: -Wall+ other-extensions: TypeApplications -benchmark bench-builder-all+benchmark storable type: exitcode-stdio-1.0 hs-source-dirs: benchmarks main-is: StorableBench.hs- build-depends: base >= 4.7 && <= 5, vector, criterion, vinyl == 0.5, mwc-random, lens, linear- ghc-options: -O2 -fllvm+ build-depends: base,+ vector,+ criterion,+ vinyl,+ mwc-random,+ microlens,+ linear,+ primitive+ ghc-options: -O2+-- -ddump-to-file -ddump-simpl -dsuppress-module-prefixes -dsuppress-uniques default-language: Haskell2010 -test-suite doctests+benchmark equality type: exitcode-stdio-1.0+ hs-source-dirs: benchmarks+ main-is: EqualityBench.hs+ build-depends: base, criterion, vinyl+ ghc-options: -O2+ default-language: Haskell2010++benchmark accessors+ type: exitcode-stdio-1.0+ hs-source-dirs: benchmarks+ main-is: AccessorsBench.hs+ build-depends: base, criterion, tagged, vinyl, microlens+ other-modules: Bench.ARec+ Bench.SRec+ Bench.Rec+ ghc-options: -O2+ default-language: Haskell2010++benchmark asa+ type: exitcode-stdio-1.0+ hs-source-dirs: benchmarks+ main-is: AsABench.hs+ build-depends: base, criterion, vinyl+ ghc-options: -O2+ default-language: Haskell2010++-- TODO: Use cabal-docspec+-- test-suite doctests+-- type: exitcode-stdio-1.0+-- hs-source-dirs: tests+-- other-modules: Intro+-- main-is: doctests.hs+-- if impl (ghc < 9.0.1)+-- build-depends: base, lens, doctest >= 0.8, singletons >= 0.10 && < 3, vinyl+-- else+-- build-depends: base, lens, doctest >= 0.8, singletons-th >= 3 && < 3.1, vinyl+-- default-language: Haskell2010++test-suite aeson+ type: exitcode-stdio-1.0 hs-source-dirs: tests- main-is: Intro.lhs- build-depends: base >= 4.7 && <= 5, lens, vinyl == 0.5, doctest >= 0.8, singletons >= 0.10+ main-is: Aeson.hs+ build-depends: base, hspec, aeson >= 1.4, text, mtl, vinyl,+ vector, unordered-containers, lens, lens-aeson default-language: Haskell2010++test-suite spec+ type: exitcode-stdio-1.0+ hs-source-dirs: tests+ main-is: Spec.hs+ other-modules: CoRecSpec+ XRecSpec+ Test.ARec+ build-depends: base+ , vinyl+ , microlens+ , hspec+ , should-not-typecheck >= 2.0 && < 2.2+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010