26
$\begingroup$

This is a methodological question with two parts:

  • How to use monadic programming in Mathematica?

  • Why use monadic programming in Mathematica?

In my opinion the questions are inter-related -- we cannot answer one of them without answering the other.

It would be nice to get concrete monads implementations and/or reasons to use monads, but I am also interested in personal opinions and experiences. (Like this one.)

Many people are puzzled by monads or wonder why others make big deal about them. This question is looking for relevant answers for those concerns.

(Also, in the MSE monad discussions I have seen people say they want to use or implement monads in Mathematica but not why.)

Definitions

Simply put, monads provide a generalized interface to sequential computation. Hence monads are a way to impose or enforce a certain regular behavior in building computations.

Monads (in programming) are defined in this dedicated Wikipedia article.

The most detailed of the definitions given in the linked Wikipedia article is based on the operators 'return' and 'bind' -- let us call it the "Haskell definition". There is also an alternative definition (outlined in the linked Wikipedia article) that uses 'fmap' and 'join' -- let us call it the "Scala definition".

The Haskell definition -- Mathematica-localized

Here are operators for a monad associated with a certain symbol M:

  1. monad unit function ("return" in Haskell notation) is Unit[x_] := M[x];
  2. monad bind function (">>=" in Haskell notation) is a rule like Bind[M[x_], f_] := f[x] with MatchQ[f[x],M[_]] giving True.

(See the monad Maybe for an example.)

Here is an illustration formula showing a monad pipeline:

enter image description here

From the definition and formula it should be clear that if for the result f[x] of Bind the test MatchQ[f[x],_M] is True then the result is ready to be fed to the next binding operation in monad's pipeline.

The monad laws

The monad laws definitions are taken from https://wiki.haskell.org/Monad_laws. In the monad laws given below the symbol "⟹" is for monad's binding operation and ↦ is for a function in anonymous form.

Here is a table with the laws:

Guide to answers

Please consider the following questions while giving an answer.

  • Why do you want to use monads?

    • Because of FP studies, coming to Mathematica from another FP language, architectural concerns, convenience, etc.
  • How often do you use monads?

    • Even if you (dis-)like them?
  • When do you use monads?

    • At exploratory phases of programming projects.

    • For code reuse purposes.

    • To impose rigid structures of a program design.

    • For readability of code.

    • For highly specialized, well understood tasks.

  • Why you do not use monads in Mathematica?

    • But generally would...
  • What is the simplest implementation (in Mathematica) that you use or programmed?

  • Does the implementation in your answer use the Haskell or the Scala definition?

    • Why the used definition was chosen?
$\endgroup$
  • 2
    $\begingroup$ One issue is dealing w/ Missing values and "missing" Keys, w/ similar problems using relational or multi-variable operators as pointed out in @An Diogo's post. eg Missing[]+1 now head Plus rather than the more desirable Missing[Failed,Plus]. Even if DeleteMissing works w/ levelspec, often it's a question of filtering levels that stack up in complex ways. // Not so much Modad as Duad etc >> "Pythagoras: His Life and Teachings") $\endgroup$ – alancalvitti Jun 15 '17 at 23:19
  • $\begingroup$ @alancalvitti Interesting point. Generally speaking, I think the so called monad transformers are applicable for handling systematically missing entities (values, keys, etc.) It is another question how good that approach is in Mathematica (i.e. effective, concise, and lightweight.) A monad transformer I recently made related to this MSE question works fairly well. (Maybe I should expand on this to an answer here.) $\endgroup$ – Anton Antonov Jun 17 '17 at 15:07
6
$\begingroup$

(This answer comprises a few of the sections of the blog post "Monad code generation and extension"; see also [AA11].)

The basic Maybe monad

It is fairly easy to program the basic monad Maybe discussed in [Wk1].

The goal of the Maybe monad is to provide easy exception handling in a sequence of chained computational steps. If one of the computation steps fails then the whole pipeline returns a designated failure symbol, say None otherwise the result after the last step is wrapped in another designated symbol, say Maybe.

Here is the special version of the generic pipeline formula for the Maybe monad:

"Monad-formula-maybe"

Here is the minimal code to get a functional Maybe monad (for a more detailed exposition of code and explanations see [AA7]):

MaybeUnitQ[x_] := MatchQ[x, None] || MatchQ[x, Maybe[___]];

MaybeUnit[None] := None;
MaybeUnit[x_] := Maybe[x];

MaybeBind[None, f_] := None;
MaybeBind[Maybe[x_], f_] := 
  Block[{res = f[x]}, If[FreeQ[res, None], res, None]];

