3
$\begingroup$

I'm following this example here; Code Readability and Object-Oriented Code

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, HoldFirst];
circle /: setSize[c_circle, size_] := c[[2]] = size;

Although I can successfully call the get functions, I cannot get a signature match for the set function.

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

In[1603]:= getSize@circ

Out[1603]= 23

In[1604]:= setSize[circ, 22]

Out[1604]= setSize[circ, 22]

If the Hold attribute is not set, then you get a Set::setps error as the first argument is replaced with its literal value rather than the symbol.

Any ideas?

$\endgroup$
0

3 Answers 3

3
$\begingroup$

I think simplest approach is to do something like:

setSize[circle[a_, b_], size_] := circle[a,size]

and then to use:

c = circle[{1, 1}, 2];
c = setSize[c, 4]

circle[{1, 1}, 4]

If you really want to use your approach, something like:

ClearAll[setSize]
SetAttributes[setSize, HoldFirst]

setSize[c_, size_] /; MatchQ[c, _circle] := (c[[2]] = size; c)

might do what you want:

setSize[c, 10]
c

circle[{1, 1}, 10]

circle[{1, 1}, 10]

$\endgroup$
3
$\begingroup$

Due to the problems discussed in the comments, I deleted the previous answer and substituted a new one. This is based on the use of a closure, which is likely to have been discussed previously on this site.

In[1]:= circle[{x_, y_}, size_] :=
 Module[{self, xc = x, yc = y, sizec = size},
  self["getX"] := xc;
  self["getY"] := yc;
  self["getPos"] := {xc, yc};
  self["getSize"] := sizec;
  self["setSize", newSize_] := sizec = newSize;
  self
  ]

In[2]:= circ = circle[{2, 3}, 23]

Out[2]= self$3546

Module creates local variables with the Temporary attribute, which normally get garbage collected when no longer referenced. However, the last line exports 'self', which gets assigned as an OwnValue of circ. As a result, the variables continue to be referenced and are not garbage colllected. The function circle[{x,y},size] acts as a constructor, storing its arguments into local variables - state variables for the "object". The various definitions for self act on these state variables, getting or setting them for this example. I use SetDelayed to force evaluation. So when setSize is used, the state variable sizec is modified, and a subsequent getSize retrieves the new value. (Strictly speaking, I only needed to do this for the size, but this allows you to add setters for x,y, etc. if you desire.) I chose to use strings instead of global symbols, but global symbols should also work fine.

Here are the results of circ using getX, getY, getPos, and getSize:

In[3]:= circ /@ {"getX", "getY", "getPos", "getSize"}

Out[3]= {2, 3, {2, 3}, 23}

Now modify the size from 23 to 22:

In[4]:= circ["setSize", 22]

Out[4]= 22

Retrieve the size using the getSize "method":

In[5]:= circ["getSize"]

Out[5]= 22

This approach avoids the problems discussed in the comments with my earlier attempt. The changes affect the local variables inside Module, but don't change the Downvalues for circ or circle.

enter image description here

$\endgroup$
5
  • $\begingroup$ Thanks for the workaround - nice and simple, although I would be curious to know why you can't use Hold. $\endgroup$ Jun 3, 2023 at 17:12
  • $\begingroup$ If you use Hold, you don't evaluate the argument, so it remains a symbol 'c'. The symbol doesn't have the Head circle, so the pattern doesn't match. $\endgroup$
    – user87932
    Jun 3, 2023 at 17:26
  • $\begingroup$ What happens when you execute circle[{2, 3}, 23] after the code? Alternatively execute ? circ and ? circle. It does not seem that setSize[circ, 22] is change the value of circ; rather, it sets another value for circle. Perhaps should use Protect[circle] in the original definitions? $\endgroup$
    – Michael E2
    Jun 3, 2023 at 17:42
  • 1
    $\begingroup$ @Michael E2 You're right, circ has the original definition, and circle[..23] = circle[...22] in the Downvalues. This isn't a good approach. $\endgroup$
    – user87932
    Jun 3, 2023 at 18:09
  • $\begingroup$ Thanks, I'm using this updated version. $\endgroup$ Jun 6, 2023 at 1:13
2
$\begingroup$

I'm not an expert on OOP, and the other answer in the Q&A the OP linked discusses some limitations of trying to emulate OOP in Mathematica. But, throwing caution to the wind, when I've done it, I take a modified approach. The basic object has the form class[dataVariable], where class is HoldFirst or HoldAll and identifies the class of the object and dataVariable is a symbol whose ownvalues define the characteristics of the object. Usually the dataVariable is generated by Unique or Module in a private context. To make the output look nice, I usually define an output format for it (I'm not particularly expert at that, either).

Here's a minimal modification to show the idea:

circle // ClearAll;
circle // Attributes = {HoldFirst};
circle /: new[circle, center_, radius_] := (* instantiation *)
 Module[{circle`data}, (* symbol in circle` context *)
  circle`data = {center, radius};
  circle[circle`data]
  ];
circle /: getSize[c_circle] := c[[1]][[2]]; (* NOT c[[1, 2]]! *)
circle /: setSize[c : circle[data_], size_] :=
  (data[[2]] = size; c); (* returns the circle[] *)

(* formatting *)
validCircleQ[c_, test_ : (True &)] := 
  MatchQ[c, circle[_Symbol]] && 
   MatchQ[First[c], {{_?test, _?test}, _?test}];
MakeBoxes[e : circle[c_]?validCircleQ, StandardForm] := 
  Replace[c, {p_, 
     r_} :> (InterpretationBox[#, e] &[
      RowBox[{"circle", "[", 
        RowBox[{MakeBoxes[p], ",", MakeBoxes[r]}], "]"}]
      ])
   ];

Examples:

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

(*  circle[{2, 3}, 23]  *)

% // FullForm

(*  circle[circle`data$85563]  *)

% // First

(*  {{2, 3}, 23}  *)

setSize[circ, 50]

(*  circle[{2, 3}, 50]  *)

% // First

(*  {{2, 3}, 50}  *)

getSize[circ]

(*  50  *)

For data, I usually use associations. It's nice to have words tagging the data components, and the order does not matter in an Association. In this case it could be the following:

circle`data = <|"Position" -> center, "Size" -> radius|>

One could then look up characteristics of an object with this:

circle /: get[circle[data_], args__] := Lookup[data, args]

get[circ, "Size"]

As I said, I'm not an expert on OOP, but I know there are important aspects of OOP such as inheritance and so forth. Don't ask me about them. I don't know how to do inheritance in a pleasant way. Maybe not in anyway. If you like OOP without inheritance, basically just object and methods, then the above is a good start.

$\endgroup$
1
  • $\begingroup$ Thanks. Yes, I'm just interested in the abstract data type being a nice way to bring data and methods together. I don't need the other features of OOP. $\endgroup$ Jun 6, 2023 at 0:37

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.