Skip to content

Commit 1348fc7

Browse files
committed
IO monad arrives
1 parent 794b3ce commit 1348fc7

File tree

8 files changed

+236
-18
lines changed

8 files changed

+236
-18
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
1010
- ***Breaking Change***: `Effect#accept()` is now the required method to implement in the functional interface
1111
- ***Breaking Change***: `Fn0#apply()` is now the required method to implement in the functional interface
1212
- ***Breaking Change***: `GTBy`, `GT`, `LTBy`, `LT`, `GTEBy`, `GTE`, `LTEBy`, and `LTE` take the right-hand side first for more intuitive partial application
13+
- ***Breaking Change***: `Effect` now returns an `IO`
14+
- ***Breaking Change***: `Alter` now returns an `IO`
1315
- `RightAny` overload returns `Monoid`
1416
- monoids now all fold with respect to `foldMap`
1517
- monoid folding now implicitly starts with the identity, regardless of iterable population
@@ -29,7 +31,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
2931
- `Alter`, for applying an `Effect` to an input and returning it, presumably altered
3032
- `Clamp`, for clamping a value between two bounds
3133
- `Between`, for determining if a value is in a closed interval
32-
- `Strong`, profunctor strength
34+
- `Strong`, profunctor strength
35+
- `IO` monad
3336

3437
### Deprecated
3538
- `AddAll` semigroup, in favor of the monoid that no longer mutates any argument

src/main/java/com/jnape/palatable/lambda/functions/Effect.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import java.util.function.Consumer;
77
import java.util.function.Function;
88

9-
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
9+
import static com.jnape.palatable.lambda.functions.Fn0.fn0;
10+
import static com.jnape.palatable.lambda.functions.IO.io;
1011

