From 9367644f9377739dcf79b9ac29bff9002b4d9d70 Mon Sep 17 00:00:00 2001 From: "Glen K. Peterson" Date: Fri, 21 Jan 2022 10:18:02 -0500 Subject: [PATCH] - Added OneOf5. A second-class Union Type like OneOf4, but with one more. - Removed OneOf2OrNone; use Option instead. The [OneOf2OrNoneTest](src/test/java/org/organicdesign/fp/oneOf/OneOf2OrNoneTest.java) shows how. - Changed the contained object in all OneOf_ classes from `@Nullable` to `@NotNull` and from `private` to `protected`. If you use @NotNull annotations in your subclass, you can now overload the static factory methods in your subclasses (you can use the same name for all of them). Instead of storing null in a OneOf, simply return a `null` OneOf_ or `Option.none()`. The `protected` lets subclasses access the contained item for easily implementing an interface that the possible values share. The [OneOf2Test](src/test/java/org/organicdesign/fp/oneOf/OneOf2Test.java) shows how. - Test coverage reached 95% by line. --- CHANGE_LOG.md | 13 ++ Paguro.iml | 22 +-- README.md | 2 +- pom.xml | 2 +- .../org/organicdesign/fp/function/Fn0.java | 20 +- .../org/organicdesign/fp/oneOf/OneOf2.java | 35 ++-- .../organicdesign/fp/oneOf/OneOf2OrNone.java | 98 ---------- .../org/organicdesign/fp/oneOf/OneOf3.java | 38 ++-- .../org/organicdesign/fp/oneOf/OneOf4.java | 42 ++-- .../org/organicdesign/fp/oneOf/OneOf5.java | 104 ++++++++++ .../org/organicdesign/fp/oneOf/package.html | 26 +-- .../organicdesign/fp/function/Fn0Test.java | 44 ++--- .../organicdesign/fp/function/Fn2Test.java | 3 + .../fp/oneOf/OneOf2OrNoneTest.java | 88 ++++----- .../organicdesign/fp/oneOf/OneOf2Test.java | 94 ++++++--- .../organicdesign/fp/oneOf/OneOf3Test.java | 51 ++--- .../organicdesign/fp/oneOf/OneOf4Test.java | 80 ++++---- .../organicdesign/fp/oneOf/OneOf5Test.java | 183 ++++++++++++++++++ 18 files changed, 581 insertions(+), 364 deletions(-) delete mode 100644 src/main/java/org/organicdesign/fp/oneOf/OneOf2OrNone.java create mode 100644 src/main/java/org/organicdesign/fp/oneOf/OneOf5.java create mode 100644 src/test/java/org/organicdesign/fp/oneOf/OneOf5Test.java diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index 70e3c160..814baf5c 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -5,6 +5,17 @@ releases on the way from an old version to a new one. Fix any deprecation warni release before upgrading to the next one. The documentation next to each Deprecated annotation tells you what to use instead. Once we delete the deprecated methods, that documentation goes too. +## Release 3.10.0 2022-01-21: "OneOf5" +- Added OneOf5. A second-class Union Type like OneOf4, but with one more. +- Removed OneOf2OrNone; use Option instead. + The [OneOf2OrNoneTest](src/test/java/org/organicdesign/fp/oneOf/OneOf2OrNoneTest.java) shows how. +- Changed the contained object in all OneOf_ classes from `@Nullable` to `@NotNull` and from `private` to `protected`. + If you use @NotNull annotations in your subclass, you can now overload the static factory methods in your subclasses (you can use the same name for all of them). + Instead of storing null in a OneOf, simply return a `null` OneOf_ or `Option.none()`. + The `protected` lets subclasses access the contained item for easily implementing an interface that the possible values share. + The [OneOf2Test](src/test/java/org/organicdesign/fp/oneOf/OneOf2Test.java) shows how. +- Test coverage reached 95% by line. + ## Release 3.9.0 2021-12-30: Removed None.none() - Use Option.none() instead of None.none(). They are duplicate methods. @@ -236,6 +247,7 @@ public class String_Integer extends OneOf2 { public static String_Integer str(String cre) { return new String_Integer(cre, null, 1); } public static String_Integer in(Integer rev) { return new String_Integer(null, rev, 2); } +} ``` to (MAKE SURE TO USE ZERO-BASED INDICES!): ```java @@ -244,6 +256,7 @@ public class String_Integer extends OneOf2 { public static String_Integer str(String cre) { return new String_Integer(cre, 0); } public static String_Integer in(Integer rev) { return new String_Integer(rev, 1); } +} ``` If you used the super::throw1 and super::throw2 methods, those have disappeared in favor of just using match everywhere. diff --git a/Paguro.iml b/Paguro.iml index d140e4a0..32fa4c0c 100644 --- a/Paguro.iml +++ b/Paguro.iml @@ -10,19 +10,17 @@ - + - - - - - - - + + + + + + + - - - - + + \ No newline at end of file diff --git a/README.md b/README.md index b5d8b213..ad2627ae 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Paguro is short for the Latin "Paguroidea" - the name of the Hermit Crab superfa * `map(tup(1, "single"), tup(2, "double"), tup(3, "triple"))` - an immutable map that uses integers to look up appropriate strings. * **Extensible, immutable tuples** [api](https://javadoc.io/doc/org.organicdesign/Paguro/latest/org/organicdesign/fp/tuple/package-summary.html) / [src](src/main/java/org/organicdesign/fp/tuple) - use them for rapid, yet type-safe prototyping, then later extend them to make your own lightweight, immutable Java classes with correct `equals()`, `hashCode()`, and `toString()` implementations. * **Lazy initialization** [api](https://javadoc.io/doc/org.organicdesign/Paguro/latest/org/organicdesign/fp/function/LazyRef.html) / [src](src/main/java/org/organicdesign/fp/function/LazyRef.java) - LazyRef thread-safely performs initialization and frees initialization resources on first use. Subsequent uses get the now-constant initialized value. Use this instead of static initializers to avoid initialization loops. Cache results of expensive operations for reuse. -* **Union types** [api](https://javadoc.io/doc/org.organicdesign/Paguro/latest/org/organicdesign/fp/oneOf/package-summary.html) / [src](src/main/java/org/organicdesign/fp/oneOf) - Not as nice as being built into the language, but they extend type safety outside the object hierarchy. +* **Union types** [api](https://javadoc.io/doc/org.organicdesign/Paguro/latest/org/organicdesign/fp/oneOf/package-summary.html) / [test](src/test/java/org/organicdesign/fp/oneOf/OneOf2Test.java) - Not as nice as being built into the language, but they extend type safety outside the object hierarchy. * **Memoization** [api](https://javadoc.io/doc/org.organicdesign/Paguro/latest/org/organicdesign/fp/function/Fn3.html) / [src](src/main/java/org/organicdesign/fp/function/Fn3.java) - Turns function calls into hashtable lookups to speed up slow functions over a limited range of inputs. * **Tiny** with no dependencies - The entire project fits in a ~270K jar file. diff --git a/pom.xml b/pom.xml index a296563f..fb21b648 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ http://mvnrepository.com/artifact/org.organicdesign/Paguro --> org.organicdesign Paguro - 3.9.0 + 3.10.0 jar Paguro diff --git a/src/main/java/org/organicdesign/fp/function/Fn0.java b/src/main/java/org/organicdesign/fp/function/Fn0.java index 4e064ebf..3f8fe8e3 100644 --- a/src/main/java/org/organicdesign/fp/function/Fn0.java +++ b/src/main/java/org/organicdesign/fp/function/Fn0.java @@ -14,7 +14,6 @@ package org.organicdesign.fp.function; -import java.io.Serializable; import java.util.concurrent.Callable; import java.util.function.Supplier; @@ -50,12 +49,22 @@ default U apply() { // ========================================== Static ========================================== // Enums are serializable. Anonymous classes and lambdas are not. +// Dear Glen, +// You have implemented this twice now. There are 3 reasons to leave it commented out: +// 1. `() -> none` is brief, clear, and beautiful. +// 2. You hate the method reference syntax (it's ambiguous). +// 3. Encourage using Option over null. // /** One-of-a-kind Fn0's. */ -// enum ConstObjObj implements Fn0 { +// enum ConstObj implements Fn0> { // NULL { -// @Override public Object applyEx() throws Exception { return null; } +// @Override +// public @NotNull Option applyEx() throws Exception { return Option.none(); } // } // } +// +// /** Returns a type-safe version of the {@link ConstObj#NULL} thunk. */ +// @SuppressWarnings("unchecked") +// static @NotNull Fn0 nullSupplier() { return (Fn0) ConstObj.NULL; } // /** // Wraps a value in a constant function. If you need to "memoize" some really expensive @@ -76,9 +85,4 @@ default U apply() { // } // // static Fn0 constantFunction(final K k) { return new Constant<>(k); } - -// Don't think this is necessary. Is it? -// default Supplier asSupplier() { -// return () -> apply(); -// } } diff --git a/src/main/java/org/organicdesign/fp/oneOf/OneOf2.java b/src/main/java/org/organicdesign/fp/oneOf/OneOf2.java index c56de285..4db5e01b 100644 --- a/src/main/java/org/organicdesign/fp/oneOf/OneOf2.java +++ b/src/main/java/org/organicdesign/fp/oneOf/OneOf2.java @@ -15,7 +15,6 @@ package org.organicdesign.fp.oneOf; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.organicdesign.fp.collections.ImList; import org.organicdesign.fp.function.Fn1; import org.organicdesign.fp.type.RuntimeTypes; @@ -75,26 +74,26 @@ static class String_Integer extends OneOf2 { // If not an Integer at runtime throws "Expected a(n) Integer but found a(n) String" 3 + x.integer(); } + + Instead of putting a Null object in here, either return a null OneOf2 or wrap OneOf2 in an {@link Option} */ -// TODO: Should this implement javax.lang.model.type.UnionType somehow? public class OneOf2 { - - private final @Nullable Object item; + protected final @NotNull Object item; private final int sel; @SuppressWarnings("rawtypes") private final @NotNull ImList types; /** - Protected constructor for subclassing. A, B, and C parameters can be null, but if one is non-null, the index - must specify the non-null value (to keep you from assigning a bogus index value). - - @param o the item - @param aClass class 0 (to have at runtime for descriptive error messages and toString()). - @param bClass class 1 (to have at runtime for descriptive error messages and toString()). - @param index 0 means this represents an A, 1 represents a B, 2 represents a C, 3 means D + * Protected constructor for subclassing. + * Be extremely careful to pass the correct index! + * + * @param o the item + * @param aClass class 0 + * @param bClass class 1 + * @param index 0 means this represents an A, 1 represents a B */ protected OneOf2( - @Nullable Object o, + @NotNull Object o, @NotNull Class aClass, @NotNull Class bClass, int index @@ -107,7 +106,7 @@ protected OneOf2( } else if (index > 1) { throw new IllegalArgumentException("Selected item index must be 0-1"); } - if ( (o != null) && (!types.get(index).isInstance(o)) ) { + if (!types.get(index).isInstance(o)) { throw new ClassCastException("You specified index " + index + ", indicating a(n) " + types.get(index).getCanonicalName() + "," + " but passed a " + o.getClass().getCanonicalName()); @@ -115,11 +114,11 @@ protected OneOf2( } /** - Languages that have union types built in have a match statement that works like this method. - Exactly one of these functions will be executed - determined by which type of item this object holds. - @param fa the function to be executed if this OneOf stores the first type. - @param fb the function to be executed if this OneOf stores the second type. - @return the return value of whichever function is executed. + * Languages that have union types built in have a match statement that works like this method. + * Exactly one of these functions will be executed - determined by which type of item this object holds. + * @param fa applied iff this stores the first type. + * @param fb applied iff this stores the second type. + * @return the return value of whichever function is executed. */ // We only store one item and its type is erased, so we have to cast it at runtime. // If sel is managed correctly, this ensures that cast is accurate. diff --git a/src/main/java/org/organicdesign/fp/oneOf/OneOf2OrNone.java b/src/main/java/org/organicdesign/fp/oneOf/OneOf2OrNone.java deleted file mode 100644 index 6eb0e417..00000000 --- a/src/main/java/org/organicdesign/fp/oneOf/OneOf2OrNone.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.organicdesign.fp.oneOf; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.organicdesign.fp.collections.ImList; -import org.organicdesign.fp.function.Fn0; -import org.organicdesign.fp.function.Fn1; -import org.organicdesign.fp.type.RuntimeTypes; - -import java.util.Objects; - -import static org.organicdesign.fp.type.RuntimeTypes.union2Str; - -/** - Holds one of 2 values or {@link None}. See {@link OneOf2} for a full description. If you're passing the same type, - you probably want a tuple instead. - */ -public abstract class OneOf2OrNone { - - private final @Nullable Object item; - private final int sel; - @SuppressWarnings("rawtypes") - private final @NotNull ImList types; - - /** - Protected constructor for subclassing. - - @param o the item (may be null) - @param aClass class 0 (to have at runtime for descriptive error messages and toString()). - @param bClass class 1 (to have at runtime for descriptive error messages and toString()). - @param index 0 means this represents an A, 1 represents a B, 2 represents a None - */ - protected OneOf2OrNone( - @Nullable Object o, - @NotNull Class aClass, - @NotNull Class bClass, - int index - ) { - types = RuntimeTypes.registerClasses(aClass, bClass, None.class); - sel = index; - item = o; - if (index < 0) { - throw new IllegalArgumentException("Selected item index must be 0-2"); - } else if (index > 2) { - throw new IllegalArgumentException("Selected item index must be 0-2"); - } else if ( (index == 2) && (o != null) ) { - throw new IllegalArgumentException("You specified the index " + index + - " meaning 'None' but passed a value: " + o); - } - if ( (o != null) && (!types.get(index).isInstance(o)) ) { - throw new ClassCastException("You specified index " + index + ", indicating a(n) " + - types.get(index).getCanonicalName() + "," + - " but passed a " + o.getClass().getCanonicalName()); - } - } - - /** - Languages that have union types built in have a match statement that works like this method. - Exactly one of these functions will be executed - determined by which type of item this object holds. - @param fa the function to be executed if this OneOf stores the first type. - @param fb the function to be executed if this OneOf stores the second type. - @param fz the function to be executed if this OneOf stores a None. - @return the return value of whichever function is executed. - */ - // We only store one item and its type is erased, so we have to cast it at runtime. - // If sel is managed correctly, this ensures that cast is accurate. - @SuppressWarnings("unchecked") - public R match( - @NotNull Fn1 fa, - @NotNull Fn1 fb, - @NotNull Fn0 fz - ) { - if (sel == 0) { - return fa.apply((A) item); - } else if (sel == 1) { - return fb.apply((B) item); - } else { - return fz.apply(); - } - } - - public int hashCode() { - // Simplest way to make the two items different. - return Objects.hashCode(item) + sel; - } - - @Override public boolean equals(Object other) { - if (this == other) { return true; } - if (!(other instanceof OneOf2OrNone)) { return false; } - - @SuppressWarnings("rawtypes") - OneOf2OrNone that = (OneOf2OrNone) other; - return (sel == that.sel) && - Objects.equals(item, that.item); - } - - @Override public String toString() { return union2Str(sel == 2 ? None.NONE : item, types); } -} diff --git a/src/main/java/org/organicdesign/fp/oneOf/OneOf3.java b/src/main/java/org/organicdesign/fp/oneOf/OneOf3.java index 28cf4de6..9c5ce5ca 100644 --- a/src/main/java/org/organicdesign/fp/oneOf/OneOf3.java +++ b/src/main/java/org/organicdesign/fp/oneOf/OneOf3.java @@ -1,7 +1,6 @@ package org.organicdesign.fp.oneOf; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.organicdesign.fp.collections.ImList; import org.organicdesign.fp.function.Fn1; import org.organicdesign.fp.type.RuntimeTypes; @@ -10,27 +9,24 @@ import static org.organicdesign.fp.type.RuntimeTypes.union2Str; -/** - Holds one of 3 values. See {@link OneOf2} for a full description. If you're passing the same type, you probably - want a tuple instead. - */ +/** Holds one of 3 types of value. See {@link OneOf2} for a full description. */ public class OneOf3 { - private final @Nullable Object item; + protected final @NotNull Object item; private final int sel; @SuppressWarnings("rawtypes") private final @NotNull ImList types; /** - Protected constructor for subclassing. - - @param o the item (may be null) - @param aClass class 0 (to have at runtime for descriptive error messages and toString()). - @param bClass class 1 (to have at runtime for descriptive error messages and toString()). - @param cClass class 2 (to have at runtime for descriptive error messages and toString()). - @param index 0 means this represents an A, 1 represents a B, 2 represents a C, 3 means D + * Protected constructor for subclassing. + * + * @param o the item + * @param aClass class 0 + * @param bClass class 1 + * @param cClass class 2 + * @param index 0 means this represents an A, 1 a B, and 2 a C. */ protected OneOf3( - @Nullable Object o, + @NotNull Object o, @NotNull Class aClass, @NotNull Class bClass, @NotNull Class cClass, @@ -44,7 +40,7 @@ protected OneOf3( } else if (index > 2) { throw new IllegalArgumentException("Selected item index must be 0-2"); } - if ( (o != null) && (!types.get(index).isInstance(o)) ) { + if (!types.get(index).isInstance(o)) { throw new ClassCastException("You specified index " + index + ", indicating a(n) " + types.get(index).getCanonicalName() + "," + " but passed a " + o.getClass().getCanonicalName()); @@ -52,12 +48,12 @@ protected OneOf3( } /** - Languages that have union types built in have a match statement that works like this method. - Exactly one of these functions will be executed - determined by which type of item this object holds. - @param fa the function to be executed if this OneOf stores the first type. - @param fb the function to be executed if this OneOf stores the second type. - @param fc the function to be executed if this OneOf stores the third type. - @return the return value of whichever function is executed. + * Languages that have union types built in have a match statement that works like this method. + * Exactly one of these functions will be executed - determined by which type of item this object holds. + * @param fa applied iff this stores the first type. + * @param fb applied iff this stores the second type. + * @param fc applied iff this stores the third type. + * @return the return value of whichever function is executed. */ // We only store one item and its type is erased, so we have to cast it at runtime. // If sel is managed correctly, this ensures that cast is accurate. diff --git a/src/main/java/org/organicdesign/fp/oneOf/OneOf4.java b/src/main/java/org/organicdesign/fp/oneOf/OneOf4.java index 7e09cea9..eca47ec3 100644 --- a/src/main/java/org/organicdesign/fp/oneOf/OneOf4.java +++ b/src/main/java/org/organicdesign/fp/oneOf/OneOf4.java @@ -1,7 +1,6 @@ package org.organicdesign.fp.oneOf; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.organicdesign.fp.collections.ImList; import org.organicdesign.fp.function.Fn1; import org.organicdesign.fp.type.RuntimeTypes; @@ -10,28 +9,25 @@ import static org.organicdesign.fp.type.RuntimeTypes.union2Str; -/** - Holds one of 4 values. See {@link OneOf2} for a full description. If you're passing the same type, you probably - want a tuple instead. - */ +/** Holds one of 4 types of value. See {@link OneOf2} for a full description. */ public abstract class OneOf4 { - private final @Nullable Object item; + protected final @NotNull Object item; private final int sel; @SuppressWarnings("rawtypes") private final @NotNull ImList types; /** - Protected constructor for subclassing. - - @param o the item (may be null) - @param aClass class 0 (to have at runtime for descriptive error messages and toString()). - @param bClass class 1 (to have at runtime for descriptive error messages and toString()). - @param cClass class 2 (to have at runtime for descriptive error messages and toString()). - @param dClass class 3 (to have at runtime for descriptive error messages and toString()). - @param index 0 means this represents an A, 1 represents a B, 2 represents a C, 3 means D + * Protected constructor for subclassing. + * + * @param o the item + * @param aClass class 0 + * @param bClass class 1 + * @param cClass class 2 + * @param dClass class 3 + * @param index 0 means this represents an A, 1 a B, 2 a C, and 3 a D. */ protected OneOf4( - @Nullable Object o, + @NotNull Object o, @NotNull Class aClass, @NotNull Class bClass, @NotNull Class cClass, @@ -46,7 +42,7 @@ protected OneOf4( } else if (index > 3) { throw new IllegalArgumentException("Selected item index must be 0-3"); } - if ( (o != null) && (!types.get(index).isInstance(o)) ) { + if (!types.get(index).isInstance(o)) { throw new ClassCastException("You specified index " + index + ", indicating a(n) " + types.get(index).getCanonicalName() + "," + " but passed a " + o.getClass().getCanonicalName()); @@ -54,13 +50,13 @@ protected OneOf4( } /** - Languages that have union types built in have a match statement that works like this method. - Exactly one of these functions will be executed - determined by which type of item this object holds. - @param fa the function to be executed if this OneOf stores the first type. - @param fb the function to be executed if this OneOf stores the second type. - @param fc the function to be executed if this OneOf stores the third type. - @param fd the function to be executed if this OneOf stores the fourth type. - @return the return value of whichever function is executed. + * Languages that have union types built in have a match statement that works like this method. + * Exactly one of these functions will be executed - determined by which type of item this object holds. + * @param fa applied iff this stores the first type. + * @param fb applied iff this stores the second type. + * @param fc applied iff this stores the third type. + * @param fd applied iff this stores the fourth type. + * @return the return value of whichever function is executed. */ // We only store one item and its type is erased, so we have to cast it at runtime. // If sel is managed correctly, this ensures that cast is accurate. diff --git a/src/main/java/org/organicdesign/fp/oneOf/OneOf5.java b/src/main/java/org/organicdesign/fp/oneOf/OneOf5.java new file mode 100644 index 00000000..58c20205 --- /dev/null +++ b/src/main/java/org/organicdesign/fp/oneOf/OneOf5.java @@ -0,0 +1,104 @@ +package org.organicdesign.fp.oneOf; + +import org.jetbrains.annotations.NotNull; +import org.organicdesign.fp.collections.ImList; +import org.organicdesign.fp.function.Fn1; +import org.organicdesign.fp.type.RuntimeTypes; + +import java.util.Objects; + +import static org.organicdesign.fp.type.RuntimeTypes.union2Str; + +/** Holds one of 5 types of value. See {@link OneOf2} for a full description. */ +public abstract class OneOf5 { + protected final @NotNull Object item; + private final int sel; + @SuppressWarnings("rawtypes") + private final @NotNull ImList types; + + /** + * Protected constructor for subclassing. + * + * @param o the item + * @param aClass class 0 + * @param bClass class 1 + * @param cClass class 2 + * @param dClass class 3 + * @param eClass class 4 + * @param index 0 means this represents an A, 1 a B, 2 a C, 3 a D, and 4 an E. + */ + protected OneOf5( + @NotNull Object o, + @NotNull Class aClass, + @NotNull Class bClass, + @NotNull Class cClass, + @NotNull Class dClass, + @NotNull Class eClass, + int index + ) { + types = RuntimeTypes.registerClasses(aClass, bClass, cClass, dClass, eClass); + sel = index; + item = o; + if (index < 0) { + throw new IllegalArgumentException("Selected item index must be 0-4"); + } else if (index > 4) { + throw new IllegalArgumentException("Selected item index must be 0-4"); + } + if (!types.get(index).isInstance(o)) { + throw new ClassCastException("You specified index " + index + ", indicating a(n) " + + types.get(index).getCanonicalName() + "," + + " but passed a " + o.getClass().getCanonicalName()); + } + } + + /** + * Languages that have union types built in have a match statement that works like this method. + * Exactly one of these functions will be executed - determined by which type of item this object holds. + * @param fa applied iff this stores the first type. + * @param fb applied iff this stores the second type. + * @param fc applied iff this stores the third type. + * @param fd applied iff this stores the fourth type. + * @param fe applied iff this stores the fifth type. + * @return the return value of whichever function is executed. + */ + // We only store one item and its type is erased, so we have to cast it at runtime. + // If sel is managed correctly, this ensures that cast is accurate. + @SuppressWarnings("unchecked") + public R match( + @NotNull Fn1 fa, + @NotNull Fn1 fb, + @NotNull Fn1 fc, + @NotNull Fn1 fd, + @NotNull Fn1 fe + ) { + if (sel == 0) { + return fa.apply((A) item); + } else if (sel == 1) { + return fb.apply((B) item); + } else if (sel == 2) { + return fc.apply((C) item); + } else if (sel == 3) { + return fd.apply((D) item); + } else { + return fe.apply((E) item); + } + } + + public int hashCode() { + // Simplest way to make the two items different. + return Objects.hashCode(item) + sel; + } + + @Override public boolean equals(Object other) { + if (this == other) { return true; } + if (!(other instanceof OneOf5)) { return false; } + + @SuppressWarnings("rawtypes") + OneOf5 that = (OneOf5) other; + return (sel == that.sel) && + Objects.equals(item, that.item); + } + + @Override + public @NotNull String toString() { return union2Str(item, types); } +} \ No newline at end of file diff --git a/src/main/java/org/organicdesign/fp/oneOf/package.html b/src/main/java/org/organicdesign/fp/oneOf/package.html index 1a5791b3..990d2552 100644 --- a/src/main/java/org/organicdesign/fp/oneOf/package.html +++ b/src/main/java/org/organicdesign/fp/oneOf/package.html @@ -1,4 +1,4 @@ - +

