From 16aa07e89b1a19cf0298a0a95258fb461f8e7413 Mon Sep 17 00:00:00 2001 From: Nikita Volkov Date: Sat, 9 Dec 2023 15:56:29 +0300 Subject: [PATCH] Modernize --- .github/workflows/check.yaml | 29 + .../workflows/on-push-to-master-or-pr.yaml | 17 + .github/workflows/on-push-to-release.yaml | 32 + .travis.yml | 63 -- cabal.project | 1 + domain.cabal | 196 ++-- inline-demo/Main.hs | 44 +- library/Domain.hs | 933 +++++++++--------- library/Domain/Attoparsec/General.hs | 19 +- library/Domain/Attoparsec/TypeString.hs | 24 +- library/Domain/Docs.hs | 526 +++++----- library/Domain/Prelude.hs | 57 +- library/Domain/Resolvers/TypeCentricDoc.hs | 54 +- library/Domain/TH/InstanceDec.hs | 97 +- library/Domain/TH/InstanceDecs.hs | 65 +- library/Domain/TH/TypeDec.hs | 7 +- library/Domain/Text.hs | 15 +- .../Domain/YamlUnscrambler/TypeCentricDoc.hs | 65 +- loading-demo/Main.hs | 18 +- stack.yaml | 9 - stack.yaml.lock | 61 -- test/Main.hs | 70 +- test/Util/TH/LeafTypes.hs | 25 +- 23 files changed, 1259 insertions(+), 1168 deletions(-) create mode 100644 .github/workflows/check.yaml create mode 100644 .github/workflows/on-push-to-master-or-pr.yaml create mode 100644 .github/workflows/on-push-to-release.yaml delete mode 100644 .travis.yml create mode 100644 cabal.project delete mode 100644 stack.yaml delete mode 100644 stack.yaml.lock diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml new file mode 100644 index 0000000..373a213 --- /dev/null +++ b/.github/workflows/check.yaml @@ -0,0 +1,29 @@ +name: Compile, test and check the docs + +on: + workflow_call: + +jobs: + + check: + + strategy: + fail-fast: false + matrix: + include: + - ghc: 8.8.1 + ghc-options: "" + ignore-haddock: true + ignore-cabal-check: true + - ghc: latest + + runs-on: ubuntu-latest + + steps: + + - uses: nikita-volkov/build-and-test-cabal-package.github-action@v1 + with: + ghc: ${{matrix.ghc}} + ghc-options: ${{matrix.ghc-options}} + ignore-haddock: ${{matrix.ignore-haddock}} + ignore-cabal-check: ${{matrix.ignore-cabal-check}} diff --git a/.github/workflows/on-push-to-master-or-pr.yaml b/.github/workflows/on-push-to-master-or-pr.yaml new file mode 100644 index 0000000..79c21f5 --- /dev/null +++ b/.github/workflows/on-push-to-master-or-pr.yaml @@ -0,0 +1,17 @@ +name: Compile, test and check the docs + +on: + push: + branches: + - master + pull_request: + +jobs: + + format: + uses: nikita-volkov/haskell-hackage-lib-github-actions-workflows/.github/workflows/format.yaml@v2 + secrets: inherit + + check: + uses: ./.github/workflows/check.yaml + secrets: inherit diff --git a/.github/workflows/on-push-to-release.yaml b/.github/workflows/on-push-to-release.yaml new file mode 100644 index 0000000..9024112 --- /dev/null +++ b/.github/workflows/on-push-to-release.yaml @@ -0,0 +1,32 @@ +name: Release the lib to Hackage + +on: + push: + branches: + - supermajor + - major + - minor + - patch + +concurrency: + group: release + cancel-in-progress: false + +jobs: + + format: + uses: nikita-volkov/haskell-hackage-lib-github-actions-workflows/.github/workflows/format.yaml@v2 + secrets: inherit + + check: + uses: ./.github/workflows/check.yaml + secrets: inherit + + release: + needs: + - format + - check + uses: nikita-volkov/haskell-hackage-lib-github-actions-workflows/.github/workflows/release.yaml@v2 + secrets: inherit + with: + prefix-tag-with-v: false diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4bea0f3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,63 +0,0 @@ -if: tag IS blank - -env: - - ghc=8.6.5 - - ghc=8.8.4 - - ghc=8.10.2 benchmarks=1 tests=1 - -install: - # Set up the Shell to treat the semicolon as && - - set -eo pipefail - # Install GHC and Cabal - - - cabal=${cabal=2.4}; - travis_retry sudo add-apt-repository -y ppa:hvr/ghc; - travis_retry sudo apt-get update; - travis_retry sudo apt-get install cabal-install-$cabal ghc-$ghc; - export PATH=/opt/ghc/$ghc/bin:/opt/cabal/$cabal/bin:$PATH; - # Update the Cabal database - - cabal v1-update - # Switch to the distro: - - - export pkg_name=$(cabal info . | awk '{print $2;exit}'); - cabal sdist; - cd dist; - tar xzvf $pkg_name.tar.gz; - cd $pkg_name; - # Install the lower bound dependencies - - - if [ "$lower_bound_dependencies" = "1" ]; - then - constraint_options=( - ); - fi; - # Install executables - - - if [ "$install_happy" = "1" ]; - then - cabal v1-install happy; - fi; - # Install the library dependencies - - cabal v1-install --only-dependencies --reorder-goals --force-reinstalls - ${constraint_options[@]} - $([ "$tests" = "1" ] && echo "--enable-tests") - $([ "$benchmarks" = "1" ] && echo "--enable-benchmarks") - # Build the library - - cabal v1-build - # Configure and build the remaining stuff - - cabal v1-configure - $([ "$tests" = "1" ] && echo "--enable-tests") - $([ "$benchmarks" = "1" ] && echo "--enable-benchmarks") - -f doctest - - cabal v1-build - -script: - - | - if [ "$tests" = "1" ]; - then - cabal v1-test --show-details=always; - fi; - if [ "$benchmarks" = "1" ]; - then - cabal v1-bench --benchmark-options=-s; - fi; diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..e6fdbad --- /dev/null +++ b/cabal.project @@ -0,0 +1 @@ +packages: . diff --git a/domain.cabal b/domain.cabal index fac0395..86e1da7 100644 --- a/domain.cabal +++ b/domain.cabal @@ -1,35 +1,78 @@ -name: domain -version: 0.1.1.4 -synopsis: Codegen helping you define domain models +cabal-version: 3.0 +name: domain +version: 0.1.1.4 +synopsis: Codegen helping you define domain models description: - For introduction and demo skip to [Readme](#readme). - For documentation and syntax reference see the "Domain.Docs" module. - For API documentation refer to the "Domain" module, - which exports the whole API of this package. -homepage: https://github.com/nikita-volkov/domain -bug-reports: https://github.com/nikita-volkov/domain/issues -author: Nikita Volkov -maintainer: Nikita Volkov -copyright: (c) 2020 Nikita Volkov -license: MIT -license-file: LICENSE -build-type: Simple -cabal-version: >=1.10 + which exports the whole API of this package. + +category: IDL +homepage: https://github.com/nikita-volkov/domain +bug-reports: https://github.com/nikita-volkov/domain/issues +author: Nikita Volkov +maintainer: Nikita Volkov +copyright: (c) 2020 Nikita Volkov +license: MIT +license-file: LICENSE extra-source-files: README.md samples/*.yaml source-repository head - type: git + type: git location: git://github.com/nikita-volkov/domain.git library - hs-source-dirs: library - default-extensions: BangPatterns, BlockArguments, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveLift, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedLabels, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators - default-language: Haskell2010 + hs-source-dirs: library + default-extensions: + NoImplicitPrelude + NoMonomorphismRestriction + BangPatterns + BlockArguments + ConstraintKinds + DataKinds + DefaultSignatures + DeriveDataTypeable + DeriveFoldable + DeriveFunctor + DeriveGeneric + DeriveLift + DeriveTraversable + EmptyDataDecls + FlexibleContexts + FlexibleInstances + FunctionalDependencies + GADTs + GeneralizedNewtypeDeriving + InstanceSigs + LambdaCase + LiberalTypeSynonyms + MagicHash + MultiParamTypeClasses + MultiWayIf + OverloadedLabels + OverloadedStrings + ParallelListComp + PatternGuards + QuasiQuotes + RankNTypes + RecordWildCards + ScopedTypeVariables + StandaloneDeriving + StrictData + TemplateHaskell + TupleSections + TypeApplications + TypeFamilies + TypeOperators + + default-language: Haskell2010 exposed-modules: Domain Domain.Docs + other-modules: Domain.Attoparsec.General Domain.Attoparsec.TypeString @@ -42,58 +85,97 @@ library Domain.TH.InstanceDecs Domain.TH.TypeDec Domain.YamlUnscrambler.TypeCentricDoc + build-depends: - attoparsec >=0.13 && <0.15, - base >=4.9 && <5, - bytestring >=0.10 && <0.12, - domain-core >=0.1 && <0.2, - foldl >=1.4.9 && <2, - hashable >=1 && <2, - parser-combinators >=1.3 && <1.4, - template-haskell >=2.13 && <3, - template-haskell-compat-v0208 >=0.1.6 && <0.2, - text >=1.2.3 && <3, - th-lego >=0.2.3 && <0.4, - yaml-unscrambler >=0.1 && <0.2 + , attoparsec >=0.13 && <0.15 + , base >=4.9 && <5 + , bytestring >=0.10 && <0.13 + , domain-core ^>=0.1.0.4 + , foldl >=1.4.9 && <2 + , hashable >=1 && <2 + , parser-combinators >=1.3 && <1.4 + , template-haskell >=2.13 && <3 + , text >=1.2.3 && <3 + , th-lego ^>=0.3.0.3 + , yaml-unscrambler >=0.1 && <0.2 test-suite loading-demo - type: exitcode-stdio-1.0 - hs-source-dirs: loading-demo - main-is: Main.hs + type: exitcode-stdio-1.0 + hs-source-dirs: loading-demo + main-is: Main.hs default-language: Haskell2010 build-depends: - base, - domain, - text + , base + , domain + , text test-suite inline-demo - type: exitcode-stdio-1.0 - hs-source-dirs: inline-demo - main-is: Main.hs + type: exitcode-stdio-1.0 + hs-source-dirs: inline-demo + main-is: Main.hs default-language: Haskell2010 build-depends: - base, - domain, - text + , base + , domain + , text test-suite test - type: exitcode-stdio-1.0 - hs-source-dirs: test - default-extensions: BangPatterns, BlockArguments, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveLift, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, InstanceSigs, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedLabels, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, StrictData, TemplateHaskell, TupleSections, TypeApplications, TypeFamilies, TypeOperators - default-language: Haskell2010 - main-is: Main.hs + type: exitcode-stdio-1.0 + hs-source-dirs: test + default-extensions: + NoImplicitPrelude + NoMonomorphismRestriction + BangPatterns + BlockArguments + ConstraintKinds + DataKinds + DefaultSignatures + DeriveDataTypeable + DeriveFoldable + DeriveFunctor + DeriveGeneric + DeriveLift + DeriveTraversable + EmptyDataDecls + FlexibleContexts + FlexibleInstances + FunctionalDependencies + GADTs + GeneralizedNewtypeDeriving + InstanceSigs + LambdaCase + LiberalTypeSynonyms + MagicHash + MultiParamTypeClasses + MultiWayIf + OverloadedLabels + OverloadedStrings + ParallelListComp + PatternGuards + QuasiQuotes + RankNTypes + RecordWildCards + ScopedTypeVariables + StandaloneDeriving + StrictData + TemplateHaskell + TupleSections + TypeApplications + TypeFamilies + TypeOperators + + default-language: Haskell2010 + main-is: Main.hs other-modules: Util.TH Util.TH.LeafTypes + build-depends: - domain, - domain-core, - QuickCheck >=2.8.1 && <3, - quickcheck-instances >=0.3.11 && <0.4, - rerebase >=1.10.0.1 && <2, - tasty >=0.12 && <2, - tasty-hunit >=0.9 && <0.11, - tasty-quickcheck >=0.9 && <0.11, - template-haskell, - template-haskell-compat-v0208 >=0.1.6 && <0.2, - th-orphans >=0.13 && <0.14 + , domain + , domain-core + , rerebase >=1.10.0.1 && <2 + , tasty >=0.12 && <2 + , tasty-hunit >=0.9 && <0.11 + , template-haskell + , template-haskell-compat-v0208 >=0.1.6 && <0.2 + , th-orphans >=0.13 && <0.14 diff --git a/inline-demo/Main.hs b/inline-demo/Main.hs index e9bc522..e530b0f 100644 --- a/inline-demo/Main.hs +++ b/inline-demo/Main.hs @@ -1,22 +1,32 @@ -{-# LANGUAGE - QuasiQuotes, TemplateHaskell, - StandaloneDeriving, DeriveGeneric, DeriveDataTypeable, DeriveLift, - FlexibleInstances, MultiParamTypeClasses, - DataKinds, TypeFamilies, - OverloadedStrings, OverloadedLabels, TypeApplications - #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveLift #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedLabels #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# OPTIONS_GHC -Wno-type-equality-requires-operators #-} + module Main where import Data.Text (Text) import Data.Word (Word16, Word32, Word64) import Domain - main :: IO () main = return () -declare (Just (False, True)) stdDeriver [schema| +declare + (Just (False, True)) + stdDeriver + [schema| ServiceAddress: sum: @@ -51,19 +61,17 @@ declare (Just (False, True)) stdDeriver [schema| |] -{-| -Shows how you can construct sum-types and enum-types using labels. - -We need to specify the type for the #name constructor member, -because otherwise the compiler interprets it as String. --} +-- | +-- Shows how you can construct sum-types and enum-types using labels. +-- +-- We need to specify the type for the #name constructor member, +-- because otherwise the compiler interprets it as String. serviceAddress :: ServiceAddress serviceAddress = #network (NetworkAddress #tcp (#name ("local" :: Text)) 1234) -{-| -Shows how you can map. Unfortunately that requires a lot of manual typing. --} +-- | +-- Shows how you can map. Unfortunately that requires a lot of manual typing. updatedServiceAddress :: ServiceAddress updatedServiceAddress = #network (#port (succ @Word16) :: NetworkAddress -> NetworkAddress) serviceAddress diff --git a/library/Domain.hs b/library/Domain.hs index 24dcc93..da3b8d2 100644 --- a/library/Domain.hs +++ b/library/Domain.hs @@ -1,175 +1,172 @@ -{-| -This module contains the whole API of \"domain\". - -Many functions come with collapsed example sections. -Do check them out for better understanding. --} +-- | +-- This module contains the whole API of \"domain\". +-- +-- Many functions come with collapsed example sections. +-- Do check them out for better understanding. module Domain -( - -- * Declaration - declare, - -- * Schema - Schema, - schema, - loadSchema, - -- * Deriver - Deriver.Deriver, - stdDeriver, - -- ** Common - enumDeriver, - boundedDeriver, - showDeriver, - eqDeriver, - ordDeriver, - genericDeriver, - dataDeriver, - typeableDeriver, - hashableDeriver, - liftDeriver, - -- ** HasField - hasFieldDeriver, - -- ** IsLabel - constructorIsLabelDeriver, - accessorIsLabelDeriver, - mapperIsLabelDeriver, - -- * Clarifications - -- ** Type Equality Constraint #type-equality-constraint# - -- | - -- You may have noticed that some instances (in particular of 'IsLabel') - -- have some unusual tilde (@~@) constraint: - -- - -- @ - -- instance a ~ TransportProtocol => IsLabel "protocol" (NetworkAddress -> a) - -- @ - -- - -- This constraint states that types are equal. - -- You might be wondering why do that instead of just - -- - -- @ - -- instance IsLabel "protocol" (NetworkAddress -> TransportProtocol) - -- @ - -- - -- The reason is that it helps the compiler pick up this instance having - -- only the non-variable parts of the type signature, - -- since type equality is verified after the instance match. - -- This provides for better type inference and better error messages. - -- - -- In case of our example we're ensuring that the compiler will pick - -- up the instance for any function parameterised by @NetworkAddress@. -) + ( -- * Declaration + declare, + + -- * Schema + Schema, + schema, + loadSchema, + + -- * Deriver + Deriver.Deriver, + stdDeriver, + + -- ** Common + enumDeriver, + boundedDeriver, + showDeriver, + eqDeriver, + ordDeriver, + genericDeriver, + dataDeriver, + typeableDeriver, + hashableDeriver, + liftDeriver, + + -- ** HasField + hasFieldDeriver, + + -- ** IsLabel + constructorIsLabelDeriver, + accessorIsLabelDeriver, + mapperIsLabelDeriver, + + -- * Clarifications + + -- ** Type Equality Constraint #type-equality-constraint# + + -- | + -- You may have noticed that some instances (in particular of 'IsLabel') + -- have some unusual tilde (@~@) constraint: + -- + -- @ + -- instance a ~ TransportProtocol => IsLabel "protocol" (NetworkAddress -> a) + -- @ + -- + -- This constraint states that types are equal. + -- You might be wondering why do that instead of just + -- + -- @ + -- instance IsLabel "protocol" (NetworkAddress -> TransportProtocol) + -- @ + -- + -- The reason is that it helps the compiler pick up this instance having + -- only the non-variable parts of the type signature, + -- since type equality is verified after the instance match. + -- This provides for better type inference and better error messages. + -- + -- In case of our example we're ensuring that the compiler will pick + -- up the instance for any function parameterised by @NetworkAddress@. + ) where -import Domain.Prelude hiding (liftEither, readFile, lift) -import Language.Haskell.TH.Syntax -import Language.Haskell.TH.Quote import qualified Data.ByteString as ByteString import qualified Data.Text.Encoding as Text +import Domain.Prelude hiding (readFile) import qualified Domain.Resolvers.TypeCentricDoc as TypeCentricResolver -import qualified Domain.TH.TypeDec as TypeDec import qualified Domain.TH.InstanceDecs as InstanceDecs +import qualified Domain.TH.TypeDec as TypeDec import qualified Domain.YamlUnscrambler.TypeCentricDoc as TypeCentricYaml import qualified DomainCore.Deriver as Deriver import qualified DomainCore.Model as Model +import Language.Haskell.TH.Quote +import Language.Haskell.TH.Syntax import qualified YamlUnscrambler - -{-| -Declare datatypes and typeclass instances -from a schema definition according to the provided settings. - -Use this function in combination with the 'schema' quasi-quoter or -the 'loadSchema' function. -__For examples__ refer to their documentation. - -Call it on the top-level (where you declare your module members). --} +-- | +-- Declare datatypes and typeclass instances +-- from a schema definition according to the provided settings. +-- +-- Use this function in combination with the 'schema' quasi-quoter or +-- the 'loadSchema' function. +-- __For examples__ refer to their documentation. +-- +-- Call it on the top-level (where you declare your module members). declare :: - {-| - Field naming. - When nothing, no fields will be generated. - Otherwise the first wrapped boolean specifies, - whether to prefix the names with underscore, - and the second - whether to prefix with the type name. - Please notice that when you choose not to prefix with the type name - you need to have the @DuplicateRecords@ extension enabled. - -} + -- | + -- Field naming. + -- When nothing, no fields will be generated. + -- Otherwise the first wrapped boolean specifies, + -- whether to prefix the names with underscore, + -- and the second - whether to prefix with the type name. + -- Please notice that when you choose not to prefix with the type name + -- you need to have the @DuplicateRecords@ extension enabled. Maybe (Bool, Bool) -> - {-| - Which instances to derive and how. - -} + -- | + -- Which instances to derive and how. Deriver.Deriver -> - {-| - Schema definition. - -} + -- | + -- Schema definition. Schema -> - {-| - Template Haskell action splicing the generated code on declaration level. - -} + -- | + -- Template Haskell action splicing the generated code on declaration level. Q [Dec] declare fieldNaming (Deriver.Deriver derive) (Schema schema) = do instanceDecs <- fmap (nub . concat) (traverse derive schema) return (fmap (TypeDec.typeDec fieldNaming) schema <> instanceDecs) - -- * Schema -------------------------- -{-| -Parsed and validated schema. +------------------------- -You can only produce it using the 'schema' quasi-quoter or -the 'loadSchema' function -and generate the code from it using 'declare'. --} -newtype Schema = - Schema [Model.TypeDec] +-- | +-- Parsed and validated schema. +-- +-- You can only produce it using the 'schema' quasi-quoter or +-- the 'loadSchema' function +-- and generate the code from it using 'declare'. +newtype Schema + = Schema [Model.TypeDec] deriving (Lift) -{-| -Quasi-quoter, which parses a YAML schema into a 'Schema' expression. - -Use 'declare' to generate the code from it. - -==== __Example__ - -@ -{\-# LANGUAGE - QuasiQuotes, TemplateHaskell, - StandaloneDeriving, DeriveGeneric, DeriveDataTypeable, DeriveLift, - FlexibleInstances, MultiParamTypeClasses, - DataKinds, TypeFamilies - #-\} -module Model where - -import Data.Text (Text) -import Data.Word (Word16, Word32, Word64) -import Domain - -'declare' - (Just (False, True)) - 'stdDeriver' - ['schema'| - - Host: - sum: - ip: Ip - name: Text - - Ip: - sum: - v4: Word32 - v6: Word128 - - Word128: - product: - part1: Word64 - part2: Word64 - - |] -@ - --} +-- | +-- Quasi-quoter, which parses a YAML schema into a 'Schema' expression. +-- +-- Use 'declare' to generate the code from it. +-- +-- ==== __Example__ +-- +-- @ +-- {\-# LANGUAGE +-- QuasiQuotes, TemplateHaskell, +-- StandaloneDeriving, DeriveGeneric, DeriveDataTypeable, DeriveLift, +-- FlexibleInstances, MultiParamTypeClasses, +-- DataKinds, TypeFamilies +-- #-\} +-- module Model where +-- +-- import Data.Text (Text) +-- import Data.Word (Word16, Word32, Word64) +-- import Domain +-- +-- 'declare' +-- (Just (False, True)) +-- 'stdDeriver' +-- ['schema'| +-- +-- Host: +-- sum: +-- ip: Ip +-- name: Text +-- +-- Ip: +-- sum: +-- v4: Word32 +-- v6: Word128 +-- +-- Word128: +-- product: +-- part1: Word64 +-- part2: Word64 +-- +-- |] +-- @ schema :: QuasiQuoter schema = QuasiQuoter exp pat type_ dec @@ -185,46 +182,43 @@ schema = dec = unsupported -{-| -Load and parse a YAML file into a schema definition. - -Use 'declare' to generate the code from it. - -==== __Example__ - -@ -{\-# LANGUAGE - TemplateHaskell, - StandaloneDeriving, DeriveGeneric, DeriveDataTypeable, DeriveLift, - FlexibleInstances, MultiParamTypeClasses, - DataKinds, TypeFamilies - #-\} -module Model where - -import Data.Text (Text) -import Data.Word (Word16, Word32, Word64) -import Domain - -'declare' - (Just (True, False)) - 'stdDeriver' - =<< 'loadSchema' "domain.yaml" -@ --} +-- | +-- Load and parse a YAML file into a schema definition. +-- +-- Use 'declare' to generate the code from it. +-- +-- ==== __Example__ +-- +-- @ +-- {\-# LANGUAGE +-- TemplateHaskell, +-- StandaloneDeriving, DeriveGeneric, DeriveDataTypeable, DeriveLift, +-- FlexibleInstances, MultiParamTypeClasses, +-- DataKinds, TypeFamilies +-- #-\} +-- module Model where +-- +-- import Data.Text (Text) +-- import Data.Word (Word16, Word32, Word64) +-- import Domain +-- +-- 'declare' +-- (Just (True, False)) +-- 'stdDeriver' +-- =<< 'loadSchema' "domain.yaml" +-- @ loadSchema :: - {-| - Path to the schema file relative to the root of the project. - -} + -- | + -- Path to the schema file relative to the root of the project. FilePath -> - {-| - Template Haskell action producing a valid schema. - -} + -- | + -- Template Haskell action producing a valid schema. Q Schema loadSchema path = readFile path >>= parseByteString - -- * Helpers + ------------------------- readFile :: FilePath -> Q ByteString @@ -251,349 +245,350 @@ parseByteString input = liftEither :: Either Text a -> Q a liftEither = - \ case + \case Left err -> fail (toList err) - Right a -> return a - + Right a -> return a -- * Deriver + ------------------------- -{-| -Combination of all derivers exported by this module. --} +-- | +-- Combination of all derivers exported by this module. +stdDeriver :: Deriver.Deriver stdDeriver = - mconcat [ - enumDeriver, - boundedDeriver, - showDeriver, - eqDeriver, - ordDeriver, - genericDeriver, - dataDeriver, - typeableDeriver, - hashableDeriver, - liftDeriver, - hasFieldDeriver, - constructorIsLabelDeriver, - mapperIsLabelDeriver, - accessorIsLabelDeriver + mconcat + [ enumDeriver, + boundedDeriver, + showDeriver, + eqDeriver, + ordDeriver, + genericDeriver, + dataDeriver, + typeableDeriver, + hashableDeriver, + liftDeriver, + hasFieldDeriver, + constructorIsLabelDeriver, + mapperIsLabelDeriver, + accessorIsLabelDeriver ] -{-| -Derives 'Enum' for enums or sums having no members in all variants. - -Requires to have the @StandaloneDeriving@ compiler extension enabled. --} +-- | +-- Derives 'Enum' for enums or sums having no members in all variants. +-- +-- Requires to have the @StandaloneDeriving@ compiler extension enabled. +enumDeriver :: Deriver.Deriver enumDeriver = Deriver.effectless InstanceDecs.enum -{-| -Derives 'Bounded' for enums. - -Requires to have the @StandaloneDeriving@ compiler extension enabled. --} +-- | +-- Derives 'Bounded' for enums. +-- +-- Requires to have the @StandaloneDeriving@ compiler extension enabled. +boundedDeriver :: Deriver.Deriver boundedDeriver = Deriver.effectless InstanceDecs.bounded -{-| -Derives 'Show'. - -Requires to have the @StandaloneDeriving@ compiler extension enabled. --} +-- | +-- Derives 'Show'. +-- +-- Requires to have the @StandaloneDeriving@ compiler extension enabled. +showDeriver :: Deriver.Deriver showDeriver = Deriver.effectless InstanceDecs.show -{-| -Derives 'Eq'. - -Requires to have the @StandaloneDeriving@ compiler extension enabled. --} +-- | +-- Derives 'Eq'. +-- +-- Requires to have the @StandaloneDeriving@ compiler extension enabled. +eqDeriver :: Deriver.Deriver eqDeriver = Deriver.effectless InstanceDecs.eq -{-| -Derives 'Ord'. - -Requires to have the @StandaloneDeriving@ compiler extension enabled. --} +-- | +-- Derives 'Ord'. +-- +-- Requires to have the @StandaloneDeriving@ compiler extension enabled. +ordDeriver :: Deriver.Deriver ordDeriver = Deriver.effectless InstanceDecs.ord -{-| -Derives 'Generic'. - -Requires to have the @StandaloneDeriving@ and @DeriveGeneric@ compiler extensions enabled. --} +-- | +-- Derives 'Generic'. +-- +-- Requires to have the @StandaloneDeriving@ and @DeriveGeneric@ compiler extensions enabled. +genericDeriver :: Deriver.Deriver genericDeriver = Deriver.effectless InstanceDecs.generic -{-| -Derives 'Data'. - -Requires to have the @StandaloneDeriving@ and @DeriveDataTypeable@ compiler extensions enabled. --} +-- | +-- Derives 'Data'. +-- +-- Requires to have the @StandaloneDeriving@ and @DeriveDataTypeable@ compiler extensions enabled. +dataDeriver :: Deriver.Deriver dataDeriver = Deriver.effectless InstanceDecs.data_ -{-| -Derives 'Typeable'. - -Requires to have the @StandaloneDeriving@ and @DeriveDataTypeable@ compiler extensions enabled. --} +-- | +-- Derives 'Typeable'. +-- +-- Requires to have the @StandaloneDeriving@ and @DeriveDataTypeable@ compiler extensions enabled. +typeableDeriver :: Deriver.Deriver typeableDeriver = Deriver.effectless InstanceDecs.typeable -{-| -Generates 'Generic'-based instances of 'Hashable'. --} +-- | +-- Generates 'Generic'-based instances of 'Hashable'. +hashableDeriver :: Deriver.Deriver hashableDeriver = Deriver.effectless InstanceDecs.hashable -{-| -Derives 'Lift'. - -Requires to have the @StandaloneDeriving@ and @DeriveLift@ compiler extensions enabled. --} +-- | +-- Derives 'Lift'. +-- +-- Requires to have the @StandaloneDeriving@ and @DeriveLift@ compiler extensions enabled. +liftDeriver :: Deriver.Deriver liftDeriver = Deriver.effectless InstanceDecs.lift -- ** HasField -------------------------- - -{-| -Derives 'HasField' with unprefixed field names. - -For each field of a product generates instances mapping to their values. -For each constructor of a sum maps to a 'Maybe' tuple of members of that constructor, -unless there\'s no members, in which case it maps to 'Bool'. - -For each variant of an enum maps to 'Bool' signaling whether the value equals to it. +------------------------- -/Please notice that if you choose to generate unprefixed record field accessors, it will conflict with this deriver, since it\'s gonna generate duplicate instances./ --} +-- | +-- Derives 'HasField' with unprefixed field names. +-- +-- For each field of a product generates instances mapping to their values. +-- +-- For each constructor of a sum maps to a 'Maybe' tuple of members of that constructor, +-- unless there\'s no members, in which case it maps to 'Bool'. +-- +-- For each variant of an enum maps to 'Bool' signaling whether the value equals to it. +-- +-- /Please notice that if you choose to generate unprefixed record field accessors, it will conflict with this deriver, since it\'s gonna generate duplicate instances./ +hasFieldDeriver :: Deriver.Deriver hasFieldDeriver = Deriver.effectless InstanceDecs.hasField - -- * IsLabel -------------------------- - -{-| -Generates instances of 'IsLabel' for wrappers, enums and sums, -providing mappings from labels to constructors. - -==== __Sum Example__ -Having the following schema: - -@ -Host: - sum: - ip: Ip - name: Text -@ - -The following instances will be generated: - -@ -instance a ~ Ip => IsLabel "ip" (a -> Host) where - fromLabel = IpHost - -instance a ~ Text => IsLabel "name" (a -> Host) where - fromLabel = NameHost -@ - -In case you\'re wondering what this tilde (@~@) constraint business is about, -refer to the [Type Equality Constraint](#type-equality-constraint) section. - -==== __Enum Example__ - -Having the following schema: - -@ -TransportProtocol: - enum: - - tcp - - udp -@ - -The following instances will be generated: - -@ -instance IsLabel "tcp" TransportProtocol where - fromLabel = TcpTransportProtocol +------------------------- -instance IsLabel "udp" TransportProtocol where - fromLabel = UdpTransportProtocol -@ --} +-- | +-- Generates instances of 'IsLabel' for wrappers, enums and sums, +-- providing mappings from labels to constructors. +-- +-- ==== __Sum Example__ +-- +-- Having the following schema: +-- +-- @ +-- Host: +-- sum: +-- ip: Ip +-- name: Text +-- @ +-- +-- The following instances will be generated: +-- +-- @ +-- instance a ~ Ip => IsLabel "ip" (a -> Host) where +-- fromLabel = IpHost +-- +-- instance a ~ Text => IsLabel "name" (a -> Host) where +-- fromLabel = NameHost +-- @ +-- +-- In case you\'re wondering what this tilde (@~@) constraint business is about, +-- refer to the [Type Equality Constraint](#type-equality-constraint) section. +-- +-- ==== __Enum Example__ +-- +-- Having the following schema: +-- +-- @ +-- TransportProtocol: +-- enum: +-- - tcp +-- - udp +-- @ +-- +-- The following instances will be generated: +-- +-- @ +-- instance IsLabel "tcp" TransportProtocol where +-- fromLabel = TcpTransportProtocol +-- +-- instance IsLabel "udp" TransportProtocol where +-- fromLabel = UdpTransportProtocol +-- @ +constructorIsLabelDeriver :: Deriver.Deriver constructorIsLabelDeriver = Deriver.effectless InstanceDecs.constructorIsLabel -{-| -Generates instances of 'IsLabel' for enums, sums and products, -providing accessors to their components. - -==== __Product Example__ - -Having the following schema: - -@ -NetworkAddress: - product: - protocol: TransportProtocol - host: Host - port: Word16 -@ - -The following instances will be generated: - -@ -instance a ~ TransportProtocol => IsLabel "protocol" (NetworkAddress -> a) where - fromLabel (NetworkAddress a _ _) = a - -instance a ~ Host => IsLabel "host" (NetworkAddress -> a) where - fromLabel (NetworkAddress _ b _) = b - -instance a ~ Word16 => IsLabel "port" (NetworkAddress -> a) where - fromLabel (NetworkAddress _ _ c) = c -@ - -In case you\'re wondering what this tilde (@~@) constraint business is about, -refer to the [Type Equality Constraint](#type-equality-constraint) section. - -==== __Sum Example__ - -Having the following schema: - -@ -Host: - sum: - ip: Ip - name: Text -@ - -The following instances will be generated: - -@ -instance a ~ Maybe Ip => IsLabel "ip" (Host -> a) where - fromLabel (IpHost a) = Just a - fromLabel _ = Nothing - -instance a ~ Maybe Text => IsLabel "name" (Host -> a) where - fromLabel (NameHost a) = Just a - fromLabel _ = Nothing -@ - -In case you\'re wondering what this tilde (@~@) constraint business is about, -refer to the [Type Equality Constraint](#type-equality-constraint) section. - -==== __Enum Example__ - -Having the following schema: - -@ -TransportProtocol: - enum: - - tcp - - udp -@ - -The following instances will be generated: - -@ -instance a ~ Bool => IsLabel "tcp" (TransportProtocol -> a) where - fromLabel TcpTransportProtocol = True - fromLabel _ = False - -instance a ~ Bool => IsLabel "udp" (TransportProtocol -> a) where - fromLabel UdpTransportProtocol = True - fromLabel _ = False -@ - -In case you\'re wondering what this tilde (@~@) constraint business is about, -refer to the [Type Equality Constraint](#type-equality-constraint) section. --} +-- | +-- Generates instances of 'IsLabel' for enums, sums and products, +-- providing accessors to their components. +-- +-- ==== __Product Example__ +-- +-- Having the following schema: +-- +-- @ +-- NetworkAddress: +-- product: +-- protocol: TransportProtocol +-- host: Host +-- port: Word16 +-- @ +-- +-- The following instances will be generated: +-- +-- @ +-- instance a ~ TransportProtocol => IsLabel "protocol" (NetworkAddress -> a) where +-- fromLabel (NetworkAddress a _ _) = a +-- +-- instance a ~ Host => IsLabel "host" (NetworkAddress -> a) where +-- fromLabel (NetworkAddress _ b _) = b +-- +-- instance a ~ Word16 => IsLabel "port" (NetworkAddress -> a) where +-- fromLabel (NetworkAddress _ _ c) = c +-- @ +-- +-- In case you\'re wondering what this tilde (@~@) constraint business is about, +-- refer to the [Type Equality Constraint](#type-equality-constraint) section. +-- +-- ==== __Sum Example__ +-- +-- Having the following schema: +-- +-- @ +-- Host: +-- sum: +-- ip: Ip +-- name: Text +-- @ +-- +-- The following instances will be generated: +-- +-- @ +-- instance a ~ Maybe Ip => IsLabel "ip" (Host -> a) where +-- fromLabel (IpHost a) = Just a +-- fromLabel _ = Nothing +-- +-- instance a ~ Maybe Text => IsLabel "name" (Host -> a) where +-- fromLabel (NameHost a) = Just a +-- fromLabel _ = Nothing +-- @ +-- +-- In case you\'re wondering what this tilde (@~@) constraint business is about, +-- refer to the [Type Equality Constraint](#type-equality-constraint) section. +-- +-- ==== __Enum Example__ +-- +-- Having the following schema: +-- +-- @ +-- TransportProtocol: +-- enum: +-- - tcp +-- - udp +-- @ +-- +-- The following instances will be generated: +-- +-- @ +-- instance a ~ Bool => IsLabel "tcp" (TransportProtocol -> a) where +-- fromLabel TcpTransportProtocol = True +-- fromLabel _ = False +-- +-- instance a ~ Bool => IsLabel "udp" (TransportProtocol -> a) where +-- fromLabel UdpTransportProtocol = True +-- fromLabel _ = False +-- @ +-- +-- In case you\'re wondering what this tilde (@~@) constraint business is about, +-- refer to the [Type Equality Constraint](#type-equality-constraint) section. +accessorIsLabelDeriver :: Deriver.Deriver accessorIsLabelDeriver = Deriver.effectless InstanceDecs.accessorIsLabel -{-| -Generates instances of 'IsLabel' for sums and products, -providing mappers over their components. - -==== __Product Example__ - -Having the following schema: - -@ -NetworkAddress: - product: - protocol: TransportProtocol - host: Host - port: Word16 -@ - -The following instances will be generated: - -@ -instance - mapper ~ (TransportProtocol -> TransportProtocol) => - IsLabel "protocol" (mapper -> NetworkAddress -> NetworkAddress) - where - fromLabel mapper (NetworkAddress a b c) = - NetworkAddress (mapper a) b c - -instance - mapper ~ (Host -> Host) => - IsLabel "host" (mapper -> NetworkAddress -> NetworkAddress) - where - fromLabel mapper (NetworkAddress a b c) = - NetworkAddress a (mapper b) c - -instance - mapper ~ (Word16 -> Word16) => - IsLabel "port" (mapper -> NetworkAddress -> NetworkAddress) - where - fromLabel mapper (NetworkAddress a b c) = - NetworkAddress a b (mapper c) -@ - -In case you\'re wondering what this tilde (@~@) constraint business is about, -refer to the [Type Equality Constraint](#type-equality-constraint) section. - -==== __Sum Example__ - -Having the following schema: - -@ -Host: - sum: - ip: Ip - name: Text -@ - -The following instances will be generated: - -@ -instance - mapper ~ (Ip -> Ip) => - IsLabel "ip" (mapper -> Host -> Host) - where - fromLabel fn (IpHost a) = IpHost (fn a) - fromLabel _ a = a - -instance - mapper ~ (Text -> Text) => - IsLabel "name" (mapper -> Host -> Host) - where - fromLabel fn (NameHost a) = NameHost (fn a) - fromLabel _ a = a -@ - -In case you\'re wondering what this tilde (@~@) constraint business is about, -refer to the [Type Equality Constraint](#type-equality-constraint) section. --} +-- | +-- Generates instances of 'IsLabel' for sums and products, +-- providing mappers over their components. +-- +-- ==== __Product Example__ +-- +-- Having the following schema: +-- +-- @ +-- NetworkAddress: +-- product: +-- protocol: TransportProtocol +-- host: Host +-- port: Word16 +-- @ +-- +-- The following instances will be generated: +-- +-- @ +-- instance +-- mapper ~ (TransportProtocol -> TransportProtocol) => +-- IsLabel "protocol" (mapper -> NetworkAddress -> NetworkAddress) +-- where +-- fromLabel mapper (NetworkAddress a b c) = +-- NetworkAddress (mapper a) b c +-- +-- instance +-- mapper ~ (Host -> Host) => +-- IsLabel "host" (mapper -> NetworkAddress -> NetworkAddress) +-- where +-- fromLabel mapper (NetworkAddress a b c) = +-- NetworkAddress a (mapper b) c +-- +-- instance +-- mapper ~ (Word16 -> Word16) => +-- IsLabel "port" (mapper -> NetworkAddress -> NetworkAddress) +-- where +-- fromLabel mapper (NetworkAddress a b c) = +-- NetworkAddress a b (mapper c) +-- @ +-- +-- In case you\'re wondering what this tilde (@~@) constraint business is about, +-- refer to the [Type Equality Constraint](#type-equality-constraint) section. +-- +-- ==== __Sum Example__ +-- +-- Having the following schema: +-- +-- @ +-- Host: +-- sum: +-- ip: Ip +-- name: Text +-- @ +-- +-- The following instances will be generated: +-- +-- @ +-- instance +-- mapper ~ (Ip -> Ip) => +-- IsLabel "ip" (mapper -> Host -> Host) +-- where +-- fromLabel fn (IpHost a) = IpHost (fn a) +-- fromLabel _ a = a +-- +-- instance +-- mapper ~ (Text -> Text) => +-- IsLabel "name" (mapper -> Host -> Host) +-- where +-- fromLabel fn (NameHost a) = NameHost (fn a) +-- fromLabel _ a = a +-- @ +-- +-- In case you\'re wondering what this tilde (@~@) constraint business is about, +-- refer to the [Type Equality Constraint](#type-equality-constraint) section. +mapperIsLabelDeriver :: Deriver.Deriver mapperIsLabelDeriver = Deriver.effectless InstanceDecs.mapperIsLabel diff --git a/library/Domain/Attoparsec/General.hs b/library/Domain/Attoparsec/General.hs index 97b4b9a..506c8d5 100644 --- a/library/Domain/Attoparsec/General.hs +++ b/library/Domain/Attoparsec/General.hs @@ -1,20 +1,22 @@ -module Domain.Attoparsec.General -where +module Domain.Attoparsec.General where -import Domain.Prelude hiding (takeWhile) import Data.Attoparsec.Text import qualified Data.Text as Text +import Domain.Prelude hiding (takeWhile) - +only :: Parser a -> Parser a only parser = skipSpace *> parser <* skipSpace <* endOfInput +commaSeparated :: Parser a -> Parser [a] commaSeparated parser = sepBy parser comma +comma :: Parser Char comma = skipSpace *> char ',' <* skipSpace +inParens :: Parser b -> Parser b inParens parser = do char '(' @@ -24,6 +26,7 @@ inParens parser = char ')' return a +inSquareBrackets :: Parser b -> Parser b inSquareBrackets parser = do char '[' @@ -33,17 +36,21 @@ inSquareBrackets parser = char ']' return a +skipSpace1 :: Parser () skipSpace1 = space *> skipSpace +name :: (Char -> Bool) -> Parser Text name firstCharPred = do a <- satisfy firstCharPred - b <- takeWhile (\ a -> isAlphaNum a || a == '\'' || a == '_') + b <- takeWhile (\a -> isAlphaNum a || a == '\'' || a == '_') return (Text.cons a b) +ucName :: Parser Text ucName = name isUpper +lcName :: Parser Text lcName = - name (\ a -> isLower a || a == '_') + name (\a -> isLower a || a == '_') diff --git a/library/Domain/Attoparsec/TypeString.hs b/library/Domain/Attoparsec/TypeString.hs index 433db75..b087ba4 100644 --- a/library/Domain/Attoparsec/TypeString.hs +++ b/library/Domain/Attoparsec/TypeString.hs @@ -1,27 +1,27 @@ -module Domain.Attoparsec.TypeString -where +module Domain.Attoparsec.TypeString where -import Domain.Prelude hiding (takeWhile) -import Domain.Models.TypeString +import Control.Applicative.Combinators.NonEmpty import Data.Attoparsec.Text hiding (sepBy1) import Domain.Attoparsec.General -import Control.Applicative.Combinators.NonEmpty - +import Domain.Models.TypeString +import Domain.Prelude hiding (takeWhile) +commaSeq :: Parser [NonEmpty Unit] commaSeq = commaSeparated appSeq +appSeq :: Parser (NonEmpty Unit) appSeq = sepBy1 unit skipSpace1 +unit :: Parser Unit unit = - asum [ - InSquareBracketsUnit <$> inSquareBrackets appSeq - , - InParensUnit <$> inParens commaSeq - , - RefUnit <$> typeRef + asum + [ InSquareBracketsUnit <$> inSquareBrackets appSeq, + InParensUnit <$> inParens commaSeq, + RefUnit <$> typeRef ] +typeRef :: Parser (NonEmpty Text) typeRef = sepBy1 ucName (char '.') diff --git a/library/Domain/Docs.hs b/library/Domain/Docs.hs index 6b219ad..5d4b435 100644 --- a/library/Domain/Docs.hs +++ b/library/Domain/Docs.hs @@ -1,261 +1,269 @@ module Domain.Docs -( - -- * How it works - {-| - \"domain\" operates around Schema AST which describes the structure of your model. - This AST gets constructed by either parsing a file or a quasi-quote - conforming to a <#g:schemaSyntaxReference further described> format. - Then it is used to generate Haskell type declarations and - typeclass instances according to your configuration. - All that is done at compile time, so you're incurring zero run time cost - for using \"domain\". - -} - -- * Schema Syntax Reference #schemaSyntaxReference# - {-| - Schema definition is a YAML document listing declarations of your domain - types. The listing is represented as a dictionary from type names to their - definitions. There is 3 types of definitions: <#product Product>, - <#sum Sum>, <#enum Enum>. - -} - -- ** Product #product# - {-| - Defines a type comprised of other types using - , - associating a unique textual label with each member. You may know it as - \"record\". - - Here\'s an example of a product type declaration in schema: - - > NetworkAddress: - > product: - > protocol: TransportProtocol - > host: Host - > port: Word16 - - Depending on the settings you provide one of the following Haskell type - declarations can be generated from it: - - > data NetworkAddress = - > NetworkAddress !TransportProtocol !Host !Word16 - - > data NetworkAddress = - > NetworkAddress { - > networkAddressProtocol :: !TransportProtocol, - > networkAddressHost :: !Host, - > networkAddressPort :: !Word16 - > } - - > data NetworkAddress = - > NetworkAddress { - > _protocol :: !TransportProtocol, - > _host :: !Host, - > _port :: !Word16 - > } - - > data NetworkAddress = - > NetworkAddress { - > protocol :: !TransportProtocol, - > host :: !Host, - > port :: !Word16 - > } - -} - -- *** Accessing fields #accessing-product-fields# - {-| - - Regardless of the way you choose to generate the data declaration, neat - mechanisms of accessing members can be provided using the automatically - generated @IsLabel@ instances or instances of @LabelOptic@ (using the - \"domain-optics\" package). - - E.g., here\'s how you can be accessing the members of the example - data-type: - - > getNetworkAddressPort :: NetworkAddress -> Word16 - > getNetworkAddressPort = #port - - > mapNetworkAddressHost :: (Host -> Host) -> NetworkAddress -> NetworkAddress - > mapNetworkAddressHost = over #host -- Using "domain-optics" and "optics" - - -} - -- ** Sum #sum# - {-| - - Defines a type comprised of other types using - , - associating a unique textual label with each member. You may know it as - tagged union or variant. - - Here\'s an example of a schema declaration of a sum type: - - > Host: - > sum: - > ip: Ip - > name: Text - - The following Haskell code will be generated from it: - - > data Host = - > IpHost !Ip | - > NameHost !Text - - As you can see the constructor names are intentionally made to be - unambiguous. You may already be thinking \"But the code is gonna get so - verbose\". It\'s not. Thanks to the automatically generatable @IsLabel@ - and @LabelOptic@ instances. - - E.g., here\'s how you\'ll be able to access the variants of the - data-type: - - > getHostIp :: Host -> Maybe Ip - > getHostIp = #ip - - > ipHost :: Ip -> Host - > ipHost = #ip - - > mapHostIp :: (Ip -> Ip) -> Host -> Host - > mapHostIp = over #ip -- Using "domain-optics" and "optics" - - -} - -- *** Multi-member sums #multi-member-sums# - {-| - - It is possible to provide multiple members of a sum variant using a - comma-separated list or YAML sequence. You can provide zero members as - well. E.g., - - > Error: - > sum: - > channel: - > - ChannelId - > - Text - > connectionLost: - - This will generate the following declaration: - - > data Error = - > ChannelError !ChannelId !Text | - > ConnectionLostError - - Depending on the number of variant members the generated accessors will - point to tuples or booleans: - - > getErrorChannel :: Error -> Maybe (ChannelId, Text) - > getErrorChannel = #channel - > - > getErrorConnectionLost :: Error -> Bool - > getErrorConnectionLost = #connectionLost - -} - -- ** Enum #enum# - {-| - Type which can have one value out of a specific set of options. - - Here\'s an example of a schema declaration of an enum type: - - > TransportProtocol: - > enum: - > - tcp - > - udp - - This will generate the following Haskell data type: - - > data TransportProtocol = - > TcpTransportProtocol | - > UdpTransportProtocol - - The following 'IsLabel' helpers will be available for it: - - > tcpTransportProtocol :: TransportProtocol - > tcpTransportProtocol = #tcp - > - > getTransportProtocolTcp :: TransportProtocol -> Bool - > getTransportProtocolTcp = #tcp - - -} - -- ** Notes #notes# - -- *** List Data-type #list-data-type# - {-| - Since square brackets get interpreted in YAML as array literal, you have to - explicitly state that the value is a string literal. To achieve that prefix - the value with the vertical line character (@|@). E.g., - - > Artist: - > product: - > name: Text - > genres: | [Genre] - -} - -- *** Reserved Names - {-| - You can use the otherwise banned field names like \"data\", \"type\", - \"class\". - -} - -- *** Newtypes - {-| - Single-field products get represented as newtypes, - so use them whenever you need to generate a newtype declaration. - -} - -- *** Type Aliases - {-| - Schemas intentionally lack support for type aliases, - since they haven't yet proven to be very useful in practice. - - However we\'re open for discussion on the subject. - So do provide your arguments on the project\'s issue tracker - if you feel like they should be added as a feature. - -} - -- *** Polymorphic Types - {-| - Polymorphic types are not supported. - Domain model is expected to consist of specific data structures, - not abstractions. - -} - -- * Instance Derivation - {-| - Instance derivation is intentionally isolated from the schema definition - to let both tasks be focused. - Instances get derived for all the types in your schema that they are suitable for. - We treat schema as a group entity over multiple types - having them share settings including the instance generation rules. - - Whenever you find yourself in a situation where you need different instances - for parts of your model it should serve as a signal that you\'re likely - dealing with multiple models merged into one. - The solution to such situation is to extract smaller models. - When dealing with Domain Schema that is what will also let you - generate different instances. - -} - -- ** Custom Derivers - {-| - The \"domain\" package does not expose any means to create custom derivers, - since its API focuses on their usage as part of the problems of - the general audience. - To create custom derivers you\'ll have to use the - ["domain-core"](http://hackage.haskell.org/package/domain-core) package, - which exposes the internal definition of the 'DomainCore.Deriver.Deriver' abstraction and - everything you need to define custom derivers. - - Such isolation of libraries lets us have a stable API for the general audience, - serving for better backward compatibility, and keep it isolated from - the distractions of lower level details. - -} - -- ** Deriver Extensions - {-| - We expect the community to publish their general custom derivers as extensional - packages. - - So far the following packages are available: - - - ["domain-aeson"](http://hackage.haskell.org/package/domain-aeson) - provides - integration with the ["aeson"](http://hackage.haskell.org/package/aeson) package. - - ["domain-cereal"](http://hackage.haskell.org/package/domain-cereal) - provides - integration with the ["cereal"](http://hackage.haskell.org/package/cereal) package. - - ["domain-optics"](http://hackage.haskell.org/package/domain-optics) - provides - integration with the ["optics"](http://hackage.haskell.org/package/optics) package. - - If you\'re looking to contribute, - some likely needed candidates for extensions are \"QuickCheck\", \"binary\". - -} -) + ( -- * How it works + + -- | + -- \"domain\" operates around Schema AST which describes the structure of your model. + -- This AST gets constructed by either parsing a file or a quasi-quote + -- conforming to a <#g:schemaSyntaxReference further described> format. + -- Then it is used to generate Haskell type declarations and + -- typeclass instances according to your configuration. + -- All that is done at compile time, so you're incurring zero run time cost + -- for using \"domain\". + + -- * Schema Syntax Reference #schemaSyntaxReference# + + -- | + -- Schema definition is a YAML document listing declarations of your domain + -- types. The listing is represented as a dictionary from type names to their + -- definitions. There is 3 types of definitions: <#product Product>, + -- <#sum Sum>, <#enum Enum>. + + -- ** Product #product# + + -- | + -- Defines a type comprised of other types using + -- , + -- associating a unique textual label with each member. You may know it as + -- \"record\". + -- + -- Here\'s an example of a product type declaration in schema: + -- + -- > NetworkAddress: + -- > product: + -- > protocol: TransportProtocol + -- > host: Host + -- > port: Word16 + -- + -- Depending on the settings you provide one of the following Haskell type + -- declarations can be generated from it: + -- + -- > data NetworkAddress = + -- > NetworkAddress !TransportProtocol !Host !Word16 + -- + -- > data NetworkAddress = + -- > NetworkAddress { + -- > networkAddressProtocol :: !TransportProtocol, + -- > networkAddressHost :: !Host, + -- > networkAddressPort :: !Word16 + -- > } + -- + -- > data NetworkAddress = + -- > NetworkAddress { + -- > _protocol :: !TransportProtocol, + -- > _host :: !Host, + -- > _port :: !Word16 + -- > } + -- + -- > data NetworkAddress = + -- > NetworkAddress { + -- > protocol :: !TransportProtocol, + -- > host :: !Host, + -- > port :: !Word16 + -- > } + + -- *** Accessing fields #accessing-product-fields# + + -- | + -- + -- Regardless of the way you choose to generate the data declaration, neat + -- mechanisms of accessing members can be provided using the automatically + -- generated @IsLabel@ instances or instances of @LabelOptic@ (using the + -- \"domain-optics\" package). + -- + -- E.g., here\'s how you can be accessing the members of the example + -- data-type: + -- + -- > getNetworkAddressPort :: NetworkAddress -> Word16 + -- > getNetworkAddressPort = #port + -- + -- > mapNetworkAddressHost :: (Host -> Host) -> NetworkAddress -> NetworkAddress + -- > mapNetworkAddressHost = over #host -- Using "domain-optics" and "optics" + + -- ** Sum #sum# + + -- | + -- + -- Defines a type comprised of other types using + -- , + -- associating a unique textual label with each member. You may know it as + -- tagged union or variant. + -- + -- Here\'s an example of a schema declaration of a sum type: + -- + -- > Host: + -- > sum: + -- > ip: Ip + -- > name: Text + -- + -- The following Haskell code will be generated from it: + -- + -- > data Host = + -- > IpHost !Ip | + -- > NameHost !Text + -- + -- As you can see the constructor names are intentionally made to be + -- unambiguous. You may already be thinking \"But the code is gonna get so + -- verbose\". It\'s not. Thanks to the automatically generatable @IsLabel@ + -- and @LabelOptic@ instances. + -- + -- E.g., here\'s how you\'ll be able to access the variants of the + -- data-type: + -- + -- > getHostIp :: Host -> Maybe Ip + -- > getHostIp = #ip + -- + -- > ipHost :: Ip -> Host + -- > ipHost = #ip + -- + -- > mapHostIp :: (Ip -> Ip) -> Host -> Host + -- > mapHostIp = over #ip -- Using "domain-optics" and "optics" + + -- *** Multi-member sums #multi-member-sums# + + -- | + -- + -- It is possible to provide multiple members of a sum variant using a + -- comma-separated list or YAML sequence. You can provide zero members as + -- well. E.g., + -- + -- > Error: + -- > sum: + -- > channel: + -- > - ChannelId + -- > - Text + -- > connectionLost: + -- + -- This will generate the following declaration: + -- + -- > data Error = + -- > ChannelError !ChannelId !Text | + -- > ConnectionLostError + -- + -- Depending on the number of variant members the generated accessors will + -- point to tuples or booleans: + -- + -- > getErrorChannel :: Error -> Maybe (ChannelId, Text) + -- > getErrorChannel = #channel + -- > + -- > getErrorConnectionLost :: Error -> Bool + -- > getErrorConnectionLost = #connectionLost + + -- ** Enum #enum# + + -- | + -- Type which can have one value out of a specific set of options. + -- + -- Here\'s an example of a schema declaration of an enum type: + -- + -- > TransportProtocol: + -- > enum: + -- > - tcp + -- > - udp + -- + -- This will generate the following Haskell data type: + -- + -- > data TransportProtocol = + -- > TcpTransportProtocol | + -- > UdpTransportProtocol + -- + -- The following 'IsLabel' helpers will be available for it: + -- + -- > tcpTransportProtocol :: TransportProtocol + -- > tcpTransportProtocol = #tcp + -- > + -- > getTransportProtocolTcp :: TransportProtocol -> Bool + -- > getTransportProtocolTcp = #tcp + + -- ** Notes #notes# + + -- *** List Data-type #list-data-type# + + -- | + -- Since square brackets get interpreted in YAML as array literal, you have to + -- explicitly state that the value is a string literal. To achieve that prefix + -- the value with the vertical line character (@|@). E.g., + -- + -- > Artist: + -- > product: + -- > name: Text + -- > genres: | [Genre] + + -- *** Reserved Names + + -- | + -- You can use the otherwise banned field names like \"data\", \"type\", + -- \"class\". + + -- *** Newtypes + + -- | + -- Single-field products get represented as newtypes, + -- so use them whenever you need to generate a newtype declaration. + + -- *** Type Aliases + + -- | + -- Schemas intentionally lack support for type aliases, + -- since they haven't yet proven to be very useful in practice. + -- + -- However we\'re open for discussion on the subject. + -- So do provide your arguments on the project\'s issue tracker + -- if you feel like they should be added as a feature. + + -- *** Polymorphic Types + + -- | + -- Polymorphic types are not supported. + -- Domain model is expected to consist of specific data structures, + -- not abstractions. + + -- * Instance Derivation + + -- | + -- Instance derivation is intentionally isolated from the schema definition + -- to let both tasks be focused. + -- Instances get derived for all the types in your schema that they are suitable for. + -- We treat schema as a group entity over multiple types + -- having them share settings including the instance generation rules. + -- + -- Whenever you find yourself in a situation where you need different instances + -- for parts of your model it should serve as a signal that you\'re likely + -- dealing with multiple models merged into one. + -- The solution to such situation is to extract smaller models. + -- When dealing with Domain Schema that is what will also let you + -- generate different instances. + + -- ** Custom Derivers + + -- | + -- The \"domain\" package does not expose any means to create custom derivers, + -- since its API focuses on their usage as part of the problems of + -- the general audience. + -- To create custom derivers you\'ll have to use the + -- ["domain-core"](http://hackage.haskell.org/package/domain-core) package, + -- which exposes the internal definition of the 'DomainCore.Deriver.Deriver' abstraction and + -- everything you need to define custom derivers. + -- + -- Such isolation of libraries lets us have a stable API for the general audience, + -- serving for better backward compatibility, and keep it isolated from + -- the distractions of lower level details. + + -- ** Deriver Extensions + + -- | + -- We expect the community to publish their general custom derivers as extensional + -- packages. + -- + -- So far the following packages are available: + -- + -- - ["domain-aeson"](http://hackage.haskell.org/package/domain-aeson) - provides + -- integration with the ["aeson"](http://hackage.haskell.org/package/aeson) package. + -- - ["domain-cereal"](http://hackage.haskell.org/package/domain-cereal) - provides + -- integration with the ["cereal"](http://hackage.haskell.org/package/cereal) package. + -- - ["domain-optics"](http://hackage.haskell.org/package/domain-optics) - provides + -- integration with the ["optics"](http://hackage.haskell.org/package/optics) package. + -- + -- If you\'re looking to contribute, + -- some likely needed candidates for extensions are \"QuickCheck\", \"binary\". + ) where - -import Domain.Prelude hiding (liftEither, readFile, lift) -import Domain diff --git a/library/Domain/Prelude.hs b/library/Domain/Prelude.hs index e99fbf7..7abc30c 100644 --- a/library/Domain/Prelude.hs +++ b/library/Domain/Prelude.hs @@ -1,25 +1,23 @@ module Domain.Prelude -( - module Exports, - showAsText, -) + ( module Exports, + showAsText, + ) where --- base -------------------------- -import Control.Applicative as Exports hiding (WrappedArrow(..)) +import Control.Applicative as Exports hiding (WrappedArrow (..)) import Control.Arrow as Exports hiding (first, second) import Control.Category as Exports import Control.Concurrent as Exports import Control.Exception as Exports -import Control.Monad as Exports hiding (fail, mapM_, sequence_, forM_, msum, mapM, sequence, forM) -import Control.Monad.IO.Class as Exports +import Control.Monad as Exports hiding (fail, forM, forM_, mapM, mapM_, msum, sequence, sequence_) import Control.Monad.Fail as Exports import Control.Monad.Fix as Exports hiding (fix) +import Control.Monad.IO.Class as Exports import Control.Monad.ST as Exports import Data.Bifunctor as Exports import Data.Bits as Exports import Data.Bool as Exports +import Data.ByteString as Exports (ByteString) import Data.Char as Exports import Data.Coerce as Exports import Data.Complex as Exports @@ -29,14 +27,15 @@ import Data.Either as Exports import Data.Fixed as Exports import Data.Foldable as Exports hiding (toList) import Data.Function as Exports hiding (id, (.)) -import Data.Functor as Exports +import Data.Functor as Exports hiding (unzip) import Data.Functor.Compose as Exports import Data.Functor.Contravariant as Exports -import Data.Int as Exports +import Data.Hashable as Exports (Hashable) import Data.IORef as Exports +import Data.Int as Exports import Data.Ix as Exports -import Data.List as Exports hiding (sortOn, isSubsequenceOf, uncons, concat, foldr, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, find, maximumBy, minimumBy, mapAccumL, mapAccumR, foldl') -import Data.List.NonEmpty as Exports (NonEmpty(..)) +import Data.List as Exports hiding (all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons) +import Data.List.NonEmpty as Exports (NonEmpty (..)) import Data.Maybe as Exports import Data.Monoid as Exports hiding (Alt) import Data.Ord as Exports @@ -44,6 +43,7 @@ import Data.Proxy as Exports import Data.Ratio as Exports import Data.STRef as Exports import Data.String as Exports +import Data.Text as Exports (Text) import Data.Traversable as Exports import Data.Tuple as Exports import Data.Unique as Exports @@ -55,14 +55,14 @@ import Foreign.ForeignPtr as Exports import Foreign.Ptr as Exports import Foreign.StablePtr as Exports import Foreign.Storable as Exports -import GHC.Conc as Exports hiding (orElse, withMVar, threadWaitWriteSTM, threadWaitWrite, threadWaitReadSTM, threadWaitRead) -import GHC.Exts as Exports (IsList(..), lazy, inline, sortWith, groupWith) +import GHC.Conc as Exports hiding (orElse, threadWaitRead, threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, withMVar) +import GHC.Exts as Exports (IsList (..), groupWith, inline, lazy, sortWith) import GHC.Generics as Exports (Generic) import GHC.IO.Exception as Exports import GHC.OverloadedLabels as Exports import GHC.Records as Exports +import Language.Haskell.TH.Syntax as Exports (Lift) import Numeric as Exports -import Prelude as Exports hiding (fail, concat, foldr, mapM_, sequence_, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, mapM, sequence, id, (.)) import System.Environment as Exports import System.Exit as Exports import System.IO as Exports (Handle, hClose) @@ -71,27 +71,10 @@ import System.IO.Unsafe as Exports import System.Mem as Exports import System.Mem.StableName as Exports import System.Timeout as Exports -import Text.ParserCombinators.ReadP as Exports (ReadP, ReadS, readP_to_S, readS_to_P) -import Text.ParserCombinators.ReadPrec as Exports (ReadPrec, readPrec_to_P, readP_to_Prec, readPrec_to_S, readS_to_Prec) -import Text.Printf as Exports (printf, hPrintf) -import Text.Read as Exports (Read(..), readMaybe, readEither) +import Text.Printf as Exports (hPrintf, printf) +import Text.Read as Exports (Read (..), readEither, readMaybe) import Unsafe.Coerce as Exports +import Prelude as Exports hiding (all, and, any, concat, concatMap, elem, fail, foldl, foldl1, foldr, foldr1, id, mapM, mapM_, maximum, minimum, notElem, or, product, sequence, sequence_, sum, (.)) --- text -------------------------- -import Data.Text as Exports (Text) - --- bytestring -------------------------- -import Data.ByteString as Exports (ByteString) - --- hashable -------------------------- -import Data.Hashable as Exports (Hashable) - --- template-haskell -------------------------- -import Language.Haskell.TH.Syntax as Exports (Lift) - -showAsText :: Show a => a -> Text +showAsText :: (Show a) => a -> Text showAsText = show >>> fromString diff --git a/library/Domain/Resolvers/TypeCentricDoc.hs b/library/Domain/Resolvers/TypeCentricDoc.hs index 89d0579..2d225c2 100644 --- a/library/Domain/Resolvers/TypeCentricDoc.hs +++ b/library/Domain/Resolvers/TypeCentricDoc.hs @@ -1,19 +1,17 @@ -module Domain.Resolvers.TypeCentricDoc -where +module Domain.Resolvers.TypeCentricDoc where -import Domain.Prelude hiding (lookup) -import DomainCore.Model +import qualified Data.Text as Text import qualified Domain.Models.TypeCentricDoc as Doc import qualified Domain.Models.TypeString as TypeString -import qualified Data.Text as Text +import Domain.Prelude hiding (lookup) import qualified Domain.Text as Text +import DomainCore.Model - -eliminateDoc :: Applicative f => Doc.Doc -> f [TypeDec] +eliminateDoc :: (Applicative f) => Doc.Doc -> f [TypeDec] eliminateDoc = traverse (uncurry (structureTypeDecs [])) >>> fmap join -structureTypeDecs :: Applicative f => [Text] -> Text -> Doc.Structure -> f [TypeDec] +structureTypeDecs :: (Applicative f) => [Text] -> Text -> Doc.Structure -> f [TypeDec] structureTypeDecs namespace name structure = (:) <$> primary <*> structureGeneratedTypeDecs nextNamespace structure where @@ -25,9 +23,9 @@ structureTypeDecs namespace name structure = nextNamespace = name : namespace -structureGeneratedTypeDecs :: Applicative f => [Text] -> Doc.Structure -> f [TypeDec] +structureGeneratedTypeDecs :: (Applicative f) => [Text] -> Doc.Structure -> f [TypeDec] structureGeneratedTypeDecs namespace = - \ case + \case Doc.ProductStructure structure -> traverse (uncurry (nestedTypeExpressionTypeDecs namespace . Text.ucFirst)) structure & fmap join @@ -37,16 +35,17 @@ structureGeneratedTypeDecs namespace = _ -> pure [] +nestedTypeExpressionTypeDecs :: (Applicative f) => [Text] -> Text -> Doc.NestedTypeExpression -> f [TypeDec] nestedTypeExpressionTypeDecs namespace name = - \ case + \case Doc.StructureNestedTypeExpression a -> structureTypeDecs namespace name a _ -> pure [] -structureTypeDef :: Applicative f => [Text] -> Doc.Structure -> f TypeDef +structureTypeDef :: (Applicative f) => [Text] -> Doc.Structure -> f TypeDef structureTypeDef namespace = - \ case + \case Doc.ProductStructure structure -> ProductTypeDef <$> traverse (uncurry (eliminateProductStructureUnit namespace)) structure Doc.SumStructure structure -> @@ -54,45 +53,48 @@ structureTypeDef namespace = Doc.EnumStructure variants -> pure (SumTypeDef (fmap (,[]) variants)) -eliminateProductStructureUnit :: Applicative f => [Text] -> Text -> Doc.NestedTypeExpression -> f (Text, Type) +eliminateProductStructureUnit :: (Applicative f) => [Text] -> Text -> Doc.NestedTypeExpression -> f (Text, Type) eliminateProductStructureUnit namespace name productTypeExpression = (,) name <$> nestedTypeExpressionType namespace name productTypeExpression -eliminateSumStructureUnit :: Applicative f => [Text] -> Text -> [Doc.NestedTypeExpression] -> f (Text, [Type]) +eliminateSumStructureUnit :: (Applicative f) => [Text] -> Text -> [Doc.NestedTypeExpression] -> f (Text, [Type]) eliminateSumStructureUnit namespace name sumTypeExpression = (,) name <$> traverse (nestedTypeExpressionType namespace name) sumTypeExpression -nestedTypeExpressionType :: Applicative f => [Text] -> Text -> Doc.NestedTypeExpression -> f Type +nestedTypeExpressionType :: (Applicative f) => [Text] -> Text -> Doc.NestedTypeExpression -> f Type nestedTypeExpressionType namespace name = - \ case + \case Doc.AppSeqNestedTypeExpression a -> AppType <$> eliminateTypeStringAppSeq a Doc.StructureNestedTypeExpression _ -> pure (RefType (Text.concat (reverse (Text.ucFirst name : namespace)))) +eliminateTypeStringCommaSeq :: (Traversable t, Applicative f) => t (NonEmpty TypeString.Unit) -> f (t (NonEmpty Type)) eliminateTypeStringCommaSeq = traverse eliminateTypeStringAppSeq -eliminateTypeStringAppSeq :: Applicative f => NonEmpty TypeString.Unit -> f (NonEmpty Type) +eliminateTypeStringAppSeq :: (Applicative f) => NonEmpty TypeString.Unit -> f (NonEmpty Type) eliminateTypeStringAppSeq = traverse eliminateTypeStringUnit +eliminateTypeStringUnit :: (Applicative f) => TypeString.Unit -> f Type eliminateTypeStringUnit = - \ case + \case TypeString.InSquareBracketsUnit appSeq -> - eliminateTypeStringAppSeq appSeq & - fmap (ListType . AppType) + eliminateTypeStringAppSeq appSeq + & fmap (ListType . AppType) TypeString.InParensUnit commaSeq -> - eliminateTypeStringCommaSeq commaSeq & - fmap (tupleIfNotOne . fmap AppType) + eliminateTypeStringCommaSeq commaSeq + & fmap (tupleIfNotOne . fmap AppType) where tupleIfNotOne = - \ case + \case [a] -> a a -> TupleType a TypeString.RefUnit typeRef -> - eliminateTypeRef typeRef & - fmap RefType + eliminateTypeRef typeRef + & fmap RefType +eliminateTypeRef :: (Applicative f) => NonEmpty Text -> f Text eliminateTypeRef = pure . Text.intercalate "." . toList diff --git a/library/Domain/TH/InstanceDec.hs b/library/Domain/TH/InstanceDec.hs index 9fb711b..e6ecda2 100644 --- a/library/Domain/TH/InstanceDec.hs +++ b/library/Domain/TH/InstanceDec.hs @@ -1,19 +1,16 @@ -{-| -Model-adapted instance declaration templates. --} -module Domain.TH.InstanceDec -where +-- | +-- Model-adapted instance declaration templates. +module Domain.TH.InstanceDec where import Domain.Prelude import DomainCore.Model -import qualified Language.Haskell.TH as TH import qualified DomainCore.TH as CoreTH -import qualified Data.Text as Text -import qualified THLego.Instances as Instances +import qualified Language.Haskell.TH as TH import qualified THLego.Helpers as Helpers - +import qualified THLego.Instances as Instances -- * HasField + ------------------------- enumHasField :: Text -> Text -> TH.Dec @@ -30,10 +27,8 @@ enumHasField typeName label = sumHasField :: Text -> Text -> [Type] -> TH.Dec sumHasField typeName label memberTypes = if null memberTypes - then - Instances.enumHasField thFieldLabel thOwnerType thConstructorName - else - Instances.sumHasField thFieldLabel thOwnerType thConstructorName thMemberTypes + then Instances.enumHasField thFieldLabel thOwnerType thConstructorName + else Instances.sumHasField thFieldLabel thOwnerType thConstructorName thMemberTypes where thFieldLabel = Helpers.textTyLit label @@ -46,8 +41,13 @@ sumHasField typeName label memberTypes = productHasField :: Text -> Text -> Type -> Int -> Int -> TH.Dec productHasField typeName fieldName projectionType numMemberTypes offset = - Instances.productHasField thFieldLabel thOwnerType thProjectionType - thConstructorName numMemberTypes offset + Instances.productHasField + thFieldLabel + thOwnerType + thProjectionType + thConstructorName + numMemberTypes + offset where thFieldLabel = Helpers.textTyLit fieldName @@ -58,18 +58,23 @@ productHasField typeName fieldName projectionType numMemberTypes offset = thConstructorName = Helpers.textName typeName - -- * IsLabel + ------------------------- -- ** Accessor + ------------------------- productAccessorIsLabel :: Text -> Text -> Type -> Int -> Int -> TH.Dec productAccessorIsLabel typeName fieldName projectionType numMemberTypes offset = Instances.productAccessorIsLabel - thFieldLabel thOwnerType thProjectionType thConstructorName - numMemberTypes offset + thFieldLabel + thOwnerType + thProjectionType + thConstructorName + numMemberTypes + offset where thFieldLabel = Helpers.textTyLit fieldName @@ -85,10 +90,15 @@ sumAccessorIsLabel typeName label memberTypes = if null memberTypes then Instances.enumAccessorIsLabel - thFieldLabel thOwnerType thConstructorName + thFieldLabel + thOwnerType + thConstructorName else Instances.sumAccessorIsLabel - thFieldLabel thOwnerType thConstructorName thMemberTypes + thFieldLabel + thOwnerType + thConstructorName + thMemberTypes where thFieldLabel = Helpers.textTyLit label @@ -102,7 +112,9 @@ sumAccessorIsLabel typeName label memberTypes = enumAccessorIsLabel :: Text -> Text -> TH.Dec enumAccessorIsLabel typeName label = Instances.enumAccessorIsLabel - thFieldLabel thOwnerType thConstructorName + thFieldLabel + thOwnerType + thConstructorName where thFieldLabel = Helpers.textTyLit label @@ -112,12 +124,16 @@ enumAccessorIsLabel typeName label = CoreTH.sumConstructorName typeName label -- ** Constructor + ------------------------- curriedSumConstructorIsLabel :: Text -> Text -> [Type] -> TH.Dec curriedSumConstructorIsLabel typeName label memberTypes = Instances.sumConstructorIsLabel - thFieldLabel thOwnerType thConstructorName thMemberTypes + thFieldLabel + thOwnerType + thConstructorName + thMemberTypes where thFieldLabel = Helpers.textTyLit label @@ -131,7 +147,10 @@ curriedSumConstructorIsLabel typeName label memberTypes = uncurriedSumConstructorIsLabel :: Text -> Text -> [Type] -> TH.Dec uncurriedSumConstructorIsLabel typeName label memberTypes = Instances.tupleAdtConstructorIsLabel - thFieldLabel thOwnerType thConstructorName thMemberTypes + thFieldLabel + thOwnerType + thConstructorName + thMemberTypes where thFieldLabel = Helpers.textTyLit label @@ -145,7 +164,9 @@ uncurriedSumConstructorIsLabel typeName label memberTypes = enumConstructorIsLabel :: Text -> Text -> TH.Dec enumConstructorIsLabel typeName label = Instances.enumConstructorIsLabel - thFieldLabel thOwnerType thConstructorName + thFieldLabel + thOwnerType + thConstructorName where thFieldLabel = Helpers.textTyLit label @@ -157,7 +178,10 @@ enumConstructorIsLabel typeName label = wrapperConstructorIsLabel :: Text -> Type -> TH.Dec wrapperConstructorIsLabel typeName memberType = Instances.newtypeConstructorIsLabel - thFieldLabel thOwnerType thConstructorName thMemberType + thFieldLabel + thOwnerType + thConstructorName + thMemberType where thFieldLabel = TH.StrTyLit "value" @@ -169,12 +193,18 @@ wrapperConstructorIsLabel typeName memberType = CoreTH.typeType memberType -- ** Mapper + ------------------------- wrapperMapperIsLabel :: Text -> Type -> TH.Dec wrapperMapperIsLabel typeName memberType = Instances.productMapperIsLabel - thFieldLabel thOwnerType thMemberType thConstructorName 1 0 + thFieldLabel + thOwnerType + thMemberType + thConstructorName + 1 + 0 where thFieldLabel = TH.StrTyLit "value" @@ -188,8 +218,12 @@ wrapperMapperIsLabel typeName memberType = productMapperIsLabel :: Text -> Text -> Type -> Int -> Int -> TH.Dec productMapperIsLabel typeName fieldName projectionType numMemberTypes offset = Instances.productMapperIsLabel - thFieldLabel thOwnerType thProjectionType thConstructorName - numMemberTypes offset + thFieldLabel + thOwnerType + thProjectionType + thConstructorName + numMemberTypes + offset where thFieldLabel = Helpers.textTyLit fieldName @@ -203,7 +237,10 @@ productMapperIsLabel typeName fieldName projectionType numMemberTypes offset = sumMapperIsLabel :: Text -> Text -> [Type] -> TH.Dec sumMapperIsLabel typeName label memberTypes = Instances.sumMapperIsLabel - thFieldLabel thOwnerType thConstructorName thMemberTypes + thFieldLabel + thOwnerType + thConstructorName + thMemberTypes where thFieldLabel = Helpers.textTyLit label @@ -214,8 +251,6 @@ sumMapperIsLabel typeName label memberTypes = thMemberTypes = fmap CoreTH.typeType memberTypes - --- * ------------------------- deriving_ :: TH.Name -> Text -> TH.Dec diff --git a/library/Domain/TH/InstanceDecs.hs b/library/Domain/TH/InstanceDecs.hs index 0680bac..536d4e1 100644 --- a/library/Domain/TH/InstanceDecs.hs +++ b/library/Domain/TH/InstanceDecs.hs @@ -1,11 +1,9 @@ -module Domain.TH.InstanceDecs -where +module Domain.TH.InstanceDecs where import Domain.Prelude -import DomainCore.Model import qualified Domain.TH.InstanceDec as InstanceDec -import qualified Language.Haskell.TH as TH (Dec, Name) - +import DomainCore.Model +import qualified Language.Haskell.TH as TH (Dec) hasField :: TypeDec -> [TH.Dec] hasField (TypeDec typeName typeDef) = @@ -34,10 +32,11 @@ accessorIsLabel (TypeDec typeName typeDef) = zipper offset (fieldName, fieldType) = InstanceDec.productAccessorIsLabel typeName fieldName fieldType numMembers offset SumTypeDef variants -> - variants & - fmap (\ (variantName, memberTypes) -> - InstanceDec.sumAccessorIsLabel typeName variantName memberTypes - ) + variants + & fmap + ( \(variantName, memberTypes) -> + InstanceDec.sumAccessorIsLabel typeName variantName memberTypes + ) constructorIsLabel :: TypeDec -> [TH.Dec] constructorIsLabel (TypeDec typeName typeDef) = @@ -45,24 +44,25 @@ constructorIsLabel (TypeDec typeName typeDef) = ProductTypeDef members -> [] SumTypeDef variants -> - variants & - fmap (\ (variantName, memberTypes) -> - InstanceDec.curriedSumConstructorIsLabel typeName variantName memberTypes) + variants + & fmap + ( \(variantName, memberTypes) -> + InstanceDec.curriedSumConstructorIsLabel typeName variantName memberTypes + ) variantConstructorIsLabel :: Text -> (Text, [Type]) -> [TH.Dec] variantConstructorIsLabel typeName (variantName, memberTypes) = - let - curried = - InstanceDec.curriedSumConstructorIsLabel typeName variantName memberTypes - uncurried = - InstanceDec.uncurriedSumConstructorIsLabel typeName variantName memberTypes - in case memberTypes of - [] -> - [curried] - [_] -> - [curried] - _ -> - [curried, uncurried] + let curried = + InstanceDec.curriedSumConstructorIsLabel typeName variantName memberTypes + uncurried = + InstanceDec.uncurriedSumConstructorIsLabel typeName variantName memberTypes + in case memberTypes of + [] -> + [curried] + [_] -> + [curried] + _ -> + [curried, uncurried] mapperIsLabel :: TypeDec -> [TH.Dec] mapperIsLabel (TypeDec typeName typeDef) = @@ -81,8 +81,8 @@ mapperIsLabel (TypeDec typeName typeDef) = then empty else pure (InstanceDec.sumMapperIsLabel typeName variantName memberTypes) - -- * Deriving + ------------------------- byNonAliasName :: (Text -> TH.Dec) -> TypeDec -> [TH.Dec] @@ -92,37 +92,48 @@ byNonAliasName cont (TypeDec a b) = byEnumName :: (Text -> TH.Dec) -> TypeDec -> [TH.Dec] byEnumName cont (TypeDec name def) = case def of - SumTypeDef variants | all (null . snd) variants -> - [cont name] + SumTypeDef variants + | all (null . snd) variants -> + [cont name] _ -> [] +enum :: TypeDec -> [TH.Dec] enum = byEnumName (InstanceDec.deriving_ ''Enum) +bounded :: TypeDec -> [TH.Dec] bounded = byEnumName (InstanceDec.deriving_ ''Bounded) +show :: TypeDec -> [TH.Dec] show = byNonAliasName (InstanceDec.deriving_ ''Show) +eq :: TypeDec -> [TH.Dec] eq = byNonAliasName (InstanceDec.deriving_ ''Eq) +ord :: TypeDec -> [TH.Dec] ord = byNonAliasName (InstanceDec.deriving_ ''Ord) +generic :: TypeDec -> [TH.Dec] generic = byNonAliasName (InstanceDec.deriving_ ''Generic) +data_ :: TypeDec -> [TH.Dec] data_ = byNonAliasName (InstanceDec.deriving_ ''Data) +typeable :: TypeDec -> [TH.Dec] typeable = byNonAliasName (InstanceDec.deriving_ ''Typeable) +hashable :: TypeDec -> [TH.Dec] hashable = byNonAliasName (InstanceDec.empty ''Hashable) +lift :: TypeDec -> [TH.Dec] lift = byNonAliasName (InstanceDec.deriving_ ''Lift) diff --git a/library/Domain/TH/TypeDec.hs b/library/Domain/TH/TypeDec.hs index 70c22c4..abc02f4 100644 --- a/library/Domain/TH/TypeDec.hs +++ b/library/Domain/TH/TypeDec.hs @@ -1,13 +1,12 @@ -module Domain.TH.TypeDec -where +module Domain.TH.TypeDec where import Domain.Prelude import DomainCore.Model +import qualified DomainCore.TH as CoreTH import qualified Language.Haskell.TH as TH import qualified THLego.Helpers as TH -import qualified DomainCore.TH as CoreTH - +typeDec :: Maybe (Bool, Bool) -> TypeDec -> TH.Dec typeDec fieldNaming (TypeDec a b) = case b of SumTypeDef b -> diff --git a/library/Domain/Text.hs b/library/Domain/Text.hs index 225a11a..c07e00f 100644 --- a/library/Domain/Text.hs +++ b/library/Domain/Text.hs @@ -1,17 +1,18 @@ -module Domain.Text -where +module Domain.Text where -import Domain.Prelude -import Data.Text import qualified Data.Char as Char +import Data.Text +import Domain.Prelude - +mapFirstChar :: (Char -> Char) -> Text -> Text mapFirstChar fn = - foldMap (\ (a, b) -> cons (fn a) b) . - uncons + foldMap (\(a, b) -> cons (fn a) b) + . uncons +ucFirst :: Text -> Text ucFirst = mapFirstChar Char.toUpper +lcFirst :: Text -> Text lcFirst = mapFirstChar Char.toLower diff --git a/library/Domain/YamlUnscrambler/TypeCentricDoc.hs b/library/Domain/YamlUnscrambler/TypeCentricDoc.hs index d7be5d6..886e4c3 100644 --- a/library/Domain/YamlUnscrambler/TypeCentricDoc.hs +++ b/library/Domain/YamlUnscrambler/TypeCentricDoc.hs @@ -1,15 +1,15 @@ -module Domain.YamlUnscrambler.TypeCentricDoc -where +module Domain.YamlUnscrambler.TypeCentricDoc where -import Domain.Prelude -import Domain.Models.TypeCentricDoc -import YamlUnscrambler -import qualified Domain.Attoparsec.TypeString as TypeStringAttoparsec -import qualified Domain.Attoparsec.General as GeneralAttoparsec import qualified Control.Foldl as Fold import qualified Data.Text as Text +import qualified Domain.Attoparsec.General as GeneralAttoparsec +import qualified Domain.Attoparsec.TypeString as TypeStringAttoparsec +import Domain.Models.TypeCentricDoc +import qualified Domain.Models.TypeString as TypeStringModel +import Domain.Prelude +import YamlUnscrambler - +doc :: Value [(Text, Structure)] doc = value onScalar (Just onMapping) Nothing where @@ -19,24 +19,23 @@ doc = foldMapping (,) Fold.list typeNameString structure where typeNameString = - formattedString "type name" $ \ input -> + formattedString "type name" $ \input -> case Text.uncons input of Just (h, t) -> if isUpper h then - if Text.all (\ a -> isAlphaNum a || a == '\'' || a == '_') t - then - Right input - else - Left "Contains invalid chars" - else - Left "First char is not upper-case" + if Text.all (\a -> isAlphaNum a || a == '\'' || a == '_') t + then Right input + else Left "Contains invalid chars" + else Left "First char is not upper-case" Nothing -> Left "Empty string" +structure :: Value Structure structure = value [] (Just structureMapping) Nothing +byFieldName :: Value val -> Value [(Text, val)] byFieldName onElement = value onScalar (Just onMapping) Nothing where @@ -45,22 +44,23 @@ byFieldName onElement = onMapping = foldMapping (,) Fold.list textString onElement +sumTypeExpression :: Value [NestedTypeExpression] sumTypeExpression = value onScalar (Just onMapping) (Just onSequence) where onScalar = - [ - nullScalar [] - , - fmap (fmap AppSeqNestedTypeExpression) $ - stringScalar $ attoparsedString "Type signature" $ - GeneralAttoparsec.only TypeStringAttoparsec.commaSeq - ] + [ nullScalar [], + fmap (fmap AppSeqNestedTypeExpression) + $ stringScalar + $ attoparsedString "Type signature" + $ GeneralAttoparsec.only TypeStringAttoparsec.commaSeq + ] onMapping = pure . StructureNestedTypeExpression <$> structureMapping onSequence = foldSequence Fold.list nestedTypeExpression +nestedTypeExpression :: Value NestedTypeExpression nestedTypeExpression = value [onScalar] (Just onMapping) Nothing where @@ -69,25 +69,30 @@ nestedTypeExpression = onMapping = StructureNestedTypeExpression <$> structureMapping +enumVariants :: Value [Text] enumVariants = sequenceValue (foldSequence Fold.list variant) where variant = scalarsValue [stringScalar textString] - -- * Scalar + ------------------------- +appTypeStringScalar :: Scalar (NonEmpty TypeStringModel.Unit) appTypeStringScalar = - stringScalar $ attoparsedString "Type signature" $ - GeneralAttoparsec.only TypeStringAttoparsec.appSeq + stringScalar + $ attoparsedString "Type signature" + $ GeneralAttoparsec.only TypeStringAttoparsec.appSeq -- * Mapping + ------------------------- +structureMapping :: Mapping Structure structureMapping = - byKeyMapping (CaseSensitive True) $ - atByKey "product" (ProductStructure <$> byFieldName nestedTypeExpression) <|> - atByKey "sum" (SumStructure <$> byFieldName sumTypeExpression) <|> - atByKey "enum" (EnumStructure <$> enumVariants) + byKeyMapping (CaseSensitive True) + $ atByKey "product" (ProductStructure <$> byFieldName nestedTypeExpression) + <|> atByKey "sum" (SumStructure <$> byFieldName sumTypeExpression) + <|> atByKey "enum" (EnumStructure <$> enumVariants) diff --git a/loading-demo/Main.hs b/loading-demo/Main.hs index 07f25c7..05a493f 100644 --- a/loading-demo/Main.hs +++ b/loading-demo/Main.hs @@ -1,16 +1,20 @@ -{-# LANGUAGE - TemplateHaskell, - StandaloneDeriving, DeriveGeneric, DeriveDataTypeable, DeriveLift, - FlexibleInstances, MultiParamTypeClasses, - DataKinds, TypeFamilies - #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveLift #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} +{-# OPTIONS_GHC -Wno-type-equality-requires-operators #-} + module Main where import Data.Text (Text) import Data.Word (Word16, Word32, Word64) import Domain - main :: IO () main = return () diff --git a/stack.yaml b/stack.yaml deleted file mode 100644 index 9a2c583..0000000 --- a/stack.yaml +++ /dev/null @@ -1,9 +0,0 @@ -resolver: nightly-2021-06-24 -extra-deps: - - acc-0.1.3 - - attoparsec-data-1.0.5.2 - - attoparsec-time-1.0.1.2 - - domain-core-0.1 - - th-lego-0.2.3 - - yaml-unscrambler-0.1.0.3 - - template-haskell-compat-v0208-0.1.6 diff --git a/stack.yaml.lock b/stack.yaml.lock deleted file mode 100644 index d802461..0000000 --- a/stack.yaml.lock +++ /dev/null @@ -1,61 +0,0 @@ -# This file was autogenerated by Stack. -# You should not edit this file by hand. -# For more information, please see the documentation at: -# https://docs.haskellstack.org/en/stable/lock_files - -packages: -- completed: - hackage: acc-0.1.3@sha256:8c63dda349e4b37bb8de1977bc35db05d77c34fa85becb09dcb7e29bc00d52f9,3806 - pantry-tree: - size: 499 - sha256: fef5cfe3383d092f512a28f6031f9bd532763e1e66e7d0ae8be182cd6b4880fd - original: - hackage: acc-0.1.3 -- completed: - hackage: attoparsec-data-1.0.5.2@sha256:b7a74c47d7758ccbc3b1ebe909996d2075e46c9a9b99f91e3163bf546ff8c6e5,1542 - pantry-tree: - size: 527 - sha256: 92f309f53e2ba6c715bed19a950b4ff23ea0f2b61b26afebfc93579aec312921 - original: - hackage: attoparsec-data-1.0.5.2 -- completed: - hackage: attoparsec-time-1.0.1.2@sha256:4b2e24e2ecf1787561428c2ef666a7f2f3e63cd190412afc8ad96176f303ba5f,1633 - pantry-tree: - size: 535 - sha256: 9502c502910cebc271a7dc30b6b4c9684ad245594c1bc76e9da3d4dcfa59b499 - original: - hackage: attoparsec-time-1.0.1.2 -- completed: - hackage: domain-core-0.1@sha256:1e7610f229157f097eb1a89b10dfbc893d0ac0780f738d646649ff1fdc495a36,1710 - pantry-tree: - size: 493 - sha256: 9d07e6e84db4d70c297f855e7776b8e04b6bb7586e1f535c0dc6a62ca46acacb - original: - hackage: domain-core-0.1 -- completed: - hackage: th-lego-0.2.3@sha256:54f9abc6b0133868b7d3198c83755aa7c035449aa88b545111d1fe8c1093a669,2777 - pantry-tree: - size: 471 - sha256: 59b8d924ea7dd5675a2c609d1c8e7de62544876595576efd22db1734bc914649 - original: - hackage: th-lego-0.2.3 -- completed: - hackage: yaml-unscrambler-0.1.0.3@sha256:4d4aa2dd5ab1bb4e4dbf9ba260e2ef690a849659596bd32205ef2ae99fb7766d,3564 - pantry-tree: - size: 1363 - sha256: 64c68fc285fedd4c54159960a58b040b1dece2ca23e6c2174c7089fa2dd6b149 - original: - hackage: yaml-unscrambler-0.1.0.3 -- completed: - hackage: template-haskell-compat-v0208-0.1.6@sha256:f6ede586642947f925f04876ca082b1d113fd74a3fac458b88b29fcf01ac1068,1522 - pantry-tree: - size: 341 - sha256: 35a6fabd7701f2bb4b8820b8a4dffc0a3122d9c6161a5acf94ce38b5e53f17c1 - original: - hackage: template-haskell-compat-v0208-0.1.6 -snapshots: -- completed: - size: 524750 - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/nightly/2021/6/24.yaml - sha256: c8e830e08daf4ff0278dec8c838a15f1a59c98c1d9f3467deda7ac68ad73dfc4 - original: nightly-2021-06-24 diff --git a/test/Main.hs b/test/Main.hs index 8d517f6..c3cbbee 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -1,55 +1,53 @@ module Main where -import Prelude hiding (assert) +import qualified Domain +import qualified DomainCore.Model as Model import Language.Haskell.TH.Instances () -import Test.QuickCheck.Instances +import qualified Language.Haskell.TH.Syntax as TH import Test.Tasty -import Test.Tasty.Runners import Test.Tasty.HUnit -import Test.Tasty.QuickCheck -import qualified Domain -import qualified DomainCore.Model as Model -import qualified Test.QuickCheck as QuickCheck -import qualified Data.Text as Text import qualified Util.TH as TH import qualified Util.TH.LeafTypes as THLeafTypes -import qualified Language.Haskell.TH.Syntax as TH - +import Prelude hiding (assert) main :: IO () main = - defaultMain $ - testGroup "All tests" [ - testCase "Should fail when wrong member of sum-type is supplied" $ let - res :: Maybe [Model.TypeDec] - res = - [TH.maybeDecsQQ| + defaultMain + $ testGroup + "All tests" + [ testCase "Should fail when wrong member of sum-type is supplied" + $ let res :: Maybe [Model.TypeDec] + res = + [TH.maybeDecsQQ| A: sum: a: c: Int b: Char, Double |] - in case res of - Just res -> - assertFailure (show res) - Nothing -> - return () - , - testCase "Nested structures shouldn't contain any unit-tuple types" $ let - decs :: [TH.Dec] - decs = - $(TH.lift - =<< Domain.declare Nothing mempty [Domain.schema| + in case res of + Just res -> + assertFailure (show res) + Nothing -> + return (), + testCase "Nested structures shouldn't contain any unit-tuple types" + $ let decs :: [TH.Dec] + decs = + $( TH.lift + =<< Domain.declare + Nothing + mempty + [Domain.schema| A: product: a: Maybe (Maybe Int) - |]) - leafTypes = - foldMap THLeafTypes.fromDec decs - in case elemIndex (TH.TupleT 1) leafTypes of - Just _ -> - assertFailure (show decs) - Nothing -> - return () - ] + |] + ) + leafTypes = + foldMap THLeafTypes.fromDec decs + in case elemIndex (TH.TupleT 1) leafTypes of + Just _ -> + assertFailure (show decs) + Nothing -> + return () + ] diff --git a/test/Util/TH/LeafTypes.hs b/test/Util/TH/LeafTypes.hs index e6c5f86..220af00 100644 --- a/test/Util/TH/LeafTypes.hs +++ b/test/Util/TH/LeafTypes.hs @@ -1,33 +1,40 @@ module Util.TH.LeafTypes where -import Prelude import Language.Haskell.TH.Syntax import TemplateHaskell.Compat.V0208 +import Prelude - +fromDec :: Dec -> [Kind] fromDec = - \ case + \case NewtypeD a _ b c d _ -> - fromCxt a <> - concatMap fromTyVarBndr b <> - foldMap fromType c <> - fromCon d + fromCxt a + <> concatMap fromTyVarBndr b + <> foldMap fromType c + <> fromCon d + _ -> error "TODO" +fromTyVarBndr :: TyVarBndr flag -> [Kind] fromTyVarBndr = maybeToList . tyVarBndrKind +fromCxt :: Cxt -> [Kind] fromCxt = concatMap fromType +fromCon :: Con -> [Kind] fromCon = - \ case + \case NormalC _ bangTypes -> concatMap fromBangType bangTypes + _ -> error "TODO" +fromBangType :: (a, Type) -> [Kind] fromBangType (_, t) = fromType t +fromType :: Type -> [Kind] fromType = - \ case + \case ForallT a b c -> concatMap fromTyVarBndr a <> fromCxt b <> fromType c ForallVisT a b ->