🏠 🌘

my blog


kacper topolnicki
kacpertopol@gmail.com

monads and “do” notation in the Wolfram Language, part 1

2021-01-31

motivation, categorical interpretation of the Wolfram Language

This is the first part of a series of posts that introduce “do” notation to the Wolfram Language. There are many ways of doing this. I will be using an approach described in my article on arXiv, these posts will be slightly edited versions of section from that paper.

The motivation for this work stems from a practical problem of writing a Wolfram Language (WL) application aimed to abstract away the notion of scalar valued functions for numerical computations. The goal was for the user to specify, in the WL, the number of arguments for a function, which arguments are discrete indices and which arguments are floating point numbers. For floating point arguments the user additionally defines points and weights used for interpolations and integrations over these arguments and finally specifies which arguments can be used for purposes of code parallelization. Given this input, the job of the application is to create a FORTRAN 90 module and a directory with supplementary data. The user can import this module and work with an abstraction of the scalar valued functions: store function values on different cores and handle them using different MPI and OPENMP threads, interpolate function values and integrate over function arguments.

The implementation works, more or less :-) Basing the implementation around monadic types was really helpful. The monadic style of programming can also be appropriate for many other problems. Here a simple approach to include monads and the Haskell style “do" notation in the WL is proposed. This combined with the powerful functions built into the Mathematica system can lead to the production of very powerful code.

This work was influenced by an excellent book by Bartosz Milewski containing an introduction to Category Theory. First, in this post, a categorical interpretation of the WL is introduced. In the next post the implementation of monadic types and the “do” notation will be described. Further posts will contain examples and tips for practical implementations. The text contains snippets of WL code and pseudo-code. Some code fragments will use comments (*...*) to hold additional information intended for interpretation by the reader.

In order to think about the WL as a category a subset of the WL will be considered consisting of functions defined in the following manner:

    f[(*pattern a*)] := (*expression*)

where f stands for the function name (other symbols can also be used), pattern a is a WL language pattern expression that specifies the arguments accepted by the function f and expression is a WL expression that can be constructed from the named parts of pattern a. This is not directly enforced by the WL, but ideally all possible results of f match a different pattern, pattern b, so that:

    MatchQ[f[(*expression matching pattern a*)],(*pattern b*)]

is always true. Curried functions can be used and this can be interpreted as replacing f with f[x], f[x][y], … in the pseudo code above and treating the replacement as a whole set of functions built from the different values of x, y, …

It is seems natural to treat this subset of the WL as a category with morphisms being functions (e.g. f, f[x], f[x][y], …) and objects being types defined by WL patterns (e.g. the set of all WL expressions that match pattern a defines type a and the set of expressions that match pattern b defines type b). In this context a monad is a type function: it takes a pattern that defines one type and returns a new pattern that defines a different type. Some additional definitions are necessary and they discussed next time.