Please be gentle with me - this is my first ever post to stackexchange.
Firstly, I'd like to say that I found Faysal's post both fascinating and outstandingly useful (I would upvote it if I could, but I have insufficient reputation). It introduces techniques I'm sure I shall use a great deal. However, it doesn't, quite, cater for all use cases. Where a function is always referenced with its parameter list, then it works for me very well. But, where a function is referenced without explicit arguments, e.g.
FCN /@ ll
ExpandCode doesn't dig down to expand the function definition, resulting in the inclusion of a "MainEvaluate"
in the compiled code. I have attempted to fix this up with the modified version of ExpandCodeAux
shown below. I have also slightly modified the criteria function so that it takes a string argument, and checks the putative function symbol name against a list of functions that should be expanded (expandFns
) or, alternatively, a list of functions which should NOT be expanded (nonExpandFns
). All other code from Faysal's original post remains unchanged. I have tested it with code that includes functions defined as:
FCN1[x_] := <expression in x>
as
FCN2 = Function[{x},<expression in x>]
and as
FCN3 = <expression in #>&
and it produced functional, self-contained fully-compiled code. However, it clearly has many limitations. Specifically, it won't work for function definitions that rely on pattern matching on the argument list to select between different forms. It assumes that the first DownValue definition is the one you want.
I rather fear that my additions have obscured the essential elegance of the original, and I might well have introduced the possibility of incorrect behaviour in some cases. (I always check that the compiled code reproduces test cases run on the interpreted version for this reason). I'm also quite sure that my ham-fisted use of the the various Hold
constructs (the result of much experimentation) could be improved upon.
I hope, nevertheless, that this might prove to be of some use.
criteriaFcn = Function[symbName,
If[nonExpandFns == {},
Or @@ (StringMatchQ[expandFns, symbName]),
Not[Or @@ (StringMatchQ[nonExpandFns, symbName])]
]
]
SetAttributes[unpattern,HoldAll]
unpattern[args__] :=
Sequence @@({args} /. Verbatim[Pattern][a_,Verbatim[Blank][]]->a)
ExpandCodeAux[code_, criteria_, fullEval_] := Module[{oneStep},
code /.
{ (*Caters for cases where a function is referenced with explicit \
parameters*)
(x_Symbol[args___] /;
With[{xref = HoldForm[x]},
criteria@ToString@xref
] :>
With[
{eval = If[fullEval@x,
x@args,
oneStep = With[{Step = step@x@args}, HoldForm@Step];
If[oneStep === HoldForm@x@args,
oneStep,
ExpandCodeAux[oneStep, criteria, fullEval]]]
},
eval /; True
]
),
(*For cases where the function is defined as a DownValue,
but is referenced without its parameters (as in f/@ll) *)
(x_Symbol /;
With[{xref = HoldForm[x], dv = DownValues[Unevaluated@x]},
dv != {} && criteria@ToString@xref
] :>
With[
{
eval =
(ExpandCodeAux[
First@With[{dv = DownValues[Unevaluated@x]}, dv] /.
Verbatim[RuleDelayed][
Verbatim[HoldPattern][HoldPattern[x][args__]],
rhs_] :>
Function[Evaluate@List@unpattern[Unevaluated@args],
HoldForm@rhs], criteria, fullEval])
},
eval /; True
]
),
(*A symbol to which a pure function has been assigned as one of \
its OwnValues, as in f = Function[a,a^2].*)
(x_Symbol /;
With[{xref = HoldForm[x], ov = OwnValues[Unevaluated@x]},
Length[ov] > 0
&&
MatchQ[First@ov,
Verbatim[RuleDelayed][
Verbatim[HoldPattern]@HoldPattern@x, _]]
&& criteria@ToString@xref
] :>
With
[
{eval =
ExpandCodeAux[(First@
With[{ov = OwnValues[Unevaluated@x]}, ov]) /.
Verbatim[RuleDelayed][
Verbatim[HoldPattern]@HoldPattern[x], rhs_] :> rhs,
criteria, fullEval]
},
eval /; True
]
)
}
];
Set[]
; withSetDelayed[]
, it always seems to require kernel evaluation. $\endgroup$