Skip to content

Commit cc746ca

Browse files
committed
Adding Kleisli arrows
1 parent 5b9864c commit cc746ca

File tree

6 files changed

+150
-11
lines changed

6 files changed

+150
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
99
- `Try`, a `Monad` representing an expression-like analog of `try/catch/finally`
1010
- `CheckedRunnable`, the `Runnable` counterpart to `CheckedSupplier` that can throw checked exceptions
1111
- `Unit`, the lambda analog to `Void`, except actually inhabited by a singleton instance
12+
- `Kleisli`, the abstract representation of a `Kleisli` arrow (`Monad#flatMap`) as an `Fn1`
1213

1314
### Changed
1415
- `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object`
1516
- `Peek2` now accepts the more general `BoundedBifunctor`
17+
- `Identity`, `Compose`, and `Const` functors all have better `toString` implementations
1618

1719
### Deprecated
1820
- `Either#trying` in favor of `Try#trying`
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.jnape.palatable.lambda.functions.specialized;
2+
3+
import com.jnape.palatable.lambda.functions.Fn1;
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+
/**
10+
* The Kleisli arrow of a {@link Monad}, manifest as simply an <code>{@link Fn1}&lt;A, MB&gt;</code>. This can be
11+
* thought of as a fixed, portable {@link Monad#flatMap(Function)}.
12+
*
13+
* @param <A> the input argument type
14+
* @param <M> the {@link Monad} unification parameter
15+
* @param <MB> the output {@link Monad} type
16+
*/
17+
@FunctionalInterface
18+
public interface Kleisli<A, B, M extends Monad, MB extends Monad<B, M>> extends Fn1<A, MB> {
19+
20+
/**
21+
* Left-to-right composition of two compatible {@link Kleisli} arrows, yielding a new {@link Kleisli} arrow.
22+
*
23+
* @param after the arrow to execute after this one
24+
* @param <C> the new return parameter type
25+
* @param <MC> the {@link Monad} instance to return
26+
* @return the composition of the two arrows as a new {@link Kleisli} arrow
27+
*/
28+
default <C, MC extends Monad<C, M>> Kleisli<A, C, M, MC> andThen(Kleisli<B, C, M, MC> after) {
29+
return a -> apply(a).flatMap(after).coerce();
30+
}
31+
32+
/**
33+
* Right-to-left composition of two compatible {@link Kleisli} arrows, yielding a new {@link Kleisli} arrow.
34+
*
35+
* @param before the arrow to execute before this one
36+
* @param <Z> the new input argument type
37+
* @param <MA> the {@link Monad} instance to flatMap with this arrow
38+
* @return the composition of the two arrows as a new {@link Kleisli} arrow
39+
*/
40+
default <Z, MA extends Monad<A, M>> Kleisli<Z, B, M, MB> compose(Kleisli<Z, A, M, MA> before) {
41+
return z -> before.apply(z).flatMap(this).coerce();
42+
}
43+
44+
/**
45+
* {@inheritDoc}
46+
*/
47+
@Override
48+
default <Z> Kleisli<Z, B, M, MB> compose(Function<? super Z, ? extends A> before) {
49+
return Fn1.super.compose(before)::apply;
50+
}
51+
52+
/**
53+
* {@inheritDoc}
54+
*/
55+
@Override
56+
default <C> Kleisli<A, B, M, MB> discardR(Applicative<C, Fn1<A, ?>> appB) {
57+
return Fn1.super.discardR(appB)::apply;
58+
}
59+
60+
/**
61+
* {@inheritDoc}
62+
*/
63+
@Override
64+
default <Z> Kleisli<Z, B, M, MB> contraMap(Function<? super Z, ? extends A> fn) {
65+
return Fn1.super.contraMap(fn)::apply;
66+
}
67+
68+
/**
69+
* {@inheritDoc}
70+
*/
71+
@Override
72+
default <Z> Kleisli<Z, B, M, MB> diMapL(Function<? super Z, ? extends A> fn) {
73+
return Fn1.super.diMapL(fn)::apply;
74+
}
75+
76+
/**
77+
* Adapt a compatible function into a {@link Kleisli} arrow.
78+
*
79+
* @param fn the function
80+
* @param <A> the input argument type
81+
* @param <B> the output parameter type
82+
* @param <M> the {@link Monad} unification parameter
83+
* @param <MB> the returned {@link Monad} instance
84+
* @return the function adapted as a {@link Kleisli} arrow
85+
*/
86+
static <A, B, M extends Monad, MB extends Monad<B, M>> Kleisli<A, B, M, MB> kleisli(
87+
Function<? super A, ? extends MB> fn) {
88+
return fn::apply;
89+
}
90+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,11 @@ public boolean equals(Object other) {
5959
public int hashCode() {
6060
return Objects.hash(fga);
6161
}
62+
63+
@Override
64+
public String toString() {
65+
return "Compose{" +
66+
"fga=" + fga +
67+
'}';
68+
}
6269
}

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

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

3-
import com.jnape.palatable.lambda.monad.Monad;
43
import com.jnape.palatable.lambda.functor.Applicative;
54
import com.jnape.palatable.lambda.functor.Bifunctor;
5+
import com.jnape.palatable.lambda.monad.Monad;
66
import com.jnape.palatable.lambda.traversable.Traversable;
77

88
import java.util.Objects;
@@ -34,16 +34,6 @@ public A runConst() {
3434
return a;
3535
}
3636

37-
@Override
38-
public boolean equals(Object other) {
39-
return other instanceof Const && Objects.equals(a, ((Const) other).a);
40-
}
41-
42-
@Override
43-
public int hashCode() {
44-
return Objects.hash(a);
45-
}
46-
4737
/**
4838
* Map over the right parameter. Note that because <code>B</code> is never actually known quantity outside of a type
4939
* signature, this is effectively a no-op that serves only to alter <code>Const's</code> type signature.
@@ -131,4 +121,21 @@ public <C, D> Const<C, D> biMap(Function<? super A, ? extends C> lFn,
131121
Function<? super B, ? extends D> rFn) {
132122
return new Const<>(lFn.apply(a));
133123
}
124+
125+
@Override
126+
public boolean equals(Object other) {
127+
return other instanceof Const && Objects.equals(a, ((Const) other).a);
128+
}
129+
130+
@Override
131+
public int hashCode() {
132+
return Objects.hash(a);
133+
}
134+
135+
@Override
136+
public String toString() {
137+
return "Const{" +
138+
"a=" + a +
139+
'}';
140+
}
134141
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,11 @@ public boolean equals(Object other) {
8181
public int hashCode() {
8282
return Objects.hash(a);
8383
}
84+
85+
@Override
86+
public String toString() {
87+
return "Identity{" +
88+
"a=" + a +
89+
'}';
90+
}
8491
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.jnape.palatable.lambda.functions.specialized;
2+
3+
import com.jnape.palatable.lambda.functor.builtin.Identity;
4+
import org.junit.Test;
5+
6+
import static com.jnape.palatable.lambda.functions.specialized.Kleisli.kleisli;
7+
import static java.lang.Integer.parseInt;
8+
import static org.junit.Assert.assertEquals;
9+
10+
public class KleisliTest {
11+
12+
private static final Kleisli<Integer, String, Identity, Identity<String>> G = kleisli(i -> new Identity<>(i.toString()));
13+
private static final Kleisli<String, Integer, Identity, Identity<Integer>> F = kleisli(s -> new Identity<>(parseInt(s)));
14+
15+
@Test
16+
public void leftToRightComposition() {
17+
assertEquals(new Identity<>(1), G.andThen(F).apply(1));
18+
assertEquals(new Identity<>("1"), F.andThen(G).apply("1"));
19+
}
20+
21+
@Test
22+
public void rightToLeftComposition() {
23+
assertEquals(new Identity<>("1"), G.compose(F).apply("1"));
24+
assertEquals(new Identity<>(1), F.compose(G).apply(1));
25+
}
26+
}

0 commit comments

Comments
 (0)