This package contains Option which has Some() and None() which is useful for indicating "Not-Found" or "End-of-stream/file". It also has Or which has Good() and Bad() for functional error @@ -38,7 +38,7 @@

Really, we want to move to a type system where types are sets. ML had this in 1990. - Then Object Oriented programming took the world by storm and types without Objects waned. + Then Object-Oriented programming took the world by storm and types without Objects waned. In retrospect, we may have missed something.

Here's an example using OneOf2. @@ -48,21 +48,21 @@ // You need to subclass a OneOf class with your own class like this: static class Str_Int extends OneOf2<String,Integer> { // Constructor - private Str_Int(Object o, int n) { super(o, String.class, Integer.class, n); } + private Str_Int(@NotNull Object o, int n) { super(o, String.class, Integer.class, n); } // Static Factory Methods: // Make sure to use consecutive integers starting from zero // for the second constructor argument. These ints represent // indices that select the classes you passed to the call to // super() above. So String uses index 0: - static Str_Int ofStr(String o) { return new Str_Int(o, 0); } + static Str_Int of(@NotNull String o) { return new Str_Int(o, 0); } // Integer uses index 1: - static Str_Int ofInt(Integer o) { return new Str_Int(o, 1); } + static Str_Int of(@NotNull Integer o) { return new Str_Int(o, 1); } } // Now create a new instance with your factory -Str_Int soi = Str_Int.ofInt(57); +Str_Int soi = Str_Int.of(57); // Finally, use your new instance. Notice that this forces you // to account for all cases of what the union type could contain. @@ -100,7 +100,7 @@ } -

