23
$\begingroup$

The basic problem I have been running into is making readable code, where in other languages I am more familiar with I might have been using class or struct like objects. For example, you can imagine I might want to create a circle object, which would, at the very least, store the radius and position of the circle. In an object-oriented language, I might access the $x$-coordinate of the center of the circle using something like circ.pos.x. In Mathematica, however, I find myself using nothing more than nested lists, and as a result, wind up with code looking something like circ[[2]][[1]] (circ[[1]] is the radius and circ[[2]] is the position vector, say). You can imagine how code like this could become very unreadable very fast. Presumably, this is because I'm not 'doing it right'. So, then, what is the 'right' way to implement something like this? Does Mathematica support built-in object-oriented features I'm not aware of?

$\endgroup$
5
  • $\begingroup$ See related question and another related question $\endgroup$
    – Eli Lansey
    Jul 24, 2012 at 20:44
  • 1
    $\begingroup$ And yet another related question.. Instead of nested lists, you can use rules, such as {radius -> 3}. $\endgroup$
    – Szabolcs
    Jul 24, 2012 at 21:14
  • 5
    $\begingroup$ You might find your code marginally less unwieldy if you type circ[[2,1]] instead of circ[[2]][[1]]. $\endgroup$
    – Verbeia
    Jul 24, 2012 at 21:55
  • $\begingroup$ Taking your circle example, you can use a representation similar to what Mma itself uses for the graphics primitive: just write Circle[{0,0}, 2}]. Then when defining a function that processes a circle, you can decompose the data using patterns: fun[circ: Circle[pos: {x_,y_}, r_]] := .... Now you can refer to the position as pos, radius as r, etc. You can use similar patterns in replacements or other functions. This is just a small example on what I would do in Mathematica in certain situations when having to handle a circle. $\endgroup$
    – Szabolcs
    Jul 25, 2012 at 6:38
  • $\begingroup$ I was stunned today when I opened a package written two months ago and see lines like this Map[PieceJoin,(Sort[#,#1[[1,1]]<#2[[1,1]]&]&/@Transpose@Partition[Flatten[{Map[Outer[List,{#[[1]]},#[[2]],1]&,Transpose[{solveRegions,solfInRegions}],1],Map[Outer[List,{#[[1]]},#[[2]],1]&,Transpose[{freeRegions,solfInFreeRegions}],1]},3],Length[CIni]]),1]. I was suppose to remove a bug from that package, but immediately decided to close that window and let the bug stay there. $\endgroup$ Apr 1, 2014 at 3:31

2 Answers 2

20
$\begingroup$

It is probably debatable to what extent it has built-in object oriented features. In any case, this answer is not intended to lead you to try to emulate object oriented programming, which is in general a bad idea. (see @Leonid 's answer)

However, it is not debatable that Mathematica is tremendously flexible (as to style and notation at least, the evaluation model is quite limited) and offers you a lot of more options than the one you mention you are taking to write clear code. (With great freedom comes great responsibility)

The most natural way of doing what you want the Mathematica way is using immutable data structures, as recommeded by Leonid in a comment in his answer. For example, storing a circle as circle[{x,y},r] and defining accessors and mutators. E.g

circle/:getX[c_circle]:=c[[1,1]]
circle/:getY[c_circle]:=c[[1,2]]
circle/:getPos[c_circle]:=c[[1]]
circle/:getSize[c_circle]:=c[[2]]
SetAttributes[{setSize, setX, setY, setPos}, HoldFirst];
circle/:setSize[c_circle, size_]:=c[[2]]=size;


circ= circle[{2,3}, 23];

A few others options

ClearAll[circ];
circ@pos = {2, 3};
circ@size = 23;
circ@pos
circ@size


ClearAll[circ];
circ.pos ^= {2, 3};
circ.size ^= 23;
circ.pos
circ.size


ClearAll[circ];
circ -> pos ^= {2, 3};
circ -> size ^= 23;
circ -> pos
circ -> size



ClearAll[circ];
pos = Sequence[1]; size = Sequence[2];
circ = {{2, 3}, 23};
circ[[pos]]
circ[[size]]


ClearAll[circ];
pos[circ] ^= {2, 3};
size[circ] ^= 23;
pos[circ]
size[circ]


ClearAll[circ];
circ = {"pos" -> {2, 3}, "size" -> 23};
"pos" /. circ
"size" /. circ

I am being serious when I say these are just a few. You could even end up creating your own graphical language if you start adding input aliases and playing with boxes. Useless example

ClearAll[circ, pos, size];
circ[pos] = {2, 3};
circ[size] = 23;
AppendTo[CurrentValue[EvaluationNotebook[], InputAliases], 
  "test" -> 
   TagBox[FrameBox[
     SubscriptBox[GraphicsBox[CircleBox[{0, 0}]], 
      "\[SelectionPlaceholder]"]], "test"]];
MakeExpression[
   TagBox[FrameBox[
     SubscriptBox[GraphicsBox[CircleBox[{0, 0}]], sth_]], "test"], 
   StandardForm] := 
  MakeExpression[RowBox[{"circ", "[", sth, "]"}], StandardForm];

Now, in a new cell write 2 + esc+test+esc and in the placeholder write "pos" (unquoted) or "size". Evaluate that and you get your result

Mathematica graphics

$\endgroup$
0
19
$\begingroup$

At the risk of repeating myself, I would like to stress that one has to be critical towards the superficial flexibility offered by Mathematica, when (particularly mutable) data structures are concerned. Using mutable data structures assumes a programming style for which Mathematica is not optimized. It can emulate it, yes, and we have seen a number of such emulations in the mentioned and newly posted answers (and I am myself responsible for a number of such emulations), but I personally find most of them unsatisfactory in several important aspects. For mutable data structures, some of them are:

  • Peformance (top-level overhead)
  • Instantiation and reference mechanism (usually use hash-tables or arrays as "memory heap", but this is just an emulation)
  • Lack of pass-by-reference semantics (related to the previous. Again, have to emulate, another recent attempt here)
  • Garbage collection (hard to make both reliable and efficient)

For immutable data structures (such as lists of rules), which some folks suggest to use to emulate structs, the main issues I see are

  • Element access time (often much worse than O(1))
  • Element update time (more of the same)
  • No real pass-by-reference, and even no control over it (often large structures are copied when you pass / modify such a struct)
  • Harder to estimate the complexity coming from such structures.

I suspect that the above are some of the reasons why most or all of the multiple suggested approaches based on the top-level code did not massively take off. The main reason must be, of course, performance. Here and here are two recent examples of how much the choice of the right data structures in Mathematica may affect the performance (how about 3-4 orders of magnitude difference?). The problem is, you will not get a good performance from mutable data structures implemented in the top-level, and you have to understand Mathematica really well to pick the right (for a given problem) fast immutable structures.

So, my suggestion is that we stop offering countless new emulations and stop praising Mathematica flexibility where it does not serve us well, since data structures are not the place where we have to be flexible - it is a place where we have to be clear and fast and compositional. Let's face it, currently Mathematica does not have a native support for mutable performant general data structures in the way that C or Java do. Perhaps, at some point it will, and then it will be different.

Now, why I am so much against emulations? Because at the end, you waste your time. I found that when I really need mutable data structures (which is usually for performance - demanding problems), I do things much faster in Java and link to Mathematica, than write in Mathematica and spend hours to optimize the top-level code. So, a pragmatic advice: either reformulate your problem in such a way that you can handle it with constructs natural to Mathematica (immutable lists, trees, rules, symbolic heads as containers for types, etc - if performance requirements of your problem allow that), or, if you really need mutable data structures, implement that part in a language which natively supports them, and link that to Mathematica. You will save your time.

$\endgroup$
9
  • $\begingroup$ For a lot of use cases, performance is not the main issue. However, having a few small object-like things like a circle with fields you want to access keeping the code clear, can be very important. Writing a notebook in a readable way for other non-MMA users can also be crucial. $\endgroup$
    – Rojo
    Jul 24, 2012 at 22:54
  • 1
    $\begingroup$ @Rojo I don't object to that, but then don't use mutable elements and / or operations, use immutable data structures with symbolic heads standing for types. Such as circle[{x,y},r], and define selectors and mutators as say circle/:getX[c_circle]:=c[[1,1]], etc. There is another important conceptual point which I did not list: data structures (ADT-s) serve to separate interface from implementation. But M's flexibility does not enforce that, so we often leak implementation details for our structs into the public interfaces they provide. The amount of discipline required to create and ... $\endgroup$ Jul 24, 2012 at 23:00
  • 2
    $\begingroup$ @Rojo ... use custom data structures in M is more often than not more than the threshold of implementing a problem in a different way. I've seen a number of examples where the use of M-style data structures was very beneficial from the readability viewpoint, but my point is that, because there are so many ways to do it, only quite skilled M users are able to do it well and not abuse them. OTOH, in languages like C or Java, even users with relatively weak skills can handle them reasonably well, since the number of choices is much less and number of associated pitfalls even lesser. $\endgroup$ Jul 24, 2012 at 23:04
  • 1
    $\begingroup$ ...the things you can do have to be said with the proper warnings. I would have never actually learned the pieces of advice I have read around if I had sticked with the rules "don't do this, don't do that". My answer was intended to say just the following: $\endgroup$
    – Rojo
    Jul 24, 2012 at 23:14
  • 1
    $\begingroup$ @Rojo Ok. I will edit mine too, perhaps tomorrow (got to get some sleep). It was not supposed to be an exact opposite of yours, I just wanted to encourage clear Occam's razor - style thinking about data structures, particularly mutable ones. This was a useful comment exchange, thanks :-). $\endgroup$ Jul 24, 2012 at 23:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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