1112
/**
1213
* A function returning "no result", and therefore only useful as a side-effect.
@@ -16,12 +17,11 @@
1617
* @see Consumer
1718
*/
1819
@FunctionalInterface
19-
public interface Effect<A> extends Fn1<A, Unit>, Consumer<A> {
20+
public interface Effect<A> extends Fn1<A, IO<Unit>>, Consumer<A> {
2021

2122
@Override
22-
default Unit apply(A a) {
23-
accept(a);
24-
return UNIT;
23+
default IO<Unit> apply(A a) {
24+
return io(fn0(() -> accept(a)));
2525
}
2626

2727
@Override

src/main/java/com/jnape/palatable/lambda/functions/Fn0.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import java.util.function.Supplier;
1010

1111
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
12-
import static com.jnape.palatable.lambda.functions.Effect.effect;
12+
import static com.jnape.palatable.lambda.functions.IO.io;
1313

1414
/**
1515
* A function taking "no arguments", implemented as an <code>{@link Fn1}&lt;{@link Unit}, A&gt;</code>.
@@ -120,11 +120,11 @@ static <A> Fn0<A> fn0(Fn0<A> fn) {
120120
/**
121121
* Static factory method for adapting a {@link Runnable} to an <code>{@link Fn0}&lt;{@link Unit}&gt;</code>.
122122
*
123-
* @param fn the {@link Runnable}
123+
* @param runnable the {@link Runnable}
124124
* @return the {@link Fn0}
125125
*/
126-
static Fn0<Unit> fn0(Runnable fn) {
127-
return effect(fn).thunk(UNIT);
126+
static Fn0<Unit> fn0(Runnable runnable) {
127+
return io(runnable)::unsafePerformIO;
128128
}
129129

130130
/**
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package com.jnape.palatable.lambda.functions;
2+
3+
import com.jnape.palatable.lambda.adt.Unit;
4+
import com.jnape.palatable.lambda.functor.Applicative;
5+
import com.jnape.palatable.lambda.monad.Monad;
6+
7+
import java.util.function.Function;
8+
9+
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
10+
11+
/**
12+
* A {@link Monad} representing some effectful computation to be performed.
13+
*
14+
* @param <A> the result type
15+
*/
16+
public interface IO<A> extends Monad<A, IO<?>> {
17+
18+
/**
19+
* Run the effect represented by this {@link IO} instance
20+
*
21+
* @return the result of the effect
22+
*/
23+
A unsafePerformIO();
24+
25+
/**
26+
* {@inheritDoc}
27+
*/
28+
@Override
29+
default <B> IO<B> flatMap(Function<? super A, ? extends Monad<B, IO<?>>> f) {
30+
return () -> f.apply(unsafePerformIO()).<IO<B>>coerce().unsafePerformIO();
31+
}
32+
33+
/**
34+
* {@inheritDoc}
35+
*/
36+
@Override
37+
default <B> IO<B> pure(B b) {
38+
return () -> b;
39+
}
40+
41+
/**
42+
* {@inheritDoc}
43+
*/
44+
@Override
45+
default <B> IO<B> fmap(Function<? super A, ? extends B> fn) {
46+
return Monad.super.<B>fmap(fn).coerce();
47+
}
48+
49+
/**
50+
* {@inheritDoc}
51+
*/
52+
@Override
53+
default <B> IO<B> zip(Applicative<Function<? super A, ? extends B>, IO<?>> appFn) {
54+
return Monad.super.zip(appFn).coerce();
55+
}
56+
57+
/**
58+
* {@inheritDoc}
59+
*/
60+
@Override
61+
default <B> IO<B> discardL(Applicative<B, IO<?>> appB) {
62+
return Monad.super.discardL(appB).coerce();
63+
}
64+
65+
/**
66+
* {@inheritDoc}
67+
*/
68+
@Override
69+
default <B> IO<A> discardR(Applicative<B, IO<?>> appB) {
70+
return Monad.super.discardR(appB).coerce();
71+
}
72+
73+
/**
74+
* Static factory method for coercing a lambda to an {@link IO}.
75+
*
76+
* @param io the lambda to coerce
77+
* @param <A> the result type
78+
* @return the {@link IO}
79+
*/
80+
static <A> IO<A> io(IO<A> io) {
81+
return io;
82+
}
83+
84+
/**
85+
* Static factory method for creating an {@link IO} that just returns <code>a</code> when performed.
86+
*
87+
* @param a the result
88+
* @param <A> the result type
89+
* @return the {@link IO}
90+
*/
91+
static <A> IO<A> io(A a) {
92+
return io(() -> a);
93+
}
94+
95+
/**
96+
* Static factory method for creating an {@link IO} that runs <code>runnable</code> and returns {@link Unit}.
97+
*
98+
* @param runnable the {@link Runnable}
99+
* @return the {@link IO}
100+
*/
101+
static IO<Unit> io(Runnable runnable) {
102+
return io(() -> {
103+
runnable.run();
104+
return UNIT;
105+
});
106+
}
107+
108+
/**
109+
* Static factory method for creating an {@link IO} from an <code>{@link Fn1}&lt;{@link Unit}, A&gt;</code>.
110+
*
111+
* @param fn1 the {@link Fn1}
112+
* @param <A> the result type
113+
* @return the {@link IO}
114+
*/
115+
static <A> IO<A> io(Fn1<Unit, A> fn1) {
116+
return io(() -> fn1.apply(UNIT));
117+
}
118+
}

src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,38 @@
33
import com.jnape.palatable.lambda.functions.Effect;
44
import com.jnape.palatable.lambda.functions.Fn1;
55
import com.jnape.palatable.lambda.functions.Fn2;
6+
import com.jnape.palatable.lambda.functions.IO;
67

78
import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
89

910
/**
10-
* Given an <code>{@link Effect}&lt;A&gt;</code> and some <code>A</code>, perform the effect on the <code>A</code> and
11-
* return it.
11+
* Given an <code>{@link Effect}&lt;A&gt;</code> and some <code>A</code>, produce an {@link IO} that, when run, performs
12+
* the effect on <code>A</code> and returns it.
1213
*
1314
* @param <A> the input and output
1415
*/
15-
public final class Alter<A> implements Fn2<Effect<? super A>, A, A> {
16+
public final class Alter<A> implements Fn2<Effect<? super A>, A, IO<A>> {
1617

1718
private static final Alter INSTANCE = new Alter();
1819

1920
private Alter() {
2021
}
2122

2223
@Override
23-
public A apply(Effect<? super A> effect, A a) {
24-
return effect.fmap(constantly(a)).apply(a);
24+
public IO<A> apply(Effect<? super A> effect, A a) {
25+
return effect.fmap(io -> io.fmap(constantly(a))).apply(a);
2526
}
2627

2728
@SuppressWarnings("unchecked")
2829
public static <A> Alter<A> alter() {
2930
return INSTANCE;
3031
}
3132

32-
public static <A> Fn1<A, A> alter(Effect<? super A> effect) {
33+
public static <A> Fn1<A, IO<A>> alter(Effect<? super A> effect) {
3334
return Alter.<A>alter().apply(effect);
3435
}
3536

36-
public static <A> A alter(Effect<? super A> effect, A a) {
37+
public static <A> IO<A> alter(Effect<? super A> effect, A a) {
3738
return Alter.<A>alter(effect).apply(a);
3839
}
3940
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.jnape.palatable.lambda.functions;
2+
3+
import com.jnape.palatable.traitor.annotations.TestTraits;
4+
import com.jnape.palatable.traitor.runners.Traits;
5+
import org.junit.Test;
6+
import org.junit.runner.RunWith;
7+
import testsupport.EqualityAwareIO;
8+
import testsupport.traits.ApplicativeLaws;
9+
import testsupport.traits.FunctorLaws;
10+
import testsupport.traits.MonadLaws;
11+
12+
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
13+
import static com.jnape.palatable.lambda.functions.Fn0.fn0;
14+
import static com.jnape.palatable.lambda.functions.IO.io;
15+
import static org.junit.Assert.assertEquals;
16+
17+
@RunWith(Traits.class)
18+
public class IOTest {
19+
20+
@TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class})
21+
public IO<Integer> testSubject() {
22+
return new EqualityAwareIO<>(io(1));
23+
}
24+
25+
@Test
26+
public void staticFactoryMethods() {
27+
assertEquals((Integer) 1, io(1).unsafePerformIO());
28+
assertEquals((Integer) 1, io(() -> 1).unsafePerformIO());
29+
assertEquals((Integer) 1, io(fn0(() -> 1)).unsafePerformIO());
30+
assertEquals(UNIT, io(() -> {}).unsafePerformIO());
31+
}
32+
}

src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class AlterTest {
1414
@Test
1515
public void altersInput() {
1616
ArrayList<String> input = new ArrayList<>();
17-
assertSame(input, alter(xs -> xs.add("foo"), input));
17+
assertSame(input, alter(xs -> xs.add("foo"), input).unsafePerformIO());
1818
assertEquals(singletonList("foo"), input);
1919
}
2020
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package testsupport;
2+
3+
import com.jnape.palatable.lambda.functions.IO;
4+
import com.jnape.palatable.lambda.functor.Applicative;
5+
import com.jnape.palatable.lambda.monad.Monad;
6+
7+
import java.util.function.Function;
8+
9+
import static java.util.Objects.hash;
10+
11+
public final class EqualityAwareIO<A> implements IO<A> {
12+
private final IO<A> io;
13+
14+
public EqualityAwareIO(IO<A> io) {
15+
this.io = io;
16+
}
17+
18+
@Override
19+
public A unsafePerformIO() {
20+
return io.unsafePerformIO();
21+
}
22+
23+
@Override
24+
public <B> EqualityAwareIO<B> flatMap(Function<? super A, ? extends Monad<B, IO<?>>> f) {
25+
return new EqualityAwareIO<>(io.flatMap(f));
26+
}
27+
28+
@Override
29+
public <B> EqualityAwareIO<B> fmap(Function<? super A, ? extends B> f) {
30+
return new EqualityAwareIO<>(io.fmap(f));
31+
}
32+
33+
@Override
34+
public <B> EqualityAwareIO<B> zip(Applicative<Function<? super A, ? extends B>, IO<?>> appFn) {
35+
return new EqualityAwareIO<>(io.zip(appFn));
36+
}
37+
38+
@Override
39+
public <B> EqualityAwareIO<B> pure(B b) {
40+
return new EqualityAwareIO<>(io.pure(b));
41+
}
42+
43+
44+
@Override
45+
public <B> EqualityAwareIO<B> discardL(Applicative<B, IO<?>> appB) {
46+
return new EqualityAwareIO<>(io.discardL(appB));
47+
}
48+
49+
@Override
50+
public <B> EqualityAwareIO<A> discardR(Applicative<B, IO<?>> appB) {
51+
return new EqualityAwareIO<>(io.discardR(appB));
52+
}
53+
54+
@Override
55+
@SuppressWarnings("unchecked")
56+
public boolean equals(Object other) {
57+
return other instanceof IO && ((IO<A>) other).unsafePerformIO().equals(unsafePerformIO());
58+
}
59+
60+
@Override
61+
public int hashCode() {
62+
return hash(io);
63+
}
64+
}

0 commit comments

Comments
 (0)