java-cons

This small, one interface project is a try to reimplement the mechanics used in Rackets cons cells in Java.
git clone git://git.oshgnacknak.de/java-cons.git
Log | Files | Refs | README

commit 8783d82a0b23e924b0afe0b8034619a5babeef1c
Author: Oshgnacknak <osh@oshgnacknak.de>
Date:   Thu, 14 Jan 2021 23:52:28 +0100

Java Cons

Diffstat:
Asrc/Cons.java | 31+++++++++++++++++++++++++++++++
Asrc/ConsTest.java | 21+++++++++++++++++++++
Asrc/Empty.java | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/EmptyTest.java | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/NonEmpty.java | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/NonEmptyTest.java | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 278 insertions(+), 0 deletions(-)

diff --git a/src/Cons.java b/src/Cons.java @@ -0,0 +1,30 @@ +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + +public interface Cons<V> { + + V first(); + + Cons<V> rest(); + + <W> Cons<W> map(Function<V, W> mapper); + + Cons<V> filter(Predicate<V> predicate); + + <W> W foldl(BiFunction<V, W, W> f, W init); + + <W> W foldr(BiFunction<V, W, W> f, W init); + + @SafeVarargs + static <V> Cons<V> of(V... arr) { + return fromArray(0, arr); + } + + private static <V> Cons<V> fromArray(int n, V[] arr) { + if (n >= arr.length) { + return new Empty<>(); + } + return new NonEmpty<>(arr[n], fromArray(n+1, arr)); + } +}+ \ No newline at end of file diff --git a/src/ConsTest.java b/src/ConsTest.java @@ -0,0 +1,20 @@ +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ConsTest { + + @Test + void testOf() { + var cons = Cons.of(1, 2, 3, 4); + assertEquals(3, cons.rest().rest().first()); + assertEquals("(cons 1 (cons 2 (cons 3 (cons 4 empty))))", cons.toString()); + } + + @Test + void testOfNone() { + var cons = Cons.of(); + assertEquals(new Empty<>(), cons); + assertEquals("empty", cons.toString()); + } +}+ \ No newline at end of file diff --git a/src/Empty.java b/src/Empty.java @@ -0,0 +1,46 @@ +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + +public class Empty<V> implements Cons<V> { + + @Override + public V first() { + throw new UnsupportedOperationException(); + } + + @Override + public Cons<V> rest() { + throw new UnsupportedOperationException(); + } + + @Override + public <W> Cons<W> map(Function<V, W> mapper) { + return new Empty<>(); + } + + @Override + public Cons<V> filter(Predicate<V> predicate) { + return new Empty<>(); + } + + @Override + public <W> W foldl(BiFunction<V, W, W> f, W init) { + return init; + } + + @Override + public <W> W foldr(BiFunction<V, W, W> f, W init) { + return init; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Empty; + } + + @Override + public String toString() { + return "empty"; + } +}+ \ No newline at end of file diff --git a/src/EmptyTest.java b/src/EmptyTest.java @@ -0,0 +1,43 @@ +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EmptyTest { + + Cons<Integer> empty = new Empty<>(); + + @Test + void testFirst() { + assertThrows(UnsupportedOperationException.class, () -> empty.first()); + } + + @Test + void testRest() { + assertThrows(UnsupportedOperationException.class, () -> empty.rest()); + } + + @Test + void testMap() { + assertEquals(new Empty<>(), empty.map(n -> n+1)); + } + + @Test + void testFilter() { + assertEquals(new Empty<>(), empty.filter(n -> n>4)); + } + + @Test + void testFoldl() { + assertEquals(4, empty.foldl(Integer::sum, 4)); + } + + @Test + void testFoldr() { + assertEquals(4, empty.foldr(Integer::sum, 4)); + } + + @Test + void testToString() { + assertEquals("empty", empty.toString()); + } +}+ \ No newline at end of file diff --git a/src/NonEmpty.java b/src/NonEmpty.java @@ -0,0 +1,68 @@ +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + +public class NonEmpty<V> implements Cons<V> { + + private final V first; + private final Cons<V> rest; + + public NonEmpty(V first, Cons<V> rest) { + this.first = first; + this.rest = rest; + } + + @Override + public V first() { + return first; + } + + @Override + public Cons<V> rest() { + return rest; + } + + @Override + public <W> Cons<W> map(Function<V, W> mapper) { + return new NonEmpty<>(mapper.apply(first), rest.map(mapper)); + } + + @Override + public Cons<V> filter(Predicate<V> predicate) { + var newRest = rest.filter(predicate); + return predicate.test(first) ? + new NonEmpty<>(first, newRest.filter(predicate)) : + newRest; + } + + @Override + public <W> W foldl(BiFunction<V, W, W> f, W init) { + var newInit = f.apply(first, init); + return rest.foldl(f, newInit); + } + + @Override + public <W> W foldr(BiFunction<V, W, W> f, W init) { + var value = rest.foldr(f, init); + return f.apply(first, value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Cons)) return false; + Cons<?> cons = (Cons<?>) o; + return Objects.equals(first, cons.first()) && Objects.equals(rest, cons.rest()); + } + + @Override + public int hashCode() { + return Objects.hash(first, rest); + } + + @Override + public String toString() { + return "(cons " + first + " " + rest + ")"; + } +} diff --git a/src/NonEmptyTest.java b/src/NonEmptyTest.java @@ -0,0 +1,66 @@ +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class NonEmptyTest { + + Cons<Integer> cons = new NonEmpty<>(1, new NonEmpty<>(2, new NonEmpty<>(3, new Empty<>()))); + Cons<String> stringCons = new NonEmpty<>("Hallo", new NonEmpty<>("Welt", new Empty<>())); + + @Test + void first() { + assertEquals(1, cons.first()); + } + + @Test + void rest() { + var expected = new NonEmpty<>(2, new NonEmpty<>(3, new Empty<>())); + assertEquals(expected, cons.rest()); + + var sExpected = new NonEmpty<>("Welt", new Empty<>()); + assertEquals(sExpected, stringCons.rest()); + + assertThrows(UnsupportedOperationException.class, () -> + cons.rest().rest().rest().rest().rest()); + } + + @Test + void map() { + var expected = new NonEmpty<>(1, new NonEmpty<>(4, new NonEmpty<>(9, new Empty<>()))); + assertEquals(expected, cons.map(n -> n*n)); + + expected = new NonEmpty<>(5, new NonEmpty<>(4, new Empty<>())); + assertEquals(expected, stringCons.map(String::length)); + } + + @Test + void filter() { + var expected = new NonEmpty<>(1, new NonEmpty<>(3, new Empty<>())); + assertEquals(expected, cons.filter(n -> n % 2 == 1)); + assertEquals(new Empty<>(), cons.filter(n -> n > 10)); + + var sExpected = new NonEmpty<>("Hallo", new Empty<>()); + assertEquals(sExpected, stringCons.filter(s -> s.startsWith("Ha"))); + } + + @Test + void foldl() { + assertEquals(6, cons.foldl(Integer::sum, 0)); + assertEquals("WeltHallo", stringCons.foldl(String::concat, "")); + } + + @Test + void foldr() { + assertEquals(6, cons.foldr(Integer::sum, 0)); + assertEquals("HalloWelt", stringCons.foldr(String::concat, "")); + } + + @Test + void testToString() { + var expected = "(cons 1 (cons 2 (cons 3 empty)))"; + assertEquals(expected, cons.toString()); + + expected = "(cons Hallo (cons empty))"; + assertEquals(expected, stringCons.toString()); + } +}+ \ No newline at end of file