diff --git a/FirstSteps/README.md b/FirstSteps/README.md index 6c062dc..4d1690b 100644 --- a/FirstSteps/README.md +++ b/FirstSteps/README.md @@ -39,6 +39,10 @@ - [Packages](src/test/java/pl/mperor/lab/java/lang/element/PackageTest.java) - [Optional](src/test/java/pl/mperor/lab/java/lang/element/OptionalTest.java) +### Idioms 💡 + +- [Data-Oriented Programming](src/test/java/pl/mperor/lab/java/idiom/DataOrientedProgrammingTest.java) + ### Quirks 🤔 - [Integer Cache](src/test/java/pl/mperor/lab/java/quirk/IntegerCacheTest.java) diff --git a/FirstSteps/src/test/java/pl/mperor/lab/java/idiom/DataOrientedProgrammingTest.java b/FirstSteps/src/test/java/pl/mperor/lab/java/idiom/DataOrientedProgrammingTest.java new file mode 100644 index 0000000..fda4107 --- /dev/null +++ b/FirstSteps/src/test/java/pl/mperor/lab/java/idiom/DataOrientedProgrammingTest.java @@ -0,0 +1,47 @@ +package pl.mperor.lab.java.idiom; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class DataOrientedProgrammingTest { + + @Test + public void testEvalExhaustiveSwitchOverSealedRecords() { + Expr sum = new Add(new Const(3), new Const(4)); + Expr product = new Mul(new Const(2), sum); + + Assertions.assertEquals(7, eval(sum)); + Assertions.assertEquals(14, eval(product)); + } + + @Test + public void testGuardShortCircuitsMultiplicationByZero() { + Expr neverEvaluated = new Add(new Const(1), new Const(1)); + Expr product = new Mul(new Const(0), neverEvaluated); + + Assertions.assertEquals(0, eval(product), "the `when c.value() == 0` guard matches before the generic Mul case"); + } + + // Exhaustive switch over a sealed hierarchy: the compiler proves every Expr + // subtype is handled here, so a `default` branch would be dead code. + private static double eval(Expr expr) { + return switch (expr) { + case Const(double value) -> value; + case Mul(Const c, Expr _) when c.value() == 0 -> 0; + case Mul(Expr left, Expr right) -> eval(left) * eval(right); + case Add(Expr left, Expr right) -> eval(left) + eval(right); + }; + } + + sealed interface Expr permits Const, Add, Mul {} + + record Const(double value) implements Expr {} + + record Add(Expr left, Expr right) implements Expr {} + + // Try adding `record Sub(Expr left, Expr right) implements Expr {}` to the + // `permits` list above: eval()'s switch immediately stops compiling because + // it no longer covers every Expr subtype. The sealed hierarchy turns a + // missed case into a build failure instead of a silent runtime bug. + record Mul(Expr left, Expr right) implements Expr {} +}