What if you don't use Cells any more? Dead code. +

What if you don't use Cells anymore? Dead code. What if you forget to code for some type? Runtime Bugs. What if parent can be some additional type in the future, like a Table? Runtime Bugs.

@@ -111,16 +111,16 @@ extends OneOf3<Document,Paragraph,Cell> { // Constructor - protected Doc_Para_Li_Cell(Object o, int s) { + protected Doc_Para_Li_Cell(@NotNull Object o, int s) { super(o, Document.class, Paragraph.class, Cell.class, s); } // Static factory methods - public static Doc_Para_Li_Cell ofDoc(Document document) { + public static Doc_Para_Li_Cell of(@NotNull Document document) { return new Doc_Para_Li_Cell(document, 0); } - public static Doc_Para_Li_Cell ofPara(Paragraph p) { return new Doc_Para_Li_Cell(p, 1); } - public static Doc_Para_Li_Cell ofCell(Cell c) { return new Doc_Para_Li_Cell(c, 2); } + public static Doc_Para_Li_Cell of(@NotNull Paragraph p) { return new Doc_Para_Li_Cell(p, 1); } + public static Doc_Para_Li_Cell of(@NotNull Cell c) { return new Doc_Para_Li_Cell(c, 2); } }

Now in your code, when you want to add a bunch of text, you can do it the right way for each. No casts, @@ -142,7 +142,7 @@ para -> { throw new IllegalStateException("Can't add a table to a paragraph."); }, cell -> { cell.add(table); return null; }); -

