22
$\begingroup$

Roman Maeder's object oriented programming package is nice, but I'm hoping someone can suggest a sleek and novel implementation that is easy to use.

$\endgroup$
  • 3
    $\begingroup$ Can you tell us more about what functionality of OOP you'd most like to implement in Mathematica? $\endgroup$ – Jagra Jun 6 '12 at 17:47
  • 2
    $\begingroup$ Similar: stackoverflow.com/q/7363253/615464 $\endgroup$ – Sjoerd C. de Vries Jun 6 '12 at 18:02
  • 1
    $\begingroup$ OO is a moving target, as is nicely summarized here, so you first need to define which set of features you call OO (as noted already by @Jagra). Implementing some basic OO by itself is not very difficult, but you will face some pretty hard to solve problems, such as performance and garbage collection. In this answer, I dwell a little more on this topic and link to some previous discussions on the matter, which you may find of some interest. $\endgroup$ – Leonid Shifrin Jun 6 '12 at 19:38
  • $\begingroup$ Some interesting discussions in this topic can be found on MathGroup. Here is a recent one. $\endgroup$ – Jens Jun 6 '12 at 22:16
  • $\begingroup$ Here is the answer to you long waiting for OOP in Mathematica. Please visit to my article page "OOP environment for Mathematica without additional packages". $\endgroup$ – user42019 Aug 2 '16 at 15:49
5
$\begingroup$

Well, one obvious idea would be to build on the struct implementation by Bob Beretta. You would have to add information about methods and modify the implementation of --> to consider those as well, and for polymorphism, you'd also have to store the base class (or base classes, if multiple inheritance should be supported), and have --> look there if the field or method is not found.

$\endgroup$
14
$\begingroup$

Here's I think my 5th version of this type of thing. I've got another framework for this here whose construction I explained here.

The package version of this lives on GitHub, which you can load by:

Get["https://github.com/b3m2a1/mathematica-tools/raw/master/SymbolObjects.wl"]

Why do this again

OOP is overdone (well, over-emulated) in Mathematica. Personally I've done this upwards of 5 times in some capacity, each with different levels of sophistication and memory footprint.

This time I wanted to have a very light-weight, robust implementation that doesn't affect any part of the system at all.

Basic Setup

Core idea

The driving principle behind this was to effectively make my objects Associations (hash maps), but encapsulated in a mutable Symbol with accessors and things to make them really feel like an Association.

To do this I picked a primary Head to wrap my Symbols which I called SObj and it was pretty much straightforward from there.

I'm going to omit parts of this that don't really give us much. Follow along by looking at the package if you want the entire story. It's ~500 LOC and I don't want to copy the entire text here.

Constructing a new object

The first thing I needed to do was build a constructor function, which I called SObjNew:

SObjNew[
   name : _String | Automatic : Automatic,
   templates : {___String} : {},
   a : _Association : <||>
   ] :=
  With[
   {
    symbol =
     Unique[
      c <> "$Objects`" <>
       If[StringQ@name, name, "SObj"] <>

           "$"
      ],
    state =
     Merge[
      {
       "ObjectID" -> CreateUUID["sobj-"],
       a,
       Lookup[$SObjTemplates,
        Prepend[
         templates,
         If[StringQ@name, name, "Object"]
         ], 
        {}
        ],
       "ObjectType" -> 
        If[StringQ@name, name, "Object"],
       "ObjectTemplates" -> templates
       },
      First
      ]
    },
   SetAttributes[symbol, Temporary];
   symbol = state;
   SObj[symbol]
   ];

The main things to note here: we use Unique to build a new Symbol in a specific context. We give that Symbol the attribute Temporary so it can automatically be garbage collected.

There's also stuff for managing templates (essentially classes, but with a different sort of inheritance mechanism so I didn't want to call them that).

Defining accessor functions

Since we want this to work like an Association we need to support a bunch of different types of attribute lookups. Primarily we'll support [], [[]], Extract, and Lookup. These give us the broadest coverage:

