mmap.page

Notes on A little Java, a few patterns

Advantages of Java

Design Patterns

Design patterns are used:

Introductory Language

Recommended introductory language: Scheme or ML.

Too Much Coffee

Q: Is it confusing that we need to connect int with Integer (e.g. Integer(5)) and boolean with Boolean (e.g. Boolean(false))?

A: Too much coffee does that.

Note the book uses an early version of Java. Current Java can do auto boxing and unboxing for primitive types.

Also, in this book, if the method specified its return type as Object in interface, then its implementation also annotated as returning Object, even when the implementation in fact always returns Boolean.

Q: There is no number x in the world for which

x = x + 1

So why should we expect there to be a Java p such that

p = new Top(new Anchovy(), p)

A: That’s right. But that’s what happens when you have one too many double espresso.

Semicolon on Its Own Line for Mutability

class PutSemicolonOnItsOwnLineForMutability
{
    Pie p = new Crust();
    Pie p = new Top(new Anchovy(), p)
    ; // the future begins, i.e. from this line on, references to `p` reflect the change
    Pieman yy = new Bottom();
    yy.addTop(new Anchovy())
    ; // same as above
}

LittleJava.java

// `D` means this is a data class.
abstract class Num {}

class Zero extends Num {}

class OneMoreThan extends Num {
    Num predecessor;

    // Constructor
    OneMoreThan(Num _p) {
        predecessor = _p;
    }
}

/*
We did not tell you these are Peano axioms.
We din not give formal definition.
So you can form your own definition.
Try to give it a name by yourself.
This helps you to remember and understand it better.
 */





/* This book shows how to protect direct access to property without the `private` access modifier.

   Wrap it into an interface. In that interface, only expose public methods, not the "private" property.
 */

/* Visitor pattern */
abstract class Shish
{
    OnlyOnions ooFn = new OnlyOnions();
    abstract boolean onlyOnions();
}

class OnlyOnions
{
    boolean forSkewer()
    {
        return true;
    }

    boolean forOnion(Shish s)
    {
        return s.onlyOnions();
    }

    boolean forLamb(Shish s)
    {
        return false;
    }

    boolean forTomato(Shish s)
    {
        return false;
    }
}

class Skewer extends Shish
{
    boolean onlyOnions()
    {
        return ooFn.forSkewer();
    }
}

class Onion extends Shish
{
    Shish s;
    Onion(Shish _s)
    {
        s = _s;
    }

    boolean onlyOnions()
    {
        return ooFn.forOnion(s);
    }
}
class Lamb extends Shish
{
    Shish s;
    Lamb(Shish _s)
    {
        s = _s;
    }

    boolean onlyOnions()
    {
        return ooFn.forLamb(s);
    }
}
class Tomato extends Shish
{
    Shish s;
    Tomato(Shish _s)
    {
        s = _s;
    }

    boolean onlyOnions()
    {
        return ooFn.forTomato(s);
    }
}
/* Before introducing visitor pattern,
   every subclass of Shishᴰ need to contain the logic of `onlyOnions()` in its definition.
   And the book asked "Wasn't this overwhelming?"
   I had thought it would introduce generics next.
   But it turned out to be the visitor pattern.
   Oh, I forgot Java's generics are not reified.

   If Java had reified generics:

       class Shishᴰ
       {
           <S: Shishᴰ> boolean onlyOnions(S s)
           {
               if (s instanceof Sewer)
               {
                   return false;
               }
               else if (s instanceof Onion)
               {
                   return onlyOnions(s.s);
               }
               else
               {
                   return false;
               }
           }
       }
 */



/* This is for the loyal Schemers and MLers. */

interface T
{
    // It seems Java does not allow unicode arrows in identity name.
    // So we use the Chinese character `一` (one), which has a similar shape.
    oo apply(T x);
}

interface oo
{
    Object apply(Object x);
}

interface oooo
{
    oo apply(oo x);
}

interface oooooo
{
    oo apply(oooo x);
}

class Y implements oooooo
{
    public oo apply(oooo f)
    {
        return new H(f).apply(new H(f));
    }
}

class H implements T
{
    oooo f;
    H(oooo _f)
    {
        f = _f;
    }

    public oo apply(T x)
    {
        return f.apply(new G(x));
    }
}

class G implements oo
{
    T x;

    G(T _x)
    {
        x = _x;
    }

    public Object apply(Object y)
    {
        return (x.apply(x)).apply(y);
    }
}

class MKFact implements oooo
{
    public oo apply(oo fact)
    {
        return new Fact(fact);
    }
}

class Fact implements oo
{
    oo fact;
    Fact(oo _fact)
    {
        fact = _fact;
    }