MaybeEcho[x_] := Maybe@Echo[x];
MaybeEchoFunction[f___][x_] := Maybe@EchoFunction[f][x];

MaybeOption[f_][xs_] := 
  Block[{res = f[xs]}, If[FreeQ[res, None], res, Maybe@xs]];

In order to make the pipeline form of the code we write let us give definitions to a suitable infix operator (like "⟹") to use MaybeBind:

DoubleLongRightArrow[x_?MaybeUnitQ, f_] := MaybeBind[x, f];
DoubleLongRightArrow[x_, y_, z__] := 
  DoubleLongRightArrow[DoubleLongRightArrow[x, y], z];

Here is an example of a Maybe monad pipeline using the definitions so far:

data = {0.61, 0.48, 0.92, 0.90, 0.32, 0.11};

MaybeUnit[data]⟹(* lift data into the monad *)
 (Maybe@ Join[#, RandomInteger[8, 3]] &)⟹(* add more values *)
 MaybeEcho⟹(* display current value *)
 (Maybe @ Map[If[# < 0.4, None, #] &, #] &)(* map values that are too small to None *)

(* {0.61,0.48,0.92,0.9,0.32,0.11,4,4,0}
 None *)

The result is None because:

  1. the data has a number that is too small, and

  2. the definition of MaybeBind stops the pipeline aggressively using a FreeQ[_,None] test.

Monad laws verification

Let us convince ourselves that the current definition of MaybeBind gives a monad.

The verification is straightforward to program and shows that the implemented Maybe monad adheres to the monad laws.

"Monad-laws-table-Maybe"

Extensions with polymorphic behavior

We can see from formulas (1) and (2) that the monad codes can be easily extended through overloading the pipeline functions.

For example the extension of the Maybe monad to handle of Dataset objects is fairly easy and straightforward.

Here is the formula of the Maybe monad pipeline extended with Dataset objects:

Here is an example of a polymorphic function definition for the Maybe monad:

MaybeFilter[filterFunc_][xs_] := Maybe@Select[xs, filterFunc[#] &];

MaybeFilter[critFunc_][xs_Dataset] := Maybe@xs[Select[critFunc]];

See [AA7] for more detailed examples of polymorphism in monadic programming with Mathematica / WL.

A complete discussion can be found in [H3]. (The main message of [H3] is the poly-functional and polymorphic properties of monad implementations.)

Maybe monads code generation

The package [AA2] provides a Maybe code generator that takes as an argument a prefix for the generated functions.

Here is an example:

Import["https://raw.githubusercontent.com/antononcube/MathematicaForPrediction/master/MonadicProgramming/MaybeMonadCodeGenerator.m"]

GenerateMaybeMonadCode["AnotherMaybe"]

data = {0.61, 0.48, 0.92, 0.90, 0.32, 0.11};

AnotherMaybeUnit[data]⟹
 (AnotherMaybe@Join[#, RandomInteger[8, 3]] &)⟹
 AnotherMaybeEcho⟹
 (AnotherMaybe @ Map[If[# < 0.4, None, #] &, #] &)

(* {0.61,0.48,0.92,0.9,0.32,0.11,8,7,6}
   AnotherMaybeBind: Failure when applying: Function[AnotherMaybe[Map[Function[If[Less[Slot[1], 0.4], None, Slot[1]]], Slot[1]]]]
   None *)

We see that we get the same result as above (None) and a message prompting failure.

Monad code generation is discussed further in the section "General work-flow of monad code generation utilization" of [A11].

References

[Wk1] Wikipedia entry: Monad (functional programming).

[H3] Philip Wadler, "The essence of functional programming", (1992), 19'th Annual Symposium on Principles of Programming Languages, Albuquerque, New Mexico, January 1992.

[AA2] Anton Antonov, Maybe monad code generator Mathematica package, (2017), MathematicaForPrediction at GitHub.

[AA7] Anton Antonov, "Simple monadic programming", (2017), MathematicaForPrediction at GitHub. (Preliminary version, 40% done.)

[AA11] Anton Antonov, "Monad code generation and extension", (2017), [MathematicaForPrediction at GitHub].

$\endgroup$
5
$\begingroup$
  • (This answer is code-less, but it is a type of answer I am interested to see for the question. This answer is also a section of "Monad code generation and extension"; [AA3].)

  • ( It might be a good idea before reading the rest of the answer to examine the example at the end. )

Software design with monadic programming

The application of monadic programming to a particular problem domain is very similar to designing a software framework or designing and implementing a Domain Specific Language (DSL).

The answers of the question "When to use monadic programming?" can form a large list. This answer provides only a couple of general, personal viewpoints on monadic programming in software design and architecture. The principles of monadic programming can be used to build systems from scratch (like Haskell and Scala.) Here we discuss making specialized software with or within already existing systems.

Framework design

Software framework design is about architectural solutions that capture the commonality and variability in a problem domain in such a way that: 1) significant speed-up can be achieved when making new applications, and 2) a set of policies can be imposed on the new applications.

The rigidness of the framework provides and supports its flexibility -- the framework has a backbone of rigid parts and a set of "hot spots" where new functionalities are plugged-in.

Usually Object-Oriented Programming (OOP) frameworks provide inversion of control -- the general work-flow is already established, only parts of it are changed. (This is characterized with "leave the driving to us" and "don't call us we will call you.")

The point of utilizing monadic programming is to be able to easily create different new work-flows that share certain features. (The end user is the driver, on certain rail paths.)

In my opinion making a software framework of small to moderate size with monadic programming principles would produce a library of functions each with polymorphic behaviour that can be easily sequenced in monadic pipelines. This can be contrasted with OOP framework design in which we are more likely to end up with backbone structures that (i) are static and tree-like, and (ii) are extended or specialized by plugging-in relevant objects. (Those plugged-in objects themselves can be trees, but hopefully short ones.)

DSL development

Given a problem domain the general monad structure can be used to shape and guide the development of DSLs for that problem domain.

Generally, in order to make a DSL we have to choose the language syntax and grammar. Using monadic programming the syntax and grammar commands are clear. (The monad pipelines are the commands.) What is left is "just" the choice of particular functions and their implementations.

Another way to develop such a DSL is through a grammar of natural language commands. Generally speaking, just designing the grammar -- without developing the corresponding interpreters -- would be very helpful in figuring out the components at play. Monadic programming meshes very well with this approach and applying the two approaches together can be very fruitful.

Example: pipeline with a classification monad framework

The following example was made with the packages [AA4] and [AA5].

The pipeline and its results demonstrate polymorphic behaviour over the classifier variable in the context: different functions are used if that variable is a ClassifierFunction object or an association of named ClassifierFunction objects. Note the demonstrated granularity and sequentiality of the operations coming from using a monad structure. With those kind of operations it would be easy to make interpreters for natural language DSLs.

References

[AA1] Anton Antonov, Monadic contextual classification Mathematica package, (2017), MathematicaForPrediction at GitHub.

[AA2] Anton Antonov, Monadic tracing Mathematica package, (2017), MathematicaForPrediction at GitHub.

[AA3] Anton Antonov, "Monad code generation and extension", (2017), MathematicaForPrediction at GitHub.

[AA4] Anton Antonov, "A monad for classification workflows", (2018), MathematicaForPrediction at GitHub.

$\endgroup$
3
$\begingroup$

I think the monad comprehensions style is easier for many (including me) to read and understand, especially if there is existing familiarity with set theory. I would like to make the case that code can be clearer and more easily modified using this style, which is probably why the implementors of e.g. Haskell's comprehensions syntax decided to do it (comprehensions are readily defined from other monadic functions, or directly using core language functions, this applies as well to Haskell as to WL). F# also implements a very general notion of monad, the syntax of which is close to monad comprehensions. These, as in other proposals for monadic programming in MMA, can be extended by the user.

I wrote a package for monad comprehensions with parallel generators (think Thread), which I found a very useful feature of early Haskell compilers and missed in MMA. I simply implemented the formal semantics given in the Haskell 98 report.

The general definition of a monad was implemented as in my response to a related question on monads.

The general definition is instantiated e.g.

zero[List]:={}
unit[List]:={#}&
bind[List]:=Flatten[#1 /@ #2,1]&
zipping[List]:=Thread

I then added some syntax with the Notation package which allows me to write things such as:

syntax examples of monad comprehensions

The next example shows how compact the notation can be; it implements a key definition for the semantics of value-passing CCS for parallel composition. I quote this, not in the expectation of others' running the code or understanding exactly what it does (the code uses various definitions I have not included), but to show how the notation can be used in a way that closely matches a mathematical definition. Note how pattern matching is used within the list comprehension, which obviates the need to write a bunch of special purpose functions to extract components of structured objects. Modifications to datatypes then necessitate relatively small changes to code in this style of programming. Local bindings can also be declared within monad comprehensions in this style.

Mathematica graphics

$\endgroup$
  • $\begingroup$ Thanks for posting this! $\endgroup$ – Anton Antonov Jan 4 '18 at 18:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.