Skip to content

Commit 6e95041

Browse files
committed
- initial MonadRec interface
- MonadRec impls for Identity, Either, and Maybe - laws
1 parent 6982591 commit 6e95041

File tree

8 files changed

+129
-9
lines changed

8 files changed

+129
-9
lines changed

src/main/java/com/jnape/palatable/lambda/adt/Either.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.jnape.palatable.lambda.functions.Fn0;
66
import com.jnape.palatable.lambda.functions.Fn1;
77
import com.jnape.palatable.lambda.functions.Fn2;
8+
import com.jnape.palatable.lambda.functions.recursion.RecursiveResult;
89
import com.jnape.palatable.lambda.functions.specialized.Pure;
910
import com.jnape.palatable.lambda.functions.specialized.SideEffect;
1011
import com.jnape.palatable.lambda.functor.Applicative;
@@ -13,13 +14,16 @@
1314
import com.jnape.palatable.lambda.io.IO;
1415
import com.jnape.palatable.lambda.monad.Monad;
1516
import com.jnape.palatable.lambda.monad.MonadError;
17+
import com.jnape.palatable.lambda.monad.MonadRec;
1618
import com.jnape.palatable.lambda.traversable.Traversable;
1719

1820
import java.util.Objects;
1921

2022
import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
2123
import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
2224
import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft;
25+
import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate;
26+
import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline;
2327
import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy;
2428
import static com.jnape.palatable.lambda.io.IO.io;
2529
import static java.util.Arrays.asList;
@@ -35,6 +39,7 @@
3539
public abstract class Either<L, R> implements
3640
CoProduct2<L, R, Either<L, R>>,
3741
MonadError<L, R, Either<L, ?>>,
42+
MonadRec<R, Either<L, ?>>,
3843
Traversable<R, Either<L, ?>>,
3944
Bifunctor<L, R, Either<?, ?>> {
4045

@@ -134,6 +139,16 @@ public <R2> Either<L, R2> flatMap(Fn1<? super R, ? extends Monad<R2, Either<L, ?
134139
return match(Either::left, rightFn.fmap(Monad<R2, Either<L, ?>>::coerce));
135140
}
136141

142+
/**
143+
* {@inheritDoc}
144+
*/
145+
@Override
146+
public <B> Either<L, B> trampolineM(Fn1<? super R, ? extends MonadRec<RecursiveResult<R, B>, Either<L, ?>>> fn) {
147+
return match(Either::left, trampoline(a -> fn.apply(a).<Either<L, RecursiveResult<R, B>>>coerce()
148+
.match(l -> terminate(left(l)),
149+
aOrB -> aOrB.fmap(Either::right))));
150+
}
151+
137152
@Override
138153
public final Either<R, L> invert() {
139154
return match(Either::right, Either::left);

src/main/java/com/jnape/palatable/lambda/adt/Maybe.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
88
import com.jnape.palatable.lambda.functions.Fn0;
99
import com.jnape.palatable.lambda.functions.Fn1;
10+
import com.jnape.palatable.lambda.functions.recursion.RecursiveResult;
1011
import com.jnape.palatable.lambda.functions.specialized.Pure;
1112
import com.jnape.palatable.lambda.functor.Applicative;
1213
import com.jnape.palatable.lambda.functor.Functor;
1314
import com.jnape.palatable.lambda.functor.builtin.Lazy;
1415
import com.jnape.palatable.lambda.io.IO;
1516
import com.jnape.palatable.lambda.monad.Monad;
1617
import com.jnape.palatable.lambda.monad.MonadError;
18+
import com.jnape.palatable.lambda.monad.MonadRec;
1719
import com.jnape.palatable.lambda.traversable.Traversable;
1820

1921
import java.util.Objects;
@@ -24,6 +26,8 @@
2426
import static com.jnape.palatable.lambda.functions.Fn0.fn0;
2527
import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
2628
import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
29+
import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate;
30+
import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline;
2731
import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy;
2832
import static com.jnape.palatable.lambda.io.IO.io;
2933

@@ -37,6 +41,7 @@
3741
public abstract class Maybe<A> implements
3842
CoProduct2<Unit, A, Maybe<A>>,
3943
MonadError<Unit, A, Maybe<?>>,
44+
MonadRec<A, Maybe<?>>,
4045
Traversable<A, Maybe<?>> {
4146

4247
private Maybe() {
@@ -194,6 +199,16 @@ public final <B> Maybe<B> flatMap(Fn1<? super A, ? extends Monad<B, Maybe<?>>> f
194199
return match(constantly(nothing()), f.fmap(Monad<B, Maybe<?>>::coerce));
195200
}
196201

202+
/**
203+
* {@inheritDoc}
204+
*/
205+
@Override
206+
public <B> Maybe<B> trampolineM(Fn1<? super A, ? extends MonadRec<RecursiveResult<A, B>, Maybe<?>>> fn) {
207+
return match(constantly(nothing()), trampoline(a -> fn.apply(a).<Maybe<RecursiveResult<A, B>>>coerce()
208+
.match(constantly(terminate(nothing())),
209+
aOrB -> aOrB.fmap(Maybe::just))));
210+
}
211+
197212
/**
198213
* {@inheritDoc}
199214
*/

src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
package com.jnape.palatable.lambda.functor.builtin;
22

33
import com.jnape.palatable.lambda.functions.Fn1;
4+
import com.jnape.palatable.lambda.functions.recursion.RecursiveResult;
45
import com.jnape.palatable.lambda.functions.specialized.Pure;
56
import com.jnape.palatable.lambda.functor.Applicative;
67
import com.jnape.palatable.lambda.monad.Monad;
8+
import com.jnape.palatable.lambda.monad.MonadRec;
79
import com.jnape.palatable.lambda.traversable.Traversable;
810

911
import java.util.Objects;
1012

13+
import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline;
14+
1115
/**
1216
* A functor over some value of type <code>A</code> that can be mapped over and retrieved later.
1317
*
1418
* @param <A> the value type
1519
*/
16-
public final class Identity<A> implements Monad<A, Identity<?>>, Traversable<A, Identity<?>> {
20+
public final class Identity<A> implements MonadRec<A, Identity<?>>, Traversable<A, Identity<?>> {
1721

1822
private final A a;
1923

@@ -43,7 +47,7 @@ public <B> Identity<B> flatMap(Fn1<? super A, ? extends Monad<B, Identity<?>>> f
4347
*/
4448
@Override
4549
public <B> Identity<B> fmap(Fn1<? super A, ? extends B> fn) {
46-
return Monad.super.<B>fmap(fn).coerce();
50+
return MonadRec.super.<B>fmap(fn).coerce();
4751
}
4852

4953
/**
@@ -68,23 +72,23 @@ public <B> Identity<B> zip(Applicative<Fn1<? super A, ? extends B>, Identity<?>>
6872
@Override
6973
public <B> Lazy<Identity<B>> lazyZip(
7074
Lazy<? extends Applicative<Fn1<? super A, ? extends B>, Identity<?>>> lazyAppFn) {
71-
return Monad.super.lazyZip(lazyAppFn).fmap(Monad<B, Identity<?>>::coerce);
75+
return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<B, Identity<?>>::coerce);
7276
}
7377

7478
/**
7579
* {@inheritDoc}
7680
*/
7781
@Override
7882
public <B> Identity<B> discardL(Applicative<B, Identity<?>> appB) {
79-
return Monad.super.discardL(appB).coerce();
83+
return MonadRec.super.discardL(appB).coerce();
8084
}
8185

8286
/**
8387
* {@inheritDoc}
8488
*/
8589
@Override
8690
public <B> Identity<A> discardR(Applicative<B, Identity<?>> appB) {
87-
return Monad.super.discardR(appB).coerce();
91+
return MonadRec.super.discardR(appB).coerce();
8892
}
8993

9094
/**
@@ -98,6 +102,15 @@ AppTrav extends Applicative<TravB, App>> AppTrav traverse(Fn1<? super A, ? exten
98102
return (AppTrav) fn.apply(runIdentity()).fmap(Identity::new);
99103
}
100104

105+
/**
106+
* {@inheritDoc}
107+
*/
108+
@Override
109+
public <B> Identity<B> trampolineM(Fn1<? super A, ? extends MonadRec<RecursiveResult<A, B>, Identity<?>>> fn) {
110+
return new Identity<>(trampoline(a -> fn.apply(a).<Identity<RecursiveResult<A, B>>>coerce().runIdentity(),
111+
runIdentity()));
112+
}
113+
101114
@Override
102115
public boolean equals(Object other) {
103116
return other instanceof Identity && Objects.equals(a, ((Identity) other).a);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.jnape.palatable.lambda.monad;
2+
3+
import com.jnape.palatable.lambda.functions.Fn1;
4+
import com.jnape.palatable.lambda.functions.recursion.RecursiveResult;
5+
import com.jnape.palatable.lambda.functor.Applicative;
6+
import com.jnape.palatable.lambda.functor.builtin.Lazy;
7+
8+
public interface MonadRec<A, M extends MonadRec<?, M>> extends Monad<A, M> {
9+
10+
<B> MonadRec<B, M> trampolineM(Fn1<? super A, ? extends MonadRec<RecursiveResult<A, B>, M>> fn);
11+
12+
@Override
13+
<B> MonadRec<B, M> flatMap(Fn1<? super A, ? extends Monad<B, M>> f);
14+
15+
@Override
16+
<B> MonadRec<B, M> pure(B b);
17+
18+
@Override
19+
default <B> MonadRec<B, M> fmap(Fn1<? super A, ? extends B> fn) {
20+
return Monad.super.<B>fmap(fn).coerce();
21+
}
22+
23+
@Override
24+
default <B> MonadRec<B, M> zip(Applicative<Fn1<? super A, ? extends B>, M> appFn) {
25+
return Monad.super.zip(appFn).coerce();
26+
}
27+
28+
@Override
29+
default <B> Lazy<? extends MonadRec<B, M>> lazyZip(
30+
Lazy<? extends Applicative<Fn1<? super A, ? extends B>, M>> lazyAppFn) {
31+
return Monad.super.lazyZip(lazyAppFn).fmap(Applicative<B, M>::coerce);
32+
}
33+
34+
@Override
35+
default <B> MonadRec<B, M> discardL(Applicative<B, M> appB) {
36+
return Monad.super.discardL(appB).coerce();
37+
}
38+
39+
@Override
40+
default <B> MonadRec<A, M> discardR(Applicative<B, M> appB) {
41+
return Monad.super.discardR(appB).coerce();
42+
}
43+
}

src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import testsupport.traits.BifunctorLaws;
1313
import testsupport.traits.FunctorLaws;
1414
import testsupport.traits.MonadLaws;
15+
import testsupport.traits.MonadRecLaws;
1516
import testsupport.traits.TraversableLaws;
1617

1718
import java.util.concurrent.atomic.AtomicInteger;
@@ -35,7 +36,13 @@ public class EitherTest {
3536
@Rule
3637
public ExpectedException thrown = ExpectedException.none();
3738

38-
@TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class})
39+
@TestTraits({
40+
FunctorLaws.class,
41+
ApplicativeLaws.class,
42+
MonadLaws.class,
43+
BifunctorLaws.class,
44+
TraversableLaws.class,
45+
MonadRecLaws.class})
3946
public Subjects<Either<String, Integer>> testSubjects() {
4047
return subjects(left("foo"), right(1));
4148
}

src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import testsupport.traits.ApplicativeLaws;
1111
import testsupport.traits.FunctorLaws;
1212
import testsupport.traits.MonadLaws;
13+
import testsupport.traits.MonadRecLaws;
1314
import testsupport.traits.TraversableLaws;
1415

1516
import java.util.Optional;
@@ -32,7 +33,7 @@
3233
@RunWith(Traits.class)
3334
public class MaybeTest {
3435

35-
@TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class, MonadLaws.class})
36+
@TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class, MonadLaws.class, MonadRecLaws.class})
3637
public Subjects<Maybe<Integer>> testSubject() {
3738
return subjects(Maybe.nothing(), just(1));
3839
}
@@ -114,7 +115,7 @@ public void nothingOrThrow() {
114115
@Test
115116
public void divergesIntoChoice3() {
116117
assertEquals(Choice3.a(UNIT), nothing().diverge());
117-
assertEquals(Choice3.b(1), just(1).diverge());
118+
assertEquals(Choice3.<Unit, Integer, Object>b(1), just(1).diverge());
118119
}
119120

120121
@Test

src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import testsupport.traits.ApplicativeLaws;
88
import testsupport.traits.FunctorLaws;
99
import testsupport.traits.MonadLaws;
10+
import testsupport.traits.MonadRecLaws;
1011
import testsupport.traits.TraversableLaws;
1112

1213
import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity;
@@ -15,7 +16,7 @@
1516
@RunWith(Traits.class)
1617
public class IdentityTest {
1718

18-
@TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class})
19+
@TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class})
1920
public Identity<?> testSubject() {
2021
return new Identity<>("");
2122
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package testsupport.traits;
2+
3+
import com.jnape.palatable.lambda.monad.MonadRec;
4+
import com.jnape.palatable.traitor.traits.Trait;
5+
6+
import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse;
7+
import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate;
8+
import static testsupport.Constants.STACK_EXPLODING_NUMBER;
9+
10+
public class MonadRecLaws<M extends MonadRec<?, M>> implements Trait<MonadRec<?, M>> {
11+
12+
@Override
13+
public void test(MonadRec<?, M> monadRec) {
14+
15+
16+
boolean equals = monadRec.pure(STACK_EXPLODING_NUMBER)
17+
.equals(monadRec.pure(0)
18+
.trampolineM(x -> monadRec.pure(x < STACK_EXPLODING_NUMBER
19+
? recurse(x + 1)
20+
: terminate(x))));
21+
if (!equals)
22+
throw new AssertionError("Expected m.pure(" + STACK_EXPLODING_NUMBER + ") == " +
23+
"m.pure(0).trampolineM(f)");
24+
}
25+
}

0 commit comments

Comments
 (0)