packages feed

domain 0.1 → 0.1.1

raw patch · 6 files changed

+107/−60 lines, 6 filesdep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: bytestring

API changes (from Hackage documentation)

Files

README.md view
@@ -1,38 +1,40 @@ # About -Codegen solving multiple problems of the standard Haskell approach to defining models.+Template Haskell codegen removing noise and boilerplate from domain models.  # Problem -Declaring a data model in Haskell can get cumbersome. While it is much better than in most popular languages it still suffers from boilerplate, it distracts the developer by forcing to solve unrelated problems in the same place and the code is not terribly readable. On top of that we have the notorious records problem and other naming issues to solve causing inconsistent naming conventions or absense thereof, or workarounds causing more distraction and noise. Declaring instances for typeclasses other than the basic ones requires multiple approaches depending on the class, becoming a non-trivial task for non-experts and producing even more boilerplate.+Imagine a real-life project, where you have to define the types for your problem domain: your **domain model**. How many types do you think there'll be? [A poll among Haskellers](https://twitter.com/NikitaYVolkov/status/1324360237827108870) shows that highly likely more than 30. That is 30 places for you to derive or define instances, work around the records problem and the problem of conflicting constructor names. That is a lot of boilerplate and noise, distracting you from your actual goal of modeling the data structures or learning an existing model during maintenance. Also don't forget about the boilerplate required to generate optics for your model to actually make it accessible.  # Mission -In its approach to those problems this projects sets the following goals:+In its approach to those problems this project sets the following goals:  - Let the domain model definition be focused on data and nothing else. - Let it be readable and comfortably editable, avoiding syntactic noise. - Separate its declaration from the problems of declaration of instances, accessor functions, optics and etc. - Have the records problem solved. - Have the problem of conflicting constructor names solved.-- Avoid boilerplate while doing all the above.-- Avoid complications of the build process while doing all the above.+- Avoid boilerplate in all the above.+- Avoid complications of the build process.  # Solution  This project introduces a clear boundary between the data model declaration and the rest of the code base.-It introduces a low-noise YAML format designed specifically for the problem of defining types and relations between them and that only. We call it Domain Schema.+It introduces a YAML format designed specifically for the problem of defining types and relations between them and that only. We call it Domain Schema. -Schemas can be loaded at compile time and transformed into Haskell declarations using Template Haskell. Since it's just Template Haskell, no extra build software is needed for you to use this library. It is a simple Haskell package.+Schemas can be loaded at compile time and transformed into Haskell declarations using Template Haskell. Since it's just Template Haskell, no extra build software is needed to use this library. It is a normal Haskell package. -Schema gets analysed allowing to generate all kinds of instances automatically using a set of prepackaged derivers. An API is provided for creation of custom derivers of uncovered typeclasses.+Schema gets analysed allowing to generate all kinds of instances automatically using a set of prepackaged derivers. An API is provided for creation of custom derivers for extending the library or handling special cases. -# Case in point+# Tutorial and Case in Point  We'll show you how this whole thing works on an example of a model of a service address.  ## Schema +First we need to define a schema. For that we create the following YAML document:+ ```yaml # Service can be either located on the network or # by a socket file.@@ -87,7 +89,7 @@     part2: Word64 ``` -As you can see in the specification above we're not concerned with typeclass instances or problems of name disambiguation. We're only concerned with data and relations that it has. This is what we meant by focus. It makes the experience of designing a model way smoother and the maintenance easier.+As you can see in the specification above we're not concerned with typeclass instances or problems of name disambiguation. We're only concerned with data and relations that it has. This is what we meant by focus. It makes the experience of designing and maintaining a model way smoother.  Those three methods of defining types (product, sum, enum) are all that you need to define a model of any complexity. If you understand them, there's nothing new to learn. 
domain.cabal view
@@ -1,5 +1,5 @@ name: domain-version: 0.1+version: 0.1.1 synopsis: Codegen helping you define domain models description:   - For introduction and demo skip to [Readme](#readme).@@ -44,7 +44,7 @@   build-depends:     attoparsec >=0.13 && <0.14,     base >=4.9 && <5,-    bytestring >=0.10 && <0.11,+    bytestring >=0.10 && <0.12,     domain-core >=0.1 && <0.2,     foldl >=1.4.9 && <2,     hashable >=1 && <2,
library/Domain/Models/TypeCentricDoc.hs view
@@ -9,12 +9,12 @@   [(Text, Structure)]  data Structure =-  ProductStructure [(Text, TypeString.AppSeq)] |-  SumStructure [(Text, SumTypeExpression)] |+  ProductStructure [(Text, NestedTypeExpression)] |+  SumStructure [(Text, [NestedTypeExpression])] |   EnumStructure [Text]   deriving (Show) -data SumTypeExpression =-  SequenceSumTypeExpression [TypeString.AppSeq] |-  StringSumTypeExpression TypeString.CommaSeq+data NestedTypeExpression =+  AppSeqNestedTypeExpression TypeString.AppSeq |+  StructureNestedTypeExpression Structure   deriving (Show)
library/Domain/Resolvers/TypeCentricDoc.hs view
@@ -8,39 +8,71 @@ import qualified Data.Text as Text  +eliminateDoc :: Applicative f => Doc.Doc -> f [TypeDec] eliminateDoc =-  traverse eliminateNameAndStructure+  traverse (uncurry (structureTypeDecs [])) >>> fmap join -eliminateNameAndStructure (name, structure) =-  TypeDec name <$> eliminateStructure structure+structureTypeDecs :: Applicative f => [Text] -> Text -> Doc.Structure -> f [TypeDec]+structureTypeDecs namespace name structure =+  (:) <$> primary <*> structureGeneratedTypeDecs nextNamespace structure+  where+    primary =+      TypeDec renderedName <$> structureTypeDef nextNamespace structure+      where+        renderedName =+          Text.concat (reverse nextNamespace)+    nextNamespace =+      name : namespace -eliminateStructure =+structureGeneratedTypeDecs :: Applicative f => [Text] -> Doc.Structure -> f [TypeDec]+structureGeneratedTypeDecs namespace =   \ case     Doc.ProductStructure structure ->-      ProductTypeDef <$>-      traverse eliminateProductStructureUnit structure+      traverse (uncurry (nestedTypeExpressionTypeDecs namespace . Text.toTitle)) structure+        & fmap join     Doc.SumStructure structure ->-      SumTypeDef <$>-      traverse eliminateSumStructureUnit structure+      traverse (\(a, b) -> traverse (nestedTypeExpressionTypeDecs namespace (Text.toTitle a)) b) structure+        & fmap (join . join)+    _ ->+      pure []++nestedTypeExpressionTypeDecs namespace name =+  \ case+    Doc.StructureNestedTypeExpression a ->+      structureTypeDecs namespace name a+    _ ->+      pure []++structureTypeDef :: Applicative f => [Text] -> Doc.Structure -> f TypeDef+structureTypeDef namespace =+  \ case+    Doc.ProductStructure structure ->+      ProductTypeDef <$> traverse (uncurry (eliminateProductStructureUnit namespace)) structure+    Doc.SumStructure structure ->+      SumTypeDef <$> traverse (uncurry (eliminateSumStructureUnit namespace)) structure     Doc.EnumStructure variants ->       pure (SumTypeDef (fmap (,[]) variants)) -eliminateProductStructureUnit (name, appSeq) =-  (,) name . AppType <$> eliminateTypeStringAppSeq appSeq+eliminateProductStructureUnit :: Applicative f => [Text] -> Text -> Doc.NestedTypeExpression -> f (Text, Type)+eliminateProductStructureUnit namespace name productTypeExpression =+  (,) name <$> nestedTypeExpressionType namespace name productTypeExpression -eliminateSumStructureUnit (name, sumTypeExpression) =-  (,) name <$> eliminateSumTypeExpression sumTypeExpression+eliminateSumStructureUnit :: Applicative f => [Text] -> Text -> [Doc.NestedTypeExpression] -> f (Text, [Type])+eliminateSumStructureUnit namespace name sumTypeExpression =+  (,) name <$> traverse (nestedTypeExpressionType namespace name) sumTypeExpression -eliminateSumTypeExpression =+nestedTypeExpressionType :: Applicative f => [Text] -> Text -> Doc.NestedTypeExpression -> f Type+nestedTypeExpressionType namespace name =   \ case-    Doc.SequenceSumTypeExpression a ->-      traverse (fmap AppType . eliminateTypeStringAppSeq) a-    Doc.StringSumTypeExpression a ->-      traverse (fmap AppType . eliminateTypeStringAppSeq) a+    Doc.AppSeqNestedTypeExpression a ->+      AppType <$> eliminateTypeStringAppSeq a+    Doc.StructureNestedTypeExpression _ ->+      pure (RefType (Text.concat (reverse (Text.toTitle name : namespace))))  eliminateTypeStringCommaSeq =   traverse eliminateTypeStringAppSeq +eliminateTypeStringAppSeq :: Applicative f => NonEmpty TypeString.Unit -> f (NonEmpty Type) eliminateTypeStringAppSeq =   traverse eliminateTypeStringUnit 
library/Domain/YamlUnscrambler/TypeCentricDoc.hs view
@@ -35,13 +35,7 @@                 Left "Empty string"  structure =-  value [] (Just onMapping) Nothing-  where-    onMapping =-      byKeyMapping (CaseSensitive True) $-        atByKey "product" (ProductStructure <$> byFieldName appTypeString) <|>-        atByKey "sum" (SumStructure <$> byFieldName sumTypeExpression) <|>-        atByKey "enum" (EnumStructure <$> enumVariants)+  value [] (Just structureMapping) Nothing  byFieldName onElement =   value onScalar (Just onMapping) Nothing@@ -51,28 +45,49 @@     onMapping =       foldMapping (,) Fold.list textString onElement -appTypeString =-  value [-    stringScalar $ attoparsedString "Type signature" $-    GeneralAttoparsec.only TypeStringAttoparsec.appSeq-    ] Nothing Nothing- sumTypeExpression =-  value onScalar Nothing (Just onSequence)+  value onScalar (Just onMapping) (Just onSequence)   where     onScalar =       [-        nullScalar (SequenceSumTypeExpression [])+        nullScalar []         ,-        fmap StringSumTypeExpression $+        fmap (fmap AppSeqNestedTypeExpression) $         stringScalar $ attoparsedString "Type signature" $         GeneralAttoparsec.only TypeStringAttoparsec.commaSeq         ]+    onMapping =+      pure . StructureNestedTypeExpression <$> structureMapping     onSequence =-      SequenceSumTypeExpression <$> foldSequence Fold.list appTypeString+      foldSequence Fold.list nestedTypeExpression +nestedTypeExpression =+  value [onScalar] (Just onMapping) Nothing+  where+    onScalar =+      AppSeqNestedTypeExpression <$> appTypeStringScalar+    onMapping =+      StructureNestedTypeExpression <$> structureMapping+ enumVariants =   sequenceValue (foldSequence Fold.list variant)   where     variant =       scalarsValue [stringScalar textString]+++-- * Scalar+-------------------------++appTypeStringScalar =+  stringScalar $ attoparsedString "Type signature" $+  GeneralAttoparsec.only TypeStringAttoparsec.appSeq++-- * Mapping+-------------------------++structureMapping =+  byKeyMapping (CaseSensitive True) $+    atByKey "product" (ProductStructure <$> byFieldName nestedTypeExpression) <|>+    atByKey "sum" (SumStructure <$> byFieldName sumTypeExpression) <|>+    atByKey "enum" (EnumStructure <$> enumVariants)
samples/1.yaml view
@@ -40,12 +40,10 @@ Ip:   sum:     v4: Word32-    v6: Word128--# Since the standard lib lacks a definition-# of a 128-bit word, we define a custom one-# as a product of two 64-bit words.-Word128:-  product:-    part1: Word64-    part2: Word64+    v6:+      # Since the standard lib lacks a definition+      # of a 128-bit word, we define a custom one+      # as a product of two 64-bit words.+      product:+        part1: Word64+        part2: Word64