Pass by reference for an option argument

14

8

Pass by reference can be faked by using HoldAll or something similar in the definition of a "Function". But can pass by reference be faked for symbols passed as named arguments (Options)?

Let me demonstrate:

This works (when you click on the checkboxes the list changes):

ClearAll@PassByArg;
Attributes[PassByArg] = {HoldFirst};
PassByArg[list_] := CheckboxBar[Dynamic[list], Range@Length@list];
aList = {1, 2, 3, 4, 5};
PassByArg[aList]
Dynamic[Sort@aList]

How can I pass the reference to list as an option?

Here is one of my failed attempts (it returns et::setraw: Cannot assign to raw object {1,2,3,4,5} when a checkbox is clicked.

ClearAll@PassByOption;
Options@PassByOption = {"List" -> None};
PassByOption[opts : OptionsPattern[]] := 
  With[{i = ReleaseHold@OptionValue@"List"}, 
   CheckboxBar[Dynamic[i], Range@Length@i]];
aList = {1, 2, 3, 4, 5};
PassByOption["List" -> Hold@aList]
Dynamic[aList]

I always evaluate either too much (to {1,2,3,4,5}) or too little...

Perhaps some motivation is in order: I'm developing a GUI using Dynamic. Now I would like to control a list of displayed functions in some plot. If I implement this as a reference passed with a named argument I practically don't have to change any other code:)

Ajasja

Posted 2013-04-22T20:51:22.220

Reputation: 13 114

Related: How to modify function argument?

– Mr.Wizard – 2014-04-14T20:24:43.487

Answers

13

This is a case for injector pattern:

PassByOption[opts : OptionsPattern[]] :=
  OptionValue["List"] /. Hold[l_] :> CheckboxBar[Dynamic[l], Range@Length@l]

You can check that this works with this change.

As an alternative, you can by-pass the use of Hold and clever tricks like the above, by using the longer form of OptionValue:

ClearAll@PassByOptionAlt;
Options@PassByOptionAlt = {"List" -> None};
PassByOptionAlt[opts : OptionsPattern[]] :=
  OptionValue[
     PassByOptionAlt, 
     {opts}, 
     "List", 
     Function[l, CheckboxBar[Dynamic[l], Range@Length@l], HoldAll]
  ]

This will work too, but, as mentioned by Szabolcs in comments, you will need to use RuleDelayed (:>) to pass the symbol:

PassByOptionAlt["List" :> aList]

Leonid Shifrin

Posted 2013-04-22T20:51:22.220

Reputation: 108 027

@Ajasja Always glad to help :). This question, by the way, should have been asked here long ago, since this is a common situation. – Leonid Shifrin – 2013-04-22T22:26:18.227

1

This seems to do the job :

ClearAll@PassByOption;
Attributes[PassByOption] = {HoldFirst};
PassByOption[opts___] := 
  With[{list = Hold["List"] /. Unevaluated[{opts}], 
        length = Range[Length["List" /. {opts}]]
        }, 
      CheckboxBar[Dynamic[list], length] /. Hold[x_] -> x
      ];
aList = {1, 2, 3, 4, 5};
PassByOption["List" -> aList]
Dynamic[Sort@aList]

Unevaluated is used to prevent aList (from the option "List" -> aList) to be evaluated.

Warning : as Attributes[PassByOption] = {HoldFirst} the option "List" -> aList must be the first option when you call PassByOption

andre314

Posted 2013-04-22T20:51:22.220

Reputation: 15 746

1

Just for fun here is a way to pass the reference as a string (I got this idea from @Leonids answer, but I'm not sure it brings anything useful over the injector pattern):

Here is the code:

ClearAll@PassByOptionStr;
Options@PassByOptionStr={"List"->None};
PassByOptionStr[opts:OptionsPattern[]]:=
    ToExpression[OptionValue["List"],InputForm, 
        Function[l, CheckboxBar[Dynamic[l], Range@Length@l]
    , HoldAll]]

Check that it works:

aList = {1, 2, 3, 4, 5};
PassByOptionStr["List" -> SymbolName@Unevaluated@aList]
Dynamic@aList

This is not directly related to the original question, but the next step is to test if the passed reference is valid. A convenient way to do this is using Leonids valueQ function. Apart from not leaking evaluations valueQ has some nice adavantages over the built in ValueQ. Compare for example:

{valueQ@{1 + 1}, ValueQ@{1 + 1}}

(* ==> {False, True} *)

Ajasja

Posted 2013-04-22T20:51:22.220

Reputation: 13 114