Skip to content

Commit ade8610

Browse files
committed
Adding the Writer monad
1 parent 67750c7 commit ade8610

File tree

5 files changed

+234
-10
lines changed

5 files changed

+234
-10
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/).
55

66
## [Unreleased]
7-
No changes yet
7+
### Added
8+
- `Writer`, the writer monad
89

910
## [5.0.0] - 2019-09-23
1011
### Changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package com.jnape.palatable.lambda.functor.builtin;
2+
3+
import com.jnape.palatable.lambda.adt.Unit;
4+
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
5+
import com.jnape.palatable.lambda.functions.Fn1;
6+
import com.jnape.palatable.lambda.functions.recursion.RecursiveResult;
7+
import com.jnape.palatable.lambda.functions.specialized.Pure;
8+
import com.jnape.palatable.lambda.functor.Applicative;
9+
import com.jnape.palatable.lambda.monad.Monad;
10+
import com.jnape.palatable.lambda.monad.MonadRec;
11+
import com.jnape.palatable.lambda.monad.MonadWriter;
12+
import com.jnape.palatable.lambda.monad.transformer.builtin.WriterT;
13+
import com.jnape.palatable.lambda.monoid.Monoid;
14+
15+
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
16+
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
17+
import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
18+
import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both;
19+
import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into;
20+
import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline;
21+
22+
/**
23+
* The lazy writer monad, a monad capturing some accumulation (eventually to be folded in terms of a given monoid) and
24+
* a value. Note that unlike the {@link State} monad, the {@link Writer} monad does not allow the value to be fully
25+
* derived from the accumulation.
26+
*
27+
* @param <W> the accumulation type
28+
* @param <A> the value type
29+
*/
30+
public final class Writer<W, A> implements
31+
MonadWriter<W, A, Writer<W, ?>>,
32+
MonadRec<A, Writer<W, ?>> {
33+
34+
private final Fn1<? super Monoid<W>, ? extends Tuple2<A, W>> writerFn;
35+
36+
private Writer(Fn1<? super Monoid<W>, ? extends Tuple2<A, W>> writerFn) {
37+
this.writerFn = writerFn;
38+
}
39+
40+
/**
41+
* Given a {@link Monoid} for the accumulation, run the computation represented by this {@link Writer}, accumulate
42+
* the written output in terms of the {@link Monoid}, and produce the accumulation and the value.
43+
*
44+
* @param monoid the accumulation {@link Monoid}
45+
* @return the accumulation with the value
46+
*/
47+
public Tuple2<A, W> runWriter(Monoid<W> monoid) {
48+
return writerFn.apply(monoid);
49+
}
50+
51+
/**
52+
* {@inheritDoc}
53+
*/
54+
@Override
55+
public <B> Writer<W, Tuple2<A, B>> listens(Fn1<? super W, ? extends B> fn) {
56+
return new Writer<>(monoid -> runWriter(monoid).into((a, w) -> tuple(both(constantly(a), fn, w), w)));
57+
}
58+
59+
/**
60+
* {@inheritDoc}
61+
*/
62+
@Override
63+
public Writer<W, A> censor(Fn1<? super W, ? extends W> fn) {
64+
return new Writer<>(monoid -> runWriter(monoid).fmap(fn));
65+
}
66+
67+
/**
68+
* {@inheritDoc}
69+
*/
70+
@Override
71+
public <B> Writer<W, B> trampolineM(Fn1<? super A, ? extends MonadRec<RecursiveResult<A, B>, Writer<W, ?>>> fn) {
72+
return new Writer<>(monoid -> trampoline(into((a, w) -> fn.apply(a).<Writer<W, RecursiveResult<A, B>>>coerce()
73+
.runWriter(monoid)
74+
.fmap(monoid.apply(w))
75+
.into((aOrB, w_) -> aOrB.biMap(a_ -> tuple(a_, w_), b -> tuple(b, w_)))), runWriter(monoid)));
76+
}
77+
78+
/**
79+
* {@inheritDoc}
80+
*/
81+
@Override
82+
public <B> Writer<W, B> flatMap(Fn1<? super A, ? extends Monad<B, Writer<W, ?>>> f) {
83+
return new Writer<>(monoid -> writerFn.apply(monoid)
84+
.into((a, w) -> f.apply(a).<Writer<W, B>>coerce().runWriter(monoid).fmap(monoid.apply(w))));
85+
}
86+
87+
/**
88+
* {@inheritDoc}
89+
*/
90+
@Override
91+
public <B> Writer<W, B> pure(B b) {
92+
return listen(b);
93+
}
94+
95+
/**
96+
* {@inheritDoc}
97+
*/
98+
@Override
99+
public <B> Writer<W, B> fmap(Fn1<? super A, ? extends B> fn) {
100+
return MonadRec.super.<B>fmap(fn).coerce();
101+
}
102+
103+
/**
104+
* {@inheritDoc}
105+
*/
106+
@Override
107+
public <B> Writer<W, B> zip(Applicative<Fn1<? super A, ? extends B>, Writer<W, ?>> appFn) {
108+
return MonadRec.super.zip(appFn).coerce();
109+
}
110+
111+
/**
112+
* {@inheritDoc}
113+
*/
114+
@Override
115+
public <B> Lazy<Writer<W, B>> lazyZip(
116+
Lazy<? extends Applicative<Fn1<? super A, ? extends B>, Writer<W, ?>>> lazyAppFn) {
117+
return MonadRec.super.lazyZip(lazyAppFn).fmap(MonadRec<B, Writer<W, ?>>::coerce);
118+
}
119+
120+
/**
121+
* {@inheritDoc}
122+
*/
123+
@Override
124+
public <B> Writer<W, B> discardL(Applicative<B, Writer<W, ?>> appB) {
125+
return MonadRec.super.discardL(appB).coerce();
126+
}
127+
128+
/**
129+
* {@inheritDoc}
130+
*/
131+
@Override
132+
public <B> Writer<W, A> discardR(Applicative<B, Writer<W, ?>> appB) {
133+
return MonadRec.super.discardR(appB).coerce();
134+
}
135+
136+
/**
137+
* Construct a {@link Writer} from an accumulation.
138+
*
139+
* @param <W> the accumulation type
140+
* @return the {@link Writer}
141+
*/
142+
public static <W> Writer<W, Unit> tell(W w) {
143+
return writer(tuple(UNIT, w));
144+
}
145+
146+
/**
147+
* Construct a {@link Writer} from a value.
148+
*
149+
* @param <W> the accumulation type
150+
* @param <A> the value type
151+
* @return the {@link Writer}
152+
*/
153+
public static <W, A> Writer<W, A> listen(A a) {
154+
return Writer.<W>pureWriter().apply(a);
155+
}
156+
157+
/**
158+
* Construct a {@link Writer} from an accumulation and a value.
159+
*
160+
* @param <W> the accumulation type
161+
* @param <A> the value type
162+
* @return the {@link WriterT}
163+
*/
164+
public static <W, A> Writer<W, A> writer(Tuple2<A, W> aw) {
165+
return new Writer<>(constantly(aw));
166+
}
167+
168+
/**
169+
* The canonical {@link Pure} instance for {@link Writer}.
170+
*
171+
* @param <W> the accumulation type
172+
* @return the {@link Pure} instance
173+
*/
174+
public static <W> Pure<Writer<W, ?>> pureWriter() {
175+
return new Pure<Writer<W, ?>>() {
176+
@Override
177+
public <A> Writer<W, A> checkedApply(A a) {
178+
return new Writer<>(monoid -> tuple(a, monoid.identity()));
179+
}
180+
};
181+
}
182+
}

