Monads, Effects, and Capabilities

Monads represent sequential computation via bind/>>=: "do X, and once finished, do Y". In our previous example/explanation from Hello World/Prelude/Control-Flow/How the Computer Executes FP Programs.md, we implied that Box could be used to "compute" something. In that example, however, it merely acted as a wrapper around values and functions. When we covered Effect and Aff in their respective folder, we saw that conceptually they operated very similar to Box.

Still, our Box/Effect/Aff examples did not make it clear how Monads could be a "computation." While bind/>>= insures that one computation occurs before another (i.e. sequential computation), it does not define what kinds of computation are done. Thus, we must explain what "effects"/"capabilities" are.

For the rest of this folder, we'll use the terms, "effects" and "capabilties," interchangeably. Lowercased "effect" refers to something different than the Effect monad.

Effects / Capabilities

To understand what we mean by "effects" (and due to license-related things), read through the first two sentences of Monad transformers, free monads, mtl, laws and a new approach, then continue reading this page.

Examples of Effects / Capabilities

Capabilities can be grouped together into type class functions that define computations which bind executes in a sequential manner. These type classes are specialized; they are designed to do one thing very very well.

So what kind of "capabilities" can we have? Let's now give some examples via the table below:

When we want a type of computation (effect) that...... we expect to use functions named something like ...... which are best abstracted together in a type class called...
Provides for later usage a read-only value/function that may change between different program runs
(e.g. "settings" values; dependency injection)
  • getSettingValue
  • getConfigValueStoredOnFile
  • getNumberOfPlayersInGame
MonadAsk
Modifies the state of a data structure
(e.g. changing the nth value in a list)
  • pop stack
  • replaceAt index treeOfStrings "some value"
  • (\int -> int + 1)
MonadState
Returns a computation's output and additional data that is generated during the computationMonadTell
Stops computation because of a possible error
(e.g. "file does not exist")
--MonadThrow
Deals with "callback hell"
(e.g. de-invert inversion of control)
--MonadCont
When we want to extend the functionality of...... with the ability to...... we can use its extension type class called...
MonadAskModify the read-only value for one computation
(e.g. makeFontSizeMoreAccessible getFontSize displayPage)
MonadReader
MonadTellModify or use the additional non-output data before completing a computationMonadWriter
MonadThrowCatch and handle the error that was thrown
(e.g. create the missing file)
MonadError

Modeling Effects

In this folder, we'll only cover MTL/ReaderT Design Pattern and Free/Run. The article to which we referred above overviews more ideas, but that is not our current focus. It might be worth returning to it after one has read through the rest of this folder.

Composing Monads

As we explained previously, bind/>>='s type signature forces one to only return the same Box-like monad type that is used in bind:

bind :: forall a. f   a -> ( a    -> f   b)           -> f    b
bind :: forall a. Box a -> ( a    -> Box b         )  -> Box  b
bind             (Box 4)   (\four -> Box (show four)) == Box "4"

In other words, if we use one monad, we cannot use any other monads. So, how do we get around this limitation?

We saw the same problem earlier when we wanted to run an Effect monad inside of an Aff monad. We fixed it by "lifting" the Effect monad into the Aff monad via a NaturalTransformation/~>. This was abstracted into a type class specific for Effect ~> someOtherMonad in MonadEffect.

MTL and Free use different approaches to solving this problem and its solution is what creates the Onion Architecture-like idea we mentioned before. As we saw earlier in Nate's video in this folder's ReadMe, mtl is the "Final Encoding" style and Free/Run is the "Initial Encoding" style.

The following ideas are quick overviews of each approach. Their terminology and exact details will be explained in their upcoming folder. It is not meant to be clearly understandable at first.

MTL Approach

Monad Transformers are thus named because they "transform" some other monad by augmenting it with additional functions. One monad (e.g. Box) can only use bind and pure to do sequential computation. However, we can "transform" Box, so that it now has state-manipulating functions like get/set/modify. "MTL" refers to the original "Monad Transformers Library" (I believe).

In the MTL-approach, one models the above effects using functions (we'll show how later). Since monad transformers augment some "base" monad, it creates a stack-like picture (read from bottom to top):

BaseMonad
      ^
      | augments
      |
Pure MonadState

Since each function is a different monadic type, they cannot be used within the same monadic context in do notation. Thus, one gets around this monad-composition problem by creating a "stack" of nested monad transformers (i.e. functions). Following the previous idea, we can define something similar to a NaturalTransformation that "lifts" the effects of one monadic function (e.g. a function that implements MonadReader) into another monadic function (e.g. a function that implements MonadState), which augments the base monad. Using a visual, it produces this diagram (read from bottom to top):

BaseMonad
      ^
      | augments
      |
Pure MonadState_TargetMonad
      ^
      | gets lifted into
      |
Pure MonadReader_SourceMonad

Generalizing this idea, we must ultimately create a "stack" of these function-based monad-transformers. Using a visual, it produces this diagram (read from bottom to top):

BaseMonad
      ^
      | augments
      |
Pure MonadState
      ^
      | gets lifted into
      |
Pure MonadWriter
      ^
      | gets lifted into
      |
Pure MonadReader

This idea is at the heart of the type class called MonadTrans. Again, you should feel somewhat confused right now and a bit overwhelmed. However, we'll refer to these ideas later to help explain why we make some of the design choices that we do. By the end of this folder, this will all make sense.

Free

In the Free-approach, one models the above effects using data structures (again, we'll show how later). Essentially, one uses domain-specific languages (DSLs) created via data structures to define an Abstract Syntax Tree (AST). Such trees describe computations but do not run them. Later on, an AST is "interpreted" (via a NaturalTransformation/~>) into a final base monad that actually runs the computation. Using a visual, it produces this diagram (read top to bottom):

Pure High-Level Language
      |
      | gets interpreted into
      |
     \ /
Base Monad

Due to how interpreters work, one can define high-level ASTs that are interpreted into lower-level ASTs before being run by a base monad. Using a visual, it produces this diagram (read from bottom to top):

Pure AST via High-Level Language
      |
      | gets interpreted into
      |
     \ /
Pure AST via Medium-Level Language
      |
      | gets interpreted into
      |
     \ /
Pure AST via Low-Level Language
      |
      | gets interpreted into
      |
     \ /
Base Monad