packages feed

supply-chain (empty) → 0.0.0.0

raw patch · 12 files changed

+660/−0 lines, 12 filesdep +basedep +supply-chain-core

Dependencies added: base, supply-chain-core

Files

+ changelog.md view
@@ -0,0 +1,3 @@+# 0.0.0.0 (2022-11-08)++* Initial release
+ license.txt view
@@ -0,0 +1,13 @@+Copyright 2022 Mission Valley Software LLC++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++    http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.
+ readme.md view
@@ -0,0 +1,243 @@+A *supply chain* represents a flow of information from one `Vendor` to the next,+and so on, ultimately reaching a `Job` that returns a product.++```haskell+run (vendor1 >-> vendor2 >-> vendor3 >- job)+```++A job or vendor can place an `order`, which is fulfilled by the vendor+*upstream* of it. In the above example:++* `vendor2` is downstream of `vendor1`; the orders made by `vendor2` are served+  by `vendor1`.+* The orders made by the `job` are served by `vendor3`.+* The orders made by `vendor3` are served by `vendor2`.+* `vendor1` does not make any requests (its upstream interface is `Const Void`).+++## Interfaces++An *interface* is a type constructor (kind `Type -> Type`) that describes the+requests and responses exchanged between a vendor and a job.++If a job's upstream interface is `i`, then when the job makes a request of type+`i x`, it receives a response of type `x`.++Values of a type of this kind represent requests. Each constructor will+typically have a constraint that specifies what type of response is expected in+return. Types of this kind are therefore often GADTs. Types of this kind are+also often not functors.++The lack of any interface at all can be expressed as `Const Void`.+++## Actions++An *action* is a monadic context such as `IO`.++The lack of any actions at all can be expressed as `Const Void`. (If you are+used to dealing with monad transformers, you might be familiar with using+`Identity` as a trivial context, but such a layer is not needed here.)+++## Jobs++`Job up action product` is a monadic context that supports:++  - Making requests on the `up` interface+  - Performing side effects in the `action` context+  - Returning a single `product` value++```+            ▲   │+      up a  │   │  a+            │   ▼+┌─────────────────────────┐+│  Job up action product  │+└─────────────────────────┘+              │+              │  product+              ▼+```+++## Writing jobs++`Job` belongs to the `Monad` class, and there are two functions for making+requests and performing actions respectively:++```haskell+order :: up product -> Job up action product+```++```haskell+perform :: action product -> Job up action product+```++A job may also be altered by connecting it to a vendor; see *Vendor-to-job+connection* below.+++## Vendors++`Vendor up down action` can:++  - Respond to requests received via the `down` interface+  - Make requests on the `up` interface+  - Perform side effects in the `action` context++```+              ▲   │+        up a  │   │  a+              │   ▼+┌───────────────────────────┐+│   Vendor up down action   │+└───────────────────────────┘+              ▲   │+      down b  │   │  b+              │   ▼+```++The most common way to use vendors is to connect them to jobs using `(>->)` and+`(>-)`.+++## Vendor-to-vendor connection++If `i` is the downstream interface of vendor `v1` and the upstream interface of+vendor `v2`, then we can form the composition `v1 >-> v2`.++```haskell+(>->) :: Vendor up i action+      -> Vendor i down action+      -> Vendor up down action+```++```+             ▲   │+       up a  │   │  a+             │   ▼              ─┐+┌────────────────────────┐       │+│   Vendor up i action   │  v1   │+└────────────────────────┘       │+             ▲   │               │+        i b  │   │  b            │  v1 >-> v2+             │   ▼               │+┌────────────────────────┐       │+│  Vendor i down action  │  v2   │+└────────────────────────┘       │+             ▲   │              ─┘+     down c  │   │  c+             │   ▼+```++When the downstream vendor makes a request of type `i b`, the upstream vendor+replies with a response of type `b`.++`(>->)` and `id` form a category.++```haskell+id :: Vendor i i action  -- from "SupplyChain.Vendor"+```++The `(>->)` operator is associative, and `id` is its identity.++- `(a >-> b) >-> c` = `a >-> (b >-> c)`+- `a >-> id` = `a`+- `a` = `id >-> a`+++## Vendor-to-job connection++If `i` is the downstream interface of vendor `v` and the upstream interface of+job `j`, then we can form the composition `v >- j`.++```haskell+(>-) :: Vendor up i action+     -> Job i action product+     -> Job up action product+```++```+             ▲   │+       up a  │   │  a+             │   ▼              ─┐+┌────────────────────────┐       │+│   Vendor up i action   │  v    │+└────────────────────────┘       │+             ▲   │               │+        i b  │   │  b            │  v >- j+             │   ▼               │+┌────────────────────────┐       │+│  Job i action product  │  j    │+└────────────────────────┘       │+              │                 ─┘+              │  product+              ▼+```++When the job makes a request of type `i b`, the vendor replies with a response+of type `b`.++`(>->)` and `(>-)` together are associative.++- `(a >-> b) >- c` = `a >-> (b >- c)`+++## Writing vendors++We define vendors using the `Vendor` constructor. Please inspect its type+carefully:++```haskell+forall product. down product -> Job up action (Referral up down action product)+```++A vendor is a function that accepts a request. The request type is polymorphic+but constrained by the vendor's downstream interface.++```haskell+forall product. down product -> Job up action (Referral up down action product)+                ^^^^^^^^^^^^+```++A vendor has an upstream interface and can do everything a job can, therefore+the request handler operates in a `Job` context.++```haskell+forall product. down product -> Job up action (Referral up down action product)+                                ^^^^^^^^^^^^^+```++This allows the vendor to undertake a monadic sequence involving `order` and+`perform` while fulfilling the request.++The final step in fulfilling a request is to return a `Referral`.++```haskell+forall product. down product -> Job up action (Referral up down action product)+                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^+```++A `Referral` is written using its `Referral` constructor, which has two parameters:++```haskell+Referral :: product -> Vendor up down action -> Referral up down action product+```++The first is the vendor's response to the client's request.++```haskell+Referral :: product -> Vendor up down action -> Referral up down action product+            ^^^^^^^+```++The second is a new `Vendor`.++```haskell+Referral :: product -> Vendor up down action -> Referral up down action product+                       ^^^^^^^^^^^^^^^^^^^^^+```++This latter component is what allows vendors to be stateful, and it is usually+defined recursively.
+ supply-chain.cabal view
@@ -0,0 +1,52 @@+cabal-version: 3.0++name: supply-chain+version: 0.0.0.0++category: Monads, Streaming+synopsis: Composable request-response pipelines++description:+    @Job@ is a free monad, plus a little extra. It is parameterized on+    two type constructors: one for dynamic effects, and one for static+    effects. The @Vendor@ type is similar to job, but a vendor can also+    /respond to/ requests, and thus it has two dynamic interfaces: one+    upstream and one downstream. We can connect vendors to jobs or to+    other vendors, creating a pipeline (or "supply chain", if you like)+    along the dynamic interfaces.++author: Chris Martin+maintainer: Chris Martin, Julie Moronuki++homepage: https://github.com/typeclasses/supply-chain+bug-reports: https://github.com/typeclasses/supply-chain/issues++license: Apache-2.0+license-file: license.txt++extra-doc-files: readme.md changelog.md++common base+    default-language: GHC2021+    ghc-options: -Wall++    default-extensions:+        LambdaCase+        NoImplicitPrelude++    build-depends:+        base ^>= 4.16 || ^>= 4.17+      , supply-chain-core ^>= 0.0++library+    import: base+    hs-source-dirs: supply-chain+    exposed-modules:+        SupplyChain+        SupplyChain.Alter+        SupplyChain.Effect+        SupplyChain.Job+        SupplyChain.JobAndVendor+        SupplyChain.Referral+        SupplyChain.Unit+        SupplyChain.Vendor
+ supply-chain/SupplyChain.hs view
@@ -0,0 +1,18 @@+module SupplyChain+  (++    {- * Job type -} Job,+    {- * Making jobs -} order, perform,+    {- * Running jobs -} run, eval,+    {- * Vendor type -} Vendor (Vendor, handle), Referral (Referral),+    {- * Vendor connection -} (>->),+    {- * Vendor-job connection -} (>-), (>+),+    {- * Vendor/job conversion -} once, loop, loop', Unit (Unit),+  )+  where++import SupplyChain.Job (Job, order, perform, run, eval)+import SupplyChain.JobAndVendor (loop, loop', once, (>-), (>+))+import SupplyChain.Referral (Referral (Referral))+import SupplyChain.Unit (Unit (Unit))+import SupplyChain.Vendor (Vendor (Vendor, handle), (>->))
+ supply-chain/SupplyChain/Alter.hs view
@@ -0,0 +1,58 @@+-- | Description: functions for modifying requests and actions++module SupplyChain.Alter+  (+    {- * General -} job, vendor, request, perform,+    {- * Simplified -} job', vendor', request', perform',+  )+  where++import SupplyChain.Core.Effect (Effect)+import SupplyChain.Core.Job (Job)+import SupplyChain.Core.Vendor (Vendor (..))++import qualified SupplyChain.Core.Effect as Effect+import qualified SupplyChain.Core.Job as Job+import qualified SupplyChain.Core.Vendor as Vendor++import Data.Function ((.))++job :: (forall x. Effect up action x -> Job up' action' x)+    -- ^ Transformation applied to each effect that the job evokes+    -> Job up action product -> Job up' action' product+job = Job.alter++job' :: (forall x. Effect up action x -> Effect up' action' x)+    -- ^ Transformation applied to each effect that the job evokes+    -> Job up action product -> Job up' action' product+job' f = Job.alter (Job.effect . f)++vendor :: (forall x. Effect up action x -> Job up' action' x)+    -- ^ Transformation applied to each effect that the vendor evokes+    -> Vendor up down action -> Vendor up' down action'+vendor = Vendor.alter++vendor' :: (forall x. Effect up action x -> Effect up' action' x)+    -- ^ Transformation applied to each effect that the vendor evokes+    -> Vendor up down action -> Vendor up' down action'+vendor' f = Vendor.alter (Job.effect . f)++request :: (up product -> Effect up' action product)+    -- ^ Transformation applied if the effect is a request+    -> Effect up action product -> Effect up' action product+request = Effect.alterRequest++request' :: (up product -> up' product)+    -- ^ Transformation applied if the effect is a request+    -> Effect up action product -> Effect up' action product+request' f = Effect.alterRequest (Effect.Request . f)++perform :: (action product -> Effect up action' product)+    -- ^ Transformation applied if the effect is an action+    -> Effect up action product -> Effect up action' product+perform = Effect.alterPerform++perform' :: (action product -> action' product)+    -- ^ Transformation applied if the effect is an action+    -> Effect up action product -> Effect up action' product+perform' f = Effect.alterPerform (Effect.Perform . f)
+ supply-chain/SupplyChain/Effect.hs view
@@ -0,0 +1,35 @@+-- | Description: an /effect/ is either /request/ or /perform/++module SupplyChain.Effect+  (+    {- * Type -} Effect (Request, Perform),+    {- * General alteration -} alterRequest, alterPerform,+    {- * Simplified alteration -} alterRequest', alterPerform',+  )+  where++import SupplyChain.Core.Effect (Effect (Request, Perform))++import qualified SupplyChain.Core.Effect as Effect++import Data.Function ((.))++alterRequest :: (up product -> Effect up' action product)+    -- ^ Transformation applied if the effect is a request+    -> Effect up action product -> Effect up' action product+alterRequest = Effect.alterRequest++alterRequest' :: (up product -> up' product)+    -- ^ Transformation applied if the effect is a request+    -> Effect up action product -> Effect up' action product+alterRequest' f = Effect.alterRequest (Effect.Request . f)++alterPerform :: (action product -> Effect up action' product)+    -- ^ Transformation applied if the effect is an action+    -> Effect up action product -> Effect up action' product+alterPerform = Effect.alterPerform++alterPerform' :: (action product -> action' product)+    -- ^ Transformation applied if the effect is an action+    -> Effect up action product -> Effect up action' product+alterPerform' f = Effect.alterPerform (Effect.Perform . f)
+ supply-chain/SupplyChain/Job.hs view
@@ -0,0 +1,59 @@+-- | Description: a /job/ makes requests, performs actions, and returns++module SupplyChain.Job+  (+    {- * Type -} Job,+    {- * Construction -} perform, order, effect,+    {- * Running -} run, eval,+    {- * Alteration -} alter, alter',+  )+  where++import SupplyChain.Core.Effect (Effect)+import SupplyChain.Core.Job (Job)++import qualified SupplyChain.Core.Job as Job++import Control.Monad (Monad)+import Data.Function ((.))+import Data.Functor.Const (Const)+import Data.Void (Void)++alter :: (forall x. Effect up action x -> Job up' action' x)+    -- ^ Transformation applied to each effect that the job evokes+    -> Job up action product -> Job up' action' product+alter f = Job.alter f++alter' :: (forall x. Effect up action x -> Effect up' action' x)+    -- ^ Transformation applied to each effect that the job evokes+    -> Job up action product -> Job up' action' product+alter' f = Job.alter (effect . f)++perform :: action product -- ^ Action+    -> Job up action product -- ^ Job+perform = Job.perform++order :: up product -- ^ Request+    -> Job up action product -- ^ Job+order = Job.order++effect :: Effect up action product -- ^ Effect+    -> Job up action product -- ^ Job+effect = Job.effect++{-| Run a job in its action context++   The job must not make requests, so its upstream interface+   is @Const Void@. -}+run :: Monad action =>+    Job (Const Void) action product -- ^ Job+    -> action product -- ^ Action+run = Job.run++{-| Evaluate a job with no context++    The job must evokes neither request nor actions, so both+    its upstream and action contexts are @Const Void@. -}+eval :: Job (Const Void) (Const Void) product -- ^ Job+    -> product -- ^ Result+eval = Job.eval
+ supply-chain/SupplyChain/JobAndVendor.hs view
@@ -0,0 +1,46 @@+-- | Description: /job/ + /vendor/++module SupplyChain.JobAndVendor+  (+    {- * Connection -} (>-), (>+),+    {- * Conversion -} once, loop, loop',+  )+  where++import SupplyChain.Core.Job (Job)+import SupplyChain.Core.Referral (Referral (Referral))+import SupplyChain.Core.Unit (Unit)+import SupplyChain.Core.Vendor (Vendor (Vendor, handle))++import qualified SupplyChain.Core.JobAndVendor as Core+import qualified SupplyChain.Core.Connect as Core++import Data.Functor ((<&>))++{-| Modify a job with a vendor that interprets its requests -}+(>-) :: Vendor up down action -- ^ Upstream+     -> Job down action product -- ^ Downstream+     -> Job up action product+(>-) = (Core.>-)++{-| Connect a vendor to a job, producing a job which returns both the product+    and a new version of the vendor.++    Use this function instead of '(>-)' if you need to attach a succession+    of jobs to one stateful vendor. -}+(>+) :: Vendor up down action -- ^ Upstream+     -> Job down action product -- ^ Downstream+     -> Job up action (Referral up down action product)+(>+) = (Core.>+)++loop :: Job up action product -- ^ Job+    -> Vendor up (Unit product) action -- ^ Vendor+loop = Core.loop++once :: Vendor up (Unit product) action -- ^ Vendor+    -> Job up action product -- ^ Job+once = Core.once++loop' :: (forall x. down x -> Job up action x) -- ^ Stateless handler+    -> Vendor up down action -- ^ Vendor+loop' f = go where go = Vendor{ handle = \x -> f x <&> (`Referral` go) }
+ supply-chain/SupplyChain/Referral.hs view
@@ -0,0 +1,26 @@+-- | Description: a /referral/ consists of a product and a new vendor++module SupplyChain.Referral+  (+    {- * Type -} Referral (Referral, product, next),+    {- * Alteration -} alter, alter',+  )+  where++import SupplyChain.Core.Effect (Effect)+import SupplyChain.Core.Job (Job, effect)+import SupplyChain.Core.Referral (Referral (Referral, product, next))++import qualified SupplyChain.Core.Referral as Referral++import Data.Function ((.))++alter :: (forall x. Effect up action x -> Job up' action' x)+    -- ^ Transformation applied to each effect that the 'next' vendor evokes+    -> Referral up down action product -> Referral up' down action' product+alter = Referral.alter++alter' :: (forall x. Effect up action x -> Effect up' action' x)+    -- ^ Transformation applied to each effect that the 'next' vendor evokes+    -> Referral up down action product -> Referral up' down action' product+alter' f = Referral.alter (effect . f)
+ supply-chain/SupplyChain/Unit.hs view
@@ -0,0 +1,10 @@+-- | Description: /unit/ is a simple interface with one request and a fixed+--                response type++module SupplyChain.Unit+  (+    {- * Type -} Unit (Unit),+  )+  where++import SupplyChain.Core.Unit (Unit (Unit))
+ supply-chain/SupplyChain/Vendor.hs view
@@ -0,0 +1,97 @@+-- | Description: a /vendor/ responds to requests, makes requests, and+--                performs actions++module SupplyChain.Vendor+  (+    {- * Type -} Vendor (Vendor, handle),+    {- * Connection -} (>->), id,+    {- * Some simple vendors -} function, action, map, absurd,+    {- * Running -} run, eval, eval',+    {- * Alteration -} alter, alter',+  )+  where++import SupplyChain.Core.Effect (Effect)+import SupplyChain.Core.Job (Job, effect)+import SupplyChain.Core.Referral (Referral (..))+import SupplyChain.Core.Vendor (Vendor (..))+import SupplyChain.JobAndVendor (loop')++import qualified SupplyChain.Core.Connect as Connect+import qualified SupplyChain.Core.Job as Job+import qualified SupplyChain.Core.Referral as Referral+import qualified SupplyChain.Core.Vendor as Vendor++import Control.Applicative (pure)+import Control.Monad (Monad)+import Data.Function ((.))+import Data.Functor.Const (Const)+import Data.Void (Void)++-- | Connect two vendors; the first interprets requests made by the second+(>->) :: Vendor up middle action -- ^ Upstream+    -> Vendor middle down action -- ^ Downstream+    -> Vendor up down action+(>->) = (Connect.>->)++{-| Run a vendor in its action context++   The vendor must not make requests, so its upstream interface+   is @Const Void@. -}+run :: Monad action => Vendor (Const Void) down action -- ^ Vendor+    -> down product -- ^ Request+    -> action (Referral (Const Void) down action product)+run = Vendor.run++{-| Evaluate a vendor with no context++    The vendor must evokes neither request nor actions, so both+    its upstream and action contexts are @Const Void@. -}+eval :: Vendor (Const Void) down (Const Void) -- ^ Vendor+    -> down product -- ^ Request+    -> Referral (Const Void) down (Const Void) product+eval = Vendor.eval++{-| Evaluate a vendor with no context++    The vendor must evokes neither request nor actions, so both+    its upstream and action contexts are @Const Void@. -}+eval' :: Vendor (Const Void) down (Const Void) -- ^ Vendor+    -> down product -- ^ Request+    -> product+eval' v r = Referral.product (Vendor.eval v r)++-- | Vendor that never responds to any requests+absurd :: Vendor up (Const Void) action+absurd = Vendor (\case{})++-- | The identity for '(>->)'; does nothing at all+id :: Vendor i i action+id = loop' Job.order++{-| A simple stateless vendor that responds to each+    request by applying a pure function -}+function :: (forall response. down response -> response)+    -> Vendor up down action+function f = loop' (pure . f)++{-| A simple stateless vendor that responds to each+    request by applying an effectful function -}+action :: (forall response. down response -> action response)+    -> Vendor up down action+action f = loop' (Job.perform . f)++{-| A vendor that applies a transformation to each request+    and then simply forwards it upstream. -}+map :: (forall x. down x -> up x) -> Vendor up down action+map f = loop' (Job.order . f)++alter :: (forall x. Effect up action x -> Job up' action' x)+    -- ^ Transformation applied to each effect that the vendor evokes+    -> Vendor up down action -> Vendor up' down action'+alter = Vendor.alter++alter' :: (forall x. Effect up action x -> Effect up' action' x)+    -- ^ Transformation applied to each effect that the vendor evokes+    -> Vendor up down action -> Vendor up' down action'+alter' f = Vendor.alter (effect . f)