27
$\begingroup$

I have been trying to code monad-like structures in Mathematica. However, I am facing some issues and I was wondering if anyone can help. I will give some background in the hope it can be useful to others. Also, while there may be a native way to perform some of these tasks in Mathematica and I would like also to know it, I am more interested in how to code them.

First, let's look at a Haskel-like Maybe monad. The idea is that some computations can fail and return nothing instead of a valid result. For example, consider the safe division:

safedivide[a_, b_] := If[b == 0, nth[a], a/b];
nth[_] := nothing

(Here the function nth[_] serves to fix a problem with the upvalues of nothing defined later, otherwise safedivision will return nothing in all cases).

Thus,

safedivide[1,0]

returns nothing whereas

safedivide[0,1]

returns 0. Now, we would like to be able to use nothing as the argument of a function so that if one of the intermediate steps returns nothing then the whole computation returns nothing. This can be accomplished defining UpValues for nothing as follows:

nothing/:f_[___,nothing,___]=nothing

Accordingly, if we attempt to compute

Sin[safedivide[1,0]+3]

we obtain nothing whereas Sin[safedivide[0,1]+3] gives Sin[3]. This construction works well, at least as far as I can see, with built-in function. For example

Max[{1, 2, 3, nothing, 5}]

returns nothing.

So far so good, and now I got really ambitious and decided to create a comment wrapper. The idea is that functions can return their usual outputs or return them wrapped in a comment. For example,

commentedsquare[x_] := If[x == 0, comment[0, "Zero"], x^2]

Thus, commentedsquare[0] returns comment[0, "Zero"]. We want to pass through all these comments to various functions and collect them at the end. I understand that this can be achieved by the Reap-Sow construct, for example as in

commentedsquare2[x_] := If[x == 0, Sow["Zero"]; 0, x^2]
Reap[commentedsquare2[0]]

However, for the sake of generality, I would like a construction that would work as follows: for a generic function f:

f[comment[x,"abc"], comment[y,"def"], z]=comment[f[x,y,z], "abc","def"]

That is, the comments would be passed through the function and the function evaluates at the first argument of comment.

I managed to code this for functions of a single variable as follows:

notcomment=(!(#===comment)&);
comment /: f_?notcomment[comment[a_, b___]] := comment[f[a], b];
comment[comment[a_, b___], c___]:= comment[a, b, c];
comment[a_] := a;

For example,

comment[comment[1, "abc"], "def"]

returns comment[1, "abc", "def"] and

N[comment[Cos[Sin[comment[comment[Sin[comment[comment[1]]], "abc"], "def"]]]]]

returns, as it should, comment[0.734665, "abc", "def"].

Now, my main issue is to extend this to functions of several variables. With the preceding code

Sin[comment[Pi, "abc"]] + 1

returns 1 + comment[0, "abc"], and so I have attempted to code something like:

comment/:f_?notcomment[a___,comment[b_,c___],d___]:=comment[f[a,b,d],c];

which has problems with recursion and simply does not work. I really tried several alternatives but nothing seems to do the right job.

Any ideas?

$\endgroup$
18
$\begingroup$

General

My (biased) view on monads is that they are loudly advertised in Haskell and F#, but I kind of do not see what is the big deal. (Granted, similar things are said about OOP Design Patterns, but I like them and use them a lot.)

Monad programming is more important for functional programming languages like Haskell because of their strong type function definition. Not so much in LISP-like languages.

Monad programming style is not so hard to apply/use in LISP or Mathematica. That can be done in at least several ways:

Monadic programming example (in Mathematica)

This functional parsers implementation in Mathematica, FunctionalParsers.m, introduced and demonstrated in these blog posts, is an application of the "monadic programming" style. Recent developments in Mathematica make the use of that style much easier.

The monad for the functional parsers in FunctionalParsers.m is defined for the functions

P: {{_String...}, _ParseTree } -> { {{_String...}, _ParseTree } ... }

This definition allows the chaining of parsers that are of basic, combinator, or transforming types. (This kind of chaining is discussed here.)

For more detailed definitions and explanations see "Functional parsers for an integration requests language grammar".

MSE links to applications

The reason I am listing these applications is because in order to implement functional parsers packages in Mathematica, R, and Lua I followed the monad style in the Haskell based article "Functional parsers" by Fokker. Hence successful applications would demonstrate the monads usefulness.

  1. This answer for the question "How to parse a clojure expression?".

  2. This answer for the question "Programming of a natural language interface".

  3. This answer for the question "Writing functions with “Method” options".

  4. On the code re-factoring and design side, this answer for the question "Functions with changeable global variables".

$\endgroup$
  • $\begingroup$ Thanks - I really appreaciate all these references. $\endgroup$ – Diogo Gomes Sep 5 '16 at 5:02
  • $\begingroup$ @DiogoGomes Great, I am glad you find my answer useful. $\endgroup$ – Anton Antonov Sep 5 '16 at 15:13
10
$\begingroup$

Your question is very broad. A full answer would require a lengthy tutorial on meta-programming in Mathematica. I'm not going to come anywhere close to doing that, but I will give you a hint to help you get started.

There are essentially two ways to introduce emulations of concepts from another programming language (OPL) into Mathematica (WL).

  1. Determine what built-in functions in WL provide identical or similar functionality or can be combined to provide such functionality as is found in OPL. Then use the WL equivalents to implement what ever application you have in mind. With this approach you have work with normal WL syntax and your finished application will likely look quite different than it would look if it were implemented in OPL.

  2. Introduce new syntax into WL that emulates the OPL syntax. This can be done, but it requires, in addition to carrying out what was discussed under 1), writing a preprocessor to convert OPL syntax into WL syntax and (often) a postprocessor to convert WL syntax into OPL syntax.

You reject limiting yourself to the first approach, which can be difficult enough in itself and are asking how to do the second, which piles Pelion upon Ossa, but if you are up to it, I salute you.

WL has some hook symbols that allow you install syntax conversion functions into the top-level processing loop. Basically, you define a function to do the conversion and set the appropriate hook to be that function. An input hook will be applied to any top-level user input. An output hook to any top-level evaluator output.

The hooks you should look at are $Pre, $PreRead, $PrePrint, and $Post. The Documentation Center has examples on the use of these hooks and you can find more examples on this site.

$\endgroup$
  • 4
    $\begingroup$ "... piles Pelion upon Ossa,,,", oh, big +1 just for the semi-obscure idiom. $\endgroup$ – ciao Sep 4 '16 at 23:39
  • $\begingroup$ Thanks a lot - your answer solved the problem. I posted a solution using the $Post hook. $\endgroup$ – Diogo Gomes Sep 5 '16 at 4:59
7
$\begingroup$

Thanks to the suggestion of m_goldberg, I have found the following solution. We use the $Post hook as follows:

notcom := (# =!= comment) &;
commentprocessor = Function[expression,
FixedPoint[
# /. {f_?notcom[z___, comment[x_, w___], y___] :> comment[f[z, x, y], w], 
comment[comment[xx_, w___], z___] :> comment[xx, w, z]} &,
expression]
]
$Post = commentprocessor

then,

N[Sin[comment[1, "One"] - E^comment[0, "Zero"]]]

returns, as expected

comment[0., "Zero", "One"]

and

Sin[comment[Pi, "abc"]] + 1

comment[1, "abc"].

$\endgroup$

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.