(*
SObjAccess[___]:
Basic [___] wrapper for SObj
*)
SObjAccess[
  o : SObj[s_Symbol],
  k__
  ] :=
 If[$SObjGetDecorate, SObjGetDecorate[o, #] &, Identity]@
  s[k]
(*
SObjPart[___]:
Part wrapper for SObj
*)
SObjPart // Clear
SObjPart[
   o : SObj[s_Symbol], 
   k : _Span | _List
   ] :=
  If[$SObjGetWrap, SObj, Identity]@Evaluate@
    s[[k]];
SObjPart[
   o : SObj[s_Symbol], 
   k__
   ] :=
  If[$SObjGetDecorate, SObjGetDecorate[o, #] &, Identity]@
   s[[k]];
(*
SObjExtract[___]:
Extract wrapper for SObj
*)
SObjExtract // Clear
SObjExtract[
   o : SObj[s_Symbol], 
   k : {_List} | Except[_List]
   ] :=
  If[$SObjGetDecorate, SObjGetDecorate[o, #] &, Identity]@
   Extract[s, k];
SObjExtract[
   o : SObj[s_Symbol], 
   k_
   ] :=
  If[$SObjGetWrap, SObj, Identity]@Evaluate@
    Extract[s, k];
SObjExtract[
   o : SObj[s_Symbol], 
   k_,
   h_
   ] :=
  If[$SObjGetDecorate, SObjGetDecorate[o, #] &, Identity]@
   Extract[s, k, h];
(*
SObjLookup[___]:
Lookup wrapper for SObj
*)
SObjLookup[
   o : SObj[s_Symbol], 
   k_
   ] :=
  If[$SObjGetDecorate, SObjGetDecorate[o, #] &, Identity]@
   Lookup[s, k];
SObjLookup[
   o : SObj[s_Symbol], 
   k_,
   d_
   ] :=
  If[$SObjGetDecorate, SObjGetDecorate[o, #] &, Identity]@
   Lookup[s, k, d];
SObjLookup[
   o : {__SObj},
   k_
   ] :=
  If[$SObjGetDecorate, SObjGetDecorate[o, #] &, Identity]@
   Lookup[SObjSymbol /@ o, k];
SObjLookup[
   o : {__SObj},
   k_,
   d_
   ] :=
  If[$SObjGetDecorate, SObjGetDecorate[o, #] &, Identity]@
   Lookup[SObjSymbol /@ o, k, d];

You'll notice two weird parameters here, $SObjGetDecorate and $SObjGetWrap. The first of these handles a special type of attribute I called an SObjMethod. SObjGetDecorate automatically binds these to the instance they were extracted from. It's a convenience, but can be inefficient, so we allow it to be turned off. The latter handles the fact that we want these to act like Association and when you do <|1->2, 2->3, 3->4|>[[2;;]] you get an Association back. By analogy we should expect to get an SObj out when we do obj[[2;;]].

Defining setter functions

Association supports a few different setting methods, a[___]=val, a[[__]]=val, and AssociateTo[a, rules]. We want to support all of them for flexibility. Happily this is pretty easy as we just delegate to the held Symbol:

(*
SObjSet[___]:
Set wrapper for SObj
*)
SObjSet[SObj[s_], prop_, val_] :=
  s[prop] = val;
(*
SObjSetPart[___]:
Set Part wrapper for SObj
*)
SObjSetPart[SObj[s_], part__, val_] :=
  s[[part]] = val;
(*
SObjAssociateTo[___]:
AssociateTo wrapper for SObj
*)
SObjAssociateTo[SObj[s_], parts_] :=
  (AssociateTo[s, parts];);

And with this we've defined the core of our lower-level interface.

Setting up a good object type

This is where the bulk of the fun starts. We'll want our SObj object to delegate to all those methods we just defined in the right cases.

Constructor

First we define a constructor case:

SObj[
   name : _String | Automatic : Automatic,
   templates : {___String} : {},
   a : _Association : <||>
   ] :=
  SObjNew[name, templates, a];

NoEntry

Then we'll do a funky thing, since we're going to want our SObj to look atomic, we'll make sure that if it's a valid object it's got System`Private`NoEntryQ.

o : SObj[s_Symbol] /; (
   System`Private`EntryQ[Unevaluated@o] &&
    SObjSymbolQ[s]
   ) :=
 (System`Private`SetNoEntry[Unevaluated@o]; o)

Property access

Then we define our property access, mostly via UpValues:

(o : SObj[s_Symbol]?SObjQ)["Properties"] :=
  SObjKeys@o;
(o : SObj[s_Symbol]?SObjQ)[
   i__
   ] :=
  (SObjAccess[o, i]);
SObj /: Lookup[SObj[s_Symbol]?SObjQ, i__] :=
  (SObjLookup[s, i]);
SObj /: Part[o : SObj[s_], p___] :=
  (SObjPart[o, p] /; SObjQ@o);
SObj /: Extract[o : SObj[s_], 
   e___] :=
  (SObjExtract[o, e] /; SObjQ@o);

Property setting

Here's where most of my old attempts have really struggled. It's easy to redefine things like a / : Set[a[p_], b_]:=(mySet[a, p, b]), but it's harder to handle Part and friends without things getting messy. Happily we now have the function Language`SetMutationHandler which will do this for us.

We'll want to handle all the cases we declared before, so we set it up like this:

SetAttributes[SObjMutationHandler, HoldAllComplete];
SObj /: Set[o_SObj?SObjQ[prop_], newvalue_] :=

  SObjSet[o, prop, newvalue];
SObjMutationHandler[
   Set[(sym : (_SObj | _Symbol)?SObjQ)[prop_], newvalue_]
   ] := SObjSet[sym, prop, newvalue];
SObjMutationHandler[
   Set[Part[(sym : (_SObj | _Symbol)?SObjQ), p__], newvalue_]
   ] := SObjSetPart[sym, p, newvalue];
SObjMutationHandler[
   AssociateTo[(sym : (_SObj | _Symbol)?SObjQ), stuff_]
   ] := SObjAssociateTo[sym, stuff];
Language`SetMutationHandler[SObj, SObjMutationHandler];

A brief note on MutationHandlers

Note that without a mutation handler, there are a few core cases that would be very tough. For instance, if we wanted to set a part on an object and tried it naively:

a = obj[uuid];
a[[1]] = 2;
a

obj[2]

it doesn't work.

Similarly, if we tried to set a part on an Association we set as a SubValue:

b = obj[uuid];
b[1] = <||>;
b[1, 2] = 10;
b[1]

<||>

this also fails

Alternatively, if we use a MutationHandler in a minimal object implementation:

myO`myO~SetAttributes~HoldFirst;
myO`myO[s_Symbol][k__] := s[k];
myO`myO[[s_Symbol]][k__] := s[[k]];
myO`setPart[myO`myO[s_Symbol], p__, v_] :=
  s[[p]] = v;
myO`setKey[myO`myO[s_Symbol], p__, v_] :=
  s[p] = v;
myO`mutate~SetAttributes~HoldAllComplete;
myO`mutate[a_Symbol?(Head[#] === myO`myO &)[k__]~Set~ v_] :=

  myO`setKey[a, k, v];
myO`mutate[a_Symbol?(Head[#] === myO`myO &)[[k__]]~Set~ v_] :=

  myO`setPart[a, k, v];
Language`SetMutationHandler[myO`myO, myO`mutate];

We can handle those cases:

o = myO`myO[Evaluate@Unique[]];
o[1]
o[1] = 2

$1406[1]

2

o =
  With[{s = Unique[]},
    s = Range[10];
   myO`myO[s]
   ];
o[[1]] = 2;
First@o

{2, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Attributes

Finally we'll need to make this thing HoldFirst:

SObj~SetAttributes~HoldFirst

Take-aways

Hopefully that was a simple enough explanation of one of many takes on OOP in Mathematica. The key things that this provides for us:

  1. All top-level
  2. Temporary allows for Automatic garbage collection
  3. By using the Association interface we can get efficient object emulation
  4. System`Private`SetNoEntry allows our object to act atomic
  5. Language`SetMutationHandler fixes the issue of how to set parts on the object when UpValues will only go to depth 1 as well as the more crucial issue of how to handle mutating the object if it's bound to a symbol.

Classes

I added in an instantiation / class system to this based on simply having an object with a "New" method that will call a function SObjInstantiate that will build an instance from the object.

Similar features are simple enough building off that.


Examples

Basic mutability

Here's a little example of how this works:

Get["https://github.com/b3m2a1/mathematica-tools/raw/master/SymbolObjects.wl"];
myObj = SObj[]

asd

AssociateTo[myObj, 
 KeyMap[CanonicalName]@ChemicalData["Water", "Association"]
 ]

myObj // Length

113

myObj["BondLengths"]

{Quantity[1., "Angstroms"]}

myObj["BondLengths"] *= 2

myObj["BondLengths"]

{Quantity[1.9, "Angstroms"]}

myObj[["BondLengths", 1, 2]] = "Kilojoules"

"Kilojoules"

myObj["BondLengths"]

{Quantity[1.9, "Kilojoules"]}

We can see it generally does act like an Association (although Lookup of course doesn't really work for it), but this object is stateful, is never copied, and when we use it with another Symbol the changes map back:

asd = myObj;

asd["BondLengths"] *= 2

myObj["BondLengths"]

{Quantity[3.8, "Kilojoules"]}

Implementing an iterator

Here's how we can build an iter type with this. First we set up a Next and Skip functions that would work with a passed Symbol capturing an Association:

iterNext[iter_, Optional[1, 1]] :=
  Quiet[
   Check[
    (iter["Index"] += 1; #) &@
     iter[["Iterable", iter["Index"]]],
     EndOfBuffer,
    {Part::partw}
    ],
   {Part::partw}
   ];
iterNext[iter_, n_Integer] :=

  Block[{tally = 0, i = iter["Index"], res, range},
   range =
    i +
     If[n > 0,
      Range[0, n - 1], 
      Range[-1, n, -1]
      ];
   res =
    Join[
     Map[
      Quiet[
        Check[
         iter[["Iterable", #]],
         tally++;
          EndOfBuffer,
         {Part::partw}
         ],
        {Part::partw}
        ] &,
      Select[range, Positive]
      ],
     ConstantArray[EndOfBuffer, Length@Select[range, Not@*Positive]]
     ];
   iter["Index"] = Max@{i + n - tally, 1};
   res
   ];
iterSkip[iter_, n : _Integer : 
    1] :=
  (iter["Index"] = Max@{iter["Index"] + n, 1};);
iterExtend[iter_, l_] :=
  With[{i = Join[iter["Iterable"], l]},
   iter["Iterable"] = i;
   ];

Then we define a function for initializing the iter and a class template which we bind to the $SObjTemplates variable provided by the package.

iterInit[obj_, iter_: {}] :=
  (
   AssociateTo[
    obj,
    {
     "Iterable" -> iter,
     "Index" -> 1
     }
    ];
   obj
   );
SymbolObjects`Package`$SObjTemplates["Iterator"] =
  <|
   "ObjectInstanceProperties" ->
    <|
     "Next" ->
      SymbolObjects`Package`SObjMethod[iterNext],
     "Skip" ->
      SymbolObjects`Package`SObjMethod[iterSkip],
     "Extend" ->
      SymbolObjects`Package`SObjMethod[iterExtend],
     "ExhaustedQ" ->
      SymbolObjects`Package`SObjProperty[
       With[{res = iterNext[#] === EndOfBuffer},
         iterNext[#, -1];
         res
         ] &
       ]
     |>,
   "ObjectInitialize" -> iterInit
   |>;

And now we can initialize our "Iterator" class:

iter = SObj["Iterator", {"Class"}]

asdasd

Then we can instantiate the iter:

inst = iter["New"][Range[5]]

asddd

inst["Next"][]

1

inst["Skip"][]

inst["Next"][5]

{3, 4, 5, EndOfBuffer, EndOfBuffer}

inst["Skip"][-5];
inst["Next"][5]

{1, 2, 3, 4, 5}

inst["Extend"][Range[10]];
inst["Skip"][5]
inst["Next"][10]

{6, 7, 8, 9, 10, EndOfBuffer, EndOfBuffer, EndOfBuffer, EndOfBuffer, EndOfBuffer}

inst["ExhaustedQ"]

True

Final notes

If this interests you, look at the package, email me with questions. If there are things you want to see in the package, let me know and I'll try to get to them when I have time. I really just knocked this thing out as fast as possible (maybe like ~2 or 3 hours) so it's undoubtedly highly incomplete.

$\endgroup$
  • $\begingroup$ Can you elaborate on the problematic cases where mutation handler really helps? $\endgroup$ – Kuba Feb 13 '18 at 10:46
  • $\begingroup$ @Kuba added a note on that $\endgroup$ – b3m2a1 Feb 13 '18 at 19:19
11
$\begingroup$

[...] but I'm hoping someone can suggest a sleek and novel implementation that is easy to use.

The answer to this is to use Object-Oriented Design Patterns in Mathematica as explained and exemplified in the presentation "Object Oriented Design Patterns" at the Wolfram Technology Conference 2015. (The presentation recording is also uploaded at YouTube.)

Here is a link to a document describing how to implement OOP Design Patterns in Mathematica:

"Implementation of Object-Oriented Programming Design Patterns in Mathematica"

The described approach does not require the use of preliminary implementations, packages, or extra code.

Design Patterns brought OOP into maturity. Design Patterns help overcome limitations of programming languages, give higher level abstractions for program design, and provide design transformation guidance. Because of this it is much better to emulate OOP in Mathematica through Design Patterns than through emulation of OOP objects. (The latter is done in all other approaches and projects I have seen.)

Related posts/descriptions/answers

  1. MSE discussion "Which Object-oriented paradigm approach to use in Mathematica?".

  2. Blog post "Object-Oriented Design Patterns in Mathematica".

  3. Blog post "UML diagrams creation and generation".

  4. This answer in the discussion General strategies to write big code in Mathematica?.

  5. This answer in the discussion Can one identify the design patterns of Mathematica?.

$\endgroup$
  • 1
    $\begingroup$ I think this is a great point. I use OOP as an architectural and design construct all the time but only a few times have a truly used any of my proper object frameworks for serious things. It’s much lighter weight and with the introduction of Association in my opinion almost always the way to go (unless you really need the extra encapsulation and mutability that a true object brings). $\endgroup$ – b3m2a1 Feb 12 '18 at 8:55
  • $\begingroup$ @b3m2a1 Right, the introduction of Association makes some OOP approaches more convenient. For example, see this answer of "Functions with changeable global variables" that compares the use of Association and the use of faithful OOP. $\endgroup$ – Anton Antonov Feb 13 '18 at 15:28
2
$\begingroup$

Minimal Object Implementation

As part of extending my other answer, I came up with a minimal mutable object implementation:

myO`myO~SetAttributes~HoldFirst;
myO`myO[s_Symbol][k__] := s[k];
myO`myO[[s_Symbol]][k__] := s[[k]];
myO`setPart[myO`myO[s_Symbol], p__, v_] :=
  s[[p]] = v;
myO`setKey[myO`myO[s_Symbol], p__, v_] :=
  s[p] = v;
myO`mutate~SetAttributes~HoldAllComplete;
myO`mutate[a_Symbol?(Head[#] === myO`myO &)[k__]~Set~ v_] :=

  myO`setKey[a, k, v];
myO`mutate[a_Symbol?(Head[#] === myO`myO &)[[k__]]~Set~ v_] :=

  myO`setPart[a, k, v];
Language`SetMutationHandler[myO`myO, myO`mutate];
myO`myO[] := myO`myO[Evaluate@Unique[]];

For those who don't like to load packages, this will get you most of what you want

Slightly-Less Minimal

Here's an object implementation that lives somewhere between that minimal one and my packaged up one. It's basically the package minus the template / class interface and constructor and things.

BeginPackage["Obj`"];
Unprotect["Obj`*"];
ClearAll["Obj`*", "Obj`*`*"];
Obj::usage = "object wrapper";
MakeObj::usage = "Makes an object";
Protect["Obj`*"];
BeginPackage["`Package`"];
apply::usage = "applies a function to an Obj";
mutate::usage = "mutates an Obj";
EndPackage[];
Begin["`Private`"];
(* constructor *)
Obj~SetAttributes~HoldFirst;
MakeObj[e_] :=
  With[{u = Unique[`$Objects`Obj]},
   u = e;
   SetAttributes[u, Temporary];
   Obj[u]
   ];
Obj[] := MakeObj[<||>];
(* define a application function *)

apply~SetAttributes~HoldAllComplete;
apply[Obj[s_Symbol], f_, args___] := f[s, args];
apply[Obj[s_Symbol][a___], f_, args___] := f[s[a], args];
apply[Obj[s_Symbol][[a___]], f_, args___] := f[s[[a]], args];
apply[e_, r__] :=
  apply[Evaluate@e, r] /; MatchQ[e, Obj[_Symbol]];
(* define access interface via application function *)
(o_Obj)[k___] :=
   apply[o, Function[Null, #[k], HoldFirst]];
$basicInterfaceFunctions =
  {
   Part, Extract, Take, Drop, First, Last, Most, Rest, Length, 
   KeyTake, KeyDrop, MemberQ, KeyMemberQ, KeyExistsQ
   };
Map[(Obj /: #[o_Obj, a___] := 
     apply[o, #, a]) &, $basicInterfaceFunctions];
Obj /: Normal[Obj[s_Symbol]] := s;
(* define mutation interface via apply function *)

SetAttributes[mutate, HoldAllComplete];
$basicMutationFunctions =
  {
   Set, SetDelayed, Unset, PrependTo, AppendTo, AssociateTo, 
   KeyDropFrom, AddTo, SubtractFrom, TimesBy, DivideBy
   };
Map[
  (mutate[#[o : (_Symbol?(MatchQ[#, Obj[_Symbol]] &) | Obj[_Symbol]), 
       a___]] :=
     apply[o, #, a];
    mutate[#[
       o : (_Symbol?(MatchQ[#, Obj[_Symbol]] &) | Obj[_Symbol])[k__], 
       a___]] :=
     apply[o, #, a];
    mutate[#[
       o : (_Symbol?(MatchQ[#, Obj[_Symbol]] &) | 
           Obj[_Symbol])[[k___]], a___]] :=
     apply[o, #, a]
    ) &,
  $basicMutationFunctions
  ];
Language`SetMutationHandler[Obj, mutate];
End[];
EndPackage[];
$\endgroup$
1
$\begingroup$

Data abstraction is one aspect of object-oriented programming, in my opinion, the most important aspect. I frequently use a schema abstracted, I hope in a very obvious way, from the following example, to implement data abstraction. I can just keyboard new 'classes' according to this schema without thinking much at all. My answer ignores many aspects of "object-oriented programming," such as namespace control (all my symbols must be free of global definitions), polymorphism (it's not straightforward to inherit from my 'class' simulacrum), lifetime management (I can only hope Mathematica's garbage collector does the right thing), and many other aspects, I am sure. I offer this only because it's been useful to me many times and it doesn't require any special support. And, in the hope that it is self-explantory, here it is with no further prose.

ClearAll[complex, real, imaginary, magnitude, phase, asMMA, setReal, 
  setImaginary, setMagnitude, setPhase, setEulerian, setCartesian];
(* Constructor for 'complex' numbers stands in for the 'class' \
concept. There are no functions, here, for algebraic operations on my \
complex numbers, but I hope it's obvious how to add them. *)
complex[
   r : (_Real | _Integer | _Rational),
   i : (_Real | _Integer | _Rational)] :=
  Module[{this,(* a perforce unique 'instance' symbol *)
    realPart, 
    imaginaryPart, phasePart, magnitudePart,
    setEulerian, setCartesian (* closures that depend on other unique symbols *)},
   (* private *)
   realPart = r;
   imaginaryPart = i;
   setEulerian[] :=
    (magnitudePart = 
      Sqrt[realPart^2 + imaginaryPart^2];
     phasePart = ArcTan[realPart, imaginaryPart];);
   setCartesian[] :=
    (realPart = magnitudePart*Cos[phasePart];
     imaginaryPart = magnitudePart*Sin[phasePart]);
   (* initialize! *)
   setEulerian[];
   (* public getters; patterns unique to the instance, but installed in the global namespace *)
   this@real[] := realPart;
   this@imaginary[] := imaginaryPart;
   this@magnitude[] := magnitudePart;
   this@phase[] := phasePart;
   this@asMMA[] := Complex[realPart, imaginaryPart];
   (* public setters *)
   this@setReal[newR_] := (realPart = newR; setEulerian[];);
   this@setImaginary[newI_] := (imaginaryPart = newI; 
     setEulerian[];);
   this@setMagnitude[newM_] := (magnitudePart = newM; setCartesian[];);
   this@setPhase[new\[Phi]_] := (phasePart = Mod[new\[Phi], 2 \[Pi]]; 
     setCartesian[]);
   (* return 'this' instance symbols as result of constructor *)
  this   ];
aComplexNumber = complex[42, 157];
Print[aComplexNumber@real[]];
aComplexNumber@setReal[39];
Print[aComplexNumber@real[]];
Print[aComplexNumber@asMMA[]];
Print[N@aComplexNumber@phase[]];
Print[N@aComplexNumber@magnitude[]];
aComplexNumber@setPhase[0];
Print[N@aComplexNumber@asMMA[]];
42
39
39+157 I
1.32732
161.771
161.771 +0. I
$\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.