prairie 0.0.3.0 → 0.0.4.0
raw patch · 8 files changed
+178/−1 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Prairie.AsRecord: AsRecord :: rec -> AsRecord rec
+ Prairie.AsRecord: [unAsRecord] :: AsRecord rec -> rec
+ Prairie.AsRecord: instance (Prairie.Class.Record rec, Prairie.Class.FieldDict GHC.Base.Semigroup rec) => GHC.Base.Semigroup (Prairie.AsRecord.AsRecord rec)
+ Prairie.AsRecord: instance (Prairie.Class.Record rec, Prairie.Class.FieldDict GHC.Base.Semigroup rec, Prairie.Class.FieldDict GHC.Base.Monoid rec) => GHC.Base.Monoid (Prairie.AsRecord.AsRecord rec)
+ Prairie.AsRecord: newtype AsRecord rec
+ Prairie.Monoid: emptyRecord :: (Record rec, FieldDict Monoid rec) => rec
+ Prairie.Semigroup: appendRecord :: forall rec. (Record rec, FieldDict Semigroup rec) => rec -> rec -> rec
+ Prairie.Zip: zipWithRecord :: forall rec. Record rec => (forall ty. ty -> ty -> Field rec ty -> ty) -> rec -> rec -> rec
Files
- ChangeLog.md +8/−0
- prairie.cabal +5/−1
- src/Prairie.hs +6/−0
- src/Prairie/AsRecord.hs +51/−0
- src/Prairie/Monoid.hs +10/−0
- src/Prairie/Semigroup.hs +17/−0
- src/Prairie/Zip.hs +34/−0
- test/Spec.hs +47/−0
ChangeLog.md view
@@ -1,5 +1,13 @@ # Changelog for prairie +## 0.0.4.0++- [#13](https://github.com/parsonsmatt/prairie/pull/13)+ - Introduce `Prairie.Semigroup`, allowing you to combine two records by semigroup-appending their fields together.+ - Introduce `Prairie.Zip`, allowing you to combine two records by specifying how to combine their fields. This fuels `Prairie.Semigroup`.+ - Introduce `Prairie.Monoid`, allowing you to make an `emptyRecord` with `mempty` at each field.+ - Introduce `Prairie.AsRecord`, allowing you to derive instances for records based on their `Record` and `FieldDict` instances.+ ## 0.0.3.0 - [#8](https://github.com/parsonsmatt/prairie/pull/8)
prairie.cabal view
@@ -1,7 +1,7 @@ cabal-version: 1.12 name: prairie-version: 0.0.3.0+version: 0.0.4.0 description: Please see the README on GitHub at <https://github.com/parsonsmatt/prairie#readme> homepage: https://github.com/parsonsmatt/prairie#readme bug-reports: https://github.com/parsonsmatt/prairie/issues@@ -24,12 +24,16 @@ library exposed-modules: Prairie+ Prairie.AsRecord Prairie.Class Prairie.Update Prairie.Diff Prairie.Fold+ Prairie.Zip Prairie.Traverse Prairie.TH+ Prairie.Semigroup+ Prairie.Monoid build-depends: base >= 4.13 && < 5
src/Prairie.hs view
@@ -7,7 +7,10 @@ , module Prairie.Diff , module Prairie.Fold , module Prairie.Traverse+ , module Prairie.Zip , module Prairie.TH+ , module Prairie.Semigroup+ , module Prairie.Monoid ) where import Prairie.Class@@ -16,3 +19,6 @@ import Prairie.TH import Prairie.Traverse import Prairie.Update+import Prairie.Zip+import Prairie.Semigroup+import Prairie.Monoid
+ src/Prairie/AsRecord.hs view
@@ -0,0 +1,51 @@+-- | This module contains a newtype 'AsRecord' which is used to provide+-- a variety of default instances for types based on their 'Record'+-- instance.+module Prairie.AsRecord where++import Prairie.Class+import Prairie.Monoid+import Prairie.Semigroup++-- | This @newtype@ is intended for use with @DerivingVia@.+--+-- For an example use, let's consider this @User@ datatype:+--+-- > data Foo = Foo+-- > { ints :: [Int]+-- > , char :: First Char+-- > }+-- >+-- > mkRecord ''Foo+--+-- Let's say we want to define an instance of 'Semigroup' for this type, so+-- that we can combine two of them. Ordinarily, we'd need to write+-- a boilerplate-y instance:+--+-- > instance Semigroup Foo where+-- > f0 <> f1 = Foo+-- > { ints = f0.ints <> f1.ints+-- > , char = f0.char <> f1.char+-- > }+--+-- With @DerivingVia@, we can use 'AsRecord' to provide it easily:+--+-- @+-- deriving via AsRecord Foo instance Semigroup Foo+-- @+--+-- @since 0.0.4.0+newtype AsRecord rec = AsRecord { unAsRecord :: rec }++-- |+--+-- @since 0.0.4.0+instance (Record rec, FieldDict Semigroup rec) => Semigroup (AsRecord rec) where+ AsRecord r0 <> AsRecord r1 =+ AsRecord (appendRecord r0 r1)++-- |+--+-- @since 0.0.4.0+instance (Record rec, FieldDict Semigroup rec, FieldDict Monoid rec) => Monoid (AsRecord rec) where+ mempty = AsRecord emptyRecord
+ src/Prairie/Monoid.hs view
@@ -0,0 +1,10 @@+module Prairie.Monoid where++import Prairie.Class++-- | Create a 'mempty' 'Record' assuming that all of the fields of the+-- record have a 'Monoid' instance.+--+-- @since 0.0.4.0+emptyRecord :: (Record rec, FieldDict Monoid rec) => rec+emptyRecord = tabulateRecord (\field -> withFieldDict @Monoid field mempty)
+ src/Prairie/Semigroup.hs view
@@ -0,0 +1,17 @@+-- | This module provides the ability to append two records using '(<>)',+-- provided that all of their fields have an instance of 'Semigroup'.+module Prairie.Semigroup where++import Prairie.Class+import Prairie.Zip++-- | Zip two records together using a 'Semigroup' append.+--+-- @since 0.0.4.0+appendRecord+ :: forall rec. (Record rec, FieldDict Semigroup rec)+ => rec+ -> rec+ -> rec+appendRecord =+ zipWithRecord (\a b field -> withFieldDict @Semigroup field (a <> b))
+ src/Prairie/Zip.hs view
@@ -0,0 +1,34 @@+module Prairie.Zip where++import Prairie.Class+import Prairie.Fold++-- | Take two records and zip them together with the provided function.+--+-- The field is given for the final parameter in the function, allowing you to use @LambdaCase@.+--+-- @+-- zipWithRecord+-- (\a b ->+-- \case+-- UserName ->+-- a <> b+-- UserAge ->+-- a + b+-- )+-- @+--+-- @since 0.0.4.0+zipWithRecord+ :: forall rec. (Record rec)+ => (forall ty. ty -> ty -> Field rec ty -> ty)+ -> rec+ -> rec+ -> rec+zipWithRecord k r0 r1 =+ foldRecord f r0 r0+ where+ f :: ty -> rec -> Field rec ty -> rec+ f v0 rec field =+ let v1 = getRecordField field r1+ in setRecordField field (k v0 v1 field) rec
test/Spec.hs view
@@ -1,12 +1,18 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE BlockArguments #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-}@@ -24,6 +30,8 @@ import Data.Aeson import Data.Kind (Type) import Data.Monoid+import GHC.Records+import Prairie.AsRecord import Test.Hspec data User = User { name :: String, age :: Int }@@ -36,6 +44,17 @@ exampleUser = User "Alice" 30 +data Foo = Foo+ { ints :: [Int]+ , char :: First Char+ }+ deriving (Show, Eq)++mkRecord ''Foo++deriving via AsRecord Foo instance Semigroup Foo+deriving via AsRecord Foo instance Monoid Foo+ data T a = T { x :: a, y :: Int } instance Record (T a) where@@ -197,3 +216,31 @@ User { name = "", age = 30 } `shouldBe` Nothing++ describe "Semigroup" do+ it "can combine two records" do+ let f0 = Foo [1] (First Nothing)+ f1 = Foo [2,3] (First (Just 'a'))+ f0 <> f1+ `shouldBe`+ Foo [1,2,3] (First (Just 'a'))++ describe "Monoid" do+ it "can produce an empty record" do+ let obvious =+ Foo mempty mempty+ mempty `shouldBe` obvious++ describe "Zip" do+ it "can combine two records" do+ let u0 = User "Matt" 35+ u1 = User "ttaM" 53+ zipWithRecord (\a b -> \case+ UserName ->+ a <> b+ UserAge ->+ a + b+ ) u0 u1+ `shouldBe`+ User "MattttaM" (35 + 53)+