src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.jnape.palatable.lambda.functions.specialized.Pure;
99
import com.jnape.palatable.lambda.functor.Applicative;
1010
import com.jnape.palatable.lambda.functor.builtin.Lazy;
11+
import com.jnape.palatable.lambda.functor.builtin.Writer;
1112
import com.jnape.palatable.lambda.monad.Monad;
1213
import com.jnape.palatable.lambda.monad.MonadRec;
1314
import com.jnape.palatable.lambda.monad.MonadWriter;
@@ -23,7 +24,7 @@
2324
import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler;
2425

2526
/**
26-
* A strict transformer for a {@link Tuple2} holding a value and an accumulation.
27+
* A {@link MonadT monad transformer} for {@link Writer}.
2728
*
2829
* @param <W> the accumulation type
2930
* @param <M> the {@link Monad monadic embedding}
@@ -177,14 +178,14 @@ public static <W, M extends MonadRec<?, M>, A> WriterT<W, M, A> listen(MonadRec<
177178
/**
178179
* Lift a value and an accumulation embedded in a {@link Monad} into a {@link WriterT}.
179180
*
180-
* @param mwa the value and accumulation inside a {@link Monad}
181+
* @param maw the value and accumulation inside a {@link Monad}
181182
* @param <W> the accumulation type
182183
* @param <M> the {@link Monad} type
183184
* @param <A> the value type
184185
* @return the {@link WriterT}
185186
*/
186-
public static <W, M extends MonadRec<?, M>, A> WriterT<W, M, A> writerT(MonadRec<Tuple2<A, W>, M> mwa) {
187-
return new WriterT<>(constantly(mwa));
187+
public static <W, M extends MonadRec<?, M>, A> WriterT<W, M, A> writerT(MonadRec<Tuple2<A, W>, M> maw) {
188+
return new WriterT<>(constantly(maw));
188189
}
189190

190191
/**
@@ -198,7 +199,7 @@ public static <W, M extends MonadRec<?, M>, A> WriterT<W, M, A> writerT(MonadRec
198199
public static <W, M extends MonadRec<?, M>> Pure<WriterT<W, M, ?>> pureWriterT(Pure<M> pureM) {
199200
return new Pure<WriterT<W, M, ?>>() {
200201
@Override
201-
public <A> WriterT<W, M, A> checkedApply(A a) throws Throwable {
202+
public <A> WriterT<W, M, A> checkedApply(A a) {
202203
return listen(pureM.<A, MonadRec<A, M>>apply(a));
203204
}
204205
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.jnape.palatable.lambda.functor.builtin;
2+
3+
import com.jnape.palatable.lambda.functions.Fn1;
4+
import com.jnape.palatable.traitor.annotations.TestTraits;
5+
import com.jnape.palatable.traitor.framework.Subjects;
6+
import com.jnape.palatable.traitor.runners.Traits;
7+
import org.junit.Test;
8+
import org.junit.runner.RunWith;
9+
import testsupport.traits.ApplicativeLaws;
10+
import testsupport.traits.Equivalence;
11+
import testsupport.traits.FunctorLaws;
12+
import testsupport.traits.MonadLaws;
13+
import testsupport.traits.MonadRecLaws;
14+
import testsupport.traits.MonadWriterLaws;
15+
16+
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
17+
import static com.jnape.palatable.lambda.functor.builtin.Writer.listen;
18+
import static com.jnape.palatable.lambda.functor.builtin.Writer.tell;
19+
import static com.jnape.palatable.lambda.functor.builtin.Writer.writer;
20+
import static com.jnape.palatable.lambda.monoid.builtin.Join.join;
21+
import static com.jnape.palatable.traitor.framework.Subjects.subjects;
22+
import static org.junit.Assert.assertEquals;
23+
import static testsupport.traits.Equivalence.equivalence;
24+
25+
@RunWith(Traits.class)
26+
public class WriterTest {
27+
28+
@TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class, MonadWriterLaws.class})
29+
public Subjects<Equivalence<Writer<String, ?>>> testSubject() {
30+
Fn1<Writer<String, ?>, Object> runWriter = w -> w.runWriter(join());
31+
return subjects(equivalence(tell("foo"), runWriter),
32+
equivalence(listen(1), runWriter),
33+
equivalence(writer(tuple(1, "foo")), runWriter));
34+
}
35+
36+
@Test
37+
public void tellListenInteraction() {
38+
assertEquals(tuple(1, "hello, world!"),
39+
tell("hello, ")
40+
.discardL(listen(1))
41+
.discardR(tell("world!"))
42+
.runWriter(join()));
43+
}
44+
}

src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterTTest.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.jnape.palatable.lambda.monad.transformer.builtin;
22

3-
import com.jnape.palatable.lambda.adt.Maybe;
43
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
54
import com.jnape.palatable.lambda.functor.builtin.Identity;
65
import com.jnape.palatable.traitor.annotations.TestTraits;
@@ -14,11 +13,8 @@
1413
import testsupport.traits.MonadRecLaws;
1514
import testsupport.traits.MonadWriterLaws;
1615

17-
import static com.jnape.palatable.lambda.adt.Maybe.just;
1816
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
1917
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
20-
import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse;
21-
import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate;
2218
import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity;
2319
import static com.jnape.palatable.lambda.monad.transformer.builtin.WriterT.writerT;
2420
import static com.jnape.palatable.lambda.monoid.builtin.Join.join;

0 commit comments

Comments
 (0)