    public Object apply(Object i)
    {
        int inti = ((Integer) i).intValue();
        if (inti == 0)
        {
            return new Integer(1);
        }
        else
        {
            return
                new Integer(
                    inti
                    *
                    ((Integer)
                        fact.apply(new Integer(inti - 1))).intValue());
        }
    }
}

// Try to figure out how the above code works.
// First the concrete one `Fact`.
// To construct a `Fact`, we need a `fact`.
// Suppose we already have `fact`, then we call `fact.apply(n - 1)`.
// To successfully continue the recursion,
// `fact.apply(n - 1)` should be equivalent to something like `New Fact(...).apply(n - 1)`.
// Oh! We need to construct a new `Fact`, which requires a `fact` again.
// But wait. We already have `fact`, so we can reuse it.
// That's it -- self reference.
class Dummy implements oo
{
    public Object apply(Object x)
    {
        return new Fact(this).apply(x);
    }
}

// It works.
// And it also what `MKFact.apply` needs.
//
// Let's move on.
// `Fact` implements `o一oᴵ`, and `MKFact` implements `oo一ooᴵ`.
// `o一oᴵ` is like a constructor,
// and `oo一ooᴵ` is like a higher-order function taking a function and returning a function.
// Similarly, `oo一oo一ooᴵ` is like a function taking a higher-order function that takes a function and returning a function.
// Also `Tᴵ` is like a higher-order function returning a function.
//
// Now is the `Y`, `H`, `G` classes.
// Our `Dummy` class works, but it makes `MKFact` redundant.
// We need to find a way to produce `Fact` from `MKFact` without defining extra classes.
// Let's look at the types.
// `Fact` implements `o一oᴵ`, and `MKFact` implements `oo一ooᴵ`.
// So we need something that takes `oo一ooᴵ` and returns `o一oᴵ`, a.k.a `oo一ooᴵ -> o一oᴵ`.
// `Y.apply` happens to have such a signature.
// Thus probably we can get a `Fact` through `new Y().apply(new MKFact())`?
// And it works.
// Why?
// Revisit our `Dummy` class.
// In `New Fact(new Dummy())`, `(new Dummy).apply` calls back `New Fact(this)`.
// Next we demonstrate `new Y().apply(new MKFact())` is an equivalent form without `this`.
// And by the definition of `Y`, `new Y().apply(new MKFact())` is `new MKFact().apply(new G(new H(new MKFact())))`.
// Let `x = new G(new H(new MKFact()))`, we have `new MKFact().apply(x)`.
// By the definition of `MKFact`, it is `new Fact(x)`.
// Then we check what is `new Fact(x).apply(n)`.
// Fill in the value of `x`, it is `new Fact(new G(new H(new MKFact())).apply(n)`.
// By the definition of `Fact`, it is `new G(new H(new MKFact())).apply(n)`.
// By the definition of `G`, it is `new H(new MKFact()).apply(new H(new MKFact())).apply(n)`.
// By the definition of `H`, it is `new MKFact().apply(new G(new H(new MKFact()))).apply(n)`.
// By the definition of `MKFact`, it is `new Fact(x).apply(n)`.
// That is it. Self-referring to `Fact` itself without using `this`!
//
// This is the mighty Y combinator.
// The scheme version is in The Little Schemer, chapter 9.
//
// Let's walk through the reinvention of it in Java.
//
// First let's write a straightforward recursion version of `fact`.
class StaticFact
{
    static int fact(int n)
    {
        if (n == 0)
        {
            return 1;
        }
        else
        {
            return n * fact(n - 1);
        }
    }
}

// Hmm, we haven't introduced static method in this book.
// We did mention using static method like `Math.max` in footnotes,
// but we never explain how to *declare* a static method.
// Thus we changed it to a non static version.
// The definition is almost the same.
class NormalFact
{
    int fact(int n)
    {
        if (n == 0)
        {
            return 1;
        }
        else
        {
            return n * fact(n - 1);
        }
    }
}

// We refer `fact` itself in function body, which is not possible in lambda calculus.
// So we pass a function as parameter instead.
// Oh, no! Java dose not support first class function.
// Hmm, in fact we could pass a function via the reflection API, or as a lambda in Java 8.
// But none of them is available when this book is written.
// Thus we wrap the function in a class.
// Note that we do not need a separate class.
class Fact1
{
    int apply(Fact1 f, int n)
    {
        if (n == 0)
        {
            return 1;
        }
        else
        {
            return n * f.apply(f, n - 1);
        }
    }
}
// Nice!
// But lambda can only accept one parameter.
// We need to change `Fact1.apply` to return a function.
// Again in Java we return a wrapped class instead.
// To make future changes easier,
// we declare an additional interface instead of using hard-coded class.
interface Fact2
{
    int apply(int n);
}
class NClosure implements Fact2
{
    Fact2 f;
    NClosure(Fact2 _f)
    {
        f = _f;
    }

