0
$\begingroup$

In the (as always) insigthful answer by Leonard Shifrin to this MMA SE post

https://mathematica.stackexchange.com/questions/52393/how-to-make-use-of-associations

the makePersonInfoMutable struct implementation deals with the case where there are two struct members, corresponding to the arguments fname and lname. I wanted to extend the code to deal with the case of a variable number of struct members, with both the member names (fnames) and member data (lnames) passed as arguments. (I was actually thinking of the case where the second argument would be a list of real numbers, but the exact nature of the second argument is not relevant). The code below, mostly copied from Mr. Shifrin's answer, shows the issue:

ClearAll[makePersonInfoMutable];
makePersonInfoMutable[fname_, lname_, fnames_List, lnames_List] := 
  Module[{fn = fname, ln = lname, fns = fnames, lns = lnames, 
    instance},
   SetAttributes[instance, HoldAll];
   instance[getFirstName[]] := fn;
   instance[getLName[fns[1]][]] := lns[[1]];
   instance@setFirstName[name_] := fn = name;
   instance@getLastName[] := ln;
   instance@setLastName[name_] := ln = name;
   instance@getFullName[] := fn <> " " <> ln;
   instance];
pinfo = makePersonInfoMutable["Leonard", 
   "Shifrin", {"Joe", "Mary"}, {"Smith", "Jackson"}];
pinfo@getFirstName[]
(*  Leonard  *)
pinfo[getLName["Joe"][]]
(*  instance$1356[getName["Joe"][]]  *)

I only added two lines the original code. The first one, inside the Module, is

instance[getName[fns[1]][]] := lns[[1]];

(I used 1 as the index, but more generally this could be i within a Do command, or something similar.)

The second added line of code,

pinfo[getLName["Joe"][]]

and its output show I did not get what I expected, which would be

(* Smith  *)

For comparison, I placed this second added line just below the similar command in the original post, which of course works fine.

I am sure there is a way to do this, but I cannot figure it out. Any help will be greatly appreciated.s

$\endgroup$
2
$\begingroup$

Explanation

The temporary symbol instance$... created by makePersonInfoMutable is HoldAll,

pinfo = makePersonInfoMutable["Leonard", "Shifrin", {"Joe", "Mary"}, {"Smith", "Jackson"}];

Attributes[instance$298]
(* {HoldAll, Temporary} *)

Because of this attribute, the additional line of code

instance[getLName[fns[[1]]][]] := lns[[1]];

creates a definition with the subexpression fns$...[[1]],

Cases[DownValues[instance$298], 
    def : HoldPattern[Verbatim[HoldPattern][_[getLName[_][]]] :> _] :> def
]
(* {HoldPattern[instance$298[getLName[fns$298[[1]]][]]] :> lns$298[[1]]} *)

instead of creating it with "Joe" (where "Joe" is fns$298[[1]]). This means that no definition exists for pinfo[getLName["Joe"][]], and this is why it is returned unevaluated. Note, on the contrary, that this evaluates:

pinfo[getLName[fns$298[[1]]][]]
(* "Smith" *)

since the held argument of pinfo matches the definition.

Solution

A possible solution is to make the subexpression evaluate before giving it to the definition. This can be done by changing the additional line of code to

With[{fs = fns[[1]]}, instance[getLName[fs][]] := lns[[1]]];

which then gives the expected output:

pinfo = makePersonInfoMutable["Leonard", "Shifrin", {"Joe", "Mary"}, {"Smith", "Jackson"}];

pinfo[getLName["Joe"][]]
(* "Smith" *)
$\endgroup$
  • $\begingroup$ Thanks. Your suggestion works. Beginner's questions: 1) Why does the problem remain if the SetAttributes line is removed, 2) Why doesn't instance[getLName[Evaluate@fns[1]][]] := lns[[1]]; fix the problem?, 3) In pinfo[getLName[fns$298[[1]]][]] I guess the execution is already outside Module, since pinfo is already defined. If that is the case, how is fns$XXX available?, 4) If you wrap a Do around the With[{...}] statement, even leaving 1 in place instead of i the problem reappears. Why? $\endgroup$ – Soldalma Oct 22 '16 at 0:31
  • $\begingroup$ The Do problem I mentioned in the previous comment can be solved by Do[With[{fs = fns[[i]], j = i}, instance[getLName[fs][]] := lns[[j]]], {i, n}] $\endgroup$ – Soldalma Oct 22 '16 at 0:56
  • 1
    $\begingroup$ 1) It works fine, but you need to change fns[1] to fns[[1]]. You have a typo in the code. 2) The Evaluate would be too deep. You would need instead (with the SetAttributes kept, otherwise it is not useful): instance[Evaluate@getLName[fns[[1]]][]] := lns[[1]]. (Note that the type is corrected here as well.) 3) The variable fns$XXX is created as soon as the Module is called, so it can be accessed outside its scope. $\endgroup$ – user31159 Oct 22 '16 at 1:11
  • $\begingroup$ Indeed it works without HoldAll and the typo corrected. That being the case, should I get rid of the SetAttributes line? $\endgroup$ – Soldalma Oct 22 '16 at 3:26
  • 1
    $\begingroup$ The HoldAll attribute may not be useful in this short version of the code, so it can probably be removed. $\endgroup$ – user31159 Oct 22 '16 at 11:50

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.