7
$\begingroup$

Consider the following example: you have a function framed producing a cell with a button that, when pushed, calls framed again. How to supply options to framed such that whenever the button is pushed, the "embedded" framed inherits these options? If, however, the framed is called not by the button, it should use the default options (as it cannot inherit it from a parent).

The following example works as expected (any new cell will inherit a -> 1111:

Options[framed] = {a -> 1};
Options[button] = {"framedOptions" -> Options@framed};
button[x_, opts : OptionsPattern[]] := 
  Button[x, Print@framed[x, OptionValue@"framedOptions"]];
framed[x_, opts : OptionsPattern[]] := 
  Framed[{button[x, "framedOptions" -> Flatten@{opts}], OptionValue@a}];

Print@framed["Push", a -> 1111];

Pushing the displayed button yields a second cell with inherited options:

Mathematica graphics

But for certain reasons I want to avoid unnecessary options like "framedOptions" and use some kind of scoping as there could be multiple embeddings and recursion and I don't want to forward "framedOptions" all the time. No scoping would work however, as the button funcion is only evaluated at runtime.

Options[framed] = {a -> 1};
button[x_] := Button[x, Print@framed@x];
framed[x_, opts : OptionsPattern[]] := Framed[
   Internal`InheritedBlock[{framed = framed},
    SetOptions[framed, Flatten@{opts}];
    {button@x, OptionValue@a}
    ]];

Print@framed["Push", a -> 1111];

Mathematica graphics

Note that the problem extends to attributes or any part of a function definition. What I would really need is an InheritedDynamicBlock (nonexistent) that not justs inherits the function definition but also keeps it local to a certain part of the screen (where the original content is displayed). Any idea?

$\endgroup$
1
  • $\begingroup$ Title might be too general but I really didn't know how to formulate the problem in a more specific way. $\endgroup$ Mar 2, 2014 at 13:06

1 Answer 1

6
$\begingroup$

How about this:

ClearAll[framed, button, $opts];
    $opts = {};
Options[framed] = {a -> 1};
button[x_] :=
  With[{opts = $opts},
    Button[x, Print@framed[x, opts]]];

framed[x_, opts : OptionsPattern[]] :=
  Block[{$opts = Flatten[{opts}]},
    Framed[{button[x], OptionValue@a}]
  ];

and you use this as before:

Print@framed["Push", a -> 1111]

The scheme here is that we use Block (dynamic scoping), to affect the way the button function is constructed, at run-time, when framed is called. So, the code of button gets those options embedded when framed is called, and passes those options further when the button is pressed. I don't see a way to avoid such a mix of lexical and dynamic scoping altogether, in this specific case.

EDIT

You can also hide the global $opts somewhat, and make the code better structured (particularly if, as you mentioned, functions button anf framed can live in different packages), by having a separate package looking something like

BeginPackage["Bindings`"]

LexicallyBound::usage = "LexicallyBound[var_Symbol][code] lexically binds the variable var in code, to an existing at the time of the call dynamic binding";

DynamicallyBound::usage = "DynamicallyBound[arg][code] executes code with a dynamic binding to the value of arg";

Begin["`Private`"]

$opts = {};

LexicallyBound[var_Symbol] := 
    Function[code, With @@ Hold[{var = $opts}, code], HoldAll];

DynamicallyBound[arg_] :=
    Function[code, Block[{$opts = arg}, code], HoldAll];

End[]
EndPackage[]

and now, you import this package in both of your packages, and define:

ClearAll[framed];
Options[framed] = {a -> 1};
framed[x_, opts : OptionsPattern[]] := 
   DynamicallyBound[Flatten[{opts}]][Framed[{button[x], OptionValue@a}]];

in one package, and

button[x_] := LexicallyBound[opts][Button[x, Print@framed[x, opts]]];

in another one.

Note that with this method, you can only use a single binding, at any time. It can be extended to become "binding objects", so that you could create many independent bindings. I can provide more details on this upon request.

$\endgroup$
5
  • $\begingroup$ Working nicely, thanks! I have two problems with this though: 1) it introduces a global and 2) modifies button (wraps it). But I guess there is no way it can be done without globals, modifications of the internal function or forwarding options. $\endgroup$ Mar 2, 2014 at 14:45
  • $\begingroup$ @IstvánZachar Re: introduces a global - nothing prevents you from wrapping all the above code in Module[{$opts},...], making this kind of local. Re: modifies button - I don't see any way around this - since your button calls are asynchronous (from the point of view of the control flow), you have to store the option settings somewhere. You either have to do this by code-generation (like I did), or create more state, something like an object with an ID, and a storage also linked to this ID, to store the options. The second method is more general but far less idiomatic for Mathematica. $\endgroup$ Mar 2, 2014 at 15:04
  • $\begingroup$ Well, there is one thing that prevents an outermost Module wrapper: framed and button are in different packages. But I can live with the global, it is still much better than my solution that invokes unwanted bookeeping when the scenario gets more complex. I thought of the ID-based method too, but that seemed an overkill. So for now, I'll stick to your method. $\endgroup$ Mar 2, 2014 at 15:45
  • $\begingroup$ @IstvánZachar See my update, I have described an extension of this, which may serve you better. $\endgroup$ Mar 2, 2014 at 16:11
  • $\begingroup$ @LeonidShifrin If you're around, could you please join me in chat? $\endgroup$
    – rm -rf
    Mar 4, 2014 at 18:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.