    public int apply(int n)
    {
        if (n == 0)
        {
            return 1;
        }
        else
        {
            return n * (f.apply(f)).apply(n - 1);
        }
    }
}
class Fact2
{
    Fact2 apply(Fact2 f)
    {
        return new NClosure(f);
    }
}
// This is the poor man's Y combinator.

// Look at this line `return n * (f.apply(f)).apply(n - 1);`,
// if it is `g.apply(n - 1)` then it would be similar to the original recursion version.
// Let's `g = f.apply(f)`:
class GG implements Fact2
{
    Fact3 f;
    GG(Fact3 _f)
    {
        f = _f;
    }

    public int apply(int n)
    {
        return (f.apply(f)).apply(n);
    }
}
class Fact3
{
    Fact2 apply(Fact3 f)
    {
        return new GClosure(f);
    }
}

class GClosure implements Fact2
{
    Fact3 f;
    GG g;
    GClosure(Fact3 _f)
    {
        f = _f;
        g = new GG(f);
    }

    public int apply(int n)
    {
        if (n == 0)
        {
            return 1;
        }
        else
        {
            return n * g.apply(n - 1);
        }
    }
}

// We are almost done.
// `GClosure.apply()` is the recursion definition we want.
// Let's create something that takes a `GClosure`.
class YY
{
    Fact2 f;
    YY(Fact2 _f)
    {
        f = _f;
    }

    int apply(int n)
    {
        return f.apply(n);
    }
}
// Hmm, the problem is we still need `Fact3`, whose definition hard coded reference to `GClosure`.
// We could made the constructor of `Fact3` taking `GClosure` as a parameter.
// That is, to construct a `Fact3`, we need a `GClosure`,
// and to construct a `GClosure`, we need a `Fact3`.
// Now if we make `Fact3` and `GClosure` one thing, it will be self-referral.
class Fact0 implements Fact2
{
    Fact2 g;
    Fact0(Fact2 _g)
    {
        g = _g;
    }

    public int apply(int n)
    {
        if (n == 0)
        {
            return 1;
        }
        else
        {
            return n * g.apply(n - 1);
        }
    }
}
// Now the tricky part is constructing `Fact0(_f)`.
// Again this smells like self-reference.
// Just as we introduced an additional function as parameter before,
// to construct `Fact0(_f)` we probably need an additional function.
class MKFact0
{
    Fact2 apply(Fact2 fact2)
    {
        return new Fact0(fact2);
    }
}
// Now we need to define `Y0` such that `new Y0().apply(new MKFact0())` will construct a `Fact0`.
// It is hard to write the `apply` method of `Y0`.
// Let's go back to last iteration.
// `GG` is not abstract. It has `Fact3` hard coded in its definition.
// Let's refactor it.
class G0 implements Fact2
{
    Fact2 g;
    G0(Fact2 _g)
    {
        g = _g;
    }

    public int apply(int n)
    {
        return (g.apply(g)).apply(n);
    }
}
// Oops! `g.apply(g)` is invalid.
// From here we may try to rewrite `G0`.
// Or we may try to rewrite `Fact2ᴵ`, `int -> int` is too restrictive,
// i.e. if we cannot solve a specific problem,
// try to solve its more general form.

// Rewrite `Fact2ᴵ`, substitute `int` with `Object`, we get `o一oᴵ`.
// Change `Fact0` and `MKFact0` accordingly, we get `Fact` and `MKFact`.
// `Fact` implements `o一oᴵ`, we need to define an interface for `MKFact` as well. That is `oo一ooᴵ`.
// Now we try to rewrite `G0`.
// We find that `g.apply(g)` still cause type mismatch.
// `g` cannot be an ordinary `Object`, which may no have an `apply` method defined.
// Also `g` cannot be an `o一oᴵ`, which cannot take itself (`o一oᴵ`) as a parameter.
// Thus we need a new type (interface).
// An interface whose `apply` method takes anything (an `Object`) and returns an `o一oᴵ`.
// Thus we have `Tᴵ`.
// Then we look at `Y`.
// We still dose not know how to write its `apply` method, but we know the signature of it.
// It takes `MKFact`, a.k.a. `oo一ooᴵ`, and returns `Fact`, a.k.a. `o一oᴵ`.
// Thus we have `oo一oo一ooᴵ`.
//
// We try to pass `MKFact` to `G`, `new G(new MKFact())`.
// Not possible. We need a bridge to connect `oo一ooᴵ` and `Tᴵ`,
// i.e. something takes a `MKFact` a.k.a. `oo一ooᴵ` and produces a `Tᴵ` for `G`.
class H implements T
{
    oooo f;
    H(oooo _f)
    {
        f = _f;
    }

