Preamble
The problem is not as trivial as it may seem on the first glance. The main problem is that many symbols are localized by (lexical) scoping constructs and should not be counted. To fully solve this, we need a parser for Mathematica code, that would take scoping into account.
One of the most complete treatments of this problem was given by David Wagner in his Mathematica Journal article, and replicated partially in his book. I will follow his ideas but show my own implementation. I will implement a sort of a simplistic recusrive descent parser which would take scoping into account. This is not a complete thing, but it will illustrate certain subtleties involved (in particular, we should prevent premature evaluation of pieces of code during the analysis, so this is a good excercise in working with held/unevaluated expressions).
Implementation (for illustration only, does not pretend to be complete)
Here is the code:
ClearAll[getDeclaredSymbols, getDependenciesInDeclarations, $OneStepDependencies,
getSymbolDependencies, getPatternSymbols,inSymbolDependencies, $inDepends];
SetAttributes[{getDeclaredSymbols, getDependenciesInDeclarations,
getSymbolDependencies, getPatternSymbols,inSymbolDependencies}, HoldAll];
$OneStepDependencies = False;
inSymbolDependencies[_] = False;
globalProperties[] =
{DownValues, UpValues, OwnValues, SubValues, FormatValues, NValues,
Options, DefaultValues};
getDeclaredSymbols[{decs___}] :=
Thread@Replace[HoldComplete[{decs}], HoldPattern[a_ = rhs_] :> a, {2}];
getDependenciesInDeclarations[{decs___}, dependsF_] :=
Flatten@Cases[Unevaluated[{decs}],
HoldPattern[Set[a_, rhs_]] :> dependsF[rhs]];
getPatternSymbols[expr_] :=
Cases[
Unevaluated[expr],
Verbatim[Pattern][ss_, _] :> HoldComplete[ss],
{0, Infinity}, Heads -> True];
getSymbolDependencies[s_Symbol, dependsF_] :=
Module[{result},
inSymbolDependencies[s] = True;
result =
Append[
Replace[
Flatten[Function[prop, prop[s]] /@ globalProperties[]],
{
(HoldPattern[lhs_] :> rhs_) :>
With[{excl = getPatternSymbols[lhs]},
Complement[
Join[
withExcludedSymbols[dependsF[rhs], excl],
Module[{res},
(* To avoid infinite recursion *)
depends[s] = {HoldComplete[s]};
res = withExcludedSymbols[dependsF[lhs], excl];
depends[s] =.;
res
]
],
excl]
],
x_ :> dependsF[x]
},
{1}
],
HoldComplete[s]
];
inSymbolDependencies[s] =.;
result] /; ! TrueQ[inSymbolDependencies[s]];
getSymbolDependencies[s_Symbol, dependsF_] := {};
(* This function prevents leaking symbols on which global symbols colliding with
** the pattern names (symbols) may depend
*)
ClearAll[withExcludedSymbols];
SetAttributes[withExcludedSymbols, HoldFirst];
withExcludedSymbols[code_, syms : {___HoldComplete}] :=
Module[{result, alreadyDisabled },
SetAttributes[alreadyDisabled, HoldAllComplete];
alreadyDisabled[_] = False;
Replace[syms,
HoldComplete[s_] :>
If[! inSymbolDependencies[s],
inSymbolDependencies[s] = True,
(* else *)
alreadyDisabled[s] = True
],
{1}];
result = code;
Replace[syms,
HoldComplete[s_] :>
If[! alreadyDisabled[s], inSymbolDependencies[s] =.],
{1}
];
ClearAll[alreadyDisabled];
result
];
(* The main function *)
ClearAll[depends];
SetAttributes[depends, HoldAll];
depends[(RuleDelayed | SetDelayed)[lhs_, rhs_]] :=
With[{pts = getPatternSymbols[lhs]},
Complement[
Join[
withExcludedSymbols[depends[lhs], pts],
withExcludedSymbols[depends[rhs], pts]
],
pts]
];
depends[Function[Null, body_, atts_]] := depends[body];
depends[Function[body_]] := depends[body];
depends[Function[var_, body_]] := depends[Function[{var}, body]];
depends[Function[{vars__}, body_]] :=
Complement[depends[body], Thread[HoldComplete[{vars}]]];
depends[(With | Module)[decs_, body_]] :=
Complement[
Join[
depends[body],
getDependenciesInDeclarations[decs, depends]
],
getDeclaredSymbols[decs]
];
depends[f_[elems___]] :=
Union[depends[Unevaluated[f]],
Sequence @@ Map[depends, Unevaluated[{elems}]]];
depends[s_Symbol /; Context[s] === "System`"] := {};
depends[s_Symbol] /; ! $OneStepDependencies || ! TrueQ[$inDepends] :=
Block[{$inDepends = True},
Union@Flatten@getSymbolDependencies[s, depends ]
];
depends[s_Symbol] := {HoldComplete[s]};
depends[a_ /; AtomQ[Unevaluated[a]]] := {};
Illustration
First, a few simple examples:
In[100]:= depends[Function[{a,b,c},a+b+c+d]]
Out[100]= {HoldComplete[d]}
In[101]:= depends[With[{d=e},Function[{a,b,c},a+b+c+d]]]
Out[101]= {HoldComplete[e]}
In[102]:= depends[p:{a_Integer,b_Integer}:>Total[p]]
Out[102]= {}
In[103]:= depends[p:{a_Integer,b_Integer}:>Total[p]*(a+b)^c]
Out[103]= {HoldComplete[c]}
Now, a power example:
In[223]:= depends[depends]
Out[223]=
{HoldComplete[depends],HoldComplete[getDeclaredSymbols],
HoldComplete[getDependenciesInDeclarations],HoldComplete[getPatternSymbols],
HoldComplete[getSymbolDependencies],HoldComplete[globalProperties],
HoldComplete[inSymbolDependencies],HoldComplete[withExcludedSymbols],
HoldComplete[$inDepends],HoldComplete[$OneStepDependencies]}
As you can see, my code can handle recursive functions. The code of depends
has many more symbols, but we only found those which are global (not localized by any of the scoping constructs).
Note that by default, all dependent symbols on all levels are included. To only get the "first-level" functions / symbols on which a given symbol depends, one has to set the variabe $OneStepDependencies
to True
:
In[224]:=
$OneStepDependencies =True;
depends[depends]
Out[225]= {HoldComplete[depends],HoldComplete[getDeclaredSymbols],
HoldComplete[getDependenciesInDeclarations],HoldComplete[getPatternSymbols],
HoldComplete[getSymbolDependencies],HoldComplete[withExcludedSymbols],
HoldComplete[$inDepends],HoldComplete[$OneStepDependencies]}
This last regime can be used to reconstruct the dependency tree, as for example suggested in the answer by @Szabolcs.
Applicability
This answer is considerably more complex than the one by @Szabolcs, and probably also (considerably) slower, at least in some cases. When should one use it? The answer I think depends on how critical it is to find all dependencies. If one just needs to have a rough visual picture for the dependencies, then @Szabolcs's suggestion should work well in most cases. The present asnwer may have advantages when:
You want to analyze dependencies in an arbitrary piece of code, not necessarily placed in a function (this one is easily if not super-conveniently circumvented in @Szabolcs's approach by first creating a dummy zero-argument function with your code and then analyzing that)
It is critical for you to find all dependencies.
Things like
$functionDoingSomething = Function[var,If[test[var],f[var],g[var]]]
myFunction[x_,y_]:= x+ $functionDoingSomething [y]
will escape from the dependencies found by the @Szabolcs's code (as he mentioned himself in the comments), and can therefore cut away whole dependency sub-branches (for f
, g
and test
here). There are other cases, for example related to UpValues
, dependencies through Options
and Defaults
, and perhaps other possibilities as well.
There may be several situations when finding all dependencies correctly is critical. One is when you are using introspection programmatically, as one of the meta-programming tools - in such case you must be sure everything is correct, since you are building on top of this functionality. To generalize, you might need to use something like what I suggested (bug-free though :)), every time when the end user of this functionality will be someone (or something, like other function) other than yourself.
It may also be that you need the precise dependency picture for yourself, even if you don't intend to use it programmatically further.
In many cases however, all this is not very critical, and the suggestion by @Szabolcs may represent a better and easier alternative. The question is basically - do you want to create user-level or system-level tools.
Limitations, flaws and subtleties
EDIT
The current version of the code certainly contains bugs. For example, it can not handle the GraphEdit
example from the answer of @Szabolcs without errors. While I hope to get these bugs fixed soon, I invite anyone interested to help me debugging the code. Feel free to update the answer, once you are sure that you correctly identified and truly fixed some bugs.
END EDIT
I did not intend this to be complete, so things like UpSetDelayed
and TagSetDelayed
are not covered, as well as probably some others. I did not also cover dynamic scoping (Block
, Table
, Do
, etc), because in most cases dynamic scoping still means dependencies. The code above can however be straightforwardly extended to cover the cases missed here (and I might do that soon).
The code can be refactored further to have a more readable / nicer form. I intend to do this soon.
ToExpression["boo"]
. The only way gets into my mind is to actually run the code and to trace it. $\endgroup$ToExpression
is one of the reasons why I think thatToExpression
and related things (which lead to parsing at run-time) must be avoided whenever possible - they reduce the power of introspective features in Mathematica. There are cases when they may be necessary, but at least these things must be then isolated so that not many functions use them. In most cases however, creating functions at run-time is possible within the fully parsed Mathematica code. The code-analysis problem is still well-formulated though, even countingToExpression
and friends. $\endgroup$SymbolDependencyGraph
resource function available for this: resources.wolframcloud.com/FunctionRepository/resources/… $\endgroup$