Now if you decide not to use Cells any more, you switch Doc_Para_Li_Cell to extend OneOf2, rename it to Doc_Para_Li +

Now if you decide not to use Cells anymore, switch Doc_Para_Li_Cell to extend OneOf2, rename it to Doc_Para_Li and fix all the compile-time errors. To add a Table type, extend OneOf4. No dead code. No bugs. Union types extend type safety outside the object hierarchy. @@ -150,7 +150,7 @@ Well, Kotlin already has it for nulls, just not in the general case.

diff --git a/src/test/java/org/organicdesign/fp/function/Fn0Test.java b/src/test/java/org/organicdesign/fp/function/Fn0Test.java index 6ee31245..f793e08c 100644 --- a/src/test/java/org/organicdesign/fp/function/Fn0Test.java +++ b/src/test/java/org/organicdesign/fp/function/Fn0Test.java @@ -1,19 +1,18 @@ package org.organicdesign.fp.function; -import java.io.IOException; -import java.util.concurrent.Callable; - import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import static org.junit.Assert.*; -import static org.organicdesign.testUtils.EqualsContract.equalsDistinctHashCode; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; @RunWith(JUnit4.class) public class Fn0Test { @Test(expected = RuntimeException.class) public void applyIOException() { + //noinspection Convert2Lambda new Fn0() { @Override public Integer applyEx() throws Exception { throw new IOException("test exception"); @@ -23,19 +22,17 @@ public void applyIOException() { @Test(expected = IllegalStateException.class) public void applyIllegalStateException() { - new Fn0() { - @Override public Integer applyEx() throws Exception { - throw new IllegalStateException("test exception"); - } - }.apply(); + ((Fn0) () -> { + throw new IllegalStateException("test exception"); + }).apply(); } -// @Test public void constantFunction() throws Exception { -// Fn0 f = Fn0.constantFunction(7); -// assertEquals(Integer.valueOf(7), f.apply()); -// assertEquals(Integer.valueOf(7), f.applyEx()); -// assertEquals(Integer.valueOf(7), f.get()); -// assertEquals(Integer.valueOf(7), f.call()); + @Test public void constantFunction() throws Exception { + Fn0 f = () -> 7; + assertEquals(Integer.valueOf(7), f.apply()); + assertEquals(Integer.valueOf(7), f.applyEx()); + assertEquals(Integer.valueOf(7), f.get()); + assertEquals(Integer.valueOf(7), f.call()); // assertEquals(f.hashCode(), Fn0.constantFunction(Integer.valueOf(7)).hashCode()); // assertTrue(f.equals(Fn0.constantFunction(Integer.valueOf(7)))); // @@ -51,19 +48,12 @@ public void applyIllegalStateException() { // assertNotEquals(Fn0.constantFunction(null), null); // // assertFalse(Fn0.constantFunction(35).equals((Callable) () -> 35)); -// } + } @Test(expected = IllegalStateException.class) public void testCall() throws Exception { - new Fn0() { - @Override public Integer applyEx() throws Exception { - throw new IllegalStateException("test exception"); - } - }.call(); - + ((Fn0) () -> { + throw new IllegalStateException("test exception"); + }).call(); } - -// @Test public void testNull() { -// assertNull(Fn0.ConstObjObj.NULL.apply()); -// } } diff --git a/src/test/java/org/organicdesign/fp/function/Fn2Test.java b/src/test/java/org/organicdesign/fp/function/Fn2Test.java index 61db23ff..4cd2ebe7 100644 --- a/src/test/java/org/organicdesign/fp/function/Fn2Test.java +++ b/src/test/java/org/organicdesign/fp/function/Fn2Test.java @@ -61,5 +61,8 @@ public void applyIllegalStateException() { assertEquals(3, counter.get()); assertEquals("3~2.5", g.apply(3, 2.5)); assertEquals(3, counter.get()); + + assertEquals(3.5, Fn2.first().apply(3.5, 4.0)); + assertEquals(4.0, Fn2.second().apply(3.5, 4.0)); } } diff --git a/src/test/java/org/organicdesign/fp/oneOf/OneOf2OrNoneTest.java b/src/test/java/org/organicdesign/fp/oneOf/OneOf2OrNoneTest.java index 9aefbab6..a43ccfed 100644 --- a/src/test/java/org/organicdesign/fp/oneOf/OneOf2OrNoneTest.java +++ b/src/test/java/org/organicdesign/fp/oneOf/OneOf2OrNoneTest.java @@ -1,5 +1,6 @@ package org.organicdesign.fp.oneOf; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import org.organicdesign.testUtils.EqualsContract; @@ -9,81 +10,70 @@ public class OneOf2OrNoneTest { - static class Str_Int_None extends OneOf2OrNone { - + static class Str_Int extends OneOf2 { // Constructor - private Str_Int_None(Object o, int n) { super(o, String.class, Integer.class, n); } + private Str_Int(@NotNull Object o, int n) { super(o, String.class, Integer.class, n); } // Static factory methods - static Str_Int_None ofStr(String o) { return new Str_Int_None(o, 0); } - static Str_Int_None ofInt(Integer o) { return new Str_Int_None(o, 1); } - static Str_Int_None ofNone() { return new Str_Int_None(null, 2); } + static Option of(@NotNull String o) { return Option.some(new Str_Int(o, 0)); } + static Option of(@NotNull Integer o) { return Option.some(new Str_Int(o, 1)); } + static Option ofNone() { return Option.none(); } } @Test public void testBasics() { - Str_Int_None sin = Str_Int_None.ofInt(57); - assertEquals(Integer.valueOf(57), sin.match(x -> -99, - y -> y, - () -> -99)); - assertEquals("57:String|Integer|None", sin.toString()); - - sin = Str_Int_None.ofStr("right"); - assertEquals("right", sin.match(x -> x, - y -> "wrong", - () -> "wrong")); - assertEquals("\"right\":String|Integer|None", sin.toString()); - - sin = Str_Int_None.ofNone(); - assertEquals("right", sin.match(x -> "wrong", - y -> "wrong", - () -> "right")); - assertEquals("None:String|Integer|None", sin.toString()); + Option si = Str_Int.of(57); + assertEquals(Integer.valueOf(57), si.match(some -> some.match(x -> -99, + y -> y), + () -> -99)); + assertEquals("Some(57:String|Integer)", si.toString()); + + si = Str_Int.of("right"); + assertEquals("right", si.match(some -> some.match(x -> x, + y -> "wrong"), + () -> "wrong")); + assertEquals("Some(\"right\":String|Integer)", si.toString()); + + si = Str_Int.ofNone(); + assertEquals("right", si.match(some -> some.match(x -> "wrong", + y -> "wrong"), + () -> "right")); + assertEquals("None", si.toString()); assertEquals(None.NONE, serializeDeserialize(none())); } @Test public void testEquality() { - assertEquals(0, Str_Int_None.ofStr(null).hashCode()); - assertEquals(1, Str_Int_None.ofInt(null).hashCode()); - assertEquals(2, Str_Int_None.ofNone().hashCode()); - - EqualsContract.equalsDistinctHashCode(Str_Int_None.ofStr("one"), Str_Int_None.ofStr("one"), - Str_Int_None.ofStr("one"), - Str_Int_None.ofStr("onf")); + assertEquals(0, Str_Int.of("").hashCode()); + assertEquals(1, Str_Int.of(0).hashCode()); + assertEquals(none().hashCode(), Str_Int.ofNone().hashCode()); - EqualsContract.equalsDistinctHashCode(Str_Int_None.ofInt(97), Str_Int_None.ofInt(97), - Str_Int_None.ofInt(97), - Str_Int_None.ofInt(-97)); + EqualsContract.equalsDistinctHashCode(Str_Int.of("one"), Str_Int.of("one"), + Str_Int.of("one"), + Str_Int.of("onf")); - EqualsContract.equalsDistinctHashCode(Str_Int_None.ofNone(), Str_Int_None.ofNone(), - Str_Int_None.ofNone(), - Str_Int_None.ofInt(-97)); + EqualsContract.equalsDistinctHashCode(Str_Int.of(97), Str_Int.of(97), + Str_Int.of(97), + Str_Int.of(-97)); } @Test(expected = IllegalArgumentException.class) - public void subClassEx__n() { new Str_Int_None(null, -1); } - - @Test(expected = IllegalArgumentException.class) - public void subClassExa_n() { new Str_Int_None("hi", -1); } + public void subClassExa_n() { new Str_Int("hi", -1); } @Test(expected = IllegalArgumentException.class) - public void subClassEx_bn() { new Str_Int_None(1, -1); } + public void subClassEx_bn() { new Str_Int(1, -1); } @Test(expected = ClassCastException.class) - public void subClassExab_0() { new Str_Int_None(1, 0); } + public void subClassExab_0() { new Str_Int(1, 0); } @Test(expected = ClassCastException.class) - public void subClassExa_1() { new Str_Int_None("hi", 1); } - - @Test(expected = IllegalArgumentException.class) - public void subClassExa_2() { new Str_Int_None("hi", 2); } + public void subClassExa_1() { new Str_Int("hi", 1); } @Test(expected = IllegalArgumentException.class) - public void subClassEx_b2() { new Str_Int_None(1, 2); } + public void subClassExa_2() { new Str_Int("hi", 2); } @Test(expected = IllegalArgumentException.class) - public void subClassEx_n3() { new Str_Int_None(null, 3); } + public void subClassEx_b2() { new Str_Int(1, 2); } @Test(expected = IllegalArgumentException.class) - public void subClassEx_b3() { new Str_Int_None(1, 3); } + public void subClassEx_b3() { new Str_Int(1, 3); } } \ No newline at end of file diff --git a/src/test/java/org/organicdesign/fp/oneOf/OneOf2Test.java b/src/test/java/org/organicdesign/fp/oneOf/OneOf2Test.java index f247c7a7..6325cae3 100644 --- a/src/test/java/org/organicdesign/fp/oneOf/OneOf2Test.java +++ b/src/test/java/org/organicdesign/fp/oneOf/OneOf2Test.java @@ -1,5 +1,6 @@ package org.organicdesign.fp.oneOf; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import org.organicdesign.testUtils.EqualsContract; @@ -7,58 +8,95 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +/** + * 2nd-class union types for Java. There are 2 examples here. One of a union of unrelated types (String and Integer) + * and the other of two types (String and StringBuilder) that share a common interface (CharSequence). + * You may want to add an "OrNull" or "OrMissing" case to any of the OneOf_ types, in which case + * look at {@link OneOf2OrNoneTest} to see how this is done. + */ public class OneOf2Test { + /** No common ancestor */ static class Str_Int extends OneOf2 { // Constructor - private Str_Int(Object o, int n) { super(o, String.class, Integer.class, n); } + private Str_Int(@NotNull Object o, int n) { super(o, String.class, Integer.class, n); } // Static factory methods - static Str_Int ofStr(String o) { return new Str_Int(o, 0); } - static Str_Int ofInt(Integer o) { return new Str_Int(o, 1); } + static Str_Int of(@NotNull String o) { return new Str_Int(o, 0); } + static Str_Int of(@NotNull Integer o) { return new Str_Int(o, 1); } } @Test public void testBasics() { - Str_Int oota = Str_Int.ofInt(57); + Str_Int oota = Str_Int.of(57); assertEquals(Integer.valueOf(57), oota.match(x -> -99, y -> y)); -// assertEquals(Integer.valueOf(57), oota.integer()); - Str_Int ootb = Str_Int.ofStr("right"); + Str_Int ootb = Str_Int.of("right"); assertEquals("right", ootb.match(x -> x, y -> "wrong")); -// assertEquals("right", ootb.str()); } -// @Test(expected = ClassCastException.class) -// public void testEx1() { Str_Int.ofInt(57).throw1("hi"); } -// -// @Test(expected = ClassCastException.class) -// public void testEx2() { Str_Int.ofStr("good").throw2(23); } - @Test public void testEquality() { - assertEquals(0, Str_Int.ofStr(null).hashCode()); - assertEquals(1, Str_Int.ofInt(null).hashCode()); + assertEquals(0, Str_Int.of("").hashCode()); + assertEquals(1, Str_Int.of(0).hashCode()); - assertFalse(Str_Int.ofInt(41).equals(Str_Int.ofStr("A"))); - assertFalse(Str_Int.ofStr("A").equals(Str_Int.ofInt(41))); + assertFalse(Str_Int.of(41).equals(Str_Int.of("A"))); + assertFalse(Str_Int.of("A").equals(Str_Int.of(41))); - assertFalse(Str_Int.ofInt(65).equals(Str_Int.ofStr("A"))); - assertFalse(Str_Int.ofStr("A").equals(Str_Int.ofInt(65))); + assertFalse(Str_Int.of(65).equals(Str_Int.of("A"))); + assertFalse(Str_Int.of("A").equals(Str_Int.of(65))); - assertTrue(Str_Int.ofInt(37).equals(Str_Int.ofInt(37))); + assertTrue(Str_Int.of(37).equals(Str_Int.of(37))); - EqualsContract.equalsDistinctHashCode(Str_Int.ofStr("one"), Str_Int.ofStr("one"), - Str_Int.ofStr("one"), - Str_Int.ofStr("onf")); + EqualsContract.equalsDistinctHashCode(Str_Int.of("one"), Str_Int.of("one"), + Str_Int.of("one"), + Str_Int.of("onf")); - EqualsContract.equalsDistinctHashCode(Str_Int.ofInt(97), Str_Int.ofInt(97), - Str_Int.ofInt(97), - Str_Int.ofInt(-97)); + EqualsContract.equalsDistinctHashCode(Str_Int.of(97), Str_Int.of(97), + Str_Int.of(97), + Str_Int.of(-97)); } - @Test(expected = IllegalArgumentException.class) - public void subClassEx____n() { new Str_Int(null, -1); } + /** + * String and StringBuilder both implement CharSequence, so our OneOf2 subtype can too. + * Polymorphism when you want it, union types when you don't. + */ + static class Str_Bldr extends OneOf2 implements CharSequence { + // Constructor + private Str_Bldr(@NotNull Object o, int n) { super(o, String.class, StringBuilder.class, n); } + + // Static factory methods + static Str_Bldr of(@NotNull String o) { return new Str_Bldr(o, 0); } + static Str_Bldr of(@NotNull StringBuilder o) { return new Str_Bldr(o, 1); } + + /** Access `super.item` and cast it to the common interface to implement these methods */ + @Override + public int length() { return ((CharSequence) item).length(); } + + @Override + public char charAt(int index) { return ((CharSequence) item).charAt(index); } + + @NotNull + @Override + public CharSequence subSequence(int start, int end) { return ((CharSequence) item).subSequence(start, end); } + } + + @Test + public void testImplementsCommonSuperclass() { + Str_Bldr sOrBs = Str_Bldr.of("hello"); + assertEquals(5, sOrBs.length()); + assertEquals('e', sOrBs.charAt(1)); + assertEquals("el", sOrBs.subSequence(1, 3)); + assertTrue(sOrBs.match(s -> true, + sB -> false)); + + Str_Bldr sOrBb = Str_Bldr.of(new StringBuilder("goodbye")); + assertEquals(7, sOrBb.length()); + assertEquals('y', sOrBb.charAt(5)); + assertEquals("db", sOrBb.subSequence(3, 5)); + assertTrue(sOrBb.match(s -> false, + sB -> true)); + } @Test(expected = IllegalArgumentException.class) public void subClassExa___n() { new Str_Int("hi", -1); } diff --git a/src/test/java/org/organicdesign/fp/oneOf/OneOf3Test.java b/src/test/java/org/organicdesign/fp/oneOf/OneOf3Test.java index 500f491f..db92ab1a 100644 --- a/src/test/java/org/organicdesign/fp/oneOf/OneOf3Test.java +++ b/src/test/java/org/organicdesign/fp/oneOf/OneOf3Test.java @@ -1,5 +1,6 @@ package org.organicdesign.fp.oneOf; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import org.organicdesign.testUtils.EqualsContract; @@ -11,31 +12,31 @@ public class OneOf3Test { static class Str_Int_Float extends OneOf3 { // Constructor - private Str_Int_Float(Object o, int n) { + private Str_Int_Float(@NotNull Object o, int n) { super(o, String.class, Integer.class, Float.class, n); } // Static factory methods - static Str_Int_Float ofStr(String o) { return new Str_Int_Float(o, 0); } - static Str_Int_Float ofInt(Integer o) { return new Str_Int_Float(o, 1); } - static Str_Int_Float ofFloat(Float o) { return new Str_Int_Float(o, 2); } + static Str_Int_Float of(@NotNull String o) { return new Str_Int_Float(o, 0); } + static Str_Int_Float of(@NotNull Integer o) { return new Str_Int_Float(o, 1); } + static Str_Int_Float of(@NotNull Float o) { return new Str_Int_Float(o, 2); } } @Test public void testBasics() { - Str_Int_Float oots = Str_Int_Float.ofStr("right"); + Str_Int_Float oots = Str_Int_Float.of("right"); assertEquals("right", oots.match(s -> s, i -> "wrong", f -> "bad")); assertEquals("\"right\":String|Integer|Float", oots.toString()); - Str_Int_Float ooti = Str_Int_Float.ofInt(57); + Str_Int_Float ooti = Str_Int_Float.of(57); assertEquals(Integer.valueOf(57), ooti.match(s -> -99, i -> i, f -> 99)); assertEquals("57:String|Integer|Float", ooti.toString()); - Str_Int_Float ootf = Str_Int_Float.ofFloat(57.2f); + Str_Int_Float ootf = Str_Int_Float.of(57.2f); assertEquals(Float.valueOf(57.2f), ootf.match(s -> -99f, i -> 99f, f -> f)); @@ -43,30 +44,30 @@ public void testBasics() { } @Test public void testEquality() { - assertEquals(0, Str_Int_Float.ofStr(null).hashCode()); - assertEquals(1, Str_Int_Float.ofInt(null).hashCode()); - assertEquals(2, Str_Int_Float.ofFloat(null).hashCode()); + assertEquals(0, Str_Int_Float.of("").hashCode()); + assertEquals(1, Str_Int_Float.of(0).hashCode()); + assertEquals(2, Str_Int_Float.of(0f).hashCode()); - assertFalse(Str_Int_Float.ofFloat(5f).equals(Str_Int_Float.ofInt(5))); - assertFalse(Str_Int_Float.ofInt(41).equals(Str_Int_Float.ofStr("A"))); - assertFalse(Str_Int_Float.ofStr("A").equals(Str_Int_Float.ofInt(41))); + assertFalse(Str_Int_Float.of(5f).equals(Str_Int_Float.of(5))); + assertFalse(Str_Int_Float.of(41).equals(Str_Int_Float.of("A"))); + assertFalse(Str_Int_Float.of("A").equals(Str_Int_Float.of(41))); - assertFalse(Str_Int_Float.ofInt(65).equals(Str_Int_Float.ofStr("A"))); - assertFalse(Str_Int_Float.ofStr("A").equals(Str_Int_Float.ofInt(65))); + assertFalse(Str_Int_Float.of(65).equals(Str_Int_Float.of("A"))); + assertFalse(Str_Int_Float.of("A").equals(Str_Int_Float.of(65))); - assertTrue(Str_Int_Float.ofInt(37).equals(Str_Int_Float.ofInt(37))); + assertTrue(Str_Int_Float.of(37).equals(Str_Int_Float.of(37))); - EqualsContract.equalsDistinctHashCode(Str_Int_Float.ofStr("one"), Str_Int_Float.ofStr("one"), - Str_Int_Float.ofStr("one"), - Str_Int_Float.ofStr("onf")); + EqualsContract.equalsDistinctHashCode(Str_Int_Float.of("one"), Str_Int_Float.of("one"), + Str_Int_Float.of("one"), + Str_Int_Float.of("onf")); - EqualsContract.equalsDistinctHashCode(Str_Int_Float.ofInt(97), Str_Int_Float.ofInt(97), - Str_Int_Float.ofInt(97), - Str_Int_Float.ofInt(-97)); + EqualsContract.equalsDistinctHashCode(Str_Int_Float.of(97), Str_Int_Float.of(97), + Str_Int_Float.of(97), + Str_Int_Float.of(-97)); - EqualsContract.equalsDistinctHashCode(Str_Int_Float.ofFloat(17f), Str_Int_Float.ofFloat(17f), - Str_Int_Float.ofFloat(17f), - Str_Int_Float.ofFloat(-17f)); + EqualsContract.equalsDistinctHashCode(Str_Int_Float.of(17f), Str_Int_Float.of(17f), + Str_Int_Float.of(17f), + Str_Int_Float.of(-17f)); } @Test(expected = IllegalArgumentException.class) diff --git a/src/test/java/org/organicdesign/fp/oneOf/OneOf4Test.java b/src/test/java/org/organicdesign/fp/oneOf/OneOf4Test.java index cad945fc..7e3b5d51 100644 --- a/src/test/java/org/organicdesign/fp/oneOf/OneOf4Test.java +++ b/src/test/java/org/organicdesign/fp/oneOf/OneOf4Test.java @@ -17,36 +17,36 @@ private Str_Int_Float_Dub(Object o, int sel) { } // Static factory methods - static Str_Int_Float_Dub ofStr(String o) { return new Str_Int_Float_Dub(o, 0); } - static Str_Int_Float_Dub ofInt(Integer o) { return new Str_Int_Float_Dub(o, 1); } - static Str_Int_Float_Dub ofFloat(Float o) { return new Str_Int_Float_Dub(o, 2); } - static Str_Int_Float_Dub ofDub(Double o) { return new Str_Int_Float_Dub(o, 3); } + static Str_Int_Float_Dub of(String o) { return new Str_Int_Float_Dub(o, 0); } + static Str_Int_Float_Dub of(Integer o) { return new Str_Int_Float_Dub(o, 1); } + static Str_Int_Float_Dub of(Float o) { return new Str_Int_Float_Dub(o, 2); } + static Str_Int_Float_Dub of(Double o) { return new Str_Int_Float_Dub(o, 3); } } @Test public void testBasics() { - Str_Int_Float_Dub oots = Str_Int_Float_Dub.ofStr("right"); + Str_Int_Float_Dub oots = Str_Int_Float_Dub.of("right"); assertEquals("right", oots.match(s -> s, i -> "wrong", f -> "bad", d -> "evil")); assertEquals("\"right\":String|Integer|Float|Double", oots.toString()); - Str_Int_Float_Dub ooti = Str_Int_Float_Dub.ofInt(57); + Str_Int_Float_Dub ooti = Str_Int_Float_Dub.of(57); assertEquals(Integer.valueOf(57), ooti.match(s -> -99, i -> i, f -> 99, d -> 2)); assertEquals("57:String|Integer|Float|Double", ooti.toString()); - Str_Int_Float_Dub ootf = Str_Int_Float_Dub.ofFloat(57.2f); + Str_Int_Float_Dub ootf = Str_Int_Float_Dub.of(57.2f); assertEquals(Float.valueOf(57.2f), ootf.match(s -> -99f, i -> 99f, f -> f, d -> 2)); assertEquals("57.2:String|Integer|Float|Double", ootf.toString()); - Str_Int_Float_Dub ootd = Str_Int_Float_Dub.ofDub(17.2); + Str_Int_Float_Dub ootd = Str_Int_Float_Dub.of(17.2); assertEquals(Double.valueOf(17.2), ootd.match(s -> -99f, i -> 99f, f -> 2, @@ -55,38 +55,38 @@ public void testBasics() { } @Test public void testEquality() { - assertEquals(0, Str_Int_Float_Dub.ofStr(null).hashCode()); - assertEquals(1, Str_Int_Float_Dub.ofInt(null).hashCode()); - assertEquals(2, Str_Int_Float_Dub.ofFloat(null).hashCode()); - assertEquals(3, Str_Int_Float_Dub.ofDub(null).hashCode()); - assertNotEquals(3, Str_Int_Float_Dub.ofDub(-1.3).hashCode()); - - assertFalse(Str_Int_Float_Dub.ofFloat(5f).equals(Str_Int_Float_Dub.ofInt(5))); - assertFalse(Str_Int_Float_Dub.ofInt(41).equals(Str_Int_Float_Dub.ofStr("A"))); - assertFalse(Str_Int_Float_Dub.ofStr("A").equals(Str_Int_Float_Dub.ofInt(41))); - assertFalse(Str_Int_Float_Dub.ofFloat(-19.3f).equals(Str_Int_Float_Dub.ofDub(-19.3))); - - assertFalse(Str_Int_Float_Dub.ofInt(65).equals(Str_Int_Float_Dub.ofStr("A"))); - assertFalse(Str_Int_Float_Dub.ofStr("A").equals(Str_Int_Float_Dub.ofInt(65))); - assertFalse(Str_Int_Float_Dub.ofFloat(65.0f).equals(Str_Int_Float_Dub.ofDub(65.0))); - - assertTrue(Str_Int_Float_Dub.ofInt(37).equals(Str_Int_Float_Dub.ofInt(37))); - - EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub.ofStr("one"), Str_Int_Float_Dub.ofStr("one"), - Str_Int_Float_Dub.ofStr("one"), - Str_Int_Float_Dub.ofStr("onf")); - - EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub.ofInt(97), Str_Int_Float_Dub.ofInt(97), - Str_Int_Float_Dub.ofInt(97), - Str_Int_Float_Dub.ofInt(-97)); - - EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub.ofFloat(17f), Str_Int_Float_Dub.ofFloat(17f), - Str_Int_Float_Dub.ofFloat(17f), - Str_Int_Float_Dub.ofFloat(-17f)); - - EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub.ofDub(31.7), Str_Int_Float_Dub.ofDub(31.7), - Str_Int_Float_Dub.ofDub(31.7), - Str_Int_Float_Dub.ofDub(-3333.7)); + assertEquals(0, Str_Int_Float_Dub.of("").hashCode()); + assertEquals(1, Str_Int_Float_Dub.of(0).hashCode()); + assertEquals(2, Str_Int_Float_Dub.of(0f).hashCode()); + assertEquals(3, Str_Int_Float_Dub.of(0.0).hashCode()); + assertNotEquals(3, Str_Int_Float_Dub.of(-1.3).hashCode()); + + assertFalse(Str_Int_Float_Dub.of(5f).equals(Str_Int_Float_Dub.of(5))); + assertFalse(Str_Int_Float_Dub.of(41).equals(Str_Int_Float_Dub.of("A"))); + assertFalse(Str_Int_Float_Dub.of("A").equals(Str_Int_Float_Dub.of(41))); + assertFalse(Str_Int_Float_Dub.of(-19.3f).equals(Str_Int_Float_Dub.of(-19.3))); + + assertFalse(Str_Int_Float_Dub.of(65).equals(Str_Int_Float_Dub.of("A"))); + assertFalse(Str_Int_Float_Dub.of("A").equals(Str_Int_Float_Dub.of(65))); + assertFalse(Str_Int_Float_Dub.of(65.0f).equals(Str_Int_Float_Dub.of(65.0))); + + assertTrue(Str_Int_Float_Dub.of(37).equals(Str_Int_Float_Dub.of(37))); + + EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub.of("one"), Str_Int_Float_Dub.of("one"), + Str_Int_Float_Dub.of("one"), + Str_Int_Float_Dub.of("onf")); + + EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub.of(97), Str_Int_Float_Dub.of(97), + Str_Int_Float_Dub.of(97), + Str_Int_Float_Dub.of(-97)); + + EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub.of(17f), Str_Int_Float_Dub.of(17f), + Str_Int_Float_Dub.of(17f), + Str_Int_Float_Dub.of(-17f)); + + EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub.of(31.7), Str_Int_Float_Dub.of(31.7), + Str_Int_Float_Dub.of(31.7), + Str_Int_Float_Dub.of(-3333.7)); } @Test(expected = IllegalArgumentException.class) diff --git a/src/test/java/org/organicdesign/fp/oneOf/OneOf5Test.java b/src/test/java/org/organicdesign/fp/oneOf/OneOf5Test.java new file mode 100644 index 00000000..f65c2443 --- /dev/null +++ b/src/test/java/org/organicdesign/fp/oneOf/OneOf5Test.java @@ -0,0 +1,183 @@ +package org.organicdesign.fp.oneOf; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.organicdesign.testUtils.EqualsContract; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.*; + +public class OneOf5Test { + static class Str_Int_Float_Dub_Char extends OneOf5 { + + // Constructor + private Str_Int_Float_Dub_Char(@NotNull Object o, int sel) { + super(o, String.class, Integer.class, Float.class, Double.class, Character.class, sel); + } + + // Static factory methods + static Str_Int_Float_Dub_Char of(@NotNull String o) { return new Str_Int_Float_Dub_Char(o, 0); } + static Str_Int_Float_Dub_Char of(@NotNull Integer o) { return new Str_Int_Float_Dub_Char(o, 1); } + static Str_Int_Float_Dub_Char of(@NotNull Float o) { return new Str_Int_Float_Dub_Char(o, 2); } + static Str_Int_Float_Dub_Char of(@NotNull Double o) { return new Str_Int_Float_Dub_Char(o, 3); } + static Str_Int_Float_Dub_Char of(@NotNull Character o) { return new Str_Int_Float_Dub_Char(o, 4); } + } + + @Test + public void testBasics() { + Str_Int_Float_Dub_Char oo5 = Str_Int_Float_Dub_Char.of("right"); + assertEquals("right", oo5.match(s -> s, + i -> "wrong", + f -> "bad", + d -> "evil", + c -> "awful")); + assertEquals("\"right\":String|Integer|Float|Double|Character", oo5.toString()); + + oo5 = Str_Int_Float_Dub_Char.of(57); + assertEquals(Integer.valueOf(57), oo5.match(s -> -99, + i -> i, + f -> 99, + d -> 2, + c -> 55)); + assertEquals("57:String|Integer|Float|Double|Character", oo5.toString()); + + oo5 = Str_Int_Float_Dub_Char.of(57.2f); + assertEquals(Float.valueOf(57.2f), oo5.match(s -> -99f, + i -> 99f, + f -> f, + d -> 2, + c -> 6)); + assertEquals("57.2:String|Integer|Float|Double|Character", oo5.toString()); + + oo5 = Str_Int_Float_Dub_Char.of(17.2); + assertEquals(Double.valueOf(17.2), oo5.match(s -> -99f, + i -> 99f, + f -> 2, + d -> d, + c -> -55f)); + assertEquals("17.2:String|Integer|Float|Double|Character", oo5.toString()); + + oo5 = Str_Int_Float_Dub_Char.of('c'); + assertEquals(Character.valueOf('c'), oo5.match(s -> 's', + i -> 'i', + f -> 'f', + d -> 'd', + c -> c)); + assertEquals("c:String|Integer|Float|Double|Character", oo5.toString()); + } + + @Test public void testEquality() { + assertEquals(0, Str_Int_Float_Dub_Char.of("").hashCode()); + assertEquals(1, Str_Int_Float_Dub_Char.of(0).hashCode()); + assertEquals(2, Str_Int_Float_Dub_Char.of(0.0f).hashCode()); + assertEquals(3, Str_Int_Float_Dub_Char.of(0.0).hashCode()); + assertEquals(36, Str_Int_Float_Dub_Char.of(' ').hashCode()); + assertNotEquals(3, Str_Int_Float_Dub_Char.of(-1.3).hashCode()); + + assertFalse(Str_Int_Float_Dub_Char.of(5f).equals(Str_Int_Float_Dub_Char.of(5))); + assertFalse(Str_Int_Float_Dub_Char.of(41).equals(Str_Int_Float_Dub_Char.of("A"))); + assertFalse(Str_Int_Float_Dub_Char.of("A").equals(Str_Int_Float_Dub_Char.of(41))); + assertFalse(Str_Int_Float_Dub_Char.of(-19.3f).equals(Str_Int_Float_Dub_Char.of(-19.3))); + + assertFalse(Str_Int_Float_Dub_Char.of(65).equals(Str_Int_Float_Dub_Char.of("A"))); + assertFalse(Str_Int_Float_Dub_Char.of("A").equals(Str_Int_Float_Dub_Char.of(65))); + assertFalse(Str_Int_Float_Dub_Char.of(65.0f).equals(Str_Int_Float_Dub_Char.of(65.0))); + + assertTrue(Str_Int_Float_Dub_Char.of(37).equals(Str_Int_Float_Dub_Char.of(37))); + + EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub_Char.of("one"), Str_Int_Float_Dub_Char.of("one"), + Str_Int_Float_Dub_Char.of("one"), + Str_Int_Float_Dub_Char.of("onf")); + + EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub_Char.of(97), Str_Int_Float_Dub_Char.of(97), + Str_Int_Float_Dub_Char.of(97), + Str_Int_Float_Dub_Char.of(-97)); + + EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub_Char.of(17f), Str_Int_Float_Dub_Char.of(17f), + Str_Int_Float_Dub_Char.of(17f), + Str_Int_Float_Dub_Char.of(-17f)); + + EqualsContract.equalsDistinctHashCode(Str_Int_Float_Dub_Char.of(31.7), Str_Int_Float_Dub_Char.of(31.7), + Str_Int_Float_Dub_Char.of(31.7), + Str_Int_Float_Dub_Char.of(-3333.7)); + } + + @Test(expected = IllegalArgumentException.class) + public void subClassExa____n() { new Str_Int_Float_Dub_Char("hi", -1); } + + @Test(expected = ClassCastException.class) + public void subClassEx_b___0() { new Str_Int_Float_Dub_Char(1, 0); } + + @Test(expected = ClassCastException.class) + public void subClassEx__c__0() { new Str_Int_Float_Dub_Char(2f, 0); } + + @Test(expected = ClassCastException.class) + public void subClassEx___d_0() { new Str_Int_Float_Dub_Char(3.0, 0); } + + @Test(expected = ClassCastException.class) + public void subClassEx____e0() { new Str_Int_Float_Dub_Char('x', 0); } + + + @Test(expected = ClassCastException.class) + public void subClassExa____1() { new Str_Int_Float_Dub_Char("hi", 1); } + + @Test(expected = IllegalArgumentException.class) + public void subClassEx_b___n() { new Str_Int_Float_Dub_Char(1, 5); } + + @Test(expected = ClassCastException.class) + public void subClassEx__c__1() { new Str_Int_Float_Dub_Char(2f, 1); } + + @Test(expected = ClassCastException.class) + public void subClassEx___d_1() { new Str_Int_Float_Dub_Char(3.0, 1); } + + @Test(expected = ClassCastException.class) + public void subClassEx____e1() { new Str_Int_Float_Dub_Char('x', 1); } + + + @Test(expected = ClassCastException.class) + public void subClassExa____2() { new Str_Int_Float_Dub_Char("hi", 2); } + + @Test(expected = ClassCastException.class) + public void subClassEx_b___2() { new Str_Int_Float_Dub_Char(1, 2); } + + @Test(expected = IllegalArgumentException.class) + public void subClassEx__c__n() { new Str_Int_Float_Dub_Char(2f, -1); } + + @Test(expected = ClassCastException.class) + public void subClassEx___d_2() { new Str_Int_Float_Dub_Char(3.0, 2); } + + @Test(expected = ClassCastException.class) + public void subClassEx____e2() { new Str_Int_Float_Dub_Char('x', 2); } + + + @Test(expected = ClassCastException.class) + public void subClassExa____3() { new Str_Int_Float_Dub_Char("hi", 3); } + + @Test(expected = ClassCastException.class) + public void subClassEx_b___3() { new Str_Int_Float_Dub_Char(1, 3); } + + @Test(expected = ClassCastException.class) + public void subClassEx__c__3() { new Str_Int_Float_Dub_Char(2f, 3); } + + @Test(expected = IllegalArgumentException.class) + public void subClassEx___d_n() { new Str_Int_Float_Dub_Char(3.0, 5); } + + @Test(expected = ClassCastException.class) + public void subClassEx____e3() { new Str_Int_Float_Dub_Char('x', 3); } + + + @Test(expected = ClassCastException.class) + public void subClassExa____4() { new Str_Int_Float_Dub_Char("hi", 4); } + + @Test(expected = ClassCastException.class) + public void subClassEx_b___4() { new Str_Int_Float_Dub_Char(1, 4); } + + @Test(expected = ClassCastException.class) + public void subClassEx__c__4() { new Str_Int_Float_Dub_Char(2f, 4); } + + @Test(expected = ClassCastException.class) + public void subClassEx___d_4() { new Str_Int_Float_Dub_Char(3.0, 4); } + + @Test(expected = IllegalArgumentException.class) + public void subClassEx____en() { new Str_Int_Float_Dub_Char('x', -1); } +} \ No newline at end of file

If you like Paguro and are interested in a new JVM and maybe compile-to-JavaScript language - with Union and Intersection types instead of Object Oriented hierarchies, take a look at the + with Union and Intersection types instead of Object-Oriented hierarchies, take a look at the evolving spec for the Cymling programming language. It may need some help to become reality.