What is the equivalent of $ModuleNumber for DynamicModule
Intro
The answer is: Yes, if you know what you want and it makes sense.
Let's say we want to get current parent's module number from withing Module
, this is the way to go:
Module[{x}, {x, $ModuleNumber -1 }]
For DynamicModule
it does NOT make sense in context of initial evaluation but:
it makes sense in context of any evaluation triggered from within generated
DynamicModuleBox
, at the end it is the FrontEnd who owns it:DynamicModule[{x} , { $DynamicModuleNumber (* does NOT make sense *) , Dynamic @ $DynamicModuleNumber (* DOES make sense *) , Button["run", Print @ $DynamicModuleNumber] (* DOES make sense on click*) } ]
one needs to be aware and EXPECT that if you close/open notebook/cdf containing this
DynamicModuleBox
, it will change.you need to EXPECT that if you copy and paste the parent box/cell, it will change
it may change if you edit an output cell the
DynamicModuleBox
lives infor modularized/nested apps with multiple
DynamicModuleBox
instances one needs to know what is going on to stay in control. That is, you can always get the module number inInitialization
and pass by value to nested elements in the body if they are built from DynamicModules too.
Those limitations enclose a good bunch of use cases anyway so one may wonder why the api to that number is not public/more user friendly.
Example
DynamicModule[{x, y}
, {
Dynamic @ DynamicModuleNumber[]
, Dynamic@x
, Dynamic@y
, Button["set x", x = DynamicModuleNumber[]]
, Button["set y", y = DynamicModuleNumber[], Method -> "Queued"]
}
] (* twice *)
Code
So, let's cook up something with respect to what makes sense. Your answer already does something like this but it is too cumbersome for me.
DynamicModuleNumber::noparent="DynamicMoudelNumber not executed in DynamicModule";
DynamicModuleNumber::nokids="DynamicMoudelNumber can not access any variable of parent DynamicModule";
DynamicModuleNumber[]:= DynamicModuleNumber[FrontEnd`Private`names]
DynamicModuleNumber[HoldPattern[FrontEnd`Private`names]]:=(
Message[DynamicModuleNumber::noparent];$Failed
);
DynamicModuleNumber[{}]:=(Message[DynamicModuleNumber::nokids];$Failed);
DynamicModuleNumber[{Hold[sym_Symbol],___}]:=First @ StringCases[
SymbolName[Unevaluated[sym]]
, __~~"$$"~~dmn:DigitCharacter..~~EndOfString:>ToExpression[dmn]
]
Explanation
When FE sends a packet to evaluation, which comes from an object (Dynamic/Button
) embedded in DynamicModuleBox
, it wraps the packet with ExecuteInDynamicModule
.
E.g. clicking this button
DynamicModule[{}, Button["print", foo[]] ]
will result in FE --> K
System`EvaluatePacket[
FrontEnd`SynchronousDynamicEvaluate[
FE`ExecuteInDynamicModule[
{}
, 89
, TimeConstrained[
Function[Global`foo[]][BoxData["\"print\""], Automatic, 1, {}]
, 6.0
]
]
, 0
, 9.0
, 383.25714111328125
]
]
89
there is the parent's dynamic module number. Fortunately FE`ExecuteInDynamicModule
can be PrintDefinitions-ed
so we can learn that it does something like:
ExecuteInDynamicModule[init_, serialno_, body_] := Block[
{names, ...},
...
names = Symbol @ StringJoin[
"FE`DynamicModuleVariableList$", ToString @ serialno
];
...
... body ...
...
];
It means that dynamically scoped (Block) names
(FrontEnd`Private`names
) will contain information about serialno
, which turn out to be a list of variables {Hold[x$$234], ...}
.
Great, assuming there is at least one variable in parent DynamicModuleBox
we can parse this symbol, which is what the last down value of DynamicModuleNumber
does.
Short answer: there is no equivalent of $ModuleNumber
for DynamicModule
.
Authoritative comments by John Fultz (here and below) about the scoping of DynamicModule
and how and why it is differing from the comparably simpler scoping of Module
:
[...] "this would require a concept of
$ModuleNumber
to be handled natively by the FE in the typesetting. Which could not be relied upon in the global namespace, so we'd have to be constantly rewriting the typesetting based upon the global state of the FE. Right now, the only concept of$ModuleNumber
is one attached to the kernel instantiation, which does not require typesetting and can be different in different sessions. I'm not saying that would have been impossible to do this, but implementation would have been quite tricksy."[...] "the case outlined here is equivalent to something like
Module[{x}, Function[{y}, Module[{x}, {x, y}]][x]]
. Except in typesetting.Module
evaluates away on us after doing its replacement, butDynamicModuleBox
is a persistent creature, and we must always be able to determine whichDynamicModuleBox
we want each variable to target in order to solve the problem. You have to do that by renaming the variables in the typesetting. If you're not thinking of this as a typesetting problem, you're not properly understanding the problem."
So, yes, there's no equivalent, but just as a quick work-around we can leverage our knowledge of how the FE works to extract it. Consider this:
DynamicModule[{
dmlist,
serialno
},
Dynamic@serialno,
Initialization :>
(
serialno =
Max@
ToExpression@
StringTrim[
Names["FE`DynamicModuleVariableList$*"],
"FE`DynamicModuleVariableList$"
];
dmlist =
ToExpression["FE`DynamicModuleVariableList$" <> ToString[serialno]]
)
]
That serialno
is now a handle we can use to access the variables the DynamicModule
is serializing for us. The dmlist
is just a fun example of what it's useful for.
I can't remember why I wanted it, but now it's there.
Just for fun here's how it let's us figure out what a DynamicModule
var is called from the boxes:
dynamicModuleSerialNoExtract[box_DynamicModuleBox] :=
FirstCase[box,
HoldPattern[_Symbol?(
Function[Null,
SymbolName[Unevaluated@#] === "serialno$$",
HoldAllComplete
]) = sn_Integer] :> sn,
$Failed,
\[Infinity]
];
dynamicModuleSerialNoExtract[e_] :=
FirstCase[e,
d_DynamicModuleBox :>
dynamicModuleSerialNoExtract[d],
$Failed,
\[Infinity]
];
dynamicModuleVarExtract[varName_String, e_] :=
Replace[dynamicModuleSerialNoExtract[e],
i_Integer :> ToExpression@("FE`" <> varName <> "$" <> ToString[i])
];
dynamicModuleVarExtract[var_Symbol, e_] :=
dynamicModuleVarExtract[
Evaluate@ToString[Unevaluated[var]],
e
];
dynamicModuleVarExtract[a_?(MatchQ[_String | _Symbol]), e_] :=
dynamicModuleVarExtract[Evaluate@a, e];
dynamicModuleVarExtract~SetAttributes~HoldFirst
It scrapes out this serialno
and then sticks it onto the var in question.
Then here's a quick wrapper function for this DynamicModule
form:
serializedDynamicModule[{vars___},
e : Except[_?OptionQ] : Dynamic[serialno],
ops___?OptionQ] :=
DynamicModule[{
serialno,
vars
},
e,
Initialization :>
(
serialno =
Max@
ToExpression@
StringTrim[
Names["FE`DynamicModuleVariableList$*"],
"FE`DynamicModuleVariableList$"
];
),
ops
];
serializedDynamicModule~SetAttributes~HoldAllComplete
And finally here's the thing in action:
NotebookWrite[InputNotebook[],
Cell[BoxData@ToBoxes@serializedDynamicModule[{}],"Output"]
];
dynamicModuleVarExtract[DynamicModuleVariableList,NotebookRead@NextCell[]]
893
{Hold[FE`serialno$$893]}
Note that we really shouldn't be able to extract this, so this is something of a win, even if it's largely without application.