// higher order function version 
// but still not MSP
public class HigherOrder2 {
    public static interface ILambda<R,P> {
        public R apply(P param);
    }
    public static class TimesTwo implements ILambda<Double,Double> {
        public Double apply(Double param) { return param*2; }
    }
    
    public static class PlusFour implements ILambda<Double,Double> {
        public Double apply(Double param) { return param+4; }
    }
    
    public static class SquareRoot implements ILambda<Double,Double> {
        public Double apply(Double param) { return Math.sqrt(param); }
    }
    
    public static void printTable(ILambda<Double,Double> f,
                                  double x1,
                                  double x2,
                                  int n) {
        assert n>1;
        
        double x = x1;
        double delta = (x2-x1)/(double)(n-1);
        
        // f.apply(x) just means what f(x) means in math!
        double y = f.apply(x);
        System.out.println("x                      f(x)");
        System.out.printf("%20.10f %20.10f\n", x, y);
        for(int i=0; i<(n-1); ++i) {
            x += delta;
            y = f.apply(x);
            System.out.printf("%20.10f %20.10f\n", x, y);
        }
    }
    
    public static void main(String[] args) {
        printTable(new TimesTwo(), -5, 5, 11);
        printTable(new PlusFour(), -5, 5, 11);
        printTable(new SquareRoot(), -5, 5, 11);
        
        // anonymous inner class
        // We define a new ILambda from Double to Double
        // without giving it a name. When we do that, we
        // need to fill in all the methods that are still
        // abstract. Here, it is just the apply method.
        printTable(new ILambda<Double,Double>() {
            public Double apply(Double param) {
                return param*param;
            }
        }, -5, 5, 11);
    }
}
