packages feed

vinyl 0.5.1 → 0.14.3

raw patch · 33 files changed

Files

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