the second input argument type
* @param the third input argument type
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java
new file mode 100644
index 000000000..63c60c051
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java
@@ -0,0 +1,43 @@
+package com.jnape.palatable.lambda.functions.builtin.fn2;
+
+import com.jnape.palatable.lambda.functions.Fn1;
+import com.jnape.palatable.lambda.functions.Fn2;
+
+/**
+ * Function application, represented as a higher-order {@link Fn2} that receives an {@link Fn1} and its argument, and
+ * applies it. Useful for treating application as a combinator, e.g.:
+ *
+ * {@code
+ * List> fns = asList(x -> x + 1, x -> x, x -> x - 1);
+ * List args = asList(0, 1, 2);
+ * Iterable results = zipWith($(), fns, args); // [1, 1, 1]
+ * }
+ *
+ *
+ * @param the applied {@link Fn1 Fn1's} input type
+ * @param the applied {@link Fn1 Fn1's} output type
+ */
+public final class $ implements Fn2, A, B> {
+ private static final $, ?> INSTANCE = new $<>();
+
+ private $() {
+ }
+
+ @Override
+ public B checkedApply(Fn1 super A, ? extends B> fn, A a) {
+ return fn.apply(a);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static $ $() {
+ return ($) INSTANCE;
+ }
+
+ public static Fn1 $(Fn1 super A, ? extends B> fn) {
+ return $.$().apply(fn);
+ }
+
+ public static B $(Fn1 super A, ? extends B> fn, A a) {
+ return $.$(fn).apply(a);
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java
index 606e7d894..d651795b5 100644
--- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java
+++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java
@@ -8,6 +8,7 @@
import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functions.Fn2;
import com.jnape.palatable.lambda.functions.recursion.RecursiveResult;
+import com.jnape.palatable.lambda.functions.specialized.Lift;
import com.jnape.palatable.lambda.functions.specialized.Pure;
import com.jnape.palatable.lambda.functor.Applicative;
import com.jnape.palatable.lambda.functor.builtin.Lazy;
@@ -28,6 +29,9 @@
import static com.jnape.palatable.lambda.adt.choice.Choice2.a;
import static com.jnape.palatable.lambda.adt.choice.Choice2.b;
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
+import static com.jnape.palatable.lambda.functions.Fn0.fn0;
+import static com.jnape.palatable.lambda.functions.Fn1.withSelf;
+import static com.jnape.palatable.lambda.functions.builtin.fn2.$.$;
import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into;
import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler;
import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft;
@@ -62,21 +66,15 @@
* @param the element type
*/
public class IterateT, A> implements
- MonadT, IterateT, ?>> {
+ MonadT, IterateT, ?>> {
private final Pure pureM;
- private final ImmutableQueue> conses;
- private final ImmutableQueue>>, M>>, IterateT>> middles;
- private final ImmutableQueue> snocs;
+ private final ImmutableQueue>>, M>>, MonadRec>> spine;
private IterateT(Pure pureM,
- ImmutableQueue> conses,
- ImmutableQueue>>, M>>, IterateT>> middles,
- ImmutableQueue> snocs) {
+ ImmutableQueue>>, M>>, MonadRec>> spine) {
this.pureM = pureM;
- this.conses = conses;
- this.middles = middles;
- this.snocs = snocs;
+ this.spine = spine;
}
/**
@@ -86,7 +84,15 @@ private IterateT(Pure pureM,
* @return the embedded {@link Monad}
*/
public >>, M>> MMTA runIterateT() {
- return pureM., MonadRec, M>>apply(this).trampolineM(IterateT::resume).coerce();
+ MonadRec>>, M>>, MonadRec>>, M>
+ mSpine = pureM.apply(spine);
+ return mSpine.trampolineM(tSpine -> tSpine.head().>>, M>>, MonadRec>>, Maybe>>>, M>>match(
+ ___ -> pureM.apply(terminate(nothing())),
+ thunkOrReal -> thunkOrReal.match(
+ thunk -> thunk.apply().fmap(m -> m.match(
+ ___ -> recurse(tSpine.tail()),
+ t -> terminate(just(t.fmap(as -> new IterateT<>(pureM, as.spine.concat(tSpine.tail()))))))),
+ real -> real.fmap(a -> terminate(just(tuple(a, new IterateT<>(pureM, tSpine.tail())))))))).coerce();
}
/**
@@ -96,7 +102,7 @@ public >>, M>> MMTA runIter
* @return the cons'ed {@link IterateT}
*/
public final IterateT cons(MonadRec head) {
- return new IterateT<>(pureM, conses.pushFront(head), middles, snocs);
+ return new IterateT<>(pureM, spine.pushFront(b(head)));
}
/**
@@ -106,7 +112,7 @@ public final IterateT cons(MonadRec head) {
* @return the snoc'ed {@link IterateT}
*/
public final IterateT snoc(MonadRec last) {
- return new IterateT<>(pureM, conses, middles, snocs.pushBack(last));
+ return new IterateT<>(pureM, spine.pushBack(b(last)));
}
/**
@@ -116,13 +122,7 @@ public final IterateT snoc(MonadRec last) {
* @return the concatenated {@link IterateT}
*/
public IterateT concat(IterateT other) {
- return new IterateT<>(pureM,
- conses,
- middles.pushBack(b(new IterateT<>(pureM,
- snocs.concat(other.conses),
- other.middles,
- other.snocs))),
- ImmutableQueue.empty());
+ return new IterateT<>(pureM, spine.concat(other.spine));
}
/**
@@ -188,9 +188,19 @@ public > IterateT lift(MonadRec nb) {
* {@inheritDoc}
*/
@Override
+ @SuppressWarnings("RedundantTypeArguments")
public IterateT trampolineM(
Fn1 super A, ? extends MonadRec, IterateT>> fn) {
- return trampolineM(fn, ImmutableQueue.>>empty().pushBack(flatMap(fn)));
+ return $(withSelf(
+ (self, queued) -> suspended(
+ () -> pureM.>, MonadRec>, M>>apply(queued)
+ .trampolineM(q -> q.runIterateT().>, Maybe>>>>fmap(m -> m.match(
+ __ -> terminate(nothing()),
+ into((rr, tail) -> rr.biMap(
+ a -> fn.apply(a).>>coerce().concat(tail),
+ b -> just(tuple(b, self.apply(tail)))))))),
+ pureM)),
+ flatMap(fn));
}
/**
@@ -199,11 +209,15 @@ public IterateT trampolineM(
@Override
public IterateT flatMap(Fn1 super A, ? extends Monad>> f) {
return suspended(() -> maybeT(runIterateT())
- .flatMap(into((a, as) -> maybeT(f.apply(a)
- .>coerce()
- .concat(as.flatMap(f))
- .runIterateT())))
- .runMaybeT(), pureM);
+ .trampolineM(into((a, as) -> maybeT(
+ f.apply(a).>coerce().runIterateT()
+ .flatMap(maybePair -> maybePair.match(
+ fn0(() -> as.runIterateT()
+ .fmap(maybeResult -> maybeResult.fmap(RecursiveResult::recurse))),
+ t -> pureM.apply(just(terminate(t.fmap(mb -> mb.concat(as.flatMap(f))))))
+ )))))
+ .runMaybeT(),
+ pureM);
}
/**
@@ -219,7 +233,7 @@ public IterateT fmap(Fn1 super A, ? extends B> fn) {
*/
@Override
public IterateT pure(B b) {
- return singleton(runIterateT().pure(b));
+ return singleton(pureM.>apply(b));
}
/**
@@ -286,54 +300,6 @@ public IterateT discardR(Applicative> appB) {
return MonadT.super.discardR(appB).coerce();
}
- private MonadRec, Maybe>>>, M> resume() {
- return conses.head().match(
- __ -> middles.head().match(
- ___ -> snocs.head().match(
- ____ -> pureM.apply(terminate(nothing())),
- ma -> ma.fmap(a -> terminate(just(tuple(a, new IterateT<>(pureM,
- snocs.tail(),
- ImmutableQueue.empty(),
- ImmutableQueue.empty())))))),
- lazyOrStrict -> lazyOrStrict.match(
- lazy -> lazy.apply().fmap(maybeRes -> maybeRes.match(
- ___ -> recurse(new IterateT<>(pureM, ImmutableQueue.empty(), middles.tail(), snocs)),
- into((a, as) -> recurse(new IterateT<>(pureM,
- ImmutableQueue.singleton(pureM.apply(a)),
- ImmutableQueue.singleton(b(migrateForward(as))),
- ImmutableQueue.empty())))
- )),
- strict -> pureM.apply(recurse(migrateForward(strict))))),
- ma -> ma.fmap(a -> terminate(just(tuple(a, new IterateT<>(pureM, conses.tail(), middles, snocs))))));
- }
-
- private IterateT migrateForward(IterateT as) {
- if (middles.tail().isEmpty()) {
- return new IterateT<>(pureM, conses.concat(as.conses), as.middles, as.snocs.concat(snocs));
- }
-
- IterateT lasts = new IterateT<>(pureM, as.snocs, middles.tail(), snocs);
- return new IterateT<>(pureM, as.conses, as.middles.pushBack(b(lasts)), ImmutableQueue.empty());
- }
-
- private IterateT trampolineM(Fn1 super A, ? extends MonadRec, IterateT>> fn,
- ImmutableQueue>> queued) {
- return suspended(() -> {
- MonadRec>>, M> pureQueue = pureM.apply(queued);
- return pureQueue.trampolineM(
- q -> q.head().match(
- __ -> pureM.apply(terminate(nothing())),
- next -> next.runIterateT().flatMap(maybeMore -> maybeMore.match(
- __ -> pureM.apply(terminate(nothing())),
- t -> t.into((aOrB, rest) -> aOrB.match(
- a -> pureM.apply(recurse(q.pushFront(fn.apply(a).coerce()))),
- b -> trampolineM(fn, q.tail().pushFront(rest))
- .cons(pureM.apply(b))
- .runIterateT()
- .fmap(RecursiveResult::terminate)))))));
- }, pureM);
- }
-
/**
* Static factory method for creating an empty {@link IterateT}.
*
@@ -343,7 +309,7 @@ private IterateT trampolineM(Fn1 super A, ? extends MonadRec, A> IterateT empty(Pure pureM) {
- return new IterateT<>(pureM, ImmutableQueue.empty(), ImmutableQueue.empty(), ImmutableQueue.empty());
+ return new IterateT<>(pureM, ImmutableQueue.empty());
}
/**
@@ -355,10 +321,7 @@ public static , A> IterateT empty(Pure pureM)
* @return the singleton {@link IterateT}
*/
public static , A> IterateT singleton(MonadRec ma) {
- return new IterateT<>(Pure.of(ma),
- ImmutableQueue.>empty().pushFront(ma),
- ImmutableQueue.empty(),
- ImmutableQueue.empty());
+ return IterateT.empty(Pure.of(ma)).cons(ma);
}
/**
@@ -371,12 +334,7 @@ public static , A> IterateT singleton(MonadRec, A> IterateT iterateT(
MonadRec>>, M> unwrapped) {
- return new IterateT<>(
- Pure.of(unwrapped),
- ImmutableQueue.empty(),
- ImmutableQueue.>>, M>>, IterateT>>empty()
- .pushFront(a(() -> unwrapped)),
- ImmutableQueue.empty());
+ return suspended(() -> unwrapped, Pure.of(unwrapped));
}
/**
@@ -393,9 +351,7 @@ public static , A> IterateT of(
MonadRec ma, MonadRec... mas) {
@SuppressWarnings("varargs")
List> as = asList(mas);
- return foldLeft(IterateT::snoc,
- empty(Pure.of(ma)),
- com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons(ma, as));
+ return foldLeft(IterateT::snoc, singleton(ma), as);
}
/**
@@ -412,9 +368,10 @@ public static , A> IterateT of(
*/
public static , A, B> IterateT unfold(
Fn1 super B, ? extends MonadRec>, M>> fn, MonadRec mb) {
- return suspended(() -> maybeT(mb.flatMap(fn))
- .fmap(ab -> ab.fmap(b -> unfold(fn, mb.pure(b))))
- .runMaybeT(), Pure.of(mb));
+ Pure pureM = Pure.of(mb);
+ return $(withSelf((self, mmb) -> suspended(() -> maybeT(mmb.flatMap(fn))
+ .fmap(ab -> ab.>fmap(b -> self.apply(pureM.apply(b))))
+ .runMaybeT(), pureM)), mb);
}
/**
@@ -429,12 +386,7 @@ public static , A, B> IterateT unfold(
*/
public static , A> IterateT suspended(
Fn0>>, M>> thunk, Pure pureM) {
- return new IterateT<>(pureM,
- ImmutableQueue.empty(),
- ImmutableQueue
- .>>, M>>, IterateT>>empty()
- .pushFront(a(thunk)),
- ImmutableQueue.empty());
+ return new IterateT<>(pureM, ImmutableQueue.singleton(a(thunk)));
}
/**
@@ -451,4 +403,29 @@ public static IterateT, A> fromIterator(Iterator as) {
return nothing();
}), io(() -> as));
}
+
+ /**
+ * The canonical {@link Pure} instance for {@link IterateT}.
+ *
+ * @param pureM the argument {@link Monad} {@link Pure}
+ * @param the argument {@link Monad} witness
+ * @return the {@link Pure} instance
+ */
+ public static > Pure> pureIterateT(Pure pureM) {
+ return new Pure>() {
+ @Override
+ public IterateT checkedApply(A a) {
+ return liftIterateT().apply(pureM.>apply(a));
+ }
+ };
+ }
+
+ /**
+ * {@link Lift} for {@link IterateT}.
+ *
+ * @return the {@link Monad} lifted into {@link IterateT}
+ */
+ public static Lift> liftIterateT() {
+ return IterateT::singleton;
+ }
}
diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java
index c2e02fd94..95dcdfd21 100644
--- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java
+++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java
@@ -1,6 +1,7 @@
package com.jnape.palatable.lambda.monad.transformer.builtin;
import com.jnape.palatable.lambda.adt.Maybe;
+import com.jnape.palatable.lambda.adt.Unit;
import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functions.recursion.RecursiveResult;
import com.jnape.palatable.lambda.functions.specialized.Lift;
@@ -9,6 +10,7 @@
import com.jnape.palatable.lambda.functor.builtin.Compose;
import com.jnape.palatable.lambda.functor.builtin.Lazy;
import com.jnape.palatable.lambda.monad.Monad;
+import com.jnape.palatable.lambda.monad.MonadError;
import com.jnape.palatable.lambda.monad.MonadRec;
import com.jnape.palatable.lambda.monad.transformer.MonadT;
@@ -16,6 +18,8 @@
import static com.jnape.palatable.lambda.adt.Maybe.just;
import static com.jnape.palatable.lambda.adt.Maybe.nothing;
+import static com.jnape.palatable.lambda.adt.Unit.UNIT;
+import static com.jnape.palatable.lambda.functions.Fn0.fn0;
import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate;
@@ -26,7 +30,7 @@
* @param the carrier type
*/
public final class MaybeT, A> implements
- MonadT, MaybeT, ?>> {
+ MonadT, MaybeT, ?>>, MonadError> {
private final MonadRec, M> mma;
@@ -67,6 +71,24 @@ public MaybeT or(MaybeT other) {
a -> mMaybeA.pure(just(a)))));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MaybeT throwError(Unit unit) {
+ return maybeT(mma.pure(nothing()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MaybeT catchError(Fn1 super Unit, ? extends Monad>> recoveryFn) {
+ return maybeT(mma.flatMap(maybeA -> maybeA.match(
+ fn0(() -> recoveryFn.apply(UNIT).>coerce().runMaybeT()),
+ a -> mma.pure(just(a)))));
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java
index 443470c6e..e7f93a168 100644
--- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java
+++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java
@@ -120,7 +120,7 @@ public ReaderT pure(B b) {
*/
@Override
public ReaderT fmap(Fn1 super A, ? extends B> fn) {
- return MonadT.super.fmap(fn).coerce();
+ return readerT(r -> runReaderT(r).fmap(fn));
}
/**
diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java
index 071ce0dc5..219ad3e03 100644
--- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java
+++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java
@@ -144,7 +144,7 @@ public StateT pure(B b) {
*/
@Override
public StateT fmap(Fn1 super A, ? extends B> fn) {
- return MonadT.super.fmap(fn).coerce();
+ return stateT(s -> runStateT(s).fmap(t -> t.biMapL(fn)));
}
/**
@@ -292,7 +292,7 @@ public static , A> StateT stateT(
public static > Pure> pureStateT(Pure pureM) {
return new Pure>() {
@Override
- public StateT checkedApply(A a) throws Throwable {
+ public StateT checkedApply(A a) {
return stateT(pureM.>apply(a));
}
};
diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java
index 1f75ce450..3e3513a3a 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java
@@ -2,9 +2,15 @@
import org.junit.Test;
-import static com.jnape.palatable.lambda.adt.hlist.HList.*;
+import static com.jnape.palatable.lambda.adt.hlist.HList.cons;
+import static com.jnape.palatable.lambda.adt.hlist.HList.nil;
+import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList;
+import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
public class HListTest {
@@ -48,16 +54,15 @@ public void nilReusesInstance() {
}
@Test
- @SuppressWarnings({"EqualsWithItself", "EqualsBetweenInconvertibleTypes"})
public void equality() {
- assertTrue(nil().equals(nil()));
- assertTrue(cons(1, nil()).equals(cons(1, nil())));
+ assertEquals(nil(), nil());
+ assertEquals(cons(1, nil()), cons(1, nil()));
- assertFalse(cons(1, nil()).equals(nil()));
- assertFalse(nil().equals(cons(1, nil())));
+ assertNotEquals(cons(1, nil()), nil());
+ assertNotEquals(nil(), cons(1, nil()));
- assertFalse(cons(1, cons(2, nil())).equals(cons(1, nil())));
- assertFalse(cons(1, nil()).equals(cons(1, cons(2, nil()))));
+ assertNotEquals(cons(1, cons(2, nil())), cons(1, nil()));
+ assertNotEquals(cons(1, nil()), cons(1, cons(2, nil())));
}
@Test
diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java
index 57a9f85b7..7a577f2ce 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java
@@ -13,6 +13,7 @@
import static com.jnape.palatable.lambda.adt.hlist.HList.nil;
import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList;
+import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
import static com.jnape.palatable.lambda.adt.hlist.SingletonHList.pureSingletonHList;
import static org.junit.Assert.assertEquals;
@@ -46,6 +47,11 @@ public void cons() {
assertEquals(new Tuple2<>("0", singletonHList), singletonHList.cons("0"));
}
+ @Test
+ public void snoc() {
+ assertEquals(tuple((byte) 127, 'x'), singletonHList((byte) 127).snoc('x'));
+ }
+
@Test
public void intoAppliesHeadToFn() {
assertEquals("FOO", singletonHList("foo").into(String::toUpperCase));
diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java
index 32483a168..6637386ce 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java
@@ -63,11 +63,21 @@ public void tail() {
assertEquals(new SingletonHList<>(2), tuple2.tail());
}
+ @Test
+ public void init() {
+ assertEquals(new SingletonHList<>(1), tuple2.init());
+ }
+
@Test
public void cons() {
assertEquals(new Tuple3<>(0, tuple2), tuple2.cons(0));
}
+ @Test
+ public void snoc() {
+ assertEquals(tuple(Long.MAX_VALUE, 123, "hi"), tuple(Long.MAX_VALUE, 123).snoc("hi"));
+ }
+
@Test
public void accessors() {
assertEquals((Integer) 1, tuple2._1());
diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java
index 9ac57fcfb..a4cad8198 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java
@@ -18,6 +18,7 @@
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
import static com.jnape.palatable.lambda.adt.hlist.Tuple3.pureTuple;
import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat;
+import static java.time.Duration.ofSeconds;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
@@ -62,6 +63,12 @@ public void cons() {
assertEquals(new Tuple4<>(0, tuple3), tuple3.cons(0));
}
+ @Test
+ public void snoc() {
+ assertEquals(tuple("qux", Long.MIN_VALUE, 7, ofSeconds(13)),
+ tuple("qux", Long.MIN_VALUE, 7).snoc(ofSeconds(13)));
+ }
+
@Test
public void accessors() {
assertEquals((Integer) 1, tuple3._1());
@@ -119,4 +126,10 @@ public void staticPure() {
Tuple3 tuple = pureTuple(1, "2").apply('3');
assertEquals(tuple(1, "2", '3'), tuple);
}
+
+ @Test
+ public void init() {
+ assertEquals(tuple(1, 2),
+ tuple(1, 2, 3).init());
+ }
}
diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java
index b2a1d6319..13d5da838 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java
@@ -62,6 +62,11 @@ public void cons() {
assertEquals(new Tuple5<>(0, tuple4), tuple4.cons(0));
}
+ @Test
+ public void snoc() {
+ assertEquals(tuple("qux", 7, "foo", 13L, 17), tuple("qux", 7, "foo", 13L).snoc(17));
+ }
+
@Test
public void accessors() {
assertEquals((Integer) 1, tuple4._1());
@@ -122,4 +127,10 @@ public void staticPure() {
Tuple4 tuple = pureTuple(1, "2", '3').apply(true);
assertEquals(tuple(1, "2", '3', true), tuple);
}
+
+ @Test
+ public void init() {
+ assertEquals(tuple(1, 2, 3),
+ tuple(1, 2, 3, 4).init());
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java
index 83b300b97..7b81a2190 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java
@@ -63,6 +63,11 @@ public void cons() {
assertEquals(new HCons<>(0, tuple5), tuple5.cons(0));
}
+ @Test
+ public void snoc() {
+ assertEquals(tuple("a", 5, "b", 7, "c", 11), tuple("a", 5, "b", 7, "c").snoc(11));
+ }
+
@Test
public void accessors() {
assertEquals((Integer) 1, tuple5._1());
@@ -128,4 +133,10 @@ public void staticPure() {
Tuple5 tuple = pureTuple(1, "2", '3', true).apply(5f);
assertEquals(tuple(1, "2", '3', true, 5f), tuple);
}
+
+ @Test
+ public void init() {
+ assertEquals(tuple(1, 2, 3, 4),
+ tuple(1, 2, 3, 4, 5).init());
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java
index 902b4b254..880eee1e4 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java
@@ -64,6 +64,11 @@ public void cons() {
assertEquals(new HCons<>(0, tuple6), tuple6.cons(0));
}
+ @Test
+ public void snoc() {
+ assertEquals(tuple(5L, "a", 7, "b", 11, "c", 13), tuple(5L, "a", 7, "b", 11, "c").snoc(13));
+ }
+
@Test
public void accessors() {
assertEquals((Float) 2.0f, tuple6._1());
@@ -132,4 +137,10 @@ public void staticPure() {
Tuple6 tuple = pureTuple(1, "2", '3', true, 5f).apply((byte) 6);
assertEquals(tuple(1, "2", '3', true, 5f, (byte) 6), tuple);
}
+
+ @Test
+ public void init() {
+ assertEquals(tuple(1, 2, 3, 4, 5),
+ tuple(1, 2, 3, 4, 5, 6).init());
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java
index 0e8a68aec..d5b13fd24 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java
@@ -64,6 +64,11 @@ public void cons() {
assertEquals(new HCons<>(0, tuple7), tuple7.cons(0));
}
+ @Test
+ public void snoc() {
+ assertEquals(tuple("b", 7L, "c", 11, "d", 13, "e", 'f'), tuple("b", 7L, "c", 11, "d", 13, "e").snoc('f'));
+ }
+
@Test
public void accessors() {
assertEquals((Byte) (byte) 127, tuple7._1());
@@ -136,4 +141,10 @@ public void staticPure() {
pureTuple((byte) 1, (short) 2, 3, 4L, 5F, 6D).apply(true);
assertEquals(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true), tuple);
}
+
+ @Test
+ public void init() {
+ assertEquals(tuple(1, 2, 3, 4, 5, 6),
+ tuple(1, 2, 3, 4, 5, 6, 7).init());
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java
index 4d2a8cb76..ced5531b5 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java
@@ -14,6 +14,8 @@
import testsupport.traits.MonadRecLaws;
import testsupport.traits.TraversableLaws;
+import java.time.LocalDate;
+
import static com.jnape.palatable.lambda.adt.Maybe.just;
import static com.jnape.palatable.lambda.adt.Maybe.nothing;
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
@@ -67,6 +69,15 @@ public void cons() {
assertEquals(new HCons<>(0, tuple8), tuple8.cons(0));
}
+ @Test
+ public void snoc() {
+ LocalDate last = LocalDate.of(2020, 4, 14);
+ HCons> actual =
+ tuple("b", 7L, "c", 11, "d", 13, "e", 15L).snoc(last);
+ assertEquals("b", actual.head());
+ assertEquals(actual.tail(), tuple(7L, "c", 11, "d", 13, "e", 15L, last));
+ }
+
@Test
public void accessors() {
assertEquals((Short) (short) 65535, tuple8._1());
@@ -147,4 +158,10 @@ public void staticPure() {
pureTuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true).apply('8');
assertEquals(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true, '8'), tuple);
}
+
+ @Test
+ public void init() {
+ assertEquals(tuple(1, 2, 3, 4, 5, 6, 7),
+ tuple(1, 2, 3, 4, 5, 6, 7, 8).init());
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java
index 39400c6b0..047aea1db 100644
--- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java
+++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java
@@ -8,8 +8,8 @@
import testsupport.traits.Equivalence;
import testsupport.traits.FunctorLaws;
import testsupport.traits.MonadLaws;
-import testsupport.traits.MonadRecLaws;
import testsupport.traits.MonadReaderLaws;
+import testsupport.traits.MonadRecLaws;
import testsupport.traits.MonadWriterLaws;
import java.util.function.Function;
@@ -105,4 +105,9 @@ public void staticPure() {
Fn1 fn1 = Fn1.pureFn1().apply(1);
assertEquals((Integer) 1, fn1.apply("anything"));
}
+
+ @Test
+ public void withSelf() {
+ assertEquals((Integer) 15, Fn1.withSelf((f, x) -> x > 1 ? x + f.apply(x - 1) : x).apply(5));
+ }
}
diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java
new file mode 100644
index 000000000..166e99f29
--- /dev/null
+++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java
@@ -0,0 +1,21 @@
+package com.jnape.palatable.lambda.functions.builtin.fn2;
+
+import org.junit.Test;
+
+import static com.jnape.palatable.lambda.functions.Fn2.fn2;
+import static com.jnape.palatable.lambda.functions.builtin.fn2.$.$;
+import static org.junit.Assert.assertEquals;
+
+public class $Test {
+
+ @Test
+ public void application() {
+ assertEquals((Integer) 1, $(x -> x + 1, 0));
+ assertEquals((Integer) 1, $.$(x -> x + 1).apply(0));
+ }
+
+ @Test
+ public void curryingInference() {
+ assertEquals((Integer) 1, $($(fn2(Integer::sum), 0), 1));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java
index 00768b0c3..a0e974d43 100644
--- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java
+++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java
@@ -8,6 +8,7 @@
import com.jnape.palatable.lambda.functor.builtin.Lazy;
import com.jnape.palatable.lambda.functor.builtin.Writer;
import com.jnape.palatable.lambda.io.IO;
+import com.jnape.palatable.lambda.monoid.Monoid;
import com.jnape.palatable.traitor.annotations.TestTraits;
import com.jnape.palatable.traitor.framework.Subjects;
import com.jnape.palatable.traitor.runners.Traits;
@@ -23,11 +24,13 @@
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
import static com.jnape.palatable.lambda.adt.Maybe.just;
import static com.jnape.palatable.lambda.adt.Maybe.nothing;
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
+import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
import static com.jnape.palatable.lambda.functions.builtin.fn2.LTE.lte;
import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times;
import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse;
@@ -40,6 +43,10 @@
import static com.jnape.palatable.lambda.functor.builtin.Writer.writer;
import static com.jnape.palatable.lambda.io.IO.io;
import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.empty;
+import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.iterateT;
+import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.liftIterateT;
+import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.of;
+import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.pureIterateT;
import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.singleton;
import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.unfold;
import static com.jnape.palatable.lambda.monoid.builtin.AddAll.addAll;
@@ -242,4 +249,68 @@ public void concatIsStackSafe() {
assertEquals(new Identity<>(10_000),
bigIterateT.fold((x, y) -> new Identity<>(x + y), new Identity<>(0)));
}
+
+ @Test
+ public void staticPure() {
+ assertEquals(new Identity<>(singletonList(1)),
+ pureIterateT(pureIdentity())
+ ., Integer>>apply(1)
+ ., Identity>>toCollection(ArrayList::new));
+ }
+
+ @Test
+ public void staticLift() {
+ assertEquals(new Identity<>(singletonList(1)),
+ liftIterateT()
+ ., IterateT, Integer>>apply(new Identity<>(1))
+ ., Identity>>toCollection(ArrayList::new));
+ }
+
+ @Test
+ public void trampolineMRecursesBreadth() {
+ IterateT, Integer> firstFour = of(new Identity<>(1), new Identity<>(2), new Identity<>(3), new Identity<>(4));
+ IterateT, Integer> trampolined = firstFour
+ .trampolineM(x -> (x % 3 == 0 && (x < 30))
+ ? of(new Identity<>(terminate(x + 10)), new Identity<>(recurse(x + 11)), new Identity<>(recurse(x + 12)), new Identity<>(recurse(x + 13)))
+ : singleton(new Identity<>(terminate(x))));
+ assertThat(trampolined, iterates(1, 2, 13, 14, 25, 26, 37, 38, 39, 40, 28, 16, 4));
+ }
+
+ @Test
+ public void flatMapToEmptyStackSafety() {
+ assertEquals(new Identity<>(UNIT),
+ unfold(x -> new Identity<>(x <= STACK_EXPLODING_NUMBER ? just(tuple(x, x + 1)) : nothing()),
+ new Identity<>(1))
+ .flatMap(constantly(iterateT(new Identity<>(nothing()))))
+ .forEach(constantly(new Identity<>(UNIT))));
+
+ assertEquals((Integer) 1_250_025_000,
+ unfold(x -> listen(x <= STACK_EXPLODING_NUMBER ? just(tuple(x, x + 1)) : nothing()),
+ Writer.listen(1))
+ .flatMap(x -> iterateT(writer(tuple(nothing(), x))))
+ .>forEach(constantly(listen(UNIT)))
+ .runWriter(Monoid.monoid(Integer::sum, 0))
+ ._2());
+ }
+
+ @Test
+ public void flatMapCostsNoMoreEffortThanRequiredToYieldFirstValue() {
+ AtomicInteger flatMapCost = new AtomicInteger(0);
+ AtomicInteger unfoldCost = new AtomicInteger(0);
+ assertEquals(just(1),
+ unfold(x -> {
+ unfoldCost.incrementAndGet();
+ return new Identity<>(x <= 10 ? just(tuple(x, x + 1)) : nothing());
+ },
+ new Identity<>(1))
+ .flatMap(x -> {
+ flatMapCost.incrementAndGet();
+ return singleton(new Identity<>(x));
+ })
+ ., Integer>>>>>runIterateT()
+ .runIdentity()
+ .fmap(Tuple2::_1));
+ assertEquals(1, flatMapCost.get());
+ assertEquals(1, unfoldCost.get());
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java
index 2c578fbc6..1a81e1c57 100644
--- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java
+++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java
@@ -22,14 +22,18 @@
import static com.jnape.palatable.lambda.adt.Either.right;
import static com.jnape.palatable.lambda.adt.Maybe.just;
import static com.jnape.palatable.lambda.adt.Maybe.nothing;
+import static com.jnape.palatable.lambda.adt.Unit.UNIT;
import static com.jnape.palatable.lambda.functions.builtin.fn2.GT.gt;
import static com.jnape.palatable.lambda.functions.builtin.fn2.LT.lt;
import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity;
import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy;
import static com.jnape.palatable.lambda.io.IO.io;
-import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.*;
+import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.liftMaybeT;
+import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.maybeT;
+import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.pureMaybeT;
import static com.jnape.palatable.traitor.framework.Subjects.subjects;
import static org.junit.Assert.assertEquals;
+import static testsupport.assertion.MonadErrorAssert.assertLaws;
@RunWith(Traits.class)
public class MaybeTTest {
@@ -41,6 +45,13 @@ public class MaybeTTest {
maybeT(left("foo")));
}
+ @Test
+ public void monadError() {
+ assertLaws(subjects(maybeT(new Identity<>(nothing())), maybeT(new Identity<>(just(1)))),
+ UNIT,
+ e -> maybeT(new Identity<>(just(2))));
+ }
+
@Test
public void lazyZip() {
assertEquals(maybeT(right(just(2))),
diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java
index 6972c9fb6..cbcf1ef8b 100644
--- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java
+++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java
@@ -2,6 +2,7 @@
import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.lambda.adt.Unit;
+import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functor.builtin.Identity;
import com.jnape.palatable.lambda.io.IO;
import com.jnape.palatable.traitor.annotations.TestTraits;
@@ -12,6 +13,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
import static com.jnape.palatable.lambda.adt.Maybe.just;
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
@@ -82,4 +84,17 @@ public void composedZip() {
.unsafePerformAsyncIO(Executors.newFixedThreadPool(2))
.join();
}
+
+ @Test
+ public void fmapInteractions() {
+ AtomicInteger invocations = new AtomicInteger(0);
+ ReaderT, Integer> readerT = readerT(i -> {
+ invocations.incrementAndGet();
+ return new Identity<>(i);
+ });
+
+ Fn1 plusOne = x -> x + 1;
+ readerT.fmap(plusOne).fmap(plusOne).fmap(plusOne).runReaderT(0);
+ assertEquals(1, invocations.get());
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java
index b51888c9d..785165952 100644
--- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java
+++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java
@@ -16,15 +16,18 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import static com.jnape.palatable.lambda.adt.Maybe.just;
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
+import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity;
import static com.jnape.palatable.lambda.optics.functions.Set.set;
import static com.jnape.palatable.lambda.optics.lenses.ListLens.elementAt;
import static java.util.Arrays.asList;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
import static testsupport.matchers.StateTMatcher.whenEvaluated;
import static testsupport.matchers.StateTMatcher.whenExecuted;
import static testsupport.matchers.StateTMatcher.whenRun;
@@ -119,4 +122,18 @@ public void staticLift() {
assertThat(StateT.liftStateT().apply(new Identity<>(1)),
whenRun("foo", new Identity<>(tuple(1, "foo"))));
}
+
+ @Test
+ public void fmapInteractions() {
+ AtomicInteger invocations = new AtomicInteger(0);
+ StateT., Integer>gets(x -> {
+ invocations.incrementAndGet();
+ return new Identity<>(x);
+ })
+ .fmap(id())
+ .fmap(id())
+ .fmap(id())
+ .>>runStateT(0);
+ assertEquals(1, invocations.get());
+ }
}
\ No newline at end of file