diff --git a/functional-programming-java/src/main/java/org/gym/fp/fpjava/collection/List.java b/functional-programming-java/src/main/java/org/gym/fp/fpjava/collection/List.java new file mode 100644 index 0000000..464d9e3 --- /dev/null +++ b/functional-programming-java/src/main/java/org/gym/fp/fpjava/collection/List.java @@ -0,0 +1,179 @@ +package org.gym.fp.fpjava.collection; + +import org.gym.fp.fpjava.type.Function; +import org.gym.fp.fpjava.type.TailCall; + +import static org.gym.fp.fpjava.type.TailCall.ret; +import static org.gym.fp.fpjava.type.TailCall.suspend; + +public abstract class List { + + public abstract A head(); + + public abstract List tail(); + + public abstract boolean isEmpty(); + + public abstract List setHead(A a); + + public abstract List drop(int n); + + public abstract List dropWhile(Function predicate); + + public abstract List reverse(); + + public abstract List init(); + + public List cons(A a) { + return new Cons<>(a, this); + } + + @SuppressWarnings("rawtypes") + public static final List NIL = new Nil(); + + private List() { + } + + private static class Nil extends List { + + private Nil() { + } + + @Override + public A head() { + throw new IllegalStateException("head called en empty list"); + } + + @Override + public List tail() { + throw new IllegalStateException("tail called en empty list"); + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public List setHead(A a) { + throw new IllegalStateException("setHead called on empty list"); + } + + @Override + public List drop(int n) { + return this; + } + + @Override + public List dropWhile(Function predicate) { + return this; + } + + @Override + public List reverse() { + return this; + } + + @Override + public List init() { + throw new IllegalStateException("init called on empty list"); + } + + @Override + public String toString() { + return "[NIL]"; + } + } + + private static class Cons extends List { + private final A head; + private final List tail; + + private Cons(A head, List tail) { + this.head = head; + this.tail = tail; + } + + @Override + public A head() { + return head; + } + + @Override + public List tail() { + return tail; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public List setHead(A h) { + return new Cons<>(h, tail()); + } + + @Override + public List drop(int n) { + return n <= 0 ? this : drop(this, n).eval(); + } + + @Override + public List dropWhile(Function predicate) { + return dropWhile(this, predicate).eval(); + } + + @Override + public List reverse() { + return reverse(list(), this).eval(); + } + + @Override + public List init() { + return reverse().tail().reverse(); + } + + public TailCall> reverse(List acc, List list) { + return list.isEmpty() + ? ret(acc) + : suspend(() -> reverse(new Cons<>(list.head(), acc), list.tail())); + } + + private TailCall> dropWhile(List list, Function predicate) { + return !list.isEmpty() && predicate.apply(list.head()) + ? suspend(() -> dropWhile(list.tail(), predicate)) + : ret(list); + } + + private TailCall> drop(List list, int n) { + return n <= 0 || list.isEmpty() + ? ret(list) + : suspend(() -> drop(list.tail(), n - 1)); + } + + @Override + public String toString() { + return String.format("[%sNIL]", toString(new StringBuilder(), this).eval()); + } + + private TailCall toString(StringBuilder acc, List list) { + return list.isEmpty() + ? ret(acc) + : suspend(() -> toString(acc.append(list.head()).append(", "), list.tail())); + } + } + + public static List list() { + return NIL; + } + + @SafeVarargs + public static List list(A... elements) { + List n = list(); + for (int i = elements.length - 1; i >= 0; i--) { + n = new Cons<>(elements[i], n); + } + return n; + } +}