This folder will explain the basic idea of purescript-run
and then solve the paper's version of the Expression Problem using it.
If you recall, xgromxx
mentions purescript-run
in a comment in ADT8.purs
. (The ReadMe of this library provides an overview of the ideas we've explained here.)
What is purescript-run
? Why would we use that over Free
? There are three reasons.
First, let's look at the type of Run
:
newtype Run r a = Run (Free (VariantF r) a)
We can see that Run
is a compile-time-only type that specifies the Functor
type of Free
to the open CoProduct
type: VariantF
.
Let's compare the same idea encoded in both forms (note: Run
will use naming conventions that will be explained below):
free :: Free (VariantF (add :: FProxy Add, subtract :: FProxy Subtract)) a
-- is the same as
run :: Run (ADD + SUBTRACT) a
In short, Run
draws attention to the effects used and eliminates other distracting "noise" that occurs due to a lot of types.
Second, this library exposes helper functions that add a MonadRec
type class constraint to guarantee that stack overflows won't occur. Due to the recursive nature by which one "interprets" a Free
monad, Free
-based computations can sometimes result in stack overflows. These helper functions make it trivial to insure stack-safety. See the "Stack-Safety" section at the bottom of the project's ReadMe for more info.
Third, this library already defines types and functions for using and working with different effects (e.g. StateT
, ReaderT
, WriterT
, etc. but for the Free
monad). One does not need to re-implement these types for each project, so that the code works every time. (These are also covered more below)
Let's look at a few core functions (the following block of code is licensed under the MIT license:
newtype Run r a = Run (Free (VariantF r) a)
-- `Run`'s version of `Free`'s `liftF`
lift
∷ ∀ sym r1 r2 f a
. Row.Cons sym (FProxy f) r1 r2
⇒ IsSymbol sym
⇒ Functor f
⇒ SProxy sym
→ f a
→ Run r2 a
-- Run (Free ( VariantF (row :: type)) output)
lift symbol dataType = Run <<< liftF <<< inj symbol dataType
-- This function will appear later in this folder's code
-- | Extracts the value from a purely interpreted program.
extract ∷ ∀ a. Run () a → a
-- `Run`'s version of `Free`'s `resume`
peel :: forall a r. Run r a -> Either (VariantF r (Run r a)) a
Let's look at some of the type aliases it provides:
type EFFECT = FProxy Effect
type AFF = FProxy Aff
Rather than typing (fieldName :: FProxy Functor)
, we use an all-caps type alias: (fieldName :: FUNCTOR)
. This improves code readability, so we will follow suit.
purescript-run
has a few other type aliases that will look familiar.
newtype Reader e a = Reader (e → a)
type READER e = FProxy (Reader e)
data State s a = State (s → s) (s → a)
type STATE s = FProxy (State s)
data Writer w a = Writer w a
type WRITER w = FProxy (Writer w)
newtype Except e a = Except e
type EXCEPT e = FProxy (Except e)
type FAIL = EXCEPT Unit
The takeaways here:
- As stated above,
purescript-run
already defines and properly handles the types that make the same effects we saw in theMTL
folder work out-of-box. - The
a
in each type is the output type, so it is excluded. FAIL
indicates an error whose type we don't care about.
If we look at some of the functions that each of the above MTL-like types provide, we'll notice another pattern. Each type (e.g. Reader
) seems to define its own Symbol
(e.g. _reader :: SProxy "reader"
) for the corresponding type in VariantF
's row type (e.g. READER
).
However, if one wanted to use a custom Symbol
name for their usage of an MTL-like type (e.g. Reader
), they can append at
to the function and get the same thing. In other words:
liftReader readerObj = liftReaderAt _reader readerObj
liftReaderAt symbol readerObj = -- implementation
ask = askAt _reader
askAt symbol = -- implementation
In short, one can use a Run
-based monad to do two different state computations in the same function, something which the unmodified MTL
approach via MonadState
cannot do.
- See this project's
Hello World/Projects/src/Simplest Program
folder for an example of what a very simple program with aRun
-based architecture looks like. - A simple program using multiple effects
- A short explanation from Free to Run that also covers
Coproduct
/VariantF
and whose code can be found in the project's test directory: