Set::setps error? Twitter.m's OOP-like approach?

233 views
Skip to first unread message

Erik Max Francis

unread,
Nov 3, 2009, 2:58:33 AM11/3/09
to
I was experimenting with vaguely OOP-like wrappers around data similar
to how Twitter.m does things:

http://blog.wolfram.com/2009/04/30/twittering-with-mathematica/

I was experimenting with "mutable objects" and I'm running into a
scoping problem of some type that I don't understand. Here's some
sample code creating a wrapper around an "account balance":

In[1]:= {NAME, BALANCE} = Range[2]

Out[1]= {1, 2}

In[2]:= create[name_, balance_] := Account[name, balance]

In[3]:= getName[account_Account] := account[[NAME]]

In[4]:= getBalance[account_Account] := account[[BALANCE]]

In[5]:= deposit[account_Account, amount_] :=
account[[BALANCE]] += amount

The deposit function doesn't work, though:

In[6]:= a = create["test", 100]

Out[6]= Account["test", 100]

In[7]:= getBalance[a]

Out[7]= 100

In[8]:= deposit[a, 25]

During evaluation of In[8]:= Set::setps: Account[test,100] in the part
assignment is not a symbol. >>

Out[8]= 125

In[9]:= getBalance[a]

Out[9]= 100

But here's what I'm confused; if I do what getBalance does manually, it
works fine:

In[10]:= a[[BALANCE]] += 25

Out[10]= 125

In[11]:= a

Out[11]= Account["test", 125]

The Set::setps error is about Blocks so I suppose I'm running into some
scoping issue but I don't follow what it is. What am I missing here?
What's the right way to write getBalance?

Do Mathematica users find Twitter.m's approach to wrapping around
objects palatable in the first place? (My eventual application is for a
wrapper around tensor objects where multiple things have to be carted
around and the normal display of such objects would be far too ungainly
to be acceptable anyway.)

Thanks.

--
Erik Max Francis && m...@alcyone.com && http://www.alcyone.com/max/
San Jose, CA, USA && 37 18 N 121 57 W && AIM/Y!M/Skype erikmaxfrancis
Wyrd has swept all my kin / all the brave chiefs away! / Now I must
follow them! -- Beowulf

ADL

unread,
Nov 4, 2009, 1:32:19 AM11/4/09
to
You might try with this "overloading" approach:

{NAME, BALANCE} = Range[2];
ClearAll[Account, create, getName, getBalance, deposit];
Account /: (f_[p___, Account[t_, n_], m___] /;
MemberQ[{Plus, Subtract, Times, Divide, Power}, f]) :=
Account[t, f[p, n, m]];
create[name_, balance_] := Account[name, balance];
getName[account_Account] := account[[NAME]];
getBalance[account_Account] := account[[BALANCE]];
deposit[account_Account, amount_] := account = account + amount;

This is the resulting behavior:

In[8]:= a=create["test",Random[Integer,{0,200}]]
Out[8]= Account(test,176)

In[9]:= getBalance[a]

Out[9]= 176

In[10]:= b=a+10
Out[10]= Account(test,186)

In[11]:= b=a 10
Out[11]= Account(test,1760)

In[12]:= b=10a
Out[12]= Account(test,1760)

In[13]:= b=a/10
Out[13]= Account(test,88/5)

In[14]:= b=10/a
Out[14]= Account(test,5/88)

In[15]:= b=a^2
Out[15]= Account(test,30976)

In[16]:= deposit[a,25]
Out[16]= Account(test,201)

In[17]:= deposit[b,25]
Out[17]= Account(test,31001)

In[18]:= a
Out[18]= Account(test,201)

In[19]:= b
Out[19]= Account(test,31001)

ADL


On Nov 3, 8:58 am, Erik Max Francis <m...@alcyone.com> wrote:
> I was experimenting with vaguely OOP-like wrappers around data similar
> to how Twitter.m does things:
>

> http://blog.wolfram.com/2009/04/30/twittering-with-mathema=

> objects palatable in the first place? (My eventual application is for =


a
> wrapper around tensor objects where multiple things have to be carted
> around and the normal display of such objects would be far too ungainly
> to be acceptable anyway.)
>
> Thanks.
>
> --

> Erik Max Francis && m...@alcyone.com &&http://www.alcyone.com/max/


> San Jose, CA, USA && 37 18 N 121 57 W && AIM/Y!M/Skype erikmaxfrancis

> Wyrd has swept all my kin / all the brave chiefs away! / Now I mus=
t
> follow them! -- Beowulf


dh

unread,
Nov 4, 2009, 1:36:17 AM11/4/09
to

Hi Erik,

what you want to do is a bit tricky.

By: deposit[a, 25] you want to assigne to the variable "a" an expression

like: Account[..,..]

By default all arguments to a function are evaluated prior to calling

the function. Therefore your variable "account" does not contain "a" but

the expression "Account["test", 100]". To obtain the variable name you

must prevent the evaluation by e.g. specifying;

SetAttributes[deposit,HoldFirst].

Inside the function you must again take precautiion against evaluation

of the variable by specifying e.g.:

Unevaluated[account] =

As you have not defined an addition rule for "Account" we need to create

the full expression on the right side of the assignment:

Unevaluated[account] = Account[account[[1]], account[[2]] + amount]

Here "account" contains the evaluated "a", namely: "Account["test", 100]"

Therefore, deposit would be declared e.g. like:

SetAttributes[deposit, HoldFirst];

deposit[account_, amount_] :=

Unevaluated[account] = Account[account[[1]], account[[2]] + amount]

Daniel

ragfield

unread,
Nov 4, 2009, 1:39:40 AM11/4/09
to
On Nov 3, 2:58 am, Erik Max Francis <m...@alcyone.com> wrote:
> The deposit function doesn't work, though:
>
> In[6]:= a = create["test", 100]
>
> Out[6]= Account["test", 100]
>
> In[7]:= getBalance[a]
>
> Out[7]= 100
>
> In[8]:= deposit[a, 25]
>
> During evaluation of In[8]:= Set::setps: Account[test,100] in the part
> assignment is not a symbol. >>
>
> Out[8]= 125
>
> In[9]:= getBalance[a]
>
> Out[9]= 100
>
> But here's what I'm confused; if I do what getBalance does manually, it
> works fine:
>
> In[10]:= a[[BALANCE]] += 25
>
> Out[10]= 125
>
> In[11]:= a
>
> Out[11]= Account["test", 125]
>
> The Set::setps error is about Blocks so I suppose I'm running into some
> scoping issue but I don't follow what it is. What am I missing here?
> What's the right way to write getBalance?


By the time deposit[] is called "a" has already evaluated to Account
["test", 100], which means a[[BALANCE]] += 25 at that point really
means 100 += 25. What you are looking for is "call by reference"
behavior, and the way to get it is to use one of the Hold*
attributes. In this case you want to first argument of your deposit[]
function to remain unevaluated so the actual symbol "a" is passed into
the function in rather than the value of "a". You'll also need to
change the pattern of the first argument from _Account to _Symbol.
This works how you want:

SetAttributes[deposit, HoldFirst]

deposit[account_Symbol, amount_] := account[[BALANCE]] += amount

In[55]:= deposit[a, 25]

Out[55]= 125

In[56]:= getBalance[a]

Out[56]= 125

-Rob

Daniel Lichtblau

unread,
Nov 4, 2009, 1:43:08 AM11/4/09
to
Erik Max Francis wrote:
> I was experimenting with vaguely OOP-like wrappers around data similar
> to how Twitter.m does things:
>
> http://blog.wolfram.com/2009/04/30/twittering-with-mathematica/
>
> I was experimenting with "mutable objects" and I'm running into a
> scoping problem of some type that I don't understand. Here's some
> sample code creating a wrapper around an "account balance":
>
> In[1]:= {NAME, BALANCE} = Range[2]
>
> Out[1]= {1, 2}
>
> In[2]:= create[name_, balance_] := Account[name, balance]
>
> In[3]:= getName[account_Account] := account[[NAME]]
>
> In[4]:= getBalance[account_Account] := account[[BALANCE]]
>
> In[5]:= deposit[account_Account, amount_] :=
> account[[BALANCE]] += amount
>
> The deposit function doesn't work, though:
>
> In[6]:= a = create["test", 100]
>
> Out[6]= Account["test", 100]
>
> In[7]:= getBalance[a]
>
> Out[7]= 100
>
> In[8]:= deposit[a, 25]
>
> During evaluation of In[8]:= Set::setps: Account[test,100] in the part
> assignment is not a symbol. >>
>
> Out[8]= 125
>
> In[9]:= getBalance[a]
>
> Out[9]= 100
>
> But here's what I'm confused; if I do what getBalance does manually, it
> works fine:
>
> In[10]:= a[[BALANCE]] += 25
>
> Out[10]= 125
>
> In[11]:= a
>
> Out[11]= Account["test", 125]
>
> The Set::setps error is about Blocks so I suppose I'm running into some
> scoping issue but I don't follow what it is. What am I missing here?
> What's the right way to write getBalance?

You are using the formal argument "account" in the manner of a
call-by-reference" parameter. One can emulate that in Mathematica by
making it a held argument.

SetAttributes[deposit, HoldFirst]

Now you have a new problem, which is that the head will not be
recognized as Account (it will be Symbol, because all that is seen
before the rule fires is "a", sans quotes). No big deal, we'll evaluate
it and check the head.

deposit[account_, amount_] /;
Head[Evaluate[account]] === Account :=
account[[BALANCE]] += amount

That should work for the purpose you have in mind.


> Do Mathematica users find Twitter.m's approach to wrapping around
> objects palatable in the first place? (My eventual application is for a
> wrapper around tensor objects where multiple things have to be carted
> around and the normal display of such objects would be far too ungainly
> to be acceptable anyway.)
>
> Thanks.

I'll reread that blog, but I suspect it's one of those things that is
incomprehensible to those past a certain age.


Daniel Lichtblau
Wolfram Research

Erik Max Francis

unread,
Nov 10, 2009, 6:03:22 AM11/10/09
to

This is true but here's what's confusing me, and it's obviously a
misunderstanding of how Mathematica treats objects and what object
mutation means in Mathematica (if indeed that's really allowed in any
meaningful sense in Mathematica). If we continue where I left off, I
can't mutate the value of `a` as above, but I _can_ test it against the
global value of `a` with the `===` operator and verify that it's the
same object (repeating just enough of the definitions to get the idea):

In[67]:= {NAME, BALANCE} = Range[2];


create[name_, balance_] := Account[name, balance]

getName[account_Account] := account[[NAME]]

getBalance[account_Account] := account[[BALANCE]]

deposit[account_Account, amount_] := account[[BALANCE]] += amount

In[90]:= a = create["globals", 100]

Out[90]= Account["globals", 100]

In[91]:= isTheSameAsTheGlobalAQ[account_Account] := account === a

In[92]:= x = a

Out[92]= Account["globals", 100]

In[95]:= isTheSameAsTheGlobalAQ[x]

Out[95]= True

So I can verify it's the same object, but I can't mutate it -- that
generates the aforementioned Set::setps error. But doing it manually
with a itself works fine. How come? What is it about Mathematica and
values that I'm not getting? It's obviously something fundamental, but
I haven't encountered it in a book before.

Clearly Mathematica has a more functional approach, so what I'm doing is
naughty anyway. Do I take it that the idea of mutating an object in a
local context and expecting it to have wider-ranging results (as, say,
in other languages like Python or Java or Lisp) is just simply mistaken?

--
Erik Max Francis && m...@alcyone.com && http://www.alcyone.com/max/
San Jose, CA, USA && 37 18 N 121 57 W && AIM/Y!M/Skype erikmaxfrancis

Can I walk with you / 'Till the day that my heart stops beating
-- India Arie

Erik Max Francis

unread,
Nov 10, 2009, 6:10:42 AM11/10/09
to

Okay. I see that since Mathematica is far more functional a language
than other languages that I've used and so what I was trying to do there
was quite alien -- sufficiently so that I really shouldn't try. (In
this particular case, I was just taking a rather typical OOP example and
playing with it in Python, it wasn't central to my question below ...)

>> Do Mathematica users find Twitter.m's approach to wrapping around
>> objects palatable in the first place? (My eventual application is for a
>> wrapper around tensor objects where multiple things have to be carted
>> around and the normal display of such objects would be far too ungainly
>> to be acceptable anyway.)
>

> I'll reread that blog, but I suspect it's one of those things that is
> incomprehensible to those past a certain age.

Well, when I boil down when I'm talking about here it's not so much OOP
per se but rather opaque types, giving the ability to pack lots of stuff
in an object, hide it with a nice face by using Format[..., Standard] :=
...., and then define a bunch of functions that take it at the first
argument and manipulate it.

The context I mentioned above is a tensor library where you don't really
want to see the components of the tensor unless you specifically ask for
it -- a 4-dimensional Riemann tensor, after all, has 4^4 components --
but instead you just want to specify a set of coordinates, a metric
using those coordinates, and then go to town creating and manipulating
those tensors, then crank out specific calculations based on them.

Does that make more sense as being something reasonably Mathematica-ish,
using that approach for type opaqueness?

DrMajorBob

unread,
Nov 11, 2009, 4:27:21 AM11/11/09
to
> So I can verify it's the same object, but I can't mutate it

Help says: "lhs === rhs yields True if the expression lhs is identical to
rhs, and yields False otherwise." That means the two EXPRESSIONS are
identical, not that the two are the same OBJECT.

For instance:

First I set x and y to the same value:

x = f[5]
y = x

f[5]

f[5]

They test identical (SameQ):

x === y

True

Now I change x:

x = 2

2

But y is unchanged, since x and y are different objects:

y

f[5]

Now I define a function:

Clear[f]
SetAttributes[f, HoldFirst]
f[x_] := (x++; Null)

Next I pass x to f, and show the new value of x:

f[x]
x

3

Now look at the value of y, again:

y

Increment::rvalue: 5 is not a variable with a value, so its value cannot
be changed. >>

Do the same again:

y

Increment::rvalue: 5 is not a variable with a value, so its value cannot
be changed. >>

We get exactly the same error message, since y is still f[5].

Test that:

y===f[5]

Increment::rvalue: 5 is not a variable with a value, so its value cannot
be changed. >>
Increment::rvalue: 5 is not a variable with a value, so its value cannot
be changed. >>
True

Yes, y is still f[5], even though the error appears again (once for the
left hand side, again for the right).

Now change f, and show the value of y again.

f = Sin@# &;
y

Sin[5]

Bobby

On Tue, 10 Nov 2009 05:02:59 -0600, Erik Max Francis <m...@alcyone.com>
wrote:

> This is true but here's what's confusing me, and it's obviously a
> misunderstanding of how Mathematica treats objects and what object
> mutation means in Mathematica (if indeed that's really allowed in any
> meaningful sense in Mathematica). If we continue where I left off, I
> can't mutate the value of `a` as above, but I _can_ test it against the
> global value of `a` with the `===` operator and verify that it's the
> same object (repeating just enough of the definitions to get the idea):
>

> In[67]:= {NAME, BALANCE} = Range[2];


> create[name_, balance_] := Account[name, balance]

> getName[account_Account] := account[[NAME]]

> getBalance[account_Account] := account[[BALANCE]]

> deposit[account_Account, amount_] := account[[BALANCE]] += amount
>
> In[90]:= a = create["globals", 100]
>
> Out[90]= Account["globals", 100]
>
> In[91]:= isTheSameAsTheGlobalAQ[account_Account] := account === a
>
> In[92]:= x = a
>
> Out[92]= Account["globals", 100]
>
> In[95]:= isTheSameAsTheGlobalAQ[x]
>
> Out[95]= True
>
> So I can verify it's the same object, but I can't mutate it -- that
> generates the aforementioned Set::setps error. But doing it manually
> with a itself works fine. How come? What is it about Mathematica and
> values that I'm not getting? It's obviously something fundamental, but
> I haven't encountered it in a book before.
>
> Clearly Mathematica has a more functional approach, so what I'm doing is
> naughty anyway. Do I take it that the idea of mutating an object in a
> local context and expecting it to have wider-ranging results (as, say,
> in other languages like Python or Java or Lisp) is just simply mistaken?
>


--
DrMaj...@yahoo.com

Leonid Shifrin

unread,
Nov 11, 2009, 4:30:07 AM11/11/09
to
Hi Erik,

If you really want to do OOP in Mathematica, you may also check out the
package classes.m by Roman Maeder (and of course his books "The Mathematica
Programmer I,II" and "Computer Science with Mathematica"), and the book of
John Gray "Mastering Mathematica" for the detailed discussion of how one can
implement and use OOP in Mathematica.

If all you need is to create and use an ADT (no inhertiance, no
polymorphism),
you can do it relatively easily with UpValues. Here, for example, I define
a type "Pair", with operators <new> and <delete>, getters and setters.

Unprotect[pair, setFirst, getFirst, setSecond, getSecond, new, delete];
ClearAll[pair, setFirst, getFirst, setSecond, getSecond, new, delete];
Module[{first, second},
first[_] := {};
second[_] := {};
pair /: new[pair[]] := pair[Unique[]];
pair /: pair[tag_].delete[] := (first[tag] =.; second[tag] =.);
pair /: pair[tag_].setFirst[value_] := first[tag] = value;
pair /: pair[tag_].getFirst[] := first[tag];
pair /: pair[tag_].setSecond[value_] := second[tag] = value;
pair /: pair[tag_].getSecond[] := second[tag];
Format[pair[x_Symbol]] := "pair[" <> ToString[Hash[x]] <> "]";
];
Protect[pair, setFirst, getFirst, setSecond, getSecond, new, delete];

I beleive that implementing ADT in the above manner lets one avoid the
pass-by-reference problems you mentioned. Note that while definitions are
attached to <pair>, they redefine the action of Dot (.) on their patterns -
this is why we can use the usual OO notation.

Here is how we could use it :

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

{10, 20}

Creating a list of new pair objects :

pairs = Table[new[pair[]], {10}]

{"pair[430427975]", "pair[430428059]", "pair[430428060]", "pair[430428057]",
\
"pair[430428058]", "pair[430428063]", "pair[430428064]", "pair[430428061]",
\
"pair[430428062]", "pair[430428051]"}

Setting the fields :

Module[{i},
For[i = 1, i <= 10, i++,
pairs[[i]].setFirst[10*i];
pairs[[i]].setSecond[20*i];]]

Checking the fields :

#.getFirst[] & /@ pairs

{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}

#.getSecond[] & /@ pairs

{20, 40, 60, 80, 100, 120, 140, 160, 180, 200}


Here is how you could type-check the code that uses your data type: the
following function, for example, takes a sequence of <pair>-s, sums up
corresponding fields and produces a new pair with fields equal to these
sums. The pattern __pair constitutes a type check.

Clear[sumFields];
sumFields[args__pair] :=
With[{result = new[pair[]]},
With[{sums =
Total /@ Transpose[{#.getFirst[], #.getSecond[]} & /@ {args}]},
result.setFirst[sums[[1]]];
result.setSecond[sums[[2]]];
result]];

With this ADT construction, you will have to remember to explicilty use
<delete> on objects you no longer need, to avoid memory leaks, because the
internal variables <first>, <second> inside Module will not be automatically
garbage-collected by Mathematica (since they are referenced by global
symbols - <pair> in this case). Another thing: since Unique[] is used to
give each instance of our "object" an identity, it is only unique within a
single Mathematica session.

#.delete[] & /@pairs;

One good thing about UpValues is that the names <new>, <delete>,
<setFirst>,<getFirst>, etc are available should you wish to use them in
defining more than one ADT at the same time - the definitions are really
attached to the container (type) name - <pair> in this case.

Hope this helps.

Regards,
Leonid

On Tue, Nov 10, 2009 at 2:03 PM, Erik Max Francis <m...@alcyone.com> wrote:

> Okay. I see that since Mathematica is far more functional a language
> than other languages that I've used and so what I was trying to do there
> was quite alien -- sufficiently so that I really shouldn't try. (In
> this particular case, I was just taking a rather typical OOP example and
> playing with it in Python, it wasn't central to my question below ...)
>

> >> Do Mathematica users find Twitter.m's approach to wrapping around
> >> objects palatable in the first place? (My eventual application is for a
> >> wrapper around tensor objects where multiple things have to be carted
> >> around and the normal display of such objects would be far too ungainly
> >> to be acceptable anyway.)
> >

> > I'll reread that blog, but I suspect it's one of those things that is
> > incomprehensible to those past a certain age.
>

Daniel Lichtblau

unread,
Nov 11, 2009, 4:31:05 AM11/11/09
to
Erik Max Francis wrote:

> Daniel Lichtblau wrote:
>> Erik Max Francis wrote:
>>> I was experimenting with vaguely OOP-like wrappers around data similar
>>> to how Twitter.m does things:
>>>
>>> http://blog.wolfram.com/2009/04/30/twittering-with-mathematica/
>>>
>>> I was experimenting with "mutable objects" and I'm running into a
>>> scoping problem of some type that I don't understand. Here's some
>>> sample code creating a wrapper around an "account balance":
>>> [...hope I'm not snipping too much...]

>
> Okay. I see that since Mathematica is far more functional a language
> than other languages that I've used and so what I was trying to do there
> was quite alien -- sufficiently so that I really shouldn't try. (In
> this particular case, I was just taking a rather typical OOP example and
> playing with it in Python, it wasn't central to my question below ...)

I'm not sure why this seems alien. People use such constructs in
Mathematica quite often.


> [...]


> Well, when I boil down when I'm talking about here it's not so much OOP
> per se but rather opaque types, giving the ability to pack lots of stuff
> in an object, hide it with a nice face by using Format[..., Standard] :=
> ...., and then define a bunch of functions that take it at the first
> argument and manipulate it.
>
> The context I mentioned above is a tensor library where you don't really
> want to see the components of the tensor unless you specifically ask for
> it -- a 4-dimensional Riemann tensor, after all, has 4^4 components --
> but instead you just want to specify a set of coordinates, a metric
> using those coordinates, and then go to town creating and manipulating
> those tensors, then crank out specific calculations based on them.
>
> Does that make more sense as being something reasonably Mathematica-ish,
> using that approach for type opaqueness?

Yes. I see no reason not to do that in Mathematica. I won't say it is
common, but people do use Mathematica in the way you describe. Often it
is for similar reasons: hide the mess, expose the functionality (that
may not be an entirely accurate paraphrasing of what you describe, but I
think it is not too far from the mark).

Daniel Lichtblau
Wolfram Research

Erik Max Francis

unread,
Nov 12, 2009, 6:07:10 AM11/12/09
to
DrMajorBob wrote:
>> So I can verify it's the same object, but I can't mutate it
>
> Help says: "lhs === rhs yields True if the expression lhs is identical to
> rhs, and yields False otherwise." That means the two EXPRESSIONS are
> identical, not that the two are the same OBJECT.

Well, that sure explains that! Obviously pure misunderstanding on my
part, I read something about `===` somewhere along the line that was
mistaken and just carried it forward. Should have double-checked the
(excellent) documentation itself first.

Thanks!

--
Erik Max Francis && m...@alcyone.com && http://www.alcyone.com/max/
San Jose, CA, USA && 37 18 N 121 57 W && AIM/Y!M/Skype erikmaxfrancis

It is dark in my favorite dream.
-- Aaliyah

Erik Max Francis

unread,
Nov 13, 2009, 5:56:55 AM11/13/09
to

Thanks again to everyone for their thoughts.

--
Erik Max Francis && m...@alcyone.com && http://www.alcyone.com/max/
San Jose, CA, USA && 37 18 N 121 57 W && AIM/Y!M/Skype erikmaxfrancis

They have rights who dare defend them.
-- Roger Baldwin

Reply all
Reply to author
Forward
0 new messages