vinyl 0.1.3 → 0.2
raw patch · 11 files changed
+158/−114 lines, 11 filesdep +transformersdep −mtldep ~basedep ~lens
Dependencies added: transformers
Dependencies removed: mtl
Dependency ranges changed: base, lens
Files
- Data/Vinyl.hs +1/−1
- Data/Vinyl/Classes.hs +8/−5
- Data/Vinyl/Field.hs +9/−0
- Data/Vinyl/Lens.hs +67/−71
- Data/Vinyl/Rec.hs +34/−6
- Data/Vinyl/Relation.hs +5/−6
- Data/Vinyl/Validation.hs +1/−2
- Data/Vinyl/Witnesses.hs +0/−1
- benchmarks/StorableBench.hs +17/−7
- tests/Intro.lhs +11/−10
- vinyl.cabal +5/−5
Data/Vinyl.hs view
@@ -4,7 +4,7 @@ , module Data.Vinyl.Field , module Data.Vinyl.Rec , module Data.Vinyl.Relation- , module Data.Vinyl.Classes,+ , module Data.Vinyl.Classes ) where import Data.Vinyl.Classes
Data/Vinyl/Classes.hs view
@@ -1,22 +1,25 @@ {-# LANGUAGE KindSignatures #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PolyKinds #-}-{-# LANGUAGE Safe #-} {-# LANGUAGE TypeOperators #-} module Data.Vinyl.Classes where import Control.Applicative-import Control.Monad.Identity+import Data.Functor.Identity -- | This class is a generalized, but non-pointed version of 'Applicative'. This -- is useful for types which range over functors rather than sets. class Apply (arr :: k -> k -> k) (f :: k -> *) where (<<*>>) :: f (arr a b) -> f a -> f b --- | To accumulate effects distributed over a data type, you 'run' it.-class Run t where- run :: Applicative f => t f -> f (t Identity)+-- | To accumulate effects distributed over a data type, you 'dist' it.+class Dist t where+ dist :: Applicative f => t f -> f (t Identity)++-- | If a record is homogenous, you can fold over it.+class FoldRec r a where+ foldRec :: (a -> b -> b) -> b -> r -> b -- | '(~>)' is a morphism between functors. newtype (f ~> g) x = NT { runNT :: f x -> g x }
Data/Vinyl/Field.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-}@@ -7,11 +8,19 @@ module Data.Vinyl.Field where +#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 707+import Data.Proxy+#endif import GHC.TypeLits -- | A field contains a key and a type. data (:::) :: Symbol -> * -> * where Field :: sy ::: t +#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 707+instance KnownSymbol sy => Show (sy ::: t) where+ show Field = symbolVal (Proxy :: Proxy sy)+#else instance SingI sy => Show (sy ::: t) where show Field = fromSing (sing :: Sing sy)+#endif
Data/Vinyl/Lens.hs view
@@ -1,84 +1,80 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE NoMonomorphismRestriction #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Data.Vinyl.Lens- ( module Control.Lens- , RLens- , rLens- , rGet- , rPut- , rMod- , RLens'- , rLens'- ) where--import Data.Vinyl.Field-import Data.Vinyl.Rec-import Data.Vinyl.Witnesses--import Control.Lens-import Control.Monad.Identity--type RLens sy t = IElem (sy ::: t) rs => Lens' (PlainRec rs) t-type RLens' f sy t = IElem (sy ::: t) rs => Lens' (Rec rs f) (f t)---- | Generates a lens for a record in the 'Identity' functor.-rLens :: (sy ::: t) -> RLens sy t-rLens f = rLens' f . lens runIdentity (const Identity)-{-# INLINE rLens #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# 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+import Control.Applicative+import Data.Functor.Identity+import Data.Vinyl.Field+import Data.Vinyl.Rec+import Data.Vinyl.Witnesses --- | Generates a lens of a record in an arbitrary functor.-rLens' :: (sy ::: t) -> RLens' f sy t-rLens' f = rLensAux f implicitly-{-# INLINE rLens' #-}+-- | Project a field from a 'Rec'.+rGet' :: IElem (sy ::: t) rs => (sy ::: t) -> Rec rs f -> f t+rGet' r = getConst . rLens' r Const+{-# INLINE rGet' #-} -rGet = view . rLens+-- | Project a field from a 'PlainRec'.+rGet :: IElem (sy ::: t) rs => (sy ::: t) -> PlainRec rs -> t+rGet = (runIdentity .) . rGet' {-# INLINE rGet #-} -rPut = set . rLens+-- | Set a field in a 'Rec' over an arbitrary functor.+rPut' :: IElem (sy ::: t) rs => (sy ::: t) -> f t -> Rec rs f -> Rec rs f+rPut' r x = runIdentity . rLens' r (Identity . const x)+{-# INLINE rPut' #-}++-- | Set a field in a 'PlainRec'.+rPut :: IElem (sy:::t) rs => (sy:::t) -> t -> PlainRec rs -> PlainRec rs+rPut r x = rPut' r (Identity x) {-# INLINE rPut #-} -rMod = over . rLens+-- | Modify a field.+rMod :: (IElem (sy:::t) rs, Functor f)+ => (sy:::t) -> (t -> t) -> Rec rs f -> Rec rs f+rMod r f = runIdentity . rLens' r (Identity . fmap f) {-# INLINE rMod #-} --- We manually unroll several levels of record traversal via 'Elem'--- values to help GHC eliminate the 'Implicit' dictionaries at--- runtime.--{-# INLINE rLensAux #-}-rLensAux :: forall f r sy t rs. (r ~ (sy ::: t))- => r -> Elem r rs -> Lens' (Rec rs f) (f t)-rLensAux _ = go- where goHere :: Elem r rs' -> Lens' (Rec rs' f) (f t)- goHere Here = lens (\(x :& _) -> x) (\(_ :& xs) x -> x :& xs)- goHere _ = error "Unintended base case invocation"+-- We manually unroll several levels of 'Elem' value traversal to help+-- GHC statically index into small records. - go :: Elem r rs' -> Lens' (Rec rs' f) (f t)- go Here = goHere Here- go (There Here) = rLensPrepend $ goHere Here- go (There (There Here)) = rLensPrepend $ rLensPrepend $ goHere Here- go (There (There (There Here))) =- rLensPrepend $ rLensPrepend $ rLensPrepend $ goHere Here- go (There (There (There (There Here)))) =- rLensPrepend $ rLensPrepend $ rLensPrepend $ rLensPrepend $ goHere Here- go (There (There (There (There p)))) =- rLensPrepend $ rLensPrepend $ rLensPrepend $ rLensPrepend $ go' p+-- | Provide a lens to a record field. Note that this implementation+-- does not support polymorphic update. In the parlance of the @lens@+-- package,+--+-- > rLens' :: IElem (sy:::t) rs => (sy:::t) -> Lens' (Rec rs f) (f t)+rLens' :: forall r rs sy t f g. (r ~ (sy:::t), IElem r rs, Functor g)+ => r -> (f t -> g (f t)) -> Rec rs f -> g (Rec rs f)+rLens' _ f = go implicitly+ where go :: Elem r rr -> Rec rr f -> g (Rec rr f)+ 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 #-} - go' :: Elem r rs' -> Lens' (Rec rs' f) (f t)- go' Here = goHere Here- go' (There p) = rLensPrepend $ go p+ go' :: Elem r rr -> Rec rr f -> g (Rec rr f)+ go' Here (x :& xs) = fmap (:& xs) (f x)+ go' (There p) (x :& xs) = fmap (x :&) (go p xs) {-# INLINABLE go' #-}--rLensPrepend :: Lens' (Rec rs f) (f t) -> Lens' (Rec (l ': rs) f) (f t)-rLensPrepend l = lens (\(_ :& xs) -> view l xs) (\(a :& xs) x -> a :& (set l x xs))-{-# INLINE rLensPrepend #-}---- rLens' _ Here = lens (\(x :& xs) -> runIdentity x) (\(_ :& xs) x -> Identity x :& xs)--- rLens' f (There p) = rLensPrepend $ rLens' f p+{-# INLINE rLens' #-} +-- | 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 :: IElem (sy:::t) rs => (sy:::t) -> Lens' (PlainRec rs) t+rLens :: forall r rs sy t g. (r ~ (sy:::t), IElem r rs, Functor g)+ => r -> (t -> g t) -> PlainRec rs -> g (PlainRec rs)+rLens r = rLens' r . lenser runIdentity (const Identity)+ where lenser sa sbt afb s = sbt s <$> afb (sa s)+{-# INLINE rLens #-}
Data/Vinyl/Rec.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-}@@ -19,16 +20,18 @@ , PlainRec , (=:) , (<+>)+ , type (++) , fixRecord ) where import Data.Vinyl.Classes import Control.Applicative-import Control.Monad.Identity+import Data.Functor.Identity import Data.Vinyl.Field import Foreign.Ptr (castPtr, plusPtr) import Foreign.Storable (Storable(..)) import GHC.TypeLits+import Data.Monoid -- | A record is parameterized by a list of fields and a functor -- to be applied to each of those fields.@@ -63,7 +66,13 @@ instance Show (Rec '[] f) where show RNil = "{}"-instance (SingI sy, Show (g t), Show (Rec fs g)) => Show (Rec ((sy ::: t) ': fs) g) where+instance (+#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 707+ KnownSymbol sy,+#else+ SingI sy,+#endif+ Show (g t), Show (Rec fs g)) => Show (Rec ((sy ::: t) ': fs) g) where show (x :& xs) = show (Field :: sy ::: t) ++ " :=: " ++ show x ++ ", " ++ show xs @@ -72,15 +81,34 @@ instance (Eq (g t), Eq (Rec fs g)) => Eq (Rec ((s ::: t) ': fs) g) where (x :& xs) == (y :& ys) = (x == y) && (xs == ys) ++instance Monoid (Rec '[] f) where+ mempty = RNil+ RNil `mappend` RNil = RNil+instance (Monoid t, Monoid (Rec fs g), Applicative g) => Monoid (Rec ((s ::: t) ': fs) g) where+ mempty = pure mempty :& mempty+ (x :& xs) `mappend` (y :& ys) = liftA2 mappend x y :& (xs `mappend` ys)++ -- | Records can be applied to each other. instance Apply (~>) (Rec rs) where RNil <<*>> RNil = RNil (f :& fs) <<*>> (x :& xs) = runNT f x :& (fs <<*>> xs) --- | Records may be 'run' to accumulate the effects of their fields.-instance Run (Rec rs) where- run RNil = pure RNil- run (x :& xs) = (:&) <$> (pure <$> x) <*> run xs+-- | Records may be distributed to accumulate the effects of their fields.+instance Dist (Rec rs) where+ dist RNil = pure RNil+ dist (x :& xs) = (:&) <$> (pure <$> x) <*> dist xs++instance FoldRec (Rec '[] f) a where+ foldRec _ z RNil = z++instance FoldRec (Rec fs g) (g t) => FoldRec (Rec ((s ::: t) ': fs) g) (g t) where+ foldRec f z (x :& xs) = f x (foldRec f z xs)++-- | Accumulates a homogenous record into a list+recToList :: FoldRec (Rec fs g) (g t) => Rec fs g -> [g t]+recToList = foldRec (\e a -> [e] ++ a) [] -- | We provide a 'Show' instance for 'Identity'. instance Show a => Show (Identity a) where
Data/Vinyl/Relation.hs view
@@ -14,14 +14,13 @@ ( (<:)(..) , (:~:) , (~=)- , rIso+ -- , rIso ) where import Data.Vinyl.Field import Data.Vinyl.Lens import Data.Vinyl.Rec import Data.Vinyl.Witnesses-import Control.Monad.Identity import GHC.Prim (Constraint) @@ -45,14 +44,14 @@ instance Rec xs f <: Rec '[] f where cast _ = RNil -instance (y ~ (sy ::: t), IElem y xs, PlainRec xs <: PlainRec ys) => PlainRec xs <: PlainRec (y ': ys) where- cast r = Identity (rGet field r) :& cast r+instance (y ~ (sy ::: t), IElem y xs, Rec xs f <: Rec ys f) => Rec xs f <: Rec (y ': ys) f where+ cast r = rGet' field r :& cast r where field = lookupField (implicitly :: Elem y xs) r lookupField :: Elem x xs -> Rec xs f -> x lookupField Here (_ :& _) = Field lookupField (There p) (_ :& xs) = lookupField p xs -rIso :: (r1 :~: r2) => Iso' r1 r2-rIso = iso cast cast+-- rIso :: (r1 :~: r2) => Iso' r1 r2+-- rIso = iso cast cast
Data/Vinyl/Validation.hs view
@@ -1,10 +1,9 @@-{-# LANGUAGE Safe #-} {-# LANGUAGE TypeOperators #-} module Data.Vinyl.Validation where import Control.Applicative-import Control.Monad.Identity+import Data.Functor.Identity import Data.Monoid import Data.Vinyl.Classes
Data/Vinyl/Witnesses.hs view
@@ -6,7 +6,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverlappingInstances #-} {-# LANGUAGE PolyKinds #-}-{-# LANGUAGE Safe #-} {-# LANGUAGE TypeOperators #-} module Data.Vinyl.Witnesses where
benchmarks/StorableBench.hs view
@@ -8,6 +8,8 @@ -- record of "Linear" finite dimensional vector types, and a vinyl -- record of linear fields. import Control.Applicative+import Control.Lens+import Control.Monad (when) import qualified Data.Foldable as F import qualified Data.Vector.Storable as V import qualified Data.Vector.Storable.Mutable as VM@@ -31,24 +33,32 @@ type MyFields a = [ "pos" ::: V3 a, "tex" ::: V2 a, "normal" ::: V3 a ] type MyVertex a = PlainRec (MyFields a) +doubleNviL :: V.Vector (MyVertex Float) -> V.Vector (MyVertex Float)+doubleNviL = V.map (rLens vNorm . _y *~ (2::Float))++vinylNSumL :: (Num a, Storable a) => V.Vector (MyVertex a) -> a+vinylNSumL = V.sum . V.map (F.sum . view (rLens vNorm))+ doubleNvi :: V.Vector (MyVertex Float) -> V.Vector (MyVertex Float)-doubleNvi = V.map (rLens vNorm . _y *~ (2::Float))+doubleNvi = V.map (rMod vNorm (_y *~ (2::Float))) vinylNSum :: (Num a, Storable a) => V.Vector (MyVertex a) -> a-vinylNSum = V.sum . V.map (F.sum . view (rLens vNorm))+vinylNSum = V.sum . V.map (F.sum . rGet vNorm) 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- putStrLn $ "Sanity: " ++ show (vinylNSum $ doubleNvi vinylVerts)- ++ " ==? " ++- show (flatNSum $ doubleNfl flatVerts)- ++ " ==? " ++- show (reasNSum $ doubleNre reasVerts)+ vinylAns = vinylNSum $ doubleNvi vinylVerts+ vinylLans = vinylNSumL $ doubleNviL vinylVerts+ flatAns = flatNSum $ doubleNfl flatVerts+ reasAns = reasNSum $ doubleNre reasVerts+ when (any (/= vinylAns) [vinylLans, flatAns, reasAns])+ (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 , bench "reasonable" $ whnf (reasNSum . doubleNre) reasVerts ] where n = 1000
tests/Intro.lhs view
@@ -24,7 +24,8 @@ > import Data.Vinyl.Unicode > import Data.Vinyl.Validation > import Control.Applicative-> import Control.Monad.Identity+> import Control.Lens+> import Data.Functor.Identity > import Data.Char > import Test.DocTest @@ -78,7 +79,7 @@ > -- | > -- >>> tucker' ^. rLens sleeping > -- False-> -- +> -- > -- >>> tucker ^. rLens sleeping > -- True > --@@ -163,7 +164,7 @@ > validatePerson p = (\n a -> name =: n <+> age =: a) <$> vName <*> vAge where > vName = validateName (rGet name p) > vAge = validateAge (rGet age p)-> +> > validateName str | all isAlpha str = Success str > validateName _ = Failure [ "name must be alphabetic" ] > validateAge i | i >= 0 = Success i@@ -229,18 +230,18 @@ 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 run:+(PlainRec Person)`) using `dist`: -> runGoodPerson = run goodPersonResult-> runBadPerson = run badPersonResult+> distGoodPerson = dist goodPersonResult+> distBadPerson = dist badPersonResult -`runGoodPerson === Success name :=: "Jon", age :=: 20, {}`-`runBadPerson === Failure ["name must be alphabetic"]``+`distGoodPerson === Success name :=: "Jon", age :=: 20, {}`+`distBadPerson === Failure ["name must be alphabetic"]`` > -- |-> -- >>> isSuccess runGoodPerson+> -- >>> isSuccess distGoodPerson > -- True-> -- >>> isSuccess runBadPerson+> -- >>> isSuccess distBadPerson > -- False Fixing a polymorphic record into the Identity Functor
vinyl.cabal view
@@ -1,5 +1,5 @@ name: vinyl-version: 0.1.3+version: 0.2 synopsis: Extensible Records -- description: license: MIT@@ -23,20 +23,20 @@ Data.Vinyl.Witnesses, Data.Vinyl.Rec, Data.Vinyl.Relation, Data.Vinyl.Unicode, Data.Vinyl.Classes, Data.Vinyl.Validation- build-depends: base ==4.6.*, lens >=3.8, ghc-prim, mtl+ build-depends: base >=4.6 && <= 5, ghc-prim, transformers default-language: Haskell2010 benchmark bench-builder-all type: exitcode-stdio-1.0 hs-source-dirs: benchmarks main-is: StorableBench.hs- build-depends: base, vector, criterion, vinyl, mwc-random, linear+ build-depends: base >= 4.6 && <= 5, vector, criterion, vinyl, mwc-random, lens, linear ghc-options: -O2 -fllvm- default-language: Haskell2010 + default-language: Haskell2010 test-suite doctests type: exitcode-stdio-1.0 hs-source-dirs: tests main-is: Intro.lhs- build-depends: base, mtl, vinyl, doctest >= 0.8+ build-depends: base >= 4.6 && <= 5, lens, transformers, vinyl, doctest >= 0.8 default-language: Haskell2010