1
$\begingroup$

What is the best way to achieve

{v,u} = f[]; (* Returns value and usage description *)
v::usage = u;

from inside f, i.e.,

v = f[]; (* Sets v::usage *)

such that ?v yields u?


From answers and comments by rcollyer and Mr.Wizard, it seems my requested feature goes somewhat against Mathematica's programming language principles. For completeness, here's what caused my question:

f loads a dataset and returns an association containing various tensors, index structures and other information. Available keys depend on the dataset. I wanted to achieve two goals:

a) An easy way to obtain a help text for the returned data structure v in v=f[...];. The standard help request ?v seemed a good choice, hence my question for setting v::usage. I wanted to avoid the user having to type {v,msg}=f[...]; v::usage=msg;. I also wanted to avoid having to remember non-standard usage like v["usage"] or similar.

b) Avoiding Mathematica freezing for minutes when accidentally typing ?v, as it does currently due to v being large.

$\endgroup$
  • $\begingroup$ To be clear, you want f to set the usage message for v in addition to returning a value for v, correct? $\endgroup$ – rcollyer Jul 23 '15 at 15:23
  • $\begingroup$ This seems related: (314) $\endgroup$ – Mr.Wizard Jul 23 '15 at 15:25
  • 1
    $\begingroup$ @rcollyer Yes. I want the user to be able to use simple assignments while at the same time being able to get some help on the assigned variable's new functionality after assignment. $\endgroup$ – mrupp Jul 23 '15 at 15:27
  • $\begingroup$ @Mr.Wizard f returns something complicated; it gets neither v nor message as parameters. The usage message is generated dynamically during the computation done by f. It describes the return value of f. $\endgroup$ – mrupp Jul 23 '15 at 15:30
  • $\begingroup$ @Mr.Wizard, doesn't he just mean that f provides a value and a message, and given any Set operation v=f[something] he wants v to aquire that value and the message as a ::usage? But like you are implying, if you just pass a Symbol as a third argument to f you can do that easily. $\endgroup$ – Marius Ladegård Meyer Jul 23 '15 at 16:01
4
$\begingroup$

Caveat

Instead of answering your question I am going to try to convince you not to do what you are describing. While it is true that Mathematica is highly configurable in many areas there are nevertheless paradigms and principles that are best heeded.

You did not give an example of where or why you feel a usage message is needed so it is difficult to offer alternative approaches. One should not need a usage message on a simple assignment, e.g. a = 3.14. If your f is actually generating a function there are other ways you could approach documentation.

The general functional style of Mathematica coding goes against what you request. The expression returned by f[] is independent from v in v = f[] which is why there is no direct method for what you want. rcollyer provided a partial work-around for this but had to use SetDelayed to make it even minimally robust. SetDelayed already has a meaning however and use in this way would be extremely confusing. Further I argue that the value should be independent from the symbol to which it is assigned. One should be able to pass around and process a value (expression) without worrying about a Symbol and usage message that is somehow bound to it.

Even if Set is made to work, and that can be done via the box-level processing of $PreRead, it would not easily be made robust. I am not against such manipulations in general but I am not going to help you shoot your users in the foot with this so you'll have to implement that on your own if you choose this path.

If you would explain why you feel the need for usage messages, what f[] is actually returning, and who your users are, perhaps I can recommend a different methodology that will still achieve your strategic goals.


By the way, and hoping to be a bit more directly helpful to you, you may wish to read:


Reply

Thank you for explaining your situation. It really does help frame the question, and I can now see the motivation for your request. Nevertheless I think it is better to use a custom command e.g. rcollyer's SetVariable or the syntactic-sugar equivalent, or make the symbol a parameter as Marius described. These are nearly as convenient and they do not require deviating from standard practice. They would also inform your users that a nonstandard assignment may be taking place rather than hiding it behind trickery which I think will ultimately serve them.

For the sake of providing an answer: a different approach you may consider, not without its own downside, is to modify the System function Information to handle Association expressions differently.

First you will need to load step from How do I evaluate only one step of an expression?

Then run this once in a session (or put it in init.m, along with step):

Unprotect[Information];

PrependTo[DownValues @ Information,
  HoldPattern[Information[s_String?System`Dump`validStringExpressionQ, ___]] :>
   With[{val = step @@ ToHeldExpression[s]},
    CellPrint @ Cell[Style[ReleaseHold[val]["usage"], "MSG"], "Print", "PrintUsage"];
     /; MatchQ[val, _ @ _Association] && KeyExistsQ[ReleaseHold @ val, "usage"]
   ]
];

Protect[Information];

Now when you assign an Association with a "usage" key:

largeAssoc = AssociationThread @@ RandomInteger[9999, {2, 500}];

