LinkedList.java
package church.util;

import java.util.function.BiFunction;
import java.util.function.Predicate;

import church.lang.ArraySupport;
import church.util.CollectionsSupport.EquivalenceRelation;
import church.util.CollectionsSupport.LeftFold;
import church.util.CollectionsSupport.RightFold;

public abstract class LinkedList<T> {
    public interface Visitor<T, S> {
        S empty();

        S cons(T element, LinkedList<T> rest);
    }

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

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

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

    @SuppressWarnings("unchecked")
    public static <T> LinkedList<T> list(T... elements) {
        return ArraySupport.<T>rightFold().reduce(LinkedList::cons, empty(), elements);
    }

    public static <T> int size(LinkedList<T> list) {
        return list.accept(new Visitor<T, Integer>() {
            @Override
            public Integer empty() {
                return 0;
            }

            @Override
            public Integer cons(T element, LinkedList<T> rest) {
                return 1 + rest.accept(this);
            }
        });
    }

    public static <T> T find(LinkedList<T> list, Predicate<T> selector, T defaultValue) {
        return list.accept(new Visitor<T, T>() {
            @Override
            public T empty() {
                return defaultValue;
            }

            @Override
            public T cons(T entry, LinkedList<T> rest) {
                return selector.test(entry) ? entry : rest.accept(this);
            }
        });
    }

    public static <T> boolean contains(EquivalenceRelation<T> equivalence, LinkedList<T> list, T example) {
        return find(list, element -> equivalence.equal(example, element), null) != null;
    }

    public static <T> LeftFold<LinkedList<T>, T> leftFold() {
        return new LeftFold<LinkedList<T>, T>() {
            @Override
            public <R> R reduce(BiFunction<R, T, R> accumulator, R initialValue, LinkedList<T> list) {
                // Pure functional version, allocates O(n) new Visitors.
                /* 
                return new Object() { 
                    private R recurse(LinkedList<T> l, R result) { 
                        return l.accept(new Visitor<T, R>() { 
                            @Override 
                            public R empty() { 
                                return result; 
                            } 
 
                            @Override 
                            public R cons(T entry, LinkedList<T> rest) { 
                                return recurse(rest, accumulator.apply(result, entry)); 
                            } 
                        }); 
                    } 
                }.recurse(list, initialValue); 
                */
//                Iterative version (using assignment).
                return list.accept(new Visitor<T, R>() {
                    private R result = initialValue;

                    @Override
                    public R empty() {
                        return result;
                    }

                    @Override
                    public R cons(T entry, LinkedList<T> rest) {
                        result = accumulator.apply(result, entry);
                        return rest.accept(this);
                    }
                });
            }
        };
    }

    public static <T> RightFold<LinkedList<T>, T> rightFold() {
        return new RightFold<LinkedList<T>, T>() {
            @Override
            public <R> R reduce(BiFunction<T, R, R> accumulator, R initialValue, LinkedList<T> list) {
                return list.accept(new Visitor<T, R>() {
                    @Override
                    public R empty() {
                        return initialValue;
                    }

                    @Override
                    public R cons(T entry, LinkedList<T> rest) {
                        return accumulator.apply(entry, rest.accept(this));
                    }
                });
            }
        };
    }
}