    public oo apply(T x)
    {
        return ...;
    }
}
// Now we need to fill in the missing `apply` definition for `H`.
// It returns `o一oᴵ`. Do we have something returns `o一oᴵ`?
// `f.apply` happens to produce `o一oᴵ`.
class H implements T
{
    oooo f;
    H(oooo _f)
    {
        f = _f;
    }

    public oo apply(T x)
    {
        return f.apply(...);
    }
}
// `f.apply()` takes an `o一oᴵ`. Do we have something to transform `Tᴵ` to `o一oᴵ`?
// The `apply` method of `Tᴵ`.
// Do we have some class implements `Tᴵ`, except `H` itself?
// No.
// So we need to implement one?
// Wait. `G` implements `o一oᴵ` and its construction method takes `Tᴵ` as parameter.
// So `new G(x)` fills in the gap.
class H implements T
{
    oooo f;
    H(oooo _f)
    {
        f = _f;
    }

    public oo apply(T x)
    {
        return f.apply(new G(x));
    }
}
// In fact, this is the very definition given in the book.
//
// At last, let's complete `Y`.
class Y implements oooooo
{
    public oo apply(oooo f)
    {
        return ...;
    }
}
// We need something takes `oo一ooᴵ` and produces `o一oᴵ`.
// `H` takes `oo一ooᴵ` in its construction method, and produces `o一oᴵ` by its `apply` method.
class Y implements oooooo
{
    public oo apply(oooo f)
    {
        return new H(f).apply(...);
    }
}
// The parameter of `H(f).apply()` need to be a `Tᴵ`.
// Do we have something implements `Tᴵ`?
// `H` itself.
// So
class Y implements oooooo
{
    public oo apply(oooo f)
    {
        return new H(f).apply(new H(f));
    }
}
// The very definition given in the book.

// If you still have difficulties to understand it in Java,
// try use another language.
// For example,
//
// ```js
// // Poor man's Y combinator
// ((f => n => n == 0 ? 1 : n * f(f)(n - 1))
//   (f => n => n == 0 ? 1 : 0 * f(f)(n - 1))
//   3)
// ```
//
// It would be perfect if it is `f(n -1)` instead of `f(f(n)(n - 1))`.
// Let `g = f(f)`:
//
// ```js
// ((f => ((g => n => n == 0 ? 1 : n * g(n - 1)) f(f))
//   # same as above
//   3)
// ```
//
// Hmm, `(g => n => n == 0 ? 1 : n * g(n - 1)` is just what we want!
// Let `h = (g => n => n == 0 ? 1 : n * g(n - 1)`:
//
// ```js
// (((h =>
//    (f => h(f(f)))
//    (f => h(f(f))))
//      (g => n => n == 0 ? 1 : n * g(n - 1))
//      3)
// ```
//
// Look! The factorial logic is factor out: `(g => n => n == 0 ? 1 : n * g(n - 1))`.
// And this is the Y combinator.
//
// ```js
// Y =
//   (h =>
//    (f => h(f(f)))
//    (f => h(f(f))))
// ```
//
// To construct `fact` with Y combinator: `Y(g => n => n == 0 ? 1 : n * g(n - 1))`.
//
// Hope this helps to understand those `Y`, `H`, `G` classes in Java.

public class LittleJava
{
    public static void main(String[] args)
    {
        Fact fact = new Fact(new Dummy());
        System.out.println(fact.apply(new Integer(5)));
        MKFact mKFact = new MKFact();

        Fact newFact = (Fact) mKFact.apply(new Dummy());
        System.out.println(newFact.apply(new Integer(5)));

        Fact yFact = (Fact) new Y().apply(new MKFact());
        System.out.println(yFact.apply(new Integer(5)));

        System.out.println("static recursion version");
        System.out.println(StaticFact.fact(5));

        System.out.println("normal version");
        NormalFact normalFact = new NormalFact();
        System.out.println(normalFact.fact(5));

        System.out.println("Fact1");
        Fact1 fact1 = new Fact1();
        System.out.println(fact1.apply(fact1, 5));

        System.out.println("Fact2: poor man's Y combinator");
        Fact2 fact2 = new Fact2();
        System.out.println((fact2.apply(fact2)).apply(5));

        System.out.println("Fact3: g = f(f)");
        Fact3 fact3 = new Fact3();
        System.out.println((fact3.apply(fact3)).apply(5));

        System.out.println("YY");
        YY yy = new YY(new GClosure(new Fact3()));
        System.out.println(yy.apply(5));
    }
}