vinyl 0.4.3 → 0.5
raw patch · 21 files changed
+533/−720 lines, 21 filesdep −template-haskelldep ~basedep ~vinyl
Dependencies removed: template-haskell
Dependency ranges changed: base, vinyl
Files
- Data/Vinyl.hs +0/−6
- Data/Vinyl/Constraint.hs +0/−50
- Data/Vinyl/Core.hs +162/−51
- Data/Vinyl/Derived.hs +9/−17
- Data/Vinyl/Functor.hs +75/−17
- Data/Vinyl/Idiom/Identity.hs +0/−25
- Data/Vinyl/Idiom/Thunk.hs +0/−25
- Data/Vinyl/Idiom/Validation.hs +0/−32
- Data/Vinyl/Lens.hs +121/−65
- Data/Vinyl/Notation.hs +10/−10
- Data/Vinyl/Operators.hs +0/−100
- Data/Vinyl/TH.hs +0/−59
- Data/Vinyl/TyFun.hs +0/−14
- Data/Vinyl/TypeLevel.hs +39/−0
- Data/Vinyl/Universe.hs +0/−7
- Data/Vinyl/Universe/Const.hs +0/−13
- Data/Vinyl/Universe/Field.hs +0/−26
- Data/Vinyl/Universe/Id.hs +0/−13
- Data/Vinyl/Witnesses.hs +0/−29
- tests/Intro.lhs +112/−145
- vinyl.cabal +5/−16
Data/Vinyl.hs view
@@ -1,16 +1,10 @@ module Data.Vinyl ( module Data.Vinyl.Core , module Data.Vinyl.Derived- , module Data.Vinyl.Operators , module Data.Vinyl.Lens- , module Data.Vinyl.Witnesses- , module Data.Vinyl.Constraint ) where import Data.Vinyl.Core import Data.Vinyl.Derived-import Data.Vinyl.Operators import Data.Vinyl.Lens-import Data.Vinyl.Constraint-import Data.Vinyl.Witnesses
− Data/Vinyl/Constraint.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}--module Data.Vinyl.Constraint- ( (<:)(..)- , (:~:)- , (~=)- , RecAll- ) where--import Data.Vinyl.Core-import Data.Vinyl.Witnesses-import Data.Vinyl.TyFun-import GHC.Prim (Constraint)---- | One record is a subtype of another if the fields of the latter are a--- subset of the fields of the former.-class (xs :: [k]) <: (ys :: [k]) where- cast :: Rec el f xs -> Rec el f ys--instance xs <: '[] where- cast _ = RNil--instance (y ∈ xs, xs <: ys) => xs <: (y ': ys) where- cast xs = ith (implicitly :: Elem y xs) xs :& cast xs- where- ith :: Elem r rs -> Rec el f rs -> f (el $ r)- ith Here (a :& _) = a- ith (There p) (_ :& as) = ith p as---- | If two records types are subtypes of each other, that means that they--- differ only in order of fields.-type r1 :~: r2 = (r1 <: r2, r2 <: r1)---- | Term-level record congruence.-(~=) :: (Eq (Rec el f xs), xs :~: ys) => Rec el f xs -> Rec el f ys -> Bool-x ~= y = x == (cast y)--type family RecAll (el :: TyFun k l -> *) (f :: * -> *) (rs :: [k]) (c :: * -> Constraint) :: Constraint-type instance RecAll el f '[] c = ()-type instance RecAll el f (r ': rs) c = (c (f (el $ r)), RecAll el f rs c)-
Data/Vinyl/Core.hs view
@@ -1,80 +1,191 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} module Data.Vinyl.Core where -import Data.Vinyl.TyFun-import Control.Applicative import Data.Monoid-import Data.Vinyl.Idiom.Identity import Foreign.Ptr (castPtr, plusPtr) import Foreign.Storable (Storable(..))+import Data.Vinyl.Functor+import Control.Applicative hiding (Const(..))+import Data.Typeable (Proxy(..))+import Data.List (intercalate)+import Data.Vinyl.TypeLevel --- | A record is parameterized by a universe @u@, list of rows @rs@, a large--- elimination @el@, and a type constructor @f@ to be applied to the--- interpretation @el r@ of each of those @r@.-data Rec (el :: TyFun u * -> *) (f :: * -> *) (rrs :: [u]) where- RNil :: Rec el f '[]- (:&) :: !(f (el $ r)) -> !(Rec el f rs) -> Rec el f (r ': rs)+-- | 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+-- inhabitants of the kind @u@; the type of values at any label @r :: u@ is+-- given by its interpretation @f r :: *@.+data Rec :: (u -> *) -> [u] -> * where+ RNil :: Rec f '[]+ (:&) :: !(f r) -> !(Rec f rs) -> Rec f (r ': rs)+ infixr :&+infixr 5 <+>+infixl 8 <<$>>+infixl 8 <<*>> --- | Shorthand for a record with a single field. Lifts the field's--- value into the chosen functor automatically.-(=:) :: Applicative f => sing k -> el $ k -> Rec el f '[ k ]-_ =: x = pure x :& RNil+-- | 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) --- | Shorthand for a record with a single field. This is useful for--- @Applicative@ or @Monad@ic intialization of records as in the idiom:------ > dist $ myField <-: someIO <+> yourField <-: otherIO-(<-:) :: sing r -> f (el $ r) -> Rec el f '[r]-_ <-: x = x :& RNil-infixr 6 <-:+-- | A shorthand for 'rappend'.+(<+>)+ :: Rec f as+ -> Rec f bs+ -> Rec f (as ++ bs)+(<+>) = rappend --- | Records constructed using the above combinators will often be polymorphic--- in their interpreter @el@. To avoid providing a type annotation, one can--- provide their interpreters with a singleton tag and pass that in.-withUniverse :: (forall x. el x) -> Rec el f rs -> Rec el f rs-withUniverse _ x = x-{-# INLINE withUniverse #-}+-- | '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 #-} -instance Monoid (Rec el f '[]) where+-- | 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 (<<*>>) #-}++-- | Given a section of some functor, records in that functor of any size are+-- inhabited.+class RecApplicative rs where+ rpure+ :: (forall x. f x)+ -> Rec f rs+instance RecApplicative '[] where+ rpure _ = RNil+ {-# INLINE rpure #-}+instance RecApplicative rs => RecApplicative (r ': rs) where+ rpure s = s :& rpure s+ {-# INLINE rpure #-}++-- | 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++-- | 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++-- | Wrap up a value with a capability given by its type+data Dict c a where+ Dict+ :: c a+ => a+ -> Dict c a++-- | 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++-- | 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+ show xs =+ (\str -> "{" <> str <> "}")+ . intercalate "; "+ . recordToList+ . rmap (\(Compose (Dict x)) -> Const $ show x)+ $ reifyConstraint (Proxy :: Proxy Show) xs++instance Monoid (Rec f '[]) where mempty = RNil RNil `mappend` RNil = RNil -instance (Monoid (el $ r), Monoid (Rec el f rs), Applicative f) => Monoid (Rec el f (r ': rs)) where- mempty = pure mempty :& mempty- (x :& xs) `mappend` (y :& ys) = liftA2 mappend x y :& (xs `mappend` ys)+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) -instance Eq (Rec el f '[]) where+instance Eq (Rec f '[]) where _ == _ = True-instance (Eq (f (el $ r)), Eq (Rec el f rs)) => Eq (Rec el f (r ': rs)) where+instance (Eq (f r), Eq (Rec f rs)) => Eq (Rec f (r ': rs)) where (x :& xs) == (y :& ys) = (x == y) && (xs == ys) -instance Storable (Rec el Identity '[]) where+instance Storable (Rec f '[]) where sizeOf _ = 0 alignment _ = 0 peek _ = return RNil poke _ RNil = return () -instance (Storable (el $ r), Storable (Rec el Identity rs)) => Storable (Rec el Identity (r ': rs)) where- sizeOf _ = sizeOf (undefined :: el $ r) + sizeOf (undefined :: Rec el Identity rs)+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 #-}- alignment _ = alignment (undefined :: el $ r)+ alignment _ = alignment (undefined :: f r) {-# INLINABLE alignment #-} peek ptr = do !x <- peek (castPtr ptr)- !xs <- peek (ptr `plusPtr` sizeOf (undefined :: el $ r))- return $ Identity x :& xs+ !xs <- peek (ptr `plusPtr` sizeOf (undefined :: f r))+ return $ x :& xs {-# INLINABLE peek #-}- poke ptr (Identity !x :& xs) = poke (castPtr ptr) x >>- poke (ptr `plusPtr` sizeOf (undefined :: el $ r)) xs+ poke ptr (!x :& xs) = poke (castPtr ptr) x >> poke (ptr `plusPtr` sizeOf (undefined :: f r)) xs {-# INLINEABLE poke #-}-
Data/Vinyl/Derived.hs view
@@ -1,26 +1,18 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} module Data.Vinyl.Derived where import Data.Vinyl.Core-import qualified Data.Vinyl.Idiom.Identity as I-import qualified Data.Vinyl.Idiom.Thunk as I-import qualified Data.Vinyl.Universe as U--import Control.Applicative--type PlainRec el = Rec el I.Identity-type LazyPlainRec el = Rec el I.Thunk-type FieldRec = Rec U.ElField-type PlainFieldRec = Rec U.ElField I.Identity-type HList = Rec U.Id I.Identity-type LazyHList = Rec U.Id I.Thunk+import Data.Vinyl.Functor --- | Fixes a polymorphic record into the 'Identity' functor.-toPlainRec :: (forall f. Applicative f => Rec el f rs) -> PlainRec el rs-toPlainRec xs = xs+import GHC.TypeLits -toLazyPlainRec :: (forall f. Applicative f => Rec el f rs) -> LazyPlainRec el rs-toLazyPlainRec xs = xs+data ElField (field :: (Symbol, *)) where+ Field :: KnownSymbol s => t -> ElField '(s,t) +type FieldRec = Rec ElField+type HList = Rec Identity+type LazyHList = Rec Thunk
Data/Vinyl/Functor.hs view
@@ -1,14 +1,85 @@-{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeOperators #-} module Data.Vinyl.Functor where import Control.Applicative+import Data.Foldable+import Data.Traversable+import Foreign.Storable -class Presheaf f where- contramap :: (a -> b) -> (f b -> f a)+newtype Identity a+ = Identity { getIdentity :: a }+ deriving ( Functor+ , Foldable+ , Traversable+ , Storable+ ) -newtype Lift op f g x = Lift { runLift :: op (f x) (g x) }+data Thunk a+ = Thunk { getThunk :: a }+ deriving ( Functor+ , Foldable+ , Traversable+ ) +newtype Lift (op :: l -> l' -> *) (f :: k -> l) (g :: k -> l') (x :: k)+ = Lift { getLift :: op (f x) (g x) }++newtype Compose (f :: l -> *) (g :: k -> l) (x :: k)+ = Compose { getCompose :: f (g x) }+ deriving (Storable)++type f :. g = Compose f g++newtype Const (a :: *) (b :: k)+ = Const { getConst :: a }+ deriving ( Functor+ , Foldable+ , Traversable+ , Storable+ )++instance (Functor f, Functor g) => Functor (Compose f g) where+ fmap f (Compose x) = Compose (fmap (fmap f) x)++instance (Foldable f, Foldable g) => Foldable (Compose f g) where+ foldMap f (Compose t) = foldMap (foldMap f) t++instance (Traversable f, Traversable g) => Traversable (Compose f g) where+ traverse f (Compose t) = Compose <$> traverse (traverse f) t++instance (Applicative f, Applicative g) => Applicative (Compose f g) where+ pure x = Compose (pure (pure x))+ Compose f <*> Compose x = Compose ((<*>) <$> f <*> x)++instance Applicative Identity where+ pure = Identity+ Identity f <*> Identity x = Identity (f x)++instance Monad Identity where+ return = Identity+ Identity x >>= f = f x++instance Show a => Show (Identity a) where+ show (Identity x) = show x++instance Applicative Thunk where+ pure = Thunk+ (Thunk f) <*> (Thunk x) = Thunk (f x)++instance Monad Thunk where+ return = Thunk+ (Thunk x) >>= f = f x++instance Show a => Show (Thunk a) where+ show (Thunk x) = show x+ instance (Functor f, Functor g) => Functor (Lift (,) f g) where fmap f (Lift (x, y)) = Lift (fmap f x, fmap f y) @@ -16,20 +87,7 @@ fmap f (Lift (Left x)) = Lift . Left . fmap f $ x fmap f (Lift (Right x)) = Lift . Right . fmap f $ x -instance (Presheaf f, Presheaf g) => Presheaf (Lift (,) f g) where- contramap f (Lift (x, y)) = Lift (contramap f x, contramap f y)--instance (Presheaf f, Presheaf g) => Presheaf (Lift Either f g) where- contramap f (Lift (Left x)) = Lift . Left . contramap f $ x- contramap f (Lift (Right x)) = Lift . Right . contramap f $ x- instance (Applicative f, Applicative g) => Applicative (Lift (,) f g) where pure x = Lift (pure x, pure x) Lift (f, g) <*> Lift (x, y) = Lift (f <*> x, g <*> y)--instance (Presheaf f, Functor g) => Functor (Lift (->) f g) where- fmap f (Lift ηx) = Lift $ fmap f . ηx . contramap f--instance (Functor f, Presheaf g) => Presheaf (Lift (->) f g) where- contramap f (Lift ηx) = Lift $ contramap f . ηx . fmap f
− Data/Vinyl/Idiom/Identity.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveTraversable #-}--module Data.Vinyl.Idiom.Identity where--import Control.Applicative-import Data.Foldable-import Data.Traversable--newtype Identity a- = Identity- { runIdentity :: a- } deriving (Functor, Foldable, Traversable)--instance Applicative Identity where- pure = Identity- (Identity f) <*> (Identity x) = Identity (f x)--instance Monad Identity where- return = Identity- (Identity x) >>= f = f x--instance Show a => Show (Identity a) where- show (Identity x) = show x
− Data/Vinyl/Idiom/Thunk.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveTraversable #-}--module Data.Vinyl.Idiom.Thunk where--import Control.Applicative-import Data.Foldable-import Data.Traversable--data Thunk a- = Thunk- { runThunk :: a- } deriving (Functor, Foldable, Traversable)--instance Applicative Thunk where- pure = Thunk- (Thunk f) <*> (Thunk x) = Thunk (f x)--instance Monad Thunk where- return = Thunk- (Thunk x) >>= f = f x--instance Show a => Show (Thunk a) where- show (Thunk x) = show x
− Data/Vinyl/Idiom/Validation.hs
@@ -1,32 +0,0 @@-{-# LANGUAGE TypeOperators #-}--module Data.Vinyl.Idiom.Validation where--import Data.Vinyl.Idiom.Identity-import Data.Vinyl.Functor--import Control.Applicative-import Data.Monoid---- | A type which is similar to 'Either', except that it has a--- slightly different Applicative instance.-data Result e a- = Failure e- | Success a- deriving (Show, Eq)---- | Validators transform identities into results.-type Validator e = Lift (->) Identity (Result e)--instance Functor (Result e) where- fmap f (Success x) = Success $ f x- fmap _ (Failure e) = Failure e---- | The 'Applicative' instance to 'Result' relies on its error type--- being a 'Monoid'. That way, it can accumulate errors.-instance Monoid e => Applicative (Result e) where- pure = Success- (Success f) <*> (Success x) = Success $ f x- (Failure e) <*> (Success _) = Failure e- (Success _) <*> (Failure e) = Failure e- (Failure e) <*> (Failure e') = Failure $ e <> e'
Data/Vinyl/Lens.hs view
@@ -1,82 +1,138 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GADTs #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} --- | A small, /en passant/ lens implementation to provide accessors--- for record fields. Lenses produced with 'rLens' are fully--- compatible with the @lens@ package.-module Data.Vinyl.Lens where+module Data.Vinyl.Lens+ ( RElem(..)+ , RSubset(..)+ , REquivalent+ , type (∈)+ , type (⊆)+ , type (≅)+ , type (<:)+ , type (:~:)+ ) where import Data.Vinyl.Core-import Data.Vinyl.Derived-import Data.Vinyl.TyFun-import Data.Vinyl.Witnesses-import Data.Vinyl.Idiom.Identity+import Data.Vinyl.Functor+import Data.Vinyl.TypeLevel+import Data.Typeable (Proxy(..)) -import Control.Applicative+-- | 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 --- | Project a field from a 'Rec'.-rGet' :: (r ∈ rs) => sing r -> Rec el f rs -> f (el $ r)-rGet' r = getConst . rLens' r Const-{-# INLINE rGet' #-}+ -- | 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) --- | Project a field from a 'PlainRec'.-rGet :: (r ∈ rs) => sing r -> PlainRec el rs -> el $ r-rGet = (runIdentity .) . rGet'-{-# INLINE rGet #-}+ -- | For Vinyl users who are not using the @lens@ package, we provide a getter.+ rget+ :: sing r+ -> Rec f rs+ -> f r+ rget k = getConst . rlens k Const --- | Set a field in a 'Rec' over an arbitrary functor.-rPut' :: (r ∈ rs) => sing r -> f (el $ r) -> Rec el f rs -> Rec el f rs-rPut' r x = runIdentity . rLens' r (Identity . const x)-{-# INLINE rPut' #-}+ -- | 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) --- | Set a field in a 'PlainRec'.-rPut :: (r ∈ rs) => sing r -> el $ r -> PlainRec el rs -> PlainRec el rs-rPut r x = rPut' r (Identity x)-{-# INLINE rPut #-}+-- This is an internal convenience stolen from the @lens@ library.+lens+ :: Functor f+ => (t -> s)+ -> (t -> a -> b)+ -> (s -> f a)+ -> t+ -> f b+lens sa sbt afb s = fmap (sbt s) $ afb (sa s)+{-# INLINE lens #-} --- | Modify a field.-rMod :: (r ∈ rs , Functor f) => sing r -> (el $ r -> el $ r) -> Rec el f rs -> Rec el f rs-rMod r f = runIdentity . rLens' r (Identity . fmap f)-{-# INLINE rMod #-}+instance RElem r (r ': rs) Z where+ rlens _ f (x :& xs) = fmap (:& xs) (f x)+ {-# INLINE rlens #-} --- We manually unroll several levels of 'Elem' value traversal to help--- GHC statically index into small records.+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 #-} --- | Provide a lens to a record field. Note that this implementation--- does not support polymorphic update. In the parlance of the @lens@--- package,------ > rLens' :: (r ∈ rs) => Sing r -> Lens' (Rec el f rs) (f (el $ r))-rLens' :: forall r rs f g el sing. (r ∈ rs , Functor g) => sing r -> (f (el $ r) -> g (f (el $ r))) -> Rec el f rs -> g (Rec el f rs)-rLens' _ f = go implicitly- where go :: Elem r rr -> Rec el f rr -> g (Rec el f rr)- go Here (x :& xs) = fmap (:& xs) (f x)- go (There Here) (a :& x :& xs) = fmap ((a :&) . (:& xs)) (f x)- go (There (There Here)) (a :& b :& x :& xs) =- fmap (\x' -> a :& b :& x' :& xs) (f x)- go (There (There (There Here))) (a :& b :& c :& x :& xs) =- fmap (\x' -> a :& b :& c :& x' :& xs) (f x)- go (There (There (There (There Here)))) (a :& b :& c :& d :& x :& xs) =- fmap (\x' -> a :& b :& c :& d :& x' :& xs) (f x)- go (There (There (There (There p)))) (a :& b :& c :& d :& xs) =- fmap (\xs' -> a :& b :& c :& d :& xs') (go' p xs)- {-# INLINE go #-}+-- | 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 - go' :: Elem r rr -> Rec el f rr -> g (Rec el f rr)- go' Here (x :& xs) = fmap (:& xs) (f x)- go' (There p) (x :& xs) = fmap (x :&) (go p xs)- {-# INLINABLE go' #-}-{-# INLINE rLens' #-}+ -- | 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) --- | A lens into a 'PlainRec' that smoothly interoperates with lenses--- from the @lens@ package. Note that polymorphic update is not--- supported. In the parlance of the @lens@ package,------ > rLens :: (r ∈ rs) => sing r -> Lens' (PlainRec el rs) (el $ r)-rLens :: forall r rs g el sing. (r ∈ rs , Functor g) => sing r -> (el $ r -> g (el $ r)) -> PlainRec el rs -> g (PlainRec el rs)-rLens r = rLens' r . lenser runIdentity (const Identity)- where lenser sa sbt afb s = sbt s <$> afb (sa s)-{-# INLINE rLens #-}+ -- | 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 #-}++ -- | 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 #-}++instance RSubset '[] ss '[] where+ rsubset = lens (const RNil) const++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+ where+ set :: Rec f ss -> Rec f (r ': rs) -> Rec f ss+ set ss (r :& rs) = rput r $ rreplace 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)++-- | A shorthand for 'RElem' which supplies its index.+type r ∈ rs = RElem r rs (RIndex r rs)++-- | A shorthand for 'RSubset' which supplies its image.+type rs ⊆ ss = RSubset rs ss (RImage rs ss)++-- | A shorthand for 'REquivalent' which supplies its images.+type rs ≅ ss = REquivalent rs ss (RImage rs ss) (RImage ss rs)++-- | A non-unicode equivalent of @(⊆)@.+type rs <: ss = rs ⊆ ss++-- | A non-unicode equivalent of @(≅)@.+type rs :~: ss = rs ≅ ss
Data/Vinyl/Notation.hs view
@@ -1,17 +1,17 @@+{-# LANGUAGE ExplicitNamespaces #-}+ module Data.Vinyl.Notation- ( (:~:)- , (<-:)- , (<:)()- , (<+>)+ ( (<+>) , (<<*>>) , (<<$>>)- , (=:)- , (~=)+ , (<<&>>) , Rec((:&))- , Semantics((:~>))+ , type (∈)+ , type (⊆)+ , type (≅)+ , type (<:)+ , type (:~:) ) where -import Data.Vinyl.Constraint import Data.Vinyl.Core-import Data.Vinyl.Operators-import Data.Vinyl.TH+import Data.Vinyl.Lens
− Data/Vinyl/Operators.hs
@@ -1,100 +0,0 @@-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}--module Data.Vinyl.Operators- ( (<<$>>)- , (<<*>>)- , (<+>)- , type (++)- , RecApplicative(rpure)- , rtraverse- , rdist- , rdistLazy- , FoldRec(foldRec)- , recToList- , showWithNames- , rshow- ) where--import Data.Vinyl.Core-import Data.Vinyl.Functor-import Data.Vinyl.TyFun-import Data.Vinyl.Witnesses-import Data.Vinyl.Constraint-import Data.Vinyl.Derived-import qualified Data.Vinyl.Idiom.Identity as I-import qualified Data.Vinyl.Idiom.Thunk as I-import qualified Data.Vinyl.Universe.Const as U--import Control.Applicative-import qualified Data.List as L (intercalate)---- | Append for records.-(<+>) :: Rec el f as -> Rec el f bs -> Rec el f (as ++ bs)-RNil <+> xs = xs-(x :& xs) <+> ys = x :& (xs <+> ys)-infixr 5 <+>---- | Append for type-level lists.-type family (as :: [k]) ++ (bs :: [k]) :: [k]-type instance '[] ++ bs = bs-type instance (a ': as) ++ bs = a ': (as ++ bs)--(<<$>>) :: (forall x. f x -> g x) -> Rec el f rs -> Rec el g rs-_ <<$>> RNil = RNil-eta <<$>> x :& xs = eta x :& (eta <<$>> xs)-infixl 8 <<$>>-{-# INLINE (<<$>>) #-}--(<<*>>) :: Rec el (Lift (->) f g) rs -> Rec el f rs -> Rec el g rs-RNil <<*>> RNil = RNil-f :& fs <<*>> x :& xs = runLift f x :& (fs <<*>> xs)-infixl 8 <<*>>-{-# INLINE (<<*>>) #-}--class RecApplicative rs where- rpure :: (forall x. f x) -> Rec el f rs-instance RecApplicative '[] where- rpure _ = RNil-instance RecApplicative rs => RecApplicative (f ': rs) where- rpure s = s :& rpure s--class FoldRec r a where- foldRec :: (a -> b -> b) -> b -> r -> b-instance FoldRec (Rec el f '[]) a where- foldRec _ z RNil = z-instance (t ~ (el $ r), FoldRec (Rec el f rs) (f t)) => FoldRec (Rec el f (r ': rs)) (f t) where- foldRec f z (x :& xs) = f x (foldRec f z xs)---- | Accumulates a homogenous record into a list-recToList :: FoldRec (Rec el f rs) (f t) => Rec el f rs -> [f t]-recToList = foldRec (\e a -> [e] ++ a) []--rtraverse :: Applicative h => (forall x. f x -> h (g x)) -> Rec el f rs -> h (Rec el g rs)-rtraverse _ RNil = pure RNil-rtraverse f (x :& xs) = (:&) <$> f x <*> rtraverse f xs--rdist :: Applicative f => Rec el f rs -> f (PlainRec el rs)-rdist = rtraverse $ fmap I.Identity--rdistLazy :: Applicative f => Rec el f rs -> f (LazyPlainRec el rs)-rdistLazy = rtraverse $ fmap I.Thunk--showWithNames :: RecAll el f rs Show => PlainRec (U.Const String) rs -> Rec el f rs -> String-showWithNames names rec = "{ " ++ L.intercalate ", " (go names rec []) ++ " }"- where- go :: RecAll el f rs Show => PlainRec (U.Const String) rs -> Rec el f rs -> [String] -> [String]- go RNil RNil ss = ss- go (I.Identity n :& ns) (x :& xs) ss = (n ++ " =: " ++ show x) : go ns xs ss--rshow :: (Implicit (PlainRec (U.Const String) rs), RecAll el f rs Show) => Rec el f rs -> String-rshow = showWithNames implicitly-
− Data/Vinyl/TH.hs
@@ -1,59 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE ExistentialQuantification #-}-{-# LANGUAGE FlexibleInstances #-}--module Data.Vinyl.TH- ( makeUniverse- , makeUniverse'- , Semantics(..)- , semantics- ) where--import Language.Haskell.TH-import Data.Vinyl.TyFun--makeUniverse :: Name -> Q [Dec]-makeUniverse u = makeUniverse' u ("El" ++ nameBase u)--makeUniverse' :: Name -> String -> Q [Dec]-makeUniverse' u elName = do- let elu = mkName elName- u' <- conT u-- tvs <- do- el <- newName "el"- tyfun <- conT ''TyFun- return [KindedTV el (AppT (AppT tyfun u') StarT)]-- let cons = [NormalC elu []]- return [DataD [] elu tvs cons []]--class TyRep r where- asType :: r -> TypeQ-instance TyRep Name where- asType = conT-instance TyRep (Q Type) where- asType = id--data Semantics = forall s t. (TyRep t, TyRep s) => t :~> s--semantics :: Name -> [Semantics] -> Q [Dec]-semantics elu sems = sequence (map inst sems)- where- inst :: Semantics -> Q Dec- inst (u :~> t) = do- elu' <- conT elu- u' <- asType u- t' <- asType t- return $ TySynInstD ''App-#if __GLASGOW_HASKELL__ > 707- (TySynEqn [elu',u'] t')-#else- [elu',u'] t'-#endif
− Data/Vinyl/TyFun.hs
@@ -1,14 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}--module Data.Vinyl.TyFun where--data TyFun :: * -> * -> *-type family App (f :: TyFun k l -> *) (a :: k) :: l--data TC :: (k -> *) -> TyFun k * -> *-type instance App (TC t) x = t x-type f $ x = App f x-
+ Data/Vinyl/TypeLevel.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++module Data.Vinyl.TypeLevel where++import GHC.Exts++-- | 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++-- | 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)++-- | 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++-- | 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 = ()+ RecAll f (r ': rs) c = (c (f r), RecAll f rs c)++-- | Append for type-level lists.+type family (as :: [k]) ++ (bs :: [k]) :: [k] where+ '[] ++ bs = bs+ (a ': as) ++ bs = a ': (as ++ bs)+
− Data/Vinyl/Universe.hs
@@ -1,7 +0,0 @@-module Data.Vinyl.Universe- ( module Data.Vinyl.Universe.Id- , module Data.Vinyl.Universe.Field- ) where--import Data.Vinyl.Universe.Id-import Data.Vinyl.Universe.Field
− Data/Vinyl/Universe/Const.hs
@@ -1,13 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeFamilies #-}--module Data.Vinyl.Universe.Const (Const(..)) where--import Data.Vinyl.TyFun--data Const :: * -> (TyFun k *) -> * where- Const :: Const t el--type instance App (Const t) x = t
− Data/Vinyl/Universe/Field.hs
@@ -1,26 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}--module Data.Vinyl.Universe.Field where--import Data.Vinyl.TyFun-import GHC.TypeLits--data (sy :: k) ::: (t :: *)--#if __GLASGOW_HASKELL__ > 707-data SField :: * -> * where- SField :: KnownSymbol sy => SField (sy ::: t)-#else-data SField :: * -> * where- SField :: SingE (Kind :: Symbol) str => SField (sy ::: t)-#endif--data ElField :: (TyFun * *) -> * where- ElField :: ElField el-type instance App ElField (sy ::: t) = t
− Data/Vinyl/Universe/Id.hs
@@ -1,13 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeFamilies #-}--module Data.Vinyl.Universe.Id (Id(..)) where--import Data.Vinyl.TyFun--data Id :: (TyFun k k) -> * where- Id :: Id el-type instance App Id x = x-
− Data/Vinyl/Witnesses.hs
@@ -1,29 +0,0 @@-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverlappingInstances #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeOperators #-}--module Data.Vinyl.Witnesses where--class Implicit p where- implicitly :: p---- | An inductive list membership proposition.-data Elem :: k -> [k] -> * where- Here :: Elem x (x ': xs)- There :: Elem x xs -> Elem x (y ': xs)---- | A constraint for implicit resolution of list membership proofs.-type IElem x xs = Implicit (Elem x xs)-type x ∈ xs = IElem x xs--instance Implicit (Elem x (x ': xs)) where- implicitly = Here-instance Implicit (Elem x xs) => Implicit (Elem x (y ': xs)) where- implicitly = There implicitly-
tests/Intro.lhs view
@@ -19,17 +19,12 @@ > {-# LANGUAGE DataKinds, PolyKinds, TypeOperators, TypeFamilies #-} > {-# LANGUAGE FlexibleContexts, FlexibleInstances, NoMonomorphismRestriction #-}-> {-# LANGUAGE GADTs, TemplateHaskell, TypeSynonymInstances #-}+> {-# LANGUAGE GADTs, TypeSynonymInstances, TemplateHaskell, StandaloneDeriving #-} > import Data.Vinyl-> import Data.Vinyl.TyFun-> import Data.Vinyl.TH > import Data.Vinyl.Functor-> import Data.Vinyl.Idiom.Identity-> import Data.Vinyl.Idiom.Validation-> import Data.Vinyl.Witnesses-> import qualified Data.Vinyl.Universe.Const as U > import Control.Applicative > import Control.Lens hiding (Identity)+> import Control.Lens.TH > import Data.Char > import Test.DocTest > import Data.Singletons.TH@@ -37,51 +32,47 @@ Let’s define a universe of fields which we want to use: > data Fields = Name | Age | Sleeping | Master deriving Show-> genSingletons [ ''Fields ]-> makeUniverse' ''Fields "ElF"-> semantics ''ElF [ 'Name :~> ''String-> , 'Age :~> ''Int-> , 'Sleeping :~> ''Bool-> ]--Now, let’s try to make an entity that represents a man:--> jon = SName =: "jon"-> <+> SAge =: 20-> <+> SSleeping =: False+> 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 -We could make an alias for the sort of entity that jon is:+> 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 -> type LifeForm = [Name, Age, Sleeping]-> jon :: PlainRec ElF LifeForm+> (=:) :: sing f -> ElF f -> Attr f+> _ =: x = Attr x -We can print out the record by assigning names to each field:+> genSingletons [ ''Fields ] -> instance Implicit (PlainRec (U.Const String) [ Name, Age, Sleeping ]) where-> implicitly = SName =: "name"-> <+> SAge =: "age"-> <+> SSleeping =: "sleeping"+Now, let’s try to make an entity that represents a man: -> -- | >>> rshow jon-> -- "{ name =: \"jon\", age =: 20, sleeping =: False }"+> jon = (SName =: "jon")+> :& (SAge =: 23)+> :& (SSleeping =: False)+> :& RNil -The types are inferred, though, so this is unnecessary unless you’d-like to reuse the type later. Now, make a dog! Dogs are life-forms,-but unlike men, they have masters. So, let’s build my dog:+Automatically, we can show the record: -> semantics ''ElF [ 'Master :~> [t| PlainRec ElF LifeForm |] ]+> -- |+> -- >>> show jon+> -- "{name: \"jon\"; age: 23; sleeping: False}" -> tucker = withUniverse ElF $-> SName =: "tucker"-> <+> SAge =: 7-> <+> SSleeping =: True-> <+> SMaster =: jon+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: -It was necessary to specify the interpreter for the universe in which `tucker`-lives, since (lacking a type annotation), records constructed using `(<+>)` and-`(=:)` are polymorphic with respect to `el`. We can help along the type-inference by giving it explicitly using `withUniverse`.+> tucker = (SName =: "tucker")+> :& (SAge =: 9)+> :& (SSleeping =: True)+> :& (SMaster =: jon)+> :& RNil Using Lenses ------------@@ -92,9 +83,10 @@ on a particular field in the record for access and update, without losing additional information: -> wakeUp :: (Sleeping ∈ fields) => PlainRec ElF fields -> PlainRec ElF fields-> wakeUp = SSleeping `rPut` False +> 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@@ -104,34 +96,29 @@ > jon' = wakeUp jon > -- |-> -- >>> tucker' ^. rLens SSleeping-> -- False+> -- >>> tucker' ^. rlens SSleeping+> -- sleeping: False > ---> -- >>> tucker ^. rLens SSleeping-> -- True+> -- >>> tucker ^. rlens SSleeping+> -- sleeping: True > ---> -- >>> jon' ^. rLens SSleeping-> -- False+> -- >>> 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 :: (Master ∈ fields) => Lens' (PlainRec ElF fields) Bool-> masterSleeping = rLens SMaster . rLens SSleeping-> tucker'' = masterSleeping .~ True $ tucker'-+> masterSleeping = rlens SMaster . unAttr . rlens SSleeping+> tucker'' = masterSleeping .~ (SSleeping =: True) $ tucker' > -- | >>> tucker'' ^. masterSleeping-> -- True--Again, the type annotation is unnecessary.-+> -- sleeping: True Subtyping Relation and Coercion ------------------------------- -A record `PlainRec xs` is a subtype of a record `PlainRec ys` if `ys ⊆ xs`;+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@@ -140,73 +127,80 @@ Therefore, the following works: -> upcastedTucker :: PlainRec ElF LifeForm-> upcastedTucker = cast (toPlainRec tucker)--The reason for using `toPlainRec` will become clear a bit later.+> 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: -< cast :: r1 <: r2 => Rec r1 f -> Rec r2 f+< 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 ------------------------------------- -So far, we’ve been working with the `PlainRec` type; but below that,-there is something a bit more advanced called `Rec`, which looks like-this:+Consider the following declaration: -< data Rec :: (TyFun u * -> *) -> (* -> *) -> [u] -> * where-< RNil :: Rec el f '[]-< (:&) :: f (el $ r) -> Rec el f rs -> Rec el f (r ': rs)+< data Rec :: (u -> *) -> [u] -> * where+< RNil :: Rec f '[]+< (:&) :: f r -> Rec f rs -> Rec f (r ': rs) -The second parameter is a functor, in which every element of the-record will be placed. In `PlainRec`, the functor is just set to-`Identity`. Let’s try and motivate this stuff with an example.+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 a type that’s included here called-`Result e a`, which is similar to `Either`, except that its-`Applicative` instance accumulates monoidal errors on the left.+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 :: PlainRec ElF Person-> goodPerson = SName =: "Jon"-> <+> SAge =: 20-> badPerson = SName =: "J#@#$on"-> <+> SAge =: 20+> goodPerson :: Rec Attr Person+> goodPerson = (SName =: "Jon")+> :& (SAge =: 20)+> :& RNil -> validatePerson :: PlainRec ElF Person -> Result [String] (PlainRec ElF Person)-> validatePerson p = (\n a -> SName =: n <+> SAge =: a) <$> vName <*> vAge where-> vName = validateName (rGet SName p)-> vAge = validateAge (rGet SAge p)+> 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 = Success str-> validateName _ = Failure [ "name must be alphabetic" ]-> validateAge i | i >= 0 = Success i-> validateAge _ = Failure [ "age must be positive" ]+> validateName str | all isAlpha str = Just str+> validateName _ = Nothing+> validateAge i | i >= 0 = Just i+> validateAge _ = Nothing > -- $setup-> -- >>> let isSuccess (Success _) = True; isSuccess _ = False+> -- >>> let isJust (Just _) = True; isJust _ = False > -- |-> -- >>> isSuccess $ validatePerson goodPerson+> -- >>> isJust $ validatePerson goodPerson > -- True > ---> -- >>> isSuccess $ validatePerson badPerson+> -- >>> isJust $ validatePerson badPerson > -- False -The results are as expected (`Success` for `goodPerson`, and a-`Failure` with one error for `badPerson`); but this was not very fun-to build.+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@@ -215,21 +209,19 @@ 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. -Vinyl provides a type of validators, which is the class of functions from the-`Identity` functor to the `Result` functor at some type.--< type Validator e = Lift (->) Identity ~> Result e+> 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 ElF (Validator [String]) Person-> vperson = Lift validateName :& Lift validateAge :& RNil where-> validateName (Identity str) | all isAlpha str = Success str-> validateName _ = Failure [ "name must be alphabetic" ]-> validateAge (Identity i) | i >= 0 = Success i-> validateAge _ = Failure [ "age must be positive" ]-+> 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@@ -238,56 +230,31 @@ > goodPersonResult = vperson <<*>> goodPerson > badPersonResult = vperson <<*>> badPerson -< goodPersonResult === SName :=: Success "Jon", SAge :=: Success 20, {}-< badPersonResult === SName :=: Failure ["name must be alphabetic"], SAge :=: Success 20, {}- > -- |-> -- >>> isSuccess $ goodPersonResult ^. rLens' SName+> -- >>> isJust . getCompose $ goodPersonResult ^. rlens SName > -- True-> -- >>> isSuccess $ goodPersonResult ^. rLens' SAge+> -- >>> isJust . getCompose $ goodPersonResult ^. rlens SAge > -- True-> -- >>> isSuccess $ badPersonResult ^. rLens' SName+> -- >>> isJust . getCompose $ badPersonResult ^. rlens SName > -- False-> -- >>> isSuccess $ badPersonResult ^. rLens' SAge+> -- >>> 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 `Result [String]-(PlainRec Person)`) using `rdist`: -> distGoodPerson = rdist goodPersonResult-> distBadPerson = rdist badPersonResult+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`: -< distGoodPerson === Success name :=: "Jon", age :=: 20, {}-< distBadPerson === Failure ["name must be alphabetic"]+> mgoodPerson :: Maybe (Rec Attr Person)+> mgoodPerson = rtraverse getCompose goodPersonResult +> mbadPerson = rtraverse getCompose badPersonResult+ > -- |-> -- >>> isSuccess distGoodPerson+> -- >>> isJust mgoodPerson > -- True-> -- >>> isSuccess distBadPerson+> -- >>> isJust mbadPerson > -- False -Fixing a polymorphic record into the Identity Functor--------------------------------------------------------If you produced a record using `(=:)` and `(<+>)` without providing a-type annotation, then its type is something like this:--< record :: Applicative f => Rec el f [ <bunch of stuff> ]--The problem is then we can’t do anything with the record that requires-us to know what its functor is. For instance, `cast` will fail. So, we-might try to provide a type annotation, but that can be a bit brittle-and frustrating to have to do. To alleviate this problem, `toPlainRec` is-provided:--< toPlainRec :: (forall f. Applicative f => Rec el f rs) -> PlainRec el rs-------(We must define a main value for doctest to run.)- > main :: IO () > main = doctest ["tests/Intro.lhs"]-
vinyl.cabal view
@@ -1,5 +1,5 @@ name: vinyl-version: 0.4.3+version: 0.5 synopsis: Extensible Records -- description: license: MIT@@ -21,23 +21,12 @@ library exposed-modules: Data.Vinyl , Data.Vinyl.Core- , Data.Vinyl.Operators , Data.Vinyl.Lens , Data.Vinyl.Derived- , Data.Vinyl.Witnesses- , Data.Vinyl.Constraint- , Data.Vinyl.Idiom.Validation- , Data.Vinyl.Idiom.Identity- , Data.Vinyl.Idiom.Thunk- , Data.Vinyl.TyFun+ , Data.Vinyl.TypeLevel , Data.Vinyl.Functor , Data.Vinyl.Notation- , Data.Vinyl.Universe- , Data.Vinyl.Universe.Id- , Data.Vinyl.Universe.Const- , Data.Vinyl.Universe.Field- , Data.Vinyl.TH- build-depends: base >=4.6 && <= 5, ghc-prim, template-haskell >= 2.7.0.0+ build-depends: base >=4.7 && <= 5, ghc-prim 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 @@ -45,7 +34,7 @@ type: exitcode-stdio-1.0 hs-source-dirs: benchmarks main-is: StorableBench.hs- build-depends: base >= 4.6 && <= 5, vector, criterion, vinyl == 0.4.3, mwc-random, lens, linear+ build-depends: base >= 4.7 && <= 5, vector, criterion, vinyl == 0.5, mwc-random, lens, linear ghc-options: -O2 -fllvm default-language: Haskell2010 @@ -53,5 +42,5 @@ type: exitcode-stdio-1.0 hs-source-dirs: tests main-is: Intro.lhs- build-depends: base >= 4.6 && <= 5, lens, vinyl == 0.4.3, doctest >= 0.8, singletons >= 0.10+ build-depends: base >= 4.7 && <= 5, lens, vinyl == 0.5, doctest >= 0.8, singletons >= 0.10 default-language: Haskell2010