generic-data-asserts (empty) → 0.1.1
raw patch · 6 files changed
+205/−0 lines, 6 filesdep +basedep +generic-data-assertsdep +type-spec
Dependencies added: base, generic-data-asserts, type-spec
Files
- CHANGELOG.md +4/−0
- LICENSE +20/−0
- README.md +19/−0
- generic-data-asserts.cabal +73/−0
- src/Generic/Data/Rep/Assert.hs +62/−0
- test/Main.hs +27/−0
+ CHANGELOG.md view
@@ -0,0 +1,4 @@+## 0.1.1 (2024-04-04)+Initial release.++ * extracted from generic-data-functions
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2024 Ben Orchard (@raehik) <thefirstmuffinman@gmail.com>++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,19 @@+[gdf-hackage]: https://hackage.haskell.org/package/generic-data-functions++# generic-data-asserts+Structural assertions on the generic data representations (`GHC.Generic.Rep a`).++Sometimes, we want to write generics that only work on certain data types with a+certain shape e.g. non-sum types (single constructor). Achieving this is fairly+straightforward if we add a `TypeError` constraint on the relevant generic+representation unwrapping instance (here, the `(:+:)` constructor sum type).++This library effectively pulls those checks out of generic code and runs them by+separately. This way, we can simplify our generics, and make them more flexible+(e.g. a user may choose whether to permit void types at compile time or not).++This began as a minor feature in my [generic-data-functions][gdf-hackage]+library.++## License+Provided under the MIT license. See `LICENSE` for license text.
+ generic-data-asserts.cabal view
@@ -0,0 +1,73 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.35.2.+--+-- see: https://github.com/sol/hpack++name: generic-data-asserts+version: 0.1.1+synopsis: Structural assertions on generic data representations.+description: Please see README.md.+category: Data, Serialization+homepage: https://github.com/raehik/generic-rep-asserts#readme+bug-reports: https://github.com/raehik/generic-rep-asserts/issues+author: Ben Orchard+maintainer: Ben Orchard <thefirstmuffinman@gmail.com>+license: MIT+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ CHANGELOG.md++source-repository head+ type: git+ location: https://github.com/raehik/generic-rep-asserts++library+ exposed-modules:+ Generic.Data.Rep.Assert+ other-modules:+ Paths_generic_data_asserts+ hs-source-dirs:+ src+ default-extensions:+ LambdaCase+ NoStarIsType+ DerivingVia+ DeriveAnyClass+ GADTs+ RoleAnnotations+ DefaultSignatures+ TypeFamilies+ DataKinds+ MagicHash+ ghc-options: -Wall+ build-depends:+ base >=4.14 && <5+ default-language: GHC2021++test-suite spec+ type: exitcode-stdio-1.0+ main-is: Main.hs+ other-modules:+ Paths_generic_data_asserts+ hs-source-dirs:+ test+ default-extensions:+ LambdaCase+ NoStarIsType+ DerivingVia+ DeriveAnyClass+ GADTs+ RoleAnnotations+ DefaultSignatures+ TypeFamilies+ DataKinds+ MagicHash+ ghc-options: -Wall+ build-depends:+ base >=4.14 && <5+ , generic-data-asserts+ , type-spec >=0.4.0.0 && <0.5+ default-language: GHC2021
+ src/Generic/Data/Rep/Assert.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE UndecidableInstances #-} -- for IsEnum++-- | Structural assertions on generic data representation.++module Generic.Data.Rep.Assert where++import GHC.Generics+import GHC.TypeError+import GHC.TypeLits ( type Symbol )+import Data.Kind ( type Constraint )++type GAssertError :: k -> Symbol -> Constraint+type GAssertError a msg = TypeError+ ('Text "Assertion on generic representation failed for type: "+ :<>: 'ShowType a+ :$$: 'Text "Message: " :<>: 'Text msg)++type family StripD1 a where StripD1 (D1 _ a) = a++-- | Type is not void i.e. has at least one constructor.+type GAssertNotVoid a = Assert (IsNotVoid (StripD1 (Rep a)))+ (GAssertError a "not non-void type (>=1 constructor)")+type family IsNotVoid a where+ IsNotVoid V1 = False+ IsNotVoid _ = True++-- | Type is not a sum type i.e. has at most one constructor.+--+-- Permits void types.+type GAssertNotSum a = Assert (IsNotSum (StripD1 (Rep a)))+ (GAssertError a "not non-sum type (1 constructor)")+type family IsNotSum a where+ IsNotSum (_ :+: _) = False+ IsNotSum _ = True++-- | Type is a sum type i.e. has >=2 constructors.+--+-- Permits void types.+type GAssertSum a = Assert (IsSum (StripD1 (Rep a)))+ (GAssertError a "not sum type (>=2 constructors)")+type family IsSum a where+ IsSum (C1 _ _) = False+ IsSum _ = True++-- | Type has only empty constructors.+--+-- Permits void types.+type GAssertEnum a = Assert (IsEnum (StripD1 (Rep a)))+ (GAssertError a "not enum type (all empty constructors)")+type family IsEnum a where+ IsEnum V1 = True+ IsEnum (C1 _ a) = ConsIsEmpty a+ IsEnum (l :+: r) = IsEnum l `And` IsEnum r++type family ConsIsEmpty a where+ ConsIsEmpty U1 = True+ ConsIsEmpty _ = False++-- | Type level boolean AND.+type family And l r where+ And True True = True+ And _ _ = False
+ test/Main.hs view
@@ -0,0 +1,27 @@+module Main where++import Test.TypeSpec+import GHC.Generics+import Generic.Data.Rep.Assert+import Data.Kind++data DSum = DSum1 () | DSum2 deriving stock Generic+data DEnum = DEnum1 | DEnum2 | DEnum3 deriving stock Generic+data DNonSum = DNonSum () () deriving stock Generic+data DVoid deriving stock Generic++main :: IO ()+main = print spec++spec+ :: Expect '[ AssertPasses (GAssertNotVoid DSum)+ , AssertPasses (GAssertSum DSum)+ , AssertPasses (GAssertNotSum DNonSum)+ , AssertPasses (GAssertEnum DEnum)+ ]+spec = Valid++type AssertPasses a = Is a (() :: Constraint)++-- can't write this because GHC finds the TypeError before type-spec+--type AssertFails a = Isn't a (() :: Constraint)