Skip to content

Commit

Permalink
- Added OneOf5. A second-class Union Type like OneOf4, but with one m…
Browse files Browse the repository at this point in the history
…ore.

- Removed OneOf2OrNone; use Option<OneOf2> 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<OneOf_>.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.
  • Loading branch information
GlenKPeterson committed Jan 21, 2022
1 parent 1ce6deb commit 9367644
Show file tree
Hide file tree
Showing 18 changed files with 581 additions and 364 deletions.
13 changes: 13 additions & 0 deletions CHANGE_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<OneOf2> 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<OneOf_>.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.
Expand Down Expand Up @@ -236,6 +247,7 @@ public class String_Integer extends OneOf2<String,Integer> {

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
Expand All @@ -244,6 +256,7 @@ public class String_Integer extends OneOf2<String,Integer> {

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.
Expand Down
22 changes: 10 additions & 12 deletions Paguro.iml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,17 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.13" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.13.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains:annotations:20.1.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.organicdesign:TestUtils:0.0.19" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-stdlib:1.4.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.organicdesign:Indented:0.0.15" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains:annotations:23.0.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.organicdesign:TestUtils:1.0.6" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-stdlib:1.6.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.organicdesign:Indented:0.0.20" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-test-junit:1.4.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-test-annotations-common:1.4.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-test:1.4.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-test-common:1.4.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-test-junit:1.6.10" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-test:1.6.10" level="project" />
</component>
</module>
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ http://mvnrepository.com/artifact/org.organicdesign/Paguro
-->
<groupId>org.organicdesign</groupId>
<artifactId>Paguro</artifactId>
<version>3.9.0</version>
<version>3.10.0</version>
<packaging>jar</packaging>

<name>Paguro</name>
Expand Down
20 changes: 12 additions & 8 deletions src/main/java/org/organicdesign/fp/function/Fn0.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

package org.organicdesign.fp.function;

import java.io.Serializable;
import java.util.concurrent.Callable;
import java.util.function.Supplier;

Expand Down Expand Up @@ -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<Object> {
// enum ConstObj implements Fn0<Option<Object>> {
// NULL {
// @Override public Object applyEx() throws Exception { return null; }
// @Override
// public @NotNull Option<Object> applyEx() throws Exception { return Option.none(); }
// }
// }
//
// /** Returns a type-safe version of the {@link ConstObj#NULL} thunk. */
// @SuppressWarnings("unchecked")
// static <S> @NotNull Fn0<S> nullSupplier() { return (Fn0<S>) ConstObj.NULL; }

// /**
// Wraps a value in a constant function. If you need to "memoize" some really expensive
Expand All @@ -76,9 +85,4 @@ default U apply() {
// }
//
// static <K> Fn0<K> constantFunction(final K k) { return new Constant<>(k); }

// Don't think this is necessary. Is it?
// default Supplier<U> asSupplier() {
// return () -> apply();
// }
}
35 changes: 17 additions & 18 deletions src/main/java/org/organicdesign/fp/oneOf/OneOf2.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -75,26 +74,26 @@ static class String_Integer extends OneOf2<String,Integer> {
// If not an Integer at runtime throws "Expected a(n) Integer but found a(n) String"
3 + x.integer();
}</pre>
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<A,B> {

private final @Nullable Object item;
protected final @NotNull Object item;
private final int sel;
@SuppressWarnings("rawtypes")
private final @NotNull ImList<Class> 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<A> aClass,
@NotNull Class<B> bClass,
int index
Expand All @@ -107,19 +106,19 @@ 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());
}
}

/**
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.
Expand Down
98 changes: 0 additions & 98 deletions src/main/java/org/organicdesign/fp/oneOf/OneOf2OrNone.java

This file was deleted.

38 changes: 17 additions & 21 deletions src/main/java/org/organicdesign/fp/oneOf/OneOf3.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<A,B,C> {
private final @Nullable Object item;
protected final @NotNull Object item;
private final int sel;
@SuppressWarnings("rawtypes")
private final @NotNull ImList<Class> 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<A> aClass,
@NotNull Class<B> bClass,
@NotNull Class<C> cClass,
Expand All @@ -44,20 +40,20 @@ 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());
}
}

/**
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.
Expand Down
Loading

0 comments on commit 9367644

Please sign in to comment.