Skip to content

Commit 8f84785

Browse files
refactor: Start making Option (some methods implemented, not all yet)
1 parent a661ea2 commit 8f84785

5 files changed

Lines changed: 293 additions & 0 deletions

File tree

src/main/java/net/marcellperger/mathexpr/util/Util.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.function.Consumer;
1111
import java.util.function.Function;
1212
import java.util.function.Predicate;
13+
import java.util.function.Supplier;
1314
import java.util.function.UnaryOperator;
1415

1516
@SuppressWarnings("unused")
@@ -249,4 +250,11 @@ public void forEachRemaining(Consumer<? super T> action) {
249250
return v;
250251
};
251252
}
253+
254+
public static @NotNull Supplier<VoidVal> runnableToSupplier(Runnable runnable) {
255+
return () -> {
256+
runnable.run();
257+
return VoidVal.val();
258+
};
259+
}
252260
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package net.marcellperger.mathexpr.util.rs;
2+
3+
import net.marcellperger.mathexpr.util.Util;
4+
import org.jetbrains.annotations.Contract;
5+
import org.jetbrains.annotations.NotNull;
6+
7+
import java.util.function.Consumer;
8+
import java.util.function.Function;
9+
import java.util.function.Predicate;
10+
import java.util.function.Supplier;
11+
12+
public sealed interface Option<T> {
13+
record Some<T>(T value) implements Option<T> {
14+
@Contract(pure = true)
15+
@Override
16+
public @NotNull String toString() {
17+
return "Some(" + value + ')';
18+
}
19+
}
20+
21+
record None<T>() implements Option<T> {
22+
@Contract(" -> new")
23+
public <U> @NotNull None<U> cast() {
24+
return new None<>();
25+
}
26+
27+
@Contract(pure = true)
28+
@Override
29+
public @NotNull String toString() {
30+
return "None";
31+
}
32+
}
33+
34+
@Contract("_ -> new")
35+
static <T> @NotNull Some<T> newSome(T value) {
36+
return new Some<>(value);
37+
}
38+
39+
@Contract(" -> new")
40+
static <T> @NotNull None<T> newNone() {
41+
return new None<>();
42+
}
43+
44+
default boolean isSome() {
45+
return this instanceof Some<T>;
46+
}
47+
48+
default boolean isNone() {
49+
return this instanceof None<T>;
50+
}
51+
52+
default boolean isSomeAnd(Predicate<? super T> predicate) {
53+
return switch (this) {
54+
case None() -> false;
55+
case Some(T value) -> predicate.test(value);
56+
};
57+
}
58+
// I don't think we need as_slice because .stream().toList() / .collect()
59+
// and there are already easy ways of Option -> Collection using .map etc.
60+
61+
default <U> Option<U> map(Function<? super T, ? extends U> fn) {
62+
return switch (this) {
63+
case Some(T value) -> new Some<>(fn.apply(value));
64+
case None<T> n -> n.cast();
65+
};
66+
}
67+
default <U> U mapOr(U default_, Function<? super T, ? extends U> fn) {
68+
return mapOrElse(() -> default_, fn);
69+
}
70+
default <U> U mapOrElse(Supplier<? extends U> defaultFn, Function<? super T, ? extends U> fn) {
71+
return switch (this) {
72+
case Some(T value) -> fn.apply(value);
73+
case None() -> defaultFn.get();
74+
};
75+
}
76+
default Option<T> mapErr(Runnable noneFn) { // there is nothing to map from or to (None -> None)!
77+
if(isNone()) noneFn.run();
78+
return this;
79+
}
80+
81+
// not Rust functions, but provides more logical argument order
82+
// (if/else vs else/if) than mapOrElse
83+
default void ifThenElse(Consumer<? super T> someFn, Runnable noneFn) {
84+
mapOrElse(Util.runnableToSupplier(noneFn), Util.consumerToFunction(someFn));
85+
}
86+
default <U> U ifThenElse(Function<? super T, ? extends U> someFn, Supplier<? extends U> noneFn) {
87+
return mapOrElse(noneFn, someFn);
88+
}
89+
90+
default Option<T> inspect(Consumer<? super T> fn) {
91+
return map(Util.consumerToIdentityFunc(fn));
92+
}
93+
// not in Rust but could be useful (same as mapErr)
94+
default Option<T> inspectErr(Runnable noneFn) {
95+
if(isNone()) noneFn.run();
96+
return this;
97+
}
98+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package net.marcellperger.mathexpr.util.rs;
2+
3+
import org.jetbrains.annotations.Contract;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
public class OptionPanicException extends PanicException {
7+
public OptionPanicException() {
8+
}
9+
10+
public OptionPanicException(String message) {
11+
super(message);
12+
}
13+
14+
public OptionPanicException(String message, Throwable cause) {
15+
super(message, cause);
16+
}
17+
18+
public OptionPanicException(Throwable cause) {
19+
super(cause);
20+
}
21+
22+
@SuppressWarnings("unused") // may be used later
23+
@Contract(value = " -> new", pure = true)
24+
public static @NotNull Builder builder() {
25+
return new Builder();
26+
}
27+
28+
public static class Builder extends PanicException.Builder {
29+
@Override
30+
public OptionPanicException build() {
31+
return build(OptionPanicException::new, OptionPanicException::new,
32+
OptionPanicException::new, OptionPanicException::new);
33+
}
34+
}
35+
}

src/test/java/net/marcellperger/mathexpr/MiniMock.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ public void accept(T arg) {
147147
}
148148
}
149149

