8
$\begingroup$

This question appears impossibly long so apologies in advance (skip to the bottom if you want to see the actual questions).

I want to be able to mock Java Classes in Mathematica code. For example, I may have a simple Java bean Point that has two fields x and y. Using JLink, I would interact with objects of the point class as:

    obj = JavaNew["java.awt.Point"]
    obj@x = 22
    obj@y = 42

There are countless ways that this could be done in Mathematica but to mock (so I can later swap out the Mathematica code with the actual Java classes) I'd like to be able to do something along the lines of

    obj = Point[0, 0]
    obj@x = 22
    obj@y = 42
    obj == Point[22, 42] (* evals to True*) 

I have taken two stabs at this. First using the Notation package and overriding the following type of expressions:

Notation[
 ParsedBoxWrapper[
   RowBox[{RowBox[{"obj_", "@", "field_"}], "=", "val_"}]] [DoubleLongLeftRightArrow] 
    ParsedBoxWrapper[
      RowBox[{"ApplySet", "[", RowBox[{"obj_", ",", " ", "field_", ",", "val_"}], "]"}]]

The above maps code like

    obj_@field_ = val
    obj@x = -99    (* for example *)

to

    ApplySet[obj_, field_, val_]
    ApplySet[obj, x, -99]   (* for example *)

ApplySet in turn is redirected to either a setter on the "class" or passed through to the usual Mathematica handling on the basis of a conditional evaluation that checks that obj is a "class" or not.

     ApplySet[obj_, field_, val_] := set[obj, obj, field, val] /; MBeanQ[Head[obj]]
     (* pass obj through twice, once in HoldFirst the second evaluated as the data *)
     (* otherwise just pass through to normal handling for the Prefix @ *)
     ApplySet[obj_, field_, val_] := obj[field] = val

This approach works well in the FrontEnd and it seemed to be able to do everything I needed and more. However, when I moved this to packages the Notation and related MakeExpression/MakeBoxes doesn't seem to work. I did use Action -> PrintNotationRules to get the the underlying NotationMakeExpressions/NotationMakeBoxes. For example, here is the NotationMakeExpressions:

NotationMakeExpression[
  RowBox[{Notation`Private`lhs___, 
    RowBox[{obj_, "@", field_}], "=", val_, 
    Notation`Private`rhs___}], StandardForm] := 
  MakeExpression[RowBox[{Notation`Private`lhs, 
    RowBox[{"ApplySet", "[", 
      RowBox[{obj, ",", field, ",", val}], "]"}], 
    Notation`Private`rhs}], StandardForm]   

My second crack was to override the behaviour of Set and redirect in a similar way, testing for a "class". For example:

Set[obj_[field_], val_] := CallSet[obj, obj, field, Null, val] /; CallSetQ[Head[obj]];
Set[obj_[field_[idx_]], val_] := CallSet[obj, obj, field, idx, val] /; CallSetQ[Head[obj]];
Set[obj_, fieldUpdates__List] := CallSet[obj, obj, fieldUpdates] /; CallSetQ[Head[obj]];

This approach works well enough in both the FrontEnd and in packages, but of course it requires overriding something pretty fundamental. Even setting aside the potential performance concerns, it just doesn't seem right.

I'm sure that this is documentation somewhere but I couldn't find it.

Are MakeExpression and MakeBoxes really Frontend functions that aren't available in kernel to packages? Is there a smarter way to do this?

$\endgroup$
3
  • 3
    $\begingroup$ Strongly related (or even dupe) question. Alas, the answer there isn't very satisfactory. $\endgroup$ Jan 24, 2013 at 11:32
  • $\begingroup$ I'd be happy to work on answer if the actual problem was articulated. You start out by saying you want to mock Java objects, then you give a good start (just provide definitions using @ in the expected form), and then basically say "this doesn't work" and launch into a discussion involving typesetting such code. Why? What's the problem? $\endgroup$
    – Joel Klein
    Jan 24, 2013 at 14:56
  • $\begingroup$ Agreed, question not well articulated with most of the ink devoted to what I couldn't get to work. I think it could have been shortened to what is the best way to do mutable updates to a data structure using @ operator?" $\endgroup$
    – pjc42
    Jan 24, 2013 at 18:09

1 Answer 1

9
$\begingroup$

I would not think that either of the methods you mentioned is good enough. Notation` package is more or less limited to the FrontEnd (although one can come up with some hacks to make it work also in packages), while overloading Set is generally a bad idea.

Having in mind your particular application, something like this may work:

ClearAll[makePoint];
makePoint[obj_Symbol] :=
  Module[{},
    SetAttributes[obj, HoldAll];
    obj /: Set[obj, Point[xx_, yy_]] :=
      (
         obj[x] = xx;
         obj[y] = yy;
      );
    obj /: Set[obj, _] := $Failed;
    obj /: Equal[obj, Point[xx_, yy_]] :=
       obj[x] == xx && obj[y] == yy;
    obj /: Equal[obj, _] := False;
    obj];

This works on a per-symbol basis. You first "declare" your symbol to be an object of type Point by calling a "constructor" makePoint:

makePoint[obj]
(* obj *)

Then you have the desired outcome for your input:

obj=Point[0,0];
obj@x=22;
obj@y=42;
obj==Point[22,42] 

(* True  *)

One can use some meta-programming to generalize this and automate the generation of boilerplate code. I've done it here, but I dislike the idea of object's fields in the context of Mathematica (since they will promote mutable operations for objects, and that does not sit well with Mathematica's immutable expressions, IMO), so I did not implement them. But, as I've shown here, it is rather trivial to do (and I will likely add them to my framework, since many people are used to them).

There are some limitations here. You will have to live with your symbols being HoldAll, and you won't be able to change the precedance of @, so for things like x@y@z, you will either have to use parentheses like (x@y)@z, or construct some more complicated definitions which would take x[y[z]] and understand that what was meant was x[y][z].

$\endgroup$
2
  • $\begingroup$ Leonid, many thanks. Yes I see now how I can make this work for what I need. I had focused my attention on the "type" Point instead of the instances, obj. I used meta programming to add the sorts of definitions you have to Point instead of the obj. I thought of Point as my class definition and obj as just reference to it. This worked for pretty much everything except mutable updates. I was faking the mutability using a reassignment obj = updatedPoint inside the setter but this required getting a HoldFirst somewhere and obj@x = 42 => Point[0,0][x] = 22. This led to my use of Notation. Thanks. $\endgroup$
    – pjc42
    Jan 24, 2013 at 18:04
  • $\begingroup$ @pjc42 Glad if I could help. Thanks for the accept, too. $\endgroup$ Jan 24, 2013 at 18:17

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.