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