// Programming Languages, Dan Grossman, CSE341 // Bonus example on higher-order functions with plain objects and generics, // drawing connections between functions and objects // (This file has not been tested.) // (You shouldn't do any of this explicitly now that Java 8 and later // have syntactic support for lambdas as shown at the end, // but this pulls together concepts better for 341.) interface Func { B m(A x); } interface Pred { boolean m(A x); } class List { T head; List tail; List(T x, List xs) { head = x; tail = xs; } // * the advantage of a static method is it allows xs to be null // -- a more OO way would be a subclass for empty lists (see below) // * a more efficient way in Java would be a messy while loop // where you keep a pointer to the previous element and mutate it // -- (try it if you don't believe it's messy) // -- it's more efficent because Java VM doesn't optimize tail calls // (maybe some day) static List map(Func f, List xs) { if(xs==null) return null; return new List(f.m(xs.head), map(f,xs.tail)); } static List filter(Pred f, List xs) { if(xs==null) return null; if(f.m(xs.head)) return new List(xs.head, filter(f,xs.tail)); return filter(f,xs.tail); } // * again recursion would be more elegant but less efficient // * again an instance method would be more common, but then // all clients have to special-case null static int length(List xs) { int ans = 0; while(xs != null) { ++ans; xs = xs.tail; } return ans; } } class ExampleClients { // no environment needed static List doubleAll(List xs) { return List.map((new Func() { public Integer m(Integer x) { return x * 2; } }), xs); } // the key point here is that the "final int n" is in the environment // without inner classes, we'd need a class definition with an explicit // field to hold n and pass n to the constructor as shown next // (In Java 8 and later, you can leave final off, but it is inferred -- // if countNs does mutate n, then the inner object won't be allowed to use // it.) static int countNs(List xs, final int n) { return List.length(List.filter((new Pred() { public boolean m(Integer x) { return x==n; } }), xs)); } static int countNs2(List xs, int n) { return List.length(List.filter(new ForCountNs2(n), xs)); } } class ForCountNs2 implements Pred { int n; // aha, explicit environment, created by constructor ForCountNs2(int _n) { n = _n; } public boolean m(Integer x) { return x == n; } } // now here's a version that does the more OO thing. If we use null instead // instance methods, then clients have to check the null case, which is far // less convenient, so we choose instead /not/ to represent empty lists with // null. abstract class List2 { abstract List2 map(Func f); abstract List2 filter(Pred f); abstract int length(); } class NonEmptyList extends List2 { T head; List2 tail; NonEmptyList(T x, List2 xs) { head = x; tail = xs; } // ooh covariant subtyping on return type NonEmptyList map(Func f) { return new NonEmptyList(f.m(head), tail.map(f)); } List2 filter(Pred f) { if(f.m(head)) return new NonEmptyList(head, tail.filter(f)); return tail.filter(f); } int length() { return 1 + tail.length(); } } class Null extends List2 { Null() {} // could omit this as it's implied // ooh covariant subtyping on return type Null map(Func f) { return new Null(); // the above is "right" for the type system but wasteful // this will rightfully generate a warning but would be okay: // return (Null)this; } Null filter(Pred f) { return this; } int length() { return 0; } } class ExampleClients2 { static List2 doubleAll(List2 xs) { return xs.map(new Func() { public Integer m(Integer x) { return x * 2; } }); } // the key point here is that the "final int n" is in the environment // without inner classes, we'd need a class definition with an explicit // field to hold n and pass n to the constructor static int countNs(List2 xs, final int n) { return xs.filter(new Pred() { public boolean m(Integer x) { return x==n; } }) .length(); } } // now lambdas with implicit conversion to one-method interfaces // basically syntactic sugar for clients class ExampleClients3 { static List2 doubleAll(List2 xs) { return xs.map((Integer x) -> x * 2); } static int countNs(List2 xs, final int n) { return xs.filter((Integer x) -> x == n).length(); } }