supply-chain: Composable request-response pipelines

[ apache, library, monads, streaming ] [ Propose Tags ] [ Report a vulnerability ]

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.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.0.0.0, 0.0.0.1, 0.0.1.0
Change log changelog.md
Dependencies base (>=4.16 && <4.18), supply-chain-core (>=0.0.0 && <0.0.1) [details]
License Apache-2.0
Author Chris Martin
Maintainer Chris Martin, Julie Moronuki
Category Monads, Streaming
Home page https://github.com/typeclasses/supply-chain
Bug tracker https://github.com/typeclasses/supply-chain/issues
Uploaded by chris_martin at 2023-03-03T16:44:55Z
Distributions
Reverse Dependencies 1 direct, 0 indirect [details]
Downloads 192 total (3 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2023-03-03 [all 1 reports]

Readme for supply-chain-0.0.1.0

[back to package description]

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.

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:

order :: up product -> Job up action product
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.

(>->) :: 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.

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.

(>-) :: 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:

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.

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.

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.

forall product. down product -> Job up action (Referral up down action product)
                                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

A Referral is written using its Referral constructor, which has two parameters:

Referral :: product -> Vendor up down action -> Referral up down action product

The first is the vendor's response to the client's request.

Referral :: product -> Vendor up down action -> Referral up down action product
            ^^^^^^^

The second is a new Vendor.

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.

Notes on package versioning

This supply-chain package re-exports types and values from supply-chain-core. Since a supply-chain-core API change can cause a supply-chain API change, our version bounds on the supply-chain-core dependency must include three digits. A major or minor version bump in supply-chain-core prompts a corresponding bump in supply-chain if any re-exported entities have changed.