150+
public static class MockedRunnable extends BaseMockedCallable<VoidVal> implements Runnable {
151+
public MockedRunnable() {
152+
calls = new ZST_List();
153+
}
154+
155+
@Override
156+
public void run() { handleCall(VoidVal.val()); }
157+
}
158+
150159
// oof, this List interface is monstrously big, with very few defaults!
151160
static class ZST_List implements List<VoidVal> {
152161
private int m_size = 0; // I wish this could be long but size() requires int
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package net.marcellperger.mathexpr.util.rs;
2+
3+
import net.marcellperger.mathexpr.MiniMock.*;
4+
import net.marcellperger.mathexpr.util.rs.Option.Some;
5+
import net.marcellperger.mathexpr.util.rs.Option.None;
6+
import org.junit.jupiter.api.Test;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
10+
class OptionTest {
11+
Some<Integer> getSome() {
12+
return Option.newSome(314);
13+
}
14+
None<Integer> getNone() {
15+
return Option.newNone();
16+
}
17+
18+
@Test
19+
void isSome() {
20+
assertTrue(getSome().isSome());
21+
assertFalse(getNone().isSome());
22+
}
23+
24+
@Test
25+
void isNone() {
26+
assertFalse(getSome().isNone());
27+
assertTrue(getNone().isNone());
28+
}
29+
30+
@Test
31+
void isSomeAnd() {
32+
MockedPredicate<Integer> mfFalse = new MockedPredicate<>(false);
33+
MockedPredicate<Integer> mfTrue = new MockedPredicate<>(true);
34+
{
35+
assertTrue(getSome().isSomeAnd(mfTrue));
36+
mfTrue.assertCalledOnceWith(314);
37+
assertFalse(getSome().isSomeAnd(mfFalse));
38+
mfFalse.assertCalledOnceWith(314);
39+
}
40+
mfTrue.reset();
41+
mfFalse.reset();
42+
{
43+
assertFalse(getNone().isSomeAnd(mfTrue));
44+
mfTrue.assertNotCalled();
45+
assertFalse(getNone().isSomeAnd(mfFalse));
46+
mfFalse.assertNotCalled();
47+
}
48+
}
49+
50+
@Test
51+
void map() {
52+
MockedFunction<Integer, Integer> mfAdd1 = new MockedFunction<>(i -> i + 1);
53+
assertEquals(Option.newSome(7), Option.newSome(6).map(mfAdd1));
54+
mfAdd1.assertCalledOnceWith(6);
55+
mfAdd1.reset();
56+
assertEquals(Option.newNone(), Option.<Integer>newNone().map(mfAdd1));
57+
mfAdd1.assertNotCalled();
58+
}
59+
60+
@Test
61+
void mapOr() {
62+
MockedFunction<Integer, Integer> mfAdd1 = new MockedFunction<>(i -> i + 1);
63+
assertEquals(7, Option.newSome(6).mapOr(-1, mfAdd1));
64+
mfAdd1.assertCalledOnceWith(6);
65+
mfAdd1.reset();
66+
assertEquals(-1, Option.<Integer>newNone().mapOr(-1, mfAdd1));
67+
mfAdd1.assertNotCalled();
68+
}
69+
70+
@Test
71+
void mapOrElse() {
72+
MockedSupplier<Integer> mSupplier = new MockedSupplier<>(-6);
73+
MockedFunction<Integer, Integer> mfAdd1 = new MockedFunction<>(i -> i + 1);
74+
{
75+
assertEquals(7, Option.newSome(6).mapOrElse(mSupplier, mfAdd1));
76+
mfAdd1.assertCalledOnceWith(6);
77+
mSupplier.assertNotCalled();
78+
}
79+
mfAdd1.reset();
80+
mSupplier.reset();
81+
{
82+
assertEquals(-6, Option.<Integer>newNone().mapOrElse(mSupplier, mfAdd1));
83+
mfAdd1.assertNotCalled();
84+
mSupplier.assertCalledOnce();
85+
}
86+
}
87+
88+
@Test
89+
void ifThenElse__func_supp() {
90+
MockedSupplier<Integer> mSupplier = new MockedSupplier<>(-6);
91+
MockedFunction<Integer, Integer> mfAdd1 = new MockedFunction<>(i -> i + 1);
92+
{
93+
assertEquals(7, Option.newSome(6).ifThenElse(mfAdd1, mSupplier));
94+
mfAdd1.assertCalledOnceWith(6);
95+
mSupplier.assertNotCalled();
96+
}
97+
mfAdd1.reset();
98+
mSupplier.reset();
99+
{
100+
assertEquals(-6, Option.<Integer>newNone().ifThenElse(mfAdd1, mSupplier));
101+
mfAdd1.assertNotCalled();
102+
mSupplier.assertCalledOnce();
103+
}
104+
}
105+
106+
@Test
107+
void ifThenElse__cons_runnable() {
108+
MockedConsumer<Integer> mCons = new MockedConsumer<>();
109+
MockedRunnable mRunnable = new MockedRunnable();
110+
{
111+
getSome().ifThenElse(mCons, mRunnable);
112+
mCons.assertCalledOnceWith(314);
113+
mRunnable.assertNotCalled();
114+
}
115+
mCons.reset();
116+
mRunnable.reset();
117+
{
118+
getNone().ifThenElse(mCons, mRunnable);
119+
mCons.assertNotCalled();
120+
mRunnable.assertCalledOnce();
121+
}
122+
}
123+
124+
@Test
125+
void inspect() {
126+
MockedConsumer<Integer> intCons = new MockedConsumer<>();
127+
assertEquals(getSome(), getSome().inspect(intCons));
128+
intCons.assertCalledOnceWith(314);
129+
intCons.reset();
130+
assertEquals(getNone(), getNone().inspect(intCons));
131+
intCons.assertNotCalled();
132+
}
133+
134+
@Test
135+
void inspectErr() {
136+
MockedRunnable mRunnable = new MockedRunnable();
137+
assertEquals(getNone(), getNone().inspectErr(mRunnable));
138+
mRunnable.assertCalledOnce();
139+
mRunnable.reset();
140+
assertEquals(getSome(), getSome().inspectErr(mRunnable));
141+
mRunnable.assertNotCalled();
142+
}
143+
}

0 commit comments

Comments
 (0)