AssociateTo[largeAssoc,
  "usage" -> "this is a sample Association with a usage message and quite a few keys"];

You will find that ?name automatically prints this usage message:

?largeAssoc

this is a sample Association with a usage message and quite a few keys

If this idea is pleasing to you, you can customize what is printed with additional information, perhaps a Short form of the association, etc.

The downside I mentioned is twofold:

  1. We are tinkering with System functionality which risks breaking something in an unanticipated way. We also still produce behavior that is not "normal" which may confuse users who come to depend on it.

  2. To check that an Association is assigned a given Symbol, e.g. largeAssoc, some evaluation must take place. I used my step function to prevent full evaluation, which means that you can use ?hold with a definition of hold := Print["Fail!"] without the message printing, but it is at least conceivable that we will introduce some kind of evaluation leak with this definition.

| improve this answer | |
$\endgroup$
  • $\begingroup$ I added an explanation on the origin of my request to my question. $\endgroup$ – mrupp Jul 24 '15 at 8:43
  • $\begingroup$ @mrupp please see the reply in my answer $\endgroup$ – Mr.Wizard Jul 24 '15 at 10:42
  • $\begingroup$ Truthfully, the more I think about this, I think a custom head with a summary box might be the way to go. It requires, no tampering with the system, and it could be set up to allow default arguments to be passed back out, i.e. it uses Lookup. $\endgroup$ – rcollyer Jul 24 '15 at 17:21
  • $\begingroup$ @rcollyer I did consider that but I rejected it at the thought of overloading every associations function to work with it. I don't think a simple UpValue will work as _Association does not appear at level one in all methods. $\endgroup$ – Mr.Wizard Jul 24 '15 at 21:10
  • $\begingroup$ Actually, my thought is a custom head, e.g. myAssoc[Association[...]], where the DownValues for myAssoc feed directly into the inner association, unless otherwise desired. Then, you can easily create a summary box for it, and do not have to override a thing. $\endgroup$ – rcollyer Jul 24 '15 at 21:14
4
$\begingroup$

As follows, are three different methods to answer your question. They each have there merits and flaws. The first is a literal attempt to do as you ask, but it requires modifying the behavior of a System` function to do it. The second method deviates from what you ask by using a different binary operator for the assignment, but still links a message to the symbol. The final method does not attach a message, but provides the information through the new summary box forms. Additionally, it changes the return type to enable this.

Method 1

This can be done. It requires fiddling with some machinery that might best be left alone, but it is straightforward. You need to set an UpValue on f when it is found on the right hand side of SetDelayed, e.g.

Clear[f];
f /: SetDelayed[v_Symbol, p_f] :=  (v::msg = "Blah, Blah, Blah!"; v = p)
f[] := "bob"
f[x_] := x^2

where all the details, like the message, what f returns, etc. need to be adjusted to suite. But,

In[100]:= ClearAll[v]
Messages@v
v := f[q]
Messages@v

Out[101]= {}

Out[102]= q^2

Out[103]= {HoldPattern[v::msg] :> "Blah, Blah, Blah!"}

Note, I used SetDelayed, not Set here, because SetDelayed is HoldRest and Set will evaluate f[] before the UpValue code can kick in.


Method 2

While the code, above, is almost literally what you ask for, it will very likely just confuse your users. Have them use this construct, instead:

Clear[f, SetVariable];
SetAttributes[SetVariable, HoldRest];
SetVariable[v_Symbol, p_f] := (v::msg = "Blah, Blah, Blah!"; v = p)
f[] := "bob"
f[x_] := x^2

The usage is a bit clunkier, e.g.

SetVariable[v, f[x]]
(* x^2 *)

v::msg
(*"Blah, Blah, Blah!"*)

But, it is less likely to confuse your users, long term. If you are absolutely bent on using an infix operator for this, you can find one that is unused here. I would consider using DoubleLeftArrow (esc-space-<-=-esc, or \[DoubleLeftArrow]) or DoubleRightArrow (esc-space-=->-esc, or \[DoubleRightArrow] to do it. Setting this up is simple

Clear[DoubleRightArrow];
SetAttributes[DoubleRightArrow, HoldFirst];
DoubleRightArrow[p_f, v_Symbol] := 
  (ClearAll[v]; v::msg = "Blah, Blah, Blah!"; v = p)

with usage

f[x] \[DoubleRightArrow] v
(* x^2 *)

Note, the use of ClearAll[v] which the original code and SetVariable both should have to prevent circular references.


Method 3

An alternative is to create a custom head that uses a custom head that forwards the information to an inner association, then you can set up a formatting rule specifically for it and do not have to override any system functions. First, some boilerplate code to forward the keys to the inner association:

ClearAll[assocWrapper];
assocWrapper[a_Association]["Properties"] := Sort[Keys[a]~Join~{"Properties"}]
assocWrapper[a_Association][k_] := Lookup[a, k, {}]

Two things to note here. First, I would recommend added "Properties" as shown, since it is a common accessor. Other default accessors can be added, as well, but I would keep them to a minimum as they override the keys that are present in the association. Second, I used Lookup, instead of direct access to the association as you could add a "default" key to the association and use that instead of any empty list.

For formatting, I would use the new summary boxes, described here. For example,

assocWrapper /: MakeBoxes[as:assocWrapper[a_Association], fmt_] :=
  BoxForm`ArrangeSummaryBox[assocWrapper, as,
   (* a better icon is needed here *)
   IconData["MoonPhase", Length[a]/10],
   {
     BoxForm`SummaryItem[{"Keys: ", Length@a}]
   },
   (* displayed when + is clicked *)
   {
     BoxForm`SummaryItem[{"extra info", "available upon request"}]
   },
   fmt
  ]

