9
$\begingroup$

What would be the best way to give a module its own context and have its local variables and Modules belong to that context?

$\endgroup$
8
  • 1
    $\begingroup$ After seeing the update 2 I don't know where to start helping. Perhaps you'll be happy just reading the implementation of the Pair in my answer, taken from an old Leonid post. Then, you just don't copy-paste it if you need a copy, but run again the code. $\endgroup$
    – Rojo
    Jan 24, 2012 at 7:37
  • 2
    $\begingroup$ @Nasser: your questions always seem to end up amazingly long! $\endgroup$
    – Simon
    Jan 25, 2012 at 4:22
  • 4
    $\begingroup$ Nasser, I noticed you like to add replies to your questions instead of just updating them with relevant information, much like a discussion. This makes the problem quite difficult to follow for anyone who hasn't been there from the beginning. You might get more and better answers if you keep the question concise and easy to read. Ideally these questions should eventually become cleaned up enough to be useful for anyone who finds them with a search engine, not just the original poster. ... $\endgroup$
    – Szabolcs
    Jan 25, 2012 at 11:29
  • 2
    $\begingroup$ ... You clearly put a lot of effort into the question, making screenshots, and annotating them to make them easier to follow. But sometimes less is more ... and if more people are able to (or are willing to) read through the whole question, you might get more good responses. $\endgroup$
    – Szabolcs
    Jan 25, 2012 at 11:30
  • 1
    $\begingroup$ @Nasser I added variants to your edit(6) that don't need a dispatcher function. So you can use objects quite easily using your Manipulate code and my suggestions. $\endgroup$
    – faysou
    Jan 25, 2012 at 14:52

6 Answers 6

6
$\begingroup$

Here is an idea:

SetAttributes[account, HoldAll ]

makeAccount[ initBalance_ ] :=
 Module[ { balance = initBalance },
  account[ balance ]
  ]

account /: balance[ account[ bal_ ] ] := bal

account /: deposit[ account[ bal_ ], newBal_ ] := ( bal += newBal )

account /: withdraw[ account[ bal_ ], amount_ ] := ( bal -= amount ) /; 
  amount <= bal
account /: withdraw[ account[ bal_ ], amount_ ] := 
 Print[ "Save some cash first" ] /; amount > bal

Format[ acc_account  /; ValueQ[ balance[ acc ] ] ] := "-account-"

Then use this as:

a1 = makeAccount[100]
a2 = makeAccount[150]

balance[a1]

100

deposit[a1, 100]

200

balance[a1]

200

balance[a2]

150

This works because of the unique symbol:

FullForm[a1]

Edit:

Here is a version without SetAttributes

makeAccount1[ initBalance_ ] :=

 Module[ { balance = initBalance , withdraw, deposit, amount, 
   dispatch},

  withdraw[ amount_ ] :=
   Module[ {},
    If[ balance >= amount,
     balance -= amount; balance,
     Print[ "Insufficient funds" ] 
     ]
    ];

  deposit[ amount_ ] :=
   Module[ {},
    balance += amount; 
    balance
    ];

  amount[ amount_ ] :=
   Module[ {},
    balance
    ];

  dispatch[m_] :=
   Which[
    StringMatchQ[ m, "withdraw" ], withdraw,
    StringMatchQ[ m, "deposit" ], deposit,
    StringMatchQ[ m, "amount" ], amount,
    (* else *)
    True, Print["Unknown request -- MAKE_ACCOUNT ", m]
    ];

  Return[ dispatch ];
  ]

Use as follows:

acc = makeAccount1[ 100 ] 
acc2 = makeAccount1[ 200 ] 

dispatch$99 dispatch$100

acc[  "withdraw" ][ 10 ]

90

acc2[ "withdraw" ][ 60 ]

140

acc2["amount"][]

140

$\endgroup$
2
  • $\begingroup$ Ah, sorry. missed the SetAttributes. $\endgroup$
    – user21
    Jan 24, 2012 at 8:28
  • $\begingroup$ @NasserM.Abbasi, see edit for a version with out SetAttributes and such... have fun with it. $\endgroup$
    – user21
    Jan 24, 2012 at 20:32
6
$\begingroup$

Here's one approach, although I don't think it will actually meet your needs.

The main thing is that care needs to be taken to make sure symbols are created in the correct context, with context being set during parsing, not evaluation.

moduleState[context_String] := 
 With[{init = ToExpression[context <> "init"], 
   state = ToExpression[context <> "state"], 
   get = ToExpression[context <> "get"], 
   set = ToExpression[context <> "set"]}, 
  Module[{state}, init[] := state = 0;
    init[];
    get[] := state;
    set[x_] := state = x;];]

A quick test:

moduleState["obj1`"]
moduleState["obj2`"]

obj1`init[];
obj2`init[];

obj1`set[10]
Out[6]= 10
{obj1`get[], obj2`get[]}
Out[7]= {10, 0}

Unfortunately, since you're intending to use this for CDF or a demonstration, I expect you'll run into problems because ToExpression is usually blacklisted in those scenarios.

$\endgroup$
1
  • $\begingroup$ You can use Symbol instead of ToExpression. I think that should be ok for use in demonstrations. $\endgroup$
    – Simon
    Jan 24, 2012 at 5:30
4
$\begingroup$

If each will have it's own methods, you can't avoid writing every method by hand, and, you can always precede your modules by Begin["objX`"] and end them with End[], which I believe is quite neat... If they have the same methods but different state like your example, you can use what Brett suggested I guess.

Or, if you are just interested in how to implement a basic ADT with some state, basic methods, getters, constructor, check this Usenet post by Leonid when he implements the Pair object, without using a context for each object.

I'm copying it simplified here

Module[{first, second},
  first[_] := {};
  second[_] := {};
  new[pair[]] := pair[Unique[]];
  pair[tag_]@delete[] := (first[tag] =.; second[tag] =.);
  pair[tag_]@setFirst[value_] := first[tag] = value;
  pair[tag_]@getFirst[] := first[tag];
  pair[tag_]@setSecond[value_] := second[tag] = value;
  pair[tag_]@getSecond[] := second[tag];
  ];

and you use it

pr = new[pair[]];
pr@setFirst[10];
pr@setSecond[20];
{pr@getFirst[], pr@getSecond[]}

Basically, "first" and "second" are private attributes, and he stores the state of each instance of Pair as DownValues to first and second. The methods are common to all instances so don't need to be defined more than once.

I've also seen around a small OOP package around that associates a class to a context, so the methods are defined in the context of the class instead of the Global context, which makes sense...

$\endgroup$
0
3
$\begingroup$

I recommend you to have a look at my answer here https://mathematica.stackexchange.com/a/999/66 in order to use a symbol as an object.

The idea is to use DownValues for storing properties and UpValues for storing methods.

Update: Here are two variants of edit(6) that do not require a dispatcher function.

Update2: Code updated to reflect the correct initialization in method3 of this post How to eliminate the need to double evaluate a Manipulate so that a Module in its Initialization section works?.

First one: variables comes from the Module of makeSolver, functions are stored in a solver symbol.

Manipulate[
   tick;

    If[ running,
        {done, res} = run[solver1];
        If[ Not[done],
            Pause[.01];
            tick++
        ]
    ];

    res
    ,
    Grid[
        {
            {
                Button["run", running = True;tick++],
                Button["init", 
                    res = "initialized!";
                    init[solver1];
                    running = False;
                    tick++
                ]
            }
           }
       ]
    ,
    {{tick, 0}, None}
    ,
    {{running, False}, None}
    ,
    {{res, ""}, None}
    ,
    {solver1, None}
    ,
    TrackedSymbols :> {tick}
    ,
    Initialization :>
        {   
            makeSolver[] :=
                Module[ {solver,r,u,n},
                       r = Range[-Pi, Pi, Pi/20];

                       init[solver] ^:= 
                        Module[ {i},
                            n = 0;
                            u = Table[r[[i]], {i, 1, Length[r]}]
                        ];

                    run[solver] ^:= 
                        Module[ {x, done = False},
                               If[ n < Length[u],
                                    n++;
                                    x = u[[n]];

                                    u[[n]] = {x, Sin[x]};
                                   ,
                                   done = True
                            ];

                               {done, ListPlot[u[[1 ;; n]], PlotRange -> {{-Pi, Pi}, {-1, 1}}]}
                           ];

                       Return[solver]
                   ];

                 solver1=makeSolver[];
                 init[solver1];
           }
   ]

Second one: everything is stored in a solver symbol but assignments to part of a list stored in this symbol are not straightforward.

Manipulate[
   tick;

    If[ running,
        {done, res} = run[solver1];
        If[ Not[done],
            Pause[.01];
            tick++
        ]
    ];

    res
    ,
    Grid[
        {
            {
                Button["run", running = True;tick++],
                Button["init", 
                    res = "initialized!";
                    init[solver1];
                    running = False;
                    tick++
                ]
            }
           }
       ]
    ,
    {{tick, 0}, None}
    ,
    {{running, False}, None}
    ,
    {{res, ""}, None}
    ,
    {solver1, None}
    ,
    TrackedSymbols :> {tick}
    ,
    Initialization :>
        {   
            makeSolver[] :=
                Module[{solver},

                    solver["r"]=Range[-Pi, Pi, Pi/20];

                    init[solver] ^:= 
                        Module[{i},
                            solver["n"] = 0;
                            solver["u"] = Table[solver["r"][[i]], {i, 1, Length[solver["r"]]}]
                           ];

                       run[solver] ^:= 
                           Module[{x, done = False},
                               If[solver["n"] < Length[solver["u"]],
                                   solver["n"]++;
                                   x = solver["u"][[solver["n"]]];

                                   solver["u"] = ReplacePart[solver["u"], solver["n"] -> {x, Sin[x]}]
                                   ,
                                   done = True
                               ];

                               {done, ListPlot[solver["u"][[1 ;; solver["n"]]], PlotRange -> {{-Pi, Pi}, {-1, 1}}]}
                           ];

                       Return[solver]
                   ];

               solver1=makeSolver[];
               init[solver1];
        }
]
$\endgroup$
2
  • $\begingroup$ Note that the symbols DownValues and UpValues are not used explicitely. Also you don't need the redefinition of Set (this was more for reference as this is not obvious to write). You can define a getter and setter function similarly to my definition of function. $\endgroup$
    – faysou
    Jan 24, 2012 at 9:17
  • $\begingroup$ I think you can still use the ideas described before ObjectSet, if options are not allowed you can probably get around this also. $\endgroup$
    – faysou
    Jan 24, 2012 at 9:25
2
$\begingroup$

Honestly I found your question to be too hard to follow in all details, maybe you should try to give a short summary in the beginning. Anyway, if I understood what you want I think there is no reason to do something very complicated. It just needs a very small change to Ruebenko's first approach to avoid the need for SetAttributes.

I tried to reprogram your example with that approach, here is the result:

