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.