packages feed

vinyl 0.13.1 → 0.13.2

raw patch · 6 files changed

+28/−331 lines, 6 filesdep −doctestdep −singletonsdep −singletons-thdep ~basedep ~hspec

Dependencies removed: doctest, singletons, singletons-th

Dependency ranges changed: base, hspec

Files

CHANGELOG.md view
@@ -1,3 +1,6 @@+# 0.14.0+- 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 
Data/Vinyl/ARec.hs view
@@ -11,8 +11,6 @@   , toARec   , fromARec   , aget-  , aput-  , alens   , arecGetSubset   , arecSetSubset   , arecRepsMatchCoercion
Data/Vinyl/ARec/Internal.hs view
@@ -29,8 +29,8 @@   , toARec   , fromARec   , aget-  , aput-  , alens+  , unsafeAput+  , unsafeAlens   , arecGetSubset   , arecSetSubset   , arecRepsMatchCoercion@@ -119,17 +119,17 @@ {-# INLINE aget #-}  -- | Set a field in an 'ARec'.-aput :: forall t t' f ts ts'. (NatToInt (RIndex t ts))+unsafeAput :: forall t t' f ts ts'. (NatToInt (RIndex t ts))       => f t' -> ARec f ts -> ARec f ts'-aput x (ARec arr) = ARec (arr Array.// [(i, unsafeCoerce x)])+unsafeAput x (ARec arr) = ARec (arr Array.// [(i, unsafeCoerce x)])   where i = natToInt @(RIndex t ts)-{-# INLINE aput #-}+{-# INLINE unsafeAput #-}  -- | Define a lens for a field of an 'ARec'.-alens :: forall f g t t' ts ts'. (Functor g, NatToInt (RIndex t ts))+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')-alens f ar = fmap (flip (aput @t) ar) (f (aget ar))-{-# INLINE alens #-}+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@@ -137,20 +137,20 @@ --   rput = aput  instance RecElem ARec t t' (t ': ts) (t' ': ts) 'Z where-  rlensC = alens+  rlensC = unsafeAlens   {-# INLINE rlensC #-}   rgetC = aget   {-# INLINE rgetC #-}-  rputC = aput @t+  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 = alens+  rlensC = unsafeAlens   {-# INLINE rlensC #-}   rgetC = aget   {-# INLINE rgetC #-}-  rputC = aput @t+  rputC = unsafeAput @t   {-# INLINE rputC #-}  -- | Get a subset of a record's fields.
− tests/Intro.lhs
@@ -1,291 +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 singletons--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 #-}-> {-# LANGUAGE TypeApplications #-}-> {-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ >= 810-> {-# LANGUAGE StandaloneKindSignatures #-}-#endif--> module Intro where-> import Data.Vinyl-> import Data.Vinyl.Functor-> import Control.Lens hiding (Identity)-> import Data.Char-> import Test.DocTest-> import Data.Singletons.TH (genSingletons)--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:--> type family ElF (f :: Fields) :: * where->   ElF Name = String->   ElF Age = Int->   ElF Sleeping = Bool->   ElF Master = Rec Attr LifeForm--Unfortunately, type families aren't first class in Haskell.  That's-why we also need a data type, with which we will parametrise `Rec`:--> 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--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`.--> (=::) :: sing f -> ElF f -> Attr f-> _ =:: x = Attr x--We generate the necessary singletons for each field label using-Template Haskell:--> genSingletons [ ''Fields ]--Now, let’s try to make an entity that represents a human:--> 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 humans, 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 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:---> 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--> -- |-> -- >>> :set -XTypeApplications -XDataKinds-> -- >>> 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:--> masterSleeping = rlens @Master . unAttr . rlens @Sleeping-> 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, `rcast` 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 @'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--> -- $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--> -- |-> -- >>> :set -XTypeApplications -XDataKinds-> -- >>> 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`:--> 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", "Data/Vinyl/Tutorial/Overview.hs"]
− tests/doctests.hs
@@ -1,14 +0,0 @@-{-# language CPP #-}-import Test.DocTest--main :: IO ()-main = doctest [ "-package lens"-               , "-package doctest"-#if __GLASGOW_HASKELL__ >= 900            -               , "-package singletons-th"-#else-               , "-package singletons"-#endif-               , "tests/Intro.lhs"-               , "Data/Vinyl/Functor.hs"-               , "Data/Vinyl/Curry.hs" ]
vinyl.cabal view
@@ -1,5 +1,5 @@ name:                vinyl-version:             0.13.1+version:             0.13.2 synopsis:            Extensible Records -- description: license:             MIT@@ -89,16 +89,17 @@   ghc-options:      -O2   default-language: Haskell2010 -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+-- 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@@ -116,7 +117,7 @@   build-depends:       base                      , vinyl                      , microlens-                     , hspec >= 2.2.4 && < 2.8+                     , hspec                      , should-not-typecheck >= 2.0 && < 2.2   ghc-options:         -threaded -rtsopts -with-rtsopts=-N   default-language:    Haskell2010