LinkedList.java
package com.acme.adt;

import static com.acme.adt.LinkedList.List.*;

@SuppressWarnings("unchecked")
public class LinkedList {
    public abstract static class List<T> {
        public interface Visitor<T, S> {
            default S get(List<T> receiver) {
                S result = receiver.accept(this);
                return result == null ? defaultImplementation(receiver) : result;
            }

            default S getOrDefault(List<T> receiver, S defaultValue) {
                S result = receiver.accept(this);
                return result == null ? defaultValue : result;
            }

            default S nil() {
                return null;
            }

            default S cons(T first, List<T> rest) {
                return null;
            }

            default S defaultImplementation(List<T> e) {
                throw new UnsupportedOperationException();
            }
        }

        public abstract <S> S accept(Visitor<T, S> visitor);

        public static <T> List<T> nil() {
            return new List<T>() {
                public <S> S accept(Visitor<T, S> visitor) {
                    return visitor.nil();
                }
            };
        }

        public static <T> List<T> cons(T first, List<T> rest) {
            return new List<T>() {
                public <S> S accept(Visitor<T, S> visitor) {
                    return visitor.cons(first, rest);
                }
            };
        }

    }

    public static <T> int length1(List<T> $0) {
        return new List.Visitor<T, Integer>() {
            public Integer nil() {
                return 0;
            }

            public Integer cons(T first, List<T> rest) {
                return (1 + LinkedList.length1(rest));
            }

        }
                .get($0);
    }

    public static <T> int length2(List<T> l) {
        return new List.Visitor<T, Integer>() {
            public Integer nil() {
                return 0;
            }

            public Integer cons(T first, List<T> rest) {
                return (1 + LinkedList.length2(rest));
            }

        }
                .get(l);
    }

    public static void main(String[] args) {
        List<Integer> l = cons(1, cons(2, nil()));
        assert length1(l) == 2;
        assert length2(l) == 2;
    }

}