Manipulate[
 trigger;
 If[simulation@"running", simulation@"update"];
 simulation@"result",
 Grid[{{
    Button["run", simulation@"setrunning"],
    Button["init", simulation@"init"]
    }}],
 {{trigger, 0}, None},
 {simulation, None},
 TrackedSymbols :> {trigger},
 Initialization :> (
   simulation = Module[{instance},
     instance@"range" = Range[-Pi, Pi, Pi/20];
     instance@"init" := (
       instance@"step" = 0;
       instance@"running" = False;
       instance@"result" = "initialized";
       trigger = AbsoluteTime[];
       );
     instance@"init";
     instance@"setrunning" := (
       simulation@"running" = True; 
       trigger = AbsoluteTime[]
       );
     instance@"update" := (
       instance@"step" += 1;
       If[instance@"step" > Length[instance@"range"],
        instance@"running" = False,
        instance@"result" = ListPlot[
           {#, Sin[#]} & /@ Take[instance@"range", instance@"step"],
           PlotRange -> {{-Pi, Pi}, {-1, 1}}
           ];
        ];
       trigger = AbsoluteTime[];
       Pause[0.01];
       );
     instance
     ]
   )]

I have not checked whether it passes the demonstrations site checks but I think the above doesn't use anything that could be considered dangerous. You will notice that I used strings instead of symbols for all the "fields" and "method" labels. Of course the code would look much cleaner and more standard if I'd use symbols, which can be done just as well. But I think it would make things more complicated (and need extra code) if you want them to be properly localized and all that. Another thing that's a matter of taste is whether you want to use different syntax for methods so they can be recognized easier as such (or want to pass arguments). You could just as well have used e.g. instance@"init"[] or "init"[instance], but for the latter you'd need to use TagSet as here (would that be allowed?):

instance /: "init"@instance := ... 

The whole approach would of course become much more interesting when using several of such "Objects", like here:

Manipulate[
 trigger;
 Column[{
   Grid[
    MapIndexed[Flatten[{#2, #1, #1@"balance"}] &, accounts]
    ],
   message
   }],
 Grid[{
   {
    Button["add account", AppendTo[accounts, makeAccount[0]]; 
     trigger = AbsoluteTime[]],
    Dynamic[trigger; 
     PopupMenu[Dynamic[currentaccount], Range[Length[accounts]]]]
    },
   {
    InputField[Dynamic[currentvalue], Number, ImageSize -> 120],
    ActionMenu["action", {
      "withdraw" :> (accounts[[currentaccount]]@
         "withdraw"[currentvalue]; trigger = AbsoluteTime[]), 
      "deposit" :> (accounts[[currentaccount]]@
         "deposit"[currentvalue]; trigger = AbsoluteTime[])
      }]
    }
   }
  ],
 {{currentaccount, 1}, None},
 {{currentvalue, 0}, None},
 {{currentaction, "deposit"}, None},
 {trigger, None},
 {accounts, None},
 {{message, ""}, None},
 Initialization :> (
   makeAccount[balance_] := Module[{account},
     account@"balance" = balance;
     account@"deposit"[amount_] := (account@"balance" += amount);
     account@"withdraw"[amount_] := 
      If[amount <= account@"balance", account@"balance" -= amount];
     account
     ];
   accounts = {makeAccount[0]};
   )]

If you see an advantage in doing so, you could of course create the "instance" symbols in an own context, but actually I think there is no reason in bothering with namespaces with that approach...

$\endgroup$
2
$\begingroup$

We could use one of the (many!) patterns that simulate OOP-style objects. For example, we could define a constructor function:

student[name$_, mark$_:"unknown"] :=
  Module[{name = name$, mark = mark$, self}
  , self@getName[] := name
  ; self@setName[v_] := name = v
  ; self@getMark[] := mark
  ; self@setMark[v_] := mark = v
  ; Format[self] := HoldForm@student[##]&[name, mark]
  ; self
  ]

Then we can construct a couple of objects:

s1 = student["Fred"]
s2 = student["Sue"]

... query for properties ...

s1@getName[] (* "Fred" *)
s1@getMark[] (* "unknown" *)

... update some properties ...

s1@setMark[85]
s2@setMark[87]

... and query in ways routine to Mathematica but alien to many OO languages:

students = {s1, s2}
Through @ students @ getName[]

(* {"Fred", "Sue"} *)

Outer[#1@#2&, students, {getName[], getMark[]}] // Grid
(*
    Fred  85
    Sue   87
*)
$\endgroup$

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.