summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Culkin <scorpress@gmail.com>2020-12-01 20:20:27 -0500
committerBen Culkin <scorpress@gmail.com>2020-12-01 20:20:27 -0500
commit097a33bc2ecaa64a664550ddd62ccd8de47c51d0 (patch)
treedf9680c1f33fddf4dcbe538593ee73703afb91ce
parentc85db1bef75e5c9b7287ab7fdb6e1380d577c674 (diff)
An assortment of changes
-rw-r--r--src/example/java/bjc/functypes/FixpointExample.java37
-rw-r--r--src/main/java/bjc/data/Contexts.java23
-rw-r--r--src/main/java/bjc/data/IContext.java44
-rw-r--r--src/main/java/bjc/esodata/TapeLibrary.java163
-rw-r--r--src/main/java/bjc/esodata/TapeView.java102
-rw-r--r--src/main/java/bjc/functypes/Combinators.java171
-rw-r--r--src/main/java/bjc/functypes/FunctionalIsomorphism.java50
-rw-r--r--src/main/java/bjc/functypes/ID.java2
-rw-r--r--src/main/java/bjc/functypes/Isomorphism.java54
-rw-r--r--src/main/java/bjc/functypes/ThrowFunction.java85
-rw-r--r--src/main/java/bjc/functypes/Thrower.java165
-rw-r--r--src/test/java/bjc/functypes/IDTest.java7
12 files changed, 870 insertions, 33 deletions
diff --git a/src/example/java/bjc/functypes/FixpointExample.java b/src/example/java/bjc/functypes/FixpointExample.java
index 8d3e658..71dab4a 100644
--- a/src/example/java/bjc/functypes/FixpointExample.java
+++ b/src/example/java/bjc/functypes/FixpointExample.java
@@ -1,17 +1,28 @@
package bjc.functypes;
+import static bjc.functypes.Combinators.*;
+
import java.util.function.*;
-public class FixpointExample {
- public static void main(String[] args) {
- BiFunction<Integer, Function<Integer, Integer>, Integer> func
- = (input, self) -> {
- if (input <= 1) return 1;
- else return input * self.apply(input - 1);
- };
-
- Function<Integer, Integer> factorial = Fixpoints.fix(func);
-
- for (int i = 0; i < 10; i++) System.out.println(factorial.apply(i));
- }
-}
+/**
+ * Examples about how fixpoints work.
+ *
+ * @author Ben Culkin
+ *
+ */
+public class FixpointExample
+{
+ /**
+ * Main method.
+ *
+ * @param args Unused CLI args.
+ */
+ public static void main(String[] args) {
+ Function<Integer, Integer> factorial = Fixpoints.fix((input, self) -> {
+ if (input <= 1) return 1;
+ else return input * self.apply(input - 1);
+ });
+
+ times(10, andThen(factorial::apply, System.out::println));
+ }
+} \ No newline at end of file
diff --git a/src/main/java/bjc/data/Contexts.java b/src/main/java/bjc/data/Contexts.java
index 0769447..75c8480 100644
--- a/src/main/java/bjc/data/Contexts.java
+++ b/src/main/java/bjc/data/Contexts.java
@@ -2,17 +2,38 @@ package bjc.data;
import java.util.*;
+/**
+ * Utility methods for dealing with contexts.
+ *
+ * @author Ben Culkin
+ *
+ */
public class Contexts {
+ /**
+ * The null context, which always throws an exception.
+ */
public static final IContext NULL = new NullContextImpl();
private Contexts() {
throw new UnsupportedOperationException();
}
+ /**
+ * Create a new context with no parent.
+ *
+ * @return A context with no parent.
+ */
public static IContext create() {
return new ContextImpl(NULL);
}
+ /**
+ * Create a context with the specified parent.
+ *
+ * @param parent The parent of this context.
+ *
+ * @return A context with the given context as its parent.
+ */
public static IContext create(IContext parent) {
return new ContextImpl(parent);
}
@@ -47,7 +68,7 @@ public class Contexts {
public ContextImpl(IContext parent) {
this.parent = parent;
- this.objects = new HashMap<String, Object>();
+ this.objects = new HashMap<>();
}
@Override
diff --git a/src/main/java/bjc/data/IContext.java b/src/main/java/bjc/data/IContext.java
index a1073f1..e519501 100644
--- a/src/main/java/bjc/data/IContext.java
+++ b/src/main/java/bjc/data/IContext.java
@@ -1,14 +1,58 @@
package bjc.data;
+/**
+ * Represents a 'context' which is a hierarchical set of objects.
+ * @author Ben Culkin
+ *
+ */
public interface IContext {
+ /**
+ * Register an object with this context.
+ *
+ * @param name The name of the object.
+ * @param o The object to register.
+ */
void register(String name, Object o);
+ /**
+ * Get the parent of this context.
+ *
+ * @return The parent of this context.
+ */
IContext getParent();
+ /**
+ * Get an object from this context.
+ *
+ * @param name The name of the object.
+ *
+ * @return The object bound to that name.
+ */
Object get(String name);
+ /**
+ * Get an object which is an instance of the provided class or a subclass
+ * thereof.
+ *
+ * @param <T> The type of the object.
+ *
+ * @param contract The class of the object.
+ *
+ * @return An instance of the provided class.
+ */
<T> T get(Class<T> contract);
+ /**
+ * Get a named object which is an instance of the provided class or a subclass
+ * thereof.
+ *
+ * @param <T> The type of the object.
+ *
+ * @param name The name of the object
+ * @param contract The class of the object.
+ *
+ * @return An instance of the provided class, with the given name..
+ */
default <T> T get(String name, Class<T> contract) {
Object obj = get(name);
return obj == null
diff --git a/src/main/java/bjc/esodata/TapeLibrary.java b/src/main/java/bjc/esodata/TapeLibrary.java
new file mode 100644
index 0000000..922833f
--- /dev/null
+++ b/src/main/java/bjc/esodata/TapeLibrary.java
@@ -0,0 +1,163 @@
+package bjc.esodata;
+
+import java.util.*;
+
+/**
+ * Represents a library of possible tapes, with a single tape 'mounted' or active
+ * at a time.
+ *
+ * @author Ben Culkin
+ *
+ * @param <ElementType> The type stored on each tape.
+ */
+public class TapeLibrary<ElementType> implements TapeView<ElementType>
+{
+ private final Map<String, Tape<ElementType>> library;
+
+ private String currentLabel;
+ private Tape<ElementType> currentTape;
+
+ private boolean allowAutoCreation;
+
+ /**
+ * Get a view of this tape library as a map.
+ *
+ * Modifications to this map will apply to the library, but will not
+ * affect whether a given tape is mounted or not. Be forewarned.
+ *
+ * @return A view onto this tape library as a map.
+ */
+ public Map<String, Tape<ElementType>> asMap()
+ {
+ return library;
+ }
+
+ /**
+ * Create a new empty tape library, with no tape mounted.
+ */
+ public TapeLibrary()
+ {
+ library = new HashMap<>();
+ }
+
+ /**
+ * Create a new tape, with a given tape mounted by default.
+ *
+ * @param label The label of the tape.
+ * @param tape The tape to mount.
+ */
+ public TapeLibrary(String label, Tape<ElementType> tape)
+ {
+ this();
+ library.put(label, tape);
+
+ this.currentLabel = label;
+ this.currentTape = tape;
+ }
+
+ @Override
+ public Tape<ElementType> tapeView()
+ {
+ return currentTape;
+ }
+
+ /**
+ * Insert a tape into this library.
+ *
+ * @param label The label to use for the tape.
+ * @param tape The tape to add.
+ *
+ * @return The tape which previously had that label, or null if there was not one.
+ */
+ public Tape<ElementType> insertTape(String label, Tape<ElementType> tape)
+ {
+ return library.put(label, tape);
+ }
+
+ /**
+ * Remove a tape from this library.
+ *
+ * @param label The label of the tape to remove.
+ *
+ * @return The tape which had that label, or null if there was not one.
+ */
+ public Tape<ElementType> removeTape(String label)
+ {
+ return library.remove(label);
+ }
+
+ /**
+ * Check if this library has a tape with a given label.
+ *
+ * @param label The label of the tape to check for.
+ *
+ * @return Whether or not the library contains a tape with that label.
+ */
+ public boolean hasTape(String label)
+ {
+ return allowAutoCreation ? true : library.containsKey(label);
+ }
+
+ /**
+ * Mount a different tape in the library.
+ *
+ * @param label The label of the tape to mount.
+ *
+ * @return True if the tape was successfully mounted, false otherwise.
+ */
+ public boolean mountTape(String label)
+ {
+ if (library.containsKey(label) || allowAutoCreation)
+ {
+ currentLabel = label;
+ currentTape = library.computeIfAbsent(
+ label,
+ (ignored) -> new SingleTape<>());
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the label of the current tape.
+ *
+ * @return The label of the current tape, or null if no tape is
+ * currently 'mounted'.
+ */
+ public String currentLabel()
+ {
+ return currentLabel;
+ }
+
+ /**
+ * Unmount the currently mounted tape.
+ */
+ public void ejectTape()
+ {
+ currentTape = null;
+ currentLabel = null;
+ }
+
+ /**
+ * Check if this tape library currently allows auto-creation of
+ * non-existing tapes.
+ *
+ * @return Whether or not auto-creation of tapes is currently allowed.
+ */
+ public boolean isAllowAutoCreation() {
+ return allowAutoCreation;
+ }
+
+ /**
+ * Set whether or not this library allows auto-creation of non-existing
+ * tapes.
+ *
+ * @param allowAutoCreation Whether tape auto-creation is allowed.
+ */
+ public void setAllowAutoCreation(boolean allowAutoCreation)
+ {
+ this.allowAutoCreation = allowAutoCreation;
+ }
+}
diff --git a/src/main/java/bjc/esodata/TapeView.java b/src/main/java/bjc/esodata/TapeView.java
new file mode 100644
index 0000000..bfc33a9
--- /dev/null
+++ b/src/main/java/bjc/esodata/TapeView.java
@@ -0,0 +1,102 @@
+package bjc.esodata;
+
+/**
+ * An interface which allows you to view a given type as a tape.
+ *
+ * @author Ben Culkin
+ *
+ * @param <ElementType> The type of element contained in the tape.
+ */
+public interface TapeView<ElementType> extends Tape<ElementType>
+{
+ /**
+ * Return a view of this object as a tape.
+ *
+ * @return A view of this object as a tape.
+ */
+ public Tape<ElementType> tapeView();
+
+ @Override
+ public default ElementType item()
+ {
+ return tapeView().item();
+ }
+
+ @Override
+ public default void item(ElementType itm)
+ {
+ tapeView().item(itm);
+ }
+
+ @Override
+ public default int size()
+ {
+ return tapeView().size();
+ }
+
+ @Override
+ public default int position()
+ {
+ return tapeView().position();
+ }
+
+ @Override
+ public default void insertBefore(ElementType itm)
+ {
+ tapeView().insertBefore(itm);
+ }
+
+ @Override
+ public default void insertAfter(ElementType itm)
+ {
+ tapeView().insertAfter(itm);
+ }
+
+ @Override
+ public default ElementType remove()
+ {
+ return tapeView().remove();
+ }
+
+ @Override
+ public default void first()
+ {
+ tapeView().first();
+ }
+
+ @Override
+ public default void last()
+ {
+ tapeView().last();
+ }
+
+ @Override
+ public default boolean left()
+ {
+ return tapeView().left();
+ }
+
+ @Override
+ public default boolean left(int amt)
+ {
+ return tapeView().left(amt);
+ }
+
+ @Override
+ public default boolean right()
+ {
+ return tapeView().right();
+ }
+
+ @Override
+ public default boolean right(int amt)
+ {
+ return tapeView().right(amt);
+ }
+
+ @Override
+ public default boolean seekTo(int pos)
+ {
+ return tapeView().seekTo(pos);
+ }
+}
diff --git a/src/main/java/bjc/functypes/Combinators.java b/src/main/java/bjc/functypes/Combinators.java
index 21238c2..5632b70 100644
--- a/src/main/java/bjc/functypes/Combinators.java
+++ b/src/main/java/bjc/functypes/Combinators.java
@@ -1,7 +1,12 @@
package bjc.functypes;
+import static java.util.stream.Collectors.*;
+
+import java.util.*;
import java.util.function.*;
+import bjc.data.*;
+
/**
* A bunch of only slightly related function combinators.
*
@@ -42,7 +47,8 @@ public class Combinators {
* @return A function that runs the provided action before calling the function.
*/
public static <Input, Output> Function<Input, Output> beforeThis(
- Consumer<Input> action, Function<Input, Output> terminal)
+ Consumer<Input> action,
+ Function<Input, Output> terminal)
{
return (arg) -> {
action.accept(arg);
@@ -62,7 +68,8 @@ public class Combinators {
* @return A function that calls the provided action after the function.
*/
public static <Input, Output> Consumer<Input> andThen(
- Function<Input, Output> initial, Consumer<Output> action)
+ Function<Input, Output> initial,
+ Consumer<Output> action)
{
return (arg) -> action.accept(initial.apply(arg));
}
@@ -130,4 +137,164 @@ public class Combinators {
{
for (int i = 0; i < times; i++) action.accept(i);
}
+
+ /**
+ * Invoke a wrapper instead of invoking a normal function.
+ *
+ * @param <Input> The input type to the function.
+ * @param <Output> The output type to the function.
+ *
+ * @param function The function to apply.
+ * @param wrapper The wrapper around a function.
+ *
+ * @return A function that invokes the wrapper instead.
+ */
+ public static <Input, Output> Function<Input, Output> around(
+ Function<Input, Output> function,
+ BiFunction<Input, Function<Input, Output>, Output> wrapper)
+ {
+ return (input) -> wrapper.apply(input, function);
+ }
+
+ /**
+ * Only run a given function when the argument satisfies a condition.
+ *
+ * @param <Input> The input type of the function.
+ * @param <Output> The output type of the function.
+ *
+ * @param function The function to run.
+ * @param guard The guard to use for checking the input.
+ *
+ * @return A function which takes the given input, and only calls the
+ * function if the guard returns true. Otherwise, it will return
+ * an empty optional.
+ */
+ public static <Input, Output> Function<Input, Optional<Output>> provided(
+ Function<Input, Output> function,
+ Predicate<Input> guard)
+ {
+ return iftt(guard,
+ (arg) -> Optional.ofNullable(function.apply(arg)),
+ (ignored) -> Optional.empty()
+ );
+ }
+
+ /**
+ * Concatenate two functions together, so that they run on the same argument.
+ *
+ * @param <Input> The type of the input.
+ * @param <Output1> The type of the first output.
+ * @param <Output2> The type of the second output.
+ *
+ * @param funcA The first function to call.
+ * @param funcB The second function to call.
+ *
+ * @return A function that returns a pair of the results of calling both
+ * functions.
+ */
+ public static
+ <Input, Output1, Output2> Function<Input, IPair<Output1, Output2>>
+ concat(Function<Input, Output1> funcA, Function<Input, Output2> funcB)
+ {
+ return (arg) -> IPair.pair(funcA.apply(arg), funcB.apply(arg));
+ }
+
+ /**
+ * Concatenate a series of functions together, returning a list of their
+ * results.
+ *
+ * @param <Input> The input type for the functions.
+ * @param <Output> The output type for the functions.
+ *
+ * @param funcs The series of functions to call.
+ *
+ * @return A function that calls each of those functions, and returns a
+ * list of their results.
+ */
+ @SafeVarargs
+ public static
+ <Input, Output> Function<Input, List<Output>>
+ concat(Function<Input, Output>... funcs)
+ {
+ List<Function<Input, Output>> funcList = Arrays.asList(funcs);
+
+ // Kind of a nuisance that Java can't properly guess the type of
+ // our mapper function, but oh well.
+
+ return (arg) ->
+ funcList.stream()
+ .map((Function<Function<Input, Output>, Output>)
+ (func) -> func.apply(arg))
+ .collect(toList());
+ }
+
+ /**
+ * Return a function that does a series of actions upon a value, then returns
+ * that value.
+ *
+ * @param <Type> The type given as an argument
+ *
+ * @param consumers The actions to perform on the value.
+ *
+ * @return A function that performs those arguments on a value.
+ */
+ @SafeVarargs
+ public static <Type> Function<Type, Type> doWith(Consumer<Type>... consumers)
+ {
+ return (arg) -> {
+ for (Consumer<Type> consumer : consumers) consumer.accept(arg);
+ return arg;
+ };
+ }
+
+ /**
+ * Perform a series of actions upon a value, then return that value.
+ *
+ * @param <Type> The type given as an argument
+ *
+ * @param input The value to use.
+ * @param consumers The actions to perform on the value.
+ *
+ * @return A function that performs those arguments on a value.
+ */
+ @SafeVarargs
+ public static <Type> Type with(Type input, Consumer<Type>... consumers)
+ {
+ return doWith(consumers).apply(input);
+ }
+
+ /**
+ * Return a function that does a series of actions upon a value, then returns
+ * that value.
+ *
+ * @param <Type> The type given as an argument
+ *
+ * @param functions The actions to perform on the value.
+ *
+ * @return A function that performs those arguments on a value.
+ */
+ @SafeVarargs
+ public static <Type> Function<Type, Type> doWith(Function<Type, ?>... functions)
+ {
+ return (arg) -> {
+ for (Function<Type, ?> function : functions) function.apply(arg);
+ return arg;
+ };
+ }
+
+ /**
+ * Perform a series of actions upon a value, then return that value.
+ *
+ * @param <Type> The type given as an argument
+ *
+ * @param input The value to use.
+ * @param functions The actions to perform on the value.
+ *
+ * @return A function that performs those arguments on a value.
+ */
+ @SafeVarargs
+ public static <Type> Type with(Type input, Function<Type, ?>... functions)
+ {
+ return doWith(functions).apply(input);
+ }
} \ No newline at end of file
diff --git a/src/main/java/bjc/functypes/FunctionalIsomorphism.java b/src/main/java/bjc/functypes/FunctionalIsomorphism.java
new file mode 100644
index 0000000..8518338
--- /dev/null
+++ b/src/main/java/bjc/functypes/FunctionalIsomorphism.java
@@ -0,0 +1,50 @@
+package bjc.functypes;
+
+import java.util.function.Function;
+
+/**
+ * A pair of functions to transform between a pair of types.
+ *
+ * @author bjculkin
+ *
+ * @param <Source>
+ * The source type of the isomorphism.
+ *
+ * @param <Dest>
+ * The destination type of isomorphism.
+ */
+public class FunctionalIsomorphism<Source, Dest> implements Isomorphism<Source, Dest>
+{
+ /* The function to the destination type. */
+ private Function<Source, Dest> toFunc;
+ /* The function to the source type. */
+ private Function<Dest, Source> fromFunc;
+
+ /**
+ * Create a new isomorphism.
+ *
+ * @param to
+ * The 'forward' function, from the source to the definition.
+ *
+ * @param from
+ * The 'backward' function, from the definition to the source.
+ */
+ public FunctionalIsomorphism(Function<Source, Dest> to,
+ Function<Dest, Source> from)
+ {
+ toFunc = to;
+ fromFunc = from;
+ }
+
+ @Override
+ public Dest to(Source val)
+ {
+ return toFunc.apply(val);
+ }
+
+ @Override
+ public Source from(Dest val)
+ {
+ return fromFunc.apply(val);
+ }
+}
diff --git a/src/main/java/bjc/functypes/ID.java b/src/main/java/bjc/functypes/ID.java
index f830d66..53a4c84 100644
--- a/src/main/java/bjc/functypes/ID.java
+++ b/src/main/java/bjc/functypes/ID.java
@@ -10,6 +10,8 @@ import java.util.function.UnaryOperator;
public class ID {
/**
* Create an identity function.
+ *
+ * @param <A> The type of the function.
*
* @return A identity function.
*/
diff --git a/src/main/java/bjc/functypes/Isomorphism.java b/src/main/java/bjc/functypes/Isomorphism.java
new file mode 100644
index 0000000..db633e6
--- /dev/null
+++ b/src/main/java/bjc/functypes/Isomorphism.java
@@ -0,0 +1,54 @@
+package bjc.functypes;
+
+import java.util.function.*;
+
+/**
+ * Denotes that two types are essentially equivalent, by providing a means to
+ * convert between them.
+ *
+ * @author Ben Culkin
+ *
+ * @param <Source> The first type.
+ * @param <Dest> The second type.
+ */
+public interface Isomorphism<Source, Dest>
+{
+
+ /**
+ * Apply the isomorphism forward.
+ *
+ * @param val
+ * The source value.
+ *
+ * @return The destination value.
+ */
+ Dest to(Source val);
+
+ /**
+ * Apply the isomorphism backward.
+ *
+ * @param val
+ * The destination value.
+ *
+ * @return The source value.
+ */
+ Source from(Dest val);
+
+ /**
+ * Create an isomorphism from a pair of functions.
+ *
+ * @param <Src> The source type.
+ * @param <Dst> The destination type.
+ *
+ * @param forward The function from source to destination.
+ * @param backward The function from destination to source.
+ *
+ * @return An isomorphism between the two types.
+ */
+ static <Src, Dst> Isomorphism<Src, Dst> from(
+ Function<Src, Dst> forward,
+ Function<Dst, Src> backward)
+ {
+ return new FunctionalIsomorphism<>(forward, backward);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/bjc/functypes/ThrowFunction.java b/src/main/java/bjc/functypes/ThrowFunction.java
index b72d735..132f55c 100644
--- a/src/main/java/bjc/functypes/ThrowFunction.java
+++ b/src/main/java/bjc/functypes/ThrowFunction.java
@@ -11,7 +11,8 @@ import java.util.function.*;
* @param <ReturnType> The output to the function.
* @param <ExType> The type of exception thrown.
*/
-public interface ThrowFunction<InputType, ReturnType, ExType extends Throwable> {
+public interface ThrowFunction<InputType, ReturnType, ExType extends Throwable>
+{
/**
* Does the possibly throwing computation embodied by this function.
*
@@ -38,15 +39,20 @@ public interface ThrowFunction<InputType, ReturnType, ExType extends Throwable>
*/
@SuppressWarnings("unchecked")
default Function<InputType, ReturnType> swallow(
- Class<ExType> clasz, Function<ExType, ReturnType> handler) {
- return (inp) -> {
- try {
+ Class<ExType> clasz, Function<ExType, ReturnType> handler)
+ {
+ return (inp) ->
+ {
+ try
+ {
return this.apply(inp);
} catch (Throwable ex) {
- if (clasz.isInstance(ex)) {
+ if (clasz.isInstance(ex))
+ {
// Swallow this
return handler.apply((ExType) ex);
- } else {
+ } else
+ {
String msg = "Exception of incorrect type to be handled, only "
+ clasz.getName()
+ " are handled";
@@ -68,14 +74,19 @@ public interface ThrowFunction<InputType, ReturnType, ExType extends Throwable>
@SuppressWarnings("unchecked")
default Function<InputType, ReturnType> recover(
Class<ExType> clasz, BiFunction<InputType, ExType, InputType> rescue) {
- return Fixpoints.fix((arg, self) -> {
- try {
+ return Fixpoints.fix((arg, self) ->
+ {
+ try
+ {
return this.apply(arg);
- } catch (Throwable ex) {
- if (clasz.isInstance(ex)) {
+ } catch (Throwable ex)
+ {
+ if (clasz.isInstance(ex))
+ {
// Swallow this
return self.apply(rescue.apply(arg, (ExType) ex));
- } else {
+ } else
+ {
String msg = "Exception of incorrect type to be handled, only "
+ clasz.getName()
+ " are handled";
@@ -85,4 +96,56 @@ public interface ThrowFunction<InputType, ReturnType, ExType extends Throwable>
}
});
}
+
+ /**
+ * Create a {@link Thrower} which will yield the result of calling this
+ * function with the given argument.
+ *
+ * @param arg The argument to use.
+ *
+ * @return A thrower which will call this function with the given value.
+ */
+ default Thrower<ReturnType, ExType> suspend(InputType arg)
+ {
+ return () -> this.apply(arg);
+ }
+
+ /**
+ * Compose two throwing functions together.
+ *
+ * @param <NewOutput> The newly output type.
+ *
+ * @param func The throwing function to compose this with.
+ *
+ * @return A throwing function that composes the two.
+ */
+ default <NewOutput> ThrowFunction<InputType, NewOutput, ExType>
+ compose(
+ ThrowFunction<ReturnType, NewOutput, ExType> func) {
+ return (arg) -> func.apply(this.apply(arg));
+ }
+
+ /**
+ * ThrowFunctions and functions which return a {@link Thrower} are isomorphic.
+ *
+ * @param <InpType> The function input type.
+ * @param <OutType> The function output type.
+ * @param <ExType> The exception type.
+ *
+ * @return The isomorphism between them.
+ */
+ static <InpType, OutType, ExType extends Throwable>
+ Isomorphism<ThrowFunction<InpType, OutType, ExType>, Function<InpType, Thrower<OutType, ExType>>>
+ getIso()
+ {
+ // @FIXME Nov 23, 2020 Ben Culkin :EquivISO
+ // Fix this to strip wrappers when appropriate, so that going
+ // backwards and forwards leaves you where you started, not under
+ // two levels of indirection.
+ return Isomorphism.from((throwFun) -> {
+ return (arg) -> throwFun.suspend(arg);
+ }, (thrower) -> {
+ return (arg) -> thrower.apply(arg).extract();
+ });
+ }
}
diff --git a/src/main/java/bjc/functypes/Thrower.java b/src/main/java/bjc/functypes/Thrower.java
new file mode 100644
index 0000000..eb03eaf
--- /dev/null
+++ b/src/main/java/bjc/functypes/Thrower.java
@@ -0,0 +1,165 @@
+package bjc.functypes;
+
+import java.util.*;
+import java.util.function.*;
+
+/**
+ * A source for a value that could produce an exception.
+ *
+ * @author Ben Culkin
+ *
+ * @param <ValueType> The type of value possibly produced.
+ * @param <ExceptionType> The type of exception possibly thrown.
+ */
+@FunctionalInterface
+public interface Thrower<ValueType, ExceptionType extends Throwable>
+{
+ /**
+ * Attempt to get the value.
+ *
+ * @return The value from this source.
+ *
+ * @throws ExceptionType If something goes wrong getting the value.
+ */
+ ValueType extract() throws ExceptionType;
+
+ /**
+ * Converts this thrower into a memoized one.
+ *
+ * Note that if this does throw an exception, it won't be 'memoized' so
+ * that the next call will call this Thrower again.
+ *
+ * @return A memoizing thrower.
+ */
+ default Thrower<ValueType, ExceptionType> memoize() {
+ return new MemoizedThrower<>(this);
+ }
+
+ /**
+ * Applies a function to the result of this thrower.
+ *
+ * @param <NewOutputType> The new type output by the function.
+ *
+ * @param func The function to apply.
+ *
+ * @return A thrower that is the result of applying the given function.
+ */
+ default <NewOutputType> Thrower<NewOutputType, ExceptionType> bind(
+ Function<ValueType, Thrower<NewOutputType, ExceptionType>> func)
+ {
+ return () -> func.apply(extract()).extract();
+ }
+ /**
+ * Create a thrower that yields a given value.
+ *
+ * @param <ValType> The type of the value.
+ *
+ * @param val The value to yield.
+ *
+ * @return A thrower that will always yield that value.
+ */
+ static <ValType>
+ Thrower<ValType, ? extends Throwable> from(ValType val)
+ {
+ return () -> val;
+ }
+
+ /**
+ * Create a thrower that yields a given value.
+ *
+ * @param <ValType> The type of the value.
+ *
+ * @param val The value to yield.
+ *
+ * @return A thrower that will always yield that value.
+ */
+ static <ValType>
+ Thrower<ValType, ? extends Throwable> from(Supplier<ValType> val)
+ {
+ return val::get;
+ }
+
+ /**
+ * Convert a function on values to one over throwers.
+ *
+ * @param <Input> The function input type.
+ * @param <Output> The function output type.
+ * @param <ExType> The exception possibly thrown.
+ *
+ * @param func The function to convert.
+ *
+ * @return A function which operates on throwers instead.
+ */
+ static <Input, Output, ExType extends Throwable>
+ Function<Thrower<Input, ExType>,Thrower<Output, ExType>> fmap(
+ Function<Input, Output> func)
+ {
+ return (input) -> () -> func.apply(input.extract());
+ }
+
+ /**
+ * Convert a list of throwers into a thrower that returns a list.
+ *
+ * @param <Output> The type output by the thrower.
+ * @param <ExType> The type of exception thrown.
+ *
+ * @param throwers The list of throwers.
+ *
+ * @return A thrower that returns a list of results.
+ */
+ static <Output, ExType extends Throwable>
+ Thrower<List<Output>, ExType> seq(List<Thrower<Output, ExType>> throwers)
+ {
+ return () -> {
+ List<Output> results = new ArrayList<>(throwers.size());
+ for (Thrower<Output, ExType> thrower : throwers)
+ {
+ results.add(thrower.extract());
+ }
+ return results;
+ };
+ }
+
+ /**
+ * Convert a array of throwers into a thrower that returns a list.
+ *
+ * @param <Output> The type output by the thrower.
+ * @param <ExType> The type of exception thrown.
+ *
+ * @param throwers The array of throwers.
+ *
+ * @return A thrower that returns a list of results.
+ */
+ @SafeVarargs
+ static <Output, ExType extends Throwable>
+ Thrower<List<Output>, ExType> seq(Thrower<Output, ExType>... throwers)
+ {
+ return () -> {
+ List<Output> results = new ArrayList<>(throwers.length);
+ for (Thrower<Output, ExType> thrower : throwers)
+ {
+ results.add(thrower.extract());
+ }
+ return results;
+ };
+ }
+}
+
+class MemoizedThrower<ValueType, ExceptionType extends Throwable>
+implements Thrower<ValueType, ExceptionType>
+{
+ private final Thrower<ValueType, ExceptionType> source;
+ private ValueType val;
+
+ public MemoizedThrower(Thrower<ValueType, ExceptionType> source)
+ {
+ this.source = source;
+ }
+
+ @Override
+ public ValueType extract() throws ExceptionType
+ {
+ if (val == null) val = source.extract();
+ return val;
+ }
+}
diff --git a/src/test/java/bjc/functypes/IDTest.java b/src/test/java/bjc/functypes/IDTest.java
index 0865282..69c6133 100644
--- a/src/test/java/bjc/functypes/IDTest.java
+++ b/src/test/java/bjc/functypes/IDTest.java
@@ -6,12 +6,7 @@ import java.util.function.*;
import org.junit.*;
-/**
- * Test for ID.
- *
- * @author Ben Culkin
- *
- */
+@SuppressWarnings("javadoc")
public class IDTest {
@Test