shouting versus dotting
Benji Smith
dlanguage at benjismith.net
Tue Oct 7 10:36:34 PDT 2008
Andrei Alexandrescu wrote:
> Benji Smith wrote:
>> Hmmmmmm... I still hate opCall, though :)
>
> One context in which I found opCall very valuable is parameterized
> functions: functions that take, say, 1 argument but are parameterized by
> another. An example is a Gaussian kernel of parameterized bandwidth:
>
> double gaussianKernel(double d)
> {
> return exp(-d * d / alpha * alpha);
> }
>
> We'd like to be able to configure alpha properly (not via a global,
> because there could be several Gaussian kernels around). A solution
> would be to use delegates, but that's inefficient and a kernel is
> evaluated so often it's not even funny. So a good solution is to make
> the function a struct with state:
>
> struct GaussianKernel
> {
> private double alpha2InvNeg = -1;
>
> double opCall(double d) const
> {
> return exp(d * d * alpha2InvNeg);
> }
>
> void alpha(double a)
> {
> enforce(a > 0);
> alpha2InvNeg = -1 / (a * a);
> }
>
> double alpha() const
> {
> return sqrt(-1 / alpha2InvNeg);
> }
> }
>
>
> Andrei
Great example!
I've actually implemented the same thing (in Java). I have a
KernelDensityEstimator class, for estimating the shape of a
distribution, based on sample values from that distribution.
The KDE constructor takes a Function object to define the kernel.
(Rather than defining a "Kernel" as an interface or an abstract class, I
define a more general "Function" interface, and let the caller make his
own choice about whether his function makes an appropriate kernel.)
So the code looks like this:
interface Function {
public double project(double value);
}
class GaussianFunction implements Function {
private double height;
private double mean;
private double stdev;
private double denominator;
public GaussianFunction(double height, double mean, double stdev) {
enforce(height > 0);
enforce(!Double.isInfinite(height));
enforce(!Double.isNaN(height));
enforce(!Double.isInfinite(mean));
enforce(!Double.isNaN(mean));
enforce(stdev > 0);
enforce(!Double.isInfinite(stdev));
enforce(!Double.isNaN(stdev));
this.height = height;
this.mean = mean;
this.stdev = stdev;
// Pre-calculate the denominator of the exponent
// (since it won't vary from call to call).
this.denominator = 2 * stdev * stdev;
}
public double project(double value) {
double difference = x - mean;
double numerator = difference * difference;
return height * Math.exp(- (numerator / denominator));
}
}
class KernelDensityEstimator {
private Function kernel;
private List<Double> samples;
public KernelDensityEstimator(Function kernel) {
this.kernel = kernel;
this.samples = new ArrayList<Double>();
}
public double addSample(double sample) {
samples.add(sample);
}
public estimatedProbabilityOf(double value) {
double sum = 0;
for (double sample : samples) {
sum += kernel.project(sample);
}
return sum / samples.size();
}
}
Anyhoo... maybe that's a little too much code to dump, but I wanted to
show the definition of the concept of a "Function", separated from the
definition of a particular function class, separated from the
instantiation of that class, and separated from the invocation of the
function.
In my mind, opCall would encourage code like this:
foreach (double x; values) {
GaussianKernel(alpha)(x);
}
In this code, the alpha gets set... over and over and over again. If the
construction of the parametric function is distinct from the calling of
it, the user is encouraged to write code more like this:
auto kernel = new GuassianKernel(alpha);
foreach (double x; values) {
auto result = kernel.valueOf(x);
}
It can be convincingly argued that a Function object shouldn't have a
*named* method. Or at least, if it does have a named method, the name
should be the same at the name of the function. But then that makes it
hard to use functions polymorphically.
What you and I are doing is pretty much the same, except that I call my
function "project" (since a function is a projection from an x value to
a corresponding y value), whereas you use opCall.
The nice thing about using a named function, though, is that it allows
me to pass functions around polymorphically, and since Java doesn't have
function pointers, this is the best solution to the problem.
I totally agree with you, though, that this is the best possible use for
opCall.
--benji
More information about the Digitalmars-d
mailing list