Which gives

enter image description here

Lastly, you need to protect assocWrapper

Protect[assocWrapper];

or this will happen

a = assocWrapper[<|"key" -> "info"|>]
a["key"] = "new stuff"
b = assocWrapper[<|"key" -> "other info"|>]
b["key"]
(* "new stuff" *)

This highlights this methods disadvantage: the associations are immutable.

| improve this answer | |
$\endgroup$
  • $\begingroup$ Making SetDelayed into something that is not delayed is quite pathological. I cannot vote for this even if its what the OP wants. :-/ $\endgroup$ – Mr.Wizard Jul 23 '15 at 16:10
  • $\begingroup$ @Mr.Wizard I did put a warning in ... and, no matter how bad the idea, it sometimes can be done. $\endgroup$ – rcollyer Jul 23 '15 at 16:32
  • $\begingroup$ I do see your point, but the whole purpose of this construct is misguided; it is supposed to make it easier for (presumably inexperienced) users, yet this will only make it harder. Surely they will not know to use := and if they are told to use it here it will only cause lasting confusion. $\endgroup$ – Mr.Wizard Jul 23 '15 at 16:46
  • $\begingroup$ This would be a solution if it could be made to work with Set as well. If it helps, it would be possible to obtain the message from the value (the function returns an association, I could add a value like v["usage"]). The point would be not having to remember v["usage"] but to be able to use the standard ?v help query. Currently, if one accidentally types ?v, Mathematica is unresponsive for minutes while trying to print a large amount of data. $\endgroup$ – mrupp Jul 23 '15 at 18:51
  • 1
    $\begingroup$ @Mr.Wizard since this is to be for less advanced users, I added a different recommendation. $\endgroup$ – rcollyer Jul 23 '15 at 19:23
1
$\begingroup$

rcollyer provides the literal answer to your question in his answer. Another way to do it is simply to give f the HoldFirst attribute and make it take an additional first argument, namely the symbol you want to link the message to, i.e. v. Then you can do v::usage="bla" and v=value inside the definition of f, and just call it like

f[v,...]

instead of

v=f[...]

As Mr. Wizard's comment states, making v the first argument and Holding it allows for reassignment. Example:

SetAttributes[f, HoldFirst];

f[v_Symbol, x_] :=
  Module[{val, mess},
    val = x^2;
    mess = "Message for argument " <> ToString[x];
    v::usage = mess;
    v = val;
  ]

Doing f[symb, 3] now gives

??symb

Message for argument 3

symb = 9
| improve this answer | |
$\endgroup$
  • 1
    $\begingroup$ It would be better to make the symbol the first parameter and add HoldFirst to the function to allow reassignment. $\endgroup$ – Mr.Wizard Jul 23 '15 at 16:30
  • $\begingroup$ Yes, but I wanted to allow for "standard" syntax like v = f[...] or v := f[...] without having to pass the left-hand-side as an argument. $\endgroup$ – mrupp Jul 23 '15 at 18:35
  • $\begingroup$ Like @Mr.Wizard writes in his answer, symbols receiving help messages after Seting them to the result of some function is hardly "standard", neither syntactically nor methodologically. $\endgroup$ – Marius Ladegård Meyer Jul 23 '15 at 20:50
  • $\begingroup$ I would like to up-vote this but I was waiting for you to add HoldFirst as explained. Do you disagree with that recommendation? $\endgroup$ – Mr.Wizard Jul 23 '15 at 22:00
  • $\begingroup$ @Mr.Wizard I agree, just didn't have time before sleep ;) Added. $\endgroup$ – Marius Ladegård Meyer Jul 24 '15 at 4:18

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.