diff --git a/.floo b/.floo new file mode 100644 index 000000000..4775d1079 --- /dev/null +++ b/.floo @@ -0,0 +1,3 @@ +{ + "url": "https://floobits.com/jnape/lambda" +} \ No newline at end of file diff --git a/.flooignore b/.flooignore new file mode 100644 index 000000000..ed824d39a --- /dev/null +++ b/.flooignore @@ -0,0 +1,6 @@ +extern +node_modules +tmp +vendor +.idea/workspace.xml +.idea/misc.xml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..66b54c05c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [jnape] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 000000000..296a02977 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,45 @@ +name: Java CI + +on: + push: + pull_request: + +jobs: + build-java-1_8: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Maven + run: mvn clean verify + + build-java-11: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Build with Maven + run: mvn clean verify + + build-java-14: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 14 + uses: actions/setup-java@v1 + with: + java-version: 14 + - name: Build with Maven + run: mvn clean verify diff --git a/.gitignore b/.gitignore index 3425b85ff..a0b059d5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .idea/* target/* -*.iml \ No newline at end of file +*.iml +.java-version +.DS_Store diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9bcf99945..000000000 --- a/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: java -jdk: - - oraclejdk8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ebcf9408..3aecf9b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,513 @@ # Change Log + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +### Added +- `ReaderT#ask`, a static factory method for returning an identity `ReaderT` + +### Fixed +- nested `DropWhile`s no longer incorrectly deforest using disjunction + +## [5.4.0] - 2021-09-17 + +### Changed +- `Absent` folds short-circuit on the first `nothing()` +- `EitherMatcher#isLeftThat/isRightThat` support contravariant bounds on their delegates + +### Added +- `IterateT#runStep`, a method used to run a single step of an IterateT without the contractual guarantee of emitting a + value or reaching the end +- `These#fromMaybes :: Maybe a -> Maybe b -> Maybe (These a b)` +- `EitherMatcher#isLeftOf/isRightOf` for asserting equality + +### Fixed +- `WriterT` now keeps an immediate reference to the embedded monad's `pure` + +## [5.3.0] - 2020-12-07 + +### Changed +- `IterateT#unfold` now only computes a single `Pure` for the given input +- `ReaderT#fmap` and `StateT#fmap` avoid unnecessary calls to `pure` +- `MaybeT` implements `MonadError` + +### Added +- `$`, function application represented as a higher-order `Fn2` +- `Fn1#withSelf`, a static method for constructing a self-referencing `Fn1` +- `HNil/SingletonHList/TupleX#snoc`, a method to add a new last element (append to a tuple) +- `Tuple2-8#init`, for populating a `TupleN` with all but the last element + +### Fixed +- `IterateT#trampolineM` now yields and stages all recursive result values, rather + than prematurely terminating on the first termination result +- `IterateT#flatMap` is now stack-safe regardless of how many consecutive empty `IterateT`s + are returned and regardless of whether the monad is strict or lazy or internally trampolined + +## [5.2.0] - 2020-02-12 + +### Changed +- `HList#cons` static factory method auto-promotes to specialized `HList` if there is one +- `EitherT` gains a `MonadError` instance + +### Added +- `MergeHMaps`, a `Monoid` that merges `HMap`s by merging the values via key-specified `Semigroup`s +- `Id#id` overload that accepts an argument and returns it +- `MaybeT#or`, choose the first `MaybeT` that represents an effect around `just` a value +- `MaybeT#filter`, filter a `Maybe` inside an effect +- `StateMatcher, StateTMatcher, WriterTMatcher` +- `ReaderT#and`, category composition between `ReaderT` instances: `(a -> m b) -> (b -> m c) -> (a -> m c)` +- `IterateT`, [`ListT` done right](https://wiki.haskell.org/ListT_done_right) +- `Comparison`, a type-safe sum of `LT`, `EQ`, and `GT` orderings +- `Compare`, a function taking a `Comparator` and returning a `Comparison` +- `Min/Max/...With` variants for inequality testing with a `Comparator` + +## [5.1.0] - 2019-10-13 +### Changed +- All monad transformers that can support composable parallelism do support it + +### Added +- `Writer`, the writer monad +- `EndoK`, a monoid formed under endomorphism for any monad +- `AutoBracket`, a specialized form of `Bracket` for `AutoCloseable` that closes the resource during cleanup + +### Fixed +- `SafeT#zip` is now stack-safe regardless of the underlying monad's `zip` implementation + +### Deprecated +- `Force`, in favor if traversing into an `IO` and explicitly running it + +## [5.0.0] - 2019-09-23 +### Changed +- ***Breaking Change***: `MonadT` is now witnessed by a parameter for better subtyping, and no longer requires a common + `run` interface; each `run` method is now `runXXXT()`, where `XXX` is the name of the transformer in question +- ***Breaking Change***: `Applicative#zip` and derivatives evaluate from left to right now across the board. +- ***Breaking Change***: `testsupport.EquatableM` replaced with `Equivalence` +- `Alter` now merely requires an `Fn1` instead of an explicit `Effect` +- `IO` now internally trampolines all forms of composition, including `lazyZip`; + sequencing very large iterables of `IO` will work, if you have the heap, and + retain parallelization inflection points + +### Added +- `MonadRec`, monads that support a stack-safe `trampolineM` method with defaults for all exported monads +- `MonadError`, monads that can be thrown to and caught from, with defaults for `IO`, `Either`, `Maybe`, and `Try` +- `MonadBase`, an interface representing lifting infrastructure for `Monad`s +- `MonadReader` and `MonadWriter`, general interfaces for reading from an environment and accumulating results +- `SafeT`, a stack-safe monad transformer for any `MonadRec` +- `ReaderT`, the transformer for the reader monad +- `WriterT`, a monad transformer for an accumulation and a value +- `StateT`, the `State` monad transformer +- `Lift`, an existentially-quantified lifting function for some `MonadBase` type +- `IO#interruptible`, for wrapping an `IO` in a thread interruption check +- `IO#monitorSync`, for wrapping an `IO` in a `synchronized` block on a given lock object +- `IO#pin`, for pinning an `IO` to an `Executor` without yet executing it +- `IO#fuse`, for fusing the fork opportunities of a given `IO` into a single linearized `IO` +- `IO#memoize`, for memoizing an `IO` by caching its first successful result +- `Until`, for repeatedly executing an `IO` until its result matches a predicate +- `Optic#andThen`, `Optic#compose`, and other defaults added +- `Prism#andThen`, `Prism#compose` begets another `Prism` +- `Prism#fromPartial` public interfaces +- `Tuple2-8#fromIterable`, for populating a `TupleN` with the first `N` elements of an `Iterable` +- `Fn2#curry`, for converting an `Fn1,C>` to an `Fn2` +- `EquivalenceTrait`, a traitor `Trait` to make it easier to test properties of type-classes with a separate equivalence + relation + +### Deprecated +- `Peek`, `Peek2`, `Maybe#peek`, and `Either#peek` in favor of explicitly matching into `IO` and running it +- `IO#exceptionally` in favor of `IO#catchError` (from `MonadError`) + +## [4.0.0] - 2019-05-20 +### Changed +- ***Breaking Change***: `IO` is now sealed and moved to its own package. Most previous constructions using the static + factory methods should continue to work (by simply targeting `Supplier` now instead of an + anonymous `IO`), but some might need to be reworked, and subtyping is obviously no longer + supported. +- ***Breaking Change***: Breaking all dependency on `java.util.function` types across the board. All `Fn*` types target + methods now support throwing `Throwable`; `apply` is now defaulted and will simply bypass javac + to throw checked exceptions as if they were unchecked. All `Checked` variants have been + eliminated as a consequence, as they are no longer necessary. Also, straggler functions like + `Partial2/3` that only existed to aid in partial application of non-curried functions are now + superfluous, and have also been eliminated. +- ***Breaking Change***: `FoldRight` now requires `Lazy` as part of its interface to support short-circuiting operations +- ***Breaking Change***: Eliminated all raw types and java11 warnings. This required using capture in unification + parameters for Functor and friends, so nearly every functor's type-signature changed. +- ***Breaking Change***: `Strong` is now called `Cartesian` to better reflect the type of strength +- ***Breaking Change***: new Optic type hierarchy more faithfully encodes profunctor constraints on optics, new `Optic` + type is now the supertype of `Lens` and `Iso`, and `lens` package has been moved to `optics` +- ***Breaking Change***: `Try` and `Either` no longer preserve `Throwable` type since it was inherently not type-safe + anyway; Try is therefore no longer a `Bifunctor`, and `orThrow` can be used to declare checked + exceptions that could be caught by corresponding catch blocks +- `IO` is now stack-safe, regardless of whether the composition nests linearly or recursively + +### Added +- `Lazy`, a monad supporting stack-safe lazy evaluation +- `LazyRec`, a function for writing stack-safe recursive algorithms embedded in `Lazy` +- `Applicative#lazyZip`, for zipping two applicatives in a way that might not require evaluation of one applicative +- `MonadT`, a general interface representing monad transformers +- `MaybeT`, a monad transformer for `Maybe` +- `EitherT`, a monad transformer for `Either` +- `IdentityT`, a monad transformer for `Identity` +- `LazyT`, a monad transformer for `Lazy` +- `Endo`, a monoid formed by `Fn1` under composition +- `State`, the state `Monad` +- `Downcast`, a function supporting unchecked down-casting +- `Cocartesian`, profunctorial strength in cocartesian coproduct terms +- `Prism`, an `Optic` that is nearly an `Iso` but can fail in one direction +- `Market`, `Tagged`, profunctors supporting optics +- `Re` for viewing an `Optic` in one direction reliably +- `Pre` for viewing at most one value from an `Optic` in one direction +- `SideEffect`, for representing side-effects runnable by `IO` +- `IO#safe`, mapping an `IO` to an `IO>` that will never throw +- `IO#ensuring`, like `finally` semantics for `IO`s +- `IO#throwing`, for producing an `IO` that will throw a given `Throwable` when executed +- `Bracket`, for bracketing an `IO` operation with a mapping operation and a cleanup operation + +## [3.3.0] - 2019-02-18 +### Added +- `MergeMaps`, a `Monoid` on `Map` formed by `Map#merge` +- `CheckedEffect` is now a `CheckedFn1` +- `CheckedSupplier` is now a `CheckedFn1` +- `CheckedFn1` now overrides all possible methods with covariant return type +- `MapLens#asCopy` has overload taking copy function +- `MapLens#valueAt` has overload taking copy function +- `SortWith` for sorting an `Iterable` given a `Comparator` over its elements +- `IO#externallyManaged`, for supplying an `IO` with externally-managed futures +- test jar is now published +- `Monad#join` static alias for `flatMap(id())` +- `Effect#effect` static factory method taking `Fn1` +- `IO#unsafePerformAsyncIO` overloads for running `IO`s asynchronously +- `IO`s automatically encode parallelism in composition +- `IO#exceptionally` for recovering from failure during `IO` operation +- `Optic`, a generic supertype for all profunctor optics + +### Fixed +- issue where certain ways to compose `Effect`s unintentionally nullified the effect + +### Removed +- `AddAll` semigroup, deprecated in previous release +- Dyadic `Either#flatMap()`, deprecated in previous release + +## [3.2.0] - 2018-12-08 +### Changed +- ***Breaking Change***: `Difference` and `Intersection` no longer instances of `Semigroup` and moved to + `functions.builtin.fn2` package +- ***Breaking Change***: `Absent` moved to `semigroup.builtin` package +- ***Breaking Change***: `Effect#accept()` is now the required method to implement in the functional interface +- ***Breaking Change***: `Fn0#apply()` is now the required method to implement in the functional interface +- ***Breaking Change***: `GTBy`, `GT`, `LTBy`, `LT`, `GTEBy`, `GTE`, `LTEBy`, and `LTE` take the right-hand side first + for more intuitive partial application +- ***Breaking Change***: `Effect` now returns an `IO` +- `RightAny` overload returns `Monoid` +- monoids now all fold with respect to `foldMap` +- monoid folding now implicitly starts with the identity, regardless of iterable population +- `Concat` monoid can now fold infinite iterables +- all `Function` are now `Function` for better compatibility +- `Either#diverge` returns a `Choice3` +- `Maybe` is now a `CoProduct2` of `Unit` and `A` +- `Fn0` now additionally implements `Callable` +- `CheckedRunnable` is an `IO` + +### Added +- `Predicate#predicate` static factory method +- `Product2-8` left/right rotation methods +- `Tuple2-8` specializations of left/right product rotation +- `CheckedEffect`, an `Effect` variant that can throw checked exceptions +- `CheckedFn1#checked`, convenience static factory method to aid inference +- `LiftA3-8`, higher-arity analogs to `LiftA2` +- `Alter`, for applying an `Effect` to an input and returning it, presumably altered +- `Clamp`, for clamping a value between two bounds +- `Between`, for determining if a value is in a closed interval +- `Strong`, profunctor strength +- `IO` monad +- `RunAll` semigroup and monoid instance for `IO` + +### Deprecated +- `AddAll` semigroup, in favor of the monoid that no longer mutates any argument +- Dyadic `Either#flatMap()`, in favor of `Either#match` + +## [3.1.0] - 2018-07-16 +### Added +- `Fn3-8` static factory overloads to aid in coercing lambdas +- Adding composition guarantees to `LensLike` +- `CmpEqBy`, `CmpEq`, `GTBy`, `GT`, `LTBy`, `LT`, `GTEBy`, `GTE`, `LTEBy`, and `LTE` inequality checks +- `MinBy`, `MaxBy`, `Min`, and `Max` semigroups +- `Product2-8` interfaces, representing general product types +- `Union`, a monoid that behaves like a lazy set union on `Iterable`s +- `Difference`, a semigroup that behaves like a partially lazy set difference on `Iterable`s +- `LambdaMap`, extension point for `j.u.Map`, similar to `LambdaIterable` +- `Sequence#sequence` overloads for `j.u.Map` that traverse via intermediate `LambdaMap` instances +- `Intersection`, a semigroup that behaves like a lazy set intersection on `Iterable`s +- `Fn0`, a function from `Unit` to some value +- `Fn1#thunk`, producing an `Fn0` +- `Absent`, a monoid over `Maybe` that is absence biased +- `RateLimit`, a function that iterates elements from an `Iterable` according to some rate limit +- `Try#withResources`, `Try`'s expression analog to Java 7's try-with-resources statement +- `Occurrences`, for counting the occurrences of the members of an `Iterable` +- `Effect`, an `Fn0` returning `UNIT` +- `Noop`, a no-op `Effect` +- `Fn1#widen`, add an ignored argument to the beginning of any function to raise its arity by one + +### Changed +- `Tuple2-8` now implement `Product2-8` +- `Into` now accepts `Map.Entry` +- `Into3-8` now accept a product of the same cardinality, instead of requiring a tuple +- `CoProduct2-8#project` now return generalized products +- `Choice2-8#project` return tuples +- `liftA2` receives more parameters to aid inference +- `Compose#getCompose` now supports inference + +### Removed +- `MapLens#mappingValues`, deprecated in a prior release +- `CollectionLens#asSet`, deprecated in a prior release +- `CollectionLens#asStream`, deprecated in a prior release + +## [3.0.3] - 2018-05-27 +### Added +- `Lens#toIso`, for converting a lens to an iso +- `HMap#hMap` overloads up to 8 bindings deep +- `Schema`, schemas for extracting multiple values from `HMap`s by aggregating `TypeSafeKey`s + +### Fixed +- Deforested iterables execute in intended nesting order, where essential + +## [3.0.2] - 2018-05-21 +### Added +- `IterableLens#mapping`, an `Iso` that maps values + +### Changed +- `TypeSafeKey.Simple` now has a default `#apply` implementation + +### Fixed +- mapped `TypeSafeKey` instances can be used for initial put in an `HMap`, and the base key can be used to retrieve them +- Merged pull request fixing issue storing values at mapped `TypeSafeKey` in `singletonHMap` + +## [3.0.1] - 2018-05-13 +### Changed +- `ToMap` accepts an `Iterable` covariant in `Map.Entry` +- `RecursiveResult#invert` is also a `RecursiveResult` +- `First`/`And`/`Or` monoids all utilize short-circuiting +- `Monoid#foldLeft/foldRight` delegate to `Monoid#reduceLeft/reduceRight`, respectively + +### Added +- `Upcast` for safely casting up a type hierarchy +- `SetLens`, lenses operating on `Set`s +- `ToArray`, for converting `Iterable` to `A[]` + +## [3.0.0] - 2018-05-04 +### Changed +- ***Breaking Change***: `Sequence` now has two more type parameters to aid in inference +- ***Breaking Change***: `Traversable#traverse` now has three more type parameters to aid in inference +- ***Breaking Change***: `Monad#zip` now forces `m a -> b` before `m a` in default `Applicative#zip` implementation; + this is only breaking for types that are sensitive to computation order (the resulting values are the same) +- ***Breaking Change***: `TypeSafeKey` is now dually parametric (single parameter analog is preserved in + `TypeSafeKey.Simple`) +- `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` +- `Peek2` now accepts the more general `BoundedBifunctor` +- `Identity`, `Compose`, and `Const` functors all have better `toString` implementations +- `Into3-8` now supports functions with parameter variance +- `HListLens#tail` is now covariant in `Tail` parameter +- More functions now automatically deforest nested calls (`concat` `cons`, `cycle`, `distinct`, `drop`, `dropwhile`, + `filter`, `map`, `reverse`, `snoc`, `take`, `takewhile`, `tail`) +- `Flatten` calls `Iterator#hasNext` less aggressively, allowing for better laziness +- `Lens` subtypes `LensLike` +- `View`/`Set`/`Over` now only require `LensLike` +- `HMap#keys` now returns a `Set` +- `HMap#values` now returns a `Collection` +- `Unfoldr` is now lazier, deferring all computations until `hasNext/next` calls +- `Present` is now a singleton + +### Added +- `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters +- `Try`, a `Monad` representing an expression-like analog of `try/catch/finally` +- `CheckedRunnable`, the `Runnable` counterpart to `CheckedSupplier` that can throw checked exceptions +- `Unit`, the lambda analog to `Void`, except actually inhabited by a singleton instance +- `Kleisli`, the abstract representation of a `Kleisli` arrow (`Monad#flatMap`) as an `Fn1` +- `These`, a `CoProduct3` of `A`, `B`, or `Tuple2` +- `Span`, for splitting an `Iterable` into contiguous elements matching a predicate +- `MagnetizeBy` and `Magnetize`, for grouping elements by pairwise predicate tests +- `Both`, for dually applying two functions and producing a `Tuple2` of their results +- `Lens#both`, for dually focusing with two lenses at once +- `IfThenElse`, an expression form for `if` statements +- `CheckedRunnable` and `CheckedSupplier` conversion and convenience methods +- `LensLike`, common capabilities that make a type usable as if it were a `Lens` +- `Iso`, isomorphisms between two types (invertible functions that are also lenses) +- `Exchange`, a `Profunctor` that can extract the morphisms from an `Iso` +- `HMapLens`, lenses focusing on `HMap` +- `MapLens#mappingValues(Iso)`, a lawful lens that maps the values of a `j.u.Map` +- `Under`, the inverse of `Over` for `Iso` +- `TypeSafeKey` is an `Iso` and supports mapping +- `TypeSafeKey.Simple`, the single parameter version of `TypeSafeKey` +- `Either#trying` overloads that accept `CheckedRunnable` + +### Deprecated +- `MapLens#mappingValues(Function)` is now deprecated in favor of the overload that takes an Iso + +## [2.1.1] - 2018-01-16 +### Changed +- ***Breaking Change***: Moved `Trampoline` and `RecursiveResult` to better package + +## [2.1.0] - 2018-01-14 +### Changed +- ***Breaking Change***: `CollectionLens#asSet` is now lawful and preserves new incoming values in the update set +- ***Breaking Change***: `IterableLens#head` is now a `Lens.Simple, Maybe>` and is lawful +- ***Breaking Change***: `ListLens#elementAt` is now a `Lens.Simple, Maybe>` supporting defensive copies +- ***Breaking Change***: `MapLens#valueAt` is now a `Lens.Simple, Maybe>` supporting defensive copies +- `MapLens#keys` now uses defensive copies and does not alter the focused on map +- `MapLens#values` now uses defensive copies and does not alter the focused on map +- `MapLens#inverted` now uses defensive copies and does not alter the focused on map +- `HListLens#head` is now covariant in the tail of both `S` and `T` +- `Predicate#contraMap` is now covariant in its return type +- `BiPredicate#contraMap` and `BiPredicate#diMapL` are now both covariant in their return types + +### Added +- `Fn3#fn3` and `Fn4#fn4` static factory methods +- `Fn5` through `Fn8` +- `Tuple5#into` +- `Tuple6` through `Tuple8` +- `CoProduct6` through `CoProduct8` and `Choice6` through `Choice8` +- `CoProduct5#diverge` and `Choice5#diverge` +- `Into3` through `Into8`, for applying a `Tuple*` to an `Fn*` +- `Times`, for successively accumulating a result by iterating a function over a value some number of times +- `Slide`, for "sliding" a window of some number of elements across an `Iterable` +- `Either#filter` overload supporting a function from `R` to `L` in the failing predicate case +- `CollectionLens#asSet(Function)`, a proper analog of `CollectionLens#asSet()` that uses defensive copies +- `CollectionLens#asStream(Function)`, a proper analog of `CollectionLens#asStream()` that uses defensive copies +- Explicitly calling attention to all unlawful lenses in their documentation +- `Peek` and `Peek2`, for "peeking" at the value contained inside any given `Functor` or `Bifunctor` with given + side-effects +- `Trampoline` and `RecursiveResult` for modeling primitive tail-recursive functions that can be trampolined + +### Removed +- `Either#toOptional`, deprecated in previous release +- `Either#fromOptional`, deprecated in previous release +- `sequence` overloads supporting `Optional`, deprecated in previous release +- `OptionalLens`, deprecated in previous release +- `TraversableIterable`, deprecated in previous release +- `Traversables`, deprecated in previous release + +### Deprecated +- `CollectionLens#asSet()` in favor of `CollectionLens#asSet(Function)` +- `CollectionLens#asStream()` in favor of `CollectionLens#asStream(Function)` + +## [2.0.0] - 2017-11-13 +### Changed +- ***Breaking Change***: `java.util.Optional` replaced with `Maybe` across the board +- `Profunctor#diMap/L/R` parameters allow variance +- `Either#toOptional` no longer allows `null` values in the right side, and is now in sync with CoProduct#projectB +- `Unfoldr` allows variance on input + +### Fixed +- `CoProductN#embed` no longer eagerly invokes functions +- `PrependAll` now only creates `O(1)` `Iterable`s instead of `O(3n + 1)` + +### Added +- `Monad` arrives. The following `Applicative`s are now also `Monad`: + - `Lens` + - `Const` + - `Tuple*` + - `Choice*` + - `Identity` + - `Either` + - `Fn*` + - `LambdaIterable` + - `Maybe` + - `SingletonHList` +- `Force`, for forcing iteration of an `Iterable` to perform any side-effects +- `Snoc`, for lazily appending an element to the end of an `Iterable` +- `Coalesce`, for folding an `Iterable>` into an `Either, Iterable>` +- `And`, `Or`, and `Xor` all gain `BiPredicate` properties +- `LambdaIterable`, an adapter `Iterable` that support lambda types +- `Maybe`, lambda's analog of `java.util.Optional` conforming to all the lambda types +- `Contravariant`, an interface representing functors that map contravariantly over their parameters +- `Profunctor` extends `Contravariant` +- `Tails`, for iterating all the tail element subsequences of an `Iterable` +- `Inits`, for iterating all the initial element subsequences of an `Iterable` +- `Init`, for iterating all but the last element of an `Iterable` +- `CatMaybes`, for unwrapping the present values in an `Iterable>` to produce an `Iterable` + +### Removed +- `Fn1#then(Function)`, deprecated in previous release +- `Fn1#adapt(Function function)`, deprecated in previous release +- `Fn2#adapt(BiFunction biFunction)`, deprecated in previous release + +### Deprecated +- `Traversables` and all methods therein, in favor of either `LambdaIterable` or `Maybe` +- `TraversableOptional` in favor of `Maybe` +- `TraversableIterable` in favor of `LambdaIterable` +- `Sequence` overloads supporting `Optional` in favor of converting `Optional` to `Maybe` and then sequencing +- `Either#toOptional` and `Either#fromOptional` in favor of its `Maybe` counterparts + +## [1.6.3] - 2017-09-27 +### Changed +- Loosening variance on `Fn2#fn2` and `Fn1#fn1` + +### Fixed +- `ConcatenatingIterator` bug where deeply nested `xs` skip elements + +### Deprecated +- `Fn1#then` in favor of `Fn1#andThen` (redundant) +- `Fn1#adapt` in favor of `Fn1#fn1` (rename) +- `Fn2#adapt` in favor of `Fn2#fn2` (rename) + +### Added +- `Fn1#andThen` overload to support composition with `Bifunction` +- `Fn1#compose` overload to support composition with `Bifunction` and `Fn2` +- `LiftA2` to lift and apply a `Bifunction` to two `Applicative`s +- `Flatten` to lazily flatten nested `Iterable>`s to `Iterable` +- `Replicate`, short-hand composition of `take` and `repeat` +- `Distinct` to produce an `Iterable` of distinct values in another `Iterable` +- `Sort` and `SortBy` for eagerly, monolithically sorting `Iterable`s and producing `List`s +- `IterableLens`, general lenses over `Iterable` +- `Xor`, a monoid representing logical exclusive-or + +## [1.6.2] - 2017-08-20 +### Changed +- Removing need for various suppressed unchecked warnings in `ChoiceN` types +- `HList` abstract super type loses both unnecessary parameters + +### Fixed +- ClassCastException `BiPredicate.flip` + +### Added +- `Uncons`, for destructuring an `Iterable` into its head and tail +- `Compose` semigroup and monoid formed over `CompletableFuture` +- `Monoid` and `Semigroup` both preserve type specificity through `flip` calls + +## [1.6.1] - 2017-06-17 +### Changed +- Loosening visibility on `Traversables` methods to `public` + +## [1.6.0] - 2017-06-04 +### Changed +- `Functor`, `Bifunctor`, and `Profunctor` (as well as all instances) get a unification parameter +- `Identity` supports value equality +- `Const` supports value equality +- `partition` now only requires iterables of `CoProudct2` +- `CoProductN`s receive a unification parameter, which trickles down to `Either` and `Choice`s +- `Concat` now represents a monoid for `Iterable`; previous `Concat` semigroup and monoid renamed to more appropriate + `AddAll` +- `Lens` is now an instance of `Profunctor` + +### Added +- `Either#invert` is pulled up into `CoProduct2` and additionally specialized for `Choice2` +- `CoProductN#embed` +- `not`, used for negating predicate functions +- `empty`, used to test if an Iterable is empty +- `groupBy`, for folding an Iterable into a Map given a key function +- `Applicative` arrives; all functors gain applicative properties +- `Traversable` arrives; `SingletonHList`, `Tuple*`, `Choice*`, `Either`, `Identity`, and `Const` gain traversable + properties +- `TraversableOptional` and `TraversableIterable` for adapting `Optional` and `Iterable`, respectively, to `Traversable` +- `sequence` for wrapping a traversable in an applicative during traversal +- `Compose`, an applicative functor that represents type-level functor composition + ## [1.5.6] - 2017-02-11 +### Changed +- `CoProductN.[a-e]()` static factory methods moved to equivalent `ChoiceN` class. Coproduct interfaces now solely + represent methods, no longer have anonymous implementations, and no longer require a `Functor` constraint + ### Added - `ChoiceN` types, representing concrete coproduct implementations that are also `Functor` and `BiFunctor` - `toMap`, `last`, `cons`, `prependAll`, `intersperse` @@ -13,18 +515,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `First` and `Last` monoids over `Optional` - `And` and `Or` monoids over `Boolean` +## [1.5.5] - 2016-12-17 ### Changed -- `CoProductN.[a-e]()` static factory methods moved to equivalent `ChoiceN` class. Coproduct interfaces now solely represent methods, no longer have anonymous implementations, and no longer require a `Functor` constraint +- semigroups and monoids moved under `fn2` package -## [1.5.5] - 2016-12-17 ### Added - `CoProductN#project`, to project disjoint union types into tuples of `Optional` values - `CoProductN#converge`, to drop the magnitude of a coproduct down by one type - `toCollection` and `size` -### Changed -- semigroups and monoids moved under `fn2` package - ## [1.5.4] - 2016-11-27 ### Added - `Fn1/2#adapt` to switch between lambda and `java.util.function` types more easily @@ -42,12 +541,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Either` is now a `CoProduct2` ## [1.5.2] - 2016-09-24 -### Added -- Heterogeneous list indexes arrive via `Index` - ### Changed - `Lens` static factory method renaming +### Added +- Heterogeneous list indexes arrive via `Index` + ## [1.5.1] - 2016-08-30 ### Added - Independent `Lens` parameter mapping via `mapS`, `mapT`, `mapA`, and `mapB` @@ -61,36 +560,37 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [1.4] - 2016-08-08 ### Changed -- All function input values become `java.util.function` types, and all function output values remain lambda types, for better compatibility +- All function input values become `java.util.function` types, and all function output values remain lambda types, for + better compatibility ## [1.3] - 2016-07-31 -### Added -- `HList` specializations support random access lookup - ### Changed - `Profunctor` inheritance hierarchy - Renaming `Identity` to `Id` - `Monadic/Dyadic/TriadicFunction` is now `Fn1/2/3` +### Added +- `HList` specializations support random access lookup + ## [1.2] - 2016-06-27 +### Changed +- `Tuple`s moved under `HList` as specialized subtypes + ### Added - `Either#peek` - `HMap`, heterogeneous maps - `Tuple2` is now also a `Map.Entry` +## [1.1] - 2016-06-21 ### Changed -- `Tuple`s moved under `HList` as specialized subtypes +- Better interoperability between lambda and `java.util.function` types -## [1.1] - 2016-06-21 ### Added - `scanLeft` -- `HList`, heterogenous lists +- `HList`, heterogeneous lists - Added up to `Tuple5` - `Either`, specialized coproduct with success/failure semantics -### Changed -- Better interoperability between lambda and `java.util.function` types - ## [1.0] - 2015-12-29 ### Added - Initial implementation of first-class curried functions @@ -100,8 +600,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Monadic/Dyadic/TriadicFunction`, `Predicate`, `Tuple2`, `Tuple3` - `Functor`, `BiFunctor`, `ProFunctor` -[Unreleased]: https://github.com/palatable/lambda/compare/lambda-1.5.6...HEAD -[1.5.6]: https://github.com/palatable/lambda/compare/lambda-1.5.5...1.5.6 +[Unreleased]: https://github.com/palatable/lambda/compare/lambda-5.4.0...HEAD +[5.4.0]: https://github.com/palatable/lambda/compare/lambda-5.3.0...lambda-5.4.0 +[5.3.0]: https://github.com/palatable/lambda/compare/lambda-5.2.0...lambda-5.3.0 +[5.2.0]: https://github.com/palatable/lambda/compare/lambda-5.1.0...lambda-5.2.0 +[5.1.0]: https://github.com/palatable/lambda/compare/lambda-5.0.0...lambda-5.1.0 +[5.0.0]: https://github.com/palatable/lambda/compare/lambda-4.0.0...lambda-5.0.0 +[4.0.0]: https://github.com/palatable/lambda/compare/lambda-3.3.0...lambda-4.0.0 +[3.3.0]: https://github.com/palatable/lambda/compare/lambda-3.2.0...lambda-3.3.0 +[3.2.0]: https://github.com/palatable/lambda/compare/lambda-3.1.0...lambda-3.2.0 +[3.1.0]: https://github.com/palatable/lambda/compare/lambda-3.0.3...lambda-3.1.0 +[3.0.3]: https://github.com/palatable/lambda/compare/lambda-3.0.2...lambda-3.0.3 +[3.0.2]: https://github.com/palatable/lambda/compare/lambda-3.0.1...lambda-3.0.2 +[3.0.1]: https://github.com/palatable/lambda/compare/lambda-3.0.0...lambda-3.0.1 +[3.0.0]: https://github.com/palatable/lambda/compare/lambda-2.1.1...lambda-3.0.0 +[2.1.1]: https://github.com/palatable/lambda/compare/lambda-2.1.0...lambda-2.1.1 +[2.1.0]: https://github.com/palatable/lambda/compare/lambda-2.0.0...lambda-2.1.0 +[2.0.0]: https://github.com/palatable/lambda/compare/lambda-1.6.3...lambda-2.0.0 +[1.6.3]: https://github.com/palatable/lambda/compare/lambda-1.6.2...lambda-1.6.3 +[1.6.2]: https://github.com/palatable/lambda/compare/lambda-1.6.1...lambda-1.6.2 +[1.6.1]: https://github.com/palatable/lambda/compare/lambda-1.6.0...lambda-1.6.1 +[1.6.0]: https://github.com/palatable/lambda/compare/lambda-1.5.6...lambda-1.6.0 +[1.5.6]: https://github.com/palatable/lambda/compare/lambda-1.5.5...lambda-1.5.6 [1.5.5]: https://github.com/palatable/lambda/compare/lambda-1.5.4...lambda-1.5.5 [1.5.4]: https://github.com/palatable/lambda/compare/lambda-1.5.3...lambda-1.5.4 [1.5.3]: https://github.com/palatable/lambda/compare/lambda-1.5.2...lambda-1.5.3 @@ -112,4 +632,4 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). [1.3]: https://github.com/palatable/lambda/compare/lambda-1.2...lambda-1.3 [1.2]: https://github.com/palatable/lambda/compare/lambda-1.1...lambda-1.2 [1.1]: https://github.com/palatable/lambda/compare/lambda-1.0...lambda-1.1 -[1.0]: https://github.com/palatable/lambda/commits/lambda-1.0 \ No newline at end of file +[1.0]: https://github.com/palatable/lambda/commits/lambda-1.0 diff --git a/LICENSE b/LICENSE index 89c5bf75e..fb5630322 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 palatable +Copyright (c) 2019 John Napier (jnape) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 31c2fa53e..7d84c6295 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,35 @@ λ ====== -[![Build Status](https://travis-ci.org/palatable/lambda.svg)](https://travis-ci.org/palatable/lambda) +[![Actions Status](https://github.com/palatable/lambda/workflows/Java%20CI/badge.svg)](https://github.com/palatable/lambda/actions) [![Lambda](https://img.shields.io/maven-central/v/com.jnape.palatable/lambda.svg)](http://search.maven.org/#search%7Cga%7C1%7Ccom.jnape.palatable.lambda) +[Join the chat on Discord](https://discord.gg/wR7k8RAKM5) +[![Floobits Status](https://floobits.com/jnape/lambda.svg)](https://floobits.com/jnape/lambda/redirect) -Functional patterns for Java 8 +Functional patterns for Java #### Table of Contents - [Background](#background) - [Installation](#installation) - [Examples](#examples) + - [Semigroups](#semigroups) + - [Monoids](#monoids) + - [Functors](#functors) + - [Bifunctors](#bifunctors) + - [Profunctors](#profunctors) + - [Applicatives](#applicatives) + - [Monads](#monads) + - [Traversables](#traversables) - [ADTs](#adts) - - [HLists](#hlists) + - [Maybe](#maybe) + - [HList](#hlist) - [Tuples](#tuples) - [HMaps](#hmaps) - [CoProducts](#coproducts) - [Either](#either) - [Lenses](#lenses) - - [Notes](#notes) + - [Notes](#notes) + - [Ecosystem](#ecosystem) - [License](#license) Background @@ -41,21 +53,21 @@ Although the library is currently (very) small, these values should always be th ------------ Add the following dependency to your: - + `pom.xml` ([Maven](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html)): - + ```xml com.jnape.palatable lambda - 1.5.6 + 5.4.0 ``` - + `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): - + ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5.6' +compile group: 'com.jnape.palatable', name: 'lambda', version: '5.4.0' ``` Examples @@ -63,22 +75,22 @@ compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5.6' First, the obligatory `map`/`filter`/`reduce` example: ```Java -Integer sumOfEvenIncrements = +Maybe sumOfEvenIncrements = reduceLeft((x, y) -> x + y, filter(x -> x % 2 == 0, map(x -> x + 1, asList(1, 2, 3, 4, 5)))); -//-> 12 +//-> Just 12 ``` Every function in lambda is [curried](https://www.wikiwand.com/en/Currying), so we could have also done this: ```Java -Fn1, Integer> sumOfEvenIncrementsFn = +Fn1, Maybe> sumOfEvenIncrementsFn = map((Integer x) -> x + 1) - .then(filter(x -> x % 2 == 0)) - .then(reduceLeft((x, y) -> x + y)); + .fmap(filter(x -> x % 2 == 0)) + .fmap(reduceLeft((x, y) -> x + y)); -Integer sumOfEvenIncrements = sumOfEvenIncrementsFn.apply(asList(1, 2, 3, 4, 5)); -//-> 12 +Maybe sumOfEvenIncrements = sumOfEvenIncrementsFn.apply(asList(1, 2, 3, 4, 5)); +//-> Just 12 ``` How about the positive squares below 100: @@ -94,7 +106,7 @@ We could have also used `unfoldr`: ```Java Iterable positiveSquaresBelow100 = unfoldr(x -> { int square = x * x; - return square < 100 ? Optional.of(tuple(square, x + 1)) : Optional.empty(); + return square < 100 ? Maybe.just(tuple(square, x + 1)) : Maybe.nothing(); }, 1); //-> [1, 4, 9, 16, 25, 36, 49, 64, 81] ``` @@ -104,7 +116,7 @@ What if we want the cross product of a domain and codomain: ```Java Iterable> crossProduct = take(10, cartesianProduct(asList(1, 2, 3), asList("a", "b", "c"))); -//-> (1,"a"), (1,"b"), (1,"c"), (2,"a"), (2,"b"), (2,"c"), (3,"a"), (3,"b"), (3,"c") +//-> [(1,"a"), (1,"b"), (1,"c"), (2,"a"), (2,"b"), (2,"c"), (3,"a"), (3,"b"), (3,"c")] ``` Let's compose two functions: @@ -113,9 +125,9 @@ Let's compose two functions: Fn1 add = x -> x + 1; Fn1 subtract = x -> x -1; -Fn1 noOp = add.then(subtract); +Fn1 noOp = add.fmap(subtract); // same as -Fn1 alsoNoOp = subtract.compose(add); +Fn1 alsoNoOp = subtract.contraMap(add); ``` And partially apply some: @@ -132,16 +144,369 @@ And have fun with 3s: ```Java Iterable> multiplesOf3InGroupsOf3 = - take(10, inGroupsOf(3, unfoldr(x -> Optional.of(tuple(x * 3, x + 1)), 1))); + take(3, inGroupsOf(3, unfoldr(x -> Maybe.just(tuple(x * 3, x + 1)), 1))); //-> [[3, 6, 9], [12, 15, 18], [21, 24, 27]] ``` Check out the [tests](https://github.com/palatable/lambda/tree/master/src/test/java/com/jnape/palatable/lambda/functions/builtin) or [javadoc](http://palatable.github.io/lambda/javadoc/) for more examples. +Semigroups +---- + +[Semigroups](https://en.wikipedia.org/wiki/Semigroup) are supported via `Semigroup`, a subtype of `Fn2`, and add left and right folds over an `Iterable`. + +```Java +Semigroup add = (augend, addend) -> augend + addend; +add.apply(1, 2); //-> 3 +add.foldLeft(0, asList(1, 2, 3)); //-> 6 +``` + +Lambda ships some default logical semigroups for lambda types and core JDK types. Common examples are: + +- `AddAll` for concatenating two `Collection`s +- `Collapse` for collapsing two `Tuple2`s together +- `Merge` for merging two `Either`s using left-biasing semantics + +Check out the [semigroup](https://palatable.github.io/lambda/javadoc/com/jnape/palatable/lambda/semigroup/builtin/package-summary.html) package for more examples. + +Monoids +---- + +[Monoids](https://en.wikipedia.org/wiki/Monoid) are supported via `Monoid`, a subtype of `Semigroup` with an `A #identity()` method, and add left and right reduces over an `Iterable`, as well as `foldMap`. + +```Java +Monoid multiply = monoid((x, y) -> x * y, 1); +multiply.reduceLeft(emptyList()); //-> 1 +multiply.reduceLeft(asList(1, 2, 3)); //-> 6 +multiply.foldMap(Integer::parseInt, asList("1", "2", "3")); //-> also 6 +``` + +Some commonly used lambda monoid implementations include: + +- `Present` for merging together two `Optional`s +- `Join` for joining two `String`s +- `And` for logical conjunction of two `Boolean`s +- `Or` for logical disjunction of two `Boolean`s + +Additionally, instances of `Monoid` can be trivially synthesized from instances of `Semigroup` via the `Monoid#monoid` static factory method, taking the `Semigroup` and the identity element `A` or a supplier of the identity element `Supplier`. + +Check out the [monoid](https://palatable.github.io/lambda/javadoc/com/jnape/palatable/lambda/monoid/builtin/package-summary.html) package for more examples. + +Functors +---- + +Functors are implemented via the `Functor` interface, and are sub-typed by every function type that lambda exports, as well as many of the [ADTs](#adts). + +```Java +public final class Slot implements Functor { + private final A a; + + public Slot(A a) { + this.a = a; + } + + public A getA() { + return a; + } + + @Override + public Slot fmap(Function fn) { + return new Slot<>(fn.apply(a)); + } +} + +Slot intSlot = new Slot<>(1); +Slot stringSlot = intSlot.fmap(x -> "number: " + x); +stringSlot.getA(); //-> "number: 1" +``` + +Examples of functors include: + +- `Fn*`, `Semigroup`, and `Monoid` +- `SingletonHList` and `Tuple*` +- `Choice*` +- `Either` +- `Const`, `Identity`, and `Compose` +- `Lens` + +Implementing `Functor` is as simple as providing a definition for the covariant mapping function `#fmap` (ideally satisfying the [two laws](https://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Functor.html)). + +### Bifunctors + +Bifunctors -- functors that support two parameters that can be covariantly mapped over -- are implemented via the `Bifunctor` interface. + +```Java +public final class Pair implements Bifunctor { + private final A a; + private final B b; + + public Pair(A a, B b) { + this.a = a; + this.b = b; + } + + public A getA() { + return a; + } + + public B getB() { + return b; + } + + @Override + public Pair biMap(Function lFn, + Function rFn) { + return new Pair<>(lFn.apply(a), rFn.apply(b)); + } +} + +Pair stringIntPair = new Pair<>("str", 1); +Pair intBooleanPair = stringIntPair.biMap(String::length, x -> x % 2 == 0); +intBooleanPair.getA(); //-> 3 +intBooleanPair.getB(); //-> false +``` + +Examples of bifunctors include: + +- `Tuple*` +- `Choice*` +- `Either` +- `Const` + +Implementing `Bifunctor` requires implementing *either* `biMapL` and `biMapR` *or* `biMap`. As with `Functor`, there are a [few laws](https://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Bifunctor.html) that well-behaved instances of `Bifunctor` should adhere to. + +### Profunctors + +Profunctors -- functors that support one parameter that can be mapped over contravariantly, and a second parameter that can be mapped over covariantly -- are implemented via the `Profunctor` interface. + +```Java +Fn1 add2 = (x) -> x + 2; +add2.diMap(Integer::parseInt, Object::toString).apply("1"); //-> "3" +``` + +Examples of profunctors include: + +- `Fn*` +- `Lens` + +Implementing `Profunctor` requires implementing *either* `diMapL` and `diMapR` *or* `diMap`. As with `Functor` and `Bifunctor`, there are [some laws](https://hackage.haskell.org/package/profunctors-5.2/docs/Data-Profunctor.html) that well behaved instances of `Profunctor` should adhere to. + +### Applicatives + +Applicative functors -- functors that can be applied together with a 2-arity or higher function -- are implemented via the `Applicative` interface. + +```Java +public final class Slot implements Applicative { + private final A a; + + public Slot(A a) { + this.a = a; + } + + public A getA() { + return a; + } + + @Override + public Slot fmap(Function fn) { + return pure(fn.apply(a)); + } + + @Override + public Slot pure(B b) { + return new Slot<>(b); + } + + @Override + public Slot zip(Applicative, Slot> appFn) { + return pure(appFn.>>coerce().getA().apply(getA())); + } +} + +Fn2 add = (x, y) -> x + y; +Slot x = new Slot<>(1); +Slot y = new Slot<>(2); +Slot z = y.zip(x.fmap(add)); //-> Slot{a=3} +``` + +Examples of applicative functors include: + +- `Fn*`, `Semigroup`, and `Monoid` +- `SingletonHList` and `Tuple*` +- `Choice*` +- `Either` +- `Const`, `Identity`, and `Compose` +- `Lens` + +In addition to implementing `fmap` from `Functor`, implementing an applicative functor involves providing two methods: `pure`, a method that lifts a value into the functor; and `zip`, a method that applies a lifted function to a lifted value, returning a new lifted value. As usual, there are [some laws](https://hackage.haskell.org/package/base-4.9.1.0/docs/Control-Applicative.html) that should be adhered to. + +### Monads + +Monads are applicative functors that additionally support a chaining operation, `flatMap :: (a -> f b) -> f a -> f b`: a function from the functor's parameter to a new instance of the same functor over a potentially different parameter. Because the function passed to `flatMap` can return a different instance of the same functor, functors can take advantage of multiple constructions that yield different functorial operations, like short-circuiting, as in the following example using `Either`: + +```Java +class Person { + Optional occupation() { + return Optional.empty(); + } +} + +class Occupation { +} + +public static void main(String[] args) { + Fn1> parseId = str -> Either.trying(() -> Integer.parseInt(str), __ -> str + " is not a valid id"); + + Map database = new HashMap<>(); + Fn1> lookupById = id -> Either.fromOptional(Optional.ofNullable(database.get(id)), + () -> "No person found for id " + id); + Fn1> getOccupation = p -> Either.fromOptional(p.occupation(), () -> "Person was unemployed"); + + Either occupationOrError = + parseId.apply("12") // Either + .flatMap(lookupById) // Either + .flatMap(getOccupation); // Either +} +``` + +In the previous example, if any of `parseId`, `lookupById`, or `getOccupation` fail, no further `flatMap` computations can succeed, so the result short-circuits to the first `left` value that is returned. This is completely predictable from the type signature of `Monad` and `Either`: `Either` is a `Monad`, so the single arity `flatMap` can have nothing to map in the case where there is no `R` value. With experience, it generally becomes quickly clear what the logical behavior of `flatMap` *must* be given the type signatures. + +That's it. Monads are neither [elephants](http://james-iry.blogspot.com/2007/09/monads-are-elephants-part-1.html) nor are they [burritos](https://blog.plover.com/prog/burritos.html); they're simply types that support a) the ability to lift a value into them, and b) a chaining function `flatMap :: (a -> f b) -> f a -> f b` that can potentially return different instances of the same monad. If a type can do those two things (and obeys [the laws](https://wiki.haskell.org/Monad_laws)), it is a monad. + +Further, if a type is a monad, it is necessarily an `Applicative`, which makes it necessarily a `Functor`, so *lambda* enforces this tautology via a hierarchical constraint. + +### Traversables + +Traversable functors -- functors that can be "traversed from left to right" -- are implemented via the `Traversable` interface. + +```Java +public abstract class Maybe implements Traversable { + private Maybe() { + } + + @Override + public abstract Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure); + + @Override + public abstract Maybe fmap(Function fn); + + private static final class Just extends Maybe { + private final A a; + + private Just(A a) { + this.a = a; + } + + @Override + public Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure) { + return fn.apply(a).fmap(Just::new); + } + + @Override + public Maybe fmap(Function fn) { + return new Just<>(fn.apply(a)); + } + } + + private static final class Nothing extends Maybe { + @Override + @SuppressWarnings("unchecked") + public Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure) { + return pure.apply((Maybe) this).fmap(x -> (Maybe) x); + } + + @Override + @SuppressWarnings("unchecked") + public Maybe fmap(Function fn) { + return (Maybe) this; + } + } +} + +Maybe just1 = Maybe.just(1); +Maybe nothing = Maybe.nothing(); + +Either> traversedJust = just1.traverse(x -> right(x + 1), empty -> left("empty")) + .fmap(x -> (Maybe) x) + .coerce(); //-> Right(Just(2)) + +Either> traversedNothing = nothing.traverse(x -> right(x + 1), empty -> left("empty")) + .fmap(x -> (Maybe) x) + .coerce(); //-> Left("empty") +``` + +Examples of traversable functors include: + +- `SingletonHList` and `Tuple*` +- `Choice*` +- `Either` +- `Const` and `Identity` +- `LambdaIterable` for wrapping `Iterable` in an instance of `Traversable` + +In addition to implementing `fmap` from `Functor`, implementing a traversable functor involves providing an implementation of `traverse`. + +As always, there are [some laws](https://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Traversable.html) that should be observed. + ADTs ---- -In addition to the functions above, lambda also supports a few first-class [algebraic data types](https://www.wikiwand.com/en/Algebraic_data_type). +Lambda also supports a few first-class [algebraic data types](https://www.wikiwand.com/en/Algebraic_data_type). + +### Maybe + +`Maybe` is the _lambda_ analog to `java.util.Optional`. It behaves in much of the same way as `j.u.Optional`, except that it quite intentionally does not support the inherently unsafe `j.u.Optional#get`. + +```Java +Maybe maybeInt = Maybe.just(1); // Just 1 +Maybe maybeString = Maybe.nothing(); // Nothing +``` + +Also, because it's a _lambda_ type, it takes advantage of the full functor hierarchy, as well as some helpful conversion functions: + +```Java +Maybe just = Maybe.maybe("string"); // Just "string" +Maybe nothing = Maybe.maybe(null); // Nothing + +Maybe maybeX = Maybe.just(1); +Maybe maybeY = Maybe.just(2); + +maybeY.zip(maybeX.fmap(x -> y -> x + y)); // Just 3 +maybeY.zip(nothing()); // Nothing +Maybe.nothing().zip(maybeX.fmap(x -> y -> x + y)); // Nothing + +Either right = maybeX.toEither(() -> "was empty"); // Right 1 +Either left = Maybe.nothing().toEither(() -> "was empty"); // Left "was empty" + +Maybe.fromEither(right); // Just 1 +Maybe.fromEither(left); // Nothing +``` + +Finally, for compatibility purposes, `Maybe` and `j.u.Optional` can be trivially converted back and forth: + +```Java +Maybe just1 = Maybe.just(1); // Just 1 +Optional present1 = just1.toOptional(); // Optional.of(1) + +Optional empty = Optional.empty(); // Optional.empty() +Maybe nothing = Maybe.fromOptional(empty); // Nothing +``` + +***Note***: One compatibility difference between `j.u.Optional` and `Maybe` is how `map`/`fmap` behave regarding functions that return `null`: `j.u.Optional` re-wraps `null` results from `map` operations in another `j.u.Optional`, whereas `Maybe` considers this to be an error, and throws an exception. The reason `Maybe` throws in this case is because `fmap` is not an operation to be called speculatively, and so any function that returns `null` in the context of an `fmap` operation is considered to be erroneous. Instead of calling `fmap` with a function that might return `null`, the function result should be wrapped in a `Maybe` and `flatMap` should be used, as illustrated in the following example: + +```Java +Function nullResultFn = __ -> null; + +Optional.of(1).map(nullResultFn); // Optional.empty() +Maybe.just(1).fmap(nullResultFn); // throws NullPointerException + +Maybe.just(1).flatMap(nullResultFn.andThen(Maybe::maybe)); // Nothing +``` ### Heterogeneous Lists (HLists) @@ -163,21 +528,24 @@ HNil nil = hList.tail().tail(); One of the primary downsides to using `HList`s in Java is how quickly the type signature grows. -To address this, tuples in lambda are specializations of `HList`s up to 5 elements deep, with added support for index-based accessor methods. +To address this, tuples in lambda are specializations of `HList`s up to 8 elements deep, with added support for index-based accessor methods. ```Java HNil nil = HList.nil(); -SingletonHList singleton = nil.cons(5); -Tuple2 tuple2 = singleton.cons(4); -Tuple3 tuple3 = tuple2.cons(3); -Tuple4 tuple4 = tuple3.cons(2); -Tuple5 tuple5 = tuple4.cons(1); - -System.out.println(tuple2._1()); // prints 4 -System.out.println(tuple5._5()); // prints 5 +SingletonHList singleton = nil.cons(8); +Tuple2 tuple2 = singleton.cons(7); +Tuple3 tuple3 = tuple2.cons(6); +Tuple4 tuple4 = tuple3.cons(5); +Tuple5 tuple5 = tuple4.cons(4); +Tuple6 tuple6 = tuple5.cons(3); +Tuple7 tuple7 = tuple6.cons(2); +Tuple8 tuple8 = tuple7.cons(1); + +System.out.println(tuple2._1()); // prints 7 +System.out.println(tuple8._8()); // prints 8 ``` -Additionally, `HList` provides convenience static factory methods for directly constructing lists of up to 5 elements: +Additionally, `HList` provides convenience static factory methods for directly constructing lists of up to 8 elements: ```Java SingletonHList singleton = HList.singletonHList(1); @@ -185,6 +553,9 @@ Tuple2 tuple2 = HList.tuple(1, 2); Tuple3 tuple3 = HList.tuple(1, 2, 3); Tuple4 tuple4 = HList.tuple(1, 2, 3, 4); Tuple5 tuple5 = HList.tuple(1, 2, 3, 4, 5); +Tuple6 tuple6 = HList.tuple(1, 2, 3, 4, 5, 6); +Tuple7 tuple7 = HList.tuple(1, 2, 3, 4, 5, 6, 7); +Tuple8 tuple8 = HList.tuple(1, 2, 3, 4, 5, 6, 7, 8); ``` `Index` can be used for type-safe retrieval and updating of elements at specific indexes: @@ -232,36 +603,36 @@ HMap hmap = HMap.hMap(stringKey, "string value", Optional stringValue = hmap.get(stringKey); // Optional["string value"] Optional intValue = hmap.get(intKey); // Optional[1] Optional anotherIntValue = hmap.get(anotherIntKey); // Optional.empty -``` +``` ### CoProducts `CoProduct`s generalize unions of disparate types in a single consolidated type, and the `ChoiceN` ADTs represent canonical implementations of these coproduct types. ```Java -CoProduct3 string = Choice3.a("string"); -CoProduct3 integer = Choice3.b(1); -CoProduct3 character = Choice3.c('a'); +CoProduct3 string = Choice3.a("string"); +CoProduct3 integer = Choice3.b(1); +CoProduct3 character = Choice3.c('a'); ``` Rather than supporting explicit value unwrapping, which would necessarily jeopardize type safety, `CoProduct`s support a `match` method that takes one function per possible value type and maps it to a final common result type: ```Java -CoProduct3 string = Choice3.a("string"); -CoProduct3 integer = Choice3.b(1); -CoProduct3 character = Choice3.c('a'); +CoProduct3 string = Choice3.a("string"); +CoProduct3 integer = Choice3.b(1); +CoProduct3 character = Choice3.c('a'); Integer result = string.match(String::length, identity(), Character::charCount); // 6 ``` -Additionally, because a `CoProduct2` guarantees a subset of a `CoProduct3`, the `diverge` method exists between `CoProduct` types of single magnitude differences to make it easy to use a more convergent `CoProduct` where a more divergent `CoProduct` is expected: - +Additionally, because a `CoProduct2` guarantees a subset of a `CoProduct3`, the `diverge` method exists between `CoProduct` types of single magnitude differences to make it easy to use a more convergent `CoProduct` where a more divergent `CoProduct` is expected: + ```Java -CoProduct2 coProduct2 = Choice2.a("string"); -CoProduct3 coProduct3 = coProduct2.diverge(); // still just the coProduct2 value, adapted to the coProduct3 shape +CoProduct2 coProduct2 = Choice2.a("string"); +CoProduct3 coProduct3 = coProduct2.diverge(); // still just the coProduct2 value, adapted to the coProduct3 shape ``` -There are `CoProduct` and `Choice` specializations for type unions of up to 5 different types: `CoProduct2` through `CoProduct5`, and `Choice2` through `Choice5`, respectively. +There are `CoProduct` and `Choice` specializations for type unions of up to 8 different types: `CoProduct2` through `CoProduct8`, and `Choice2` through `Choice8`, respectively. ### Either @@ -368,6 +739,24 @@ Wherever possible, _lambda_ maintains interface compatibility with similar, fami Unfortunately, due to Java's type hierarchy and inheritance inconsistencies, this is not always possible. One surprising example of this is how `Fn1` extends `j.u.f.Function`, but `Fn2` does not extend `j.u.f.BiFunction`. This is because `j.u.f.BiFunction` itself does not extend `j.u.f.Function`, but it does define methods that collide with `j.u.f.Function`. For this reason, both `Fn1` and `Fn2` cannot extend their Java counterparts without sacrificing their own inheritance hierarchy. These types of asymmetries are, unfortunately, not uncommon; however, wherever these situations arise, measures are taken to attempt to ease the transition in and out of core Java types (in the case of `Fn2`, a supplemental `#toBiFunction` method is added). I do not take these inconveniences for granted, and I'm regularly looking for ways to minimize the negative impact of this as much as possible. Suggestions and use cases that highlight particular pain points here are particularly appreciated. +Ecosystem +----- + +### Official extension libraries: + +These are officially supported libraries that extend lambda's core functionality and are developed under the same governance and processes as lambda. + +- [Shōki](https://github.com/palatable/shoki) - Purely functional, persistent data structures for the JVM + +### Third-party community libraries: + +These are open-sourced community projects that rely on _lambda_ for significant functionality, but are not necessarily affiliated with lambda and have their own separate maintainers. If you use _lambda_ in your own open-sourced project, feel free to create an issue and I'll be happy to review the project and add it to this section! + +- [Enhanced Iterables](https://github.com/kschuetz/enhanced-iterables) - Kevin Schuetz [@kschuetz](https://github.com/kschuetz) +- [Collection Views](https://github.com/kschuetz/collection-views) - Kevin Schuetz [@kschuetz](https://github.com/kschuetz) +- [WuWei](https://github.com/nomicflux/WuWei) - Michael Anderson [@nomicflux](https://github.com/nomicflux) - `ST` monad for safe mutability +- [Kraftwerk](https://github.com/kschuetz/kraftwerk) - Kevin Schuetz [@kschuetz](https://github.com/kschuetz) - random data generators and combinators + License ------- diff --git a/pom.xml b/pom.xml index fb88cf903..bae03b1e3 100644 --- a/pom.xml +++ b/pom.xml @@ -9,12 +9,12 @@ lambda - 1.5.6 + 5.5.1-SNAPSHOT jar Lambda - Functional patterns for Java 8 + Functional patterns for Java http://www.github.com/palatable/lambda @@ -48,15 +48,15 @@ jnape John Napier - john@jnape.com + jnape09@gmail.com - 3.1 - 1.0 + 1.3.0 3.3 - 1.3 + 2.1 + 3.1.1 @@ -66,13 +66,15 @@ org.hamcrest - hamcrest-all - ${hamcrest-all.version} + hamcrest + ${hamcrest.version} test org.mockito - mockito-all + mockito-core + 2.28.2 + test com.jnape.palatable @@ -80,12 +82,6 @@ ${traitor.version} test - - org.apache.commons - commons-lang3 - ${commons-lang3.version} - test - @@ -97,7 +93,30 @@ 1.8 1.8 + + -Xlint:all + -Werror + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + com.jnape.palatable.lambda + + + + + + test-jar + + + diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 4d6c988c4..a26b6a42e 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -1,31 +1,48 @@ package com.jnape.palatable.lambda.adt; +import com.jnape.palatable.lambda.adt.choice.Choice3; import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; -import com.jnape.palatable.lambda.functions.specialized.checked.CheckedFn1; -import com.jnape.palatable.lambda.functions.specialized.checked.CheckedSupplier; +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functions.specialized.SideEffect; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadError; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.io.IO.io; import static java.util.Arrays.asList; /** - * The binary tagged union. General semantics tend to connote "success" values via the right value and "failure" values - * via the left values. Eithers are both Functors over their right value and - * Bifunctors over both values. + * The binary tagged union, implemented as a specialized {@link CoProduct2}. General semantics tend to connote "success" + * values via the right value and "failure" values via the left values. {@link Either}s are both {@link Monad}s and + * {@link Traversable}s over their right value and are {@link Bifunctor}s over both values. * * @param The left parameter type * @param The right parameter type */ -public abstract class Either implements CoProduct2, Functor, Bifunctor { +public abstract class Either implements + CoProduct2>, + MonadError>, + MonadRec>, + Traversable>, + Bifunctor> { private Either() { } @@ -47,18 +64,18 @@ public final R or(R defaultValue) { * @param recoveryFn a function from L to R * @return either the wrapped value (if right) or the result of the left value applied to recoveryFn */ - public final R recover(Function recoveryFn) { + public final R recover(Fn1 recoveryFn) { return match(recoveryFn, id()); } /** - * Inverse of recover. If this is a right value, apply the wrapped value to forfeitFn and return it; otherwise, - * return the wrapped left value; + * Inverse of recover. If this is a right value, apply the wrapped value to forfeitFn and return it; + * otherwise, return the wrapped left value. * * @param forfeitFn a function from R to L * @return either the wrapped value (if left) or the result of the right value applied to forfeitFn */ - public final L forfeit(Function forfeitFn) { + public final L forfeit(Fn1 forfeitFn) { return match(id(), forfeitFn); } @@ -66,30 +83,44 @@ public final L forfeit(Function forfeitFn) { * Return the wrapped value if this is a right; otherwise, map the wrapped left value to a T and throw * it. * - * @param throwableFn a function from L to T * @param the left parameter type (the throwable type) + * @param throwableFn a function from L to T * @return the wrapped value if this is a right * @throws T the result of applying the wrapped left value to throwableFn, if this is a left */ - public final R orThrow(Function throwableFn) throws T { - return match((CheckedFn1) l -> { + public final R orThrow(Fn1 throwableFn) throws T { + return match(l -> { throw throwableFn.apply(l); }, id()); } /** - * If this is a right value, apply pred to it. If the result is true, return the same value; otherwise, return the - * result of leftSupplier wrapped as a left value. + * If this is a right value, apply pred to it. If the result is true, return the same + * value; otherwise, return the result of leftSupplier wrapped as a left value. *

* If this is a left value, return it. * - * @param pred the predicate to apply to a right value - * @param leftSupplier the supplier of a left value if pred fails + * @param pred the predicate to apply to a right value + * @param leftFn0 the supplier of a left value if pred fails * @return this if a left value or a right value that pred matches; otherwise, the result of leftSupplier wrapped in * a left */ - public final Either filter(Function pred, Supplier leftSupplier) { - return flatMap(r -> pred.apply(r) ? right(r) : left(leftSupplier.get())); + public final Either filter(Fn1 pred, Fn0 leftFn0) { + return filter(pred, __ -> leftFn0.apply()); + } + + /** + * If this is a right value, apply pred to it. If the result is true, return the same + * value; otherwise, return the results of applying the right value to leftFn wrapped as a left value. + * + * @param pred the predicate to apply to a right value + * @param leftFn the function from the right value to a left value if pred fails + * @return this is a left value or a right value that pred matches; otherwise, the result of leftFn applied to the + * right value, wrapped in a left + */ + public final Either filter(Fn1 pred, + Fn1 leftFn) { + return flatMap(r -> pred.apply(r) ? right(r) : left(leftFn.apply(r))); } /** @@ -103,34 +134,25 @@ public final Either filter(Function pred, Supplier * @param the new right parameter type * @return the Either resulting from applying rightFn to this right value, or this left value if left */ - public final Either flatMap(Function> rightFn) { - return flatMap(Either::left, rightFn); + @Override + @SuppressWarnings("RedundantTypeArguments") + public Either flatMap(Fn1>> rightFn) { + return match(Either::left, rightFn.fmap(Monad>::coerce)); } /** - * If a right value, apply rightFn to the unwrapped right value and return the resulting - * Either; otherwise, apply the unwrapped left value to leftFn and return the resulting - * Either. - * - * @param leftFn the function to apply if a left value - * @param rightFn the function to apply if a right value - * @param the new left parameter type - * @param the new right parameter type - * @return the result of either rightFn or leftFn, depending on whether this is a right or a left + * {@inheritDoc} */ - public final Either flatMap(Function> leftFn, - Function> rightFn) { - return match(leftFn, rightFn); + @Override + public Either trampolineM(Fn1, Either>> fn) { + return match(Either::left, trampoline(a -> fn.apply(a).>>coerce() + .match(l -> terminate(left(l)), + aOrB -> aOrB.fmap(Either::right)))); } - /** - * Transform an Either<L, R> into an Either<R, L>, preserving the current - * values. - * - * @return The inverted either - */ + @Override public final Either invert() { - return flatMap(Either::right, Either::left); + return match(Either::right, Either::left); } /** @@ -145,8 +167,9 @@ public final Either invert() { * @return the merged Either */ @SafeVarargs - public final Either merge(BiFunction leftFn, - BiFunction rightFn, + @SuppressWarnings("varargs") + public final Either merge(Fn2 leftFn, + Fn2 rightFn, Either... others) { return foldLeft((x, y) -> x.match(l1 -> y.match(l2 -> left(leftFn.apply(l1, l2)), r -> left(l1)), r1 -> y.match(Either::left, r2 -> right(rightFn.apply(r1, r2)))), @@ -157,29 +180,28 @@ public final Either merge(BiFunction le /** * Perform side-effects against a wrapped right value, returning back the Either unaltered. * - * @param rightConsumer the effecting consumer + * @param effect the effecting consumer * @return the Either, unaltered + * @deprecated in favor of {@link Either#match(Fn1, Fn1) matching} into an {@link IO} and explicitly running it */ - public Either peek(Consumer rightConsumer) { - return peek(l -> { - }, rightConsumer); + @Deprecated + public Either peek(Fn1> effect) { + return match(l -> io(Either.left(l)), + r -> effect.apply(r).fmap(constantly(this))) + .unsafePerformIO(); } /** * Perform side-effects against a wrapped right or left value, returning back the Either unaltered. * - * @param leftConsumer the effecting consumer for left values - * @param rightConsumer the effecting consumer for right values + * @param leftEffect the effecting consumer for left values + * @param rightEffect the effecting consumer for right values * @return the Either, unaltered + * @deprecated in favor of {@link Either#match(Fn1, Fn1) matching} into an {@link IO} and explicitly running it */ - public Either peek(Consumer leftConsumer, Consumer rightConsumer) { - return flatMap(l -> { - leftConsumer.accept(l); - return this; - }, r -> { - rightConsumer.accept(r); - return this; - }); + @Deprecated + public Either peek(Fn1> leftEffect, Fn1> rightEffect) { + return match(leftEffect, rightEffect).fmap(constantly(this)).unsafePerformIO(); } /** @@ -187,93 +209,199 @@ public Either peek(Consumer leftConsumer, Consumer rightConsumer) { * V), unwrap the value stored in this Either, apply the appropriate mapping function, * and return the result. * + * @param the result type * @param leftFn the left value mapping function * @param rightFn the right value mapping function - * @param the result type * @return the result of applying the appropriate mapping function to the wrapped value */ - public abstract V match(Function leftFn, Function rightFn); + @Override + public abstract V match(Fn1 leftFn, Fn1 rightFn); + /** + * {@inheritDoc} + */ @Override - public final Either fmap(Function fn) { - return biMapR(fn); + public Choice3 diverge() { + return match(Choice3::a, Choice3::b); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final Either biMapL(Function fn) { - return (Either) Bifunctor.super.biMapL(fn); + public final Either fmap(Fn1 fn) { + return MonadError.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final Either biMapR(Function fn) { - return (Either) Bifunctor.super.biMapR(fn); + public final Either biMapL(Fn1 fn) { + return (Either) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - public final Either biMap(Function leftFn, - Function rightFn) { + public final Either biMapR(Fn1 fn) { + return (Either) Bifunctor.super.biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public final Either biMap(Fn1 leftFn, + Fn1 rightFn) { return match(l -> left(leftFn.apply(l)), r -> right(rightFn.apply(r))); } /** - * In the left case, returns an {@link Optional#empty}; otherwise, returns {@link Optional#ofNullable} around the - * right value. + * {@inheritDoc} + */ + @Override + public final Either pure(R2 r2) { + return right(r2); + } + + /** + * {@inheritDoc} + */ + @Override + public final Either zip(Applicative, Either> appFn) { + return MonadError.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Either>> lazyAppFn) { + return match(l -> lazy(left(l)), + r -> lazyAppFn.fmap(eitherLF -> eitherLF.fmap(f -> f.apply(r)).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public final Either discardL(Applicative> appB) { + return MonadError.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final Either discardR(Applicative> appB) { + return MonadError.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Either throwError(L l) { + return left(l); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("RedundantTypeArguments") + public Either catchError(Fn1>> recoveryFn) { + return match(recoveryFn.fmap(Monad>::coerce), Either::right); + } + + /** + * {@inheritDoc} + */ + @Override + public final , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(l -> pure.apply(Either.left(l).coerce()), + r -> fn.apply(r).>fmap(Either::right).fmap(Functor::coerce)) + .coerce(); + } + + /** + * In the left case, returns a {@link Maybe#nothing()}; otherwise, returns {@link Maybe#maybe} around the right + * value. * - * @return an Optional around the right value, or empty if left + * @return Maybe the right value */ - public Optional toOptional() { - return match(__ -> Optional.empty(), Optional::ofNullable); + public final Maybe toMaybe() { + return projectB(); } /** - * Convert an {@link Optional}<R> into an Either<L, R>, supplying the left value from - * leftFn in the case of {@link Optional#empty()}. + * Convert a {@link Maybe}<R> into an Either<L, R>, supplying the left value from + * leftFn in the case of {@link Maybe#nothing()}. * - * @param optional the optional - * @param leftFn the supplier to use for left values - * @param the left parameter type - * @param the right parameter type - * @return a right value of the contained optional value, or a left value of leftFn's result + * @param the left parameter type + * @param the right parameter type + * @param maybe the maybe + * @param leftFn0 the supplier to use for left values + * @return a right value of the contained maybe value, or a left value of leftFn's result */ - public static Either fromOptional(Optional optional, Supplier leftFn) { - return optional.>map(Either::right) - .orElseGet(() -> left(leftFn.get())); + public static Either fromMaybe(Maybe maybe, Fn0 leftFn0) { + return maybe.>fmap(Either::right) + .orElseGet(() -> left(leftFn0.apply())); } /** - * Attempt to execute the {@link CheckedSupplier}, returning its result in a right value. If the supplier throws an + * Attempt to execute the {@link Fn0}, returning its result in a right value. If the supplier throws an * exception, apply leftFn to it, wrap it in a left value and return it. * - * @param supplier the supplier of the right value - * @param leftFn a function mapping E to L - * @param the most contravariant exception that the supplier might throw - * @param the left parameter type - * @param the right parameter type + * @param the left parameter type + * @param the right parameter type + * @param fn0 the supplier of the right value + * @param leftFn a function mapping E to L * @return the supplier result as a right value, or leftFn's mapping result as a left value */ - @SuppressWarnings("unchecked") - public static Either trying(CheckedSupplier supplier, - Function leftFn) { - try { - return right(supplier.get()); - } catch (Exception e) { - return left(leftFn.apply((E) e)); - } + public static Either trying(Fn0 fn0, Fn1 leftFn) { + return Try.trying(fn0).toEither(leftFn); } /** - * Attempt to execute the {@link CheckedSupplier}, returning its result in a right value. If the supplier throws an + * Attempt to execute the {@link Fn0}, returning its result in a right value. If the supplier throws an * exception, wrap it in a left value and return it. * - * @param supplier the supplier of the right value - * @param the left parameter type (the most contravariant exception that supplier might throw) - * @param the right parameter type + * @param fn0 the supplier of the right value + * @param the right parameter type * @return the supplier result as a right value, or a left value of the thrown exception */ - public static Either trying(CheckedSupplier supplier) { - return trying(supplier, id()); + public static Either trying(Fn0 fn0) { + return trying(fn0, id()); + } + + /** + * Attempt to execute the {@link SideEffect}, returning {@link Unit} in a right value. If the runnable throws + * an exception, apply leftFn to it, wrap it in a left value, and return it. + * + * @param the left parameter type + * @param sideEffect the runnable + * @param leftFn a function mapping E to L + * @return {@link Unit} as a right value, or leftFn's mapping result as a left value + */ + public static Either trying(SideEffect sideEffect, Fn1 leftFn) { + return Try.trying(sideEffect).toEither(leftFn); + } + + /** + * Attempt to execute the {@link SideEffect}, returning {@link Unit} in a right value. If the runnable throws + * exception, wrap it in a left value and return it. + * + * @param sideEffect the runnable + * @return {@link Unit} as a right value, or a left value of the thrown exception + */ + public static Either trying(SideEffect sideEffect) { + return trying(sideEffect, id()); } /** @@ -300,6 +428,16 @@ public static Either right(R r) { return new Right<>(r); } + /** + * The canonical {@link Pure} instance for {@link Either}. + * + * @param the left type + * @return the {@link Pure} instance + */ + public static Pure> pureEither() { + return Either::right; + } + private static final class Left extends Either { private final L l; @@ -308,7 +446,7 @@ private Left(L l) { } @Override - public V match(Function leftFn, Function rightFn) { + public V match(Fn1 leftFn, Fn1 rightFn) { return leftFn.apply(l); } @@ -338,7 +476,7 @@ private Right(R r) { } @Override - public V match(Function leftFn, Function rightFn) { + public V match(Fn1 leftFn, Fn1 rightFn) { return rightFn.apply(r); } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java new file mode 100644 index 000000000..dc5595983 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java @@ -0,0 +1,371 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.adt.choice.Choice3; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadError; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; +import java.util.Optional; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.Fn0.fn0; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.io.IO.io; + +/** + * The optional type, representing a potentially absent value. This is lambda's analog of {@link Optional}, supporting + * all the usual suspects like {@link Functor}, {@link Applicative}, {@link Traversable}, etc. + * + * @param the optional parameter type + * @see Optional + */ +public abstract class Maybe implements + CoProduct2>, + MonadError>, + MonadRec>, + Traversable> { + + private Maybe() { + } + + /** + * If the value is present, return it; otherwise, return the value supplied by otherSupplier. + * + * @param otherFn0 the supplier for the other value + * @return this value, or the supplied other value + */ + public final A orElseGet(Fn0 otherFn0) { + return match(__ -> otherFn0.apply(), id()); + } + + /** + * If the value is present, return it; otherwise, return other. + * + * @param other the other value + * @return this value, or the other value + */ + public final A orElse(A other) { + return orElseGet(() -> other); + } + + /** + * If the value is present, return it; otherwise, throw the {@link Throwable} supplied by + * throwableSupplier. + * + * @param throwableSupplier the supplier of the potentially thrown {@link Throwable} + * @param the Throwable type + * @return the value, if present + * @throws E the throwable, if the value is absent + */ + public final A orElseThrow(Fn0 throwableSupplier) throws E { + return orElseGet(fn0(() -> { + throw throwableSupplier.apply(); + })); + } + + /** + * If this value is present and satisfies predicate, return just the value; otherwise, + * return nothing. + * + * @param predicate the predicate to apply to the possibly absent value + * @return maybe the present value that satisfied the predicate + */ + public final Maybe filter(Fn1 predicate) { + return flatMap(a -> predicate.apply(a) ? just(a) : nothing()); + } + + /** + * {@inheritDoc} + */ + @Override + public Maybe throwError(Unit unit) { + return nothing(); + } + + /** + * {@inheritDoc} + */ + @Override + public Maybe catchError(Fn1>> recoveryFn) { + return match(recoveryFn, Maybe::just).coerce(); + } + + /** + * If this value is absent, return the value supplied by lSupplier wrapped in Either.left. + * Otherwise, wrap the value in Either.right and return it. + * + * @param the left parameter type + * @param lFn0 the supplier for the left value + * @return this value wrapped in an Either.right, or an Either.left around the result of lSupplier + */ + public final Either toEither(Fn0 lFn0) { + return fmap(Either::right).orElseGet(() -> left(lFn0.apply())); + } + + /** + * Convert to {@link Optional}. + * + * @return the Optional + */ + public final Optional toOptional() { + return fmap(Optional::of).orElseGet(Optional::empty); + } + + /** + * Lift the value into the {@link Maybe} monad + * + * @param b the value + * @param the value type + * @return Just b + */ + @Override + public final Maybe pure(B b) { + return just(b); + } + + /** + * {@inheritDoc} + *

+ * If the value is present, return {@link Maybe#just} fn applied to the value; otherwise, return + * {@link Maybe#nothing}. + */ + @Override + public final Maybe fmap(Fn1 fn) { + return MonadError.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final Maybe zip(Applicative, Maybe> appFn) { + return MonadError.super.zip(appFn).coerce(); + } + + /** + * Terminate early if this is a {@link Nothing}; otherwise, continue the {@link Applicative#zip zip}. + * + * @param the result type + * @param lazyAppFn the lazy other applicative instance + * @return the zipped {@link Maybe} + */ + @Override + public Lazy> lazyZip(Lazy, Maybe>> lazyAppFn) { + return match(constantly(lazy(nothing())), + a -> lazyAppFn.fmap(maybeF -> maybeF.fmap(f -> f.apply(a)).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public final Maybe discardL(Applicative> appB) { + return MonadError.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final Maybe discardR(Applicative> appB) { + return MonadError.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("RedundantTypeArguments") + @Override + public final Maybe flatMap(Fn1>> f) { + return match(constantly(nothing()), f.fmap(Monad>::coerce)); + } + + /** + * {@inheritDoc} + */ + @Override + public Maybe trampolineM(Fn1, Maybe>> fn) { + return match(constantly(nothing()), trampoline(a -> fn.apply(a).>>coerce() + .match(constantly(terminate(nothing())), + aOrB -> aOrB.fmap(Maybe::just)))); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice3 diverge() { + return match(Choice3::a, Choice3::b); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple2, Maybe> project() { + return CoProduct2.super.project().into(HList::tuple); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice2 invert() { + return match(Choice2::b, Choice2::a); + } + + /** + * If this value is present, accept it by consumer; otherwise, do nothing. + * + * @param effect the consumer + * @return the same Maybe instance + * @deprecated in favor of {@link Maybe#match(Fn1, Fn1) matching} into an {@link IO} and explicitly running it + */ + @Deprecated + public final Maybe peek(Fn1> effect) { + return match(constantly(io(this)), a -> effect.apply(a).fmap(constantly(this))).unsafePerformIO(); + } + + @Override + @SuppressWarnings("unchecked") + public final , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(__ -> pure.apply((TravB) Maybe.nothing()), a -> (AppTrav) fn.apply(a).fmap(Maybe::just)); + } + + /** + * Convenience static factory method for creating a {@link Maybe} from an {@link Either}. If either is + * a right value, wrap the value in a just and return it; otherwise, return {@link #nothing()}. + * + * @param either the either instance + * @param the potential right value + * @return "Just" the right value, or nothing + */ + public static Maybe fromEither(Either either) { + return either.toMaybe(); + } + + /** + * Convenience static factory method for creating a {@link Maybe} from an {@link Optional}. + * + * @param optional the optional + * @param the optional parameter type + * @return the equivalent Maybe instance + */ + public static Maybe fromOptional(Optional optional) { + return optional.map(Maybe::just).orElse(Maybe.nothing()); + } + + /** + * Lift a potentially null value into {@link Maybe}. If a is not null, returns just(a); + * otherwise, returns {@link #nothing()}. + * + * @param a the potentially null value + * @param the value parameter type + * @return "Just" the value, or nothing + */ + public static Maybe maybe(A a) { + return a == null ? nothing() : just(a); + } + + /** + * Lift a non-null value into {@link Maybe}. This differs from {@link Maybe#maybe} in that the value *must* be + * non-null; if it is null, a {@link NullPointerException} is thrown. + * + * @param a the non-null value + * @param the value parameter type + * @return "Just" the value + * @throws NullPointerException if a is null + */ + public static Maybe just(A a) { + if (a == null) + throw new NullPointerException(); + return new Just<>(a); + } + + /** + * Return nothing. + * + * @param the type of the value, if there was one + * @return nothing + */ + @SuppressWarnings("unchecked") + public static Maybe nothing() { + return (Maybe) Nothing.INSTANCE; + } + + /** + * The canonical {@link Pure} instance for {@link Maybe}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureMaybe() { + return Maybe::just; + } + + private static final class Nothing extends Maybe { + private static final Nothing INSTANCE = new Nothing<>(); + + private Nothing() { + } + + @Override + public R match(Fn1 aFn, Fn1 bFn) { + return aFn.apply(UNIT); + } + + @Override + public String toString() { + return "Nothing"; + } + } + + private static final class Just extends Maybe { + + private final A a; + + private Just(A a) { + this.a = a; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn) { + return bFn.apply(a); + } + + @Override + public boolean equals(Object other) { + return other instanceof Just && Objects.equals(this.a, ((Just) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Just " + a; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/These.java b/src/main/java/com/jnape/palatable/lambda/adt/These.java new file mode 100644 index 000000000..4fc4cc27b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/These.java @@ -0,0 +1,308 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.recursion.Trampoline; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + +/** + * The coproduct of a coproduct ({@link CoProduct2}<A, B>) and its product ({@link + * Tuple2}<A, B>), represented as a {@link CoProduct3}<A, B, {@link Tuple2}<A, + * B>>. + * + * @param the first possible type + * @param the second possible type + */ +public abstract class These implements + CoProduct3, These>, + MonadRec>, + Bifunctor>, + Traversable> { + + private These() { + } + + /** + * {@inheritDoc} + */ + @Override + public final These biMap(Fn1 lFn, + Fn1 rFn) { + return match(a -> a(lFn.apply(a)), b -> b(rFn.apply(b)), into((a, b) -> both(lFn.apply(a), rFn.apply(b)))); + } + + /** + * {@inheritDoc} + */ + @Override + public final These flatMap(Fn1>> f) { + return match(These::a, + b -> f.apply(b).coerce(), + into((a, b) -> f.apply(b) + .>coerce() + .match(constantly(a(a)), + c -> both(a, c), + into((__, c) -> both(a, c))))); + } + + /** + * {@inheritDoc} + */ + @Override + public These trampolineM( + Fn1, These>> fn) { + return flatMap(Trampoline.>trampoline( + b -> sequence(fn.apply(b).>>coerce(), + RecursiveResult::terminate))); + } + + /** + * {@inheritDoc} + */ + @Override + public final These pure(C c) { + return match(a -> both(a, c), b -> b(c), into((a, b) -> both(a, c))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravC extends Traversable>, + AppTrav extends Applicative> + AppTrav traverse(Fn1> fn, Fn1 pure) { + return match(a -> pure.apply(These.a(a).coerce()), + b -> fn.apply(b).fmap(this::pure).fmap(Applicative::coerce).coerce(), + into((a, b) -> fn.apply(b).fmap(c -> both(a, c)).fmap(Applicative::coerce).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public final These biMapL(Fn1 fn) { + return (These) Bifunctor.super.biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public final These biMapR(Fn1 fn) { + return (These) Bifunctor.super.biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public final These fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final These zip(Applicative, These> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, These>> lazyAppFn) { + return projectA().>>fmap(a -> lazy(a(a))) + .orElseGet(() -> MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce)); + } + + /** + * {@inheritDoc} + */ + @Override + public final These discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final These discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * Static factory method for wrapping a value of type A in a {@link These}. + * + * @param a the value + * @param the first possible type + * @param the second possible type + * @return the wrapped value as a {@link These}<A,B> + */ + public static These a(A a) { + return new _A<>(a); + } + + /** + * Static factory method for wrapping a value of type B in a {@link These}. + * + * @param b the value + * @param the first possible type + * @param the second possible type + * @return the wrapped value as a {@link These}<A,B> + */ + public static These b(B b) { + return new _B<>(b); + } + + /** + * Static factory method for wrapping a value of type A and a value of type B in a {@link + * These}. + * + * @param a the first value + * @param b the second value + * @param the first possible type + * @param the second possible type + * @return the wrapped values as a {@link These}<A,B> + */ + public static These both(A a, B b) { + return new Both<>(tuple(a, b)); + } + + /** + * Convenience method for converting a pair of {@link Maybe}s into a {@link Maybe} of {@link These}. If both + * {@link Maybe}s are {@link Maybe#just} then the result is a {@link Maybe#just} {@link These#both}. If only one + * {@link Maybe} is {@link Maybe#just} then it will be {@link Maybe#just} {@link These#a} or + * {@link Maybe#just} {@link These#b}. If both {@link Maybe}s are {@link Maybe#nothing} then the result will be + * {@link Maybe#nothing}. + * + * @param maybeA the first optional value + * @param maybeB the second optional value + * @param the first possible type + * @param the second possible type + * @return the wrapped values as a {@link Maybe}<{@link These}<A,B>> + */ + public static Maybe> fromMaybes(Maybe maybeA, Maybe maybeB) { + return maybeA.fmap(a -> maybeB.fmap(b -> both(a, b)).orElse(a(a))) + .fmap(Maybe::just) + .orElse(maybeB.fmap(These::b)); + } + + /** + * The canonical {@link Pure} instance for {@link These}. + * + * @param the first possible type + * @return the {@link Pure} instance + */ + public static Pure> pureThese() { + return These::b; + } + + private static final class _A extends These { + + private final A a; + + private _A(A a) { + this.a = a; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1, ? extends R> cFn) { + return aFn.apply(a); + } + + @Override + public boolean equals(Object other) { + return other instanceof These._A && Objects.equals(a, ((_A) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "These{a=" + a + '}'; + } + } + + private static final class _B extends These { + private final B b; + + private _B(B b) { + this.b = b; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1, ? extends R> cFn) { + return bFn.apply(b); + } + + @Override + public boolean equals(Object other) { + return other instanceof These._B && Objects.equals(b, ((_B) other).b); + } + + @Override + public int hashCode() { + return Objects.hash(b); + } + + @Override + public String toString() { + return "These{b=" + b + '}'; + } + } + + private static final class Both extends These { + private final Tuple2 both; + + private Both(Tuple2 tuple) { + this.both = tuple; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1, ? extends R> cFn) { + return cFn.apply(both); + } + + @Override + public boolean equals(Object other) { + return other instanceof Both && Objects.equals(both, ((Both) other).both); + } + + @Override + public int hashCode() { + return Objects.hash(both); + } + + @Override + public String toString() { + return "These{both=" + both + '}'; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Try.java b/src/main/java/com/jnape/palatable/lambda/adt/Try.java new file mode 100644 index 000000000..90cd28091 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/Try.java @@ -0,0 +1,478 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functions.specialized.SideEffect; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadError; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.internal.Runtime.throwChecked; + +/** + * A {@link Monad} of the evaluation outcome of an expression that might throw. Try/catch/finally semantics map to + * trying/catching/ensuring, respectively. + * + * @param the possibly successful expression result + * @see Either + */ +public abstract class Try implements + MonadError>, + MonadRec>, + Traversable>, + CoProduct2> { + + private Try() { + } + + /** + * Catch any instance of throwableType and map it to a success value. + * + * @param the {@link Throwable} (sub)type + * @param throwableType the {@link Throwable} (sub)type to be caught + * @param recoveryFn the function mapping the {@link Throwable} to the result + * @return a new {@link Try} instance around either the original successful result or the mapped result + */ + public final Try catching(Class throwableType, + Fn1 recoveryFn) { + return catching(throwableType::isInstance, t -> recoveryFn.apply(Downcast.downcast(t))); + } + + /** + * Catch any thrown T satisfying predicate and map it to a success value. + * + * @param predicate the predicate + * @param recoveryFn the function mapping the {@link Throwable} to the result + * @return a new {@link Try} instance around either the original successful result or the mapped result + */ + public final Try catching(Fn1 predicate, + Fn1 recoveryFn) { + return match(t -> predicate.apply(t) ? success(recoveryFn.apply(t)) : failure(t), Try::success); + } + + /** + * Run the provided runnable regardless of whether this is a success or a failure (the {@link Try} analog to + * finally. + *

+ * If the runnable runs successfully, the result is preserved as is. If the runnable itself throws, and the result + * was a success, the result becomes a failure over the newly-thrown {@link Throwable}. If the result was a failure + * over some {@link Throwable} t1, and the runnable throws a new {@link Throwable} t2, the + * result is a failure over t1 with t2 added to t1 as a suppressed exception. + * + * @param sideEffect the runnable block of code to execute + * @return the same {@link Try} instance if runnable completes successfully; otherwise, a {@link Try} conforming to + * rules above + */ + public final Try ensuring(SideEffect sideEffect) { + return this.>match(t -> trying(sideEffect) + .>fmap(constantly(failure(t))) + .recover(t2 -> { + t.addSuppressed(t2); + return failure(t); + }), + a -> trying(sideEffect).fmap(constantly(a))); + } + + /** + * If this is a success, return the wrapped value. Otherwise, apply the {@link Throwable} to fn and + * return the result. + * + * @param fn the function mapping the potential {@link Throwable} T to A + * @return a success value + */ + public final A recover(Fn1 fn) { + return match(fn, id()); + } + + /** + * If this is a failure, return the wrapped value. Otherwise, apply the success value to fn and return + * the result. + * + * @param fn the function mapping the potential A to T + * @return a failure value + */ + public final Throwable forfeit(Fn1 fn) { + return match(id(), fn); + } + + /** + * If this is a success value, return it. Otherwise, rethrow the captured failure. + * + * @param a declarable exception type used for catching checked exceptions + * @return possibly the success value + * @throws T anything that the call site may want to explicitly catch or indicate could be thrown + */ + public final A orThrow() throws T { + try { + return orThrow(id()); + } catch (Throwable t) { + throw throwChecked(t); + } + } + + /** + * If this is a success value, return it. Otherwise, transform the captured failure with fn and throw + * the result. + * + * @param fn the {@link Throwable} transformation + * @param the type of the thrown {@link Throwable} + * @return possibly the success value + * @throws T the transformation output + */ + public abstract A orThrow(Fn1 fn) throws T; + + /** + * If this is a success, wrap the value in a {@link Maybe#just} and return it. Otherwise, return {@link + * Maybe#nothing()}. + * + * @return {@link Maybe} the success value + */ + public final Maybe toMaybe() { + return match(__ -> nothing(), Maybe::just); + } + + /** + * If this is a success, wrap the value in a {@link Either#right} and return it. Otherwise, return the {@link + * Throwable} in an {@link Either#left}. + * + * @return {@link Either} the success value or the {@link Throwable} + */ + public final Either toEither() { + return toEither(id()); + } + + /** + * If this is a success, wrap the value in a {@link Either#right} and return it. Otherwise, apply the mapping + * function to the failure {@link Throwable}, re-wrap it in an {@link Either#left}, and return it. + * + * @param the {@link Either} left parameter type + * @param fn the mapping function + * @return {@link Either} the success value or the mapped left value + */ + public final Either toEither(Fn1 fn) { + return match(fn.fmap(Either::left), Either::right); + } + + /** + * {@inheritDoc} + */ + @Override + public Try throwError(Throwable throwable) { + return failure(throwable); + } + + /** + * {@inheritDoc} + */ + @Override + public Try catchError(Fn1>> recoveryFn) { + return match(t -> recoveryFn.apply(t).coerce(), Try::success); + } + + /** + * {@inheritDoc} + */ + @Override + public Try fmap(Fn1 fn) { + return MonadError.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Try flatMap(Fn1>> f) { + return match(Try::failure, a -> f.apply(a).coerce()); + } + + /** + * {@inheritDoc} + */ + @Override + public Try pure(B b) { + return success(b); + } + + /** + * {@inheritDoc} + */ + @Override + public Try zip(Applicative, Try> appFn) { + return MonadError.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip(Lazy, Try>> lazyAppFn) { + return match(f -> lazy(failure(f)), + s -> lazyAppFn.fmap(tryF -> tryF.fmap(f -> f.apply(s)).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public Try discardL(Applicative> appB) { + return MonadError.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Try discardR(Applicative> appB) { + return MonadError.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Try trampolineM(Fn1, Try>> fn) { + return flatMap(trampoline(a -> fn.apply(a).>>coerce().match( + t -> terminate(failure(t)), + aOrB -> aOrB.fmap(Try::success) + ))); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(t -> pure.apply((TravB) failure(t)), + a -> fn.apply(a).fmap(Try::success).fmap(Applicative::coerce).coerce()); + } + + /** + * Static factory method for creating a success value. + * + * @param a the wrapped value + * @param the success parameter type + * @return a success value of a + */ + public static Try success(A a) { + return new Success<>(a); + } + + /** + * Static factory method for creating a failure value. + * + * @param t the {@link Throwable} + * @param the success parameter type + * @return a failure value of t + */ + public static Try failure(Throwable t) { + return new Failure<>(t); + } + + /** + * Execute supplier, returning a success A or a failure of the thrown {@link Throwable}. + * + * @param supplier the supplier + * @param the possible success type + * @return a new {@link Try} around either a successful A result or the thrown {@link Throwable} + */ + public static Try trying(Fn0 supplier) { + try { + return success(supplier.apply()); + } catch (Throwable t) { + return failure(t); + } + } + + /** + * Execute runnable, returning a success {@link Unit} or a failure of the thrown {@link Throwable}. + * + * @param sideEffect the runnable + * @return a new {@link Try} around either a successful {@link Unit} result or the thrown {@link Throwable} + */ + public static Try trying(SideEffect sideEffect) { + return trying(() -> { + IO.io(sideEffect).unsafePerformIO(); + return UNIT; + }); + } + + /** + * Given a {@link Fn0}<{@link AutoCloseable}> aSupplier and an {@link Fn1} + * fn, apply fn to the result of aSupplier, ensuring that the result has its + * {@link AutoCloseable#close() close} method invoked, regardless of the outcome. + *

+ * If the resource creation process throws, the function body throws, or the + * {@link AutoCloseable#close() close method} throws, the result is a failure. If both the function body and the + * {@link AutoCloseable#close() close method} throw, the result is a failure over the function body + * {@link Throwable} with the {@link AutoCloseable#close() close method} {@link Throwable} added as a + * {@link Throwable#addSuppressed(Throwable) suppressed} {@link Throwable}. If only the + * {@link AutoCloseable#close() close method} throws, the result is a failure over that {@link Throwable}. + *

+ * Note that withResources calls can be nested, in which case all of the above specified exception + * handling applies, where closing the previously created resource is considered part of the body of the next + * withResources calls, and {@link Throwable Throwables} are considered suppressed in the same manner. + * Additionally, {@link AutoCloseable#close() close methods} are invoked in the inverse order of resource creation. + *

+ * This is {@link Try}'s equivalent of + * + * try-with-resources, introduced in Java 7. + * + * @param fn0 the resource supplier + * @param fn the function body + * @param the resource type + * @param the function return type + * @return a {@link Try} representing the result of the function's application to the resource + */ + @SuppressWarnings("try") + public static Try withResources( + Fn0 fn0, + Fn1> fn) { + return trying(() -> { + try (A resource = fn0.apply()) { + return fn.apply(resource).fmap(upcast()); + } + }).flatMap(id()); + } + + /** + * Convenience overload of {@link Try#withResources(Fn0, Fn1) withResources} that cascades dependent resource + * creation via nested calls. + * + * @param fn0 the first resource supplier + * @param bFn the dependent resource function + * @param fn the function body + * @param the first resource type + * @param the second resource type + * @param the function return type + * @return a {@link Try} representing the result of the function's application to the dependent resource + */ + public static Try withResources( + Fn0 fn0, + Fn1 bFn, + Fn1> fn) { + return withResources(fn0, a -> withResources(() -> bFn.apply(a), fn::apply)); + } + + /** + * Convenience overload of {@link Try#withResources(Fn0, Fn1, Fn1) withResources} that + * cascades + * two dependent resource creations via nested calls. + * + * @param fn0 the first resource supplier + * @param bFn the second resource function + * @param cFn the final resource function + * @param fn the function body + * @param the first resource type + * @param the second resource type + * @param the final resource type + * @param the function return type + * @return a {@link Try} representing the result of the function's application to the final dependent resource + */ + public static Try withResources( + Fn0 fn0, + Fn1 bFn, + Fn1 cFn, + Fn1> fn) { + return withResources(fn0, bFn, b -> withResources(() -> cFn.apply(b), fn::apply)); + } + + /** + * The canonical {@link Pure} instance for {@link Try}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureTry() { + return Try::success; + } + + private static final class Failure extends Try { + private final Throwable t; + + private Failure(Throwable t) { + this.t = t; + } + + @Override + public A orThrow(Fn1 fn) throws T { + throw fn.apply(t); + } + + @Override + public R match(Fn1 aFn, Fn1 bFn) { + return aFn.apply(t); + } + + @Override + public boolean equals(Object other) { + return other instanceof Failure && Objects.equals(t, ((Failure) other).t); + } + + @Override + public int hashCode() { + return Objects.hash(t); + } + + @Override + public String toString() { + return "Failure{" + + "t=" + t + + '}'; + } + } + + private static final class Success extends Try { + private final A a; + + private Success(A a) { + this.a = a; + } + + @Override + public A orThrow(Fn1 fn) { + return a; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn) { + return bFn.apply(a); + } + + @Override + public boolean equals(Object other) { + return other instanceof Success && Objects.equals(a, ((Success) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Success{" + + "a=" + a + + '}'; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Unit.java b/src/main/java/com/jnape/palatable/lambda/adt/Unit.java new file mode 100644 index 000000000..0373cdac7 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/Unit.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.adt; + +/** + * The empty return type. Unlike {@link Void}, this type is actually inhabited by a singleton instance that can be used, + * rather than having to resort to null. + */ +public final class Unit { + + /** + * The singleton instance. + */ + public static final Unit UNIT = new Unit(); + + private Unit() { + } + + @Override + public String toString() { + return "Unit{}"; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java index 2fe7a03ed..067762f16 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java @@ -1,63 +1,184 @@ package com.jnape.palatable.lambda.adt.choice; import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** - * Canonical ADT representation of {@link CoProduct2} that is also a {@link Functor} and {@link Bifunctor}. Unlike - * {@link Either}, there is no concept of "success" or "failure", so the domain of reasonable function semantics is - * more limited. + * Canonical ADT representation of {@link CoProduct2}. Unlike {@link Either}, there is no concept of "success" or + * "failure", so the domain of reasonable function semantics is more limited. * - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice + * @param the first possible type + * @param the second possible type * @see Either * @see Choice3 */ -public abstract class Choice2 implements CoProduct2, Functor, Bifunctor { +public abstract class Choice2 implements + CoProduct2>, + MonadRec>, + Bifunctor>, + Traversable> { private Choice2() { } + /** + * Specialize this choice's projection to a {@link Tuple2}. + * + * @return a {@link Tuple2} + */ + @Override + public Tuple2, Maybe> project() { + return into(HList::tuple, CoProduct2.super.project()); + } + + /** + * {@inheritDoc} + */ @Override public final Choice3 diverge() { return match(Choice3::a, Choice3::b); } + /** + * {@inheritDoc} + */ + @Override + public Choice2 invert() { + return match(Choice2::b, Choice2::a); + } + + /** + * {@inheritDoc} + */ @Override - public final Choice2 fmap(Function fn) { - return biMapR(fn); + public final Choice2 fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final Choice2 biMapL(Function fn) { - return (Choice2) Bifunctor.super.biMapL(fn); + public final Choice2 biMapL(Fn1 fn) { + return (Choice2) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final Choice2 biMapR(Function fn) { - return (Choice2) Bifunctor.super.biMapR(fn); + public final Choice2 biMapR(Fn1 fn) { + return (Choice2) Bifunctor.super.biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public final Choice2 biMap(Function lFn, - Function rFn) { + public final Choice2 biMap(Fn1 lFn, + Fn1 rFn) { return match(a -> a(lFn.apply(a)), b -> b(rFn.apply(b))); } + /** + * {@inheritDoc} + */ + @Override + public Choice2 pure(C c) { + return b(c); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice2 zip(Applicative, Choice2> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Choice2>> lazyAppFn) { + return match(a -> lazy(a(a)), + b -> lazyAppFn.fmap(choiceF -> choiceF.fmap(f -> f.apply(b)).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice2 discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice2 discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final Choice2 flatMap(Fn1>> f) { + return match(Choice2::a, b -> f.apply(b).coerce()); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice2 trampolineM(Fn1, Choice2>> fn) { + return match(Choice2::a, + trampoline(b -> fn.apply(b).>>coerce() + .match(a -> terminate(a(a)), + bOrC -> bOrC.fmap(Choice2::b)))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(a -> pure.apply(Choice2.a(a).coerce()), + b -> fn.apply(b).>fmap(Choice2::b).fmap(Functor::coerce).coerce()); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice2}. * * @param a the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @return the wrapped value as a Choice2<A, B> + * @param the first possible type + * @param the second possible type + * @return the wrapped value as a {@link Choice2}<A, B> */ public static Choice2 a(A a) { return new _A<>(a); @@ -67,14 +188,24 @@ public static Choice2 a(A a) { * Static factory method for wrapping a value of type B in a {@link Choice2}. * * @param b the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @return the wrapped value as a Choice2<A, B> + * @param the first possible type + * @param the second possible type + * @return the wrapped value as a {@link Choice2}<A, B> */ public static Choice2 b(B b) { return new _B<>(b); } + /** + * The canonical {@link Pure} instance for {@link Choice2}. + * + * @param the first possible type + * @return the {@link Pure} instance + */ + public static Pure> pureChoice() { + return Choice2::b; + } + private static final class _A extends Choice2 { private final A a; @@ -84,7 +215,7 @@ private _A(A a) { } @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return aFn.apply(a); } @@ -116,7 +247,7 @@ private _B(B b) { } @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return bFn.apply(b); } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java index 52119c199..8116344e5 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java @@ -1,68 +1,189 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into3.into3; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** - * Canonical ADT representation of {@link CoProduct3} that is also a {@link Functor} and {@link Bifunctor}. + * Canonical ADT representation of {@link CoProduct3}. * - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice + * @param the first possible type + * @param the second possible type + * @param the third possible type * @see Choice2 * @see Choice4 */ -public abstract class Choice3 implements CoProduct3, Functor, Bifunctor { +public abstract class Choice3 implements + CoProduct3>, + MonadRec>, + Bifunctor>, + Traversable> { private Choice3() { } + /** + * Specialize this choice's projection to a {@link Tuple3}. + * + * @return a {@link Tuple3} + */ + @Override + public Tuple3, Maybe, Maybe> project() { + return into3(HList::tuple, CoProduct3.super.project()); + } + + /** + * {@inheritDoc} + */ @Override public final Choice4 diverge() { return match(Choice4::a, Choice4::b, Choice4::c); } + /** + * {@inheritDoc} + */ @Override - public final Choice2 converge(Function> convergenceFn) { - return match(Choice2::a, Choice2::b, convergenceFn.andThen(cp2 -> cp2.match(Choice2::a, Choice2::b))); + public final Choice2 converge(Fn1> convergenceFn) { + return match(Choice2::a, Choice2::b, convergenceFn.fmap(cp2 -> cp2.match(Choice2::a, Choice2::b))); } + /** + * {@inheritDoc} + */ @Override - public final Choice3 fmap(Function fn) { - return biMapR(fn); + public final Choice3 fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final Choice3 biMapL(Function fn) { - return (Choice3) Bifunctor.super.biMapL(fn); + public final Choice3 biMapL(Fn1 fn) { + return (Choice3) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final Choice3 biMapR(Function fn) { - return (Choice3) Bifunctor.super.biMapR(fn); + public final Choice3 biMapR(Fn1 fn) { + return (Choice3) Bifunctor.super.biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public final Choice3 biMap(Function lFn, - Function rFn) { + public final Choice3 biMap(Fn1 lFn, + Fn1 rFn) { return match(Choice3::a, b -> b(lFn.apply(b)), c -> c(rFn.apply(c))); } + /** + * {@inheritDoc} + */ + @Override + public Choice3 pure(D d) { + return c(d); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice3 zip(Applicative, Choice3> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Choice3>> lazyAppFn) { + return match(a -> lazy(a(a)), + b -> lazy(b(b)), + c -> lazyAppFn.fmap(choiceF -> choiceF.fmap(f -> f.apply(c)).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice3 discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice3 discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice3 flatMap(Fn1>> f) { + return match(Choice3::a, Choice3::b, c -> f.apply(c).coerce()); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice3 trampolineM( + Fn1, Choice3>> fn) { + return flatMap(trampoline(c -> fn.apply(c).>>coerce() + .match(a -> terminate(a(a)), + b -> terminate(b(b)), + r -> r.fmap(Choice3::c)))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(a -> pure.apply(Choice3.a(a).coerce()), + b -> pure.apply(Choice3.b(b).coerce()), + c -> fn.apply(c).>fmap(Choice3::c).fmap(Functor::coerce)) + .coerce(); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice3}. * * @param a the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @return the wrapped value as a Choice3<A, B, C> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @return the wrapped value as a {@link Choice3}<A, B, C> */ public static Choice3 a(A a) { return new _A<>(a); @@ -72,10 +193,10 @@ public static Choice3 a(A a) { * Static factory method for wrapping a value of type A in a {@link Choice3}. * * @param b the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @return the wrapped value as a Choice3<A, B, C> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @return the wrapped value as a {@link Choice3}<A, B, C> */ public static Choice3 b(B b) { return new _B<>(b); @@ -85,15 +206,26 @@ public static Choice3 b(B b) { * Static factory method for wrapping a value of type A in a {@link Choice3}. * * @param c the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @return the wrapped value as a Choice3<A, B, C> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @return the wrapped value as a {@link Choice3}<A, B, C> */ public static Choice3 c(C c) { return new _C<>(c); } + /** + * The canonical {@link Pure} instance for {@link Choice3}. + * + * @param the first possible type + * @param the second possible type + * @return the {@link Pure} instance + */ + public static Pure> pureChoice() { + return Choice3::c; + } + private static final class _A extends Choice3 { private final A a; @@ -103,8 +235,8 @@ private _A(A a) { } @Override - public R match(Function aFn, Function bFn, - Function cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn) { return aFn.apply(a); } @@ -136,8 +268,8 @@ private _B(B b) { } @Override - public R match(Function aFn, Function bFn, - Function cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn) { return bFn.apply(b); } @@ -169,8 +301,8 @@ private _C(C c) { } @Override - public R match(Function aFn, Function bFn, - Function cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn) { return cFn.apply(c); } @@ -192,6 +324,4 @@ public String toString() { '}'; } } - - } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java index d9ef5efd6..7f5afb2e6 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java @@ -1,73 +1,195 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; import com.jnape.palatable.lambda.adt.coproduct.CoProduct4; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple4; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into4.into4; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** - * Canonical ADT representation of {@link CoProduct4} that is also a {@link Functor} and {@link Bifunctor}. + * Canonical ADT representation of {@link CoProduct4}. * - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type * @see Choice3 * @see Choice5 */ -public abstract class Choice4 implements CoProduct4, Functor, Bifunctor { +public abstract class Choice4 implements + CoProduct4>, + MonadRec>, + Bifunctor>, + Traversable> { private Choice4() { } + /** + * Specialize this choice's projection to a {@link Tuple4}. + * + * @return a {@link Tuple4} + */ + @Override + public Tuple4, Maybe, Maybe, Maybe> project() { + return into4(HList::tuple, CoProduct4.super.project()); + } + + /** + * {@inheritDoc} + */ @Override public Choice5 diverge() { return match(Choice5::a, Choice5::b, Choice5::c, Choice5::d); } + /** + * {@inheritDoc} + */ @Override - public Choice3 converge(Function> convergenceFn) { - return match(Choice3::a, - Choice3::b, - Choice3::c, - convergenceFn.andThen(cp3 -> cp3.match(Choice3::a, Choice3::b, Choice3::c))); + public Choice3 converge(Fn1> convergenceFn) { + return match(Choice3::a, Choice3::b, Choice3::c, + convergenceFn.fmap(cp3 -> cp3.match(Choice3::a, Choice3::b, Choice3::c))); } + /** + * {@inheritDoc} + */ @Override - public final Choice4 fmap(Function fn) { - return biMapR(fn); + public final Choice4 fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final Choice4 biMapL(Function fn) { - return (Choice4) Bifunctor.super.biMapL(fn); + public final Choice4 biMapL(Fn1 fn) { + return (Choice4) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final Choice4 biMapR(Function fn) { - return (Choice4) Bifunctor.super.biMapR(fn); + public final Choice4 biMapR(Fn1 fn) { + return (Choice4) Bifunctor.super.biMapR(fn); } @Override - public final Choice4 biMap(Function lFn, - Function rFn) { + public final Choice4 biMap(Fn1 lFn, + Fn1 rFn) { return match(Choice4::a, Choice4::b, c -> c(lFn.apply(c)), d -> d(rFn.apply(d))); } + /** + * {@inheritDoc} + */ + @Override + public Choice4 pure(E e) { + return d(e); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice4 zip(Applicative, Choice4> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Choice4>> lazyAppFn) { + return match(a -> lazy(a(a)), + b -> lazy(b(b)), + c -> lazy(c(c)), + d -> lazyAppFn.fmap(choiceF -> choiceF.fmap(f -> f.apply(d)).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice4 discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice4 discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice4 flatMap(Fn1>> f) { + return match(Choice4::a, Choice4::b, Choice4::c, d -> f.apply(d).coerce()); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice4 trampolineM( + Fn1, Choice4>> fn) { + return match(Choice4::a, + Choice4::b, + Choice4::c, + trampoline(d -> fn.apply(d).>>coerce() + .match(a -> terminate(a(a)), + b -> terminate(b(b)), + c -> terminate(c(c)), + dOrE -> dOrE.fmap(Choice4::d)))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(a -> pure.apply(Choice4.a(a).coerce()), + b -> pure.apply(Choice4.b(b).coerce()), + c -> pure.apply(Choice4.c(c).coerce()), + d -> fn.apply(d).>fmap(Choice4::d).fmap(Functor::coerce)) + .coerce(); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice4}. * * @param a the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice - * @return the wrapped value as a Choice4<A, B, C, D> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @return the wrapped value as a {@link Choice4}<A, B, C, D> */ public static Choice4 a(A a) { return new _A<>(a); @@ -77,11 +199,11 @@ public static Choice4 a(A a) { * Static factory method for wrapping a value of type B in a {@link Choice4}. * * @param b the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice - * @return the wrapped value as a Choice4<A, B, C, D> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @return the wrapped value as a {@link Choice4}<A, B, C, D> */ public static Choice4 b(B b) { return new _B<>(b); @@ -91,11 +213,11 @@ public static Choice4 b(B b) { * Static factory method for wrapping a value of type C in a {@link Choice4}. * * @param c the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice - * @return the wrapped value as a Choice4<A, B, C, D> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @return the wrapped value as a {@link Choice4}<A, B, C, D> */ public static Choice4 c(C c) { return new _C<>(c); @@ -105,16 +227,28 @@ public static Choice4 c(C c) { * Static factory method for wrapping a value of type D in a {@link Choice4}. * * @param d the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice - * @return the wrapped value as a Choice4<A, B, C, D> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @return the wrapped value as a {@link Choice4}<A, B, C, D> */ public static Choice4 d(D d) { return new _D<>(d); } + /** + * The canonical {@link Pure} instance for {@link Choice4}. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @return the {@link Pure} instance + */ + public static Pure> pureChoice() { + return Choice4::d; + } + private static final class _A extends Choice4 { private final A a; @@ -124,8 +258,8 @@ private _A(A a) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn) { return aFn.apply(a); } @@ -157,8 +291,8 @@ private _B(B b) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn) { return bFn.apply(b); } @@ -190,8 +324,8 @@ private _C(C c) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn) { return cFn.apply(c); } @@ -223,8 +357,8 @@ private _D(D d) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn) { return dFn.apply(d); } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java index 5603a4fe6..4a085c94a 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java @@ -1,71 +1,200 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct4; import com.jnape.palatable.lambda.adt.coproduct.CoProduct5; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple5; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into5.into5; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** - * Canonical ADT representation of {@link CoProduct5} that is also a {@link Functor} and {@link Bifunctor}. + * Canonical ADT representation of {@link CoProduct5}. * - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice - * @param a type parameter representing the fifth possible type of this choice + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type * @see Choice4 + * @see Choice6 */ -public abstract class Choice5 implements CoProduct5, Functor, Bifunctor { +public abstract class Choice5 implements + CoProduct5>, + MonadRec>, + Bifunctor>, + Traversable> { private Choice5() { } + /** + * Specialize this choice's projection to a {@link Tuple5}. + * + * @return a {@link Tuple5} + */ @Override - public Choice4 converge(Function> convergenceFn) { - return match(Choice4::a, - Choice4::b, - Choice4::c, - Choice4::d, - convergenceFn.andThen(cp4 -> cp4.match(Choice4::a, Choice4::b, Choice4::c, Choice4::d))); + public Tuple5, Maybe, Maybe, Maybe, Maybe> project() { + return into5(HList::tuple, CoProduct5.super.project()); } + /** + * {@inheritDoc} + */ @Override - public Choice5 fmap(Function fn) { - return biMapR(fn); + public Choice6 diverge() { + return match(Choice6::a, Choice6::b, Choice6::c, Choice6::d, Choice6::e); } + /** + * {@inheritDoc} + */ + @Override + public Choice4 converge(Fn1> convergenceFn) { + return match(Choice4::a, Choice4::b, Choice4::c, Choice4::d, + convergenceFn.fmap(cp4 -> cp4.match(Choice4::a, Choice4::b, Choice4::c, Choice4::d))); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice5 fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Choice5 biMapL(Function fn) { - return (Choice5) Bifunctor.super.biMapL(fn); + public Choice5 biMapL(Fn1 fn) { + return (Choice5) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Choice5 biMapR(Function fn) { - return (Choice5) Bifunctor.super.biMapR(fn); + public Choice5 biMapR(Fn1 fn) { + return (Choice5) Bifunctor.super.biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Choice5 biMap(Function lFn, - Function rFn) { + public Choice5 biMap(Fn1 lFn, + Fn1 rFn) { return match(Choice5::a, Choice5::b, Choice5::c, d -> d(lFn.apply(d)), e -> e(rFn.apply(e))); } + /** + * {@inheritDoc} + */ + @Override + public Choice5 pure(F f) { + return e(f); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice5 zip(Applicative, Choice5> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Choice5>> lazyAppFn) { + return match(a -> lazy(a(a)), + b -> lazy(b(b)), + c -> lazy(c(c)), + d -> lazy(d(d)), + e -> lazyAppFn.fmap(choiceF -> choiceF.fmap(f -> f.apply(e)).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice5 discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice5 discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice5 flatMap(Fn1>> f) { + return match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, e -> f.apply(e).coerce()); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice5 trampolineM( + Fn1, Choice5>> fn) { + return flatMap(trampoline(e -> fn.apply(e).>>coerce().match( + a -> terminate(Choice5.a(a)), + b -> terminate(Choice5.b(b)), + c -> terminate(Choice5.c(c)), + d -> terminate(Choice5.d(d)), + eRec -> eRec.fmap(Choice5::e)))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(a -> pure.apply(Choice5.a(a).coerce()), + b -> pure.apply(Choice5.b(b).coerce()), + c -> pure.apply(Choice5.c(c).coerce()), + d -> pure.apply(Choice5.d(d).coerce()), + e -> fn.apply(e).>fmap(Choice5::e) + .fmap(Applicative::coerce)) + .coerce(); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice5}. * * @param a the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice - * @param a type parameter representing the fifth possible type of this choice - * @return the wrapped value as a Choice5<A, B, C, D, E> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @return the wrapped value as a {@link Choice5}<A, B, C, D, E> */ public static Choice5 a(A a) { return new _A<>(a); @@ -75,12 +204,12 @@ public static Choice5 a(A a) { * Static factory method for wrapping a value of type B in a {@link Choice5}. * * @param b the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice - * @param a type parameter representing the fifth possible type of this choice - * @return the wrapped value as a Choice5<A, B, C, D, E> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @return the wrapped value as a {@link Choice5}<A, B, C, D, E> */ public static Choice5 b(B b) { return new _B<>(b); @@ -90,12 +219,12 @@ public static Choice5 b(B b) { * Static factory method for wrapping a value of type C in a {@link Choice5}. * * @param c the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice - * @param a type parameter representing the fifth possible type of this choice - * @return the wrapped value as a Choice5<A, B, C, D, E> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @return the wrapped value as a {@link Choice5}<A, B, C, D, E> */ public static Choice5 c(C c) { return new _C<>(c); @@ -105,12 +234,12 @@ public static Choice5 c(C c) { * Static factory method for wrapping a value of type D in a {@link Choice5}. * * @param d the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice - * @param a type parameter representing the fifth possible type of this choice - * @return the wrapped value as a Choice5<A, B, C, D, E> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @return the wrapped value as a {@link Choice5}<A, B, C, D, E> */ public static Choice5 d(D d) { return new _D<>(d); @@ -120,17 +249,30 @@ public static Choice5 d(D d) { * Static factory method for wrapping a value of type E in a {@link Choice5}. * * @param e the value - * @param a type parameter representing the first possible type of this choice - * @param a type parameter representing the second possible type of this choice - * @param a type parameter representing the third possible type of this choice - * @param a type parameter representing the fourth possible type of this choice - * @param a type parameter representing the fifth possible type of this choice - * @return the wrapped value as a Choice5<A, B, C, D, E> + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @return the wrapped value as a {@link Choice5}<A, B, C, D, E> */ public static Choice5 e(E e) { return new _E<>(e); } + /** + * The canonical {@link Pure} instance for {@link Choice5}. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @return the {@link Pure} instance + */ + public static Pure> pureChoice() { + return Choice5::e; + } + private static final class _A extends Choice5 { private final A a; @@ -140,9 +282,9 @@ private _A(A a) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return aFn.apply(a); } @@ -174,9 +316,9 @@ private _B(B b) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return bFn.apply(b); } @@ -208,9 +350,9 @@ private _C(C c) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return cFn.apply(c); } @@ -242,9 +384,9 @@ private _D(D d) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return dFn.apply(d); } @@ -276,9 +418,9 @@ private _E(E e) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return eFn.apply(e); } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice6.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice6.java new file mode 100644 index 000000000..ec0eacfc5 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice6.java @@ -0,0 +1,494 @@ +package com.jnape.palatable.lambda.adt.choice; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct5; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct6; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple6; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into6.into6; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + +/** + * Canonical ADT representation of {@link CoProduct6}. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @see Choice5 + * @see Choice7 + */ +public abstract class Choice6 implements + CoProduct6>, + MonadRec>, + Bifunctor>, + Traversable> { + + private Choice6() { + } + + /** + * Specialize this choice's projection to a {@link Tuple6}. + * + * @return a {@link Tuple6} + */ + @Override + public Tuple6, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + return into6(HList::tuple, CoProduct6.super.project()); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 diverge() { + return match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, Choice7::f); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice5 converge(Fn1> convergenceFn) { + return match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, Choice5::e, + convergenceFn.fmap(cp5 -> cp5.match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, Choice5::e))); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 biMapL(Fn1 fn) { + return (Choice6) Bifunctor.super.biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 biMapR(Fn1 fn) { + return (Choice6) Bifunctor.super.biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 biMap(Fn1 lFn, + Fn1 rFn) { + return match(Choice6::a, Choice6::b, Choice6::c, Choice6::d, e -> e(lFn.apply(e)), f -> f(rFn.apply(f))); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 pure(G g) { + return f(g); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 zip( + Applicative, Choice6> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Choice6>> lazyAppFn) { + return match(a -> lazy(a(a)), + b -> lazy(b(b)), + c -> lazy(c(c)), + d -> lazy(d(d)), + e -> lazy(e(e)), + f -> lazyAppFn.fmap(choiceFn -> choiceFn.fmap(fn -> fn.apply(f)).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 flatMap(Fn1>> fn) { + return match(Choice6::a, Choice6::b, Choice6::c, Choice6::d, Choice6::e, f -> fn.apply(f).coerce()); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 trampolineM( + Fn1, Choice6>> fn) { + return flatMap(trampoline(f -> fn.apply(f).>>coerce().match( + a -> terminate(Choice6.a(a)), + b -> terminate(Choice6.b(b)), + c -> terminate(Choice6.c(c)), + d -> terminate(Choice6.d(d)), + e -> terminate(Choice6.e(e)), + fRec -> fRec.fmap(Choice6::f)))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(a -> pure.apply(Choice6.a(a).coerce()), + b -> pure.apply(Choice6.b(b).coerce()), + c -> pure.apply(Choice6.c(c).coerce()), + d -> pure.apply(Choice6.d(d).coerce()), + e -> pure.apply(Choice6.e(e).coerce()), + f -> fn.apply(f).>fmap(Choice6::f) + .fmap(Applicative::coerce)) + .coerce(); + } + + /** + * Static factory method for wrapping a value of type A in a {@link Choice6}. + * + * @param a the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @return the wrapped value as a {@link Choice6}<A, B, C, D, E, F> + */ + public static Choice6 a(A a) { + return new _A<>(a); + } + + /** + * Static factory method for wrapping a value of type B in a {@link Choice6}. + * + * @param b the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @return the wrapped value as a {@link Choice6}<A, B, C, D, E, F> + */ + public static Choice6 b(B b) { + return new _B<>(b); + } + + /** + * Static factory method for wrapping a value of type C in a {@link Choice6}. + * + * @param c the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @return the wrapped value as a {@link Choice6}<A, B, C, D, E, F> + */ + public static Choice6 c(C c) { + return new _C<>(c); + } + + /** + * Static factory method for wrapping a value of type D in a {@link Choice6}. + * + * @param d the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @return the wrapped value as a {@link Choice6}<A, B, C, D, E, F> + */ + public static Choice6 d(D d) { + return new _D<>(d); + } + + /** + * Static factory method for wrapping a value of type E in a {@link Choice6}. + * + * @param e the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @return the wrapped value as a {@link Choice6}<A, B, C, D, E, F> + */ + public static Choice6 e(E e) { + return new _E<>(e); + } + + /** + * Static factory method for wrapping a value of type F in a {@link Choice6}. + * + * @param f the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @return the wrapped value as a {@link Choice6}<A, B, C, D, E, F> + */ + public static Choice6 f(F f) { + return new _F<>(f); + } + + /** + * The canonical {@link Pure} instance for {@link Choice6}. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @return the {@link Pure} instance + */ + public static Pure> pureChoice() { + return Choice6::f; + } + + private static final class _A extends Choice6 { + + private final A a; + + private _A(A a) { + this.a = a; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return aFn.apply(a); + } + + @Override + public boolean equals(Object other) { + return other instanceof _A + && Objects.equals(a, ((_A) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Choice6{a=" + a + '}'; + } + } + + private static final class _B extends Choice6 { + + private final B b; + + private _B(B b) { + this.b = b; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return bFn.apply(b); + } + + @Override + public boolean equals(Object other) { + return other instanceof _B + && Objects.equals(b, ((_B) other).b); + } + + @Override + public int hashCode() { + return Objects.hash(b); + } + + @Override + public String toString() { + return "Choice6{b=" + b + '}'; + } + } + + private static final class _C extends Choice6 { + + private final C c; + + private _C(C c) { + this.c = c; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return cFn.apply(c); + } + + @Override + public boolean equals(Object other) { + return other instanceof _C + && Objects.equals(c, ((_C) other).c); + } + + @Override + public int hashCode() { + return Objects.hash(c); + } + + @Override + public String toString() { + return "Choice6{c=" + c + '}'; + } + } + + private static final class _D extends Choice6 { + + private final D d; + + private _D(D d) { + this.d = d; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return dFn.apply(d); + } + + @Override + public boolean equals(Object other) { + return other instanceof _D + && Objects.equals(d, ((_D) other).d); + } + + @Override + public int hashCode() { + return Objects.hash(d); + } + + @Override + public String toString() { + return "Choice6{d=" + d + '}'; + } + } + + private static final class _E extends Choice6 { + + private final E e; + + private _E(E e) { + this.e = e; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return eFn.apply(e); + } + + @Override + public boolean equals(Object other) { + return other instanceof _E + && Objects.equals(e, ((_E) other).e); + } + + @Override + public int hashCode() { + return Objects.hash(e); + } + + @Override + public String toString() { + return "Choice6{e=" + e + '}'; + } + } + + private static final class _F extends Choice6 { + + private final F f; + + private _F(F f) { + this.f = f; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return fFn.apply(f); + } + + @Override + public boolean equals(Object other) { + return other instanceof _F + && Objects.equals(f, ((_F) other).f); + } + + @Override + public int hashCode() { + return Objects.hash(f); + } + + @Override + public String toString() { + return "Choice6{f=" + f + '}'; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice7.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice7.java new file mode 100644 index 000000000..bf0131e84 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice7.java @@ -0,0 +1,563 @@ +package com.jnape.palatable.lambda.adt.choice; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct6; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct7; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple7; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into7.into7; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + +/** + * Canonical ADT representation of {@link CoProduct7}. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @see Choice6 + * @see Choice8 + */ +public abstract class Choice7 implements + CoProduct7>, + MonadRec>, + Bifunctor>, + Traversable> { + + private Choice7() { + } + + /** + * Specialize this choice's projection to a {@link Tuple7}. + * + * @return a {@link Tuple7} + */ + @Override + public Tuple7, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + return into7(HList::tuple, CoProduct7.super.project()); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 diverge() { + return match(Choice8::a, Choice8::b, Choice8::c, Choice8::d, Choice8::e, Choice8::f, Choice8::g); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice6 converge(Fn1> convergenceFn) { + return match(Choice6::a, Choice6::b, Choice6::c, Choice6::d, Choice6::e, Choice6::f, + convergenceFn.fmap(cp6 -> cp6.match(Choice6::a, Choice6::b, Choice6::c, Choice6::d, Choice6::e, + Choice6::f))); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 biMapL(Fn1 fn) { + return (Choice7) Bifunctor.super.biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 biMapR(Fn1 fn) { + return (Choice7) Bifunctor.super.biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 biMap(Fn1 lFn, + Fn1 rFn) { + return match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, f -> f(lFn.apply(f)), g -> g(rFn.apply(g))); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 pure(H h) { + return g(h); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 zip( + Applicative, Choice7> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Choice7>> lazyAppFn) { + return match(a -> lazy(a(a)), + b -> lazy(b(b)), + c -> lazy(c(c)), + d -> lazy(d(d)), + e -> lazy(e(e)), + f -> lazy(f(f)), + g -> lazyAppFn.fmap(choiceF -> choiceF.fmap(f -> f.apply(g)).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 flatMap( + Fn1>> fn) { + return match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, Choice7::f, g -> fn.apply(g).coerce()); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 trampolineM( + Fn1, Choice7>> fn) { + return flatMap(trampoline(g -> fn.apply(g).>>coerce().match( + a -> terminate(a(a)), + b -> terminate(b(b)), + c -> terminate(c(c)), + d -> terminate(d(d)), + e -> terminate(e(e)), + f -> terminate(f(f)), + gRec -> gRec.fmap(Choice7::g)))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(a -> pure.apply(Choice7.a(a).coerce()), + b -> pure.apply(Choice7.b(b).coerce()), + c -> pure.apply(Choice7.c(c).coerce()), + d -> pure.apply(Choice7.d(d).coerce()), + e -> pure.apply(Choice7.e(e).coerce()), + f -> pure.apply(Choice7.f(f).coerce()), + g -> fn.apply(g).>fmap(Choice7::g) + .fmap(Applicative::coerce)) + .coerce(); + } + + /** + * Static factory method for wrapping a value of type A in a {@link Choice7}. + * + * @param a the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @return the wrapped value as a {@link Choice7}<A, B, C, D, E, F, G> + */ + public static Choice7 a(A a) { + return new _A<>(a); + } + + /** + * Static factory method for wrapping a value of type B in a {@link Choice7}. + * + * @param b the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @return the wrapped value as a {@link Choice7}<A, B, C, D, E, F, G> + */ + public static Choice7 b(B b) { + return new _B<>(b); + } + + /** + * Static factory method for wrapping a value of type C in a {@link Choice7}. + * + * @param c the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @return the wrapped value as a {@link Choice7}<A, B, C, D, E, F, G> + */ + public static Choice7 c(C c) { + return new _C<>(c); + } + + /** + * Static factory method for wrapping a value of type D in a {@link Choice7}. + * + * @param d the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @return the wrapped value as a {@link Choice7}<A, B, C, D, E, F, G> + */ + public static Choice7 d(D d) { + return new _D<>(d); + } + + /** + * Static factory method for wrapping a value of type E in a {@link Choice7}. + * + * @param e the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @return the wrapped value as a {@link Choice7}<A, B, C, D, E, F, G> + */ + public static Choice7 e(E e) { + return new _E<>(e); + } + + /** + * Static factory method for wrapping a value of type F in a {@link Choice7}. + * + * @param f the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @return the wrapped value as a {@link Choice7}<A, B, C, D, E, F, G> + */ + public static Choice7 f(F f) { + return new _F<>(f); + } + + /** + * Static factory method for wrapping a value of type G in a {@link Choice7}. + * + * @param g the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @return the wrapped value as a {@link Choice7}<A, B, C, D, E, F, G> + */ + public static Choice7 g(G g) { + return new _G<>(g); + } + + /** + * The canonical {@link Pure} instance for {@link Choice7}. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @return the {@link Pure} instance + */ + public static Pure> pureChoice() { + return Choice7::g; + } + + private static final class _A extends Choice7 { + + private final A a; + + private _A(A a) { + this.a = a; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return aFn.apply(a); + } + + @Override + public boolean equals(Object other) { + return other instanceof _A + && Objects.equals(a, ((_A) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Choice7{a=" + a + '}'; + } + } + + private static final class _B extends Choice7 { + + private final B b; + + private _B(B b) { + this.b = b; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return bFn.apply(b); + } + + @Override + public boolean equals(Object other) { + return other instanceof _B + && Objects.equals(b, ((_B) other).b); + } + + @Override + public int hashCode() { + return Objects.hash(b); + } + + @Override + public String toString() { + return "Choice7{b=" + b + '}'; + } + } + + private static final class _C extends Choice7 { + + private final C c; + + private _C(C c) { + this.c = c; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return cFn.apply(c); + } + + @Override + public boolean equals(Object other) { + return other instanceof _C + && Objects.equals(c, ((_C) other).c); + } + + @Override + public int hashCode() { + return Objects.hash(c); + } + + @Override + public String toString() { + return "Choice7{c=" + c + '}'; + } + } + + private static final class _D extends Choice7 { + + private final D d; + + private _D(D d) { + this.d = d; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return dFn.apply(d); + } + + @Override + public boolean equals(Object other) { + return other instanceof _D + && Objects.equals(d, ((_D) other).d); + } + + @Override + public int hashCode() { + return Objects.hash(d); + } + + @Override + public String toString() { + return "Choice7{d=" + d + '}'; + } + } + + private static final class _E extends Choice7 { + + private final E e; + + private _E(E e) { + this.e = e; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return eFn.apply(e); + } + + @Override + public boolean equals(Object other) { + return other instanceof _E + && Objects.equals(e, ((_E) other).e); + } + + @Override + public int hashCode() { + return Objects.hash(e); + } + + @Override + public String toString() { + return "Choice7{e=" + e + '}'; + } + } + + private static final class _F extends Choice7 { + + private final F f; + + private _F(F f) { + this.f = f; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return fFn.apply(f); + } + + @Override + public boolean equals(Object other) { + return other instanceof _F + && Objects.equals(f, ((_F) other).f); + } + + @Override + public int hashCode() { + return Objects.hash(f); + } + + @Override + public String toString() { + return "Choice7{f=" + f + '}'; + } + } + + private static final class _G extends Choice7 { + + private final G g; + + private _G(G g) { + this.g = g; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return gFn.apply(g); + } + + @Override + public boolean equals(Object other) { + return other instanceof _G + && Objects.equals(g, ((_G) other).g); + } + + @Override + public int hashCode() { + return Objects.hash(g); + } + + @Override + public String toString() { + return "Choice7{g=" + g + '}'; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice8.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice8.java new file mode 100644 index 000000000..edd220a7d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice8.java @@ -0,0 +1,618 @@ +package com.jnape.palatable.lambda.adt.choice; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct7; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct8; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple8; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into8.into8; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + +/** + * Canonical ADT representation of {@link CoProduct8}. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @see Choice7 + */ +public abstract class Choice8 implements + CoProduct8>, + MonadRec>, + Bifunctor>, + Traversable> { + + private Choice8() { + } + + /** + * Specialize this choice's projection to a {@link Tuple8}. + * + * @return a {@link Tuple8} + */ + @Override + public Tuple8, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + return into8(HList::tuple, CoProduct8.super.project()); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice7 converge( + Fn1> convergenceFn) { + return match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, Choice7::f, Choice7::g, + convergenceFn.fmap(cp7 -> cp7.match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, + Choice7::f, Choice7::g))); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 biMapL(Fn1 fn) { + return (Choice8) Bifunctor.super.biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 biMapR(Fn1 fn) { + return (Choice8) Bifunctor.super.biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 biMap(Fn1 lFn, + Fn1 rFn) { + return match(Choice8::a, Choice8::b, Choice8::c, Choice8::d, Choice8::e, Choice8::f, g -> g(lFn.apply(g)), h -> h(rFn.apply(h))); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 pure(I i) { + return h(i); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 zip( + Applicative, Choice8> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Choice8>> lazyAppFn) { + return match(a -> lazy(a(a)), + b -> lazy(b(b)), + c -> lazy(c(c)), + d -> lazy(d(d)), + e -> lazy(e(e)), + f -> lazy(f(f)), + g -> lazy(g(g)), + h -> lazyAppFn.fmap(choiceF -> choiceF.fmap(f -> f.apply(h)).coerce())); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 flatMap( + Fn1>> fn) { + return match(Choice8::a, Choice8::b, Choice8::c, Choice8::d, Choice8::e, Choice8::f, Choice8::g, h -> fn.apply(h).coerce()); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice8 trampolineM( + Fn1, Choice8>> fn) { + return flatMap(trampoline(h -> fn.apply(h).>>coerce().match( + a -> terminate(a(a)), + b -> terminate(b(b)), + c -> terminate(c(c)), + d -> terminate(d(d)), + e -> terminate(e(e)), + f -> terminate(f(f)), + g -> terminate(g(g)), + hRec -> hRec.fmap(Choice8::h)))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(a -> pure.apply(Choice8.a(a).coerce()), + b -> pure.apply(Choice8.b(b).coerce()), + c -> pure.apply(Choice8.c(c).coerce()), + d -> pure.apply(Choice8.d(d).coerce()), + e -> pure.apply(Choice8.e(e).coerce()), + f -> pure.apply(Choice8.f(f).coerce()), + g -> pure.apply(Choice8.g(g).coerce()), + h -> fn.apply(h).>fmap(Choice8::h) + .fmap(Applicative::coerce)) + .coerce(); + } + + /** + * Static factory method for wrapping a value of type A in a {@link Choice8}. + * + * @param a the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @return the wrapped value as a {@link Choice8}<A, B, C, D, E, F, G, H> + */ + public static Choice8 a(A a) { + return new _A<>(a); + } + + /** + * Static factory method for wrapping a value of type B in a {@link Choice8}. + * + * @param b the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @return the wrapped value as a {@link Choice8}<A, B, C, D, E, F, G, H> + */ + public static Choice8 b(B b) { + return new _B<>(b); + } + + /** + * Static factory method for wrapping a value of type C in a {@link Choice8}. + * + * @param c the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @return the wrapped value as a {@link Choice8}<A, B, C, D, E, F, G, H> + */ + public static Choice8 c(C c) { + return new _C<>(c); + } + + /** + * Static factory method for wrapping a value of type D in a {@link Choice8}. + * + * @param d the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @return the wrapped value as a {@link Choice8}<A, B, C, D, E, F, G, H> + */ + public static Choice8 d(D d) { + return new _D<>(d); + } + + /** + * Static factory method for wrapping a value of type E in a {@link Choice8}. + * + * @param e the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @return the wrapped value as a {@link Choice8}<A, B, C, D, E, F, G, H> + */ + public static Choice8 e(E e) { + return new _E<>(e); + } + + /** + * Static factory method for wrapping a value of type F in a {@link Choice8}. + * + * @param f the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @return the wrapped value as a {@link Choice8}<A, B, C, D, E, F, G, H> + */ + public static Choice8 f(F f) { + return new _F<>(f); + } + + /** + * Static factory method for wrapping a value of type G in a {@link Choice8}. + * + * @param g the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @return the wrapped value as a {@link Choice8}<A, B, C, D, E, F, G, H> + */ + public static Choice8 g(G g) { + return new _G<>(g); + } + + /** + * Static factory method for wrapping a value of type H in a {@link Choice8}. + * + * @param h the value + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @return the wrapped value as a {@link Choice8}<A, B, C, D, E, F, G, H> + */ + public static Choice8 h(H h) { + return new _H<>(h); + } + + /** + * The canonical {@link Pure} instance for {@link Choice8}. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @return the {@link Pure} instance + */ + public static Pure> pureChoice() { + return Choice8::h; + } + + private static final class _A extends Choice8 { + + private final A a; + + private _A(A a) { + this.a = a; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return aFn.apply(a); + } + + @Override + public boolean equals(Object other) { + return other instanceof _A + && Objects.equals(a, ((_A) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Choice8{a=" + a + '}'; + } + } + + private static final class _B extends Choice8 { + + private final B b; + + private _B(B b) { + this.b = b; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return bFn.apply(b); + } + + @Override + public boolean equals(Object other) { + return other instanceof _B + && Objects.equals(b, ((_B) other).b); + } + + @Override + public int hashCode() { + return Objects.hash(b); + } + + @Override + public String toString() { + return "Choice8{b=" + b + '}'; + } + } + + private static final class _C extends Choice8 { + + private final C c; + + private _C(C c) { + this.c = c; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return cFn.apply(c); + } + + @Override + public boolean equals(Object other) { + return other instanceof _C + && Objects.equals(c, ((_C) other).c); + } + + @Override + public int hashCode() { + return Objects.hash(c); + } + + @Override + public String toString() { + return "Choice8{c=" + c + '}'; + } + } + + private static final class _D extends Choice8 { + + private final D d; + + private _D(D d) { + this.d = d; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return dFn.apply(d); + } + + @Override + public boolean equals(Object other) { + return other instanceof _D + && Objects.equals(d, ((_D) other).d); + } + + @Override + public int hashCode() { + return Objects.hash(d); + } + + @Override + public String toString() { + return "Choice8{d=" + d + '}'; + } + } + + private static final class _E extends Choice8 { + + private final E e; + + private _E(E e) { + this.e = e; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return eFn.apply(e); + } + + @Override + public boolean equals(Object other) { + return other instanceof _E + && Objects.equals(e, ((_E) other).e); + } + + @Override + public int hashCode() { + return Objects.hash(e); + } + + @Override + public String toString() { + return "Choice8{e=" + e + '}'; + } + } + + private static final class _F extends Choice8 { + + private final F f; + + private _F(F f) { + this.f = f; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return fFn.apply(f); + } + + @Override + public boolean equals(Object other) { + return other instanceof _F + && Objects.equals(f, ((_F) other).f); + } + + @Override + public int hashCode() { + return Objects.hash(f); + } + + @Override + public String toString() { + return "Choice8{f=" + f + '}'; + } + } + + private static final class _G extends Choice8 { + + private final G g; + + private _G(G g) { + this.g = g; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return gFn.apply(g); + } + + @Override + public boolean equals(Object other) { + return other instanceof _G + && Objects.equals(g, ((_G) other).g); + } + + @Override + public int hashCode() { + return Objects.hash(g); + } + + @Override + public String toString() { + return "Choice8{g=" + g + '}'; + } + } + + private static final class _H extends Choice8 { + + private final H h; + + private _H(H h) { + this.h = h; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return hFn.apply(h); + } + + @Override + public boolean equals(Object other) { + return other instanceof _H + && Objects.equals(h, ((_H) other).h); + } + + @Override + public int hashCode() { + return Objects.hash(h); + } + + @Override + public String toString() { + return "Choice8{h=" + h + '}'; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java index 77f347412..ecb08fece 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java @@ -1,38 +1,41 @@ package com.jnape.palatable.lambda.adt.coproduct; import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.choice.Choice2; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; - -import java.util.Optional; -import java.util.function.Function; +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functions.Fn1; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** - * A generalization of the coproduct of two types A and B. Coproducts represent the disjoint - * union of two or more distinct types, and provides an interface for specifying morphisms from those types to a common - * result type. + * A generalization of the coproduct of two types. Coproducts represent the disjoint union of two or more distinct + * types, and provides an interface for specifying morphisms from those types to a common result type. *

* Learn more about Coproducts. * - * @param a type parameter representing the first possible type of this coproduct - * @param a type parameter representing the second possible type of this coproduct + * @param the first possible type + * @param the second possible type + * @param the recursive type of this coproduct (used for embedding) * @see Choice2 * @see Either */ @FunctionalInterface -public interface CoProduct2 { +public interface CoProduct2> { /** * Type-safe convergence requiring a match against all potential types. * + * @param result type * @param aFn morphism A -> R * @param bFn morphism B -> R - * @param result type - * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R + * @return the result of applying the appropriate morphism to this coproduct's unwrapped value */ - R match(Function aFn, Function bFn); + R match(Fn1 aFn, Fn1 bFn); /** * Diverge this coproduct by introducing another possible type that it could represent. As no morphisms can be @@ -53,44 +56,75 @@ public interface CoProduct2 { * single magnitude difference. * * @param the additional possible type of this coproduct - * @return a coproduct of the initial types plus the new type + * @return a {@link CoProduct3}<A, B, C> */ - default CoProduct3 diverge() { - return new CoProduct3() { + default CoProduct3> diverge() { + return new CoProduct3>() { @Override - public R match(Function aFn, Function bFn, - Function cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn) { return CoProduct2.this.match(aFn, bFn); } }; } /** - * Project this coproduct onto a tuple, such that the slot in the tuple that corresponds to this coproduct's value - * is present, while the other slots are absent. + * Project this coproduct onto a product, such that the index in the product that corresponds to this coproduct's + * value is present, while the other indices are absent. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection */ - default Tuple2, Optional> project() { - return match(a -> tuple(Optional.of(a), Optional.empty()), - b -> tuple(Optional.empty(), Optional.of(b))); + default Product2, Maybe> project() { + return match(a -> tuple(just(a), nothing()), + b -> tuple(nothing(), just(b))); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ - default Optional projectA() { + default Maybe projectA() { return project()._1(); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ - default Optional projectB() { + default Maybe projectB() { return project()._2(); } + + /** + * Swap the type parameters. + * + * @return The inverted coproduct + */ + default CoProduct2> invert() { + return new CoProduct2>() { + @Override + public R match(Fn1 aFn, Fn1 bFn) { + return CoProduct2.this.match(bFn, aFn); + } + }; + } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct2#match}, but without unwrapping the + * value. + * + * @param result type + * @param aFn morphism A v B -> R, applied in the A case + * @param bFn morphism A v B -> R, applied in the B case + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Fn1 aFn, Fn1 bFn) { + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn))) + .apply((CP2) this); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java index 1bc4312d7..aee747317 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java @@ -1,48 +1,52 @@ package com.jnape.palatable.lambda.adt.coproduct; -import com.jnape.palatable.lambda.adt.hlist.Tuple3; - -import java.util.Optional; -import java.util.function.Function; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.adt.product.Product3; +import com.jnape.palatable.lambda.functions.Fn1; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** - * A generalization of the coproduct of three types A, B, and C. + * A generalization of the coproduct of three types. * - * @param a type parameter representing the first possible type of this coproduct - * @param a type parameter representing the second possible type of this coproduct - * @param a type parameter representing the third possible type of this coproduct + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the recursive type of this coproduct (used for embedding) * @see CoProduct2 */ @FunctionalInterface -public interface CoProduct3 { +public interface CoProduct3> { /** * Type-safe convergence requiring a match against all potential types. * + * @param result type * @param aFn morphism A -> R * @param bFn morphism B -> R * @param cFn morphism C -> R - * @param result type - * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R - * @see CoProduct2#match(Function, Function) + * @return the result of applying the appropriate morphism to this coproduct's unwrapped value + * @see CoProduct2#match(Fn1, Fn1) */ - R match(Function aFn, Function bFn, - Function cFn); + R match(Fn1 aFn, Fn1 bFn, Fn1 cFn); /** * Diverge this coproduct by introducing another possible type that it could represent. * * @param the additional possible type of this coproduct - * @return a Coproduct4<A, B, C, D> + * @return a {@link CoProduct4}<A, B, C, D> * @see CoProduct2#diverge() */ - default CoProduct4 diverge() { - return new CoProduct4() { + default CoProduct4> diverge() { + return new CoProduct4>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn) { return CoProduct3.this.match(aFn, bFn, cFn); } }; @@ -59,58 +63,69 @@ public R match(Function aFn, Function * * @param convergenceFn function from last possible type to earlier type - * @return a coproduct of the initial types without the terminal type + * @return a {@link CoProduct2}<A, B> */ - default CoProduct2 converge(Function> convergenceFn) { - return match(a -> new CoProduct2() { - @Override - public R match(Function aFn, Function bFn) { - return aFn.apply(a); - } - }, b -> new CoProduct2() { - @Override - public R match(Function aFn, Function bFn) { - return bFn.apply(b); - } - }, convergenceFn); + default CoProduct2> converge( + Fn1> convergenceFn) { + return match(Choice2::a, Choice2::b, convergenceFn::apply); } /** - * Project this coproduct onto a tuple. + * Project this coproduct onto a product. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection * @see CoProduct2#project() */ - default Tuple3, Optional, Optional> project() { - return match(a -> tuple(Optional.of(a), Optional.empty(), Optional.empty()), - b -> tuple(Optional.empty(), Optional.of(b), Optional.empty()), - c -> tuple(Optional.empty(), Optional.empty(), Optional.of(c))); + default Product3, Maybe, Maybe> project() { + return match(a -> tuple(just(a), nothing(), nothing()), + b -> tuple(nothing(), just(b), nothing()), + c -> tuple(nothing(), nothing(), just(c))); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ - default Optional projectA() { + default Maybe projectA() { return project()._1(); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ - default Optional projectB() { + default Maybe projectB() { return project()._2(); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. * * @return an optional value representing the projection of the "c" type index */ - default Optional projectC() { + default Maybe projectC() { return project()._3(); } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct3#match}, but without unwrapping the + * value. + * + * @param result type + * @param aFn morphism A v B v C -> R, applied in the A case + * @param bFn morphism A v B v C -> R, applied in the B case + * @param cFn morphism A v B v C -> R, applied in the C case + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Fn1 aFn, Fn1 bFn, + Fn1 cFn) { + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn)), + constantly(fn1(cFn))) + .apply((CP3) this); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java index 739252a54..4deea96eb 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java @@ -1,39 +1,44 @@ package com.jnape.palatable.lambda.adt.coproduct; -import com.jnape.palatable.lambda.adt.hlist.Tuple4; - -import java.util.Optional; -import java.util.function.Function; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice3; +import com.jnape.palatable.lambda.adt.product.Product4; +import com.jnape.palatable.lambda.functions.Fn1; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** - * A generalization of the coproduct of four types A, B, C, and D. + * A generalization of the coproduct of four types. * - * @param a type parameter representing the first possible type of this coproduct - * @param a type parameter representing the second possible type of this coproduct - * @param a type parameter representing the third possible type of this coproduct - * @param a type parameter representing the fourth possible type of this coproduct + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the recursive type of this coproduct (used for embedding) * @see CoProduct2 */ @FunctionalInterface -public interface CoProduct4 { +public interface CoProduct4> { /** * Type-safe convergence requiring a match against all potential types. * + * @param result type * @param aFn morphism A -> R * @param bFn morphism B -> R * @param cFn morphism C -> R * @param dFn morphism D -> R - * @param result type * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R - * @see CoProduct2#match(Function, Function) + * @see CoProduct2#match(Fn1, Fn1) */ - R match(Function aFn, - Function bFn, - Function cFn, - Function dFn); + R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn); /** * Diverge this coproduct by introducing another possible type that it could represent. @@ -42,12 +47,12 @@ R match(Function aFn, * @return a Coproduct5<A, B, C, D, E> * @see CoProduct2#diverge() */ - default CoProduct5 diverge() { - return new CoProduct5() { + default CoProduct5> diverge() { + return new CoProduct5>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return CoProduct4.this.match(aFn, bFn, cFn, dFn); } }; @@ -58,77 +63,85 @@ public R match(Function aFn, Function converge(Function> convergenceFn) { - return match(a -> new CoProduct3() { - @Override - public R match(Function aFn, Function bFn, - Function cFn) { - return aFn.apply(a); - } - }, b -> new CoProduct3() { - @Override - public R match(Function aFn, Function bFn, - Function cFn) { - return bFn.apply(b); - } - }, c -> new CoProduct3() { - @Override - public R match(Function aFn, Function bFn, - Function cFn) { - return cFn.apply(c); - } - }, convergenceFn); + default CoProduct3> converge( + Fn1> convergenceFn) { + return match(Choice3::a, Choice3::b, Choice3::c, convergenceFn::apply); } /** - * Project this coproduct onto a tuple. + * Project this coproduct onto a product. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection * @see CoProduct2#project() */ - default Tuple4, Optional, Optional, Optional> project() { - return match(a -> tuple(Optional.of(a), Optional.empty(), Optional.empty(), Optional.empty()), - b -> tuple(Optional.empty(), Optional.of(b), Optional.empty(), Optional.empty()), - c -> tuple(Optional.empty(), Optional.empty(), Optional.of(c), Optional.empty()), - d -> tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(d))); + default Product4, Maybe, Maybe, Maybe> project() { + return match(a -> tuple(just(a), nothing(), nothing(), nothing()), + b -> tuple(nothing(), just(b), nothing(), nothing()), + c -> tuple(nothing(), nothing(), just(c), nothing()), + d -> tuple(nothing(), nothing(), nothing(), just(d))); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ - default Optional projectA() { + default Maybe projectA() { return project()._1(); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ - default Optional projectB() { + default Maybe projectB() { return project()._2(); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. * * @return an optional value representing the projection of the "c" type index */ - default Optional projectC() { + default Maybe projectC() { return project()._3(); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fourth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fourth slot value. * * @return an optional value representing the projection of the "d" type index */ - default Optional projectD() { + default Maybe projectD() { return project()._4(); } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct4#match}, but without unwrapping the + * value. + * + * @param result type + * @param aFn morphism A v B v C v D -> R, applied in the A case + * @param bFn morphism A v B v C v D -> R, applied in the B case + * @param cFn morphism A v B v C v D -> R, applied in the C case + * @param dFn morphism A v B v C v D -> R, applied in the D case + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn) { + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn)), + constantly(fn1(cFn)), + constantly(fn1(dFn))) + .apply((CP4) this); + } + } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java index 9379a3d7c..03b594ca2 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java @@ -1,135 +1,161 @@ package com.jnape.palatable.lambda.adt.coproduct; -import com.jnape.palatable.lambda.adt.hlist.Tuple5; - -import java.util.Optional; -import java.util.function.Function; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice4; +import com.jnape.palatable.lambda.adt.product.Product5; +import com.jnape.palatable.lambda.functions.Fn1; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** - * A generalization of the coproduct of five types A, B, C, D, and - * E. + * A generalization of the coproduct of five types. * - * @param a type parameter representing the first possible type of this coproduct - * @param a type parameter representing the second possible type of this coproduct - * @param a type parameter representing the third possible type of this coproduct - * @param a type parameter representing the fourth possible type of this coproduct - * @param a type parameter representing the fifth possible type of this coproduct + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the recursive type of this coproduct (used for embedding) * @see CoProduct2 */ @FunctionalInterface -public interface CoProduct5 { +public interface CoProduct5> { /** * Type-safe convergence requiring a match against all potential types. * + * @param result type * @param aFn morphism A -> R * @param bFn morphism B -> R * @param cFn morphism C -> R * @param dFn morphism D -> R * @param eFn morphism E -> R - * @param result type * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R - * @see CoProduct2#match(Function, Function) + * @see CoProduct2#match(Fn1, Fn1) + */ + R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn, + Fn1 eFn); + + /** + * Diverge this coproduct by introducing another possible type that it could represent. + * + * @param the additional possible type of this coproduct + * @return a Coproduct6<A, B, C, D, E, F> + * @see CoProduct2#diverge() */ - R match(Function aFn, - Function bFn, - Function cFn, - Function dFn, - Function eFn); + default CoProduct6> diverge() { + return new CoProduct6>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return CoProduct5.this.match(aFn, bFn, cFn, dFn, eFn); + } + }; + } /** * Converge this coproduct down to a lower order coproduct by mapping the last possible type into an earlier * possible type. * * @param convergenceFn morphism E -> {@link CoProduct4}<A, B, C, D> - * @return a CoProduct4<A, B, C, D> + * @return a {@link CoProduct4}<A, B, C, D> */ - default CoProduct4 converge(Function> convergenceFn) { - return match(a -> new CoProduct4() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { - return aFn.apply(a); - } - }, b -> new CoProduct4() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { - return bFn.apply(b); - } - }, c -> new CoProduct4() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { - return cFn.apply(c); - } - }, d -> new CoProduct4() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { - return dFn.apply(d); - } - }, convergenceFn::apply); + default CoProduct4> converge( + Fn1> convergenceFn) { + return match(Choice4::a, Choice4::b, Choice4::c, Choice4::d, convergenceFn::apply); } /** - * Project this coproduct onto a tuple. + * Project this coproduct onto a product. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection * @see CoProduct2#project() */ - default Tuple5, Optional, Optional, Optional, Optional> project() { - return match(a -> tuple(Optional.of(a), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()), - b -> tuple(Optional.empty(), Optional.of(b), Optional.empty(), Optional.empty(), Optional.empty()), - c -> tuple(Optional.empty(), Optional.empty(), Optional.of(c), Optional.empty(), Optional.empty()), - d -> tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(d), Optional.empty()), - e -> tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(e))); + default Product5, Maybe, Maybe, Maybe, Maybe> project() { + return match(a -> tuple(just(a), nothing(), nothing(), nothing(), nothing()), + b -> tuple(nothing(), just(b), nothing(), nothing(), nothing()), + c -> tuple(nothing(), nothing(), just(c), nothing(), nothing()), + d -> tuple(nothing(), nothing(), nothing(), just(d), nothing()), + e -> tuple(nothing(), nothing(), nothing(), nothing(), just(e))); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ - default Optional projectA() { + default Maybe projectA() { return project()._1(); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ - default Optional projectB() { + default Maybe projectB() { return project()._2(); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. * * @return an optional value representing the projection of the "c" type index */ - default Optional projectC() { + default Maybe projectC() { return project()._3(); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fourth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fourth slot value. * * @return an optional value representing the projection of the "d" type index */ - default Optional projectD() { + default Maybe projectD() { return project()._4(); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fifth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fifth slot value. * * @return an optional value representing the projection of the "e" type index */ - default Optional projectE() { + default Maybe projectE() { return project()._5(); } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct5#match}, but without unwrapping the + * value. + * + * @param result type + * @param aFn morphism A v B v C v D v E -> R, applied in the A case + * @param bFn morphism A v B v C v D v E -> R, applied in the B case + * @param cFn morphism A v B v C v D v E -> R, applied in the C case + * @param dFn morphism A v B v C v D v E -> R, applied in the D case + * @param eFn morphism A v B v C v D v E -> R, applied in the E case + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn, + Fn1 eFn) { + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn)), + constantly(fn1(cFn)), + constantly(fn1(dFn)), + constantly(fn1(eFn))) + .apply((CP5) this); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct6.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct6.java new file mode 100644 index 000000000..d8d8d2301 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct6.java @@ -0,0 +1,178 @@ +package com.jnape.palatable.lambda.adt.coproduct; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice5; +import com.jnape.palatable.lambda.adt.product.Product6; +import com.jnape.palatable.lambda.functions.Fn1; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * A generalization of the coproduct of six types. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the recursive type of this coproduct (used for embedding) + * @see CoProduct2 + */ +@FunctionalInterface +public interface CoProduct6> { + + /** + * Type-safe convergence requiring a match against all potential types. + * + * @param result type + * @param aFn morphism A -> R + * @param bFn morphism B -> R + * @param cFn morphism C -> R + * @param dFn morphism D -> R + * @param eFn morphism E -> R + * @param fFn morphism F -> R + * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R + * @see CoProduct2#match(Fn1, Fn1) + */ + R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn, + Fn1 eFn, + Fn1 fFn); + + /** + * Diverge this coproduct by introducing another possible type that it could represent. + * + * @param the additional possible type of this coproduct + * @return a Coproduct7<A, B, C, D, E, F, G> + * @see CoProduct2#diverge() + */ + default CoProduct7> diverge() { + return new CoProduct7>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return CoProduct6.this.match(aFn, bFn, cFn, dFn, eFn, fFn); + } + }; + } + + /** + * Converge this coproduct down to a lower order coproduct by mapping the last possible type into an earlier + * possible type. + * + * @param convergenceFn morphism F -> {@link CoProduct5}<A, B, C, D, E> + * @return a {@link CoProduct5}<A, B, C, D, E> + */ + default CoProduct5> converge( + Fn1> convergenceFn) { + return match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, Choice5::e, convergenceFn::apply); + } + + /** + * Project this coproduct onto a product. + * + * @return a product of the coproduct projection + * @see CoProduct2#project() + */ + default Product6, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + return match(a -> tuple(just(a), nothing(), nothing(), nothing(), nothing(), nothing()), + b -> tuple(nothing(), just(b), nothing(), nothing(), nothing(), nothing()), + c -> tuple(nothing(), nothing(), just(c), nothing(), nothing(), nothing()), + d -> tuple(nothing(), nothing(), nothing(), just(d), nothing(), nothing()), + e -> tuple(nothing(), nothing(), nothing(), nothing(), just(e), nothing()), + f -> tuple(nothing(), nothing(), nothing(), nothing(), nothing(), just(f))); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. + * + * @return an optional value representing the projection of the "a" type index + */ + default Maybe projectA() { + return project()._1(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. + * + * @return an optional value representing the projection of the "b" type index + */ + default Maybe projectB() { + return project()._2(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. + * + * @return an optional value representing the projection of the "c" type index + */ + default Maybe projectC() { + return project()._3(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the fourth slot value. + * + * @return an optional value representing the projection of the "d" type index + */ + default Maybe projectD() { + return project()._4(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the fifth slot value. + * + * @return an optional value representing the projection of the "e" type index + */ + default Maybe projectE() { + return project()._5(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the sixth slot value. + * + * @return an optional value representing the projection of the "f" type index + */ + default Maybe projectF() { + return project()._6(); + } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct6#match}, but without unwrapping the + * value. + * + * @param result type + * @param aFn morphism A v B v C v D v E v F -> R, applied in the A case + * @param bFn morphism A v B v C v D v E v F -> R, applied in the B case + * @param cFn morphism A v B v C v D v E v F -> R, applied in the C case + * @param dFn morphism A v B v C v D v E v F -> R, applied in the D case + * @param eFn morphism A v B v C v D v E v F -> R, applied in the E case + * @param fFn morphism A v B v C v D v E v F -> R, applied in the F case + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn, + Fn1 eFn, + Fn1 fFn) { + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn)), + constantly(fn1(cFn)), + constantly(fn1(dFn)), + constantly(fn1(eFn)), + constantly(fn1(fFn))) + .apply((CP6) this); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct7.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct7.java new file mode 100644 index 000000000..0f7647f96 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct7.java @@ -0,0 +1,194 @@ +package com.jnape.palatable.lambda.adt.coproduct; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice6; +import com.jnape.palatable.lambda.adt.product.Product7; +import com.jnape.palatable.lambda.functions.Fn1; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * A generalization of the coproduct of seven types. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the recursive type of this coproduct (used for embedding) + * @see CoProduct2 + */ +@FunctionalInterface +public interface CoProduct7> { + + /** + * Type-safe convergence requiring a match against all potential types. + * + * @param result type + * @param aFn morphism A -> R + * @param bFn morphism B -> R + * @param cFn morphism C -> R + * @param dFn morphism D -> R + * @param eFn morphism E -> R + * @param fFn morphism F -> R + * @param gFn morphism G -> R + * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R + * @see CoProduct2#match(Fn1, Fn1) + */ + R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn, + Fn1 eFn, + Fn1 fFn, + Fn1 gFn); + + /** + * Diverge this coproduct by introducing another possible type that it could represent. + * + * @param the additional possible type of this coproduct + * @return a Coproduct8<A, B, C, D, E, F, G, H> + * @see CoProduct2#diverge() + */ + default CoProduct8> diverge() { + return new CoProduct8>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return CoProduct7.this.match(aFn, bFn, cFn, dFn, eFn, fFn, gFn); + } + }; + } + + /** + * Converge this coproduct down to a lower order coproduct by mapping the last possible type into an earlier + * possible type. + * + * @param convergenceFn morphism G -> {@link CoProduct6}<A, B, C, D, E, F> + * @return a {@link CoProduct6}<A, B, C, D, E, F> + */ + default CoProduct6> converge( + Fn1> convergenceFn) { + return match(Choice6::a, Choice6::b, Choice6::c, Choice6::d, Choice6::e, Choice6::f, convergenceFn::apply); + } + + /** + * Project this coproduct onto a product. + * + * @return a product of the coproduct projection + * @see CoProduct2#project() + */ + default Product7, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + return match(a -> tuple(just(a), nothing(), nothing(), nothing(), nothing(), nothing(), nothing()), + b -> tuple(nothing(), just(b), nothing(), nothing(), nothing(), nothing(), nothing()), + c -> tuple(nothing(), nothing(), just(c), nothing(), nothing(), nothing(), nothing()), + d -> tuple(nothing(), nothing(), nothing(), just(d), nothing(), nothing(), nothing()), + e -> tuple(nothing(), nothing(), nothing(), nothing(), just(e), nothing(), nothing()), + f -> tuple(nothing(), nothing(), nothing(), nothing(), nothing(), just(f), nothing()), + g -> tuple(nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), just(g))); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. + * + * @return an optional value representing the projection of the "a" type index + */ + default Maybe projectA() { + return project()._1(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. + * + * @return an optional value representing the projection of the "b" type index + */ + default Maybe projectB() { + return project()._2(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. + * + * @return an optional value representing the projection of the "c" type index + */ + default Maybe projectC() { + return project()._3(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the fourth slot value. + * + * @return an optional value representing the projection of the "d" type index + */ + default Maybe projectD() { + return project()._4(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the fifth slot value. + * + * @return an optional value representing the projection of the "e" type index + */ + default Maybe projectE() { + return project()._5(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the sixth slot value. + * + * @return an optional value representing the projection of the "f" type index + */ + default Maybe projectF() { + return project()._6(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the seventh slot value. + * + * @return an optional value representing the projection of the "g" type index + */ + default Maybe projectG() { + return project()._7(); + } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct7#match}, but without unwrapping the + * value. + * + * @param result type + * @param aFn morphism A v B v C v D v E v F v G -> R, applied in the A case + * @param bFn morphism A v B v C v D v E v F v G -> R, applied in the B case + * @param cFn morphism A v B v C v D v E v F v G -> R, applied in the C case + * @param dFn morphism A v B v C v D v E v F v G -> R, applied in the D case + * @param eFn morphism A v B v C v D v E v F v G -> R, applied in the E case + * @param fFn morphism A v B v C v D v E v F v G -> R, applied in the F case + * @param gFn morphism A v B v C v D v E v F v G -> R, applied in the G case + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn, + Fn1 eFn, + Fn1 fFn, + Fn1 gFn) { + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn)), + constantly(fn1(cFn)), + constantly(fn1(dFn)), + constantly(fn1(eFn)), + constantly(fn1(fFn)), + constantly(fn1(gFn))) + .apply((CP7) this); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct8.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct8.java new file mode 100644 index 000000000..9653e178a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct8.java @@ -0,0 +1,192 @@ +package com.jnape.palatable.lambda.adt.coproduct; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice7; +import com.jnape.palatable.lambda.adt.product.Product8; +import com.jnape.palatable.lambda.functions.Fn1; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * A generalization of the coproduct of eight types. + * + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @param the recursive type of this coproduct (used for embedding) + * @see CoProduct2 + */ +@FunctionalInterface +public interface CoProduct8> { + + /** + * Type-safe convergence requiring a match against all potential types. + * + * @param result type + * @param aFn morphism A -> R + * @param bFn morphism B -> R + * @param cFn morphism C -> R + * @param dFn morphism D -> R + * @param eFn morphism E -> R + * @param fFn morphism F -> R + * @param gFn morphism G -> R + * @param hFn morphism H -> R + * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R + * @see CoProduct2#match(Fn1, Fn1) + */ + R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn, + Fn1 eFn, + Fn1 fFn, + Fn1 gFn, + Fn1 hFn); + + /** + * Converge this coproduct down to a lower order coproduct by mapping the last possible type into an earlier + * possible type. + * + * @param convergenceFn morphism G -> {@link CoProduct6}<A, B, C, D, E, F, G> + * @return a {@link CoProduct7}<A, B, C, D, E, F, G> + */ + default CoProduct7> converge( + Fn1> convergenceFn) { + return match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, Choice7::f, Choice7::g, + convergenceFn::apply); + } + + /** + * Project this coproduct onto a product. + * + * @return a product of the coproduct projection + * @see CoProduct2#project() + */ + default Product8, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + return match(a -> tuple(just(a), nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), nothing()), + b -> tuple(nothing(), just(b), nothing(), nothing(), nothing(), nothing(), nothing(), nothing()), + c -> tuple(nothing(), nothing(), just(c), nothing(), nothing(), nothing(), nothing(), nothing()), + d -> tuple(nothing(), nothing(), nothing(), just(d), nothing(), nothing(), nothing(), nothing()), + e -> tuple(nothing(), nothing(), nothing(), nothing(), just(e), nothing(), nothing(), nothing()), + f -> tuple(nothing(), nothing(), nothing(), nothing(), nothing(), just(f), nothing(), nothing()), + g -> tuple(nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), just(g), nothing()), + h -> tuple(nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), just(h))); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. + * + * @return an optional value representing the projection of the "a" type index + */ + default Maybe projectA() { + return project()._1(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. + * + * @return an optional value representing the projection of the "b" type index + */ + default Maybe projectB() { + return project()._2(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. + * + * @return an optional value representing the projection of the "c" type index + */ + default Maybe projectC() { + return project()._3(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the fourth slot value. + * + * @return an optional value representing the projection of the "d" type index + */ + default Maybe projectD() { + return project()._4(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the fifth slot value. + * + * @return an optional value representing the projection of the "e" type index + */ + default Maybe projectE() { + return project()._5(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the sixth slot value. + * + * @return an optional value representing the projection of the "f" type index + */ + default Maybe projectF() { + return project()._6(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the seventh slot value. + * + * @return an optional value representing the projection of the "g" type index + */ + default Maybe projectG() { + return project()._7(); + } + + /** + * Convenience method for projecting this coproduct onto a product and then extracting the eighth slot value. + * + * @return an optional value representing the projection of the "h" type index + */ + default Maybe projectH() { + return project()._8(); + } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct8#match}, but without unwrapping the + * value. + * + * @param result type + * @param aFn morphism A v B v C v D v E v F v G v H -> R, applied in the A case + * @param bFn morphism A v B v C v D v E v F v G v H -> R, applied in the B case + * @param cFn morphism A v B v C v D v E v F v G v H -> R, applied in the C case + * @param dFn morphism A v B v C v D v E v F v G v H -> R, applied in the D case + * @param eFn morphism A v B v C v D v E v F v G v H -> R, applied in the E case + * @param fFn morphism A v B v C v D v E v F v G v H -> R, applied in the F case + * @param gFn morphism A v B v C v D v E v F v G v H -> R, applied in the G case + * @param hFn morphism A v B v C v D v E v F v G v H -> R, applied in the H case + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn, + Fn1 eFn, + Fn1 fFn, + Fn1 gFn, + Fn1 hFn) { + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn)), + constantly(fn1(cFn)), + constantly(fn1(dFn)), + constantly(fn1(eFn)), + constantly(fn1(fFn)), + constantly(fn1(gFn)), + constantly(fn1(hFn))) + .apply((CP8) this); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java index c59f50847..705e43bad 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java @@ -1,5 +1,7 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast; + import java.util.Objects; /** @@ -7,15 +9,14 @@ * that due to its rapidly expanding type signature, specializations exist up to certain depths to minimize typing * overhead. * - * @param The head element type - * @param The encoded recursive tail HList type * @see SingletonHList * @see Tuple2 * @see Tuple3 * @see Tuple4 * @see Tuple5 + * @see Tuple6 */ -public abstract class HList> { +public abstract class HList { private HList() { } @@ -27,7 +28,7 @@ private HList() { * @param the new head type * @return the updated HList */ - public abstract HCons> cons(NewHead newHead); + public abstract HCons cons(NewHead newHead); @Override public final String toString() { @@ -35,7 +36,7 @@ public final String toString() { HList next = this; while (next != HNil.INSTANCE) { - HCons hCons = (HCons) next; + HCons hCons = (HCons) next; body.append(" ").append(hCons.head).append(" "); next = hCons.tail; if (next != HNil.INSTANCE) @@ -63,8 +64,8 @@ public static HNil nil() { * @param the tail type * @return the newly created HList */ - public static > HCons cons(Head head, Tail tail) { - return new HCons<>(head, tail); + public static HCons cons(Head head, Tail tail) { + return Downcast., HCons>downcast(tail.cons(head)); } /** @@ -88,7 +89,6 @@ public static SingletonHList singletonHList(Head head) { * @return the 2-element HList * @see Tuple2 */ - @SuppressWarnings("JavaDoc") public static <_1, _2> Tuple2<_1, _2> tuple(_1 _1, _2 _2) { return singletonHList(_2).cons(_1); } @@ -105,7 +105,6 @@ public static <_1, _2> Tuple2<_1, _2> tuple(_1 _1, _2 _2) { * @return the 3-element HList * @see Tuple3 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3> Tuple3<_1, _2, _3> tuple(_1 _1, _2 _2, _3 _3) { return tuple(_2, _3).cons(_1); } @@ -124,7 +123,6 @@ public static <_1, _2, _3> Tuple3<_1, _2, _3> tuple(_1 _1, _2 _2, _3 _3) { * @return the 4-element HList * @see Tuple4 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4> Tuple4<_1, _2, _3, _4> tuple(_1 _1, _2 _2, _3 _3, _4 _4) { return tuple(_2, _3, _4).cons(_1); } @@ -145,18 +143,93 @@ public static <_1, _2, _3, _4> Tuple4<_1, _2, _3, _4> tuple(_1 _1, _2 _2, _3 _3, * @return the 5-element HList * @see Tuple5 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4, _5> Tuple5<_1, _2, _3, _4, _5> tuple(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5) { return tuple(_2, _3, _4, _5).cons(_1); } + /** + * Static factory method for creating a 6-element HList. + * + * @param _1 the head element + * @param _2 the second element + * @param _3 the third element + * @param _4 the fourth element + * @param _5 the fifth element + * @param _6 the sixth element + * @param <_1> the head element type + * @param <_2> the second element type + * @param <_3> the third element type + * @param <_4> the fourth element type + * @param <_5> the fifth element type + * @param <_6> the sixth element type + * @return the 6-element HList + * @see Tuple6 + */ + public static <_1, _2, _3, _4, _5, _6> Tuple6<_1, _2, _3, _4, _5, _6> tuple(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, + _6 _6) { + return tuple(_2, _3, _4, _5, _6).cons(_1); + } + + /** + * Static factory method for creating a 7-element HList. + * + * @param _1 the head element + * @param _2 the second element + * @param _3 the third element + * @param _4 the fourth element + * @param _5 the fifth element + * @param _6 the sixth element + * @param _7 the seventh element + * @param <_1> the head element type + * @param <_2> the second element type + * @param <_3> the third element type + * @param <_4> the fourth element type + * @param <_5> the fifth element type + * @param <_6> the sixth element type + * @param <_7> the seventh element type + * @return the 7-element HList + * @see Tuple7 + */ + public static <_1, _2, _3, _4, _5, _6, _7> Tuple7<_1, _2, _3, _4, _5, _6, _7> tuple(_1 _1, _2 _2, _3 _3, _4 _4, + _5 _5, _6 _6, _7 _7) { + return tuple(_2, _3, _4, _5, _6, _7).cons(_1); + } + + /** + * Static factory method for creating an 8-element HList. + * + * @param _1 the head element + * @param _2 the second element + * @param _3 the third element + * @param _4 the fourth element + * @param _5 the fifth element + * @param _6 the sixth element + * @param _7 the seventh element + * @param _8 the eighth element + * @param <_1> the head element type + * @param <_2> the second element type + * @param <_3> the third element type + * @param <_4> the fourth element type + * @param <_5> the fifth element type + * @param <_6> the sixth element type + * @param <_7> the seventh element type + * @param <_8> the eighth element type + * @return the 8-element HList + * @see Tuple8 + */ + public static <_1, _2, _3, _4, _5, _6, _7, _8> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> tuple(_1 _1, _2 _2, _3 _3, + _4 _4, _5 _5, _6 _6, + _7 _7, _8 _8) { + return tuple(_2, _3, _4, _5, _6, _7, _8).cons(_1); + } + /** * The consing of a head element to a tail HList. * * @param the head element type * @param the HList tail type */ - public static class HCons> extends HList { + public static class HCons extends HList { private final Head head; private final Tail tail; @@ -191,7 +264,7 @@ public Tail tail() { @Override public final boolean equals(Object other) { if (other instanceof HCons) { - HCons that = (HCons) other; + HCons that = (HCons) other; return this.head.equals(that.head) && this.tail.equals(that.tail); } @@ -207,7 +280,7 @@ public final int hashCode() { /** * The empty HList. */ - public static final class HNil extends HList { + public static final class HNil extends HList { private static final HNil INSTANCE = new HNil(); private HNil() { diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Index.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Index.java index 12f1eeebd..6a59384c6 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Index.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Index.java @@ -55,7 +55,7 @@ private Index() { private static final class Z extends Index> { - private static final Z INSTANCE = new Z(); + private static final Z INSTANCE = new Z<>(); @Override public Target get(HCons hList) { diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java index 6e62e1a66..8e8516a47 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java @@ -2,9 +2,16 @@ import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.hlist.HList.HNil; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; -import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A singleton HList. Supports random access. @@ -16,19 +23,128 @@ * @see Tuple4 * @see Tuple5 */ -public class SingletonHList<_1> extends HCons<_1, HNil> implements Functor<_1> { +public class SingletonHList<_1> extends HCons<_1, HNil> implements + MonadRec<_1, SingletonHList>, + Traversable<_1, SingletonHList> { SingletonHList(_1 _1) { super(_1, nil()); } + /** + * {@inheritDoc} + */ @Override public <_0> Tuple2<_0, _1> cons(_0 _0) { return new Tuple2<>(_0, this); } + + /** + * Snoc an element onto the back of this {@link SingletonHList}. + * + * @param _2 the new last element + * @param <_2> the new last element type + * @return the new {@link Tuple2} + */ + public <_2> Tuple2<_1, _2> snoc(_2 _2) { + return tuple(head(), _2); + } + + /** + * {@inheritDoc} + */ + @Override + public <_1Prime> SingletonHList<_1Prime> fmap(Fn1 fn) { + return MonadRec.super.<_1Prime>fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_1Prime> SingletonHList<_1Prime> pure(_1Prime _1Prime) { + return singletonHList(_1Prime); + } + + /** + * {@inheritDoc} + */ + @Override + public <_1Prime> SingletonHList<_1Prime> zip( + Applicative, SingletonHList> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ @Override - public <_1Prime> SingletonHList<_1Prime> fmap(Function fn) { - return new SingletonHList<>(fn.apply(head())); + public <_1Prime> Lazy> lazyZip( + Lazy, SingletonHList>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_1Prime, SingletonHList>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public <_1Prime> SingletonHList<_1Prime> discardL(Applicative<_1Prime, SingletonHList> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_1Prime> SingletonHList<_1> discardR(Applicative<_1Prime, SingletonHList> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_1Prime> SingletonHList<_1Prime> flatMap(Fn1>> f) { + return f.apply(head()).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_1Prime> SingletonHList<_1Prime> trampolineM( + Fn1, SingletonHList>> fn) { + return fmap(trampoline(head -> fn.apply(head).>>coerce().head())); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return fn.apply(head()).fmap(SingletonHList::new).fmap(Applicative::coerce).coerce(); + } + + /** + * Apply {@link SingletonHList#head()} to fn and return the result. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the head to the function + */ + public R into(Fn1 fn) { + return fn.apply(head()); + } + + /** + * The canonical {@link Pure} instance for {@link SingletonHList}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureSingletonHList() { + return HList::singletonHList; } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java index 95fc2b305..aafa40b5e 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java @@ -1,25 +1,46 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Head; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.MonadWriter; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A 2-element tuple product type, implemented as a specialized HList. Supports random access. * * @param <_1> The first slot element type * @param <_2> The second slot element type + * @see Product2 * @see HList * @see SingletonHList * @see Tuple3 * @see Tuple4 * @see Tuple5 */ -public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements Map.Entry<_1, _2>, Functor<_2>, Bifunctor<_1, _2> { +public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements + Product2<_1, _2>, + MonadRec<_2, Tuple2<_1, ?>>, + MonadWriter<_1, _2, Tuple2<_1, ?>>, + Bifunctor<_1, _2, Tuple2>, + Traversable<_2, Tuple2<_1, ?>> { private final _1 _1; private final _2 _2; @@ -27,82 +48,204 @@ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements Map Tuple2(_1 _1, SingletonHList<_2> tail) { super(_1, tail); this._1 = _1; - _2 = tail.head(); + _2 = tail.head(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_3> Tuple2<_1, Tuple2<_2, _3>> listens(Fn1 fn) { + return fmap(both(id(), constantly(fn.apply(_1)))); } + /** + * {@inheritDoc} + */ + @Override + public Tuple2<_1, _2> censor(Fn1 fn) { + return biMapL(fn); + } + + /** + * {@inheritDoc} + */ @Override public <_0> Tuple3<_0, _1, _2> cons(_0 _0) { return new Tuple3<>(_0, this); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple2}. * - * @return the head element + * @param _3 the new last element + * @param <_3> the new last element type + * @return the new {@link Tuple3} + */ + public <_3> Tuple3<_1, _2, _3> snoc(_3 _3) { + return tuple(_1, _2, _3); + } + + /** + * {@inheritDoc} */ + @Override public _1 _1() { return _1; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Destructure and apply this tuple to a function accepting the same number of arguments as this tuple's - * slots. This can be thought of as a kind of dual to uncurrying a function and applying a tuple to it. - * - * @param fn the function to apply - * @param the return type of the function - * @return the result of applying the destructured tuple to the function + * {@inheritDoc} */ - public R into(BiFunction fn) { - return fn.apply(_1, _2); - } - @Override public _1 getKey() { return _1(); } + /** + * {@inheritDoc} + */ @Override public _2 getValue() { return _2(); } + /** + * {@inheritDoc} + */ @Override public _2 setValue(_2 value) { throw new UnsupportedOperationException(); } + /** + * {@inheritDoc} + */ + @Override + public Tuple2<_2, _1> invert() { + return tuple(_2, _1); + } + + /** + * {@inheritDoc} + */ @Override - public <_2Prime> Tuple2<_1, _2Prime> fmap(Function fn) { - return tuple(_1(), fn.apply(_2())); + public <_2Prime> Tuple2<_1, _2Prime> fmap(Fn1 fn) { + return MonadRec.super.<_2Prime>fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_1Prime> Tuple2<_1Prime, _2> biMapL(Function fn) { - return (Tuple2<_1Prime, _2>) Bifunctor.super.biMapL(fn); + public <_1Prime> Tuple2<_1Prime, _2> biMapL(Fn1 fn) { + return (Tuple2<_1Prime, _2>) Bifunctor.super.<_1Prime>biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_2Prime> Tuple2<_1, _2Prime> biMapR(Function fn) { - return (Tuple2<_1, _2Prime>) Bifunctor.super.biMapR(fn); + public <_2Prime> Tuple2<_1, _2Prime> biMapR(Fn1 fn) { + return (Tuple2<_1, _2Prime>) Bifunctor.super.<_2Prime>biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public <_1Prime, _2Prime> Tuple2<_1Prime, _2Prime> biMap(Function lFn, - Function rFn) { + public <_1Prime, _2Prime> Tuple2<_1Prime, _2Prime> biMap(Fn1 lFn, + Fn1 rFn) { return new Tuple2<>(lFn.apply(_1()), tail().fmap(rFn)); } + /** + * {@inheritDoc} + */ + @Override + public <_2Prime> Tuple2<_1, _2Prime> pure(_2Prime _2Prime) { + return tuple(_1, _2Prime); + } + + /** + * {@inheritDoc} + */ + @Override + public <_2Prime> Tuple2<_1, _2Prime> zip( + Applicative, Tuple2<_1, ?>> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_2Prime> Lazy> lazyZip( + Lazy, Tuple2<_1, ?>>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_2Prime, Tuple2<_1, ?>>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public <_2Prime> Tuple2<_1, _2Prime> discardL(Applicative<_2Prime, Tuple2<_1, ?>> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_2Prime> Tuple2<_1, _2> discardR(Applicative<_2Prime, Tuple2<_1, ?>> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_2Prime> Tuple2<_1, _2Prime> flatMap(Fn1>> f) { + return pure(f.apply(_2).>coerce()._2()); + } + + /** + * {@inheritDoc} + */ + @Override + public <_2Prime> Tuple2<_1, _2Prime> trampolineM( + Fn1, Tuple2<_1, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce()._2())); + } + + /** + * {@inheritDoc} + */ + @Override + public <_2Prime, App extends Applicative, TravB extends Traversable<_2Prime, Tuple2<_1, ?>>, + AppTrav extends Applicative> AppTrav traverse( + Fn1> fn, + Fn1 pure) { + return fn.apply(_2).fmap(_2Prime -> fmap(constantly(_2Prime))).fmap(Applicative::coerce).coerce(); + } + + /** + * Returns a {@link SingletonHList}<_1> of the first element. + * + * @return The {@link SingletonHList}<_1> + */ + public SingletonHList<_1> init() { + return invert().tail(); + } + /** * Static factory method for creating Tuple2s from {@link java.util.Map.Entry}s. * @@ -116,7 +259,7 @@ public static Tuple2 fromEntry(Map.Entry entry) { } /** - * Given a value of type A, produced an instance of this tuple with each slot set to that value. + * Given a value of type A, produce an instance of this tuple with each slot set to that value. * * @param a the value to fill the tuple with * @param the value type @@ -125,4 +268,32 @@ public static Tuple2 fromEntry(Map.Entry entry) { public static Tuple2 fill(A a) { return tuple(a, a); } + + /** + * Return {@link Maybe#just(Object) just} the first two elements from the given {@link Iterable}, or + * {@link Maybe#nothing() nothing} if there are less than two elements. + * + * @param as the {@link Iterable} + * @param the {@link Iterable} element type + * @return {@link Maybe} the first two elements of the given {@link Iterable} + */ + public static Maybe> fromIterable(Iterable as) { + return uncons(as).flatMap(tail -> tail.traverse(Head::head, Maybe::just)); + } + + /** + * The canonical {@link Pure} instance for {@link Tuple2}. + * + * @param _1 the head element + * @param <_1> the head element type + * @return the {@link Pure} instance + */ + public static <_1> Pure> pureTuple(_1 _1) { + return new Pure>() { + @Override + public <_2> Tuple2<_1, _2> checkedApply(_2 _2) { + return tuple(_1, _2); + } + }; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java index aa07008ad..b392ffe38 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java @@ -1,11 +1,22 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; -import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.adt.product.Product3; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; -import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A 3-element tuple product type, implemented as a specialized HList. Supports random access. @@ -13,13 +24,19 @@ * @param <_1> The first slot element type * @param <_2> The second slot element type * @param <_3> The third slot element type + * @see Product3 * @see HList * @see SingletonHList * @see Tuple2 * @see Tuple4 * @see Tuple5 */ -public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements Functor<_3>, Bifunctor<_2, _3> { +public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements + Product3<_1, _2, _3>, + MonadRec<_3, Tuple3<_1, _2, ?>>, + Bifunctor<_2, _3, Tuple3<_1, ?, ?>>, + Traversable<_3, Tuple3<_1, _2, ?>> { + private final _1 _1; private final _2 _2; private final _3 _3; @@ -27,78 +44,191 @@ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements Fun Tuple3(_1 _1, Tuple2<_2, _3> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); + _2 = tail._1(); + _3 = tail._2(); } + /** + * {@inheritDoc} + */ @Override public <_0> Tuple4<_0, _1, _2, _3> cons(_0 _0) { return new Tuple4<>(_0, this); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple3}. * - * @return the head element + * @param _4 the new last element + * @param <_4> the new last element type + * @return the new {@link Tuple4} */ + public <_4> Tuple4<_1, _2, _3, _4> snoc(_4 _4) { + return tuple(_1, _2, _3, _4); + } + + /** + * {@inheritDoc} + */ + @Override public _1 _1() { return _1; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Retrieve the third element in constant time. - * - * @return the third element + * {@inheritDoc} */ + @Override public _3 _3() { return _3; } /** - * Destructure and apply this tuple to a function accepting the same number of arguments as this tuple's - * slots. - * - * @param fn the function to apply - * @param the return type of the function - * @return the result of applying the destructured tuple to the function - * @see Tuple2#into + * {@inheritDoc} + */ + @Override + public Tuple3<_2, _3, _1> rotateL3() { + return tuple(_2, _3, _1); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple3<_3, _1, _2> rotateR3() { + return tuple(_3, _1, _2); + } + + /** + * {@inheritDoc} */ - public R into(Fn3 fn) { - return fn.apply(_1, _2, _3); + @Override + public Tuple3<_2, _1, _3> invert() { + return tuple(_2, _1, _3); } + /** + * {@inheritDoc} + */ @Override - public <_3Prime> Tuple3<_1, _2, _3Prime> fmap(Function fn) { - return biMapR(fn); + public <_3Prime> Tuple3<_1, _2, _3Prime> fmap(Fn1 fn) { + return (Tuple3<_1, _2, _3Prime>) MonadRec.super.<_3Prime>fmap(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_2Prime> Tuple3<_1, _2Prime, _3> biMapL(Function fn) { - return (Tuple3<_1, _2Prime, _3>) Bifunctor.super.biMapL(fn); + public <_2Prime> Tuple3<_1, _2Prime, _3> biMapL(Fn1 fn) { + return (Tuple3<_1, _2Prime, _3>) Bifunctor.super.<_2Prime>biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_3Prime> Tuple3<_1, _2, _3Prime> biMapR(Function fn) { - return (Tuple3<_1, _2, _3Prime>) Bifunctor.super.biMapR(fn); + public <_3Prime> Tuple3<_1, _2, _3Prime> biMapR(Fn1 fn) { + return (Tuple3<_1, _2, _3Prime>) Bifunctor.super.<_3Prime>biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public <_2Prime, _3Prime> Tuple3<_1, _2Prime, _3Prime> biMap(Function lFn, - Function rFn) { + public <_2Prime, _3Prime> Tuple3<_1, _2Prime, _3Prime> biMap(Fn1 lFn, + Fn1 rFn) { return new Tuple3<>(_1(), tail().biMap(lFn, rFn)); } + /** + * {@inheritDoc} + */ + @Override + public <_3Prime> Tuple3<_1, _2, _3Prime> pure(_3Prime _3Prime) { + return tuple(_1, _2, _3Prime); + } + + /** + * {@inheritDoc} + */ + @Override + public <_3Prime> Tuple3<_1, _2, _3Prime> zip( + Applicative, Tuple3<_1, _2, ?>> appFn) { + return biMapR(appFn.>>coerce()._3()::apply); + } + + /** + * {@inheritDoc} + */ + @Override + public <_3Prime> Lazy> lazyZip( + Lazy, Tuple3<_1, _2, ?>>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_3Prime, Tuple3<_1, _2, ?>>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public <_3Prime> Tuple3<_1, _2, _3Prime> discardL(Applicative<_3Prime, Tuple3<_1, _2, ?>> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_3Prime> Tuple3<_1, _2, _3> discardR(Applicative<_3Prime, Tuple3<_1, _2, ?>> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_3Prime> Tuple3<_1, _2, _3Prime> flatMap( + Fn1>> f) { + return pure(f.apply(_3).>coerce()._3); + } + + /** + * {@inheritDoc} + */ + @Override + public <_3Prime> Tuple3<_1, _2, _3Prime> trampolineM( + Fn1, Tuple3<_1, _2, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce()._3())); + } + + /** + * {@inheritDoc} + */ + @Override + public <_3Prime, App extends Applicative, TravB extends Traversable<_3Prime, Tuple3<_1, _2, ?>>, + AppTrav extends Applicative> AppTrav traverse( + Fn1> fn, + Fn1 pure) { + return fn.apply(_3).fmap(_3Prime -> fmap(constantly(_3Prime))).fmap(Applicative::coerce).coerce(); + } + + /** + * Returns a {@link Tuple2}<_1, _2> of all the elements of this + * {@link Tuple3}<_1, _2, _3> except the last. + * + * @return The {@link Tuple2}<_1, _2> representing all but the last element + */ + public Tuple2<_1, _2> init() { + return rotateR3().tail(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * @@ -110,4 +240,34 @@ public <_2Prime, _3Prime> Tuple3<_1, _2Prime, _3Prime> biMap(Function Tuple3 fill(A a) { return tuple(a, a, a); } + + /** + * Return {@link Maybe#just(Object) just} the first three elements from the given {@link Iterable}, or + * {@link Maybe#nothing() nothing} if there are less than three elements. + * + * @param as the {@link Iterable} + * @param the {@link Iterable} element type + * @return {@link Maybe} the first three elements of the given {@link Iterable} + */ + public static Maybe> fromIterable(Iterable as) { + return uncons(as).flatMap(Into.into((head, tail) -> Tuple2.fromIterable(tail).fmap(t -> t.cons(head)))); + } + + /** + * The canonical {@link Pure} instance for {@link Tuple3}. + * + * @param _1 the head element + * @param _2 the second element + * @param <_1> the head element type + * @param <_2> the second element type + * @return the {@link Pure} instance + */ + public static <_1, _2> Pure> pureTuple(_1 _1, _2 _2) { + return new Pure>() { + @Override + public <_3> Tuple3<_1, _2, _3> checkedApply(_3 _3) throws Throwable { + return tuple(_1, _2, _3); + } + }; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java index fc456c966..d8dc42868 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java @@ -1,11 +1,22 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; -import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.adt.product.Product4; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; -import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A 4-element tuple product type, implemented as a specialized HList. Supports random access. @@ -14,13 +25,19 @@ * @param <_2> The second slot element type * @param <_3> The third slot element type * @param <_4> The fourth slot element type + * @see Product4 * @see HList * @see SingletonHList * @see Tuple2 * @see Tuple3 * @see Tuple5 */ -public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implements Functor<_4>, Bifunctor<_3, _4> { +public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implements + Product4<_1, _2, _3, _4>, + MonadRec<_4, Tuple4<_1, _2, _3, ?>>, + Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>>, + Traversable<_4, Tuple4<_1, _2, _3, ?>> { + private final _1 _1; private final _2 _2; private final _3 _3; @@ -29,88 +46,216 @@ public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implem Tuple4(_1 _1, Tuple3<_2, _3, _4> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); } + /** + * {@inheritDoc} + */ @Override public <_0> Tuple5<_0, _1, _2, _3, _4> cons(_0 _0) { return new Tuple5<>(_0, this); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple4}. * - * @return the head element + * @param _5 the new last element + * @param <_5> the new last element type + * @return the new {@link Tuple5} + */ + public <_5> Tuple5<_1, _2, _3, _4, _5> snoc(_5 _5) { + return tuple(_1, _2, _3, _4, _5); + } + + /** + * {@inheritDoc} */ + @Override public _1 _1() { return _1; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Retrieve the third element in constant time. - * - * @return the third element + * {@inheritDoc} */ + @Override public _3 _3() { return _3; } /** - * Retrieve the fourth element in constant time. - * - * @return the fourth element + * {@inheritDoc} */ + @Override public _4 _4() { return _4; } /** - * Destructure and apply this tuple to a function accepting the same number of arguments as this tuple's - * slots. - * - * @param fn the function to apply - * @param the return type of the function - * @return the result of applying the destructured tuple to the function - * @see Tuple2#into + * {@inheritDoc} + */ + @Override + public Tuple4<_2, _3, _4, _1> rotateL4() { + return tuple(_2, _3, _4, _1); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple4<_4, _1, _2, _3> rotateR4() { + return tuple(_4, _1, _2, _3); + } + + /** + * {@inheritDoc} */ - public R into(Fn4 fn) { - return fn.apply(_1, _2, _3, _4); + @Override + public Tuple4<_2, _3, _1, _4> rotateL3() { + return tuple(_2, _3, _1, _4); } + /** + * {@inheritDoc} + */ @Override - public <_4Prime> Tuple4<_1, _2, _3, _4Prime> fmap(Function fn) { - return biMapR(fn); + public Tuple4<_3, _1, _2, _4> rotateR3() { + return tuple(_3, _1, _2, _4); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_3Prime> Tuple4<_1, _2, _3Prime, _4> biMapL(Function fn) { - return (Tuple4<_1, _2, _3Prime, _4>) Bifunctor.super.biMapL(fn); + public Tuple4<_2, _1, _3, _4> invert() { + return tuple(_2, _1, _3, _4); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_4Prime> Tuple4<_1, _2, _3, _4Prime> biMapR(Function fn) { - return (Tuple4<_1, _2, _3, _4Prime>) Bifunctor.super.biMapR(fn); + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> fmap(Fn1 fn) { + return (Tuple4<_1, _2, _3, _4Prime>) MonadRec.super.<_4Prime>fmap(fn); } + /** + * {@inheritDoc} + */ @Override - public <_3Prime, _4Prime> Tuple4<_1, _2, _3Prime, _4Prime> biMap(Function lFn, - Function rFn) { + public <_3Prime> Tuple4<_1, _2, _3Prime, _4> biMapL(Fn1 fn) { + return (Tuple4<_1, _2, _3Prime, _4>) Bifunctor.super.<_3Prime>biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> biMapR(Fn1 fn) { + return (Tuple4<_1, _2, _3, _4Prime>) Bifunctor.super.<_4Prime>biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public <_3Prime, _4Prime> Tuple4<_1, _2, _3Prime, _4Prime> biMap(Fn1 lFn, + Fn1 rFn) { return new Tuple4<>(_1(), tail().biMap(lFn, rFn)); } + /** + * {@inheritDoc} + */ + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> pure(_4Prime _4Prime) { + return tuple(_1, _2, _3, _4Prime); + } + + /** + * {@inheritDoc} + */ + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> zip( + Applicative, Tuple4<_1, _2, _3, ?>> appFn) { + return biMapR(appFn.>>coerce()._4()::apply); + } + + /** + * {@inheritDoc} + */ + @Override + public <_4Prime> Lazy> lazyZip( + Lazy, Tuple4<_1, _2, _3, ?>>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_4Prime, Tuple4<_1, _2, _3, ?>>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> discardL(Applicative<_4Prime, Tuple4<_1, _2, _3, ?>> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4> discardR(Applicative<_4Prime, Tuple4<_1, _2, _3, ?>> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> flatMap( + Fn1>> f) { + return pure(f.apply(_4).>coerce()._4); + } + + /** + * {@inheritDoc} + */ + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> trampolineM( + Fn1, Tuple4<_1, _2, _3, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce()._4())); + } + + /** + * {@inheritDoc} + */ + @Override + public <_4Prime, App extends Applicative, TravB extends Traversable<_4Prime, Tuple4<_1, _2, _3, ?>>, + AppTrav extends Applicative> AppTrav traverse( + Fn1> fn, + Fn1 pure) { + return fn.apply(_4).fmap(_4Prime -> fmap(constantly(_4Prime))).fmap(Applicative::coerce).coerce(); + } + + /** + * Returns a {@link Tuple3}<_1, _2, _3> of all the elements of this + * {@link Tuple4}<_1, _2, _3, _4> except the last. + * + * @return The {@link Tuple3}<_1, _2, _3> representing all but the last element + */ + public Tuple3<_1, _2, _3> init() { + return rotateR4().tail(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * @@ -122,4 +267,36 @@ public <_3Prime, _4Prime> Tuple4<_1, _2, _3Prime, _4Prime> biMap(Function Tuple4 fill(A a) { return tuple(a, a, a, a); } + + /** + * Return {@link Maybe#just(Object) just} the first four elements from the given {@link Iterable}, or + * {@link Maybe#nothing() nothing} if there are less than four elements. + * + * @param as the {@link Iterable} + * @param the {@link Iterable} element type + * @return {@link Maybe} the first four elements of the given {@link Iterable} + */ + public static Maybe> fromIterable(Iterable as) { + return uncons(as).flatMap(Into.into((head, tail) -> Tuple3.fromIterable(tail).fmap(t -> t.cons(head)))); + } + + /** + * The canonical {@link Pure} instance for {@link Tuple4}. + * + * @param _1 the head element + * @param _2 the second element + * @param _3 the third element + * @param <_1> the head element type + * @param <_2> the second element type + * @param <_3> the third element type + * @return the {@link Pure} instance + */ + public static <_1, _2, _3> Pure> pureTuple(_1 _1, _2 _2, _3 _3) { + return new Pure>() { + @Override + public <_4> Tuple4<_1, _2, _3, _4> checkedApply(_4 _4) throws Throwable { + return tuple(_1, _2, _3, _4); + } + }; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java index 101440fdb..5a6f20cbb 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java @@ -1,10 +1,22 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.adt.product.Product5; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; -import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A 5-element tuple product type, implemented as a specialized HList. Supports random access. @@ -14,13 +26,19 @@ * @param <_3> The third slot element type * @param <_4> The fourth slot element type * @param <_5> The fifth slot element type + * @see Product5 * @see HList * @see SingletonHList * @see Tuple2 * @see Tuple3 * @see Tuple4 */ -public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> implements Functor<_5>, Bifunctor<_4, _5> { +public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> implements + Product5<_1, _2, _3, _4, _5>, + MonadRec<_5, Tuple5<_1, _2, _3, _4, ?>>, + Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>>, + Traversable<_5, Tuple5<_1, _2, _3, _4, ?>> { + private final _1 _1; private final _2 _2; private final _3 _3; @@ -30,85 +48,241 @@ public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5> Tuple5(_1 _1, Tuple4<_2, _3, _4, _5> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); - _5 = tail._4(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); } + /** + * {@inheritDoc} + */ @Override - public <_0> HCons<_0, Tuple5<_1, _2, _3, _4, _5>> cons(_0 _0) { - return new HCons<>(_0, this); + public <_0> Tuple6<_0, _1, _2, _3, _4, _5> cons(_0 _0) { + return new Tuple6<>(_0, this); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple5}. * - * @return the head element + * @param _6 the new last element + * @param <_6> the new last element type + * @return the new {@link Tuple6} + */ + public <_6> Tuple6<_1, _2, _3, _4, _5, _6> snoc(_6 _6) { + return tuple(_1, _2, _3, _4, _5, _6); + } + + /** + * {@inheritDoc} */ + @Override public _1 _1() { return _1; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Retrieve the third element in constant time. - * - * @return the third element + * {@inheritDoc} */ + @Override public _3 _3() { return _3; } /** - * Retrieve the fourth element in constant time. - * - * @return the fourth element + * {@inheritDoc} */ + @Override public _4 _4() { return _4; } /** - * Retrieve the fifth element in constant time. - * - * @return the fifth element + * {@inheritDoc} */ + @Override public _5 _5() { return _5; } + /** + * {@inheritDoc} + */ + @Override + public Tuple5<_2, _3, _4, _5, _1> rotateL5() { + return tuple(_2, _3, _4, _5, _1); + } + + /** + * {@inheritDoc} + */ @Override - public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> fmap(Function fn) { - return biMapR(fn); + public Tuple5<_5, _1, _2, _3, _4> rotateR5() { + return tuple(_5, _1, _2, _3, _4); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_4Prime> Tuple5<_1, _2, _3, _4Prime, _5> biMapL(Function fn) { - return (Tuple5<_1, _2, _3, _4Prime, _5>) Bifunctor.super.biMapL(fn); + public Tuple5<_2, _3, _4, _1, _5> rotateL4() { + return tuple(_2, _3, _4, _1, _5); } + /** + * {@inheritDoc} + */ + @Override + public Tuple5<_4, _1, _2, _3, _5> rotateR4() { + return tuple(_4, _1, _2, _3, _5); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple5<_2, _3, _1, _4, _5> rotateL3() { + return tuple(_2, _3, _1, _4, _5); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple5<_3, _1, _2, _4, _5> rotateR3() { + return tuple(_3, _1, _2, _4, _5); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple5<_2, _1, _3, _4, _5> invert() { + return tuple(_2, _1, _3, _4, _5); + } + + /** + * {@inheritDoc} + */ + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> fmap(Fn1 fn) { + return MonadRec.super.<_5Prime>fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_4Prime> Tuple5<_1, _2, _3, _4Prime, _5> biMapL(Fn1 fn) { + return (Tuple5<_1, _2, _3, _4Prime, _5>) Bifunctor.super.<_4Prime>biMapL(fn); + } + + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> biMapR(Function fn) { - return (Tuple5<_1, _2, _3, _4, _5Prime>) Bifunctor.super.biMapR(fn); + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> biMapR(Fn1 fn) { + return (Tuple5<_1, _2, _3, _4, _5Prime>) Bifunctor.super.<_5Prime>biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public <_4Prime, _5Prime> Tuple5<_1, _2, _3, _4Prime, _5Prime> biMap(Function lFn, - Function rFn) { + public <_4Prime, _5Prime> Tuple5<_1, _2, _3, _4Prime, _5Prime> biMap(Fn1 lFn, + Fn1 rFn) { return new Tuple5<>(_1(), tail().biMap(lFn, rFn)); } + /** + * {@inheritDoc} + */ + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> pure(_5Prime _5Prime) { + return tuple(_1, _2, _3, _4, _5Prime); + } + + /** + * {@inheritDoc} + */ + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> zip( + Applicative, Tuple5<_1, _2, _3, _4, ?>> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_5Prime> Lazy> lazyZip( + Lazy, Tuple5<_1, _2, _3, _4, ?>>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_5Prime, Tuple5<_1, _2, _3, _4, ?>>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> discardL(Applicative<_5Prime, Tuple5<_1, _2, _3, _4, ?>> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5> discardR(Applicative<_5Prime, Tuple5<_1, _2, _3, _4, ?>> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> flatMap( + Fn1>> f) { + return pure(f.apply(_5).>coerce()._5()); + } + + /** + * {@inheritDoc} + */ + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> trampolineM( + Fn1, Tuple5<_1, _2, _3, _4, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce()._5())); + } + + /** + * {@inheritDoc} + */ + @Override + public <_5Prime, App extends Applicative, TravB extends Traversable<_5Prime, Tuple5<_1, _2, _3, _4, ?>>, + AppTrav extends Applicative> AppTrav traverse( + Fn1> fn, + Fn1 pure) { + return fn.apply(_5).fmap(_3Prime -> fmap(constantly(_3Prime))).fmap(Applicative::coerce).coerce(); + } + + /** + * Returns a {@link Tuple4}<_1, _2, _3, _4> of all the elements of this + * {@link Tuple5}<_1, _2, _3, _4, _5> except the last. + * + * @return The {@link Tuple4}<_1, _2, _3, _4> representing all but the last element + */ + public Tuple4<_1, _2, _3, _4> init() { + return rotateR5().tail(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * @@ -120,4 +294,38 @@ public <_4Prime, _5Prime> Tuple5<_1, _2, _3, _4Prime, _5Prime> biMap(Function Tuple5 fill(A a) { return tuple(a, a, a, a, a); } + + /** + * Return {@link Maybe#just(Object) just} the first five elements from the given {@link Iterable}, or + * {@link Maybe#nothing() nothing} if there are less than five elements. + * + * @param as the {@link Iterable} + * @param the {@link Iterable} element type + * @return {@link Maybe} the first five elements of the given {@link Iterable} + */ + public static Maybe> fromIterable(Iterable as) { + return uncons(as).flatMap(Into.into((head, tail) -> Tuple4.fromIterable(tail).fmap(t -> t.cons(head)))); + } + + /** + * The canonical {@link Pure} instance for {@link Tuple5}. + * + * @param _1 the head element + * @param _2 the second element + * @param _3 the third element + * @param _4 the fourth element + * @param <_1> the head element type + * @param <_2> the second element type + * @param <_3> the third element type + * @param <_4> the fourth element type + * @return the {@link Pure} instance + */ + public static <_1, _2, _3, _4> Pure> pureTuple(_1 _1, _2 _2, _3 _3, _4 _4) { + return new Pure>() { + @Override + public <_5> Tuple5<_1, _2, _3, _4, _5> checkedApply(_5 _5) throws Throwable { + return tuple(_1, _2, _3, _4, _5); + } + }; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java new file mode 100644 index 000000000..1034e3b6b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java @@ -0,0 +1,365 @@ +package com.jnape.palatable.lambda.adt.hlist; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.adt.product.Product6; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; + +/** + * A 6-element tuple product type, implemented as a specialized HList. Supports random access. + * + * @param <_1> The first slot element type + * @param <_2> The second slot element type + * @param <_3> The third slot element type + * @param <_4> The fourth slot element type + * @param <_5> The fifth slot element type + * @param <_6> The sixth slot element type + * @see Product6 + * @see HList + * @see SingletonHList + * @see Tuple2 + * @see Tuple3 + * @see Tuple4 + * @see Tuple5 + */ +public class Tuple6<_1, _2, _3, _4, _5, _6> extends HCons<_1, Tuple5<_2, _3, _4, _5, _6>> implements + Product6<_1, _2, _3, _4, _5, _6>, + MonadRec<_6, Tuple6<_1, _2, _3, _4, _5, ?>>, + Bifunctor<_5, _6, Tuple6<_1, _2, _3, _4, ?, ?>>, + Traversable<_6, Tuple6<_1, _2, _3, _4, _5, ?>> { + + private final _1 _1; + private final _2 _2; + private final _3 _3; + private final _4 _4; + private final _5 _5; + private final _6 _6; + + Tuple6(_1 _1, Tuple5<_2, _3, _4, _5, _6> tail) { + super(_1, tail); + this._1 = _1; + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); + _6 = tail._5(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_0> Tuple7<_0, _1, _2, _3, _4, _5, _6> cons(_0 _0) { + return new Tuple7<>(_0, this); + } + + /** + * Snoc an element onto the back of this {@link Tuple6}. + * + * @param _7 the new last element + * @param <_7> the new last element type + * @return the new {@link Tuple7} + */ + public <_7> Tuple7<_1, _2, _3, _4, _5, _6, _7> snoc(_7 _7) { + return tuple(_1, _2, _3, _4, _5, _6, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public _1 _1() { + return _1; + } + + /** + * {@inheritDoc} + */ + @Override + public _2 _2() { + return _2; + } + + /** + * {@inheritDoc} + */ + @Override + public _3 _3() { + return _3; + } + + /** + * {@inheritDoc} + */ + @Override + public _4 _4() { + return _4; + } + + /** + * {@inheritDoc} + */ + @Override + public _5 _5() { + return _5; + } + + /** + * {@inheritDoc} + */ + @Override + public _6 _6() { + return _6; + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple6<_2, _3, _4, _5, _6, _1> rotateL6() { + return tuple(_2, _3, _4, _5, _6, _1); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple6<_6, _1, _2, _3, _4, _5> rotateR6() { + return tuple(_6, _1, _2, _3, _4, _5); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple6<_2, _3, _4, _5, _1, _6> rotateL5() { + return tuple(_2, _3, _4, _5, _1, _6); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple6<_5, _1, _2, _3, _4, _6> rotateR5() { + return tuple(_5, _1, _2, _3, _4, _6); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple6<_2, _3, _4, _1, _5, _6> rotateL4() { + return tuple(_2, _3, _4, _1, _5, _6); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple6<_4, _1, _2, _3, _5, _6> rotateR4() { + return tuple(_4, _1, _2, _3, _5, _6); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple6<_2, _3, _1, _4, _5, _6> rotateL3() { + return tuple(_2, _3, _1, _4, _5, _6); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple6<_3, _1, _2, _4, _5, _6> rotateR3() { + return tuple(_3, _1, _2, _4, _5, _6); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple6<_2, _1, _3, _4, _5, _6> invert() { + return tuple(_2, _1, _3, _4, _5, _6); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> fmap(Fn1 fn) { + return MonadRec.super.<_6Prime>fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_5Prime> Tuple6<_1, _2, _3, _4, _5Prime, _6> biMapL(Fn1 fn) { + return (Tuple6<_1, _2, _3, _4, _5Prime, _6>) Bifunctor.super.<_5Prime>biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> biMapR(Fn1 fn) { + return (Tuple6<_1, _2, _3, _4, _5, _6Prime>) Bifunctor.super.<_6Prime>biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public <_5Prime, _6Prime> Tuple6<_1, _2, _3, _4, _5Prime, _6Prime> biMap( + Fn1 lFn, + Fn1 rFn) { + return new Tuple6<>(_1(), tail().biMap(lFn, rFn)); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> pure(_6Prime _6Prime) { + return tuple(_1, _2, _3, _4, _5, _6Prime); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> zip( + Applicative, Tuple6<_1, _2, _3, _4, _5, ?>> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Lazy> lazyZip( + Lazy, Tuple6<_1, _2, _3, _4, _5, ?>>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> discardL( + Applicative<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6> discardR(Applicative<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> flatMap( + Fn1>> f) { + return pure(f.apply(_6).>coerce()._6()); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> trampolineM( + Fn1, Tuple6<_1, _2, _3, _4, _5, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce() + ._6())); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime, App extends Applicative, TravB extends Traversable<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>>, + AppTrav extends Applicative> AppTrav traverse( + Fn1> fn, + Fn1 pure) { + return fn.apply(_6).fmap(_6Prime -> fmap(constantly(_6Prime))).fmap(Applicative::coerce).coerce(); + } + + /** + * Returns a {@link Tuple5}<_1, _2, _3, _4, _5> of all the elements of this + * {@link Tuple6}<_1, _2, _3, _4, _5, _6> except the last. + * + * @return The {@link Tuple5}<_1, _2, _3, _4, _5> representing all but the last element + */ + public Tuple5<_1, _2, _3, _4, _5> init() { + return rotateR6().tail(); + } + + /** + * Given a value of type A, produced an instance of this tuple with each slot set to that value. + * + * @param a the value to fill the tuple with + * @param the value type + * @return the filled tuple + * @see Tuple2#fill + */ + public static Tuple6 fill(A a) { + return tuple(a, a, a, a, a, a); + } + + /** + * Return {@link Maybe#just(Object) just} the first six elements from the given {@link Iterable}, or + * {@link Maybe#nothing() nothing} if there are less than six elements. + * + * @param as the {@link Iterable} + * @param the {@link Iterable} element type + * @return {@link Maybe} the first six elements of the given {@link Iterable} + */ + public static Maybe> fromIterable(Iterable as) { + return uncons(as).flatMap(Into.into((head, tail) -> Tuple5.fromIterable(tail).fmap(t -> t.cons(head)))); + } + + /** + * The canonical {@link Pure} instance for {@link Tuple6}. + * + * @param _1 the head element + * @param _2 the second element + * @param _3 the third element + * @param _4 the fourth element + * @param _5 the fifth element + * @param <_1> the head element type + * @param <_2> the second element type + * @param <_3> the third element type + * @param <_4> the fourth element type + * @param <_5> the fifth element type + * @return the {@link Pure} instance + */ + public static <_1, _2, _3, _4, _5> Pure> pureTuple(_1 _1, _2 _2, _3 _3, _4 _4, + _5 _5) { + return new Pure>() { + @Override + public <_6> Tuple6<_1, _2, _3, _4, _5, _6> checkedApply(_6 _6) { + return tuple(_1, _2, _3, _4, _5, _6); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java new file mode 100644 index 000000000..cdfe552d6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java @@ -0,0 +1,397 @@ +package com.jnape.palatable.lambda.adt.hlist; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.adt.product.Product7; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; + +/** + * A 7-element tuple product type, implemented as a specialized HList. Supports random access. + * + * @param <_1> The first slot element type + * @param <_2> The second slot element type + * @param <_3> The third slot element type + * @param <_4> The fourth slot element type + * @param <_5> The fifth slot element type + * @param <_6> The sixth slot element type + * @param <_7> The seventh slot element type + * @see Product7 + * @see HList + * @see SingletonHList + * @see Tuple2 + * @see Tuple3 + * @see Tuple4 + * @see Tuple5 + * @see Tuple6 + */ +public class Tuple7<_1, _2, _3, _4, _5, _6, _7> extends HCons<_1, Tuple6<_2, _3, _4, _5, _6, _7>> implements + Product7<_1, _2, _3, _4, _5, _6, _7>, + MonadRec<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, + Bifunctor<_6, _7, Tuple7<_1, _2, _3, _4, _5, ?, ?>>, + Traversable<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>> { + + private final _1 _1; + private final _2 _2; + private final _3 _3; + private final _4 _4; + private final _5 _5; + private final _6 _6; + private final _7 _7; + + Tuple7(_1 _1, Tuple6<_2, _3, _4, _5, _6, _7> tail) { + super(_1, tail); + this._1 = _1; + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); + _6 = tail._5(); + _7 = tail._6(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_0> Tuple8<_0, _1, _2, _3, _4, _5, _6, _7> cons(_0 _0) { + return new Tuple8<>(_0, this); + } + + /** + * Snoc an element onto the back of this {@link Tuple7}. + * + * @param _8 the new last element + * @param <_8> the new last element type + * @return the new {@link Tuple8} + */ + public <_8> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> snoc(_8 _8) { + return tuple(_1, _2, _3, _4, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public _1 _1() { + return _1; + } + + /** + * {@inheritDoc} + */ + @Override + public _2 _2() { + return _2; + } + + /** + * {@inheritDoc} + */ + @Override + public _3 _3() { + return _3; + } + + /** + * {@inheritDoc} + */ + @Override + public _4 _4() { + return _4; + } + + /** + * {@inheritDoc} + */ + @Override + public _5 _5() { + return _5; + } + + /** + * {@inheritDoc} + */ + @Override + public _6 _6() { + return _6; + } + + /** + * {@inheritDoc} + */ + @Override + public _7 _7() { + return _7; + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_2, _3, _4, _5, _6, _7, _1> rotateL7() { + return tuple(_2, _3, _4, _5, _6, _7, _1); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_7, _1, _2, _3, _4, _5, _6> rotateR7() { + return tuple(_7, _1, _2, _3, _4, _5, _6); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_2, _3, _4, _5, _6, _1, _7> rotateL6() { + return tuple(_2, _3, _4, _5, _6, _1, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_6, _1, _2, _3, _4, _5, _7> rotateR6() { + return tuple(_6, _1, _2, _3, _4, _5, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_2, _3, _4, _5, _1, _6, _7> rotateL5() { + return tuple(_2, _3, _4, _5, _1, _6, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_5, _1, _2, _3, _4, _6, _7> rotateR5() { + return tuple(_5, _1, _2, _3, _4, _6, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_2, _3, _4, _1, _5, _6, _7> rotateL4() { + return tuple(_2, _3, _4, _1, _5, _6, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_4, _1, _2, _3, _5, _6, _7> rotateR4() { + return tuple(_4, _1, _2, _3, _5, _6, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_2, _3, _1, _4, _5, _6, _7> rotateL3() { + return tuple(_2, _3, _1, _4, _5, _6, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_3, _1, _2, _4, _5, _6, _7> rotateR3() { + return tuple(_3, _1, _2, _4, _5, _6, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_2, _1, _3, _4, _5, _6, _7> invert() { + return tuple(_2, _1, _3, _4, _5, _6, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> fmap(Fn1 fn) { + return MonadRec.super.<_7Prime>fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple7<_1, _2, _3, _4, _5, _6Prime, _7> biMapL(Fn1 fn) { + return (Tuple7<_1, _2, _3, _4, _5, _6Prime, _7>) Bifunctor.super.<_6Prime>biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> biMapR(Fn1 fn) { + return (Tuple7<_1, _2, _3, _4, _5, _6, _7Prime>) Bifunctor.super.<_7Prime>biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public <_6Prime, _7Prime> Tuple7<_1, _2, _3, _4, _5, _6Prime, _7Prime> biMap( + Fn1 lFn, + Fn1 rFn) { + return new Tuple7<>(_1(), tail().biMap(lFn, rFn)); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> pure(_7Prime _7Prime) { + return tuple(_1, _2, _3, _4, _5, _6, _7Prime); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> zip( + Applicative, Tuple7<_1, _2, _3, _4, _5, _6, ?>> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Lazy> lazyZip( + Lazy, Tuple7<_1, _2, _3, _4, _5, _6, ?>>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> discardL( + Applicative<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7> discardR( + Applicative<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> flatMap( + Fn1>> f) { + return pure(f.apply(_7).>coerce()._7()); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> trampolineM( + Fn1, Tuple7<_1, _2, _3, _4, _5, _6, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce() + ._7())); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime, App extends Applicative, + TravB extends Traversable<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, + AppTrav extends Applicative> AppTrav traverse( + Fn1> fn, + Fn1 pure) { + return fn.apply(_7).fmap(_7Prime -> fmap(constantly(_7Prime))).fmap(Applicative::coerce).coerce(); + } + + /** + * Returns a {@link Tuple6}<_1, _2, _3, _4, _5, _6> of all the elements of this + * {@link Tuple7}<_1, _2, _3, _4, _5, _6, _7> except the last. + * + * @return The {@link Tuple6}<_1, _2, _3, _4, _5, _6> representing all but the last element + */ + public Tuple6<_1, _2, _3, _4, _5, _6> init() { + return rotateR7().tail(); + } + + /** + * Given a value of type A, produced an instance of this tuple with each slot set to that value. + * + * @param a the value to fill the tuple with + * @param the value type + * @return the filled tuple + * @see Tuple2#fill + */ + public static Tuple7 fill(A a) { + return tuple(a, a, a, a, a, a, a); + } + + /** + * Return {@link Maybe#just(Object) just} the first seven elements from the given {@link Iterable}, or + * {@link Maybe#nothing() nothing} if there are less than seven elements. + * + * @param as the {@link Iterable} + * @param the {@link Iterable} element type + * @return {@link Maybe} the first seven elements of the given {@link Iterable} + */ + public static Maybe> fromIterable(Iterable as) { + return uncons(as).flatMap(Into.into((head, tail) -> Tuple6.fromIterable(tail).fmap(t -> t.cons(head)))); + } + + /** + * The canonical {@link Pure} instance for {@link Tuple7}. + * + * @param _1 the head element + * @param _2 the second element + * @param _3 the third element + * @param _4 the fourth element + * @param _5 the fifth element + * @param _6 the sixth element + * @param <_1> the head element type + * @param <_2> the second element type + * @param <_3> the third element type + * @param <_4> the fourth element type + * @param <_5> the fifth element type + * @param <_6> the sixth element type + * @return the {@link Pure} instance + */ + public static <_1, _2, _3, _4, _5, _6> Pure> pureTuple(_1 _1, _2 _2, _3 _3, _4 _4, + _5 _5, _6 _6) { + return new Pure>() { + @Override + public <_7> Tuple7<_1, _2, _3, _4, _5, _6, _7> checkedApply(_7 _7) throws Throwable { + return tuple(_1, _2, _3, _4, _5, _6, _7); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java new file mode 100644 index 000000000..079a30611 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java @@ -0,0 +1,432 @@ +package com.jnape.palatable.lambda.adt.hlist; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.adt.product.Product8; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; + +/** + * An 8-element tuple product type, implemented as a specialized HList. Supports random access. + * + * @param <_1> The first slot element type + * @param <_2> The second slot element type + * @param <_3> The third slot element type + * @param <_4> The fourth slot element type + * @param <_5> The fifth slot element type + * @param <_6> The sixth slot element type + * @param <_7> The seventh slot element type + * @param <_8> The eighth slot element type + * @see Product8 + * @see HList + * @see SingletonHList + * @see Tuple2 + * @see Tuple3 + * @see Tuple4 + * @see Tuple5 + * @see Tuple6 + * @see Tuple7 + */ +public class Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> extends HCons<_1, Tuple7<_2, _3, _4, _5, _6, _7, _8>> implements + Product8<_1, _2, _3, _4, _5, _6, _7, _8>, + MonadRec<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, + Bifunctor<_7, _8, Tuple8<_1, _2, _3, _4, _5, _6, ?, ?>>, + Traversable<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>> { + + private final _1 _1; + private final _2 _2; + private final _3 _3; + private final _4 _4; + private final _5 _5; + private final _6 _6; + private final _7 _7; + private final _8 _8; + + Tuple8(_1 _1, Tuple7<_2, _3, _4, _5, _6, _7, _8> tail) { + super(_1, tail); + this._1 = _1; + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); + _6 = tail._5(); + _7 = tail._6(); + _8 = tail._7(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_0> HCons<_0, Tuple8<_1, _2, _3, _4, _5, _6, _7, _8>> cons(_0 _0) { + return new HCons<>(_0, this); + } + + /** + * Snoc an element onto the back of this {@link Tuple8}. + * + * @param _9 the new last element + * @param <_9> the new last element type + * @return the new {@link HCons consed} {@link Tuple8} + */ + public <_9> HCons<_1, Tuple8<_2, _3, _4, _5, _6, _7, _8, _9>> snoc(_9 _9) { + return singletonHList(_9).cons(_8).cons(_7).cons(_6).cons(_5).cons(_4).cons(_3).cons(_2).cons(_1); + } + + /** + * {@inheritDoc} + */ + @Override + public _1 _1() { + return _1; + } + + /** + * {@inheritDoc} + */ + @Override + public _2 _2() { + return _2; + } + + /** + * {@inheritDoc} + */ + @Override + public _3 _3() { + return _3; + } + + /** + * {@inheritDoc} + */ + @Override + public _4 _4() { + return _4; + } + + /** + * {@inheritDoc} + */ + @Override + public _5 _5() { + return _5; + } + + /** + * {@inheritDoc} + */ + @Override + public _6 _6() { + return _6; + } + + /** + * {@inheritDoc} + */ + @Override + public _7 _7() { + return _7; + } + + /** + * {@inheritDoc} + */ + @Override + public _8 _8() { + return _8; + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _3, _4, _5, _6, _7, _8, _1> rotateL8() { + return tuple(_2, _3, _4, _5, _6, _7, _8, _1); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_8, _1, _2, _3, _4, _5, _6, _7> rotateR8() { + return tuple(_8, _1, _2, _3, _4, _5, _6, _7); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _3, _4, _5, _6, _7, _1, _8> rotateL7() { + return tuple(_2, _3, _4, _5, _6, _7, _1, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_7, _1, _2, _3, _4, _5, _6, _8> rotateR7() { + return tuple(_7, _1, _2, _3, _4, _5, _6, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _3, _4, _5, _6, _1, _7, _8> rotateL6() { + return tuple(_2, _3, _4, _5, _6, _1, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_6, _1, _2, _3, _4, _5, _7, _8> rotateR6() { + return tuple(_6, _1, _2, _3, _4, _5, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _3, _4, _5, _1, _6, _7, _8> rotateL5() { + return tuple(_2, _3, _4, _5, _1, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_5, _1, _2, _3, _4, _6, _7, _8> rotateR5() { + return tuple(_5, _1, _2, _3, _4, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _3, _4, _1, _5, _6, _7, _8> rotateL4() { + return tuple(_2, _3, _4, _1, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_4, _1, _2, _3, _5, _6, _7, _8> rotateR4() { + return tuple(_4, _1, _2, _3, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _3, _1, _4, _5, _6, _7, _8> rotateL3() { + return tuple(_2, _3, _1, _4, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_3, _1, _2, _4, _5, _6, _7, _8> rotateR3() { + return tuple(_3, _1, _2, _4, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _1, _3, _4, _5, _6, _7, _8> invert() { + return tuple(_2, _1, _3, _4, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> fmap(Fn1 fn) { + return MonadRec.super.<_8Prime>fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8> biMapL(Fn1 fn) { + return (Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8>) Bifunctor.super.<_7Prime>biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> biMapR(Fn1 fn) { + return (Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime>) Bifunctor.super.<_8Prime>biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime, _8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8Prime> biMap( + Fn1 lFn, + Fn1 rFn) { + return new Tuple8<>(_1(), tail().biMap(lFn, rFn)); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> pure(_8Prime _8Prime) { + return tuple(_1, _2, _3, _4, _5, _6, _7, _8Prime); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> zip( + Applicative, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Lazy> lazyZip( + Lazy, + Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> discardL( + Applicative<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> discardR( + Applicative<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> flatMap( + Fn1>> f) { + return pure(f.apply(_8).>coerce()._8()); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> trampolineM( + Fn1, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x) + .>>coerce() + ._8())); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime, App extends Applicative, + TravB extends Traversable<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, + AppTrav extends Applicative> AppTrav traverse( + Fn1> fn, + Fn1 pure) { + return fn.apply(_8).fmap(_8Prime -> fmap(constantly(_8Prime))).fmap(Applicative::coerce).coerce(); + } + + /** + * Returns a {@link Tuple7}<_1, _2, _3, _4, _5, _6, _7> of all the elements of this + * {@link Tuple8}<_1, _2, _3, _4, _5, _6, _7, _8> except the last. + * + * @return The {@link Tuple7}<_1, _2, _3, _4, _5, _6, _7> representing all but the last element + */ + public Tuple7<_1, _2, _3, _4, _5, _6, _7> init() { + return rotateR8().tail(); + } + + /** + * Given a value of type A, produced an instance of this tuple with each slot set to that value. + * + * @param a the value to fill the tuple with + * @param the value type + * @return the filled tuple + * @see Tuple2#fill + */ + public static Tuple8 fill(A a) { + return tuple(a, a, a, a, a, a, a, a); + } + + /** + * Return {@link Maybe#just(Object) just} the first eight elements from the given {@link Iterable}, or + * {@link Maybe#nothing() nothing} if there are less than eight elements. + * + * @param as the {@link Iterable} + * @param the {@link Iterable} element type + * @return {@link Maybe} the first seven elements of the given {@link Iterable} + */ + public static Maybe> fromIterable(Iterable as) { + return uncons(as).flatMap(Into.into((head, tail) -> Tuple7.fromIterable(tail).fmap(t -> t.cons(head)))); + } + + /** + * The canonical {@link Pure} instance for {@link Tuple8}. + * + * @param _1 the head element + * @param _2 the second element + * @param _3 the third element + * @param _4 the fourth element + * @param _5 the fifth element + * @param _6 the sixth element + * @param _7 the seventh element + * @param <_1> the head element type + * @param <_2> the second element type + * @param <_3> the third element type + * @param <_4> the fourth element type + * @param <_5> the fifth element type + * @param <_6> the sixth element type + * @param <_7> the seventh element type + * @return the {@link Pure} instance + */ + public static <_1, _2, _3, _4, _5, _6, _7> Pure> pureTuple(_1 _1, _2 _2, + _3 _3, _4 _4, + _5 _5, _6 _6, + _7 _7) { + return new Pure>() { + @Override + public <_8> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> checkedApply(_8 _8) throws Throwable { + return tuple(_1, _2, _3, _4, _5, _6, _7, _8); + } + }; + } + +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java index 1fc5ad770..62c922ece 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java @@ -1,18 +1,25 @@ package com.jnape.palatable.lambda.adt.hmap; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; -import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; +import static com.jnape.palatable.lambda.adt.Maybe.maybe; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.optics.functions.View.view; import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonMap; /** * An immutable heterogeneous mapping from a parametrized type-safe key to any value, supporting a minimal mapping @@ -21,13 +28,13 @@ * @see TypeSafeKey * @see com.jnape.palatable.lambda.adt.hlist.HList */ -public class HMap implements Iterable> { +public final class HMap implements Iterable, Object>> { private static final HMap EMPTY = new HMap(emptyMap()); - private final Map table; + private final Map, Object> table; - private HMap(Map table) { + private HMap(Map, Object> table) { this.table = table; } @@ -35,12 +42,12 @@ private HMap(Map table) { * Retrieve the value at this key. * * @param key the key - * @param the value type - * @return the value at this key wrapped in an {@link Optional}, or {@link Optional#empty}. + * @param the value type + * @param the value type + * @return Maybe the value at this key */ - @SuppressWarnings("unchecked") - public Optional get(TypeSafeKey key) { - return Optional.ofNullable((T) table.get(key)); + public Maybe get(TypeSafeKey key) { + return maybe(Downcast.downcast(table.get(key))).fmap(view(key)); } /** @@ -51,8 +58,9 @@ public Optional get(TypeSafeKey key) { * @return the value at this key * @throws NoSuchElementException if the key is unmapped */ - public V demand(TypeSafeKey key) throws NoSuchElementException { - return get(key).orElseThrow(() -> new NoSuchElementException("Demanded value for key " + key + ", but couldn't find one.")); + public V demand(TypeSafeKey key) throws NoSuchElementException { + return get(key).orElseThrow(() -> new NoSuchElementException("Demanded value for key " + key + + ", but couldn't find one.")); } /** @@ -63,8 +71,8 @@ public V demand(TypeSafeKey key) throws NoSuchElementException { * @param the value type * @return the updated HMap */ - public HMap put(TypeSafeKey key, V value) { - return alter(t -> t.put(key, value)); + public HMap put(TypeSafeKey key, V value) { + return alter(t -> t.put(key, view(key.mirror(), value))); } /** @@ -83,7 +91,7 @@ public HMap putAll(HMap hMap) { * @param key the key * @return true if the key is mapped; false otherwise */ - public boolean containsKey(TypeSafeKey key) { + public boolean containsKey(TypeSafeKey key) { return table.containsKey(key); } @@ -93,7 +101,7 @@ public boolean containsKey(TypeSafeKey key) { * @param key the key * @return the updated HMap */ - public HMap remove(TypeSafeKey key) { + public HMap remove(TypeSafeKey key) { return alter(t -> t.remove(key)); } @@ -107,22 +115,34 @@ public HMap removeAll(HMap hMap) { return alter(t -> t.keySet().removeAll(hMap.table.keySet())); } + /** + * Test whether this {@link HMap} is empty. + * + * @return true if the {@link HMap} is empty; false otherwise. + */ + public boolean isEmpty() { + return table.isEmpty(); + } + /** * Retrieve all the mapped keys. + *

+ * Note that unlike with {@link Map#keySet()}, the resulting key set is not "live"; in fact + * that is, alterations to the resulting key set have no effect on the backing {@link HMap}. * - * @return an Iterable of all the mapped keys + * @return a {@link Set} of all the mapped keys */ - public Iterable keys() { - return map(Tuple2::_1, this); + public Set> keys() { + return new HashSet<>(table.keySet()); } /** * Retrieve all the mapped values. * - * @return an Iterable of all the mapped values + * @return a {@link List} of all the mapped values */ - public Iterable values() { - return map(Tuple2::_2, this); + public Collection values() { + return new ArrayList<>(table.values()); } /** @@ -131,12 +151,12 @@ public Iterable values() { * * @return the map view */ - public Map toMap() { + public Map, Object> toMap() { return new HashMap<>(table); } @Override - public Iterator> iterator() { + public Iterator, Object>> iterator() { return map(Tuple2::fromEntry, table.entrySet()).iterator(); } @@ -161,8 +181,8 @@ public String toString() { '}'; } - private HMap alter(Consumer> alterFn) { - HashMap copy = new HashMap<>(table); + private HMap alter(Consumer, Object>> alterFn) { + HashMap, Object> copy = new HashMap<>(table); alterFn.accept(copy); return new HMap(copy); } @@ -184,8 +204,8 @@ public static HMap emptyHMap() { * @param the only mapped value type * @return a singleton HMap */ - public static HMap singletonHMap(TypeSafeKey key, V value) { - return new HMap(singletonMap(key, value)); + public static HMap singletonHMap(TypeSafeKey key, V value) { + return emptyHMap().put(key, value); } /** @@ -199,8 +219,8 @@ public static HMap singletonHMap(TypeSafeKey key, V value) { * @param value2's type * @return an HMap with the given associations */ - public static HMap hMap(TypeSafeKey key1, V1 value1, - TypeSafeKey key2, V2 value2) { + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2) { return singletonHMap(key1, value1).put(key2, value2); } @@ -218,9 +238,197 @@ public static HMap hMap(TypeSafeKey key1, V1 value1, * @param value3's type * @return an HMap with the given associations */ - public static HMap hMap(TypeSafeKey key1, V1 value1, - TypeSafeKey key2, V2 value2, - TypeSafeKey key3, V3 value3) { - return hMap(key1, value1, key2, value2).put(key3, value3); + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3) { + return hMap(key1, value1, + key2, value2) + .put(key3, value3); } + + /** + * Static factory method for creating an HMap from four given associations. + * + * @param key1 the first mapped key + * @param value1 the value mapped at key1 + * @param key2 the second mapped key + * @param value2 the value mapped at key2 + * @param key3 the third mapped key + * @param value3 the value mapped at key3 + * @param key4 the fourth mapped key + * @param value4 the value mapped at key4 + * @param value1's type + * @param value2's type + * @param value3's type + * @param value4's type + * @return an HMap with the given associations + */ + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3, + TypeSafeKey key4, V4 value4) { + return hMap(key1, value1, + key2, value2, + key3, value3) + .put(key4, value4); + } + + /** + * Static factory method for creating an HMap from five given associations. + * + * @param key1 the first mapped key + * @param value1 the value mapped at key1 + * @param key2 the second mapped key + * @param value2 the value mapped at key2 + * @param key3 the third mapped key + * @param value3 the value mapped at key3 + * @param key4 the fourth mapped key + * @param value4 the value mapped at key4 + * @param key5 the fifth mapped key + * @param value5 the value mapped at key5 + * @param value1's type + * @param value2's type + * @param value3's type + * @param value4's type + * @param value5's type + * @return an HMap with the given associations + */ + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3, + TypeSafeKey key4, V4 value4, + TypeSafeKey key5, V5 value5) { + return hMap(key1, value1, + key2, value2, + key3, value3, + key4, value4) + .put(key5, value5); + } + + /** + * Static factory method for creating an HMap from six given associations. + * + * @param key1 the first mapped key + * @param value1 the value mapped at key1 + * @param key2 the second mapped key + * @param value2 the value mapped at key2 + * @param key3 the third mapped key + * @param value3 the value mapped at key3 + * @param key4 the fourth mapped key + * @param value4 the value mapped at key4 + * @param key5 the fifth mapped key + * @param value5 the value mapped at key5 + * @param key6 the sixth mapped key + * @param value6 the value mapped at key6 + * @param value1's type + * @param value2's type + * @param value3's type + * @param value4's type + * @param value5's type + * @param value6's type + * @return an HMap with the given associations + */ + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3, + TypeSafeKey key4, V4 value4, + TypeSafeKey key5, V5 value5, + TypeSafeKey key6, V6 value6) { + return hMap(key1, value1, + key2, value2, + key3, value3, + key4, value4, + key5, value5) + .put(key6, value6); + } + + /** + * Static factory method for creating an HMap from seven given associations. + * + * @param key1 the first mapped key + * @param value1 the value mapped at key1 + * @param key2 the second mapped key + * @param value2 the value mapped at key2 + * @param key3 the third mapped key + * @param value3 the value mapped at key3 + * @param key4 the fourth mapped key + * @param value4 the value mapped at key4 + * @param key5 the fifth mapped key + * @param value5 the value mapped at key5 + * @param key6 the sixth mapped key + * @param value6 the value mapped at key6 + * @param key7 the seventh mapped key + * @param value7 the value mapped at key7 + * @param value1's type + * @param value2's type + * @param value3's type + * @param value4's type + * @param value5's type + * @param value6's type + * @param value7's type + * @return an HMap with the given associations + */ + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3, + TypeSafeKey key4, V4 value4, + TypeSafeKey key5, V5 value5, + TypeSafeKey key6, V6 value6, + TypeSafeKey key7, V7 value7) { + return hMap(key1, value1, + key2, value2, + key3, value3, + key4, value4, + key5, value5, + key6, value6) + .put(key7, value7); + } + + /** + * Static factory method for creating an HMap from eight given associations. + * + * @param key1 the first mapped key + * @param value1 the value mapped at key1 + * @param key2 the second mapped key + * @param value2 the value mapped at key2 + * @param key3 the third mapped key + * @param value3 the value mapped at key3 + * @param key4 the fourth mapped key + * @param value4 the value mapped at key4 + * @param key5 the fifth mapped key + * @param value5 the value mapped at key5 + * @param key6 the sixth mapped key + * @param value6 the value mapped at key6 + * @param key7 the seventh mapped key + * @param value7 the value mapped at key7 + * @param key8 the eighth mapped key + * @param value8 the value mapped at key8 + * @param value1's type + * @param value2's type + * @param value3's type + * @param value4's type + * @param value5's type + * @param value6's type + * @param value7's type + * @param value8's type + * @return an HMap with the given associations + */ + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3, + TypeSafeKey key4, V4 value4, + TypeSafeKey key5, V5 value5, + TypeSafeKey key6, V6 value6, + TypeSafeKey key7, V7 value7, + TypeSafeKey key8, V8 value8) { + return hMap(key1, value1, + key2, value2, + key3, value3, + key4, value4, + key5, value5, + key6, value6, + key7, value7) + .put(key8, value8); + } + } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java new file mode 100644 index 000000000..61c9b181a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java @@ -0,0 +1,245 @@ +package com.jnape.palatable.lambda.adt.hmap; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.adt.hlist.SingletonHList; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.adt.hlist.Tuple4; +import com.jnape.palatable.lambda.adt.hlist.Tuple5; +import com.jnape.palatable.lambda.adt.hlist.Tuple6; +import com.jnape.palatable.lambda.adt.hlist.Tuple7; +import com.jnape.palatable.lambda.adt.hlist.Tuple8; +import com.jnape.palatable.lambda.functions.builtin.fn2.Both; +import com.jnape.palatable.lambda.functor.Cartesian; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.optics.Lens; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.optics.lenses.HMapLens.valueAt; + +/** + * A lens that focuses on the {@link HList heterogeneous list} of values pointed at by one or more + * {@link TypeSafeKey typesafe keys} that must all exist in the same {@link HMap} to be collectively extracted. Note + * that if any of the keys is absent in the map, the result will be {@link Maybe#nothing()}. + * + * @param the {@link HList} of values to focus on + * @see TypeSafeKey + */ +public interface Schema extends Lens.Simple> { + + /** + * Add a new {@link TypeSafeKey} to the head of this {@link Schema}. + * + * @param key the new head key + * @param the value the head key focuses on + * @param the new {@link HCons} of values + * @return the updated {@link Schema} + */ + @SuppressWarnings({"unchecked", "RedundantTypeArguments"}) + default > Schema add(TypeSafeKey key) { + Lens, Maybe> lens = Lens.both(this, valueAt(key)) + .>mapA(into((maybeValues, maybeA) -> maybeValues + .zip(maybeA.fmap(a -> values -> (NewValues) values.cons(a))))) + .>mapB(Both.both(maybeNewValues -> maybeNewValues.fmap(HCons::tail), + maybeNewValues -> maybeNewValues.fmap(HCons::head))); + return new Schema() { + @Override + public >, + CoF extends Functor>, + FB extends Functor, ? extends CoF>, + FT extends Functor, + PAFB extends Profunctor, FB, ? extends CoP>, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return lens.apply(pafb); + } + }; + } + + /** + * Create a {@link Schema} from a single {@link TypeSafeKey}. + * + * @param key the {@link TypeSafeKey} + * @param the type of value the key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey key) { + Lens>, Maybe>> lens = valueAt(key) + .mapA(ma -> ma.fmap(HList::singletonHList)) + .mapB(maybeSingletonA -> maybeSingletonA.fmap(HCons::head)); + return new Schema>() { + @Override + public >, + CoF extends Functor>, + FB extends Functor>, ? extends CoF>, + FT extends Functor, + PAFB extends Profunctor>, FB, ? extends CoP>, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return lens.apply(pafb); + } + }; + } + + /** + * Create a {@link Schema} from two {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey) { + return schema(bKey).add(aKey); + } + + /** + * Create a {@link Schema} from three {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey) { + return schema(bKey, cKey).add(aKey); + } + + /** + * Create a {@link Schema} from four {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey, + TypeSafeKey dKey) { + return schema(bKey, cKey, dKey).add(aKey); + } + + /** + * Create a {@link Schema} from five {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey, + TypeSafeKey dKey, + TypeSafeKey eKey) { + return schema(bKey, cKey, dKey, eKey).add(aKey); + } + + /** + * Create a {@link Schema} from six {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param fKey the sixth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @param the type of value the sixth key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey, + TypeSafeKey dKey, + TypeSafeKey eKey, + TypeSafeKey fKey) { + return schema(bKey, cKey, dKey, eKey, fKey).add(aKey); + } + + /** + * Create a {@link Schema} from seven {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param fKey the sixth {@link TypeSafeKey} + * @param gKey the seventh {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @param the type of value the sixth key focuses on + * @param the type of value the seventh key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey, + TypeSafeKey dKey, + TypeSafeKey eKey, + TypeSafeKey fKey, + TypeSafeKey gKey) { + return schema(bKey, cKey, dKey, eKey, fKey, gKey).add(aKey); + } + + /** + * Create a {@link Schema} from eight {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param fKey the sixth {@link TypeSafeKey} + * @param gKey the seventh {@link TypeSafeKey} + * @param hKey the eighth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @param the type of value the sixth key focuses on + * @param the type of value the seventh key focuses on + * @param the type of value the eighth key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey, + TypeSafeKey dKey, + TypeSafeKey eKey, + TypeSafeKey fKey, + TypeSafeKey gKey, + TypeSafeKey hKey) { + return schema(bKey, cKey, dKey, eKey, fKey, gKey, hKey).add(aKey); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java index 576b87d5c..d023f37d8 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java @@ -1,25 +1,131 @@ package com.jnape.palatable.lambda.adt.hmap; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Optic; + +import java.util.Objects; + /** - * An interface representing a parametrized key for use in HMaps. The parameter specifies the type of the value stored - * at this binding inside the HMap. + * An interface representing a parametrized key for use in {@link HMap}s. Additionally, every {@link TypeSafeKey} is an + * {@link Iso} from the type the value is stored as to the type it's viewed and set as (on the way in / on the way out). + * This allows multiple keys to map to the same value, but to view the value as different types. *

+ * Particularly of note is the fact that values stored at this key are still stored as their original manifest + * type, and are not duplicated - which is to say, putting a value at a key, yielding a new key via composition, + * and putting a new value at the new key still only results in a single entry in the {@link HMap}. Additionally, + * all previous keys involved in the new key's composition are still able to resolve the value in their native type. + * + * @param f the other simple iso + * @param the new value type + * @return the new {@link TypeSafeKey} + */ + @Override + default TypeSafeKey andThen(Iso.Simple f) { + Iso.Simple composed = Iso.Simple.super.andThen(f); + return new TypeSafeKey() { + @Override + public >, + CoF extends Functor>, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return composed.apply(pafb); + } + + @Override + public int hashCode() { + return TypeSafeKey.this.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return TypeSafeKey.this.equals(obj); + } + }; + } /** - * Static factory method for creating a unique type-safe key + * Static factory method for creating a simple type-safe key * - * @param the type of value stored at this key + * @param the type of value stored at this key * @return a unique type-safe key */ - static TypeSafeKey typeSafeKey() { - return new TypeSafeKey() { + static Simple typeSafeKey() { + return new TypeSafeKey.Simple() { + @Override + public boolean equals(Object obj) { + return obj instanceof Simple ? this == obj : Objects.equals(obj, this); + } + + @Override + public int hashCode() { + return super.hashCode(); + } }; } -} + + /** + * A simplified {@link TypeSafeKey} that can only view a value of type A as an A. + * + * @param The type of the value that this key maps to inside an {@link HMap} + */ + interface Simple extends TypeSafeKey { + + @Override + @SuppressWarnings("unchecked") + default >, + CoF extends Functor>, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply( + PAFB pafb) { + return (PSFT) pafb; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product2.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product2.java new file mode 100644 index 000000000..0a76df761 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product2.java @@ -0,0 +1,92 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.Map; + +/** + * The minimal shape of the combination of two potentially distinctly typed values, supporting destructuring via + * explicitly named indexing methods, as well as via a combining function. + *

+ * For more information, read about products. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @see Tuple2 + */ +public interface Product2<_1, _2> extends Map.Entry<_1, _2> { + + /** + * Retrieve the first element. + * + * @return the first element + */ + _1 _1(); + + /** + * Retrieve the second element. + * + * @return the second element + */ + _2 _2(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param the return type of the function + * @param fn the function to apply + * @return the result of applying the destructured product to the function + */ + default R into(Fn2 fn) { + return fn.apply(_1(), _2()); + } + + /** + * Rotate the first two slots of this product. + * + * @return the rotated product + */ + default Product2<_2, _1> invert() { + return into((_1, _2) -> product(_2, _1)); + } + + @Override + default _1 getKey() { + return _1(); + } + + @Override + default _2 getValue() { + return _2(); + } + + @Override + default _2 setValue(_2 value) { + throw new UnsupportedOperationException(); + } + + /** + * Static factory method for creating a generic {@link Product2}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @return the {@link Product2} + */ + static <_1, _2> Product2<_1, _2> product(_1 _1, _2 _2) { + return new Product2<_1, _2>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product3.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product3.java new file mode 100644 index 000000000..18da0491f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product3.java @@ -0,0 +1,88 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.functions.Fn3; + +/** + * A product with three values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @see Product2 + * @see Tuple3 + */ +public interface Product3<_1, _2, _3> extends Product2<_1, _2> { + + /** + * Retrieve the third element. + * + * @return the third element + */ + _3 _3(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into(Fn3 fn) { + return Product2.super.into(fn).apply(_3()); + } + + /** + * Rotate the first three values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product3<_2, _3, _1> rotateL3() { + return into((_1, _2, _3) -> product(_2, _3, _1)); + } + + /** + * Rotate the first three values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product3<_3, _1, _2> rotateR3() { + return into((_1, _2, _3) -> product(_3, _1, _2)); + } + + @Override + default Product3<_2, _1, _3> invert() { + return into((_1, _2, _3) -> product(_2, _1, _3)); + } + + /** + * Static factory method for creating a generic {@link Product3}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @return the {@link Product3} + */ + static <_1, _2, _3> Product3<_1, _2, _3> product(_1 _1, _2 _2, _3 _3) { + return new Product3<_1, _2, _3>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product4.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product4.java new file mode 100644 index 000000000..87a33376d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product4.java @@ -0,0 +1,107 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple4; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn4; + +/** + * A product with four values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @param <_4> The fourth element type + * @see Product2 + * @see Tuple4 + */ +public interface Product4<_1, _2, _3, _4> extends Product3<_1, _2, _3> { + + /** + * Retrieve the fourth element. + * + * @return the fourth element + */ + _4 _4(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into(Fn4 fn) { + return Product3.super.>into(fn).apply(_4()); + } + + /** + * Rotate the first four values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product4<_2, _3, _4, _1> rotateL4() { + return into((_1, _2, _3, _4) -> product(_2, _3, _4, _1)); + } + + /** + * Rotate the first four values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product4<_4, _1, _2, _3> rotateR4() { + return into((_1, _2, _3, _4) -> product(_4, _1, _2, _3)); + } + + @Override + default Product4<_2, _3, _1, _4> rotateL3() { + return into((_1, _2, _3, _4) -> product(_2, _3, _1, _4)); + } + + @Override + default Product4<_3, _1, _2, _4> rotateR3() { + return into((_1, _2, _3, _4) -> product(_3, _1, _2, _4)); + } + + @Override + default Product4<_2, _1, _3, _4> invert() { + return into((_1, _2, _3, _4) -> product(_2, _1, _3, _4)); + } + + /** + * Static factory method for creating a generic {@link Product4}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param _4 the fourth slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @param <_4> the fourth slot type + * @return the {@link Product4} + */ + static <_1, _2, _3, _4> Product4<_1, _2, _3, _4> product(_1 _1, _2 _2, _3 _3, _4 _4) { + return new Product4<_1, _2, _3, _4>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + + @Override + public _4 _4() { + return _4; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product5.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product5.java new file mode 100644 index 000000000..864671bbe --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product5.java @@ -0,0 +1,125 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple5; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn5; + +/** + * A product with five values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @param <_4> The fourth element type + * @param <_5> The fifth element type + * @see Product2 + * @see Tuple5 + */ +public interface Product5<_1, _2, _3, _4, _5> extends Product4<_1, _2, _3, _4> { + + /** + * Retrieve the fifth element. + * + * @return the fifth element + */ + _5 _5(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into(Fn5 fn) { + return Product4.super.>into(fn).apply(_5()); + } + + /** + * Rotate the first five values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product5<_2, _3, _4, _5, _1> rotateL5() { + return into((_1, _2, _3, _4, _5) -> product(_2, _3, _4, _5, _1)); + } + + /** + * Rotate the first five values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product5<_5, _1, _2, _3, _4> rotateR5() { + return into((_1, _2, _3, _4, _5) -> product(_5, _1, _2, _3, _4)); + } + + @Override + default Product5<_2, _3, _4, _1, _5> rotateL4() { + return into((_1, _2, _3, _4, _5) -> product(_2, _3, _4, _1, _5)); + } + + @Override + default Product5<_4, _1, _2, _3, _5> rotateR4() { + return into((_1, _2, _3, _4, _5) -> product(_4, _1, _2, _3, _5)); + } + + @Override + default Product5<_2, _3, _1, _4, _5> rotateL3() { + return into((_1, _2, _3, _4, _5) -> product(_2, _3, _1, _4, _5)); + } + + @Override + default Product5<_3, _1, _2, _4, _5> rotateR3() { + return into((_1, _2, _3, _4, _5) -> product(_3, _1, _2, _4, _5)); + } + + @Override + default Product5<_2, _1, _3, _4, _5> invert() { + return into((_1, _2, _3, _4, _5) -> product(_2, _1, _3, _4, _5)); + } + + /** + * Static factory method for creating a generic {@link Product5}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param _4 the fourth slot + * @param _5 the fifth slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @param <_4> the fourth slot type + * @param <_5> the fifth slot type + * @return the {@link Product5} + */ + static <_1, _2, _3, _4, _5> Product5<_1, _2, _3, _4, _5> product(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5) { + return new Product5<_1, _2, _3, _4, _5>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + + @Override + public _4 _4() { + return _4; + } + + @Override + public _5 _5() { + return _5; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product6.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product6.java new file mode 100644 index 000000000..2ea16a9a5 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product6.java @@ -0,0 +1,144 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple6; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn6; + +/** + * A product with six values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @param <_4> The fourth element type + * @param <_5> The fifth element type + * @param <_6> The sixth element type + * @see Product2 + * @see Tuple6 + */ +public interface Product6<_1, _2, _3, _4, _5, _6> extends Product5<_1, _2, _3, _4, _5> { + + /** + * Retrieve the sixth element. + * + * @return the sixth element + */ + _6 _6(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into(Fn6 fn) { + return Product5.super.>into(fn).apply(_6()); + } + + /** + * Rotate the first six values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product6<_2, _3, _4, _5, _6, _1> rotateL6() { + return into((_1, _2, _3, _4, _5, _6) -> product(_2, _3, _4, _5, _6, _1)); + } + + /** + * Rotate the first six values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product6<_6, _1, _2, _3, _4, _5> rotateR6() { + return into((_1, _2, _3, _4, _5, _6) -> product(_6, _1, _2, _3, _4, _5)); + } + + @Override + default Product6<_2, _3, _4, _5, _1, _6> rotateL5() { + return into((_1, _2, _3, _4, _5, _6) -> product(_2, _3, _4, _5, _1, _6)); + } + + @Override + default Product6<_5, _1, _2, _3, _4, _6> rotateR5() { + return into((_1, _2, _3, _4, _5, _6) -> product(_5, _1, _2, _3, _4, _6)); + } + + @Override + default Product6<_2, _3, _4, _1, _5, _6> rotateL4() { + return into((_1, _2, _3, _4, _5, _6) -> product(_2, _3, _4, _1, _5, _6)); + } + + @Override + default Product6<_4, _1, _2, _3, _5, _6> rotateR4() { + return into((_1, _2, _3, _4, _5, _6) -> product(_4, _1, _2, _3, _5, _6)); + } + + @Override + default Product6<_2, _3, _1, _4, _5, _6> rotateL3() { + return into((_1, _2, _3, _4, _5, _6) -> product(_2, _3, _1, _4, _5, _6)); + } + + @Override + default Product6<_3, _1, _2, _4, _5, _6> rotateR3() { + return into((_1, _2, _3, _4, _5, _6) -> product(_3, _1, _2, _4, _5, _6)); + } + + @Override + default Product6<_2, _1, _3, _4, _5, _6> invert() { + return into((_1, _2, _3, _4, _5, _6) -> product(_2, _1, _3, _4, _5, _6)); + } + + /** + * Static factory method for creating a generic {@link Product6}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param _4 the fourth slot + * @param _5 the fifth slot + * @param _6 the sixth slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @param <_4> the fourth slot type + * @param <_5> the fifth slot type + * @param <_6> the sixth slot type + * @return the {@link Product6} + */ + static <_1, _2, _3, _4, _5, _6> Product6<_1, _2, _3, _4, _5, _6> product(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, + _6 _6) { + return new Product6<_1, _2, _3, _4, _5, _6>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + + @Override + public _4 _4() { + return _4; + } + + @Override + public _5 _5() { + return _5; + } + + @Override + public _6 _6() { + return _6; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product7.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product7.java new file mode 100644 index 000000000..75b9fc1d8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product7.java @@ -0,0 +1,163 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple7; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn7; + +/** + * A product with seven values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @param <_4> The fourth element type + * @param <_5> The fifth element type + * @param <_6> The sixth element type + * @param <_7> The seventh element type + * @see Product2 + * @see Tuple7 + */ +public interface Product7<_1, _2, _3, _4, _5, _6, _7> extends Product6<_1, _2, _3, _4, _5, _6> { + + /** + * Retrieve the seventh element. + * + * @return the seventh element + */ + _7 _7(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into( + Fn7 fn) { + return Product6.super.>into(fn).apply(_7()); + } + + /** + * Rotate the first seven values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product7<_2, _3, _4, _5, _6, _7, _1> rotateL7() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _3, _4, _5, _6, _7, _1)); + } + + /** + * Rotate the first seven values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product7<_7, _1, _2, _3, _4, _5, _6> rotateR7() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_7, _1, _2, _3, _4, _5, _6)); + } + + @Override + default Product7<_2, _3, _4, _5, _6, _1, _7> rotateL6() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _3, _4, _5, _6, _1, _7)); + } + + @Override + default Product7<_6, _1, _2, _3, _4, _5, _7> rotateR6() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_6, _1, _2, _3, _4, _5, _7)); + } + + @Override + default Product7<_2, _3, _4, _5, _1, _6, _7> rotateL5() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _3, _4, _5, _1, _6, _7)); + } + + @Override + default Product7<_5, _1, _2, _3, _4, _6, _7> rotateR5() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_5, _1, _2, _3, _4, _6, _7)); + } + + @Override + default Product7<_2, _3, _4, _1, _5, _6, _7> rotateL4() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _3, _4, _1, _5, _6, _7)); + } + + @Override + default Product7<_4, _1, _2, _3, _5, _6, _7> rotateR4() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_4, _1, _2, _3, _5, _6, _7)); + } + + @Override + default Product7<_2, _3, _1, _4, _5, _6, _7> rotateL3() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _3, _1, _4, _5, _6, _7)); + } + + @Override + default Product7<_3, _1, _2, _4, _5, _6, _7> rotateR3() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_3, _1, _2, _4, _5, _6, _7)); + } + + @Override + default Product7<_2, _1, _3, _4, _5, _6, _7> invert() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _1, _3, _4, _5, _6, _7)); + } + + /** + * Static factory method for creating a generic {@link Product7}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param _4 the fourth slot + * @param _5 the fifth slot + * @param _6 the sixth slot + * @param _7 the seventh slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @param <_4> the fourth slot type + * @param <_5> the fifth slot type + * @param <_6> the sixth slot type + * @param <_7> the seventh slot type + * @return the {@link Product7} + */ + static <_1, _2, _3, _4, _5, _6, _7> Product7<_1, _2, _3, _4, _5, _6, _7> product(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, + _6 _6, _7 _7) { + return new Product7<_1, _2, _3, _4, _5, _6, _7>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + + @Override + public _4 _4() { + return _4; + } + + @Override + public _5 _5() { + return _5; + } + + @Override + public _6 _6() { + return _6; + } + + @Override + public _7 _7() { + return _7; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product8.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product8.java new file mode 100644 index 000000000..2b51e1bf0 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product8.java @@ -0,0 +1,182 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple8; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn8; + +/** + * A product with eight values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @param <_4> The fourth element type + * @param <_5> The fifth element type + * @param <_6> The sixth element type + * @param <_7> The seventh element type + * @param <_8> The eighth element type + * @see Product2 + * @see Tuple8 + */ +public interface Product8<_1, _2, _3, _4, _5, _6, _7, _8> extends Product7<_1, _2, _3, _4, _5, _6, _7> { + + /** + * Retrieve the eighth element. + * + * @return the eighth element + */ + _8 _8(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into( + Fn8 fn) { + return Product7.super.>into(fn).apply(_8()); + } + + /** + * Rotate all eight values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product8<_2, _3, _4, _5, _6, _7, _8, _1> rotateL8() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _4, _5, _6, _7, _8, _1)); + } + + /** + * Rotate all eight values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product8<_8, _1, _2, _3, _4, _5, _6, _7> rotateR8() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_8, _1, _2, _3, _4, _5, _6, _7)); + } + + @Override + default Product8<_2, _3, _4, _5, _6, _7, _1, _8> rotateL7() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _4, _5, _6, _7, _1, _8)); + } + + @Override + default Product8<_7, _1, _2, _3, _4, _5, _6, _8> rotateR7() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_7, _1, _2, _3, _4, _5, _6, _8)); + } + + @Override + default Product8<_2, _3, _4, _5, _6, _1, _7, _8> rotateL6() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _4, _5, _6, _1, _7, _8)); + } + + @Override + default Product8<_6, _1, _2, _3, _4, _5, _7, _8> rotateR6() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_6, _1, _2, _3, _4, _5, _7, _8)); + } + + @Override + default Product8<_2, _3, _4, _5, _1, _6, _7, _8> rotateL5() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _4, _5, _1, _6, _7, _8)); + } + + @Override + default Product8<_5, _1, _2, _3, _4, _6, _7, _8> rotateR5() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_5, _1, _2, _3, _4, _6, _7, _8)); + } + + @Override + default Product8<_2, _3, _4, _1, _5, _6, _7, _8> rotateL4() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _4, _1, _5, _6, _7, _8)); + } + + @Override + default Product8<_4, _1, _2, _3, _5, _6, _7, _8> rotateR4() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_4, _1, _2, _3, _5, _6, _7, _8)); + } + + @Override + default Product8<_2, _3, _1, _4, _5, _6, _7, _8> rotateL3() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _1, _4, _5, _6, _7, _8)); + } + + @Override + default Product8<_3, _1, _2, _4, _5, _6, _7, _8> rotateR3() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_3, _1, _2, _4, _5, _6, _7, _8)); + } + + @Override + default Product8<_2, _1, _3, _4, _5, _6, _7, _8> invert() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _1, _3, _4, _5, _6, _7, _8)); + } + + /** + * Static factory method for creating a generic {@link Product8}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param _4 the fourth slot + * @param _5 the fifth slot + * @param _6 the sixth slot + * @param _7 the seventh slot + * @param _8 the eighth slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @param <_4> the fourth slot type + * @param <_5> the fifth slot type + * @param <_6> the sixth slot type + * @param <_7> the seventh slot type + * @param <_8> the eighth slot type + * @return the {@link Product8} + */ + static <_1, _2, _3, _4, _5, _6, _7, _8> Product8<_1, _2, _3, _4, _5, _6, _7, _8> product(_1 _1, _2 _2, _3 _3, _4 _4, + _5 _5, _6 _6, _7 _7, + _8 _8) { + return new Product8<_1, _2, _3, _4, _5, _6, _7, _8>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + + @Override + public _4 _4() { + return _4; + } + + @Override + public _5 _5() { + return _5; + } + + @Override + public _6 _6() { + return _6; + } + + @Override + public _7 _7() { + return _7; + } + + @Override + public _8 _8() { + return _8; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Effect.java b/src/main/java/com/jnape/palatable/lambda/functions/Effect.java new file mode 100644 index 000000000..648c42eab --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/Effect.java @@ -0,0 +1,126 @@ +package com.jnape.palatable.lambda.functions; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.specialized.SideEffect; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.internal.Runtime; +import com.jnape.palatable.lambda.io.IO; + +import java.util.function.Consumer; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.specialized.SideEffect.NOOP; +import static com.jnape.palatable.lambda.io.IO.io; + +/** + * A function returning "no result", and therefore only useful as a side-effect. + * + * @param the argument type + * @see Fn0 + */ +@FunctionalInterface +public interface Effect extends Fn1> { + + @Override + IO checkedApply(A a) throws Throwable; + + /** + * Convert this {@link Effect} to a java {@link Consumer} + * + * @return the {@link Consumer} + */ + default Consumer toConsumer() { + return a -> apply(a).unsafePerformIO(); + } + + /** + * {@inheritDoc} + */ + @Override + default IO apply(A a) { + try { + return checkedApply(a); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * Left-to-right composition of {@link Effect Effects}. + * + * @param effect the other {@link Effect} + * @return the composed {@link Effect} + */ + default Effect andThen(Effect effect) { + return a -> apply(a).flatMap(constantly(effect.apply(a))); + } + + /** + * {@inheritDoc} + */ + @Override + default Effect diMapL(Fn1 fn) { + return effect(Fn1.super.diMapL(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Effect contraMap(Fn1 fn) { + return effect(Fn1.super.contraMap(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Effect discardR(Applicative> appB) { + return effect(Fn1.super.discardR(appB)); + } + + /** + * Static factory method to create an {@link Effect} from a java {@link Consumer}. + * + * @param consumer the {@link Consumer} + * @param the input type + * @return the {@link Effect} + */ + static Effect fromConsumer(Consumer consumer) { + return a -> io(() -> consumer.accept(a)); + } + + /** + * Create an {@link Effect} from a {@link SideEffect}; + * + * @param sideEffect the {@link SideEffect} + * @param any desired input type + * @return the {@link Effect} + */ + static Effect effect(SideEffect sideEffect) { + return effect(constantly(io(sideEffect))); + } + + /** + * Create an {@link Effect} that accepts an input and does nothing; + * + * @param any desired input type + * @return the noop {@link Effect} + */ + @SuppressWarnings("unused") + static Effect noop() { + return effect(NOOP); + } + + /** + * Create an {@link Effect} from an {@link Fn1} that yields an {@link IO}. + * + * @param fn the function + * @param the effect argument type + * @return the effect + */ + static Effect effect(Fn1> fn) { + return fn.fmap(io -> io.fmap(constantly(UNIT)))::apply; + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn0.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn0.java new file mode 100644 index 000000000..3352046fe --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn0.java @@ -0,0 +1,143 @@ +package com.jnape.palatable.lambda.functions; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; + +import java.util.concurrent.Callable; +import java.util.function.Supplier; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; + +/** + * A function taking "no arguments", implemented as an {@link Fn1}<{@link Unit}, A>. + * + * @param the result type + * @see Fn1 + * @see Callable + */ +@FunctionalInterface +public interface Fn0 extends Fn1 { + + A checkedApply() throws Throwable; + + /** + * Convenience method for applying this {@link Fn0} without providing an explicit {@link Unit}. + * + * @return the result + */ + default A apply() { + return apply(UNIT); + } + + /** + * Convert this {@link Fn0} to a java {@link Supplier} + * + * @return the {@link Supplier} + */ + default Supplier toSupplier() { + return this::apply; + } + + /** + * Convert this {@link Fn0} to a java {@link Callable} + * + * @return the {@link Callable} + */ + default Callable toCallable() { + return this::apply; + } + + /** + * {@inheritDoc} + */ + @Override + default A checkedApply(Unit unit) throws Throwable { + return checkedApply(); + } + + @Override + default Fn0 flatMap(Fn1>> f) { + return Fn1.super.flatMap(f).thunk(UNIT); + } + + @Override + default Fn0 fmap(Fn1 f) { + return Fn1.super.fmap(f).thunk(UNIT); + } + + @Override + default Fn0 pure(B b) { + return Fn1.super.pure(b).thunk(UNIT); + } + + @Override + default Fn0 zip(Applicative, Fn1> appFn) { + return Fn1.super.zip(appFn).thunk(UNIT); + } + + @Override + default Fn0 zip(Fn2 appFn) { + return Fn1.super.zip(appFn).thunk(UNIT); + } + + @Override + default Fn0 discardL(Applicative> appB) { + return Fn1.super.discardL(appB).thunk(UNIT); + } + + @Override + default Fn0 discardR(Applicative> appB) { + return Fn1.super.discardR(appB).thunk(UNIT); + } + + @Override + default Fn0 diMapR(Fn1 fn) { + return Fn1.super.diMapR(fn).thunk(UNIT); + } + + /** + * Convenience method for converting a {@link Supplier} to an {@link Fn0}. + * + * @param supplier the supplier + * @param the output type + * @return the {@link Fn0} + */ + static Fn0 fromSupplier(Supplier supplier) { + return supplier::get; + } + + /** + * Convenience method for converting a {@link Callable} to an {@link Fn0}. + * + * @param callable the callable + * @param the output type + * @return the {@link Fn0} + */ + static Fn0 fromCallable(Callable callable) { + return callable::call; + } + + /** + * Static factory method for coercing a lambda to an {@link Fn0}. + * + * @param fn the lambda to coerce + * @param the output type + * @return the {@link Fn0} + */ + static Fn0 fn0(Fn0 fn) { + return fn; + } + + /** + * Static factory method for adapting an {@link Fn1}<Unit, A> to an + * {@link Fn0}<A>. + * + * @param fn the {@link Fn1} + * @param the output type + * @return the {@link Fn0} + */ + static Fn0 fn0(Fn1 fn) { + return fn0(() -> fn.apply(UNIT)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java index ccb0eafe3..cb4904d35 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -1,10 +1,27 @@ package com.jnape.palatable.lambda.functions; -import com.jnape.palatable.lambda.functor.Functor; -import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.builtin.fn1.Constantly; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Cartesian; +import com.jnape.palatable.lambda.functor.Cocartesian; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.internal.Runtime; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadReader; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.MonadWriter; import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.Fn2.curried; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; + /** * A function taking a single argument. This is the core function type that all other function types extend and * auto-curry with. @@ -13,52 +30,177 @@ * @param The result type */ @FunctionalInterface -public interface Fn1 extends Functor, Profunctor, Function { +public interface Fn1 extends + MonadRec>, + MonadReader>, + MonadWriter>, + Cartesian>, + Cocartesian> { /** - * Invoke this function with the given argument. + * Invoke this function explosively with the given argument. * * @param a the argument * @return the result of the function application */ - B apply(A a); + default B apply(A a) { + try { + return checkedApply(a); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } /** - * Left-to-right composition, such that g.then(f).apply(x) is equivalent to - * f.apply(g.apply(x)). + * Invoke this function with the given argument, potentially throwing any {@link Throwable}. * - * @param f the function to invoke with this function's return value - * @param the return type of the next function to invoke - * @return a function representing the composition of this function and f + * @param a the argument + * @return the result of the function application + * @throws Throwable anything possibly thrown by the function + */ + B checkedApply(A a) throws Throwable; + + /** + * Convert this {@link Fn1} to an {@link Fn0} by supplying an argument to this function. Useful for fixing an + * argument now, but deferring application until a later time. + * + * @param a the argument + * @return an {@link Fn0} + */ + default Fn0 thunk(A a) { + return () -> apply(a); + } + + /** + * Widen this function's argument list by prepending an ignored argument of any type to the front. + * + * @param the new first argument type + * @return the widened function + */ + default Fn2 widen() { + return curried(constantly(this)); + } + + /** + * Convert this {@link Fn1} to a java {@link Function}. + * + * @return the {@link Function} + */ + default Function toFunction() { + return this::apply; + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 local(Fn1 fn) { + return contraMap(fn); + } + + /** + * {@inheritDoc} */ - default Fn1 then(Function f) { - return fmap(f); + @Override + default Fn1> listens(Fn1 fn) { + return carry().fmap(t -> t.biMapL(fn).invert()); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 censor(Fn1 fn) { + return a -> apply(fn.apply(a)); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 flatMap(Fn1>> f) { + return a -> f.apply(apply(a)).>coerce().apply(a); } /** - * Also left-to-right composition (sadly). + * Left-to-right composition. * * @param the return type of the next function to invoke * @param f the function to invoke with this function's return value * @return a function representing the composition of this function and f - * @see Fn1#then(Function) */ @Override - default Fn1 fmap(Function f) { + default Fn1 fmap(Fn1 f) { return a -> f.apply(apply(a)); } + /** + * {@inheritDoc} + */ + @Override + default Fn1 pure(C c) { + return __ -> c; + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 zip(Applicative, Fn1> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + default Fn1 zip(Fn2 appFn) { + return zip((Fn1>) (Object) appFn); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip(Lazy, Fn1>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 trampolineM(Fn1, Fn1>> fn) { + return a -> trampoline(b -> fn.apply(b).>>coerce().apply(a), apply(a)); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + /** * Contravariantly map over the argument to this function, producing a function that takes the new argument type, * and produces the same result. * * @param the new argument type * @param fn the contravariant argument mapping function - * @return a new function from Z (the new argument type) to B (the same result) + * @return an {@link Fn1}<Z, B> */ @Override - default Fn1 diMapL(Function fn) { - return (Fn1) Profunctor.super.diMapL(fn); + default Fn1 diMapL(Fn1 fn) { + return (Fn1) Cartesian.super.diMapL(fn); } /** @@ -67,11 +209,11 @@ default Fn1 diMapL(Function fn) { * * @param the new result type * @param fn the covariant result mapping function - * @return a new function from A (the same argument type) to C (the new result type) + * @return an {@link Fn1}<A, C> */ @Override - default Fn1 diMapR(Function fn) { - return (Fn1) Profunctor.super.diMapR(fn); + default Fn1 diMapR(Fn1 fn) { + return (Fn1) Cartesian.super.diMapR(fn); } /** @@ -81,49 +223,133 @@ default Fn1 diMapR(Function fn) { * @param the new result type * @param lFn the contravariant argument mapping function * @param rFn the covariant result mapping function - * @return a new function from Z (the new argument type) to C (the new result type) + * @return an {@link Fn1}<Z, C> */ @Override - default Fn1 diMap(Function lFn, Function rFn) { - return lFn.andThen(this).andThen(rFn)::apply; + default Fn1 diMap(Fn1 lFn, Fn1 rFn) { + return lFn.fmap(this).fmap(rFn)::apply; } /** - * Override of {@link Function#compose(Function)}, returning an instance of Fn1 for compatibility. - * Right-to-left composition. + * Pair a value with the input to this function, and preserve the paired value through to the output. * - * @param before the function who's return value is this function's argument - * @param the new argument type - * @return a new function from Z (the new argument type) to B (the same result type) + * @param the paired value + * @return the strengthened {@link Fn1} */ @Override - default Fn1 compose(Function before) { - return z -> apply(before.apply(z)); + default Fn1, Tuple2> cartesian() { + return t -> t.fmap(this); } /** - * Override of {@link Function#andThen(Function)}, returning an instance of Fn1 for compatibility. - * Left-to-right composition. + * {@inheritDoc} + */ + @Override + default Fn1> carry() { + return (Fn1>) Cartesian.super.carry(); + } + + /** + * Choose between either applying this function or returning back a different result altogether. * - * @param after the function to invoke on this function's return value - * @param the new result type - * @return a new function from A (the same argument type) to C (the new result type) + * @param the potentially different result + * @return teh strengthened {@link Fn1} */ @Override - default Fn1 andThen(Function after) { - return a -> after.apply(apply(a)); + default Fn1, Choice2> cocartesian() { + return a -> a.fmap(this); } /** - * Static factory method for wrapping a {@link Function} in an {@link Fn1}. Useful for avoid explicit casting when - * using method references as {@link Fn1}s. + * Choose between a successful result b or returning back the input, a. * - * @param function the function to adapt - * @param the input argument type + * @return an {@link Fn1} that chooses between its input (in case of failure) or its output. + */ + @Override + default Fn1> choose() { + return a -> Either.trying(() -> apply(a), constantly(a)).match(Choice2::a, Choice2::b); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 contraMap(Fn1 fn) { + return (Fn1) Cartesian.super.contraMap(fn); + } + + /** + * Right-to-left composition between different arity functions. Preserves highest arity in the return type. + * + * @param before the function to pass its return value to this function's input + * @param the resulting function's first argument type + * @param the resulting function's second argument type + * @return an {@link Fn2}<Y, Z, B> + */ + default Fn2 compose(Fn2 before) { + return curried(before.fmap(this::contraMap))::apply; + } + + /** + * Left-to-right composition between different arity functions. Preserves highest arity in the return type. + * + * @param after the function to invoke on this function's return value + * @param the resulting function's second argument type + * @param the resulting function's return type + * @return an {@link Fn2}<A, C, D> + */ + default Fn2 andThen(Fn2 after) { + return (a, c) -> after.apply(apply(a), c); + } + + default Fn1 self() { + return this; + } + + /** + * Static factory method for avoid explicit casting when using method references as {@link Fn1}s. + * + * @param fn the function to adapt + * @param the input type + * @param the output type + * @return the {@link Fn1} + */ + static Fn1 fn1(Fn1 fn) { + return fn::apply; + } + + /** + * Static factory method for wrapping a java {@link Function} in an {@link Fn1}. + * + * @param function the function + * @param the input type * @param the output type - * @return the Fn1 + * @return the {@link Fn1} */ - static Fn1 adapt(Function function) { + static Fn1 fromFunction(Function function) { return function::apply; } + + /** + * The canonical {@link Pure} instance for {@link Fn1}. + * + * @param the input type + * @return the {@link Pure} instance + */ + static Pure> pureFn1() { + return Constantly::constantly; + } + + /** + * Construct an {@link Fn1} that has a reference to itself in scope at the time it is executed (presumably for + * recursive invocations). + * + * @param fn the body of the function, with access to itself + * @param the input type + * @param the output type + * @return the {@link Fn1} + */ + static Fn1 withSelf(Fn2, ? super A, ? extends B> fn) { + return a -> fn.apply(withSelf(fn), a); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java index c449fb2aa..0da885cdf 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java @@ -1,13 +1,21 @@ package com.jnape.palatable.lambda.functions; import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.internal.Runtime; import java.util.function.BiFunction; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn3.fn3; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + /** - * A function taking two arguments. Note that defining Fn2 in terms of Fn1 provides a - * reasonable approximation of currying in the form of multiple apply overloads that take different numbers - * of arguments. + * A function taking two arguments. + *

+ * Note that defining {@link Fn2} in terms of Fn1 provides a reasonable approximation of currying in the + * form of multiple {@link Fn2#apply} overloads that take different numbers of arguments. * * @param The first argument type * @param The second argument type @@ -17,6 +25,8 @@ @FunctionalInterface public interface Fn2 extends Fn1> { + C checkedApply(A a, B b) throws Throwable; + /** * Invoke this function with the given arguments. * @@ -24,13 +34,35 @@ public interface Fn2 extends Fn1> { * @param b the second argument * @return the result of the function application */ - C apply(A a, B b); + default C apply(A a, B b) { + try { + return checkedApply(a, b); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 checkedApply(A a) throws Throwable { + return b -> checkedApply(a, b); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn3 widen() { + return fn3(constantly(this)); + } /** * Partially apply this function by passing its first argument. * * @param a the first argument - * @return an Fn1 that takes the second argument and returns the result + * @return an {@link Fn1}<B, C> */ @Override default Fn1 apply(A a) { @@ -40,25 +72,25 @@ default Fn1 apply(A a) { /** * Flip the order of the arguments. * - * @return an Fn2 that takes the first and second arguments in reversed order + * @return an {@link Fn2}<B, A, C> */ default Fn2 flip() { return (b, a) -> apply(a, b); } /** - * Returns an Fn1 that takes the arguments as a Tuple2<A, B>. + * Returns an {@link Fn1} that takes the arguments as a {@link Product2}<A, B>. * - * @return an Fn1 taking a Tuple2 + * @return an {@link Fn1} taking a {@link Product2} */ - default Fn1, C> uncurry() { + default Fn1, C> uncurry() { return (ab) -> apply(ab._1(), ab._2()); } /** - * View this Fn2 as a j.u.f.BiFunction. + * View this {@link Fn2} as a {@link BiFunction}. * - * @return the same logic as a BiFunction + * @return the same logic as a {@link BiFunction} * @see BiFunction */ default BiFunction toBiFunction() { @@ -66,16 +98,86 @@ default BiFunction toBiFunction() { } /** - * Static factory method for wrapping a {@link BiFunction} in an {@link Fn2}. Useful for avoid explicit casting when - * using method references as {@link Fn2}s. + * {@inheritDoc} + */ + @Override + default Fn2 discardR(Applicative> appB) { + return curried(Fn1.super.discardR(appB)); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn2 diMapL(Fn1 fn) { + return curried(Fn1.super.diMapL(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn2 contraMap(Fn1 fn) { + return curried(Fn1.super.contraMap(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn3 compose(Fn2 before) { + return fn3(Fn1.super.compose(before)); + } + + /** + * Static factory method for wrapping a {@link BiFunction} in an {@link Fn2}. * * @param biFunction the biFunction to adapt * @param the first input argument type * @param the second input argument type * @param the output type - * @return the Fn2 + * @return the {@link Fn2} */ - static Fn2 adapt(BiFunction biFunction) { + static Fn2 fromBiFunction(BiFunction biFunction) { return biFunction::apply; } + + /** + * Static factory method for wrapping a curried {@link Fn1} in an {@link Fn2}. + * + * @param curriedFn1 the curried fn1 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the output type + * @return the {@link Fn2} + */ + static Fn2 curried(Fn1> curriedFn1) { + return (a, b) -> curriedFn1.apply(a).apply(b); + } + + /** + * Static factory method for wrapping an uncurried {@link Fn1} in an {@link Fn2}. + * + * @param uncurriedFn1 the uncurried {@link Fn1} to adapt + * @param the first input argument type + * @param the second input argument type + * @param the output type + * @return the {@link Fn2} + */ + static Fn2 curry(Fn1, ? extends C> uncurriedFn1) { + return (a, b) -> uncurriedFn1.apply(tuple(a, b)); + } + + /** + * Static method to aid inference. + * + * @param fn2 the {@link Fn2} + * @param the first input type + * @param the second input type + * @param the output type + * @return the {@link Fn2} + */ + static Fn2 fn2(Fn2 fn2) { + return fn2; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn3.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn3.java index 7e21f9d79..881d848ff 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn3.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn3.java @@ -1,9 +1,14 @@ package com.jnape.palatable.lambda.functions; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.internal.Runtime; + +import static com.jnape.palatable.lambda.functions.Fn4.fn4; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** - * A function taking three arguments. Defined in terms of Fn2, so similarly auto-curried. + * A function taking three arguments. Defined in terms of {@link Fn2}, so similarly auto-curried. * * @param The first argument type * @param The second argument type @@ -14,6 +19,8 @@ @FunctionalInterface public interface Fn3 extends Fn2> { + D checkedApply(A a, B b, C c) throws Throwable; + /** * Invoke this function with the given arguments. * @@ -22,13 +29,35 @@ public interface Fn3 extends Fn2> { * @param c the third argument * @return the result of the function application */ - D apply(A a, B b, C c); + default D apply(A a, B b, C c) { + try { + return checkedApply(a, b, c); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 checkedApply(A a, B b) throws Throwable { + return c -> checkedApply(a, b, c); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn4 widen() { + return fn4(constantly(this)); + } /** * Partially apply this function by taking its first argument. * * @param a the first argument - * @return an Fn2 that takes the second and third argument and returns the result + * @return an {@link Fn2}<B, C, D> */ @Override default Fn2 apply(A a) { @@ -40,7 +69,7 @@ default Fn2 apply(A a) { * * @param a the first argument * @param b the second argument - * @return an Fn1 that takes the third argument and returns the result + * @return an {@link Fn1}<C, D> */ @Override default Fn1 apply(A a, B b) { @@ -50,7 +79,7 @@ default Fn1 apply(A a, B b) { /** * Flip the order of the first two arguments. * - * @return an Fn3 that takes the first and second arguments in reversed order + * @return an {@link Fn3}<B, A, C, D> */ @Override default Fn3 flip() { @@ -58,13 +87,75 @@ default Fn3 flip() { } /** - * Returns an Fn2 that takes the first two arguments as a Tuple2<A, B> and the third - * argument. + * Returns an {@link Fn2} that takes the first two arguments as a {@link Product2}<A, B> and the + * third argument. * - * @return an Fn2 taking a Tuple2 and the third argument + * @return an {@link Fn2} taking a {@link Product2} and the third argument */ @Override - default Fn2, C, D> uncurry() { + default Fn2, C, D> uncurry() { return (ab, c) -> apply(ab._1(), ab._2(), c); } + + @Override + default Fn3 discardR(Applicative> appB) { + return fn3(Fn2.super.discardR(appB)); + } + + @Override + default Fn3 diMapL(Fn1 fn) { + return fn3(Fn2.super.diMapL(fn)); + } + + @Override + default Fn3 contraMap(Fn1 fn) { + return fn3(Fn2.super.contraMap(fn)); + } + + @Override + default Fn4 compose(Fn2 before) { + return fn4(Fn2.super.compose(before)); + } + + /** + * Static factory method for wrapping a curried {@link Fn1} in an {@link Fn3}. + * + * @param curriedFn1 the curried fn1 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the output type + * @return the {@link Fn3} + */ + static Fn3 fn3(Fn1> curriedFn1) { + return (a, b, c) -> curriedFn1.apply(a).apply(b, c); + } + + /** + * Static factory method for wrapping a curried {@link Fn2} in an {@link Fn3}. + * + * @param curriedFn2 the curried fn2 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the output type + * @return the {@link Fn3} + */ + static Fn3 fn3(Fn2> curriedFn2) { + return (a, b, c) -> curriedFn2.apply(a, b).apply(c); + } + + /** + * Static factory method for coercing a lambda to an {@link Fn3}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the output type + * @return the {@link Fn3} + */ + static Fn3 fn3(Fn3 fn) { + return fn; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn4.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn4.java index cb093a7f1..f2e9f7d47 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn4.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn4.java @@ -1,9 +1,14 @@ package com.jnape.palatable.lambda.functions; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.internal.Runtime; + +import static com.jnape.palatable.lambda.functions.Fn5.fn5; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** - * A function taking four arguments. Defined in terms of Fn3, so similarly auto-curried. + * A function taking four arguments. Defined in terms of {@link Fn3}, so similarly auto-curried. * * @param The first argument type * @param The second argument type @@ -15,6 +20,8 @@ @FunctionalInterface public interface Fn4 extends Fn3> { + E checkedApply(A a, B b, C c, D d) throws Throwable; + /** * Invoke this function with the given arguments. * @@ -24,13 +31,35 @@ public interface Fn4 extends Fn3> { * @param d the fourth argument * @return the result of the function application */ - E apply(A a, B b, C c, D d); + default E apply(A a, B b, C c, D d) { + try { + return checkedApply(a, b, c, d); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 checkedApply(A a, B b, C c) throws Throwable { + return d -> checkedApply(a, b, c, d); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn5 widen() { + return fn5(constantly(this)); + } /** * Partially apply this function by taking its first argument. * * @param a the first argument - * @return an Fn3 that takes the second, third, and fourth argument and returns the result + * @return an {@link Fn3}<B, C, D, E> */ @Override default Fn3 apply(A a) { @@ -42,7 +71,7 @@ default Fn3 apply(A a) { * * @param a the first argument * @param b the second argument - * @return an Fn2 that takes the third and fourth arguments and returns the result + * @return an {@link Fn2}<C, D, E> */ @Override default Fn2 apply(A a, B b) { @@ -55,7 +84,7 @@ default Fn2 apply(A a, B b) { * @param a the first argument * @param b the second argument * @param c the third argument - * @return an Fn1 that takes the fourth argument and returns the result + * @return an {@link Fn1}<D, E> */ @Override default Fn1 apply(A a, B b, C c) { @@ -65,7 +94,7 @@ default Fn1 apply(A a, B b, C c) { /** * Flip the order of the first two arguments. * - * @return an Fn3 that takes the first and second arguments in reversed order + * @return an {@link Fn4}<B, A, C, D, E> */ @Override default Fn4 flip() { @@ -73,13 +102,93 @@ default Fn4 flip() { } /** - * Returns an Fn3 that takes the first two arguments as a Tuple2<A, B> and the third - * and fourth arguments. + * Returns an {@link Fn3} that takes the first two arguments as a {@link Product2}<A, B> and the + * third and fourth arguments. * - * @return an Fn3 taking a Tuple2 and the third and fourth arguments + * @return an {@link Fn3} taking a {@link Product2} and the third and fourth arguments */ @Override - default Fn3, C, D, E> uncurry() { + default Fn3, C, D, E> uncurry() { return (ab, c, d) -> apply(ab._1(), ab._2(), c, d); } + + @Override + default Fn4 discardR(Applicative> appB) { + return fn4(Fn3.super.discardR(appB)); + } + + @Override + default Fn4 diMapL(Fn1 fn) { + return fn4(Fn3.super.diMapL(fn)); + } + + @Override + default Fn4 contraMap(Fn1 fn) { + return fn4(Fn3.super.contraMap(fn)); + } + + @Override + default Fn5 compose(Fn2 before) { + return fn5(Fn3.super.compose(before)); + } + + /** + * Static factory method for wrapping a curried {@link Fn1} in an {@link Fn4}. + * + * @param curriedFn1 the curried fn1 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the output type + * @return the {@link Fn4} + */ + static Fn4 fn4(Fn1> curriedFn1) { + return (a, b, c, d) -> curriedFn1.apply(a).apply(b, c, d); + } + + /** + * Static factory method for wrapping a curried {@link Fn2} in an {@link Fn4}. + * + * @param curriedFn2 the curried fn2 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the output type + * @return the {@link Fn4} + */ + static Fn4 fn4(Fn2> curriedFn2) { + return (a, b, c, d) -> curriedFn2.apply(a, b).apply(c, d); + } + + /** + * Static factory method for wrapping a curried {@link Fn3} in an {@link Fn4}. + * + * @param curriedFn3 the curried fn3 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the output type + * @return the {@link Fn4} + */ + static Fn4 fn4(Fn3> curriedFn3) { + return (a, b, c, d) -> curriedFn3.apply(a, b, c).apply(d); + } + + /** + * Static factory method for coercing a lambda to an {@link Fn4}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the output type + * @return the {@link Fn4} + */ + static Fn4 fn4(Fn4 fn) { + return fn; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java new file mode 100644 index 000000000..c89b8dadd --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java @@ -0,0 +1,231 @@ +package com.jnape.palatable.lambda.functions; + +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.internal.Runtime; + +import static com.jnape.palatable.lambda.functions.Fn6.fn6; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * A function taking five arguments. Defined in terms of {@link Fn4}, so similarly auto-curried. + * + * @param The first argument type + * @param The second argument type + * @param The third argument type + * @param The fourth argument type + * @param The fifth argument type + * @param The return type + * @see Fn4 + */ +@FunctionalInterface +public interface Fn5 extends Fn4> { + + F checkedApply(A a, B b, C c, D d, E e) throws Throwable; + + /** + * Invoke this function with the given arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @param e the fifth argument + * @return the result of the function application + */ + default F apply(A a, B b, C c, D d, E e) { + try { + return checkedApply(a, b, c, d, e); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 checkedApply(A a, B b, C c, D d) throws Throwable { + return e -> checkedApply(a, b, c, d, e); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn6 widen() { + return fn6(constantly(this)); + } + + /** + * Partially apply this function by taking its first argument. + * + * @param a the first argument + * @return an {@link Fn5} that takes the remaining arguments and returns the result + */ + @Override + default Fn4 apply(A a) { + return (b, c, d, e) -> apply(a, b, c, d, e); + } + + /** + * Partially apply this function by taking its first two arguments. + * + * @param a the first argument + * @param b the second argument + * @return an {@link Fn3} that takes the remaining arguments and returns the result + */ + @Override + default Fn3 apply(A a, B b) { + return (c, d, e) -> apply(a, b, c, d, e); + } + + /** + * Partially apply this function by taking its first three arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @return an {@link Fn2} that takes remaining arguments and returns the result + */ + @Override + default Fn2 apply(A a, B b, C c) { + return (d, e) -> apply(a, b, c, d, e); + } + + /** + * Partially apply this function by taking its first four arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @return an {@link Fn1} that takes the remaining argument and returns the result + */ + @Override + default Fn1 apply(A a, B b, C c, D d) { + return (e) -> apply(a, b, c, d, e); + } + + /** + * Flip the order of the first two arguments. + * + * @return an {@link Fn5} that takes the first and second arguments in reversed order + */ + @Override + default Fn5 flip() { + return (b, a, c, d, e) -> apply(a, b, c, d, e); + } + + /** + * Returns an {@link Fn4} that takes the first two arguments as a {@link Product2}<A, B> and the + * remaining arguments. + * + * @return an {@link Fn4} taking a {@link Product2} and the remaining arguments + */ + @Override + default Fn4, C, D, E, F> uncurry() { + return (ab, c, d, e) -> apply(ab._1(), ab._2(), c, d, e); + } + + @Override + default Fn5 discardR(Applicative> appB) { + return fn5(Fn4.super.discardR(appB)); + } + + @Override + default Fn5 diMapL(Fn1 fn) { + return fn5(Fn4.super.diMapL(fn)); + } + + @Override + default Fn5 contraMap(Fn1 fn) { + return fn5(Fn4.super.contraMap(fn)); + } + + @Override + default Fn6 compose(Fn2 before) { + return fn6(Fn4.super.compose(before)); + } + + /** + * Static factory method for wrapping a curried {@link Fn1} in an {@link Fn5}. + * + * @param curriedFn1 the curried fn1 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the output type + * @return the {@link Fn5} + */ + static Fn5 fn5(Fn1> curriedFn1) { + return (a, b, c, d, e) -> curriedFn1.apply(a).apply(b, c, d, e); + } + + /** + * Static factory method for wrapping a curried {@link Fn2} in an {@link Fn5}. + * + * @param curriedFn2 the curried fn2 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the output type + * @return the {@link Fn5} + */ + static Fn5 fn5(Fn2> curriedFn2) { + return (a, b, c, d, e) -> curriedFn2.apply(a, b).apply(c, d, e); + } + + /** + * Static factory method for wrapping a curried {@link Fn3} in an {@link Fn5}. + * + * @param curriedFn3 the curried fn3 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the output type + * @return the {@link Fn5} + */ + static Fn5 fn5(Fn3> curriedFn3) { + return (a, b, c, d, e) -> curriedFn3.apply(a, b, c).apply(d, e); + } + + /** + * Static factory method for wrapping a curried {@link Fn4} in an {@link Fn5}. + * + * @param curriedFn4 the curried fn4 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the output type + * @return the {@link Fn5} + */ + static Fn5 fn5(Fn4> curriedFn4) { + return (a, b, c, d, e) -> curriedFn4.apply(a, b, c, d).apply(e); + } + + /** + * Static factory method for coercing a lambda to an {@link Fn5}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the output type + * @return the {@link Fn5} + */ + static Fn5 fn5(Fn5 fn) { + return fn; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java new file mode 100644 index 000000000..4e7bbea89 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java @@ -0,0 +1,270 @@ +package com.jnape.palatable.lambda.functions; + +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.internal.Runtime; + +import static com.jnape.palatable.lambda.functions.Fn7.fn7; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * A function taking six arguments. Defined in terms of {@link Fn5}, so similarly auto-curried. + * + * @param The first argument type + * @param The second argument type + * @param The third argument type + * @param The fourth argument type + * @param The fifth argument type + * @param The sixth argument type + * @param The return type + * @see Fn5 + */ +@FunctionalInterface +public interface Fn6 extends Fn5> { + + G checkedApply(A a, B b, C c, D d, E e, F f) throws Throwable; + + /** + * Invoke this function with the given arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @param e the fifth argument + * @param f the sixth argument + * @return the result of the function application + */ + default G apply(A a, B b, C c, D d, E e, F f) { + try { + return checkedApply(a, b, c, d, e, f); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 checkedApply(A a, B b, C c, D d, E e) throws Throwable { + return f -> checkedApply(a, b, c, d, e, f); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn7 widen() { + return fn7(constantly(this)); + } + + /** + * Partially apply this function by taking its first argument. + * + * @param a the first argument + * @return an {@link Fn5} that takes the remaining arguments and returns the result + */ + @Override + default Fn5 apply(A a) { + return (b, c, d, e, f) -> apply(a, b, c, d, e, f); + } + + /** + * Partially apply this function by taking its first two arguments. + * + * @param a the first argument + * @param b the second argument + * @return an {@link Fn4} that takes the remaining arguments and returns the result + */ + @Override + default Fn4 apply(A a, B b) { + return (c, d, e, f) -> apply(a, b, c, d, e, f); + } + + /** + * Partially apply this function by taking its first three arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @return an {@link Fn3} that takes remaining arguments and returns the result + */ + @Override + default Fn3 apply(A a, B b, C c) { + return (d, e, f) -> apply(a, b, c, d, e, f); + } + + /** + * Partially apply this function by taking its first four arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @return an {@link Fn2} that takes the remaining arguments and returns the result + */ + @Override + default Fn2 apply(A a, B b, C c, D d) { + return (e, f) -> apply(a, b, c, d, e, f); + } + + /** + * Partially apply this function by taking its first five arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @param e the fifth argument + * @return an {@link Fn1} that takes the remaining argument and returns the result + */ + @Override + default Fn1 apply(A a, B b, C c, D d, E e) { + return (f) -> apply(a, b, c, d, e, f); + } + + /** + * Flip the order of the first two arguments. + * + * @return an {@link Fn6} that takes the first and second arguments in reversed order + */ + @Override + default Fn6 flip() { + return (b, a, c, d, e, f) -> apply(a, b, c, d, e, f); + } + + /** + * Returns an {@link Fn5} that takes the first two arguments as a {@link Product2}<A, B> and the + * remaining arguments. + * + * @return an {@link Fn5} taking a {@link Product2} and the remaining arguments + */ + @Override + default Fn5, C, D, E, F, G> uncurry() { + return (ab, c, d, e, f) -> apply(ab._1(), ab._2(), c, d, e, f); + } + + @Override + default Fn6 discardR(Applicative> appB) { + return fn6(Fn5.super.discardR(appB)); + } + + @Override + default Fn6 diMapL(Fn1 fn) { + return fn6(Fn5.super.diMapL(fn)); + } + + @Override + default Fn6 contraMap(Fn1 fn) { + return fn6(Fn5.super.contraMap(fn)); + } + + @Override + default Fn7 compose(Fn2 before) { + return fn7(Fn5.super.compose(before)); + } + + /** + * Static factory method for wrapping a curried {@link Fn1} in an {@link Fn6}. + * + * @param curriedFn1 the curried fn1 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the output type + * @return the {@link Fn6} + */ + static Fn6 fn6(Fn1> curriedFn1) { + return (a, b, c, d, e, f) -> curriedFn1.apply(a).apply(b, c, d, e, f); + } + + /** + * Static factory method for wrapping a curried {@link Fn2} in an {@link Fn6}. + * + * @param curriedFn2 the curried fn2 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the output type + * @return the {@link Fn6} + */ + static Fn6 fn6(Fn2> curriedFn2) { + return (a, b, c, d, e, f) -> curriedFn2.apply(a, b).apply(c, d, e, f); + } + + /** + * Static factory method for wrapping a curried {@link Fn3} in an {@link Fn6}. + * + * @param curriedFn3 the curried fn3 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the output type + * @return the {@link Fn6} + */ + static Fn6 fn6(Fn3> curriedFn3) { + return (a, b, c, d, e, f) -> curriedFn3.apply(a, b, c).apply(d, e, f); + } + + /** + * Static factory method for wrapping a curried {@link Fn4} in an {@link Fn6}. + * + * @param curriedFn4 the curried fn4 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the output type + * @return the {@link Fn6} + */ + static Fn6 fn6(Fn4> curriedFn4) { + return (a, b, c, d, e, f) -> curriedFn4.apply(a, b, c, d).apply(e, f); + } + + /** + * Static factory method for wrapping a curried {@link Fn5} in an {@link Fn6}. + * + * @param curriedFn5 the curried fn4 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the output type + * @return the {@link Fn6} + */ + static Fn6 fn6(Fn5> curriedFn5) { + return (a, b, c, d, e, f) -> curriedFn5.apply(a, b, c, d, e).apply(f); + } + + /** + * Static factory method for coercing a lambda to an {@link Fn6}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the output type + * @return the {@link Fn6} + */ + static Fn6 fn6(Fn6 fn) { + return fn; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java new file mode 100644 index 000000000..15f7a3be9 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java @@ -0,0 +1,311 @@ +package com.jnape.palatable.lambda.functions; + +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.internal.Runtime; + +import static com.jnape.palatable.lambda.functions.Fn8.fn8; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * A function taking seven arguments. Defined in terms of {@link Fn6}, so similarly auto-curried. + * + * @param The first argument type + * @param The second argument type + * @param The third argument type + * @param The fourth argument type + * @param The fifth argument type + * @param The sixth argument type + * @param The seventh argument type + * @param The return type + * @see Fn6 + */ +@FunctionalInterface +public interface Fn7 extends Fn6> { + + H checkedApply(A a, B b, C c, D d, E e, F f, G g) throws Throwable; + + /** + * Invoke this function with the given arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @param e the fifth argument + * @param f the sixth argument + * @param g the seventh argument + * @return the result of the function application + */ + default H apply(A a, B b, C c, D d, E e, F f, G g) { + try { + return checkedApply(a, b, c, d, e, f, g); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 checkedApply(A a, B b, C c, D d, E e, F f) throws Throwable { + return g -> checkedApply(a, b, c, d, e, f, g); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn8 widen() { + return fn8(constantly(this)); + } + + /** + * Partially apply this function by taking its first argument. + * + * @param a the first argument + * @return an {@link Fn6} that takes the remaining arguments and returns the result + */ + @Override + default Fn6 apply(A a) { + return (b, c, d, e, f, g) -> apply(a, b, c, d, e, f, g); + } + + /** + * Partially apply this function by taking its first two arguments. + * + * @param a the first argument + * @param b the second argument + * @return an {@link Fn5} that takes the remaining arguments and returns the result + */ + @Override + default Fn5 apply(A a, B b) { + return (c, d, e, f, g) -> apply(a, b, c, d, e, f, g); + } + + /** + * Partially apply this function by taking its first three arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @return an {@link Fn4} that takes remaining arguments and returns the result + */ + @Override + default Fn4 apply(A a, B b, C c) { + return (d, e, f, g) -> apply(a, b, c, d, e, f, g); + } + + /** + * Partially apply this function by taking its first four arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @return an {@link Fn3} that takes the remaining arguments and returns the result + */ + @Override + default Fn3 apply(A a, B b, C c, D d) { + return (e, f, g) -> apply(a, b, c, d, e, f, g); + } + + /** + * Partially apply this function by taking its first five arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @param e the fifth argument + * @return an {@link Fn2} that takes the remaining arguments and returns the result + */ + @Override + default Fn2 apply(A a, B b, C c, D d, E e) { + return (f, g) -> apply(a, b, c, d, e, f, g); + } + + /** + * Partially apply this function by taking its first six arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @param e the fifth argument + * @param f the sixth argument + * @return an {@link Fn1} that takes the remaining argument and returns the result + */ + @Override + default Fn1 apply(A a, B b, C c, D d, E e, F f) { + return (g) -> apply(a, b, c, d, e, f, g); + } + + /** + * Flip the order of the first two arguments. + * + * @return an {@link Fn7} that takes the first and second arguments in reversed order + */ + @Override + default Fn7 flip() { + return (b, a, c, d, e, f, g) -> apply(a, b, c, d, e, f, g); + } + + /** + * Returns an {@link Fn6} that takes the first two arguments as a {@link Product2}<A, B> and the + * remaining arguments. + * + * @return an {@link Fn6} taking a {@link Product2} and the remaining arguments + */ + @Override + default Fn6, C, D, E, F, G, H> uncurry() { + return (ab, c, d, e, f, g) -> apply(ab._1(), ab._2(), c, d, e, f, g); + } + + @Override + default Fn7 discardR(Applicative> appB) { + return fn7(Fn6.super.discardR(appB)); + } + + @Override + default Fn7 diMapL(Fn1 fn) { + return fn7(Fn6.super.diMapL(fn)); + } + + @Override + default Fn7 contraMap(Fn1 fn) { + return fn7(Fn6.super.contraMap(fn)); + } + + @Override + default Fn8 compose(Fn2 before) { + return fn8(Fn6.super.compose(before)); + } + + /** + * Static factory method for wrapping a curried {@link Fn1} in an {@link Fn7}. + * + * @param curriedFn1 the curried fn1 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the output type + * @return the {@link Fn7} + */ + static Fn7 fn7(Fn1> curriedFn1) { + return (a, b, c, d, e, f, g) -> curriedFn1.apply(a).apply(b, c, d, e, f, g); + } + + /** + * Static factory method for wrapping a curried {@link Fn2} in an {@link Fn7}. + * + * @param curriedFn2 the curried fn2 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the output type + * @return the {@link Fn7} + */ + static Fn7 fn7(Fn2> curriedFn2) { + return (a, b, c, d, e, f, g) -> curriedFn2.apply(a, b).apply(c, d, e, f, g); + } + + /** + * Static factory method for wrapping a curried {@link Fn3} in an {@link Fn7}. + * + * @param curriedFn3 the curried fn3 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the output type + * @return the {@link Fn7} + */ + static Fn7 fn7(Fn3> curriedFn3) { + return (a, b, c, d, e, f, g) -> curriedFn3.apply(a, b, c).apply(d, e, f, g); + } + + /** + * Static factory method for wrapping a curried {@link Fn4} in an {@link Fn7}. + * + * @param curriedFn4 the curried fn4 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the output type + * @return the {@link Fn7} + */ + static Fn7 fn7(Fn4> curriedFn4) { + return (a, b, c, d, e, f, g) -> curriedFn4.apply(a, b, c, d).apply(e, f, g); + } + + /** + * Static factory method for wrapping a curried {@link Fn5} in an {@link Fn7}. + * + * @param curriedFn5 the curried fn5 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the output type + * @return the {@link Fn7} + */ + static Fn7 fn7(Fn5> curriedFn5) { + return (a, b, c, d, e, f, g) -> curriedFn5.apply(a, b, c, d, e).apply(f, g); + } + + /** + * Static factory method for wrapping a curried {@link Fn6} in an {@link Fn7}. + * + * @param curriedFn6 the curried fn6 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the output type + * @return the {@link Fn7} + */ + static Fn7 fn7(Fn6> curriedFn6) { + return (a, b, c, d, e, f, g) -> curriedFn6.apply(a, b, c, d, e, f).apply(g); + } + + /** + * Static factory method for coercing a lambda to an {@link Fn7}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the output type + * @return the {@link Fn7} + */ + static Fn7 fn7(Fn7 fn) { + return fn; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java new file mode 100644 index 000000000..60f3a4a3c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java @@ -0,0 +1,352 @@ +package com.jnape.palatable.lambda.functions; + +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.internal.Runtime; + +/** + * A function taking eight arguments. Defined in terms of {@link Fn7}, so similarly auto-curried. + * + * @param The first argument type + * @param The second argument type + * @param The third argument type + * @param The fourth argument type + * @param The fifth argument type + * @param The sixth argument type + * @param The seventh argument type + * @param The eighth argument type + * @param The return type + * @see Fn7 + */ +@FunctionalInterface +public interface Fn8 extends Fn7> { + + I checkedApply(A a, B b, C c, D d, E e, F f, G g, H h) throws Throwable; + + /** + * Invoke this function with the given arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @param e the fifth argument + * @param f the sixth argument + * @param g the seventh argument + * @param h the eighth argument + * @return the result of the function application + */ + default I apply(A a, B b, C c, D d, E e, F f, G g, H h) { + try { + return checkedApply(a, b, c, d, e, f, g, h); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 checkedApply(A a, B b, C c, D d, E e, F f, G g) throws Throwable { + return h -> checkedApply(a, b, c, d, e, f, g, h); + } + + /** + * Partially apply this function by taking its first argument. + * + * @param a the first argument + * @return an {@link Fn7} that takes the remaining arguments and returns the result + */ + @Override + default Fn7 apply(A a) { + return (b, c, d, e, f, g, h) -> apply(a, b, c, d, e, f, g, h); + } + + /** + * Partially apply this function by taking its first two arguments. + * + * @param a the first argument + * @param b the second argument + * @return an {@link Fn6} that takes the remaining arguments and returns the result + */ + @Override + default Fn6 apply(A a, B b) { + return (c, d, e, f, g, h) -> apply(a, b, c, d, e, f, g, h); + } + + /** + * Partially apply this function by taking its first three arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @return an {@link Fn5} that takes remaining arguments and returns the result + */ + @Override + default Fn5 apply(A a, B b, C c) { + return (d, e, f, g, h) -> apply(a, b, c, d, e, f, g, h); + } + + /** + * Partially apply this function by taking its first four arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @return an {@link Fn4} that takes the remaining arguments and returns the result + */ + @Override + default Fn4 apply(A a, B b, C c, D d) { + return (e, f, g, h) -> apply(a, b, c, d, e, f, g, h); + } + + /** + * Partially apply this function by taking its first five arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @param e the fifth argument + * @return an {@link Fn3} that takes the remaining arguments and returns the result + */ + @Override + default Fn3 apply(A a, B b, C c, D d, E e) { + return (f, g, h) -> apply(a, b, c, d, e, f, g, h); + } + + /** + * Partially apply this function by taking its first six arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @param e the fifth argument + * @param f the sixth argument + * @return an {@link Fn2} that takes the remaining arguments and returns the result + */ + @Override + default Fn2 apply(A a, B b, C c, D d, E e, F f) { + return (g, h) -> apply(a, b, c, d, e, f, g, h); + } + + /** + * Partially apply this function by taking its first seven arguments. + * + * @param a the first argument + * @param b the second argument + * @param c the third argument + * @param d the fourth argument + * @param e the fifth argument + * @param f the sixth argument + * @param g the seventh argument + * @return an {@link Fn1} that takes the remaining argument and returns the result + */ + @Override + default Fn1 apply(A a, B b, C c, D d, E e, F f, G g) { + return (h) -> apply(a, b, c, d, e, f, g, h); + } + + /** + * Flip the order of the first two arguments. + * + * @return an {@link Fn8} that takes the first and second arguments in reversed order + */ + @Override + default Fn8 flip() { + return (b, a, c, d, e, f, g, h) -> apply(a, b, c, d, e, f, g, h); + } + + /** + * Returns an {@link Fn7} that takes the first two arguments as a {@link Product2}<A, B> and the + * remaining arguments. + * + * @return an {@link Fn7} taking a {@link Product2} and the remaining arguments + */ + @Override + default Fn7, C, D, E, F, G, H, I> uncurry() { + return (ab, c, d, e, f, g, h) -> apply(ab._1(), ab._2(), c, d, e, f, g, h); + } + + @Override + default Fn8 discardR(Applicative> appB) { + return fn8(Fn7.super.discardR(appB)); + } + + @Override + default Fn8 diMapL(Fn1 fn) { + return fn8(Fn7.super.diMapL(fn)); + } + + @Override + default Fn8 contraMap(Fn1 fn) { + return fn8(Fn7.super.contraMap(fn)); + } + + @Override + default Fn8> compose(Fn2 before) { + return Fn7.super.compose(before); + } + + /** + * Static factory method for wrapping a curried {@link Fn1} in an {@link Fn8}. + * + * @param curriedFn1 the curried fn1 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the eighth input argument type + * @param the output type + * @return the {@link Fn8} + */ + static Fn8 fn8( + Fn1> curriedFn1) { + return (a, b, c, d, e, f, g, h) -> curriedFn1.apply(a).apply(b, c, d, e, f, g, h); + } + + /** + * Static factory method for wrapping a curried {@link Fn2} in an {@link Fn8}. + * + * @param curriedFn2 the curried fn2 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the eighth input argument type + * @param the output type + * @return the {@link Fn8} + */ + static Fn8 fn8( + Fn2> curriedFn2) { + return (a, b, c, d, e, f, g, h) -> curriedFn2.apply(a, b).apply(c, d, e, f, g, h); + } + + /** + * Static factory method for wrapping a curried {@link Fn3} in an {@link Fn8}. + * + * @param curriedFn3 the curried fn3 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the eighth input argument type + * @param the output type + * @return the {@link Fn8} + */ + static Fn8 fn8( + Fn3> curriedFn3) { + return (a, b, c, d, e, f, g, h) -> curriedFn3.apply(a, b, c).apply(d, e, f, g, h); + } + + /** + * Static factory method for wrapping a curried {@link Fn4} in an {@link Fn8}. + * + * @param curriedFn4 the curried fn4 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the eighth input argument type + * @param the output type + * @return the {@link Fn8} + */ + static Fn8 fn8( + Fn4> curriedFn4) { + return (a, b, c, d, e, f, g, h) -> curriedFn4.apply(a, b, c, d).apply(e, f, g, h); + } + + /** + * Static factory method for wrapping a curried {@link Fn5} in an {@link Fn8}. + * + * @param curriedFn5 the curried fn5 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the eighth input argument type + * @param the output type + * @return the {@link Fn8} + */ + static Fn8 fn8( + Fn5> curriedFn5) { + return (a, b, c, d, e, f, g, h) -> curriedFn5.apply(a, b, c, d, e).apply(f, g, h); + } + + /** + * Static factory method for wrapping a curried {@link Fn6} in an {@link Fn8}. + * + * @param curriedFn6 the curried fn6 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the eighth input argument type + * @param the output type + * @return the {@link Fn8} + */ + static Fn8 fn8( + Fn6> curriedFn6) { + return (a, b, c, d, e, f, g, h) -> curriedFn6.apply(a, b, c, d, e, f).apply(g, h); + } + + /** + * Static factory method for wrapping a curried {@link Fn7} in an {@link Fn8}. + * + * @param curriedFn7 the curried fn7 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the eighth input argument type + * @param the output type + * @return the {@link Fn8} + */ + static Fn8 fn8( + Fn7> curriedFn7) { + return (a, b, c, d, e, f, g, h) -> curriedFn7.apply(a, b, c, d, e, f, g).apply(h); + } + + /** + * Static factory method for coercing a lambda to an {@link Fn8}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the eighth input argument type + * @param the output type + * @return the {@link Fn8} + */ + static Fn8 fn8(Fn8 fn) { + return fn; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybes.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybes.java new file mode 100644 index 000000000..9833e2a32 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybes.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.Collections; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; + +/** + * Given an {@link Iterable}<{@link Maybe}<A>>, return an + * {@link Iterable}<A> of only the present values. + * + * @param the {@link Maybe} element type, as well as the resulting {@link Iterable} element type + */ +public final class CatMaybes implements Fn1>, Iterable> { + private static final CatMaybes INSTANCE = new CatMaybes<>(); + + private CatMaybes() { + } + + @Override + public Iterable checkedApply(Iterable> maybes) { + return flatten(map(m -> m.>fmap(Collections::singletonList) + .orElse(Collections::emptyIterator), maybes)); + } + + @SuppressWarnings("unchecked") + public static CatMaybes catMaybes() { + return (CatMaybes) INSTANCE; + } + + public static Iterable catMaybes(Iterable> as) { + return CatMaybes.catMaybes().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Coalesce.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Coalesce.java new file mode 100644 index 000000000..3154e07ea --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Coalesce.java @@ -0,0 +1,51 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Snoc; +import com.jnape.palatable.lambda.monoid.builtin.Merge; + +import java.util.Collections; + +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; + +/** + * Fold an {@link Iterable}<{@link Either}<L, R>> into an {@link Either}<{@link + * Iterable}<L>, {@link Iterable}<R>>, preserving all results of the side that's returned. That + * is, if the result is a left, it will contain all left values; if it is a right, it will + * contain all right values. + *

+ * It may be useful to think of this as a more efficient version of {@link Merge}<{@link Iterable}<L>, + * {@link Iterable}<R>>. + * + * @param the left parameter type + * @param the right parameter type + */ +public final class Coalesce implements Fn1>, Either, Iterable>> { + + private static final Coalesce INSTANCE = new Coalesce<>(); + + private Coalesce() { + } + + @Override + public Either, Iterable> checkedApply(Iterable> eithers) { + return foldLeft((acc, e) -> acc + .biMapL(ls -> e.match(Snoc.snoc().flip().apply(ls), constantly(ls))) + .flatMap(rs -> e.biMap(Collections::singletonList, + Snoc.snoc().flip().apply(rs))), + right(Collections::emptyIterator), + eithers); + } + + @SuppressWarnings("unchecked") + public static Coalesce coalesce() { + return (Coalesce) INSTANCE; + } + + public static Either, Iterable> coalesce(Iterable> eithers) { + return Coalesce.coalesce().apply(eithers); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Constantly.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Constantly.java index 9f89da517..252eb53df 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Constantly.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Constantly.java @@ -11,19 +11,19 @@ */ public final class Constantly implements Fn2 { - private static final Constantly INSTANCE = new Constantly(); + private static final Constantly INSTANCE = new Constantly<>(); private Constantly() { } @Override - public A apply(A a, B b) { + public A checkedApply(A a, B b) { return a; } @SuppressWarnings("unchecked") public static Constantly constantly() { - return INSTANCE; + return (Constantly) INSTANCE; } public static Fn1 constantly(A a) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java index 440d50041..a5aaaa25b 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iterators.CyclicIterator; +import com.jnape.palatable.lambda.internal.iteration.CyclicIterable; import static java.util.Arrays.asList; @@ -13,19 +13,19 @@ */ public final class Cycle implements Fn1, Iterable> { - private static final Cycle INSTANCE = new Cycle(); + private static final Cycle INSTANCE = new Cycle<>(); private Cycle() { } @Override - public Iterable apply(Iterable as) { - return () -> new CyclicIterator<>(as.iterator()); + public Iterable checkedApply(Iterable as) { + return new CyclicIterable<>(as); } @SuppressWarnings("unchecked") public static Cycle cycle() { - return INSTANCE; + return (Cycle) INSTANCE; } public static Iterable cycle(Iterable as) { @@ -33,6 +33,7 @@ public static Iterable cycle(Iterable as) { } @SafeVarargs + @SuppressWarnings("varargs") public static Iterable cycle(A... as) { return cycle(asList(as)); } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Distinct.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Distinct.java new file mode 100644 index 000000000..30b97dec4 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Distinct.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.iteration.DistinctIterable; + +/** + * Return an {@link Iterable} of the distinct values from the given input {@link Iterable}. + * + * @param the Iterable element type + */ +public final class Distinct implements Fn1, Iterable> { + private static final Distinct INSTANCE = new Distinct<>(); + + private Distinct() { + } + + @Override + public Iterable checkedApply(Iterable as) { + return new DistinctIterable<>(as); + } + + @SuppressWarnings("unchecked") + public static Distinct distinct() { + return (Distinct) INSTANCE; + } + + public static Iterable distinct(Iterable as) { + return Distinct.distinct().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Downcast.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Downcast.java new file mode 100644 index 000000000..d9c0c3985 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Downcast.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +/** + * Covariantly cast a value of type B to a value of subtype A. Unsafe. + * + * @param the subtype + * @param the supertype + */ +public final class Downcast implements Fn1 { + + private static final Downcast INSTANCE = new Downcast<>(); + + private Downcast() { + } + + @Override + @SuppressWarnings("unchecked") + public A checkedApply(B b) { + return (A) b; + } + + @SuppressWarnings("unchecked") + public static Downcast downcast() { + return (Downcast) INSTANCE; + } + + public static A downcast(B b) { + return Downcast.downcast().apply(b); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Empty.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Empty.java new file mode 100644 index 000000000..6ea6e6254 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Empty.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +/** + * A predicate that returns true if as is empty; false otherwise. + * + * @param the iterable element type + */ +public final class Empty implements Predicate> { + + private static final Empty INSTANCE = new Empty<>(); + + private Empty() { + } + + @Override + public Boolean checkedApply(Iterable as) { + return !as.iterator().hasNext(); + } + + @SuppressWarnings("unchecked") + public static Empty empty() { + return (Empty) INSTANCE; + } + + public static Boolean empty(Iterable as) { + return Empty.empty().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Flatten.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Flatten.java new file mode 100644 index 000000000..812452965 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Flatten.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.iteration.FlatteningIterator; + +/** + * Given a nested {@link Iterable} of {@link Iterable}s, return a lazily flattening {@link Iterable} + * of the nested elements. + * + * @param the nested Iterable element type + */ +public final class Flatten implements Fn1>, Iterable> { + private static final Flatten INSTANCE = new Flatten<>(); + + private Flatten() { + } + + @Override + public Iterable checkedApply(Iterable> iterables) { + return () -> new FlatteningIterator<>(iterables.iterator()); + } + + @SuppressWarnings("unchecked") + public static Flatten flatten() { + return (Flatten) INSTANCE; + } + + public static Iterable flatten(Iterable> as) { + return Flatten.flatten().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java new file mode 100644 index 000000000..1c12a04fa --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.traversable.LambdaIterable; + +/** + * Force a full iteration of an {@link Iterable}, presumably to perform any side-effects contained therein. Returns the + * {@link Iterable} back. + * + * @param the Iterable element type + * @deprecated in favor of {@link LambdaIterable#traverse(Fn1, Fn1) traversing} into an {@link IO} and running it + */ +@Deprecated +public final class Force implements Fn1, Iterable> { + + private static final Force INSTANCE = new Force<>(); + + private Force() { + } + + @Override + @SuppressWarnings("StatementWithEmptyBody") + public Iterable checkedApply(Iterable as) { + for (A ignored : as) { + } + return as; + } + + @SuppressWarnings("unchecked") + public static Force force() { + return (Force) INSTANCE; + } + + public static Iterable force(Iterable as) { + return Force.force().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Head.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Head.java index 2ce501dee..a7c2c16fd 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Head.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Head.java @@ -1,37 +1,38 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import java.util.Iterator; -import java.util.Optional; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; /** - * Retrieve the head element of an Iterable, wrapped in an Optional. If the - * Iterable is empty, the result is Optional.empty(). + * Retrieve the head element of an {@link Iterable}, wrapped in an {@link Maybe}. If the {@link Iterable} is empty, the + * result is {@link Maybe#nothing()}. * * @param the Iterable element type */ -public final class Head implements Fn1, Optional> { +public final class Head implements Fn1, Maybe> { - private static final Head INSTANCE = new Head(); + private static final Head INSTANCE = new Head<>(); private Head() { } @Override - public Optional apply(Iterable as) { + public Maybe checkedApply(Iterable as) { Iterator iterator = as.iterator(); - return iterator.hasNext() - ? Optional.of(iterator.next()) - : Optional.empty(); + return iterator.hasNext() ? just(iterator.next()) : nothing(); } @SuppressWarnings("unchecked") public static Head head() { - return INSTANCE; + return (Head) INSTANCE; } - public static Optional head(Iterable as) { + public static Maybe head(Iterable as) { return Head.head().apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Id.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Id.java index 640927f50..c92cf0c81 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Id.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Id.java @@ -9,18 +9,22 @@ */ public final class Id implements Fn1 { - private static final Id INSTANCE = new Id(); + private static final Id INSTANCE = new Id<>(); private Id() { } @Override - public A apply(A a) { + public A checkedApply(A a) { return a; } @SuppressWarnings("unchecked") public static Id id() { - return INSTANCE; + return (Id) INSTANCE; + } + + public static A id(A a) { + return Id.id().apply(a); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java new file mode 100644 index 000000000..66cea88bf --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.iteration.InitIterator; + +/** + * Given an {@link Iterable}<A>, produce an + * {@link Iterable}<A> of all elements but the last one. + * + * @param the Iterable element type + */ +public final class Init implements Fn1, Iterable> { + + private static final Init INSTANCE = new Init<>(); + + private Init() { + } + + @Override + public Iterable checkedApply(Iterable as) { + return () -> new InitIterator<>(as); + } + + @SuppressWarnings("unchecked") + public static Init init() { + return (Init) INSTANCE; + } + + public static Iterable init(Iterable as) { + return Init.init().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Inits.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Inits.java new file mode 100644 index 000000000..846762200 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Inits.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Snoc; + +import java.util.Collections; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.ScanLeft.scanLeft; + +/** + * Given an {@link Iterable}<A>, produce an + * {@link Iterable}<{@link Iterable}<A>>, representing all of the subsequences of initial + * elements, ordered by size, starting with the empty {@link Iterable}. + *

+ * For example, inits(asList(1,2,3)) would iterate [], [1], [1,2], + * and [1,2,3]. + * + * @param the Iterable element type + */ +public final class Inits implements Fn1, Iterable>> { + + private static final Inits INSTANCE = new Inits<>(); + + private Inits() { + } + + @Override + public Iterable> checkedApply(Iterable as) { + return scanLeft(Snoc.snoc().flip(), Collections::emptyIterator, as); + } + + @SuppressWarnings("unchecked") + public static Inits inits() { + return (Inits) INSTANCE; + } + + public static Iterable> inits(Iterable as) { + return Inits.inits().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Last.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Last.java index 4b993c35c..a71c1dda6 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Last.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Last.java @@ -1,37 +1,38 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.maybe; /** - * Retrieve the last element of an Iterable, wrapped in an Optional. If the - * Iterable is empty, the result is Optional.empty(). + * Retrieve the last element of an {@link Iterable}, wrapped in a {@link Maybe}. If the {@link Iterable} is empty, the + * result is {@link Maybe#nothing()}. * * @param the Iterable element type */ -public final class Last implements Fn1, Optional> { +public final class Last implements Fn1, Maybe> { - private static final Last INSTANCE = new Last(); + private static final Last INSTANCE = new Last<>(); private Last() { } @Override - public Optional apply(Iterable as) { + public Maybe checkedApply(Iterable as) { A last = null; for (A a : as) { last = a; } - return Optional.ofNullable(last); + return maybe(last); } @SuppressWarnings("unchecked") public static Last last() { - return INSTANCE; + return (Last) INSTANCE; } - public static Optional last(Iterable as) { + public static Maybe last(Iterable as) { return Last.last().apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Magnetize.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Magnetize.java new file mode 100644 index 000000000..ce2d2e2a3 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Magnetize.java @@ -0,0 +1,33 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.functions.builtin.fn2.MagnetizeBy.magnetizeBy; + +/** + * {@link Magnetize} an {@link Iterable} using value equality as the magnetizing function. + * + * @param the Iterable element type + */ +public final class Magnetize implements Fn1, Iterable>> { + + private static final Magnetize INSTANCE = new Magnetize<>(); + + private Magnetize() { + } + + @Override + public Iterable> checkedApply(Iterable as) { + return magnetizeBy(eq(), as); + } + + @SuppressWarnings("unchecked") + public static Magnetize magnetize() { + return (Magnetize) INSTANCE; + } + + public static Iterable> magnetize(Iterable as) { + return Magnetize.magnetize().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Not.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Not.java new file mode 100644 index 000000000..e148fadcb --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Not.java @@ -0,0 +1,35 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +/** + * Negate a predicate function. + * + * @param the input argument type + */ +public final class Not implements BiPredicate, A> { + private static final Not INSTANCE = new Not<>(); + + private Not() { + } + + @Override + public Boolean checkedApply(Fn1 pred, A a) { + return !pred.apply(a); + } + + @SuppressWarnings("unchecked") + public static Not not() { + return (Not) INSTANCE; + } + + public static Predicate not(Fn1 pred) { + return Not.not().apply(pred); + } + + public static Boolean not(Fn1 pred, A a) { + return not(pred).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Occurrences.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Occurrences.java new file mode 100644 index 000000000..6fea549c8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Occurrences.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.HashMap; +import java.util.Map; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; + +/** + * Given an {@link Iterable}<A>, return a {@link Map}<A, Long> representing each + * unique element in the {@link Iterable} paired with its number of occurrences. + * + * @param the {@link Iterable} element type + */ +public final class Occurrences implements Fn1, Map> { + private static final Occurrences INSTANCE = new Occurrences<>(); + + private Occurrences() { + } + + @Override + public Map checkedApply(Iterable as) { + return foldLeft((occurrences, a) -> { + occurrences.put(a, occurrences.getOrDefault(a, 0L) + 1); + return occurrences; + }, new HashMap<>(), as); + } + + @SuppressWarnings("unchecked") + public static Occurrences occurrences() { + return (Occurrences) INSTANCE; + } + + public static Map occurrences(Iterable as) { + return Occurrences.occurrences().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Repeat.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Repeat.java index 30da5141f..0c91253d7 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Repeat.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Repeat.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iterators.RepetitiousIterator; +import com.jnape.palatable.lambda.internal.iteration.RepetitiousIterator; /** * Given a value, return an infinite Iterable that repeatedly iterates that value. @@ -10,19 +10,19 @@ */ public final class Repeat implements Fn1> { - private static final Repeat INSTANCE = new Repeat(); + private static final Repeat INSTANCE = new Repeat<>(); private Repeat() { } @Override - public Iterable apply(A a) { + public Iterable checkedApply(A a) { return () -> new RepetitiousIterator<>(a); } @SuppressWarnings("unchecked") public static Repeat repeat() { - return INSTANCE; + return (Repeat) INSTANCE; } public static Iterable repeat(A a) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java index f2ab76ae6..54cbc5263 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iterators.ReversingIterator; +import com.jnape.palatable.lambda.internal.iteration.ReversingIterable; /** * Given an Iterable, return a reversed representation of that Iterable. Note that reversing @@ -11,19 +11,19 @@ */ public final class Reverse implements Fn1, Iterable> { - private static final Reverse INSTANCE = new Reverse(); + private static final Reverse INSTANCE = new Reverse<>(); private Reverse() { } @Override - public Iterable apply(Iterable as) { - return () -> new ReversingIterator<>(as.iterator()); + public Iterable checkedApply(Iterable as) { + return new ReversingIterable<>(as); } @SuppressWarnings("unchecked") public static Reverse reverse() { - return INSTANCE; + return (Reverse) INSTANCE; } public static Iterable reverse(Iterable as) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Size.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Size.java index 95dca3f36..061636248 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Size.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Size.java @@ -12,7 +12,7 @@ private Size() { } @Override - public Long apply(Iterable iterable) { + public Long checkedApply(Iterable iterable) { if (iterable instanceof Collection) return (long) ((Collection) iterable).size(); diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Sort.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Sort.java new file mode 100644 index 000000000..da00cfaaf --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Sort.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.SortBy; +import com.jnape.palatable.lambda.functions.builtin.fn2.SortWith; + +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.SortBy.sortBy; + +/** + * Given an {@link Iterable} of {@link Comparable} elements, return a {@link List} of the sorted elements. Note that + * this is both eager and monolithic. + * + * @param the input Iterable and output List element type + * @see SortBy + * @see SortWith + */ +public final class Sort> implements Fn1, List> { + + private static final Sort INSTANCE = new Sort<>(); + + private Sort() { + } + + @Override + public List checkedApply(Iterable as) { + return sortBy(id(), as); + } + + @SuppressWarnings("unchecked") + public static > Sort sort() { + return (Sort) INSTANCE; + } + + public static > List sort(Iterable as) { + return Sort.sort().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tail.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tail.java index e155134fc..9f776ccf0 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tail.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tail.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; -import java.util.Iterator; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Drop.drop; /** * Returns the tail of an Iterable; the is, an Iterable of all the elements except for the @@ -12,24 +12,19 @@ */ public final class Tail implements Fn1, Iterable> { - private static final Tail INSTANCE = new Tail(); + private static final Tail INSTANCE = new Tail<>(); private Tail() { } @Override - public Iterable apply(Iterable as) { - return () -> { - Iterator iterator = as.iterator(); - if (iterator.hasNext()) - iterator.next(); - return iterator; - }; + public Iterable checkedApply(Iterable as) { + return drop(1, as); } @SuppressWarnings("unchecked") public static Tail tail() { - return INSTANCE; + return (Tail) INSTANCE; } public static Iterable tail(Iterable as) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tails.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tails.java new file mode 100644 index 000000000..7179d1f31 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tails.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Drop.drop; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Snoc.snoc; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Unfoldr.unfoldr; +import static com.jnape.palatable.lambda.functions.builtin.fn3.ZipWith.zipWith; +import static java.util.Collections.emptyList; + +/** + * Given an {@link Iterable}<A>, produce an + * {@link Iterable}<{@link Iterable}<A>>, representing all of the subsequences of tail + * elements, ordered by size, starting with the full {@link Iterable}. + *

+ * For example, tails(asList(1,2,3)) would iterate [1,2,3], [2,3], + * [3], and []. + * + * @param the Iterable element type + */ +public final class Tails implements Fn1, Iterable>> { + + private static final Tails INSTANCE = new Tails<>(); + + private Tails() { + } + + @Override + public Iterable> checkedApply(Iterable as) { + return snoc(emptyList(), zipWith((a, __) -> a, unfoldr(k -> just(tuple(drop(k, as), k + 1)), 0), as)); + } + + @SuppressWarnings("unchecked") + public static Tails tails() { + return (Tails) INSTANCE; + } + + public static Iterable> tails(Iterable as) { + return Tails.tails().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Uncons.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Uncons.java new file mode 100644 index 000000000..dc27d9bb3 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Uncons.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Tail.tail; + +/** + * Destructure an {@link Iterable} into a {@link Tuple2} of its head and tail, wrapped in an {@link Maybe}. If the + * {@link Iterable} is empty, returns {@link Maybe#nothing()}. + * + * @param the Iterable element type + */ +public final class Uncons implements Fn1, Maybe>>> { + + private static final Uncons INSTANCE = new Uncons<>(); + + private Uncons() { + } + + @Override + public Maybe>> checkedApply(Iterable as) { + return head(as).fmap(a -> tuple(a, tail(as))); + } + + @SuppressWarnings("unchecked") + public static Uncons uncons() { + return (Uncons) INSTANCE; + } + + public static Maybe>> uncons(Iterable as) { + return Uncons.uncons().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Upcast.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Upcast.java new file mode 100644 index 000000000..f4f187021 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Upcast.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +/** + * Upcast a value of type B to a value of type A that B extends. This is + * principally useful when dealing with parametric types that are invariant in their parameters and a cast is + * necessary for compatibility purposes. + *

+ * Example: + *

+ * {@code
+ * Iterable have = new ArrayList<>();
+ * Iterable want = map(upcast(), have); // necessary due to invariance in parameter
+ * }
+ * 
+ *

+ * Note that this is universally safe. + * + * @param the covariant type + * @param the contravariant type + */ +public final class Upcast implements Fn1 { + + private static final Upcast INSTANCE = new Upcast<>(); + + private Upcast() { + } + + @Override + public B checkedApply(A a) { + return a; + } + + @SuppressWarnings("unchecked") + public static Upcast upcast() { + return (Upcast) INSTANCE; + } + + public static B upcast(A a) { + return Upcast.upcast().apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java new file mode 100644 index 000000000..63c60c051 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +/** + * Function application, represented as a higher-order {@link Fn2} that receives an {@link Fn1} and its argument, and + * applies it. Useful for treating application as a combinator, e.g.: + *

+ * {@code
+ * List> fns     = asList(x -> x + 1, x -> x, x -> x - 1);
+ * List               args    = asList(0, 1, 2);
+ * Iterable           results = zipWith($(), fns, args); // [1, 1, 1]
+ * }
+ * 
+ * + * @param the applied {@link Fn1 Fn1's} input type + * @param the applied {@link Fn1 Fn1's} output type + */ +public final class $ implements Fn2, A, B> { + private static final $ INSTANCE = new $<>(); + + private $() { + } + + @Override + public B checkedApply(Fn1 fn, A a) { + return fn.apply(a); + } + + @SuppressWarnings("unchecked") + public static $ $() { + return ($) INSTANCE; + } + + public static Fn1 $(Fn1 fn) { + return $.$().apply(fn); + } + + public static B $(Fn1 fn, A a) { + return $.$(fn).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java index 53ecac095..26d76101f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java @@ -3,8 +3,6 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.specialized.BiPredicate; -import java.util.function.Function; - /** * Eagerly apply a predicate to each element in an Iterable, returning true if every element * satisfies the predicate, and false otherwise. This method short-circuits on the first false @@ -13,15 +11,15 @@ * @param The input Iterable element type * @see Any */ -public final class All implements BiPredicate, Iterable> { +public final class All implements BiPredicate, Iterable> { - private static final All INSTANCE = new All(); + private static final All INSTANCE = new All<>(); private All() { } @Override - public Boolean apply(Function predicate, Iterable as) { + public Boolean checkedApply(Fn1 predicate, Iterable as) { for (A a : as) if (!predicate.apply(a)) return false; @@ -31,14 +29,14 @@ public Boolean apply(Function predicate, Iterable as) { @SuppressWarnings("unchecked") public static All all() { - return INSTANCE; + return (All) INSTANCE; } - public static Fn1, Boolean> all(Function predicate) { + public static Fn1, ? extends Boolean> all(Fn1 predicate) { return All.all().apply(predicate); } - public static Boolean all(Function predicate, Iterable as) { + public static Boolean all(Fn1 predicate, Iterable as) { return All.all(predicate).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java new file mode 100644 index 000000000..b830a7e9c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Effect; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.io.IO; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * Given an {@link Effect}<A> and some A, produce an {@link IO} that, when run, performs + * the effect on A and returns it. + * + * @param the input and output + */ +public final class Alter implements Fn2>, A, IO> { + + private static final Alter INSTANCE = new Alter<>(); + + private Alter() { + } + + @Override + public IO checkedApply(Fn1> effect, A a) { + return effect.fmap(io -> io.fmap(constantly(a))).apply(a); + } + + @SuppressWarnings("unchecked") + public static Alter alter() { + return (Alter) INSTANCE; + } + + public static Fn1> alter(Effect effect) { + return Alter.alter().apply(effect); + } + + public static IO alter(Effect effect, A a) { + return Alter.alter(effect).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java index 09917f2a3..524b83159 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java @@ -2,8 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.specialized.BiPredicate; - -import java.util.function.Function; +import com.jnape.palatable.lambda.functions.specialized.Predicate; /** * Eagerly apply a predicate to each element in an Iterable, returning true if any element @@ -13,15 +12,15 @@ * @param The input Iterable element type * @see All */ -public final class Any implements BiPredicate, Iterable> { +public final class Any implements BiPredicate, Iterable> { - private static final Any INSTANCE = new Any(); + private static final Any INSTANCE = new Any<>(); private Any() { } @Override - public Boolean apply(Function predicate, Iterable as) { + public Boolean checkedApply(Fn1 predicate, Iterable as) { for (A a : as) if (predicate.apply(a)) return true; @@ -31,14 +30,14 @@ public Boolean apply(Function predicate, Iterable as) { @SuppressWarnings("unchecked") public static Any any() { - return INSTANCE; + return (Any) INSTANCE; } - public static Fn1, Boolean> any(Function predicate) { + public static Predicate> any(Fn1 predicate) { return Any.any().apply(predicate); } - public static Boolean any(Function predicate, Iterable as) { + public static Boolean any(Fn1 predicate, Iterable as) { return Any.any(predicate).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/AutoBracket.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/AutoBracket.java new file mode 100644 index 000000000..cf5108bbb --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/AutoBracket.java @@ -0,0 +1,48 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn3.Bracket; +import com.jnape.palatable.lambda.io.IO; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Bracket.bracket; +import static com.jnape.palatable.lambda.io.IO.io; + +/** + * Given an {@link IO} yielding some {@link AutoCloseable} type A and a kleisli arrow from that type to a + * new {@link IO} of type B, attempt to provision the A, applying the body operation if + * provisioning was successful and ensuring that {@link AutoCloseable#close} is called regardless of whether the body + * succeeds or fails. + *

+ * This is the canonical {@link Bracket bracketing} operation for {@link AutoCloseable AutoCloseables}. + * + * @param the initial {@link AutoCloseable} value type to map and clean up + * @param the resulting type + * @see Bracket + */ +public final class AutoBracket implements + Fn2, Fn1>, IO> { + + private static final AutoBracket INSTANCE = new AutoBracket<>(); + + private AutoBracket() { + } + + @Override + public IO checkedApply(IO io, Fn1> bodyIO) { + return bracket(io, a -> io(a::close), bodyIO); + } + + @SuppressWarnings("unchecked") + public static AutoBracket autoBracket() { + return (AutoBracket) INSTANCE; + } + + public static Fn1>, IO> autoBracket(IO io) { + return AutoBracket.autoBracket().apply(io); + } + + public static IO autoBracket(IO io, Fn1> bodyIO) { + return AutoBracket.autoBracket(io).apply(bodyIO); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Both.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Both.java new file mode 100644 index 000000000..0d22f7216 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Both.java @@ -0,0 +1,44 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; + +/** + * Given two functions f and g, produce a + * {@link Fn1}<A, {@link Tuple2}<B, C>> (the dual application of both functions). + * + * @param both function's input type + * @param the first function return type + * @param the second function return type + */ +public final class Both implements + Fn3, Fn1, A, Tuple2> { + + private static final Both INSTANCE = new Both<>(); + + private Both() { + } + + @Override + public Tuple2 checkedApply(Fn1 f, Fn1 g, A a) { + return Tuple2.fill(a).biMap(f::apply, g::apply); + } + + @SuppressWarnings("unchecked") + public static Both both() { + return (Both) INSTANCE; + } + + public static Fn1, Fn1>> both(Fn1 f) { + return Both.both().apply(f); + } + + public static Fn1> both(Fn1 f, Fn1 g) { + return Both.both(f).apply(g); + } + + public static Tuple2 both(Fn1 f, Fn1 g, A a) { + return Both.both(f, g).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProduct.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProduct.java index ec22bb0ed..17b5ca94b 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProduct.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProduct.java @@ -3,7 +3,7 @@ import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.CombinatorialIterator; +import com.jnape.palatable.lambda.internal.iteration.CombinatorialIterator; /** * Lazily compute the cartesian product of an Iterable<A> and Iterable<B>, @@ -21,19 +21,19 @@ */ public final class CartesianProduct implements Fn2, Iterable, Iterable>> { - private static final CartesianProduct INSTANCE = new CartesianProduct(); + private static final CartesianProduct INSTANCE = new CartesianProduct<>(); private CartesianProduct() { } @Override - public Iterable> apply(Iterable as, Iterable bs) { + public Iterable> checkedApply(Iterable as, Iterable bs) { return () -> new CombinatorialIterator<>(as.iterator(), bs.iterator()); } @SuppressWarnings("unchecked") public static CartesianProduct cartesianProduct() { - return INSTANCE; + return (CartesianProduct) INSTANCE; } public static Fn1, Iterable>> cartesianProduct(Iterable as) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CmpEq.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CmpEq.java new file mode 100644 index 000000000..3a91ade4c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CmpEq.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; + +/** + * Given two {@link Comparable} values of type A, return true if the first value is strictly + * equal to the second value (according to {@link Comparable#compareTo(Object)}; otherwise, return false. + * + * @param the value type + * @see CmpEqBy + * @see LT + * @see GT + */ +public final class CmpEq> implements BiPredicate { + + private static final CmpEq INSTANCE = new CmpEq<>(); + + private CmpEq() { + } + + @Override + public Boolean checkedApply(A x, A y) { + return cmpEqBy(id(), x, y); + } + + @SuppressWarnings("unchecked") + public static > CmpEq cmpEq() { + return (CmpEq) INSTANCE; + } + + public static > Predicate cmpEq(A x) { + return CmpEq.cmpEq().apply(x); + } + + public static > Boolean cmpEq(A x, A y) { + return cmpEq(x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Cons.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Cons.java index a4e146cae..63dabbaf2 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Cons.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Cons.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.ConsingIterator; +import com.jnape.palatable.lambda.internal.iteration.ConsingIterator; /** * Prepend an element to an Iterable. @@ -11,13 +11,13 @@ */ public final class Cons implements Fn2, Iterable> { - private static final Cons INSTANCE = new Cons(); + private static final Cons INSTANCE = new Cons<>(); private Cons() { } @Override - public Iterable apply(A a, Iterable as) { + public Iterable checkedApply(A a, Iterable as) { return () -> new ConsingIterator<>(a, as); } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Difference.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Difference.java new file mode 100644 index 000000000..38735673c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Difference.java @@ -0,0 +1,58 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn1.Distinct; + +import java.util.HashSet; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Distinct.distinct; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Empty.empty; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; + +/** + * Given two {@link Iterable Iterables} xs and ys, return the {@link Distinct distinct} + * elements of xs that are not in ys. Note that this is not symmetric + * difference. + *

+ * This operation preserves order, so the resulting elements from xs are iterated in the order that + * they uniquely occur in. + * + * @param the {@link Iterable} element type + */ +public final class Difference implements Fn2, Iterable, Iterable> { + + private static final Difference INSTANCE = new Difference<>(); + + private Difference() { + } + + @Override + public Iterable checkedApply(Iterable xs, Iterable ys) { + return () -> { + if (empty(xs)) + return xs.iterator(); + + if (empty(ys)) + return distinct(xs).iterator(); + + //todo: a pre-order depth-first fold of the expression tree would make this stack-safe + HashSet uniqueYs = toCollection(HashSet::new, ys); + return distinct(filter(a -> !uniqueYs.contains(a), xs)).iterator(); + }; + } + + @SuppressWarnings("unchecked") + public static Difference difference() { + return (Difference) INSTANCE; + } + + public static Fn1, Iterable> difference(Iterable xs) { + return Difference.difference().apply(xs); + } + + public static Iterable difference(Iterable xs, Iterable ys) { + return difference(xs).apply(ys); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Drop.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Drop.java index a0c1d9082..7229e51e9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Drop.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Drop.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.DroppingIterator; +import com.jnape.palatable.lambda.internal.iteration.DroppingIterable; /** * Lazily skip the first n elements from an Iterable by returning an Iterable @@ -15,19 +15,19 @@ */ public final class Drop implements Fn2, Iterable> { - private static final Drop INSTANCE = new Drop(); + private static final Drop INSTANCE = new Drop<>(); private Drop() { } @Override - public Iterable apply(Integer n, Iterable as) { - return () -> new DroppingIterator<>(n, as.iterator()); + public Iterable checkedApply(Integer n, Iterable as) { + return new DroppingIterable<>(n, as); } @SuppressWarnings("unchecked") public static Drop drop() { - return INSTANCE; + return (Drop) INSTANCE; } public static Fn1, Iterable> drop(int n) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java index a11472d68..a39ff3942 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java @@ -2,9 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.PredicatedDroppingIterator; - -import java.util.function.Function; +import com.jnape.palatable.lambda.internal.iteration.PredicatedDroppingIterable; /** * Lazily limit the Iterable by skipping the first contiguous group of elements that satisfy the predicate, @@ -16,28 +14,28 @@ * @see TakeWhile */ -public final class DropWhile implements Fn2, Iterable, Iterable> { +public final class DropWhile implements Fn2, Iterable, Iterable> { - private static final DropWhile INSTANCE = new DropWhile(); + private static final DropWhile INSTANCE = new DropWhile<>(); private DropWhile() { } @Override - public Iterable apply(Function predicate, Iterable as) { - return () -> new PredicatedDroppingIterator<>(predicate, as.iterator()); + public Iterable checkedApply(Fn1 predicate, Iterable as) { + return new PredicatedDroppingIterable<>(predicate, as); } @SuppressWarnings("unchecked") public static DropWhile dropWhile() { - return INSTANCE; + return (DropWhile) INSTANCE; } - public static Fn1, Iterable> dropWhile(Function predicate) { + public static Fn1, Iterable> dropWhile(Fn1 predicate) { return DropWhile.dropWhile().apply(predicate); } - public static Iterable dropWhile(Function predicate, Iterable as) { + public static Iterable dropWhile(Fn1 predicate, Iterable as) { return DropWhile.dropWhile(predicate).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Eq.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Eq.java index 83cce842c..adb903523 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Eq.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Eq.java @@ -3,6 +3,8 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.functions.specialized.Predicate; +import java.util.Objects; + /** * Type-safe equality in function form; uses {@link Object#equals}, not ==. * @@ -10,19 +12,19 @@ */ public final class Eq implements BiPredicate { - private static final Eq INSTANCE = new Eq(); + private static final Eq INSTANCE = new Eq<>(); private Eq() { } @Override - public Boolean apply(A x, A y) { - return x == null ? y == null : x.equals(y); + public Boolean checkedApply(A x, A y) { + return Objects.equals(x, y); } @SuppressWarnings("unchecked") public static Eq eq() { - return INSTANCE; + return (Eq) INSTANCE; } public static Predicate eq(A x) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java index ebad91eb5..2b445ca11 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java @@ -2,9 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.FilteringIterator; - -import java.util.function.Function; +import com.jnape.palatable.lambda.internal.iteration.FilteringIterable; /** * Lazily apply a predicate to each element in an Iterable, returning an Iterable of just the @@ -14,28 +12,28 @@ * @see TakeWhile * @see DropWhile */ -public final class Filter implements Fn2, Iterable, Iterable> { +public final class Filter implements Fn2, Iterable, Iterable> { - private static final Filter INSTANCE = new Filter(); + private static final Filter INSTANCE = new Filter<>(); private Filter() { } @Override - public Iterable apply(Function predicate, Iterable as) { - return () -> new FilteringIterator<>(predicate, as.iterator()); + public Iterable checkedApply(Fn1 predicate, Iterable as) { + return new FilteringIterable<>(predicate, as); } @SuppressWarnings("unchecked") public static Filter filter() { - return INSTANCE; + return (Filter) INSTANCE; } - public static Fn1, Iterable> filter(Function predicate) { + public static Fn1, Iterable> filter(Fn1 predicate) { return Filter.filter().apply(predicate); } - public static Iterable filter(Function predicate, Iterable as) { + public static Iterable filter(Fn1 predicate, Iterable as) { return Filter.filter(predicate).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Find.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Find.java index 583f29d20..0bb5c4e6b 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Find.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Find.java @@ -1,45 +1,43 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.functions.specialized.Predicate; - -import java.util.Optional; -import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Not.not; import static com.jnape.palatable.lambda.functions.builtin.fn2.DropWhile.dropWhile; /** * Iterate the elements in an Iterable, applying a predicate to each one, returning the first element that - * matches the predicate, wrapped in an Optional. If no elements match the predicate, the result is - * Optional.empty(). This function short-circuits, and so is safe to use on potentially infinite - * Iterables that guarantee to have an eventually matching element. + * matches the predicate, wrapped in a {@link Maybe}. If no elements match the predicate, the result is + * {@link Maybe#nothing()}. This function short-circuits, and so is safe to use on potentially infinite {@link Iterable} + * instances that guarantee to have an eventually matching element. * * @param the Iterable element type */ -public final class Find implements Fn2, Iterable, Optional> { +public final class Find implements Fn2, Iterable, Maybe> { - private static final Find INSTANCE = new Find(); + private static final Find INSTANCE = new Find<>(); private Find() { } @Override - public Optional apply(Function predicate, Iterable as) { - return head(dropWhile(((Predicate) predicate::apply).negate(), as)); + public Maybe checkedApply(Fn1 predicate, Iterable as) { + return head(dropWhile(not(predicate), as)); } @SuppressWarnings("unchecked") public static Find find() { - return INSTANCE; + return (Find) INSTANCE; } - public static Fn1, Optional> find(Function predicate) { + public static Fn1, Maybe> find(Fn1 predicate) { return Find.find().apply(predicate); } - public static Optional find(Function predicate, Iterable as) { + public static Maybe find(Fn1 predicate, Iterable as) { return Find.find(predicate).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GT.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GT.java new file mode 100644 index 000000000..09d1be4ae --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GT.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.builtin.fn3.GTBy; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTBy.gtBy; + +/** + * Given two {@link Comparable} values of type A, return true if the second value is strictly + * greater than the first value; otherwise, return false. + * + * @param the value type + * @see GTBy + * @see LT + */ +public final class GT> implements BiPredicate { + + private static final GT INSTANCE = new GT<>(); + + private GT() { + } + + @Override + public Boolean checkedApply(A y, A x) { + return gtBy(id(), y, x); + } + + @SuppressWarnings("unchecked") + public static > GT gt() { + return (GT) INSTANCE; + } + + public static > Predicate gt(A y) { + return GT.gt().apply(y); + } + + public static > Boolean gt(A y, A x) { + return gt(y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTE.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTE.java new file mode 100644 index 000000000..89413cea6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTE.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.builtin.fn3.GTEBy; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEBy.gteBy; + +/** + * Given two {@link Comparable} values of type A, return true if the second value is greater + * than or equal to the first value according to {@link Comparable#compareTo(Object)}; otherwise, return false. + * + * @param the value type + * @see GTEBy + * @see LTE + */ +public final class GTE> implements BiPredicate { + + private static final GTE INSTANCE = new GTE<>(); + + private GTE() { + } + + @Override + public Boolean checkedApply(A y, A x) { + return gteBy(id(), y, x); + } + + @SuppressWarnings("unchecked") + public static > GTE gte() { + return (GTE) INSTANCE; + } + + public static > Predicate gte(A y) { + return GTE.gte().apply(y); + } + + public static > Boolean gte(A y, A x) { + return gte(y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java new file mode 100644 index 000000000..42e61912a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java @@ -0,0 +1,49 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; + +/** + * Given an Iterable<V> vs and a key function V -> K f, + * fold vs into a Map<K, List<V>> by applying f to each element of + * vs, retaining values that map to the same key in a list, in the order they were iterated in. + * + * @param the Map key type + * @param the Map value type + * @see InGroupsOf + */ +public final class GroupBy implements Fn2, Iterable, Map>> { + + private static final GroupBy INSTANCE = new GroupBy<>(); + + private GroupBy() { + } + + @Override + public Map> checkedApply(Fn1 keyFn, Iterable vs) { + return foldLeft((m, v) -> { + m.computeIfAbsent(keyFn.apply(v), __ -> new ArrayList<>()).add(v); + return m; + }, new HashMap<>(), vs); + } + + @SuppressWarnings("unchecked") + public static GroupBy groupBy() { + return (GroupBy) INSTANCE; + } + + public static Fn1, Map>> groupBy(Fn1 keyFn) { + return GroupBy.groupBy().apply(keyFn); + } + + public static Map> groupBy(Fn1 keyFn, Iterable vs) { + return GroupBy.groupBy(keyFn).apply(vs); + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOf.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOf.java index ebdf7cab7..673d6c980 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOf.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOf.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.GroupingIterator; +import com.jnape.palatable.lambda.internal.iteration.GroupingIterator; /** * Lazily group the Iterable by returning an Iterable of smaller Iterables of @@ -14,19 +14,19 @@ */ public final class InGroupsOf implements Fn2, Iterable>> { - private static final InGroupsOf INSTANCE = new InGroupsOf(); + private static final InGroupsOf INSTANCE = new InGroupsOf<>(); private InGroupsOf() { } @Override - public Iterable> apply(Integer k, Iterable as) { + public Iterable> checkedApply(Integer k, Iterable as) { return () -> new GroupingIterator<>(k, as.iterator()); } @SuppressWarnings("unchecked") public static InGroupsOf inGroupsOf() { - return INSTANCE; + return (InGroupsOf) INSTANCE; } public static Fn1, Iterable>> inGroupsOf(Integer k) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersperse.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersperse.java index d7f0e3b63..a4a0b2e51 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersperse.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersperse.java @@ -6,21 +6,28 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Tail.tail; import static com.jnape.palatable.lambda.functions.builtin.fn2.PrependAll.prependAll; +/** + * Lazily inject the provided separator value between each value in the supplied Iterable. An + * Iterable with fewer than two elements is left untouched. + * + * @param the Iterable parameter type + * @see PrependAll + */ public final class Intersperse implements Fn2, Iterable> { - private static final Intersperse INSTANCE = new Intersperse(); + private static final Intersperse INSTANCE = new Intersperse<>(); private Intersperse() { } @Override - public Iterable apply(A a, Iterable as) { + public Iterable checkedApply(A a, Iterable as) { return tail(prependAll(a, as)); } @SuppressWarnings("unchecked") public static Intersperse intersperse() { - return INSTANCE; + return (Intersperse) INSTANCE; } public static Fn1, Iterable> intersperse(A a) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into.java new file mode 100644 index 000000000..523c08543 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.Map; + +/** + * Given an {@link Fn2}<A, B, C> and a {@link Map.Entry}<A, B>, destructure the + * entry and apply the key and value as arguments to the function, returning the result. + * + * @param the first argument type + * @param the second argument type + * @param the result type + */ +public final class Into implements Fn2, Map.Entry, C> { + + private static final Into INSTANCE = new Into<>(); + + private Into() { + } + + @Override + public C checkedApply(Fn2 fn, Map.Entry entry) { + return fn.apply(entry.getKey(), entry.getValue()); + } + + @SuppressWarnings("unchecked") + public static Into into() { + return (Into) INSTANCE; + } + + public static Fn1, C> into( + Fn2 fn) { + return Into.into().apply(fn); + } + + public static C into(Fn2 fn, + Map.Entry entry) { + return Into.into(fn).apply(entry); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into1.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into1.java new file mode 100644 index 000000000..3b574146b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into1.java @@ -0,0 +1,35 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.hlist.SingletonHList; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +/** + * Given an {@link Fn1}<A, B> and a {@link SingletonHList}<A>, pop the head and + * apply it to the function, returning the result. + * + * @param the first argument type + * @param the result type + */ +public final class Into1 implements Fn2, SingletonHList, B> { + + private static final Into1 INSTANCE = new Into1<>(); + + @Override + public B checkedApply(Fn1 fn, SingletonHList singletonHList) { + return fn.apply(singletonHList.head()); + } + + @SuppressWarnings("unchecked") + public static Into1 into1() { + return (Into1) INSTANCE; + } + + public static Fn1, B> into1(Fn1 fn) { + return Into1.into1().apply(fn); + } + + public static B into1(Fn1 fn, SingletonHList singletonHList) { + return Into1.into1(fn).apply(singletonHList); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into3.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into3.java new file mode 100644 index 000000000..d58969d57 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into3.java @@ -0,0 +1,39 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.product.Product3; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; + +/** + * Given an {@link Fn3}<A, B, C, D> and a {@link Product3}<A, B, C>, destructure + * the product and apply the slots as arguments to the function, returning the result. + * + * @param the first argument type + * @param the second argument type + * @param the third argument type + * @param the result type + */ +public final class Into3 implements Fn2, Product3, D> { + + private static final Into3 INSTANCE = new Into3<>(); + + @Override + public D checkedApply(Fn3 fn, Product3 product) { + return product.into(fn); + } + + @SuppressWarnings("unchecked") + public static Into3 into3() { + return (Into3) INSTANCE; + } + + public static Fn1, D> into3(Fn3 fn) { + return Into3.into3().apply(fn); + } + + public static D into3(Fn3 fn, + Product3 product) { + return Into3.into3(fn).apply(product); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4.java new file mode 100644 index 000000000..14dba6f90 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.product.Product4; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn4; + +/** + * Given an {@link Fn4}<A, B, C, D, E> and a {@link Product4}<A, B, C, D>, + * destructure the product and apply the slots as arguments to the function, returning the result. + * + * @param the first argument type + * @param the second argument type + * @param the third argument type + * @param the fourth argument type + * @param the result type + */ +public final class Into4 implements Fn2, Product4, E> { + + private static final Into4 INSTANCE = new Into4<>(); + + @Override + public E checkedApply(Fn4 fn, + Product4 product) { + return product.into(fn); + } + + @SuppressWarnings("unchecked") + public static Into4 into4() { + return (Into4) INSTANCE; + } + + public static Fn1, E> into4( + Fn4 fn) { + return Into4.into4().apply(fn); + } + + public static E into4(Fn4 fn, + Product4 product) { + return Into4.into4(fn).apply(product); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5.java new file mode 100644 index 000000000..8cbdf25f5 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.product.Product5; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn5; + +/** + * Given an {@link Fn5}<A, B, C, D, E, F> and a {@link Product5}<A, B, C, D, E>, + * destructure the product and apply the slots as arguments to the function, returning the result. + * + * @param the first argument type + * @param the second argument type + * @param the third argument type + * @param the fourth argument type + * @param the fifth argument type + * @param the result type + */ +public final class Into5 implements Fn2, Product5, F> { + + private static final Into5 INSTANCE = new Into5<>(); + + @Override + public F checkedApply(Fn5 fn, + Product5 product) { + return product.into(fn); + } + + @SuppressWarnings("unchecked") + public static Into5 into5() { + return (Into5) INSTANCE; + } + + public static Fn1, F> into5( + Fn5 fn) { + return Into5.into5().apply(fn); + } + + public static F into5(Fn5 fn, + Product5 product) { + return Into5.into5(fn).apply(product); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6.java new file mode 100644 index 000000000..0c59771f2 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.product.Product6; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn6; + +/** + * Given an {@link Fn6}<A, B, C, D, E, F, G> and a + * {@link Product6}<A, B, C, D, E, F>, destructure the product and apply the slots as arguments to + * the function, returning the result. + * + * @param the first argument type + * @param the second argument type + * @param the third argument type + * @param the fourth argument type + * @param the fifth argument type + * @param the sixth argument type + * @param the result type + */ +public final class Into6 implements Fn2, Product6, G> { + + private static final Into6 INSTANCE = new Into6<>(); + + @Override + public G checkedApply(Fn6 fn, + Product6 product) { + return product.into(fn); + } + + @SuppressWarnings("unchecked") + public static Into6 into6() { + return (Into6) INSTANCE; + } + + public static Fn1, G> into6( + Fn6 fn) { + return Into6.into6().apply(fn); + } + + public static G into6( + Fn6 fn, + Product6 product) { + return Into6.into6(fn).apply(product); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7.java new file mode 100644 index 000000000..612068814 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7.java @@ -0,0 +1,48 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.product.Product7; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn7; + +/** + * Given an {@link Fn7}<A, B, C, D, E, F, G, H> and a + * {@link Product7}<A, B, C, D, E, F, G>, destructure the product and apply the slots as arguments to + * the function, returning the result. + * + * @param the first argument type + * @param the second argument type + * @param the third argument type + * @param the fourth argument type + * @param the fifth argument type + * @param the sixth argument type + * @param the seventh argument type + * @param the result type + */ +public final class Into7 implements Fn2, Product7, H> { + + private static final Into7 INSTANCE = new Into7<>(); + + @Override + public H checkedApply( + Fn7 fn, + Product7 product) { + return product.into(fn); + } + + @SuppressWarnings("unchecked") + public static Into7 into7() { + return (Into7) INSTANCE; + } + + public static Fn1, H> into7( + Fn7 fn) { + return Into7.into7().apply(fn); + } + + public static H into7( + Fn7 fn, + Product7 product) { + return Into7.into7(fn).apply(product); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8.java new file mode 100644 index 000000000..fdad33a72 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8.java @@ -0,0 +1,49 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.product.Product8; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn8; + +/** + * Given an {@link Fn8}<A, B, C, D, E, F, G, H, I> and a + * {@link Product8}<A, B, C, D, E, F, G, H>, destructure the product and apply the slots as arguments + * to the function, returning the result. + * + * @param the first argument type + * @param the second argument type + * @param the third argument type + * @param the fourth argument type + * @param the fifth argument type + * @param the sixth argument type + * @param the seventh argument type + * @param the eighth argument type + * @param the result type + */ +public final class Into8 implements Fn2, Product8, I> { + + private static final Into8 INSTANCE = new Into8<>(); + + @Override + public I checkedApply( + Fn8 fn, + Product8 product) { + return product.into(fn); + } + + @SuppressWarnings("unchecked") + public static Into8 into8() { + return (Into8) INSTANCE; + } + + public static Fn1, I> into8( + Fn8 fn) { + return Into8.into8().apply(fn); + } + + public static I into8( + Fn8 fn, + Product8 product) { + return Into8.into8(fn).apply(product); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Iterate.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Iterate.java index 18c299abe..19fba6f79 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Iterate.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Iterate.java @@ -3,41 +3,39 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import java.util.Optional; -import java.util.function.Function; - +import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn2.Unfoldr.unfoldr; /** - * Lazily generate an infinite Iterable from the successive applications of the function first to the - * initial seed value, then to the result, and so on; i.e., the result of iterate(x -> x + 1, 0) would - * produce an infinite Iterable over the elements 0, 1, 2, 3, ... and so on. + * Lazily generate an infinite {@link Iterable} from the successive applications of the function first to the initial + * seed value, then to the result, and so on; i.e., the result of iterate(x -> x + 1, 0) would produce + * an infinite {@link Iterable} over the elements 0, 1, 2, 3, ... and so on. * * @param The Iterable element type */ -public final class Iterate implements Fn2, A, Iterable> { +public final class Iterate implements Fn2, A, Iterable> { - private static final Iterate INSTANCE = new Iterate(); + private static final Iterate INSTANCE = new Iterate<>(); private Iterate() { } @Override - public Iterable apply(Function fn, A seed) { - return unfoldr(a -> Optional.of(tuple(a, fn.apply(a))), seed); + public Iterable checkedApply(Fn1 fn, A seed) { + return unfoldr(a -> just(tuple(a, fn.apply(a))), seed); } @SuppressWarnings("unchecked") public static Iterate iterate() { - return INSTANCE; + return (Iterate) INSTANCE; } - public static Fn1> iterate(Function fn) { + public static Fn1> iterate(Fn1 fn) { return Iterate.iterate().apply(fn); } - public static Iterable iterate(Function fn, A seed) { + public static Iterable iterate(Fn1 fn, A seed) { return Iterate.iterate(fn).apply(seed); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LT.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LT.java new file mode 100644 index 000000000..359d59b66 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LT.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.builtin.fn3.LTBy; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTBy.ltBy; + +/** + * Given two {@link Comparable} values of type A, return true if the second value is strictly + * less than the first value; otherwise, return false. + * + * @param the value type + * @see LTBy + * @see GT + */ +public final class LT> implements BiPredicate { + + private static final LT INSTANCE = new LT<>(); + + private LT() { + } + + @Override + public Boolean checkedApply(A y, A x) { + return ltBy(id(), y, x); + } + + @SuppressWarnings("unchecked") + public static > LT lt() { + return (LT) INSTANCE; + } + + public static > Predicate lt(A y) { + return LT.lt().apply(y); + } + + public static > Boolean lt(A y, A x) { + return lt(y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTE.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTE.java new file mode 100644 index 000000000..85f07b61e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTE.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.builtin.fn3.LTEBy; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEBy.lteBy; + +/** + * Given two {@link Comparable} values of type A, return true if the second value is less than + * or equal to the first value according to {@link Comparable#compareTo(Object)} otherwise, return false. + * + * @param the value typ + * @see LTEBy + * @see GTE + */ +public final class LTE> implements BiPredicate { + + private static final LTE INSTANCE = new LTE<>(); + + private LTE() { + } + + @Override + public Boolean checkedApply(A y, A x) { + return lteBy(id(), y, x); + } + + @SuppressWarnings("unchecked") + public static > LTE lte() { + return (LTE) INSTANCE; + } + + public static > Predicate lte(A y) { + return LTE.lte().apply(y); + } + + public static > Boolean lte(A y, A x) { + return lte(y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRec.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRec.java new file mode 100644 index 000000000..c1b7253d2 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRec.java @@ -0,0 +1,55 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.specialized.Kleisli; +import com.jnape.palatable.lambda.functor.builtin.Lazy; + +import static com.jnape.palatable.lambda.functions.specialized.Kleisli.kleisli; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + +/** + * Given a {@link Fn2} that receives a recursive function and an input and yields a {@link Lazy lazy} result, and an + * input, produce a {@link Lazy lazy} result that, when forced, will recursively invoke the function until it terminates + * in a stack-safe way. + *

+ * Example: + *

+ * {@code
+ * Lazy lazyFactorial = lazyRec((fact, x) -> x.equals(ONE)
+ *                                                       ? lazy(x)
+ *                                                       : fact.apply(x.subtract(ONE)).fmap(y -> y.multiply(x)),
+ *                                                  BigInteger.valueOf(50_000));
+ * BigInteger value = lazyFactorial.value(); // 3.34732050959714483691547609407148647791277322381045 x 10^213236
+ * }
+ * 
+ * + * @param the input type + * @param the output type + */ +public final class LazyRec implements + Fn2, Lazy>, A, Lazy>, A, Lazy> { + + private static final LazyRec INSTANCE = new LazyRec<>(); + + private LazyRec() { + } + + @Override + public Lazy checkedApply(Fn2, Lazy>, A, Lazy> fn, A a) { + return lazy(a).flatMap(fn.apply(lazyRec(fn))); + } + + @SuppressWarnings("unchecked") + public static LazyRec lazyRec() { + return (LazyRec) INSTANCE; + } + + public static Kleisli, Lazy> lazyRec( + Fn2, Lazy>, A, Lazy> fn) { + return kleisli(LazyRec.lazyRec().apply(fn)); + } + + public static Lazy lazyRec(Fn2, Lazy>, A, Lazy> fn, A a) { + return lazyRec(fn).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java new file mode 100644 index 000000000..43697b8a5 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java @@ -0,0 +1,61 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.Collections; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Drop.drop; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Unfoldr.unfoldr; + +/** + * Given a binary predicate and an {@link Iterable}<A>, return an {@link Iterable}<{@link + * Iterable}<A>> of the contiguous groups of elements that match the predicate pairwise. + *

+ * Example: magnetizeBy((x, y) -> x <= y, asList(1, 2, 3, 2, 2, 3, 2, 1)); // [[1, 2, 3], [2, 2, 3], [2], + * [1]] + * + * @param the {@link Iterable} element type + */ +public final class MagnetizeBy implements Fn2, Iterable, Iterable>> { + + private static final MagnetizeBy INSTANCE = new MagnetizeBy<>(); + + private MagnetizeBy() { + } + + @Override + public Iterable> checkedApply(Fn2 predicate, + Iterable as) { + return () -> uncons(as).fmap(into((A head, Iterable tail) -> { + Iterable group = cons(head, unfoldr(into((pivot, ys) -> uncons(ys) + .flatMap(into((y, recurse) -> predicate.apply(pivot, y) + ? just(tuple(y, tuple(y, recurse))) + : nothing()))), tuple(head, tail))); + return cons(group, () -> apply(predicate, drop(size(group).intValue(), as)).iterator()); + })).orElseGet(() -> Collections::emptyIterator).iterator(); + } + + @SuppressWarnings("unchecked") + public static MagnetizeBy magnetizeBy() { + return (MagnetizeBy) INSTANCE; + } + + public static Fn1, Iterable>> magnetizeBy( + Fn2 predicate) { + return MagnetizeBy.magnetizeBy().apply(predicate); + } + + public static Iterable> magnetizeBy( + Fn2 predicate, + Iterable as) { + return MagnetizeBy.magnetizeBy(predicate).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java index 1736edb0f..ace10d667 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java @@ -2,9 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.MappingIterator; - -import java.util.function.Function; +import com.jnape.palatable.lambda.internal.iteration.MappingIterable; /** * Lazily apply a function to each element in an Iterable, producing an Iterable of the mapped @@ -13,28 +11,28 @@ * @param A type contravariant to the input Iterable element type * @param A type covariant to the output Iterable element type */ -public final class Map implements Fn2, Iterable, Iterable> { +public final class Map implements Fn2, Iterable, Iterable> { - private static final Map INSTANCE = new Map(); + private static final Map INSTANCE = new Map<>(); private Map() { } @Override - public Iterable apply(Function fn, Iterable as) { - return () -> new MappingIterator<>(fn, as.iterator()); + public Iterable checkedApply(Fn1 fn, Iterable as) { + return new MappingIterable<>(fn, as); } @SuppressWarnings("unchecked") public static Map map() { - return INSTANCE; + return (Map) INSTANCE; } - public static Fn1, Iterable> map(Function fn) { + public static Fn1, Iterable> map(Fn1 fn) { return Map.map().apply(fn); } - public static Iterable map(Function fn, Iterable as) { + public static Iterable map(Fn1 fn, Iterable as) { return Map.map(fn).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial2.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial2.java deleted file mode 100644 index 2c2317470..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial2.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jnape.palatable.lambda.functions.builtin.fn2; - -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.Fn2; - -import java.util.function.BiFunction; - -/** - * Partially apply (fix) the first argument of a {@link BiFunction}, producing an Fn1 that - * takes the remaining argument. This is isomorphic to calling {@link Fn2#apply(Object)}. - * - * @param The type of the value to be supplied - * @param The input argument type of the resulting function - * @param The return type of the resulting function - * @see Partial3 - */ -public final class Partial2 implements Fn2, A, Fn1> { - - private static final Partial2 INSTANCE = new Partial2(); - - private Partial2() { - } - - @Override - public Fn1 apply(BiFunction fn, A a) { - return b -> fn.apply(a, b); - } - - @SuppressWarnings("unchecked") - public static Partial2, A, Fn1> partial2() { - return INSTANCE; - } - - public static Fn1> partial2(BiFunction fn) { - return Partial2.partial2().apply(new Partial2().toBiFunction(), fn); - } - - public static Fn1 partial2(BiFunction fn, A a) { - return partial2(fn).apply(a); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial3.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial3.java deleted file mode 100644 index 8473a356b..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial3.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jnape.palatable.lambda.functions.builtin.fn2; - -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.functions.Fn3; - -/** - * Partially apply (fix) the first argument of a Fn3, producing a Fn2 that takes the remaining - * two argument. This is isomorphic to calling {@link Fn3#apply(Object)}. - * - * @param The type of the value to be supplied - * @param The first input argument type of the resulting function - * @param The second input argument type of the resulting function - * @param The return type of the resulting function - * @see Partial2 - */ -public final class Partial3 implements Fn2, A, Fn2> { - - private static final Partial3 INSTANCE = new Partial3(); - - private Partial3() { - } - - @Override - public Fn2 apply(Fn3 fn, A a) { - return fn.apply(a); - } - - @SuppressWarnings("unchecked") - public static Partial3 partial3() { - return INSTANCE; - } - - public static Fn1> partial3(Fn3 fn) { - return Partial3.partial3().apply(fn); - } - - public static Fn2 partial3(Fn3 fn, A a) { - return partial3(fn).apply(a); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java index 90f142288..20efa316f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java @@ -1,63 +1,56 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import java.util.Optional; -import java.util.function.Function; +import java.util.Collections; -import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static java.util.Collections.emptySet; /** - * Given an Iterable<A> as and a disjoint mapping function a -> Either<L, - * R>, return a {@link Tuple2} over the lazily unwrapped left L and right R values - * in the first and second slots, respectively. Note that while the tuple must be constructed eagerly, the left and - * right iterables contained therein are both lazy, so comprehension over infinite iterables is supported. + * Given an Iterable<A> as and a disjoint mapping function a -> + * CoProduct2<A, B>, return a {@link Tuple2} over the lazily unwrapped left A and right + * B values in the first and second slots, respectively. Note that while the tuple must be constructed + * eagerly, the left and right iterables contained therein are both lazy, so comprehension over infinite iterables is + * supported. * * @param A type contravariant to the input Iterable element type - * @param The output left Iterable element type, as well as the Either L type - * @param The output right Iterable element type, as well as the Either R type - * @see Either + * @param The output left Iterable element type, as well as the CoProduct2 A type + * @param The output right Iterable element type, as well as the CoProduct2 B type + * @see CoProduct2 */ -public final class Partition implements Fn2>, Iterable, Tuple2, Iterable>> { +public final class Partition implements Fn2>, Iterable, Tuple2, Iterable>> { - private static final Partition INSTANCE = new Partition(); + private static final Partition INSTANCE = new Partition<>(); private Partition() { } @Override - public Tuple2, Iterable> apply(Function> function, - Iterable as) { - Iterable> eithers = map(function, as); - - Iterable lefts = unwrapRight(map(Either::invert, eithers)); - Iterable rights = unwrapRight(map(id(), eithers)); - - return tuple(lefts, rights); - } - - private Iterable unwrapRight(Iterable> eithers) { - return map(Optional::get, filter(Optional::isPresent, map(Either::toOptional, eithers))); + public Tuple2, Iterable> checkedApply(Fn1> function, + Iterable as) { + return Tuple2.>>fill(map(function, as)) + .biMap(Map., Iterable>map(cp -> cp.match(Collections::singleton, __ -> emptySet())), + Map., Iterable>map(cp -> cp.match(__ -> emptySet(), Collections::singleton))) + .biMap(flatten(), flatten()); } @SuppressWarnings("unchecked") public static Partition partition() { - return INSTANCE; + return (Partition) INSTANCE; } public static Fn1, Tuple2, Iterable>> partition( - Function> function) { + Fn1> function) { return Partition.partition().apply(function); } public static Tuple2, Iterable> partition( - Function> function, + Fn1> function, Iterable as) { return Partition.partition(function).apply(as); } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek.java new file mode 100644 index 000000000..c793b45b7 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek.java @@ -0,0 +1,45 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Effect; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.io.IO; + +/** + * Given an {@link Effect}, "peek" at the value contained inside a {@link Functor} via {@link Functor#fmap(Fn1)}, + * applying the {@link Effect} to the contained value, if there is one. + * + * @param the functor parameter type + * @param the functor type + * @deprecated in favor of producing an {@link IO} from the given {@link Functor} and explicitly running it + */ +@Deprecated +public final class Peek> implements Fn2>, FA, FA> { + private static final Peek INSTANCE = new Peek<>(); + + private Peek() { + } + + @Override + @SuppressWarnings("unchecked") + public FA checkedApply(Fn1> effect, FA fa) { + return (FA) fa.fmap(a -> { + effect.apply(a).unsafePerformIO(); + return a; + }).coerce(); + } + + @SuppressWarnings("unchecked") + public static > Peek peek() { + return (Peek) INSTANCE; + } + + public static > Fn1 peek(Fn1> effect) { + return Peek.peek().apply(effect); + } + + public static > FA peek(Fn1> effect, FA fa) { + return Peek.peek(effect).apply(fa); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek2.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek2.java new file mode 100644 index 000000000..bcc98eb55 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek2.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Effect; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.BoundedBifunctor; +import com.jnape.palatable.lambda.io.IO; + +/** + * Given two {@link Effect}s, "peek" at the values contained inside a {@link Bifunctor} via + * {@link BoundedBifunctor#biMap(Fn1, Fn1)}, applying the {@link Effect}s to the contained values, if there are any. + * + * @param the bifunctor's first parameter type + * @param the bifunctor's second parameter type + * @param the bifunctor type + * @deprecated in favor of producing an {@link IO} from the given {@link BoundedBifunctor} and explicitly running it + */ +@Deprecated +public final class Peek2> implements + Fn3>, Fn1>, FAB, FAB> { + private static final Peek2 INSTANCE = new Peek2<>(); + + private Peek2() { + } + + @Override + @SuppressWarnings("unchecked") + public FAB checkedApply(Fn1> effectA, Fn1> effectB, FAB fab) { + return (FAB) fab.biMap(a -> { + effectA.apply(a).unsafePerformIO(); + return a; + }, b -> { + effectB.apply(b).unsafePerformIO(); + return b; + }); + } + + @SuppressWarnings("unchecked") + public static > Peek2 peek2() { + return (Peek2) INSTANCE; + } + + public static > + Fn2>, FAB, FAB> peek2(Fn1> effectA) { + return Peek2.peek2().apply(effectA); + } + + public static > Fn1 peek2( + Fn1> effectA, + Fn1> effectB) { + return Peek2.peek2(effectA).apply(effectB); + } + + public static > FAB peek2( + Fn1> effectA, + Fn1> effectB, + FAB fab) { + return Peek2.peek2(effectA, effectB).apply(fab); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java index d29ae7c6c..ad25f29f7 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java @@ -2,27 +2,30 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; - -import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Tail.tail; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; -import static java.util.Collections.emptyList; - +import com.jnape.palatable.lambda.internal.iteration.PrependingIterator; + +/** + * Lazily prepend each value with of the Iterable with the supplied separator value. An empty + * Iterable is left untouched. + * + * @param the Iterable parameter type + * @see Intersperse + */ public final class PrependAll implements Fn2, Iterable> { - private static final PrependAll INSTANCE = new PrependAll(); + private static final PrependAll INSTANCE = new PrependAll<>(); private PrependAll() { } @Override - public Iterable apply(A a, Iterable as) { - return () -> head(as).map(head -> cons(a, cons(head, prependAll(a, tail(as))))).orElse(emptyList()).iterator(); + public Iterable checkedApply(A a, Iterable as) { + return () -> new PrependingIterator<>(a, as.iterator()); } @SuppressWarnings("unchecked") public static PrependAll prependAll() { - return INSTANCE; + return (PrependAll) INSTANCE; } public static Fn1, Iterable> prependAll(A a) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeft.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeft.java index d333ba2e9..aae71023f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeft.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeft.java @@ -1,54 +1,52 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; import java.util.Iterator; -import java.util.Optional; -import java.util.function.BiFunction; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; /** - * Given an Iterable of As and a {@link BiFunction}<A, A, A>, iteratively - * accumulate over the Iterable, returning an Optional<A> (if the Iterable - * is empty, the result is Optional.empty(); otherwise, the result is wrapped in - * Optional.of(). For this reason, null accumulation results are considered erroneous and will - * throw. + * Given an {@link Iterable}<A> and a {@link Fn2}<A, A, A>, iteratively + * accumulate over the {@link Iterable}, returning {@link Maybe}<A>. If the {@link Iterable} is + * empty, the result is {@link Maybe#nothing()}; otherwise, the result is wrapped in {@link Maybe#just}. For this + * reason, null accumulation results are considered erroneous and will throw. *

- * This function is isomorphic to a left fold over the Iterable where the head element is the starting - * accumulation value and the result is lifted into an Optional. + * This function is isomorphic to a left fold over the {@link Iterable} where the head element is the starting + * accumulation value and the result is lifted into {@link Maybe}. * * @param The input Iterable element type, as well as the accumulation type * @see ReduceRight - * @see com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft + * @see FoldLeft */ -public final class ReduceLeft implements Fn2, Iterable, Optional> { +public final class ReduceLeft implements Fn2, Iterable, Maybe> { - private static final ReduceLeft INSTANCE = new ReduceLeft(); + private static final ReduceLeft INSTANCE = new ReduceLeft<>(); private ReduceLeft() { } @Override - public Optional apply(BiFunction fn, Iterable as) { + public Maybe checkedApply(Fn2 fn, Iterable as) { Iterator iterator = as.iterator(); - if (!iterator.hasNext()) - return Optional.empty(); - - return Optional.of(foldLeft(fn, iterator.next(), () -> iterator)); + return !iterator.hasNext() ? nothing() : just(foldLeft(fn, iterator.next(), () -> iterator)); } @SuppressWarnings("unchecked") public static ReduceLeft reduceLeft() { - return INSTANCE; + return (ReduceLeft) INSTANCE; } - public static Fn1, Optional> reduceLeft(BiFunction fn) { + public static Fn1, Maybe> reduceLeft(Fn2 fn) { return ReduceLeft.reduceLeft().apply(fn); } - public static Optional reduceLeft(BiFunction fn, Iterable as) { + public static Maybe reduceLeft(Fn2 fn, Iterable as) { return ReduceLeft.reduceLeft(fn).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRight.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRight.java index 4e2181024..6d027c594 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRight.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRight.java @@ -1,50 +1,48 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; - -import java.util.Optional; -import java.util.function.BiFunction; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight; import static com.jnape.palatable.lambda.functions.builtin.fn1.Reverse.reverse; import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft.reduceLeft; /** - * Given an Iterable of As and a {@link BiFunction}<A, A, A>, iteratively - * accumulate over the Iterable, returning an Optional<A> (if the Iterable - * is empty, the result is Optional.empty(); otherwise, the result is wrapped in - * Optional.of(). For this reason, null accumulation results are considered erroneous and will - * throw. + * Given an {@link Iterable}<A> and a {@link Fn2}<A, A, A>, iteratively + * accumulate over the {@link Iterable}, returning {@link Maybe}<A>. If the {@link Iterable} is + * empty, the result is {@link Maybe#nothing()}; otherwise, the result is wrapped in {@link Maybe#just}. For this + * reason, null accumulation results are considered erroneous and will throw. *

- * This function is isomorphic to a right fold over the Iterable where the tail element is the starting - * accumulation value and the result is lifted into an Optional. + * This function is isomorphic to a right fold over the {@link Iterable} where the tail element is the starting + * accumulation value and the result is lifted into {@link Maybe}. * * @param The input Iterable element type, as well as the accumulation type * @see ReduceLeft - * @see com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight + * @see FoldRight */ -public final class ReduceRight implements Fn2, Iterable, Optional> { +public final class ReduceRight implements Fn2, Iterable, Maybe> { - private static final ReduceRight INSTANCE = new ReduceRight(); + private static final ReduceRight INSTANCE = new ReduceRight<>(); private ReduceRight() { } @Override - public final Optional apply(BiFunction fn, Iterable as) { + public final Maybe checkedApply(Fn2 fn, Iterable as) { return reduceLeft((b, a) -> fn.apply(a, b), reverse(as)); } @SuppressWarnings("unchecked") public static ReduceRight reduceRight() { - return INSTANCE; + return (ReduceRight) INSTANCE; } - public static Fn1, Optional> reduceRight(BiFunction fn) { + public static Fn1, Maybe> reduceRight(Fn2 fn) { return ReduceRight.reduceRight().apply(fn); } - public static Optional reduceRight(BiFunction fn, Iterable as) { + public static Maybe reduceRight(Fn2 fn, Iterable as) { return ReduceRight.reduceRight(fn).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Replicate.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Replicate.java new file mode 100644 index 000000000..98d436461 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Replicate.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; + +/** + * Produce an {@link Iterable} of a value n times. + * + * @param the output Iterable element type + */ +public final class Replicate implements Fn2> { + + private static final Replicate INSTANCE = new Replicate<>(); + + private Replicate() { + } + + @Override + public Iterable checkedApply(Integer n, A a) { + return take(n, repeat(a)); + } + + @SuppressWarnings("unchecked") + public static Replicate replicate() { + return (Replicate) INSTANCE; + } + + public static Fn1> replicate(Integer n) { + return Replicate.replicate().apply(n); + } + + public static Iterable replicate(Integer n, A a) { + return Replicate.replicate(n).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java new file mode 100644 index 000000000..bed18e1aa --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java @@ -0,0 +1,95 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.traversable.LambdaIterable; +import com.jnape.palatable.lambda.traversable.LambdaMap; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Map; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * Given a {@link Traversable} of {@link Applicative}s and a pure {@link Applicative} constructor, traverse the + * elements from left to right, zipping the {@link Applicative}s together and collecting the results. If the + * {@link Traversable} is empty, simply wrap it in the {@link Applicative} by calling pure. + *

+ * Modulo any type-level coercion, this is equivalent to traversable.traverse(id(), pure). + *

+ * Note that specialized overloads exist for certain built-in JDK types that would otherwise be instances + * {@link Traversable} if it weren't for the extensibility problem. + * + * @param the Traversable element type + * @param the Applicative unification parameter + * @param the Traversable unification parameter + * @param the Traversable instance wrapped in the output Applicative + * @param the concrete parametrized output Applicative type + */ +public final class Sequence, Trav extends Traversable, + TravA extends Traversable, + AppTrav extends Applicative> implements + Fn2, Trav>, Fn1, AppTrav> { + + private static final Sequence INSTANCE = new Sequence<>(); + + private Sequence() { + } + + @Override + public AppTrav checkedApply(Traversable, Trav> traversable, + Fn1 pure) { + return traversable.traverse(id(), pure); + } + + @SuppressWarnings("unchecked") + public static , Trav extends Traversable, + TravA extends Traversable, + AppTrav extends Applicative> Sequence sequence() { + return (Sequence) INSTANCE; + } + + public static , Trav extends Traversable, + TravA extends Traversable, + AppTrav extends Applicative> Fn1, AppTrav> sequence( + Traversable, Trav> traversable) { + return Sequence.sequence().apply(traversable); + } + + public static , Trav extends Traversable, + TravA extends Traversable, + AppTrav extends Applicative> AppTrav sequence( + Traversable, Trav> traversable, + Fn1 pure) { + return Sequence.sequence(traversable).apply(pure); + } + + @SuppressWarnings({"unchecked", "RedundantTypeArguments"}) + public static , AppIterable extends Applicative, App>> + Fn1, ? extends AppIterable>, AppIterable> sequence( + Iterable> iterableApp) { + return pure -> (AppIterable) Sequence., LambdaIterable, Applicative, App>>sequence( + LambdaIterable.wrap(iterableApp), x -> pure.apply(x.unwrap()).fmap(LambdaIterable::wrap)) + .fmap(LambdaIterable::unwrap); + } + + public static , AppIterable extends Applicative, App>> + AppIterable sequence(Iterable> iterableApp, + Fn1, ? extends AppIterable> pure) { + return Sequence.sequence(iterableApp).apply(pure); + } + + @SuppressWarnings({"unchecked", "RedundantTypeArguments"}) + public static , AppMap extends Applicative, App>> + Fn1, ? extends AppMap>, AppMap> sequence(Map> mapApp) { + return pure -> (AppMap) Sequence., LambdaMap, Applicative, App>>sequence( + LambdaMap.wrap(mapApp), x -> pure.apply(x.unwrap()).fmap(LambdaMap::wrap)) + .fmap(LambdaMap::unwrap); + } + + public static , AppMap extends Applicative, App>> + AppMap sequence(Map> mapApp, Fn1, ? extends AppMap> pure) { + return Sequence.sequence(mapApp).apply(pure); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Slide.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Slide.java new file mode 100644 index 000000000..1a65422d6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Slide.java @@ -0,0 +1,49 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Init.init; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Tails.tails; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; + +/** + * Given an {@link Iterable}<A>, "slide" a window of k elements across the {@link + * Iterable} by one element at a time, returning an {@link Iterable}<{@link Iterable}<A>>. + *

+ * Example: + * + * slide(2, asList(1, 2, 3, 4, 5)); // [[1, 2], [2, 3], [3, 4], [4, 5]] + * + * @param the Iterable element type + */ +public final class Slide implements Fn2, Iterable>> { + + private static final Slide INSTANCE = new Slide<>(); + + private Slide() { + } + + @Override + public Iterable> checkedApply(Integer k, Iterable as) { + if (k == 0) + throw new IllegalArgumentException("k must be greater than 0"); + + return times(k, init(), map(take(k), tails(as))); + } + + @SuppressWarnings("unchecked") + public static Slide slide() { + return (Slide) INSTANCE; + } + + public static Fn1, Iterable>> slide(Integer k) { + return Slide.slide().apply(k); + } + + public static Iterable> slide(Integer k, Iterable as) { + return Slide.slide(k).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java new file mode 100644 index 000000000..bfd8007bc --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.internal.iteration.SnocIterable; + +/** + * Opposite of {@link Cons}: lazily append an element to the end of the given {@link Iterable}. + *

+ * Note that obtaining both laziness and stack-safety is particularly tricky here, and requires an initial eager + * deforestation of O(k) traversals where k is the number of contiguously nested + * {@link Snoc}s. + * + * @param the Iterable element type + */ +public final class Snoc implements Fn2, Iterable> { + + private static final Snoc INSTANCE = new Snoc<>(); + + private Snoc() { + } + + @Override + public Iterable checkedApply(A a, Iterable as) { + return new SnocIterable<>(a, as); + } + + @SuppressWarnings("unchecked") + public static Snoc snoc() { + return (Snoc) INSTANCE; + } + + public static Fn1, Iterable> snoc(A a) { + return Snoc.snoc().apply(a); + } + + public static Iterable snoc(A a, Iterable as) { + return snoc(a).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortBy.java new file mode 100644 index 000000000..13ea9d0df --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortBy.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn1.Sort; + +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.SortWith.sortWith; +import static java.util.Comparator.comparing; + +/** + * Given an {@link Iterable} and some mapping function from the {@link Iterable} element type to some + * {@link Comparable} type, produce a sorted {@link List} of the original elements based on sorting applied to the + * result of the mapping function. Note that this is both eager and monolithic. + * + * @param the input Iterable and output List element type + * @param the mapped Comparable type + * @see Sort + * @see SortWith + */ +public final class SortBy> implements Fn2, Iterable, List> { + + private static final SortBy INSTANCE = new SortBy<>(); + + private SortBy() { + } + + @Override + public List checkedApply(Fn1 fn, Iterable as) { + return sortWith(comparing(fn.toFunction()), as); + } + + @SuppressWarnings("unchecked") + public static > SortBy sortBy() { + return (SortBy) INSTANCE; + } + + public static > Fn1, List> sortBy(Fn1 fn) { + return SortBy.sortBy().apply(fn); + } + + public static > List sortBy(Fn1 fn, Iterable as) { + return SortBy.sortBy(fn).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortWith.java new file mode 100644 index 000000000..2c93239ee --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortWith.java @@ -0,0 +1,48 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn1.Sort; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; + +/** + * Given an {@link Iterable} and a {@link java.util.Comparator} over the {@link Iterable} element type, produce a + * sorted {@link List} of the original elements based on sorting applied by the {@link java.util.Comparator}. Note that + * this is both eager and monolithic. + * + * @param the input Iterable and output List element type + * @see Sort + * @see SortBy + */ +public final class SortWith implements Fn2, Iterable, List> { + + private static final SortWith INSTANCE = new SortWith<>(); + + private SortWith() { + } + + @Override + public List checkedApply(Comparator comparator, Iterable as) { + List result = toCollection(ArrayList::new, as); + result.sort(comparator); + return result; + } + + @SuppressWarnings("unchecked") + public static SortWith sortWith() { + return (SortWith) INSTANCE; + } + + public static Fn1, List> sortWith(Comparator comparator) { + return SortWith.sortWith().apply(comparator); + } + + public static List sortWith(Comparator comparator, Iterable as) { + return SortWith.sortWith(comparator).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Span.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Span.java new file mode 100644 index 000000000..966b6518d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Span.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.DropWhile.dropWhile; +import static com.jnape.palatable.lambda.functions.builtin.fn2.TakeWhile.takeWhile; + +/** + * Given a predicate, return a {@link Tuple2} where the first slot is the front contiguous elements of an {@link + * Iterable} matching the predicate and the second slot is all the remaining elements. + * + * @param the {@link Iterable} element type + */ +public final class Span implements + Fn2, Iterable, Tuple2, Iterable>> { + + private static final Span INSTANCE = new Span<>(); + + private Span() { + } + + @Override + public Tuple2, Iterable> checkedApply(Fn1 predicate, Iterable as) { + return Tuple2.fill(as).biMap(takeWhile(predicate), dropWhile(predicate)); + } + + @SuppressWarnings("unchecked") + public static Span span() { + return (Span) INSTANCE; + } + + public static Fn1, Tuple2, Iterable>> span( + Fn1 predicate) { + return Span.span().apply(predicate); + } + + public static Tuple2, Iterable> span(Fn1 predicate, + Iterable as) { + return Span.span(predicate).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java index a67bdcfd4..15ddbcad2 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.TakingIterator; +import com.jnape.palatable.lambda.internal.iteration.TakingIterable; /** * Lazily limit the Iterable to n elements by returning an Iterable that stops @@ -15,19 +15,19 @@ */ public final class Take implements Fn2, Iterable> { - private static final Take INSTANCE = new Take(); + private static final Take INSTANCE = new Take<>(); private Take() { } @Override - public Iterable apply(Integer n, Iterable as) { - return () -> new TakingIterator<>(n, as.iterator()); + public Iterable checkedApply(Integer n, Iterable as) { + return new TakingIterable<>(n, as); } @SuppressWarnings("unchecked") public static Take take() { - return INSTANCE; + return (Take) INSTANCE; } public static Fn1, Iterable> take(int n) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java index e14f5caf8..07524b7b4 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java @@ -2,9 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.PredicatedTakingIterator; - -import java.util.function.Function; +import com.jnape.palatable.lambda.internal.iteration.PredicatedTakingIterable; /** * Lazily limit the Iterable to the first group of contiguous elements that satisfy the predicate by @@ -15,28 +13,28 @@ * @see Filter * @see DropWhile */ -public final class TakeWhile implements Fn2, Iterable, Iterable> { +public final class TakeWhile implements Fn2, Iterable, Iterable> { - private static final TakeWhile INSTANCE = new TakeWhile(); + private static final TakeWhile INSTANCE = new TakeWhile<>(); private TakeWhile() { } @Override - public Iterable apply(Function predicate, Iterable as) { - return () -> new PredicatedTakingIterator<>(predicate, as.iterator()); + public Iterable checkedApply(Fn1 predicate, Iterable as) { + return new PredicatedTakingIterable<>(predicate, as); } @SuppressWarnings("unchecked") public static TakeWhile takeWhile() { - return INSTANCE; + return (TakeWhile) INSTANCE; } - public static Fn1, Iterable> takeWhile(Function predicate) { + public static Fn1, Iterable> takeWhile(Fn1 predicate) { return TakeWhile.takeWhile().apply(predicate); } - public static Iterable takeWhile(Function predicate, Iterable as) { + public static Iterable takeWhile(Fn1 predicate, Iterable as) { return TakeWhile.takeWhile(predicate).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToArray.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToArray.java new file mode 100644 index 000000000..cecaa1543 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToArray.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.lang.reflect.Array; +import java.util.Collection; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; + +/** + * Write all the elements of an {@link Iterable} directly into an array of the specified type. If the {@link Iterable} + * is an instance of {@link Collection}, use {@link Collection#toArray(Object[])}. + * + * @param the {@link Iterable} element type + */ +public final class ToArray implements Fn2, Iterable, A[]> { + + private static final ToArray INSTANCE = new ToArray<>(); + + private ToArray() { + } + + @Override + @SuppressWarnings("unchecked") + public A[] checkedApply(Class arrayType, Iterable as) { + A[] array = (A[]) Array.newInstance(arrayType.getComponentType(), size(as).intValue()); + if (as instanceof Collection) + return ((Collection) as).toArray(array); + + int index = 0; + for (A a : as) { + array[index++] = a; + } + return array; + } + + @SuppressWarnings("unchecked") + public static ToArray toArray() { + return (ToArray) INSTANCE; + } + + public static Fn1, A[]> toArray(Class arrayType) { + return ToArray.toArray().apply(arrayType); + } + + public static A[] toArray(Class arrayType, Iterable as) { + return toArray(arrayType).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollection.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollection.java index b9b46b893..535d90e0a 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollection.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollection.java @@ -1,43 +1,43 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.functions.Fn0; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import java.util.Collection; -import java.util.function.Supplier; /** - * Given a {@link Supplier} of some {@link Collection} C, create an instance of C and add - * all of the elements in the provided Iterable to the instance. Note that instances of C - * must support {@link Collection#add} (which is to say, must not throw on invocation). + * Given an {@link Fn0} of some {@link Collection} C, create an instance of C and add all of + * the elements in the provided Iterable to the instance. Note that instances of C must + * support {@link Collection#add} (which is to say, must not throw on invocation). * * @param the iterable element type * @param the resulting collection type */ -public final class ToCollection> implements Fn2, Iterable, C> { +public final class ToCollection> implements Fn2, Iterable, C> { - private static final ToCollection INSTANCE = new ToCollection(); + private static final ToCollection INSTANCE = new ToCollection<>(); private ToCollection() { } @Override - public C apply(Supplier cSupplier, Iterable as) { - C c = cSupplier.get(); + public C checkedApply(Fn0 cFn0, Iterable as) { + C c = cFn0.apply(); as.forEach(c::add); return c; } @SuppressWarnings("unchecked") public static > ToCollection toCollection() { - return INSTANCE; + return (ToCollection) INSTANCE; } - public static > Fn1, C> toCollection(Supplier cSupplier) { - return ToCollection.toCollection().apply(cSupplier); + public static > Fn1, C> toCollection(Fn0 cFn0) { + return ToCollection.toCollection().apply(cFn0); } - public static > C toCollection(Supplier cSupplier, Iterable as) { - return toCollection(cSupplier).apply(as); + public static > C toCollection(Fn0 cFn0, Iterable as) { + return toCollection(cFn0).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMap.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMap.java index 7db0b82ab..85dad9e41 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMap.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMap.java @@ -1,44 +1,48 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.functions.Fn0; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import java.util.Map; -import java.util.function.Supplier; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; /** - * Given a {@link Supplier} of some {@link Map} M, create an instance of M and put - * all of the entries in the provided Iterable into the instance. Note that instances of M - * must support {@link java.util.Map#put} (which is to say, must not throw on invocation). + * Given an {@link Fn0} of some {@link Map} M, create an instance of M and put all of the + * entries in the provided Iterable into the instance. Note that instances of M must support + * {@link java.util.Map#put} (which is to say, must not throw on invocation). * * @param the key element type * @param the value element type * @param the resulting map type */ -public final class ToMap> implements Fn2, Iterable>, M> { +public final class ToMap> implements Fn2, Iterable>, M> { - private static final ToMap INSTANCE = new ToMap<>(); + private static final ToMap INSTANCE = new ToMap<>(); private ToMap() { } @Override - public M apply(Supplier mSupplier, Iterable> entries) { - M m = mSupplier.get(); - entries.forEach(kv -> m.put(kv.getKey(), kv.getValue())); - return m; + public M checkedApply(Fn0 mFn0, Iterable> entries) { + return foldLeft((m, kv) -> { + m.put(kv.getKey(), kv.getValue()); + return m; + }, mFn0.apply(), entries); } @SuppressWarnings("unchecked") public static > ToMap toMap() { - return INSTANCE; + return (ToMap) INSTANCE; } - public static > Fn1>, M> toMap(Supplier mSupplier) { - return ToMap.toMap().apply(mSupplier); + public static > Fn1>, M> toMap(Fn0 mFn0) { + return ToMap.toMap().apply(mFn0); } - public static > M toMap(Supplier mSupplier, Iterable> entries) { - return toMap(mSupplier).apply(entries); + public static > M toMap(Fn0 mFn0, + Iterable> entries) { + return toMap(mFn0).apply(entries); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Tupler2.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Tupler2.java index de1b4a283..939518d38 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Tupler2.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Tupler2.java @@ -15,19 +15,19 @@ */ public final class Tupler2 implements Fn2> { - private static final Tupler2 INSTANCE = new Tupler2(); + private static final Tupler2 INSTANCE = new Tupler2<>(); private Tupler2() { } @Override - public Tuple2 apply(A a, B b) { + public Tuple2 checkedApply(A a, B b) { return tuple(a, b); } @SuppressWarnings("unchecked") public static Tupler2 tupler() { - return INSTANCE; + return (Tupler2) INSTANCE; } public static Fn1> tupler(A a) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java index 196d743a6..dbf8c8480 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java @@ -1,19 +1,17 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.UnfoldingIterator; - -import java.util.Optional; -import java.util.function.Function; +import com.jnape.palatable.lambda.internal.iteration.UnfoldingIterator; /** - * Given an initial seed value and a function that takes the seed type and produces an Optional<{@link + * Given an initial seed value and a function that takes the seed type and produces an {@link Maybe}<{@link * Tuple2}<X, Seed>>, where the tuple's first slot represents the next Iterable element, * and the second slot represents the next input to the unfolding function, unfold an Iterable of - * Xs. Returning Optional.empty() from the unfolding function is a signal that the - * Iterable is fully unfolded. + * Xs. Returning {@link Maybe#nothing()} from the unfolding function is a signal that the {@link Iterable} + * is fully unfolded. *

* For more information, read about Anamorphisms. *

@@ -21,36 +19,36 @@ *

  * {@code
  * Iterable zeroThroughTenInclusive = unfoldr(x -> x <= 10
- *         ? Optional.of(tuple(x, x + 1))
- *         : Optional.empty(), 0);
+ *         ? Maybe.just(tuple(x, x + 1))
+ *         : Maybe.nothing(), 0);
  * }
  * 
* * @param The output Iterable element type * @param The unfolding function input type */ -public final class Unfoldr implements Fn2>>, B, Iterable> { +public final class Unfoldr implements Fn2>>, B, Iterable> { - private static final Unfoldr INSTANCE = new Unfoldr(); + private static final Unfoldr INSTANCE = new Unfoldr<>(); private Unfoldr() { } @Override - public Iterable apply(Function>> fn, B b) { + public Iterable checkedApply(Fn1>> fn, B b) { return () -> new UnfoldingIterator<>(fn, b); } @SuppressWarnings("unchecked") public static Unfoldr unfoldr() { - return INSTANCE; + return (Unfoldr) INSTANCE; } - public static Fn1> unfoldr(Function>> fn) { + public static Fn1> unfoldr(Fn1>> fn) { return Unfoldr.unfoldr().apply(fn); } - public static Iterable unfoldr(Function>> fn, B b) { + public static Iterable unfoldr(Fn1>> fn, B b) { return unfoldr(fn).apply(b); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Until.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Until.java new file mode 100644 index 000000000..be5b1f5b4 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Until.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.io.IO; + +import static com.jnape.palatable.lambda.io.IO.io; + +/** + * Given a {@link Fn1 predicate function} for a value of some type A and an {@link IO} that yields a value + * of type A, produce an {@link IO} that repeatedly executes the original {@link IO} until the predicate + * returns true when applied to the yielded value. + * + * @param the {@link IO} value type + */ +public final class Until implements Fn2, IO, IO> { + + private static final Until INSTANCE = new Until<>(); + + private Until() { + } + + @Override + public IO checkedApply(Fn1 pred, IO io) { + return io.flatMap(a -> pred.apply(a) ? io(a) : until(pred, io)); + } + + @SuppressWarnings("unchecked") + public static Until until() { + return (Until) INSTANCE; + } + + public static Fn1, IO> until(Fn1 pred) { + return Until.until().apply(pred); + } + + public static IO until(Fn1 pred, IO io) { + return Until.until(pred).apply(io); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Zip.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Zip.java index 323f00e02..ff41594b1 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Zip.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Zip.java @@ -4,6 +4,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler; import static com.jnape.palatable.lambda.functions.builtin.fn3.ZipWith.zipWith; /** @@ -17,19 +18,19 @@ */ public final class Zip implements Fn2, Iterable, Iterable>> { - private static final Zip INSTANCE = new Zip(); + private static final Zip INSTANCE = new Zip<>(); private Zip() { } @Override - public Iterable> apply(Iterable as, Iterable bs) { - return zipWith(Tupler2.tupler().toBiFunction(), as, bs); + public Iterable> checkedApply(Iterable as, Iterable bs) { + return zipWith(tupler(), as, bs); } @SuppressWarnings("unchecked") public static Zip zip() { - return INSTANCE; + return (Zip) INSTANCE; } public static Fn1, Iterable>> zip(Iterable as) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Between.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Between.java new file mode 100644 index 000000000..ccb98929f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Between.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Clamp.clamp; + +/** + * Given two bounds and a value, return whether or not the value is greater than or equal to the lower bound and less + * than or equal to the upper bound. + * + * @param the bounds and input type + */ +public final class Between> implements Fn3 { + + private static final Between INSTANCE = new Between<>(); + + private Between() { + } + + @Override + public Boolean checkedApply(A lower, A upper, A a) { + return clamp(lower, upper, a).equals(a); + } + + @SuppressWarnings("unchecked") + public static > Between between() { + return (Between) INSTANCE; + } + + public static > BiPredicate between(A lower) { + return Between.between().apply(lower)::apply; + } + + public static > Predicate between(A lower, A upper) { + return between(lower).apply(upper); + } + + public static > Boolean between(A lower, A upper, A a) { + return between(lower, upper).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Bracket.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Bracket.java new file mode 100644 index 000000000..59e6f512d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Bracket.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.Monad; + +/** + * Given an {@link IO} that yields some type A, a cleanup operation to run if a value of that type could be + * provisioned, and a kleisli arrow from that type to a new {@link IO} of type B, produce an + * {@link IO}<B> that, when run, will provision the A, + * {@link Monad#flatMap(Fn1) flatMap} it to B, and clean up the original value if it was produced in the + * first place. + * + * @param the initial value to map and clean up + * @param the resulting type + */ +public final class Bracket implements + Fn3, Fn1>, Fn1>, IO> { + + private static final Bracket INSTANCE = new Bracket<>(); + + private Bracket() { + } + + @Override + public IO checkedApply(IO io, Fn1> cleanupIO, + Fn1> bodyIO) throws Throwable { + return io.flatMap(a -> bodyIO.apply(a).ensuring(cleanupIO.apply(a))); + } + + @SuppressWarnings("unchecked") + public static Bracket bracket() { + return (Bracket) INSTANCE; + } + + public static Fn2>, Fn1>, IO> bracket( + IO io) { + return Bracket.bracket().apply(io); + } + + public static Fn1>, IO> bracket( + IO io, Fn1> cleanupIO) { + return Bracket.bracket(io).apply(cleanupIO); + } + + public static IO bracket(IO io, Fn1> cleanupIO, + Fn1> bodyIO) { + return Bracket.bracket(io, cleanupIO).apply(bodyIO); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Clamp.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Clamp.java new file mode 100644 index 000000000..3343afae4 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Clamp.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; + +import static com.jnape.palatable.lambda.semigroup.builtin.Max.max; +import static com.jnape.palatable.lambda.semigroup.builtin.Min.min; + +/** + * Given two bounds and a value, "clamp" the value between the bounds via the following algorithm: + * - if the value is strictly less than the lower bound, return the lower bound + * - if the value is strictly greater than the upper bound, return the upper bound + * - otherwise, return the value + * + * @param the bounds and input type + */ +public final class Clamp> implements Fn3 { + + private static final Clamp INSTANCE = new Clamp<>(); + + private Clamp() { + } + + @Override + public A checkedApply(A lower, A upper, A a) { + return max(min(lower, upper)).fmap(min(max(lower, upper))).apply(a); + } + + @SuppressWarnings("unchecked") + public static > Clamp clamp() { + return (Clamp) INSTANCE; + } + + public static > Fn2 clamp(A lower) { + return Clamp.clamp().apply(lower); + } + + public static > Fn1 clamp(A lower, A upper) { + return clamp(lower).apply(upper); + } + + public static > A clamp(A lower, A upper, A a) { + return clamp(lower, upper).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java new file mode 100644 index 000000000..a6e04baa9 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java @@ -0,0 +1,63 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.CmpEq; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqWith.cmpEqWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B and two values + * of type A, return true if the first value is strictly equal to the second value (according + * to {@link Comparable#compareTo(Object)} in terms of their mapped B results; otherwise, return false. + * + * @param the value type + * @param the mapped comparison type + * @see CmpEq + * @see CmpEqWith + * @see LTBy + * @see GTBy + */ +public final class CmpEqBy> implements Fn3, A, A, Boolean> { + + private static final CmpEqBy INSTANCE = new CmpEqBy<>(); + + private CmpEqBy() { + } + + @Override + public Boolean checkedApply(Fn1 compareFn, A x, A y) { + return cmpEqWith(comparing(compareFn.toFunction()), x, y); + } + + @Override + public BiPredicate apply(Fn1 compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Fn1 compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static > CmpEqBy cmpEqBy() { + return (CmpEqBy) INSTANCE; + } + + public static > BiPredicate cmpEqBy(Fn1 compareFn) { + return CmpEqBy.cmpEqBy().apply(compareFn); + } + + public static > Predicate cmpEqBy(Fn1 compareFn, A x) { + return CmpEqBy.cmpEqBy(compareFn).apply(x); + } + + public static > Boolean cmpEqBy(Fn1 compareFn, A x, A y) { + return cmpEqBy(compareFn, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java new file mode 100644 index 000000000..ff95cd2fc --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.equal; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, return + * true if the first value is strictly equal to the second value (according to + * {@link Comparator#compare(Object, Object)} otherwise, return false. + * + * @param the value type + * @see CmpEqBy + * @see LTBy + * @see GTBy + * @see Compare + */ +public final class CmpEqWith implements Fn3, A, A, Boolean> { + + private static final CmpEqWith INSTANCE = new CmpEqWith<>(); + + private CmpEqWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(equal()); + } + + @SuppressWarnings("unchecked") + public static CmpEqWith cmpEqWith() { + return (CmpEqWith) INSTANCE; + } + + public static BiPredicate cmpEqWith(Comparator comparator) { + return CmpEqWith.cmpEqWith().apply(comparator); + } + + public static Predicate cmpEqWith(Comparator comparator, A x) { + return CmpEqWith.cmpEqWith(comparator).apply(x); + } + + public static Boolean cmpEqWith(Comparator comparator, A x, A y) { + return cmpEqWith(comparator, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java new file mode 100644 index 000000000..b388feca9 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java @@ -0,0 +1,56 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.ordering.ComparisonRelation; + +import java.util.Comparator; + +/** + * Given a {@link Comparator} from some type A and two values of type A, return a + * {@link ComparisonRelation} of the first value with reference to the second value (according to + * {@link Comparator#compare(Object, Object)}. The order of parameters is flipped with respect to + * {@link Comparator#compare(Object, Object)} for more idiomatic partial application. + *

+ * Example: + *

+ * {@code
+ *  Compare.compare(naturalOrder(), 1, 2); // ComparisonRelation.GreaterThan
+ *  Compare.compare(naturalOrder(), 2, 1); // ComparisonRelation.LessThan
+ *  Compare.compare(naturalOrder(), 1, 1); // ComparisonRelation.Equal
+ * }
+ * 
+ * + * @param
the value type + * @see Comparator + * @see Compare + */ +public final class Compare implements Fn3, A, A, ComparisonRelation> { + private static final Compare INSTANCE = new Compare<>(); + + private Compare() { + } + + @Override + public ComparisonRelation checkedApply(Comparator aComparator, A a, A a2) throws Throwable { + return ComparisonRelation.fromInt(aComparator.compare(a2, a)); + } + + @SuppressWarnings("unchecked") + public static Compare compare() { + return (Compare) INSTANCE; + } + + public static Fn2 compare(Comparator comparator) { + return Compare.compare().apply(comparator); + } + + public static Fn1 compare(Comparator comparator, A a) { + return compare(comparator).apply(a); + } + + public static ComparisonRelation compare(Comparator aComparator, A a, A a2) { + return compare(aComparator, a).apply(a2); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldLeft.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldLeft.java index 17fb496d1..e05b1bcf0 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldLeft.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldLeft.java @@ -4,12 +4,10 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; -import java.util.function.BiFunction; - /** - * Given an Iterable of As, a starting value B, and a {@link - * BiFunction}<B, A, B>, iteratively accumulate over the Iterable, ultimately returning a - * final B value. If the Iterable is empty, just return the starting B value. + * Given an Iterable of As, a starting value B, and a + * {@link Fn2}<B, A, B>, iteratively accumulate over the Iterable, ultimately returning + * a final B value. If the Iterable is empty, just return the starting B value. * Note that, as the name implies, this function accumulates from left to right, such that foldLeft(f, 0, * asList(1, 2, 3, 4, 5)) is evaluated as f(f(f(f(f(0, 1), 2), 3), 4), 5). *

@@ -20,15 +18,15 @@ * @param The accumulation type * @see FoldRight */ -public final class FoldLeft implements Fn3, B, Iterable, B> { +public final class FoldLeft implements Fn3, B, Iterable, B> { - private static final FoldLeft INSTANCE = new FoldLeft(); + private static final FoldLeft INSTANCE = new FoldLeft<>(); private FoldLeft() { } @Override - public B apply(BiFunction fn, B acc, Iterable as) { + public B checkedApply(Fn2 fn, B acc, Iterable as) { B accumulation = acc; for (A a : as) accumulation = fn.apply(accumulation, a); @@ -37,18 +35,18 @@ public B apply(BiFunction fn, B acc, Iterable @SuppressWarnings("unchecked") public static FoldLeft foldLeft() { - return INSTANCE; + return (FoldLeft) INSTANCE; } - public static Fn2, B> foldLeft(BiFunction fn) { + public static Fn2, B> foldLeft(Fn2 fn) { return FoldLeft.foldLeft().apply(fn); } - public static Fn1, B> foldLeft(BiFunction fn, B acc) { + public static Fn1, B> foldLeft(Fn2 fn, B acc) { return FoldLeft.foldLeft(fn).apply(acc); } - public static B foldLeft(BiFunction fn, B acc, Iterable as) { + public static B foldLeft(Fn2 fn, B acc, Iterable as) { return FoldLeft.foldLeft(fn, acc).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRight.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRight.java index bb6f49f2c..21116dd84 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRight.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRight.java @@ -3,18 +3,31 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.builtin.Lazy; -import java.util.function.BiFunction; - -import static com.jnape.palatable.lambda.functions.builtin.fn1.Reverse.reverse; -import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +import static com.jnape.palatable.lambda.functions.builtin.fn2.LazyRec.lazyRec; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** - * Given an Iterable of As, a starting value B, and a {@link - * BiFunction}<A, B, B>, iteratively accumulate over the Iterable, ultimately returning a - * final B value. If the Iterable is empty, just return the starting B value. - * This function is the iterative inverse of {@link FoldLeft}, such that foldRight(f, 0, asList(1, 2, 3, 4, - * 5)) is evaluated as f(f(f(f(f(0, 5), 4), 3), 2), 1). + * Given an Iterable of As, a starting {@link Lazy lazy} value B, and a + * {@link Fn2}<A, {@link Lazy}<B>, {@link Lazy}<B>>, iteratively accumulate over the + * Iterable, ultimately returning a final {@link Lazy}<B> value. If the + * Iterable is empty, just return the starting {@link Lazy}<B> value. This function is + * computationally the iterative inverse of {@link FoldLeft}, but uses {@link Lazy} to allow support stack-safe + * execution. + *

+ * Example: + *

+ * {@code
+ * Lazy> lazyCopy = foldRight(
+ *     (head, lazyTail) -> lazy(cons(head, () -> lazyTail.value().iterator())),
+ *     lazy(emptyList()),
+ *     iterate(x -> x + 1, 0));
+ * Iterable copy = () -> lazyCopy.value().iterator();
+ * take(3, copy).forEach(System.out::println); // prints "1, 2, 3"
+ * take(3, copy).forEach(System.out::println); // prints "1, 2, 3"
+ * }
+ * 
*

* For more information, read about Catamorphisms. @@ -23,32 +36,41 @@ * @param The accumulation type * @see FoldLeft */ -public final class FoldRight implements Fn3, B, Iterable, B> { +public final class FoldRight implements + Fn3, ? extends Lazy>, Lazy, Iterable, Lazy> { - private static final FoldRight INSTANCE = new FoldRight(); + private static final FoldRight INSTANCE = new FoldRight<>(); private FoldRight() { } @Override - public B apply(BiFunction fn, B acc, Iterable as) { - return foldLeft((b, a) -> fn.apply(a, b), acc, reverse(as)); + public Lazy checkedApply(Fn2, ? extends Lazy> fn, Lazy acc, + Iterable as) { + return lazyRec((f, lazyIt) -> lazyIt.flatMap(it -> it.hasNext() + ? fn.apply(it.next(), f.apply(lazy(it))) + : acc), + lazy(as::iterator)); } @SuppressWarnings("unchecked") public static FoldRight foldRight() { - return INSTANCE; + return (FoldRight) INSTANCE; } - public static Fn2, B> foldRight(BiFunction fn) { + public static Fn2, Iterable, Lazy> foldRight( + Fn2, ? extends Lazy> fn) { return FoldRight.foldRight().apply(fn); } - public static Fn1, B> foldRight(BiFunction fn, B acc) { + public static Fn1, Lazy> foldRight( + Fn2, ? extends Lazy> fn, + Lazy acc) { return FoldRight.foldRight(fn).apply(acc); } - public static B foldRight(BiFunction fn, B acc, Iterable as) { + public static Lazy foldRight(Fn2, ? extends Lazy> fn, Lazy acc, + Iterable as) { return FoldRight.foldRight(fn, acc).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTBy.java new file mode 100644 index 000000000..caddec12e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTBy.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.GT; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B and two values + * of type A, return true if the second value is strictly greater than the first value in + * terms of their mapped B results; otherwise, return false. + * + * @param the value type + * @param the mapped comparison type + * @see GT + * @see GTWith + * @see LTBy + */ +public final class GTBy> implements Fn3, A, A, Boolean> { + + private static final GTBy INSTANCE = new GTBy<>(); + + private GTBy() { + } + + @Override + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return gtWith(comparing(compareFn.toFunction()), y, x); + } + + @Override + public BiPredicate apply(Fn1 compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Fn1 compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static > GTBy gtBy() { + return (GTBy) INSTANCE; + } + + public static > BiPredicate gtBy(Fn1 fn) { + return GTBy.gtBy().apply(fn); + } + + public static > Predicate gtBy(Fn1 fn, A y) { + return GTBy.gtBy(fn).apply(y); + } + + public static > Boolean gtBy(Fn1 fn, A y, A x) { + return gtBy(fn, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEBy.java new file mode 100644 index 000000000..7a1b4acad --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEBy.java @@ -0,0 +1,63 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.GTE; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEWith.gteWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B and two values + * of type A, return true if the second value is greater than or equal to the first value in + * terms of their mapped B results according to {@link Comparable#compareTo(Object)}; otherwise, return + * false. + * + * @param the value type + * @param the mapped comparison type + * @see GTE + * @see GTEWith + * @see LTEBy + */ +public final class GTEBy> implements Fn3, A, A, Boolean> { + + private static final GTEBy INSTANCE = new GTEBy<>(); + + private GTEBy() { + } + + @Override + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return gteWith(comparing(compareFn.toFunction()), y, x); + } + + @Override + public BiPredicate apply(Fn1 compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Fn1 compareFn, A y) { + return predicate(Fn3.super.apply(compareFn, y)); + } + + @SuppressWarnings("unchecked") + public static > GTEBy gteBy() { + return (GTEBy) INSTANCE; + } + + public static > BiPredicate gteBy(Fn1 fn) { + return GTEBy.gteBy().apply(fn); + } + + public static > Predicate gteBy(Fn1 fn, A y) { + return GTEBy.gteBy(fn).apply(y); + } + + public static > Boolean gteBy(Fn1 fn, A y, A x) { + return gteBy(fn, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java new file mode 100644 index 000000000..29a5dfe82 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.GTE; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, + * return true if the second value is greater than or equal to the first value in + * terms of their mapped B results according to {@link Comparator#compare(Object, Object)}; + * otherwise, return false. + * + * @param the value type + * @see GTE + * @see GTEBy + * @see LTEWith + */ +public final class GTEWith implements Fn3, A, A, Boolean> { + + private static final GTEWith INSTANCE = new GTEWith<>(); + + private GTEWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return !ltWith(comparator, a, a2); + } + + @SuppressWarnings("unchecked") + public static GTEWith gteWith() { + return (GTEWith) INSTANCE; + } + + public static BiPredicate gteWith(Comparator comparator) { + return GTEWith.gteWith().apply(comparator); + } + + public static Predicate gteWith(Comparator comparator, A y) { + return GTEWith.gteWith(comparator).apply(y); + } + + public static Boolean gteWith(Comparator comparator, A y, A x) { + return gteWith(comparator, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java new file mode 100644 index 000000000..d65bec00f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.GT; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.greaterThan; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, + * return true if the second value is strictly greater than the first value in + * terms of their mapped B results; otherwise, return false. + * + * @param the value type + * @see GT + * @see GTBy + * @see LTWith + */ +public final class GTWith implements Fn3, A, A, Boolean> { + + private static final GTWith INSTANCE = new GTWith<>(); + + private GTWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static GTWith gtWith() { + return (GTWith) INSTANCE; + } + + public static BiPredicate gtWith(Comparator comparator) { + return GTWith.gtWith().apply(comparator); + } + + public static Predicate gtWith(Comparator comparator, A y) { + return GTWith.gtWith(comparator).apply(y); + } + + public static Boolean gtWith(Comparator comparator, A y, A x) { + return gtWith(comparator, y).apply(x); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(greaterThan()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTBy.java new file mode 100644 index 000000000..05e163844 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTBy.java @@ -0,0 +1,61 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.LT; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B and two values + * of type A, return true if the second value is strictly less than the first value in terms + * of their mapped B results; otherwise, return false. + * + * @param the value type + * @param the mapped comparison type + * @see LT + * @see GTBy + */ +public final class LTBy> implements Fn3, A, A, Boolean> { + + private static final LTBy INSTANCE = new LTBy<>(); + + private LTBy() { + } + + @Override + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return ltWith(comparing(compareFn.toFunction()), y, x); + } + + @Override + public BiPredicate apply(Fn1 compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Fn1 compareFn, A y) { + return predicate(Fn3.super.apply(compareFn, y)); + } + + @SuppressWarnings("unchecked") + public static > LTBy ltBy() { + return (LTBy) INSTANCE; + } + + public static > BiPredicate ltBy(Fn1 fn) { + return LTBy.ltBy().apply(fn); + } + + public static > Predicate ltBy(Fn1 fn, A y) { + return LTBy.ltBy(fn).apply(y); + } + + public static > Boolean ltBy(Fn1 fn, A y, A x) { + return ltBy(fn, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEBy.java new file mode 100644 index 000000000..4c377d583 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEBy.java @@ -0,0 +1,61 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.LTE; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEWith.lteWith; +import static java.util.Comparator.comparing; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B and two values + * of type A, return true if the second value is less than or equal to the first value in + * terms of their mapped B results according to {@link Comparable#compareTo(Object)}; otherwise, return + * false. + * + * @param the value type + * @param the mapped comparison type + * @see LTE + * @see GTEBy + */ +public final class LTEBy> implements Fn3, A, A, Boolean> { + + private static final LTEBy INSTANCE = new LTEBy<>(); + + private LTEBy() { + } + + @Override + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return lteWith(comparing(compareFn.toFunction()), y, x); + } + + @Override + public BiPredicate apply(Fn1 compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Fn1 compareFn, A y) { + return Fn3.super.apply(compareFn, y)::apply; + } + + @SuppressWarnings("unchecked") + public static > LTEBy lteBy() { + return (LTEBy) INSTANCE; + } + + public static > BiPredicate lteBy(Fn1 fn) { + return LTEBy.lteBy().apply(fn); + } + + public static > Predicate lteBy(Fn1 fn, A y) { + return LTEBy.lteBy(fn).apply(y); + } + + public static > Boolean lteBy(Fn1 fn, A y, A x) { + return lteBy(fn, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java new file mode 100644 index 000000000..403f5c15f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.LTE; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, + * return true if the second value is less than or equal to the first value in + * terms of their mapped B results according to {@link Comparator#compare(Object, Object)}; + * otherwise, return false. + * + * @param the value type + * @see LTE + * @see LTEBy + * @see GTEWith + */ +public final class LTEWith implements Fn3, A, A, Boolean> { + + private static final LTEWith INSTANCE = new LTEWith<>(); + + private LTEWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return !gtWith(comparator, a, a2); + } + + @SuppressWarnings("unchecked") + public static LTEWith lteWith() { + return (LTEWith) INSTANCE; + } + + public static BiPredicate lteWith(Comparator comparator) { + return LTEWith.lteWith().apply(comparator); + } + + public static Predicate lteWith(Comparator comparator, A y) { + return LTEWith.lteWith(comparator).apply(y); + } + + public static Boolean lteWith(Comparator comparator, A y, A x) { + return lteWith(comparator, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java new file mode 100644 index 000000000..14652a4c6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.LT; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.lessThan; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a comparator for some type A and two values of type A, + * return true if the second value is strictly less than than the first value in + * terms of their mapped B results; otherwise, return false. + * + * @param the value type + * @see LT + * @see LTBy + * @see GTWith + */ +public final class LTWith implements Fn3, A, A, Boolean> { + + private static final LTWith INSTANCE = new LTWith<>(); + + private LTWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static LTWith ltWith() { + return (LTWith) INSTANCE; + } + + public static BiPredicate ltWith(Comparator comparator) { + return LTWith.ltWith().apply(comparator); + } + + public static Predicate ltWith(Comparator comparator, A y) { + return LTWith.ltWith(comparator).apply(y); + } + + public static Boolean ltWith(Comparator comparator, A y, A x) { + return ltWith(comparator, y).apply(x); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(lessThan()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2.java new file mode 100644 index 000000000..0badec8f5 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2.java @@ -0,0 +1,54 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn2} to two {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. Functionally equivalent to appB.zip(appA.fmap(fn)). + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's return type + * @param the applicative witness + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA2, AppC extends Applicative> implements + Fn3, Applicative, Applicative, AppC> { + + private static final LiftA2 INSTANCE = new LiftA2<>(); + + private LiftA2() { + } + + @Override + public AppC checkedApply(Fn2 fn, + Applicative appA, + Applicative appB) { + return appA.zip(appB.fmap(b -> a -> fn.apply(a, b))).coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppC extends Applicative> + LiftA2 liftA2() { + return (LiftA2) INSTANCE; + } + + public static , AppC extends Applicative> + Fn2, Applicative, AppC> liftA2(Fn2 fn) { + return LiftA2.liftA2().apply(fn); + } + + public static , AppC extends Applicative> + Fn1, AppC> liftA2(Fn2 fn, Applicative appA) { + return LiftA2.liftA2(fn).apply(appA); + } + + public static , AppC extends Applicative> + AppC liftA2(Fn2 fn, Applicative appA, Applicative appB) { + return LiftA2.liftA2(fn, appA).apply(appB); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ScanLeft.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ScanLeft.java index a6af6210b..5db4f413a 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ScanLeft.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ScanLeft.java @@ -3,47 +3,46 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; -import com.jnape.palatable.lambda.iterators.ScanningIterator; - -import java.util.function.BiFunction; +import com.jnape.palatable.lambda.internal.iteration.ScanningIterator; /** - * Given an Iterable of As, a starting value B, and a {@link - * BiFunction}<B, A, B>, iteratively accumulate over the Iterable, collecting each function - * application result, finally returning an Iterable of all the results. Note that, as the name implies, - * this function accumulates from left to right, such that scanLeft(f, 0, asList(1,2,3,4,5)) is evaluated - * as 0, f(0, 1), f(f(0, 1), 2), f(f(f(0, 1), 2), 3), f(f(f(f(0, 1), 2), 3), 4), f(f(f(f(f(0, 1), 2), 3), 4), 5). + * Given an Iterable of As, a starting value B, and a + * {@link Fn2}<B, A, B>, iteratively accumulate over the Iterable, collecting each + * function application result, finally returning an Iterable of all the results. Note that, as the name + * implies, this function accumulates from left to right, such that scanLeft(f, 0, asList(1,2,3,4,5)) is + * evaluated as 0, f(0, 1), f(f(0, 1), 2), f(f(f(0, 1), 2), 3), f(f(f(f(0, 1), 2), 3), 4), f(f(f(f(f(0, 1), 2), + * 3), 4), 5). * * @param The Iterable element type * @param The accumulation type * @see FoldLeft */ -public final class ScanLeft implements Fn3, B, Iterable, Iterable> { +public final class ScanLeft implements Fn3, B, Iterable, Iterable> { - private static final ScanLeft INSTANCE = new ScanLeft(); + private static final ScanLeft INSTANCE = new ScanLeft<>(); private ScanLeft() { } @Override - public Iterable apply(BiFunction fn, B b, Iterable as) { + public Iterable checkedApply(Fn2 fn, B b, Iterable as) { return () -> new ScanningIterator<>(fn, b, as.iterator()); } @SuppressWarnings("unchecked") public static ScanLeft scanLeft() { - return INSTANCE; + return (ScanLeft) INSTANCE; } - public static Fn2, Iterable> scanLeft(BiFunction fn) { + public static Fn2, Iterable> scanLeft(Fn2 fn) { return ScanLeft.scanLeft().apply(fn); } - public static Fn1, Iterable> scanLeft(BiFunction fn, B b) { + public static Fn1, Iterable> scanLeft(Fn2 fn, B b) { return ScanLeft.scanLeft(fn).apply(b); } - public static Iterable scanLeft(BiFunction fn, B b, Iterable as) { + public static Iterable scanLeft(Fn2 fn, B b, Iterable as) { return ScanLeft.scanLeft(fn, b).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Times.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Times.java new file mode 100644 index 000000000..c23277e63 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Times.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; + +/** + * Given some number of times n to invoke a function A -> A, and given an input + * A, iteratively apply the function to the input, and then to the result of the invocation, a total of + * n times, returning the result. + *

+ * Example: + * + * times(3, x -> x + 1, 0); // 3 + * + * @param the input and output type + */ +public final class Times implements Fn3, A, A> { + + private static final Times INSTANCE = new Times<>(); + + private Times() { + } + + @Override + public A checkedApply(Integer n, Fn1 fn, A a) { + if (n < 0) + throw new IllegalStateException("n must not be less than 0"); + + return foldLeft((acc, f) -> f.apply(acc), a, replicate(n, fn)); + } + + @SuppressWarnings("unchecked") + public static Times times() { + return (Times) INSTANCE; + } + + public static Fn2, A, A> times(Integer n) { + return Times.times().apply(n); + } + + public static Fn1 times(Integer n, Fn1 fn) { + return Times.times(n).apply(fn); + } + + public static A times(Integer n, Fn1 fn, A a) { + return Times.times(n, fn).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWith.java index 28a83fdeb..f9af0c1f2 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWith.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWith.java @@ -3,9 +3,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; -import com.jnape.palatable.lambda.iterators.ZippingIterator; - -import java.util.function.BiFunction; +import com.jnape.palatable.lambda.internal.iteration.ZippingIterator; /** * Zip together two Iterables by applying a zipping function to the successive elements of each @@ -17,34 +15,35 @@ * @param The output Iterable element type * @see com.jnape.palatable.lambda.functions.builtin.fn2.Zip */ -public final class ZipWith implements Fn3, Iterable, Iterable, Iterable> { +public final class ZipWith implements Fn3, Iterable, Iterable, Iterable> { - private static final ZipWith INSTANCE = new ZipWith(); + private static final ZipWith INSTANCE = new ZipWith<>(); private ZipWith() { } @Override - public Iterable apply(BiFunction zipper, Iterable as, Iterable bs) { + public Iterable checkedApply(Fn2 zipper, Iterable as, + Iterable bs) { return () -> new ZippingIterator<>(zipper, as.iterator(), bs.iterator()); } @SuppressWarnings("unchecked") public static ZipWith zipWith() { - return INSTANCE; + return (ZipWith) INSTANCE; } public static Fn2, Iterable, Iterable> zipWith( - BiFunction zipper) { + Fn2 zipper) { return ZipWith.zipWith().apply(zipper); } - public static Fn1, Iterable> zipWith(BiFunction zipper, + public static Fn1, Iterable> zipWith(Fn2 zipper, Iterable as) { return ZipWith.zipWith(zipper).apply(as); } - public static Iterable zipWith(BiFunction zipper, Iterable as, + public static Iterable zipWith(Fn2 zipper, Iterable as, Iterable bs) { return ZipWith.zipWith(zipper, as).apply(bs); } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElse.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElse.java new file mode 100644 index 000000000..70336b79a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElse.java @@ -0,0 +1,47 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; + +public final class IfThenElse implements + Fn4, Fn1, Fn1, A, B> { + + private static final IfThenElse INSTANCE = new IfThenElse<>(); + + private IfThenElse() { + } + + @Override + public B checkedApply(Fn1 predicate, Fn1 thenCase, + Fn1 elseCase, A a) { + return predicate.apply(a) ? thenCase.apply(a) : elseCase.apply(a); + } + + @SuppressWarnings("unchecked") + public static IfThenElse ifThenElse() { + return (IfThenElse) INSTANCE; + } + + public static Fn3, Fn1, A, B> ifThenElse( + Fn1 predicate) { + return IfThenElse.ifThenElse().apply(predicate); + } + + public static Fn2, A, B> ifThenElse(Fn1 predicate, + Fn1 thenCase) { + return IfThenElse.ifThenElse(predicate).apply(thenCase); + } + + public static Fn1 ifThenElse(Fn1 predicate, + Fn1 thenCase, + Fn1 elseCase) { + return IfThenElse.ifThenElse(predicate, thenCase).apply(elseCase); + } + + public static B ifThenElse(Fn1 predicate, Fn1 thenCase, + Fn1 elseCase, A a) { + return ifThenElse(predicate, thenCase, elseCase).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3.java new file mode 100644 index 000000000..9000ad81a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn3} to three {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's third argument type + * @param the function's return type + * @param the applicative witness + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA3, AppD extends Applicative> implements + Fn4, Applicative, Applicative, Applicative, AppD> { + + private static final LiftA3 INSTANCE = new LiftA3<>(); + + private LiftA3() { + } + + @Override + public AppD checkedApply(Fn3 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return appA.zip(appB.zip(appC.fmap(c -> b -> a -> fn.apply(a, b, c)))).coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppD extends Applicative> + LiftA3 liftA3() { + return (LiftA3) INSTANCE; + } + + public static , AppD extends Applicative> + Fn3, Applicative, Applicative, AppD> liftA3(Fn3 fn) { + return LiftA3.liftA3().apply(fn); + } + + public static , AppD extends Applicative> + Fn2, Applicative, AppD> liftA3(Fn3 fn, Applicative appA) { + return LiftA3.liftA3(fn).apply(appA); + } + + public static , AppD extends Applicative> + Fn1, AppD> liftA3(Fn3 fn, Applicative appA, Applicative appB) { + return LiftA3.liftA3(fn, appA).apply(appB); + } + + public static , AppD extends Applicative> + AppD liftA3(Fn3 fn, Applicative appA, Applicative appB, Applicative appC) { + return LiftA3.liftA3(fn, appA, appB).apply(appC); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimit.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimit.java new file mode 100644 index 000000000..53d9b7189 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimit.java @@ -0,0 +1,86 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.internal.iteration.IterationInterruptedException; +import com.jnape.palatable.lambda.internal.iteration.RateLimitingIterable; + +import java.time.Duration; +import java.time.Instant; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static java.util.Collections.singleton; + +/** + * Given an {@link Fn0} of {@link Instant Instants} (presumably backed by a clock), a limit, a + * {@link Duration}, and an {@link Iterable} as, return an {@link Iterable} that iterates as + * according to the threshold specified by the limit per duration, using the {@link Fn0} to advance time. + *

+ * As an example, the following will print at most 10 elements per second: + *


+ * rateLimit(Clock.systemUTC()::instant, 10L, Duration.ofSeconds(1), iterate(x -> x + 1, 1))
+ *     .forEach(System.out::println);
+ * 
+ * Currying allows different rate limits to be combined naturally: + *

+ * Iterable<Integer> elements = iterate(x -> x + 1, 1);
+ *
+ * Supplier<Instant> instantFn0 = Clock.systemUTC()::instant;
+ * Fn1<Iterable<Integer>, Iterable<Integer>> tenPerSecond =
+ *     rateLimit(instantFn0, 10L, Duration.ofSeconds(1));
+ * Fn1<Iterable<Integer>, Iterable<Integer>> oneHundredEveryTwoMinutes =
+ *     rateLimit(instantFn0, 100L, Duration.ofMinutes(2));
+ *
+ * tenPerSecond.fmap(oneHundredEveryTwoMinutes).apply(elements).forEach(System.out::println);
+ * 
+ * In the preceding example, the elements will be printed at most 10 elements per second and 100 elements per 120 + * seconds. + *

+ * If the host {@link Thread} is {@link Thread#interrupt() interrupted} while the returned {@link Iterable} is waiting + * for the next available time slice, an {@link IterationInterruptedException} will immediately be thrown. + *

+ * Note that the returned {@link Iterable} will never iterate faster than the specified rate limit, but the earliest + * the next element is available will be dependent on the precision of the underlying instant supplier as well as any + * overhead involved in producing the element from the original {@link Iterable}. + * + * @param the {@link Iterable} element type + */ +public final class RateLimit implements Fn4, Long, Duration, Iterable, Iterable> { + + private static final RateLimit INSTANCE = new RateLimit<>(); + + private RateLimit() { + } + + @Override + public Iterable checkedApply(Fn0 instantFn0, Long limit, Duration duration, Iterable as) { + if (limit < 1) + throw new IllegalArgumentException("Limit must be greater than 0: " + limit); + + return new RateLimitingIterable<>(as, singleton(tuple(limit, duration, instantFn0))); + } + + @SuppressWarnings("unchecked") + public static RateLimit rateLimit() { + return (RateLimit) INSTANCE; + } + + public static Fn3, Iterable> rateLimit(Fn0 instantFn0) { + return RateLimit.rateLimit().apply(instantFn0); + } + + public static Fn2, Iterable> rateLimit(Fn0 instantFn0, Long limit) { + return RateLimit.rateLimit(instantFn0).apply(limit); + } + + public static Fn1, Iterable> rateLimit(Fn0 instantFn0, Long limit, Duration duration) { + return RateLimit.rateLimit(instantFn0, limit).apply(duration); + } + + public static Iterable rateLimit(Fn0 instantFn0, Long limit, Duration duration, Iterable as) { + return RateLimit.rateLimit(instantFn0, limit, duration).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4.java new file mode 100644 index 000000000..9132376dc --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4.java @@ -0,0 +1,79 @@ +package com.jnape.palatable.lambda.functions.builtin.fn5; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functions.Fn5; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn4} to four {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's third argument type + * @param the function's fourth argument type + * @param the function's return type + * @param the applicative witness + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA4, AppE extends Applicative> implements + Fn5, Applicative, Applicative, Applicative, Applicative, + AppE> { + + private static final LiftA4 INSTANCE = new LiftA4<>(); + + private LiftA4() { + } + + @Override + public AppE checkedApply(Fn4 fn, Applicative appA, Applicative appB, + Applicative appC, Applicative appD) { + return appA.zip(appB.zip(appC.zip(appD.fmap(d -> c -> b -> a -> fn.apply(a, b, c, d))))).coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppE extends Applicative> + LiftA4 liftA4() { + return (LiftA4) INSTANCE; + } + + public static , AppE extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppE> liftA4( + Fn4 fn) { + return LiftA4.liftA4().apply(fn); + } + + public static , AppE extends Applicative> + Fn3, Applicative, Applicative, AppE> liftA4(Fn4 fn, + Applicative appA) { + return LiftA4.liftA4(fn).apply(appA); + } + + public static , AppE extends Applicative> + Fn2, Applicative, AppE> liftA4(Fn4 fn, + Applicative appA, + Applicative appB) { + return LiftA4.liftA4(fn, appA).apply(appB); + } + + public static , AppE extends Applicative> + Fn1, AppE> liftA4(Fn4 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA4.liftA4(fn, appA, appB).apply(appC); + } + + public static , AppE extends Applicative> + AppE liftA4(Fn4 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA4.liftA4(fn, appA, appB, appC).apply(appD); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5.java new file mode 100644 index 000000000..7fd7c8f4f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5.java @@ -0,0 +1,93 @@ +package com.jnape.palatable.lambda.functions.builtin.fn6; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functions.Fn5; +import com.jnape.palatable.lambda.functions.Fn6; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn5} to five {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's third argument type + * @param the function's fourth argument type + * @param the function's fifth argument type + * @param the function's return type + * @param the applicative witness + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA5, AppF extends Applicative> + implements Fn6, Applicative, Applicative, Applicative, + Applicative, Applicative, AppF> { + + private static final LiftA5 INSTANCE = new LiftA5<>(); + + private LiftA5() { + } + + @Override + public AppF checkedApply(Fn5 fn, Applicative appA, Applicative appB, + Applicative appC, Applicative appD, Applicative appE) { + return appA.zip(appB.zip(appC.zip(appD.zip(appE.fmap(e -> d -> c -> b -> a -> fn.apply(a, b, c, d, e)))))) + .coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppF extends Applicative> + LiftA5 liftA5() { + return (LiftA5) INSTANCE; + } + + public static , AppF extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppF> + liftA5(Fn5 fn) { + return LiftA5.liftA5().apply(fn); + } + + public static , AppF extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppF> + liftA5(Fn5 fn, + Applicative appA) { + return LiftA5.liftA5(fn).apply(appA); + } + + public static , AppF extends Applicative> + Fn3, Applicative, Applicative, AppF> liftA5(Fn5 fn, + Applicative appA, + Applicative appB) { + return LiftA5.liftA5(fn, appA).apply(appB); + } + + public static , AppF extends Applicative> + Fn2, Applicative, AppF> liftA5(Fn5 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA5.liftA5(fn, appA, appB).apply(appC); + } + + public static , AppF extends Applicative> + Fn1, AppF> liftA5(Fn5 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA5.liftA5(fn, appA, appB, appC).apply(appD); + } + + public static , AppF extends Applicative> + AppF liftA5(Fn5 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE) { + return LiftA5.liftA5(fn, appA, appB, appC, appD).apply(appE); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6.java new file mode 100644 index 000000000..fcc7934c9 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6.java @@ -0,0 +1,117 @@ +package com.jnape.palatable.lambda.functions.builtin.fn7; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functions.Fn5; +import com.jnape.palatable.lambda.functions.Fn6; +import com.jnape.palatable.lambda.functions.Fn7; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn6} to six {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's third argument type + * @param the function's fourth argument type + * @param the function's fifth argument type + * @param the function's sixth argument type + * @param the function's return type + * @param the applicative witness + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA6, AppG extends Applicative> + implements Fn7, + Applicative, + Applicative, + Applicative, + Applicative, + Applicative, + Applicative, + AppG> { + + private static final LiftA6 INSTANCE = new LiftA6<>(); + + private LiftA6() { + } + + @Override + public AppG checkedApply(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF) { + return appA.zip(appB.zip(appC.zip(appD.zip(appE.zip(appF.fmap( + f -> e -> d -> c -> b -> a -> fn.apply(a, b, c, d, e, f))))))).coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppG extends Applicative> + LiftA6 liftA6() { + return (LiftA6) INSTANCE; + } + + public static , AppG extends Applicative> + Fn6, Applicative, Applicative, Applicative, Applicative, + Applicative, AppG> liftA6(Fn6 fn) { + return LiftA6.liftA6().apply(fn); + } + + public static , AppG extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppG> + liftA6(Fn6 fn, Applicative appA) { + return LiftA6.liftA6(fn).apply(appA); + } + + public static , AppG extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppG> + liftA6(Fn6 fn, + Applicative appA, + Applicative appB) { + return LiftA6.liftA6(fn, appA).apply(appB); + } + + public static , AppG extends Applicative> + Fn3, Applicative, Applicative, AppG> liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA6.liftA6(fn, appA, appB).apply(appC); + } + + public static , AppG extends Applicative> + Fn2, Applicative, AppG> liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA6.liftA6(fn, appA, appB, appC).apply(appD); + } + + public static , AppG extends Applicative> + Fn1, AppG> liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE) { + return LiftA6.liftA6(fn, appA, appB, appC, appD).apply(appE); + } + + public static , AppG extends Applicative> + AppG liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF) { + return LiftA6.liftA6(fn, appA, appB, appC, appD, appE).apply(appF); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7.java new file mode 100644 index 000000000..6f80b7d37 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7.java @@ -0,0 +1,128 @@ +package com.jnape.palatable.lambda.functions.builtin.fn8; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functions.Fn5; +import com.jnape.palatable.lambda.functions.Fn6; +import com.jnape.palatable.lambda.functions.Fn7; +import com.jnape.palatable.lambda.functions.Fn8; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn7} to seven {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's third argument type + * @param the function's fourth argument type + * @param the function's fifth argument type + * @param the function's sixth argument type + * @param the function's seventh argument type + * @param the function's return type + * @param the applicative unification type + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA7, AppH extends Applicative> + implements Fn8, Applicative, Applicative, Applicative, + Applicative, Applicative, Applicative, Applicative, AppH> { + + private static final LiftA7 INSTANCE = new LiftA7<>(); + + private LiftA7() { + } + + @Override + public AppH checkedApply(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF, + Applicative appG) { + return appA.zip(appB.zip(appC.zip(appD.zip(appE.zip(appF.zip(appG.fmap( + g -> f -> e -> d -> c -> b -> a -> fn.apply(a, b, c, d, e, f, g)))))))).coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppH extends Applicative> + LiftA7 liftA7() { + return (LiftA7) INSTANCE; + } + + public static , AppH extends Applicative> + Fn7, Applicative, Applicative, Applicative, Applicative, + Applicative, Applicative, AppH> liftA7(Fn7 fn) { + return LiftA7.liftA7().apply(fn); + } + + public static , AppH extends Applicative> + Fn6, Applicative, Applicative, Applicative, Applicative, + Applicative, AppH> liftA7(Fn7 fn, + Applicative appA) { + return LiftA7.liftA7(fn).apply(appA); + } + + public static , AppH extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppH> + liftA7(Fn7 fn, + Applicative appA, + Applicative appB) { + return LiftA7.liftA7(fn, appA).apply(appB); + } + + public static , AppH extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppH> liftA7( + Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA7.liftA7(fn, appA, appB).apply(appC); + } + + public static , AppH extends Applicative> + Fn3, Applicative, Applicative, AppH> liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA7.liftA7(fn, appA, appB, appC).apply(appD); + } + + public static , AppH extends Applicative> + Fn2, Applicative, AppH> liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE) { + return LiftA7.liftA7(fn, appA, appB, appC, appD).apply(appE); + } + + public static , AppH extends Applicative> + Fn1, AppH> liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF) { + return LiftA7.liftA7(fn, appA, appB, appC, appD, appE).apply(appF); + } + + public static , AppH extends Applicative> + AppH liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF, + Applicative appG) { + return LiftA7.liftA7(fn, appA, appB, appC, appD, appE, appF).apply(appG); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java b/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java new file mode 100644 index 000000000..0c4bb93c3 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java @@ -0,0 +1,102 @@ +package com.jnape.palatable.lambda.functions.ordering; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn3.Compare; + +import java.util.Comparator; + +/** + * Specialized {@link CoProduct3} representing the possible results of a ordered comparison. + * Used by {@link Compare} as the result of a comparison. + * + * @see Compare + */ +public abstract class ComparisonRelation + implements CoProduct3< + ComparisonRelation.LessThan, + ComparisonRelation.Equal, + ComparisonRelation.GreaterThan, + ComparisonRelation> { + + private ComparisonRelation() { + } + + /** + * Return a comparison relation from the result of a {@link Comparator} or {@link Comparable} result + * + * @param signifier The result of {@link Comparator#compare(Object, Object)} or {@link Comparable#compareTo(Object)} + * @return The intended {@link ComparisonRelation} of the signifier + */ + public static ComparisonRelation fromInt(int signifier) { + return signifier > 0 ? greaterThan() : signifier == 0 ? equal() : lessThan(); + } + + public static GreaterThan greaterThan() { + return GreaterThan.INSTANCE; + } + + public static LessThan lessThan() { + return LessThan.INSTANCE; + } + + public static Equal equal() { + return Equal.INSTANCE; + } + + public static final class LessThan extends ComparisonRelation { + private static final LessThan INSTANCE = new LessThan(); + + private LessThan() { + } + + @Override + public String toString() { + return "LessThan"; + } + + @Override + public R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn) { + return aFn.apply(this); + } + } + + public static final class Equal extends ComparisonRelation { + private static final Equal INSTANCE = new Equal(); + + private Equal() { + } + + public String toString() { + return "Equal"; + } + + @Override + public R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn) { + return bFn.apply(this); + } + } + + public static final class GreaterThan extends ComparisonRelation { + private static final GreaterThan INSTANCE = new GreaterThan(); + + private GreaterThan() { + } + + @Override + public String toString() { + return "GreaterThan"; + } + + @Override + public R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn) { + return cFn.apply(this); + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java b/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java new file mode 100644 index 000000000..2fd54d791 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java @@ -0,0 +1,230 @@ +package com.jnape.palatable.lambda.functions.recursion; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; + +/** + * Specialized {@link CoProduct2} representing the possible results of a primitive recursive function. + * Used by {@link Trampoline} to cheat around {@link CoProduct2#match} and quickly unpack values via + * instanceof checks to package private inner subtypes. + * + * @param the recursive function's input type + * @param the recursive function's output type + * @see Trampoline + */ +public abstract class RecursiveResult implements + CoProduct2>, + Bifunctor>, + MonadRec>, + Traversable> { + + private RecursiveResult() { + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult invert() { + return match(RecursiveResult::terminate, RecursiveResult::recurse); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult biMapL(Fn1 fn) { + return (RecursiveResult) Bifunctor.super.biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult biMapR(Fn1 fn) { + return (RecursiveResult) Bifunctor.super.biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult biMap(Fn1 lFn, + Fn1 rFn) { + return match(a -> recurse(lFn.apply(a)), b -> terminate(rFn.apply(b))); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult flatMap(Fn1>> f) { + return match(RecursiveResult::recurse, b -> f.apply(b).coerce()); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult pure(C c) { + return terminate(c); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult zip(Applicative, RecursiveResult> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult trampolineM( + Fn1, RecursiveResult>> fn) { + return flatMap(Trampoline.>trampoline( + b -> sequence(fn.apply(b).>>coerce(), + RecursiveResult::terminate))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(__ -> pure.apply(coerce()), + b -> fn.apply(b).fmap(this::pure).fmap(RecursiveResult::coerce).coerce()); + } + + /** + * Static factory method for creating a "recurse" value. + * + * @param a the value + * @param the recurse type + * @param the terminate type + * @return the {@link RecursiveResult} + */ + public static RecursiveResult recurse(A a) { + return new Recurse<>(a); + } + + /** + * Static factory method for creating a "terminate" value. + * + * @param b the value + * @param the recurse type + * @param the terminate type + * @return the {@link RecursiveResult} + */ + public static RecursiveResult terminate(B b) { + return new Terminate<>(b); + } + + /** + * The canonical {@link Pure} instance for {@link RecursiveResult}. + * + * @param the recursive function's input type + * @return the {@link Pure} instance + */ + public static Pure> pureRecursiveResult() { + return RecursiveResult::terminate; + } + + static final class Recurse extends RecursiveResult { + final A a; + + private Recurse(A a) { + this.a = a; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn) { + return aFn.apply(a); + } + + @Override + public boolean equals(Object other) { + return other instanceof Recurse && Objects.equals(a, ((Recurse) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Recurse{" + + "a=" + a + + '}'; + } + } + + static final class Terminate extends RecursiveResult { + final B b; + + private Terminate(B b) { + this.b = b; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn) { + return bFn.apply(b); + } + + @Override + public boolean equals(Object other) { + return other instanceof Terminate && Objects.equals(b, ((Terminate) other).b); + } + + @Override + public int hashCode() { + return Objects.hash(b); + } + + @Override + public String toString() { + return "Terminate{" + + "b=" + b + + '}'; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/recursion/Trampoline.java b/src/main/java/com/jnape/palatable/lambda/functions/recursion/Trampoline.java new file mode 100644 index 000000000..6637219ec --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/recursion/Trampoline.java @@ -0,0 +1,45 @@ +package com.jnape.palatable.lambda.functions.recursion; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn2.Unfoldr; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult.Recurse; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult.Terminate; + +/** + * Given an {@link Fn1}<A, {@link CoProduct2}<A, B, ?>> (analogous to "recurse" and "return" + * tail position instructions, respectively), produce a {@link Fn1}<A, B> that unrolls the original + * function by iteratively passing each result that matches the input (A) back to the original function, + * and then terminating on and returning the first output (B). + *

+ * This is isomorphic to - though presumably faster than - taking the last element of an {@link Unfoldr} call. + * + * @param the trampolined function's input type + * @param the trampolined function's output type + */ +public final class Trampoline implements Fn2>, A, B> { + + private static final Trampoline INSTANCE = new Trampoline<>(); + + @Override + public B checkedApply(Fn1> fn, A a) { + RecursiveResult next = fn.apply(a); + while (next instanceof Recurse) + next = fn.apply(((Recurse) next).a); + return ((Terminate) next).b; + } + + @SuppressWarnings("unchecked") + public static Trampoline trampoline() { + return (Trampoline) INSTANCE; + } + + public static Fn1 trampoline(Fn1> fn) { + return Trampoline.trampoline().apply(fn); + } + + public static B trampoline(Fn1> fn, A a) { + return trampoline(fn).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiMonoidFactory.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiMonoidFactory.java index fee6e7339..198316f00 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiMonoidFactory.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiMonoidFactory.java @@ -1,26 +1,41 @@ package com.jnape.palatable.lambda.functions.specialized; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.internal.Runtime; import com.jnape.palatable.lambda.monoid.Monoid; @FunctionalInterface public interface BiMonoidFactory extends BiSemigroupFactory { @Override - default MonoidFactory apply(A a) { - return b -> apply(a, b); + Monoid checkedApply(A a, B b) throws Throwable; + + @Override + default MonoidFactory checkedApply(A a) throws Throwable { + return b -> checkedApply(a, b); + } + + @Override + default Monoid apply(A a, B b) { + try { + return checkedApply(a, b); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } } @Override - Monoid apply(A a, B b); + default MonoidFactory apply(A a) { + return b -> apply(a, b); + } @Override default BiMonoidFactory flip() { - return (b, a) -> apply(a, b); + return (b, a) -> checkedApply(a, b); } @Override - default MonoidFactory, C> uncurry() { + default MonoidFactory, C> uncurry() { return ab -> apply(ab._1()).apply(ab._2()); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiPredicate.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiPredicate.java index 71d427e69..54d11cd92 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiPredicate.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiPredicate.java @@ -1,82 +1,114 @@ package com.jnape.palatable.lambda.functions.specialized; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.Applicative; /** - * A specialized {@link Fn2} that returns a Boolean when fully applied, - * or a {@link Predicate} when partially applied. + * A specialized {@link Fn2} that returns a Boolean when fully applied, or a {@link Predicate} when partially applied. * * @param the first argument type * @param the second argument type */ @FunctionalInterface -public interface BiPredicate extends Fn2, java.util.function.BiPredicate { +public interface BiPredicate extends Fn2 { /** * {@inheritDoc} */ @Override - default boolean test(A a, B b) { - return apply(a, b); + default Predicate apply(A a) { + return Fn2.super.apply(a)::apply; } /** * {@inheritDoc} */ @Override - default Predicate apply(A a) { - return Fn2.super.apply(a)::apply; + default BiPredicate flip() { + return Fn2.super.flip()::apply; } /** * {@inheritDoc} */ @Override - default BiPredicate flip() { - return (BiPredicate) Fn2.super.flip(); + default BiPredicate discardR(Applicative> appB) { + return Fn2.super.discardR(appB)::apply; } /** * {@inheritDoc} */ @Override - default Predicate> uncurry() { + default Predicate> uncurry() { return Fn2.super.uncurry()::apply; } /** - * Override of {@link java.util.function.BiPredicate#and(java.util.function.BiPredicate)}, returning an instance of - * BiPredicate for compatibility. Left-to-right composition. + * {@inheritDoc} + */ + @Override + default BiPredicate diMapL(Fn1 fn) { + return Fn2.super.diMapL(fn)::apply; + } + + /** + * {@inheritDoc} + */ + @Override + default Fn2 contraMap(Fn1 fn) { + return Fn2.super.contraMap(fn)::apply; + } + + /** + * Left-to-right short-circuiting logical conjunction. * * @param other the biPredicate to test if this one succeeds * @return a biPredicate representing the conjunction of this biPredicate and other */ - @Override - default BiPredicate and(java.util.function.BiPredicate other) { - return (a, b) -> apply(a, b) && other.test(a, b); + default BiPredicate and(BiPredicate other) { + return (a, b) -> apply(a, b) && other.apply(a, b); } /** - * Override of {@link java.util.function.BiPredicate#or(java.util.function.BiPredicate)}, returning an instance of - * BiPredicate for compatibility. Left-to-right composition. + * Left-to-right short-circuiting logical disjunction. * * @param other the biPredicate to test if this one fails * @return a biPredicate representing the disjunction of this biPredicate and other */ - @Override - default BiPredicate or(java.util.function.BiPredicate other) { - return (a, b) -> apply(a, b) || other.test(a, b); + default BiPredicate or(BiPredicate other) { + return (a, b) -> apply(a, b) || other.apply(a, b); } /** - * Override of {@link java.util.function.BiPredicate#negate()}, returning an instance of BiPredicate - * for compatibility. + * Logical negation. * * @return the negation of this biPredicate */ - @Override default BiPredicate negate() { return (a, b) -> !apply(a, b); } + + /** + * Convert this {@link BiPredicate} to a java {@link java.util.function.BiPredicate}. + * + * @return {@link java.util.function.BiPredicate} + */ + default java.util.function.BiPredicate toBiPredicate() { + return this::apply; + } + + /** + * Create a {@link BiPredicate} from a java {@link java.util.function.BiPredicate}. + * + * @param biPredicate the {@link java.util.function.BiPredicate} + * @param the first input type + * @param the second input type + * @return the {@link BiPredicate} + */ + static BiPredicate fromBiPredicate(java.util.function.BiPredicate biPredicate) { + return biPredicate::test; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiSemigroupFactory.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiSemigroupFactory.java index d7266132c..5cf0a4a17 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiSemigroupFactory.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiSemigroupFactory.java @@ -1,6 +1,6 @@ package com.jnape.palatable.lambda.functions.specialized; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functions.Fn4; import com.jnape.palatable.lambda.semigroup.Semigroup; @@ -8,7 +8,17 @@ public interface BiSemigroupFactory extends Fn4 { @Override - Semigroup apply(A a, B b); + Semigroup checkedApply(A a, B b) throws Throwable; + + @Override + default C checkedApply(A a, B b, C c, C d) throws Throwable { + return checkedApply(a, b).checkedApply(c, d); + } + + @Override + default Semigroup apply(A a, B b) { + return Fn4.super.apply(a, b)::apply; + } @Override default SemigroupFactory apply(A a) { @@ -21,12 +31,7 @@ default BiSemigroupFactory flip() { } @Override - default SemigroupFactory, C> uncurry() { + default SemigroupFactory, C> uncurry() { return ab -> apply(ab._1(), ab._2()); } - - @Override - default C apply(A a, B b, C c, C d) { - return apply(a).apply(b).apply(c, d); - } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java new file mode 100644 index 000000000..ad66e4036 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java @@ -0,0 +1,82 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; + +/** + * The Kleisli arrow of a {@link Monad}, manifest as simply an {@link Fn1}<A, MB>. This can be + * thought of as a fixed, portable {@link Monad#flatMap(Fn1)}. + * + * @param the input argument type + * @param the {@link Monad} unification parameter + * @param the output {@link Monad} type + */ +@FunctionalInterface +public interface Kleisli, MB extends Monad> extends Fn1 { + + /** + * Left-to-right composition of two compatible {@link Kleisli} arrows, yielding a new {@link Kleisli} arrow. + * + * @param after the arrow to execute after this one + * @param the new return parameter type + * @param the {@link Monad} instance to return + * @return the composition of the two arrows as a new {@link Kleisli} arrow + */ + @SuppressWarnings("overloads") + default > Kleisli andThen(Kleisli after) { + return a -> apply(a).flatMap(after).coerce(); + } + + /** + * Right-to-left composition of two compatible {@link Kleisli} arrows, yielding a new {@link Kleisli} arrow. + * + * @param before the arrow to execute before this one + * @param the new input argument type + * @param the {@link Monad} instance to flatMap with this arrow + * @return the composition of the two arrows as a new {@link Kleisli} arrow + */ + @SuppressWarnings("overloads") + default > Kleisli compose(Kleisli before) { + return z -> before.apply(z).flatMap(this).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Kleisli discardR(Applicative> appB) { + return Fn1.super.discardR(appB)::apply; + } + + /** + * {@inheritDoc} + */ + @Override + default Kleisli contraMap(Fn1 fn) { + return Fn1.super.contraMap(fn)::apply; + } + + /** + * {@inheritDoc} + */ + @Override + default Kleisli diMapL(Fn1 fn) { + return Fn1.super.diMapL(fn)::apply; + } + + /** + * Adapt a compatible function into a {@link Kleisli} arrow. + * + * @param fn the function + * @param the input argument type + * @param the output parameter type + * @param the {@link Monad} unification parameter + * @param the returned {@link Monad} instance + * @return the function adapted as a {@link Kleisli} arrow + */ + static , MB extends Monad> Kleisli kleisli( + Fn1 fn) { + return fn::apply; + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Lift.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Lift.java new file mode 100644 index 000000000..1620fac7c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Lift.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.internal.Runtime; +import com.jnape.palatable.lambda.monad.MonadBase; +import com.jnape.palatable.lambda.monad.MonadRec; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; + +/** + * Generalized, portable lifting operation for lifting a {@link MonadRec} into a {@link MonadBase}. + * + * @param the {@link MonadBase} to lift into + */ +@FunctionalInterface +public interface Lift> { + + > MonadBase checkedApply(MonadRec ga) + throws Throwable; + + default , MBA extends MonadBase> MBA apply(MonadRec ma) { + try { + return downcast(checkedApply(ma)); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * Static method to aid inference. + * + * @param lift the {@link Lift} + * @param the {@link MonadBase} to lift into + * @return the {@link Lift} + */ + static > Lift lift(Lift lift) { + return lift; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/MonoidFactory.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/MonoidFactory.java index 68657a104..1bcbb4314 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/MonoidFactory.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/MonoidFactory.java @@ -1,14 +1,24 @@ package com.jnape.palatable.lambda.functions.specialized; +import com.jnape.palatable.lambda.internal.Runtime; import com.jnape.palatable.lambda.monoid.Monoid; public interface MonoidFactory extends SemigroupFactory { + @Override + Monoid checkedApply(A a) throws Throwable; + @Override default B apply(A a, B b, B c) { return apply(a).apply(b, c); } @Override - Monoid apply(A a); + default Monoid apply(A a) { + try { + return checkedApply(a); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java index 811fcf283..f5e849bb4 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java @@ -1,79 +1,114 @@ package com.jnape.palatable.lambda.functions.specialized; import com.jnape.palatable.lambda.functions.Fn1; - -import java.util.function.Function; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.Applicative; /** * A specialized {@link Fn1} that returns a Boolean. * * @param The argument type */ -public interface Predicate extends Fn1, java.util.function.Predicate { +@FunctionalInterface +public interface Predicate extends Fn1 { /** * {@inheritDoc} */ @Override - default boolean test(A a) { - return apply(a); + default Predicate diMapL(Fn1 fn) { + return Fn1.super.diMapL(fn)::apply; } /** - * Override of {@link Function#compose(Function)}, returning an instance of Predicate for - * compatibility. Right-to-left composition. - * - * @param before the function who's return value is this predicate's argument - * @param the new argument type - * @return a new predicate of Z (the new argument type) + * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") - default Predicate compose(Function before) { - return Fn1.super.compose(before)::apply; + default Predicate contraMap(Fn1 fn) { + return Fn1.super.contraMap(fn)::apply; } /** * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") - default Predicate diMapL(Function fn) { - return Fn1.super.diMapL(fn)::apply; + default BiPredicate widen() { + return Fn1.super.widen()::checkedApply; + } + + /** + * {@inheritDoc} + */ + @Override + default Predicate discardR(Applicative> appB) { + return Fn1.super.discardR(appB)::checkedApply; + } + + /** + * {@inheritDoc} + */ + @Override + default BiPredicate compose(Fn2 before) { + return Fn1.super.compose(before)::apply; } /** - * Override of {@link java.util.function.Predicate#and(java.util.function.Predicate)}, returning an instance of - * Predicate for compatibility. Left-to-right composition. + * Left-to-right short-circuiting logical conjunction. * * @param other the predicate to test if this one succeeds * @return a predicate representing the conjunction of this predicate and other */ - @Override - default Predicate and(java.util.function.Predicate other) { - return a -> apply(a) && other.test(a); + default Predicate and(Predicate other) { + return a -> apply(a) && other.apply(a); } /** - * Override of {@link java.util.function.Predicate#or(java.util.function.Predicate)}, returning an instance of - * Predicate for compatibility. Left-to-right composition. + * Left-to-right short-circuiting logical disjunction. * * @param other the predicate to test if this one fails * @return a predicate representing the disjunction of this predicate and other */ - @Override - default Predicate or(java.util.function.Predicate other) { - return a -> apply(a) || other.test(a); + default Predicate or(Predicate other) { + return a -> apply(a) || other.apply(a); } /** - * Override of {@link java.util.function.Predicate#negate()}, returning an instance of Predicate for - * compatibility. + * Logical negation. * * @return the negation of this predicate */ - @Override default Predicate negate() { return a -> !apply(a); } + + /** + * Convert this {@link Predicate} to a java {@link java.util.function.Predicate}. + * + * @return the {@link java.util.function.Predicate} + */ + default java.util.function.Predicate toPredicate() { + return this::apply; + } + + /** + * Static factory method to create a predicate from an {@link Fn1}. + * + * @param predicate the {@link Fn1} + * @param the input type + * @return the predicate + */ + static Predicate predicate(Fn1 predicate) { + return predicate::apply; + } + + /** + * Create a {@link Predicate} from a java {@link java.util.function.Predicate}. + * + * @param predicate the java {@link java.util.function.Predicate} + * @param the input type + * @return the {@link Predicate} + */ + static Predicate fromPredicate(java.util.function.Predicate predicate) { + return predicate::test; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Pure.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Pure.java new file mode 100644 index 000000000..797a2e8aa --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Pure.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.internal.Runtime; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; + +/** + * Generalized, portable {@link Applicative#pure(Object)}, with a loosened {@link Functor} constraint. + * + * @param the {@link Functor} to lift into + */ +@FunctionalInterface +public interface Pure> { + + Functor checkedApply(A a) throws Throwable; + + default > FA apply(A a) { + try { + @SuppressWarnings("unchecked") FA fa = downcast(checkedApply(a)); + return fa; + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * Static method to aid inference. + * + * @param pure the {@link Pure} + * @param the {@link Functor} witness + * @return the {@link Pure} + */ + static > Pure pure(Pure pure) { + return pure; + } + + /** + * Extract an {@link Applicative Applicative's} {@link Applicative#pure(Object) pure} implementation to an instance + * of {@link Pure}. + * + * @param app the {@link Applicative} + * @param the witness + * @return the {@link Pure} + */ + static > Pure of(Applicative app) { + return app::pure; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/SemigroupFactory.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/SemigroupFactory.java index 4a47c277c..6d49d6a55 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/SemigroupFactory.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/SemigroupFactory.java @@ -7,10 +7,15 @@ public interface SemigroupFactory extends Fn3 { @Override - Semigroup apply(A a); + Semigroup checkedApply(A a) throws Throwable; @Override - default B apply(A a, B b, B c) { - return apply(a).apply(b, c); + default Semigroup apply(A a) { + return Fn3.super.apply(a)::apply; + } + + @Override + default B checkedApply(A a, B b, B c) throws Throwable { + return checkedApply(a).checkedApply(b, c); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/SideEffect.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/SideEffect.java new file mode 100644 index 000000000..6ef2092a1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/SideEffect.java @@ -0,0 +1,59 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.internal.Runtime; +import com.jnape.palatable.lambda.io.IO; + +/** + * An interface used to represent an effect that requires no input and produces no output, and therefore is only + * perceivable through inspection of some unreported state. Only exists because Java target-type inference requires an + * interface, or else this would all be internal, hence the inconveniently-named Ω. + *

+ * Ω should *never* be called directly. + * + * @see IO#io(SideEffect) + */ +public interface SideEffect { + + /** + * A no-op {@link SideEffect} + */ + SideEffect NOOP = () -> {}; + + @SuppressWarnings("NonAsciiCharacters") + void Ω() throws Throwable; + + /** + * Convert this {@link SideEffect} to a java {@link Runnable}. + * + * @return the {@link Runnable} + */ + default Runnable toRunnable() { + return () -> { + try { + Ω(); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + }; + } + + /** + * Create a {@link SideEffect} from a java {@link Runnable}. + * + * @param runnable the {@link Runnable} + * @return the {@link SideEffect} + */ + static SideEffect fromRunnable(Runnable runnable) { + return runnable::run; + } + + /** + * Static factory method to aid in inference. + * + * @param sideEffect the {@link SideEffect} + * @return the {@link SideEffect} + */ + static SideEffect sideEffect(SideEffect sideEffect) { + return sideEffect; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java deleted file mode 100644 index f91b83aa4..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -import com.jnape.palatable.lambda.functions.Fn1; - -import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; - -/** - * Specialized {@link Fn1} that can throw checked exceptions. - * - * @param The input type - * @param The output type - * @see CheckedSupplier - * @see Fn1 - */ -@FunctionalInterface -public interface CheckedFn1 extends Fn1 { - - @Override - default B apply(A a) { - try { - return checkedApply(a); - } catch (Throwable t) { - throw throwChecked(t); - } - } - - /** - * A version of {@link Fn1} that can throw checked exceptions. - * - * @param a the argument - * @return the result of the function application - * @throws T any Throwable thrown by the function application - */ - B checkedApply(A a) throws T; -} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java deleted file mode 100644 index dd38fc165..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -import java.util.function.Supplier; - -import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; - -/** - * Specialized {@link Supplier} that can throw checked exceptions. - * - * @param The exception type - * @param The return type - * @see CheckedFn1 - */ -@FunctionalInterface -public interface CheckedSupplier extends Supplier { - - @Override - default T get() { - try { - return checkedGet(); - } catch (Throwable t) { - throw throwChecked(t); - } - } - - /** - * A version of {@link Supplier#get()} that can throw checked exceptions. - * - * @return the supplied result - * @throws E any exception that can be thrown by this method - */ - T checkedGet() throws E; -} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/Runtime.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/Runtime.java deleted file mode 100644 index 810528676..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/Runtime.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -class Runtime { - - @SuppressWarnings("unchecked") - public static RuntimeException throwChecked(Throwable ex) throws T { - throw (T) ex; - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java new file mode 100644 index 000000000..9cf2125e1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java @@ -0,0 +1,98 @@ +package com.jnape.palatable.lambda.functor; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Lazy; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * An interface representing applicative functors - functors that can have their results combined with other functors + * of the same instance in a context-free manner. + *

+ * The same rules that apply to Functor apply to Applicative, along with the following + * additional 4 laws: + *

+ * As with Functor, Applicative instances that do not satisfy all of the functor laws, as well + * as the above applicative laws, are not well-behaved and often break down in surprising ways. + *

+ * For more information, read about + * Applicative Functors. + * + * @param The type of the parameter + * @param The unification parameter to more tightly type-constrain Applicatives to themselves + */ +public interface Applicative> extends Functor { + + /** + * Lift the value b into this applicative functor. + * + * @param b the value + * @param the type of the returned applicative's parameter + * @return an instance of this applicative over b + */ + Applicative pure(B b); + + /** + * Given another instance of this applicative over a mapping function, "zip" the two instances together using + * whatever application semantics the current applicative supports. + * + * @param appFn the other applicative instance + * @param the resulting applicative parameter type + * @return the mapped applicative + */ + Applicative zip(Applicative, App> appFn); + + /** + * Given a {@link Lazy lazy} instance of this applicative over a mapping function, "zip" the two instances together + * using whatever application semantics the current applicative supports. This is useful for applicatives that + * support lazy evaluation and early termination. + * + * @param the resulting applicative parameter type + * @param lazyAppFn the lazy other applicative instance + * @return the mapped applicative + * @see com.jnape.palatable.lambda.adt.Maybe + * @see com.jnape.palatable.lambda.adt.Either + */ + default Lazy> lazyZip( + Lazy, App>> lazyAppFn) { + return lazyAppFn.fmap(this::zip); + } + + /** + * Sequence both this Applicative and appB, discarding this Applicative's + * result and returning appB. This is generally useful for sequentially performing side-effects. + * + * @param appB the other Applicative + * @param the type of the returned Applicative's parameter + * @return appB + */ + default Applicative discardL(Applicative appB) { + return zip(appB.fmap(constantly())); + } + + /** + * Sequence both this Applicative and appB, discarding appB's result and + * returning this Applicative. This is generally useful for sequentially performing side-effects. + * + * @param appB the other Applicative + * @param the type of appB's parameter + * @return this Applicative + */ + default Applicative discardR(Applicative appB) { + return zip(appB.fmap(constantly(id()))); + } + + /** + * {@inheritDoc} + */ + @Override + default Applicative fmap(Fn1 fn) { + return zip(pure(fn)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java index 6ab64dd96..bfb201242 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java @@ -1,6 +1,6 @@ package com.jnape.palatable.lambda.functor; -import java.util.function.Function; +import com.jnape.palatable.lambda.functions.Fn1; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; @@ -9,14 +9,15 @@ *

* For more information, read about Bifunctors. * - * @param The type of the left parameter - * @param The type of the right parameter + * @param The type of the left parameter + * @param The type of the right parameter + * @param The unification parameter * @see Functor * @see Profunctor * @see com.jnape.palatable.lambda.adt.hlist.Tuple2 */ @FunctionalInterface -public interface Bifunctor { +public interface Bifunctor> extends BoundedBifunctor { /** * Covariantly map over the left parameter. @@ -25,7 +26,7 @@ public interface Bifunctor { * @param fn the mapping function * @return a bifunctor over C (the new left parameter) and B (the same right parameter) */ - default Bifunctor biMapL(Function fn) { + default Bifunctor biMapL(Fn1 fn) { return biMap(fn, id()); } @@ -37,7 +38,7 @@ default Bifunctor biMapL(Function fn) { * @param fn the mapping function * @return a bifunctor over A (the same left parameter) and C (the new right parameter) */ - default Bifunctor biMapR(Function fn) { + default Bifunctor biMapR(Fn1 fn) { return biMap(id(), fn); } @@ -51,5 +52,5 @@ default Bifunctor biMapR(Function fn) { * @param rFn the right parameter mapping function * @return a bifunctor over C (the new left parameter type) and D (the new right parameter type) */ - Bifunctor biMap(Function lFn, Function rFn); + Bifunctor biMap(Fn1 lFn, Fn1 rFn); } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/BoundedBifunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/BoundedBifunctor.java new file mode 100644 index 000000000..057c3bd29 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/BoundedBifunctor.java @@ -0,0 +1,61 @@ +package com.jnape.palatable.lambda.functor; + +import com.jnape.palatable.lambda.functions.Fn1; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * A {@link Bifunctor} that has both parameter types upper bounded; that is, neither parameters can be mapped to a value + * that is not covariant to their respective upper bounds + * + * @param The type of the left parameter + * @param The type of the right parameter + * @param The type of the left parameter upper type bound + * @param The type of the right parameter upper type bound + * @param The unification parameter + * @see Bifunctor + */ +@FunctionalInterface +public interface BoundedBifunctor< + A extends ContraA, + B extends ContraB, + ContraA, + ContraB, + BF extends BoundedBifunctor> { + + /** + * Covariantly map the left parameter into a value that is covariant to ContraA. + * + * @param the new left parameter type + * @param fn the mapping function + * @return a bifunctor of C (the new parameter type) and B (the same right parameter) + */ + default BoundedBifunctor biMapL(Fn1 fn) { + return biMap(fn, id()); + } + + /** + * Covariantly map the right parameter into a value that is covariant to ContraB. + * + * @param the new right parameter type + * @param fn the mapping function + * @return a bifunctor of A (the same left parameter) and C (the new parameter type) + */ + default BoundedBifunctor biMapR(Fn1 fn) { + return biMap(id(), fn); + } + + /** + * Dually covariantly map both the left and right parameters into values that are covariant to ContraA + * and ContraB, respectively. This is isomorphic to biMapL(lFn).biMapR(rFn). + * + * @param the new left parameter type + * @param the new right parameter type + * @param lFn the left parameter mapping function + * @param rFn the right parameter mapping function + * @return a bifunctor over C (the new left parameter type) and D (the new right parameter type) + */ + BoundedBifunctor biMap( + Fn1 lFn, + Fn1 rFn); +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Cartesian.java b/src/main/java/com/jnape/palatable/lambda/functor/Cartesian.java new file mode 100644 index 000000000..8c28dc54f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/Cartesian.java @@ -0,0 +1,65 @@ +package com.jnape.palatable.lambda.functor; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; + +/** + * {@link Profunctor} strength in the cartesian product sense: p a b -> p (c ^ a) (c ^ b) for any type + * c. + * + * @param the type of the left parameter + * @param the type of the left parameter + * @param

the unification parameter + * @see com.jnape.palatable.lambda.functions.Fn1 + * @see Cocartesian + */ +public interface Cartesian> extends Profunctor { + + /** + * Pair some type C to this profunctor's carrier types. + * + * @param the paired type + * @return the cartesian-strengthened profunctor + */ + Cartesian, Tuple2, P> cartesian(); + + /** + * Pair the covariantly-positioned carrier type with the contravariantly-positioned carrier type. This can be + * thought of as "carrying" or "inspecting" the left parameter. + * + * @return the profunctor with the first parameter carried + */ + default Cartesian, P> carry() { + return this.cartesian().contraMap(Tuple2::fill); + } + + /** + * {@inheritDoc} + */ + @Override + Cartesian diMap(Fn1 lFn, Fn1 rFn); + + /** + * {@inheritDoc} + */ + @Override + default Cartesian diMapL(Fn1 fn) { + return (Cartesian) Profunctor.super.diMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Cartesian diMapR(Fn1 fn) { + return (Cartesian) Profunctor.super.diMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Cartesian contraMap(Fn1 fn) { + return (Cartesian) Profunctor.super.contraMap(fn); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Cocartesian.java b/src/main/java/com/jnape/palatable/lambda/functor/Cocartesian.java new file mode 100644 index 000000000..07bfe13d4 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/Cocartesian.java @@ -0,0 +1,66 @@ +package com.jnape.palatable.lambda.functor; + +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.functions.Fn1; + +/** + * {@link Profunctor} strength in the cocartesian coproduct sense: p a b -> p (c v a) (c v b) for any + * type c. + * + * @param the type of the left parameter + * @param the type of the left parameter + * @param

the unification parameter + * @see com.jnape.palatable.lambda.functions.Fn1 + * @see Cartesian + */ +public interface Cocartesian> extends Profunctor { + + /** + * Choose some type C or this profunctor's carrier types. + * + * @param the choice type + * @return the cocartesian-costrengthened profunctor + */ + Cocartesian, Choice2, P> cocartesian(); + + /** + * Choose between the covariantly-positioned carrier type and the contravariantly-positioned carrier type. This can + * be used to encode partial functions a -> (_|_ v b) as total functions + * a -> (a v b). + * + * @return the profunctor with a choice + */ + default Cocartesian, P> choose() { + return this.cocartesian().contraMap(Choice2::b); + } + + /** + * {@inheritDoc} + */ + @Override + Cocartesian diMap(Fn1 lFn, Fn1 rFn); + + /** + * {@inheritDoc} + */ + @Override + default Cocartesian diMapL(Fn1 fn) { + return (Cocartesian) Profunctor.super.diMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Cocartesian diMapR(Fn1 fn) { + return (Cocartesian) Profunctor.super.diMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Cocartesian contraMap(Fn1 fn) { + return (Cocartesian) Profunctor.super.contraMap(fn); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Contravariant.java b/src/main/java/com/jnape/palatable/lambda/functor/Contravariant.java new file mode 100644 index 000000000..c596b36b0 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/Contravariant.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.functor; + +import com.jnape.palatable.lambda.functions.Fn1; + +/** + * The contravariant functor (or "co-functor"); that is, a functor that maps contravariantly (A <- B) + * over its parameter. + * Contravariant functors are not necessarily {@link Functor}s. + *

+ * For more information, read about + * Contravariant Functors. + * + * @param the type of the parameter + * @param the unification parameter + * @see Profunctor + */ +public interface Contravariant> { + + /** + * Contravariantly map A <- B. + * + * @param fn the mapping function + * @param the new parameter type + * @return the mapped Contravariant functor instance + */ + Contravariant contraMap(Fn1 fn); +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Functor.java b/src/main/java/com/jnape/palatable/lambda/functor/Functor.java index ef9b3b308..ec4323641 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Functor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Functor.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; -import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; /** * An interface for the generic covariant functorial operation map over some parameter A. @@ -12,6 +12,7 @@ * For more information, read about Functors. * * @param The type of the parameter + * @param The unification parameter * @see Bifunctor * @see Profunctor * @see Fn1 @@ -19,7 +20,7 @@ * @see com.jnape.palatable.lambda.adt.Either */ @FunctionalInterface -public interface Functor { +public interface Functor> { /** * Covariantly transmute this functor's parameter using the given mapping function. Generally this method is @@ -29,5 +30,15 @@ public interface Functor { * @param fn the mapping function * @return a functor over B (the new parameter type) */ - Functor fmap(Function fn); + Functor fmap(Fn1 fn); + + /** + * Convenience method for coercing this functor instance into another concrete type. Unsafe. + * + * @param the concrete functor instance to coerce this functor to + * @return the coerced functor + */ + default > Concrete coerce() { + return downcast(this); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java index db8562172..b9518d3e4 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java @@ -2,24 +2,36 @@ import com.jnape.palatable.lambda.functions.Fn1; -import java.util.function.Function; - import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; /** - * A dually-parametric functor that maps contravariantly over the left parameter and covariantly over the - * right. + * A dually-parametric functor that maps contravariantly over the left parameter and covariantly over the right. *

* For more information, read about Profunctors. * - * @param The type of the left parameter - * @param The type of the right parameter + * @param The type of the left parameter + * @param The type of the right parameter + * @param The unification parameter * @see Functor * @see Bifunctor + * @see Contravariant * @see Fn1 + * @see com.jnape.palatable.lambda.optics.Optic */ @FunctionalInterface -public interface Profunctor { +public interface Profunctor> extends Contravariant> { + + /** + * Dually map contravariantly over the left parameter and covariantly over the right parameter. This is isomorphic + * to diMapL(lFn).diMapR(rFn). + * + * @param the new left parameter type + * @param the new right parameter type + * @param lFn the left parameter mapping function + * @param rFn the right parameter mapping function + * @return a profunctor over Z (the new left parameter type) and C (the new right parameter type) + */ + Profunctor diMap(Fn1 lFn, Fn1 rFn); /** * Contravariantly map over the left parameter. @@ -28,7 +40,7 @@ public interface Profunctor { * @param fn the mapping function * @return a profunctor over Z (the new left parameter type) and C (the same right parameter type) */ - default Profunctor diMapL(Function fn) { + default Profunctor diMapL(Fn1 fn) { return diMap(fn, id()); } @@ -40,19 +52,15 @@ default Profunctor diMapL(Function fn) { * @param fn the mapping function * @return a profunctor over A (the same left parameter type) and C (the new right parameter type) */ - default Profunctor diMapR(Function fn) { + default Profunctor diMapR(Fn1 fn) { return diMap(id(), fn); } /** - * Dually map contravariantly over the left parameter and covariantly over the right parameter. This is isomorphic - * to diMapL(lFn).diMapR(rFn). - * - * @param the new left parameter type - * @param the new right parameter type - * @param lFn the left parameter mapping function - * @param rFn the right parameter mapping function - * @return a profunctor over Z (the new left parameter type) and C (the new right parameter tyep) + * {@inheritDoc} */ - Profunctor diMap(Function lFn, Function rFn); + @Override + default Profunctor contraMap(Fn1 fn) { + return diMapL(fn); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java new file mode 100644 index 000000000..b0f0dff76 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java @@ -0,0 +1,125 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; + +/** + * A functor representing the type-level composition of two {@link Applicative} functors; useful for preserving nested + * type-level transformations during traversal of a {@link com.jnape.palatable.lambda.traversable.Traversable}. + * + * @param The outer applicative + * @param The inner applicative + * @param The carrier type + */ +public final class Compose, G extends Applicative, A> implements + Applicative> { + + private final Applicative, F> fga; + + public Compose(Applicative, F> fga) { + this.fga = fga; + } + + @SuppressWarnings("RedundantTypeArguments") + public , FGA extends Applicative> FGA getCompose() { + return fga.fmap(Applicative::coerce).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Compose fmap(Fn1 fn) { + return new Compose<>(fga.fmap(g -> g.fmap(fn))); + } + + /** + * {@inheritDoc} + */ + @Override + public Compose pure(B b) { + return new Compose<>(fga.fmap(g -> g.pure(b))); + } + + /** + * {@inheritDoc} + */ + @Override + public Compose zip(Applicative, Compose> appFn) { + return new Compose<>(fga.zip(appFn.>>coerce() + .getCompose().fmap(gFn -> g -> g.zip(gFn)))); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Compose>> lazyAppFn) { + @SuppressWarnings("RedundantTypeArguments") + Lazy, G>, F>> lazyAppFnCoerced = + lazyAppFn + .>>fmap( + Applicative, Compose>::coerce) + .fmap(Compose>::getCompose); + + return fga.>fmap(upcast()) + .>lazyZip(lazyAppFnCoerced.fmap(fgf -> fgf.fmap(gf -> ga -> ga.zip(gf)))) + .fmap(Compose::new); + } + + /** + * {@inheritDoc} + */ + @Override + public Compose discardL(Applicative> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Compose discardR(Applicative> appB) { + return Applicative.super.discardR(appB).coerce(); + } + + @Override + public boolean equals(Object other) { + return other instanceof Compose && Objects.equals(fga, ((Compose) other).fga); + } + + @Override + public int hashCode() { + return Objects.hash(fga); + } + + @Override + public String toString() { + return "Compose{fga=" + fga + '}'; + } + + /** + * The canonical {@link Pure} instance for {@link Compose}. + * + * @param pureF the {@link Pure} constructor for the outer {@link Applicative} + * @param pureG the {@link Pure} constructor for the inner {@link Applicative} + * @param the outer {@link Applicative} type + * @param the inner {@link Applicative} type + * @return the {@link Pure} instance + */ + public static , G extends Applicative> Pure> pureCompose( + Pure pureF, Pure pureG) { + return new Pure>() { + @Override + public Compose checkedApply(A a) throws Throwable { + return new Compose<>(pureF., Applicative, F>>apply(pureG.apply(a))); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index e19261415..dfc29b1c6 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -1,9 +1,15 @@ package com.jnape.palatable.lambda.functor.builtin; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; -import java.util.function.Function; +import java.util.Objects; /** * A (surprisingly useful) functor over some phantom type B, retaining a value of type A that @@ -14,7 +20,10 @@ * @param the left parameter type, and the type of the stored value * @param the right (phantom) parameter type */ -public final class Const implements Functor, Bifunctor { +public final class Const implements + MonadRec>, + Bifunctor>, + Traversable> { private final A a; @@ -40,49 +49,135 @@ public A runConst() { * @return a Const over A (the same value) and C (the new phantom parameter) */ @Override + public Const fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override @SuppressWarnings("unchecked") - public Const fmap(Function fn) { + public Const pure(C c) { return (Const) this; } /** - * Covariantly map over the left parameter type (the value). - * - * @param fn the mapping function - * @param the new left parameter type (the value) - * @return a Const over Z (the new value) and B (the same phantom parameter) + * {@inheritDoc} + */ + @Override + public Const zip(Applicative, Const> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Const>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public Const discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Const discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") - public Const biMapL(Function fn) { - return (Const) Bifunctor.super.biMapL(fn); + public Const flatMap(Fn1>> f) { + return (Const) this; } /** - * Covariantly map over the right parameter (phantom) type. - * - * @param fn the mapping function - * @param the new right parameter (phantom) type - * @return a Const over A (the same value) and C (the new phantom parameter) + * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") - public Const biMapR(Function fn) { - return (Const) Bifunctor.super.biMapR(fn); + public Const trampolineM(Fn1, Const>> fn) { + return (Const) this; } /** - * Bifunctor's biMap, specialized for Const. - * - * @param lFn the left parameter mapping function - * @param rFn the right parameter mapping function - * @param the new left parameter type - * @param the new right parameter type - * @return a Const over C (the new value) and D (the new phantom parameter) + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return pure.apply(coerce()); + } + + /** + * {@inheritDoc} + */ + @Override + public Const biMapL(Fn1 fn) { + return (Const) Bifunctor.super.biMapL(fn); + } + + /** + * {@inheritDoc} */ @Override - public Const biMap(Function lFn, - Function rFn) { + public Const biMapR(Fn1 fn) { + return (Const) Bifunctor.super.biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Const biMap(Fn1 lFn, + Fn1 rFn) { return new Const<>(lFn.apply(a)); } + + @Override + public boolean equals(Object other) { + return other instanceof Const && Objects.equals(a, ((Const) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Const{" + + "a=" + a + + '}'; + } + + /** + * The canonical {@link Pure} instance for {@link Const}. + * + * @param a the stored value + * @param the left parameter type, and the type of the stored value + * @return the {@link Pure} instance + */ + public static Pure> pureConst(A a) { + return new Pure>() { + @Override + public Const checkedApply(B b) { + return new Const<>(a); + } + }; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Exchange.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Exchange.java new file mode 100644 index 000000000..f04050368 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Exchange.java @@ -0,0 +1,74 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.optics.Iso; + +/** + * A profunctor used to extract the isomorphic functions an {@link Iso} is composed of. + * + * @param the smaller viewed value of an {@link Iso} + * @param the smaller viewing value of an {@link Iso} + * @param the larger viewing value of an {@link Iso} + * @param the larger viewed value of an {@link Iso} + */ +public final class Exchange implements Profunctor> { + private final Fn1 sa; + private final Fn1 bt; + + public Exchange(Fn1 sa, Fn1 bt) { + this.sa = sa; + this.bt = bt; + } + + /** + * Extract the mapping S -> A. + * + * @return an {@link Fn1}<S, A> + */ + public Fn1 sa() { + return sa; + } + + /** + * Extract the mapping B -> T. + * + * @return an {@link Fn1}<B, T> + */ + public Fn1 bt() { + return bt; + } + + /** + * {@inheritDoc} + */ + @Override + public Exchange diMap(Fn1 lFn, + Fn1 rFn) { + return new Exchange<>(lFn.fmap(sa), bt.fmap(rFn)); + } + + /** + * {@inheritDoc} + */ + @Override + public Exchange diMapL(Fn1 fn) { + return (Exchange) Profunctor.super.diMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Exchange diMapR(Fn1 fn) { + return (Exchange) Profunctor.super.diMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Exchange contraMap(Fn1 fn) { + return (Exchange) Profunctor.super.contraMap(fn); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java index 04160b05f..3ee6a0dbb 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java @@ -1,15 +1,23 @@ package com.jnape.palatable.lambda.functor.builtin; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; -import java.util.function.Function; +import java.util.Objects; + +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A functor over some value of type A that can be mapped over and retrieved later. * * @param the value type */ -public final class Identity implements Functor { +public final class Identity implements MonadRec>, Traversable> { private final A a; @@ -27,14 +35,105 @@ public A runIdentity() { } /** - * Covariantly map over the value. - * - * @param fn the mapping function - * @param the new value type - * @return an Identity over B (the new value) + * {@inheritDoc} + */ + @Override + public Identity flatMap(Fn1>> f) { + return f.apply(runIdentity()).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Identity fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Identity pure(B b) { + return new Identity<>(b); + } + + /** + * {@inheritDoc} + */ + @Override + public Identity zip(Applicative, Identity> appFn) { + return new Identity<>(appFn.>>coerce().runIdentity().apply(a)); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Identity>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public Identity discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Identity discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return (AppTrav) fn.apply(runIdentity()).fmap(Identity::new); + } + + /** + * {@inheritDoc} */ @Override - public Identity fmap(Function fn) { - return new Identity<>(fn.apply(a)); + public Identity trampolineM(Fn1, Identity>> fn) { + return new Identity<>(trampoline(a -> fn.apply(a).>>coerce().runIdentity(), + runIdentity())); + } + + @Override + public boolean equals(Object other) { + return other instanceof Identity && Objects.equals(a, ((Identity) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Identity{" + + "a=" + a + + '}'; + } + + /** + * The canonical {@link Pure} instance for {@link Identity}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureIdentity() { + return Identity::new; } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Lazy.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Lazy.java new file mode 100644 index 000000000..83ec47711 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Lazy.java @@ -0,0 +1,200 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.LinkedList; +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; + +/** + * A {@link Monad} representing a lazily-computed value. Stack-safe. + * + * @param the value type + */ +public abstract class Lazy implements MonadRec>, Traversable> { + + private Lazy() { + } + + /** + * Returns the value represented by this lazy computation. + * + * @return the value + */ + public abstract A value(); + + /** + * {@inheritDoc} + */ + @Override + public Lazy flatMap(Fn1>> f) { + @SuppressWarnings("unchecked") Lazy source = (Lazy) this; + @SuppressWarnings({"unchecked", "RedundantCast"}) + Fn1> flatMap = (Fn1>) (Object) f; + return new Compose<>(source, flatMap); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return fn.apply(value()).fmap(b -> (TravB) lazy(b)).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final Lazy pure(B b) { + return lazy(b); + } + + /** + * {@inheritDoc} + */ + @Override + public final Lazy fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy zip(Applicative, Lazy> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final Lazy discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final Lazy discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy trampolineM(Fn1, Lazy>> fn) { + return flatMap(a -> fn.apply(a).>>coerce() + .flatMap(aOrB -> aOrB.match(a_ -> lazy(a_).trampolineM(fn), Lazy::lazy))); + } + + @Override + public boolean equals(Object other) { + return other instanceof Lazy && Objects.equals(value(), ((Lazy) other).value()); + } + + @Override + public int hashCode() { + return Objects.hash(value()); + } + + @Override + public String toString() { + return "Lazy{value=" + value() + "}"; + } + + /** + * Lift a pure value into a lazy computation. + * + * @param value the value + * @param the value type + * @return the new {@link Lazy} + */ + public static Lazy lazy(A value) { + return lazy(() -> value); + } + + /** + * Wrap a computation in a lazy computation. + * + * @param the value type + * @param fn0 the computation + * @return the new {@link Lazy} + */ + public static Lazy lazy(Fn0 fn0) { + return new Later<>(fn0); + } + + /** + * The canonical {@link Pure} instance for {@link Lazy}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureLazy() { + return Lazy::lazy; + } + + private static final class Later extends Lazy { + private final Fn0 fn0; + + private Later(Fn0 fn0) { + this.fn0 = fn0; + } + + @Override + public A value() { + return fn0.apply(); + } + } + + private static final class Compose extends Lazy { + private final Lazy source; + private final Fn1> flatMap; + + private Compose(Lazy source, + Fn1> flatMap) { + this.source = source; + this.flatMap = flatMap; + } + + @Override + public A value() { + @SuppressWarnings("unchecked") Tuple2, LinkedList>>> tuple = + tuple((Lazy) this, new LinkedList<>()); + @SuppressWarnings("unchecked") + A a = (A) trampoline(into((source, flatMaps) -> { + if (source instanceof Compose) { + Compose nested = (Compose) source; + flatMaps.push(nested.flatMap); + return recurse(tuple(nested.source, flatMaps)); + } + + if (flatMaps.isEmpty()) + return terminate(source.value()); + + return recurse(tuple(flatMaps.pop().apply(source.value()), flatMaps)); + }), tuple); + + return a; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Market.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Market.java new file mode 100644 index 000000000..3c8ce7576 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Market.java @@ -0,0 +1,174 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Cocartesian; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.optics.Prism; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.choice.Choice2.a; +import static com.jnape.palatable.lambda.adt.choice.Choice2.b; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; + +/** + * A profunctor used to extract the isomorphic functions a {@link Prism} is composed of. + * + * @param the output that might fail to be produced + * @param the input that guarantees its output + * @param the input that might fail to map to its output + * @param the guaranteed output + */ +public final class Market implements + MonadRec>, + Cocartesian> { + + private final Fn1 bt; + private final Fn1> sta; + + public Market(Fn1 bt, Fn1> sta) { + this.bt = fn1(bt); + this.sta = fn1(sta); + } + + /** + * Extract the mapping B -> T. + * + * @return a {@link Fn1}<B, T> + */ + public Fn1 bt() { + return bt; + } + + /** + * Extract the mapping S -> {@link Either}<T, A>. + * + * @return a {@link Fn1}<S, {@link Either}<T, A>> + */ + public Fn1> sta() { + return sta; + } + + /** + * {@inheritDoc} + */ + @Override + public Market pure(U u) { + return new Market<>(constantly(u), constantly(left(u))); + } + + /** + * {@inheritDoc} + */ + @Override + public Market flatMap(Fn1>> f) { + return new Market<>(b -> f.apply(bt().apply(b)).>coerce().bt().apply(b), + s -> sta().apply(s).invert() + .flatMap(t -> f.apply(t).>coerce().sta() + .apply(s).invert()).invert()); + } + + /** + * {@inheritDoc} + */ + @Override + public Market trampolineM( + Fn1, Market>> fn) { + Fn1 bu = Fn1.fn1(bt).trampolineM(t -> fn1(fn.apply(t).>>coerce().bt)); + Fn1> sua = Fn1.>fn1(sta) + .flatMap(tOrA -> fn1(s -> tOrA.match( + trampoline(t -> fn.apply(t).>>coerce() + .sta.apply(s) + .match(tOrU -> tOrU.match(RecursiveResult::recurse, u -> terminate(left(u))), + a -> terminate(right(a)))), + Either::right))); + return new Market<>(bu, sua); + } + + /** + * {@inheritDoc} + */ + @Override + public Market zip(Applicative, Market> appFn) { + Market> marketF = appFn.coerce(); + return new Market<>(b -> marketF.bt().apply(b).apply(bt().apply(b)), + s -> sta().apply(s).invert().zip(marketF.sta().apply(s).invert()).invert()); + } + + /** + * {@inheritDoc} + */ + @Override + public Market fmap(Fn1 fn) { + return diMapR(fn::apply); + } + + /** + * {@inheritDoc} + */ + @Override + public Market, Choice2> cocartesian() { + return new Market<>(bt.fmap(Choice2::b), + cs -> cs.fmap(sta).match(c -> left(a(c)), + tOrA -> tOrA.match(t -> left(b(t)), Either::right))); + } + + /** + * {@inheritDoc} + */ + @Override + public Market diMap(Fn1 lFn, + Fn1 rFn) { + return new Market<>(bt.fmap(rFn), sta.diMapL(lFn).diMapR(c -> c.biMapL(rFn))); + } + + /** + * {@inheritDoc} + */ + @Override + public Market diMapL(Fn1 fn) { + return (Market) Cocartesian.super.diMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Market diMapR(Fn1 fn) { + return (Market) Cocartesian.super.diMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Market contraMap(Fn1 fn) { + return (Market) Cocartesian.super.contraMap(fn); + } + + /** + * The canonical {@link Pure} instance for {@link Market}. + * + * @param the output that might fail to be produced + * @param the input that guarantees its output + * @param the input that might fail to map to its output + * @return the {@link Pure} instance + */ + public static Pure> pureMarket() { + return new Pure>() { + @Override + public Market checkedApply(T t) { + return new Market<>(constantly(t), constantly(left(t))); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/State.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/State.java new file mode 100644 index 000000000..5caa98d18 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/State.java @@ -0,0 +1,272 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadReader; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.MonadWriter; +import com.jnape.palatable.lambda.monad.transformer.builtin.StateT; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.monad.transformer.builtin.StateT.stateT; + +/** + * The state {@link Monad}, useful for iteratively building up state and state-contextualized result. + *

+ * Note that constructing an {@link IO} this way results in no intermediate futures being constructed by either + * {@link IO#unsafePerformAsyncIO()} or {@link IO#unsafePerformAsyncIO(Executor)}, and {@link IO#unsafePerformIO()} + * is synonymous with invoking {@link CompletableFuture#get()} on the externally managed future. + * + * @param supplier the source of externally managed {@link CompletableFuture completable futures} + * @param the result type + * @return the {@link IO} + */ + public static IO externallyManaged(Fn0> supplier) { + return new IO() { + @Override + public A unsafePerformIO() { + return fn0(() -> unsafePerformAsyncIO().get()).apply(); + } + + @Override + public CompletableFuture unsafePerformAsyncIO(Executor executor) { + return supplier.apply(); + } + }; + } + + /** + * The canonical {@link Pure} instance for {@link IO}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureIO() { + return IO::io; + } + + private static final class Compose extends IO { + + private final IO source; + private final Choice2, Fn1>> composition; + + private Compose(IO source, Choice2, Fn1>> composition) { + this.source = source; + this.composition = composition; + } + + @Override + public A unsafePerformIO() { + Lazy lazyA = LazyRec., Object>lazyRec( + (f, io) -> { + if (io instanceof IO.Compose) { + Compose compose = (Compose) io; + Lazy head = f.apply(compose.source); + return compose.composition + .match(zip -> head.flatMap(x -> f.apply(zip) + .>fmap(downcast()) + .fmap(g -> g.apply(x))), + flatMap -> head.fmap(flatMap).flatMap(f)); + } + return lazy(io::unsafePerformIO); + }, + this); + return downcast(lazyA.value()); + } + + @Override + @SuppressWarnings("unchecked") + public CompletableFuture unsafePerformAsyncIO(Executor executor) { + Lazy> lazyFuture = LazyRec., CompletableFuture>lazyRec( + (f, io) -> { + if (io instanceof IO.Compose) { + Compose compose = (Compose) io; + Lazy> head = f.apply(compose.source); + return compose.composition + .match(zip -> head.flatMap(futureX -> f.apply(zip) + .fmap(futureF -> futureF.thenCombineAsync( + futureX, + (f2, x) -> ((Fn1) f2).apply(x), + executor))), + flatMap -> head.fmap(futureX -> futureX + .thenComposeAsync(x -> f.apply(flatMap.apply(x)).value(), + executor))); + } + return lazy(() -> (CompletableFuture) io.unsafePerformAsyncIO(executor)); + }, + this); + + return (CompletableFuture) lazyFuture.value(); + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/MappingIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/MappingIterator.java deleted file mode 100644 index 81b46c027..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iterators/MappingIterator.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.jnape.palatable.lambda.iterators; - -import java.util.Iterator; -import java.util.function.Function; - -public class MappingIterator extends ImmutableIterator { - - private final Function function; - private final Iterator iterator; - - public MappingIterator(Function function, Iterator iterator) { - this.function = function; - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public B next() { - return function.apply(iterator.next()); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIterator.java deleted file mode 100644 index c7f8f279e..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIterator.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jnape.palatable.lambda.iterators; - -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.function.Function; - -public class PredicatedDroppingIterator extends ImmutableIterator { - private final Function predicate; - private final RewindableIterator rewindableIterator; - private boolean finishedDropping; - - public PredicatedDroppingIterator(Function predicate, Iterator asIterator) { - this.predicate = predicate; - rewindableIterator = new RewindableIterator<>(asIterator); - finishedDropping = false; - } - - @Override - public boolean hasNext() { - dropElementsIfNecessary(); - return rewindableIterator.hasNext(); - } - - @Override - public A next() { - if (hasNext()) - return rewindableIterator.next(); - - throw new NoSuchElementException(); - } - - private void dropElementsIfNecessary() { - while (rewindableIterator.hasNext() && !finishedDropping) { - if (!predicate.apply(rewindableIterator.next())) { - rewindableIterator.rewind(); - finishedDropping = true; - } - } - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java deleted file mode 100644 index f329d146b..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jnape.palatable.lambda.iterators; - -import com.jnape.palatable.lambda.adt.hlist.Tuple2; - -import java.util.NoSuchElementException; -import java.util.Optional; -import java.util.function.Function; - -public class UnfoldingIterator extends ImmutableIterator { - private final Function>> function; - private Optional> optionalAcc; - - public UnfoldingIterator(Function>> function, B b) { - this.function = function; - optionalAcc = function.apply(b); - } - - @Override - public boolean hasNext() { - return optionalAcc.isPresent(); - } - - @Override - @SuppressWarnings("OptionalGetWithoutIsPresent") - public A next() { - if (!hasNext()) - throw new NoSuchElementException(); - - Tuple2 acc = optionalAcc.get(); - A next = acc._1(); - optionalAcc = function.apply(acc._2()); - return next; - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ZippingIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/ZippingIterator.java deleted file mode 100644 index 0372e6fff..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ZippingIterator.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.jnape.palatable.lambda.iterators; - -import java.util.Iterator; -import java.util.function.BiFunction; - -public class ZippingIterator extends ImmutableIterator { - private final BiFunction zipper; - private final Iterator asIterator; - private final Iterator bsIterator; - - public ZippingIterator(BiFunction zipper, Iterator asIterator, - Iterator bsIterator) { - this.asIterator = asIterator; - this.bsIterator = bsIterator; - this.zipper = zipper; - } - - @Override - public boolean hasNext() { - return asIterator.hasNext() && bsIterator.hasNext(); - } - - @Override - public C next() { - return zipper.apply(asIterator.next(), bsIterator.next()); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java deleted file mode 100644 index 62b24ccfb..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ /dev/null @@ -1,317 +0,0 @@ -package com.jnape.palatable.lambda.lens; - -import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.functor.Functor; - -import java.util.function.BiFunction; -import java.util.function.Function; - -import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; -import static com.jnape.palatable.lambda.lens.functions.Over.over; -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; - -/** - * An approximation of van Laarhoven lenses. - *

- * A "lens" can be considered in its simplest form as the conjugation of a "getter" and a "setter"; that is, a - * unification type representing the way to retrieve a "smaller" value A from a "larger" value - * S, as well as a way to update a "smaller" value B of a "larger" value S, - * producing another "larger" value T. - *

- * Consider the following example: - *

- * {@code
- * public final class Person {
- *     private final int age;
- *
- *     public Person(int age) {
- *         this.age = age;
- *     }
- *
- *     public int getAge() {
- *         return age;
- *     }
- *
- *     public Person setAge(int age) {
- *         return new Person(age);
- *     }
- * }
- * }
- * 
- * A lens that focused on the age field of an instance of Person might look like this: - *
- * {@code
- * Lens ageLens = Lens.lens(Person::getAge, Person::setAge);
- *
- * Person adult = new Person(18);
- * Integer age = view(ageLens, adult); // 18
- *
- * Person olderAdult = set(ageLens, 19, adult);
- * Integer olderAge = view(ageLens, olderAdult); // 19
- * }
- * 
- * The pattern of a getter and setter that mutually agree on both A and B as well as on both - * S and T is so common that this can be given a simplified type signature: - *
- * {@code
- * Lens.Simple ageLens = Lens.simpleLens(Person::getAge, Person::setAge);
- *
- * Person adult = new Person(18);
- * Integer age = view(ageLens, adult); // 18
- *
- * Person olderAdult = set(ageLens, 19, adult);
- * Integer olderAge = view(ageLens, olderAdult); // 19
- * }
- * 
- * However, consider if age could be updated on a Person by being provided a date of birth, in - * the form of a LocalDate: - *
- * {@code
- * public final class Person {
- *     private final int age;
- *
- *     public Person(int age) {
- *         this.age = age;
- *     }
- *
- *     public int getAge() {
- *         return age;
- *     }
- *
- *     public Person setAge(int age) {
- *         return new Person(age);
- *     }
- *
- *     public Person setAge(LocalDate dob) {
- *         return setAge((int) YEARS.between(dob, LocalDate.now()));
- *     }
- * }
- * }
- * 
- * This is why Lens has both an A and a B: A is the value for "getting", and - * B is the potentially different value for "setting". This distinction makes lenses powerful enough to - * express the more complicated setAge case naturally: - *
- * {@code
- * Lens ageDobLens = Lens.lens(Person::getAge, Person::setAge);
- *
- * Person adult = new Person(18);
- * Integer age = view(ageDobLens, adult); // 18
- *
- * Person olderAdult = set(ageDobLens, LocalDate.of(1997, 1, 1), adult);
- * Integer olderAge = view(ageDobLens, olderAdult); // 19 at the time of this writing...anyone else feel old?
- * }
- * 
- * Additionally, we might imagine a lens that produces a different "larger" value on updating than what was given. - * Consider a lens that reads the first string from a list, but produces a Set of strings on update: - *
- * {@code
- * Lens, Set, String, String> lens = Lens.lens(
- *         l -> l.get(0),
- *         (l, s) -> {
- *             List copy = new ArrayList<>(l);
- *             copy.set(0, s);
- *             return new HashSet<>(copy);
- *         });
- *
- * String firstElement = view(lens, asList("foo", "bar")); // "foo
- * System.out.println(firstElement);
- *
- * set(lens, "oof", asList("foo", "bar")); // ["bar", "oof"]
- * set(lens, "bar", asList("foo", "bar")); // ["bar"]
- * }
- * 
- * For more information, learn - * about - * lenses. - * - * @param the type of the "larger" value for reading - * @param the type of the "larger" value for putting - * @param the type of the "smaller" value that is read - * @param the type of the "smaller" update value - */ -@FunctionalInterface -public interface Lens extends Functor { - - , FB extends Functor> FT apply(Function fn, S s); - - /** - * Fix this lens against some functor, producing a non-polymorphic runnable lens as an {@link Fn2}. - *

- * Although the Java type system does not allow enforceability, the functor instance FT should be the same as FB, - * only differentiating in their parameters. - * - * @param the type of the lifted T - * @param the type of the lifted B - * @return the lens, "fixed" to the functor - */ - default , FB extends Functor> Fixed fix() { - return this::apply; - } - - @Override - default Lens fmap(Function fn) { - return compose(Lens.lens(id(), (s, t) -> fn.apply(t))); - } - - /** - * Contravariantly map S to R, yielding a new lens. - * - * @param fn the mapping function - * @param the type of the new "larger" value for reading - * @return the new lens - */ - default Lens mapS(Function fn) { - return compose(lens(fn, (r, t) -> t)); - } - - /** - * Covariantly map T to U, yielding a new lens. - * - * @param fn the mapping function - * @param the type of the new "larger" value for putting - * @return the new lens - */ - default Lens mapT(Function fn) { - return fmap(fn); - } - - /** - * Covariantly map A to C, yielding a new lens. - * - * @param fn the mapping function - * @param the type of the new "smaller" value that is read - * @return the new lens - */ - default Lens mapA(Function fn) { - return andThen(lens(fn, (a, b) -> b)); - } - - /** - * Contravariantly map B to Z, yielding a new lens. - * - * @param fn the mapping function - * @param the type of the new "smaller" update value - * @return the new lens - */ - default Lens mapB(Function fn) { - return andThen(lens(id(), (a, z) -> fn.apply(z))); - } - - /** - * Left-to-right composition of lenses. Requires compatibility between S and T. - * - * @param f the other lens - * @param the new "smaller" value to read (previously A) - * @param the new "smaller" update value (previously B) - * @return the composed lens - */ - default Lens andThen(Lens f) { - return f.compose(this); - } - - /** - * Right-to-left composition of lenses. Requires compatibility between A and B. - * - * @param g the other lens - * @param the new "larger" value for reading (previously S) - * @param the new "larger" value for putting (previously T) - * @return the composed lens - */ - default Lens compose(Lens g) { - return lens(view(g).fmap(view(this)), (q, b) -> over(g, set(this, b), q)); - } - - /** - * Static factory method for creating a lens from a getter function and a setter function. - * - * @param getter the getter function - * @param setter the setter function - * @param the type of the "larger" value for reading - * @param the type of the "larger" value for putting - * @param the type of the "smaller" value that is read - * @param the type of the "smaller" update value - * @return the lens - */ - static Lens lens(Function getter, - BiFunction setter) { - return new Lens() { - @Override - @SuppressWarnings("unchecked") - public , FB extends Functor> FT apply(Function fn, - S s) { - return (FT) fn.apply(getter.apply(s)).fmap(b -> setter.apply(s, b)); - } - }; - } - - /** - * Static factory method for creating a simple lens from a getter function and a setter function. - * - * @param getter the getter function - * @param setter the setter function - * @param the type of both "larger" values - * @param the type of both "smaller" values - * @return the lens - */ - @SuppressWarnings("unchecked") - static Lens.Simple simpleLens(Function getter, - BiFunction setter) { - return lens(getter, setter)::apply; - } - - /** - * A convenience type with a simplified type signature for common lenses with both unified "larger" values and - * unified "smaller" values. - * - * @param the type of both "larger" values - * @param the type of both "smaller" values - */ - @FunctionalInterface - interface Simple extends Lens { - - @Override - default , FA extends Functor> Fixed fix() { - return this::apply; - } - - @SuppressWarnings("unchecked") - default Lens.Simple compose(Lens.Simple g) { - return Lens.super.compose(g)::apply; - } - - default Lens.Simple andThen(Lens.Simple f) { - return f.compose(this); - } - - /** - * A convenience type with a simplified type signature for fixed simple lenses. - * - * @param the type of both "larger" values - * @param the type of both "smaller" values - * @param the type of the lifted s - * @param the type of the lifted A - */ - @FunctionalInterface - interface Fixed, FA extends Functor> - extends Lens.Fixed { - } - } - - /** - * A lens that has been fixed to a functor. Because the lens is no longer polymorphic, it can additionally be safely - * represented as an Fn2. - * - * @param the type of the "larger" value for reading - * @param the type of the "larger" value for putting - * @param the type of the "smaller" value that is read - * @param the type of the "smaller" update value - * @param the type of the lifted T - * @param the type of the lifted B - */ - @FunctionalInterface - interface Fixed, FB extends Functor> - extends Fn2, S, FT> { - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java deleted file mode 100644 index e9c4aadd6..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.jnape.palatable.lambda.lens.functions; - -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.functions.Fn3; -import com.jnape.palatable.lambda.functor.builtin.Identity; -import com.jnape.palatable.lambda.lens.Lens; - -import java.util.function.Function; - -/** - * Given a lens, a function from A to B, and a "larger" value S, produce a - * T by retrieving the A from the S, applying the function, and updating the - * S with the B resulting from the function. - *

- * This function is similar to {@link Set}, except that it allows the setting value B to be derived from - * S via function application, rather than provided. - * - * @param the type of the larger value - * @param the type of the larger updated value - * @param the type of the smaller retrieving value - * @param the type of the smaller setting value - * @see Set - * @see View - */ -public final class Over implements Fn3, Function, S, T> { - - private static final Over INSTANCE = new Over(); - - private Over() { - } - - @Override - public T apply(Lens lens, Function fn, S s) { - return lens., Identity>fix() - .apply(fn.andThen((Function>) Identity::new), s) - .runIdentity(); - } - - @SuppressWarnings("unchecked") - public static Over over() { - return (Over) INSTANCE; - } - - public static Fn2, S, T> over( - Lens lens) { - return Over.over().apply(lens); - } - - public static Fn1 over(Lens lens, - Function fn) { - return over(lens).apply(fn); - } - - public static T over(Lens lens, Function fn, S s) { - return over(lens, fn).apply(s); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java deleted file mode 100644 index b3c5ff243..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.jnape.palatable.lambda.lens.functions; - -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.functions.Fn3; -import com.jnape.palatable.lambda.functor.builtin.Identity; -import com.jnape.palatable.lambda.lens.Lens; - -import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; -import static com.jnape.palatable.lambda.lens.functions.Over.over; - -/** - * Given a lens, a "smaller" value B, and a "larger" value S, produce a T by - * lifting the lens into {@link Identity}. - *

- * More idiomatically, this function can be used to treat a lens as a "setter" of Bs on Ss, - * potentially producing a different "larger" value, T. - * - * @param the type of the larger value - * @param the type of the larger updated value - * @param the type of the smaller retrieving value (unused, but necessary for composition) - * @param the type of the smaller setting value - * @see Over - * @see View - */ -public final class Set implements Fn3, B, S, T> { - - private static final Set INSTANCE = new Set(); - - private Set() { - } - - @Override - public T apply(Lens lens, B b, S s) { - return over(lens, constantly(b), s); - } - - @SuppressWarnings("unchecked") - public static Set set() { - return INSTANCE; - } - - public static Fn2 set(Lens lens) { - return Set.set().apply(lens); - } - - public static Fn1 set(Lens lens, B b) { - return set(lens).apply(b); - } - - public static T set(Lens lens, B b, S s) { - return set(lens, b).apply(s); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java deleted file mode 100644 index 8cab0c403..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jnape.palatable.lambda.lens.functions; - -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.functor.builtin.Const; -import com.jnape.palatable.lambda.lens.Lens; - -/** - * Given a lens and a "larger" value S, retrieve a "smaller" value A by lifting the lens into - * {@link Const}. - *

- * More idiomatically, this function can be used to treat a lens as a "getter" of As from Ss. - * - * @param the type of the larger value - * @param the type of the larger updated value (unused, but necessary for composition) - * @param the type of the smaller retrieving value - * @param the type of the smaller setting value (unused, but necessary for composition) - * @see Set - * @see Over - */ -public final class View implements Fn2, S, A> { - - private static final View INSTANCE = new View(); - - private View() { - } - - @Override - public A apply(Lens lens, S s) { - return lens., Const>fix().apply(Const::new, s).runConst(); - } - - @SuppressWarnings("unchecked") - public static View view() { - return INSTANCE; - } - - public static Fn1 view(Lens lens) { - return View.view().apply(lens); - } - - public static A view(Lens lens, S s) { - return view(lens).apply(s); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java deleted file mode 100644 index 8652259ec..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses; - -import com.jnape.palatable.lambda.adt.Either; -import com.jnape.palatable.lambda.lens.Lens; - -import java.util.Optional; - -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; - -/** - * Lenses that operate on {@link Either}s. - */ -public final class EitherLens { - - private EitherLens() { - } - - /** - * Convenience static factory method for creating a lens over right values, wrapping them in an {@link Optional}. - * When setting, an empty Optional value means to leave the either unaltered, where as a present Optional value - * replaces the either with a right over the wrapped Optional value. - * - * @param the left parameter type - * @param the right parameter type - * @return a lens that focuses on right values - */ - public static Lens.Simple, Optional> right() { - return simpleLens(Either::toOptional, (lOrR, optR) -> optR.>map(Either::right).orElse(lOrR)); - } - - /** - * Convenience static factory method for creating a lens over left values, wrapping them in an {@link Optional}. - * When setting, an empty Optional value means to leave the either unaltered, where as a present Optional value - * replaces the either with a left over the wrapped Optional value. - * - * @param the left parameter type - * @param the right parameter type - * @return a lens that focuses on left values - */ - public static Lens.Simple, Optional> left() { - return simpleLens(e -> e.match(Optional::ofNullable, __ -> Optional.empty()), - (lOrR, optL) -> optL.>map(Either::left).orElse(lOrR)); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java deleted file mode 100644 index 31f20dab1..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses; - -import com.jnape.palatable.lambda.lens.Lens; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.unLiftA; - -/** - * Lenses that operate on {@link List}s. - */ -public final class ListLens { - - private ListLens() { - } - - /** - * Convenience static factory method for creating a lens over a copy of a list. Useful for composition to avoid - * mutating a list reference. - * - * @param the list element type - * @return a lens that focuses on copies of lists - */ - public static Lens.Simple, List> asCopy() { - return simpleLens(ArrayList::new, (xs, ys) -> ys); - } - - /** - * Convenience static factory method for creating a lens that focuses on an element in a list at a particular index. - * Wraps result in an Optional to handle null values or indexes that fall outside of list boundaries. - * - * @param index the index to focus on - * @param the list element type - * @return an Optional wrapping the element at the index - */ - public static Lens, List, Optional, X> elementAt(int index) { - return lens(xs -> Optional.ofNullable(xs.size() > index ? xs.get(index) : null), - (xs, x) -> { - if (xs.size() > index) - xs.set(index, x); - return xs; - }); - } - - /** - * Convenience static factory method for creating a lens that focuses on an element in a list at a particular index, - * returning defaultValue if there is no value at that index. - * - * @param index the index to focus on - * @param defaultValue the value to use if there is no element at index - * @param the list element type - * @return the element at the index, or defaultValue - */ - @SuppressWarnings("unchecked") - public static Lens.Simple, X> elementAt(int index, X defaultValue) { - return unLiftA(elementAt(index), defaultValue)::apply; - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java deleted file mode 100644 index 8cb9ff4dc..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses; - -import com.jnape.palatable.lambda.functions.builtin.fn2.Filter; -import com.jnape.palatable.lambda.lens.Lens; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; -import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.unLiftA; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; - -/** - * Lenses that operate on {@link Map}s. - */ -public final class MapLens { - - private MapLens() { - } - - /** - * A lens that focuses on a copy of a Map. Useful for composition to avoid mutating a map reference. - * - * @param the key type - * @param the value type - * @return a lens that focuses on copies of maps - */ - public static Lens.Simple, Map> asCopy() { - return simpleLens(HashMap::new, (__, copy) -> copy); - } - - /** - * A lens that focuses on a value at a key in a map, as an {@link Optional}. - * - * @param k the key to focus on - * @param the key type - * @param the value type - * @return a lens that focuses on the value at key, as an {@link Optional} - */ - public static Lens, Map, Optional, V> valueAt(K k) { - return lens(m -> Optional.ofNullable(m.get(k)), (m, v) -> { - m.put(k, v); - return m; - }); - } - - /** - * A lens that focuses on a value at a key in a map, falling back to defaultV if the value is missing. - * - * @param k the key to focus on - * @param defaultValue the default value to use in case of a missing value at key - * @param the key type - * @param the value type - * @return a lens that focuses on the value at the key - */ - @SuppressWarnings("unchecked") - public static Lens.Simple, V> valueAt(K k, V defaultValue) { - return unLiftA(valueAt(k), defaultValue)::apply; - } - - /** - * A lens that focuses on the keys of a map. - * - * @param the key type - * @param the value type - * @return a lens that focuses on the keys of a map - */ - public static Lens.Simple, Set> keys() { - return simpleLens(Map::keySet, (m, ks) -> { - Set keys = m.keySet(); - keys.retainAll(ks); - ks.removeAll(keys); - ks.forEach(k -> m.put(k, null)); - return m; - }); - } - - /** - * A lens that focuses on the values of a map. In the case of updating the map, only the entries with a value listed - * in the update collection of values are kept. - * - * @param the key type - * @param the value type - * @return a lens that focuses on the values of a map - */ - public static Lens.Simple, Collection> values() { - return simpleLens(Map::values, (m, vs) -> { - Set valueSet = new HashSet<>(vs); - Set matchingKeys = m.entrySet().stream() - .filter(kv -> valueSet.contains(kv.getValue())) - .map(Map.Entry::getKey) - .collect(toSet()); - m.keySet().retainAll(matchingKeys); - return m; - }); - } - - /** - * A lens that focuses on the inverse of a map (keys and values swapped). In the case of multiple equal values - * becoming keys, the last one wins. - * - * @param the key type - * @param the value type - * @return a lens that focuses on the inverse of a map - */ - public static Lens.Simple, Map> inverted() { - return simpleLens(m -> { - Map inverted = new HashMap<>(); - m.entrySet().forEach(entry -> inverted.put(entry.getValue(), entry.getKey())); - return inverted; - }, (m, im) -> { - m.clear(); - m.putAll(view(inverted(), im)); - return m; - }); - } - - /** - * A lens that focuses on a map while mapping its values with the mapping function. - * - * @param fn the mapping function - * @param the key type - * @param the unfocused map value type - * @param the focused map value type - * @return a lens that focuses on a map while mapping its values - */ - public static Lens.Simple, Map> mappingValues(Function fn) { - return Lens.simpleLens(m -> m.entrySet().stream().collect(toMap(Map.Entry::getKey, kv -> fn.apply(kv.getValue()))), - (s, b) -> { - //todo: remove this madness upon arrival of either invertible functions or Iso - Set retainKeys = Filter.>filter(kv -> eq(fn.apply(kv.getValue()), b.get(kv.getKey()))) - .andThen(map(Map.Entry::getKey)) - .andThen(toCollection(HashSet::new)) - .apply(s.entrySet()); - Map copy = new HashMap<>(s); - copy.keySet().retainAll(retainKeys); - return copy; - }); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java new file mode 100644 index 000000000..1c5645905 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java @@ -0,0 +1,99 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Id; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * Monads are {@link Applicative} functors that support a flattening operation to unwrap M<M<A>> + * -> M<A>. This flattening operation, coupled with {@link Applicative#zip(Applicative)}, gives rise to + * {@link Monad#flatMap(Fn1)}, a binding operation that maps the carrier value to a new monad instance in the same + * category, and then unwraps the outer layer. + *

+ * In addition to the applicative laws, there are 3 specific monad laws that monads should obey: + *

+ *

+ * For more information, read about + * Monads. + * + * @param the type of the parameter + * @param the unification parameter to more tightly type-constrain Monads to themselves + */ +public interface Monad> extends Applicative { + + /** + * Chain dependent computations that may continue or short-circuit based on previous results. + * + * @param f the dependent computation over A + * @param the resulting monad parameter type + * @return the new monad instance + */ + Monad flatMap(Fn1> f); + + /** + * {@inheritDoc} + */ + @Override + Monad pure(B b); + + /** + * {@inheritDoc} + */ + @Override + default Monad fmap(Fn1 fn) { + return flatMap(fn.fmap(this::pure)); + } + + /** + * {@inheritDoc} + */ + @Override + default Monad zip(Applicative, M> appFn) { + return flatMap(a -> appFn., M>>coerce().fmap(f -> f.apply(a))); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip( + Lazy, M>> lazyAppFn) { + return Applicative.super.lazyZip(lazyAppFn).fmap(Applicative::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + default Monad discardL(Applicative appB) { + return Applicative.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Monad discardR(Applicative appB) { + return Applicative.super.discardR(appB).coerce(); + } + + /** + * Convenience static method equivalent to {@link Monad#flatMap(Fn1) flatMap}{@link Id#id() (id())}; + * + * @param mma the outer monad + * @param the monad type + * @param the nested type parameter + * @param the nested monad + * @return the nested monad + */ + static , A, MA extends Monad> MA join(Monad mma) { + return mma.flatMap(id()).coerce(); + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/monad/MonadBase.java b/src/main/java/com/jnape/palatable/lambda/monad/MonadBase.java new file mode 100644 index 000000000..36798e8eb --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/MonadBase.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.monad; + +/** + * A type into which a {@link MonadRec} is embedded whilst internally preserving the {@link MonadRec} structure. + * + * @param the {@link MonadRec} embedded in this {@link MonadBase} + * @param the carrier type + * @param the witness + */ +@SuppressWarnings("unused") +public interface MonadBase, A, MB extends MonadBase> { + + /** + * Lift a new argument {@link MonadRec} into this {@link MonadBase}. + * + * @param nc the argument {@link MonadRec} + * @param the {@link MonadRec} carrier type + * @param the argument {@link MonadRec} witness + * @return the new {@link MonadBase} + */ + > MonadBase lift(MonadRec nc); +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/MonadError.java b/src/main/java/com/jnape/palatable/lambda/monad/MonadError.java new file mode 100644 index 000000000..bcecdcad8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/MonadError.java @@ -0,0 +1,96 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Try; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.io.IO; + +/** + * An interface for {@link Monad monads} that can be interrupted with some type of error. The type of error is fully + * dictated by the instance of {@link MonadError} and is not necessarily analogous to Java {@link Exception exceptions} + * or even {@link Throwable}. For instance, {@link IO} can be thrown any {@link Throwable}, where as {@link Either} can + * only be "thrown" a value of its {@link Either#left(Object) left} type. + * + * @param the error type + * @param the {@link Monad} witness + * @param the carrier + * @see IO + * @see Either + * @see Try + * @see Maybe + */ +public interface MonadError> extends Monad { + + /** + * Throw an error value of type E into the {@link Monad monad}. + * + * @param e the error type + * @return the {@link Monad monad} + */ + MonadError throwError(E e); + + /** + * Catch any {@link MonadError#throwError(Object) thrown} errors inside the {@link Monad} and resume normal + * operations. + * + * @param recoveryFn the catch function + * @return the recovered {@link Monad} + */ + MonadError catchError(Fn1> recoveryFn); + + /** + * {@inheritDoc} + */ + @Override + MonadError flatMap(Fn1> f); + + /** + * {@inheritDoc} + */ + @Override + MonadError pure(B b); + + /** + * {@inheritDoc} + */ + @Override + default MonadError fmap(Fn1 fn) { + return Monad.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadError zip(Applicative, M> appFn) { + return Monad.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip( + Lazy, M>> lazyAppFn) { + return Monad.super.lazyZip(lazyAppFn).fmap(Monad::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadError discardL(Applicative appB) { + return Monad.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadError discardR(Applicative appB) { + return Monad.super.discardR(appB).coerce(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/MonadReader.java b/src/main/java/com/jnape/palatable/lambda/monad/MonadReader.java new file mode 100644 index 000000000..17fd1eddd --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/MonadReader.java @@ -0,0 +1,83 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Contravariant; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; + +/** + * A monad that is capable of reading an environment R and producing a lifted value A. This + * is strictly less powerful than an {@link Fn1}, loosening the requirement on {@link Contravariant} (and therefore + * {@link Profunctor} constraints), so is more generally applicable, offering instead a {@link MonadReader#local(Fn1)} + * mechanism for modifying the environment *after* reading it but before running the effect (as opposed to + * {@link Contravariant} functors which may modify their inputs *before* running their effects, and may therefore alter + * the input types). + * + * @param the environment + * @param the output + * @param the witness + */ +public interface MonadReader> extends Monad { + + /** + * Modify this {@link MonadReader MonadReader's} environment after reading it but before running the effect. + * + * @param fn the modification function + * @return the {@link MonadReader} with a modified environment + */ + MonadReader local(Fn1 fn); + + /** + * {@inheritDoc} + */ + @Override + MonadReader flatMap(Fn1> f); + + /** + * {@inheritDoc} + */ + @Override + MonadReader pure(B b); + + /** + * {@inheritDoc} + */ + @Override + default MonadReader fmap(Fn1 fn) { + return Monad.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadReader zip(Applicative, MR> appFn) { + return Monad.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip( + Lazy, MR>> lazyAppFn) { + return Monad.super.lazyZip(lazyAppFn).fmap(Applicative::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadReader discardL(Applicative appB) { + return Monad.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadReader discardR(Applicative appB) { + return Monad.super.discardR(appB).coerce(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/MonadRec.java b/src/main/java/com/jnape/palatable/lambda/monad/MonadRec.java new file mode 100644 index 000000000..b55fb5aa8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/MonadRec.java @@ -0,0 +1,95 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.transformer.MonadT; +import com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT; + +/** + * A class of {@link Monad monads} that offer a stack-safe interface for performing arbitrarily many + * {@link Monad#flatMap(Fn1) flatmap-like} operations via {@link MonadRec#trampolineM(Fn1)}. + *

+ * Inspired by Phil Freeman's paper + * _Stack Safety for Free_ + * + * @param the carrier type + * @param the {@link MonadRec witness} + */ +public interface MonadRec> extends Monad { + + /** + * Given some operation yielding a {@link RecursiveResult} inside this {@link MonadRec}, internally trampoline the + * operation until it yields a {@link RecursiveResult#terminate(Object) termination} instruction. + *

+ * Stack-safety depends on implementations guaranteeing that the growth of the call stack is a constant factor + * independent of the number of invocations of the operation. For various examples of how this can be achieved in + * stereotypical circumstances, see the referenced types. + * + * @param fn the function to internally trampoline + * @param the ultimate resulting carrier type + * @return the trampolined {@link MonadRec} + * @see Identity#trampolineM(Fn1) for a basic implementation + * @see Maybe#trampolineM(Fn1) for a {@link CoProduct2 coproduct} implementation + * @see Lazy#trampolineM(Fn1) for an implementation leveraging an already stack-safe {@link Monad#flatMap(Fn1)} + * @see MaybeT#trampolineM(Fn1) for a {@link MonadT monad transformer} implementation + */ + MonadRec trampolineM(Fn1, M>> fn); + + /** + * {@inheritDoc} + */ + @Override + MonadRec flatMap(Fn1> f); + + /** + * {@inheritDoc} + */ + @Override + MonadRec pure(B b); + + /** + * {@inheritDoc} + */ + @Override + default MonadRec fmap(Fn1 fn) { + return Monad.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadRec zip(Applicative, M> appFn) { + return Monad.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip( + Lazy, M>> lazyAppFn) { + return Monad.super.lazyZip(lazyAppFn).fmap(Applicative::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadRec discardL(Applicative appB) { + return Monad.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadRec discardR(Applicative appB) { + return Monad.super.discardR(appB).coerce(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/MonadWriter.java b/src/main/java/com/jnape/palatable/lambda/monad/MonadWriter.java new file mode 100644 index 000000000..906d13866 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/MonadWriter.java @@ -0,0 +1,87 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; + +/** + * A {@link Monad} that is capable of writing and accumulating state alongside a value, but is not necessarily capable + * of simultaneously accessing the state and the value. + * + * @param the accumulation type + * @param the output type + * @param the witness + */ +public interface MonadWriter> extends Monad { + + /** + * Map the accumulation into a value and pair it with the current output. + * + * @param fn the mapping function + * @param the mapped output + * @return the updated {@link MonadWriter} + */ + MonadWriter, MW> listens(Fn1 fn); + + /** + * Update the accumulated state. + * + * @param fn the update function + * @return the updated {@link MonadWriter} + */ + MonadWriter censor(Fn1 fn); + + /** + * {@inheritDoc} + */ + @Override + MonadWriter flatMap(Fn1> f); + + /** + * {@inheritDoc} + */ + @Override + MonadWriter pure(B b); + + /** + * {@inheritDoc} + */ + @Override + default MonadWriter fmap(Fn1 fn) { + return Monad.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadWriter zip(Applicative, MW> appFn) { + return Monad.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip( + Lazy, MW>> lazyAppFn) { + return Monad.super.lazyZip(lazyAppFn).fmap(Monad::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadWriter discardL(Applicative appB) { + return Monad.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadWriter discardR(Applicative appB) { + return Monad.super.discardR(appB).coerce(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/SafeT.java b/src/main/java/com/jnape/palatable/lambda/monad/SafeT.java new file mode 100644 index 000000000..6266cdfef --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/SafeT.java @@ -0,0 +1,291 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.recursion.Trampoline; +import com.jnape.palatable.lambda.functions.specialized.Lift; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.transformer.MonadT; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; + +/** + * A stack-safe {@link MonadT monad transformer} that can safely interpret deeply nested left- or right-associated + * binds for any {@link MonadRec}. + *

+ * Example: + *

+ * {@code
+ * Times.>times(100_000, f -> f.fmap(x -> x + 1), id()).apply(0); // stack-overflow
+ * Times., Integer>>times(100_000, f -> f.fmap(x -> x + 1), safeT(id()))
+ *         .>runSafeT()
+ *         .apply(0); // 100_000
+ * }
+ * 
+ *

+ * Inspired by Phil Freeman's paper + * Stack Safety for Free. + * + * @param the {@link MonadRec} instance + * @param the carrier type + */ +public final class SafeT, A> implements + MonadT, SafeT> { + + private final Body body; + private final Pure pureM; + + private SafeT(Body body, Pure pureM) { + this.body = body; + this.pureM = pureM; + } + + /** + * Recover the full structure of the embedded {@link Monad} in a stack-safe way. + * + * @param the witnessed target type + * @return the embedded {@link Monad} + */ + public > MA runSafeT() { + return body.resume().match( + fFree -> fFree.trampolineM(freeF -> freeF.resume().match( + monadRec -> monadRec.fmap(RecursiveResult::recurse), + a -> pureM.>apply(a).fmap(RecursiveResult::terminate))).coerce(), + pureM::apply); + } + + /** + * {@inheritDoc} + */ + @Override + public SafeT fmap(Fn1 fn) { + return MonadT.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public > SafeT lift(MonadRec nb) { + return liftSafeT().apply(nb); + } + + /** + * {@inheritDoc} + */ + @Override + public SafeT flatMap(Fn1>> f) { + return new SafeT<>(Body.suspend(body, a -> f.apply(a).>coerce().body), pureM); + } + + /** + * {@inheritDoc} + */ + @Override + public SafeT zip(Applicative, SafeT> appFn) { + return MonadT.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, SafeT>> lazyAppFn) { + return MonadT.super.lazyZip(lazyAppFn).fmap(Applicative>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public SafeT discardL(Applicative> appB) { + return MonadT.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public SafeT discardR(Applicative> appB) { + return MonadT.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public SafeT pure(B b) { + return pureSafeT(pureM).apply(b); + } + + /** + * {@inheritDoc} + */ + @Override + public SafeT trampolineM(Fn1, SafeT>> bounce) { + return flatMap(bounce.fmap(mab -> mab.flatMap(aOrB -> aOrB + .match(a -> mab.pure(a).trampolineM(bounce), Pure.of(mab)::apply)))); + } + + /** + * Lift any {@link MonadRec MonadRec}<A, M> into a {@link SafeT SafeT}<M, A>. + * + * @param ma the {@link MonadRec MonadRec}<A, M> + * @param the {@link MonadRec} witness + * @param the carrier type + * @return the new {@link SafeT} + */ + public static , A> SafeT safeT(MonadRec ma) { + return new SafeT<>(Body.more(ma.fmap(Body::done)), Pure.of(ma)); + } + + /** + * The canonical {@link Pure} instance for {@link SafeT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the argument {@link Monad} witness + * @return the {@link Pure} instance + */ + public static > Pure> pureSafeT(Pure pureM) { + return new Pure>() { + @Override + public SafeT checkedApply(A a) throws Throwable { + return safeT(pureM.>apply(a)); + } + }; + } + + /** + * {@link Lift} for {@link SafeT}. + * + * @return the {@link Monad} lifted into {@link SafeT} + */ + public static Lift> liftSafeT() { + return SafeT::safeT; + } + + private abstract static class Body, A> implements + CoProduct2, M>, A>, Body.Suspended, Body> { + + private Body() { + } + + public abstract Either, M>, A> resume(); + + private static , A> Body done(A a) { + return new Done<>(a); + } + + private static , A> Body more(MonadRec, M> mb) { + return new More<>(mb); + } + + private static , A, B> Body suspend(Body freeA, Fn1> fn) { + return new SafeT.Body.Suspended<>(freeA, fn); + } + + private static final class Done, A> extends Body { + private final A a; + + private Done(A a) { + this.a = a; + } + + @Override + public R match(Fn1, M>, A>, ? extends R> aFn, + Fn1, ? extends R> bFn) { + return aFn.apply(right(a)); + } + + @Override + public Either, M>, A> resume() { + return right(a); + } + } + + private static final class More, A> extends Body { + private final MonadRec, M> mfa; + + private More(MonadRec, M> mfa) { + this.mfa = mfa; + } + + @Override + public R match(Fn1, M>, A>, ? extends R> aFn, + Fn1, ? extends R> bFn) { + return aFn.apply(left(mfa)); + } + + @Override + public Either, M>, A> resume() { + return left(mfa); + } + } + + private static final class Suspended, A, B> extends Body { + private final Body source; + private final Fn1> f; + + private Suspended(Body source, Fn1> f) { + this.source = source; + this.f = f; + } + + public Either, M>, B> resume() { + Φ, Either, M>, B>>> phi = + new Φ, Either, M>, B>>>() { + @Override + public RecursiveResult, Either, M>, B>> apply( + Body source, Fn1> f) { + return source.match( + e -> e.match(more -> terminate(left(more.fmap(body -> suspend(body, f)))), + z -> recurse(f.apply(z))), + associateRight(f)); + } + }; + return Trampoline., Either, M>, B>>trampoline( + free -> free.match(RecursiveResult::terminate, suspended -> suspended.eliminate(phi)), + this); + } + + @Override + public R match(Fn1, M>, B>, ? extends R> aFn, + Fn1, ? extends R> bFn) { + return bFn.apply(this); + } + + private Fn1, RecursiveResult, Either, M>, B>>> + associateRight(Fn1> f) { + Φ, Either, M>, B>>> phi = + new Φ, Either, M>, B>>>() { + @Override + public RecursiveResult, Either, M>, B>> apply( + Body source, + Fn1> g) { + return recurse(suspend(source, x -> suspend(g.apply(x), f))); + } + }; + + return suspended -> suspended.eliminate(phi); + } + + @SuppressWarnings("NonAsciiCharacters") + private R eliminate(Φ Φ) { + return Φ.apply(source, f); + } + + @SuppressWarnings("NonAsciiCharacters") + private interface Φ, B, R> { + R apply(Body source, Fn1> fn); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/MonadT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/MonadT.java new file mode 100644 index 000000000..71ed2b24b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/MonadT.java @@ -0,0 +1,104 @@ +package com.jnape.palatable.lambda.monad.transformer; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadBase; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.builtin.EitherT; +import com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT; +import com.jnape.palatable.lambda.monad.transformer.builtin.ReaderT; + +/** + * The generic type representing a {@link Monad} transformer, exposing the argument {@link Monad} as a type parameter. + *

+ * While any two {@link Functor functors} and any two {@link Applicative applicatives} can be composed in general, the + * same is not true in general of any two {@link Monad monads}. However, there exist {@link Monad monads} that do + * compose, in general, with any other {@link Monad}. When this is the case, the combination of these + * {@link Monad monads} with any other {@link Monad} can offer composed implementations of {@link Monad#pure pure} and + * {@link Monad#flatMap(Fn1) flatMap} for free, simply by relying on the other {@link Monad monad's} implementation of + * both, as well as their own privileged knowledge about how to merge the nested {@link Monad#flatMap(Fn1) flatMap} + * call. This can be thought of as "gluing" together two {@link Monad monads}, allowing easier access to their values, + * as well as, in some cases, providing universally correct constructions of the composed short-circuiting algorithms. + *

+ * The term "monad transformer" describes this particular encoding of monadic composition, and tends to be + * named in terms of {@link Monad} for which privileged knowledge must be known in order to eliminate during + * {@link Monad#flatMap(Fn1) flatmapping}. + *

+ * For more information, read more about + * monad transformers. + * + * @param the argument {@link Monad monad} + * @param the carrier type + * @param the {@link Monad} witness + * @param the {@link MonadT} witness + * @see Monad + * @see MonadBase + * @see MaybeT + * @see EitherT + * @see ReaderT + */ +public interface MonadT, A, MT extends MonadT, T extends MonadT> + extends MonadBase, Monad, MonadRec { + + /** + * {@inheritDoc} + */ + @Override + > MonadT lift(MonadRec mb); + + /** + * {@inheritDoc} + */ + @Override + MonadT flatMap(Fn1> f); + + /** + * {@inheritDoc} + */ + @Override + MonadT pure(B b); + + /** + * {@inheritDoc} + */ + @Override + default MonadT fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadT zip(Applicative, MT> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip( + Lazy, MT>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Applicative::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadT discardL(Applicative appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadT discardR(Applicative appB) { + return MonadRec.super.discardR(appB).coerce(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherT.java new file mode 100644 index 000000000..a23a81fcf --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherT.java @@ -0,0 +1,243 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Lift; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Compose; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadError; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.MonadT; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; + +/** + * A {@link MonadT monad transformer} for {@link Either}. + * + * @param the outer {@link Monad stack-safe monad} + * @param the left type + * @param the right type + */ +public final class EitherT, L, R> implements + Bifunctor>, + MonadT, EitherT>, + MonadError> { + + private final MonadRec, M> melr; + + private EitherT(MonadRec, M> melr) { + this.melr = melr; + } + + /** + * Recover the full structure of the embedded {@link Monad}. + * + * @param the witnessed target type + * @return the embedded {@link Monad} + */ + public , M>> MELR runEitherT() { + return melr.coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public > EitherT lift(MonadRec mb) { + return EitherT.liftEitherT().apply(mb); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT flatMap(Fn1>> f) { + return eitherT(melr.flatMap(lr -> lr.match(l -> melr.pure(left(l)), + r -> f.apply(r).>coerce().runEitherT()))); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT pure(R2 r2) { + return eitherT(melr.pure(right(r2))); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT fmap(Fn1 fn) { + return MonadT.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT zip( + Applicative, EitherT> appFn) { + return eitherT(new Compose<>(this., M>>runEitherT()).zip( + new Compose<>(appFn.>>coerce() + .>, M>>runEitherT())) + .getCompose()); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, EitherT>> lazyAppFn) { + return new Compose<>(melr) + .lazyZip(lazyAppFn.fmap(maybeT -> new Compose<>( + maybeT.>>coerce() + .>, M>>runEitherT()))) + .fmap(compose -> eitherT(compose.getCompose())); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT discardL(Applicative> appB) { + return MonadT.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT discardR(Applicative> appB) { + return MonadT.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT throwError(L l) { + return eitherT(melr.pure(left(l))); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT catchError(Fn1>> recoveryFn) { + return eitherT(runEitherT().flatMap(e -> e.match( + l -> recoveryFn.apply(l).>coerce().runEitherT(), + r -> melr.pure(r).fmap(Either::right)))); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT trampolineM( + Fn1, EitherT>> fn) { + return eitherT(runEitherT().trampolineM(lOrR -> lOrR + .match(l -> melr.pure(terminate(left(l))), + r -> fn.apply(r).>>coerce() + .runEitherT() + .fmap(lOrRR -> lOrRR.match(l -> terminate(left(l)), + rr -> rr.biMap(Either::right, Either::right)))))); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT biMap(Fn1 lFn, + Fn1 rFn) { + return eitherT(melr.fmap(e -> e.biMap(lFn, rFn))); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT biMapL(Fn1 fn) { + return (EitherT) Bifunctor.super.biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT biMapR(Fn1 fn) { + return (EitherT) Bifunctor.super.biMapR(fn); + } + + @Override + public boolean equals(Object other) { + return other instanceof EitherT && Objects.equals(melr, ((EitherT) other).melr); + } + + @Override + public int hashCode() { + return Objects.hash(melr); + } + + @Override + public String toString() { + return "EitherT{melr=" + melr + '}'; + } + + /** + * Static factory method for lifting a {@link Monad}<{@link Either}<L, R>, M> into an + * {@link EitherT}. + * + * @param melr the {@link Monad}<{@link Either}<L, R>, M> + * @param the outer {@link Monad} unification parameter + * @param the left type + * @param the right type + * @return the {@link EitherT} + */ + public static , L, R> EitherT eitherT(MonadRec, M> melr) { + return new EitherT<>(melr); + } + + /** + * The canonical {@link Pure} instance for {@link EitherT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the argument {@link Monad} witness + * @param the left type + * @return the {@link Pure} instance + */ + public static , L> Pure> pureEitherT(Pure pureM) { + return new Pure>() { + @Override + public EitherT checkedApply(R r) throws Throwable { + return eitherT(pureM.>apply(r).fmap(Either::right)); + } + }; + } + + /** + * {@link Lift} for {@link EitherT}. + * + * @param the left type + * @return the {@link Monad}lifted into {@link EitherT} + */ + public static Lift> liftEitherT() { + return new Lift>() { + @Override + public > EitherT checkedApply(MonadRec ga) { + return eitherT(ga.fmap(Either::right)); + } + }; + } + +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IdentityT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IdentityT.java new file mode 100644 index 000000000..88abc703a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IdentityT.java @@ -0,0 +1,187 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Lift; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Compose; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.MonadT; + +import java.util.Objects; + +/** + * A {@link MonadT monad transformer} for {@link Identity}. + * + * @param the outer {@link Monad stack-safe monad} + * @param the carrier type + */ +public final class IdentityT, A> implements + MonadT, IdentityT> { + + private final MonadRec, M> mia; + + private IdentityT(MonadRec, M> mia) { + this.mia = mia; + } + + /** + * Recover the full structure of the embedded {@link Monad}. + * + * @param the witnessed target type + * @return the embedded {@link Monad} + */ + public , M>> MIA runIdentityT() { + return mia.coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public > IdentityT lift(MonadRec mb) { + return liftIdentityT().apply(mb); + } + + /** + * {@inheritDoc} + */ + @Override + public IdentityT flatMap(Fn1>> f) { + return identityT(mia.flatMap(identityA -> f.apply(identityA.runIdentity()) + .>coerce() + .runIdentityT())); + } + + /** + * {@inheritDoc} + */ + @Override + public IdentityT trampolineM( + Fn1, IdentityT>> fn) { + return identityT(runIdentityT().fmap(Identity::runIdentity) + .trampolineM(a -> fn.apply(a) + .>>coerce() + .runIdentityT() + .fmap(Identity::runIdentity)) + .fmap(Identity::new)); + } + + /** + * {@inheritDoc} + */ + @Override + public IdentityT pure(B b) { + return identityT(mia.pure(new Identity<>(b))); + } + + /** + * {@inheritDoc} + */ + @Override + public IdentityT fmap(Fn1 fn) { + return MonadT.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public IdentityT zip(Applicative, IdentityT> appFn) { + return identityT(new Compose<>(this., M>>runIdentityT()).zip( + new Compose<>(appFn.>>coerce() + .>, M>>runIdentityT())) + .getCompose()); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, IdentityT>> lazyAppFn) { + return new Compose<>(mia) + .lazyZip(lazyAppFn.fmap(maybeT -> new Compose<>( + maybeT.>>coerce() + .>, M>>runIdentityT()))) + .fmap(compose -> identityT(compose.getCompose())); + } + + /** + * {@inheritDoc} + */ + @Override + public IdentityT discardL(Applicative> appB) { + return MonadT.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public IdentityT discardR(Applicative> appB) { + return MonadT.super.discardR(appB).coerce(); + } + + @Override + public boolean equals(Object other) { + return other instanceof IdentityT && Objects.equals(mia, ((IdentityT) other).mia); + } + + @Override + public int hashCode() { + return Objects.hash(mia); + } + + @Override + public String toString() { + return "IdentityT{mia=" + mia + '}'; + } + + /** + * Static factory method for lifting a {@link Monad}<{@link Identity}<A>, M> into a + * {@link IdentityT}. + * + * @param mia the {@link Monad}<{@link Identity}<A>, M> + * @param the outer {@link Monad} unification parameter + * @param the carrier type + * @return the new {@link IdentityT}. + */ + public static , A> IdentityT identityT(MonadRec, M> mia) { + return new IdentityT<>(mia); + } + + /** + * The canonical {@link Pure} instance for {@link IdentityT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the argument {@link Monad} witness + * @return the {@link Pure} instance + */ + public static > Pure> pureIdentityT(Pure pureM) { + return new Pure>() { + @Override + public IdentityT checkedApply(A a) { + return identityT(pureM.>apply(a).fmap(Identity::new)); + } + }; + } + + /** + * {@link Lift} for {@link IdentityT}. + * + * @return the {@link Monad} lifted into {@link IdentityT} + */ + public static Lift> liftIdentityT() { + return new Lift>() { + @Override + public > IdentityT checkedApply(MonadRec ga) { + return identityT(ga.fmap(Identity::new)); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java new file mode 100644 index 000000000..cb719060b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java @@ -0,0 +1,450 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Lift; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.internal.ImmutableQueue; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.MonadT; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.choice.Choice2.a; +import static com.jnape.palatable.lambda.adt.choice.Choice2.b; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn0.fn0; +import static com.jnape.palatable.lambda.functions.Fn1.withSelf; +import static com.jnape.palatable.lambda.functions.builtin.fn2.$.$; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.Monad.join; +import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.maybeT; +import static java.util.Arrays.asList; + +/** + * A {@link MonadT monad transformer} over a co-inductive, singly-linked spine of values embedded in effects. This is + * analogous to Haskell's ListT (done right). All append + * operations ({@link IterateT#cons(MonadRec) cons}, {@link IterateT#snoc(MonadRec) snoc}, etc.) are O(1) space/time + * complexity. + *

+ * Due to its singly-linked embedded design, {@link IterateT} is a canonical example of purely-functional streaming + * computation. For example, to lazily print all lines from a file descriptor, an initial implementation using + * {@link IterateT} might take the following form: + *


+ * String filePath = "/tmp/a_tale_of_two_cities.txt";
+ * IterateT<IO<?>, String> streamLines = IterateT.unfold(
+ *         reader -> io(() -> maybe(reader.readLine()).fmap(line -> tuple(line, reader))),
+ *         io(() -> Files.newBufferedReader(Paths.get(filePath))));
+ *
+ * // iterative read and print lines without retaining references
+ * IO<Unit> printLines = streamLines.forEach(line -> io(() -> System.out.println(line)));
+ * printLines.unsafePerformIO(); // prints "It was the best of times, it was the worst of times, [...]"
+ * 
+ * + * @param the effect type + * @param the element type + */ +public class IterateT, A> implements MonadT, IterateT> { + + private final Pure pureM; + private final ImmutableQueue>>, M>>, MonadRec>> spine; + + private IterateT(Pure pureM, + ImmutableQueue>>, M>>, MonadRec>> spine) { + this.pureM = pureM; + this.spine = spine; + } + + /** + * Recover the full structure of the embedded {@link Monad}. + * + * @param the witnessed target type + * @return the embedded {@link Monad} + */ + public >>, M>> MMTA runIterateT() { + return pureM., MonadRec, M>>apply(this) + .>>>trampolineM(iterateT -> iterateT.runStep() + .fmap(maybeMore -> maybeMore.match( + fn0(() -> terminate(nothing())), + t -> t.into((Maybe maybeA, IterateT as) -> maybeA.match( + fn0(() -> recurse(as)), + a -> terminate(just(tuple(a, as)))))))) + .coerce(); + } + + /** + * Run a single step of this {@link IterateT}, where a step is the smallest amount of work that could possibly be + * productive in advancing through the {@link IterateT}. Useful for implementing interleaving algorithms that + * require {@link IterateT IterateTs} to yield, emit, or terminate as soon as possible, regardless of whether the + * next element is readily available. + * + * @param the witnessed target type of the step + * @return the step + */ + public , IterateT>>, M>> MStep runStep() { + return spine.head().match( + fn0(() -> pureM., IterateT>>, MStep>apply(nothing())), + thunkOrReal -> thunkOrReal.match( + thunk -> thunk.apply()., IterateT>>>fmap(m -> m.match( + fn0(() -> just(tuple(nothing(), new IterateT<>(pureM, spine.tail())))), + t -> just(t.biMap(Maybe::just, + as -> new IterateT<>(pureM, as.spine.concat(spine.tail())))))) + .coerce(), + ma -> ma.fmap(a -> just(tuple(just(a), new IterateT<>(pureM, spine.tail())))).coerce())); + } + + /** + * Add an element inside an effect to the front of this {@link IterateT}. + * + * @param head the element + * @return the cons'ed {@link IterateT} + */ + public final IterateT cons(MonadRec head) { + return new IterateT<>(pureM, spine.pushFront(b(head))); + } + + /** + * Add an element inside an effect to the back of this {@link IterateT}. + * + * @param last the element + * @return the snoc'ed {@link IterateT} + */ + public final IterateT snoc(MonadRec last) { + return new IterateT<>(pureM, spine.pushBack(b(last))); + } + + /** + * Concat this {@link IterateT} in front of the other {@link IterateT}. + * + * @param other the other {@link IterateT} + * @return the concatenated {@link IterateT} + */ + public IterateT concat(IterateT other) { + return new IterateT<>(pureM, spine.concat(other.spine)); + } + + /** + * Monolithically fold the spine of this {@link IterateT} by {@link MonadRec#trampolineM(Fn1) trampolining} the + * underlying effects (for iterative folding, use {@link IterateT#trampolineM(Fn1) trampolineM} directly). + * + * @param fn the folding function + * @param acc the starting accumulation effect + * @param the accumulation type + * @param the witnessed target result type + * @return the folded effect result + */ + public > MB fold(Fn2> fn, + MonadRec acc) { + return foldCut((b, a) -> fn.apply(b, a).fmap(RecursiveResult::recurse), acc); + } + + /** + * Monolithically fold the spine of this {@link IterateT} (with the possibility of early termination) by + * {@link MonadRec#trampolineM(Fn1) trampolining} the underlying effects (for iterative folding, use + * {@link IterateT#trampolineM(Fn1) trampolineM} directly). + * + * @param fn the folding function + * @param acc the starting accumulation effect + * @param the accumulation type + * @param the witnessed target result type + * @return the folded effect result + */ + public > MB foldCut( + Fn2, M>> fn, + MonadRec acc) { + return acc.fmap(tupler(this)) + .trampolineM(into((as, b) -> maybeT(as.runIterateT()) + .flatMap(into((a, aas) -> maybeT(fn.apply(b, a).fmap(Maybe::just)).fmap(tupler(aas)))) + .runMaybeT() + .fmap(maybeR -> maybeR.match( + __ -> terminate(b), + into((rest, rr) -> rr.biMapL(tupler(rest))))))) + .coerce(); + } + + /** + * Convenience method for {@link IterateT#fold(Fn2, MonadRec) folding} the spine of this {@link IterateT} with + * an action to perform on each element without accumulating any results. + * + * @param fn the action to perform on each element + * @param the witnessed target result type + * @return the folded effect result + */ + public > MU forEach(Fn1> fn) { + return fold((__, a) -> fn.apply(a), runIterateT().pure(UNIT)); + } + + /** + * {@inheritDoc} + */ + @Override + public > IterateT lift(MonadRec nb) { + return singleton(nb); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("RedundantTypeArguments") + public IterateT trampolineM( + Fn1, IterateT>> fn) { + return $(withSelf( + (self, queued) -> suspended( + () -> pureM.>, MonadRec>, M>>apply(queued) + .trampolineM(q -> q.runIterateT().>, Maybe>>>>fmap(m -> m.match( + __ -> terminate(nothing()), + into((rr, tail) -> rr.biMap( + a -> fn.apply(a).>>coerce().concat(tail), + b -> just(tuple(b, self.apply(tail)))))))), + pureM)), + flatMap(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT flatMap(Fn1>> f) { + return suspended(() -> maybeT(runIterateT()) + .trampolineM(into((a, as) -> maybeT( + f.apply(a).>coerce().runIterateT() + .flatMap(maybePair -> maybePair.match( + fn0(() -> as.runIterateT() + .fmap(maybeResult -> maybeResult.fmap(RecursiveResult::recurse))), + t -> pureM.apply(just(terminate(t.fmap(mb -> mb.concat(as.flatMap(f)))))) + ))))) + .runMaybeT(), + pureM); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT fmap(Fn1 fn) { + return MonadT.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT pure(B b) { + return singleton(pureM.>apply(b)); + } + + /** + * Force the underlying spine of this {@link IterateT} into a {@link Collection} of type C inside the + * context of the monadic effect, using the provided cFn0 to construct the initial instance. + *

+ * Note that this is a fundamentally monolithic operation - meaning that incremental progress is not possible - and + * as such, calling this on an infinite {@link IterateT} will result in either heap exhaustion (e.g. in the case of + * {@link List lists}) or non-termination (e.g. in the case of {@link Set sets}). + * + * @param cFn0 the {@link Collection} construction function + * @param the {@link Collection} type + * @param the witnessed target type + * @return the {@link List} inside of the effect + */ + public , MAS extends MonadRec> MAS toCollection(Fn0 cFn0) { + MonadRec>>, M> mmta = runIterateT(); + return fold((c, a) -> { + c.add(a); + return mmta.pure(c); + }, mmta.pure(cFn0.apply())); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT zip(Applicative, IterateT> appFn) { + return suspended(() -> { + MonadRec>>, M> mmta = runIterateT(); + return join(maybeT(mmta).zip( + maybeT(appFn.>>coerce().runIterateT()) + .fmap(into((f, fs) -> into((a, as) -> maybeT( + as.fmap(f) + .cons(mmta.pure(f.apply(a))) + .concat(as.cons(mmta.pure(a)).zip(fs)) + .runIterateT())))))) + .runMaybeT(); + }, pureM); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, IterateT>> lazyAppFn) { + return lazyAppFn.fmap(this::zip); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT discardL(Applicative> appB) { + return MonadT.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT discardR(Applicative> appB) { + return MonadT.super.discardR(appB).coerce(); + } + + /** + * Static factory method for creating an empty {@link IterateT}. + * + * @param pureM the {@link Pure} method for the effect + * @param the effect type + * @param the element type + * @return the empty {@link IterateT} + */ + public static , A> IterateT empty(Pure pureM) { + return new IterateT<>(pureM, ImmutableQueue.empty()); + } + + /** + * Static factory method for creating an {@link IterateT} from a single element. + * + * @param ma the element + * @param the effect type + * @param the element type + * @return the singleton {@link IterateT} + */ + public static , A> IterateT singleton(MonadRec ma) { + return IterateT.empty(Pure.of(ma)).cons(ma); + } + + /** + * Static factory method for wrapping an uncons of an {@link IterateT} in an {@link IterateT}. + * + * @param unwrapped the uncons + * @param the effect type + * @param the element type + * @return the wrapped {@link IterateT} + */ + public static , A> IterateT iterateT( + MonadRec>>, M> unwrapped) { + return suspended(() -> unwrapped, Pure.of(unwrapped)); + } + + /** + * Static factory method for creating an {@link IterateT} from a spine represented by one or more elements. + * + * @param ma the head element + * @param mas the tail elements + * @param the effect type + * @param the element type + * @return the {@link IterateT} + */ + @SafeVarargs + public static , A> IterateT of( + MonadRec ma, MonadRec... mas) { + @SuppressWarnings("varargs") + List> as = asList(mas); + return foldLeft(IterateT::snoc, singleton(ma), as); + } + + /** + * Lazily unfold an {@link IterateT} from an unfolding function fn and a starting seed value + * mb by successively applying fn to the latest seed value, producing {@link Maybe maybe} + * a value to yield out and the next seed value for the subsequent computation. + * + * @param fn the unfolding function + * @param mb the starting seed value + * @param the effect type + * @param the element type + * @param the seed type + * @return the lazily unfolding {@link IterateT} + */ + public static , A, B> IterateT unfold( + Fn1>, M>> fn, MonadRec mb) { + Pure pureM = Pure.of(mb); + return $(withSelf((self, mmb) -> suspended(() -> maybeT(mmb.flatMap(fn)) + .fmap(ab -> ab.>fmap(b -> self.apply(pureM.apply(b)))) + .runMaybeT(), pureM)), mb); + } + + /** + * Create an {@link IterateT} from a suspended computation that yields the spine of the {@link IterateT} inside the + * effect. + * + * @param thunk the suspended computation + * @param pureM the {@link Pure} method for the effect + * @param the effect type + * @param the element type + * @return the {@link IterateT} + */ + public static , A> IterateT suspended( + Fn0>>, M>> thunk, Pure pureM) { + return new IterateT<>(pureM, ImmutableQueue.singleton(a(thunk))); + } + + /** + * Lazily unfold an {@link IterateT} from an {@link Iterator} inside {@link IO}. + * + * @param as the {@link Iterator} + * @param the element type + * @return the {@link IterateT} + */ + public static IterateT, A> fromIterator(Iterator as) { + return unfold(it -> io(() -> { + if (as.hasNext()) + return just(tuple(as.next(), as)); + return nothing(); + }), io(() -> as)); + } + + /** + * The canonical {@link Pure} instance for {@link IterateT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the argument {@link Monad} witness + * @return the {@link Pure} instance + */ + public static > Pure> pureIterateT(Pure pureM) { + return new Pure>() { + @Override + public IterateT checkedApply(A a) { + return liftIterateT().apply(pureM.>apply(a)); + } + }; + } + + /** + * {@link Lift} for {@link IterateT}. + * + * @return the {@link Monad} lifted into {@link IterateT} + */ + public static Lift> liftIterateT() { + return IterateT::singleton; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/LazyT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/LazyT.java new file mode 100644 index 000000000..e9dac4d33 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/LazyT.java @@ -0,0 +1,182 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Lift; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Compose; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.MonadT; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + +/** + * A {@link MonadT monad transformer} for {@link Lazy}. Note that both {@link LazyT#flatMap(Fn1)} and + * {@link LazyT#trampolineM} must force its value. + * + * @param the outer {@link Monad stack-safe monad} + * @param the carrier type + */ +public final class LazyT, A> implements + MonadT, LazyT> { + + private final MonadRec, M> mla; + + private LazyT(MonadRec, M> mla) { + this.mla = mla; + } + + /** + * Recover the full structure of the embedded {@link Monad}. + * + * @param the witnessed target type + * @return the embedded {@link Monad} + */ + public , M>> MLA runLazyT() { + return mla.coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public > LazyT lift(MonadRec mb) { + return liftLazyT().apply(mb); + } + + /** + * {@inheritDoc} + */ + @Override + public LazyT flatMap(Fn1>> f) { + return new LazyT<>(mla.flatMap(lazyA -> f.apply(lazyA.value()).>coerce().runLazyT())); + } + + /** + * {@inheritDoc} + */ + @Override + public LazyT trampolineM(Fn1, LazyT>> fn) { + return lazyT(runLazyT().trampolineM(lazyA -> fn.apply(lazyA.value()) + .>>coerce() + .runLazyT().fmap(lAOrB -> lAOrB.value().biMap(Lazy::lazy, Lazy::lazy)))); + } + + /** + * {@inheritDoc} + */ + @Override + public LazyT pure(B b) { + return new LazyT<>(mla.pure(lazy(b))); + } + + /** + * {@inheritDoc} + */ + @Override + public LazyT fmap(Fn1 fn) { + return MonadT.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public LazyT zip(Applicative, LazyT> appFn) { + return lazyT(new Compose<>(this., M>>runLazyT()).zip( + new Compose<>(appFn.>>coerce() + .>, M>>runLazyT())).getCompose()); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, LazyT>> lazyAppFn) { + return new Compose<>(mla) + .lazyZip(lazyAppFn.fmap(lazyT -> new Compose<>( + lazyT.>>coerce() + .>, M>>runLazyT()))) + .fmap(compose -> lazyT(compose.getCompose())); + } + + /** + * {@inheritDoc} + */ + @Override + public LazyT discardL(Applicative> appB) { + return MonadT.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public LazyT discardR(Applicative> appB) { + return MonadT.super.discardR(appB).coerce(); + } + + @Override + public boolean equals(Object other) { + return other instanceof LazyT && Objects.equals(mla, ((LazyT) other).mla); + } + + @Override + public int hashCode() { + return Objects.hash(mla); + } + + @Override + public String toString() { + return "LazyT{mla=" + mla + '}'; + } + + /** + * Static factory method for lifting a {@link Monad}<{@link Lazy}<A>, M> into a + * {@link LazyT}. + * + * @param mla the {@link Monad}<{@link Lazy}<A>, M> + * @param the outer {@link Monad} unification parameter + * @param the carrier type + * @return the new {@link LazyT} + */ + public static , A> LazyT lazyT(MonadRec, M> mla) { + return new LazyT<>(mla); + } + + /** + * The canonical {@link Pure} instance for {@link LazyT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the argument {@link Monad} witness + * @return the {@link Pure} instance + */ + public static > Pure> pureLazyT(Pure pureM) { + return new Pure>() { + @Override + public LazyT checkedApply(A a) { + return lazyT(pureM.>apply(a).fmap(Lazy::lazy)); + } + }; + } + + /** + * {@link Lift} for {@link LazyT}. + * + * @return the {@link Monad} lifted into {@link LazyT} + */ + public static Lift> liftLazyT() { + return new Lift>() { + @Override + public > LazyT checkedApply(MonadRec ga) { + return lazyT(ga.fmap(Lazy::lazy)); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java new file mode 100644 index 000000000..95dcdfd21 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java @@ -0,0 +1,240 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Lift; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Compose; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadError; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.MonadT; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.Fn0.fn0; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; + +/** + * A {@link MonadT monad transformer} for {@link Maybe}. + * + * @param the outer {@link Monad stack-safe monad} + * @param the carrier type + */ +public final class MaybeT, A> implements + MonadT, MaybeT>, MonadError> { + + private final MonadRec, M> mma; + + private MaybeT(MonadRec, M> mma) { + this.mma = mma; + } + + /** + * Recover the full structure of the embedded {@link Monad}. + * + * @param the witnessed target type + * @return the embedded {@link Monad} + */ + public , M>> MMA runMaybeT() { + return mma.coerce(); + } + + /** + * If the embedded value is present and satisfies predicate + * then return just the embedded value + * + * @param predicate the predicate to apply to the embedded value + * @return maybe the satisfied value embedded under M + */ + public MaybeT filter(Fn1 predicate) { + return maybeT(mma.fmap(ma -> ma.filter(predicate))); + } + + /** + * Returns the first {@link MaybeT} that is an effect around {@link Maybe#just(Object) just} a result. + * + * @param other the other {@link MaybeT} + * @return the first present {@link MaybeT} + */ + public MaybeT or(MaybeT other) { + MonadRec, M> mMaybeA = runMaybeT(); + return maybeT(mMaybeA.flatMap(maybeA -> maybeA.match(constantly(other.runMaybeT()), + a -> mMaybeA.pure(just(a))))); + } + + /** + * {@inheritDoc} + */ + @Override + public MaybeT throwError(Unit unit) { + return maybeT(mma.pure(nothing())); + } + + /** + * {@inheritDoc} + */ + @Override + public MaybeT catchError(Fn1>> recoveryFn) { + return maybeT(mma.flatMap(maybeA -> maybeA.match( + fn0(() -> recoveryFn.apply(UNIT).>coerce().runMaybeT()), + a -> mma.pure(just(a))))); + } + + /** + * {@inheritDoc} + */ + @Override + public > MaybeT lift(MonadRec mb) { + return liftMaybeT().apply(mb); + } + + /** + * {@inheritDoc} + */ + @Override + public MaybeT fmap(Fn1 fn) { + return MonadT.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public MaybeT pure(B b) { + return maybeT(mma.pure(just(b))); + } + + /** + * {@inheritDoc} + */ + @Override + public MaybeT zip(Applicative, MaybeT> appFn) { + return maybeT(new Compose<>(this., M>>runMaybeT()).zip( + new Compose<>(appFn.>>coerce() + .>, M>>runMaybeT())) + .getCompose()); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, MaybeT>> lazyAppFn) { + return new Compose<>(mma) + .lazyZip(lazyAppFn.fmap(maybeT -> new Compose<>( + maybeT.>>coerce() + .>, M>>runMaybeT()))) + .fmap(compose -> maybeT(compose.getCompose())); + } + + /** + * {@inheritDoc} + */ + @Override + public MaybeT flatMap(Fn1>> f) { + return maybeT(mma.flatMap(ma -> ma + .match(constantly(mma.pure(nothing())), + a -> f.apply(a).>coerce().runMaybeT()))); + } + + /** + * {@inheritDoc} + */ + @Override + public MaybeT trampolineM(Fn1, MaybeT>> fn) { + return maybeT(runMaybeT().trampolineM(maybeA -> maybeA.match( + constantly(runMaybeT().pure(terminate(nothing()))), + a -> fn.apply(a).>>coerce() + .runMaybeT() + .fmap(maybeRec -> maybeRec.match( + constantly(terminate(nothing())), + aOrB -> aOrB.biMap(Maybe::just, Maybe::just)))))); + } + + /** + * {@inheritDoc} + */ + @Override + public MaybeT discardL(Applicative> appB) { + return MonadT.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public MaybeT discardR(Applicative> appB) { + return MonadT.super.discardR(appB).coerce(); + } + + @Override + public boolean equals(Object other) { + return other instanceof MaybeT && Objects.equals(mma, ((MaybeT) other).mma); + } + + @Override + public int hashCode() { + return Objects.hash(mma); + } + + @Override + public String toString() { + return "MaybeT{" + + "mma=" + mma + + '}'; + } + + /** + * Static factory method for lifting a {@link Monad}<{@link Maybe}<A>, M> into a + * {@link MaybeT}. + * + * @param mma the {@link Monad}<{@link Maybe}<A>, M> + * @param the outer {@link Monad} unification parameter + * @param the carrier type + * @return the {@link MaybeT} + */ + public static , A> MaybeT maybeT(MonadRec, M> mma) { + return new MaybeT<>(mma); + } + + /** + * The canonical {@link Pure} instance for {@link MaybeT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the argument {@link Monad} witness + * @return the {@link Pure} instance + */ + public static > Pure> pureMaybeT(Pure pureM) { + return new Pure>() { + @Override + public MaybeT checkedApply(A a) { + return maybeT(pureM.>apply(a).fmap(Maybe::just)); + } + }; + } + + /** + * {@link Lift} for {@link MaybeT}. + * s + * + * @return the {@link Monad} lifted into {@link MaybeT} + */ + public static Lift> liftMaybeT() { + return new Lift>() { + @Override + public > MaybeT checkedApply(MonadRec ga) { + return maybeT(ga.fmap(Maybe::just)); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java new file mode 100644 index 000000000..e4c8ec5f0 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java @@ -0,0 +1,265 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Lift; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Cartesian; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadReader; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.MonadT; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler; + +/** + * A {@link MonadT monad transformer} for any {@link Fn1 function} from some type R to some + * {@link MonadRec monadic} embedding {@link MonadRec}<A, M>. + * + * @param the input type + * @param the returned {@link MonadRec} + * @param the embedded output type + */ +public final class ReaderT, A> implements + MonadReader>, + Cartesian>, + MonadT, ReaderT> { + + private final Fn1> f; + + private ReaderT(Fn1> f) { + this.f = f; + } + + /** + * Run the computation represented by this {@link ReaderT}. + * + * @param r the input + * @param the witnessed target type + * @return the embedded {@link MonadRec} + */ + public > MA runReaderT(R r) { + return f.apply(r).coerce(); + } + + /** + * Map the current {@link Monad monadic} embedding to a new one in a potentially different {@link Monad}. + * + * @param fn the function + * @param the currently embedded {@link Monad} + * @param the new {@link Monad} witness + * @param the new carrier type + * @return the mapped {@link ReaderT} + */ + public , N extends MonadRec, B> ReaderT mapReaderT( + Fn1> fn) { + return readerT(r -> fn.apply(runReaderT(r).coerce())); + } + + /** + * Left-to-right composition between {@link ReaderT} instances running under the same effect and compatible between + * their inputs and outputs. + * + * @param amb the next {@link ReaderT} to run + * @param the final output type + * @return the composed {@link ReaderT} + */ + public ReaderT and(ReaderT amb) { + return readerT(r -> runReaderT(r).flatMap(amb::runReaderT)); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT local(Fn1 fn) { + return contraMap(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public > ReaderT lift(MonadRec mb) { + return ReaderT.liftReaderT().apply(mb); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT flatMap(Fn1>> f) { + return readerT(r -> runReaderT(r).flatMap(a -> f.apply(a).>coerce().runReaderT(r))); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT trampolineM( + Fn1, ReaderT>> fn) { + return readerT(r -> runReaderT(r).trampolineM(a -> fn.apply(a).>>coerce() + .runReaderT(r))); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT pure(B b) { + return readerT(r -> runReaderT(r).pure(b)); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT fmap(Fn1 fn) { + return readerT(r -> runReaderT(r).fmap(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT zip(Applicative, ReaderT> appFn) { + return readerT(r -> f.apply(r).zip(appFn.>>coerce().runReaderT(r))); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, ReaderT>> lazyAppFn) { + return lazyAppFn.fmap(this::zip); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT discardL(Applicative> appB) { + return MonadT.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT discardR(Applicative> appB) { + return MonadT.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT diMap(Fn1 lFn, Fn1 rFn) { + return readerT(q -> runReaderT(lFn.apply(q)).fmap(rFn)); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT diMapL(Fn1 fn) { + return (ReaderT) Cartesian.super.diMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT diMapR(Fn1 fn) { + return (ReaderT) Cartesian.super.diMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT contraMap(Fn1 fn) { + return (ReaderT) Cartesian.super.contraMap(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT, M, Tuple2> cartesian() { + return readerT(into((c, r) -> runReaderT(r).fmap(tupler(c)))); + } + + /** + * {@inheritDoc} + */ + @Override + public ReaderT> carry() { + return (ReaderT>) Cartesian.super.carry(); + } + + /** + * Given a {@link Pure} ask will give you access to the input within the monadic embedding + * + * @param pureM the {@link Pure} instance for the given {@link Monad} + * @param the input and output type of the returned ReaderT + * @param the returned {@link Monad} + * @return the {@link ReaderT} + */ + public static > ReaderT ask(Pure pureM) { + //noinspection Convert2MethodRef + return readerT(a -> pureM.apply(a)); + } + + /** + * Lift a {@link Fn1 function} (R -> {@link Monad}<A, M>) into a {@link ReaderT} instance. + * + * @param fn the function + * @param the input type + * @param the returned {@link Monad} + * @param the embedded output type + * @return the {@link ReaderT} + */ + public static , A> ReaderT readerT( + Fn1> fn) { + return new ReaderT<>(fn); + } + + /** + * The canonical {@link Pure} instance for {@link ReaderT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the input type + * @param the argument {@link Monad} witness + * @return the {@link Pure} instance + */ + public static > Pure> pureReaderT(Pure pureM) { + return new Pure>() { + @Override + public ReaderT checkedApply(A a) { + return readerT(__ -> pureM.apply(a)); + } + }; + } + + /** + * {@link Lift} for {@link ReaderT}. + * + * @param the environment type + * @return the {@link Monad} lifted into {@link ReaderT} + */ + public static Lift> liftReaderT() { + return new Lift>() { + @Override + public > ReaderT checkedApply(MonadRec ga) { + return readerT(constantly(ga)); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java new file mode 100644 index 000000000..219ad3e03 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java @@ -0,0 +1,310 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Lift; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.functor.builtin.State; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadReader; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.MonadWriter; +import com.jnape.palatable.lambda.monad.transformer.MonadT; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler; + + +/** + * The {@link State} {@link MonadT monad transformer}. + * + * @param the state type + * @param the {@link Monad monadic embedding} + * @param the result type + * @see State + */ +public final class StateT, A> implements + MonadT, StateT>, + MonadReader>, + MonadWriter> { + + private final Fn1, M>> stateFn; + + private StateT(Fn1, M>> stateFn) { + this.stateFn = stateFn; + } + + /** + * Run the stateful computation embedded in the {@link Monad}, returning a {@link Tuple2} of the result and the + * final state. + * + * @param s the initial state + * @param the inferred {@link Monad} result + * @return a {@link Tuple2} of the result and the final state. + */ + public , M>> MAS runStateT(S s) { + return stateFn.apply(s).coerce(); + } + + /** + * Run the stateful computation embedded in the {@link Monad}, returning the result. + * + * @param s the initial state + * @param the inferred {@link Monad} result + * @return the result + */ + public > MA evalT(S s) { + return runStateT(s).fmap(Tuple2::_1).coerce(); + } + + /** + * Run the stateful computation embedded in the {@link Monad}, returning the final state. + * + * @param s the initial state + * @param the inferred {@link Monad} result + * @return the final state + */ + public > MS execT(S s) { + return runStateT(s).fmap(Tuple2::_2).coerce(); + } + + /** + * Map both the result and the final state to a new result and final state inside the {@link Monad}. + * + * @param fn the mapping function + * @param the new {@link Monad monadic embedding} for this {@link StateT} + * @param the new state type + * @return the mapped {@link StateT} + */ + public , B> StateT mapStateT( + Fn1, M>, ? extends MonadRec, N>> fn) { + return stateT(s -> fn.apply(runStateT(s))); + } + + /** + * Map the final state to a new final state inside the same {@link Monad monadic effect} using the provided + * function. + * + * @param fn the state-mapping function + * @return the mapped {@link StateT} + */ + public StateT withStateT(Fn1> fn) { + return modify(fn).flatMap(constantly(this)); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT> listens(Fn1 fn) { + return mapStateT(mas -> mas.fmap(t -> t.into((a, s) -> tuple(tuple(a, fn.apply(s)), s)))); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT censor(Fn1 fn) { + return local(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT local(Fn1 fn) { + return stateT(s -> runStateT(fn.apply(s))); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT flatMap(Fn1>> f) { + return stateT(s -> runStateT(s).flatMap(into((a, s_) -> f.apply(a).>coerce().runStateT(s_)))); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT pure(B b) { + return stateT(s -> runStateT(s).pure(tuple(b, s))); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT fmap(Fn1 fn) { + return stateT(s -> runStateT(s).fmap(t -> t.biMapL(fn))); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT zip(Applicative, StateT> appFn) { + return MonadT.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, StateT>> lazyAppFn) { + return MonadT.super.lazyZip(lazyAppFn).fmap(MonadT, StateT>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT discardL(Applicative> appB) { + return MonadT.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT discardR(Applicative> appB) { + return MonadT.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public > StateT lift(MonadRec mb) { + return stateT(s -> mb.fmap(b -> tuple(b, s))); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT trampolineM( + Fn1, StateT>> fn) { + return StateT.stateT((Fn1., M>>fn1(this::runStateT)) + .fmap(m -> m.trampolineM(into((a, s) -> fn.apply(a) + .>>coerce().runStateT(s) + .fmap(into((aOrB, s_) -> aOrB.biMap(a_ -> tuple(a_, s_), + b -> tuple(b, s_)))))))); + } + + /** + * Given a {@link Pure pure} construction of some {@link Monad}, produce a {@link StateT} that equates its output + * with its state. + * + * @param pureM the {@link Pure pure} construction + * @param the state and value type + * @param the {@link Monad} embedding + * @return the {@link StateT} + */ + @SuppressWarnings("RedundantTypeArguments") + public static > StateT get(Pure pureM) { + return gets(pureM::>apply); + } + + /** + * Given a function that produces a value inside a {@link Monad monadic effect} from a state, produce a + * {@link StateT} that simply passes its state to the function and applies it. + * + * @param fn the function + * @param the state type + * @param the{@link Monad} embedding + * @param the value type + * @return the {@link StateT} + */ + public static , A> StateT gets(Fn1> fn) { + return stateT(s -> fn.apply(s).fmap(a -> tuple(a, s))); + } + + /** + * Lift a function that makes a stateful modification inside an {@link Monad} into {@link StateT}. + * + * @param updateFn the update function + * @param the state type + * @param the {@link Monad} embedding + * @return the {@link StateT} + */ + public static > StateT modify( + Fn1> updateFn) { + return stateT(s -> updateFn.apply(s).fmap(tupler(UNIT))); + } + + /** + * Lift a {@link MonadRec monadic state} into {@link StateT}. + * + * @param ms the state + * @param the state type + * @param the {@link MonadRec} embedding + * @return the {@link StateT} + */ + public static > StateT put(MonadRec ms) { + return modify(constantly(ms)); + } + + /** + * Lift a {@link MonadRec monadic value} into {@link StateT}. + * + * @param ma the value + * @param the state type + * @param the {@link Monad} embedding + * @param the result type + * @return the {@link StateT} + */ + public static , A> StateT stateT(MonadRec ma) { + return gets(constantly(ma)); + } + + /** + * Lift a state-sensitive {@link Monad monadically embedded} computation into {@link StateT}. + * + * @param stateFn the stateful operation + * @param the state type + * @param the {@link Monad} embedding + * @param the result type + * @return the {@link StateT} + */ + public static , A> StateT stateT( + Fn1, M>> stateFn) { + return new StateT<>(stateFn); + } + + /** + * The canonical {@link Pure} instance for {@link StateT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the state type + * @param the argument {@link Monad} witness + * @return the {@link Pure} instance + */ + public static > Pure> pureStateT(Pure pureM) { + return new Pure>() { + @Override + public StateT checkedApply(A a) { + return stateT(pureM.>apply(a)); + } + }; + } + + /** + * {@link Lift} for {@link StateT}. + * + * @param the state type + * @return the {@link Monad} lifted into {@link StateT} + */ + public static Lift> liftStateT() { + return StateT::stateT; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java new file mode 100644 index 000000000..c8625bbf8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java @@ -0,0 +1,244 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Lift; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.functor.builtin.Writer; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.MonadWriter; +import com.jnape.palatable.lambda.monad.transformer.MonadT; +import com.jnape.palatable.lambda.monoid.Monoid; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler; + +/** + * A {@link MonadT monad transformer} for {@link Writer}. + * + * @param the accumulation type + * @param the {@link Monad monadic embedding} + * @param the result type + */ +public final class WriterT, A> implements + MonadWriter>, + MonadT, WriterT> { + + private final Pure pureM; + private final Fn1, ? extends MonadRec, M>> writerFn; + + private WriterT(Pure pureM, + Fn1, ? extends MonadRec, M>> writerFn) { + this.pureM = pureM; + this.writerFn = writerFn; + } + + /** + * Given a {@link Monoid} for the accumulation, run the computation represented by this {@link WriterT} inside the + * {@link Monad monadic effect}, accumulate the written output in terms of the {@link Monoid}, and produce the + * accumulation and the result inside the {@link Monad}. + * + * @param monoid the accumulation {@link Monoid} + * @param the inferred {@link MonadRec} result + * @return the accumulation with the result + */ + public , M>> MAW runWriterT(Monoid monoid) { + return writerFn.apply(monoid).coerce(); + } + + /** + * Given a {@link Monoid} for the accumulation, run the computation represented by this {@link WriterT} inside the + * {@link Monad monadic effect}, ignoring the resulting accumulation, yielding the value in isolation. + * + * @param monoid the accumulation {@link Monoid} + * @param the inferred {@link MonadRec} result + * @return the result + */ + public > MA evalWriterT(Monoid monoid) { + return runWriterT(monoid).fmap(Tuple2::_1).coerce(); + } + + /** + * Given a {@link Monoid} for the accumulation, run the computation represented by this {@link WriterT} inside the + * {@link Monad monadic effect}, ignoring the value, yielding the accumulation in isolation. + * + * @param monoid the accumulation {@link Monoid} + * @param the inferred {@link MonadRec} accumulation + * @return the accumulation + */ + public > MW execWriterT(Monoid monoid) { + return runWriterT(monoid).fmap(Tuple2::_2).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT> listens(Fn1 fn) { + return new WriterT<>(pureM, writerFn.fmap(m -> m.fmap(into((a, w) -> both(both(constantly(a), fn), id(), w))))); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT censor(Fn1 fn) { + return new WriterT<>(pureM, writerFn.fmap(mt -> mt.fmap(t -> t.fmap(fn)))); + } + + /** + * {@inheritDoc} + */ + @Override + public > WriterT lift(MonadRec mb) { + return WriterT.liftWriterT().apply(mb); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT trampolineM( + Fn1, WriterT>> fn) { + return new WriterT<>(pureM, monoid -> runWriterT(monoid).trampolineM(into((a, w) -> fn.apply(a) + .>>coerce() + .runWriterT(monoid).fmap(t -> t.fmap(monoid.apply(w))) + .fmap(into((aOrB, w_) -> aOrB.biMap(a_ -> tuple(a_, w_), b -> tuple(b, w_))))))); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT fmap(Fn1 fn) { + return MonadT.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT pure(B b) { + return new WriterT<>(pureM, m -> pureM.apply(tuple(b, m.identity()))); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT flatMap(Fn1>> f) { + return new WriterT<>(pureM, monoid -> writerFn.apply(monoid) + .flatMap(into((a, w) -> f.apply(a).>coerce().runWriterT(monoid) + .fmap(t -> t.fmap(monoid.apply(w)))))); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT zip(Applicative, WriterT> appFn) { + return new WriterT<>(pureM, monoid -> runWriterT(monoid) + .zip(appFn.>>coerce().runWriterT(monoid) + .fmap(into((f, y) -> into((a, x) -> tuple(f.apply(a), monoid.apply(x, y))))))); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, WriterT>> lazyAppFn) { + return lazyAppFn.fmap(this::zip); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT discardL(Applicative> appB) { + return MonadT.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT discardR(Applicative> appB) { + return MonadT.super.discardR(appB).coerce(); + } + + /** + * Lift an accumulation embedded in a {@link Monad} into a {@link WriterT}. + * + * @param mw the accumulation inside a {@link Monad} + * @param the accumulation type + * @param the {@link Monad} type + * @return the {@link WriterT} + */ + public static > WriterT tell(MonadRec mw) { + return writerT(mw.fmap(tupler(UNIT))); + } + + /** + * Lift a value embedded in a {@link Monad} into a {@link WriterT}. + * + * @param ma the value inside a {@link Monad} + * @param the accumulation type + * @param the {@link Monad} type + * @param the value type + * @return the {@link WriterT} + */ + public static , A> WriterT listen(MonadRec ma) { + return new WriterT<>(Pure.of(ma), monoid -> ma.fmap(a -> tuple(a, monoid.identity()))); + } + + /** + * Lift a value and an accumulation embedded in a {@link Monad} into a {@link WriterT}. + * + * @param maw the value and accumulation inside a {@link Monad} + * @param the accumulation type + * @param the {@link Monad} type + * @param the value type + * @return the {@link WriterT} + */ + public static , A> WriterT writerT(MonadRec, M> maw) { + return new WriterT<>(Pure.of(maw), constantly(maw)); + } + + /** + * The canonical {@link Pure} instance for {@link WriterT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the accumulation type + * @param the argument {@link Monad} witness + * @return the {@link Pure} instance + */ + public static > Pure> pureWriterT(Pure pureM) { + return new Pure>() { + @Override + public WriterT checkedApply(A a) { + return listen(pureM.>apply(a)); + } + }; + } + + /** + * {@link Lift} for {@link WriterT}. + * + * @param the accumulated type + * @return the {@link Monad} lifted into {@link WriterT} + */ + public static Lift> liftWriterT() { + return WriterT::listen; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java b/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java index 0f7df445b..59f4ce1c7 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java @@ -1,14 +1,19 @@ package com.jnape.palatable.lambda.monoid; +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.builtin.fn2.Map; import com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft; import com.jnape.palatable.lambda.functions.builtin.fn2.ReduceRight; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.semigroup.Semigroup; -import java.util.function.Function; -import java.util.function.Supplier; - +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Reverse.reverse; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** * A {@link Monoid} is the pairing of a {@link Semigroup} with an identity element. @@ -33,7 +38,7 @@ public interface Monoid extends Semigroup { * @see ReduceLeft */ default A reduceLeft(Iterable as) { - return ReduceLeft.reduceLeft(toBiFunction(), as).orElse(identity()); + return foldMap(id(), as); } /** @@ -45,7 +50,7 @@ default A reduceLeft(Iterable as) { * @see ReduceRight */ default A reduceRight(Iterable as) { - return ReduceRight.reduceRight(toBiFunction(), as).orElse(identity()); + return flip().foldMap(id(), reverse(as)); } /** @@ -53,15 +58,39 @@ default A reduceRight(Iterable as) { * Iterable<A> (that is, an Iterable of elements this monoid is formed over), then * reduce the result from left to right. Under algebraic data types, this is isomorphic to a flatMap. * + * @param the input Iterable element type * @param fn the mapping function from A to B * @param bs the Iterable of Bs - * @param the input Iterable element type * @return the folded result under this Monoid * @see Map * @see Monoid#reduceLeft(Iterable) */ - default A foldMap(Function fn, Iterable bs) { - return reduceLeft(map(fn, bs)); + default A foldMap(Fn1 fn, Iterable bs) { + return FoldLeft.foldLeft(this, identity(), map(fn, bs)); + } + + /** + * {@inheritDoc} + */ + @Override + default A foldLeft(A a, Iterable as) { + return foldMap(id(), cons(a, as)); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy foldRight(A a, Iterable as) { + return lazy(() -> flip().foldMap(id(), cons(a, reverse(as)))); + } + + /** + * {@inheritDoc} + */ + @Override + default Monoid flip() { + return monoid(Semigroup.super.flip(), identity()); } /** @@ -80,21 +109,21 @@ public A identity() { } @Override - public A apply(A x, A y) { + public A checkedApply(A x, A y) { return semigroup.apply(x, y); } }; } - static Monoid monoid(Semigroup semigroup, Supplier identitySupplier) { + static Monoid monoid(Semigroup semigroup, Fn0 identityFn0) { return new Monoid() { @Override public A identity() { - return identitySupplier.get(); + return identityFn0.apply(); } @Override - public A apply(A x, A y) { + public A checkedApply(A x, A y) { return semigroup.apply(x, y); } }; diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/AddAll.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/AddAll.java new file mode 100644 index 000000000..219763fd3 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/AddAll.java @@ -0,0 +1,70 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; +import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; +import com.jnape.palatable.lambda.monoid.Monoid; + +import java.util.Collection; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; + +/** + * The {@link Monoid} instance formed under mutative concatenation for an arbitrary {@link Collection}. The collection + * subtype (C) must support {@link Collection#addAll(Collection)}. + *

+ * Note that the result is a new collection, and the inputs to this monoid are left unmodified. + * + * @see Monoid + */ +public final class AddAll> implements MonoidFactory, C> { + + private static final AddAll INSTANCE = new AddAll<>(); + + private AddAll() { + } + + @Override + public Monoid checkedApply(Fn0 cFn0) { + return new Monoid() { + @Override + public C identity() { + return cFn0.apply(); + } + + @Override + public C checkedApply(C xs, C ys) { + C c = identity(); + c.addAll(xs); + c.addAll(ys); + return c; + } + + @Override + public C foldMap(Fn1 fn, Iterable bs) { + return FoldLeft.foldLeft((x, y) -> { + x.addAll(y); + return x; + }, identity(), map(fn, bs)); + } + }; + } + + @SuppressWarnings("unchecked") + public static > AddAll addAll() { + return (AddAll) INSTANCE; + } + + public static > Monoid addAll(Fn0 collectionFn0) { + return AddAll.addAll().apply(collectionFn0); + } + + public static > Fn1 addAll(Fn0 collectionFn0, C xs) { + return addAll(collectionFn0).apply(xs); + } + + public static > C addAll(Fn0 collectionFn0, C xs, C ys) { + return addAll(collectionFn0, xs).apply(ys); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/And.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/And.java index cd10613df..2ce32994c 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/And.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/And.java @@ -1,15 +1,20 @@ package com.jnape.palatable.lambda.monoid.builtin; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.monoid.Monoid; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Not.not; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Find.find; + /** * A {@link Monoid} instance formed by Boolean. Equivalent to logical &&. * * @see Or * @see Monoid */ -public class And implements Monoid { +public final class And implements Monoid, BiPredicate { private static final And INSTANCE = new And(); @@ -22,10 +27,20 @@ public Boolean identity() { } @Override - public Boolean apply(Boolean x, Boolean y) { + public Boolean checkedApply(Boolean x, Boolean y) { return x && y; } + @Override + public Boolean foldMap(Fn1 fn, Iterable bs) { + return find(not(fn), bs).fmap(constantly(false)).orElse(true); + } + + @Override + public And flip() { + return this; + } + public static And and() { return INSTANCE; } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Collapse.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Collapse.java index 51cf36970..7713431b5 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Collapse.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Collapse.java @@ -23,20 +23,21 @@ */ public final class Collapse<_1, _2> implements BiMonoidFactory, Monoid<_2>, Tuple2<_1, _2>> { - private static final Collapse INSTANCE = new Collapse(); + private static final Collapse INSTANCE = new Collapse<>(); private Collapse() { } @Override - public Monoid> apply(Monoid<_1> _1Monoid, Monoid<_2> _2Monoid) { - Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.Collapse.collapse(_1Monoid, _2Monoid); - return Monoid.>monoid(semigroup, () -> tuple(_1Monoid.identity(), _2Monoid.identity())); + public Monoid> checkedApply(Monoid<_1> _1Monoid, Monoid<_2> _2Monoid) { + return Monoid.>monoid( + com.jnape.palatable.lambda.semigroup.builtin.Collapse.collapse(_1Monoid, _2Monoid), + () -> tuple(_1Monoid.identity(), _2Monoid.identity())); } @SuppressWarnings("unchecked") public static <_1, _2> Collapse<_1, _2> collapse() { - return INSTANCE; + return (Collapse<_1, _2>) INSTANCE; } public static <_1, _2> MonoidFactory, Tuple2<_1, _2>> collapse(Monoid<_1> _1Monoid) { diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Compose.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Compose.java new file mode 100644 index 000000000..dbffbe748 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Compose.java @@ -0,0 +1,57 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.concurrent.CompletableFuture; + +import static com.jnape.palatable.lambda.functions.Fn0.fn0; +import static com.jnape.palatable.lambda.monoid.Monoid.monoid; +import static java.util.concurrent.CompletableFuture.completedFuture; + +/** + * A {@link Monoid} instance formed by {@link CompletableFuture}<A> and a monoid over + * A. If either {@link CompletableFuture}s completes exceptionally, the result is also an exceptionally + * completed future. + *

+ * Note that this operation only takes as long as the slowest future to complete. + *

+ * For the {@link Semigroup}, see {@link com.jnape.palatable.lambda.semigroup.builtin.Compose}. + * + * @param the future parameter type + */ +public final class Compose implements MonoidFactory, CompletableFuture> { + + private static final Compose INSTANCE = new Compose<>(); + + private Compose() { + } + + @Override + public Monoid> checkedApply(Monoid aMonoid) { + return monoid(com.jnape.palatable.lambda.semigroup.builtin.Compose.compose(aMonoid), + fn0(() -> completedFuture(aMonoid.identity()))); + } + + @SuppressWarnings("unchecked") + public static Compose compose() { + return (Compose) INSTANCE; + } + + public static Monoid> compose(Monoid aMonoid) { + return Compose.compose().apply(aMonoid); + } + + public static Fn1, CompletableFuture> compose(Monoid aMonoid, + CompletableFuture x) { + return compose(aMonoid).apply(x); + } + + public static CompletableFuture compose(Monoid aMonoid, + CompletableFuture x, + CompletableFuture y) { + return compose(aMonoid, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java index 2ea4501fe..83c7c31c8 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java @@ -1,50 +1,51 @@ package com.jnape.palatable.lambda.monoid.builtin; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; +import com.jnape.palatable.lambda.internal.iteration.ConcatenatingIterable; import com.jnape.palatable.lambda.monoid.Monoid; -import com.jnape.palatable.lambda.semigroup.Semigroup; -import java.util.Collection; -import java.util.function.Supplier; +import java.util.Collections; -import static com.jnape.palatable.lambda.monoid.Monoid.monoid; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; /** - * The {@link Monoid} instance formed under concatenation for an arbitrary {@link Collection}. The collection subtype - * (C) must support {@link Collection#addAll(Collection)}. - *

- * For the {@link Semigroup}, see {@link com.jnape.palatable.lambda.semigroup.builtin.Concat}. + * The {@link Monoid} instance formed under concatenation for an arbitrary {@link Iterable}. * * @see Monoid */ -public final class Concat> implements MonoidFactory, C> { +public final class Concat implements Monoid> { - private static final Concat INSTANCE = new Concat(); + private static final Concat INSTANCE = new Concat<>(); private Concat() { } @Override - public Monoid apply(Supplier cSupplier) { - Semigroup semigroup = com.jnape.palatable.lambda.semigroup.builtin.Concat.concat(); - return monoid(semigroup, cSupplier); + public Iterable identity() { + return Collections::emptyIterator; } - @SuppressWarnings("unchecked") - public static > Concat concat() { - return INSTANCE; + @Override + public Iterable checkedApply(Iterable xs, Iterable ys) { + return new ConcatenatingIterable<>(xs, ys); + } + + @Override + public Iterable foldMap(Fn1> fn, Iterable bs) { + return flatten(map(fn, bs)); } - public static > Monoid concat(Supplier collectionSupplier) { - return Concat.concat().apply(collectionSupplier); + @SuppressWarnings("unchecked") + public static Concat concat() { + return (Concat) INSTANCE; } - public static > Fn1 concat(Supplier collectionSupplier, C xs) { - return concat(collectionSupplier).apply(xs); + public static Fn1, Iterable> concat(Iterable xs) { + return Concat.concat().apply(xs); } - public static > C concat(Supplier collectionSupplier, C xs, C ys) { - return concat(collectionSupplier, xs).apply(ys); + public static Iterable concat(Iterable xs, Iterable ys) { + return concat(xs).apply(ys); } } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Endo.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Endo.java new file mode 100644 index 000000000..990689535 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Endo.java @@ -0,0 +1,57 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.monoid.Monoid; + +import static com.jnape.palatable.lambda.functions.Fn2.curried; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * A {@link Monoid} formed by {@link Fn1} under composition. + * + * @param the input/output type to the {@link Fn1} + */ +public final class Endo implements Monoid> { + + private static final Endo INSTANCE = new Endo<>(); + + private Endo() { + } + + public A apply(Fn1 f, Fn1 g, A a) { + return apply(f, g).apply(a); + } + + @Override + public Fn1 identity() { + return id(); + } + + @Override + public Fn1 checkedApply(Fn1 f, Fn1 g) { + return f.fmap(g); + } + + @Override + public Fn2, A, A> apply(Fn1 f) { + return curried(Monoid.super.apply(f)); + } + + @SuppressWarnings("unchecked") + public static Endo endo() { + return (Endo) INSTANCE; + } + + public static Fn2, A, A> endo(Fn1 f) { + return Endo.endo().apply(f); + } + + public static Fn1 endo(Fn1 f, Fn1 g) { + return endo(f).apply(g); + } + + public static A endo(Fn1 f, Fn1 g, A a) { + return endo(f, g).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/EndoK.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/EndoK.java new file mode 100644 index 000000000..22dd6ba48 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/EndoK.java @@ -0,0 +1,59 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; +import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.SafeT; +import com.jnape.palatable.lambda.monoid.Monoid; + +import static com.jnape.palatable.lambda.functions.specialized.Kleisli.kleisli; +import static com.jnape.palatable.lambda.monad.SafeT.safeT; + +/** + * The monoid formed under monadic endomorphism. + * + * @param the {@link MonadRec} witness + * @param the carrier type + * @param the fully witnessed {@link MonadRec} type + */ +public final class EndoK, A, MA extends MonadRec> implements + MonoidFactory, Fn1> { + + private static final EndoK INSTANCE = new EndoK<>(); + + @Override + public Monoid> checkedApply(Pure pureM) { + return new Monoid>() { + @Override + public Fn1 identity() { + return pureM::apply; + } + + @Override + public Fn1 checkedApply(Fn1 f, Fn1 g) { + return a -> kleisli(f).andThen(g::apply).apply(a); + } + + @Override + public Fn1 foldMap(Fn1> fn, Iterable bs) { + return a -> FoldLeft.foldLeft((f, b) -> f.fmap(ma -> ma.flatMap(a_ -> safeT(fn.apply(b).apply(a_)))), + safeT(identity()).fmap(SafeT::safeT), + bs) + .>>runSafeT() + .apply(a) + .runSafeT(); + } + }; + } + + @SuppressWarnings("unchecked") + public static , A, MA extends MonadRec> EndoK endoK() { + return (EndoK) INSTANCE; + } + + public static , A, MA extends MonadRec> Monoid> endoK(Pure pureM) { + return EndoK.endoK().apply(pureM); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/First.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/First.java index d9ee1acd0..9c44abca9 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/First.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/First.java @@ -1,47 +1,56 @@ package com.jnape.palatable.lambda.monoid.builtin; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.monoid.Monoid; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.CatMaybes.catMaybes; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; /** - * A {@link Monoid} instance formed by {@link Optional}<A>. The application to two {@link Optional} - * values produces the first non-empty value, or Optional.empty() if all values are empty. + * A {@link Monoid} instance formed by {@link Maybe}<A>. The application to two {@link Maybe} values + * produces the first non-empty value, or {@link Maybe#nothing()} if all values are empty. * - * @param the Optional value parameter type + * @param the Maybe value parameter type * @see Last * @see Present * @see Monoid - * @see Optional + * @see Maybe */ -public final class First implements Monoid> { +public final class First implements Monoid> { - private static final First INSTANCE = new First(); + private static final First INSTANCE = new First<>(); private First() { } @Override - public Optional identity() { - return Optional.empty(); + public Maybe identity() { + return nothing(); } @Override - public Optional apply(Optional x, Optional y) { - return x.map(Optional::of).orElse(y); + public Maybe checkedApply(Maybe x, Maybe y) { + return x.fmap(Maybe::just).orElse(y); + } + + @Override + public Maybe foldMap(Fn1> fn, Iterable bs) { + return head(catMaybes(map(fn, bs))); } @SuppressWarnings("unchecked") public static First first() { - return INSTANCE; + return (First) INSTANCE; } - public static Fn1, Optional> first(Optional x) { + public static Fn1, Maybe> first(Maybe x) { return First.first().apply(x); } - public static Optional first(Optional x, Optional y) { + public static Maybe first(Maybe x, Maybe y) { return first(x).apply(y); } } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Join.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Join.java index 9f3713283..571bcbe73 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Join.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Join.java @@ -21,7 +21,7 @@ public String identity() { } @Override - public String apply(String x, String y) { + public String checkedApply(String x, String y) { return x + y; } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Last.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Last.java index 90b80f329..8b0c91073 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Last.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Last.java @@ -1,46 +1,49 @@ package com.jnape.palatable.lambda.monoid.builtin; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.monoid.Monoid; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.monoid.builtin.First.first; + /** - * A {@link Monoid} instance formed by {@link Optional}<A>. The application to two {@link Optional} - * values produces the last non-empty value, or Optional.empty() if all values are empty. + * A {@link Monoid} instance formed by {@link Maybe}<A>. The application to two {@link Maybe} + * values produces the last non-empty value, or {@link Maybe#nothing()} if all values are empty. * - * @param the Optional value parameter type + * @param the Maybe value parameter type * @see First * @see Present * @see Monoid - * @see Optional + * @see Maybe */ -public final class Last implements Monoid> { - private static final Last INSTANCE = new Last(); +public final class Last implements Monoid> { + private static final Last INSTANCE = new Last<>(); private Last() { } @Override - public Optional identity() { - return Optional.empty(); + public Maybe identity() { + return nothing(); } @Override - public Optional apply(Optional x, Optional y) { - return y.map(Optional::of).orElse(x); + public Maybe checkedApply(Maybe x, Maybe y) { + return first(y, x); } @SuppressWarnings("unchecked") public static Last last() { - return INSTANCE; + return (Last) INSTANCE; } - public static Fn1, Optional> last(Optional x) { + public static Fn1, Maybe> last(Maybe x) { return Last.last().apply(x); } - public static Optional last(Optional x, Optional y) { + public static Maybe last(Maybe x, Maybe y) { return last(x).apply(y); } } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAll.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAll.java index d5fa005a6..ca2f4634b 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAll.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAll.java @@ -27,22 +27,22 @@ * @see RightAll * @see Either */ -public class LeftAll implements MonoidFactory, Either> { +public final class LeftAll implements MonoidFactory, Either> { - private static final LeftAll INSTANCE = new LeftAll(); + private static final LeftAll INSTANCE = new LeftAll<>(); private LeftAll() { } @Override - public Monoid> apply(Monoid lMonoid) { + public Monoid> checkedApply(Monoid lMonoid) { Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.LeftAll.leftAll(lMonoid); return Monoid.>monoid(semigroup, () -> left(lMonoid.identity())); } @SuppressWarnings("unchecked") public static LeftAll leftAll() { - return INSTANCE; + return (LeftAll) INSTANCE; } public static Monoid> leftAll(Monoid lMonoid) { diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAny.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAny.java index a9035d67c..461738188 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAny.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAny.java @@ -29,20 +29,20 @@ */ public final class LeftAny implements MonoidFactory, Either> { - private static final LeftAny INSTANCE = new LeftAny(); + private static final LeftAny INSTANCE = new LeftAny<>(); private LeftAny() { } @Override - public Monoid> apply(Monoid lMonoid) { + public Monoid> checkedApply(Monoid lMonoid) { Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.LeftAny.leftAny(lMonoid); return Monoid.>monoid(semigroup, () -> left(lMonoid.identity())); } @SuppressWarnings("unchecked") public static LeftAny leftAny() { - return INSTANCE; + return (LeftAny) INSTANCE; } public static Monoid> leftAny(Monoid lMonoid) { diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Merge.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Merge.java index 798b6041a..83b3afa41 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Merge.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Merge.java @@ -22,20 +22,20 @@ */ public final class Merge implements BiMonoidFactory, Monoid, Either> { - private static final Merge INSTANCE = new Merge(); + private static final Merge INSTANCE = new Merge<>(); private Merge() { } @Override - public Monoid> apply(Semigroup lSemigroup, Monoid rMonoid) { + public Monoid> checkedApply(Semigroup lSemigroup, Monoid rMonoid) { Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.Merge.merge(lSemigroup, rMonoid); return Monoid.>monoid(semigroup, () -> right(rMonoid.identity())); } @SuppressWarnings("unchecked") public static Merge merge() { - return INSTANCE; + return (Merge) INSTANCE; } public static MonoidFactory, Either> merge(Semigroup lSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java new file mode 100644 index 000000000..fc2f28c95 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java @@ -0,0 +1,81 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hmap.HMap; +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.maybe; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.monoid.builtin.Last.last; +import static com.jnape.palatable.lambda.monoid.builtin.Present.present; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.lenses.MapLens.valueAt; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; + +/** + * A {@link Monoid} instance formed by merging {@link HMap HMaps} using the chosen + * {@link TypeSafeKey} -> {@link Semigroup} + * {@link MergeHMaps#key(TypeSafeKey, Semigroup) mappings}, defaulting to {@link Last} in case no + * {@link Semigroup} has been chosen for a given {@link TypeSafeKey}. + */ +public final class MergeHMaps implements Monoid { + + private final Map, Fn2> bindings; + private final Φ> defaultBinding; + + private MergeHMaps(Map, Fn2> bindings, + Φ> defaultBinding) { + this.bindings = bindings; + this.defaultBinding = defaultBinding; + } + + public MergeHMaps key(TypeSafeKey key, Semigroup semigroup) { + return new MergeHMaps(set(valueAt(key), just(merge(key, present(semigroup))), bindings), defaultBinding); + } + + @Override + public HMap identity() { + return HMap.emptyHMap(); + } + + @Override + public HMap checkedApply(HMap x, HMap y) throws Throwable { + return reduceLeft(asList(x, y)); + } + + @Override + public HMap foldMap(Fn1 fn, Iterable bs) { + return FoldLeft.foldLeft((acc, m) -> FoldLeft.foldLeft((result, k) -> maybe(bindings.get(k)) + .orElseGet(() -> defaultBinding.eliminate(k)) + .apply(result, m), acc, m.keys()), identity(), map(fn, bs)); + } + + public static MergeHMaps mergeHMaps() { + return new MergeHMaps(emptyMap(), new Φ>() { + @Override + public Fn2 eliminate(TypeSafeKey key) { + return merge(key, last()); + } + }); + } + + private static Fn2 merge(TypeSafeKey key, Semigroup> semigroup) { + return (x, y) -> semigroup.apply(x.get(key), y.get(key)) + .fmap(a -> x.put(key, a)) + .orElse(x); + } + + @SuppressWarnings({"NonAsciiCharacters"}) + private interface Φ { + R eliminate(TypeSafeKey key); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeMaps.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeMaps.java new file mode 100644 index 000000000..0f5c5fcc9 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeMaps.java @@ -0,0 +1,61 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.BiMonoidFactory; +import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Map; +import java.util.function.BiFunction; + +/** + * A {@link Monoid} instance formed by {@link java.util.Map#merge(Object, Object, BiFunction)} and a semigroup over + * V. Combines together multiple maps using the provided semigroup for key collisions. + * + * @param The key parameter type of the Map + * @param The value parameter type of the Map + * @see Monoid + * @see java.util.Map + */ +public final class MergeMaps implements BiMonoidFactory>, Semigroup, Map> { + + private static final MergeMaps INSTANCE = new MergeMaps<>(); + + private MergeMaps() { + } + + @Override + public Monoid> checkedApply(Fn0> mFn0, Semigroup semigroup) { + return Monoid.>monoid((x, y) -> { + Map copy = mFn0.apply(); + copy.putAll(x); + y.forEach((k, v) -> copy.merge(k, v, semigroup.toBiFunction())); + return copy; + }, mFn0); + } + + @SuppressWarnings("unchecked") + public static MergeMaps mergeMaps() { + return (MergeMaps) INSTANCE; + } + + public static MonoidFactory, Map> mergeMaps(Fn0> mFn0) { + return MergeMaps.mergeMaps().apply(mFn0); + } + + public static Monoid> mergeMaps(Fn0> mFn0, Semigroup semigroup) { + return mergeMaps(mFn0).apply(semigroup); + } + + public static Fn1, Map> mergeMaps(Fn0> mFn0, Semigroup semigroup, + Map x) { + return mergeMaps(mFn0, semigroup).apply(x); + } + + public static Map mergeMaps(Fn0> mFn0, Semigroup semigroup, Map x, + Map y) { + return mergeMaps(mFn0, semigroup, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Or.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Or.java index 55d1611d5..84c45f5f4 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Or.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Or.java @@ -1,15 +1,19 @@ package com.jnape.palatable.lambda.monoid.builtin; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.monoid.Monoid; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Find.find; + /** * A {@link Monoid} instance formed by Boolean. Equivalent to logical ||. * * @see And * @see Monoid */ -public class Or implements Monoid { +public final class Or implements Monoid, BiPredicate { private static final Or INSTANCE = new Or(); @@ -22,10 +26,20 @@ public Boolean identity() { } @Override - public Boolean apply(Boolean x, Boolean y) { + public Boolean checkedApply(Boolean x, Boolean y) { return x || y; } + @Override + public Boolean foldMap(Fn1 fn, Iterable bs) { + return find(fn, bs).fmap(constantly(true)).orElse(false); + } + + @Override + public Or flip() { + return this; + } + public static Or or() { return INSTANCE; } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java index 371ede8b3..71dc4e95f 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java @@ -1,58 +1,59 @@ package com.jnape.palatable.lambda.monoid.builtin; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; import com.jnape.palatable.lambda.monoid.Monoid; import com.jnape.palatable.lambda.semigroup.Semigroup; +import com.jnape.palatable.lambda.semigroup.builtin.Absent; -import java.util.Optional; -import java.util.function.Function; - +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.monoid.Monoid.monoid; +import static com.jnape.palatable.lambda.monoid.builtin.First.first; /** - * A {@link Monoid} instance formed by {@link Optional}<A> and a semigroup over A. The - * application to two {@link Optional} values is present-biased, such that for a given {@link Optional} x + * A {@link Monoid} instance formed by {@link Maybe}<A> and a semigroup over A. The + * application to two {@link Maybe} values is presence-biased, such that for a given {@link Maybe} x * and y: *

* - * @param the Optional value parameter type + * @param the Maybe value parameter type * @see Monoid - * @see Optional + * @see Absent + * @see Maybe */ -public final class Present implements MonoidFactory, Optional> { +public final class Present implements MonoidFactory, Maybe> { + + private static final Present INSTANCE = new Present<>(); private Present() { } @Override - public Monoid> apply(Semigroup aSemigroup) { - Semigroup> semigroup = (optX, optY) -> { - Function> combine = x -> Optional.of(optY.map(aSemigroup.apply(x)).orElse(x)); - return optX.map(combine).orElse(optY); - }; - return monoid(semigroup, Optional.empty()); + public Monoid> checkedApply(Semigroup aSemigroup) { + return monoid((maybeX, maybeY) -> first(maybeX.fmap(x -> maybeY.fmap(aSemigroup.apply(x)).orElse(x)), maybeY), + nothing()); } + @SuppressWarnings("unchecked") public static Present present() { - return new Present<>(); + return (Present) INSTANCE; } - public static Monoid> present(Semigroup semigroup) { + public static Monoid> present(Semigroup semigroup) { return Present.present().apply(semigroup); } - public static Fn1, Optional> present(Semigroup aSemigroup, Optional x) { + public static Fn1, Maybe> present(Semigroup aSemigroup, Maybe x) { return present(aSemigroup).apply(x); } - public static Optional present(Semigroup semigroup, Optional x, Optional y) { + public static Maybe present(Semigroup semigroup, Maybe x, Maybe y) { return present(semigroup, x).apply(y); } } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/PutAll.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/PutAll.java index b5c4e7ef8..b3462f175 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/PutAll.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/PutAll.java @@ -25,7 +25,7 @@ public HMap identity() { } @Override - public HMap apply(HMap x, HMap y) { + public HMap checkedApply(HMap x, HMap y) { return x.putAll(y); } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RightAll.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RightAll.java index f68fb82e2..6968ea72c 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RightAll.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RightAll.java @@ -33,7 +33,7 @@ private RightAll() { } @Override - public Monoid> apply(Monoid rMonoid) { + public Monoid> checkedApply(Monoid rMonoid) { Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.RightAll.rightAll(rMonoid); return Monoid.>monoid(semigroup, () -> right(rMonoid.identity())); } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RightAny.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RightAny.java index ad812cffd..277ec5163 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RightAny.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RightAny.java @@ -29,23 +29,23 @@ */ public final class RightAny implements MonoidFactory, Either> { - private static final RightAny INSTANCE = new RightAny(); + private static final RightAny INSTANCE = new RightAny<>(); private RightAny() { } @Override - public Monoid> apply(Monoid rMonoid) { + public Monoid> checkedApply(Monoid rMonoid) { Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.RightAny.rightAny(rMonoid); return Monoid.>monoid(semigroup, () -> right(rMonoid.identity())); } @SuppressWarnings("unchecked") public static RightAny rightAny() { - return INSTANCE; + return (RightAny) INSTANCE; } - public static Semigroup> rightAny(Monoid rMonoid) { + public static Monoid> rightAny(Monoid rMonoid) { return RightAny.rightAny().apply(rMonoid); } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RunAll.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RunAll.java new file mode 100644 index 000000000..7533f9c41 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RunAll.java @@ -0,0 +1,47 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monoid.Monoid.monoid; + +/** + * Run {@link IO} operations, aggregating their results in terms of the provided {@link Monoid}. + * + * @param the {@link IO} result + * @see com.jnape.palatable.lambda.semigroup.builtin.RunAll + */ +public final class RunAll implements MonoidFactory, IO> { + + private static final RunAll INSTANCE = new RunAll<>(); + + private RunAll() { + } + + @Override + public Monoid> checkedApply(Monoid monoid) { + Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.RunAll.runAll(monoid); + return monoid(semigroup, io(monoid.identity())); + } + + @SuppressWarnings("unchecked") + public static RunAll runAll() { + return (RunAll) INSTANCE; + } + + public static Monoid> runAll(Monoid monoid) { + return RunAll.runAll().apply(monoid); + } + + public static Fn1, IO> runAll(Monoid monoid, IO x) { + return runAll(monoid).apply(x); + } + + public static IO runAll(Monoid monoid, IO x, IO y) { + return runAll(monoid, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Trivial.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Trivial.java new file mode 100644 index 000000000..bf77b65f1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Trivial.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Constantly; +import com.jnape.palatable.lambda.monoid.Monoid; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; + +/** + * The trivial {@link Unit} {@link Monoid} formed under {@link Constantly constantly}. + */ +public final class Trivial implements Monoid { + + private static final Trivial INSTANCE = new Trivial(); + + private Trivial() { + } + + @Override + public Unit identity() { + return UNIT; + } + + @Override + public Unit checkedApply(Unit x, Unit y) throws Throwable { + return y; + } + + public static Trivial trivial() { + return INSTANCE; + } + + public static Fn1 trivial(Unit x) { + return trivial().apply(x); + } + + public static Unit trivial(Unit x, Unit y) { + return trivial(x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Union.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Union.java new file mode 100644 index 000000000..283bd2980 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Union.java @@ -0,0 +1,45 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Distinct; +import com.jnape.palatable.lambda.internal.iteration.UnioningIterable; +import com.jnape.palatable.lambda.monoid.Monoid; + +import java.util.Collections; + +/** + * Given two {@link Iterable Iterables} xs and ys, return the {@link Concat concatenation} of + * the {@link Distinct distinct} elements of both xs and ys. + * + * @param the {@link Iterable} element type + */ +public final class Union implements Monoid> { + + private static final Union INSTANCE = new Union<>(); + + private Union() { + } + + @Override + public Iterable identity() { + return Collections::emptyIterator; + } + + @Override + public Iterable checkedApply(Iterable xs, Iterable ys) { + return new UnioningIterable<>(xs, ys); + } + + @SuppressWarnings("unchecked") + public static Union union() { + return (Union) INSTANCE; + } + + public static Fn1, Iterable> union(Iterable xs) { + return Union.union().apply(xs); + } + + public static Iterable union(Iterable xs, Iterable ys) { + return union(xs).apply(ys); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java new file mode 100644 index 000000000..8ed724ad6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java @@ -0,0 +1,49 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.monoid.Monoid; + +/** + * Logical exclusive-or. Equivalent to logical ^. + *

+ * Note that this implementation behaves as a cascade of binary exclusive-or operations, as is the only possible + * monoidal behavior when applied to an unknown number of inputs. + * + * @see Or + * @see And + */ +public final class Xor implements Monoid, BiPredicate { + + private static final Xor INSTANCE = new Xor(); + + private Xor() { + } + + @Override + public Boolean identity() { + return false; + } + + @Override + public Boolean checkedApply(Boolean x, Boolean y) { + return x ^ y; + } + + @Override + public Xor flip() { + return this; + } + + public static Xor xor() { + return INSTANCE; + } + + public static Fn1 xor(Boolean x) { + return xor().apply(x); + } + + public static Boolean xor(Boolean x, Boolean y) { + return xor(x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/Iso.java b/src/main/java/com/jnape/palatable/lambda/optics/Iso.java new file mode 100644 index 000000000..04c21ccfb --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/Iso.java @@ -0,0 +1,410 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.functor.builtin.Exchange; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.optics.functions.Over; +import com.jnape.palatable.lambda.optics.functions.Set; +import com.jnape.palatable.lambda.optics.functions.View; + +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.optics.Iso.Simple.adapt; +import static com.jnape.palatable.lambda.optics.Lens.lens; +import static com.jnape.palatable.lambda.optics.Optic.optic; +import static com.jnape.palatable.lambda.optics.functions.View.view; + +/** + * An {@link Iso} (short for "isomorphism") is an invertible {@link Lens}: an {@link Optic} encoding of a + * bi-directional focusing of two types, and like {@link Lens}es, can be {@link View}ed, + * {@link Set}, and {@link Over updated}. + *

+ * As an example, consider the isomorphism between valid {@link String}s and {@link Integer}s: + *

+ * {@code
+ * Iso stringIntIso = Iso.iso(Integer::parseInt, Object::toString);
+ * Integer asInt = view(stringIntIso, "123"); // 123
+ * String asString = view(stringIntIso.mirror(), 123); // "123"
+ * }
+ * 
+ * In the previous example, stringIntIso can be viewed as an + * {@link Optic}<String, String, Integer, Integer>, and can be {@link Iso#mirror}ed and + * viewed as a {@link Optic}<Integer, Integer, String, String>. + *

+ * As with {@link Lens}, variance is supported between S/T and A/B, and where these pairs do + * not vary, a {@link Simple} iso can be used (for instance, in the previous example, stringIntIso could + * have had the simplified Iso.Simple<String, Integer> type). + *

+ * For more information, read about + * isos. + * + * @param the larger type for focusing + * @param the larger type for mirrored focusing + * @param the smaller type for focusing + * @param the smaller type for mirrored focusing + * @see Optic + * @see Lens + */ +@FunctionalInterface +public interface Iso extends + Optic, Functor, S, T, A, B>, + MonadRec>, + Profunctor> { + + /** + * Convert this {@link Iso} into a {@link Lens}. + * + * @return the equivalent lens + */ + default Lens toLens() { + return lens(this); + } + + /** + * Flip this {@link Iso} around. + * + * @return the mirrored {@link Iso} + */ + default Iso mirror() { + return unIso().into((sa, bt) -> iso(bt, sa)); + } + + /** + * Destructure this {@link Iso} into the two functions S -< A and B -< T that + * constitute the isomorphism. + * + * @return the destructured iso + */ + default Tuple2, Fn1> unIso() { + return Tuple2.fill(this., Identity, + Identity, + Identity, + Exchange>, + Exchange>>apply(new Exchange<>(id(), Identity::new)).diMapR(Identity::runIdentity)) + .biMap(e -> fn1(e.sa()), e -> fn1(e.bt())); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso pure(U u) { + return iso(view(this), constantly(u)); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso zip(Applicative, Iso> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("RedundantTypeArguments") + default Iso flatMap(Fn1>> fn) { + return unIso().>fmap(bt -> Fn2.curried( + fn1(bt.fmap(fn.>fmap(Monad>::coerce)) + .fmap(Iso::unIso) + .fmap(Tuple2::_2) + .fmap(Fn1::fn1)))) + .fmap(Fn2::uncurry) + .fmap(bbu -> bbu.diMapL(Tuple2::fill)) + .into(Iso::iso); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso trampolineM( + Fn1, Iso>> fn) { + return unIso().into((sa, bt) -> iso( + sa, + Fn1.fn1(bt).trampolineM(t -> fn1(fn.apply(t)., A, B>>coerce() + .unIso()._2())))); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso diMapL(Fn1 fn) { + return (Iso) Profunctor.super.diMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso diMapR(Fn1 fn) { + return (Iso) Profunctor.super.diMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso diMap(Fn1 lFn, + Fn1 rFn) { + return this.mapS(lFn).mapT(rFn); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso contraMap(Fn1 fn) { + return (Iso) Profunctor.super.contraMap(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso mapS(Fn1 fn) { + return iso(Optic.super.mapS(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso mapT(Fn1 fn) { + return iso(Optic.super.mapT(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso mapA(Fn1 fn) { + return iso(Optic.super.mapA(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso mapB(Fn1 fn) { + return iso(Optic.super.mapB(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso andThen(Optic, ? super Functor, A, B, Z, C> f) { + return iso(Optic.super.andThen(f)); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso compose(Optic, ? super Functor, R, U, S, T> g) { + return iso(Optic.super.compose(g)); + } + + /** + * Static factory method for creating an iso from a function and it's inverse. + * + * @param f the function + * @param g f's inverse + * @param the larger type for focusing + * @param the larger type for mirrored focusing + * @param the smaller type for focusing + * @param the smaller type for mirrored focusing + * @return the iso + */ + static Iso iso(Fn1 f, Fn1 g) { + return iso(optic(pafb -> pafb.diMap(f, fb -> fb.fmap(g)))); + } + + /** + * Promote an optic with compatible bounds to an {@link Iso}. + * + * @param optic the {@link Optic} + * @param the larger type for focusing + * @param the larger type for mirrored focusing + * @param the smaller type for focusing + * @param the smaller type for mirrored focusing + * @return the {@link Iso} + */ + static Iso iso( + Optic, ? super Functor, S, T, A, B> optic) { + return new Iso() { + @Override + public >, + CoF extends Functor>, FB extends Functor, + FT extends Functor, PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return optic.apply(pafb); + } + }; + } + + /** + * Static factory method for creating a simple {@link Iso} from a function and its inverse. + * + * @param f a function + * @param g f's inverse + * @param one side of the isomorphism + * @param the other side of the isomorphism + * @return the simple iso + */ + static Iso.Simple simpleIso(Fn1 f, Fn1 g) { + return adapt(iso(f, g)); + } + + /** + * The canonical {@link Pure} instance for {@link Iso}. + * + * @param sa one side of the isomorphism + * @param the larger type for focusing + * @param the smaller type for focusing + * @param the smaller type for mirrored focusing + * @return the {@link Pure} instance + */ + static Pure> pureIso(Fn1 sa) { + return new Pure>() { + @Override + public Iso checkedApply(T t) { + return iso(sa, constantly(t)); + } + }; + } + + /** + * A convenience type with a simplified type signature for common isos with both unified "larger" values and + * unified "smaller" values. + * + * @param the type of both "larger" values + * @param the type of both "smaller" values + */ + @FunctionalInterface + interface Simple extends Iso, Optic.Simple, Functor, S, A> { + + /** + * Compose two simple isos from right to left. + * + * @param g the other simple iso + * @param the other simple iso' larger type + * @return the composed simple iso + */ + @SuppressWarnings("overloads") + default Iso.Simple compose(Iso.Simple g) { + return Iso.Simple.adapt(Iso.super.compose(g)); + } + + /** + * Compose two simple isos from left to right. + * + * @param f the other simple iso + * @param the other simple iso' smaller type + * @return the composed simple iso + */ + @SuppressWarnings("overloads") + default Iso.Simple andThen(Iso.Simple f) { + return Iso.Simple.adapt(f.compose(this)); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso.Simple mirror() { + return Iso.Simple.adapt(Iso.super.mirror()); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens.Simple toLens() { + return Lens.Simple.adapt(Iso.super.toLens()); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso.Simple discardR(Applicative> appB) { + return Iso.Simple.adapt(Iso.super.discardR(appB)); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso.Simple andThen(Optic.Simple, ? super Functor, A, B> f) { + return Iso.Simple.adapt(Iso.super.andThen(f)); + } + + /** + * {@inheritDoc} + */ + @Override + default Iso.Simple compose(Optic.Simple, ? super Functor, R, S> g) { + return Iso.Simple.adapt(Iso.super.compose(g)); + } + + /** + * Adapt an {@link Optic} with the right variance to an {@link Iso.Simple}. + * + * @param optic the optic + * @param S/T + * @param A/B + * @return the simple iso + */ + static Iso.Simple adapt( + Optic, ? super Functor, S, S, A, A> optic) { + return new Iso.Simple() { + @Override + public >, + CoF extends Functor>, FB extends Functor, + FT extends Functor, PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return optic.apply(pafb); + } + }; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/optics/Lens.java b/src/main/java/com/jnape/palatable/lambda/optics/Lens.java new file mode 100644 index 000000000..f2b0c23d6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/Lens.java @@ -0,0 +1,476 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn2.Both; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Cartesian; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.Fn2.curried; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.optics.Iso.iso; +import static com.jnape.palatable.lambda.optics.Lens.Simple.adapt; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; + +/** + * An approximation of van Laarhoven lenses. + *

+ * A "lens" can be considered in its simplest form as the conjugation of a "getter" and a "setter"; that is, a + * unification type representing the way to retrieve a "smaller" value A from a "larger" value + * S, as well as a way to update a "smaller" value B of a "larger" value S, + * producing another "larger" value T. + *

+ * Consider the following example: + *

+ * {@code
+ * public final class Person {
+ *     private final int age;
+ *
+ *     public Person(int age) {
+ *         this.age = age;
+ *     }
+ *
+ *     public int getAge() {
+ *         return age;
+ *     }
+ *
+ *     public Person setAge(int age) {
+ *         return new Person(age);
+ *     }
+ * }
+ * }
+ * 
+ * A lens that focused on the age field of an instance of Person might look like this: + *
+ * {@code
+ * Lens ageLens = Lens.lens(Person::getAge, Person::setAge);
+ *
+ * Person adult = new Person(18);
+ * Integer age = view(ageLens, adult); // 18
+ *
+ * Person olderAdult = set(ageLens, 19, adult);
+ * Integer olderAge = view(ageLens, olderAdult); // 19
+ * }
+ * 
+ * The pattern of a getter and setter that mutually agree on both A and B as well as on both + * S and T is so common that this can be given a simplified type signature: + *
+ * {@code
+ * Lens.Simple ageLens = Lens.simpleLens(Person::getAge, Person::setAge);
+ *
+ * Person adult = new Person(18);
+ * Integer age = view(ageLens, adult); // 18
+ *
+ * Person olderAdult = set(ageLens, 19, adult);
+ * Integer olderAge = view(ageLens, olderAdult); // 19
+ * }
+ * 
+ * However, consider if age could be updated on a Person by being provided a date of birth, in + * the form of a LocalDate: + *
+ * {@code
+ * public final class Person {
+ *     private final int age;
+ *
+ *     public Person(int age) {
+ *         this.age = age;
+ *     }
+ *
+ *     public int getAge() {
+ *         return age;
+ *     }
+ *
+ *     public Person setAge(int age) {
+ *         return new Person(age);
+ *     }
+ *
+ *     public Person setAge(LocalDate dob) {
+ *         return setAge((int) YEARS.between(dob, LocalDate.now()));
+ *     }
+ * }
+ * }
+ * 
+ * This is why Lens has both an A and a B: A is the value for "getting", and + * B is the potentially different value for "setting". This distinction makes lenses powerful enough to + * express the more complicated setAge case naturally: + *
+ * {@code
+ * Lens ageDobLens = Lens.lens(Person::getAge, Person::setAge);
+ *
+ * Person adult = new Person(18);
+ * Integer age = view(ageDobLens, adult); // 18
+ *
+ * Person olderAdult = set(ageDobLens, LocalDate.of(1997, 1, 1), adult);
+ * Integer olderAge = view(ageDobLens, olderAdult); // 19 at the time of this writing...anyone else feel old?
+ * }
+ * 
+ * Additionally, we might imagine a lens that produces a different "larger" value on updating than what was given. + * Consider a lens that reads the first string from a list, but produces a Set of strings on update: + *
+ * {@code
+ * Lens, Set, String, String> lens = Lens.lens(
+ *         l -> l.get(0),
+ *         (l, s) -> {
+ *             List copy = new ArrayList<>(l);
+ *             copy.set(0, s);
+ *             return new HashSet<>(copy);
+ *         });
+ *
+ * String firstElement = view(lens, asList("foo", "bar")); // "foo
+ * System.out.println(firstElement);
+ *
+ * set(lens, "oof", asList("foo", "bar")); // ["bar", "oof"]
+ * set(lens, "bar", asList("foo", "bar")); // ["bar"]
+ * }
+ * 
+ * For more information, learn + * about + * lenses. + * + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @see Optic + * @see Iso + */ +@FunctionalInterface +public interface Lens extends + Optic, Functor, S, T, A, B>, + MonadRec>, + Profunctor> { + + /** + * {@inheritDoc} + */ + @Override + default Lens fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens pure(U u) { + return lens(view(this), (s, b) -> u); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens zip(Applicative, Lens> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens flatMap(Fn1>> f) { + + return lens(view(this), (s, b) -> set(f.apply(set(this, b, s)).>coerce(), b, s)); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens trampolineM( + Fn1, Lens>> fn) { + return lens(view(this), + curried(set(this).flip().flatMap(bt -> fn1(s -> bt + .trampolineM(t -> set(fn.apply(t)., A, B>>coerce()) + .flip().apply(s)))))); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens diMapL(Fn1 fn) { + return (Lens) Profunctor.super.diMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens diMapR(Fn1 fn) { + return (Lens) Profunctor.super.diMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens diMap(Fn1 lFn, Fn1 rFn) { + return this.mapS(lFn).mapT(rFn); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens contraMap(Fn1 fn) { + return (Lens) Profunctor.super.contraMap(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens mapS(Fn1 fn) { + return lens(Optic.super.mapS(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens mapT(Fn1 fn) { + return lens(Optic.super.mapT(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens mapA(Fn1 fn) { + return lens(Optic.super.mapA(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens mapB(Fn1 fn) { + return lens(Optic.super.mapB(fn)); + } + + /** + * Produce an {@link Iso} from this {@link Lens} by providing a default S value. + * + * @param s the default S + * @return an {@link Iso} + */ + default Iso toIso(S s) { + return iso(view(this), set(this).flip().apply(s)); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens andThen(Optic, ? super Functor, A, B, C, D> f) { + return lens(Optic.super.andThen(f)); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens compose(Optic, ? super Functor, R, U, S, T> g) { + return lens(Optic.super.compose(g)); + } + + /** + * Static factory method for creating a lens from a getter function and a setter function. + * + * @param getter the getter function + * @param setter the setter function + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the lens + */ + static Lens lens(Fn1 getter, + Fn2 setter) { + return lens(Optic., Functor, + S, T, A, B, + Functor>, + Functor>, + Cartesian>, ? extends Cartesian>, + Cartesian>, ? extends Cartesian>>optic( + afb -> afb.cartesian().diMap(s -> tuple(s, getter.apply(s)), + into((s, fb) -> fb.fmap(setter.apply(s)))))); + } + + /** + * Promote an optic with compatible bounds to a {@link Lens}. + * + * @param optic the {@link Optic} + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the {@link Lens} + */ + static Lens lens( + Optic, ? super Functor, S, T, A, B> optic) { + return new Lens() { + @Override + public >, + CoF extends Functor>, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return optic.apply(pafb); + } + }; + } + + /** + * Static factory method for creating a simple lens from a getter function and a setter function. + * + * @param getter the getter function + * @param setter the setter function + * @param the type of both "larger" values + * @param the type of both "smaller" values + * @return the lens + */ + static Lens.Simple simpleLens(Fn1 getter, + Fn2 setter) { + return adapt(lens(getter, setter)); + } + + /** + * Dually focus on two lenses at the same time. Requires S and T to be invariant between + * lenses. + * + * @param f the first lens + * @param g the second lens + * @param both larger values + * @param f's smaller viewing value + * @param g's smaller viewing value + * @param f's smaller setting value + * @param g's smaller setting value + * @return the dual-focus lens + */ + static Lens, Tuple2> both(Lens f, Lens g) { + return lens(Both.both(view(f), view(g)), (s, cd) -> cd.biMap(set(f), set(g)).into(Fn1::contraMap).apply(s)); + } + + /** + * Dually focus on two simple lenses at the same time. + * + * @param f the first lens + * @param g the second lens + * @param both larger values + * @param both smaller viewing values + * @param both smaller setting values + * @return the dual-focus simple lens + */ + static Lens.Simple> both(Lens.Simple f, Lens.Simple g) { + return adapt(both((Lens) f, g)); + } + + /** + * The canonical {@link Pure} instance for {@link Lens}. + * + * @param sa the getting function + * @param the type of the "larger" value for reading + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the {@link Pure} instance + */ + static Pure> pureLens(Fn1 sa) { + return new Pure>() { + @Override + public Lens checkedApply(T t) { + return lens(sa, (s, b) -> t); + } + }; + } + + /** + * A convenience type with a simplified type signature for common lenses with both unified "larger" values and + * unified "smaller" values. + * + * @param the type of both "larger" values + * @param the type of both "smaller" values + */ + @FunctionalInterface + interface Simple extends Lens, Optic.Simple, Functor, S, A> { + + /** + * {@inheritDoc} + */ + @Override + default Lens.Simple andThen(Optic.Simple, ? super Functor, A, B> f) { + return Lens.Simple.adapt(Lens.super.andThen(f)); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens.Simple compose(Optic.Simple, ? super Functor, R, S> g) { + return Lens.Simple.adapt(Lens.super.compose(g)); + } + + /** + * Adapt a {@link Lens} with the right variance to a {@link Lens.Simple}. + * + * @param lens the lens + * @param S/T + * @param A/B + * @return the simple lens + */ + static Lens.Simple adapt( + Optic, ? super Functor, S, S, A, A> lens) { + return new Lens.Simple() { + @Override + public >, + CoF extends Functor>, FB extends Functor, + FT extends Functor, PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return lens.apply(pafb); + } + }; + } + + /** + * Specialization of {@link Lens#both(Lens, Lens)} for simple lenses. + * + * @param f the first lens + * @param g the second lens + * @param both lens larger values + * @param lens f smaller values + * @param lens g smaller values + * @return the dual-focus simple lens + */ + static Lens.Simple> both(Lens f, Lens g) { + return Lens.Simple.adapt(Lens.both(f, g)); + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/Optic.java b/src/main/java/com/jnape/palatable/lambda/optics/Optic.java new file mode 100644 index 000000000..d0dc2d625 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/Optic.java @@ -0,0 +1,304 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; + +/** + * A generic supertype representation for profunctor optics. + *

+ * Precisely stated, for some {@link Profunctor} P and some {@link Functor} F, and for the + * types S T A B, an + * {@link Optic}<P, F, S, T, A, B> is a polymorphic function + * P<A, F<B>> -> P<S, F<T>>. + * + * @param

, F extends Functor, S, T, A, B> { + + /** + * The polymorphic arrow between profunctors in this optic interface. + * + * @param pafb the input + * @param the profunctor type constraint witnessed by the application of this optic + * @param the functor type constraint witnessed by the application of this optic + * @param the covariant parameter type of the input profunctor + * @param the covariant parameter type of the output profunctor + * @param the full input type + * @param the full output type + * @return the output profunctor + */ + , + CoF extends Functor, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb); + + /** + * Produce a monomorphic {@link Fn1} backed by this {@link Optic}. + * + * @param the covariant bound on P + * @param the covariant bound on F + * @param fixed functor over B for inference + * @param fixed functor over T for inference + * @param the fixed input profunctor type + * @param the fixed output profunctor type + * @return the monomorphic {@link Fn1} backed by this {@link Optic} + */ + default , + CoF extends Functor, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> + Fn1 monomorphize() { + return this::apply; + } + + /** + * Left-to-right composition of optics. Requires compatibility between S and T. + * + * @param f the other optic + * @param the new left side of the input profunctor + * @param the new right side's functor embedding of the input profunctor + * @return the composed optic + */ + default Optic andThen(Optic f) { + return new Optic() { + @Override + public , + CoF extends Functor, + FC extends Functor, + FT extends Functor, + PZFC extends Profunctor, + PSFT extends Profunctor> + PSFT apply(PZFC pzfc) { + return Optic.this.apply(f.apply(pzfc)); + } + }; + } + + /** + * Right-to-Left composition of optics. Requires compatibility between A and B. + * + * @param g the other optic + * @param the new left side of the output profunctor + * @param the new right side's functor embedding of the output profunctor + * @return the composed optic + */ + default Optic compose(Optic g) { + return new Optic() { + @Override + public , + CoF extends Functor, + FB extends Functor, + FU extends Functor, + PAFB extends Profunctor, + PRFU extends Profunctor> + PRFU apply(PAFB pafb) { + return g., FU, + Profunctor, ? extends CoP>, PRFU>apply(Optic.this.apply(pafb)); + } + }; + } + + /** + * Contravariantly map S to R, yielding a new optic. + * + * @param fn the mapping function + * @param the new left side of the output profunctor + * @return the new optic + */ + default Optic mapS(Fn1 fn) { + return optic(pafb -> { + Profunctor, ? extends P> psft = apply(pafb); + return psft.diMapL(fn); + }); + } + + /** + * Covariantly map T to U, yielding a new optic. + * + * @param fn the mapping function + * @param the new right side's functor embedding of the output profunctor + * @return the new optic + */ + default Optic mapT(Fn1 fn) { + return optic(pafb -> { + Profunctor, ? extends P> psft = apply(pafb); + return psft.diMapR(ft -> ft.fmap(fn)); + }); + } + + /** + * Covariantly map A to C, yielding a new optic. + * + * @param fn the mapping function + * @param the new left side of the input profunctor + * @return the new optic + */ + default Optic mapA(Fn1 fn) { + return optic(pcfb -> { + @SuppressWarnings("UnnecessaryLocalVariable") + Profunctor, ? extends P> psft = apply(pcfb.diMapL(fn)); + return psft; + }); + } + + /** + * Contravariantly map B to Z, yielding a new optic. + * + * @param the new right side's functor embedding of the input profunctor + * @param fn the mapping function + * @return the new optic + */ + default Optic mapB(Fn1 fn) { + return optic(pafz -> apply(pafz.diMapR(fz -> fz.fmap(fn)))); + } + + /** + * Promote a monomorphic function to a compatible {@link Optic}. + * + * @param fn the function + * @param

the {@link Profunctor} bound + * @param the {@link Functor} bound + * @param the left side of the output profunctor + * @param the right side's functor embedding of the output profunctor + * @param the left side of the input profunctor + * @param the right side's functor embedding of the input profunctor + * @param fixed functor over B for inference + * @param fixed functor over T for inference + * @param the input + * @param the output + * @return the {@link Optic} + */ + static

, + F extends Functor, + S, T, A, B, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> Optic optic(Fn1 fn) { + return new Optic() { + @Override + @SuppressWarnings("unchecked") + public , + CoF extends Functor, + CoFB extends Functor, + CoFT extends Functor, + CoPAFB extends Profunctor, + CoPSFT extends Profunctor> CoPSFT apply( + CoPAFB pafb) { + return (CoPSFT) fn.apply((PAFB) pafb); + } + }; + } + + /** + * Reframe an {@link Optic} according to covariant bounds. + * + * @param optic the {@link Optic} + * @param

the {@link Profunctor} type + * @param the {@link Functor} type + * @param the left side of the output profunctor + * @param the right side's functor embedding of the output profunctor + * @param the left side of the input profunctor + * @param the right side's functor embedding of the input profunctor + * @return the covariantly reframed {@link Optic} + */ + static

, + F extends Functor, + S, T, A, B> Optic reframe(Optic optic) { + return Optic.optic(optic., + Functor, + Profunctor, ? extends P>, + Profunctor, ? extends P>>monomorphize()); + } + + /** + * An convenience type with a simplified signature for {@link Optic optics} with unified S/T and + * A/B types. + * + * @param

the {@link Profunctor} bound + * @param the {@link Functor} bound + * @param the left side and right side's functor embedding of the output profunctor + * @param the left side and right side's functor embedding of the input profunctor + */ + interface Simple

, F extends Functor, S, A> + extends Optic { + + /** + * Compose two simple optics from left to right. + * + * @param f the other simple optic + * @param the new left side and right side's functor embedding of the input profunctor + * @return the composed simple optic + */ + @SuppressWarnings("overloads") + default Optic.Simple andThen(Optic.Simple f) { + Optic composed = Optic.super.andThen(f); + return new Simple() { + @Override + public , CoF extends Functor, + FB extends Functor, FT extends Functor, + PAFB extends Profunctor, PSFT extends Profunctor> + PSFT apply(PAFB pafb) { + return composed.apply(pafb); + } + }; + } + + /** + * Compose two simple optics from right to left. + * + * @param g the other simple optic + * @param the new left side and right side's functor embedding of the output profunctor + * @return the composed simple optic + */ + @SuppressWarnings("overloads") + default Optic.Simple compose(Optic.Simple g) { + Optic composed = Optic.super.compose(g); + return new Simple() { + @Override + public , CoF extends Functor, + FB extends Functor, FT extends Functor, + PAFB extends Profunctor, PSFT extends Profunctor> + PSFT apply(PAFB pafb) { + return composed.apply(pafb); + } + }; + } + + /** + * Adapt an {@link Optic} with S/T and A/B unified into a {@link Simple simple optic}. + * + * @param optic the {@link Optic} + * @param

the {@link Profunctor} bound + * @param the {@link Functor} bound + * @param the left side and the right side's functor embedding of the output profunctor + * @param the left side and the right side's functor embedding of the input profunctor + * @return the {@link Simple} optic + */ + static

, + F extends Functor, + S, A> Simple adapt(Optic optic) { + return new Simple() { + @Override + public , CoF extends Functor, + FB extends Functor, FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return optic.apply(pafb); + } + }; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/Prism.java b/src/main/java/com/jnape/palatable/lambda/optics/Prism.java new file mode 100644 index 000000000..698860cbf --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/Prism.java @@ -0,0 +1,435 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Cocartesian; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.functor.builtin.Market; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.optics.functions.Matching; +import com.jnape.palatable.lambda.optics.functions.Pre; +import com.jnape.palatable.lambda.optics.functions.Re; +import com.jnape.palatable.lambda.optics.functions.View; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; +import static com.jnape.palatable.lambda.optics.Prism.Simple.adapt; +import static com.jnape.palatable.lambda.optics.functions.Matching.matching; +import static com.jnape.palatable.lambda.optics.functions.Re.re; +import static com.jnape.palatable.lambda.optics.functions.View.view; + +/** + * Prisms are {@link Iso Isos} that can fail in one direction. Example: + *

+ * {@code
+ * Prism parseInt =
+ *     prism(str -> Either.trying(() -> Integer.parseInt(str),
+ *                                constantly(str)),
+ *           Object::toString);
+ *
+ * String         str   = view(re(parseInt), 123); // "123"
+ * Maybe works = view(pre(parseInt), "123"); // Just 123
+ * Maybe fails = view(pre(parseInt), "foo"); // Nothing
+ * }
+ * 
+ *

+ * Note that because a {@link Prism} might fail in one direction, it cannot be immediately used for + * {@link View viewing}; however, the combinators {@link Re re}, {@link Pre pre}, and {@link Matching matching} can all + * be used to provide the additional context to a {@link Prism} so it can be used for viewing. + * + * @param the input that might fail to map to its output + * @param the guaranteed output + * @param the output that might fail to be produced + * @param the input that guarantees its output + */ +@FunctionalInterface +public interface Prism extends + ProtoOptic, S, T, A, B>, + MonadRec>, + Profunctor> { + + /** + * Recover the two mappings encapsulated by this {@link Prism} by sending it through a {@link Market}. + * + * @return a {@link Tuple2 tuple} of the two mappings encapsulated by this {@link Prism} + */ + default Tuple2, Fn1>> unPrism() { + return both(Re.re().fmap(view()), matching(), this); + } + + /** + * {@inheritDoc} + */ + @Override + default >, + CoF extends Functor>, + FB extends Functor, FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + @SuppressWarnings("RedundantTypeArguments") + Optic, Identity, S, T, A, B> optic = this.>toOptic(Identity::new); + return optic.apply(pafb); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism andThen(ProtoOptic, A, B, Z, C> f) { + return prism(ProtoOptic.super.andThen(f)); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism compose(ProtoOptic, R, U, S, T> g) { + return prism(ProtoOptic.super.compose(g)); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism mapS(Fn1 fn) { + return prism(ProtoOptic.super.mapS(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism mapT(Fn1 fn) { + return prism(ProtoOptic.super.mapT(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism mapA(Fn1 fn) { + return prism(ProtoOptic.super.mapA(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism mapB(Fn1 fn) { + return prism(ProtoOptic.super.mapB(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism pure(U u) { + return prism(constantly(left(u)), constantly(u)); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism flatMap(Fn1>> f) { + return unPrism() + .into((bt, seta) -> Prism.prism( + s -> seta.apply(s).>match(t -> matching(f.apply(t).>coerce(), s), + Either::right), + b -> View.view(re(f.apply(bt.apply(b)).coerce())).apply(b))); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism zip(Applicative, Prism> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip( + Lazy, Prism>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism trampolineM( + Fn1, Prism>> fn) { + Market absu = unPrism().into(Market::new) + .trampolineM(t -> fn.apply(t)., A, B>>coerce() + .unPrism() + .into(Market>::new)); + return prism(absu.sta(), absu.bt()); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism diMap(Fn1 lFn, + Fn1 rFn) { + return unPrism().into((bt, seta) -> prism(seta.diMap(lFn, tOrA -> tOrA.biMapL(rFn)), bt.fmap(rFn))); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism diMapL(Fn1 fn) { + return (Prism) Profunctor.super.diMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism diMapR(Fn1 fn) { + return (Prism) Profunctor.super.diMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + default Prism contraMap(Fn1 fn) { + return (Prism) Profunctor.super.contraMap(fn); + } + + /** + * Static factory method for creating a {@link Prism} given a mapping from + * S -> {@link Either}<T, A> and a mapping from B -> T. + * + * @param sta the mapping from S -> {@link Either}<T, A> + * @param bt the mapping from B -> T + * @param the input that might fail to map to its output + * @param the guaranteed output + * @param the output that might fail to be produced + * @param the input that guarantees its output + * @return the {@link Prism} + */ + static Prism prism(Fn1> sta, + Fn1 bt) { + return new Prism() { + @Override + public > Optic, F, S, T, A, B> toOptic(Pure pure) { + return Optic., + F, + S, T, A, B, + Functor, + Functor, + Cocartesian, ?>, + Cocartesian, ?>>optic(pafb -> pafb.cocartesian() + .diMap(s -> sta.apply(s).match(Choice2::a, Choice2::b), + tOrFb -> tOrFb.>match(pure::apply, fb -> fb.fmap(bt)))); + } + }; + } + + /** + * Promote a {@link ProtoOptic} with compatible bounds to an {@link Prism}. + * + * @param protoOptic the {@link ProtoOptic} + * @param the input that might fail to map to its output + * @param the guaranteed output + * @param the output that might fail to be produced + * @param the input that guarantees its output + * @return the {@link Prism} + */ + static Prism prism(ProtoOptic, S, T, A, B> protoOptic) { + return new Prism() { + @Override + public > Optic, F, S, T, A, B> toOptic(Pure pure) { + Optic, F, S, T, A, B> optic = protoOptic.toOptic(pure); + return Optic.reframe(optic); + } + }; + } + + /** + * Promote an {@link Optic} with compatible bounds to an {@link Prism}. Note that because the {@link Optic} must + * guarantee an unbounded {@link Functor} constraint in order to satisfy any future covariant constraint, the + * resulting {@link Prism prism's} toOptic method will never need to consult its given + * {@link Pure lifting} function. + * + * @param optic the {@link Optic} + * @param the input that might fail to map to its output + * @param the guaranteed output + * @param the output that might fail to be produced + * @param the input that guarantees its output + * @return the {@link Prism} + */ + static Prism prism( + Optic, ? super Functor, S, T, A, B> optic) { + return new Prism() { + @Override + public > Optic, F, S, T, A, B> toOptic(Pure pure) { + return Optic.reframe(optic); + } + }; + } + + /** + * Static factory method for creating a simple {@link Prism} from a function and its potentially failing inverse. + * + * @param sMaybeA a partial mapping from S -> A + * @param as a total mapping from A -> S + * @param the input that might fail to map to its output and the guaranteed output from the other direction + * @param the output that might fail to be produced and the input that guarantees its output in the other + * direction + * @return the {@link Simple simple prism} + */ + static Prism.Simple simplePrism(Fn1> sMaybeA, + Fn1 as) { + return Prism.prism(s -> sMaybeA.apply(s).toEither(() -> s), as)::toOptic; + } + + /** + * Static factory method for creating a {@link Prism} from a partial function S -> A and a total + * function B -> S. + * + * @param partialSa the partial direction + * @param bs the reverse total direction + * @param the input that might fail to map to its output and the guaranteed output from the other + * direction + * @param the output that might fail to be produced + * @param the input that guarantees its output in the other direction + * @return the {@link Prism} + */ + static Prism fromPartial(Fn1 partialSa, + Fn1 bs) { + return prism(partialSa.diMap(downcast(), upcast()).choose(), bs); + } + + /** + * The canonical {@link Pure} instance for {@link Prism}. + * + * @param the input that might fail to map to its output + * @param the output that might fail to be produced + * @param the input that guarantees its output + * @return the {@link Pure} instance + */ + static Pure> purePrism() { + return new Pure>() { + @Override + public Prism checkedApply(T t) throws Throwable { + return prism(constantly(left(t)), constantly(t)); + } + }; + } + + /** + * A convenience type with a simplified type signature for common {@link Prism prism} with unified S/T + * and A/B types. + * + * @param the input that might fail to map to its output and the guaranteed output from the other direction + * @param the output that might fail to be produced and the input that guarantees its output in the other + * direction + */ + interface Simple extends Prism { + + /** + * Adapt a {@link Prism} with compatible bounds to a {@link Prism.Simple simple Prism}. + * + * @param prism the {@link Prism} + * @param the input that might fail to map to its output and the guaranteed output from the other + * direction + * @param the output that might fail to be produced and the input that guarantees its output in the other + * direction + * @return the {@link Prism.Simple simple Prism} + */ + static Prism.Simple adapt(Prism prism) { + return prism::toOptic; + } + + /** + * Adapt a {@link ProtoOptic} with compatible bounds to a {@link Prism.Simple simple Prism}. + * + * @param protoOptic the {@link ProtoOptic} + * @param the input that might fail to map to its output and the guaranteed output from the other + * direction + * @param the output that might fail to be produced and the input that guarantees its output in the + * other direction + * @return the {@link Prism.Simple simple Prism} + */ + static Prism.Simple adapt(ProtoOptic, S, S, A, A> protoOptic) { + return adapt(prism(protoOptic)); + } + + /** + * Adapt an {@link Optic} with compatible bounds to a {@link Prism.Simple simple Prism}. + * + * @param optic the {@link Optic} + * @param the input that might fail to map to its output and the guaranteed output from the other + * direction + * @param the output that might fail to be produced and the input that guarantees its output in the + * other direction + * @return the {@link Prism.Simple simple Prism} + */ + static Prism.Simple adapt( + Optic, ? super Functor, S, S, A, A> optic) { + return adapt(prism(optic)); + } + + /** + * Static factory method for creating a {@link Prism.Simple simple Prism} from a partial function + * S -> A and a total function A -> T. + * + * @param partialSa the partial direction + * @param as the reverse total direction + * @param the input that might fail to map to its output and the guaranteed output from the other + * direction + * @param the output that might fail to be produced and the input that guarantees its output in the + * other direction + * @return the {@link Prism.Simple simple Prism} + */ + static Prism.Simple fromPartial(Fn1 partialSa, + Fn1 as) { + return adapt(Prism.fromPartial(partialSa, as)); + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/ProtoOptic.java b/src/main/java/com/jnape/palatable/lambda/optics/ProtoOptic.java new file mode 100644 index 000000000..5904ea4be --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/ProtoOptic.java @@ -0,0 +1,135 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.functor.builtin.Identity; + +import static com.jnape.palatable.lambda.optics.Optic.reframe; + +/** + * A generic supertype representation for a profunctor {@link Optic} that requires a {@link Pure} implementation to + * derive its {@link Functor} constraint and graduate to a full-fledge {@link Optic}, equipped with a default + * {@link Optic} instance for the profunctor constraint and {@link Identity}. + * + * @param

the {@link Profunctor} bound + * @param the left side of the output profunctor + * @param the right side's functor embedding of the output profunctor + * @param the left side of the input profunctor + * @param the right side's functor embedding of the input profunctor + */ +@FunctionalInterface +public interface ProtoOptic

, S, T, A, B> + extends Optic, S, T, A, B> { + + /** + * Given a {@link Pure} lifting function, fix this {@link ProtoOptic} to the given {@link Functor} and promote it to + * an {@link Optic}. + * + * @param pure the {@link Pure} lifting function + * @param the {@link Functor} bound + * @return the {@link Optic} + */ + > Optic toOptic(Pure pure); + + /** + * {@inheritDoc} + */ + @Override + default , CoF extends Functor>, + FB extends Functor, FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return toOptic(Pure.>pure(Identity::new)).apply(pafb); + } + + /** + * Left-to-right composition of proto-optics. Requires compatibility between S and T. + * + * @param f the other proto-optic + * @param the new left side of the input profunctor + * @param the new right side's functor embedding of the input profunctor + * @return the composed proto-optic + */ + default ProtoOptic andThen(ProtoOptic f) { + return new ProtoOptic() { + @Override + public > Optic toOptic(Pure pure) { + Optic optic = f.toOptic(pure); + return ProtoOptic.this.toOptic(pure).andThen(reframe(optic)); + } + }; + } + + /** + * Right-to-Left composition of proto-optics. Requires compatibility between A and B. + * + * @param g the other proto-optic + * @param the new left side of the output profunctor + * @param the new right side's functor embedding of the output profunctor + * @return the composed proto-optic + */ + default ProtoOptic compose(ProtoOptic g) { + return new ProtoOptic() { + @Override + public > Optic toOptic(Pure pure) { + Optic optic = g.toOptic(pure); + return ProtoOptic.this.toOptic(pure).compose(reframe(optic)); + } + }; + } + + /** + * {@inheritDoc} + */ + @Override + default ProtoOptic mapS(Fn1 fn) { + return new ProtoOptic() { + @Override + public > Optic toOptic(Pure pure) { + return ProtoOptic.this.toOptic(pure).mapS(fn); + } + }; + } + + /** + * {@inheritDoc} + */ + @Override + default ProtoOptic mapT(Fn1 fn) { + return new ProtoOptic() { + @Override + public > Optic toOptic(Pure pure) { + return ProtoOptic.this.toOptic(pure).mapT(fn); + } + }; + } + + /** + * {@inheritDoc} + */ + @Override + default ProtoOptic mapA(Fn1 fn) { + return new ProtoOptic() { + @Override + public > Optic toOptic(Pure pure) { + return ProtoOptic.this.toOptic(pure).mapA(fn); + } + }; + } + + /** + * {@inheritDoc} + */ + @Override + default ProtoOptic mapB(Fn1 fn) { + return new ProtoOptic() { + @Override + public > Optic toOptic(Pure pure) { + return ProtoOptic.this.toOptic(pure).mapB(fn); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/functions/Matching.java b/src/main/java/com/jnape/palatable/lambda/optics/functions/Matching.java new file mode 100644 index 000000000..04b03442f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/functions/Matching.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Market; +import com.jnape.palatable.lambda.optics.Optic; + +public final class Matching implements + Fn2, ? super Identity, S, T, A, B>, S, Either> { + + private static final Matching INSTANCE = new Matching<>(); + + private Matching() { + } + + @Override + public Either checkedApply(Optic, ? super Identity, S, T, A, B> optic, S s) { + Market> market = new Market<>(Identity::new, Either::right); + return optic., + Identity, + Identity, + Identity, + Market>, + Market>> + apply(market).sta().apply(s) + .biMapL(Identity::runIdentity) + .match(Either::left, Either::right); + } + + @SuppressWarnings("unchecked") + public static Matching matching() { + return (Matching) INSTANCE; + } + + public static Fn1> matching( + Optic, ? super Identity, S, T, A, B> optic) { + return Matching.matching().apply(optic); + } + + public static Either matching( + Optic, ? super Identity, S, T, A, B> optic, S s) { + return matching(optic).apply(s); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/functions/Over.java b/src/main/java/com/jnape/palatable/lambda/optics/functions/Over.java new file mode 100644 index 000000000..1e76ec4ce --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/functions/Over.java @@ -0,0 +1,59 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.optics.Optic; + +/** + * Given an {@link Optic}, a function from A to B, and a "larger" value S, + * produce a T by retrieving the A from the S, applying the function, and + * updating the S with the B resulting from the function. + *

+ * This function is similar to {@link Set}, except that it allows the setting value B to be derived from + * S via function application, rather than provided. + * + * @param the type of the larger value + * @param the type of the larger updated value + * @param the type of the smaller retrieving value + * @param the type of the smaller setting value + * @see Set + * @see View + */ +public final class Over implements + Fn3, ? super Identity, S, T, A, B>, Fn1, S, T> { + + private static final Over INSTANCE = new Over<>(); + + private Over() { + } + + @Override + public T checkedApply(Optic, ? super Identity, S, T, A, B> optic, + Fn1 fn, + S s) { + return optic., Identity, Identity, Identity, Fn1>, Fn1>>apply( + a -> new Identity<>(fn.apply(a))).apply(s).runIdentity(); + } + + @SuppressWarnings("unchecked") + public static Over over() { + return (Over) INSTANCE; + } + + public static Fn2, S, T> over( + Optic, ? super Identity, S, T, A, B> optic) { + return Over.over().apply(optic); + } + + public static Fn1 over(Optic, ? super Identity, S, T, A, B> optic, + Fn1 fn) { + return over(optic).apply(fn); + } + + public static T over(Optic, ? super Identity, S, T, A, B> optic, + Fn1 fn, S s) { + return over(optic, fn).apply(s); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/functions/Pre.java b/src/main/java/com/jnape/palatable/lambda/optics/functions/Pre.java new file mode 100644 index 000000000..f7d3c4095 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/functions/Pre.java @@ -0,0 +1,60 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.functor.builtin.Const; +import com.jnape.palatable.lambda.optics.Optic; +import com.jnape.palatable.lambda.optics.ProtoOptic; + +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.optics.Optic.reframe; + +/** + * Turn an {@link Optic} with a unary mapping that can be used for viewing some number of values into an {@link Optic} + * that views the first value, if it exists. + * + * @param the value to read from + * @param used for unification of the {@link Optic optic's} unused morphism + * @param the result to {@link Maybe maybe} read out + * @param used for unification of the {@link Optic optic's} unused morphism + */ +public final class Pre

, S, T, A, B> implements + Fn1, ?>, S, T, A, B>, + Optic, ?>, S, T, Maybe, B>> { + + private static final Pre INSTANCE = new Pre<>(); + + private Pre() { + } + + @Override + public Optic, ?>, S, T, Maybe, B> checkedApply( + Optic, ?>, S, T, A, B> optic) { + Optic, ?>, S, T, Maybe, B> mappedOptic = optic.mapA(Maybe::just); + return reframe(mappedOptic); + } + + @SuppressWarnings("unchecked") + public static

, S, T, A, B> Pre pre() { + return (Pre) INSTANCE; + } + + @SuppressWarnings("overloads") + public static

, S, T, A, B> + Optic, ?>, S, T, Maybe, B> pre(Optic, ?>, S, T, A, B> optic) { + return Pre.pre().apply(optic); + } + + @SuppressWarnings("overloads") + public static

, S, T, A, B> + Optic, ?>, S, T, Maybe, B> pre(ProtoOptic protoOptic) { + return pre(protoOptic.toOptic(new Pure, ?>>() { + @Override + public Const, X> checkedApply(X x) { + return new Const<>(nothing()); + } + })); + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/optics/functions/Re.java b/src/main/java/com/jnape/palatable/lambda/optics/functions/Re.java new file mode 100644 index 000000000..a948a0170 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/functions/Re.java @@ -0,0 +1,53 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.functor.builtin.Const; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Tagged; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Optic; +import com.jnape.palatable.lambda.optics.Prism; + +/** + * Turn an {@link Optic} with a unary mapping that can be used for setting (e.g. {@link Prism}, {@link Iso}) around for + * viewing through the other direction. + * + * @param used for unification of the {@link Optic optic's} unused morphism + * @param the result to read out + * @param used for unification of the {@link Optic optic's} unused morphism + * @param the value to read from + */ +public final class Re implements + Fn1, ? super Identity, S, T, A, B>, + Optic, Const, B, B, T, T>> { + + private static final Re INSTANCE = new Re<>(); + + private Re() { + } + + @Override + public Optic, Const, B, B, T, T> checkedApply( + Optic, ? super Identity, S, T, A, B> optic) { + return Optic., Const, B, B, T, T, + Const, Const, + Profunctor, ? extends Profunctor>, + Profunctor, ? extends Profunctor>>optic( + pafb -> pafb.diMap( + b -> optic., Identity, Identity, Identity, + Tagged>, Tagged>>apply( + new Tagged<>(new Identity<>(b))).unTagged().runIdentity(), + fb -> new Const<>(fb.runConst()))); + } + + @SuppressWarnings("unchecked") + public static Re re() { + return (Re) INSTANCE; + } + + public static Optic, Const, B, B, T, T> re( + Optic, ? super Identity, S, T, A, B> optic) { + return Re.re().apply(optic); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/functions/Set.java b/src/main/java/com/jnape/palatable/lambda/optics/functions/Set.java new file mode 100644 index 000000000..7fdd16e56 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/functions/Set.java @@ -0,0 +1,54 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.optics.Optic; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.optics.functions.Over.over; + +/** + * Given an {@link Optic}, a "smaller" value B, and a "larger" value S, produce a + * T by lifting the {@link Optic} into the {@link Identity} functor. + *

+ * More idiomatically, this function can be used to treat an {@link Optic} as a "setter" of + * Bs on Ss, potentially producing a different "larger" value, T. + * + * @param the type of the larger value + * @param the type of the larger updated value + * @param the type of the smaller retrieving value (unused, but necessary for composition) + * @param the type of the smaller setting value + * @see Over + * @see View + */ +public final class Set implements Fn3, ? super Identity, S, T, A, B>, B, S, T> { + + private static final Set INSTANCE = new Set<>(); + + private Set() { + } + + @Override + public T checkedApply(Optic, ? super Identity, S, T, A, B> optic, B b, S s) { + return over(optic, constantly(b), s); + } + + @SuppressWarnings("unchecked") + public static Set set() { + return (Set) INSTANCE; + } + + public static Fn2 set(Optic, ? super Identity, S, T, A, B> optic) { + return Set.set().apply(optic); + } + + public static Fn1 set(Optic, ? super Identity, S, T, A, B> optic, B b) { + return set(optic).apply(b); + } + + public static T set(Optic, ? super Identity, S, T, A, B> optic, B b, S s) { + return set(optic, b).apply(s); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/functions/Under.java b/src/main/java/com/jnape/palatable/lambda/optics/functions/Under.java new file mode 100644 index 000000000..81b915867 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/functions/Under.java @@ -0,0 +1,60 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.builtin.Exchange; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Optic; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * The inverse of {@link Over}: given an {@link Iso}, a function from T to S, and a "smaller" + * value B, return a "smaller" value A by traversing around the type ring (B -> T + * -> S -> A). + * + * @param the larger type for focusing + * @param the larger type for mirrored focusing + * @param the smaller type for focusing + * @param the smaller type for mirrored focusing + */ +public final class Under implements + Fn3, ? super Identity, S, T, A, B>, + Fn1, B, A> { + + private static final Under INSTANCE = new Under<>(); + + private Under() { + } + + @Override + public A checkedApply(Optic, ? super Identity, S, T, A, B> optic, + Fn1 fn, + B b) { + Exchange> exchange = optic.apply(new Exchange<>(id(), Identity::new)); + return exchange.sa().apply(fn.apply(exchange.bt().apply(b).runIdentity())); + } + + @SuppressWarnings("unchecked") + public static Under under() { + return (Under) INSTANCE; + } + + public static Fn2, B, A> under( + Optic, ? super Identity, S, T, A, B> optic) { + return Under.under().apply(optic); + } + + public static Fn1 under( + Optic, ? super Identity, S, T, A, B> optic, + Fn1 fn) { + return under(optic).apply(fn); + } + + public static A under(Optic, ? super Identity, S, T, A, B> optic, + Fn1 fn, B b) { + return under(optic, fn).apply(b); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/functions/View.java b/src/main/java/com/jnape/palatable/lambda/optics/functions/View.java new file mode 100644 index 000000000..c63074ac2 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/functions/View.java @@ -0,0 +1,47 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.builtin.Const; +import com.jnape.palatable.lambda.optics.Optic; + +/** + * Given an {@link Optic} and a "larger" value S, retrieve a "smaller" value A by lifting the + * {@link Optic} into the {@link Const} functor. + *

+ * More idiomatically, this function can be used to treat a {@link Optic} as a "getter" of As from + * Ss. + * + * @param the type of the larger value + * @param the type of the larger updated value (unused, but necessary for composition) + * @param the type of the smaller retrieving value + * @param the type of the smaller setting value (unused, but necessary for composition) + * @see Set + * @see Over + */ +public final class View implements Fn2, ? super Const, S, T, A, B>, S, A> { + + private static final View INSTANCE = new View<>(); + + private View() { + } + + @Override + public A checkedApply(Optic, ? super Const, S, T, A, B> optic, S s) { + return optic., Const, Const, Const, Fn1>, Fn1>>apply( + Const::new).apply(s).runConst(); + } + + @SuppressWarnings("unchecked") + public static View view() { + return (View) INSTANCE; + } + + public static Fn1 view(Optic, ? super Const, S, T, A, B> optic) { + return View.view().apply(optic); + } + + public static A view(Optic, ? super Const, S, T, A, B> optic, S s) { + return view(optic).apply(s); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/CollectionLens.java similarity index 52% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java rename to src/main/java/com/jnape/palatable/lambda/optics/lenses/CollectionLens.java index 5bcf16315..18ccd9faa 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/CollectionLens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/CollectionLens.java @@ -1,14 +1,14 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.optics.lenses; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.optics.Lens; import java.util.Collection; import java.util.HashSet; import java.util.Set; -import java.util.function.Function; import java.util.stream.Stream; -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; /** * Lenses that operate on {@link Collection}s. @@ -27,7 +27,7 @@ private CollectionLens() { * @param the type of the collection * @return a lens that focuses on a copy of CX */ - public static > Lens.Simple asCopy(Function copyFn) { + public static > Lens.Simple asCopy(Fn1 copyFn) { return simpleLens(copyFn, (__, copy) -> copy); } @@ -35,29 +35,41 @@ public static > Lens.Simple asCopy(Function< * Convenience static factory method for creating a lens that focuses on an arbitrary {@link Collection} as a * {@link Set}. * - * @param the collection element type - * @param the type of the collection + * @param copyFn the copying function + * @param the collection element type + * @param the type of the collection * @return a lens that focuses on a Collection as a Set */ - public static > Lens.Simple> asSet() { + public static > Lens.Simple> asSet(Fn1 copyFn) { return simpleLens(HashSet::new, (xsL, xsS) -> { - xsL.retainAll(xsS); - return xsL; + Set missing = new HashSet<>(xsS); + missing.removeAll(xsL); + CX updated = copyFn.apply(xsL); + updated.addAll(missing); + updated.retainAll(xsS); + return updated; }); } /** * Convenience static factory method for creating a lens that focuses on a Collection as a Stream. + *

+ * Note that this lens is effectively lawful, though difficult to prove given the intrinsically + * stateful and inequitable nature of {@link Stream}. * - * @param the collection element type - * @param the type of the collection + * @param copyFn the copying function + * @param the collection element type + * @param the type of the collection * @return a lens that focuses on a Collection as a stream. */ - public static > Lens.Simple> asStream() { - return simpleLens(Collection::stream, (xsL, xsS) -> { - xsL.clear(); - xsS.forEach(xsL::add); - return xsL; + @SuppressWarnings("RedundantTypeArguments") + public static > Lens.Simple> asStream( + Fn1 copyFn) { + return simpleLens(Collection::stream, (xsL, xsS) -> { + CX updated = copyFn.apply(xsL); + updated.clear(); + xsS.forEach(updated::add); + return updated; }); } } diff --git a/src/main/java/com/jnape/palatable/lambda/optics/lenses/EitherLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/EitherLens.java new file mode 100644 index 000000000..8463d0eb1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/EitherLens.java @@ -0,0 +1,47 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.optics.Lens; + +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; + +/** + * Lenses for {@link Either}. + */ +public final class EitherLens { + + private EitherLens() { + } + + /** + * Convenience static factory method for creating a lens over right values, wrapping them in a {@link Maybe}. When + * setting, a {@link Maybe#nothing()} value means to leave the {@link Either} unaltered, where as a + * {@link Maybe#just} value replaces the either with a right over the {@link Maybe}. + *

+ * Note that this lens is NOT lawful, since "you get back what you put in" fails for {@link Maybe#nothing()}. + * + * @param the left parameter type + * @param the right parameter type + * @return a lens that focuses on right values + */ + public static Lens.Simple, Maybe> _right() { + return simpleLens(CoProduct2::projectB, (lOrR, maybeR) -> maybeR.>fmap(Either::right).orElse(lOrR)); + } + + /** + * Convenience static factory method for creating a lens over left values, wrapping them in a {@link Maybe}. When + * setting, a {@link Maybe#nothing()} value means to leave the {@link Either} unaltered, where as a + * {@link Maybe#just} value replaces the either with a left over the {@link Maybe}. + *

+ * Note that this lens is NOT lawful, since "you get back what you put in" fails for {@link Maybe#nothing()}. + * + * @param the left parameter type + * @param the right parameter type + * @return a lens that focuses on left values + */ + public static Lens.Simple, Maybe> _left() { + return simpleLens(CoProduct2::projectA, (lOrR, maybeL) -> maybeL.>fmap(Either::left).orElse(lOrR)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/HListLens.java similarity index 76% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java rename to src/main/java/com/jnape/palatable/lambda/optics/lenses/HListLens.java index b211d5918..2af87a94a 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/HListLens.java @@ -1,13 +1,16 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.optics.lenses; import com.jnape.palatable.lambda.adt.hlist.HList; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.hlist.Index; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.optics.Lens; import static com.jnape.palatable.lambda.adt.hlist.HList.cons; -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; +/** + * Lenses that operate on {@link HList}s. + */ public final class HListLens { /** @@ -29,7 +32,7 @@ public final class HListLens { * @param the tail HList type * @return a lens that focuses on the head of an HList */ - public static > Lens.Simple, Head> head() { + public static Lens.Simple, Head> head() { return simpleLens(HCons::head, (hCons, newHead) -> cons(newHead, hCons.tail())); } @@ -40,7 +43,7 @@ public final class HListLens { * @param the tail HList type * @return a lens that focuses on the tail of an HList */ - public static > Lens.Simple, Tail> tail() { + public static Lens.Simple, Tail> tail() { return simpleLens(HCons::tail, (hCons, newTail) -> cons(hCons.head(), newTail)); } } diff --git a/src/main/java/com/jnape/palatable/lambda/optics/lenses/HMapLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/HMapLens.java new file mode 100644 index 000000000..f98446254 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/HMapLens.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hmap.HMap; +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import com.jnape.palatable.lambda.optics.Lens; + +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; + +/** + * Lenses that operate on {@link HMap}s. + */ +public final class HMapLens { + private HMapLens() { + } + + /** + * A lens that focuses on a value at a {@link TypeSafeKey.Simple}<A> in an {@link HMap}, as a {@link Maybe}. + * + * @param key the key + * @param the value type at the key + * @return the lens + */ + public static Lens.Simple> valueAt(TypeSafeKey key) { + return simpleLens(m -> m.get(key), (m, maybeA) -> maybeA.fmap(a -> m.put(key, a)).orElseGet(() -> m.remove(key))); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/lenses/IterableLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/IterableLens.java new file mode 100644 index 000000000..b0f125c14 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/IterableLens.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.builtin.fn1.Head; +import com.jnape.palatable.lambda.functions.builtin.fn1.Tail; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Lens; + +import static com.jnape.palatable.lambda.functions.Fn2.curried; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.optics.Iso.simpleIso; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.functions.View.view; + +/** + * Lenses that operate on {@link Iterable}s. + */ +public final class IterableLens { + + private IterableLens() { + } + + /** + * A lens focusing on the head of a given {@link Iterable}. + *

+ * Note that this lens is effectively lawful, though difficult to prove since there is no + * useful equality implementation for {@link Iterable}. + * + * @param the Iterable element type + * @return a lens focusing on the head element of an {@link Iterable} + */ + public static Lens.Simple, Maybe> head() { + return simpleLens(Head::head, (s, maybeB) -> { + Iterable tail = Tail.tail(s); + return maybeB.fmap(b -> cons(b, tail)).orElse(tail); + }); + } + + /** + * A lens focusing on the tail of an {@link Iterable}. + * + * @param the Iterable element type + * @return a lens focusing on the tail of an {@link Iterable} + */ + public static Lens.Simple, Iterable> tail() { + return simpleLens(Tail::tail, curried(Head.head().fmap(o -> o.fmap(cons()).orElse(id())))); + } + + /** + * An iso focusing on the mapped values of an {@link Iterable}. + * + * @param abIso the iso from A to B + * @param the unmapped {@link Iterable} element type + * @param the mapped {@link Iterable} element type + * @return an iso that maps {@link Iterable}<A> to {@link Iterable}<B> + */ + public static Iso.Simple, Iterable> mapping(Iso abIso) { + return simpleIso(map(view(abIso)), map(view(abIso.mirror()))); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/lenses/ListLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/ListLens.java new file mode 100644 index 000000000..383c1a226 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/ListLens.java @@ -0,0 +1,79 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.optics.Lens; + +import java.util.ArrayList; +import java.util.List; + +import static com.jnape.palatable.lambda.adt.Maybe.maybe; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.lenses.MaybeLens.unLiftA; +import static com.jnape.palatable.lambda.optics.lenses.MaybeLens.unLiftB; +import static java.lang.Math.abs; + +/** + * Lenses that operate on {@link List}s. + */ +public final class ListLens { + + private ListLens() { + } + + /** + * Convenience static factory method for creating a lens over a copy of a list. Useful for composition to avoid + * mutating a list reference. + * + * @param the list element type + * @return a lens that focuses on copies of lists + */ + public static Lens.Simple, List> asCopy() { + return simpleLens(ArrayList::new, (xs, ys) -> ys); + } + + /** + * Convenience static factory method for creating a lens that focuses on an element in a list at a particular index. + * Wraps result in a {@link Maybe} to handle null values or indexes that fall outside of list boundaries. + * + * @param index the index to focus on + * @param the list element type + * @return Maybe the element at the index + */ + public static Lens.Simple, Maybe> elementAt(int index) { + return simpleLens(xs -> maybe(xs.size() > index ? xs.get(index) : null), + (xs, maybeX) -> { + List updated = new ArrayList<>(xs); + return maybeX.fmap(x -> { + int minimumSize = index + 1; + if (minimumSize > xs.size()) { + take(abs(updated.size() - minimumSize), repeat((X) null)).forEach(updated::add); + } + updated.set(index, x); + return updated; + }).orElseGet(() -> { + if (updated.size() > index) { + updated.set(index, null); + } + return updated; + }); + }); + } + + /** + * Convenience static factory method for creating a lens that focuses on an element in a list at a particular index, + * returning defaultValue if there is no value at that index. + *

+ * Note that this lens is NOT lawful, since "putting back what you got changes nothing" fails for any value + * B where S is the empty list + * + * @param index the index to focus on + * @param defaultValue the value to use if there is no element at index + * @param the list element type + * @return the element at the index, or defaultValue + */ + public static Lens.Simple, X> elementAt(int index, X defaultValue) { + return Lens.Simple.adapt(unLiftB(unLiftA(elementAt(index), defaultValue))); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/MapLens.java new file mode 100644 index 000000000..729ca0b23 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/MapLens.java @@ -0,0 +1,197 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Filter; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Lens; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.jnape.palatable.lambda.adt.Maybe.maybe; +import static com.jnape.palatable.lambda.functions.Effect.effect; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Alter.alter; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToMap.toMap; +import static com.jnape.palatable.lambda.functions.specialized.SideEffect.sideEffect; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.optics.Lens.Simple.adapt; +import static com.jnape.palatable.lambda.optics.Lens.lens; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static com.jnape.palatable.lambda.optics.lenses.MaybeLens.unLiftA; +import static com.jnape.palatable.lambda.optics.lenses.MaybeLens.unLiftB; + +/** + * Lenses that operate on {@link Map}s. + */ +public final class MapLens { + + private MapLens() { + } + + /** + * A lens that focuses on a copy of a {@link Map} as a subtype M. Useful for composition to avoid + * mutating a map reference. + * + * @param the map subtype + * @param the key type + * @param the value type + * @param copyFn the copy function + * @return a lens that focuses on copies of maps as a specific subtype + */ + public static , K, V> Lens, M, M, M> asCopy( + Fn1, ? extends M> copyFn) { + return lens(copyFn, (__, copy) -> copy); + } + + /** + * A lens that focuses on a copy of a Map. Useful for composition to avoid mutating a map reference. + * + * @param the key type + * @param the value type + * @return a lens that focuses on copies of maps + */ + public static Lens.Simple, Map> asCopy() { + return adapt(asCopy(HashMap::new)); + } + + /** + * A lens that focuses on a value at a key in a map, as a {@link Maybe}, and produces a subtype M on + * the way back out. + * + * @param copyFn the copy function + * @param k the key to focus on + * @param the map subtype + * @param the key type + * @param the value type + * @return a lens that focuses on the value at key, as a {@link Maybe} + */ + public static , K, V> Lens, M, Maybe, Maybe> valueAt( + Fn1, ? extends M> copyFn, K k) { + return lens(m -> maybe(m.get(k)), (m, maybeV) -> maybeV + .>>fmap(v -> alter(effect(updated -> io(() -> updated.put(k, v))))) + .orElse(alter(updated -> io(sideEffect(() -> updated.remove(k))))) + .apply(copyFn.apply(m)) + .unsafePerformIO()); + } + + /** + * A lens that focuses on a value at a key in a map, as a {@link Maybe}. + * + * @param the key type + * @param the value type + * @param k the key to focus on + * @return a lens that focuses on the value at key, as a {@link Maybe} + */ + public static Lens.Simple, Maybe> valueAt(K k) { + return adapt(valueAt(HashMap::new, k)); + } + + /** + * A lens that focuses on a value at a key in a map, falling back to defaultV if the value is missing. + *

+ * Note that this lens is NOT lawful, since "putting back what you got changes nothing" fails for any value + * B where S is the empty map + * + * @param k the key to focus on + * @param defaultValue the default value to use in case of a missing value at key + * @param the key type + * @param the value type + * @return a lens that focuses on the value at the key + */ + public static Lens.Simple, V> valueAt(K k, V defaultValue) { + return adapt(unLiftB(unLiftA(valueAt(k), defaultValue))); + } + + /** + * A lens that focuses on the keys of a map. + * + * @param the key type + * @param the value type + * @return a lens that focuses on the keys of a map + */ + public static Lens.Simple, Set> keys() { + return simpleLens(m -> new HashSet<>(m.keySet()), (m, ks) -> { + HashSet ksCopy = new HashSet<>(ks); + Map updated = new HashMap<>(m); + Set keys = updated.keySet(); + keys.retainAll(ksCopy); + ksCopy.removeAll(keys); + ksCopy.forEach(k -> updated.put(k, null)); + return updated; + }); + } + + /** + * A lens that focuses on the values of a map. In the case of updating the map, only the entries with a value listed + * in the update collection of values are kept. + *

+ * Note that this lens is NOT lawful, since "you get back what you put in" fails for all values B that + * represent a non-surjective superset of the existing values in S. + * + * @param the key type + * @param the value type + * @return a lens that focuses on the values of a map + */ + public static Lens.Simple, Collection> values() { + return simpleLens(m -> new ArrayList<>(m.values()), (m, vs) -> { + Map updated = new HashMap<>(m); + Set valueSet = new HashSet<>(vs); + Set matchingKeys = Filter.>filter(kv -> valueSet.contains(kv.getValue())) + .fmap(map(Map.Entry::getKey)) + .fmap(toCollection(HashSet::new)) + .apply(updated.entrySet()); + updated.keySet().retainAll(matchingKeys); + return updated; + }); + } + + /** + * A lens that focuses on the inverse of a map (keys and values swapped). In the case of multiple equal values + * becoming keys, the last one wins. + *

+ * Note that this lens is very likely to NOT be lawful, since "you get back what you put in" will fail for any keys + * that map to the same value. + * + * @param the key type + * @param the value type + * @return a lens that focuses on the inverse of a map + */ + public static Lens.Simple, Map> inverted() { + return simpleLens(m -> { + Map inverted = new HashMap<>(); + m.forEach((key, value) -> inverted.put(value, key)); + return inverted; + }, (m, im) -> { + Map updated = new HashMap<>(m); + updated.clear(); + updated.putAll(view(inverted(), im)); + return updated; + }); + } + + /** + * A lens that focuses on a map while mapping its values with the mapping {@link Iso}. + *

+ * Note that for this lens to be lawful, iso must be lawful. + * + * @param iso the mapping {@link Iso} + * @param the key type + * @param the unfocused map value type + * @param the focused map value type + * @return a lens that focuses on a map while mapping its values + */ + public static Lens.Simple, Map> mappingValues(Iso iso) { + return simpleLens(m -> toMap(HashMap::new, map(t -> t.biMapR(view(iso)), map(Tuple2::fromEntry, m.entrySet()))), + (s, b) -> view(mappingValues(iso.mirror()), b)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/MaybeLens.java similarity index 50% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java rename to src/main/java/com/jnape/palatable/lambda/optics/lenses/MaybeLens.java index ec8eba0e6..5bb9f93da 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/MaybeLens.java @@ -1,46 +1,38 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.optics.lenses; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.optics.Lens; -import java.util.Optional; - -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; /** - * Lenses that operate on {@link Optional}s. + * Lenses that operate on {@link Maybe}. */ -public final class OptionalLens { - - private OptionalLens() { - } +public final class MaybeLens { - /** - * Convenience static factory method for creating a lens that focuses on a value as an {@link Optional}. - * - * @param the value type - * @return a lens that focuses on the value as an Optional - */ - public static Lens.Simple> asOptional() { - return simpleLens(Optional::ofNullable, (v, optV) -> optV.orElse(v)); + private MaybeLens() { } /** - * Given a lens and a default S, lift S into Optional. + * Given a lens and a default S, lift S into {@link Maybe}. + *

+ * Note that this lens is NOT lawful, since "putting back what you got changes nothing" fails for any value + * B where S is {@link Maybe#nothing()}. * * @param lens the lens - * @param defaultS the S to use if an empty Optional value is given + * @param defaultS the S to use if {@link Maybe#nothing()} is given * @param the type of the "larger" value for reading * @param the type of the "larger" value for putting * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with S lifted */ - public static Lens, T, A, B> liftS(Lens lens, S defaultS) { - return lens.mapS(optS -> optS.orElse(defaultS)); + public static Lens, T, A, B> liftS(Lens lens, S defaultS) { + return lens.mapS(m -> m.orElse(defaultS)); } /** - * Given a lens, lift T into Optional. + * Given a lens, lift T into {@link Maybe}. * * @param lens the lens * @param the type of the "larger" value for reading @@ -49,12 +41,12 @@ public static Lens, T, A, B> liftS(Lens len * @param the type of the "smaller" update value * @return the lens with T lifted */ - public static Lens, A, B> liftT(Lens lens) { - return lens.mapT(Optional::ofNullable); + public static Lens, A, B> liftT(Lens lens) { + return lens.mapT(Maybe::just); } /** - * Given a lens, lift A into Optional. + * Given a lens, lift A into {@link Maybe}. * * @param lens the lens * @param the type of the "larger" value for reading @@ -63,27 +55,30 @@ public static Lens, A, B> liftT(Lens len * @param the type of the "smaller" update value * @return the lens with A lifted */ - public static Lens, B> liftA(Lens lens) { - return lens.mapA(Optional::ofNullable); + public static Lens, B> liftA(Lens lens) { + return lens.mapA(Maybe::just); } /** - * Given a lens and a default B, lift B into Optional. + * Given a lens and a default B, lift B into {@link Maybe}. + *

+ * Note that this lens is NOT lawful, since "putting back what you got changes nothing" fails for any value + * B where S is {@link Maybe#nothing()}. * * @param lens the lens - * @param defaultB the B to use if an empty Optional value is given + * @param defaultB the B to use if {@link Maybe#nothing()} is given * @param the type of the "larger" value for reading * @param the type of the "larger" value for putting * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with B lifted */ - public static Lens> liftB(Lens lens, B defaultB) { - return lens.mapB(optB -> optB.orElse(defaultB)); + public static Lens> liftB(Lens lens, B defaultB) { + return lens.mapB(m -> m.orElse(defaultB)); } /** - * Given a lens with S lifted into Optional, flatten S back down. + * Given a lens with S lifted into {@link Maybe}, flatten S back down. * * @param lens the lens * @param the type of the "larger" value for reading @@ -92,44 +87,50 @@ public static Lens> liftB(Lens len * @param the type of the "smaller" update value * @return the lens with S flattened */ - public static Lens unLiftS(Lens, T, A, B> lens) { - return lens.mapS(Optional::ofNullable); + public static Lens unLiftS(Lens, T, A, B> lens) { + return lens.mapS(Maybe::just); } /** - * Given a lens with T lifted into Optional and a default T, flatten T back - * down. + * Given a lens with T lifted into {@link Maybe} and a default T, flatten T + * back down. + *

+ * Note that while this lens is not *necessarily* unlawful, it probably is, since the only case where "you get back + * what you put in" would not be violated is if T could never be {@link Maybe#nothing()}. * * @param lens the lens - * @param defaultT the T to use if lens produces an empty Optional + * @param defaultT the T to use if lens produces {@link Maybe#nothing()} * @param the type of the "larger" value for reading * @param the type of the "larger" value for putting * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with T flattened */ - public static Lens unLiftT(Lens, A, B> lens, T defaultT) { - return lens.mapT(optT -> optT.orElse(defaultT)); + public static Lens unLiftT(Lens, A, B> lens, T defaultT) { + return lens.mapT(m -> m.orElse(defaultT)); } /** - * Given a lens with A lifted into Optional and a default A, flatten A back - * down. + * Given a lens with A lifted into {@link Maybe} and a default A, flatten A + * back down. + *

+ * Note that while this lens is not *necessarily* unlawful, it probably is, since the only case where "putting back + * what you got changes nothing" would not be violated is if A could never be {@link Maybe#nothing()}. * * @param lens the lens - * @param defaultA the A to use if lens produces an empty Optional + * @param defaultA the A to use if lens produces {@link Maybe#nothing()} * @param the type of the "larger" value for reading * @param the type of the "larger" value for putting * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with A flattened */ - public static Lens unLiftA(Lens, B> lens, A defaultA) { - return lens.mapA(optA -> optA.orElse(defaultA)); + public static Lens unLiftA(Lens, B> lens, A defaultA) { + return lens.mapA(m -> m.orElse(defaultA)); } /** - * Given a lens with B lifted, flatten B back down. + * Given a lens with B lifted into {@link Maybe}, flatten B back down. * * @param lens the lens * @param the type of the "larger" value for reading @@ -138,7 +139,17 @@ public static Lens unLiftA(Lens, B> l * @param the type of the "smaller" update value * @return the lens with B flattened */ - public static Lens unLiftB(Lens> lens) { - return lens.mapB(Optional::ofNullable); + public static Lens unLiftB(Lens> lens) { + return lens.mapB(Maybe::just); + } + + /** + * Convenience static factory method for creating a lens that focuses on a value as a {@link Maybe}. + * + * @param the value type + * @return a lens that focuses on the value as a {@link Maybe} + */ + public static Lens.Simple> asMaybe() { + return simpleLens(Maybe::maybe, (v, maybeV) -> maybeV.orElse(v)); } } diff --git a/src/main/java/com/jnape/palatable/lambda/optics/lenses/SetLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/SetLens.java new file mode 100644 index 000000000..75c7ab756 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/SetLens.java @@ -0,0 +1,51 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.optics.Lens; + +import java.util.HashSet; +import java.util.Set; + +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; + +/** + * Lenses that operate on {@link Set}s. + */ +public final class SetLens { + + private SetLens() { + } + + /** + * A lens that focuses on whether a {@link Set} contains some value a. Note that copyFn is + * used to avoid mutating the {@link Set} in question. + * + * @param copyFn the copy function + * @param a the value in question + * @param the value type + * @param the set to focus on + * @return a lens that focuses on a value's inclusion in a given {@link Set} + */ + public static > Lens.Simple contains(Fn1 copyFn, + A a) { + return simpleLens(setA -> setA.contains(a), + (setA, include) -> { + SetA copy = copyFn.apply(setA); + if (include) copy.add(a); + else copy.remove(a); + return copy; + }); + } + + /** + * A lens that focuses on whether a {@link Set} contains some value a. Like + * {@link #contains(Fn1, Object)} but with an implicit copy function that produces {@link HashSet}s. + * + * @param a the value in question + * @param the value type + * @return a lens that focuses on a value's inclusion in a given {@link Set} + */ + public static Lens.Simple, Boolean> contains(A a) { + return contains(HashSet::new, a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/prisms/EitherPrism.java b/src/main/java/com/jnape/palatable/lambda/optics/prisms/EitherPrism.java new file mode 100644 index 000000000..462f30b0a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/prisms/EitherPrism.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.optics.Prism; + +import static com.jnape.palatable.lambda.optics.Prism.simplePrism; + +/** + * {@link Prism Prisms} for {@link Either}. + */ +public final class EitherPrism { + + private EitherPrism() { + } + + /** + * A {@link Prism} that focuses on the {@link Either#left(Object) left} value of an {@link Either}. + * + * @param the left type + * @param the right type + * @return the {@link Prism} + */ + public static Prism.Simple, L> _left() { + return simplePrism(CoProduct2::projectA, Either::left); + } + + /** + * A {@link Prism} that focuses on the {@link Either#right(Object) right} value of an {@link Either}. + * + * @param the left type + * @param the right type + * @return the {@link Prism} + */ + public static Prism.Simple, R> _right() { + return simplePrism(CoProduct2::projectB, Either::right); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/prisms/MapPrism.java b/src/main/java/com/jnape/palatable/lambda/optics/prisms/MapPrism.java new file mode 100644 index 000000000..2d1ee7930 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/prisms/MapPrism.java @@ -0,0 +1,49 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.optics.Prism; + +import java.util.HashMap; +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.Maybe.maybe; +import static com.jnape.palatable.lambda.optics.Prism.Simple.adapt; +import static com.jnape.palatable.lambda.optics.Prism.prism; +import static java.util.Collections.singletonMap; + +/** + * {@link Prism Prisms} for {@link Map Maps}. + */ +public final class MapPrism { + private MapPrism() { + } + + /** + * A {@link Prism} that focuses on the value at a key in a {@link Map}, and produces an instance of M + * on the way back out. + * + * @param copyFn the copy function + * @param k the key to focus on + * @param the {@link Map} subtype + * @param the key type + * @param the value type + * @return the {@link Prism} + */ + public static , K, V> Prism, M, V, V> valueAt(Fn1, M> copyFn, K k) { + return prism(m -> maybe(m.get(k)).toEither(copyFn.thunk(m)), + v -> copyFn.apply(singletonMap(k, v))); + } + + /** + * A {@link Prism} that focuses on the value at a key in a {@link Map} making no guarantees about the {@link Map} + * interface. + * + * @param k the key to focus on + * @param the key type + * @param the value type + * @return the {@link Prism} + */ + public static Prism.Simple, V> valueAt(K k) { + return adapt(valueAt(HashMap::new, k)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/prisms/MaybePrism.java b/src/main/java/com/jnape/palatable/lambda/optics/prisms/MaybePrism.java new file mode 100644 index 000000000..eaf5ed0f1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/prisms/MaybePrism.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.optics.Prism; + +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.optics.Prism.prism; +import static com.jnape.palatable.lambda.optics.Prism.simplePrism; + +/** + * {@link Prism Prisms} for {@link Maybe}. + */ +public final class MaybePrism { + + private MaybePrism() { + } + + /** + * A {@link Prism} that focuses on present values in a {@link Maybe}. + * + * @param {@link Maybe} the input value + * @param {@link Maybe} the output value + * @return the {@link Prism} + */ + public static Prism, Maybe, A, B> _just() { + return prism(maybeA -> maybeA.toEither(Maybe::nothing), Maybe::just); + } + + /** + * A {@link Prism} that focuses on absent values in a {@link Maybe}, for symmetry. + * + * @param {@link Maybe} the input and output value + * @return the {@link Prism} + */ + public static Prism.Simple, Unit> _nothing() { + return simplePrism(CoProduct2::projectA, constantly(nothing())); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrism.java b/src/main/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrism.java new file mode 100644 index 000000000..1a030b654 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrism.java @@ -0,0 +1,25 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import com.jnape.palatable.lambda.optics.Prism; + +import java.util.UUID; + +import static com.jnape.palatable.lambda.optics.Prism.Simple.fromPartial; + +/** + * {@link Prism Prisms} for {@link UUID}. + */ +public final class UUIDPrism { + + private UUIDPrism() { + } + + /** + * A {@link Prism} that focuses on a {@link String} as a {@link UUID}. + * + * @return the {@link Prism} + */ + public static Prism.Simple uuid() { + return fromPartial(UUID::fromString, UUID::toString); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java b/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java index a680612e0..d4cc1e6b4 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java @@ -3,6 +3,9 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; import com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight; +import com.jnape.palatable.lambda.functor.builtin.Lazy; + +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** * A Semigroup is a closed, associative category. As closure can be implied by the type signature, and @@ -23,7 +26,7 @@ public interface Semigroup extends Fn2 { * @see FoldLeft */ default A foldLeft(A a, Iterable as) { - return FoldLeft.foldLeft(toBiFunction(), a, as); + return FoldLeft.foldLeft(this, a, as); } /** @@ -35,7 +38,15 @@ default A foldLeft(A a, Iterable as) { * @return the folded result * @see FoldRight */ - default A foldRight(A a, Iterable as) { - return FoldRight.foldRight(toBiFunction(), a, as); + default Lazy foldRight(A a, Iterable as) { + return FoldRight.foldRight((y, lazyX) -> lazyX.fmap(x -> apply(y, x)), lazy(a), as); + } + + /** + * {@inheritDoc} + */ + @Override + default Semigroup flip() { + return Fn2.super.flip()::apply; } } diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java new file mode 100644 index 000000000..222a5e7dc --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java @@ -0,0 +1,94 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2.liftA2; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + +/** + * A {@link Semigroup} instance formed by {@link Maybe}<A> and a semigroup over A. The + * application to two {@link Maybe} values is absence-biased, such that for a given {@link Maybe} x + * and y: + *

+ * + * @param the Maybe value parameter type + * @see Semigroup + * @see Present + * @see Maybe + */ +public final class Absent implements SemigroupFactory, Maybe> { + + private static final Absent INSTANCE = new Absent<>(); + + private Absent() { + } + + @Override + public Semigroup> checkedApply(Semigroup aSemigroup) { + return shortCircuitSemigroup(aSemigroup); + } + + @SuppressWarnings("unchecked") + public static Absent absent() { + return (Absent) INSTANCE; + } + + public static Semigroup> absent(Semigroup aSemigroup) { + return shortCircuitSemigroup(aSemigroup); + } + + public static Fn1, Maybe> absent(Semigroup aSemigroup, Maybe x) { + return absent(aSemigroup).apply(x); + } + + public static Maybe absent(Semigroup semigroup, Maybe x, Maybe y) { + return absent(semigroup, x).apply(y); + } + + private static Semigroup> shortCircuitSemigroup(Semigroup aSemigroup) { + return new Semigroup>() { + @Override + public Maybe checkedApply(Maybe maybeX, Maybe maybeY) { + return liftA2(aSemigroup, maybeX, maybeY); + } + + @Override + public Maybe foldLeft(Maybe acc, Iterable> maybes) { + return trampoline( + into((res, it) -> res.equals(nothing()) || !it.hasNext() + ? terminate(res) + : recurse(tuple(liftA2(aSemigroup, res, it.next()), it))), + tuple(acc, maybes.iterator())); + } + + @Override + public Lazy> foldRight(Maybe accumulation, Iterable> as) { + boolean shouldShortCircuit = accumulation == nothing(); + if (shouldShortCircuit) + return lazy(accumulation); + return FoldRight.foldRight( + (maybeX, acc) -> maybeX.lazyZip(acc.fmap(maybeY -> maybeY.fmap(aSemigroup.flip()))), + lazy(accumulation), + as + ); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Collapse.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Collapse.java index 9c2046632..7b163898d 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Collapse.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Collapse.java @@ -21,20 +21,20 @@ */ public final class Collapse<_1, _2> implements BiSemigroupFactory, Semigroup<_2>, Tuple2<_1, _2>> { - private static final Collapse INSTANCE = new Collapse(); + private static final Collapse INSTANCE = new Collapse<>(); private Collapse() { } @Override - public Semigroup> apply(Semigroup<_1> _1Semigroup, Semigroup<_2> _2Semigroup) { + public Semigroup> checkedApply(Semigroup<_1> _1Semigroup, Semigroup<_2> _2Semigroup) { return (x, y) -> x.biMap(_1Semigroup.flip().apply(y._1()), _2Semigroup.flip().apply(y._2())); } @SuppressWarnings("unchecked") public static <_1, _2> Collapse<_1, _2> collapse() { - return INSTANCE; + return (Collapse<_1, _2>) INSTANCE; } public static <_1, _2> SemigroupFactory, Tuple2<_1, _2>> collapse(Semigroup<_1> _1Semigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Compose.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Compose.java new file mode 100644 index 000000000..cba9a218f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Compose.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.concurrent.CompletableFuture; + +/** + * A {@link Semigroup} instance formed by {@link CompletableFuture}<A> and a semigroup over + * A. If either {@link CompletableFuture}s completes exceptionally, the result is also an exceptionally + * completed future. + *

+ * Note that this operation only takes as long as the slowest future to complete. + *

+ * For the {@link Monoid}, see {@link com.jnape.palatable.lambda.monoid.builtin.Compose}. + * + * @param the future parameter type + */ +public final class Compose implements SemigroupFactory, CompletableFuture> { + + private static final Compose INSTANCE = new Compose<>(); + + private Compose() { + } + + @Override + public Semigroup> checkedApply(Semigroup aSemigroup) { + return (futureX, futureY) -> futureX.thenCompose(x -> futureY.thenApply(y -> aSemigroup.apply(x, y))); + } + + @SuppressWarnings("unchecked") + public static Compose compose() { + return (Compose) INSTANCE; + } + + public static Semigroup> compose(Semigroup aSemigroup) { + return Compose.compose().apply(aSemigroup); + } + + public static Fn1, CompletableFuture> compose(Semigroup aSemigroup, + CompletableFuture x) { + return compose(aSemigroup).apply(x); + } + + public static CompletableFuture compose(Semigroup aSemigroup, + CompletableFuture x, + CompletableFuture y) { + return compose(aSemigroup, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Concat.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Concat.java deleted file mode 100644 index 9d119a97f..000000000 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Concat.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.jnape.palatable.lambda.semigroup.builtin; - -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.monoid.Monoid; -import com.jnape.palatable.lambda.semigroup.Semigroup; - -import java.util.Collection; - -/** - * The {@link Semigroup} instance formed under concatenation for an arbitrary {@link Collection}. The collection subtype - * (C) must support {@link Collection#addAll(Collection)}. - *

- * For the {@link Monoid}, see {@link com.jnape.palatable.lambda.monoid.builtin.Concat}. - * - * @see Semigroup - */ -public final class Concat> implements Semigroup { - - private static final Concat INSTANCE = new Concat(); - - private Concat() { - } - - @Override - public C apply(C xs, C ys) { - xs.addAll(ys); - return xs; - } - - @SuppressWarnings("unchecked") - public static > Concat concat() { - return INSTANCE; - } - - public static > Fn1 concat(C xs) { - return Concat.concat().apply(xs); - } - - public static > C concat(C xs, C ys) { - return concat(xs).apply(ys); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Intersection.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Intersection.java new file mode 100644 index 000000000..cf33e9b3a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Intersection.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Distinct; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Distinct.distinct; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Find.find; + +/** + * Given two {@link Iterable Iterables} xs and ys, return the {@link Distinct distinct} + * elements of xs that are also in ys in order of their unique occurrence in xs. + * + * @param the {@link Iterable} element type + */ +public final class Intersection implements Semigroup> { + + private static final Intersection INSTANCE = new Intersection<>(); + + private Intersection() { + } + + @Override + public Iterable checkedApply(Iterable xs, Iterable ys) { + return filter(x -> find(eq(x), ys).fmap(constantly(true)).orElse(false), distinct(xs)); + } + + @SuppressWarnings("unchecked") + public static Intersection intersection() { + return (Intersection) INSTANCE; + } + + public static Fn1, Iterable> intersection(Iterable xs) { + return Intersection.intersection().apply(xs); + } + + public static Iterable intersection(Iterable xs, Iterable ys) { + return intersection(xs).apply(ys); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAll.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAll.java index 70ef187fd..e5ac2caf1 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAll.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAll.java @@ -30,19 +30,19 @@ */ public final class LeftAll implements SemigroupFactory, Either> { - private static final LeftAll INSTANCE = new LeftAll(); + private static final LeftAll INSTANCE = new LeftAll<>(); private LeftAll() { } @Override - public Semigroup> apply(Semigroup lSemigroup) { - return (x, y) -> x.flatMap(xL -> y.flatMap(yL -> left(lSemigroup.apply(xL, yL)), Either::right), Either::right); + public Semigroup> checkedApply(Semigroup lSemigroup) { + return (x, y) -> x.match(xL -> y.match(yL -> left(lSemigroup.apply(xL, yL)), Either::right), Either::right); } @SuppressWarnings("unchecked") public static LeftAll leftAll() { - return INSTANCE; + return (LeftAll) INSTANCE; } public static Semigroup> leftAll(Semigroup lSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAny.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAny.java index 1bc166dca..91a892dfb 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAny.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAny.java @@ -29,21 +29,21 @@ */ public final class LeftAny implements SemigroupFactory, Either> { - private static final LeftAny INSTANCE = new LeftAny(); + private static final LeftAny INSTANCE = new LeftAny<>(); private LeftAny() { } @Override - public Semigroup> apply(Semigroup lSemigroup) { - return (x, y) -> x.flatMap(xL -> y.flatMap(yL -> left(lSemigroup.apply(xL, yL)), - yR -> left(xL)), - xR -> y); + public Semigroup> checkedApply(Semigroup lSemigroup) { + return (x, y) -> x.match(xL -> y.match(yL -> left(lSemigroup.apply(xL, yL)), + yR -> left(xL)), + xR -> y); } @SuppressWarnings("unchecked") public static LeftAny leftAny() { - return INSTANCE; + return (LeftAny) INSTANCE; } public static Semigroup> leftAny(Semigroup lSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Max.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Max.java new file mode 100644 index 000000000..a9cc3db66 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Max.java @@ -0,0 +1,45 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.semigroup.builtin.MaxBy.maxBy; + +/** + * A {@link Semigroup} over A that chooses between two values x and y via the + * following rules: + *

+ * + * @param the value type + * @see MaxBy + * @see Min + */ +public final class Max> implements Semigroup { + + private static final Max INSTANCE = new Max<>(); + + private Max() { + } + + @Override + public A checkedApply(A x, A y) { + return maxBy(id(), x, y); + } + + @SuppressWarnings("unchecked") + public static > Max max() { + return (Max) INSTANCE; + } + + public static > Fn1 max(A x) { + return Max.max().apply(x); + } + + public static > A max(A x, A y) { + return max(x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java new file mode 100644 index 000000000..2abee980e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTBy.ltBy; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B, produce a + * {@link Semigroup} over A that chooses between two values x and y via the + * following rules: + *
    + *
  • If x is strictly less than y in terms of B, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @param the mapped comparison type + * @see Max + * @see MaxWith + * @see MinBy + */ +public final class MaxBy> implements SemigroupFactory, A> { + + private static final MaxBy INSTANCE = new MaxBy<>(); + + private MaxBy() { + } + + @Override + public Semigroup checkedApply(Fn1 compareFn) { + return (x, y) -> ltBy(compareFn, y, x) ? y : x; + } + + @SuppressWarnings("unchecked") + public static > MaxBy maxBy() { + return (MaxBy) INSTANCE; + } + + public static > Semigroup maxBy(Fn1 compareFn) { + return MaxBy.maxBy().apply(compareFn); + } + + public static > Fn1 maxBy(Fn1 compareFn, A x) { + return MaxBy.maxBy(compareFn).apply(x); + } + + public static > A maxBy(Fn1 compareFn, A x, A y) { + return maxBy(compareFn, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java new file mode 100644 index 000000000..ed3f07373 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; + +/** + * Given a comparator for some type A, produce a {@link Semigroup} over A that chooses + * between two values x and y via the following rules: + *
    + *
  • If x is strictly less than y in terms of B, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @see Max + * @see MaxBy + * @see MinWith + */ +public final class MaxWith implements SemigroupFactory, A> { + + private static final MaxWith INSTANCE = new MaxWith<>(); + + private MaxWith() { + } + + @SuppressWarnings("unchecked") + public static MaxWith maxWith() { + return (MaxWith) INSTANCE; + } + + public static Semigroup maxWith(Comparator compareFn) { + return MaxWith.maxWith().apply(compareFn); + } + + public static Fn1 maxWith(Comparator compareFn, A x) { + return MaxWith.maxWith(compareFn).apply(x); + } + + public static A maxWith(Comparator compareFn, A x, A y) { + return maxWith(compareFn, x).apply(y); + } + + @Override + public Semigroup checkedApply(Comparator comparator) { + return (x, y) -> ltWith(comparator, y, x) ? y : x; + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Merge.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Merge.java index 75bd81ffd..833e1aa57 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Merge.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Merge.java @@ -20,19 +20,19 @@ */ public final class Merge implements BiSemigroupFactory, Semigroup, Either> { - private static final Merge INSTANCE = new Merge(); + private static final Merge INSTANCE = new Merge<>(); private Merge() { } @Override - public Semigroup> apply(Semigroup lSemigroup, Semigroup rSemigroup) { + public Semigroup> checkedApply(Semigroup lSemigroup, Semigroup rSemigroup) { return (x, y) -> x.merge(lSemigroup::apply, rSemigroup::apply, y); } @SuppressWarnings("unchecked") public static Merge merge() { - return INSTANCE; + return (Merge) INSTANCE; } public static SemigroupFactory, Either> merge(Semigroup lSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Min.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Min.java new file mode 100644 index 000000000..747bd5d25 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Min.java @@ -0,0 +1,45 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.semigroup.builtin.MinBy.minBy; + +/** + * A {@link Semigroup} over A that chooses between two values x and y via the + * following rules: + *
    + *
  • If x is strictly greater than y, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @see MinBy + * @see Max + */ +public final class Min> implements Semigroup { + + private static final Min INSTANCE = new Min<>(); + + private Min() { + } + + @Override + public A checkedApply(A x, A y) { + return minBy(id(), x, y); + } + + @SuppressWarnings("unchecked") + public static > Min min() { + return (Min) INSTANCE; + } + + public static > Fn1 min(A x) { + return Min.min().apply(x); + } + + public static > A min(A x, A y) { + return min(x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinBy.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinBy.java new file mode 100644 index 000000000..07ee12999 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinBy.java @@ -0,0 +1,51 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTBy.gtBy; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B, produce a + * {@link Semigroup} over A that chooses between two values x and y via the + * following rules: + *
    + *
  • If x is strictly greater than y in terms of B, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @param the mapped comparison type + * @see Min + * @see MaxBy + */ +public final class MinBy> implements SemigroupFactory, A> { + + private static final MinBy INSTANCE = new MinBy<>(); + + private MinBy() { + } + + @Override + public Semigroup checkedApply(Fn1 compareFn) { + return (x, y) -> gtBy(compareFn, y, x) ? y : x; + } + + @SuppressWarnings("unchecked") + public static > MinBy minBy() { + return (MinBy) INSTANCE; + } + + public static > Semigroup minBy(Fn1 compareFn) { + return MinBy.minBy().apply(compareFn); + } + + public static > Fn1 minBy(Fn1 compareFn, A x) { + return MinBy.minBy(compareFn).apply(x); + } + + public static > A minBy(Fn1 compareFn, A x, A y) { + return minBy(compareFn, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java new file mode 100644 index 000000000..05eec2528 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; + +/** + * Given a comparator for some type A, produce a {@link Semigroup} over A that chooses + * between two values x and y via the following rules: + *
    + *
  • If x is strictly greater than y in terms of B, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @see Min + * @see MinBy + * @see MaxBy + */ +public final class MinWith implements SemigroupFactory, A> { + + private static final MinWith INSTANCE = new MinWith<>(); + + private MinWith() { + } + + @SuppressWarnings("unchecked") + public static MinWith minWith() { + return (MinWith) INSTANCE; + } + + public static Semigroup minWith(Comparator compareFn) { + return MinWith.minWith().apply(compareFn); + } + + public static Fn1 minWith(Comparator compareFn, A x) { + return MinWith.minWith(compareFn).apply(x); + } + + public static A minWith(Comparator compareFn, A x, A y) { + return minWith(compareFn, x).apply(y); + } + + @Override + public Semigroup checkedApply(Comparator comparator) { + return (x, y) -> gtWith(comparator, y, x) ? y : x; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAll.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAll.java index 7f1bdab5b..5bb6fa018 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAll.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAll.java @@ -29,19 +29,19 @@ */ public final class RightAll implements SemigroupFactory, Either> { - private static final RightAll INSTANCE = new RightAll(); + private static final RightAll INSTANCE = new RightAll<>(); private RightAll() { } @Override - public Semigroup> apply(Semigroup rSemigroup) { + public Semigroup> checkedApply(Semigroup rSemigroup) { return (eitherX, eitherY) -> eitherX.flatMap(xR -> eitherY.flatMap(yR -> right(rSemigroup.apply(xR, yR)))); } @SuppressWarnings("unchecked") public static RightAll rightAll() { - return INSTANCE; + return (RightAll) INSTANCE; } public static Semigroup> rightAll(Semigroup rSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAny.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAny.java index 04531cbfb..8dcc439a3 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAny.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAny.java @@ -30,21 +30,21 @@ */ public final class RightAny implements SemigroupFactory, Either> { - private static final RightAny INSTANCE = new RightAny(); + private static final RightAny INSTANCE = new RightAny<>(); private RightAny() { } @Override - public Semigroup> apply(Semigroup rSemigroup) { - return (x, y) -> x.flatMap(constantly(y), - xR -> y.flatMap(constantly(right(xR)), - rSemigroup.apply(xR).andThen(Either::right))); + public Semigroup> checkedApply(Semigroup rSemigroup) { + return (x, y) -> x.match(constantly(y), + xR -> y.match(constantly(right(xR)), + rSemigroup.apply(xR).fmap(Either::right))); } @SuppressWarnings("unchecked") public static RightAny rightAny() { - return INSTANCE; + return (RightAny) INSTANCE; } public static Semigroup> rightAny(Semigroup rSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RunAll.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RunAll.java new file mode 100644 index 000000000..f84d0243a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RunAll.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +/** + * Run {@link IO} operations, aggregating their results in terms of the provided {@link Semigroup}. + * + * @param the {@link IO} result + * @see com.jnape.palatable.lambda.monoid.builtin.RunAll + */ +public final class RunAll implements SemigroupFactory, IO> { + + private static final RunAll INSTANCE = new RunAll<>(); + + private RunAll() { + } + + @Override + public Semigroup> checkedApply(Semigroup semigroup) { + return (ioX, ioY) -> ioY.zip(ioX.fmap(semigroup)); + } + + @SuppressWarnings("unchecked") + public static RunAll runAll() { + return (RunAll) INSTANCE; + } + + public static Semigroup> runAll(Semigroup semigroup) { + return RunAll.runAll().apply(semigroup); + } + + public static Fn1, IO> runAll(Semigroup semigroup, IO ioX) { + return runAll(semigroup).apply(ioX); + } + + public static IO runAll(Semigroup semigroup, IO ioX, IO ioY) { + return runAll(semigroup, ioX).apply(ioY); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java new file mode 100644 index 000000000..0134549b1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java @@ -0,0 +1,200 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Empty; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.internal.iteration.TrampoliningIterator; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; + +import java.util.Iterator; +import java.util.Objects; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; + +/** + * Extension point for {@link Iterable} to adapt lambda core types like {@link Monad} and {@link Traversable}. + * + * @param the {@link Iterable} element type + * @see LambdaMap + */ +public final class LambdaIterable implements + MonadRec>, + Traversable> { + + private final Iterable as; + + @SuppressWarnings("unchecked") + private LambdaIterable(Iterable as) { + this.as = (Iterable) as; + } + + /** + * Unwrap the underlying {@link Iterable}. + * + * @return the wrapped {@link Iterable} + */ + public Iterable unwrap() { + return as; + } + + /** + * {@inheritDoc} + */ + @Override + public LambdaIterable fmap(Fn1 fn) { + return wrap(map(fn, as)); + } + + /** + * {@inheritDoc} + */ + @Override + public LambdaIterable pure(B b) { + return wrap(singleton(b)); + } + + /** + * {@inheritDoc} + *

+ * In this case, calculate the cartesian product of applications of all functions in appFn to all + * values wrapped by this {@link LambdaIterable}. + * + * @param appFn the other applicative instance + * @param the new parameter type + * @return the zipped LambdaIterable + */ + @Override + public LambdaIterable zip(Applicative, LambdaIterable> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, LambdaIterable>> lazyAppFn) { + return Empty.empty(as) + ? lazy(LambdaIterable.empty()) + : MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public LambdaIterable discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public LambdaIterable discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public LambdaIterable flatMap(Fn1>> f) { + return wrap(flatten(map(a -> f.apply(a).>coerce().unwrap(), as))); + } + + /** + * {@inheritDoc} + */ + @Override + public LambdaIterable trampolineM( + Fn1, LambdaIterable>> fn) { + return flatMap(a -> wrap(() -> new TrampoliningIterator<>( + x -> fn.apply(x) + .>>coerce() + .unwrap(), + a))); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return FoldRight.foldRight( + (a, lazyAppTrav) -> (Lazy) fn.apply(a) + .lazyZip(lazyAppTrav., App>>fmap(appTrav -> appTrav + .fmap(travB -> b -> (TravB) wrap(cons(b, ((LambdaIterable) travB).unwrap()))))), + lazy(() -> pure.apply((TravB) empty())), + as + ).value(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof LambdaIterable) { + Iterator xs = as.iterator(); + Iterator ys = ((LambdaIterable) other).as.iterator(); + + while (xs.hasNext() && ys.hasNext()) + if (!Objects.equals(xs.next(), ys.next())) + return false; + + return xs.hasNext() == ys.hasNext(); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(as); + } + + /** + * Wrap an {@link Iterable} in a {@link LambdaIterable}. + * + * @param as the Iterable + * @param the Iterable element type + * @return the Iterable wrapped in a {@link LambdaIterable} + */ + public static LambdaIterable wrap(Iterable as) { + return new LambdaIterable<>(as); + } + + /** + * Construct an empty {@link LambdaIterable} by wrapping {@link java.util.Collections#emptyList()}. + * + * @param the Iterable element type + * @return an empty {@link LambdaIterable} + */ + public static LambdaIterable empty() { + return wrap(emptyList()); + } + + /** + * The canonical {@link Pure} instance for {@link LambdaIterable}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureLambdaIterable() { + return new Pure>() { + @Override + public LambdaIterable checkedApply(A a) throws Throwable { + return wrap(singleton(a)); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaMap.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaMap.java new file mode 100644 index 000000000..f8e8c7fd2 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaMap.java @@ -0,0 +1,97 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn2.curried; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToMap.toMap; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +import static java.util.Collections.emptyMap; + +/** + * Extension point for {@link Map} to adapt lambda core types like {@link Functor} and {@link Traversable}. + * + * @param the {@link Map} element type + * @see LambdaIterable + */ +public final class LambdaMap implements Functor>, Traversable> { + private final Map map; + + private LambdaMap(Map map) { + this.map = map; + } + + /** + * Unwrap the underlying {@link Map}. + * + * @return the wrapped {@link Map} + */ + public Map unwrap() { + return map; + } + + @Override + public LambdaMap fmap(Fn1 fn) { + return wrap(toMap(HashMap::new, map(entry -> tuple(entry.getKey(), fn.apply(entry.getValue())), map.entrySet()))); + } + + @Override + @SuppressWarnings("unchecked") + public , TravC extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return foldLeft(curried(appTrav -> into((k, appV) -> (AppTrav) appTrav.zip(appV.fmap(v -> m -> { + ((LambdaMap) m).unwrap().put(k, v); + return m; + })))), + pure.apply((TravC) LambdaMap.wrap(new HashMap<>())), + this.fmap(fn).unwrap().entrySet()); + } + + @Override + public boolean equals(Object other) { + return other instanceof LambdaMap && Objects.equals(map, ((LambdaMap) other).map); + } + + @Override + public int hashCode() { + return Objects.hash(map); + } + + @Override + public String toString() { + return "LambdaMap{map=" + map + '}'; + } + + /** + * Wrap a {@link Map} in a {@link LambdaMap}. + * + * @param map the {@link Map} + * @param the key type + * @param the value type + * @return the {@link Map} wrapped in a {@link LambdaMap} + */ + public static LambdaMap wrap(Map map) { + return new LambdaMap<>(map); + } + + /** + * Construct an empty {@link LambdaMap} by wrapping {@link Collections#emptyMap()} + * + * @param the key type + * @param the value type + * @return an empty {@link LambdaMap} + */ + public static LambdaMap empty() { + return wrap(emptyMap()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java new file mode 100644 index 000000000..54caa1317 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java @@ -0,0 +1,57 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Identity; + +/** + * An interface for a class of data structures that can be "traversed from left to right" in a structure-preserving + * way, successively applying some applicative computation to each element and collapsing the results into a single + * resulting applicative. + *

+ * The same rules that apply to Functor apply to Traversable, along with the following + * additional 3 laws: + *

+ *

+ * For more information, read about + * Traversables. + * + * @param The type of the parameter + * @param The unification parameter + */ +public interface Traversable> extends Functor { + + /** + * Apply fn to each element of this traversable from left to right, and collapse the results into + * a single resulting applicative, potentially with the assistance of the applicative's pure function. + * + * @param the resulting element type + * @param the result applicative type + * @param this Traversable instance over B + * @param the full inferred resulting type from the traversal + * @param fn the function to apply + * @param pure the applicative pure function + * @return the traversed Traversable, wrapped inside an applicative + */ + , TravB extends Traversable, + AppTrav extends Applicative> AppTrav traverse( + Fn1> fn, Fn1 pure); + + /** + * {@inheritDoc} + */ + @Override + default Traversable fmap(Fn1 fn) { + return traverse(a -> new Identity(fn.apply(a)), Identity::new).runIdentity(); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 165d9b019..50921161d 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -1,29 +1,60 @@ package com.jnape.palatable.lambda.adt; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; -import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiFunction; -import static com.jnape.palatable.lambda.adt.Either.fromOptional; +import static com.jnape.palatable.lambda.adt.Either.fromMaybe; import static com.jnape.palatable.lambda.adt.Either.left; import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static testsupport.assertion.MonadErrorAssert.assertLaws; +@RunWith(Traits.class) public class EitherTest { @Rule public ExpectedException thrown = ExpectedException.none(); + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + BifunctorLaws.class, + TraversableLaws.class, + MonadRecLaws.class}) + public Subjects> testSubjects() { + return subjects(left("foo"), right(1)); + } + + @Test + public void monadError() { + assertLaws(subjects(left("a"), right(1)), "bar", e -> right(e.length())); + } + @Test public void recoverLiftsLeftAndFlattensRight() { - Either left = left("foo"); + Either left = left("foo"); Either right = right(1); assertThat(left.recover(l -> -1), is(-1)); @@ -32,7 +63,7 @@ public void recoverLiftsLeftAndFlattensRight() { @Test public void forfeitLiftsRightAndFlattensLeft() { - Either left = left("foo"); + Either left = left("foo"); Either right = right(1); assertThat(left.forfeit(r -> "bar"), is("foo")); @@ -41,7 +72,7 @@ public void forfeitLiftsRightAndFlattensLeft() { @Test public void orReplacesLeftAndFlattensRight() { - Either left = left("foo"); + Either left = left("foo"); Either right = right(1); assertThat(left.or(-1), is(-1)); @@ -50,7 +81,7 @@ public void orReplacesLeftAndFlattensRight() { @Test public void orThrowFlattensRightOrThrowsException() { - Either left = left("foo"); + Either left = left("foo"); Either right = right(1); assertThat(right.orThrow(IllegalStateException::new), is(1)); @@ -63,7 +94,7 @@ public void orThrowFlattensRightOrThrowsException() { @Test public void filterLiftsRight() { - Either left = left("foo"); + Either left = left("foo"); Either right = right(1); assertThat(left.filter(x -> true, () -> "bar"), is(left)); @@ -73,42 +104,35 @@ public void filterLiftsRight() { } @Test - public void monadicFlatMapLiftsRightAndFlattensBackToEither() { - Either left = left("foo"); + public void filterSupportsFunctionFromRToL() { + Either left = left("foo"); Either right = right(1); - assertThat(left.flatMap(r -> right(r + 1)), is(left("foo"))); - assertThat(right.flatMap(r -> right(r + 1)), is(right(2))); + assertThat(left.filter(x -> true, Object::toString), is(left)); + assertThat(left.filter(x -> false, Object::toString), is(left)); + assertThat(right.filter(x -> true, Object::toString), is(right)); + assertThat(right.filter(x -> false, Object::toString), is(left("1"))); } @Test - public void dyadicFlatMapDuallyLiftsAndFlattensBackToEither() { - Either left = left("foo"); + public void monadicFlatMapLiftsRightAndFlattensBackToEither() { + Either left = left("foo"); Either right = right(1); - assertThat(left.flatMap(l -> left(l + "bar"), r -> right(r + 1)), is(left("foobar"))); - assertThat(right.flatMap(l -> left(l + "bar"), r -> right(r + 1)), is(right(2))); - } - - @Test - public void invertSwapsParameters() { - Either left = left("foo"); - assertEquals(right("foo"), left.invert()); - - Either right = right(1); - assertEquals(left(1), right.invert()); + assertThat(left.flatMap(r -> right(r + 1)), is(left("foo"))); + assertThat(right.flatMap(r -> right(r + 1)), is(right(2))); } @Test public void mergeDuallyLiftsAndCombinesBiasingLeft() { - Either left1 = left("foo"); + Either left1 = left("foo"); Either right1 = right(1); - Either left2 = left("bar"); + Either left2 = left("bar"); Either right2 = right(2); - BiFunction concat = String::concat; - BiFunction add = (r1, r2) -> r1 + r2; + Fn2 concat = String::concat; + Fn2 add = Integer::sum; assertThat(left1.merge(concat, add, left2), is(left("foobar"))); assertThat(left1.merge(concat, add, right2), is(left1)); @@ -118,7 +142,7 @@ public void mergeDuallyLiftsAndCombinesBiasingLeft() { @Test public void matchDuallyLiftsAndFlattens() { - Either left = left("foo"); + Either left = left("foo"); Either right = right(1); assertThat(left.match(l -> l + "bar", r -> r + 1), is("foobar")); @@ -126,48 +150,29 @@ public void matchDuallyLiftsAndFlattens() { } @Test - public void toOptionalMapsEitherToOptional() { - assertEquals(Optional.of(1), Either.right(1).toOptional()); - assertEquals(Optional.empty(), Either.right(null).toOptional()); - assertEquals(Optional.empty(), Either.left("fail").toOptional()); + public void toMaybeMapsEitherToOptional() { + assertEquals(just(1), Either.right(1).toMaybe()); + assertEquals(nothing(), Either.left("fail").toMaybe()); } @Test - public void fromOptionalMapsOptionalToEither() { - Optional present = Optional.of(1); - Optional absent = Optional.empty(); + public void fromMaybeMapsMaybeToEither() { + Maybe just = just(1); + Maybe nothing = nothing(); - assertThat(fromOptional(present, () -> "fail"), is(right(1))); - assertThat(fromOptional(absent, () -> "fail"), is(left("fail"))); + assertThat(fromMaybe(just, () -> "fail"), is(right(1))); + assertThat(fromMaybe(nothing, () -> "fail"), is(left("fail"))); } @Test - public void fromOptionalDoesNotEvaluateLeftFnForRight() { - Optional present = Optional.of(1); - AtomicInteger atomicInteger = new AtomicInteger(0); - fromOptional(present, atomicInteger::incrementAndGet); + public void fromMaybeDoesNotEvaluateLeftFnForRight() { + Maybe just = just(1); + AtomicInteger atomicInteger = new AtomicInteger(0); + fromMaybe(just, atomicInteger::incrementAndGet); assertThat(atomicInteger.get(), is(0)); } - @Test - public void functorProperties() { - Either left = left("foo"); - Either right = right(1); - - assertThat(left.fmap(r -> r + 1), is(left)); - assertThat(right.fmap(r -> r + 1), is(right(2))); - } - - @Test - public void bifunctorProperties() { - Either left = left("foo"); - Either right = right(1); - - assertThat(left.biMap(l -> l + "bar", r -> r + 1), is(left("foobar"))); - assertThat(right.biMap(l -> l + "bar", r -> r + 1), is(right(2))); - } - @Test public void monadicTryingLiftsCheckedSupplier() { assertEquals(right(1), Either.trying(() -> 1)); @@ -187,33 +192,31 @@ public void dyadicTryingLiftsCheckedSupplierMappingAnyThrownExceptions() { } @Test - public void monadicPeekLiftsIOToTheRight() { - Either left = left("foo"); - Either right = right(1); - - AtomicInteger intRef = new AtomicInteger(); - - left.peek(intRef::set); - assertEquals(0, intRef.get()); - - right.peek(intRef::set); - assertEquals(1, intRef.get()); + public void dyadicTryingWithRunnable() { + assertEquals(right(UNIT), Either.trying(() -> {}, Throwable::getMessage)); + assertEquals(left("expected"), Either.trying(() -> { + throw new IllegalStateException("expected"); + }, Throwable::getMessage)); } @Test - public void dyadicPeekDuallyLiftsIO() { - Either left = left("foo"); - Either right = right(1); - - AtomicReference stringRef = new AtomicReference<>(); - AtomicInteger intRef = new AtomicInteger(); + public void monadTryingWithRunnable() { + assertEquals(right(UNIT), Either.trying(() -> {})); + IllegalStateException expected = new IllegalStateException("expected"); + assertEquals(left(expected), Either.trying(() -> {throw expected;})); + } - left.peek(stringRef::set, intRef::set); - assertEquals("foo", stringRef.get()); - assertEquals(0, intRef.get()); + @Test + public void lazyZip() { + assertEquals(right(2), right(1).lazyZip(lazy(right(x -> x + 1))).value()); + assertEquals(left("foo"), left("foo").lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } - right.peek(stringRef::set, intRef::set); - assertEquals("foo", stringRef.get()); - assertEquals(1, intRef.get()); + @Test + public void staticPure() { + Either either = Either.pureEither().apply(1); + assertEquals(right(1), either); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java b/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java new file mode 100644 index 000000000..6668dbc19 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java @@ -0,0 +1,146 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.adt.choice.Choice3; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import java.util.Optional; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Maybe.pureMaybe; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static testsupport.assertion.MonadErrorAssert.assertLaws; + +@RunWith(Traits.class) +public class MaybeTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Subjects> testSubject() { + return subjects(Maybe.nothing(), just(1)); + } + + @Test + public void monadError() { + assertLaws(subjects(nothing(), just(1)), UNIT, constantly(just(2))); + } + + @Test(expected = NullPointerException.class) + public void justMustBeNonNull() { + just(null); + } + + @Test + public void nothingReusesInstance() { + assertSame(Maybe.nothing(), Maybe.nothing()); + } + + @Test + public void maybeAllowsNull() { + assertEquals(just(1), Maybe.maybe(1)); + assertEquals(Maybe.nothing(), Maybe.maybe(null)); + } + + @Test + public void orElseGet() { + assertEquals((Integer) 1, just(1).orElseGet(() -> -1)); + assertEquals(-1, Maybe.nothing().orElseGet(() -> -1)); + } + + @Test + public void orElse() { + assertEquals((Integer) 1, just(1).orElse(-1)); + assertEquals(-1, Maybe.nothing().orElse(-1)); + } + + @Test + public void filter() { + assertEquals(just(1), just(1).filter(eq(1))); + assertEquals(nothing(), just(0).filter(eq(1))); + assertEquals(nothing(), nothing().filter(eq(1))); + } + + @Test + public void toOptional() { + assertEquals(Optional.of(1), just(1).toOptional()); + assertEquals(Optional.empty(), Maybe.nothing().toOptional()); + } + + @Test + public void fromOptional() { + assertEquals(just(1), Maybe.fromOptional(Optional.of(1))); + assertEquals(Maybe.nothing(), Maybe.fromOptional(Optional.empty())); + } + + @Test + public void toEither() { + assertEquals(right(1), just(1).toEither(() -> "empty")); + assertEquals(left("empty"), nothing().toEither(() -> "empty")); + } + + @Test + public void fromEither() { + assertEquals(just(1), Maybe.fromEither(right(1))); + assertEquals(nothing(), Maybe.fromEither(left("failure"))); + } + + @Test + public void justOrThrow() { + just(1).orElseThrow(IllegalStateException::new); + } + + @Test(expected = IllegalStateException.class) + public void nothingOrThrow() { + nothing().orElseThrow(IllegalStateException::new); + } + + @Test + public void divergesIntoChoice3() { + assertEquals(Choice3.a(UNIT), nothing().diverge()); + assertEquals(Choice3.b(1), just(1).diverge()); + } + + @Test + public void projectsIntoTuple2() { + assertEquals(tuple(just(UNIT), nothing()), nothing().project()); + assertEquals(tuple(nothing(), just(1)), just(1).project()); + } + + @Test + public void invertsIntoChoice2() { + assertEquals(Choice2.b(UNIT), nothing().invert()); + assertEquals(Choice2.a(1), just(1).invert()); + } + + @Test + public void lazyZip() { + assertEquals(just(2), just(1).lazyZip(lazy(() -> just(x -> x + 1))).value()); + assertEquals(nothing(), nothing().lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + Maybe maybe = pureMaybe().apply(1); + assertEquals(just(1), maybe); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java new file mode 100644 index 000000000..e9f764d49 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.These.a; +import static com.jnape.palatable.lambda.adt.These.b; +import static com.jnape.palatable.lambda.adt.These.both; +import static com.jnape.palatable.lambda.adt.These.fromMaybes; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; + +@RunWith(Traits.class) +public class TheseTest { + + @TestTraits({FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + TraversableLaws.class, + BifunctorLaws.class}) + public Subjects> testSubject() { + return subjects(a("foo"), b(1), both("foo", 1)); + } + + @Test + public void lazyZip() { + assertEquals(b(2), b(1).lazyZip(lazy(b(x -> x + 1))).value()); + assertEquals(both("foo", 2), b(1).lazyZip(lazy(both("foo", x -> x + 1))).value()); + assertEquals(both("foo", 2), both("foo", 1).lazyZip(lazy(both("bar", x -> x + 1))).value()); + assertEquals(both("foo", 2), both("foo", 1).lazyZip(lazy(b(x -> x + 1))).value()); + assertEquals(a(1), a(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + These these = These.pureThese().apply(1); + assertEquals(b(1), these); + } + + @Test + public void fromMaybesPermutations() { + assertEquals(nothing(), fromMaybes(nothing(), nothing())); + assertEquals(just(These.a(1)), fromMaybes(just(1), nothing())); + assertEquals(just(These.b(1)), fromMaybes(nothing(), just(1))); + assertEquals(just(These.both(1, "hello")), fromMaybes(just(1), just("hello"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java new file mode 100644 index 000000000..dd4ff5dfb --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java @@ -0,0 +1,269 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Try.failure; +import static com.jnape.palatable.lambda.adt.Try.pureTry; +import static com.jnape.palatable.lambda.adt.Try.success; +import static com.jnape.palatable.lambda.adt.Try.trying; +import static com.jnape.palatable.lambda.adt.Try.withResources; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static testsupport.assertion.MonadErrorAssert.assertLaws; +import static testsupport.matchers.EitherMatcher.isLeftThat; + +@RunWith(Traits.class) +public class TryTest { + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) + public Subjects> testSubject() { + return subjects(failure(new IllegalStateException()), success(1)); + } + + @Test + public void monadError() { + assertLaws(subjects(failure(new IllegalStateException("a")), + success(1)), + new IOException("bar"), + t -> success(t.getMessage().length())); + } + + @Test + public void catchingWithGenericPredicate() { + Try caught = Try.failure(new RuntimeException()) + .catching(__ -> false, r -> "caught first") + .catching(__ -> true, r -> "caught second"); + + assertEquals(success("caught second"), caught); + } + + @Test + public void catchingIsANoOpForSuccess() { + Try caught = success("success") + .catching(__ -> true, __ -> "caught"); + + assertEquals(success("success"), caught); + } + + @Test + public void firstMatchingCatchBlockWins() { + Try caught = Try.failure(new IllegalStateException()) + .catching(__ -> true, __ -> "first") + .catching(__ -> true, __ -> "second"); + + assertEquals(success("first"), caught); + } + + @Test + public void catchBasedOnExceptionType() { + Try caught = Try.failure(new IllegalStateException()) + .catching(IllegalArgumentException.class, __ -> "illegal argument exception") + .catching(IllegalStateException.class, __ -> "illegal state exception") + .catching(RuntimeException.class, __ -> "runtime exception"); + + assertEquals(success("illegal state exception"), caught); + } + + @Test + public void ensureIfSuccess() { + AtomicInteger invocations = new AtomicInteger(0); + success(1).ensuring((invocations::incrementAndGet)); + assertEquals(1, invocations.get()); + } + + @Test + public void ensureIfFailure() { + AtomicInteger invocations = new AtomicInteger(0); + Try.failure(new IllegalStateException()).ensuring((invocations::incrementAndGet)); + assertEquals(1, invocations.get()); + } + + @Test + public void exceptionThrownInEnsuringBlockIsCaught() { + IllegalStateException expected = new IllegalStateException(); + assertEquals(Try.failure(expected), success(1).ensuring(() -> {throw expected;})); + + Either actual = Try.failure(new IllegalArgumentException()) + .ensuring(() -> { throw expected;}) + .toEither(); + assertThat(actual, isLeftThat(instanceOf(IllegalArgumentException.class))); + assertEquals(left(expected), actual.biMapL(t -> t.getSuppressed()[0])); + } + + @Test + public void forfeitEnsuresFailure() { + IllegalStateException expected = new IllegalStateException(); + assertEquals(expected, Try.failure(expected).forfeit(__ -> new IllegalArgumentException())); + assertEquals(expected, Try.success(1).forfeit(__ -> expected)); + } + + @Test + public void recoverEnsuresSuccess() { + assertEquals((Integer) 1, Try.success(1).recover(constantly(1))); + assertEquals((Integer) 1, Try.failure(new IllegalArgumentException()).recover(constantly(1))); + } + + @Test + public void orThrow() throws Throwable { + assertEquals((Integer) 1, trying(() -> 1).orThrow()); + + Throwable expected = new Exception("expected"); + thrown.expect(equalTo(expected)); + trying(() -> {throw expected;}).orThrow(); + } + + @Test + public void toMaybe() { + assertEquals(just("foo"), success("foo").toMaybe()); + assertEquals(nothing(), Try.failure(new IllegalStateException()).toMaybe()); + } + + @Test + public void toEither() { + assertEquals(right("foo"), success("foo").toEither()); + + IllegalStateException exception = new IllegalStateException(); + assertEquals(left(exception), Try.failure(exception).toEither()); + } + + @Test + public void toEitherWithLeftMappingFunction() { + assertEquals(right(1), success(1).toEither(__ -> "fail")); + assertEquals(left("fail"), Try.failure(new IllegalStateException("fail")).toEither(Throwable::getMessage)); + } + + @Test + public void tryingCatchesAnyThrowableThrownDuringEvaluation() { + IllegalStateException expected = new IllegalStateException(); + assertEquals(failure(expected), trying(() -> {throw expected;})); + + assertEquals(success("foo"), trying(() -> "foo")); + } + + @Test + public void withResourcesCleansUpAutoCloseableInSuccessCase() { + AtomicBoolean closed = new AtomicBoolean(false); + assertEquals(success(1), withResources(() -> (AutoCloseable) () -> closed.set(true), resource -> success(1))); + assertTrue(closed.get()); + } + + @Test + public void withResourcesCleansUpAutoCloseableInFailureCase() { + AtomicBoolean closed = new AtomicBoolean(false); + RuntimeException exception = new RuntimeException(); + assertEquals(Try.failure(exception), withResources(() -> (AutoCloseable) () -> closed.set(true), + resource -> { throw exception; })); + assertTrue(closed.get()); + } + + @Test + public void withResourcesExposesResourceCreationFailure() { + IOException ioException = new IOException(); + assertEquals(Try.failure(ioException), withResources(() -> { throw ioException; }, resource -> success(1))); + } + + @Test + public void withResourcesExposesResourceCloseFailure() { + IOException ioException = new IOException(); + assertEquals(Try.failure(ioException), withResources(() -> (AutoCloseable) () -> { throw ioException; }, + resource -> success(1))); + } + + @Test + public void withResourcesPreservesSuppressedExceptionThrownDuringClose() { + RuntimeException rootException = new RuntimeException(); + IOException nestedIOException = new IOException(); + Try failure = withResources(() -> (AutoCloseable) () -> { throw nestedIOException; }, + resource -> { throw rootException; }); + Throwable thrown = failure.recover(id()); + + assertEquals(thrown, rootException); + assertArrayEquals(new Throwable[]{nestedIOException}, thrown.getSuppressed()); + } + + @Test + public void cascadingWithResourcesClosesInInverseOrder() { + List closeMessages = new ArrayList<>(); + assertEquals(success(1), withResources(() -> (AutoCloseable) () -> closeMessages.add("close a"), + a -> () -> closeMessages.add("close b"), + b -> () -> closeMessages.add("close c"), + c -> success(1))); + assertEquals(asList("close c", "close b", "close a"), closeMessages); + } + + @Test + public void lazyZip() { + assertEquals(success(2), success(1).lazyZip(lazy(success(x -> x + 1))).value()); + IllegalStateException e = new IllegalStateException(); + assertEquals(failure(e), failure(e).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void orThrowCanStillThrowCheckedExceptions() { + try { + Try.trying(() -> { + throw new RuntimeException(); + }).orThrow(); + fail("Expected RuntimeException to be thrown, but nothing was"); + } catch (IOException ioException) { + fail("Expected thrown exception to not be IOException, but merely proving it can still be caught"); + } catch (Exception expected) { + } + } + + @Test + public void orThrowCanTransformFirst() { + try { + Try.trying(() -> { + throw new IllegalStateException(); + }).orThrow(IllegalArgumentException::new); + fail("Expected RuntimeException to be thrown, but nothing was"); + } catch (IllegalStateException ioException) { + fail("Expected thrown exception to not be IllegalStateException, but it was"); + } catch (IllegalArgumentException expected) { + } catch (Exception e) { + fail("A different exception altogether was thrown."); + } + } + + @Test + public void staticPure() { + Try try_ = pureTry().apply(1); + assertEquals(success(1), try_); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java index d7597d9ab..234b423a6 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java @@ -1,12 +1,20 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.*; import static com.jnape.palatable.lambda.adt.choice.Choice2.a; import static com.jnape.palatable.lambda.adt.choice.Choice2.b; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class Choice2Test { private Choice2 a; @@ -18,21 +26,33 @@ public void setUp() { b = b(true); } + @TestTraits({FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + BifunctorLaws.class, + TraversableLaws.class, + MonadRecLaws.class}) + public Subjects> testSubjects() { + return subjects(a("foo"), b(1)); + } + @Test public void divergeStaysInChoice() { - assertEquals(Choice3.a(1), a.diverge()); - assertEquals(Choice3.b(true), b.diverge()); + assertEquals(Choice3.a(1), a.diverge()); + assertEquals(Choice3.b(true), b.diverge()); } @Test - public void functorProperties() { - assertEquals(a, a.fmap(bool -> !bool)); - assertEquals(b(false), b.fmap(bool -> !bool)); + public void lazyZip() { + assertEquals(b(2), b(1).lazyZip(lazy(b(x -> x + 1))).value()); + assertEquals(a("foo"), a("foo").lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); } @Test - public void bifunctorProperties() { - assertEquals(a(-1), a.biMap(i -> i * -1, bool -> !bool)); - assertEquals(b(false), b.biMap(i -> i * -1, bool -> !bool)); + public void staticPure() { + Choice2 choice = Choice2.pureChoice().apply((short) 2); + assertEquals(b((short) 2), choice); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java index 0b4b0a9f8..8454d9f67 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java @@ -1,12 +1,21 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.*; +import static com.jnape.palatable.lambda.adt.choice.Choice3.a; import static com.jnape.palatable.lambda.adt.choice.Choice3.b; import static com.jnape.palatable.lambda.adt.choice.Choice3.c; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class Choice3Test { private Choice3 a; @@ -20,6 +29,11 @@ public void setUp() { c = Choice3.c(true); } + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class, MonadRecLaws.class}) + public Subjects> testSubjects() { + return subjects(a("foo"), b(1), c(true)); + } + @Test public void convergeStaysInChoice() { assertEquals(Choice2.a(1), a.converge(c -> Choice2.b(c.toString()))); @@ -35,16 +49,19 @@ public void divergeStaysInChoice() { } @Test - public void functorProperties() { - assertEquals(a, a.fmap(bool -> !bool)); - assertEquals(b, b.fmap(bool -> !bool)); - assertEquals(c(false), c.fmap(bool -> !bool)); + public void lazyZip() { + assertEquals(Choice3.c(2), c(1).lazyZip(lazy(c(x -> x + 1))).value()); + assertEquals(Choice3.b(1), b(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(Choice3.a(1), a(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); } @Test - public void bifunctorProperties() { - assertEquals(a, a.biMap(String::toUpperCase, bool -> !bool)); - assertEquals(b("TWO"), b.biMap(String::toUpperCase, bool -> !bool)); - assertEquals(c(false), c.biMap(String::toUpperCase, bool -> !bool)); + public void staticPure() { + Choice3 choice = Choice3.pureChoice().apply(3); + assertEquals(c(3), choice); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java index 5be43ca7c..630a8979e 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java @@ -1,14 +1,27 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice4.a; import static com.jnape.palatable.lambda.adt.choice.Choice4.b; import static com.jnape.palatable.lambda.adt.choice.Choice4.c; import static com.jnape.palatable.lambda.adt.choice.Choice4.d; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class Choice4Test { private Choice4 a; @@ -24,35 +37,49 @@ public void setUp() { d = d(4D); } + @TestTraits({FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + BifunctorLaws.class, + TraversableLaws.class, + MonadRecLaws.class}) + public Subjects> testSubjects() { + return subjects(a("foo"), b(1), c(true), d('a')); + } + @Test public void convergeStaysInChoice() { - assertEquals(Choice3.a(1), a.converge(d -> Choice3.b(d.toString()))); - assertEquals(Choice3.b("two"), b.converge(d -> Choice3.b(d.toString()))); - assertEquals(Choice3.c(true), c.converge(d -> Choice3.b(d.toString()))); - assertEquals(Choice3.b("4.0"), d.converge(d -> Choice3.b(d.toString()))); + assertEquals(Choice3.a(1), a.converge(d -> Choice3.b(d.toString()))); + assertEquals(Choice3.b("two"), b.converge(d -> Choice3.b(d.toString()))); + assertEquals(Choice3.c(true), c.converge(d -> Choice3.b(d.toString()))); + assertEquals(Choice3.b("4.0"), d.converge(d -> Choice3.b(d.toString()))); } @Test public void divergeStaysInChoice() { - assertEquals(Choice5.a(1), a.diverge()); - assertEquals(Choice5.b("two"), b.diverge()); - assertEquals(Choice5.c(true), c.diverge()); - assertEquals(Choice5.d(4D), d.diverge()); + assertEquals(Choice5.a(1), a.diverge()); + assertEquals(Choice5.b("two"), b.diverge()); + assertEquals(Choice5.c(true), c.diverge()); + assertEquals(Choice5.d(4D), d.diverge()); } @Test - public void functorProperties() { - assertEquals(a, a.fmap(d -> -d)); - assertEquals(b, b.fmap(d -> -d)); - assertEquals(c, c.fmap(d -> -d)); - assertEquals(d(-4D), d.fmap(d -> -d)); + public void lazyZip() { + assertEquals(d(2), d(1).lazyZip(lazy(d(x -> x + 1))).value()); + assertEquals(a(1), a(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(b(1), b(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(c(1), c(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); } @Test - public void bifunctorProperties() { - assertEquals(a, a.biMap(c -> !c, d -> -d)); - assertEquals(b, b.biMap(c -> !c, d -> -d)); - assertEquals(c(false), c.biMap(c -> !c, d -> -d)); - assertEquals(d(-4D), d.biMap(c -> !c, d -> -d)); + public void staticPure() { + Choice4 choice = Choice4.pureChoice().apply(4L); + assertEquals(d(4L), choice); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java index 6792fa4d2..b078dea7d 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java @@ -1,15 +1,19 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.*; -import static com.jnape.palatable.lambda.adt.choice.Choice5.a; -import static com.jnape.palatable.lambda.adt.choice.Choice5.b; -import static com.jnape.palatable.lambda.adt.choice.Choice5.c; -import static com.jnape.palatable.lambda.adt.choice.Choice5.d; -import static com.jnape.palatable.lambda.adt.choice.Choice5.e; +import static com.jnape.palatable.lambda.adt.choice.Choice5.*; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class Choice5Test { private Choice5 a; @@ -27,6 +31,16 @@ public void setUp() { e = e('z'); } + @TestTraits({FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + BifunctorLaws.class, + TraversableLaws.class, + MonadRecLaws.class}) + public Subjects> testSubjects() { + return subjects(Choice5.a("foo"), Choice5.b(1), Choice5.c(true), Choice5.d('a'), Choice5.e(2d)); + } + @Test public void convergeStaysInChoice() { assertEquals(Choice4.a(1), a.converge(e -> Choice4.b(e.toString()))); @@ -37,20 +51,34 @@ public void convergeStaysInChoice() { } @Test - public void functorProperties() { - assertEquals(a, a.fmap(Character::toUpperCase)); - assertEquals(b, b.fmap(Character::toUpperCase)); - assertEquals(c, c.fmap(Character::toUpperCase)); - assertEquals(d, d.fmap(Character::toUpperCase)); - assertEquals(e('Z'), e.fmap(Character::toUpperCase)); + public void divergeStaysInChoice() { + assertEquals(Choice6.a(1), a.diverge()); + assertEquals(Choice6.b("two"), b.diverge()); + assertEquals(Choice6.c(true), c.diverge()); + assertEquals(Choice6.d(4D), d.diverge()); + assertEquals(Choice6.e('z'), e.diverge()); + } + + @Test + public void lazyZip() { + assertEquals(e(2), e(1).lazyZip(lazy(e(x -> x + 1))).value()); + assertEquals(a(1), a(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(b(1), b(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(c(1), c(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(d(1), d(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); } @Test - public void bifunctorProperties() { - assertEquals(a, a.biMap(d -> -d, Character::toUpperCase)); - assertEquals(b, b.biMap(d -> -d, Character::toUpperCase)); - assertEquals(c, c.biMap(d -> -d, Character::toUpperCase)); - assertEquals(d(-4D), d.biMap(d -> -d, Character::toUpperCase)); - assertEquals(e('Z'), e.biMap(d -> -d, Character::toUpperCase)); + public void staticPure() { + Choice5 choice = Choice5.pureChoice().apply(5f); + assertEquals(e(5f), choice); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice6Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice6Test.java new file mode 100644 index 000000000..1d761f0ee --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice6Test.java @@ -0,0 +1,107 @@ +package com.jnape.palatable.lambda.adt.choice; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct5; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import static com.jnape.palatable.lambda.adt.choice.Choice6.a; +import static com.jnape.palatable.lambda.adt.choice.Choice6.b; +import static com.jnape.palatable.lambda.adt.choice.Choice6.c; +import static com.jnape.palatable.lambda.adt.choice.Choice6.d; +import static com.jnape.palatable.lambda.adt.choice.Choice6.e; +import static com.jnape.palatable.lambda.adt.choice.Choice6.f; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; + +@RunWith(Traits.class) +public class Choice6Test { + + private Choice6 a; + private Choice6 b; + private Choice6 c; + private Choice6 d; + private Choice6 e; + private Choice6 f; + + @Before + public void setUp() { + a = a(1); + b = b("two"); + c = c(true); + d = d(4d); + e = e('z'); + f = f(5L); + } + + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + BifunctorLaws.class, + TraversableLaws.class, + MonadRecLaws.class}) + public Subjects> testSubjects() { + return subjects(a("foo"), b(1), c(true), d('a'), e(2d), f(5L)); + } + + @Test + public void convergeStaysInChoice() { + Fn1> convergenceFn = f -> Choice5.b(f.toString()); + + assertEquals(Choice5.a(1), a.converge(convergenceFn)); + assertEquals(Choice5.b("two"), b.converge(convergenceFn)); + assertEquals(Choice5.c(true), c.converge(convergenceFn)); + assertEquals(Choice5.d(4d), d.converge(convergenceFn)); + assertEquals(Choice5.e('z'), e.converge(convergenceFn)); + assertEquals(Choice5.b("5"), f.converge(convergenceFn)); + } + + @Test + public void divergeStaysInChoice() { + assertEquals(Choice7.a(1), a.diverge()); + assertEquals(Choice7.b("two"), b.diverge()); + assertEquals(Choice7.c(true), c.diverge()); + assertEquals(Choice7.d(4D), d.diverge()); + assertEquals(Choice7.e('z'), e.diverge()); + assertEquals(Choice7.f(5L), f.diverge()); + } + + @Test + public void lazyZip() { + assertEquals(f(2), f(1).lazyZip(lazy(f(x -> x + 1))).value()); + assertEquals(a(1), a(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(b(1), b(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(c(1), c(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(d(1), d(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(e(1), e(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + Choice6 choice = + Choice6.pureChoice().apply(6d); + assertEquals(f(6d), choice); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice7Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice7Test.java new file mode 100644 index 000000000..fd2dc7637 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice7Test.java @@ -0,0 +1,115 @@ +package com.jnape.palatable.lambda.adt.choice; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct6; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import static com.jnape.palatable.lambda.adt.choice.Choice7.a; +import static com.jnape.palatable.lambda.adt.choice.Choice7.b; +import static com.jnape.palatable.lambda.adt.choice.Choice7.c; +import static com.jnape.palatable.lambda.adt.choice.Choice7.d; +import static com.jnape.palatable.lambda.adt.choice.Choice7.e; +import static com.jnape.palatable.lambda.adt.choice.Choice7.f; +import static com.jnape.palatable.lambda.adt.choice.Choice7.g; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; + +@RunWith(Traits.class) +public class Choice7Test { + + private Choice7 a; + private Choice7 b; + private Choice7 c; + private Choice7 d; + private Choice7 e; + private Choice7 f; + private Choice7 g; + + @Before + public void setUp() { + a = a(1); + b = b("two"); + c = c(true); + d = d(4d); + e = e('z'); + f = f(5L); + g = g(6F); + } + + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + BifunctorLaws.class, + TraversableLaws.class, + MonadRecLaws.class}) + public Subjects> testSubjects() { + return subjects(a("foo"), b(1), c(true), d('a'), e(2d), f(5L), g(6F)); + } + + @Test + public void convergeStaysInChoice() { + Fn1> convergenceFn = g -> Choice6.b(g.toString()); + + assertEquals(Choice6.a(1), a.converge(convergenceFn)); + assertEquals(Choice6.b("two"), b.converge(convergenceFn)); + assertEquals(Choice6.c(true), c.converge(convergenceFn)); + assertEquals(Choice6.d(4d), d.converge(convergenceFn)); + assertEquals(Choice6.e('z'), e.converge(convergenceFn)); + assertEquals(Choice6.f(5L), f.converge(convergenceFn)); + assertEquals(Choice6.b("6.0"), g.converge(convergenceFn)); + } + + @Test + public void divergeStaysInChoice() { + assertEquals(Choice8.a(1), a.diverge()); + assertEquals(Choice8.b("two"), b.diverge()); + assertEquals(Choice8.c(true), c.diverge()); + assertEquals(Choice8.d(4D), d.diverge()); + assertEquals(Choice8.e('z'), e.diverge()); + assertEquals(Choice8.f(5L), f.diverge()); + assertEquals(Choice8.g(6F), g.diverge()); + } + + @Test + public void lazyZip() { + assertEquals(g(2), g(1).lazyZip(lazy(g(x -> x + 1))).value()); + assertEquals(a(1), a(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(b(1), b(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(c(1), c(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(d(1), d(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(e(1), e(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(f(1), f(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + Choice7 choice = + Choice7.pureChoice().apply(true); + assertEquals(g(true), choice); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice8Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice8Test.java new file mode 100644 index 000000000..acabc9f78 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice8Test.java @@ -0,0 +1,112 @@ +package com.jnape.palatable.lambda.adt.choice; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct7; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import static com.jnape.palatable.lambda.adt.choice.Choice8.a; +import static com.jnape.palatable.lambda.adt.choice.Choice8.b; +import static com.jnape.palatable.lambda.adt.choice.Choice8.c; +import static com.jnape.palatable.lambda.adt.choice.Choice8.d; +import static com.jnape.palatable.lambda.adt.choice.Choice8.e; +import static com.jnape.palatable.lambda.adt.choice.Choice8.f; +import static com.jnape.palatable.lambda.adt.choice.Choice8.g; +import static com.jnape.palatable.lambda.adt.choice.Choice8.h; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; + +@RunWith(Traits.class) +public class Choice8Test { + + private Choice8 a; + private Choice8 b; + private Choice8 c; + private Choice8 d; + private Choice8 e; + private Choice8 f; + private Choice8 g; + private Choice8 h; + + @Before + public void setUp() { + a = a(1); + b = b("two"); + c = c(true); + d = d(4d); + e = e('z'); + f = f(5L); + g = g(6F); + h = h((short) 7); + } + + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + BifunctorLaws.class, + TraversableLaws.class, + MonadRecLaws.class}) + public Subjects> testSubjects() { + return subjects(a("foo"), b(1), c(true), d('a'), e(2d), f(5L), g(6F), h((short) 7)); + } + + @Test + public void convergeStaysInChoice() { + Fn1> convergenceFn = + h -> Choice7.b(h.toString()); + + assertEquals(Choice7.a(1), a.converge(convergenceFn)); + assertEquals(Choice7.b("two"), b.converge(convergenceFn)); + assertEquals(Choice7.c(true), c.converge(convergenceFn)); + assertEquals(Choice7.d(4d), d.converge(convergenceFn)); + assertEquals(Choice7.e('z'), e.converge(convergenceFn)); + assertEquals(Choice7.f(5L), f.converge(convergenceFn)); + assertEquals(Choice7.g(6F), g.converge(convergenceFn)); + assertEquals(Choice7.b("7"), h.converge(convergenceFn)); + } + + @Test + public void lazyZip() { + assertEquals(h(2), h(1).lazyZip(lazy(h(x -> x + 1))).value()); + assertEquals(a(1), a(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(b(1), b(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(c(1), c(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(d(1), d(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(e(1), e(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(f(1), f(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + assertEquals(g(1), g(1).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + Choice8 choice = + Choice8.pureChoice().apply('c'); + assertEquals(h('c'), choice); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java index 3f5be9f76..d5a07cca6 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java @@ -1,31 +1,32 @@ package com.jnape.palatable.lambda.adt.coproduct; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Before; import org.junit.Test; -import java.util.Optional; -import java.util.function.Function; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static org.junit.Assert.assertEquals; public class CoProduct2Test { - private CoProduct2 a; - private CoProduct2 b; + private CoProduct2 a; + private CoProduct2 b; @Before public void setUp() { - a = new CoProduct2() { + a = new CoProduct2>() { @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return aFn.apply(1); } }; - b = new CoProduct2() { + b = new CoProduct2>() { @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return bFn.apply(true); } }; @@ -33,19 +34,31 @@ public R match(Function aFn, Function divergeA = a.diverge(); + CoProduct3 divergeA = a.diverge(); assertEquals(1, divergeA.match(id(), id(), id())); - CoProduct3 divergeB = b.diverge(); + CoProduct3 divergeB = b.diverge(); assertEquals(true, divergeB.match(id(), id(), id())); } @Test public void projections() { - assertEquals(tuple(Optional.of(1), Optional.empty()), a.project()); - assertEquals(tuple(Optional.empty(), Optional.of(true)), b.project()); + assertEquals(tuple(just(1), nothing()), a.project()); + assertEquals(tuple(nothing(), just(true)), b.project()); assertEquals(tuple(a.projectA(), a.projectB()), a.project()); assertEquals(tuple(b.projectA(), b.projectB()), b.project()); } + + @Test + public void invert() { + assertEquals(just(1), a.invert().projectB()); + assertEquals(just(true), b.invert().projectA()); + } + + @Test + public void embed() { + assertEquals(just(a), a.embed(Maybe::just, Maybe::just)); + assertEquals(just(b), b.embed(Maybe::just, Maybe::just)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java index 2288b3dd2..c80a50be2 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java @@ -1,41 +1,44 @@ package com.jnape.palatable.lambda.adt.coproduct; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.adt.choice.Choice3; +import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Before; import org.junit.Test; -import java.util.Optional; -import java.util.function.Function; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static org.junit.Assert.assertEquals; public class CoProduct3Test { - private CoProduct3 a; - private CoProduct3 b; - private CoProduct3 c; + private CoProduct3 a; + private CoProduct3 b; + private CoProduct3 c; @Before public void setUp() { - a = new CoProduct3() { + a = new CoProduct3>() { @Override - public R match(Function aFn, Function bFn, - Function cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn) { return aFn.apply(1); } }; - b = new CoProduct3() { + b = new CoProduct3>() { @Override - public R match(Function aFn, Function bFn, - Function cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn) { return bFn.apply("two"); } }; - c = new CoProduct3() { + c = new CoProduct3>() { @Override - public R match(Function aFn, Function bFn, - Function cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn) { return cFn.apply(true); } }; @@ -57,37 +60,28 @@ public void diverge() { @Test public void converge() { - Function> convergenceFn = x -> x ? new CoProduct2() { - @Override - public R match(Function aFn, Function bFn) { - return aFn.apply(-1); - } - } : new CoProduct2() { - @Override - public R match(Function aFn, Function bFn) { - return bFn.apply("false"); - } - }; + Fn1> convergenceFn = x -> x ? Choice2.a(-1) : Choice2.b("false"); assertEquals(1, a.converge(convergenceFn).match(id(), id())); assertEquals("two", b.converge(convergenceFn).match(id(), id())); assertEquals(-1, c.converge(convergenceFn).match(id(), id())); - assertEquals("false", new CoProduct3() { - @Override - public R match(Function aFn, Function bFn, - Function cFn) { - return cFn.apply(false); - } - }.converge(convergenceFn).match(id(), id())); + assertEquals("false", Choice3.c(false).converge(convergenceFn).match(id(), id())); } @Test public void projections() { - assertEquals(tuple(Optional.of(1), Optional.empty(), Optional.empty()), a.project()); - assertEquals(tuple(Optional.empty(), Optional.of("two"), Optional.empty()), b.project()); - assertEquals(tuple(Optional.empty(), Optional.empty(), Optional.of(true)), c.project()); + assertEquals(tuple(just(1), nothing(), nothing()), a.project()); + assertEquals(tuple(nothing(), just("two"), nothing()), b.project()); + assertEquals(tuple(nothing(), nothing(), just(true)), c.project()); assertEquals(tuple(a.projectA(), a.projectB(), a.projectC()), a.project()); assertEquals(tuple(b.projectA(), b.projectB(), b.projectC()), b.project()); assertEquals(tuple(c.projectA(), c.projectB(), c.projectC()), c.project()); } + + @Test + public void embed() { + assertEquals(just(a), a.embed(Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(b), b.embed(Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(c), c.embed(Maybe::just, Maybe::just, Maybe::just)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4Test.java index 015aed644..810a1781c 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4Test.java @@ -1,32 +1,55 @@ package com.jnape.palatable.lambda.adt.coproduct; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice3; +import com.jnape.palatable.lambda.adt.choice.Choice4; +import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Before; import org.junit.Test; -import java.util.Optional; -import java.util.function.Function; - -import static com.jnape.palatable.lambda.adt.choice.Choice4.a; -import static com.jnape.palatable.lambda.adt.choice.Choice4.b; -import static com.jnape.palatable.lambda.adt.choice.Choice4.c; -import static com.jnape.palatable.lambda.adt.choice.Choice4.d; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static org.junit.Assert.assertEquals; public class CoProduct4Test { - private CoProduct4 a; - private CoProduct4 b; - private CoProduct4 c; - private CoProduct4 d; + private CoProduct4 a; + private CoProduct4 b; + private CoProduct4 c; + private CoProduct4 d; @Before - public void setUp() throws Exception { - a = a(1); - b = b("two"); - c = c(true); - d = d(4D); + public void setUp() { + a = new CoProduct4>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn) { + return aFn.apply(1); + } + }; + b = new CoProduct4>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn) { + return bFn.apply("two"); + } + }; + c = new CoProduct4>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn) { + return cFn.apply(true); + } + }; + d = new CoProduct4>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn) { + return dFn.apply(4D); + } + }; } @Test @@ -47,62 +70,38 @@ public void diverge() { @Test public void converge() { - Function> convergenceFn = x -> x.equals(1d) ? new CoProduct3() { - @Override - public R match(Function aFn, Function bFn, - Function cFn) { - return aFn.apply(1); - } - } : x.equals(2d) - ? new CoProduct3() { - @Override - public R match(Function aFn, Function bFn, - Function cFn) { - return bFn.apply("b"); - } - } : new CoProduct3() { - @Override - public R match(Function aFn, Function bFn, - Function cFn) { - return cFn.apply(false); - } - }; + Fn1> convergenceFn = x -> + x.equals(1d) + ? Choice3.a(1) + : x.equals(2d) + ? Choice3.b("b") + : Choice3.c(false); assertEquals(1, a.converge(convergenceFn).match(id(), id(), id())); assertEquals("two", b.converge(convergenceFn).match(id(), id(), id())); assertEquals(true, c.converge(convergenceFn).match(id(), id(), id())); - assertEquals(1, new CoProduct4() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { - return dFn.apply(1d); - } - }.converge(convergenceFn).match(id(), id(), id())); - assertEquals("b", new CoProduct4() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { - return dFn.apply(2d); - } - }.converge(convergenceFn).match(id(), id(), id())); - assertEquals(false, new CoProduct4() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn) { - return dFn.apply(3d); - } - }.converge(convergenceFn).match(id(), id(), id())); + assertEquals(1, Choice4.d(1D).converge(convergenceFn).match(id(), id(), id())); + assertEquals("b", Choice4.d(2D).converge(convergenceFn).match(id(), id(), id())); + assertEquals(false, Choice4.d(3D).converge(convergenceFn).match(id(), id(), id())); } @Test public void projections() { - assertEquals(tuple(Optional.of(1), Optional.empty(), Optional.empty(), Optional.empty()), a.project()); - assertEquals(tuple(Optional.empty(), Optional.of("two"), Optional.empty(), Optional.empty()), b.project()); - assertEquals(tuple(Optional.empty(), Optional.empty(), Optional.of(true), Optional.empty()), c.project()); - assertEquals(tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(4D)), d.project()); + assertEquals(tuple(just(1), nothing(), nothing(), nothing()), a.project()); + assertEquals(tuple(nothing(), just("two"), nothing(), nothing()), b.project()); + assertEquals(tuple(nothing(), nothing(), just(true), nothing()), c.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), just(4D)), d.project()); assertEquals(tuple(a.projectA(), a.projectB(), a.projectC(), a.projectD()), a.project()); assertEquals(tuple(b.projectA(), b.projectB(), b.projectC(), b.projectD()), b.project()); assertEquals(tuple(c.projectA(), c.projectB(), c.projectC(), c.projectD()), c.project()); assertEquals(tuple(d.projectA(), d.projectB(), d.projectC(), d.projectD()), d.project()); } + + @Test + public void embed() { + assertEquals(just(a), a.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(b), b.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(c), c.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(d), d.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5Test.java index 5a34cc763..bd986dea1 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5Test.java @@ -1,62 +1,65 @@ package com.jnape.palatable.lambda.adt.coproduct; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice4; +import com.jnape.palatable.lambda.adt.choice.Choice5; +import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Before; import org.junit.Test; -import java.util.Optional; -import java.util.function.Function; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static org.junit.Assert.assertEquals; public class CoProduct5Test { - private CoProduct5 a; - private CoProduct5 b; - private CoProduct5 c; - private CoProduct5 d; - private CoProduct5 e; + private CoProduct5 a; + private CoProduct5 b; + private CoProduct5 c; + private CoProduct5 d; + private CoProduct5 e; @Before public void setUp() { - a = new CoProduct5() { + a = new CoProduct5>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return aFn.apply(1); } }; - b = new CoProduct5() { + b = new CoProduct5>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return bFn.apply("two"); } }; - c = new CoProduct5() { + c = new CoProduct5>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return cFn.apply(true); } }; - d = new CoProduct5() { + d = new CoProduct5>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return dFn.apply(4d); } }; - e = new CoProduct5() { + e = new CoProduct5>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn) { return eFn.apply('z'); } }; @@ -71,89 +74,41 @@ public void match() { assertEquals('z', e.match(id(), id(), id(), id(), id())); } + @Test + public void diverge() { + assertEquals(1, a.diverge().match(id(), id(), id(), id(), id(), id())); + assertEquals("two", b.diverge().match(id(), id(), id(), id(), id(), id())); + assertEquals(true, c.diverge().match(id(), id(), id(), id(), id(), id())); + assertEquals(4D, d.diverge().match(id(), id(), id(), id(), id(), id())); + assertEquals('z', e.diverge().match(id(), id(), id(), id(), id(), id())); + } + @Test public void converge() { - Function> convergenceFn = x -> - x.equals('a') ? new CoProduct4() { - @Override - public R match(Function aFn, - Function bFn, - Function cFn, - Function dFn) { - return aFn.apply(1); - } - } : x.equals('b') - ? new CoProduct4() { - @Override - public R match(Function aFn, - Function bFn, - Function cFn, - Function dFn) { - return bFn.apply("b"); - } - } : x.equals('c') - ? new CoProduct4() { - @Override - public R match(Function aFn, - Function bFn, - Function cFn, - Function dFn) { - return cFn.apply(false); - } - } : new CoProduct4() { - @Override - public R match(Function aFn, - Function bFn, - Function cFn, - Function dFn) { - return dFn.apply(1d); - } - }; + Fn1> convergenceFn = x -> x.equals('a') + ? Choice4.a(1) + : x.equals('b') + ? Choice4.b("b") + : x.equals('c') + ? Choice4.c(false) + : Choice4.d(1D); assertEquals(1, a.converge(convergenceFn).match(id(), id(), id(), id())); assertEquals("two", b.converge(convergenceFn).match(id(), id(), id(), id())); assertEquals(true, c.converge(convergenceFn).match(id(), id(), id(), id())); assertEquals(4D, d.converge(convergenceFn).match(id(), id(), id(), id())); - assertEquals(1, new CoProduct5() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { - return eFn.apply('a'); - } - }.converge(convergenceFn).match(id(), id(), id(), id())); - assertEquals("b", new CoProduct5() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { - return eFn.apply('b'); - } - }.converge(convergenceFn).match(id(), id(), id(), id())); - assertEquals(false, new CoProduct5() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { - return eFn.apply('c'); - } - }.converge(convergenceFn).match(id(), id(), id(), id())); - assertEquals(1d, new CoProduct5() { - @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn) { - return eFn.apply('d'); - } - }.converge(convergenceFn).match(id(), id(), id(), id())); + assertEquals(1, Choice5.e('a').converge(convergenceFn).match(id(), id(), id(), id())); + assertEquals("b", Choice5.e('b').converge(convergenceFn).match(id(), id(), id(), id())); + assertEquals(false, Choice5.e('c').converge(convergenceFn).match(id(), id(), id(), id())); + assertEquals(1d, Choice5.e('d').converge(convergenceFn).match(id(), id(), id(), id())); } @Test public void projections() { - assertEquals(tuple(Optional.of(1), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()), a.project()); - assertEquals(tuple(Optional.empty(), Optional.of("two"), Optional.empty(), Optional.empty(), Optional.empty()), b.project()); - assertEquals(tuple(Optional.empty(), Optional.empty(), Optional.of(true), Optional.empty(), Optional.empty()), c.project()); - assertEquals(tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(4D), Optional.empty()), d.project()); - assertEquals(tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.of('z')), e.project()); + assertEquals(tuple(just(1), nothing(), nothing(), nothing(), nothing()), a.project()); + assertEquals(tuple(nothing(), just("two"), nothing(), nothing(), nothing()), b.project()); + assertEquals(tuple(nothing(), nothing(), just(true), nothing(), nothing()), c.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), just(4D), nothing()), d.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), nothing(), just('z')), e.project()); assertEquals(tuple(a.projectA(), a.projectB(), a.projectC(), a.projectD(), a.projectE()), a.project()); assertEquals(tuple(b.projectA(), b.projectB(), b.projectC(), b.projectD(), b.projectE()), b.project()); @@ -161,4 +116,13 @@ public void projections() { assertEquals(tuple(d.projectA(), d.projectB(), d.projectC(), d.projectD(), d.projectE()), d.project()); assertEquals(tuple(e.projectA(), e.projectB(), e.projectC(), e.projectD(), e.projectE()), e.project()); } + + @Test + public void embed() { + assertEquals(just(a), a.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(b), b.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(c), c.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(d), d.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(e), e.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct6Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct6Test.java new file mode 100644 index 000000000..924e0507f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct6Test.java @@ -0,0 +1,147 @@ +package com.jnape.palatable.lambda.adt.coproduct; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice5; +import com.jnape.palatable.lambda.adt.choice.Choice6; +import com.jnape.palatable.lambda.functions.Fn1; +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static org.junit.Assert.assertEquals; + +public class CoProduct6Test { + + private CoProduct6 a; + private CoProduct6 b; + private CoProduct6 c; + private CoProduct6 d; + private CoProduct6 e; + private CoProduct6 f; + + @Before + public void setUp() { + a = new CoProduct6>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return aFn.apply(1); + } + }; + b = new CoProduct6>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return bFn.apply("two"); + } + }; + c = new CoProduct6>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return cFn.apply(true); + } + }; + d = new CoProduct6>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return dFn.apply(4D); + } + }; + e = new CoProduct6>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return eFn.apply('z'); + } + }; + f = new CoProduct6>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { + return fFn.apply(5L); + } + }; + } + + @Test + public void match() { + assertEquals(1, a.match(id(), id(), id(), id(), id(), id())); + assertEquals("two", b.match(id(), id(), id(), id(), id(), id())); + assertEquals(true, c.match(id(), id(), id(), id(), id(), id())); + assertEquals(4D, d.match(id(), id(), id(), id(), id(), id())); + assertEquals('z', e.match(id(), id(), id(), id(), id(), id())); + assertEquals(5L, f.match(id(), id(), id(), id(), id(), id())); + } + + @Test + public void diverge() { + assertEquals(1, a.diverge().match(id(), id(), id(), id(), id(), id(), id())); + assertEquals("two", b.diverge().match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(true, c.diverge().match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(4D, d.diverge().match(id(), id(), id(), id(), id(), id(), id())); + assertEquals('z', e.diverge().match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(5L, f.diverge().match(id(), id(), id(), id(), id(), id(), id())); + } + + @Test + public void converge() { + Fn1> convergenceFn = x -> + x.equals(1L) + ? Choice5.a(1) + : x.equals(2L) + ? Choice5.b("b") + : x.equals(3L) + ? Choice5.c(false) + : x.equals(4L) + ? Choice5.d(1D) + : Choice5.e('a'); + assertEquals(1, a.converge(convergenceFn).match(id(), id(), id(), id(), id())); + assertEquals("two", b.converge(convergenceFn).match(id(), id(), id(), id(), id())); + assertEquals(true, c.converge(convergenceFn).match(id(), id(), id(), id(), id())); + assertEquals(4D, d.converge(convergenceFn).match(id(), id(), id(), id(), id())); + assertEquals('z', e.converge(convergenceFn).match(id(), id(), id(), id(), id())); + assertEquals(1, Choice6.f(1L).converge(convergenceFn).match(id(), id(), id(), id(), id())); + assertEquals("b", Choice6.f(2L).converge(convergenceFn).match(id(), id(), id(), id(), id())); + assertEquals(false, Choice6.f(3L).converge(convergenceFn).match(id(), id(), id(), id(), id())); + assertEquals(1d, Choice6.f(4L).converge(convergenceFn).match(id(), id(), id(), id(), id())); + assertEquals('a', Choice6.f(5L).converge(convergenceFn).match(id(), id(), id(), id(), id())); + } + + @Test + public void projections() { + assertEquals(tuple(just(1), nothing(), nothing(), nothing(), nothing(), nothing()), a.project()); + assertEquals(tuple(nothing(), just("two"), nothing(), nothing(), nothing(), nothing()), b.project()); + assertEquals(tuple(nothing(), nothing(), just(true), nothing(), nothing(), nothing()), c.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), just(4D), nothing(), nothing()), d.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), nothing(), just('z'), nothing()), e.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), nothing(), nothing(), just(5L)), f.project()); + + assertEquals(tuple(a.projectA(), a.projectB(), a.projectC(), a.projectD(), a.projectE(), a.projectF()), a.project()); + assertEquals(tuple(b.projectA(), b.projectB(), b.projectC(), b.projectD(), b.projectE(), b.projectF()), b.project()); + assertEquals(tuple(c.projectA(), c.projectB(), c.projectC(), c.projectD(), c.projectE(), c.projectF()), c.project()); + assertEquals(tuple(d.projectA(), d.projectB(), d.projectC(), d.projectD(), d.projectE(), d.projectF()), d.project()); + assertEquals(tuple(e.projectA(), e.projectB(), e.projectC(), e.projectD(), e.projectE(), e.projectF()), e.project()); + assertEquals(tuple(f.projectA(), f.projectB(), f.projectC(), f.projectD(), f.projectE(), f.projectF()), f.project()); + } + + @Test + public void embed() { + assertEquals(just(a), a.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(b), b.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(c), c.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(d), d.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(e), e.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(f), f.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct7Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct7Test.java new file mode 100644 index 000000000..84999f677 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct7Test.java @@ -0,0 +1,172 @@ +package com.jnape.palatable.lambda.adt.coproduct; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice6; +import com.jnape.palatable.lambda.adt.choice.Choice7; +import com.jnape.palatable.lambda.functions.Fn1; +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static org.junit.Assert.assertEquals; + +public class CoProduct7Test { + + private CoProduct7 a; + private CoProduct7 b; + private CoProduct7 c; + private CoProduct7 d; + private CoProduct7 e; + private CoProduct7 f; + private CoProduct7 g; + + @Before + public void setUp() { + a = new CoProduct7>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return aFn.apply(1); + } + }; + b = new CoProduct7>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return bFn.apply("two"); + } + }; + c = new CoProduct7>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return cFn.apply(true); + } + }; + d = new CoProduct7>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return dFn.apply(4D); + } + }; + e = new CoProduct7>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return eFn.apply('z'); + } + }; + f = new CoProduct7>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return fFn.apply(5L); + } + }; + g = new CoProduct7>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { + return gFn.apply(6f); + } + }; + } + + @Test + public void match() { + assertEquals(1, a.match(id(), id(), id(), id(), id(), id(), id())); + assertEquals("two", b.match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(true, c.match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(4D, d.match(id(), id(), id(), id(), id(), id(), id())); + assertEquals('z', e.match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(5L, f.match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(6f, g.match(id(), id(), id(), id(), id(), id(), id())); + } + + @Test + public void diverge() { + assertEquals(1, a.diverge().match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals("two", b.diverge().match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals(true, c.diverge().match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals(4D, d.diverge().match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals('z', e.diverge().match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals(5L, f.diverge().match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals(6F, g.diverge().match(id(), id(), id(), id(), id(), id(), id(), id())); + } + + @Test + public void converge() { + Fn1> convergenceFn = x -> + x.equals(1f) + ? Choice6.a(1) + : x.equals(2f) + ? Choice6.b("b") + : x.equals(3f) + ? Choice6.c(false) + : x.equals(4f) + ? Choice6.d(1D) + : x.equals(5f) + ? Choice6.e('a') + : Choice6.f(5L); + assertEquals(1, a.converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals("two", b.converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals(true, c.converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals(4D, d.converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals('z', e.converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals(5L, f.converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals(1, Choice7.g(1F).converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals("b", Choice7.g(2F).converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals(false, Choice7.g(3F).converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals(1d, Choice7.g(4F).converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals('a', Choice7.g(5F).converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + assertEquals(5L, Choice7.g(6F).converge(convergenceFn).match(id(), id(), id(), id(), id(), id())); + } + + @Test + public void projections() { + assertEquals(tuple(just(1), nothing(), nothing(), nothing(), nothing(), nothing(), nothing()), a.project()); + assertEquals(tuple(nothing(), just("two"), nothing(), nothing(), nothing(), nothing(), nothing()), b.project()); + assertEquals(tuple(nothing(), nothing(), just(true), nothing(), nothing(), nothing(), nothing()), c.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), just(4D), nothing(), nothing(), nothing()), d.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), nothing(), just('z'), nothing(), nothing()), e.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), nothing(), nothing(), just(5L), nothing()), f.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), just(6F)), g.project()); + + assertEquals(tuple(a.projectA(), a.projectB(), a.projectC(), a.projectD(), a.projectE(), a.projectF(), a.projectG()), a.project()); + assertEquals(tuple(b.projectA(), b.projectB(), b.projectC(), b.projectD(), b.projectE(), b.projectF(), b.projectG()), b.project()); + assertEquals(tuple(c.projectA(), c.projectB(), c.projectC(), c.projectD(), c.projectE(), c.projectF(), c.projectG()), c.project()); + assertEquals(tuple(d.projectA(), d.projectB(), d.projectC(), d.projectD(), d.projectE(), d.projectF(), d.projectG()), d.project()); + assertEquals(tuple(e.projectA(), e.projectB(), e.projectC(), e.projectD(), e.projectE(), e.projectF(), e.projectG()), e.project()); + assertEquals(tuple(f.projectA(), f.projectB(), f.projectC(), f.projectD(), f.projectE(), f.projectF(), f.projectG()), f.project()); + assertEquals(tuple(g.projectA(), g.projectB(), g.projectC(), g.projectD(), g.projectE(), g.projectF(), g.projectG()), g.project()); + } + + @Test + public void embed() { + assertEquals(just(a), a.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(b), b.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(c), c.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(d), d.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(e), e.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(f), f.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(g), g.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct8Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct8Test.java new file mode 100644 index 000000000..e5620b3c0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct8Test.java @@ -0,0 +1,179 @@ +package com.jnape.palatable.lambda.adt.coproduct; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.choice.Choice7; +import com.jnape.palatable.lambda.adt.choice.Choice8; +import com.jnape.palatable.lambda.functions.Fn1; +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static org.junit.Assert.assertEquals; + +public class CoProduct8Test { + + private CoProduct8 a; + private CoProduct8 b; + private CoProduct8 c; + private CoProduct8 d; + private CoProduct8 e; + private CoProduct8 f; + private CoProduct8 g; + private CoProduct8 h; + + @Before + public void setUp() { + a = new CoProduct8>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return aFn.apply(1); + } + }; + b = new CoProduct8>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return bFn.apply("two"); + } + }; + c = new CoProduct8>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return cFn.apply(true); + } + }; + d = new CoProduct8>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return dFn.apply(4D); + } + }; + e = new CoProduct8>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return eFn.apply('z'); + } + }; + f = new CoProduct8>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return fFn.apply(5L); + } + }; + g = new CoProduct8>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return gFn.apply(6f); + } + }; + h = new CoProduct8>() { + @Override + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { + return hFn.apply((short) 7); + } + }; + } + + @Test + public void match() { + assertEquals(1, a.match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals("two", b.match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals(true, c.match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals(4D, d.match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals('z', e.match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals(5L, f.match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals(6f, g.match(id(), id(), id(), id(), id(), id(), id(), id())); + assertEquals((short) 7, h.match(id(), id(), id(), id(), id(), id(), id(), id())); + } + + @Test + public void converge() { + Fn1> convergenceFn = x -> + x.equals((short) 1) + ? Choice7.a(1) + : x.equals((short) 2) + ? Choice7.b("b") + : x.equals((short) 3) + ? Choice7.c(false) + : x.equals((short) 4) + ? Choice7.d(1D) + : x.equals((short) 5) + ? Choice7.e('a') + : x.equals((short) 6) + ? Choice7.f(5L) + : Choice7.g(6F); + assertEquals(1, a.converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals("two", b.converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(true, c.converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(4D, d.converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals('z', e.converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(5L, f.converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(6F, g.converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(1, Choice8.h((short) 1).converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals("b", Choice8.h((short) 2).converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(false, Choice8.h((short) 3).converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(1d, Choice8.h((short) 4).converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals('a', Choice8.h((short) 5).converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(5L, Choice8.h((short) 6).converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + assertEquals(6F, Choice8.h((short) 7).converge(convergenceFn).match(id(), id(), id(), id(), id(), id(), id())); + } + + @Test + public void projections() { + assertEquals(tuple(just(1), nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), nothing()), a.project()); + assertEquals(tuple(nothing(), just("two"), nothing(), nothing(), nothing(), nothing(), nothing(), nothing()), b.project()); + assertEquals(tuple(nothing(), nothing(), just(true), nothing(), nothing(), nothing(), nothing(), nothing()), c.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), just(4D), nothing(), nothing(), nothing(), nothing()), d.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), nothing(), just('z'), nothing(), nothing(), nothing()), e.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), nothing(), nothing(), just(5L), nothing(), nothing()), f.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), just(6F), nothing()), g.project()); + assertEquals(tuple(nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), just((short) 7)), h.project()); + + assertEquals(tuple(a.projectA(), a.projectB(), a.projectC(), a.projectD(), a.projectE(), a.projectF(), a.projectG(), a.projectH()), a.project()); + assertEquals(tuple(b.projectA(), b.projectB(), b.projectC(), b.projectD(), b.projectE(), b.projectF(), b.projectG(), b.projectH()), b.project()); + assertEquals(tuple(c.projectA(), c.projectB(), c.projectC(), c.projectD(), c.projectE(), c.projectF(), c.projectG(), c.projectH()), c.project()); + assertEquals(tuple(d.projectA(), d.projectB(), d.projectC(), d.projectD(), d.projectE(), d.projectF(), d.projectG(), d.projectH()), d.project()); + assertEquals(tuple(e.projectA(), e.projectB(), e.projectC(), e.projectD(), e.projectE(), e.projectF(), e.projectG(), e.projectH()), e.project()); + assertEquals(tuple(f.projectA(), f.projectB(), f.projectC(), f.projectD(), f.projectE(), f.projectF(), f.projectG(), f.projectH()), f.project()); + assertEquals(tuple(g.projectA(), g.projectB(), g.projectC(), g.projectD(), g.projectE(), g.projectF(), g.projectG(), g.projectH()), g.project()); + assertEquals(tuple(h.projectA(), h.projectB(), h.projectC(), h.projectD(), h.projectE(), h.projectF(), h.projectG(), h.projectH()), h.project()); + } + + @Test + public void embed() { + assertEquals(just(a), a.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(b), b.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(c), c.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(d), d.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(e), e.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(f), f.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(g), g.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + assertEquals(just(h), h.embed(Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just, Maybe::just)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java index 6be65b6dc..3e3513a3a 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java @@ -6,11 +6,11 @@ import static com.jnape.palatable.lambda.adt.hlist.HList.nil; import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThat; public class HListTest { @@ -36,22 +36,33 @@ public void convenienceStaticFactoryMethods() { assertEquals(nil().cons(false).cons(4.0).cons("3").cons('2').cons(1), tuple(1, '2', "3", 4.0, false)); } + @Test + public void autoPromotion() { + assertThat(cons(1, nil()), instanceOf(SingletonHList.class)); + assertThat(cons(1, singletonHList(1)), instanceOf(Tuple2.class)); + assertThat(cons(1, tuple(1, 1)), instanceOf(Tuple3.class)); + assertThat(cons(1, tuple(1, 1, 1)), instanceOf(Tuple4.class)); + assertThat(cons(1, tuple(1, 1, 1, 1)), instanceOf(Tuple5.class)); + assertThat(cons(1, tuple(1, 1, 1, 1, 1)), instanceOf(Tuple6.class)); + assertThat(cons(1, tuple(1, 1, 1, 1, 1, 1)), instanceOf(Tuple7.class)); + assertThat(cons(1, tuple(1, 1, 1, 1, 1, 1, 1)), instanceOf(Tuple8.class)); + } + @Test public void nilReusesInstance() { assertSame(nil(), nil()); } @Test - @SuppressWarnings({"EqualsWithItself", "EqualsBetweenInconvertibleTypes"}) public void equality() { - assertTrue(nil().equals(nil())); - assertTrue(cons(1, nil()).equals(cons(1, nil()))); + assertEquals(nil(), nil()); + assertEquals(cons(1, nil()), cons(1, nil())); - assertFalse(cons(1, nil()).equals(nil())); - assertFalse(nil().equals(cons(1, nil()))); + assertNotEquals(cons(1, nil()), nil()); + assertNotEquals(nil(), cons(1, nil())); - assertFalse(cons(1, cons(2, nil())).equals(cons(1, nil()))); - assertFalse(cons(1, nil()).equals(cons(1, cons(2, nil())))); + assertNotEquals(cons(1, cons(2, nil())), cons(1, nil())); + assertNotEquals(cons(1, nil()), cons(1, cons(2, nil()))); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java index 18bc68a05..7a577f2ce 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java @@ -1,11 +1,23 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.nil; +import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.SingletonHList.pureSingletonHList; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class SingletonHListTest { private SingletonHList singletonHList; @@ -15,6 +27,11 @@ public void setUp() { singletonHList = new SingletonHList<>(1); } + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) + public SingletonHList testSubject() { + return singletonHList("one"); + } + @Test public void head() { assertEquals((Integer) 1, singletonHList.head()); @@ -31,7 +48,18 @@ public void cons() { } @Test - public void functorProperties() { - assertEquals(new SingletonHList<>("1"), singletonHList.fmap(Object::toString)); + public void snoc() { + assertEquals(tuple((byte) 127, 'x'), singletonHList((byte) 127).snoc('x')); + } + + @Test + public void intoAppliesHeadToFn() { + assertEquals("FOO", singletonHList("foo").into(String::toUpperCase)); + } + + @Test + public void staticPure() { + SingletonHList singletonHList = pureSingletonHList().apply(1); + assertEquals(singletonHList(1), singletonHList); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java index b1ff6a325..6637386ce 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java @@ -1,28 +1,58 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.MonadWriterLaws; +import testsupport.traits.TraversableLaws; import java.util.HashMap; import java.util.Map; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple2.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.only; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +@RunWith(Traits.class) public class Tuple2Test { private Tuple2 tuple2; @Before - public void setUp() throws Exception { + public void setUp() { tuple2 = new Tuple2<>(1, new SingletonHList<>(2)); } + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + MonadWriterLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple2 testSubject() { + return tuple("one", 2); + } + @Test public void head() { assertEquals((Integer) 1, tuple2.head()); @@ -33,11 +63,21 @@ public void tail() { assertEquals(new SingletonHList<>(2), tuple2.tail()); } + @Test + public void init() { + assertEquals(new SingletonHList<>(1), tuple2.init()); + } + @Test public void cons() { assertEquals(new Tuple3<>(0, tuple2), tuple2.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple(Long.MAX_VALUE, 123, "hi"), tuple(Long.MAX_VALUE, 123).snoc("hi")); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple2._1()); @@ -47,7 +87,7 @@ public void accessors() { @Test public void randomAccess() { SingletonHList spiedTail = spy(singletonHList("second")); - Tuple2 tuple2 = new Tuple2<>("first", spiedTail); + Tuple2 tuple2 = new Tuple2<>("first", spiedTail); verify(spiedTail, only()).head(); tuple2._1(); @@ -66,16 +106,6 @@ public void fill() { assertEquals(tuple("foo", "foo"), Tuple2.fill("foo")); } - @Test - public void functorProperties() { - assertEquals(new Tuple2<>(1, new SingletonHList<>("2")), tuple2.fmap(Object::toString)); - } - - @Test - public void bifunctorProperties() { - assertEquals(new Tuple2<>("1", new SingletonHList<>("2")), tuple2.biMap(Object::toString, Object::toString)); - } - @Test public void mapEntryProperties() { assertEquals((Integer) 1, tuple2.getKey()); @@ -88,6 +118,7 @@ public void setValueIsNotSupported() { } @Test + @SuppressWarnings("serial") public void staticFactoryMethodFromMapEntry() { Map.Entry stringIntEntry = new HashMap() {{ put("string", 1); @@ -95,4 +126,31 @@ public void staticFactoryMethodFromMapEntry() { assertEquals(tuple("string", 1), Tuple2.fromEntry(stringIntEntry)); } + + @Test + public void zipPrecedence() { + Tuple2 a = tuple("foo", 1); + Tuple2> b = tuple("bar", x -> x + 1); + assertEquals(tuple("foo", 2), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple2 a = tuple("foo", 1); + Fn1> b = x -> tuple("bar", x + 1); + assertEquals(tuple("foo", 2), a.flatMap(b)); + } + + @Test + public void fromIterable() { + assertEquals(nothing(), Tuple2.fromIterable(emptyList())); + assertEquals(nothing(), Tuple2.fromIterable(singletonList(1))); + assertEquals(just(tuple(1, 1)), Tuple2.fromIterable(repeat(1))); + } + + @Test + public void staticPure() { + Tuple2 tuple = pureTuple(1).apply("two"); + assertEquals(tuple(1, "two"), tuple); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java index 5f5dad667..a4cad8198 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java @@ -1,15 +1,33 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple3.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.time.Duration.ofSeconds; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +@RunWith(Traits.class) public class Tuple3Test { private Tuple3 tuple3; @@ -19,6 +37,17 @@ public void setUp() { tuple3 = new Tuple3<>(1, new Tuple2<>("2", new SingletonHList<>('3'))); } + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple3 testSubject() { + return tuple("one", 2, 3d); + } + @Test public void head() { assertEquals((Integer) 1, tuple3.head()); @@ -34,6 +63,12 @@ public void cons() { assertEquals(new Tuple4<>(0, tuple3), tuple3.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("qux", Long.MIN_VALUE, 7, ofSeconds(13)), + tuple("qux", Long.MIN_VALUE, 7).snoc(ofSeconds(13))); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple3._1()); @@ -43,8 +78,8 @@ public void accessors() { @Test public void randomAccess() { - Tuple2 spiedTail = spy(tuple("second", "third")); - Tuple3 tuple3 = new Tuple3<>("first", spiedTail); + Tuple2 spiedTail = spy(tuple("second", "third")); + Tuple3 tuple3 = new Tuple3<>("first", spiedTail); verify(spiedTail, times(1))._1(); verify(spiedTail, times(1))._2(); @@ -66,12 +101,35 @@ public void fill() { } @Test - public void functorProperties() { - assertEquals(new Tuple3<>(1, new Tuple2<>("2", new SingletonHList<>("3"))), tuple3.fmap(Object::toString)); + public void zipPrecedence() { + Tuple3 a = tuple("foo", 1, 2); + Tuple3> b = tuple("bar", 2, x -> x + 1); + assertEquals(tuple("foo", 1, 3), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple3 a = tuple("foo", 1, 2); + Fn1> b = x -> tuple("bar", 2, x + 1); + assertEquals(tuple("foo", 1, 3), a.flatMap(b)); + } + + @Test + public void fromIterable() { + assertEquals(nothing(), Tuple3.fromIterable(emptyList())); + assertEquals(nothing(), Tuple3.fromIterable(singletonList(1))); + assertEquals(just(tuple(1, 1, 1)), Tuple3.fromIterable(repeat(1))); + } + + @Test + public void staticPure() { + Tuple3 tuple = pureTuple(1, "2").apply('3'); + assertEquals(tuple(1, "2", '3'), tuple); } @Test - public void bifunctorProperties() { - assertEquals(new Tuple3<>(1, new Tuple2<>(2, new SingletonHList<>("3"))), tuple3.biMap(Integer::parseInt, Object::toString)); + public void init() { + assertEquals(tuple(1, 2), + tuple(1, 2, 3).init()); } } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java index ebaca3c5a..13d5da838 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java @@ -1,15 +1,32 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple4.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +@RunWith(Traits.class) public class Tuple4Test { private Tuple4 tuple4; @@ -19,6 +36,17 @@ public void setUp() { tuple4 = new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>('3', new SingletonHList<>(false)))); } + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple4 testSubject() { + return tuple("one", 2, 3d, 4f); + } + @Test public void head() { assertEquals((Integer) 1, tuple4.head()); @@ -34,6 +62,11 @@ public void cons() { assertEquals(new Tuple5<>(0, tuple4), tuple4.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("qux", 7, "foo", 13L, 17), tuple("qux", 7, "foo", 13L).snoc(17)); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple4._1()); @@ -44,8 +77,8 @@ public void accessors() { @Test public void randomAccess() { - Tuple3 spiedTail = spy(tuple("second", "third", "fourth")); - Tuple4 tuple4 = new Tuple4<>("first", spiedTail); + Tuple3 spiedTail = spy(tuple("second", "third", "fourth")); + Tuple4 tuple4 = new Tuple4<>("first", spiedTail); verify(spiedTail, times(1))._1(); verify(spiedTail, times(1))._2(); @@ -69,13 +102,35 @@ public void fill() { } @Test - public void functorProperties() { - assertEquals(new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>('3', new SingletonHList<>(true)))), tuple4.fmap(x -> !x)); + public void zipPrecedence() { + Tuple4 a = tuple("foo", 1, 2, 3); + Tuple4> b = tuple("foo", 1, 2, x -> x + 1); + assertEquals(tuple("foo", 1, 2, 4), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple4 a = tuple("foo", 1, 2, 3); + Fn1> b = x -> tuple("bar", 2, 3, x + 1); + assertEquals(tuple("foo", 1, 2, 4), a.flatMap(b)); + } + + @Test + public void fromIterable() { + assertEquals(nothing(), Tuple4.fromIterable(emptyList())); + assertEquals(nothing(), Tuple4.fromIterable(singletonList(1))); + assertEquals(just(tuple(1, 1, 1, 1)), Tuple4.fromIterable(repeat(1))); + } + + @Test + public void staticPure() { + Tuple4 tuple = pureTuple(1, "2", '3').apply(true); + assertEquals(tuple(1, "2", '3', true), tuple); } @Test - public void bifunctorProperties() { - assertEquals(new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>("3", new SingletonHList<>(true)))), - tuple4.biMap(Object::toString, x -> !x)); + public void init() { + assertEquals(tuple(1, 2, 3), + tuple(1, 2, 3, 4).init()); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java index b6065098c..7b81a2190 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java @@ -1,16 +1,33 @@ package com.jnape.palatable.lambda.adt.hlist; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple5.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +@RunWith(Traits.class) public class Tuple5Test { private Tuple5 tuple5; @@ -20,6 +37,17 @@ public void setUp() { tuple5 = new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L))))); } + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple5 testSubject() { + return tuple("one", 2, 3d, 4f, '5'); + } + @Test public void head() { assertEquals((Integer) 1, tuple5.head()); @@ -35,6 +63,11 @@ public void cons() { assertEquals(new HCons<>(0, tuple5), tuple5.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("a", 5, "b", 7, "c", 11), tuple("a", 5, "b", 7, "c").snoc(11)); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple5._1()); @@ -46,8 +79,8 @@ public void accessors() { @Test public void randomAccess() { - Tuple4 spiedTail = spy(tuple("second", "third", "fourth", "fifth")); - Tuple5 tuple5 = new Tuple5<>("first", spiedTail); + Tuple4 spiedTail = spy(tuple("second", "third", "fourth", "fifth")); + Tuple5 tuple5 = new Tuple5<>("first", spiedTail); verify(spiedTail, times(1))._1(); verify(spiedTail, times(1))._2(); @@ -61,20 +94,49 @@ public void randomAccess() { verifyNoMoreInteractions(spiedTail); } + @Test + public void into() { + Tuple5 tuple = tuple("foo", 1, 2.0d, false, 3f); + assertEquals("foo12.0false3.0", tuple.into((s, i, d, b, f) -> s + i + d + b + f)); + } + @Test public void fill() { assertEquals(tuple("foo", "foo", "foo", "foo", "foo"), Tuple5.fill("foo")); } @Test - public void functorProperties() { - assertEquals(new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>("5"))))), - tuple5.fmap(Object::toString)); + public void zipPrecedence() { + Tuple5 a = + tuple("foo", 1, 2, 3, 4); + Tuple5> b = + tuple("bar", 2, 3, 4, x -> x + 1); + assertEquals(tuple("foo", 1, 2, 3, 5), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple5 a = tuple("foo", 1, 2, 3, 4); + Fn1> b = x -> tuple("bar", 2, 3, 4, x + 1); + assertEquals(tuple("foo", 1, 2, 3, 5), a.flatMap(b)); + } + + @Test + public void fromIterable() { + assertEquals(nothing(), Tuple5.fromIterable(emptyList())); + assertEquals(nothing(), Tuple5.fromIterable(singletonList(1))); + assertEquals(just(tuple(1, 1, 1, 1, 1)), Tuple5.fromIterable(repeat(1))); + } + + @Test + public void staticPure() { + Tuple5 tuple = pureTuple(1, "2", '3', true).apply(5f); + assertEquals(tuple(1, "2", '3', true, 5f), tuple); } @Test - public void bifunctorProperties() { - assertEquals(new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(true, new SingletonHList<>("5"))))), - tuple5.biMap(x -> !x, Object::toString)); + public void init() { + assertEquals(tuple(1, 2, 3, 4), + tuple(1, 2, 3, 4, 5).init()); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java new file mode 100644 index 000000000..880eee1e4 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java @@ -0,0 +1,146 @@ +package com.jnape.palatable.lambda.adt.hlist; + +import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple6.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@RunWith(Traits.class) +public class Tuple6Test { + + private Tuple6 tuple6; + + @Before + public void setUp() { + tuple6 = new Tuple6<>(2.0f, new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L)))))); + } + + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple6 testSubject() { + return tuple("one", 2, 3d, 4f, '5', (byte) 6); + } + + @Test + public void head() { + assertEquals((Float) 2.0F, tuple6.head()); + } + + @Test + public void tail() { + assertEquals(new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L))))), + tuple6.tail()); + } + + @Test + public void cons() { + assertEquals(new HCons<>(0, tuple6), tuple6.cons(0)); + } + + @Test + public void snoc() { + assertEquals(tuple(5L, "a", 7, "b", 11, "c", 13), tuple(5L, "a", 7, "b", 11, "c").snoc(13)); + } + + @Test + public void accessors() { + assertEquals((Float) 2.0f, tuple6._1()); + assertEquals((Integer) 1, tuple6._2()); + assertEquals("2", tuple6._3()); + assertEquals((Character) '3', tuple6._4()); + assertEquals(false, tuple6._5()); + assertEquals((Long) 5L, tuple6._6()); + } + + @Test + public void randomAccess() { + Tuple5 spiedTail = spy(tuple("second", "third", "fourth", "fifth", "sixth")); + Tuple6 tuple6 = new Tuple6<>("first", spiedTail); + + verify(spiedTail, times(1))._1(); + verify(spiedTail, times(1))._2(); + verify(spiedTail, times(1))._3(); + verify(spiedTail, times(1))._4(); + verify(spiedTail, times(1))._5(); + tuple6._1(); + tuple6._2(); + tuple6._3(); + tuple6._4(); + tuple6._5(); + tuple6._6(); + verifyNoMoreInteractions(spiedTail); + } + + @Test + public void into() { + Tuple6 tuple = tuple("foo", 1, 2.0d, false, 3f, (short) 4); + assertEquals("foo12.0false3.04", tuple.into((s, i, d, b, f, sh) -> s + i + d + b + f + sh)); + } + + @Test + public void fill() { + assertEquals(tuple("foo", "foo", "foo", "foo", "foo", "foo"), Tuple6.fill("foo")); + } + + @Test + public void zipPrecedence() { + Tuple6 a = + tuple("foo", 1, 2, 3, 4, 5); + Tuple6> b = + tuple("bar", 2, 3, 4, 5, x -> x + 1); + assertEquals(tuple("foo", 1, 2, 3, 4, 6), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple6 a = tuple("foo", 1, 2, 3, 4, 5); + Fn1> b = x -> tuple("bar", 2, 3, 4, 5, x + 1); + assertEquals(tuple("foo", 1, 2, 3, 4, 6), a.flatMap(b)); + } + + @Test + public void fromIterable() { + assertEquals(nothing(), Tuple6.fromIterable(emptyList())); + assertEquals(nothing(), Tuple6.fromIterable(singletonList(1))); + assertEquals(just(tuple(1, 1, 1, 1, 1, 1)), Tuple6.fromIterable(repeat(1))); + } + + @Test + public void staticPure() { + Tuple6 tuple = pureTuple(1, "2", '3', true, 5f).apply((byte) 6); + assertEquals(tuple(1, "2", '3', true, 5f, (byte) 6), tuple); + } + + @Test + public void init() { + assertEquals(tuple(1, 2, 3, 4, 5), + tuple(1, 2, 3, 4, 5, 6).init()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java new file mode 100644 index 000000000..d5b13fd24 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java @@ -0,0 +1,150 @@ +package com.jnape.palatable.lambda.adt.hlist; + +import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple7.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@RunWith(Traits.class) +public class Tuple7Test { + + private Tuple7 tuple7; + + @Before + public void setUp() { + tuple7 = new Tuple7<>((byte) 127, new Tuple6<>(2.0f, new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L))))))); + } + + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple7 testSubject() { + return tuple("one", 2, 3d, 4f, '5', (byte) 6, 7L); + } + + @Test + public void head() { + assertEquals((Byte) (byte) 127, tuple7.head()); + } + + @Test + public void tail() { + assertEquals(new Tuple6<>(2.0f, new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L)))))), + tuple7.tail()); + } + + @Test + public void cons() { + assertEquals(new HCons<>(0, tuple7), tuple7.cons(0)); + } + + @Test + public void snoc() { + assertEquals(tuple("b", 7L, "c", 11, "d", 13, "e", 'f'), tuple("b", 7L, "c", 11, "d", 13, "e").snoc('f')); + } + + @Test + public void accessors() { + assertEquals((Byte) (byte) 127, tuple7._1()); + assertEquals((Float) 2.0f, tuple7._2()); + assertEquals((Integer) 1, tuple7._3()); + assertEquals("2", tuple7._4()); + assertEquals((Character) '3', tuple7._5()); + assertEquals(false, tuple7._6()); + assertEquals((Long) 5L, tuple7._7()); + } + + @Test + public void randomAccess() { + Tuple6 spiedTail = spy(tuple("second", "third", "fourth", "fifth", "sixth", "seventh")); + Tuple7 tuple7 = new Tuple7<>("first", spiedTail); + + verify(spiedTail, times(1))._1(); + verify(spiedTail, times(1))._2(); + verify(spiedTail, times(1))._3(); + verify(spiedTail, times(1))._4(); + verify(spiedTail, times(1))._5(); + verify(spiedTail, times(1))._6(); + tuple7._1(); + tuple7._2(); + tuple7._3(); + tuple7._4(); + tuple7._5(); + tuple7._6(); + tuple7._7(); + verifyNoMoreInteractions(spiedTail); + } + + @Test + public void fill() { + assertEquals(tuple("foo", "foo", "foo", "foo", "foo", "foo", "foo"), Tuple7.fill("foo")); + } + + @Test + public void into() { + Tuple7 tuple = tuple("foo", 1, 2.0d, false, 3f, (short) 4, (byte) 5); + assertEquals("foo12.0false3.045", tuple.into((s, i, d, b, f, sh, by) -> s + i + d + b + f + sh + by)); + } + + @Test + public void zipPrecedence() { + Tuple7 a = + tuple("foo", 1, 2, 3, 4, 5, 6); + Tuple7> b = + tuple("bar", 2, 3, 4, 5, 6, x -> x + 1); + assertEquals(tuple("foo", 1, 2, 3, 4, 5, 7), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple7 a = tuple("foo", 1, 2, 3, 4, 5, 6); + Fn1> b = x -> tuple("bar", 2, 3, 4, 5, 6, x + 1); + assertEquals(tuple("foo", 1, 2, 3, 4, 5, 7), a.flatMap(b)); + } + + @Test + public void fromIterable() { + assertEquals(nothing(), Tuple7.fromIterable(emptyList())); + assertEquals(nothing(), Tuple7.fromIterable(singletonList(1))); + assertEquals(just(tuple(1, 1, 1, 1, 1, 1, 1)), Tuple7.fromIterable(repeat(1))); + } + + @Test + public void staticPure() { + Tuple7 tuple = + pureTuple((byte) 1, (short) 2, 3, 4L, 5F, 6D).apply(true); + assertEquals(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true), tuple); + } + + @Test + public void init() { + assertEquals(tuple(1, 2, 3, 4, 5, 6), + tuple(1, 2, 3, 4, 5, 6, 7).init()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java new file mode 100644 index 000000000..ced5531b5 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java @@ -0,0 +1,167 @@ +package com.jnape.palatable.lambda.adt.hlist; + +import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import java.time.LocalDate; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple8.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@RunWith(Traits.class) +public class Tuple8Test { + + private Tuple8 tuple8; + + @Before + public void setUp() { + Tuple2 tuple2 = new Tuple2<>(false, new SingletonHList<>(5L)); + Tuple4 tuple4 = new Tuple4<>("2", new Tuple3<>('3', tuple2)); + tuple8 = new Tuple8<>((short) 65535, new Tuple7<>((byte) 127, new Tuple6<>(2.0f, new Tuple5<>(1, tuple4)))); + } + + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple8 testSubject() { + return tuple("one", 2, 3d, 4f, '5', (byte) 6, 7L, (short) 65535); + } + + @Test + public void head() { + assertEquals((Short) (short) 65535, tuple8.head()); + } + + @Test + public void tail() { + Tuple2 tuple2 = new Tuple2<>(false, new SingletonHList<>(5L)); + Tuple4 tuple4 = new Tuple4<>("2", new Tuple3<>('3', tuple2)); + assertEquals(new Tuple7<>((byte) 127, new Tuple6<>(2.0f, new Tuple5<>(1, tuple4))), tuple8.tail()); + } + + @Test + public void cons() { + assertEquals(new HCons<>(0, tuple8), tuple8.cons(0)); + } + + @Test + public void snoc() { + LocalDate last = LocalDate.of(2020, 4, 14); + HCons> actual = + tuple("b", 7L, "c", 11, "d", 13, "e", 15L).snoc(last); + assertEquals("b", actual.head()); + assertEquals(actual.tail(), tuple(7L, "c", 11, "d", 13, "e", 15L, last)); + } + + @Test + public void accessors() { + assertEquals((Short) (short) 65535, tuple8._1()); + assertEquals((Byte) (byte) 127, tuple8._2()); + assertEquals((Float) 2.0f, tuple8._3()); + assertEquals((Integer) 1, tuple8._4()); + assertEquals("2", tuple8._5()); + assertEquals((Character) '3', tuple8._6()); + assertEquals(false, tuple8._7()); + assertEquals((Long) 5L, tuple8._8()); + } + + @Test + public void randomAccess() { + Tuple7 spiedTail = + spy(tuple("second", "third", "fourth", "fifth", "sixth", "seventh", "eighth")); + Tuple8 tuple8 = + new Tuple8<>("first", spiedTail); + + verify(spiedTail, times(1))._1(); + verify(spiedTail, times(1))._2(); + verify(spiedTail, times(1))._3(); + verify(spiedTail, times(1))._4(); + verify(spiedTail, times(1))._5(); + verify(spiedTail, times(1))._6(); + verify(spiedTail, times(1))._7(); + tuple8._1(); + tuple8._2(); + tuple8._3(); + tuple8._4(); + tuple8._5(); + tuple8._6(); + tuple8._7(); + tuple8._8(); + verifyNoMoreInteractions(spiedTail); + } + + @Test + public void fill() { + assertEquals(tuple("foo", "foo", "foo", "foo", "foo", "foo", "foo", "foo"), Tuple8.fill("foo")); + } + + @Test + public void into() { + Tuple8 tuple = + tuple("foo", 1, 2.0d, false, 3f, (short) 4, (byte) 5, 6L); + assertEquals("foo12.0false3.0456", tuple.into((s, i, d, b, f, sh, by, l) -> s + i + d + b + f + sh + by + l)); + } + + @Test + public void zipPrecedence() { + Tuple8 a + = tuple("foo", 1, 2, 3, 4, 5, 6, 7); + Tuple8> b + = tuple("bar", 2, 3, 4, 5, 6, 7, x -> x + 1); + assertEquals(tuple("foo", 1, 2, 3, 4, 5, 6, 8), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple8 a = + tuple("foo", 1, 2, 3, 4, 5, 6, 7); + Fn1> b = + x -> tuple("bar", 2, 3, 4, 5, 6, 7, x + 1); + assertEquals(tuple("foo", 1, 2, 3, 4, 5, 6, 8), a.flatMap(b)); + } + + @Test + public void fromIterable() { + assertEquals(nothing(), Tuple8.fromIterable(emptyList())); + assertEquals(nothing(), Tuple8.fromIterable(singletonList(1))); + assertEquals(just(tuple(1, 1, 1, 1, 1, 1, 1, 1)), Tuple8.fromIterable(repeat(1))); + } + + @Test + public void staticPure() { + Tuple8 tuple = + pureTuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true).apply('8'); + assertEquals(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true, '8'), tuple); + } + + @Test + public void init() { + assertEquals(tuple(1, 2, 3, 4, 5, 6, 7), + tuple(1, 2, 3, 4, 5, 6, 7, 8).init()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java index 649c6b6fd..5064c8048 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java @@ -2,15 +2,19 @@ import org.junit.Test; +import java.math.BigInteger; import java.util.HashMap; import java.util.NoSuchElementException; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; import static com.jnape.palatable.lambda.adt.hmap.HMap.singletonHMap; import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static com.jnape.palatable.lambda.optics.Iso.simpleIso; +import static java.math.BigInteger.ONE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -23,28 +27,59 @@ public class HMapTest { @Test public void getForPresentKey() { - TypeSafeKey stringKey = typeSafeKey(); - assertEquals(Optional.of("string value"), + TypeSafeKey stringKey = typeSafeKey(); + assertEquals(just("string value"), singletonHMap(stringKey, "string value").get(stringKey)); } @Test public void getForAbsentKey() { - assertEquals(Optional.empty(), + assertEquals(nothing(), singletonHMap(typeSafeKey(), "string value") .get(typeSafeKey())); } + @Test + public void isEmpty() { + assertTrue(emptyHMap().isEmpty()); + assertFalse(singletonHMap(typeSafeKey(), "foo").isEmpty()); + } + + @Test + public void storesTypeSafeKeyBaseValue() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey longKey = stringKey.andThen(simpleIso(Long::parseLong, + String::valueOf)); + TypeSafeKey bigIntegerKey = longKey.andThen(simpleIso(BigInteger::valueOf, + BigInteger::longValue)); + + HMap hMap = singletonHMap(stringKey, "1"); + assertEquals(just("1"), hMap.get(stringKey)); + assertEquals(just(1L), hMap.get(longKey)); + assertEquals(just(ONE), hMap.get(bigIntegerKey)); + + assertNotEquals(typeSafeKey(), typeSafeKey()); + + assertEquals(emptyHMap().put(longKey, 1L).get(longKey), emptyHMap().put(stringKey, "1").get(longKey)); + assertEquals(emptyHMap().put(stringKey, "1").get(stringKey), emptyHMap().put(longKey, 1L).get(stringKey)); + assertEquals(emptyHMap().put(stringKey, "1").get(stringKey), + emptyHMap().put(bigIntegerKey, ONE).get(stringKey)); + + assertEquals(singletonHMap(stringKey, "1"), singletonHMap(longKey, 1L)); + assertEquals(singletonHMap(stringKey, "1"), singletonHMap(bigIntegerKey, ONE)); + assertEquals(singletonHMap(longKey, 1L), singletonHMap(bigIntegerKey, ONE)); + } + @Test public void getForPresentKeyWithNullValue() { - TypeSafeKey stringKey = typeSafeKey(); - assertEquals(Optional.empty(), + TypeSafeKey stringKey = typeSafeKey(); + assertEquals(nothing(), singletonHMap(stringKey, null).get(stringKey)); } @Test public void put() { - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertEquals(singletonHMap(stringKey, "string value"), emptyHMap().put(stringKey, "string value")); @@ -56,9 +91,9 @@ public void put() { @Test public void putAll() { - TypeSafeKey stringKey1 = typeSafeKey(); - TypeSafeKey stringKey2 = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); HMap left = hMap(stringKey1, "string value", intKey, 1); @@ -77,8 +112,8 @@ public void putAll() { @Test public void remove() { - TypeSafeKey stringKey1 = typeSafeKey(); - TypeSafeKey stringKey2 = typeSafeKey(); + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); assertEquals(emptyHMap(), emptyHMap() .put(stringKey1, "string value") @@ -93,8 +128,8 @@ public void remove() { @Test public void removeAll() { - TypeSafeKey stringKey1 = typeSafeKey(); - TypeSafeKey stringKey2 = typeSafeKey(); + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); HMap hMap1 = hMap(stringKey1, "foo", stringKey2, "bar"); @@ -106,9 +141,9 @@ public void removeAll() { @Test public void containsKey() { - TypeSafeKey stringKey1 = typeSafeKey(); - TypeSafeKey stringKey2 = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); HMap hMap = singletonHMap(stringKey1, "string"); @@ -119,7 +154,7 @@ public void containsKey() { @Test public void demandForPresentKey() { - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertEquals("string value", singletonHMap(stringKey, "string value").demand(stringKey)); } @@ -130,11 +165,12 @@ public void demandForAbsentKey() { } @Test + @SuppressWarnings("serial") public void toMap() { - TypeSafeKey stringKey = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); - assertEquals(new HashMap() {{ + assertEquals(new HashMap, Object>() {{ put(stringKey, "string"); put(intKey, 1); }}, hMap(stringKey, "string", @@ -143,7 +179,7 @@ public void toMap() { @Test public void iteratesKVPairsAsTuples() { - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertThat(singletonHMap(stringKey, "string value"), iterates(tuple(stringKey, "string value"))); @@ -151,7 +187,7 @@ public void iteratesKVPairsAsTuples() { @Test public void keys() { - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertThat(singletonHMap(stringKey, "string value").keys(), iterates(stringKey)); @@ -165,39 +201,95 @@ public void values() { @Test public void convenienceStaticFactoryMethods() { - TypeSafeKey stringKey = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); - TypeSafeKey floatKey = typeSafeKey(); - assertEquals(emptyHMap().put(stringKey, "string value"), + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey.Simple intKey = typeSafeKey(); + TypeSafeKey.Simple floatKey = typeSafeKey(); + TypeSafeKey.Simple byteKey = typeSafeKey(); + TypeSafeKey.Simple shortKey = typeSafeKey(); + TypeSafeKey.Simple longKey = typeSafeKey(); + TypeSafeKey.Simple doubleKey = typeSafeKey(); + TypeSafeKey.Simple charKey = typeSafeKey(); + + HMap m1 = emptyHMap().put(stringKey, "string value"); + HMap m2 = m1.put(intKey, 1); + HMap m3 = m2.put(floatKey, 1f); + HMap m4 = m3.put(byteKey, (byte) 1); + HMap m5 = m4.put(shortKey, (short) 1); + HMap m6 = m5.put(longKey, 1L); + HMap m7 = m6.put(doubleKey, 1D); + HMap m8 = m7.put(charKey, '1'); + + assertEquals(m1, singletonHMap(stringKey, "string value")); - assertEquals(emptyHMap().put(stringKey, "string value").put(intKey, 1), + + assertEquals(m2, hMap(stringKey, "string value", intKey, 1)); - assertEquals(emptyHMap().put(stringKey, "string value").put(intKey, 1).put(floatKey, 1f), + + assertEquals(m3, hMap(stringKey, "string value", intKey, 1, floatKey, 1f)); + + assertEquals(m4, + hMap(stringKey, "string value", + intKey, 1, + floatKey, 1f, + byteKey, (byte) 1)); + + assertEquals(m5, + hMap(stringKey, "string value", + intKey, 1, + floatKey, 1f, + byteKey, (byte) 1, + shortKey, (short) 1)); + + assertEquals(m6, + hMap(stringKey, "string value", + intKey, 1, + floatKey, 1f, + byteKey, (byte) 1, + shortKey, (short) 1, + longKey, 1L)); + + assertEquals(m7, + hMap(stringKey, "string value", + intKey, 1, + floatKey, 1f, + byteKey, (byte) 1, + shortKey, (short) 1, + longKey, 1L, + doubleKey, 1D)); + + assertEquals(m8, + hMap(stringKey, "string value", + intKey, 1, + floatKey, 1f, + byteKey, (byte) 1, + shortKey, (short) 1, + longKey, 1L, + doubleKey, 1D, + charKey, '1')); } @Test - @SuppressWarnings("EqualsWithItself") public void equality() { - assertTrue(emptyHMap().equals(emptyHMap())); + assertEquals(emptyHMap(), emptyHMap()); - TypeSafeKey stringKey = typeSafeKey(); - assertTrue(emptyHMap().put(stringKey, "one").equals(emptyHMap().put(stringKey, "one"))); + TypeSafeKey stringKey = typeSafeKey(); + assertEquals(emptyHMap().put(stringKey, "one"), emptyHMap().put(stringKey, "one")); - assertFalse(emptyHMap().equals(emptyHMap().put(stringKey, "string key"))); - assertFalse(emptyHMap().put(stringKey, "string key").equals(emptyHMap())); - assertFalse(emptyHMap().put(typeSafeKey(), "one").equals(emptyHMap().put(typeSafeKey(), "one"))); - assertFalse(emptyHMap().put(typeSafeKey(), "one").equals(emptyHMap().put(typeSafeKey(), 1))); - assertFalse(emptyHMap().put(typeSafeKey(), 1).equals(emptyHMap().put(typeSafeKey(), "one"))); + assertNotEquals(emptyHMap(), emptyHMap().put(stringKey, "string key")); + assertNotEquals(emptyHMap().put(stringKey, "string key"), emptyHMap()); + assertNotEquals(emptyHMap().put(typeSafeKey(), "one"), emptyHMap().put(typeSafeKey(), "one")); + assertNotEquals(emptyHMap().put(typeSafeKey(), "one"), emptyHMap().put(typeSafeKey(), 1)); + assertNotEquals(emptyHMap().put(typeSafeKey(), 1), emptyHMap().put(typeSafeKey(), "one")); } @Test public void hashCodeUsesDecentDistribution() { assertEquals(emptyHMap().hashCode(), emptyHMap().hashCode()); - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertEquals(singletonHMap(stringKey, "string value").hashCode(), singletonHMap(stringKey, "string value").hashCode()); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/SchemaTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/SchemaTest.java new file mode 100644 index 000000000..d5cf6500c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/SchemaTest.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.adt.hmap; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; +import static com.jnape.palatable.lambda.adt.hmap.Schema.schema; +import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + +public class SchemaTest { + + @Test + public void extractsValuesAtKeysFromMap() { + TypeSafeKey.Simple byteKey = typeSafeKey(); + TypeSafeKey.Simple shortKey = typeSafeKey(); + TypeSafeKey.Simple intKey = typeSafeKey(); + TypeSafeKey.Simple longKey = typeSafeKey(); + TypeSafeKey.Simple floatKey = typeSafeKey(); + TypeSafeKey.Simple doubleKey = typeSafeKey(); + TypeSafeKey.Simple charKey = typeSafeKey(); + TypeSafeKey.Simple booleanKey = typeSafeKey(); + + HMap m = hMap(byteKey, (byte) 1, + shortKey, (short) 2, + intKey, 3, + longKey, 4L, + floatKey, 5F, + doubleKey, 6D, + charKey, '7', + booleanKey, true); + + assertLensLawfulness(schema(byteKey, shortKey, intKey, longKey, floatKey, doubleKey, charKey, booleanKey), + asList(emptyHMap(), + m), + asList(nothing(), + just(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, '7', true)))); + } + + @Test + public void extractsNothingIfAnyKeysMissing() { + assertEquals(nothing(), view(schema(typeSafeKey()), emptyHMap())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/SimpleTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/SimpleTest.java new file mode 100644 index 000000000..d3a908a99 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/SimpleTest.java @@ -0,0 +1,14 @@ +package com.jnape.palatable.lambda.adt.hmap; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static org.junit.Assert.assertNotEquals; + +public class SimpleTest { + + @Test + public void usesReferenceEquality() { + assertNotEquals(typeSafeKey(), typeSafeKey()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java index dbd29379e..cf9f68cf8 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java @@ -2,13 +2,73 @@ import org.junit.Test; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; -import static org.junit.Assert.assertFalse; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.optics.Iso.simpleIso; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + public class TypeSafeKeyTest { @Test - public void usesReferenceEquality() { - assertFalse(typeSafeKey().equals(typeSafeKey())); + public void lensLawfulness() { + assertLensLawfulness(TypeSafeKey.typeSafeKey().andThen(simpleIso(Integer::parseInt, Object::toString)), + asList("123", "0"), + asList(456, -1)); + } + + @Test + public void compositionMapsOriginalValueInAndOutOfHMap() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey intKey = stringKey.andThen(simpleIso(Integer::parseInt, Object::toString)); + HMap map = emptyHMap().put(stringKey, "123"); + + assertEquals(just("123"), map.get(stringKey)); + assertEquals(just(123), map.get(intKey)); + + HMap updated = map.put(intKey, 456); + assertEquals(just("456"), updated.get(stringKey)); + assertEquals(just(456), updated.get(intKey)); + + assertEquals(1, updated.keys().size()); + } + + @Test + public void discardRPreservesTypeSafeKey() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey discardedKey = stringKey.discardR(simpleIso(id(), id())); + HMap map = emptyHMap().put(stringKey, "123"); + + assertEquals(just("123"), map.get(discardedKey)); + } + + @Test + public void defaultEquality() { + TypeSafeKey.Simple keyA = typeSafeKey(); + TypeSafeKey mappedKeyA = keyA.andThen(simpleIso(id(), id())); + + assertEquals(keyA, keyA); + assertEquals(keyA, mappedKeyA); + assertEquals(mappedKeyA, keyA); + assertEquals(keyA.hashCode(), mappedKeyA.hashCode()); + + TypeSafeKey.Simple keyB = typeSafeKey(); + assertNotEquals(keyA, keyB); + assertNotEquals(keyB, keyA); + assertNotEquals(keyB, mappedKeyA); + assertNotEquals(mappedKeyA, keyB); + + TypeSafeKey differentMappedKeyA = keyA.andThen(simpleIso(id(), id())); + assertEquals(keyA, differentMappedKeyA); + assertEquals(differentMappedKeyA, keyA); + assertEquals(mappedKeyA, differentMappedKeyA); + assertEquals(differentMappedKeyA, mappedKeyA); + assertEquals(keyA.hashCode(), differentMappedKeyA.hashCode()); + assertEquals(mappedKeyA.hashCode(), differentMappedKeyA.hashCode()); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product2Test.java new file mode 100644 index 000000000..4fe4837e0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product2Test.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product2.product; +import static org.junit.Assert.assertEquals; + +public class Product2Test { + + private Product2 product; + + @Before + public void setUp() { + product = product("a", "b"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + } + + @Test + public void invert() { + assertEquals("ba", product.invert().into((a, b) -> a + b)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product3Test.java new file mode 100644 index 000000000..f6533e682 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product3Test.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product3.product; +import static org.junit.Assert.assertEquals; + +public class Product3Test { + + private Product3 product; + + @Before + public void setUp() { + product = product("a", "b", "c"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + } + + @Test + public void rotations() { + assertEquals("bac", product.invert().into((a, b, c) -> a + b + c)); + assertEquals("bca", product.rotateL3().into((a, b, c) -> a + b + c)); + assertEquals("cab", product.rotateR3().into((a, b, c) -> a + b + c)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product4Test.java new file mode 100644 index 000000000..12d6b3c19 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product4Test.java @@ -0,0 +1,34 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product4.product; +import static org.junit.Assert.assertEquals; + +public class Product4Test { + + private Product4 product; + + @Before + public void setUp() { + product = product("a", "b", "c", "d"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + assertEquals("d", product._4()); + } + + @Test + public void rotations() { + assertEquals("bacd", product.invert().into((a, b, c, d) -> a + b + c + d)); + assertEquals("bcad", product.rotateL3().into((a, b, c, d) -> a + b + c + d)); + assertEquals("cabd", product.rotateR3().into((a, b, c, d) -> a + b + c + d)); + assertEquals("bcda", product.rotateL4().into((a, b, c, d) -> a + b + c + d)); + assertEquals("dabc", product.rotateR4().into((a, b, c, d) -> a + b + c + d)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product5Test.java new file mode 100644 index 000000000..63723741f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product5Test.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product5.product; +import static org.junit.Assert.assertEquals; + +public class Product5Test { + + private Product5 product; + + @Before + public void setUp() { + product = product("a", "b", "c", "d", "e"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + assertEquals("d", product._4()); + assertEquals("e", product._5()); + } + + @Test + public void rotations() { + assertEquals("bacde", product.invert().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("bcade", product.rotateL3().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("cabde", product.rotateR3().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("bcdae", product.rotateL4().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("dabce", product.rotateR4().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("bcdea", product.rotateL5().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("eabcd", product.rotateR5().into((a, b, c, d, e) -> a + b + c + d + e)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product6Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product6Test.java new file mode 100644 index 000000000..aa6e0ceb7 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product6Test.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product6.product; +import static org.junit.Assert.assertEquals; + +public class Product6Test { + + private Product6 product; + + @Before + public void setUp() { + product = product("a", "b", "c", "d", "e", "f"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + assertEquals("d", product._4()); + assertEquals("e", product._5()); + assertEquals("f", product._6()); + } + + @Test + public void rotations() { + assertEquals("bacdef", product.invert().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("bcadef", product.rotateL3().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("cabdef", product.rotateR3().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("bcdaef", product.rotateL4().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("dabcef", product.rotateR4().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("bcdeaf", product.rotateL5().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("eabcdf", product.rotateR5().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("bcdefa", product.rotateL6().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("fabcde", product.rotateR6().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product7Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product7Test.java new file mode 100644 index 000000000..a5cd04b5f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product7Test.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product7.product; +import static org.junit.Assert.assertEquals; + +public class Product7Test { + + private Product7 product; + + @Before + public void setUp() { + product = product("a", "b", "c", "d", "e", "f", "g"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + assertEquals("d", product._4()); + assertEquals("e", product._5()); + assertEquals("f", product._6()); + assertEquals("g", product._7()); + } + + @Test + public void rotations() { + assertEquals("bacdefg", product.invert().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("bcadefg", product.rotateL3().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("cabdefg", product.rotateR3().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("bcdaefg", product.rotateL4().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("dabcefg", product.rotateR4().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("bcdeafg", product.rotateL5().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("eabcdfg", product.rotateR5().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("bcdefag", product.rotateL6().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("fabcdeg", product.rotateR6().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("bcdefga", product.rotateL7().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("gabcdef", product.rotateR7().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product8Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product8Test.java new file mode 100644 index 000000000..64360b763 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product8Test.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product8.product; +import static org.junit.Assert.assertEquals; + +public class Product8Test { + + private Product8 product; + + @Before + public void setUp() { + product = product("a", "b", "c", "d", "e", "f", "g", "h"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + assertEquals("d", product._4()); + assertEquals("e", product._5()); + assertEquals("f", product._6()); + assertEquals("g", product._7()); + assertEquals("h", product._8()); + } + + @Test + public void rotations() { + assertEquals("bacdefgh", product.invert().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcadefgh", product.rotateL3().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("cabdefgh", product.rotateR3().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcdaefgh", product.rotateL4().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("dabcefgh", product.rotateR4().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcdeafgh", product.rotateL5().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("eabcdfgh", product.rotateR5().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcdefagh", product.rotateL6().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("fabcdegh", product.rotateR6().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcdefgah", product.rotateL7().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("gabcdefh", product.rotateR7().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcdefgha", product.rotateL8().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("habcdefg", product.rotateR8().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java b/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java new file mode 100644 index 000000000..456d2619c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java @@ -0,0 +1,76 @@ +package com.jnape.palatable.lambda.functions; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.io.IO; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import static com.jnape.palatable.lambda.functions.Effect.effect; +import static com.jnape.palatable.lambda.functions.Effect.fromConsumer; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Alter.alter; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; +import static com.jnape.palatable.lambda.functions.specialized.SideEffect.sideEffect; +import static com.jnape.palatable.lambda.io.IO.io; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class EffectTest { + + @Test + public void covariantReturns() { + List results = new ArrayList<>(); + + Effect effect = fromConsumer(results::add); + Effect diMapL = effect.diMapL(Object::toString); + Effect contraMap = effect.contraMap(Object::toString); + Effect stringEffect = effect.discardR(constantly("1")); + + assertThat(sequence(asList(effect.apply("1"), + diMapL.apply("2"), + contraMap.apply("3"), + stringEffect.apply("4")), + IO::io) + .fmap(constantly(results)), + yieldsValue(equalTo(asList("1", "2", "3", "4")))); + } + + @Test + public void andThen() { + AtomicInteger counter = new AtomicInteger(); + Effect inc = c -> io(sideEffect(c::incrementAndGet)); + + assertThat(alter(inc.andThen(inc), counter).fmap(AtomicInteger::get), + yieldsValue(equalTo(2))); + } + + @Test + public void staticFactoryMethods() { + AtomicInteger counter = new AtomicInteger(); + + Effect sideEffect = effect(counter::incrementAndGet); + assertThat(sideEffect.apply("foo").flatMap(constantly(io(counter::get))), + yieldsValue(equalTo(1))); + + Effect fnEffect = Effect.fromConsumer(AtomicInteger::incrementAndGet); + assertThat(fnEffect.apply(counter).flatMap(constantly(io(counter::get))), + yieldsValue(equalTo(2))); + } + + @Test + public void toConsumer() { + @SuppressWarnings("RedundantTypeArguments") Effect> addFoo = l -> IO.io(() -> l.add("foo")); + Consumer> consumer = addFoo.toConsumer(); + ArrayList list = new ArrayList<>(); + consumer.accept(list); + assertEquals(singletonList("foo"), list); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn0Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn0Test.java new file mode 100644 index 000000000..87af14209 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn0Test.java @@ -0,0 +1,39 @@ +package com.jnape.palatable.lambda.functions; + +import org.junit.Test; + +import java.util.concurrent.Callable; +import java.util.function.Supplier; + +import static org.junit.Assert.assertEquals; + +public class Fn0Test { + + @Test + public void fromSupplier() { + Supplier supplier = () -> 1; + Fn0 fn0 = Fn0.fromSupplier(supplier); + assertEquals((Integer) 1, fn0.apply()); + } + + @Test + public void fromCallable() { + Callable callable = () -> 1; + Fn0 fn0 = Fn0.fromCallable(callable); + assertEquals((Integer) 1, fn0.apply()); + } + + @Test + public void toSupplier() { + Fn0 fn0 = () -> 1; + Supplier supplier = fn0.toSupplier(); + assertEquals((Integer) 1, supplier.get()); + } + + @Test + public void toCallable() throws Exception { + Fn0 fn0 = () -> 1; + Callable callable = fn0.toCallable(); + assertEquals((Integer) 1, callable.call()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java index 26baebef4..047aea1db 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -1,21 +1,41 @@ package com.jnape.palatable.lambda.functions; -import org.hamcrest.MatcherAssert; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadReaderLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.MonadWriterLaws; import java.util.function.Function; -import static org.hamcrest.core.Is.is; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.choice.Choice2.a; +import static com.jnape.palatable.lambda.adt.choice.Choice2.b; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.Fn1.fromFunction; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft.reduceLeft; +import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static testsupport.traits.Equivalence.equivalence; +@RunWith(Traits.class) public class Fn1Test { - @Test - public void functorProperties() { - Fn1 add2 = integer -> integer + 2; - Fn1 toString = Object::toString; - - MatcherAssert.assertThat(add2.fmap(toString).apply(2), is(toString.apply(add2.apply(2)))); + @TestTraits({FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + MonadReaderLaws.class, + MonadWriterLaws.class}) + public Equivalence> testSubject() { + return equivalence(fn1(Integer::parseInt), f -> f.apply("1")); } @Test @@ -28,16 +48,66 @@ public void profunctorProperties() { } @Test - public void thenIsJustAnAliasForFmap() { - Fn1 add2 = integer -> integer + 2; + public void staticFactoryMethod() { + assertEquals((Integer) 1, Fn1.fn1(Integer::parseInt).apply("1")); + Function function = Integer::parseInt; + Fn1 fn1 = fromFunction(function); + assertEquals((Integer) 1, fn1.apply("1")); + } + + @Test + public void thunk() { Fn1 toString = Object::toString; + assertEquals("1", toString.thunk(1).apply()); + } + + @Test + public void widen() { + Fn1 addOne = x -> x + 1; + assertEquals(just(4), reduceLeft(addOne.widen(), asList(1, 2, 3))); + } + + @Test + public void cartesian() { + Fn1 add1 = x -> x + 1; + assertEquals(tuple("a", 2), add1.cartesian().apply(tuple("a", 1))); + } + + @Test + public void carry() { + Fn1 add1 = x -> x + 1; + assertEquals(tuple(1, 2), add1.carry().apply(1)); + } + + @Test + public void cocartesian() { + Fn1 add1 = x -> x + 1; + assertEquals(a("foo"), add1.cocartesian().apply(a("foo"))); + assertEquals(b(2), add1.cocartesian().apply(b(1))); + } - MatcherAssert.assertThat(add2.then(toString).apply(2), is(toString.apply(add2.apply(2)))); + @Test + public void choose() { + Fn1 add1 = Integer::parseInt; + assertEquals(b(123), add1.choose().apply("123")); + assertEquals(a("foo"), add1.choose().apply("foo")); + } + + @Test + public void toFunction() { + Fn1 add1 = x -> x + 1; + Function function = add1.toFunction(); + assertEquals((Integer) 2, function.apply(1)); + } + + @Test + public void staticPure() { + Fn1 fn1 = Fn1.pureFn1().apply(1); + assertEquals((Integer) 1, fn1.apply("anything")); } @Test - public void adapt() { - Function parseInt = Integer::parseInt; - assertEquals((Integer) 1, Fn1.adapt(parseInt).apply("1")); + public void withSelf() { + assertEquals((Integer) 15, Fn1.withSelf((f, x) -> x > 1 ? x + f.apply(x - 1) : x).apply(5)); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java index 6a845f11a..97a65cd99 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java @@ -2,9 +2,11 @@ import org.junit.Test; +import java.util.Map; import java.util.function.BiFunction; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -36,8 +38,26 @@ public void toBiFunction() { } @Test - public void adapt() { - BiFunction format = String::format; - assertEquals("foo bar", Fn2.adapt(format).apply("foo %s", "bar")); + public void curried() { + Fn1> curriedFn1 = (x) -> (y) -> String.format(x, y); + assertEquals("foo bar", Fn2.curried(curriedFn1).apply("foo %s", "bar")); + } + + @Test + public void fn2() { + Fn2 fn2 = Fn2.fn2(String::format); + assertEquals("foo bar", fn2.apply("foo %s", "bar")); + } + + @Test + public void fromBiFunction() { + BiFunction biFunction = String::format; + assertEquals("foo bar", Fn2.fromBiFunction(biFunction).apply("foo %s", "bar")); + } + + @Test + public void curry() { + Fn1, String> uncurried = into((a, b) -> a + b); + assertEquals("foobar", Fn2.curry(uncurried).apply("foo", "bar")); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java index 7214064f2..78ec9a225 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java @@ -3,7 +3,9 @@ import org.junit.Test; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn3.fn3; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; public class Fn3Test { @@ -28,4 +30,15 @@ public void flipsFirstAndSecondArgument() { public void uncurries() { assertThat(CHECK_MULTIPLICATION.uncurry().apply(tuple(2, 3), 6), is(true)); } + + @Test + public void staticFactoryMethods() { + Fn1> fn1 = a -> (b, c) -> a + b + c; + assertEquals("abc", fn3(fn1).apply("a", "b", "c")); + + Fn2> fn2 = (a, b) -> c -> a + b + c; + assertEquals("abc", fn3(fn2).apply("a", "b", "c")); + + assertEquals("abc", Fn3.fn3((a, b, c) -> a + b + c).apply("a", "b", "c")); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn4Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn4Test.java new file mode 100644 index 000000000..5808dac74 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn4Test.java @@ -0,0 +1,47 @@ +package com.jnape.palatable.lambda.functions; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn4.fn4; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class Fn4Test { + + private static final Fn4 APPEND = + (s1, s2, s3, s4) -> s1 + s2 + s3 + s4; + + @Test + public void canBePartiallyApplied() { + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d"), is("abcd")); + assertThat(APPEND.apply("a").apply("b").apply("c", "d"), is("abcd")); + assertThat(APPEND.apply("a").apply("b", "c", "d"), is("abcd")); + assertThat(APPEND.apply("a", "b", "c", "d"), is("abcd")); + } + + @Test + public void flipsFirstAndSecondArgument() { + assertThat(APPEND.flip().apply("a", "b", "c", "d"), is("bacd")); + } + + @Test + public void uncurries() { + assertThat(APPEND.uncurry().apply(tuple("a", "b"), "c", "d"), is("abcd")); + } + + @Test + public void staticFactoryMethods() { + Fn1> fn1 = a -> (b, c, d) -> a + b + c + d; + assertEquals("abcd", fn4(fn1).apply("a", "b", "c", "d")); + + Fn2> fn2 = (a, b) -> (c, d) -> a + b + c + d; + assertEquals("abcd", fn4(fn2).apply("a", "b", "c", "d")); + + Fn3> fn3 = (a, b, c) -> (d) -> a + b + c + d; + assertEquals("abcd", fn4(fn3).apply("a", "b", "c", "d")); + + assertEquals("abcd", Fn4.fn4((a, b, c, d) -> a + b + c + d).apply("a", "b", "c", "d")); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn5Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn5Test.java new file mode 100644 index 000000000..f7f8097b8 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn5Test.java @@ -0,0 +1,51 @@ +package com.jnape.palatable.lambda.functions; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn5.fn5; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class Fn5Test { + + private static final Fn5 APPEND = + (s1, s2, s3, s4, s5) -> s1 + s2 + s3 + s4 + s5; + + @Test + public void canBePartiallyApplied() { + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d").apply("e"), is("abcde")); + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d", "e"), is("abcde")); + assertThat(APPEND.apply("a").apply("b").apply("c", "d", "e"), is("abcde")); + assertThat(APPEND.apply("a").apply("b", "c", "d", "e"), is("abcde")); + assertThat(APPEND.apply("a", "b", "c", "d", "e"), is("abcde")); + } + + @Test + public void flipsFirstAndSecondArgument() { + assertThat(APPEND.flip().apply("a", "b", "c", "d", "e"), is("bacde")); + } + + @Test + public void uncurries() { + assertThat(APPEND.uncurry().apply(tuple("a", "b"), "c", "d", "e"), is("abcde")); + } + + @Test + public void staticFactoryMethods() { + Fn1> fn1 = a -> (b, c, d, e) -> a + b + c + d + e; + assertEquals("abcde", fn5(fn1).apply("a", "b", "c", "d", "e")); + + Fn2> fn2 = (a, b) -> (c, d, e) -> a + b + c + d + e; + assertEquals("abcde", fn5(fn2).apply("a", "b", "c", "d", "e")); + + Fn3> fn3 = (a, b, c) -> (d, e) -> a + b + c + d + e; + assertEquals("abcde", fn5(fn3).apply("a", "b", "c", "d", "e")); + + Fn4> fn4 = (a, b, c, d) -> (e) -> a + b + c + d + e; + assertEquals("abcde", fn5(fn4).apply("a", "b", "c", "d", "e")); + + assertEquals("abcde", Fn5.fn5((a, b, c, d, e) -> a + b + c + d + e).apply("a", "b", "c", "d", "e")); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn6Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn6Test.java new file mode 100644 index 000000000..2f9faaa2d --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn6Test.java @@ -0,0 +1,54 @@ +package com.jnape.palatable.lambda.functions; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class Fn6Test { + + private static final Fn6 APPEND = + (s1, s2, s3, s4, s5, s6) -> s1 + s2 + s3 + s4 + s5 + s6; + + @Test + public void canBePartiallyApplied() { + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d").apply("e").apply("f"), is("abcdef")); + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d").apply("e", "f"), is("abcdef")); + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d", "e", "f"), is("abcdef")); + assertThat(APPEND.apply("a").apply("b").apply("c", "d", "e", "f"), is("abcdef")); + assertThat(APPEND.apply("a").apply("b", "c", "d", "e", "f"), is("abcdef")); + assertThat(APPEND.apply("a", "b", "c", "d", "e", "f"), is("abcdef")); + } + + @Test + public void flipsFirstAndSecondArgument() { + assertThat(APPEND.flip().apply("a", "b", "c", "d", "e", "f"), is("bacdef")); + } + + @Test + public void uncurries() { + assertThat(APPEND.uncurry().apply(tuple("a", "b"), "c", "d", "e", "f"), is("abcdef")); + } + + @Test + public void staticFactoryMethods() { + Fn1> fn1 = a -> (b, c, d, e, f) -> a + b + c + d + e + f; + assertEquals("abcdef", Fn6.fn6(fn1).apply("a", "b", "c", "d", "e", "f")); + + Fn2> fn2 = (a, b) -> (c, d, e, f) -> a + b + c + d + e + f; + assertEquals("abcdef", Fn6.fn6(fn2).apply("a", "b", "c", "d", "e", "f")); + + Fn3> fn3 = (a, b, c) -> (d, e, f) -> a + b + c + d + e + f; + assertEquals("abcdef", Fn6.fn6(fn3).apply("a", "b", "c", "d", "e", "f")); + + Fn4> fn4 = (a, b, c, d) -> (e, f) -> a + b + c + d + e + f; + assertEquals("abcdef", Fn6.fn6(fn4).apply("a", "b", "c", "d", "e", "f")); + + Fn5> fn5 = (a, b, c, d, e) -> (f) -> a + b + c + d + e + f; + assertEquals("abcdef", Fn6.fn6(fn5).apply("a", "b", "c", "d", "e", "f")); + + assertEquals("abcdef", Fn6.fn6((a, b, c, d, e, f) -> a + b + c + d + e + f).apply("a", "b", "c", "d", "e", "f")); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn7Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn7Test.java new file mode 100644 index 000000000..ed361c26c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn7Test.java @@ -0,0 +1,58 @@ +package com.jnape.palatable.lambda.functions; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class Fn7Test { + + private static final Fn7 APPEND = + (s1, s2, s3, s4, s5, s6, s7) -> s1 + s2 + s3 + s4 + s5 + s6 + s7; + + @Test + public void canBePartiallyApplied() { + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d").apply("e").apply("f").apply("g"), is("abcdefg")); + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d").apply("e").apply("f", "g"), is("abcdefg")); + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d").apply("e", "f", "g"), is("abcdefg")); + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d", "e", "f", "g"), is("abcdefg")); + assertThat(APPEND.apply("a").apply("b").apply("c", "d", "e", "f", "g"), is("abcdefg")); + assertThat(APPEND.apply("a").apply("b", "c", "d", "e", "f", "g"), is("abcdefg")); + assertThat(APPEND.apply("a", "b", "c", "d", "e", "f", "g"), is("abcdefg")); + } + + @Test + public void flipsFirstAndSecondArgument() { + assertThat(APPEND.flip().apply("a", "b", "c", "d", "e", "f", "g"), is("bacdefg")); + } + + @Test + public void uncurries() { + assertThat(APPEND.uncurry().apply(tuple("a", "b"), "c", "d", "e", "f", "g"), is("abcdefg")); + } + + @Test + public void staticFactoryMethod() { + Fn1> fn1 = a -> (b, c, d, e, f, g) -> a + b + c + d + e + f + g; + assertEquals("abcdefg", Fn7.fn7(fn1).apply("a", "b", "c", "d", "e", "f", "g")); + + Fn2> fn2 = (a, b) -> (c, d, e, f, g) -> a + b + c + d + e + f + g; + assertEquals("abcdefg", Fn7.fn7(fn2).apply("a", "b", "c", "d", "e", "f", "g")); + + Fn3> fn3 = (a, b, c) -> (d, e, f, g) -> a + b + c + d + e + f + g; + assertEquals("abcdefg", Fn7.fn7(fn3).apply("a", "b", "c", "d", "e", "f", "g")); + + Fn4> fn4 = (a, b, c, d) -> (e, f, g) -> a + b + c + d + e + f + g; + assertEquals("abcdefg", Fn7.fn7(fn4).apply("a", "b", "c", "d", "e", "f", "g")); + + Fn5> fn5 = (a, b, c, d, e) -> (f, g) -> a + b + c + d + e + f + g; + assertEquals("abcdefg", Fn7.fn7(fn5).apply("a", "b", "c", "d", "e", "f", "g")); + + Fn6> fn6 = (a, b, c, d, e, f) -> (g) -> a + b + c + d + e + f + g; + assertEquals("abcdefg", Fn7.fn7(fn6).apply("a", "b", "c", "d", "e", "f", "g")); + + assertEquals("abcdefg", Fn7.fn7((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g).apply("a", "b", "c", "d", "e", "f", "g")); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn8Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn8Test.java new file mode 100644 index 000000000..058168eb1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn8Test.java @@ -0,0 +1,63 @@ +package com.jnape.palatable.lambda.functions; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn8.fn8; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class Fn8Test { + + private static final Fn8 APPEND = + (s1, s2, s3, s4, s5, s6, s7, s8) -> s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8; + + @Test + public void canBePartiallyApplied() { + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d").apply("e").apply("f").apply("g").apply("h"), is("abcdefgh")); + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d").apply("e").apply("f").apply("g", "h"), is("abcdefgh")); + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d").apply("e").apply("f", "g", "h"), is("abcdefgh")); + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d").apply("e", "f", "g", "h"), is("abcdefgh")); + assertThat(APPEND.apply("a").apply("b").apply("c").apply("d", "e", "f", "g", "h"), is("abcdefgh")); + assertThat(APPEND.apply("a").apply("b").apply("c", "d", "e", "f", "g", "h"), is("abcdefgh")); + assertThat(APPEND.apply("a").apply("b", "c", "d", "e", "f", "g", "h"), is("abcdefgh")); + assertThat(APPEND.apply("a", "b", "c", "d", "e", "f", "g", "h"), is("abcdefgh")); + } + + @Test + public void flipsFirstAndSecondArgument() { + assertThat(APPEND.flip().apply("a", "b", "c", "d", "e", "f", "g", "h"), is("bacdefgh")); + } + + @Test + public void uncurries() { + assertThat(APPEND.uncurry().apply(tuple("a", "b"), "c", "d", "e", "f", "g", "h"), is("abcdefgh")); + } + + @Test + public void staticFactoryMethods() { + Fn1> fn1 = a -> (b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h; + assertEquals("abcdefgh", fn8(fn1).apply("a", "b", "c", "d", "e", "f", "g", "h")); + + Fn2> fn2 = (a, b) -> (c, d, e, f, g, h) -> a + b + c + d + e + f + g + h; + assertEquals("abcdefgh", fn8(fn2).apply("a", "b", "c", "d", "e", "f", "g", "h")); + + Fn3> fn3 = (a, b, c) -> (d, e, f, g, h) -> a + b + c + d + e + f + g + h; + assertEquals("abcdefgh", fn8(fn3).apply("a", "b", "c", "d", "e", "f", "g", "h")); + + Fn4> fn4 = (a, b, c, d) -> (e, f, g, h) -> a + b + c + d + e + f + g + h; + assertEquals("abcdefgh", fn8(fn4).apply("a", "b", "c", "d", "e", "f", "g", "h")); + + Fn5> fn5 = (a, b, c, d, e) -> (f, g, h) -> a + b + c + d + e + f + g + h; + assertEquals("abcdefgh", fn8(fn5).apply("a", "b", "c", "d", "e", "f", "g", "h")); + + Fn6> fn6 = (a, b, c, d, e, f) -> (g, h) -> a + b + c + d + e + f + g + h; + assertEquals("abcdefgh", fn8(fn6).apply("a", "b", "c", "d", "e", "f", "g", "h")); + + Fn7> fn7 = (a, b, c, d, e, f, g) -> (h) -> a + b + c + d + e + f + g + h; + assertEquals("abcdefgh", fn8(fn7).apply("a", "b", "c", "d", "e", "f", "g", "h")); + + assertEquals("abcdefgh", Fn8.fn8((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h).apply("a", "b", "c", "d", "e", "f", "g", "h")); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybesTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybesTest.java new file mode 100644 index 000000000..ed841a988 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybesTest.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.CatMaybes.catMaybes; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class CatMaybesTest { + + @Test + public void empty() { + assertThat(catMaybes(emptyList()), isEmpty()); + } + + @Test + public void onlyNothingsIsEquivalentToEmpty() { + assertThat(catMaybes(asList(nothing(), nothing(), nothing())), isEmpty()); + } + + @Test + public void nonEmpty() { + assertThat(catMaybes(asList(nothing(), just(1), just(2), nothing(), just(3))), iterates(1, 2, 3)); + } + + @Test + public void infiniteIterableSupport() { + assertThat(take(3, catMaybes(repeat(just(1)))), iterates(1, 1, 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java new file mode 100644 index 000000000..6502e6104 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Coalesce.coalesce; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.EitherMatcher.isLeftThat; +import static testsupport.matchers.EitherMatcher.isRightThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +public class CoalesceTest { + + @Test + public void empty() { + assertThat(coalesce(emptyList()), isRightThat(isEmpty())); + } + + @Test + public void allRights() { + assertThat(coalesce(asList(right(1), right(2), right(3))), isRightThat(iterates(1, 2, 3))); + } + + @Test + public void allLefts() { + assertThat(coalesce(asList(left("foo"), left("bar"), left("baz"))), isLeftThat(iterates("foo", "bar", "baz"))); + } + + @Test + public void someRightsAndLefts() { + assertThat(coalesce(asList(right(1), left("foo"), right(2), left("bar"), right(3), left("baz"))), + isLeftThat(iterates("foo", "bar", "baz"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CycleTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CycleTest.java index f9acf8d22..d9c04a459 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CycleTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CycleTest.java @@ -17,7 +17,7 @@ public class CycleTest { @TestTraits({Laziness.class, ImmutableIteration.class, InfiniteIteration.class}) - public Cycle createTestSubject() { + public Cycle createTestSubject() { return cycle(); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DistinctTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DistinctTest.java new file mode 100644 index 000000000..b535e381c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DistinctTest.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Distinct.distinct; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class DistinctTest { + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) + public Distinct testSubject() { + return distinct(); + } + + @Test + public void producesIterableOfOnlySingleElementOccurrences() { + assertThat(distinct(asList(1, 2, 2, 3, 3, 3)), iterates(1, 2, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DowncastTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DowncastTest.java new file mode 100644 index 000000000..585419e1b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DowncastTest.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functor.Functor; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; + +public class DowncastTest { + + @Test + @SuppressWarnings("unused") + public void safeDowncast() { + CharSequence charSequence = "123"; + String s = downcast(charSequence); + + Functor> maybeInt = nothing(); + Maybe cast = downcast(maybeInt); + } + + @Test(expected = ClassCastException.class) + @SuppressWarnings({"JavacQuirks", "unused"}) + public void unsafeDowncast() { + CharSequence charSequence = "123"; + Integer explosion = downcast(charSequence); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/EmptyTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/EmptyTest.java new file mode 100644 index 000000000..41cab7b1e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/EmptyTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Empty.empty; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class EmptyTest { + + @Test + public void emptiness() { + Empty empty = empty(); + + assertTrue(empty.apply(emptySet())); + assertFalse(empty.apply(singleton(1))); + assertFalse(empty.apply(repeat(1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/FlattenTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/FlattenTest.java new file mode 100644 index 000000000..8138aaed0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/FlattenTest.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Map; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import java.util.Collection; +import java.util.Collections; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class FlattenTest { + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class}) + public Fn1, Iterable> testSubject() { + return Flatten.flatten().contraMap(Map.>map(Collections::singletonList)); + } + + @Test + public void flattensIterableOfEmptyIterables() { + assertThat(flatten(asList(emptyList(), emptyList())), isEmpty()); + } + + @Test + public void flattensSparseIterableOfPopulatedIterables() { + assertThat(flatten(asList(emptyList(), asList(1, 2, 3), emptyList(), emptyList(), singleton(4), asList(5, 6), emptyList())), + iterates(1, 2, 3, 4, 5, 6)); + } + + @Test + public void flattenMultipleLevelsOfNesting() { + assertThat(flatten(asList(asList(asList(1, 2, 3), asList(4, 5)), singletonList(asList(6, 7)))), + iterates(asList(1, 2, 3), asList(4, 5), asList(6, 7))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/HeadTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/HeadTest.java index 6c09d03b1..47a0deffb 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/HeadTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/HeadTest.java @@ -4,8 +4,8 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -16,11 +16,11 @@ public class HeadTest { @Test public void returnsTheHeadOfNonEmptyIterable() { - assertEquals(Optional.of(1), head(asList(1, 2, 3))); + assertEquals(just(1), head(asList(1, 2, 3))); } @Test public void isEmptyForEmptyIterable() { - assertEquals(Optional.empty(), head(emptyList())); + assertEquals(nothing(), head(emptyList())); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitTest.java new file mode 100644 index 000000000..b27b65903 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitTest.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Init.init; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class InitTest { + + @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, Laziness.class, ImmutableIteration.class, FiniteIteration.class}) + public Fn1, ? extends Iterable> testSubject() { + return init(); + } + + @Test + public void empty() { + assertThat(init(emptyList()), isEmpty()); + } + + @Test + public void nonEmpty() { + assertThat(init(singletonList(1)), isEmpty()); + assertThat(init(asList(1, 2, 3)), iterates(1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitsTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitsTest.java new file mode 100644 index 000000000..50b417d58 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitsTest.java @@ -0,0 +1,44 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Inits.inits; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class InitsTest { + + @TestTraits({Laziness.class, EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) + public Fn1, ? extends Iterable> testSubject() { + return inits(); + } + + @Test + public void empty() { + assertThat(inits(emptyList()), iterates(emptyList())); + } + + @Test + public void nonEmpty() { + assertThat(inits(singletonList(1)), iterates(emptyList(), singletonList(1))); + assertThat(inits(asList(1, 2, 3, 4, 5)), iterates(emptyList(), + singletonList(1), + asList(1, 2), + asList(1, 2, 3), + asList(1, 2, 3, 4), + asList(1, 2, 3, 4, 5))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java index 142ba988f..acc66f46d 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -13,11 +13,11 @@ public class LastTest { @Test public void presentForNonEmptyIterable() { - assertEquals(Optional.of(3), last(asList(1, 2, 3))); + assertEquals(just(3), last(asList(1, 2, 3))); } @Test - public void emptyForEmpyIterables() { - assertEquals(Optional.empty(), last(emptyList())); + public void emptyForEmptyIterables() { + assertEquals(nothing(), last(emptyList())); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/MagnetizeTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/MagnetizeTest.java new file mode 100644 index 000000000..cec328d11 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/MagnetizeTest.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Magnetize.magnetize; +import static java.util.Arrays.asList; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class MagnetizeTest { + + @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Laziness.class}) + public Fn1, Iterable>> testSubject() { + return magnetize(); + } + + @Test + public void magnetizesElementsByPredicateOutcome() { + assertThat(magnetize(asList(1, 1, 2, 3, 3, 3, 2, 2, 1)), + contains(iterates(1, 1), + iterates(2), + iterates(3, 3, 3), + iterates(2, 2), + iterates(1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/NotTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/NotTest.java new file mode 100644 index 000000000..6269f526b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/NotTest.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.specialized.Predicate; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Not.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class NotTest { + + @Test + public void negatesPredicate() { + Predicate isTrue = not(a -> !a); + assertTrue(isTrue.apply(true)); + assertFalse(isTrue.apply(false)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/OccurrencesTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/OccurrencesTest.java new file mode 100644 index 000000000..290fa2736 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/OccurrencesTest.java @@ -0,0 +1,29 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Occurrences.occurrences; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; + +public class OccurrencesTest { + + @Test + @SuppressWarnings("serial") + public void occurrencesOfIndividualElements() { + assertEquals(new HashMap() {{ + put("foo", 2L); + put("bar", 2L); + put("baz", 1L); + }}, occurrences(asList("foo", "bar", "foo", "baz", "bar"))); + } + + @Test + public void emptyIterableHasNoOccurrences() { + assertEquals(Collections.emptyMap(), occurrences(emptyList())); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/RepeatTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/RepeatTest.java index 80112707f..de34c53b4 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/RepeatTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/RepeatTest.java @@ -17,7 +17,7 @@ public class RepeatTest { @TestTraits({Laziness.class, ImmutableIteration.class, InfiniteIteration.class}) - public Repeat createTestSubject() { + public Repeat createTestSubject() { return repeat(); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ReverseTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ReverseTest.java index da6a94e45..39a133bcb 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ReverseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ReverseTest.java @@ -24,20 +24,20 @@ public class ReverseTest { @TestTraits({Laziness.class, ImmutableIteration.class, FiniteIteration.class, EmptyIterableSupport.class}) - public Reverse createTestSubject() { + public Reverse createTestSubject() { return reverse(); } @Test public void iteratesElementsOfAnIterableBackwards() { - Iterable words = asList("the", "rain", "in", "Spain"); + Iterable words = asList("the", "rain", "in", "Spain"); Iterable reversed = reverse(words); assertThat(reversed, iterates("Spain", "in", "rain", "the")); } @Test - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"}) public void doesNotBeginReversingUntilIterated() { Iterable mockIterable = mock(Iterable.class); Iterator mockIterator = mock(Iterator.class); @@ -50,4 +50,9 @@ public void doesNotBeginReversingUntilIterated() { verify(mockIterator).hasNext(); verify(mockIterator, never()).next(); } + + @Test + public void doubleReverseIsNoOp() { + assertThat(reverse(reverse(asList(1, 2, 3))), iterates(1, 2, 3)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SizeTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SizeTest.java index 7ee079443..1a127054f 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SizeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SizeTest.java @@ -4,13 +4,12 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; public class SizeTest { @@ -21,13 +20,20 @@ public void countsElementsInIterable() { } @Test + @SuppressWarnings("serial") public void optimizesForCollections() { - Collection collection = spy(new ArrayList() {{ - add(1); - add(2); - add(3); - }}); - when(collection.iterator()).thenThrow(new IllegalStateException("should not be using the iterator")); + Collection collection = new ArrayList() { + @Override + public Iterator iterator() { + throw new IllegalStateException("should not be using the iterator"); + } + + { + add(1); + add(2); + add(3); + } + }; assertEquals((Long) 3L, size(collection)); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SortTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SortTest.java new file mode 100644 index 000000000..61181d07c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SortTest.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import org.junit.Test; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; + +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Sort.sort; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +public class SortTest { + + @TestTraits({FiniteIteration.class, EmptyIterableSupport.class}) + public Fn1, List> testSubject() { + return sort(); + } + + @Test + public void sortsIterable() { + assertThat(sort(asList(2, 1, 3)), iterates(1, 2, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailTest.java index 055cd9c9b..57a854456 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailTest.java @@ -20,7 +20,7 @@ public class TailTest { @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Fn1 createTraitsTestSubject() { + public Fn1, ?> createTraitsTestSubject() { return tail(); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java new file mode 100644 index 000000000..a384d21cc --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java @@ -0,0 +1,54 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Tails.tails; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class TailsTest { + + @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Laziness.class}) + public Fn1, ? extends Iterable> testSubject() { + return tails(); + } + + @Test + public void empty() { + assertThat(tails(emptyList()), iterates(emptyList())); + } + + @Test + public void nonEmpty() { + assertThat(tails(singletonList(1)), iterates(singletonList(1), emptyList())); + assertThat(tails(asList(1, 2, 3, 4, 5)), iterates(asList(1, 2, 3, 4, 5), + asList(2, 3, 4, 5), + asList(3, 4, 5), + asList(4, 5), + singletonList(5), + emptyList())); + } + + @Test + public void largeNumberOfElements() { + assertEquals(just(emptyList()), last(tails(take(10_000, repeat(1))))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UnconsTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UnconsTest.java new file mode 100644 index 000000000..01abfdcd2 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UnconsTest.java @@ -0,0 +1,39 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; + +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class UnconsTest { + + @TestTraits({EmptyIterableSupport.class}) + public Uncons testSubject() { + return uncons(); + } + + @Test + public void nonEmptyIterable() { + Iterable numbers = asList(1, 2, 3); + Tuple2> headAndTail = uncons(numbers).orElseThrow(AssertionError::new); + + assertEquals((Integer) 1, headAndTail._1()); + assertThat(headAndTail._2(), iterates(2, 3)); + } + + @Test + public void emptyIterable() { + assertEquals(nothing(), uncons(emptyList())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UpcastTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UpcastTest.java new file mode 100644 index 000000000..2a5fa7e55 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UpcastTest.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static java.util.Arrays.asList; + +public class UpcastTest { + + @Test + @SuppressWarnings("unused") + public void castsUp() { + Upcast upcast = upcast(); + Iterable strings = asList("foo", "bar"); + Iterable charSequences = map(upcast, strings); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java new file mode 100644 index 000000000..166e99f29 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.Fn2.fn2; +import static com.jnape.palatable.lambda.functions.builtin.fn2.$.$; +import static org.junit.Assert.assertEquals; + +public class $Test { + + @Test + public void application() { + assertEquals((Integer) 1, $(x -> x + 1, 0)); + assertEquals((Integer) 1, $.$(x -> x + 1).apply(0)); + } + + @Test + public void curryingInference() { + assertEquals((Integer) 1, $($(fn2(Integer::sum), 0), 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AllTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AllTest.java index d746b0fc1..376295e5b 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AllTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AllTest.java @@ -7,8 +7,6 @@ import org.junit.runner.RunWith; import testsupport.traits.EmptyIterableSupport; -import java.util.function.Function; - import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.functions.builtin.fn2.All.all; @@ -19,10 +17,10 @@ @RunWith(Traits.class) public class AllTest { - private static final Function EVEN = x -> x.doubleValue() % 2 == 0; + private static final Fn1 EVEN = x -> x.doubleValue() % 2 == 0; @TestTraits({EmptyIterableSupport.class}) - public Fn1, Boolean> createTestSubject() { + public Fn1, ? extends Boolean> createTestSubject() { return all(constantly(true)); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java new file mode 100644 index 000000000..5baa26fa1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java @@ -0,0 +1,25 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import java.util.ArrayList; + +import static com.jnape.palatable.lambda.functions.Effect.fromConsumer; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Alter.alter; +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class AlterTest { + + @Test + public void altersInput() { + ArrayList input = new ArrayList<>(); + assertThat(alter(fromConsumer(xs -> xs.add("foo")), input), + yieldsValue(allOf(sameInstance(input), + equalTo(singletonList("foo"))))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AnyTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AnyTest.java index aa8f8a118..ccf0451cd 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AnyTest.java @@ -7,8 +7,6 @@ import org.junit.runner.RunWith; import testsupport.traits.EmptyIterableSupport; -import java.util.function.Function; - import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.functions.builtin.fn2.Any.any; @@ -19,7 +17,7 @@ @RunWith(Traits.class) public class AnyTest { - public static final Function EVEN = x -> x % 2 == 0; + public static final Fn1 EVEN = x -> x % 2 == 0; @TestTraits({EmptyIterableSupport.class}) public Fn1, Boolean> createTestSubject() { diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AutoBracketTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AutoBracketTest.java new file mode 100644 index 000000000..b6cd3cf4a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AutoBracketTest.java @@ -0,0 +1,48 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.io.IO; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.AutoBracket.autoBracket; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.throwsException; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class AutoBracketTest { + + private AtomicInteger closedCounter; + private AutoCloseable autoCloseable; + + @Before + public void setUp() { + closedCounter = new AtomicInteger(0); + autoCloseable = closedCounter::incrementAndGet; + } + + @Test + public void closeWhenDone() { + IO bracketed = autoBracket(io(autoCloseable), closeable -> io(1)); + + assertEquals(0, closedCounter.get()); + assertThat(bracketed, yieldsValue(equalTo(1))); + assertEquals(1, closedCounter.get()); + } + + @Test + public void closeOnException() { + RuntimeException cause = new RuntimeException(); + + IO bracketed = autoBracket(io(autoCloseable), closeable -> IO.throwing(cause)); + + assertEquals(0, closedCounter.get()); + assertThat(bracketed, throwsException(equalTo(cause))); + assertEquals(1, closedCounter.get()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/BothTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/BothTest.java new file mode 100644 index 000000000..961d2679c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/BothTest.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; +import static org.junit.Assert.assertEquals; + +public class BothTest { + + @Test + public void duallyAppliesTwoFunctionsToSameInput() { + assertEquals(tuple(1, -1), both(x -> x + 1, x -> x - 1, 0)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProductTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProductTest.java index b6b74e9e4..07780e82b 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProductTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProductTest.java @@ -18,7 +18,6 @@ import static testsupport.matchers.IterableMatcher.iterates; @RunWith(Traits.class) -@SuppressWarnings("unchecked") public class CartesianProductTest { @TestTraits({Laziness.class, ImmutableIteration.class, EmptyIterableSupport.class, FiniteIteration.class}) @@ -29,7 +28,7 @@ public Fn1, Iterable>> createTestSubjec @Test public void computesCartesianProductOfTwoEquallySizedIterables() { Iterable numbers = asList(1, 2, 3); - Iterable letters = asList("a", "b", "c"); + Iterable letters = asList("a", "b", "c"); assertThat( cartesianProduct(numbers, letters), @@ -50,7 +49,7 @@ public void computesCartesianProductOfTwoEquallySizedIterables() { @Test public void worksForTwoUnequallySizedIterables() { Iterable oneThroughThree = asList(1, 2, 3); - Iterable aAndB = asList("a", "b"); + Iterable aAndB = asList("a", "b"); assertThat( cartesianProduct(oneThroughThree, aAndB), diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CmpEqTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CmpEqTest.java new file mode 100644 index 000000000..05290241a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CmpEqTest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.CmpEq.cmpEq; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CmpEqTest { + + @Test + public void comparisons() { + assertTrue(cmpEq(1, 1)); + assertFalse(cmpEq(1, 2)); + assertFalse(cmpEq(2, 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DifferenceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DifferenceTest.java new file mode 100644 index 000000000..2b1dd6ecc --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DifferenceTest.java @@ -0,0 +1,39 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Difference.difference; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class DifferenceTest { + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) + public Fn1, Iterable> testSubject() { + return Difference.difference().flip().apply(asList(1, 2, 3)); + } + + @Test + public void semigroup() { + assertThat(difference(emptyList(), emptyList()), isEmpty()); + assertThat(difference(asList(1, 2, 3), emptyList()), iterates(1, 2, 3)); + assertThat(difference(asList(1, 2, 2, 3), emptyList()), iterates(1, 2, 3)); + assertThat(difference(emptyList(), asList(1, 2, 3)), isEmpty()); + assertThat(difference(asList(1, 2, 3), singletonList(4)), iterates(1, 2, 3)); + assertThat(difference(asList(1, 2, 3), asList(2, 4)), iterates(1, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropTest.java index 329fd13e2..62dc0e73b 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropTest.java @@ -10,8 +10,13 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.functions.builtin.fn2.Drop.drop; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static testsupport.matchers.IterableMatcher.iterates; @@ -27,4 +32,10 @@ public Fn1, Iterable> createTestSubject() { public void dropsElementsUpToLimit() { assertThat(drop(2, asList(1, 2, 3, 4)), iterates(3, 4)); } + + @Test + public void stackSafety() { + int stackBlowingNumber = 10_000; + assertEquals(just(1), head(times(stackBlowingNumber, drop(1), repeat(1)))); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java index 352aa47b4..ff4551a11 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java @@ -11,6 +11,9 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; +import java.util.ArrayList; +import java.util.List; + import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.DropWhile.dropWhile; import static java.util.Arrays.asList; @@ -23,7 +26,7 @@ public class DropWhileTest { @TestTraits({Laziness.class, ImmutableIteration.class, FiniteIteration.class, EmptyIterableSupport.class}) public Fn1, Iterable> createTestSubject() { - return dropWhile(constantly(true)); + return dropWhile(constantly(false)); } @Test @@ -41,4 +44,25 @@ public void dropsAllElementsIfPredicateNeverFails() { public void dropsNoElementsIfPredicateImmediatelyFails() { assertThat(dropWhile(constantly(false), asList(1, 2, 3)), iterates(1, 2, 3)); } + + @Test + public void deforestingExecutesPredicatesInOrder() { + List innerInvocations = new ArrayList<>(); + List outerInvocations = new ArrayList<>(); + dropWhile(y -> { + outerInvocations.add(y); + return true; + }, dropWhile(x -> { + innerInvocations.add(x); + return x > 2; + }, asList(1, 2, 3))).forEach(__ -> {}); + assertThat(innerInvocations, iterates(1)); + assertThat(outerInvocations, iterates(1, 2, 3)); + } + + @Test + public void eachLayerIsAppliedOnce() { + assertThat(dropWhile(i -> i % 2 == 0, dropWhile(i -> i % 2 == 1, asList(1, 2, 3))), + iterates(3)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FilterTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FilterTest.java index d718c55ef..cc589a984 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FilterTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FilterTest.java @@ -11,6 +11,9 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; +import java.util.ArrayList; +import java.util.List; + import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; import static java.util.Arrays.asList; @@ -21,7 +24,7 @@ public class FilterTest { @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Fn1 createTraitsTestSubject() { + public Fn1, ?> testSubject() { return filter(constantly(true)); } @@ -33,4 +36,19 @@ public void filtersOutMatchingElements() { iterates(2, 4, 6) ); } + + @Test + public void deforestingExecutesPredicatesInOrder() { + List innerInvocations = new ArrayList<>(); + List outerInvocations = new ArrayList<>(); + filter(y -> { + outerInvocations.add(y); + return true; + }, filter(x -> { + innerInvocations.add(x); + return x % 2 == 0; + }, asList(1, 2, 3))).forEach(__ -> {}); + assertThat(innerInvocations, iterates(1, 2, 3)); + assertThat(outerInvocations, iterates(2)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FindTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FindTest.java index 585a01793..76eb4cbd9 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FindTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FindTest.java @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.Find.find; import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; @@ -14,19 +14,16 @@ public class FindTest { @Test public void findsFirstElementMatchingPredicate() { - assertEquals(Optional.of("three"), - find(s -> s.length() > 3, asList("one", "two", "three", "four"))); + assertEquals(just("three"), find(s -> s.length() > 3, asList("one", "two", "three", "four"))); } @Test public void isEmptyIfNoElementsMatchPredicate() { - assertEquals(Optional.empty(), - find(s -> s.length() > 5, asList("one", "two", "three", "four"))); + assertEquals(nothing(), find(s -> s.length() > 5, asList("one", "two", "three", "four"))); } @Test public void shortCircuitsOnMatch() { - assertEquals(Optional.of(0), - find(constantly(true), iterate(x -> x + 1, 0))); + assertEquals(just(0), find(constantly(true), iterate(x -> x + 1, 0))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTETest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTETest.java new file mode 100644 index 000000000..c3fd1f89b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTETest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.GTE.gte; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTETest { + + @Test + public void comparisons() { + assertTrue(gte(1, 2)); + assertTrue(gte(1, 1)); + assertFalse(gte(2, 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTTest.java new file mode 100644 index 000000000..4f448b695 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTTest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.GT.gt; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTTest { + + @Test + public void comparisons() { + assertTrue(gt(1, 2)); + assertFalse(gt(1, 1)); + assertFalse(gt(2, 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupByTest.java new file mode 100644 index 000000000..5a9401099 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupByTest.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.GroupBy.groupBy; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; + +@SuppressWarnings("serial") +public class GroupByTest { + + @Test + public void grouping() { + assertEquals(new HashMap>() {{ + put(3, asList("one", "two")); + put(5, singletonList("three")); + }}, groupBy(String::length, asList("one", "two", "three"))); + } + + @Test + public void emptyIterableProducesEmptyMap() { + assertEquals(Collections.>emptyMap(), groupBy(id(), emptyList())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOfTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOfTest.java index c341257a9..587ee7d88 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOfTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOfTest.java @@ -16,7 +16,6 @@ import static org.junit.Assert.assertThat; import static testsupport.matchers.IterableMatcher.iterates; -@SuppressWarnings("unchecked") @RunWith(Traits.class) public class InGroupsOfTest { @@ -27,15 +26,15 @@ public Fn1, Iterable>> createTestSubject() { @Test public void evenlyDistributesGroupedElementsOverIterable() { - Iterable oneThroughSix = asList(1, 2, 3, 4, 5, 6); - Iterable> groups = inGroupsOf(2, oneThroughSix); + Iterable oneThroughSix = asList(1, 2, 3, 4, 5, 6); + Iterable> groups = inGroupsOf(2, oneThroughSix); assertThat(groups, iterates(asList(1, 2), asList(3, 4), asList(5, 6))); } @Test public void lastGroupIsUnfinishedIfIterableSizeNotEvenlyDivisibleByK() { - Iterable oneThroughFive = asList(1, 2, 3, 4, 5); - Iterable> groups = inGroupsOf(2, oneThroughFive); + Iterable oneThroughFive = asList(1, 2, 3, 4, 5); + Iterable> groups = inGroupsOf(2, oneThroughFive); assertThat(groups, iterates(asList(1, 2), asList(3, 4), singletonList(5))); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntersperseTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntersperseTest.java index c87acc89e..8a6308326 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntersperseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntersperseTest.java @@ -30,6 +30,11 @@ public void interspersesBetweenElementsInIterable() { assertThat(intersperse(0, asList(1, 2, 3)), iterates(1, 0, 2, 0, 3)); } + @Test + public void doesNotIntersperseSingletonIterable() { + assertThat(intersperse(0, asList(1)), iterates(1)); + } + @Test public void doesNotIntersperseEmptyIterable() { assertThat(intersperse(0, emptyList()), isEmpty()); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into1Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into1Test.java new file mode 100644 index 000000000..de1501fb5 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into1Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into1.into1; +import static org.junit.Assert.assertEquals; + +public class Into1Test { + + @Test + public void appliesSingletonHListHeadToFunction() { + assertEquals("FOO", into1(String::toUpperCase, singletonHList("foo"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into3Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into3Test.java new file mode 100644 index 000000000..a8255c55e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into3Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into3.into3; +import static org.junit.Assert.assertEquals; + +public class Into3Test { + + @Test + public void appliesTupleToFunction() { + assertEquals((Integer) 6, into3((a, b, c) -> a + b + c, tuple(1, 2, 3))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4Test.java new file mode 100644 index 000000000..7572a0c24 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into4.into4; +import static org.junit.Assert.assertEquals; + +public class Into4Test { + + @Test + public void appliesTupleToFunction() { + assertEquals((Integer) 10, into4((a, b, c, d) -> a + b + c + d, tuple(1, 2, 3, 4))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5Test.java new file mode 100644 index 000000000..6fa237738 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into5.into5; +import static org.junit.Assert.assertEquals; + +public class Into5Test { + + @Test + public void appliesTupleToFunction() { + assertEquals((Integer) 15, into5((a, b, c, d, e) -> a + b + c + d + e, tuple(1, 2, 3, 4, 5))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6Test.java new file mode 100644 index 000000000..b18abc4c0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into6.into6; +import static org.junit.Assert.assertEquals; + +public class Into6Test { + + @Test + public void appliesTupleToFunction() { + assertEquals((Integer) 21, into6((a, b, c, d, e, f) -> a + b + c + d + e + f, tuple(1, 2, 3, 4, 5, 6))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7Test.java new file mode 100644 index 000000000..170676552 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into7.into7; +import static org.junit.Assert.assertEquals; + +public class Into7Test { + + @Test + public void appliesTupleToFunction() { + assertEquals((Integer) 28, into7((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g, tuple(1, 2, 3, 4, 5, 6, 7))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8Test.java new file mode 100644 index 000000000..0c5efc698 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into8.into8; +import static org.junit.Assert.assertEquals; + +public class Into8Test { + + @Test + public void appliesTupleToFunction() { + assertEquals((Integer) 36, into8((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h, tuple(1, 2, 3, 4, 5, 6, 7, 8))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntoTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntoTest.java new file mode 100644 index 000000000..dfcc7b064 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntoTest.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static org.junit.Assert.assertEquals; + +public class IntoTest { + + @Test + public void appliesTupleToFunction() { + assertEquals((Integer) 3, into((a, b) -> a + b, tuple(1, 2))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IterateTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IterateTest.java index 961aa0d15..564290452 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IterateTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IterateTest.java @@ -21,7 +21,7 @@ public class IterateTest { @TestTraits({Laziness.class, InfiniteIteration.class, ImmutableIteration.class}) - public Fn1 createTestSubject() { + public Fn1, ? extends Iterable> createTestSubject() { return iterate(constantly(new ArrayList<>())); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTETest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTETest.java new file mode 100644 index 000000000..8fd684b31 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTETest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.LTE.lte; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTETest { + + @Test + public void comparisons() { + assertTrue(lte(2, 1)); + assertTrue(lte(1, 1)); + assertFalse(lte(1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTTest.java new file mode 100644 index 000000000..2c2229aa2 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTTest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.LT.lt; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTTest { + + @Test + public void comparisons() { + assertTrue(lt(2, 1)); + assertFalse(lt(1, 1)); + assertFalse(lt(1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRecTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRecTest.java new file mode 100644 index 000000000..8987fe32a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRecTest.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.LazyRec.lazyRec; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; + +public class LazyRecTest { + + @Test + public void recursivelyComputesValue() { + assertEquals(STACK_EXPLODING_NUMBER, + LazyRec.lazyRec((f, x) -> x < STACK_EXPLODING_NUMBER + ? f.apply(x + 1) + : lazy(x), + 0) + .value()); + } + + @Test + public void defersAllComputationUntilForced() { + AtomicBoolean invoked = new AtomicBoolean(false); + lazyRec((f, x) -> { + invoked.set(true); + return lazy(x); + }, + 0); + + assertFalse(invoked.get()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeByTest.java new file mode 100644 index 000000000..25b0723eb --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeByTest.java @@ -0,0 +1,54 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.functions.builtin.fn2.GTE.gte; +import static com.jnape.palatable.lambda.functions.builtin.fn2.MagnetizeBy.magnetizeBy; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class MagnetizeByTest { + + @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Laziness.class}) + public Fn1, Iterable>> testSubject() { + return magnetizeBy(eq()); + } + + @Test + @SuppressWarnings("unchecked") + public void magnetizesElementsByPredicateOutcome() { + assertThat(magnetizeBy(GTE.gte(), emptyList()), isEmpty()); + assertThat(magnetizeBy(gte(), singletonList(1)), contains(iterates(1))); + assertThat(magnetizeBy(gte(), asList(1, 2, 3, 2, 2, 3, 2, 1)), + contains(iterates(1, 2, 3), + iterates(2, 2, 3), + iterates(2), + iterates(1))); + } + + @Test + public void stackSafety() { + int stackBlowingNumber = 10_000; + assertThat(last(magnetizeBy((x, y) -> false, take(stackBlowingNumber, repeat(1)))).orElseThrow(AssertionError::new), + iterates(1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MapTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MapTest.java index eb4c68867..4e7f8967c 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MapTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MapTest.java @@ -8,6 +8,7 @@ import testsupport.traits.EmptyIterableSupport; import testsupport.traits.FiniteIteration; import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; import testsupport.traits.Laziness; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; @@ -19,8 +20,8 @@ @RunWith(Traits.class) public class MapTest { - @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Fn1 createTraitsTestSubject() { + @TestTraits({Laziness.class, EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) + public Fn1, ?> createTraitsTestSubject() { return map(id()); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial2Test.java deleted file mode 100644 index 81598f331..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial2Test.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.jnape.palatable.lambda.functions.builtin.fn2; - -import org.junit.Test; - -import java.util.function.BiFunction; - -import static com.jnape.palatable.lambda.functions.builtin.fn2.Partial2.partial2; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; - -public class Partial2Test { - - @Test - public void partiallyAppliesFunction() { - BiFunction subtract = (minuend, subtrahend) -> minuend - subtrahend; - assertThat(partial2(subtract, 3).apply(2), is(1)); - } -} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial3Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial3Test.java deleted file mode 100644 index f939c68eb..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partial3Test.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.jnape.palatable.lambda.functions.builtin.fn2; - -import com.jnape.palatable.lambda.functions.Fn3; -import org.junit.Test; - -import static com.jnape.palatable.lambda.functions.builtin.fn2.Partial3.partial3; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; - -public class Partial3Test { - - @Test - public void partiallyAppliesFunction() { - Fn3 concat = (s1, s2, s3) -> s1 + s2 + s3; - - assertThat(partial3(concat, "foo").apply(" bar", " baz"), is("foo bar baz")); - } -} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java index 3f0fe8b84..6ec5453a2 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java @@ -1,9 +1,10 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; @@ -12,13 +13,14 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; -import static com.jnape.palatable.lambda.adt.Either.left; -import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.choice.Choice2.a; +import static com.jnape.palatable.lambda.adt.choice.Choice2.b; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Cycle.cycle; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.functions.builtin.fn2.Partition.partition; import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static java.util.Arrays.asList; import static org.junit.Assert.assertThat; import static testsupport.matchers.IterableMatcher.iterates; @@ -27,14 +29,15 @@ public class PartitionTest { @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Fn1 createTraitsLeftTestSubject() { - return partition(constantly(left(1))).andThen(Tuple2::_1); + public Subjects, ?>> createTraitsTestSubject() { + return subjects(partition(constantly(a(1))).fmap(Tuple2::_1), + partition(constantly(b(1))).fmap(Tuple2::_2)); } @Test - public void partitionsIterableIntoLeftsAndRights() { - Iterable strings = asList("one", "two", "three", "four", "five"); - Tuple2, Iterable> partition = partition(s -> s.length() % 2 == 1 ? left(s) : right(s.length()), strings); + public void partitionsIterableIntoAsAndBs() { + Iterable strings = asList("one", "two", "three", "four", "five"); + Tuple2, Iterable> partition = partition(s -> s.length() % 2 == 1 ? a(s) : b(s.length()), strings); assertThat(partition._1(), iterates("one", "two", "three")); assertThat(partition._2(), iterates(4, 4)); @@ -42,8 +45,8 @@ public void partitionsIterableIntoLeftsAndRights() { @Test public void infiniteListSupport() { - Iterable> eithers = cycle(left("left"), right(1)); - Tuple2, Iterable> partition = partition(id(), eithers); + Iterable> coproducts = cycle(a("left"), b(1)); + Tuple2, Iterable> partition = partition(id(), coproducts); assertThat(take(3, partition._1()), iterates("left", "left", "left")); assertThat(take(3, partition._2()), iterates(1, 1, 1)); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeftTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeftTest.java index 8baf48eb8..6ca005aef 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeftTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeftTest.java @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft.reduceLeft; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -15,17 +15,11 @@ public class ReduceLeftTest { @Test public void reduceLeftAccumulatesLeftToRightUsingFirstElementAsStartingAccumulation() { - assertThat( - reduceLeft(explainFold(), asList("1", "2", "3", "4", "5")), - is(Optional.of("((((1 + 2) + 3) + 4) + 5)")) - ); + assertThat(reduceLeft(explainFold(), asList("1", "2", "3", "4", "5")), is(just("((((1 + 2) + 3) + 4) + 5)"))); } @Test public void isEmptyIfIterableIsEmpty() { - assertThat( - reduceLeft(explainFold(), emptyList()), - is(Optional.empty()) - ); + assertThat(reduceLeft(explainFold(), emptyList()), is(nothing())); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRightTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRightTest.java index eb4bfb8d7..1c9747684 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRightTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRightTest.java @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceRight.reduceRight; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -15,17 +15,11 @@ public class ReduceRightTest { @Test public void accumulatesRightToLeftUsingLastElementAsStartingAccumulation() { - assertThat( - reduceRight(explainFold(), asList("1", "2", "3", "4", "5")), - is(Optional.of("(1 + (2 + (3 + (4 + 5))))")) - ); + assertThat(reduceRight(explainFold(), asList("1", "2", "3", "4", "5")), is(just("(1 + (2 + (3 + (4 + 5))))"))); } @Test public void isEmptyIfIterableIsEmpty() { - assertThat( - reduceRight(explainFold(), emptyList()), - is(Optional.empty()) - ); + assertThat(reduceRight(explainFold(), emptyList()), is(nothing())); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReplicateTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReplicateTest.java new file mode 100644 index 000000000..66848b16e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReplicateTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +public class ReplicateTest { + + @Test + public void replicate0TimesProducesEmptyIterable() { + assertThat(replicate(0, 1), isEmpty()); + } + + @Test + public void replicateMoreThan0TimesProducesPopulatedIterable() { + assertThat(replicate(3, '1'), iterates('1', '1', '1')); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java new file mode 100644 index 000000000..cec572406 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java @@ -0,0 +1,77 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Compose; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import org.junit.Test; + +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +public class SequenceTest { + + @Test + public void naturality() { + Fn1, Either> t = id -> right(id.runIdentity()); + Either> traversable = right(new Identity<>(1)); + + assertEquals(t.apply(sequence(traversable, Identity::new).fmap(id())), + sequence(traversable.fmap(t), Either::right)); + } + + @Test + public void identity() { + Either> traversable = right(new Identity<>(1)); + assertEquals(new Identity<>(traversable), + sequence(traversable.fmap(Identity::new), Identity::new)); + } + + @Test + public void composition() { + Either>> traversable = right(new Identity<>(right(1))); + assertEquals(new Compose<>(sequence(traversable, Identity::new).fmap(x -> sequence(x, Either::right)).fmap(id())), + sequence(traversable.fmap(x -> new Compose<>(x.fmap(id()))), x -> new Compose<>(new Identity<>(right(x))))); + } + + @Test + public void iterableSpecialization() { + assertThat(sequence(asList(right(1), right(2)), Either::right) + .orThrow(l -> new AssertionError("Expected a right value, but was a left value of <" + l + ">")), + iterates(1, 2)); + } + + @Test + public void mapSpecialization() { + assertEquals(right(singletonMap("foo", 1)), + sequence(singletonMap("foo", right(1)), Either::right)); + } + + @Test + public void compilation() { + Either> a = sequence(just(right(1)), Either::right); + assertEquals(right(just(1)), a); + + Maybe> b = sequence(right(just(1)), Maybe::just); + assertEquals(just(right(1)), b); + + Either> c = sequence(b, Either::right); + assertEquals(a, c); + + Maybe> d = sequence(asList(just(1), just(2)), Maybe::just); + assertThat(d.orElseThrow(AssertionError::new), iterates(1, 2)); + + Either> e = sequence(singletonMap("foo", right(1)), Either::right); + assertEquals(right(singletonMap("foo", 1)), e); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SlideTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SlideTest.java new file mode 100644 index 000000000..258e221b0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SlideTest.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Drop.drop; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Slide.slide; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class SlideTest { + + @TestTraits({ImmutableIteration.class, EmptyIterableSupport.class, FiniteIteration.class, InfiniteIterableSupport.class, Laziness.class}) + public Fn1, Iterable>> testSubject() { + return slide(2); + } + + @Test + public void slidesAcrossIterable() { + assertThat(slide(1, asList(1, 2, 3)), iterates(singletonList(1), singletonList(2), singletonList(3))); + assertThat(slide(2, asList(1, 2, 3)), iterates(asList(1, 2), asList(2, 3))); + assertThat(slide(3, asList(1, 2, 3)), iterates(asList(1, 2, 3))); + assertThat(slide(4, asList(1, 2, 3)), isEmpty()); + } + + @Test(expected = IllegalArgumentException.class) + public void kMustBeGreaterThan0() { + slide(0, emptyList()); + } + + @Test + public void stackSafety() { + int stackBlowingNumber = 50_000; + Iterable> xss = slide(2, take(stackBlowingNumber, iterate(x -> x + 1, 1))); + assertThat(drop(stackBlowingNumber - 2, xss), iterates(asList(49999, 50000))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SnocTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SnocTest.java new file mode 100644 index 000000000..439a60dfd --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SnocTest.java @@ -0,0 +1,44 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import java.util.Collections; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Snoc.snoc; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class SnocTest { + + @TestTraits({Laziness.class, EmptyIterableSupport.class, ImmutableIteration.class, FiniteIteration.class, InfiniteIterableSupport.class}) + public Fn1, Iterable> testSubject() { + return snoc(1); + } + + @Test + public void appendToEmptyIterable() { + assertThat(snoc(1, Collections::emptyIterator), iterates(1)); + } + + @Test + public void appendToNonEmptyIterable() { + assertThat(snoc(4, asList(1, 2, 3)), iterates(1, 2, 3, 4)); + } + + @Test + public void deforestingOrder() { + assertThat(snoc(3, snoc(2, snoc(1, emptyList()))), iterates(1, 2, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortByTest.java new file mode 100644 index 000000000..bae0d1526 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortByTest.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; + +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.SortBy.sortBy; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class SortByTest { + + @TestTraits({FiniteIteration.class, EmptyIterableSupport.class}) + public Fn1, List> testSubject() { + return sortBy(id()); + } + + @Test + public void sortsIterable() { + assertThat(sortBy(id(), asList(2, 1, 3)), iterates(1, 2, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortWithTest.java new file mode 100644 index 000000000..d1b64b603 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortWithTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import org.junit.Test; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.SortWith.sortWith; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class SortWithTest { + + @Test + public void sortsWithGivenComparator() { + assertEquals(asList(tuple("bar", 4), tuple("baz", 3), tuple("foo", 1), tuple("foo", 2)), + sortWith(Comparator., String>comparing(Tuple2::_1) + .thenComparing(Tuple2::_2), + asList(tuple("foo", 1), tuple("foo", 2), tuple("bar", 4), tuple("baz", 3)))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SpanTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SpanTest.java new file mode 100644 index 000000000..c29320506 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SpanTest.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.monoid.builtin.Concat; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Span.span; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class SpanTest { + + @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Laziness.class}) + public Fn1, Iterable> testSubject() { + return span(eq(0)).fmap(into(Concat::concat)); + } + + @Test + public void splitsIterableAfterPredicateFailure() { + Tuple2, Iterable> spanned = span(eq(1), asList(1, 1, 1, 2, 3, 1)); + assertThat(spanned._1(), iterates(1, 1, 1)); + assertThat(spanned._2(), iterates(2, 3, 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java index 871bd3a6e..5e34c3403 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java @@ -11,6 +11,9 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; +import java.util.ArrayList; +import java.util.List; + import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.TakeWhile.takeWhile; import static java.util.Arrays.asList; @@ -29,7 +32,7 @@ public Fn1, Iterable> createTestObject() { @Test public void takesElementsWhilePredicateIsTrue() { Predicate lessThan3 = integer -> integer < 3; - Iterable numbers = takeWhile(lessThan3, asList(1, 2, 3, 4, 5)); + Iterable numbers = takeWhile(lessThan3, asList(1, 2, 3, 4, 5)); assertThat(numbers, iterates(1, 2)); } @@ -46,4 +49,19 @@ public void takesAllElementsIfPredicateNeverFails() { public void takesNoElementsIfPredicateImmediatelyFails() { assertThat(takeWhile(constantly(false), asList(1, 2, 3)), isEmpty()); } + + @Test + public void deforestingExecutesPredicatesInOrder() { + List innerInvocations = new ArrayList<>(); + List outerInvocations = new ArrayList<>(); + takeWhile(y -> { + outerInvocations.add(y); + return true; + }, takeWhile(x -> { + innerInvocations.add(x); + return x < 3; + }, asList(1, 2, 3))).forEach(__ -> {}); + assertThat(innerInvocations, iterates(1, 2, 3)); + assertThat(outerInvocations, iterates(1, 2)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToArrayTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToArrayTest.java new file mode 100644 index 000000000..0c4b3001e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToArrayTest.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import java.util.AbstractCollection; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToArray.toArray; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyIterator; +import static org.junit.Assert.assertArrayEquals; + +public class ToArrayTest { + + @Test + public void writesIterableToArray() { + assertArrayEquals(new Integer[]{1, 2, 3}, toArray(Integer[].class, asList(1, 2, 3))); + + List variance = asList(1, 2, 3); + assertArrayEquals(new Object[]{1, 2, 3}, toArray(Object[].class, variance)); + } + + @Test + public void usesCollectionToArrayIfPossible() { + Object sentinel = new Object(); + class CustomCollection extends AbstractCollection { + @Override + public Iterator iterator() { + return emptyIterator(); + } + + @Override + public int size() { + return 0; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + T[] result = Arrays.copyOf(a, 1); + result[0] = (T) sentinel; + return result; + } + } + + assertArrayEquals(new Object[]{sentinel}, toArray(Object[].class, new CustomCollection())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollectionTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollectionTest.java index 584dd876d..6188c6765 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollectionTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollectionTest.java @@ -3,8 +3,6 @@ import org.junit.Test; import java.util.ArrayList; -import java.util.Collection; -import java.util.function.Supplier; import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; import static java.util.Arrays.asList; @@ -14,7 +12,6 @@ public class ToCollectionTest { @Test public void convertsIterablesToCollectionInstance() { - Supplier> listFactory = ArrayList::new; - assertEquals(asList(1, 2, 3), toCollection(listFactory, asList(1, 2, 3))); + assertEquals(asList(1, 2, 3), toCollection(ArrayList::new, asList(1, 2, 3))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMapTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMapTest.java index 21ab43c8d..2573212f6 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMapTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMapTest.java @@ -13,6 +13,7 @@ public class ToMapTest { @Test + @SuppressWarnings("serial") public void collectsEntriesIntoMap() { Map expected = new HashMap() {{ put("foo", 1); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UnfoldrTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UnfoldrTest.java index ed23697fa..4c876912b 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UnfoldrTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UnfoldrTest.java @@ -9,24 +9,31 @@ import testsupport.traits.InfiniteIteration; import testsupport.traits.Laziness; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; import static com.jnape.palatable.lambda.functions.builtin.fn2.Unfoldr.unfoldr; import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; import static testsupport.matchers.IterableMatcher.iterates; @RunWith(Traits.class) public class UnfoldrTest { @TestTraits({Laziness.class, InfiniteIteration.class, ImmutableIteration.class}) - public Fn1 createTestSubject() { - return unfoldr(x -> Optional.of(tuple(x, x))); + public Fn1, ? extends Iterable> createTestSubject() { + return unfoldr(x -> just(tuple(x, x))); } @Test public void iteratesIterableFromSeedValueAndSuccessiveFunctionApplications() { - assertThat(take(5, unfoldr(x -> Optional.of(tuple(x, x + 1)), 0)), iterates(0, 1, 2, 3, 4)); + assertThat(take(5, unfoldr(x -> just(tuple(x, x + 1)), 0)), iterates(0, 1, 2, 3, 4)); + } + + @Test + public void emptyIteration() { + assertThat(unfoldr(constantly(nothing()), 1), isEmpty()); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UntilTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UntilTest.java new file mode 100644 index 000000000..90c63dbee --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UntilTest.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Until.until; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class UntilTest { + + @Test + public void repeatedlyExecutesUntilPredicateMatches() { + AtomicInteger counter = new AtomicInteger(0); + assertThat(until(x -> x == 10, io(counter::getAndIncrement)), + yieldsValue(equalTo(10))); + } + + @Test + public void predicateThatImmediatelyMatchesDoesNotChangeIO() { + assertThat(until(constantly(true), io(0)), yieldsValue(equalTo(0))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ZipTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ZipTest.java index 69f1545d7..ae7aa9d38 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ZipTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ZipTest.java @@ -27,9 +27,8 @@ public Fn1, Iterable>> createTestSubject } @Test - @SuppressWarnings("unchecked") public void zipsTwoIterablesTogether() { - Iterable odds = asList(1, 3, 5); + Iterable odds = asList(1, 3, 5); Iterable evens = asList(2, 4, 6); Iterable> numbers = zip(odds, evens); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BetweenTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BetweenTest.java new file mode 100644 index 000000000..2638ee7f1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BetweenTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Between.between; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class BetweenTest { + + @Test + public void testsIfValueIsBetweenClosedBounds() { + assertFalse(between(1, 10, 0)); + assertTrue(between(1, 10, 1)); + assertTrue(between(1, 10, 5)); + assertTrue(between(1, 10, 10)); + assertFalse(between(1, 10, 11)); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BracketTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BracketTest.java new file mode 100644 index 000000000..9e2f373a1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BracketTest.java @@ -0,0 +1,68 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.io.IO; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Bracket.bracket; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.throwsException; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class BracketTest { + + private AtomicInteger count; + + @Before + public void setUp() { + count = new AtomicInteger(0); + } + + @Test + public void cleanupHappyPath() { + IO hashIO = bracket(io(() -> count), c -> io(c::incrementAndGet), c -> io(c::hashCode)); + + assertEquals(0, count.get()); + assertThat(hashIO, yieldsValue(equalTo(count.hashCode()))); + assertEquals(1, count.get()); + } + + @Test + public void cleanupSadPath() { + IllegalStateException thrown = new IllegalStateException("kaboom"); + IO hashIO = bracket(io(count), c -> io(c::incrementAndGet), c -> io(() -> {throw thrown;})); + + assertThat(hashIO, throwsException(equalTo(thrown))); + assertEquals(1, count.get()); + } + + @Test + public void cleanupOnlyRunsIfInitialIORuns() { + IllegalStateException thrown = new IllegalStateException("kaboom"); + IO hashIO = bracket(io(() -> {throw thrown;}), + constantly(io(count::incrementAndGet)), + constantly(io(count::incrementAndGet))); + + assertThat(hashIO, throwsException(equalTo(thrown))); + assertEquals(0, count.get()); + } + + @Test + public void errorsInCleanupAreAddedToBodyErrors() { + IllegalStateException bodyError = new IllegalStateException("kaboom"); + IllegalStateException cleanupError = new IllegalStateException("KABOOM"); + IO hashIO = bracket(io(count), + constantly(io(() -> {throw cleanupError;})), + constantly(io(() -> {throw bodyError;}))); + + assertThat(hashIO, throwsException(equalTo(bodyError))); + assertArrayEquals(new Throwable[]{cleanupError}, bodyError.getSuppressed()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/ClampTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/ClampTest.java new file mode 100644 index 000000000..9b2bedde4 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/ClampTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Clamp.clamp; +import static org.junit.Assert.assertEquals; + +public class ClampTest { + + @Test + public void clampsValueBetweenBounds() { + assertEquals((Integer) 5, clamp(1, 10, 5)); + assertEquals((Integer) 1, clamp(1, 10, -1)); + assertEquals((Integer) 10, clamp(1, 10, 11)); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqByTest.java new file mode 100644 index 000000000..4754ecf3e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqByTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CmpEqByTest { + + @Test + public void comparisons() { + assertTrue(cmpEqBy(id(), 1, 1)); + assertFalse(cmpEqBy(id(), 1, 2)); + assertFalse(cmpEqBy(id(), 2, 1)); + + assertTrue(cmpEqBy(String::length, "b", "a")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java new file mode 100644 index 000000000..8d062f8fd --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CmpEqWithTest { + @Test + public void comparisons() { + assertTrue(cmpEqBy(id(), 1, 1)); + assertFalse(cmpEqBy(id(), 1, 2)); + assertFalse(cmpEqBy(id(), 2, 1)); + + assertTrue(cmpEqBy(String::length, "b", "a")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java new file mode 100644 index 000000000..8817f94fc --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.equal; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.greaterThan; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.lessThan; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertEquals; + +public class CompareTest { + @Test + public void comparisons() { + assertEquals(equal(), compare(naturalOrder(), 1, 1)); + assertEquals(lessThan(), compare(naturalOrder(), 2, 1)); + assertEquals(greaterThan(), compare(naturalOrder(), 1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java index b40915da7..62f6d43c9 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java @@ -1,17 +1,22 @@ package com.jnape.palatable.lambda.functions.builtin.fn3; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.EmptyIterableSupport; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight.foldRight; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; import static testsupport.functions.ExplainFold.explainFold; @RunWith(Traits.class) @@ -19,14 +24,25 @@ public class FoldRightTest { @TestTraits({EmptyIterableSupport.class}) public Fn1, Iterable> createTestSubject() { - return foldRight((o, objects) -> objects, singletonList(new Object())); + return foldRight((o, objects) -> objects, lazy(singletonList(new Object()))).fmap(Lazy::value); } @Test public void foldRightAccumulatesRightToLeft() { - assertThat( - foldRight(explainFold(), "5", asList("1", "2", "3", "4")), - is("(1 + (2 + (3 + (4 + 5))))") + assertThat(foldRight((a, lazyAcc) -> lazyAcc.fmap(acc -> explainFold().apply(a, acc)), + lazy("5"), + asList("1", "2", "3", "4")) + .value(), + is("(1 + (2 + (3 + (4 + 5))))") ); } + + @Test + public void stackSafe() { + Lazy lazy = foldRight((x, lazyY) -> x < STACK_EXPLODING_NUMBER ? lazyY.fmap(y -> y) : lazy(x), + lazy(0), + iterate(x -> x + 1, 0)); + + assertEquals(STACK_EXPLODING_NUMBER, lazy.value()); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTByTest.java new file mode 100644 index 000000000..a04ffb0b6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTByTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTBy.gtBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTByTest { + + @Test + public void comparisons() { + assertTrue(gtBy(id(), 1, 2)); + assertFalse(gtBy(id(), 1, 1)); + assertFalse(gtBy(id(), 2, 1)); + + assertTrue(gtBy(String::length, "bb", "aaa")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEByTest.java new file mode 100644 index 000000000..438d0cb84 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEByTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEBy.gteBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTEByTest { + + @Test + public void comparisons() { + assertTrue(gteBy(id(), 1, 2)); + assertTrue(gteBy(id(), 1, 1)); + assertFalse(gteBy(id(), 2, 1)); + + assertTrue(gteBy(String::length, "b", "ab")); + assertTrue(gteBy(String::length, "bc", "ab")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java new file mode 100644 index 000000000..4ccaac4ec --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEWith.gteWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTEWithTest { + @Test + public void comparisons() { + assertTrue(gteWith(naturalOrder(), 1, 2)); + assertTrue(gteWith(naturalOrder(), 1, 1)); + assertFalse(gteWith(naturalOrder(), 2, 1)); + + assertTrue(gteWith(comparing(String::length), "b", "ab")); + assertTrue(gteWith(comparing(String::length), "bc", "ab")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java new file mode 100644 index 000000000..4520ee272 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertFalse; + +public class GTWithTest { + @Test + public void comparisons() { + assertTrue(gtWith(naturalOrder(), 1, 2)); + assertFalse(gtWith(naturalOrder(), 1, 1)); + assertFalse(gtWith(naturalOrder(), 2, 1)); + + assertTrue(gtWith(comparing(String::length), "bb", "aaa")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTByTest.java new file mode 100644 index 000000000..dc511d7cf --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTByTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTBy.ltBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTByTest { + + @Test + public void comparisons() { + assertTrue(ltBy(id(), 2, 1)); + assertFalse(ltBy(id(), 1, 1)); + assertFalse(ltBy(id(), 1, 2)); + + assertTrue(ltBy(String::length, "ab", "b")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEByTest.java new file mode 100644 index 000000000..122c76070 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEByTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEBy.lteBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTEByTest { + + @Test + public void comparisons() { + assertTrue(lteBy(id(), 2, 1)); + assertTrue(lteBy(id(), 1, 1)); + assertFalse(lteBy(id(), 1, 2)); + + assertTrue(lteBy(String::length, "ab", "b")); + assertTrue(lteBy(String::length, "ab", "bc")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java new file mode 100644 index 000000000..18f749c16 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEWith.lteWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTEWithTest { + @Test + public void comparisons() { + assertTrue(lteWith(naturalOrder(), 2, 1)); + assertTrue(lteWith(naturalOrder(), 1, 1)); + assertFalse(lteWith(naturalOrder(), 1, 2)); + + assertTrue(lteWith(comparing(String::length), "ab", "b")); + assertTrue(lteWith(comparing(String::length), "ab", "bc")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java new file mode 100644 index 000000000..dacdfd2ff --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTWithTest { + @Test + public void comparisons() { + assertTrue(ltWith(naturalOrder(), 2, 1)); + assertFalse(ltWith(naturalOrder(), 1, 1)); + assertFalse(ltWith(naturalOrder(), 1, 2)); + + assertTrue(ltWith(comparing(String::length), "ab", "b")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java new file mode 100644 index 000000000..f775e60ec --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java @@ -0,0 +1,45 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn2; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2.liftA2; +import static org.junit.Assert.assertEquals; + +public class LiftA2Test { + + @Test + public void inference() { + Fn2 add = Integer::sum; + + Maybe a = liftA2(add, just(1), just(2)); + assertEquals(just(3), a); + + Maybe b = liftA2(add, just(1), nothing()); + assertEquals(nothing(), b); + + Maybe c = liftA2(add, nothing(), just(2)); + assertEquals(nothing(), c); + + Maybe d = liftA2(add, nothing(), nothing()); + assertEquals(nothing(), d); + + Either e = liftA2(add, Either.right(1), right(2)); + assertEquals(right(3), e); + + Either f = liftA2(add, left("error"), right(2)); + assertEquals(left("error"), f); + + Either g = liftA2(add, right(1), left("error")); + assertEquals(left("error"), g); + + Either h = liftA2(add, left("error"), left("another error")); + assertEquals(left("error"), h); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/TimesTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/TimesTest.java new file mode 100644 index 000000000..a87236fc3 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/TimesTest.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; +import static org.junit.Assert.assertEquals; + +public class TimesTest { + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void accumulatesFunctionNTimes() { + Fn1 inc = x -> x + 1; + assertEquals((Integer) 3, times(3, inc).apply(0)); + } + + @Test + public void zeroIsMeansReturnTheInputBack() { + Fn1 inc = x -> x + 1; + assertEquals((Integer) 0, times(0, inc).apply(0)); + } + + @Test + public void lessThanZeroIsIllegal() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("n must not be less than 0"); + + times(-1, id(), 1); + } + + @Test + public void stackSafety() { + int stackBlowingNumber = 50000; + assertEquals((Integer) stackBlowingNumber, times(stackBlowingNumber, x -> x + 1, 0)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWithTest.java index ee1408417..034fe1b9a 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWithTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWithTest.java @@ -10,8 +10,6 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; -import java.util.function.BiFunction; - import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn2.Zip.zip; import static com.jnape.palatable.lambda.functions.builtin.fn3.ZipWith.zipWith; @@ -30,18 +28,16 @@ public Fn1, Iterable> createTestSubject() { @Test public void zipsTwoIterablesTogetherWithFunction() { Iterable oneThroughFive = asList(1, 2, 3, 4, 5); - Iterable sixThroughTen = asList(6, 7, 8, 9, 10); + Iterable sixThroughTen = asList(6, 7, 8, 9, 10); - BiFunction add = (a, b) -> a + b; - Iterable sums = ZipWith.zipWith(add, oneThroughFive, sixThroughTen); + Iterable sums = zipWith(Integer::sum, oneThroughFive, sixThroughTen); assertThat(sums, iterates(7, 9, 11, 13, 15)); } @Test - @SuppressWarnings("unchecked") public void zipsAsymmetricallySizedIterables() { - Iterable men = asList("Jack", "Sonny"); + Iterable men = asList("Jack", "Sonny"); Iterable women = asList("Jill", "Cher", "Madonna"); Iterable> couples = zip(men, women); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElseTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElseTest.java new file mode 100644 index 000000000..f1e0895c6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElseTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.Predicate; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn4.IfThenElse.ifThenElse; +import static org.junit.Assert.assertEquals; + +public class IfThenElseTest { + + @Test + public void standardLogic() { + Predicate even = x -> x % 2 == 0; + Fn1 inc = x -> x + 1; + Fn1 dec = x -> x - 1; + + assertEquals((Integer) 3, ifThenElse(even, inc, dec, 2)); + assertEquals((Integer) 0, ifThenElse(even, inc, dec, 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3Test.java new file mode 100644 index 000000000..e6f65f814 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn4.LiftA3.liftA3; +import static org.junit.Assert.assertEquals; + +public class LiftA3Test { + + @Test + public void lifting() { + assertEquals(just(6), liftA3((a, b, c) -> a + b + c, just(1), just(2), just(3))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimitTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimitTest.java new file mode 100644 index 000000000..b91b92b08 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimitTest.java @@ -0,0 +1,77 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.iteration.IterationInterruptedException; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.time.InstantRecordingClock; +import testsupport.traits.Deforesting; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; + +import static com.jnape.palatable.lambda.adt.Try.trying; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn4.RateLimit.rateLimit; +import static com.jnape.palatable.lambda.functions.specialized.SideEffect.sideEffect; +import static java.time.Clock.systemUTC; +import static java.time.Duration.ZERO; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; +import static testsupport.matchers.RateLimitedIterationMatcher.iteratesAccordingToRateLimit; + +@RunWith(Traits.class) +public class RateLimitTest { + + private InstantRecordingClock clock; + + @Before + public void setUp() throws Exception { + clock = new InstantRecordingClock(systemUTC()); + } + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, Deforesting.class}) + public Fn1, Iterable> testSubject() { + return rateLimit(systemUTC()::instant, 1L, ZERO); + } + + @Test(expected = IllegalArgumentException.class) + public void lessThanOneLimitIsInvalid() { + rateLimit(clock::instant, 0L, ZERO, emptyList()); + } + + @Test + public void zeroDurationJustIteratesElements() { + assertThat(rateLimit(clock::instant, 1L, ZERO, asList(1, 2, 3)), iterates(1, 2, 3)); + } + + @Test + public void limitPerDurationIsHonoredAccordingToClock() { + Duration duration = Duration.ofMillis(10); + long limit = 2L; + assertThat(rateLimit(clock::instant, limit, duration, asList(1, 2, 3, 4)), + iteratesAccordingToRateLimit(limit, duration, asList(1, 2, 3, 4), clock)); + } + + @Test(timeout = 100, expected = IterationInterruptedException.class) + public void rateLimitingDelayIsInterruptible() throws InterruptedException { + Thread testThread = Thread.currentThread(); + CountDownLatch latch = new CountDownLatch(1); + new Thread(() -> { + trying(sideEffect(latch::await)).orThrow(); + testThread.interrupt(); + }) {{ + start(); + }}; + + rateLimit(clock::instant, 1L, Duration.ofSeconds(1), repeat(1)).forEach(xs -> latch.countDown()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4Test.java new file mode 100644 index 000000000..784e04fb6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn5; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn5.LiftA4.liftA4; +import static org.junit.Assert.assertEquals; + +public class LiftA4Test { + + @Test + public void lifting() { + assertEquals(just(10), liftA4((a, b, c, d) -> a + b + c + d, just(1), just(2), just(3), just(4))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5Test.java new file mode 100644 index 000000000..bd131e897 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5Test.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.functions.builtin.fn6; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn6.LiftA5.liftA5; +import static org.junit.Assert.assertEquals; + +public class LiftA5Test { + + @Test + public void lifting() { + assertEquals(just(15), + liftA5((a, b, c, d, e) -> a + b + c + d + e, just(1), just(2), just(3), just(4), just(5))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6Test.java new file mode 100644 index 000000000..76ed29162 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6Test.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn7; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn7.LiftA6.liftA6; +import static org.junit.Assert.assertEquals; + +public class LiftA6Test { + + @Test + public void lifting() { + assertEquals(just(21), + liftA6((a, b, c, d, e, f) -> a + b + c + d + e + f, + just(1), just(2), just(3), just(4), just(5), just(6))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7Test.java new file mode 100644 index 000000000..f81bd6ef0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7Test.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.functions.builtin.fn8; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn8.LiftA7.liftA7; +import static org.junit.Assert.assertEquals; + +public class LiftA7Test { + + @Test + public void lifting() { + assertEquals(just(28), liftA7((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g, + just(1), just(2), just(3), just(4), just(5), just(6), just(7))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java b/src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java new file mode 100644 index 000000000..8b0083661 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.functions.ordering; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.equal; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.greaterThan; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.lessThan; +import static java.lang.Integer.MAX_VALUE; +import static java.lang.Integer.MIN_VALUE; +import static org.junit.Assert.assertEquals; + +public class ComparisonRelationTest { + @Test + public void fromInt() { + assertEquals(greaterThan(), ComparisonRelation.fromInt(1)); + assertEquals(greaterThan(), ComparisonRelation.fromInt(MAX_VALUE)); + + assertEquals(equal(), ComparisonRelation.fromInt(0)); + + assertEquals(lessThan(), ComparisonRelation.fromInt(-1)); + assertEquals(lessThan(), ComparisonRelation.fromInt(MIN_VALUE)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java b/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java new file mode 100644 index 000000000..d8a156a9a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.functions.recursion; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; + +@RunWith(Traits.class) +public class RecursiveResultTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) + public Subjects> testSubject() { + return subjects(recurse("foo"), terminate(1)); + } + + @Test + public void staticPure() { + RecursiveResult recursiveResult = RecursiveResult.pureRecursiveResult().apply(1); + assertEquals(terminate(1), recursiveResult); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/recursion/TrampolineTest.java b/src/test/java/com/jnape/palatable/lambda/functions/recursion/TrampolineTest.java new file mode 100644 index 000000000..5104aa824 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/recursion/TrampolineTest.java @@ -0,0 +1,33 @@ +package com.jnape.palatable.lambda.functions.recursion; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import org.junit.Test; + +import java.math.BigInteger; +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static java.math.BigInteger.ONE; +import static org.junit.Assert.assertEquals; + +public class TrampolineTest { + + private static final + Fn1, RecursiveResult, BigInteger>> FACTORIAL = + into((x, acc) -> x.compareTo(ONE) > 0 ? recurse(tuple(x.subtract(ONE), x.multiply(acc))) : terminate(acc)); + + @Test + public void trampolinesCompatibleFunctionIntoResult() { + assertEquals(BigInteger.valueOf(3628800), trampoline(FACTORIAL, tuple(BigInteger.valueOf(10), ONE))); + } + + @Test + public void stackSafety() { + trampoline(FACTORIAL, tuple(BigInteger.valueOf(10000), ONE)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/specialized/BiPredicateTest.java b/src/test/java/com/jnape/palatable/lambda/functions/specialized/BiPredicateTest.java index e8dd3c690..9f472f37c 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/specialized/BiPredicateTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/specialized/BiPredicateTest.java @@ -2,7 +2,9 @@ import org.junit.Test; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; public class BiPredicateTest { @@ -11,43 +13,63 @@ public class BiPredicateTest { public void jufBiPredicateTest() { BiPredicate equals = String::equals; - assertTrue(equals.test("abc", "abc")); - assertFalse(equals.test("abc", "")); - assertFalse(equals.test("", "abc")); + assertTrue(equals.apply("abc", "abc")); + assertFalse(equals.apply("abc", "")); + assertFalse(equals.apply("", "abc")); } @Test public void jufBiPredicateAnd() { - BiPredicate bothOdd = (x, y) -> x % 2 == 1 && y % 2 == 1; + BiPredicate bothOdd = (x, y) -> x % 2 == 1 && y % 2 == 1; BiPredicate greaterThan = (x, y) -> x.compareTo(y) > 0; BiPredicate conjunction = bothOdd.and(greaterThan); - assertTrue(conjunction.test(3, 1)); - assertFalse(conjunction.test(3, 2)); - assertFalse(conjunction.test(3, 5)); - assertFalse(conjunction.test(4, 1)); + assertTrue(conjunction.apply(3, 1)); + assertFalse(conjunction.apply(3, 2)); + assertFalse(conjunction.apply(3, 5)); + assertFalse(conjunction.apply(4, 1)); } @Test public void jufBiPredicateOr() { - BiPredicate bothOdd = (x, y) -> x % 2 == 1 && y % 2 == 1; + BiPredicate bothOdd = (x, y) -> x % 2 == 1 && y % 2 == 1; BiPredicate greaterThan = (x, y) -> x.compareTo(y) > 0; BiPredicate disjunction = bothOdd.or(greaterThan); - assertTrue(disjunction.test(3, 2)); - assertTrue(disjunction.test(1, 3)); - assertFalse(disjunction.test(1, 2)); + assertTrue(disjunction.apply(3, 2)); + assertTrue(disjunction.apply(1, 3)); + assertFalse(disjunction.apply(1, 2)); } @Test public void jufBiPredicateNegate() { BiPredicate equals = String::equals; - assertTrue(equals.test("a", "a")); - assertFalse(equals.test("b", "a")); - assertFalse(equals.negate().test("a", "a")); - assertTrue(equals.negate().test("b", "a")); + assertTrue(equals.apply("a", "a")); + assertFalse(equals.apply("b", "a")); + assertFalse(equals.negate().apply("a", "a")); + assertTrue(equals.negate().apply("b", "a")); + } + + @Test + public void flip() { + BiPredicate equals = String::equals; + assertThat(equals.flip(), instanceOf(BiPredicate.class)); + } + + @Test + public void fromPredicate() { + java.util.function.BiPredicate jufBiPredicate = Object::equals; + BiPredicate biPredicate = BiPredicate.fromBiPredicate(jufBiPredicate); + assertTrue(biPredicate.apply("a", "a")); + } + + @Test + public void toPredicate() { + BiPredicate biPredicate = Object::equals; + java.util.function.BiPredicate jufBiPredicate = biPredicate.toBiPredicate(); + assertTrue(jufBiPredicate.test("a", "a")); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/specialized/KleisliTest.java b/src/test/java/com/jnape/palatable/lambda/functions/specialized/KleisliTest.java new file mode 100644 index 000000000..8be6844fe --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/specialized/KleisliTest.java @@ -0,0 +1,26 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.functor.builtin.Identity; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.specialized.Kleisli.kleisli; +import static java.lang.Integer.parseInt; +import static org.junit.Assert.assertEquals; + +public class KleisliTest { + + private static final Kleisli, Identity> G = kleisli(i -> new Identity<>(i.toString())); + private static final Kleisli, Identity> F = kleisli(s -> new Identity<>(parseInt(s))); + + @Test + public void leftToRightComposition() { + assertEquals(new Identity<>(1), G.andThen(F).apply(1)); + assertEquals(new Identity<>("1"), F.andThen(G).apply("1")); + } + + @Test + public void rightToLeftComposition() { + assertEquals(new Identity<>("1"), G.compose(F).apply("1")); + assertEquals(new Identity<>(1), F.compose(G).apply(1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/specialized/PredicateTest.java b/src/test/java/com/jnape/palatable/lambda/functions/specialized/PredicateTest.java index b7c8be809..67ec6fe6a 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/specialized/PredicateTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/specialized/PredicateTest.java @@ -8,47 +8,61 @@ public class PredicateTest { @Test - public void jufPredicateTest() { + public void happyPath() { Predicate notEmpty = s -> !(s == null || s.length() == 0); - assertTrue(notEmpty.test("foo")); - assertFalse(notEmpty.test("")); - assertFalse(notEmpty.test(null)); + assertTrue(notEmpty.apply("foo")); + assertFalse(notEmpty.apply("")); + assertFalse(notEmpty.apply(null)); } @Test - public void jufPredicateAnd() { - Predicate notEmpty = s -> !(s == null || s.length() == 0); + public void and() { + Predicate notEmpty = s -> !(s == null || s.length() == 0); Predicate lengthGt1 = s -> s.length() > 1; Predicate conjunction = notEmpty.and(lengthGt1); - assertTrue(conjunction.test("fo")); - assertFalse(conjunction.test("f")); - assertFalse(conjunction.test("")); - assertFalse(conjunction.test(null)); + assertTrue(conjunction.apply("fo")); + assertFalse(conjunction.apply("f")); + assertFalse(conjunction.apply("")); + assertFalse(conjunction.apply(null)); } @Test - public void jufPredicateOr() { - Predicate notEmpty = s -> !(s == null || s.length() == 0); + public void or() { + Predicate notEmpty = s -> !(s == null || s.length() == 0); Predicate lengthGt1 = s -> s != null && s.length() > 1; Predicate disjunction = lengthGt1.or(notEmpty); - assertTrue(disjunction.test("fo")); - assertTrue(disjunction.test("f")); - assertFalse(disjunction.test("")); - assertFalse(disjunction.test(null)); + assertTrue(disjunction.apply("fo")); + assertTrue(disjunction.apply("f")); + assertFalse(disjunction.apply("")); + assertFalse(disjunction.apply(null)); } @Test - public void jufPredicateNegate() { + public void negate() { Predicate isTrue = x -> x; - assertTrue(isTrue.test(true)); - assertFalse(isTrue.test(false)); - assertFalse(isTrue.negate().test(true)); - assertTrue(isTrue.negate().test(false)); + assertTrue(isTrue.apply(true)); + assertFalse(isTrue.apply(false)); + assertFalse(isTrue.negate().apply(true)); + assertTrue(isTrue.negate().apply(false)); + } + + @Test + public void fromPredicate() { + java.util.function.Predicate jufPredicate = String::isEmpty; + Predicate predicate = Predicate.fromPredicate(jufPredicate); + assertFalse(predicate.apply("123")); + } + + @Test + public void toPredicate() { + Predicate predicate = String::isEmpty; + java.util.function.Predicate jufPredicate = predicate.toPredicate(); + assertFalse(jufPredicate.test("123")); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/specialized/PureTest.java b/src/test/java/com/jnape/palatable/lambda/functions/specialized/PureTest.java new file mode 100644 index 000000000..cda6be607 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/specialized/PureTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.adt.Maybe; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static org.junit.Assert.assertEquals; + +public class PureTest { + + @Test + @SuppressWarnings("RedundantTypeArguments") + public void inference() { + assertEquals(just(1), Pure.>pure(Maybe::just).>apply(1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/specialized/SideEffectTest.java b/src/test/java/com/jnape/palatable/lambda/functions/specialized/SideEffectTest.java new file mode 100644 index 000000000..64eecfd3b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/specialized/SideEffectTest.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; + +public class SideEffectTest { + + @Test + public void fromRunnable() throws Throwable { + AtomicInteger counter = new AtomicInteger(0); + Runnable runnable = counter::incrementAndGet; + SideEffect sideEffect = SideEffect.fromRunnable(runnable); + sideEffect.Ω(); + assertEquals(1, counter.get()); + } + + @Test + public void toRunnable() { + AtomicInteger counter = new AtomicInteger(0); + SideEffect sideEffect = counter::incrementAndGet; + Runnable runnable = sideEffect.toRunnable(); + runnable.run(); + assertEquals(1, counter.get()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/BifunctorTest.java b/src/test/java/com/jnape/palatable/lambda/functor/BifunctorTest.java index afc3c0a8e..88e027a8a 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/BifunctorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/BifunctorTest.java @@ -1,10 +1,10 @@ package com.jnape.palatable.lambda.functor; +import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Test; import testsupport.applicatives.InvocationRecordingBifunctor; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static org.hamcrest.CoreMatchers.is; @@ -14,16 +14,18 @@ public class BifunctorTest { @Test public void biMapLUsesIdentityForRightBiMapFunction() { - AtomicReference rightInvocation = new AtomicReference<>(); - Bifunctor bifunctor = new InvocationRecordingBifunctor<>(new AtomicReference<>(), rightInvocation); + AtomicReference> rightInvocation = new AtomicReference<>(); + Bifunctor> bifunctor = + new InvocationRecordingBifunctor<>(new AtomicReference<>(), rightInvocation); bifunctor.biMapL(String::toUpperCase); assertThat(rightInvocation.get(), is(id())); } @Test public void biMapRUsesIdentityForLeftBiMapFunction() { - AtomicReference leftInvocation = new AtomicReference<>(); - Bifunctor bifunctor = new InvocationRecordingBifunctor<>(leftInvocation, new AtomicReference<>()); + AtomicReference> leftInvocation = new AtomicReference<>(); + Bifunctor> bifunctor = + new InvocationRecordingBifunctor<>(leftInvocation, new AtomicReference<>()); bifunctor.biMapR(String::valueOf); assertThat(leftInvocation.get(), is(id())); } diff --git a/src/test/java/com/jnape/palatable/lambda/functor/ProfunctorTest.java b/src/test/java/com/jnape/palatable/lambda/functor/ProfunctorTest.java index 5e552f7e0..6d1a34912 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/ProfunctorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/ProfunctorTest.java @@ -1,10 +1,10 @@ package com.jnape.palatable.lambda.functor; +import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Test; import testsupport.applicatives.InvocationRecordingProfunctor; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static org.hamcrest.CoreMatchers.is; @@ -14,16 +14,18 @@ public class ProfunctorTest { @Test public void diMapLUsesIdentityForRightDiMapFunction() { - AtomicReference rightInvocation = new AtomicReference<>(); - Profunctor profunctor = new InvocationRecordingProfunctor<>(new AtomicReference<>(), rightInvocation); + AtomicReference> rightInvocation = new AtomicReference<>(); + Profunctor> profunctor = + new InvocationRecordingProfunctor<>(new AtomicReference<>(), rightInvocation); profunctor.diMapL(Object::toString); assertThat(rightInvocation.get(), is(id())); } @Test public void diMapRUsesIdentityForLeftDiMapFunction() { - AtomicReference leftInvocation = new AtomicReference<>(); - Profunctor profunctor = new InvocationRecordingProfunctor<>(leftInvocation, new AtomicReference<>()); + AtomicReference> leftInvocation = new AtomicReference<>(); + Profunctor> profunctor = + new InvocationRecordingProfunctor<>(leftInvocation, new AtomicReference<>()); profunctor.diMapR(String::valueOf); assertThat(leftInvocation.get(), is(id())); } diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java new file mode 100644 index 000000000..3fea2229f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.pureMaybe; +import static com.jnape.palatable.lambda.functor.builtin.Compose.pureCompose; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static org.junit.Assert.assertEquals; + +@RunWith(Traits.class) +public class ComposeTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + public Compose, Identity, Integer> testSubject() { + return new Compose<>(new Identity<>(new Identity<>(1))); + } + + @Test + public void inference() { + Either> a = new Compose<>(right(just(1))).fmap(x -> x + 1).getCompose(); + assertEquals(right(just(2)), a); + } + + @Test + public void lazyZip() { + assertEquals(new Compose<>(right(just(2))), + new Compose<>(right(just(1))).lazyZip(lazy(new Compose<>(right(just(x -> x + 1))))).value()); + assertEquals(new Compose<>(left("foo")), + new Compose<>(left("foo")).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + Compose, Maybe, Integer> compose = pureCompose(pureIdentity(), pureMaybe()).apply(1); + assertEquals(new Compose<>(new Identity<>(just(1))), compose); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java index 5d8a06abc..fab19faa6 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java @@ -1,18 +1,36 @@ package com.jnape.palatable.lambda.functor.builtin; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.functor.builtin.Const.pureConst; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class ConstTest { - @Test - public void functorProperties() { - assertEquals("foo", new Const("foo").fmap(x -> x + 1).runConst()); + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Const testSubject() { + return new Const<>(1); } @Test - public void bifunctorProperties() { - assertEquals("FOO", new Const("foo").biMap(String::toUpperCase, x -> x + 1).runConst()); + public void staticPure() { + Const constInt = pureConst(1).apply("foo"); + assertEquals(new Const<>(1), constInt); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java index 792980e99..80e48fc15 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java @@ -1,13 +1,29 @@ package com.jnape.palatable.lambda.functor.builtin; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class IdentityTest { + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) + public Identity testSubject() { + return new Identity<>(""); + } + @Test - public void functorProperties() { - assertEquals("FOO", new Identity<>("foo").fmap(String::toUpperCase).runIdentity()); + public void staticPure() { + Identity identity = pureIdentity().apply(1); + assertEquals(new Identity<>(1), identity); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/LazyTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/LazyTest.java new file mode 100644 index 000000000..45785399b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/LazyTest.java @@ -0,0 +1,75 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.pureLazy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class LazyTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Equivalence> testSubject() { + return equivalence(lazy(0), Lazy::value); + } + + @Test + public void valueExtraction() { + assertEquals("foo", lazy("foo").value()); + assertEquals("foo", lazy(() -> "foo").value()); + } + + @Test + public void lazyEvaluation() { + AtomicBoolean invoked = new AtomicBoolean(false); + Lazy lazy = lazy(0).flatMap(x -> { + invoked.set(true); + return lazy(x + 1); + }); + assertFalse(invoked.get()); + assertEquals((Integer) 1, lazy.value()); + assertTrue(invoked.get()); + } + + @Test + public void linearStackSafety() { + assertEquals(STACK_EXPLODING_NUMBER, + times(STACK_EXPLODING_NUMBER, f -> f.fmap(x -> x + 1), lazy(0)).value()); + } + + @Test + public void recursiveStackSafety() { + assertEquals(STACK_EXPLODING_NUMBER, + new Fn1, Lazy>() { + @Override + public Lazy checkedApply(Lazy lazyX) { + return lazyX.flatMap(x -> x < STACK_EXPLODING_NUMBER + ? apply(lazy(x + 1)) + : lazy(x)); + } + }.apply(lazy(0)).value()); + } + + @Test + public void staticPure() { + Lazy lazy = pureLazy().apply(1); + assertEquals(lazy(1), lazy); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/MarketTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/MarketTest.java new file mode 100644 index 000000000..9814d5ee0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/MarketTest.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.trying; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.lang.Integer.parseInt; +import static org.junit.Assert.assertEquals; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class MarketTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Subjects>> testSubject() { + Market market = new Market<>(id(), str -> trying(() -> parseInt(str), + constantly(str))); + return subjects(equivalence(market, m -> both(m.bt(), m.sta(), "123")), + equivalence(market, m -> both(m.bt(), m.sta(), "foo"))); + } + + @Test + public void staticPure() { + Market market = Market.pureMarket().apply(1); + assertEquals((Integer) 1, market.bt().apply('a')); + assertEquals(left(1), market.sta().apply("foo")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/StateTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/StateTest.java new file mode 100644 index 000000000..7e86042d2 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/StateTest.java @@ -0,0 +1,101 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadReaderLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.MonadWriterLaws; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.StateMatcher.whenEvaluated; +import static testsupport.matchers.StateMatcher.whenExecuted; +import static testsupport.matchers.StateMatcher.whenRun; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class StateTest { + + @TestTraits({FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + MonadReaderLaws.class, + MonadWriterLaws.class}) + public Equivalence> testSubject() { + return equivalence(State.gets(String::length), s -> s.run("foo")); + } + + @Test + public void eval() { + assertThat(State.gets(id()), whenEvaluated(1, 1)); + } + + @Test + public void exec() { + assertThat(State.modify(x -> x + 1), whenExecuted(1, 2)); + } + + @Test + public void get() { + assertThat(State.get(), whenRun(1, 1, 1)); + } + + @Test + public void put() { + assertThat(State.put(1), whenRun(1, UNIT, 1)); + } + + @Test + public void gets() { + assertThat(State.gets(Integer::parseInt), whenRun("0", 0, "0")); + } + + @Test + public void modify() { + assertThat(State.modify(x -> x + 1), whenRun(0, UNIT, 1)); + } + + @Test + public void state() { + assertThat(State.state(1), whenRun(UNIT, 1, UNIT)); + assertThat(State.state(x -> tuple(x + 1, x - 1)), whenRun(0, 1, -1)); + } + + @Test + public void stateAccumulation() { + assertThat(State.get().flatMap(i -> State.put(i + 1).discardL(State.state(i))), + whenRun(0, 0, 1)); + } + + @Test + public void zipOrdering() { + assertThat(State.state(s -> tuple(0, s + "1")) + .zip(State.state(s -> tuple(x -> x + 1, s + "2"))), + whenRun("_", 1, "_12")); + } + + @Test + public void withState() { + assertThat(State.get().withState(x -> x + 1), whenRun(0, 1, 1)); + } + + @Test + public void mapState() { + assertThat(State.get().mapState(into((a, s) -> tuple(a + 1, s + 2))), whenRun(0, 1, 2)); + } + + @Test + public void staticPure() { + assertThat(State.pureState().apply(1), whenRun("foo", 1, "foo")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/TaggedTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/TaggedTest.java new file mode 100644 index 000000000..44b38acbd --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/TaggedTest.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.TraversableLaws; + +import static org.junit.Assert.assertEquals; +import testsupport.traits.MonadRecLaws; + +@RunWith(Traits.class) +public class TaggedTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) + public Tagged testSubject() { + return new Tagged<>(1); + } + + @Test + public void staticPure() { + Tagged tagged = Tagged.pureTagged().apply(1); + assertEquals(new Tagged<>(1), tagged); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/WriterTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/WriterTest.java new file mode 100644 index 000000000..62c29d33e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/WriterTest.java @@ -0,0 +1,44 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.MonadWriterLaws; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functor.builtin.Writer.listen; +import static com.jnape.palatable.lambda.functor.builtin.Writer.tell; +import static com.jnape.palatable.lambda.functor.builtin.Writer.writer; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class WriterTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class, MonadWriterLaws.class}) + public Subjects>> testSubject() { + Fn1, Object> runWriter = w -> w.runWriter(join()); + return subjects(equivalence(tell("foo"), runWriter), + equivalence(listen(1), runWriter), + equivalence(writer(tuple(1, "foo")), runWriter)); + } + + @Test + public void tellListenInteraction() { + assertEquals(tuple(1, "hello, world!"), + tell("hello, ") + .discardL(listen(1)) + .discardR(tell("world!")) + .runWriter(join())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/CombinatorialIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIteratorTest.java similarity index 92% rename from src/test/java/com/jnape/palatable/lambda/iterators/CombinatorialIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIteratorTest.java index ecfa8faab..9e52df9b3 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/CombinatorialIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIteratorTest.java @@ -1,10 +1,10 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.util.Iterator; @@ -46,7 +46,6 @@ public void doesNotHaveNextIfMoreAsButNoBs() { @Test public void doesNotHaveNextIfNoAsButMoreBs() { when(as.hasNext()).thenReturn(false); - when(bs.hasNext()).thenReturn(true); assertThat(combinatorialIterator.hasNext(), is(false)); } diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterableTest.java new file mode 100644 index 000000000..92ffd28b9 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterableTest.java @@ -0,0 +1,24 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Collections.emptyList; + +@RunWith(Traits.class) +public class ConcatenatingIterableTest { + + @TestTraits({Deforesting.class}) + public Subjects, Iterable>> testSubject() { + return subjects(xs -> new ConcatenatingIterable<>(emptyList(), xs), + xs -> new ConcatenatingIterable<>(xs, emptyList()), + xs -> new ConcatenatingIterable<>(repeat(1), xs), + xs -> new ConcatenatingIterable<>(xs, repeat(1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ConsingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ConsingIteratorTest.java similarity index 78% rename from src/test/java/com/jnape/palatable/lambda/iterators/ConsingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ConsingIteratorTest.java index 467a4b138..85f6c6ba0 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ConsingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ConsingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Before; import org.junit.Test; @@ -9,6 +9,7 @@ import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight.foldRight; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -50,10 +51,11 @@ public void doesNotHaveNextIfNoElementsLeft() { @Test public void stackSafety() { - Integer stackBlowingNumber = 1000000; - Iterable ints = foldRight((x, acc) -> () -> new ConsingIterator<>(x, acc), - (Iterable) Collections.emptyList(), - take(stackBlowingNumber, iterate(x -> x + 1, 1))); + Integer stackBlowingNumber = 10_000; + Iterable ints = foldRight((x, lazyAcc) -> lazyAcc.fmap(acc -> () -> new ConsingIterator<>(x, acc)), + lazy((Iterable) Collections.emptyList()), + take(stackBlowingNumber, iterate(x -> x + 1, 1))) + .value(); assertEquals(stackBlowingNumber, take(1, drop(stackBlowingNumber - 1, ints)).iterator().next()); diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterableTest.java new file mode 100644 index 000000000..a4ce12dce --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterableTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +@RunWith(Traits.class) +public class CyclicIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return CyclicIterable::new; + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/CyclicIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIteratorTest.java similarity index 92% rename from src/test/java/com/jnape/palatable/lambda/iterators/CyclicIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIteratorTest.java index 80c85a895..5af519d32 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/CyclicIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterableTest.java new file mode 100644 index 000000000..ae6d53ce5 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterableTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +@RunWith(Traits.class) +public class DistinctIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return DistinctIterable::new; + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterableTest.java new file mode 100644 index 000000000..cccdd2878 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterableTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +@RunWith(Traits.class) +public class DroppingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return x -> new DroppingIterable<>(1, x); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/DroppingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIteratorTest.java similarity index 81% rename from src/test/java/com/jnape/palatable/lambda/iterators/DroppingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIteratorTest.java index e1fbdde1a..0abdb857e 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/DroppingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIteratorTest.java @@ -1,11 +1,10 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.util.Iterator; @@ -18,7 +17,7 @@ public class DroppingIteratorTest { @Mock private Iterator iterator; - private DroppingIterator droppingIterator; + private DroppingIterator droppingIterator; @Before public void setUp() { @@ -40,6 +39,6 @@ public void hasNextIfIteratorHoldsMoreThanNElements() { @Test public void dropsElementsOnNextIfNotAlreadyDropped() { mockIteratorToHaveValues(iterator, 1, 2, 3, 4, 5, 6); - assertThat(droppingIterator.next(), Is.is(6)); + assertThat(droppingIterator.next(), is(6)); } } diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterableTest.java new file mode 100644 index 000000000..b2f40b516 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterableTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; + +@RunWith(Traits.class) +public class FilteringIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return filter(constantly(true)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/FilteringIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIteratorTest.java similarity index 95% rename from src/test/java/com/jnape/palatable/lambda/iterators/FilteringIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIteratorTest.java index 55481a9fc..68f3ab567 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/FilteringIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIteratorTest.java new file mode 100644 index 000000000..ce286ecd2 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIteratorTest.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Magnetize.magnetize; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyIterator; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class FlatteningIteratorTest { + + @Test + public void doesNotHaveNextForEmptyIterable() { + FlatteningIterator iterator = new FlatteningIterator<>(emptyIterator()); + assertFalse(iterator.hasNext()); + } + + @Test + public void doesNotHaveNextForIterableOfEmptyIterables() { + FlatteningIterator iterator = new FlatteningIterator<>(singletonList(emptyList()).iterator()); + assertFalse(iterator.hasNext()); + } + + @Test + public void iteratesNestedIterables() { + FlatteningIterator iterator = new FlatteningIterator<>(asList(singletonList(1), asList(2, 3), emptyList()).iterator()); + assertEquals(1, iterator.next()); + assertEquals(2, iterator.next()); + assertEquals(3, iterator.next()); + assertFalse(iterator.hasNext()); + } + + @Test(timeout = 500) + public void betterLaziness() { + assertEquals((Integer) 1, new FlatteningIterator<>(magnetize(repeat(1)).iterator()).next()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/GroupingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/GroupingIteratorTest.java similarity index 89% rename from src/test/java/com/jnape/palatable/lambda/iterators/GroupingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/GroupingIteratorTest.java index bd8cb3bac..d4e1df3ab 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/GroupingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/GroupingIteratorTest.java @@ -1,10 +1,10 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.util.Iterator; @@ -18,7 +18,7 @@ public class GroupingIteratorTest { @Mock private Iterator as; - private GroupingIterator groupingIterator; + private GroupingIterator groupingIterator; @Before public void setUp() { diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ImmutableIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ImmutableIteratorTest.java similarity index 77% rename from src/test/java/com/jnape/palatable/lambda/iterators/ImmutableIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ImmutableIteratorTest.java index 226cdd675..15369f60a 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ImmutableIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ImmutableIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Before; import org.junit.Test; @@ -7,11 +7,11 @@ public class ImmutableIteratorTest { - private ImmutableIterator immutableIterator; + private ImmutableIterator immutableIterator; @Before public void setUp() { - immutableIterator = new ImmutableIterator() { + immutableIterator = new ImmutableIterator() { @Override public boolean hasNext() { throw outOfScope(); diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/InfiniteIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/InfiniteIteratorTest.java similarity index 79% rename from src/test/java/com/jnape/palatable/lambda/iterators/InfiniteIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/InfiniteIteratorTest.java index 428541d91..a2df8b32c 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/InfiniteIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/InfiniteIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Before; import org.junit.Test; @@ -9,11 +9,11 @@ public class InfiniteIteratorTest { - private InfiniteIterator infiniteIterator; + private InfiniteIterator infiniteIterator; @Before public void setUp() { - infiniteIterator = new InfiniteIterator() { + infiniteIterator = new InfiniteIterator() { @Override public Object next() { throw outOfScope(); diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/InitIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/InitIteratorTest.java new file mode 100644 index 000000000..99f12dee2 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/InitIteratorTest.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import org.junit.Test; + +import java.util.Collections; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class InitIteratorTest { + + @Test + public void iteratesNothingForEmptyIterable() { + assertFalse(new InitIterator<>(Collections::emptyIterator).hasNext()); + } + + @Test + public void iteratesNothingForSingleElementIterable() { + assertFalse(new InitIterator<>(singletonList(1)).hasNext()); + } + + @Test + public void iteratesAllButLastElement() { + InitIterator iterator = new InitIterator<>(asList(1, 2, 3)); + + assertTrue(iterator.hasNext()); + assertEquals((Integer) 1, iterator.next()); + + assertTrue(iterator.hasNext()); + assertEquals((Integer) 2, iterator.next()); + + assertFalse(iterator.hasNext()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIterableTest.java new file mode 100644 index 000000000..b86b95650 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIterableTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; + +@RunWith(Traits.class) +public class MappingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return map(id()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/MappingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIteratorTest.java similarity index 69% rename from src/test/java/com/jnape/palatable/lambda/iterators/MappingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIteratorTest.java index da1469db9..23abb2ce7 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/MappingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Test; @@ -13,8 +13,8 @@ public class MappingIteratorTest { @Test public void nextProducesMappedResult() { - Fn1 stringToLength = String::length; - List words = asList("foo", "bar"); + Fn1 stringToLength = String::length; + List words = asList("foo", "bar"); MappingIterator mappingIterator = new MappingIterator<>(stringToLength, words.iterator()); assertThat(mappingIterator.next(), is(3)); diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterableTest.java new file mode 100644 index 000000000..55d6a3ef1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterableTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.DropWhile.dropWhile; + +@RunWith(Traits.class) +public class PredicatedDroppingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return dropWhile(constantly(false)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java similarity index 87% rename from src/test/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java index b597da2a0..44fcde9f1 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java @@ -1,15 +1,18 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.ImmutableQueue; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; +import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; +import static java.util.Collections.singletonList; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static testsupport.Mocking.mockIteratorToHaveValues; @@ -25,7 +28,7 @@ public class PredicatedDroppingIteratorTest { @Before public void setUp() { - predicatedDroppingIterator = new PredicatedDroppingIterator<>(EVEN, iterator); + predicatedDroppingIterator = new PredicatedDroppingIterator<>(ImmutableQueue.singleton(EVEN), iterator); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterableTest.java new file mode 100644 index 000000000..e819c90f2 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterableTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.TakeWhile.takeWhile; + +@RunWith(Traits.class) +public class PredicatedTakingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return takeWhile(constantly(true)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIteratorTest.java similarity index 76% rename from src/test/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIteratorTest.java index 69f8d2f51..aae11165b 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.specialized.Predicate; import org.junit.Test; @@ -6,6 +6,7 @@ import java.util.NoSuchElementException; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; @@ -15,14 +16,14 @@ public class PredicatedTakingIteratorTest { @Test public void hasNextIfPredicateSucceedsForNextElement() { - Iterable words = asList("four", "three", "two", "one"); + Iterable words = asList("four", "three", "two", "one"); PredicatedTakingIterator predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator()); assertThat(predicatedTakingIterator.hasNext(), is(true)); } @Test public void stopsTakingAfterFirstPredicateFailure() { - Iterable words = asList("once", "upon", "a", "time"); + Iterable words = asList("once", "upon", "a", "time"); PredicatedTakingIterator predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator()); predicatedTakingIterator.next(); predicatedTakingIterator.next(); @@ -31,14 +32,14 @@ public void stopsTakingAfterFirstPredicateFailure() { @Test public void doesNotHaveNextIfFirstElementFailsPredicate() { - Iterable words = asList("I", "have", "a", "dream"); + Iterable words = asList("I", "have", "a", "dream"); PredicatedTakingIterator predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator()); assertThat(predicatedTakingIterator.hasNext(), is(false)); } @Test public void doesNotHaveNextIfTakenAllElements() { - Iterable words = asList("four", "four"); + Iterable words = asList("four", "four"); PredicatedTakingIterator predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator()); predicatedTakingIterator.next(); predicatedTakingIterator.next(); @@ -47,14 +48,14 @@ public void doesNotHaveNextIfTakenAllElements() { @Test(expected = NoSuchElementException.class) public void throwsExceptionIfNextAfterFailedPredicate() { - Iterable words = asList("no"); + Iterable words = singletonList("no"); PredicatedTakingIterator predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator()); predicatedTakingIterator.next(); } @Test public void takesEverythingIfPredicateNeverFails() { - Iterable words = asList("yeah"); + Iterable words = singletonList("yeah"); PredicatedTakingIterator predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator()); assertThat(predicatedTakingIterator.next(), is("yeah")); assertThat(predicatedTakingIterator.hasNext(), is(false)); diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/PrependingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PrependingIteratorTest.java new file mode 100644 index 000000000..4e922aa43 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PrependingIteratorTest.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.NoSuchElementException; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyIterator; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PrependingIteratorTest { + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void empty() { + PrependingIterator iterator = new PrependingIterator<>(0, emptyIterator()); + assertFalse(iterator.hasNext()); + + thrown.expect(NoSuchElementException.class); + iterator.next(); + } + + @Test + public void nonEmpty() { + PrependingIterator iterator = new PrependingIterator<>(0, asList(1, 2, 3).iterator()); + + assertTrue(iterator.hasNext()); + assertEquals((Integer) 0, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 1, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 0, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 2, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 0, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 3, iterator.next()); + assertFalse(iterator.hasNext()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/RepetitiousIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIteratorTest.java similarity index 93% rename from src/test/java/com/jnape/palatable/lambda/iterators/RepetitiousIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIteratorTest.java index 2d284c00a..f99823f25 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/RepetitiousIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterableTest.java new file mode 100644 index 000000000..44ea4f446 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterableTest.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Reverse.reverse; + +@RunWith(Traits.class) +public class ReversingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return reverse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ReversingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIteratorTest.java similarity index 76% rename from src/test/java/com/jnape/palatable/lambda/iterators/ReversingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIteratorTest.java index 4d9c5d3d2..dd31b4bf7 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ReversingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIteratorTest.java @@ -1,11 +1,10 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.util.Iterator; @@ -23,7 +22,7 @@ public class ReversingIteratorTest { @Mock private Iterator iterator; - private ReversingIterator reversingIterator; + private ReversingIterator reversingIterator; @Before public void setUp() { @@ -40,20 +39,22 @@ public void doesNotHaveNextIfIteratorIsEmpty() { public void reversesIterator() { mockIteratorToHaveValues(iterator, 1, 2, 3, 4, 5); - assertThat(reversingIterator.next(), Is.is(5)); - assertThat(reversingIterator.next(), Is.is(4)); - assertThat(reversingIterator.next(), Is.is(3)); - assertThat(reversingIterator.next(), Is.is(2)); - assertThat(reversingIterator.next(), Is.is(1)); + assertThat(reversingIterator.next(), is(5)); + assertThat(reversingIterator.next(), is(4)); + assertThat(reversingIterator.next(), is(3)); + assertThat(reversingIterator.next(), is(2)); + assertThat(reversingIterator.next(), is(1)); } @Test + @SuppressWarnings("ResultOfMethodCallIgnored") public void doesNotReverseUntilNextIsCalled() { reversingIterator.hasNext(); verify(iterator, never()).next(); } @Test + @SuppressWarnings("Duplicates") public void doesNotHaveNextIfFinishedReversingIterator() { mockIteratorToHaveValues(iterator, 1, 2, 3); reversingIterator.next(); @@ -63,6 +64,7 @@ public void doesNotHaveNextIfFinishedReversingIterator() { } @Test + @SuppressWarnings("ResultOfMethodCallIgnored") public void neverInteractsWithIteratorAgainAfterInitialReverse() { mockIteratorToHaveValues(iterator, 1, 2, 3); diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/RewindableIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java similarity index 81% rename from src/test/java/com/jnape/palatable/lambda/iterators/RewindableIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java index a5c966d3c..f07fe55a5 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/RewindableIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java @@ -1,16 +1,16 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.util.Iterator; import java.util.NoSuchElementException; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static testsupport.Mocking.mockIteratorToHaveValues; @@ -19,7 +19,7 @@ public class RewindableIteratorTest { @Mock private Iterator iterator; - private RewindableIterator rewindableIterator; + private RewindableIterator rewindableIterator; @Before public void setUp() { @@ -33,7 +33,7 @@ public void rewindingQueuesPreviousElementUpForNext() { rewindableIterator.next(); rewindableIterator.rewind(); - assertThat(rewindableIterator.next(), Is.is(2)); + assertThat(rewindableIterator.next(), is(2)); } @Test @@ -52,22 +52,23 @@ public void cannotRewindIfNoValuesIterated() { rewindableIterator.rewind(); } - @Test(expected = NoSuchElementException.class) - public void cannotRewindTheSameElementTwice() { + @Test + public void canRewindTheSameElementTwice() { mockIteratorToHaveValues(iterator, 1, 2, 3); rewindableIterator.next(); rewindableIterator.rewind(); rewindableIterator.next(); rewindableIterator.rewind(); + assertEquals(1, rewindableIterator.next()); } @Test + @SuppressWarnings("Duplicates") public void doesNotHaveNextIfNoMoreElementsAndIsNotRewound() { mockIteratorToHaveValues(iterator, 1, 2, 3); rewindableIterator.next(); rewindableIterator.next(); rewindableIterator.next(); - assertThat(rewindableIterator.hasNext(), is(false)); } } diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ScanningIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ScanningIteratorTest.java similarity index 88% rename from src/test/java/com/jnape/palatable/lambda/iterators/ScanningIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ScanningIteratorTest.java index 387bc965e..43d24fad0 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ScanningIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ScanningIteratorTest.java @@ -1,9 +1,9 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; +import com.jnape.palatable.lambda.functions.Fn2; import org.junit.Test; import java.util.NoSuchElementException; -import java.util.function.BiFunction; import static java.util.Arrays.asList; import static java.util.Collections.emptyIterator; @@ -12,7 +12,7 @@ public class ScanningIteratorTest { - public static final BiFunction ADD = (x, y) -> x + y; + public static final Fn2 ADD = Integer::sum; @Test public void hasNextAtLeastForB() { diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIterableTest.java new file mode 100644 index 000000000..6451e8b4f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIterableTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +@RunWith(Traits.class) +public class SnocIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return xs -> new SnocIterable<>(1, xs); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIteratorTest.java new file mode 100644 index 000000000..96080f32b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIteratorTest.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import org.junit.Before; +import org.junit.Test; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SnocIteratorTest { + + private SnocIterator iterator; + + @Before + public void setUp() { + iterator = new SnocIterator<>(asList(1, 2).iterator(), singletonList(3).iterator()); + } + + @Test + public void hasNextIfInitNotYetIterated() { + assertTrue(iterator.hasNext()); + assertEquals((Integer) 1, iterator.next()); + } + + @Test + public void hasNextIfNoInitButLastNotYetIterated() { + iterator.next(); + iterator.next(); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 3, iterator.next()); + } + + @Test + public void doesNotHaveNextIfLastIterated() { + iterator.next(); + iterator.next(); + iterator.next(); + assertFalse(iterator.hasNext()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIterableTest.java new file mode 100644 index 000000000..e72b6f060 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIterableTest.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; + +@RunWith(Traits.class) +public class TakingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return take(1); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/TakingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIteratorTest.java similarity index 77% rename from src/test/java/com/jnape/palatable/lambda/iterators/TakingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIteratorTest.java index 9b335554a..b405da88b 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/TakingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIteratorTest.java @@ -1,11 +1,11 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Test; +import java.util.Collections; import java.util.List; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; @@ -13,14 +13,14 @@ public class TakingIteratorTest { @Test public void hasNextBeforeTakingAnyElements() { - List numbers = asList(1, 2, 3, 4, 5); + List numbers = asList(1, 2, 3, 4, 5); TakingIterator takingIterator = new TakingIterator<>(3, numbers.iterator()); assertThat(takingIterator.hasNext(), is(true)); } @Test public void doesNotHaveNextIfTakenEnoughElements() { - List vowels = asList('a', 'e', 'i', 'o', 'u'); + List vowels = asList('a', 'e', 'i', 'o', 'u'); TakingIterator takingIterator = new TakingIterator<>(3, vowels.iterator()); takingIterator.next(); takingIterator.next(); @@ -30,7 +30,7 @@ public void doesNotHaveNextIfTakenEnoughElements() { @Test public void doesNotHaveNextIfTakenAllOfIterable() { - List words = asList("we", "the", "people"); + List words = asList("we", "the", "people"); TakingIterator takingIterator = new TakingIterator<>(4, words.iterator()); takingIterator.next(); takingIterator.next(); @@ -40,7 +40,7 @@ public void doesNotHaveNextIfTakenAllOfIterable() { @Test public void doesNotHaveNextForEmptyIterable() { - TakingIterator takingIterator = new TakingIterator<>(3, emptyList().iterator()); + TakingIterator takingIterator = new TakingIterator<>(3, Collections.emptyIterator()); assertThat(takingIterator.hasNext(), is(false)); } } diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIteratorTest.java new file mode 100644 index 000000000..81b37122c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIteratorTest.java @@ -0,0 +1,72 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TrampoliningIteratorTest { + + @Test + public void hasNextIfAnyTerminateInstructions() { + TrampoliningIterator it = new TrampoliningIterator<>(x -> singleton(terminate(x + 1)), 0); + assertTrue(it.hasNext()); + assertEquals(1, it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void hasNextIfTerminateInterleavedBeforeRecurse() { + TrampoliningIterator it = new TrampoliningIterator<>( + x -> x < 3 + ? asList(terminate(x), recurse(x + 1)) + : emptyList(), + 0); + assertTrue(it.hasNext()); + assertEquals(0, it.next()); + assertTrue(it.hasNext()); + assertEquals(1, it.next()); + assertTrue(it.hasNext()); + assertEquals(2, it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void hasNextIfTerminateInterleavedAfterRecurse() { + TrampoliningIterator it = new TrampoliningIterator<>( + x -> x < 3 + ? asList(recurse(x + 1), terminate(x)) + : emptyList(), + 0); + assertTrue(it.hasNext()); + assertEquals(2, it.next()); + assertTrue(it.hasNext()); + assertEquals(1, it.next()); + assertTrue(it.hasNext()); + assertEquals(0, it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void doesNotHaveNextIfEmptyInitialResult() { + TrampoliningIterator it = new TrampoliningIterator<>(constantly(emptyList()), 0); + assertFalse(it.hasNext()); + } + + @Test + public void doesNotHaveNextIfNoTerminateInstruction() { + TrampoliningIterator it = new TrampoliningIterator<>( + x -> x < 3 + ? singleton(recurse(x + 1)) + : emptyList(), + 0); + assertFalse(it.hasNext()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIteratorTest.java new file mode 100644 index 000000000..4b1199565 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIteratorTest.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class UnfoldingIteratorTest { + + private static final Fn1>> STRINGIFY = x -> just(tuple(x.toString(), x + 1)); + + @Test + public void hasNextIfFunctionProducesPresentValue() { + assertThat(new UnfoldingIterator<>(STRINGIFY, 0).hasNext(), is(true)); + } + + @Test + public void doesNotHaveNextIfFunctionProducesEmptyValue() { + assertThat(new UnfoldingIterator(x -> nothing(), 0).hasNext(), is(false)); + } + + @Test + public void defersNextCallForAsLongAsPossible() { + AtomicInteger invocations = new AtomicInteger(0); + UnfoldingIterator iterator = new UnfoldingIterator<>(x -> { + invocations.incrementAndGet(); + return just(tuple(x.toString(), x + 1)); + }, 1); + + assertEquals(0, invocations.get()); + iterator.next(); + assertEquals(1, invocations.get()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterableTest.java new file mode 100644 index 000000000..b28bc2b61 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterableTest.java @@ -0,0 +1,24 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Collections.emptyList; + +@RunWith(Traits.class) +public class UnioningIterableTest { + @TestTraits({Deforesting.class}) + public Subjects, Iterable>> testSubject() { + return subjects(xs -> new UnioningIterable<>(emptyList(), xs), + xs -> new UnioningIterable<>(xs, emptyList()), + xs -> new UnioningIterable<>(repeat(1), xs), + xs -> new UnioningIterable<>(xs, repeat(1))); + } + +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ZippingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ZippingIteratorTest.java similarity index 75% rename from src/test/java/com/jnape/palatable/lambda/iterators/ZippingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ZippingIteratorTest.java index 561eb2534..9fb639a6a 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ZippingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ZippingIteratorTest.java @@ -1,13 +1,13 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; +import com.jnape.palatable.lambda.functions.Fn2; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.util.Iterator; -import java.util.function.BiFunction; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -17,9 +17,9 @@ @RunWith(MockitoJUnitRunner.class) public class ZippingIteratorTest { - @Mock private BiFunction zipper; - @Mock private Iterator as; - @Mock private Iterator bs; + @Mock private Fn2 zipper; + @Mock private Iterator as; + @Mock private Iterator bs; private ZippingIterator zippingIterator; @@ -38,7 +38,6 @@ public void hasNextIfMoreAsAndMoreBs() { @Test public void doesNotHaveNextIfAsDoesNotHaveNext() { when(as.hasNext()).thenReturn(false); - when(bs.hasNext()).thenReturn(true); assertThat(zippingIterator.hasNext(), is(false)); } @@ -51,9 +50,6 @@ public void doesNotHaveNextIfBsDoesNotHaveNext() { @Test public void zipsNextElementFromAsAndBs() { - when(as.hasNext()).thenReturn(true); - when(bs.hasNext()).thenReturn(true); - when(as.next()).thenReturn(1); when(bs.next()).thenReturn(2); diff --git a/src/test/java/com/jnape/palatable/lambda/io/IOTest.java b/src/test/java/com/jnape/palatable/lambda/io/IOTest.java new file mode 100644 index 000000000..723abb4fc --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/io/IOTest.java @@ -0,0 +1,513 @@ +package com.jnape.palatable.lambda.io; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2.liftA2; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.io.IO.externallyManaged; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.io.IO.pureIO; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.lang.Thread.currentThread; +import static java.util.Arrays.asList; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static java.util.concurrent.ForkJoinPool.commonPool; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; +import static testsupport.assertion.MonadErrorAssert.assertLawsEq; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class IOTest { + + public @Rule ExpectedException thrown = ExpectedException.none(); + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Equivalence> testSubject() { + return equivalence(io(1), IO::unsafePerformIO); + } + + @Test + public void monadError() { + assertLawsEq(subjects(equivalence(IO.throwing(new IllegalStateException("a")), IO::unsafePerformIO), + equivalence(io(1), IO::unsafePerformIO)), + new IOException("bar"), + e -> io(e.getMessage().length())); + } + + @Test + public void staticFactoryMethods() { + assertEquals((Integer) 1, io(1).unsafePerformIO()); + assertEquals((Integer) 1, io(() -> 1).unsafePerformIO()); + assertEquals(UNIT, io(() -> {}).unsafePerformIO()); + } + + @Test(timeout = 100) + public void unsafePerformAsyncIOWithoutExecutor() { + assertEquals((Integer) 1, io(() -> 1).unsafePerformAsyncIO().join()); + } + + @Test(timeout = 100) + public void unsafePerformAsyncIOWithExecutor() { + assertEquals((Integer) 1, io(() -> 1).unsafePerformAsyncIO(newFixedThreadPool(1)).join()); + } + + @Test + public void zipAndDerivativesComposesInParallel() { + String a = "foo"; + Fn1> f = tupler(1); + + ExecutorService executor = newFixedThreadPool(2); + CountDownLatch advanceFirst = new CountDownLatch(1); + CountDownLatch advanceSecond = new CountDownLatch(1); + + IO ioA = io(() -> { + advanceFirst.countDown(); + advanceSecond.await(); + return a; + }); + IO>> ioF = io(() -> { + advanceFirst.await(); + advanceSecond.countDown(); + return f; + }); + + IO> zip = ioA.zip(ioF); + assertEquals(f.apply(a), zip.unsafePerformAsyncIO().join()); + assertEquals(f.apply(a), zip.unsafePerformAsyncIO(executor).join()); + assertEquals(f.apply(a), zip.unsafePerformAsyncIO(executor).join()); + + IO>> discardL = ioA.discardL(ioF); + assertEquals(f, discardL.unsafePerformAsyncIO().join()); + assertEquals(f, discardL.unsafePerformAsyncIO(executor).join()); + + IO discardR = ioA.discardR(ioF); + assertEquals(a, discardR.unsafePerformAsyncIO().join()); + assertEquals(a, discardR.unsafePerformAsyncIO(executor).join()); + } + + @Test + public void delegatesToExternallyManagedFuture() { + CompletableFuture future = completedFuture(1); + IO io = externallyManaged(() -> future); + assertEquals((Integer) 1, io.unsafePerformIO()); + assertEquals((Integer) 1, io.unsafePerformAsyncIO().join()); + assertEquals((Integer) 1, io.unsafePerformAsyncIO(commonPool()).join()); + } + + @Test + public void catchError() { + Executor executor = newFixedThreadPool(2); + IO io = IO.throwing(new UnsupportedOperationException("foo")); + assertEquals("foo", io.catchError(t -> io(t::getMessage)).unsafePerformIO()); + assertEquals("foo", + io.catchError(e -> io(() -> e.getCause().getMessage())).unsafePerformAsyncIO().join()); + assertEquals("foo", + io.catchError(e -> io(() -> e.getCause().getMessage())) + .unsafePerformAsyncIO(executor).join()); + + IO externallyManaged = externallyManaged(() -> new CompletableFuture() {{ + completeExceptionally(new UnsupportedOperationException("foo")); + }}).catchError(e -> io(() -> e.getCause().getMessage())); + assertEquals("foo", externallyManaged.unsafePerformIO()); + } + + @Test + public void catchAndRethrow() { + IllegalStateException expected = new IllegalStateException("expected"); + IO catchAndRethrow = IO.throwing(expected) + .catchError(IO::throwing); + + try { + catchAndRethrow.unsafePerformIO(); + } catch (Exception actual) { + assertSame(expected, actual); + } + + try { + catchAndRethrow.unsafePerformAsyncIO().join(); + } catch (CompletionException actual) { + assertEquals(expected, actual.getCause()); + } + } + + @Test + public void safe() { + assertEquals(right(1), io(() -> 1).safe().unsafePerformIO()); + IllegalStateException thrown = new IllegalStateException("kaboom"); + assertEquals(left(thrown), io(() -> {throw thrown;}).safe().unsafePerformIO()); + } + + @Test + public void ensuring() { + AtomicInteger counter = new AtomicInteger(0); + IO incCounter = io(counter::incrementAndGet); + assertEquals("foo", io(() -> "foo").ensuring(incCounter).unsafePerformIO()); + assertEquals(1, counter.get()); + + IllegalStateException thrown = new IllegalStateException("kaboom"); + try { + io(() -> {throw thrown;}).ensuring(incCounter).unsafePerformIO(); + fail("Expected exception to have been thrown, but wasn't."); + } catch (IllegalStateException actual) { + assertEquals(thrown, actual); + assertEquals(2, counter.get()); + } + } + + @Test + public void ensuringRunsStrictlyAfterIO() { + Executor twoThreads = Executors.newFixedThreadPool(2); + AtomicInteger counter = new AtomicInteger(0); + io(() -> { + Thread.sleep(100); + counter.incrementAndGet(); + }).ensuring(io(() -> { + if (counter.get() == 0) + fail("Expected to run after initial IO, but ran first"); + })).unsafePerformAsyncIO(twoThreads).join(); + } + + @Test + public void ensuringAttachesThrownExceptionToThrownBodyException() { + IllegalStateException thrownByBody = new IllegalStateException("kaboom"); + IllegalStateException thrownByEnsuring = new IllegalStateException("KABOOM"); + + try { + io(() -> {throw thrownByBody;}).ensuring(io(() -> {throw thrownByEnsuring;})).unsafePerformIO(); + fail("Expected exception to have been thrown, but wasn't."); + } catch (IllegalStateException actual) { + assertEquals(thrownByBody, actual); + assertArrayEquals(new Throwable[]{thrownByEnsuring}, actual.getSuppressed()); + } + } + + @Test + public void throwing() { + IllegalStateException expected = new IllegalStateException("thrown"); + try { + IO.throwing(expected).unsafePerformIO(); + } catch (IllegalStateException actual) { + assertEquals(expected, actual); + } + } + + @Test + public void zipStackSafety() { + IO> zipAdd1 = io(x -> x + 1); + IO zero = io(0); + + IO leftHeavy = times(STACK_EXPLODING_NUMBER, io -> io.zip(zipAdd1), zero); + assertEquals(STACK_EXPLODING_NUMBER, leftHeavy.unsafePerformIO()); + assertEquals(STACK_EXPLODING_NUMBER, leftHeavy.unsafePerformAsyncIO().join()); + + IO rightHeavy = times(STACK_EXPLODING_NUMBER, io -> zipAdd1.zip(io.fmap(x -> f -> f.apply(x))), zero); + assertEquals(STACK_EXPLODING_NUMBER, rightHeavy.unsafePerformIO()); + assertEquals(STACK_EXPLODING_NUMBER, rightHeavy.unsafePerformAsyncIO().join()); + } + + @Test + public void discardStackSafety() { + IO discardL = times(STACK_EXPLODING_NUMBER, io -> io(1).discardL(io), io(0)); + assertEquals((Integer) 0, discardL.unsafePerformIO()); + assertEquals((Integer) 0, discardL.unsafePerformAsyncIO().join()); + + IO discardR = times(STACK_EXPLODING_NUMBER, io -> io.discardR(io(1)), io(0)); + assertEquals((Integer) 0, discardR.unsafePerformIO()); + assertEquals((Integer) 0, discardR.unsafePerformAsyncIO().join()); + } + + @Test + public void lazyZipStackSafety() { + IO> zipAdd1 = io(x -> x + 1); + IO zero = io(0); + + IO leftHeavy = times(STACK_EXPLODING_NUMBER, io -> io.lazyZip(lazy(zipAdd1)).value(), zero); + assertEquals(STACK_EXPLODING_NUMBER, leftHeavy.unsafePerformIO()); + assertEquals(STACK_EXPLODING_NUMBER, leftHeavy.unsafePerformAsyncIO().join()); + + IO rightHeavy = times(STACK_EXPLODING_NUMBER, + io -> zipAdd1.lazyZip(lazy(io.fmap(x -> f -> f.apply(x)))).value(), + zero); + assertEquals(STACK_EXPLODING_NUMBER, rightHeavy.unsafePerformIO()); + assertEquals(STACK_EXPLODING_NUMBER, rightHeavy.unsafePerformAsyncIO().join()); + } + + @Test + public void flatMapStackSafety() { + Fn1> add1 = x -> io(() -> x + 1); + IO zero = io(0); + + IO leftHeavy = times(STACK_EXPLODING_NUMBER, io -> io.flatMap(add1), zero); + assertEquals(STACK_EXPLODING_NUMBER, leftHeavy.unsafePerformIO()); + assertEquals(STACK_EXPLODING_NUMBER, leftHeavy.unsafePerformAsyncIO().join()); + + IO rightHeavy = times(STACK_EXPLODING_NUMBER, + io -> add1.apply(0).flatMap(x -> io.fmap(y -> x + y)), + zero); + assertEquals(STACK_EXPLODING_NUMBER, rightHeavy.unsafePerformIO()); + assertEquals(STACK_EXPLODING_NUMBER, rightHeavy.unsafePerformAsyncIO().join()); + } + + @Test + public void staggeredZipAndFlatMapStackSafety() { + assertEquals(STACK_EXPLODING_NUMBER, + times(STACK_EXPLODING_NUMBER, io -> io.zip(io(x -> x + 1)) + .flatMap(x -> io(() -> x)) + .zip(io(x -> x)), io(0)) + .unsafePerformIO()); + } + + @Test + public void sequenceStackSafety() { + assertEquals(STACK_EXPLODING_NUMBER, + sequence(replicate(STACK_EXPLODING_NUMBER, io(UNIT)), IO::io) + .fmap(size()) + .fmap(Long::intValue) + .unsafePerformIO()); + + assertEquals(STACK_EXPLODING_NUMBER, + sequence(replicate(STACK_EXPLODING_NUMBER, io(UNIT)), IO::io) + .fmap(size()) + .fmap(Long::intValue) + .unsafePerformAsyncIO().join()); + } + + @Test + public void sequenceIOExecutesInParallel() { + int n = 2; + CountDownLatch latch = new CountDownLatch(n); + sequence(replicate(n, io(() -> { + latch.countDown(); + if (!latch.await(100, MILLISECONDS)) { + throw new AssertionError("Expected latch to countDown in time, but didn't."); + } + })), IO::io) + .unsafePerformAsyncIO() + .join(); + } + + @Test + public void sequenceIsRepeatable() { + IO> io = sequence(replicate(3, io(UNIT)), IO::io); + + assertEquals((Long) 3L, size(io.unsafePerformIO())); + assertEquals((Long) 3L, size(io.unsafePerformIO())); + } + + @Test(expected = InterruptedException.class) + public void interruptible() { + currentThread().interrupt(); + + IO io = IO.interruptible(IO.throwing(new AssertionError("expected to never be called"))); + io.unsafePerformIO(); + } + + @Test + public void monitorSync() throws InterruptedException { + Object lock = new Object(); + List accesses = new ArrayList<>(); + CountDownLatch oneStarted = new CountDownLatch(1); + CountDownLatch twoStarted = new CountDownLatch(1); + CountDownLatch finishLine = new CountDownLatch(2); + + IO one = IO.monitorSync(lock, io(() -> { + accesses.add("one entered"); + oneStarted.countDown(); + twoStarted.await(); + Thread.sleep(100); + accesses.add("one exited"); + finishLine.countDown(); + })); + + IO two = IO.monitorSync(lock, io(() -> { + accesses.add("two entered"); + accesses.add("two exited"); + finishLine.countDown(); + })); + + new Thread(one::unsafePerformIO) {{ + start(); + }}; + + oneStarted.await(); + + new Thread(() -> { + twoStarted.countDown(); + two.unsafePerformIO(); + }) {{ + start(); + }}; + + if (!finishLine.await(1, SECONDS)) + fail("Expected threads to have completed by now, only got this far: " + accesses); + assertEquals(asList("one entered", "one exited", "two entered", "two exited"), accesses); + } + + @Test + public void fuse() { + IO currentThreadIO = io(Thread::currentThread); + IO> threads = liftA2(HList::tuple, currentThreadIO, currentThreadIO); + Executor executor = Executors.newFixedThreadPool(2); + Boolean sameThread = IO.fuse(threads).unsafePerformAsyncIO(executor).join().into(eq()); + assertTrue("Expected both IOs to run on the same Thread, but they didn't.", sameThread); + } + + @Test + public void pin() { + Thread mainThread = currentThread(); + IO currentThreadIO = io(Thread::currentThread); + Executor executor = Executors.newFixedThreadPool(2); + Thread chosenThread = IO.pin(currentThreadIO, Runnable::run).unsafePerformAsyncIO(executor).join(); + assertEquals("Expected IO to run on the main Thread, but it didn't.", mainThread, chosenThread); + } + + @Test + public void memoize() { + AtomicInteger counter = new AtomicInteger(0); + IO memoized = IO.memoize(io(counter::incrementAndGet)); + memoized.unsafePerformIO(); + memoized.unsafePerformIO(); + assertEquals(1, counter.get()); + } + + @Test + public void memoizationMutuallyExcludesSimultaneousComputation() throws InterruptedException { + AtomicInteger counter = new AtomicInteger(0); + + IO memoized = IO.memoize(io(() -> { + Thread.sleep(10); + return counter.incrementAndGet(); + })); + + Thread t1 = new Thread(memoized::unsafePerformIO) {{ + start(); + }}; + + Thread t2 = new Thread(memoized::unsafePerformIO) {{ + start(); + }}; + + t1.join(); + t2.join(); + + assertEquals(1, counter.get()); + } + + @Test + public void failuresAreNotMemoized() { + IllegalStateException exception = new IllegalStateException("not yet"); + AtomicInteger counter = new AtomicInteger(0); + IO io = IO.memoize(io(() -> { + int next = counter.incrementAndGet(); + if (next > 1) + return next; + throw exception; + })); + + assertEquals(left(exception), io.safe().unsafePerformIO()); + assertEquals((Integer) 2, io.unsafePerformIO()); + assertEquals((Integer) 2, io.unsafePerformIO()); + } + + @Test + public void staticPure() { + IO io = pureIO().apply(1); + assertEquals((Integer) 1, io.unsafePerformIO()); + } + + @Test + public void zipExecutionOrdering() { + List invocationsSync = new ArrayList<>(); + io(() -> invocationsSync.add("one")) + .zip(io(() -> invocationsSync.add("two")) + .zip(io(() -> invocationsSync.add("three")).fmap(x -> y -> z -> z))) + .unsafePerformIO(); + assertEquals(asList("one", "two", "three"), invocationsSync); + + List invocationsAsync = new ArrayList<>(); + io(() -> invocationsAsync.add("one")) + .zip(io(() -> invocationsAsync.add("two")) + .zip(io(() -> invocationsAsync.add("three")).fmap(x -> y -> z -> z))) + .unsafePerformAsyncIO(newSingleThreadExecutor()).join(); + assertEquals(asList("one", "two", "three"), invocationsSync); + } + + @Test + public void discardLExecutionOrdering() { + List invocationsSync = new ArrayList<>(); + io(() -> invocationsSync.add("one")) + .discardL(io(() -> invocationsSync.add("two"))) + .discardL(io(() -> invocationsSync.add("three"))) + .unsafePerformIO(); + assertEquals(asList("one", "two", "three"), invocationsSync); + + List invocationsAsync = new ArrayList<>(); + io(() -> invocationsAsync.add("one")) + .discardL(io(() -> invocationsAsync.add("two"))) + .discardL(io(() -> invocationsAsync.add("three"))) + .unsafePerformAsyncIO(newSingleThreadExecutor()) + .join(); + assertEquals(asList("one", "two", "three"), invocationsSync); + } + + @Test + public void discardRExecutionOrdering() { + List invocationsSync = new ArrayList<>(); + io(() -> invocationsSync.add("one")) + .discardR(io(() -> invocationsSync.add("two"))) + .discardR(io(() -> invocationsSync.add("three"))) + .unsafePerformIO(); + assertEquals(asList("one", "two", "three"), invocationsSync); + + List invocationsAsync = new ArrayList<>(); + io(() -> invocationsAsync.add("one")) + .discardR(io(() -> invocationsAsync.add("two"))) + .discardR(io(() -> invocationsAsync.add("three"))) + .unsafePerformAsyncIO(newSingleThreadExecutor()) + .join(); + assertEquals(asList("one", "two", "three"), invocationsSync); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java deleted file mode 100644 index 1aadfde06..000000000 --- a/src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.jnape.palatable.lambda.iterators; - -import com.jnape.palatable.lambda.adt.hlist.Tuple2; -import com.jnape.palatable.lambda.functions.Fn1; -import org.junit.Test; - -import java.util.Optional; - -import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; - -public class UnfoldingIteratorTest { - - private static final Fn1>> STRINGIFY = x -> Optional.of(tuple(x.toString(), x + 1)); - - @Test - public void hasNextIfFunctionProducesPresentValue() { - UnfoldingIterator unfoldingIterator = new UnfoldingIterator<>(STRINGIFY, 0); - assertThat(unfoldingIterator.hasNext(), is(true)); - } - - @Test - public void doesNotHaveNextIfFunctionProducesEmptyValue() { - UnfoldingIterator unfoldingIterator = new UnfoldingIterator<>(x -> Optional.>empty(), 0); - assertThat(unfoldingIterator.hasNext(), is(false)); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java deleted file mode 100644 index c7e1419a9..000000000 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.jnape.palatable.lambda.lens; - -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functor.builtin.Const; -import com.jnape.palatable.lambda.functor.builtin.Identity; -import org.junit.Test; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static java.lang.Integer.parseInt; -import static java.util.Arrays.asList; -import static java.util.Collections.singleton; -import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; -import static org.junit.Assert.assertEquals; - -public class LensTest { - - private static final Lens>, Map>, List, Set> EARLIER_LENS = lens(m -> m.get("foo"), (m, s) -> singletonMap("foo", s)); - private static final Lens, Set, String, Integer> LENS = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); - - @Test - public void setsUnderIdentity() { - Set ints = LENS.>, Identity>apply(s -> new Identity<>(s.length()), asList("foo", "bar", "baz")).runIdentity(); - assertEquals(singleton(3), ints); - } - - @Test - public void viewsUnderConst() { - Integer i = LENS.>, Const>apply(s -> new Const<>(s.length()), asList("foo", "bar", "baz")).runConst(); - assertEquals((Integer) 3, i); - } - - @Test - public void fix() { - Fn1> fn = s -> new Const<>(s.length()); - List s = singletonList("foo"); - - Integer fixedLensResult = LENS.>, Const>fix().apply(fn, s).runConst(); - Integer unfixedLensResult = LENS.>, Const>apply(fn, s).runConst(); - - assertEquals(unfixedLensResult, fixedLensResult); - } - - @Test - public void functorProperties() { - assertEquals(false, set(LENS.fmap(Set::isEmpty), 1, singletonList("foo"))); - } - - @Test - public void mapsIndividuallyOverParameters() { - Lens lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); - Lens, Optional, Optional, Optional> theGambit = lens - .mapS((Optional optS) -> optS.orElse("")) - .mapT(Optional::ofNullable) - .mapA(Optional::ofNullable) - .mapB((Optional optI) -> optI.orElse(-1)); - - Lens.Fixed, Optional, Optional, Optional, Identity>, Identity>> fixed = theGambit.fix(); - assertEquals(Optional.of(true), fixed.apply(optC -> new Identity<>(optC.map(c -> parseInt(Character.toString(c)))), Optional.of("321")).runIdentity()); - } - - @Test - public void composition() { - Map> map = singletonMap("foo", asList("one", "two", "three")); - assertEquals("one", view(LENS.compose(EARLIER_LENS), map)); - assertEquals(singletonMap("foo", singleton(1)), set(LENS.compose(EARLIER_LENS), 1, map)); - } - - @Test - public void andThenComposesInReverse() { - Map> map = singletonMap("foo", asList("one", "two", "three")); - assertEquals("one", view(EARLIER_LENS.andThen(LENS), map)); - assertEquals(singletonMap("foo", singleton(1)), set(EARLIER_LENS.andThen(LENS), 1, map)); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java deleted file mode 100644 index 2df8dd73a..000000000 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses; - -import com.jnape.palatable.lambda.lens.Lens; -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Stream; - -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static java.util.stream.Collectors.toList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; - -public class CollectionLensTest { - - private List xs; - - @Before - public void setUp() { - xs = new ArrayList() {{ - add("foo"); - add("bar"); - add("baz"); - }}; - } - - @Test - public void asCopyUsesMappingFunctionToFocusOnCollectionThroughCopy() { - Lens.Simple, List> asCopy = CollectionLens.asCopy(ArrayList::new); - - assertEquals(xs, view(asCopy, xs)); - assertNotSame(xs, view(asCopy, xs)); - - List updatedList = asList("foo", "bar"); - assertSame(updatedList, set(asCopy, updatedList, xs)); - } - - @Test - public void asSetFocusesOnCollectionAsSet() { - Lens.Simple, Set> asSet = CollectionLens.asSet(); - - assertEquals(new HashSet<>(xs), view(asSet, xs)); - assertEquals(singleton("foo"), view(asSet, asList("foo", "foo"))); - assertEquals(emptySet(), view(asSet, emptyList())); - - assertEquals(asList("foo", "bar"), set(asSet, new HashSet<>(asList("foo", "bar")), xs)); - assertEquals(asList("foo", "foo", "bar"), - set(asSet, - new HashSet<>(asList("foo", "bar")), - new ArrayList<>(asList("foo", "foo", "bar", "baz")))); - assertEquals(emptyList(), set(asSet, emptySet(), xs)); - assertEquals(emptyList(), set(asSet, singleton("foo"), emptyList())); - } - - @Test - public void asStreamFocusesOnCollectionAsStream() { - Lens.Simple, Stream> asStream = CollectionLens.asStream(); - - assertEquals(xs, view(asStream, xs).collect(toList())); - assertEquals(asList("foo", "bar"), set(asStream, Stream.of("foo", "bar"), xs)); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java deleted file mode 100644 index fb518dd9b..000000000 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses; - -import com.jnape.palatable.lambda.adt.Either; -import com.jnape.palatable.lambda.lens.Lens; -import org.junit.Test; - -import java.util.Optional; - -import static com.jnape.palatable.lambda.adt.Either.left; -import static com.jnape.palatable.lambda.adt.Either.right; -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static org.junit.Assert.assertEquals; - -public class EitherLensTest { - - @Test - public void rightFocusesOnRightValues() { - Lens.Simple, Optional> right = EitherLens.right(); - - assertEquals(Optional.of(1), view(right, right(1))); - assertEquals(Optional.empty(), view(right, left("fail"))); - assertEquals(right(2), set(right, Optional.of(2), right(1))); - assertEquals(right(1), set(right, Optional.empty(), right(1))); - assertEquals(right(2), set(right, Optional.of(2), left("fail"))); - assertEquals(left("fail"), set(right, Optional.empty(), left("fail"))); - } - - @Test - public void leftFocusesOnLeftValues() { - Lens.Simple, Optional> left = EitherLens.left(); - - assertEquals(Optional.of("fail"), view(left, left("fail"))); - assertEquals(Optional.empty(), view(left, right(1))); - assertEquals(left("foo"), set(left, Optional.of("foo"), left("fail"))); - assertEquals(left("fail"), set(left, Optional.empty(), left("fail"))); - assertEquals(left("foo"), set(left, Optional.of("foo"), right(1))); - assertEquals(right(1), set(left, Optional.empty(), right(1))); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/HListLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/HListLensTest.java deleted file mode 100644 index 58f09af70..000000000 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/HListLensTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses; - -import com.jnape.palatable.lambda.adt.hlist.HList.HCons; -import com.jnape.palatable.lambda.adt.hlist.Index; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; -import com.jnape.palatable.lambda.lens.Lens; -import org.junit.Test; - -import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; -import static com.jnape.palatable.lambda.lens.functions.Over.over; -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.HListLens.elementAt; -import static org.junit.Assert.assertEquals; - -public class HListLensTest { - - @Test - public void elementAtFocusesOnInvariantElementAtIndex() { - Lens.Simple>>, String> lens = - elementAt(Index.index().after().after()); - - assertEquals("foo", view(lens, tuple(true, 0, "foo"))); - assertEquals(tuple(true, 0, "bar"), set(lens, "bar", tuple(true, 0, "foo"))); - assertEquals(tuple(true, 0, "FOO"), over(lens, String::toUpperCase, tuple(true, 0, "foo"))); - } - - @Test - public void headFocusesOnHead() { - Lens.Simple>, Integer> index = HListLens.head(); - - assertEquals((Integer) 2, view(index, tuple(2, "3", '4'))); - assertEquals(tuple(0, "3", '4'), set(index, 0, tuple(2, "3", '4'))); - assertEquals(tuple(3, "3", '4'), over(index, x -> x + 1, tuple(2, "3", '4'))); - } - - @Test - public void tailFocusesOnTail() { - Lens.Simple>, Tuple2> index = HListLens.tail(); - - assertEquals(tuple("3", '4'), view(index, tuple(2, "3", '4'))); - assertEquals(tuple(2, "foo", '1'), set(index, tuple("foo", '1'), tuple(2, "3", '4'))); - assertEquals(tuple(2, "FOO", 'A'), over(index, t -> t.biMap(String::toUpperCase, Character::toUpperCase), tuple(2, "foo", 'a'))); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java deleted file mode 100644 index aa31408bd..000000000 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses; - -import com.jnape.palatable.lambda.lens.Lens; -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; - -public class ListLensTest { - - private List xs; - - @Before - public void setUp() throws Exception { - xs = new ArrayList() {{ - add("foo"); - add("bar"); - add("baz"); - }}; - } - - @Test - public void asCopyFocusesOnListThroughCopy() { - Lens.Simple, List> asCopy = ListLens.asCopy(); - - assertEquals(xs, view(asCopy, xs)); - assertNotSame(xs, view(asCopy, xs)); - - List update = asList("foo", "bar", "baz", "quux"); - assertSame(update, set(asCopy, update, xs)); - } - - @Test - public void elementAtFocusesOnElementAtIndex() { - Lens, List, Optional, String> at0 = ListLens.elementAt(0); - - assertEquals(Optional.of("foo"), view(at0, xs)); - assertEquals(Optional.empty(), view(at0, emptyList())); - assertEquals(asList("quux", "bar", "baz"), set(at0, "quux", xs)); - assertEquals(emptyList(), set(at0, "quux", emptyList())); - } - - @Test - public void elementAtWithDefaultValueFocusesOnElementAtIndex() { - Lens, List, String, String> at0 = ListLens.elementAt(0, "missing"); - - assertEquals("foo", view(at0, xs)); - assertEquals("missing", view(at0, emptyList())); - assertEquals(asList("quux", "bar", "baz"), set(at0, "quux", xs)); - assertEquals(emptyList(), set(at0, "quux", emptyList())); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java deleted file mode 100644 index 9d2047c3d..000000000 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses; - -import com.jnape.palatable.lambda.lens.Lens; -import org.junit.Before; -import org.junit.Test; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.MapLens.keys; -import static com.jnape.palatable.lambda.lens.lenses.MapLens.mappingValues; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonMap; -import static java.util.Collections.unmodifiableMap; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; - -public class MapLensTest { - - private Map m; - - @Before - public void setUp() { - m = new HashMap() {{ - put("foo", 1); - put("bar", 2); - put("baz", 3); - }}; - } - - @Test - public void asCopyFocusesOnMapThroughCopy() { - Lens.Simple, Map> asCopy = MapLens.asCopy(); - - assertEquals(m, view(asCopy, m)); - assertNotSame(m, view(asCopy, m)); - - Map update = new HashMap() {{ - put("quux", 0); - }}; - assertSame(update, set(asCopy, update, m)); - } - - @Test - public void valueAtFocusesOnValueAtKey() { - Lens, Map, Optional, Integer> atFoo = MapLens.valueAt("foo"); - - assertEquals(Optional.of(1), view(atFoo, m)); - - Map updated = set(atFoo, -1, m); - assertEquals(new HashMap() {{ - put("foo", -1); - put("bar", 2); - put("baz", 3); - }}, updated); - assertSame(m, updated); - } - - @Test - public void valueAtWithDefaultValueFocusedOnValueAtKey() { - Lens, Map, Integer, Integer> atFoo = MapLens.valueAt("foo", -1); - - assertEquals((Integer) 1, view(atFoo, m)); - assertEquals((Integer) (-1), view(atFoo, emptyMap())); - - Map updated = set(atFoo, 11, m); - assertEquals(new HashMap() {{ - put("foo", 11); - put("bar", 2); - put("baz", 3); - }}, updated); - assertSame(m, updated); - } - - @Test - public void keysFocusesOnKeys() { - Lens, Map, Set, Set> keys = keys(); - - assertEquals(m.keySet(), view(keys, m)); - - Map updated = set(keys, new HashSet<>(asList("bar", "baz", "quux")), m); - assertEquals(new HashMap() {{ - put("bar", 2); - put("baz", 3); - put("quux", null); - }}, updated); - assertSame(m, updated); - } - - @Test - public void valuesFocusesOnValues() { - Lens, Map, Collection, Collection> values = MapLens.values(); - - assertEquals(m.values(), view(values, m)); - - Map updated = set(values, asList(1, 2), m); - assertEquals(new HashMap() {{ - put("foo", 1); - put("bar", 2); - }}, updated); - assertSame(m, updated); - } - - @Test - public void invertedFocusesOnMapWithKeysAndValuesSwitched() { - Lens.Simple, Map> inverted = MapLens.inverted(); - - assertEquals(new HashMap() {{ - put(1, "foo"); - put(2, "bar"); - put(3, "baz"); - }}, view(inverted, m)); - - Map updated = set(inverted, new HashMap() {{ - put(2, "bar"); - put(3, "baz"); - }}, m); - assertEquals(new HashMap() {{ - put("bar", 2); - put("baz", 3); - }}, updated); - assertSame(m, updated); - - Map withDuplicateValues = new HashMap() {{ - put("foo", 1); - put("bar", 1); - }}; - assertEquals(new HashMap() {{ - put(1, "foo"); - }}, view(inverted, withDuplicateValues)); - } - - @Test - public void mappingValuesRetainsMapStructureWithMappedValues() { - Map m = unmodifiableMap(new HashMap() {{ - put("foo", "1"); - put("bar", "2"); - put("baz", "3"); - }}); - Lens.Simple, Map> mappingValues = mappingValues(Integer::parseInt); - - assertEquals(new HashMap() {{ - put("foo", 1); - put("bar", 2); - put("baz", 3); - }}, view(mappingValues, m)); - - Map updated = set(mappingValues, unmodifiableMap(new HashMap() {{ - put("foo", 2); - put("bar", 1); - put("baz", 3); - }}), m); - assertEquals(singletonMap("baz", "3"), updated); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java deleted file mode 100644 index b5908d600..000000000 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses; - -import com.jnape.palatable.lambda.lens.Lens; -import org.junit.Before; -import org.junit.Test; - -import java.util.Optional; - -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftA; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftB; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftS; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftT; -import static org.junit.Assert.assertEquals; - -public class OptionalLensTest { - - private Lens lens; - - @Before - public void setUp() throws Exception { - lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); - } - - @Test - public void asOptionalWrapsValuesInOptional() { - Lens.Simple> asOptional = OptionalLens.asOptional(); - - assertEquals(Optional.of("foo"), view(asOptional, "foo")); - assertEquals(Optional.empty(), view(asOptional, null)); - assertEquals("bar", set(asOptional, Optional.of("bar"), "foo")); - assertEquals("foo", set(asOptional, Optional.empty(), "foo")); - } - - @Test - public void liftSLiftsSToOptional() { - assertEquals((Character) '3', view(liftS(lens, "3"), Optional.empty())); - } - - @Test - public void liftTLiftsTToOptional() { - assertEquals(Optional.of(true), set(liftT(lens), 3, "123")); - } - - @Test - public void liftALiftsAToOptional() { - assertEquals(Optional.of('1'), view(liftA(lens), "123")); - } - - @Test - public void liftBLiftsBToOptional() { - assertEquals(true, set(OptionalLens.liftB(lens, 1), Optional.empty(), "1")); - } - - @Test - public void unLiftSPullsSOutOfOptional() { - Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); - assertEquals(Optional.of('f'), view(OptionalLens.unLiftS(liftedToOptional), "f")); - } - - @Test - public void unLiftTPullsTOutOfOptional() { - Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); - assertEquals(true, set(OptionalLens.unLiftT(liftedToOptional, false), Optional.of(3), Optional.of("321"))); - } - - @Test - public void unLiftAPullsAOutOfOptional() { - Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); - assertEquals((Character) '1', view(OptionalLens.unLiftA(liftedToOptional, '4'), Optional.empty())); - } - - @Test - public void unLiftBPullsBOutOfOptional() { - Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); - assertEquals(Optional.of(true), set(OptionalLens.unLiftB(liftedToOptional), 3, Optional.of("321"))); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java b/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java new file mode 100644 index 000000000..42e1eddd3 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java @@ -0,0 +1,35 @@ +package com.jnape.palatable.lambda.matchers; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.functor.builtin.State.state; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static testsupport.matchers.EitherMatcher.isLeftThat; +import static testsupport.matchers.EitherMatcher.isRightThat; +import static testsupport.matchers.StateMatcher.whenEvaluatedWith; +import static testsupport.matchers.StateMatcher.whenExecutedWith; +import static testsupport.matchers.StateMatcher.whenRunWith; + +public class StateMatcherTest { + + @Test + public void whenEvalWithMatcher() { + assertThat(state(right(1)), + whenEvaluatedWith("0", isRightThat(equalTo(1)))); + } + + @Test + public void whenExecWithMatcher() { + assertThat(state(right(1)), + whenExecutedWith(left("0"), isLeftThat(equalTo("0")))); + } + + @Test + public void whenRunWithMatcher() { + assertThat(state(right(1)), + whenRunWith(left("0"), isRightThat(equalTo(1)), isLeftThat(equalTo("0")))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java new file mode 100644 index 000000000..562bb8bb4 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java @@ -0,0 +1,81 @@ +package com.jnape.palatable.lambda.matchers; + +import org.hamcrest.core.IsEqual; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.StateT.gets; +import static com.jnape.palatable.lambda.monad.transformer.builtin.StateT.stateT; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertEquals; +import static testsupport.matchers.EitherMatcher.isLeftThat; +import static testsupport.matchers.EitherMatcher.isRightThat; +import static testsupport.matchers.IOMatcher.yieldsValue; +import static testsupport.matchers.StateTMatcher.whenEvaluatedWith; +import static testsupport.matchers.StateTMatcher.whenExecutedWith; +import static testsupport.matchers.StateTMatcher.whenRunWith; +import static testsupport.matchers.StateTMatcher.whenRunWithBoth; + +public class StateTMatcherTest { + + @Test + public void whenEvaluatedWithMatcher() { + assertThat(stateT(right(1)), + whenEvaluatedWith("0", isRightThat(equalTo(1)))); + } + + @Test + public void whenEvaluatedWithMatcherOnObject() { + assertThat(stateT(right(1)), + whenEvaluatedWith("0", not(equalTo(new Object())))); + } + + @Test + public void whenExecutedWithMatcher() { + assertThat(stateT(right(1)), + whenExecutedWith(left("0"), isRightThat(isLeftThat(equalTo("0"))))); + } + + @Test + public void whenExecutedWithMatcherOnObject() { + assertThat(stateT(right(1)), + whenExecutedWith(left("0"), not(equalTo(new Object())))); + } + + @Test + @SuppressWarnings("RedundantTypeArguments") + public void whenRunWithUsingTwoMatchers() { + assertThat(stateT(right(1)), + whenRunWithBoth(left("0"), + isRightThat(IsEqual.equalTo(1)), + isRightThat(isLeftThat(equalTo("0"))))); + } + + @Test + public void whenRunWithUsingOneTupleMatcher() { + assertThat(stateT(right(1)), + whenRunWith(left("0"), + isRightThat(equalTo(tuple(1, left("0")))))); + } + + @Test + public void whenRunWithUsingOneTupleMatcherOnObject() { + assertThat(stateT(right(1)), + whenRunWith(left("0"), not(equalTo(new Object())))); + } + + @Test + public void onlyRunsStateOnceWithTupleMatcher() { + AtomicInteger count = new AtomicInteger(0); + + assertThat(gets(s -> io(count::incrementAndGet)), whenRunWith(0, yieldsValue(equalTo(tuple(1, 0))))); + assertEquals(1, count.get()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/SafeTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/SafeTTest.java new file mode 100644 index 000000000..9aa64f722 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/SafeTTest.java @@ -0,0 +1,57 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Id; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; +import static com.jnape.palatable.lambda.monad.SafeT.safeT; +import static org.junit.Assert.assertEquals; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class SafeTTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Equivalence, Integer>> testSubject() { + return equivalence(safeT(new Identity<>(1)), SafeT::>runSafeT); + } + + @Test + public void stackSafelyComposesMonadRecs() { + assertEquals(STACK_EXPLODING_NUMBER, + times(STACK_EXPLODING_NUMBER, f -> f.fmap(x -> x + 1), safeT(Id.id())) + .>runSafeT() + .apply(0)); + } + + @Test + public void flatMapStackSafety() { + assertEquals(new Identity<>(STACK_EXPLODING_NUMBER), + times(STACK_EXPLODING_NUMBER, + safeT -> safeT.flatMap(x -> safeT(new Identity<>(x + 1))), + safeT(new Identity<>(0))) + .runSafeT()); + } + + @Test + public void zipStackSafety() { + assertEquals((Integer) (STACK_EXPLODING_NUMBER + 1), + times(STACK_EXPLODING_NUMBER, + safeT -> safeT.zip(safeT(fn1(x -> y -> x + y))), + safeT(Fn1.fn1(x -> x))) + .>runSafeT() + .apply(1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java new file mode 100644 index 000000000..e495fb2fd --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java @@ -0,0 +1,85 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.EitherT.eitherT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.EitherT.liftEitherT; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; +import static testsupport.assertion.MonadErrorAssert.assertLaws; + +@RunWith(Traits.class) +public class EitherTTest { + + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + BifunctorLaws.class, + MonadRecLaws.class}) + public Subjects, String, Integer>> testSubject() { + return subjects(eitherT(new Identity<>(left("foo"))), eitherT(new Identity<>(right(1)))); + } + + @Test + public void lazyZip() { + assertEquals(eitherT(just(right(2))), + eitherT(just(right(1))).lazyZip(lazy(eitherT(just(right(x -> x + 1))))).value()); + assertEquals(eitherT(nothing()), + eitherT(nothing()).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test(timeout = 500) + public void composedZip() { + CountDownLatch latch = new CountDownLatch(2); + IO countdownAndAwait = io(() -> { + latch.countDown(); + latch.await(); + }); + EitherT, Object, Unit> lifted = liftEitherT().apply(countdownAndAwait); + lifted.discardL(lifted) + .>>runEitherT() + .unsafePerformAsyncIO(Executors.newFixedThreadPool(2)) + .join(); + } + + @Test + public void staticPure() { + EitherT, String, Integer> eitherT = EitherT., String>pureEitherT(pureIdentity()) + .apply(1); + assertEquals(eitherT(new Identity<>(right(1))), eitherT); + } + + @Test + public void monadError() { + assertLaws(subjects(eitherT(new Identity<>(right(1))), + eitherT(new Identity<>(left("")))), + "bar", + str -> eitherT(new Identity<>(right(str.length())))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IdentityTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IdentityTTest.java new file mode 100644 index 000000000..07cd9a809 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IdentityTTest.java @@ -0,0 +1,67 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Maybe.pureMaybe; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IdentityT.identityT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IdentityT.liftIdentityT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IdentityT.pureIdentityT; +import static org.junit.Assert.assertEquals; + +@RunWith(Traits.class) +public class IdentityTTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public IdentityT, Integer> testSubject() { + return identityT(just(new Identity<>(1))); + } + + @Test + public void lazyZip() { + assertEquals(identityT(just(new Identity<>(2))), + identityT(just(new Identity<>(1))) + .lazyZip(lazy(identityT(just(new Identity<>(x -> x + 1))))).value()); + assertEquals(identityT(nothing()), + identityT(nothing()).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + IdentityT, Integer> identityT = pureIdentityT(pureMaybe()).apply(1); + assertEquals(identityT(just(new Identity<>(1))), identityT); + } + + @Test(timeout = 500) + public void composedZip() { + CountDownLatch latch = new CountDownLatch(2); + IO countdownAndAwait = io(() -> { + latch.countDown(); + latch.await(); + }); + IdentityT, Unit> lifted = liftIdentityT().apply(countdownAndAwait); + lifted.discardL(lifted) + .>>runIdentityT() + .unsafePerformAsyncIO(Executors.newFixedThreadPool(2)) + .join(); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java new file mode 100644 index 000000000..7c2997ca6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java @@ -0,0 +1,352 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.functor.builtin.Writer; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.LTE.lte; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.functor.builtin.Writer.listen; +import static com.jnape.palatable.lambda.functor.builtin.Writer.pureWriter; +import static com.jnape.palatable.lambda.functor.builtin.Writer.tell; +import static com.jnape.palatable.lambda.functor.builtin.Writer.writer; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.empty; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.iterateT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.liftIterateT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.of; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.pureIterateT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.singleton; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.suspended; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.unfold; +import static com.jnape.palatable.lambda.monoid.builtin.AddAll.addAll; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; +import static testsupport.matchers.IOMatcher.yieldsValue; +import static testsupport.matchers.IterateTMatcher.isEmpty; +import static testsupport.matchers.IterateTMatcher.iterates; +import static testsupport.matchers.IterateTMatcher.iteratesAll; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class IterateTTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Subjects, Integer>>> testSubjects() { + Fn1, ?>, Object> toCollection = iterateT -> iterateT + ., Identity>>fold( + (as, a) -> { + as.add(a); + return new Identity<>(as); + }, + new Identity<>(new ArrayList<>())) + .runIdentity(); + return subjects(equivalence(empty(pureIdentity()), toCollection), + equivalence(singleton(new Identity<>(0)), toCollection), + equivalence(IterateT., Integer>empty(pureIdentity()).cons(new Identity<>(1)), + toCollection), + equivalence(IterateT., Integer>empty(pureIdentity()).snoc(new Identity<>(1)), + toCollection), + equivalence(singleton(new Identity<>(0)).concat(singleton(new Identity<>(1))), + toCollection), + equivalence(unfold(x -> new Identity<>(x <= 100 ? just(tuple(x, x + 1)) : nothing()), + new Identity<>(0)), + toCollection) + ); + } + + @Test + public void emptyHasNoElements() { + assertEquals(new Identity<>(nothing()), + IterateT., Integer>empty(pureIdentity()) + ., Integer>>>>>runIterateT()); + } + + @Test + public void singletonHasOneElement() { + assertThat(singleton(new Identity<>(1)), iterates(1)); + } + + @Test + public void unfolding() { + assertThat(unfold(x -> new Identity<>(just(x).filter(lte(3)).fmap(y -> tuple(y, y + 1))), new Identity<>(1)), + iteratesAll(asList(1, 2, 3))); + } + + @Test + public void consAddsElementToFront() { + assertThat(singleton(new Identity<>(1)).cons(new Identity<>(0)), iterates(0, 1)); + } + + @Test + public void snocAddsElementToBack() { + assertThat(singleton(new Identity<>(1)).snoc(new Identity<>(2)), iterates(1, 2)); + } + + @Test + public void concatsTwoIterateTs() { + IterateT, Integer> front = singleton(new Identity<>(0)).snoc(new Identity<>(1)); + IterateT, Integer> back = singleton(new Identity<>(2)).snoc(new Identity<>(3)); + + assertThat(front.concat(back), iterates(0, 1, 2, 3)); + assertThat(IterateT., Integer>empty(pureIdentity()).concat(back), iterates(2, 3)); + assertThat(front.concat(empty(pureIdentity())), iterates(0, 1)); + assertThat(IterateT., Integer>empty(pureIdentity()).concat(empty(pureIdentity())), isEmpty()); + assertThat(singleton(new Identity<>(1)) + .concat(unfold(x -> new Identity<>(nothing()), new Identity<>(0))) + .concat(singleton(new Identity<>(2))), + iterates(1, 2)); + } + + @Test + public void ofIteratesElements() { + assertEquals(tuple(6, asList(1, 2, 3)), + IterateT., ?>, Integer>of(listen(1), listen(2), listen(3)) + ., Integer>>fold( + (x, y) -> writer(tuple(x + y, singletonList(y))), listen(0)) + .runWriter(addAll(ArrayList::new))); + } + + @Test + public void fromIterator() { + IterateT, Integer> it = IterateT.fromIterator(asList(1, 2, 3).iterator()); + assertThat(it., IO>>toCollection(ArrayList::new), + yieldsValue(equalTo(asList(1, 2, 3)))); + assertThat(it., IO>>toCollection(ArrayList::new), + yieldsValue(equalTo(emptyList()))); + } + + @Test + public void fold() { + assertEquals(tuple(6, asList(1, 2, 3)), + IterateT., ?>, Integer>of(listen(1), listen(2), listen(3)) + ., Integer>>fold( + (x, y) -> writer(tuple(x + y, singletonList(y))), listen(0)) + .runWriter(addAll(ArrayList::new))); + } + + @Test + public void foldCut() { + assertEquals(tuple(3, "012"), + IterateT.of(writer(tuple(1, "1")), + writer(tuple(2, "2")), + writer(tuple(3, "3"))) + .>foldCut( + (x, y) -> listen(y == 2 ? terminate(x + y) : recurse(x + y)), + writer(tuple(0, "0"))) + .runWriter(join())); + } + + @Test + public void zipUsesCartesianProduct() { + assertThat(IterateT.of(new Identity<>(1), new Identity<>(2), new Identity<>(3)) + .zip(IterateT.of(new Identity<>(x -> x + 1), new Identity<>(x -> x - 1))), + iterates(2, 3, 4, 0, 1, 2)); + } + + @Test(timeout = 1000) + public void zipsInParallel() { + CountDownLatch latch = new CountDownLatch(2); + singleton(io(() -> { + latch.countDown(); + latch.await(); + return 0; + })).zip(singleton(io(() -> { + latch.countDown(); + latch.await(); + return x -> x + 1; + })))., Integer>>>>>runIterateT() + .unsafePerformAsyncIO() + .join(); + } + + @Test + public void toCollection() { + assertEquals(asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), + unfold(x -> new Identity<>(x <= 10 ? just(tuple(x, x + 1)) : nothing()), new Identity<>(1)) + ., Identity>>toCollection(ArrayList::new) + .runIdentity()); + } + + @Test + public void forEach() { + assertEquals(tuple(UNIT, asList(1, 2, 3)), + IterateT., ?>, Integer>empty(pureWriter()) + .cons(listen(3)) + .cons(listen(2)) + .cons(listen(1)) + ., Unit>>forEach(x -> tell(singletonList(x))) + .runWriter(addAll(ArrayList::new))); + } + + @Test + public void foldLargeNumberOfElements() { + IterateT, Integer> largeIterateT = times(STACK_EXPLODING_NUMBER, + it -> it.cons(new Identity<>(1)), + empty(pureIdentity())); + assertEquals(new Identity<>(STACK_EXPLODING_NUMBER), + largeIterateT.fold((x, y) -> new Identity<>(x + y), new Identity<>(0))); + } + + @Test + public void stackSafetyForStrictMonads() { + IterateT, Integer> hugeStrictIterateT = + unfold(x -> new Identity<>(x <= STACK_EXPLODING_NUMBER ? just(tuple(x, x + 1)) : nothing()), + new Identity<>(1)); + Identity fold = hugeStrictIterateT.fold((x, y) -> new Identity<>(x + y), new Identity<>(0)); + assertEquals(new Identity<>(1250025000), fold); + } + + @Test + public void stackSafetyForNonStrictMonads() { + IterateT, Integer> hugeNonStrictIterateT = + unfold(x -> lazy(() -> x <= 50_000 ? just(tuple(x, x + 1)) : nothing()), lazy(0)); + Lazy fold = hugeNonStrictIterateT.fold((x, y) -> lazy(() -> x + y), lazy(0)); + assertEquals((Integer) 1250025000, fold.value()); + } + + @Test + public void concatIsStackSafe() { + IterateT, Integer> bigIterateT = times(10_000, xs -> xs.concat(singleton(new Identity<>(1))), + singleton(new Identity<>(0))); + assertEquals(new Identity<>(10_000), + bigIterateT.fold((x, y) -> new Identity<>(x + y), new Identity<>(0))); + } + + @Test + public void staticPure() { + assertEquals(new Identity<>(singletonList(1)), + pureIterateT(pureIdentity()) + ., Integer>>apply(1) + ., Identity>>toCollection(ArrayList::new)); + } + + @Test + public void staticLift() { + assertEquals(new Identity<>(singletonList(1)), + liftIterateT() + ., IterateT, Integer>>apply(new Identity<>(1)) + ., Identity>>toCollection(ArrayList::new)); + } + + @Test + public void trampolineMRecursesBreadth() { + IterateT, Integer> firstFour = of(new Identity<>(1), new Identity<>(2), new Identity<>(3), new Identity<>(4)); + IterateT, Integer> trampolined = firstFour + .trampolineM(x -> (x % 3 == 0 && (x < 30)) + ? of(new Identity<>(terminate(x + 10)), new Identity<>(recurse(x + 11)), new Identity<>(recurse(x + 12)), new Identity<>(recurse(x + 13))) + : singleton(new Identity<>(terminate(x)))); + assertThat(trampolined, iterates(1, 2, 13, 14, 25, 26, 37, 38, 39, 40, 28, 16, 4)); + } + + @Test + public void flatMapToEmptyStackSafety() { + assertEquals(new Identity<>(UNIT), + unfold(x -> new Identity<>(x <= STACK_EXPLODING_NUMBER ? just(tuple(x, x + 1)) : nothing()), + new Identity<>(1)) + .flatMap(constantly(iterateT(new Identity<>(nothing())))) + .forEach(constantly(new Identity<>(UNIT)))); + + assertEquals((Integer) 1_250_025_000, + unfold(x -> listen(x <= STACK_EXPLODING_NUMBER ? just(tuple(x, x + 1)) : nothing()), + Writer.listen(1)) + .flatMap(x -> iterateT(writer(tuple(nothing(), x)))) + .>forEach(constantly(listen(UNIT))) + .runWriter(Monoid.monoid(Integer::sum, 0)) + ._2()); + } + + @Test + public void flatMapCostsNoMoreEffortThanRequiredToYieldFirstValue() { + AtomicInteger flatMapCost = new AtomicInteger(0); + AtomicInteger unfoldCost = new AtomicInteger(0); + assertEquals(just(1), + unfold(x -> { + unfoldCost.incrementAndGet(); + return new Identity<>(x <= 10 ? just(tuple(x, x + 1)) : nothing()); + }, + new Identity<>(1)) + .flatMap(x -> { + flatMapCost.incrementAndGet(); + return singleton(new Identity<>(x)); + }) + ., Integer>>>>>runIterateT() + .runIdentity() + .fmap(Tuple2::_1)); + assertEquals(1, flatMapCost.get()); + assertEquals(1, unfoldCost.get()); + } + + @Test + public void runStep() { + assertEquals(new Identity<>(nothing()), + IterateT., Integer>empty(pureIdentity()) + ., IterateT, Integer>>>>>runStep()); + + Tuple2, IterateT, Integer>> singletonStep = + singleton(new Identity<>(1)) + ., IterateT, Integer>>>>>runStep() + .runIdentity() + .orElseThrow(AssertionError::new); + assertEquals(just(1), singletonStep._1()); + assertThat(singletonStep._2(), isEmpty()); + + Tuple2, IterateT, Integer>> emptySuspendedStep = + IterateT., Integer>suspended(() -> new Identity<>(nothing()), + pureIdentity()) + ., IterateT, Integer>>>>>runStep() + .runIdentity() + .orElseThrow(AssertionError::new); + + assertEquals(nothing(), emptySuspendedStep._1()); + assertThat(emptySuspendedStep._2(), isEmpty()); + + Tuple2, IterateT, Integer>> nonEmptySuspendedStep = + suspended(() -> new Identity<>(just(tuple(1, empty(pureIdentity())))), + pureIdentity()) + ., IterateT, Integer>>>>>runStep() + .runIdentity() + .orElseThrow(AssertionError::new); + + assertEquals(just(1), nonEmptySuspendedStep._1()); + assertThat(nonEmptySuspendedStep._2(), isEmpty()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/LazyTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/LazyTTest.java new file mode 100644 index 000000000..3db843ed1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/LazyTTest.java @@ -0,0 +1,68 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.LazyT.lazyT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.LazyT.liftLazyT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.LazyT.pureLazyT; +import static org.junit.Assert.assertEquals; + +@RunWith(Traits.class) +public class LazyTTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public LazyT, Integer> testSubject() { + return lazyT(just(lazy(1))); + } + + @Test + public void lazyZip() { + assertEquals(lazyT(just(lazy(2))), + lazyT(just(lazy(1))) + .lazyZip(lazy(lazyT(just(lazy(x -> x + 1))))).value()); + assertEquals(lazyT(nothing()), + lazyT(nothing()).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + LazyT, Integer> lazyT = pureLazyT(pureIdentity()).apply(1); + assertEquals(lazyT(new Identity<>(lazy(1))), lazyT); + } + + @Test(timeout = 500) + public void composedZip() { + CountDownLatch latch = new CountDownLatch(2); + IO countdownAndAwait = io(() -> { + latch.countDown(); + latch.await(); + }); + LazyT, Unit> lifted = liftLazyT().apply(countdownAndAwait); + lifted.discardL(lifted) + .>>runLazyT() + .unsafePerformAsyncIO(Executors.newFixedThreadPool(2)) + .join(); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java new file mode 100644 index 000000000..1a81e1c57 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java @@ -0,0 +1,103 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.builtin.fn2.GT.gt; +import static com.jnape.palatable.lambda.functions.builtin.fn2.LT.lt; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.liftMaybeT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.maybeT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.pureMaybeT; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; +import static testsupport.assertion.MonadErrorAssert.assertLaws; + +@RunWith(Traits.class) +public class MaybeTTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Subjects, Integer>> testSubject() { + return subjects(maybeT(right(just(1))), + maybeT(right(nothing())), + maybeT(left("foo"))); + } + + @Test + public void monadError() { + assertLaws(subjects(maybeT(new Identity<>(nothing())), maybeT(new Identity<>(just(1)))), + UNIT, + e -> maybeT(new Identity<>(just(2)))); + } + + @Test + public void lazyZip() { + assertEquals(maybeT(right(just(2))), + maybeT(right(just(1))).lazyZip(lazy(maybeT(right(just(x -> x + 1))))).value()); + assertEquals(maybeT(left("foo")), + maybeT(left("foo")).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + MaybeT, Integer> maybeT = pureMaybeT(pureIdentity()).apply(1); + assertEquals(maybeT(new Identity<>(just(1))), maybeT); + } + + @Test(timeout = 500) + public void composedZip() { + CountDownLatch latch = new CountDownLatch(2); + IO countdownAndAwait = io(() -> { + latch.countDown(); + latch.await(); + }); + MaybeT, Unit> lifted = liftMaybeT().apply(countdownAndAwait); + lifted.discardL(lifted) + .>>runMaybeT() + .unsafePerformAsyncIO(Executors.newFixedThreadPool(2)) + .join(); + } + + @Test + public void filter() { + MaybeT, Integer> maybeT = pureMaybeT(pureIdentity()).apply(1); + assertEquals(maybeT(new Identity<>(just(1))), maybeT.filter(gt(0))); + assertEquals(maybeT(new Identity<>(nothing())), maybeT.filter(lt(0))); + } + + @Test + public void orSelectsFirstPresentValueInsideEffect() { + assertEquals(maybeT(new Identity<>(just(1))), + maybeT(new Identity<>(just(1))).or(maybeT(new Identity<>(nothing())))); + + assertEquals(maybeT(new Identity<>(just(1))), + maybeT(new Identity<>(nothing())).or(maybeT(new Identity<>(just(1))))); + + assertEquals(maybeT(new Identity<>(just(1))), + maybeT(new Identity<>(just(1))).or(maybeT(new Identity<>(just(2))))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java new file mode 100644 index 000000000..983e7113a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java @@ -0,0 +1,107 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.*; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.ReaderT.readerT; +import static org.junit.Assert.assertEquals; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class ReaderTTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadReaderLaws.class, MonadRecLaws.class}) + public Equivalence, Integer>> testSubject() { + return equivalence(readerT(Identity::new), readerT -> readerT.runReaderT(1)); + } + + @Test + public void profunctor() { + assertEquals(new Identity<>(4), + ReaderT., Integer>readerT(Identity::new) + .diMap(String::length, x -> x + 1) + .runReaderT("123")); + } + + @Test + public void local() { + assertEquals(new Identity<>(2), + ReaderT., Integer>readerT(Identity::new) + .local(x -> x + 1) + .runReaderT(1)); + } + + @Test + public void mapReaderT() { + assertEquals(just(3), + ReaderT., String>readerT(Identity::new) + ., Maybe, Integer>mapReaderT(id -> just(id.runIdentity().length())) + .runReaderT("foo")); + } + + @Test + public void andComposesLeftToRight() { + ReaderT, Float> intToFloat = readerT(x -> new Identity<>(x.floatValue())); + ReaderT, Double> floatToDouble = readerT(f -> new Identity<>(f.doubleValue())); + + assertEquals(new Identity<>(1.), + intToFloat.and(floatToDouble).runReaderT(1)); + } + + @Test + public void staticPure() { + ReaderT, Integer> readerT = + ReaderT.>pureReaderT(pureIdentity()).apply(1); + assertEquals(new Identity<>(1), readerT.runReaderT("foo")); + } + + @Test(timeout = 500) + public void composedZip() { + CountDownLatch latch = new CountDownLatch(2); + IO countdownAndAwait = io(() -> { + latch.countDown(); + latch.await(); + }); + ReaderT, Unit> lifted = ReaderT.liftReaderT().apply(countdownAndAwait); + lifted.discardL(lifted) + .>runReaderT(UNIT) + .unsafePerformAsyncIO(Executors.newFixedThreadPool(2)) + .join(); + } + + @Test + public void fmapInteractions() { + AtomicInteger invocations = new AtomicInteger(0); + ReaderT, Integer> readerT = readerT(i -> { + invocations.incrementAndGet(); + return new Identity<>(i); + }); + + Fn1 plusOne = x -> x + 1; + readerT.fmap(plusOne).fmap(plusOne).fmap(plusOne).runReaderT(0); + assertEquals(1, invocations.get()); + } + + @Test + public void askRetrievesInput() { + assertEquals(new Identity<>(1), + ReaderT.>ask(pureIdentity()) + .>runReaderT(1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java new file mode 100644 index 000000000..785165952 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java @@ -0,0 +1,139 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadReaderLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.MonadWriterLaws; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.lenses.ListLens.elementAt; +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static testsupport.matchers.StateTMatcher.whenEvaluated; +import static testsupport.matchers.StateTMatcher.whenExecuted; +import static testsupport.matchers.StateTMatcher.whenRun; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class StateTTest { + + @TestTraits({FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + MonadReaderLaws.class, + MonadWriterLaws.class}) + public Equivalence, Integer>> testReader() { + return equivalence(StateT.gets(s -> new Identity<>(s.length())), s -> s.runStateT("foo")); + } + + @Test + public void evalAndExec() { + StateT, Integer> stateT = + StateT.stateT(str -> new Identity<>(tuple(str.length(), str + "_"))); + + assertThat(stateT, whenExecuted("_", new Identity<>("__"))); + assertThat(stateT, whenEvaluated("_", new Identity<>(1))); + } + + @Test + public void mapStateT() { + assertThat(StateT., Integer>stateT(str -> new Identity<>(tuple(str.length(), str + "_"))) + .mapStateT(id -> id.>>coerce() + .runIdentity() + .into((x, str) -> just(tuple(x + 1, str.toUpperCase())))), + whenRun("abc", just(tuple(4, "ABC_")))); + } + + @Test + public void zipping() { + assertThat( + StateT., Identity>modify(s -> new Identity<>(set(elementAt(s.size()), just("one"), s))) + .discardL(StateT.modify(s -> new Identity<>(set(elementAt(s.size()), just("two"), s)))), + whenRun(new ArrayList<>(), new Identity<>(tuple(UNIT, asList("one", "two"))))); + } + + @Test + public void withStateT() { + assertThat(StateT., Integer>stateT(str -> new Identity<>(tuple(str.length(), str + "_"))) + .withStateT(str -> new Identity<>(str.toUpperCase())), + whenRun("abc", new Identity<>(tuple(3, "ABC_")))); + } + + @Test + public void get() { + assertThat(StateT.get(pureIdentity()), + whenRun("state", new Identity<>(tuple("state", "state")))); + } + + @Test + public void gets() { + assertThat(StateT.gets(s -> new Identity<>(s.length())), + whenRun("state", new Identity<>(tuple(5, "state")))); + } + + @Test + public void put() { + assertThat(StateT.put(new Identity<>(1)), + whenRun(0, new Identity<>(tuple(UNIT, 1)))); + } + + @Test + public void modify() { + assertThat(StateT.modify(x -> new Identity<>(x + 1)), + whenRun(0, new Identity<>(tuple(UNIT, 1)))); + } + + @Test + public void stateT() { + assertThat(StateT.stateT(new Identity<>(0)), + whenRun("_", new Identity<>(tuple(0, "_")))); + assertThat(StateT.stateT(s -> new Identity<>(tuple(s.length(), s + "1"))), + whenRun("_", new Identity<>(tuple(1, "_1")))); + } + + @Test + public void staticPure() { + assertThat(StateT.>pureStateT(pureIdentity()).apply(1), + whenRun("foo", new Identity<>(tuple(1, "foo")))); + } + + @Test + public void staticLift() { + assertThat(StateT.liftStateT().apply(new Identity<>(1)), + whenRun("foo", new Identity<>(tuple(1, "foo")))); + } + + @Test + public void fmapInteractions() { + AtomicInteger invocations = new AtomicInteger(0); + StateT., Integer>gets(x -> { + invocations.incrementAndGet(); + return new Identity<>(x); + }) + .fmap(id()) + .fmap(id()) + .fmap(id()) + .>>runStateT(0); + assertEquals(1, invocations.get()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterTTest.java new file mode 100644 index 000000000..c1caa0527 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterTTest.java @@ -0,0 +1,100 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.MonadWriterLaws; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.WriterT.writerT; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; +import static com.jnape.palatable.lambda.monoid.builtin.Trivial.trivial; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.WriterTMatcher.whenEvaluatedWith; +import static testsupport.matchers.WriterTMatcher.whenExecutedWith; +import static testsupport.matchers.WriterTMatcher.whenRunWith; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class WriterTTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadWriterLaws.class, MonadRecLaws.class}) + public Equivalence, Integer>> testSubject() { + return equivalence(writerT(new Identity<>(tuple(2, ""))), writerT -> writerT.runWriterT(join())); + } + + @Test + public void accumulationUsesProvidedMonoid() { + assertThat(writerT(new Identity<>(tuple(1, "foo"))) + .discardR(WriterT.tell(new Identity<>("bar"))) + .flatMap(x -> writerT(new Identity<>(tuple(x + 1, "baz")))), + whenRunWith(join(), equalTo(new Identity<>(tuple(2, "foobarbaz"))))); + } + + @Test + public void eval() { + assertThat(writerT(new Identity<>(tuple(1, "foo"))), + whenEvaluatedWith(join(), equalTo(new Identity<>(1)))); + } + + @Test + public void exec() { + assertThat(writerT(new Identity<>(tuple(1, "foo"))), + whenExecutedWith(join(), equalTo(new Identity<>("foo")))); + } + + @Test + public void tell() { + assertThat(WriterT.tell(new Identity<>("")), + whenRunWith(join(), equalTo(new Identity<>(tuple(UNIT, ""))))); + } + + @Test + public void listen() { + assertThat(WriterT.listen(new Identity<>(1)), + whenRunWith(join(), equalTo(new Identity<>(tuple(1, ""))))); + } + + @Test + public void staticPure() { + assertThat(WriterT.>pureWriterT(pureIdentity()).apply(1), + whenRunWith(join(), equalTo(new Identity<>(tuple(1, ""))))); + } + + @Test + public void staticLift() { + assertThat(WriterT.liftWriterT().apply(new Identity<>(1)), + whenRunWith(join(), equalTo(new Identity<>(tuple(1, ""))))); + } + + @Test(timeout = 500) + public void composedZip() { + CountDownLatch latch = new CountDownLatch(2); + IO countdownAndAwait = io(() -> { + latch.countDown(); + latch.await(); + }); + WriterT, Unit> lifted = WriterT.liftWriterT().apply(countdownAndAwait); + lifted.discardL(lifted) + .>>runWriterT(trivial()) + .unsafePerformAsyncIO(Executors.newFixedThreadPool(2)) + .join(); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java index 730228769..135d87195 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java @@ -1,33 +1,43 @@ package com.jnape.palatable.lambda.monoid; -import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import org.junit.Test; import java.util.List; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.monoid.Monoid.monoid; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static testsupport.functions.ExplainFold.explainFold; public class MonoidTest { @Test public void reduceLeft() { - Monoid sum = monoid((x, y) -> x + y, 0); + Monoid sum = monoid(Integer::sum, 0); assertEquals((Integer) 6, sum.reduceLeft(asList(1, 2, 3))); } @Test public void reduceRight() { - Monoid sum = monoid((x, y) -> x + y, 0); + Monoid sum = monoid(Integer::sum, 0); assertEquals((Integer) 6, sum.reduceRight(asList(1, 2, 3))); } + @Test + public void foldRight() { + Lazy lazyString = monoid(explainFold()::apply, "0") + .foldRight("4", asList("1", "2", "3")); + assertEquals("(1 + (2 + (3 + (4 + 0))))", lazyString.value()); + } + @Test public void foldMap() { - Monoid sum = monoid((x, y) -> x + y, 0); - List> optionalInts = asList(Optional.of(1), Optional.of(2), Optional.empty(), Optional.of(3), Optional.empty()); - assertEquals((Integer) 6, sum.foldMap(optX -> optX.orElse(0), optionalInts)); + Monoid sum = monoid(Integer::sum, 0); + List> maybeInts = asList(just(1), just(2), nothing(), just(3), nothing()); + assertEquals((Integer) 6, sum.foldMap(maybeX -> maybeX.orElse(0), maybeInts)); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AddAllTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AddAllTest.java new file mode 100644 index 000000000..c1aaf214f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AddAllTest.java @@ -0,0 +1,26 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.monoid.Monoid; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static com.jnape.palatable.lambda.monoid.builtin.AddAll.addAll; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; + +public class AddAllTest { + + @Test + @SuppressWarnings("serial") + public void monoid() { + Monoid> addAll = addAll(HashSet::new); + + assertEquals(new HashSet<>(), addAll.identity()); + assertEquals(new HashSet() {{ + add(1); + add(2); + }}, addAll.apply(new HashSet<>(singleton(1)), new HashSet<>(singleton(2)))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AndTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AndTest.java index 8ae51c840..25b10aca9 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AndTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AndTest.java @@ -2,6 +2,9 @@ import org.junit.Test; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; import static com.jnape.palatable.lambda.monoid.builtin.And.and; import static org.junit.Assert.assertEquals; @@ -20,4 +23,15 @@ public void monoid() { assertEquals(false, and.apply(true, false)); assertEquals(false, and.apply(false, false)); } + + @Test(timeout = 500) + public void shortCircuiting() { + Iterable bools = cons(false, repeat(true)); + And and = and(); + + assertEquals(false, and.foldLeft(false, bools)); + assertEquals(false, and.foldLeft(true, bools)); + assertEquals(false, and.reduceLeft(bools)); + assertEquals(false, and.foldMap(id(), bools)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java index 1e0f36e9e..5c42de619 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java @@ -5,14 +5,15 @@ import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.monoid.builtin.Collapse.collapse; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; import static org.junit.Assert.assertEquals; public class CollapseTest { @Test public void monoid() { - Monoid join = Monoid.monoid((x, y) -> x + y, ""); - Monoid add = Monoid.monoid((x, y) -> x + y, 0); + Monoid join = join(); + Monoid add = Monoid.monoid(Integer::sum, 0); Collapse collapse = collapse(); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java new file mode 100644 index 000000000..faca56af2 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.monoid.Monoid; +import org.junit.Test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static com.jnape.palatable.lambda.monoid.builtin.Compose.compose; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ComposeTest { + + @Test + public void monoid() throws ExecutionException, InterruptedException { + Monoid addition = Monoid.monoid(Integer::sum, 0); + + CompletableFuture failedFuture = new CompletableFuture() {{ + completeExceptionally(new RuntimeException()); + }}; + + assertEquals((Integer) 0, compose(addition).identity().get()); + assertEquals((Integer) 3, compose(addition, completedFuture(1), completedFuture(2)).get()); + assertTrue(compose(addition, failedFuture, completedFuture(2)).isCompletedExceptionally()); + assertTrue(compose(addition, completedFuture(1), failedFuture).isCompletedExceptionally()); + assertTrue(compose(addition, failedFuture, failedFuture).isCompletedExceptionally()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java index f48e628a2..a0f89e394 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java @@ -1,25 +1,19 @@ package com.jnape.palatable.lambda.monoid.builtin; -import com.jnape.palatable.lambda.monoid.Monoid; import org.junit.Test; -import java.util.HashSet; -import java.util.Set; - import static com.jnape.palatable.lambda.monoid.builtin.Concat.concat; +import static java.util.Arrays.asList; import static java.util.Collections.singleton; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; public class ConcatTest { @Test public void monoid() { - Monoid> concat = concat(HashSet::new); - - assertEquals(new HashSet<>(), concat.identity()); - assertEquals(new HashSet() {{ - add(1); - add(2); - }}, concat.apply(new HashSet<>(singleton(1)), new HashSet<>(singleton(2)))); + assertThat(concat().identity(), isEmpty()); + assertThat(concat(asList(1, 2), singleton(3)), iterates(1, 2, 3)); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/EndoKTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/EndoKTest.java new file mode 100644 index 000000000..64e51f86e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/EndoKTest.java @@ -0,0 +1,34 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.monoid.Monoid; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.monoid.builtin.EndoK.endoK; +import static org.junit.Assert.assertEquals; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; + +public class EndoKTest { + + @Test + public void identity() { + Monoid>> endoK = endoK(pureIdentity()); + assertEquals(new Identity<>(1), endoK.identity().apply(1)); + } + + @Test + public void monoid() { + Monoid>> endoK = endoK(pureIdentity()); + assertEquals(new Identity<>(3), + endoK.apply(x -> new Identity<>(x + 1), x -> new Identity<>(x + 2)).apply(0)); + } + + @Test + public void stackSafe() { + Monoid>> endoK = endoK(pureIdentity()); + assertEquals(new Identity<>(0), endoK.reduceLeft(replicate(STACK_EXPLODING_NUMBER, Identity::new)).apply(0)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java index 149dd686d..4b5ee5960 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java @@ -1,9 +1,12 @@ package com.jnape.palatable.lambda.monoid.builtin; +import com.jnape.palatable.lambda.adt.Maybe; import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.monoid.builtin.First.first; import static org.junit.Assert.assertEquals; @@ -11,15 +14,26 @@ public class FirstTest { @Test public void identity() { - assertEquals(Optional.empty(), first().identity()); + assertEquals(nothing(), first().identity()); } @Test public void monoid() { First first = first(); - assertEquals(Optional.of(1), first.apply(Optional.of(1), Optional.of(2))); - assertEquals(Optional.of(1), first.apply(Optional.of(1), Optional.empty())); - assertEquals(Optional.of(2), first.apply(Optional.empty(), Optional.of(2))); - assertEquals(Optional.empty(), first.apply(Optional.empty(), Optional.empty())); + assertEquals(just(1), first.apply(just(1), just(2))); + assertEquals(just(1), first.apply(just(1), nothing())); + assertEquals(just(2), first.apply(nothing(), just(2))); + assertEquals(nothing(), first.apply(nothing(), nothing())); + } + + @Test(timeout = 500) + public void shortCircuiting() { + Iterable> maybeInts = repeat(just(1)); + First first = First.first(); + + assertEquals(just(1), first.foldLeft(nothing(), maybeInts)); + assertEquals(just(1), first.foldLeft(just(1), maybeInts)); + assertEquals(just(1), first.reduceLeft(maybeInts)); + assertEquals(just(1), first.foldMap(id(), maybeInts)); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LastTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LastTest.java index 339c6204c..7f263b2c5 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LastTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LastTest.java @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.monoid.builtin.Last.last; import static org.junit.Assert.assertEquals; @@ -11,15 +11,15 @@ public class LastTest { @Test public void identity() { - assertEquals(Optional.empty(), last().identity()); + assertEquals(nothing(), last().identity()); } @Test public void monoid() { Last last = last(); - assertEquals(Optional.of(2), last.apply(Optional.of(1), Optional.of(2))); - assertEquals(Optional.of(2), last.apply(Optional.empty(), Optional.of(2))); - assertEquals(Optional.of(1), last.apply(Optional.of(1), Optional.empty())); - assertEquals(Optional.empty(), last.apply(Optional.empty(), Optional.empty())); + assertEquals(just(2), last.apply(just(1), just(2))); + assertEquals(just(2), last.apply(nothing(), just(2))); + assertEquals(just(1), last.apply(just(1), nothing())); + assertEquals(nothing(), last.apply(nothing(), nothing())); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java index dc6692ed0..24426ca71 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java @@ -13,7 +13,7 @@ public class LeftAnyTest { @Test public void monoid() { LeftAny leftAny = leftAny(); - Monoid join = Monoid.monoid((x, y) -> x + y, ""); + Monoid join = Monoid.monoid((x, y) -> x + y, ""); assertEquals(left(""), leftAny.apply(join).identity()); assertEquals(left("foo"), leftAny.apply(join).apply(left("foo"), right(1))); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java new file mode 100644 index 000000000..0bf2d978b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.singletonHMap; +import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; +import static com.jnape.palatable.lambda.monoid.builtin.MergeHMaps.mergeHMaps; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class MergeHMapsTest { + + @Test + public void allKeysAccountedFor() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + MergeHMaps mergeHMaps = mergeHMaps().key(stringKey, join()); + + assertEquals(emptyHMap(), mergeHMaps.apply(emptyHMap(), emptyHMap())); + assertEquals(singletonHMap(stringKey, "foo"), + mergeHMaps.apply(singletonHMap(stringKey, "foo"), emptyHMap())); + assertEquals(singletonHMap(stringKey, "foobar"), + mergeHMaps.apply(singletonHMap(stringKey, "foo"), + singletonHMap(stringKey, "bar"))); + } + + @Test + public void unaccountedForKeyUsesLastByDefault() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + + assertEquals(singletonHMap(stringKey, "foo"), + mergeHMaps().apply(singletonHMap(stringKey, "foo"), emptyHMap())); + assertEquals(singletonHMap(stringKey, "bar"), + mergeHMaps().apply(emptyHMap(), singletonHMap(stringKey, "bar"))); + assertEquals(singletonHMap(stringKey, "bar"), + mergeHMaps().apply(singletonHMap(stringKey, "foo"), singletonHMap(stringKey, "bar"))); + } + + @Test + public void sparseKeysAcrossMaps() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey.Simple intKey = typeSafeKey(); + TypeSafeKey.Simple boolKey = typeSafeKey(); + + MergeHMaps mergeHMaps = mergeHMaps() + .key(stringKey, join()) + .key(intKey, Integer::sum); + + assertEquals(hMap(stringKey, "foobar", + intKey, 3, + boolKey, false), + mergeHMaps.reduceLeft(asList(singletonHMap(stringKey, "foo"), + singletonHMap(intKey, 1), + singletonHMap(boolKey, true), + hMap(stringKey, "bar", + intKey, 2, + boolKey, false)))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java new file mode 100644 index 000000000..fe27f4833 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java @@ -0,0 +1,44 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToMap.toMap; +import static com.jnape.palatable.lambda.monoid.builtin.MergeMaps.mergeMaps; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class MergeMapsTest { + private static final Semigroup ADD = Integer::sum; + + private Monoid> merge; + + @Before + public void setUp() { + merge = mergeMaps(HashMap::new, ADD); + } + + @Test + public void identity() { + assertTrue(merge.identity().isEmpty()); + } + + @Test + public void monoid() { + assertEquals(singletonMap("foo", 1), merge.apply(emptyMap(), singletonMap("foo", 1))); + assertEquals(singletonMap("foo", 1), merge.apply(singletonMap("foo", 1), emptyMap())); + assertEquals(singletonMap("foo", 2), + merge.apply(singletonMap("foo", 1), singletonMap("foo", 1))); + assertEquals(toMap(HashMap::new, asList(tuple("foo", 1), tuple("bar", 1))), + merge.apply(singletonMap("foo", 1), singletonMap("bar", 1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeTest.java index 2a7fd462d..d64e26542 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeTest.java @@ -19,7 +19,7 @@ public class MergeTest { private Monoid> merge; @Before - public void setUp() throws Exception { + public void setUp() { merge = merge(JOIN, ADD); } diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java index 336a98e4d..0e22e20bd 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java @@ -2,6 +2,9 @@ import org.junit.Test; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; import static com.jnape.palatable.lambda.monoid.builtin.Or.or; import static org.junit.Assert.assertEquals; @@ -20,4 +23,15 @@ public void monoid() { assertEquals(true, or.apply(false, true)); assertEquals(false, or.apply(false, false)); } + + @Test(timeout = 500) + public void shortCircuiting() { + Iterable bools = cons(true, repeat(false)); + Or or = or(); + + assertEquals(true, or.foldLeft(false, bools)); + assertEquals(true, or.foldLeft(true, bools)); + assertEquals(true, or.reduceLeft(bools)); + assertEquals(true, or.foldMap(id(), bools)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java index 058cd82ce..49eca1df6 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java @@ -3,8 +3,8 @@ import com.jnape.palatable.lambda.semigroup.Semigroup; import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.monoid.builtin.Present.present; import static org.junit.Assert.assertEquals; @@ -12,12 +12,12 @@ public class PresentTest { @Test public void monoid() { - Present present = present(); - Semigroup addition = (x, y) -> x + y; + Present present = present(); + Semigroup addition = Integer::sum; - assertEquals(Optional.of(3), present.apply(addition, Optional.of(1), Optional.of(2))); - assertEquals(Optional.of(1), present.apply(addition, Optional.empty(), Optional.of(1))); - assertEquals(Optional.of(1), present.apply(addition, Optional.of(1), Optional.empty())); - assertEquals(Optional.empty(), present.apply(addition, Optional.empty(), Optional.empty())); + assertEquals(just(3), present.apply(addition, just(1), just(2))); + assertEquals(just(1), present.apply(addition, nothing(), just(1))); + assertEquals(just(1), present.apply(addition, just(1), nothing())); + assertEquals(nothing(), present.apply(addition, nothing(), nothing())); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java index fc5500ac5..5d3f90215 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java @@ -19,8 +19,8 @@ public void identity() { @Test public void monoid() { - TypeSafeKey stringKey = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); HMap x = singletonHMap(stringKey, "string"); HMap y = singletonHMap(intKey, 1); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RightAnyTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RightAnyTest.java index 96a4d4e8a..730438ace 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RightAnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RightAnyTest.java @@ -13,7 +13,7 @@ public class RightAnyTest { @Test public void monoid() { RightAny rightAny = rightAny(); - Monoid add = Monoid.monoid((x, y) -> x + y, 0); + Monoid add = Monoid.monoid(Integer::sum, 0); assertEquals(right(0), rightAny.apply(add).identity()); assertEquals(right(1), rightAny.apply(add).apply(right(1), left("foo"))); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RunAllTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RunAllTest.java new file mode 100644 index 000000000..c7460e53e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RunAllTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.monoid.Monoid; +import org.junit.Test; + +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monoid.builtin.RunAll.runAll; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class RunAllTest { + + @Test + public void monoid() { + Monoid add = Monoid.monoid(Integer::sum, 0); + assertThat(runAll(add).apply(io(1), io(2)), yieldsValue(equalTo(3))); + assertThat(runAll(add).identity(), yieldsValue(equalTo(0))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/TrivialTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/TrivialTest.java new file mode 100644 index 000000000..da28c59f6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/TrivialTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.monoid.builtin.Trivial.trivial; +import static org.junit.Assert.assertEquals; + +public class TrivialTest { + + @Test + public void triviality() { + assertEquals(UNIT, trivial().identity()); + assertEquals(UNIT, trivial().apply(UNIT, UNIT)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/UnionTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/UnionTest.java new file mode 100644 index 000000000..5a423c679 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/UnionTest.java @@ -0,0 +1,44 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.monoid.builtin.Union.union; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class UnionTest { + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Deforesting.class}) + public Subjects, Iterable>> testSubject() { + return subjects(union(asList(1, 2, 3)), Union.union().flip().apply(asList(1, 2, 3))); + } + + @Test + public void monoid() { + assertThat(union().identity(), isEmpty()); + + assertThat(union(emptyList(), emptyList()), isEmpty()); + assertThat(union(asList(1, 2), emptyList()), iterates(1, 2)); + assertThat(union(emptyList(), singletonList(3)), iterates(3)); + assertThat(union(asList(1, 2), singletonList(3)), iterates(1, 2, 3)); + assertThat(union(asList(1, 2, 2), singletonList(3)), iterates(1, 2, 3)); + assertThat(union(asList(1, 2), asList(1, 2, 3)), iterates(1, 2, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/XorTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/XorTest.java new file mode 100644 index 000000000..0f7aefeac --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/XorTest.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.monoid.builtin.Xor.xor; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class XorTest { + + @Test + public void identity() { + assertFalse(xor().identity()); + } + + @Test + public void monoid() { + assertFalse(xor(false, false)); + assertTrue(xor(true, false)); + assertTrue(xor(false, true)); + assertFalse(xor(true, true)); + + assertTrue(xor().reduceLeft(asList(true, false, false, true, true))); + assertFalse(xor().reduceLeft(asList(true, false, true, false, false))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/IsoTest.java b/src/test/java/com/jnape/palatable/lambda/optics/IsoTest.java new file mode 100644 index 000000000..8c2bdb79f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/IsoTest.java @@ -0,0 +1,66 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import java.util.List; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.optics.Iso.iso; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.junit.Assert.assertEquals; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class IsoTest { + + private static final Iso, Integer, Double> ISO = + iso(Integer::parseInt, dbl -> dbl.toString().chars().mapToObj(x -> (char) x).collect(toList())); + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Equivalence, Integer, Double>> testSubject() { + return equivalence(ISO, iso -> view(iso, "123")); + } + + @Test + public void lensLike() { + assertEquals((Integer) 123, view(ISO, "123")); + assertEquals(asList('1', '.', '2', '3'), set(ISO, 1.23d, "234")); + } + + @Test + public void mirrorFlipsIso() { + assertEquals(asList('1', '.', '2', '3'), view(ISO.mirror(), 1.23d)); + assertEquals((Integer) 240, set(ISO.mirror(), "240", 5.67d)); + } + + @Test + public void mapsIndividuallyOverParameters() { + Iso, Maybe>, Maybe, Maybe> mapped = ISO + .mapS((Maybe maybeS) -> maybeS.orElse("")) + .mapT(Maybe::maybe) + .mapA(Maybe::maybe) + .mapB((Maybe maybeD) -> maybeD.orElse(-1d)); + + assertEquals(just(1), view(mapped, just("1"))); + assertEquals(just(asList('1', '.', '2')), view(mapped.mirror(), just(1.2d))); + } + + @Test + public void staticPure() { + Iso iso = Iso.pureIso(String::length).apply('1'); + assertEquals((Integer) 3, view(iso, "foo")); + assertEquals((Character) '1', view(iso.mirror(), true)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/LensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/LensTest.java new file mode 100644 index 000000000..8283cd4d4 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/LensTest.java @@ -0,0 +1,124 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Const; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.optics.Lens.both; +import static com.jnape.palatable.lambda.optics.Lens.lens; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.lang.Integer.parseInt; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertEquals; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class LensTest { + + private static final Lens>, Map>, List, Set> + EARLIER_LENS = lens(m -> m.get("foo"), (m, s) -> singletonMap("foo", s)); + private static final Lens, Set, String, Integer> + LENS = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Equivalence, List, Integer, String>> testSubject() { + return equivalence(lens(m -> m.get("foo"), (m, s) -> singletonList(m.get(s))), lens -> view(lens, emptyMap())); + } + + @Test + public void setsUnderIdentity() { + Set ints = LENS., Identity, Identity, Identity>, + Fn1>, Fn1, Identity>>>apply( + s -> new Identity<>(s.length())).apply(asList("foo", "bar", "baz")).runIdentity(); + assertEquals(singleton(3), ints); + } + + @Test + public void viewsUnderConst() { + Integer i = LENS., Const, Const, Const>, + Fn1>, Fn1, Const>>>apply( + s -> new Const<>(s.length())).apply(asList("foo", "bar", "baz")).runConst(); + assertEquals((Integer) 3, i); + } + + @Test + public void mapsIndividuallyOverParameters() { + Lens lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); + Lens, Maybe, Maybe, Maybe> theGambit = lens + .mapS((Maybe maybeS) -> maybeS.orElse("")) + .mapT(Maybe::maybe) + .mapA(Maybe::maybe) + .mapB((Maybe maybeI) -> maybeI.orElse(-1)); + + assertEquals(just(true), + theGambit., Identity, Identity>, Identity>, + Fn1, Identity>>, + Fn1, Identity>>>apply( + maybeC -> new Identity<>(maybeC.fmap(c -> parseInt(Character.toString(c))))) + .apply(just("321")) + .runIdentity()); + } + + @Test + public void composition() { + Map> map = singletonMap("foo", asList("one", "two", "three")); + assertEquals("one", view(LENS.compose(EARLIER_LENS), map)); + assertEquals(singletonMap("foo", singleton(1)), set(LENS.compose(EARLIER_LENS), 1, map)); + } + + @Test + public void andThenComposesInReverse() { + Map> map = singletonMap("foo", asList("one", "two", "three")); + assertEquals("one", view(EARLIER_LENS.andThen(LENS), map)); + assertEquals(singletonMap("foo", singleton(1)), set(EARLIER_LENS.andThen(LENS), 1, map)); + } + + @Test + public void bothSplitsFocusBetweenLenses() { + Lens firstChar = simpleLens(s -> s.charAt(0), (s, c) -> c + s.substring(1)); + Lens length = simpleLens(String::length, (s, k) -> s.substring(0, k)); + Lens, Tuple2> both = both(firstChar, length); + + assertEquals(tuple('a', 3), view(both, "abc")); + assertEquals("zb", set(both, tuple('z', 2), "abc")); + } + + @Test + public void bothForSimpleLenses() { + Lens.Simple stringToInt = simpleLens(Integer::parseInt, (s, i) -> s + i.toString()); + Lens.Simple stringToChar = simpleLens(s -> s.charAt(0), (s, c) -> s + c.toString()); + + assertEquals(tuple(3, '3'), view(both(stringToInt, stringToChar), "3")); + assertEquals("133", set(both(stringToInt, stringToChar), tuple(3, '3'), "1")); + } + + @Test + public void toIso() { + Iso, Set, String, Integer> iso = LENS.toIso(singletonList("")); + assertEquals("1", view(iso, asList("1", "2", "3"))); + assertEquals(singleton(1), view(iso.mirror(), 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/OpticTest.java b/src/test/java/com/jnape/palatable/lambda/optics/OpticTest.java new file mode 100644 index 000000000..00a550185 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/OpticTest.java @@ -0,0 +1,44 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Tagged; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.optics.Optic.optic; +import static org.junit.Assert.assertEquals; + +public class OpticTest { + + @Test + public void monomorphize() { + Optic, Identity, String, String, String, String> optic = optic(pafb -> pafb); + Fn1>, Tagged>> monomorphize = optic.monomorphize(); + assertEquals(new Identity<>("foo"), monomorphize.apply(new Tagged<>(new Identity<>("foo"))).unTagged()); + } + + @Test + public void reframe() { + Optic, Functor, String, String, String, String> optic = optic(pafb -> pafb); + Optic, Identity, String, String, String, String> reframed = Optic.reframe(optic); + assertEquals(new Identity<>("foo"), + reframed., Identity, Identity, Identity, + Fn1>, + Fn1>>apply(constantly(new Identity<>("foo"))).apply("bar")); + } + + @Test + public void adapt() { + Optic, Functor, String, String, String, String> optic = optic(pafb -> pafb); + Optic.Simple, Functor, String, String> simple = Optic.Simple.adapt(optic); + + assertEquals("foo", + simple., Identity, Identity, Identity, + Fn1>, + Fn1>>apply(constantly(new Identity<>("foo"))) + .apply("bar").runIdentity()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java new file mode 100644 index 000000000..22e2c5541 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java @@ -0,0 +1,101 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.optics.Prism.prism; +import static com.jnape.palatable.lambda.optics.Prism.simplePrism; +import static com.jnape.palatable.lambda.optics.functions.Matching.matching; +import static com.jnape.palatable.lambda.optics.functions.Pre.pre; +import static com.jnape.palatable.lambda.optics.functions.Re.re; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static testsupport.assertion.PrismAssert.assertPrismLawfulness; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class PrismTest { + + private static final Fn1 PARSE_INT = Fn1.fn1(Integer::parseInt); + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Equivalence> testSubject() { + return equivalence(Prism.fromPartial(Integer::parseInt, Object::toString), + prism -> matching(prism, "foo")); + } + + @Test + public void prismLaws() { + Prism prism = prism(PARSE_INT.choose(), Object::toString); + + assertEquals(just(1), view(pre(prism), view(re(prism), 1))); + assertEquals(just(123), view(pre(prism), "123").filter(a -> view(re(prism), a).equals("123"))); + assertEquals(left("foo"), matching(prism, "foo").match(t -> matching(prism, t), Either::right)); + } + + @Test + @SuppressWarnings("unused") + public void simplePrismInference() { + Prism.Simple simplePrism = simplePrism(PARSE_INT.choose().fmap(CoProduct2::projectB), + Object::toString); + } + + @Test + public void unPrismExtractsMappings() { + Prism prism = prism(PARSE_INT.choose(), Object::toString); + Fn1 is = prism.unPrism()._1(); + Fn1> sis = prism.unPrism()._2(); + + assertEquals("123", is.apply(123)); + assertEquals(right(123), sis.apply("123")); + assertEquals(left("foo"), sis.apply("foo")); + } + + @Test + public void andThen() { + Prism.Simple stringFloat = Prism.Simple.fromPartial(Float::parseFloat, Object::toString); + Prism.Simple floatInt = simplePrism(f -> just(f.intValue()).filter(i -> eq(i.floatValue(), f)), + Integer::floatValue); + Prism composed = stringFloat.andThen(floatInt); + + assertPrismLawfulness(composed, singletonList("1.2"), singletonList(1)); + assertPrismLawfulness(composed, singletonList("1.0"), singletonList(1)); + assertPrismLawfulness(composed, singletonList("foo"), emptyList()); + } + + @Test + public void composed() { + Prism.Simple stringFloat = Prism.Simple.fromPartial(Float::parseFloat, Object::toString); + Prism.Simple floatInt = simplePrism(f -> just(f.intValue()).filter(i -> eq(i.floatValue(), f)), + Integer::floatValue); + Prism composed = floatInt.compose(stringFloat); + + assertPrismLawfulness(composed, singletonList("1.2"), singletonList(1)); + assertPrismLawfulness(composed, singletonList("1.0"), singletonList(1)); + assertPrismLawfulness(composed, singletonList("foo"), emptyList()); + } + + @Test + public void staticPure() { + Prism prism = Prism.purePrism().apply('1'); + assertEquals(left('1'), matching(prism, "foo")); + assertEquals((Character) '1', set(prism, true, "bar")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/OverTest.java similarity index 68% rename from src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/functions/OverTest.java index e5a95cf4a..ef2adf7a6 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/OverTest.java @@ -1,13 +1,13 @@ -package com.jnape.palatable.lambda.lens.functions; +package com.jnape.palatable.lambda.optics.functions; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.optics.Lens; import org.junit.Test; import java.util.List; import java.util.Set; -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.functions.Over.over; +import static com.jnape.palatable.lambda.optics.Lens.lens; +import static com.jnape.palatable.lambda.optics.functions.Over.over; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/com/jnape/palatable/lambda/optics/functions/PreTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/PreTest.java new file mode 100644 index 000000000..d697108ee --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/PreTest.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Prism; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.optics.Iso.iso; +import static com.jnape.palatable.lambda.optics.Prism.prism; +import static com.jnape.palatable.lambda.optics.functions.Pre.pre; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.lang.Integer.parseInt; +import static org.junit.Assert.assertEquals; + +public class PreTest { + + @Test + public void focusOnAtMostOneValue() { + Iso iso = iso(Integer::parseInt, Object::toString); + Prism prism = prism(s -> Either.trying(() -> parseInt(s), + constantly(s)), + Object::toString); + assertEquals(just(1), view(pre(prism), "1")); + assertEquals(nothing(), view(pre(prism), "foo")); + assertEquals(just(1), view(pre(iso), "1")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/functions/ReTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/ReTest.java new file mode 100644 index 000000000..a6d4fb900 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/ReTest.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Prism; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.optics.Iso.iso; +import static com.jnape.palatable.lambda.optics.Prism.prism; +import static com.jnape.palatable.lambda.optics.functions.Re.re; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.lang.Integer.parseInt; +import static org.junit.Assert.assertEquals; + +public class ReTest { + + @Test + public void flipAroundIsoAndPrism() { + Iso iso = iso(Integer::parseInt, Object::toString); + Prism prism = prism(s -> Either.trying(() -> parseInt(s), + constantly(s)), + Object::toString); + assertEquals("1", view(re(prism), 1)); + assertEquals("1", view(re(iso), 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/SetTest.java similarity index 67% rename from src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/functions/SetTest.java index 47fa8a959..11a6bdcc5 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/SetTest.java @@ -1,12 +1,12 @@ -package com.jnape.palatable.lambda.lens.functions; +package com.jnape.palatable.lambda.optics.functions; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.optics.Lens; import org.junit.Test; import java.util.List; -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.optics.Lens.lens; +import static com.jnape.palatable.lambda.optics.functions.Set.set; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/com/jnape/palatable/lambda/optics/functions/UnderTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/UnderTest.java new file mode 100644 index 000000000..15827500b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/UnderTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.optics.Iso; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static com.jnape.palatable.lambda.optics.Iso.iso; +import static com.jnape.palatable.lambda.optics.functions.Under.under; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; + +public class UnderTest { + + @Test + public void reversesTypeRingWalk() { + Iso, Set, String, Integer> iso = iso(xs -> xs.get(0), Collections::singleton); + assertEquals("1", under(iso, set -> singletonList(set.iterator().next().toString()), 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/ViewTest.java similarity index 67% rename from src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/functions/ViewTest.java index 201ec990a..07f7a047a 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/functions/ViewTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/ViewTest.java @@ -1,13 +1,13 @@ -package com.jnape.palatable.lambda.lens.functions; +package com.jnape.palatable.lambda.optics.functions; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.optics.Lens; import org.junit.Test; import java.util.List; import java.util.Set; -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.functions.View.view; +import static com.jnape.palatable.lambda.optics.Lens.lens; +import static com.jnape.palatable.lambda.optics.functions.View.view; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/com/jnape/palatable/lambda/optics/lenses/CollectionLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/CollectionLensTest.java new file mode 100644 index 000000000..1c13ef7c9 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/CollectionLensTest.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.optics.Lens; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Stream; + +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static com.jnape.palatable.lambda.optics.lenses.CollectionLens.asCopy; +import static com.jnape.palatable.lambda.optics.lenses.CollectionLens.asSet; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.stream.Collectors.toList; +import static org.junit.Assert.assertEquals; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + +public class CollectionLensTest { + + @Test + public void asCopyUsesMappingFunctionToFocusOnCollectionThroughCopy() { + assertLensLawfulness(asCopy(ArrayList::new), + asList(emptyList(), asList("foo", "bar", "baz")), + asList(emptyList(), asList("foo", "bar", "baz"))); + } + + @Test + public void asSetFocusesOnCollectionAsSet() { + assertLensLawfulness(asSet(ArrayList::new), + asList(emptyList(), asList("foo", "bar", "baz"), asList("foo", "foo")), + asList(emptySet(), singleton("foo"), new HashSet<>(asList("foo", "bar", "baz")), new HashSet<>(asList("foo", "bar")))); + } + + @Test + public void asStreamFocusesOnCollectionAsStream() { + Lens.Simple, Stream> asStream = CollectionLens.asStream(ArrayList::new); + + assertEquals(asList("foo", "bar", "baz"), view(asStream, asList("foo", "bar", "baz")).collect(toList())); + assertEquals(asList("foo", "bar"), set(asStream, Stream.of("foo", "bar"), asList("foo", "bar", "baz"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/lenses/EitherLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/EitherLensTest.java new file mode 100644 index 000000000..ef9aee18d --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/EitherLensTest.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.optics.Lens; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static org.junit.Assert.assertEquals; + +public class EitherLensTest { + + @Test + public void rightFocusesOnRightValues() { + Lens.Simple, Maybe> right = EitherLens._right(); + + assertEquals(just(1), view(right, right(1))); + assertEquals(nothing(), view(right, left("fail"))); + assertEquals(right(2), set(right, just(2), right(1))); + assertEquals(right(1), set(right, nothing(), right(1))); + assertEquals(right(2), set(right, just(2), left("fail"))); + assertEquals(left("fail"), set(right, nothing(), left("fail"))); + } + + @Test + public void leftFocusesOnLeftValues() { + Lens.Simple, Maybe> left = EitherLens._left(); + + assertEquals(just("fail"), view(left, left("fail"))); + assertEquals(nothing(), view(left, right(1))); + assertEquals(left("foo"), set(left, just("foo"), left("fail"))); + assertEquals(left("fail"), set(left, nothing(), left("fail"))); + assertEquals(left("foo"), set(left, just("foo"), right(1))); + assertEquals(right(1), set(left, nothing(), right(1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/lenses/HListLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/HListLensTest.java new file mode 100644 index 000000000..09c8de717 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/HListLensTest.java @@ -0,0 +1,35 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.adt.hlist.Index; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.optics.lenses.HListLens.elementAt; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + +public class HListLensTest { + + @Test + public void elementAtFocusesOnInvariantElementAtIndex() { + assertLensLawfulness(elementAt(Index.index().after().after()), + asList(tuple(true, 0, "foo"), tuple(true, 0, "foo", '1')), + singletonList("bar")); + } + + @Test + public void headFocusesOnHead() { + assertLensLawfulness(HListLens.head(), + asList(singletonHList(2), tuple(2, "3"), tuple(2, "3", '4')), + singletonList(0)); + } + + @Test + public void tailFocusesOnTail() { + assertLensLawfulness(HListLens.tail(), + singletonList(tuple(2, "3", '4')), + asList(tuple("3", '5'), tuple("4", '4'), tuple("4", '5'))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/lenses/HMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/HMapLensTest.java new file mode 100644 index 000000000..94041fb6e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/HMapLensTest.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.singletonHMap; +import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static java.util.Arrays.asList; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + +public class HMapLensTest { + + @Test + public void valueAt() { + TypeSafeKey.Simple key = typeSafeKey(); + assertLensLawfulness(HMapLens.valueAt(key), + asList(emptyHMap(), + singletonHMap(key, "foo"), + hMap(key, "foo", + typeSafeKey(), "bar"), + singletonHMap(typeSafeKey(), "bar")), + asList(nothing(), + just("foo"), + just("bar"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/lenses/IterableLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/IterableLensTest.java new file mode 100644 index 000000000..3353c9eed --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/IterableLensTest.java @@ -0,0 +1,71 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Lens; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.optics.Iso.simpleIso; +import static com.jnape.palatable.lambda.optics.functions.Over.over; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +public class IterableLensTest { + + @Test + public void head() { + Lens.Simple, Maybe> head = IterableLens.head(); + + assertEquals(just(1), view(head, asList(1, 2, 3))); + assertEquals(nothing(), view(head, emptyList())); + + assertThat(set(head, just(1), emptyList()), iterates(1)); + assertThat(set(head, nothing(), emptyList()), isEmpty()); + assertThat(set(head, just(1), asList(2, 2, 3)), iterates(1, 2, 3)); + assertThat(set(head, nothing(), asList(2, 2, 3)), iterates(2, 3)); + + assertThat(over(head, maybeX -> maybeX.fmap(x -> x + 1), emptyList()), isEmpty()); + assertThat(over(head, maybeX -> maybeX.fmap(x -> x + 1), asList(1, 2, 3)), iterates(2, 2, 3)); + } + + @Test + public void tail() { + Lens.Simple, Iterable> tail = IterableLens.tail(); + + assertThat(view(tail, asList(1, 2, 3)), iterates(2, 3)); + assertThat(view(tail, emptyList()), isEmpty()); + + assertThat(set(tail, asList(2, 3), singletonList(1)), iterates(1, 2, 3)); + assertThat(set(tail, emptyList(), asList(1, 2, 3)), iterates(1)); + assertThat(set(tail, asList(1, 2, 3), emptyList()), iterates(1, 2, 3)); + assertThat(set(tail, emptyList(), emptyList()), isEmpty()); + + assertThat(over(tail, map(x -> x + 1), emptyList()), isEmpty()); + assertThat(over(tail, map(x -> x + 1), asList(1, 2, 3)), iterates(1, 3, 4)); + } + + @Test + public void mapping() { + Iso.Simple, Iterable> iso = IterableLens.mapping(simpleIso(Integer::parseInt, Object::toString)); + + assertThat(view(iso, emptyList()), isEmpty()); + assertThat(view(iso, singletonList("1")), iterates(1)); + assertThat(view(iso, asList("1", "2", "3")), iterates(1, 2, 3)); + + assertThat(set(iso, emptyList(), emptyList()), isEmpty()); + assertThat(set(iso, singletonList(1), emptyList()), iterates("1")); + assertThat(set(iso, singletonList(2), singletonList("1")), iterates("2")); + assertThat(set(iso, asList(1, 2, 3), singletonList("1")), iterates("1", "2", "3")); + assertThat(set(iso, emptyList(), singletonList("1")), isEmpty()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/lenses/ListLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/ListLensTest.java new file mode 100644 index 000000000..9a283f71c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/ListLensTest.java @@ -0,0 +1,45 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.optics.Lens; +import org.junit.Test; + +import java.util.List; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static com.jnape.palatable.lambda.optics.lenses.ListLens.asCopy; +import static com.jnape.palatable.lambda.optics.lenses.ListLens.elementAt; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + +public class ListLensTest { + + @Test + public void asCopyFocusesOnListThroughCopy() { + assertLensLawfulness(asCopy(), + asList(emptyList(), asList("foo", "bar", "baz")), + asList(emptyList(), asList("foo", "bar", "baz", "quux"))); + } + + @Test + public void elementAtFocusesOnElementAtIndex() { + assertLensLawfulness(elementAt(0), + asList(emptyList(), asList("foo", "bar", "baz"), asList("quux", "bar", "baz")), + asList(nothing(), just("foo"), just("quux"))); + } + + @Test + public void elementAtWithDefaultValueFocusesOnElementAtIndex() { + Lens, List, String, String> at0 = ListLens.elementAt(0, "missing"); + + assertEquals("foo", view(at0, asList("foo", "bar", "baz"))); + assertEquals("missing", view(at0, emptyList())); + assertEquals(asList("quux", "bar", "baz"), set(at0, "quux", asList("foo", "bar", "baz"))); + assertEquals(singletonList("quux"), set(at0, "quux", emptyList())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/lenses/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/MapLensTest.java new file mode 100644 index 000000000..5a97e5b4a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/MapLensTest.java @@ -0,0 +1,192 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.optics.Lens; +import org.junit.Test; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.optics.Iso.iso; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static com.jnape.palatable.lambda.optics.lenses.MapLens.keys; +import static com.jnape.palatable.lambda.optics.lenses.MapLens.mappingValues; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonMap; +import static java.util.Collections.unmodifiableMap; +import static org.hamcrest.core.IsCollectionContaining.hasItems; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertThat; +import static testsupport.assertion.LensAssert.assertLensLawfulness; +import static testsupport.matchers.IterableMatcher.iterates; + +@SuppressWarnings("serial") +public class MapLensTest { + + @Test + public void asCopy() { + assertLensLawfulness(MapLens.asCopy(), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }})); + } + + @Test + public void asCopyWithCopyFn() { + assertLensLawfulness(MapLens.asCopy(LinkedHashMap::new), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }})); + + assertThat(view(MapLens.asCopy(LinkedHashMap::new), new LinkedHashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}).keySet(), iterates("foo", "bar", "baz")); + } + + @Test + public void valueAt() { + assertLensLawfulness(MapLens.valueAt("foo"), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}), + asList(nothing(), just(1))); + } + + @Test + public void valueAtWithCopyFn() { + assertLensLawfulness(MapLens.valueAt("foo"), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}), + asList(nothing(), just(1))); + } + + + @Test + public void valueAtWithDefaultValue() { + Lens.Simple, Integer> atFoo = MapLens.valueAt("foo", -1); + + assertEquals((Integer) 1, view(atFoo, new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }})); + assertEquals((Integer) (-1), view(atFoo, emptyMap())); + + Map updated = set(atFoo, 11, new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}); + assertEquals(new HashMap() {{ + put("foo", 11); + put("bar", 2); + put("baz", 3); + }}, updated); + assertNotSame(new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}, updated); + } + + @Test + public void keysFocusesOnKeys() { + assertLensLawfulness(keys(), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}), + asList(emptySet(), singleton("foo"), new HashSet<>(asList("foo", "bar", "baz", "quux")), new HashSet<>(asList("foo", "baz", "quux")))); + } + + @Test + public void valuesFocusesOnValues() { + Lens.Simple, Collection> values = MapLens.values(); + + assertThat(view(values, new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}), hasItems(2, 1, 3)); + + Map updated = set(values, asList(1, 2), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}); + assertEquals(new HashMap() {{ + put("foo", 1); + put("bar", 2); + }}, updated); + assertNotSame(new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}, updated); + } + + @Test + public void invertedFocusesOnMapWithKeysAndValuesSwitched() { + assertLensLawfulness(MapLens.inverted(), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}), + asList(emptyMap(), singletonMap(1, "foo"), new HashMap() {{ + put(1, "foo"); + put(2, "bar"); + put(3, "baz"); + }})); + } + + @Test + public void mappingValuesWithIsoRetainsMapStructureWithMappedValues() { + assertLensLawfulness(mappingValues(iso(Integer::parseInt, Object::toString)), + asList(emptyMap(), + singletonMap("foo", "1"), + unmodifiableMap(new HashMap() {{ + put("foo", "1"); + put("bar", "2"); + put("baz", "3"); + }})), + asList(emptyMap(), + singletonMap("foo", 1), + unmodifiableMap(new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/lenses/MaybeLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/MaybeLensTest.java new file mode 100644 index 000000000..4542be84b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/MaybeLensTest.java @@ -0,0 +1,80 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.optics.Lens; +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.optics.Lens.lens; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static com.jnape.palatable.lambda.optics.lenses.MaybeLens.liftA; +import static com.jnape.palatable.lambda.optics.lenses.MaybeLens.liftB; +import static com.jnape.palatable.lambda.optics.lenses.MaybeLens.liftS; +import static com.jnape.palatable.lambda.optics.lenses.MaybeLens.liftT; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + +public class MaybeLensTest { + + private Lens lens; + + @Before + public void setUp() { + lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); + } + + @Test + public void asMaybeWrapsValuesInMaybe() { + assertLensLawfulness(MaybeLens.asMaybe(), + asList(null, "foo"), + asList(nothing(), just("hi"))); + } + + @Test + public void liftSLiftsSToMaybe() { + assertEquals((Character) '3', view(liftS(lens, "3"), nothing())); + } + + @Test + public void liftTLiftsTToMaybe() { + assertEquals(just(true), set(liftT(lens), 3, "123")); + } + + @Test + public void liftALiftsAToMaybe() { + assertEquals(just('1'), view(liftA(lens), "123")); + } + + @Test + public void liftBLiftsBToMaybe() { + assertEquals(true, set(MaybeLens.liftB(lens, 1), nothing(), "1")); + } + + @Test + public void unLiftSPullsSOutOfMaybe() { + Lens, Maybe, Maybe, Maybe> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals(just('f'), view(MaybeLens.unLiftS(liftedToMaybe), "f")); + } + + @Test + public void unLiftTPullsTOutOfMaybe() { + Lens, Maybe, Maybe, Maybe> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals(true, set(MaybeLens.unLiftT(liftedToMaybe, false), just(3), just("321"))); + } + + @Test + public void unLiftAPullsAOutOfMaybe() { + Lens, Maybe, Maybe, Maybe> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals((Character) '1', view(MaybeLens.unLiftA(liftedToMaybe, '4'), nothing())); + } + + @Test + public void unLiftBPullsBOutOfMaybe() { + Lens, Maybe, Maybe, Maybe> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals(just(true), set(MaybeLens.unLiftB(liftedToMaybe), 3, just("321"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/lenses/SetLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/SetLensTest.java new file mode 100644 index 000000000..6beff4b53 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/SetLensTest.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.TreeSet; + +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + +public class SetLensTest { + + @Test + public void containsWithCopyFn() { + assertLensLawfulness(SetLens.contains(HashSet::new, 1), + asList(emptySet(), + singleton(1), + singleton(2), + new HashSet<>(asList(1, 2)), + new HashSet<>(asList(2, 3))), + asList(true, false)); + assertThat(set(SetLens.contains(TreeSet::new, 1), true, emptySet()), instanceOf(TreeSet.class)); + } + + @Test + public void containsWithoutCopyFn() { + assertLensLawfulness(SetLens.contains(1), + asList(emptySet(), + singleton(1), + singleton(2), + new HashSet<>(asList(1, 2)), + new HashSet<>(asList(2, 3))), + asList(true, false)); + assertThat(set(SetLens.contains(1), true, emptySet()), instanceOf(HashSet.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/prisms/EitherPrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/prisms/EitherPrismTest.java new file mode 100644 index 000000000..cb1e09357 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/prisms/EitherPrismTest.java @@ -0,0 +1,26 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static testsupport.assertion.PrismAssert.assertPrismLawfulness; + +public class EitherPrismTest { + + @Test + public void _right() { + assertPrismLawfulness(EitherPrism._right(), + asList(left("foo"), right(1)), + singleton(1)); + } + + @Test + public void _left() { + assertPrismLawfulness(EitherPrism._left(), + asList(left("foo"), right(1)), + singleton("foo")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/prisms/MapPrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/prisms/MapPrismTest.java new file mode 100644 index 000000000..7ba1dd09f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/prisms/MapPrismTest.java @@ -0,0 +1,33 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.LinkedHashMap; + +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonMap; +import static testsupport.assertion.PrismAssert.assertPrismLawfulness; + +public class MapPrismTest { + + @Test + public void valueAtWithConstructor() { + assertPrismLawfulness(MapPrism.valueAt(LinkedHashMap::new, "foo"), + asList(new LinkedHashMap<>(), + new LinkedHashMap<>(singletonMap("foo", 1)), + new LinkedHashMap<>(singletonMap("bar", 2))), + singleton(1)); + } + + @Test + public void valueAtWithoutConstructor() { + assertPrismLawfulness(MapPrism.valueAt("foo"), + asList(new HashMap<>(), + new HashMap<>(singletonMap("foo", 1)), + new HashMap<>(singletonMap("bar", 2))), + singleton(1)); + } + +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/prisms/MaybePrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/prisms/MaybePrismTest.java new file mode 100644 index 000000000..5d772a6c5 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/prisms/MaybePrismTest.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static testsupport.assertion.PrismAssert.assertPrismLawfulness; + +public class MaybePrismTest { + + @Test + public void _just() { + assertPrismLawfulness(MaybePrism._just(), + asList(just(1), nothing()), + singleton(1)); + } + + @Test + public void _nothing() { + assertPrismLawfulness(MaybePrism._nothing(), + asList(just(1), nothing()), + singleton(UNIT)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrismTest.java new file mode 100644 index 000000000..24bf6421a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrismTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import org.junit.Test; + +import java.util.UUID; + +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static testsupport.assertion.PrismAssert.assertPrismLawfulness; + +public class UUIDPrismTest { + + @Test + public void uuid() { + UUID uuid = UUID.randomUUID(); + assertPrismLawfulness(UUIDPrism.uuid(), + asList("", "123", uuid.toString()), + singleton(uuid)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java index 94697cda3..712c09e02 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java @@ -1,22 +1,22 @@ package com.jnape.palatable.lambda.semigroup; -import com.jnape.palatable.lambda.semigroup.Semigroup; import org.junit.Test; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static testsupport.functions.ExplainFold.explainFold; public class SemigroupTest { @Test public void foldLeft() { - Semigroup sum = (x, y) -> x + y; - assertEquals((Integer) 6, sum.foldLeft(0, asList(1, 2, 3))); + Semigroup foldFn = explainFold()::apply; + assertEquals("(((0 + 1) + 2) + 3)", foldFn.foldLeft("0", asList("1", "2", "3"))); } @Test public void foldRight() { - Semigroup sum = (x, y) -> x + y; - assertEquals((Integer) 6, sum.foldRight(0, asList(1, 2, 3))); + Semigroup foldFn = explainFold()::apply; + assertEquals("(1 + (2 + (3 + 0)))", foldFn.foldRight("0", asList("1", "2", "3")).value()); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java new file mode 100644 index 000000000..9d3dc4524 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java @@ -0,0 +1,102 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.builtin.fn1.Constantly; +import com.jnape.palatable.lambda.semigroup.Semigroup; +import org.junit.Test; + +import java.util.Arrays; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.semigroup.builtin.Absent.absent; +import static org.junit.Assert.assertEquals; + +public class AbsentTest { + + @Test + public void semigroup() { + Semigroup addition = Integer::sum; + + assertEquals(just(3), absent(addition, just(1), just(2))); + assertEquals(nothing(), absent(addition, nothing(), just(1))); + assertEquals(nothing(), absent(addition, just(1), nothing())); + assertEquals(nothing(), absent(addition, nothing(), nothing())); + } + + @Test + public void foldRight() { + Absent absent = absent(); + Semigroup addition = Integer::sum; + + assertEquals(just(3), absent.apply(addition).foldRight(just(0), Arrays.asList(just(1), just(2))).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(nothing(), just(1))).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(just(1), nothing())).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(nothing(), nothing())).value()); + } + + @Test + public void foldLeft() { + Absent absent = absent(); + Semigroup addition = Integer::sum; + + assertEquals(just(3), absent.apply(addition).foldLeft(just(0), Arrays.asList(just(1), just(2)))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(nothing(), just(1)))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(just(1), nothing()))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(nothing(), nothing()))); + } + + @Test(timeout = 200) + public void foldRightShortCircuit() { + Maybe result = Absent.absent(Constantly::constantly) + .foldRight(just(UNIT), repeat(nothing())).value(); + assertEquals(nothing(), result); + + result = Absent.absent(Constantly::constantly) + .foldRight(nothing(), repeat(just(UNIT))).value(); + assertEquals(nothing(), result); + } + + @Test(timeout = 200) + public void foldLeftShortCircuit() { + Maybe result = Absent.absent(Constantly::constantly) + .foldLeft(just(UNIT), repeat(nothing())); + assertEquals(nothing(), result); + + result = Absent.absent(Constantly::constantly) + .foldLeft(nothing(), repeat(just(UNIT))); + assertEquals(nothing(), result); + } + + @Test + public void foldLeftWorksForJusts() { + Maybe result = Absent.absent(Constantly::constantly) + .foldLeft(just(UNIT), Arrays.asList(just(UNIT), just(UNIT))); + assertEquals(just(UNIT), result); + } + + @Test(timeout = 200) + public void checkedApplyFoldRightShortCircuit() { + Maybe result = Absent.absent().checkedApply(Constantly::constantly) + .foldRight(just(UNIT), repeat(nothing())).value(); + assertEquals(nothing(), result); + + result = Absent.absent().checkedApply(Constantly::constantly) + .foldRight(nothing(), repeat(just(UNIT))).value(); + assertEquals(nothing(), result); + } + + @Test(timeout = 200) + public void checkedApplyFoldLeftShortCircuit() { + Maybe result = Absent.absent().checkedApply(Constantly::constantly) + .foldLeft(just(UNIT), repeat(nothing())); + assertEquals(nothing(), result); + + result = Absent.absent().checkedApply(Constantly::constantly) + .foldLeft(nothing(), repeat(just(UNIT))); + assertEquals(nothing(), result); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/CollapseTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/CollapseTest.java index face88111..cca4de936 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/CollapseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/CollapseTest.java @@ -4,6 +4,7 @@ import org.junit.Test; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; import static com.jnape.palatable.lambda.semigroup.builtin.Collapse.collapse; import static org.junit.Assert.assertEquals; @@ -11,8 +12,8 @@ public class CollapseTest { @Test public void semigroup() { - Semigroup join = (x, y) -> x + y; - Semigroup add = (x, y) -> x + y; + Semigroup join = join(); + Semigroup add = Integer::sum; Collapse collapse = collapse(); assertEquals(tuple("foobar", 3), collapse.apply(join, add, tuple("foo", 1), tuple("bar", 2))); diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/ComposeTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/ComposeTest.java new file mode 100644 index 000000000..c145b95b5 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/ComposeTest.java @@ -0,0 +1,29 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.semigroup.Semigroup; +import org.junit.Test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static com.jnape.palatable.lambda.semigroup.builtin.Compose.compose; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ComposeTest { + + @Test + public void semigroup() throws ExecutionException, InterruptedException { + Semigroup addition = (x, y) -> x + y; + + CompletableFuture failedFuture = new CompletableFuture() {{ + completeExceptionally(new RuntimeException()); + }}; + + assertEquals((Integer) 3, compose(addition, completedFuture(1), completedFuture(2)).get()); + assertTrue(compose(addition, completedFuture(1), failedFuture).isCompletedExceptionally()); + assertTrue(compose(addition, failedFuture, completedFuture(1)).isCompletedExceptionally()); + assertTrue(compose(addition, failedFuture, failedFuture).isCompletedExceptionally()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/ConcatTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/ConcatTest.java deleted file mode 100644 index e712c8c0d..000000000 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/ConcatTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.jnape.palatable.lambda.semigroup.builtin; - -import org.junit.Test; - -import java.util.HashSet; -import java.util.Set; - -import static com.jnape.palatable.lambda.semigroup.builtin.Concat.concat; -import static java.util.Collections.singleton; -import static org.junit.Assert.assertEquals; - -public class ConcatTest { - - @Test - public void semigroup() { - Concat> concat = concat(); - - assertEquals(new HashSet() {{ - add(1); - add(2); - }}, concat.apply(new HashSet<>(singleton(1)), new HashSet<>(singleton(2)))); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/EndoTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/EndoTest.java new file mode 100644 index 000000000..4da982462 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/EndoTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.monoid.builtin.Endo; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class EndoTest { + + @Test + public void identity() { + assertEquals((Integer) 1, Endo.endo().identity().apply(1)); + } + + @Test + public void semigroup() { + assertEquals((Integer) 2, Endo.endo().apply(x -> x + 1, x -> x + 1).apply(0)); + assertEquals((Integer) 2, Endo.endo().apply(x -> x + 1, x -> x + 1, 0)); + assertEquals((Integer) 2, Endo.endo(x -> x + 1, x -> x + 1, 0)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/IntersectionTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/IntersectionTest.java new file mode 100644 index 000000000..0ecd33c71 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/IntersectionTest.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.semigroup.builtin.Intersection.intersection; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class IntersectionTest { + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class}) + public Fn1, Iterable> testSubject() { + return intersection(asList(0, 1, 2, 3)); + } + + @Test + public void intersectionOfEmptyOnEitherSideIsEmpty() { + assertThat(intersection(emptyList(), singletonList(1)), isEmpty()); + assertThat(intersection(singletonList(1), emptyList()), isEmpty()); + assertThat(intersection(emptyList(), emptyList()), isEmpty()); + } + + @Test + public void intersectionIsCommonElementsAcrossIterables() { + assertThat(intersection(asList(1, 2, 3), asList(1, 2, 3)), iterates(1, 2, 3)); + assertThat(intersection(asList(1, 2, 3), asList(2, 3, 4)), iterates(2, 3)); + assertThat(intersection(singletonList(1), singletonList(2)), isEmpty()); + assertThat(intersection(asList(1, 2, 3, 3), singletonList(3)), iterates(3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAnyTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAnyTest.java index 98026177b..2e0cd7ea0 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAnyTest.java @@ -13,7 +13,7 @@ public class LeftAnyTest { @Test public void semigroup() { LeftAny leftAny = leftAny(); - Semigroup join = (x, y) -> x + y; + Semigroup join = (x, y) -> x + y; assertEquals(left("foo"), leftAny.apply(join).apply(left("foo"), right(1))); assertEquals(left("foo"), leftAny.apply(join).apply(right(1), left("foo"))); diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxByTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxByTest.java new file mode 100644 index 000000000..a47fb0f30 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxByTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.semigroup.builtin.MaxBy.maxBy; +import static org.junit.Assert.assertEquals; + +public class MaxByTest { + + @Test + public void semigroup() { + assertEquals((Integer) 1, maxBy(id(), 1, 0)); + assertEquals((Integer) 1, maxBy(id(), 1, 1)); + assertEquals((Integer) 2, maxBy(id(), 1, 2)); + + assertEquals("ab", maxBy(String::length, "ab", "a")); + assertEquals("ab", maxBy(String::length, "ab", "cd")); + assertEquals("bc", maxBy(String::length, "a", "bc")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxTest.java new file mode 100644 index 000000000..5e18a7bcf --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.semigroup.builtin.Max.max; +import static org.junit.Assert.assertEquals; + +public class MaxTest { + + @Test + public void semigroup() { + assertEquals((Integer) 1, max(1, 0)); + assertEquals((Integer) 1, max(1, 1)); + assertEquals((Integer) 2, max(1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java new file mode 100644 index 000000000..49211aee0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.semigroup.builtin.MaxWith.maxWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertEquals; + +public class MaxWithTest { + @Test + public void semigroup() { + assertEquals((Integer) 1, maxWith(naturalOrder(), 1, 0)); + assertEquals((Integer) 1, maxWith(naturalOrder(), 1, 1)); + assertEquals((Integer) 2, maxWith(naturalOrder(), 1, 2)); + + assertEquals("ab", maxWith(comparing(String::length), "ab", "a")); + assertEquals("ab", maxWith(comparing(String::length), "ab", "cd")); + assertEquals("bc", maxWith(comparing(String::length), "a", "bc")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MergeTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MergeTest.java index c867c1302..5cd397cd4 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MergeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MergeTest.java @@ -6,6 +6,7 @@ import static com.jnape.palatable.lambda.adt.Either.left; import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; import static com.jnape.palatable.lambda.semigroup.builtin.Merge.merge; import static org.junit.Assert.assertEquals; @@ -13,8 +14,8 @@ public class MergeTest { @Test public void semigroup() { - Semigroup join = (x, y) -> x + y; - Semigroup add = (x, y) -> x + y; + Semigroup join = join(); + Semigroup add = Integer::sum; Semigroup> merge = merge(join, add); assertEquals(left("onetwo"), merge.apply(left("one"), left("two"))); diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinByTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinByTest.java new file mode 100644 index 000000000..5436b289e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinByTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.semigroup.builtin.MinBy.minBy; +import static org.junit.Assert.assertEquals; + +public class MinByTest { + + @Test + public void semigroup() { + assertEquals((Integer) 1, minBy(id(), 1, 2)); + assertEquals((Integer) 1, minBy(id(), 1, 1)); + assertEquals((Integer) 0, minBy(id(), 1, 0)); + + assertEquals("a", minBy(String::length, "a", "ab")); + assertEquals("ab", minBy(String::length, "ab", "cd")); + assertEquals("c", minBy(String::length, "ab", "c")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinTest.java new file mode 100644 index 000000000..22e4ee3a0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.semigroup.builtin.Min.min; +import static org.junit.Assert.assertEquals; + +public class MinTest { + + @Test + public void semigroup() { + assertEquals((Integer) 1, min(1, 2)); + assertEquals((Integer) 1, min(1, 1)); + assertEquals((Integer) 0, min(1, 0)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java new file mode 100644 index 000000000..e6bfb82bb --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.semigroup.builtin.MinWith.minWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertEquals; + +public class MinWithTest { + @Test + public void semigroup() { + assertEquals((Integer) 1, minWith(naturalOrder(), 1, 2)); + assertEquals((Integer) 1, minWith(naturalOrder(), 1, 1)); + assertEquals((Integer) 0, minWith(naturalOrder(), 1, 0)); + + assertEquals("a", minWith(comparing(String::length), "a", "ab")); + assertEquals("ab", minWith(comparing(String::length), "ab", "cd")); + assertEquals("c", minWith(comparing(String::length), "ab", "c")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java index ec234090e..46d8e5f5a 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java @@ -13,7 +13,7 @@ public class RightAnyTest { @Test public void semigroup() { RightAny rightAny = rightAny(); - Semigroup add = (x, y) -> x + y; + Semigroup add = Integer::sum; assertEquals(right(1), rightAny.apply(add).apply(right(1), left("foo"))); assertEquals(right(1), rightAny.apply(add).apply(left("foo"), right(1))); diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java new file mode 100644 index 000000000..2733690c4 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.semigroup.builtin.RunAll.runAll; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class RunAllTest { + + @Test + public void semigroup() { + assertThat(runAll(Integer::sum).apply(io(1), io(2)), yieldsValue(equalTo(3))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java new file mode 100644 index 000000000..ede9fde89 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java @@ -0,0 +1,111 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.TraversableLaws; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.traversable.LambdaIterable.empty; +import static com.jnape.palatable.lambda.traversable.LambdaIterable.pureLambdaIterable; +import static com.jnape.palatable.lambda.traversable.LambdaIterable.wrap; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class LambdaIterableTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Subjects> testSubject() { + return subjects(LambdaIterable.empty(), wrap(singleton(1)), wrap(replicate(100, 1))); + } + + @Test + public void trampoliningWithDeferredResult() { + assertThat(LambdaIterable.wrap(singletonList(0)) + .trampolineM(x -> wrap(x < STACK_EXPLODING_NUMBER + ? singleton(recurse(x + 1)) + : singleton(terminate(x)))) + .unwrap(), + iterates(STACK_EXPLODING_NUMBER)); + } + + @Test + public void trampoliningOncePerElement() { + assertThat(LambdaIterable.wrap(asList(1, 2, 3)) + .trampolineM(x -> wrap(x < STACK_EXPLODING_NUMBER + ? singleton(recurse(x + 1)) + : singleton(terminate(x)))) + .unwrap(), + iterates(STACK_EXPLODING_NUMBER, STACK_EXPLODING_NUMBER, STACK_EXPLODING_NUMBER)); + } + + @Test + public void trampoliningWithIncrementalResults() { + assertThat(LambdaIterable.wrap(singletonList(0)) + .trampolineM(x -> wrap(x < 10 + ? asList(terminate(x), recurse(x + 1)) + : singleton(terminate(x)))) + .unwrap(), + iterates(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); + } + + @Test + public void zipAppliesCartesianProductOfFunctionsAndValues() { + LambdaIterable xs = wrap(asList(1, 2, 3)); + LambdaIterable> fns = wrap(asList(x -> x + 1, x -> x - 1)); + assertThat(xs.zip(fns).unwrap(), iterates(2, 0, 3, 1, 4, 2)); + } + + @Test + public void earlyTraverseTermination() { + assertEquals(nothing(), wrap(repeat(1)).traverse(x -> nothing(), Maybe::just)); + assertEquals(nothing(), LambdaIterable.>wrap(cons(just(1), repeat(nothing()))) + .traverse(id(), Maybe::just)); + } + + @Test + public void traverseStackSafety() { + Maybe> traversed = wrap(replicate(STACK_EXPLODING_NUMBER, just(1))) + .traverse(id(), Maybe::just); + assertEquals(just(STACK_EXPLODING_NUMBER.longValue()), + traversed.fmap(LambdaIterable::unwrap).fmap(size())); + } + + @Test + public void lazyZip() { + assertEquals(wrap(singleton(2)), wrap(singleton(1)).lazyZip(lazy(wrap(singleton(x -> x + 1)))).value()); + assertEquals(empty(), empty().lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + LambdaIterable lambdaIterable = pureLambdaIterable().apply(1); + assertThat(lambdaIterable.unwrap(), iterates(1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaMapTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaMapTest.java new file mode 100644 index 000000000..2be8673f7 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaMapTest.java @@ -0,0 +1,29 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; + +import java.util.HashMap; + +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Collections.singletonMap; + +@RunWith(Traits.class) +public class LambdaMapTest { + + @SuppressWarnings("serial") + @TestTraits({FunctorLaws.class, TraversableLaws.class}) + public Subjects> testSubject() { + return subjects(LambdaMap.empty(), + LambdaMap.wrap(singletonMap(1, "foo")), + LambdaMap.wrap(new HashMap() {{ + put(1, "foo"); + put(2, "bar"); + put(3, "baz"); + }})); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java new file mode 100644 index 000000000..1bd8e5fa5 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +public class TraversableTest { + + @Test + public void inference() { + Either> a = just(Either.right(1)).traverse(id(), Either::right); + assertEquals(right(just(1)), a); + + Maybe> b = Either.>right(just(1)).traverse(id(), Maybe::just); + assertEquals(just(right(1)), b); + + Either> c = b.traverse(id(), Either::right); + assertEquals(a, c); + + Maybe> d = LambdaIterable.wrap(asList(just(1), just(2))).traverse(id(), Maybe::just); + assertThat(d.orElseThrow(AssertionError::new).unwrap(), iterates(1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/testsupport/Constants.java b/src/test/java/testsupport/Constants.java new file mode 100644 index 000000000..a439920d6 --- /dev/null +++ b/src/test/java/testsupport/Constants.java @@ -0,0 +1,8 @@ +package testsupport; + +public final class Constants { + public static final Integer STACK_EXPLODING_NUMBER = 50_000; + + private Constants() { + } +} diff --git a/src/test/java/testsupport/Mocking.java b/src/test/java/testsupport/Mocking.java index 041105a7b..cbc86cd7a 100644 --- a/src/test/java/testsupport/Mocking.java +++ b/src/test/java/testsupport/Mocking.java @@ -18,8 +18,9 @@ public static Iterable mockIterable() { } @SafeVarargs - public static void mockIteratorToHaveValues(Iterator iterator, T... values) { - Iterator real = asList(values).iterator(); + @SuppressWarnings("varargs") + public static void mockIteratorToHaveValues(Iterator iterator, T... values) { + Iterator real = asList(values).iterator(); when(iterator.hasNext()).then(delegateTo(real)); when(iterator.next()).then(delegateTo(real)); diff --git a/src/test/java/testsupport/applicatives/InvocationRecordingBifunctor.java b/src/test/java/testsupport/applicatives/InvocationRecordingBifunctor.java index 2ef23f622..a39803ee1 100644 --- a/src/test/java/testsupport/applicatives/InvocationRecordingBifunctor.java +++ b/src/test/java/testsupport/applicatives/InvocationRecordingBifunctor.java @@ -1,26 +1,26 @@ package testsupport.applicatives; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.Bifunctor; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -public final class InvocationRecordingBifunctor implements Bifunctor { - private final AtomicReference leftFn; - private final AtomicReference rightFn; +public final class InvocationRecordingBifunctor implements Bifunctor> { + private final AtomicReference> leftFn; + private final AtomicReference> rightFn; - public InvocationRecordingBifunctor(AtomicReference leftFn, - AtomicReference rightFn) { + public InvocationRecordingBifunctor(AtomicReference> leftFn, + AtomicReference> rightFn) { this.leftFn = leftFn; this.rightFn = rightFn; } @Override @SuppressWarnings("unchecked") - public Bifunctor biMap(Function lFn, - Function rFn) { + public InvocationRecordingBifunctor biMap(Fn1 lFn, + Fn1 rFn) { leftFn.set(lFn); rightFn.set(rFn); - return (Bifunctor) this; + return (InvocationRecordingBifunctor) this; } } diff --git a/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java b/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java index dadcec960..403dee83c 100644 --- a/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java +++ b/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java @@ -1,25 +1,26 @@ package testsupport.applicatives; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.Profunctor; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -public final class InvocationRecordingProfunctor implements Profunctor { - private final AtomicReference leftFn; - private final AtomicReference rightFn; +public final class InvocationRecordingProfunctor implements Profunctor> { + private final AtomicReference> leftFn; + private final AtomicReference> rightFn; - public InvocationRecordingProfunctor(AtomicReference leftFn, - AtomicReference rightFn) { + public InvocationRecordingProfunctor(AtomicReference> leftFn, + AtomicReference> rightFn) { this.leftFn = leftFn; this.rightFn = rightFn; } @Override @SuppressWarnings("unchecked") - public Profunctor diMap(Function lFn, Function rFn) { + public InvocationRecordingProfunctor diMap(Fn1 lFn, + Fn1 rFn) { leftFn.set(lFn); rightFn.set(rFn); - return (Profunctor) this; + return (InvocationRecordingProfunctor) this; } } diff --git a/src/test/java/testsupport/assertion/LensAssert.java b/src/test/java/testsupport/assertion/LensAssert.java new file mode 100644 index 000000000..a64e80fd5 --- /dev/null +++ b/src/test/java/testsupport/assertion/LensAssert.java @@ -0,0 +1,53 @@ +package testsupport.assertion; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn2.Map; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.lambda.optics.Optic; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.CatMaybes.catMaybes; +import static com.jnape.palatable.lambda.functions.builtin.fn2.CartesianProduct.cartesianProduct; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft.reduceLeft; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.lang.String.format; +import static java.lang.String.join; +import static java.util.Arrays.asList; + +public final class LensAssert { + + public static void assertLensLawfulness(Optic, Functor, S, S, A, A> lens, + Iterable ss, + Iterable bs) { + Iterable> cases = cartesianProduct(ss, bs); + Present.present((x, y) -> join("\n\n", x, y)) + .reduceLeft(asList(falsify("You get back what you put in", (s, b) -> view(lens, set(lens, b, s)), (s, b) -> b, cases), + falsify("Putting back what you got changes nothing", (s, b) -> set(lens, view(lens, s), s), (s, b) -> s, cases), + falsify("Setting twice is equivalent to setting once", (s, b) -> set(lens, b, set(lens, b, s)), (s, b) -> set(lens, b, s), cases))) + .match(IO::io, failures -> IO.throwing(new AssertionError("Lens law failures\n\n" + failures))) + .unsafePerformIO(); + } + + private static Maybe falsify(String label, Fn2 l, Fn2 r, + Iterable> cases) { + return Map., Maybe>map(into((s, b) -> { + X x = l.apply(s, b); + X y = r.apply(s, b); + return Objects.equals(x, y) ? nothing() : just(format("S <%s>, B <%s> (%s != %s)", s, b, x, y)); + })) + .fmap(catMaybes()) + .fmap(reduceLeft((x, y) -> x + "\n\t - " + y)) + .fmap(maybeFailures -> maybeFailures.fmap(failures -> "\"" + label + "\" failed for the following cases:\n\n\t - " + failures)) + .apply(cases); + } +} diff --git a/src/test/java/testsupport/assertion/MonadErrorAssert.java b/src/test/java/testsupport/assertion/MonadErrorAssert.java new file mode 100644 index 000000000..edcf2ba12 --- /dev/null +++ b/src/test/java/testsupport/assertion/MonadErrorAssert.java @@ -0,0 +1,46 @@ +package testsupport.assertion; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadError; +import com.jnape.palatable.traitor.framework.Subjects; +import testsupport.traits.Equivalence; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static testsupport.traits.Equivalence.equivalence; + +public final class MonadErrorAssert { + + private MonadErrorAssert() { + } + + public static , MA extends MonadError> void assertLaws( + Subjects subjects, + E e, + Fn1 recovery) { + subjects.forEach(subject -> throwCatch(equivalence(subject, id()), e, recovery) + .match(IO::io, failures -> IO.throwing(new AssertionError("MonadError law failures\n\n" + failures))) + .unsafePerformIO()); + } + + public static , MA extends MonadError> void assertLawsEq( + Subjects> subjects, + E e, + Fn1 recovery) { + subjects.forEach(subject -> throwCatch(subject, e, recovery) + .match(IO::io, failures -> IO.throwing(new AssertionError("MonadError law failures\n\n" + failures))) + .unsafePerformIO()); + } + + private static , MA extends MonadError> Maybe throwCatch( + Equivalence equivalence, + E e, + Fn1 recovery) { + return equivalence.invMap(ma -> ma.throwError(e).catchError(recovery).coerce()) + .equals(equivalence.swap(recovery.apply(e))) + ? Maybe.nothing() + : Maybe.just("ThrowCatch failed: " + equivalence + ".throwError(" + e + ")" + + ".catchError(recoveryFn) /= recovery.apply(" + e + ")"); + } +} diff --git a/src/test/java/testsupport/assertion/PrismAssert.java b/src/test/java/testsupport/assertion/PrismAssert.java new file mode 100644 index 000000000..443130fb4 --- /dev/null +++ b/src/test/java/testsupport/assertion/PrismAssert.java @@ -0,0 +1,87 @@ +package testsupport.assertion; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn2.Map; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.lambda.optics.Prism; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.Fn2.fn2; +import static com.jnape.palatable.lambda.functions.builtin.fn1.CatMaybes.catMaybes; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.CartesianProduct.cartesianProduct; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft.reduceLeft; +import static com.jnape.palatable.lambda.optics.functions.Matching.matching; +import static com.jnape.palatable.lambda.optics.functions.Pre.pre; +import static com.jnape.palatable.lambda.optics.functions.Re.re; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.lang.String.format; +import static java.lang.String.join; +import static java.util.Arrays.asList; + +public final class PrismAssert { + + public static void assertPrismLawfulness(Prism prism, + Iterable ss, + Iterable bs) { + Iterable> cases = cartesianProduct(ss, bs); + Present.present((x, y) -> join("\n\n", x, y)) + .reduceLeft(asList(falsify("The result of a review can always be successfully previewed:", + (s, b) -> view(pre(prism), view(re(prism), b)), (s, b) -> just(b), cases), + falsify("If I can preview a value from an input, I can review the input to the value", + (s, b) -> new PrismResult<>(view(pre(prism), s).fmap(constantly(s))), + (s, b) -> new PrismResult<>(just(view(re(prism), b))), cases), + falsify("A non-match result can always be converted back to an input", + (s, b) -> new PrismResult<>(matching(prism, s).projectA().fmap(matching(prism))), + (s, b) -> new PrismResult<>(just(left(s))), cases))) + .match(IO::io, failures -> IO.throwing(new AssertionError("Lens law failures\n\n" + failures))) + .unsafePerformIO(); + } + + private static Maybe falsify(String label, Fn2 l, Fn2 r, + Iterable> cases) { + return Map., Maybe>map(into((s, b) -> { + X x = l.apply(s, b); + X y = r.apply(s, b); + return Objects.equals(x, y) ? nothing() : just(format("S <%s>, B <%s> (%s != %s)", s, b, x, y)); + })) + .fmap(catMaybes()) + .fmap(reduceLeft((x, y) -> x + "\n\t - " + y)) + .fmap(maybeFailures -> maybeFailures.fmap(failures -> "\"" + label + "\" failed for the following cases:\n\n\t - " + failures)) + .apply(cases); + } + + private static final class PrismResult { + private final Maybe maybeS; + + private PrismResult(Maybe maybeS) { + this.maybeS = maybeS; + } + + @Override + public boolean equals(Object other) { + if (other instanceof PrismResult) { + return maybeS.zip(((PrismResult) other).maybeS.fmap(fn2(Objects::equals))).orElse(true); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(maybeS); + } + + @Override + public String toString() { + return maybeS.toString(); + } + } +} diff --git a/src/test/java/testsupport/concurrent/Turnstile.java b/src/test/java/testsupport/concurrent/Turnstile.java deleted file mode 100644 index 96da97d30..000000000 --- a/src/test/java/testsupport/concurrent/Turnstile.java +++ /dev/null @@ -1,53 +0,0 @@ -package testsupport.concurrent; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; - -import static java.lang.Thread.currentThread; -import static java.util.concurrent.locks.LockSupport.park; -import static java.util.concurrent.locks.LockSupport.unpark; -import static java.util.stream.StreamSupport.stream; - -public final class Turnstile { - - private final int parties; - private final BlockingQueue arrivals; - - public Turnstile(int parties) { - this.parties = parties; - arrivals = new ArrayBlockingQueue<>(parties); - } - - public void arrive() { - synchronized (this) { - arrivals.add(currentThread()); - if (arrivals.size() == parties) { - allowThrough(parties); - return; - } - } - park(); - } - - public void allowAllThrough() { - allowThrough(arrivals.size()); - } - - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - private void allowThrough(int parked) { - Thread currentThread = currentThread(); - Collection allowedThrough = new ArrayList<>(); - arrivals.drainTo(allowedThrough, parked); - stream(allowedThrough.spliterator(), false) - .filter(t -> !t.equals(currentThread)) - .forEach(t -> { - unpark(t); - try { - t.join(); - } catch (InterruptedException ignored) { - } - }); - } -} diff --git a/src/test/java/testsupport/exceptions/OutOfScopeException.java b/src/test/java/testsupport/exceptions/OutOfScopeException.java index b723b7d4c..8e8f0e4b6 100644 --- a/src/test/java/testsupport/exceptions/OutOfScopeException.java +++ b/src/test/java/testsupport/exceptions/OutOfScopeException.java @@ -1,5 +1,6 @@ package testsupport.exceptions; +@SuppressWarnings("serial") public class OutOfScopeException extends RuntimeException { public OutOfScopeException(String s) { diff --git a/src/test/java/testsupport/functions/ExplainFold.java b/src/test/java/testsupport/functions/ExplainFold.java index a168674e3..ba5c29527 100644 --- a/src/test/java/testsupport/functions/ExplainFold.java +++ b/src/test/java/testsupport/functions/ExplainFold.java @@ -1,12 +1,12 @@ package testsupport.functions; -import java.util.function.BiFunction; +import com.jnape.palatable.lambda.functions.Fn2; import static java.lang.String.format; public class ExplainFold { - public static BiFunction explainFold() { - return (acc, x) -> format("(%s + %s)", acc, x); + public static Fn2 explainFold() { + return (x, y) -> format("(%s + %s)", x, y); } } diff --git a/src/test/java/testsupport/matchers/EitherMatcher.java b/src/test/java/testsupport/matchers/EitherMatcher.java new file mode 100644 index 000000000..b18f68af6 --- /dev/null +++ b/src/test/java/testsupport/matchers/EitherMatcher.java @@ -0,0 +1,72 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.Either; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.CoreMatchers.anything; +import static org.hamcrest.CoreMatchers.equalTo; + +public final class EitherMatcher extends TypeSafeMatcher> { + private final Either, Matcher> matcher; + + private EitherMatcher(Either, Matcher> matcher) { + this.matcher = matcher; + } + + @Override + protected void describeMismatchSafely(Either item, Description mismatchDescription) { + mismatchDescription.appendText("was "); + item.match(l -> matcher.match(lMatcher -> io(() -> lMatcher.describeMismatch(l, mismatchDescription)), + rMatcher -> io(() -> mismatchDescription.appendValue(item))), + r -> matcher.match(lMatcher -> io(() -> mismatchDescription.appendValue(item)), + lMatcher -> io(() -> lMatcher.describeMismatch(r, mismatchDescription)))) + .unsafePerformIO(); + } + + @Override + protected boolean matchesSafely(Either actual) { + return actual.match(l -> matcher.match(lMatcher -> lMatcher.matches(l), + constantly(false)), + r -> matcher.match(constantly(false), + rMatcher -> rMatcher.matches(r))); + } + + @Override + public void describeTo(Description description) { + matcher.match(l -> io(() -> description.appendText("Left value of ")) + .flatMap(constantly(io(() -> l.describeTo(description)))), + r -> io(() -> description.appendText("Right value of ")) + .flatMap(constantly(io(() -> r.describeTo(description))))) + .unsafePerformIO(); + } + + public static EitherMatcher isLeftThat(Matcher lMatcher) { + return new EitherMatcher<>(left(lMatcher)); + } + + public static EitherMatcher isLeft() { + return isLeftThat(anything()); + } + + public static EitherMatcher isLeftOf(L l) { + return isLeftThat(equalTo(l)); + } + + public static EitherMatcher isRightThat(Matcher rMatcher) { + return new EitherMatcher<>(right(rMatcher)); + } + + public static EitherMatcher isRight() { + return isRightThat(anything()); + } + + public static EitherMatcher isRightOf(R r) { + return isRightThat(equalTo(r)); + } +} diff --git a/src/test/java/testsupport/matchers/FiniteIterableMatcher.java b/src/test/java/testsupport/matchers/FiniteIterableMatcher.java index 609f54d51..7f5b57d58 100644 --- a/src/test/java/testsupport/matchers/FiniteIterableMatcher.java +++ b/src/test/java/testsupport/matchers/FiniteIterableMatcher.java @@ -3,7 +3,7 @@ import org.hamcrest.BaseMatcher; import org.hamcrest.Description; -public class FiniteIterableMatcher extends BaseMatcher { +public class FiniteIterableMatcher extends BaseMatcher> { @Override public boolean matches(Object item) { @@ -21,7 +21,7 @@ public void describeMismatch(Object item, Description description) { } @SuppressWarnings("UnusedDeclaration") - private boolean supportsLessThanInfiniteIterations(Iterable iterable) { + private boolean supportsLessThanInfiniteIterations(Iterable iterable) { long sufficientlyInfinite = 1000000; long elementsIterated = 0; for (Object ignored : iterable) diff --git a/src/test/java/testsupport/matchers/IOMatcher.java b/src/test/java/testsupport/matchers/IOMatcher.java new file mode 100644 index 000000000..080cedf2e --- /dev/null +++ b/src/test/java/testsupport/matchers/IOMatcher.java @@ -0,0 +1,61 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.io.IO; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import java.util.concurrent.atomic.AtomicReference; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.Matchers.anything; + +public final class IOMatcher extends TypeSafeMatcher> { + + private final Either, Matcher> matcher; + private final AtomicReference> resultRef; + + private IOMatcher(Either, Matcher> matcher) { + this.matcher = matcher; + resultRef = new AtomicReference<>(); + } + + @Override + protected boolean matchesSafely(IO io) { + Either res = io.safe().unsafePerformIO(); + resultRef.set(res); + return res.match(t -> matcher.match(tMatcher -> tMatcher.matches(t), + aMatcher -> false), + a -> matcher.match(tMatcher -> false, + aMatcher -> aMatcher.matches(a))); + } + + @Override + public void describeTo(Description description) { + matcher.match(m -> io(() -> m.describeTo(description.appendText("IO throwing exception matching "))), + m -> io(() -> m.describeTo(description.appendText("IO yielding value matching ")))) + .unsafePerformIO(); + } + + @Override + protected void describeMismatchSafely(IO item, Description mismatchDescription) { + resultRef.get().match(t -> io(() -> mismatchDescription.appendText("IO threw " + t)), + a -> io(() -> mismatchDescription.appendText("IO yielded value " + a))) + .unsafePerformIO(); + } + + public static IOMatcher yieldsValue(Matcher matcher) { + return new IOMatcher<>(right(matcher)); + } + + public static IOMatcher completesNormally() { + return yieldsValue(anything()); + } + + public static IOMatcher throwsException(Matcher throwableMatcher) { + return new IOMatcher<>(left(throwableMatcher)); + } +} diff --git a/src/test/java/testsupport/matchers/IterableMatcher.java b/src/test/java/testsupport/matchers/IterableMatcher.java index 8b81d6d7d..ac1faee71 100644 --- a/src/test/java/testsupport/matchers/IterableMatcher.java +++ b/src/test/java/testsupport/matchers/IterableMatcher.java @@ -5,21 +5,21 @@ import java.util.ArrayList; import java.util.Iterator; +import java.util.Objects; import static java.util.Arrays.asList; -import static org.apache.commons.lang3.builder.EqualsBuilder.reflectionEquals; -public class IterableMatcher extends BaseMatcher { +public class IterableMatcher extends BaseMatcher> { - private final Iterable expected; + private final Iterable expected; - public IterableMatcher(Iterable expected) { + private IterableMatcher(Iterable expected) { this.expected = expected; } @Override public boolean matches(Object actual) { - return actual instanceof Iterable && iterablesIterateSameElementsInOrder(expected, (Iterable) actual); + return actual instanceof Iterable && iterablesIterateSameElementsInOrder(expected, (Iterable) actual); } @Override @@ -29,37 +29,39 @@ public void describeTo(Description description) { @Override public void describeMismatch(Object item, Description description) { - if (item instanceof Iterable) - description.appendText("was <").appendText(stringify((Iterable) item)).appendText(">"); - else + if (item instanceof Iterable) { + if (description.toString().endsWith("but: ")) + description.appendText("was "); + description.appendText("<").appendText(stringify((Iterable) item)).appendText(">"); + } else super.describeMismatch(item, description); } - private boolean iterablesIterateSameElementsInOrder(Iterable expected, Iterable actual) { - Iterator actualIterator = actual.iterator(); - Iterator expectedIterator = expected.iterator(); + private boolean iterablesIterateSameElementsInOrder(Iterable expected, Iterable actual) { + Iterator actualIterator = actual.iterator(); + Iterator expectedIterator = expected.iterator(); while (expectedIterator.hasNext() && actualIterator.hasNext()) { Object nextExpected = expectedIterator.next(); - Object nextActual = actualIterator.next(); + Object nextActual = actualIterator.next(); if (nextExpected instanceof Iterable && nextActual instanceof Iterable) { - if (!iterablesIterateSameElementsInOrder((Iterable) nextExpected, (Iterable) nextActual)) + if (!iterablesIterateSameElementsInOrder((Iterable) nextExpected, (Iterable) nextActual)) return false; - } else if (!reflectionEquals(nextExpected, nextActual)) + } else if (!Objects.equals(nextExpected, nextActual)) return false; } return actualIterator.hasNext() == expectedIterator.hasNext(); } - private String stringify(Iterable iterable) { + private String stringify(Iterable iterable) { StringBuilder stringBuilder = new StringBuilder().append("["); - Iterator iterator = iterable.iterator(); + Iterator iterator = iterable.iterator(); while (iterator.hasNext()) { Object next = iterator.next(); if (next instanceof Iterable) - stringBuilder.append(stringify((Iterable) next)); + stringBuilder.append(stringify((Iterable) next)); else stringBuilder.append(next); if (iterator.hasNext()) @@ -69,11 +71,16 @@ private String stringify(Iterable iterable) { } @SafeVarargs - public static IterableMatcher iterates(Element... elements) { - return new IterableMatcher(asList(elements)); + @SuppressWarnings("varargs") + public static IterableMatcher iterates(E... es) { + return new IterableMatcher<>(asList(es)); } - public static IterableMatcher isEmpty() { - return new IterableMatcher(new ArrayList()); + public static IterableMatcher iteratesAll(Iterable es) { + return new IterableMatcher<>(es); + } + + public static IterableMatcher isEmpty() { + return new IterableMatcher<>(new ArrayList<>()); } } diff --git a/src/test/java/testsupport/matchers/IterateTMatcher.java b/src/test/java/testsupport/matchers/IterateTMatcher.java new file mode 100644 index 000000000..894fa6edd --- /dev/null +++ b/src/test/java/testsupport/matchers/IterateTMatcher.java @@ -0,0 +1,48 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.monad.transformer.builtin.IterateT; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +import java.util.LinkedList; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; + +public final class IterateTMatcher extends TypeSafeMatcher, A>> { + private final Iterable as; + + private IterateTMatcher(Iterable as) { + this.as = as; + } + + @Override + protected boolean matchesSafely(IterateT, A> iterateT) { + Identity> fold = iterateT.fold((as, a) -> { + as.add(a); + return new Identity<>(as); + }, new Identity<>(new LinkedList<>())); + LinkedList as = fold.runIdentity(); + return IterableMatcher.iteratesAll(this.as).matches(as); + } + + @Override + public void describeTo(Description description) { + description.appendText("an IterateT iterating " + as.toString() + " inside Identity"); + } + + public static IterateTMatcher iteratesAll(Iterable as) { + return new IterateTMatcher<>(as); + } + + public static IterateTMatcher isEmpty() { + return new IterateTMatcher<>(emptyList()); + } + + @SafeVarargs + @SuppressWarnings("varargs") + public static IterateTMatcher iterates(A... as) { + return iteratesAll(asList(as)); + } +} diff --git a/src/test/java/testsupport/matchers/RateLimitedIterationMatcher.java b/src/test/java/testsupport/matchers/RateLimitedIterationMatcher.java new file mode 100644 index 000000000..c0f2d56ab --- /dev/null +++ b/src/test/java/testsupport/matchers/RateLimitedIterationMatcher.java @@ -0,0 +1,68 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.functions.builtin.fn2.Eq; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import testsupport.time.InstantRecordingClock; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.All.all; +import static com.jnape.palatable.lambda.functions.builtin.fn2.InGroupsOf.inGroupsOf; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Slide.slide; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Zip.zip; +import static java.time.Duration.between; + +public final class RateLimitedIterationMatcher extends TypeSafeMatcher> { + private final Iterable elements; + private final Duration delay; + private final InstantRecordingClock clock; + private final Long limit; + + public RateLimitedIterationMatcher(Long limit, Duration delay, Iterable elements, InstantRecordingClock clock) { + this.elements = elements; + this.delay = delay; + this.clock = clock; + this.limit = limit; + } + + @Override + protected boolean matchesSafely(Iterable xs) { + xs.forEach(__ -> clock.saveLastInstant()); + + Boolean enoughDelay = all(d -> d.toNanos() > delay.toNanos(), map(boundaries -> { + Iterator it = boundaries.iterator(); + Instant first = it.next(); + Instant second = it.next(); + return between(first, second); + }, slide(2, map(instants -> instants.iterator().next(), inGroupsOf(limit.intValue(), clock.instants()))))); + + Boolean sameElements = all(Eq.eq().uncurry(), zip(elements, xs)); + + return enoughDelay && sameElements; + } + + @Override + public void describeTo(Description description) { + description.appendText("Iterated elements " + toCollection(ArrayList::new, elements) + " with at least " + delay.toMillis() + "ms between groups of " + limit); + } + + @Override + protected void describeMismatchSafely(Iterable item, Description mismatchDescription) { + mismatchDescription.appendText("Iterated elements " + + toCollection(ArrayList::new, item) + + " with the following delays between groups: " + + toCollection(ArrayList::new, map(instants -> instants.iterator().next(), inGroupsOf(limit.intValue(), clock.instants())))); + } + + public static RateLimitedIterationMatcher iteratesAccordingToRateLimit(Long limit, Duration duration, + Iterable elements, + InstantRecordingClock clock) { + return new RateLimitedIterationMatcher<>(limit, duration, elements, clock); + } +} diff --git a/src/test/java/testsupport/matchers/StateMatcher.java b/src/test/java/testsupport/matchers/StateMatcher.java new file mode 100644 index 000000000..339deada8 --- /dev/null +++ b/src/test/java/testsupport/matchers/StateMatcher.java @@ -0,0 +1,90 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.These; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functor.builtin.State; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import static com.jnape.palatable.lambda.adt.These.a; +import static com.jnape.palatable.lambda.adt.These.b; +import static com.jnape.palatable.lambda.adt.These.both; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.Matchers.equalTo; + +public final class StateMatcher extends TypeSafeMatcher> { + private final S initialState; + private final These, Matcher> matchers; + + private StateMatcher(S initialState, These, Matcher> matchers) { + this.initialState = initialState; + this.matchers = matchers; + } + + @Override + protected boolean matchesSafely(State item) { + Tuple2 ran = item.run(initialState); + return matchers.match(a -> a.matches(ran._1()), + b -> b.matches(ran._2()), + ab -> ab._1().matches(ran._1()) && ab._2().matches(ran._2())); + } + + @Override + public void describeTo(Description description) { + matchers.match(a -> io(() -> a.describeTo(description.appendText("Value matching "))), + b -> io(() -> b.describeTo(description.appendText("State matching "))), + ab -> io(() -> { + description.appendText("Value matching: "); + ab._1().describeTo(description); + description.appendText(" and state matching: "); + ab._2().describeTo(description); + })) + .unsafePerformIO(); + } + + @Override + protected void describeMismatchSafely(State item, Description mismatchDescription) { + Tuple2 ran = item.run(initialState); + matchers.match(a -> io(() -> { + mismatchDescription.appendText("value matching "); + a.describeMismatch(ran._1(), mismatchDescription); + }), + b -> io(() -> { + mismatchDescription.appendText("state matching "); + b.describeMismatch(ran._2(), mismatchDescription); + }), + ab -> io(() -> { + mismatchDescription.appendText("value matching: "); + ab._1().describeMismatch(ran._1(), mismatchDescription); + mismatchDescription.appendText(" and state matching: "); + ab._2().describeMismatch(ran._2(), mismatchDescription); + })) + .unsafePerformIO(); + } + + public static StateMatcher whenRunWith(S initialState, Matcher valueMatcher, + Matcher stateMatcher) { + return new StateMatcher<>(initialState, both(valueMatcher, stateMatcher)); + } + + public static StateMatcher whenRun(S initialState, A value, S state) { + return whenRunWith(initialState, equalTo(value), equalTo(state)); + } + + public static StateMatcher whenExecutedWith(S initialState, Matcher stateMatcher) { + return new StateMatcher<>(initialState, b(stateMatcher)); + } + + public static StateMatcher whenExecuted(S initialState, S state) { + return whenExecutedWith(initialState, equalTo(state)); + } + + public static StateMatcher whenEvaluatedWith(S initialState, Matcher valueMatcher) { + return new StateMatcher<>(initialState, a(valueMatcher)); + } + + public static StateMatcher whenEvaluated(S initialState, A value) { + return whenEvaluatedWith(initialState, equalTo(value)); + } +} diff --git a/src/test/java/testsupport/matchers/StateTMatcher.java b/src/test/java/testsupport/matchers/StateTMatcher.java new file mode 100644 index 000000000..8bfa97684 --- /dev/null +++ b/src/test/java/testsupport/matchers/StateTMatcher.java @@ -0,0 +1,144 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.These; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.builtin.StateT; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.These.a; +import static com.jnape.palatable.lambda.adt.These.b; +import static com.jnape.palatable.lambda.adt.These.both; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.Matchers.equalTo; + +public final class StateTMatcher, A> extends TypeSafeMatcher> { + private final S initialState; + + private final Either< + Matcher, M>>, + These>, Matcher>>> matcher; + + private StateTMatcher(S initialState, + Either, M>>, + These>, Matcher>>> matcher) { + this.initialState = initialState; + this.matcher = matcher; + } + + @Override + protected boolean matchesSafely(StateT item) { + MonadRec, M> ran = item.runStateT(initialState); + return matcher.match(bothMatcher -> bothMatcher.matches(ran), + theseMatchers -> theseMatchers.match( + a -> a.matches(ran.fmap(Tuple2::_1)), + b -> b.matches(ran.fmap(Tuple2::_2)), + ab -> ab._1().matches(ran.fmap(Tuple2::_1)) + && ab._2().matches(ran.fmap(Tuple2::_2)))); + } + + @Override + public void describeTo(Description description) { + matcher.match(both -> io(() -> both.describeTo(description.appendText("Value and state matching "))), + these -> these.match( + a -> io(() -> a.describeTo(description.appendText("Value matching "))), + b -> io(() -> b.describeTo(description.appendText("State matching "))), + ab -> io(() -> { + description.appendText("Value run matching: "); + ab._1().describeTo(description); + description.appendText(", then state run matching: "); + ab._2().describeTo(description); + }))) + .unsafePerformIO(); + } + + @Override + protected void describeMismatchSafely(StateT item, Description mismatchDescription) { + MonadRec, M> ran = item.runStateT(initialState); + + matcher.match(bothMatcher -> io(() -> { + mismatchDescription.appendText("value and state matching "); + bothMatcher.describeMismatch(ran, mismatchDescription); + }), + theseMatchers -> theseMatchers.match( + a -> io(() -> { + mismatchDescription.appendText("value matching "); + a.describeMismatch(ran.fmap(Tuple2::_1), mismatchDescription); + }), + b -> io(() -> { + mismatchDescription.appendText("state matching "); + b.describeMismatch(ran.fmap(Tuple2::_2), mismatchDescription); + }), + ab -> io(() -> { + mismatchDescription.appendText("value run matching: "); + ab._1().describeMismatch(ran.fmap(Tuple2::_1), mismatchDescription); + mismatchDescription.appendText(", then state run matching: "); + ab._2().describeMismatch(ran.fmap(Tuple2::_2), mismatchDescription); + }))) + .unsafePerformIO(); + } + + public static , A, MAS extends MonadRec, M>> StateTMatcher + whenRunWith(S initialState, Matcher bothMatcher) { + return new StateTMatcher(initialState, left(extendMatcher(bothMatcher))); + } + + public static , A> StateTMatcher whenRun( + S initialState, MonadRec, M> both) { + return whenRunWith(initialState, equalTo(both)); + } + + public static , A, MA extends MonadRec, MS extends MonadRec> + StateTMatcher whenRunWithBoth(S initialState, + Matcher valueMatcher, + Matcher stateMatcher) { + return new StateTMatcher(initialState, right(both(extendMatcher(valueMatcher), + extendMatcher(stateMatcher)))); + } + + public static , A> StateTMatcher whenRunBoth(S initialState, + MonadRec value, + MonadRec state) { + return whenRunWithBoth(initialState, equalTo(value), equalTo(state)); + } + + public static , A, MS extends MonadRec> StateTMatcher whenExecutedWith( + S initialState, Matcher stateMatcher) { + return new StateTMatcher(initialState, right(b(extendMatcher(stateMatcher)))); + } + + public static , A> StateTMatcher whenExecuted(S initialState, + MonadRec state) { + return whenExecutedWith(initialState, equalTo(state)); + } + + public static , A, MA extends MonadRec> StateTMatcher whenEvaluatedWith( + S initialState, Matcher valueMatcher) { + return new StateTMatcher(initialState, right(a(extendMatcher(valueMatcher)))); + } + + public static , A> StateTMatcher whenEvaluated(S initialState, + MonadRec value) { + return whenEvaluatedWith(initialState, equalTo(value)); + } + + private static , MX extends MonadRec> Matcher> extendMatcher( + Matcher matcher) { + return new TypeSafeMatcher>() { + @Override + protected boolean matchesSafely(MonadRec item) { + return matcher.matches(item); + } + + @Override + public void describeTo(Description description) { + matcher.describeTo(description); + } + }; + } +} diff --git a/src/test/java/testsupport/matchers/WriterTMatcher.java b/src/test/java/testsupport/matchers/WriterTMatcher.java new file mode 100644 index 000000000..36e2786bb --- /dev/null +++ b/src/test/java/testsupport/matchers/WriterTMatcher.java @@ -0,0 +1,81 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.builtin.WriterT; +import com.jnape.palatable.lambda.monoid.Monoid; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +public final class WriterTMatcher, A> extends + TypeSafeMatcher>> { + + private final Matcher, M>> expected; + private final Monoid wMonoid; + + private WriterTMatcher(Matcher, M>> expected, Monoid wMonoid) { + this.wMonoid = wMonoid; + this.expected = expected; + } + + @Override + protected boolean matchesSafely(MonadRec> item) { + return expected.matches(item.>coerce().runWriterT(wMonoid)); + } + + @Override + public void describeTo(Description description) { + expected.describeTo(description); + } + + @Override + protected void describeMismatchSafely(MonadRec> item, Description mismatchDescription) { + expected.describeMismatch(item.>coerce().runWriterT(wMonoid), mismatchDescription); + } + + public static , A, MAW extends MonadRec, M>> + WriterTMatcher whenRunWith(Monoid wMonoid, Matcher matcher) { + return new WriterTMatcher<>(matcher, wMonoid); + } + + public static , A, MW extends MonadRec> + WriterTMatcher whenExecutedWith(Monoid wMonoid, Matcher matcher) { + return whenRunWith(wMonoid, new TypeSafeMatcher, M>>() { + @Override + protected boolean matchesSafely(MonadRec, M> item) { + return matcher.matches(item.fmap(Tuple2::_2)); + } + + @Override + public void describeTo(Description description) { + matcher.describeTo(description); + } + + @Override + protected void describeMismatchSafely(MonadRec, M> item, Description mismatchDescription) { + matcher.describeMismatch(item.fmap(Tuple2::_2), mismatchDescription); + } + }); + } + + public static , A, MA extends MonadRec> + WriterTMatcher whenEvaluatedWith(Monoid wMonoid, Matcher matcher) { + return whenRunWith(wMonoid, new TypeSafeMatcher, M>>() { + @Override + protected boolean matchesSafely(MonadRec, M> item) { + return matcher.matches(item.fmap(Tuple2::_1)); + } + + @Override + public void describeTo(Description description) { + matcher.describeTo(description); + } + + @Override + protected void describeMismatchSafely(MonadRec, M> item, Description mismatchDescription) { + matcher.describeMismatch(item.fmap(Tuple2::_1), mismatchDescription); + } + }); + } +} \ No newline at end of file diff --git a/src/test/java/testsupport/matchers/ZeroInvocationsMatcher.java b/src/test/java/testsupport/matchers/ZeroInvocationsMatcher.java index 3757eef2c..9c360867c 100644 --- a/src/test/java/testsupport/matchers/ZeroInvocationsMatcher.java +++ b/src/test/java/testsupport/matchers/ZeroInvocationsMatcher.java @@ -5,10 +5,10 @@ import org.hamcrest.Matcher; import org.mockito.exceptions.misusing.NotAMockException; import org.mockito.exceptions.verification.NoInteractionsWanted; -import org.mockito.internal.util.MockUtil; import org.mockito.invocation.Invocation; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.internal.util.MockUtil.getInvocationContainer; public class ZeroInvocationsMatcher extends BaseMatcher { @Override @@ -26,7 +26,7 @@ public boolean matches(Object item) { @Override public void describeMismatch(Object item, final Description description) { description.appendText("had these: "); - for (Invocation invocation : new MockUtil().getMockHandler(item).getInvocationContainer().getInvocations()) + for (Invocation invocation : getInvocationContainer(item).getInvocations()) description.appendText(invocation.toString()); } diff --git a/src/test/java/testsupport/time/InstantRecordingClock.java b/src/test/java/testsupport/time/InstantRecordingClock.java new file mode 100644 index 000000000..421971861 --- /dev/null +++ b/src/test/java/testsupport/time/InstantRecordingClock.java @@ -0,0 +1,42 @@ +package testsupport.time; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; + +public final class InstantRecordingClock extends Clock { + private final Clock clock; + private final List instants; + private Instant lastInstant; + + public InstantRecordingClock(Clock clock) { + this.clock = clock; + instants = new ArrayList<>(); + } + + @Override + public ZoneId getZone() { + return clock.getZone(); + } + + @Override + public Clock withZone(ZoneId zone) { + return new InstantRecordingClock(clock.withZone(zone)); + } + + @Override + public Instant instant() { + return lastInstant = clock.instant(); + } + + public Instant saveLastInstant() { + instants.add(lastInstant); + return lastInstant; + } + + public List instants() { + return instants; + } +} diff --git a/src/test/java/testsupport/traits/ApplicativeLaws.java b/src/test/java/testsupport/traits/ApplicativeLaws.java new file mode 100644 index 000000000..a193889fd --- /dev/null +++ b/src/test/java/testsupport/traits/ApplicativeLaws.java @@ -0,0 +1,106 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import java.util.Random; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static java.util.Arrays.asList; + +public class ApplicativeLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return Applicative.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + f -> f.apply(equivalence), + asList(this::testIdentity, + this::testComposition, + this::testHomomorphism, + this::testInterchange, + this::testDiscardL, + this::testDiscardR, + this::testLazyZip)) + .match(IO::io, + s -> throwing(new AssertionError("The following Applicative laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testIdentity(Equivalence> equivalence) { + return equivalence.invMap(app -> app.zip(app.pure(id()))).equals(equivalence) + ? nothing() + : just("identity (v.zip(pureId).equals(v))"); + } + + private Maybe testComposition(Equivalence> equivalence) { + Random random = new Random(); + Integer firstInt = random.nextInt(100); + Integer secondInt = random.nextInt(100); + + return equivalence.invMap(app -> app.pure("result: ") + .zip(app.>pure(x1 -> x1 + secondInt) + .zip(app.>pure(x1 -> x1 + firstInt) + .zip(app.pure(x1 -> x1::contraMap))))) + .equals(equivalence.invMap(app -> app.pure("result: ") + .zip(app.>pure(x -> x + secondInt)) + .zip(app.>pure(x -> x + firstInt)))) + ? nothing() + : just("composition (w.zip(v.zip(u.zip(pureCompose))).equals((w.zip(v)).zip(u)))"); + } + + private Maybe testHomomorphism(Equivalence> equivalence) { + Fn1 f = x -> x + 1; + int x = 1; + + return equivalence.invMap(app -> app.pure(x).zip(app.pure(f))) + .equals(equivalence.invMap(app -> app.pure(f.apply(x)))) + ? nothing() + : just("homomorphism (pureX.zip(pureF).equals(pureFx))"); + } + + private Maybe testInterchange(Equivalence> equivalence) { + int y = 1; + return equivalence.invMap(app -> app.pure(y).zip(app.pure(x -> x + 1))) + .equals(equivalence.invMap(app -> app.>pure(x -> x + 1) + .zip(app.pure(f -> f.apply(y))))) + ? nothing() + : just("interchange (pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))))"); + } + + private Maybe testDiscardL(Equivalence> equivalence) { + return equivalence.invMap(app -> app.pure("u").discardL(app.pure("v"))) + .equals(equivalence.invMap(app -> app.pure("v") + .zip(app.pure("u").zip(app.pure(constantly(id())))))) + ? nothing() + : just("discardL u.discardL(v).equals(v.zip(u.zip(applicative.pure(constantly(identity())))))"); + } + + private Maybe testDiscardR(Equivalence> equivalence) { + return equivalence.invMap(app -> app.pure("u").discardR(app.pure("v"))) + .equals(equivalence.invMap(app -> app.pure("v").zip(app.pure("u").zip(app.pure(constantly()))))) + ? nothing() + : just("discardR u.discardR(v).equals(v.zip(u.zip(applicative.pure(constantly()))))"); + } + + private Maybe testLazyZip(Equivalence> equivalence) { + return equivalence.invMap(app -> app.lazyZip(lazy(app.pure(id()))).value()) + .equals(equivalence.invMap(app -> app.zip(app.pure(id())))) + ? nothing() + : just("lazyZip app.zip(lazy(app.pure(id()))).equals(app.zip(app.pure(id())))"); + } +} diff --git a/src/test/java/testsupport/traits/BifunctorLaws.java b/src/test/java/testsupport/traits/BifunctorLaws.java new file mode 100644 index 000000000..ba5f88d7e --- /dev/null +++ b/src/test/java/testsupport/traits/BifunctorLaws.java @@ -0,0 +1,55 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static java.util.Arrays.asList; + +public class BifunctorLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return Bifunctor.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + f -> f.apply(equivalence), + asList(this::testLeftIdentity, + this::testRightIdentity, + this::testMutualIdentity) + ) + .match(IO::io, + s -> throwing(new AssertionError("The following Bifunctor laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testLeftIdentity(Equivalence> equivalence) { + return equivalence.invMap(bf -> bf.biMapL(id())).equals(equivalence) + ? nothing() + : just("left identity (bifunctor.biMapL(id()).equals(bifunctor))"); + } + + private Maybe testRightIdentity(Equivalence> equivalence) { + return equivalence.invMap(bf -> bf.biMapR(id())).equals(equivalence) + ? nothing() + : just("right identity (bifunctor.biMapR(id()).equals(bifunctor))"); + } + + private Maybe testMutualIdentity(Equivalence> equivalence) { + return equivalence.invMap(bf -> bf.biMapL(id()).biMapR(id())) + .equals(equivalence.invMap(bf -> bf.biMap(id(), id()))) + ? nothing() + : just("mutual identity (bifunctor.biMapL(id()).biMapR(id()).equals(bifunctor.biMap(id(),id()))"); + } +} diff --git a/src/test/java/testsupport/traits/Deforesting.java b/src/test/java/testsupport/traits/Deforesting.java new file mode 100644 index 000000000..cb38b87ef --- /dev/null +++ b/src/test/java/testsupport/traits/Deforesting.java @@ -0,0 +1,15 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.traits.Trait; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; + +public final class Deforesting implements Trait, Iterable>> { + + @Override + public void test(Fn1, Iterable> fn) { + times(10_000, fn, repeat(1)).iterator().next(); + } +} diff --git a/src/test/java/testsupport/traits/EmptyIterableSupport.java b/src/test/java/testsupport/traits/EmptyIterableSupport.java index a0d2d029a..d30b57a3e 100644 --- a/src/test/java/testsupport/traits/EmptyIterableSupport.java +++ b/src/test/java/testsupport/traits/EmptyIterableSupport.java @@ -5,12 +5,12 @@ import java.util.ArrayList; -public class EmptyIterableSupport implements Trait> { +public class EmptyIterableSupport implements Trait, ?>> { @Override - public void test(Fn1 testSubject) { + public void test(Fn1, ?> testSubject) { try { - testSubject.apply(new ArrayList()); + testSubject.apply(new ArrayList<>()); } catch (Exception e) { throw new AssertionError("Expected support for empty iterable arguments", e); } diff --git a/src/test/java/testsupport/traits/Equivalence.java b/src/test/java/testsupport/traits/Equivalence.java new file mode 100644 index 000000000..7421256f3 --- /dev/null +++ b/src/test/java/testsupport/traits/Equivalence.java @@ -0,0 +1,53 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +public final class Equivalence { + + private final A value; + private final Fn1 eq; + + private Equivalence(A value, Fn1 eq) { + this.value = value; + this.eq = eq; + } + + public A getValue() { + return value; + } + + public Equivalence swap(A a) { + return invMap(constantly(a)); + } + + public Equivalence invMap(Fn1 equivalence) { + return new Equivalence<>(value, equivalence.fmap(this.eq)); + } + + @Override + public String toString() { + return value.getClass().getSimpleName() + " (surrogate)"; + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Equivalence) { + @SuppressWarnings("unchecked") Equivalence other = (Equivalence) obj; + return Objects.equals(eq.apply(value), other.eq.apply(other.value)); + } + return false; + } + + public static Equivalence equivalence(A value, Fn1 equivalence) { + return new Equivalence<>(value, equivalence); + } +} diff --git a/src/test/java/testsupport/traits/EquivalenceTrait.java b/src/test/java/testsupport/traits/EquivalenceTrait.java new file mode 100644 index 000000000..6ac3dbee5 --- /dev/null +++ b/src/test/java/testsupport/traits/EquivalenceTrait.java @@ -0,0 +1,33 @@ +package testsupport.traits; + +import com.jnape.palatable.traitor.traits.Trait; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static testsupport.traits.Equivalence.equivalence; + +public interface EquivalenceTrait extends Trait { + + Class type(); + + void test(Equivalence equivalence); + + @Override + default void test(Object value) { + Class type = type(); + if (value instanceof Equivalence) { + if (type.isInstance(((Equivalence) value).getValue())) { + @SuppressWarnings("unchecked") Equivalence equivalenceC = (Equivalence) value; + test(equivalenceC); + return; + } else { + throw new ClassCastException("Unable to create " + type.getSimpleName() + + " surrogate for value of type " + value.getClass()); + } + } + if (!type.isInstance(value)) + throw new ClassCastException("Unable to create " + type.getSimpleName() + " surrogate for value of type " + + value.getClass().getSimpleName()); + test(equivalence(downcast(value), id())); + } +} diff --git a/src/test/java/testsupport/traits/FiniteIteration.java b/src/test/java/testsupport/traits/FiniteIteration.java index 882632f17..03168d2b6 100644 --- a/src/test/java/testsupport/traits/FiniteIteration.java +++ b/src/test/java/testsupport/traits/FiniteIteration.java @@ -8,11 +8,11 @@ import static org.hamcrest.core.Is.is; import static testsupport.matchers.FiniteIterableMatcher.finitelyIterable; -public class FiniteIteration implements Trait> { +public class FiniteIteration implements Trait, Iterable>> { @Override - public void test(Fn1 testSubject) { - Iterable result = testSubject.apply(asList(1, 2, 3)); + public void test(Fn1, Iterable> testSubject) { + Iterable result = testSubject.apply(asList(1, 2, 3)); assertThat(result, is(finitelyIterable())); } } diff --git a/src/test/java/testsupport/traits/FunctorLaws.java b/src/test/java/testsupport/traits/FunctorLaws.java new file mode 100644 index 000000000..0371b1c81 --- /dev/null +++ b/src/test/java/testsupport/traits/FunctorLaws.java @@ -0,0 +1,50 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static java.util.Arrays.asList; + +public class FunctorLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return Functor.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + fn -> fn.apply(equivalence), + asList(this::testIdentity, + this::testComposition)) + .match(IO::io, + s -> throwing(new AssertionError("The following Functor laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testIdentity(Equivalence> equivalence) { + return equivalence.invMap(f -> f.fmap(id())).equals(equivalence) + ? nothing() + : just("identity (f.fmap(identity()).equals(f))"); + } + + private Maybe testComposition(Equivalence> equivalence) { + Fn1 g = x -> x * 3; + Fn1 h = x -> x - 2; + return equivalence.invMap(f -> f.fmap(constantly(1)).fmap(g).fmap(h)) + .equals(equivalence.invMap(f -> f.fmap(constantly(1)).fmap(g.fmap(h)))) + ? nothing() + : just("composition (functor.fmap(f.contraMap(g)).equals(functor.fmap(g).fmap(f)))"); + } +} diff --git a/src/test/java/testsupport/traits/ImmutableIteration.java b/src/test/java/testsupport/traits/ImmutableIteration.java index ce6e7db55..d6a74737e 100644 --- a/src/test/java/testsupport/traits/ImmutableIteration.java +++ b/src/test/java/testsupport/traits/ImmutableIteration.java @@ -7,11 +7,11 @@ import static org.junit.Assert.fail; -public class ImmutableIteration implements Trait> { +public class ImmutableIteration implements Trait, Iterable>> { @Override - public void test(Fn1 testSubject) { - Iterable result = testSubject.apply(new ArrayList()); + public void test(Fn1, Iterable> testSubject) { + Iterable result = testSubject.apply(new ArrayList<>()); try { result.iterator().remove(); fail("Expected remove() to throw Exception, but it didn't."); diff --git a/src/test/java/testsupport/traits/InfiniteIterableSupport.java b/src/test/java/testsupport/traits/InfiniteIterableSupport.java index 3c870f1ad..3c1131341 100644 --- a/src/test/java/testsupport/traits/InfiniteIterableSupport.java +++ b/src/test/java/testsupport/traits/InfiniteIterableSupport.java @@ -9,10 +9,10 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.fail; -public class InfiniteIterableSupport implements Trait> { +public class InfiniteIterableSupport implements Trait, Iterable>> { @Override - public void test(Fn1 testSubject) { + public void test(Fn1, Iterable> testSubject) { CountDownLatch latch = new CountDownLatch(1); new Thread(() -> { testSubject.apply(repeat(0)).iterator().next(); diff --git a/src/test/java/testsupport/traits/InfiniteIteration.java b/src/test/java/testsupport/traits/InfiniteIteration.java index 12072fe78..085907ce2 100644 --- a/src/test/java/testsupport/traits/InfiniteIteration.java +++ b/src/test/java/testsupport/traits/InfiniteIteration.java @@ -9,11 +9,11 @@ import static org.hamcrest.core.Is.is; import static testsupport.matchers.FiniteIterableMatcher.finitelyIterable; -public class InfiniteIteration implements Trait> { +public class InfiniteIteration implements Trait, Iterable>> { @Override - public void test(Fn1 testSubject) { - Iterable result = testSubject.apply(asList(1, 2, 3)); + public void test(Fn1, Iterable> testSubject) { + Iterable result = testSubject.apply(asList(1, 2, 3)); assertThat(result, is(not(finitelyIterable()))); } } diff --git a/src/test/java/testsupport/traits/Laziness.java b/src/test/java/testsupport/traits/Laziness.java index 216201df7..cd0a58b6c 100644 --- a/src/test/java/testsupport/traits/Laziness.java +++ b/src/test/java/testsupport/traits/Laziness.java @@ -7,11 +7,11 @@ import static testsupport.Mocking.mockIterable; import static testsupport.matchers.ZeroInvocationsMatcher.wasNeverInteractedWith; -public class Laziness implements Trait> { +public class Laziness implements Trait, Iterable>> { @Override - public void test(Fn1 testSubject) { - Iterable iterable = mockIterable(); + public void test(Fn1, Iterable> testSubject) { + Iterable iterable = mockIterable(); testSubject.apply(iterable); assertThat(iterable, wasNeverInteractedWith()); diff --git a/src/test/java/testsupport/traits/MonadLaws.java b/src/test/java/testsupport/traits/MonadLaws.java new file mode 100644 index 000000000..eda3b45c2 --- /dev/null +++ b/src/test/java/testsupport/traits/MonadLaws.java @@ -0,0 +1,67 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static com.jnape.palatable.lambda.monad.Monad.join; +import static java.util.Arrays.asList; + +public class MonadLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return Monad.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap(f -> f.apply(equivalence), asList( + this::testLeftIdentity, + this::testRightIdentity, + this::testAssociativity, + this::testJoin)) + .match(IO::io, + s -> throwing(new AssertionError("The following Monad laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testLeftIdentity(Equivalence> equivalence) { + Object a = new Object(); + return equivalence.invMap(m -> m.pure(a.hashCode())) + .equals(equivalence.invMap(m -> m.pure(a.hashCode()))) + ? nothing() + : just("left identity (m.pure(a).flatMap(fn).equals(fn.apply(a)))"); + } + + private Maybe testRightIdentity(Equivalence> equivalence) { + return equivalence.invMap(m -> m.flatMap(m::pure)).equals(equivalence) + ? nothing() + : just("right identity: (m.flatMap(m::pure).equals(m))"); + } + + private Maybe testAssociativity(Equivalence> equivalence) { + Object a = new Object(); + Object b = new Object(); + return equivalence.invMap(m -> m.flatMap(constantly(m.pure(a))).flatMap(constantly(m.pure(b)))) + .equals(equivalence.invMap(m -> m.flatMap(__ -> m.pure(a).flatMap(constantly(m.pure(b)))))) + ? nothing() + : just("associativity: (m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g))))"); + } + + private Maybe testJoin(Equivalence> equivalence) { + return equivalence.invMap(m -> m.pure(m).flatMap(id())) + .equals(equivalence.invMap(m -> join(m.pure(m)))) + ? nothing() + : just("join: (m.pure(m).flatMap(id())).equals(Monad.join(m.pure(m)))"); + } +} diff --git a/src/test/java/testsupport/traits/MonadReaderLaws.java b/src/test/java/testsupport/traits/MonadReaderLaws.java new file mode 100644 index 000000000..2553500ae --- /dev/null +++ b/src/test/java/testsupport/traits/MonadReaderLaws.java @@ -0,0 +1,40 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadReader; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static java.util.Collections.singletonList; + +public class MonadReaderLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return MonadReader.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + f -> f.apply(equivalence), + singletonList(this::testLocalIdentity) + ) + .match(IO::io, + s -> throwing(new AssertionError("The following MonadReader laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testLocalIdentity(Equivalence> equivalence) { + return equivalence.invMap(mr -> mr.local(id())).equals(equivalence) + ? nothing() + : just("local identity (mr.local(id()).equals(mr))"); + } +} diff --git a/src/test/java/testsupport/traits/MonadRecLaws.java b/src/test/java/testsupport/traits/MonadRecLaws.java new file mode 100644 index 000000000..00f96eb45 --- /dev/null +++ b/src/test/java/testsupport/traits/MonadRecLaws.java @@ -0,0 +1,43 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static java.util.Collections.singletonList; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; + +public class MonadRecLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return MonadRec.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + fn -> fn.apply(equivalence), + singletonList(this::testStackSafety)) + .match(IO::io, + s -> IO.throwing(new AssertionError("The following MonadRec laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testStackSafety(Equivalence> equivalence) { + return equivalence.invMap(mr -> mr.pure(0) + .trampolineM(x -> mr.pure(x < STACK_EXPLODING_NUMBER + ? RecursiveResult.recurse(x + 1) + : RecursiveResult.terminate(x)))) + .equals(equivalence.invMap(mr -> mr.pure(STACK_EXPLODING_NUMBER))) + ? nothing() + : just("stack-safety (m.pure(" + STACK_EXPLODING_NUMBER + ").equals(m.pure(0).trampolineM(f)))"); + } +} diff --git a/src/test/java/testsupport/traits/MonadWriterLaws.java b/src/test/java/testsupport/traits/MonadWriterLaws.java new file mode 100644 index 000000000..8024535b8 --- /dev/null +++ b/src/test/java/testsupport/traits/MonadWriterLaws.java @@ -0,0 +1,48 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadWriter; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static java.util.Arrays.asList; + +public class MonadWriterLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return MonadWriter.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + f -> f.apply(equivalence), asList( + this::testCensor, + this::testListens) + ) + .match(IO::io, + s -> throwing(new AssertionError("The following MonadWriter laws did not hold for instance" + + " of " + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testCensor(Equivalence> equivalence) { + return equivalence.invMap(mw -> mw.censor(id())).equals(equivalence) + ? nothing() + : just("censor (mw.censor(id()).equals(mw))"); + } + + private Maybe testListens(Equivalence> equivalence) { + return equivalence.invMap(mw -> mw.listens(id()).fmap(Tuple2::_1)).equals(equivalence) + ? nothing() + : just("censor (mw.censor(id()).equals(mw))"); + } +} diff --git a/src/test/java/testsupport/traits/TraversableLaws.java b/src/test/java/testsupport/traits/TraversableLaws.java new file mode 100644 index 000000000..3b85202ba --- /dev/null +++ b/src/test/java/testsupport/traits/TraversableLaws.java @@ -0,0 +1,86 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Compose; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.lambda.traversable.Traversable; + +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static java.util.Arrays.asList; + +public class TraversableLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return Traversable.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + f -> f.apply(equivalence), + asList(this::testNaturality, + this::testIdentity, + this::testComposition) + ) + .match(IO::io, + s -> throwing(new AssertionError("The following Traversable laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + @SuppressWarnings("unchecked") + private Maybe testNaturality(Equivalence> equivalence) { + Fn1> f = Identity::new; + Fn1, Either> t = id -> right(id.runIdentity()); + + Fn1, Identity>> pureFn = Identity::new; + Fn1, Either>> pureFn2 = Either::right; + + Traversable trav = equivalence.getValue(); + return t.apply(trav.traverse(f, pureFn).fmap(id())).fmap(value -> equivalence.swap((Traversable) value)) + .equals(trav.traverse(t.contraMap(f), pureFn2) + .fmap(equivalence::swap)) + ? nothing() + : just("naturality (t.apply(trav.traverse(f, pureFn).fmap(id()).coerce())\n" + + " .equals(trav.traverse(t.contraMap(f), pureFn2)" + + ".fmap(id()).coerce()))"); + } + + private Maybe testIdentity(Equivalence> equivalence) { + Traversable trav = equivalence.getValue(); + return trav.traverse(Identity::new, Identity::new).fmap(equivalence::swap) + .equals(new Identity<>(trav).fmap(equivalence::swap)) + ? nothing() + : just("identity (trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new Identity<>(trav))"); + } + + private Maybe testComposition(Equivalence> equivalence) { + Fn1> f = Identity::new; + Fn1>> g = Identity::new; + + Traversable trav = equivalence.getValue(); + return trav.traverse(f.fmap(x -> x.fmap(g)).fmap(Compose::new), + x -> new Compose<>(new Identity<>(new Identity<>(x)))) + .fmap(equivalence::swap) + .equals(new Compose<>(trav.traverse(f, Identity::new) + .fmap(t -> t.traverse(g, Identity::new))) + .fmap(equivalence::swap)) + ? nothing() + : just("compose (trav.traverse(f.fmap(x -> x.fmap(g)).fmap(Compose::new), x -> new Compose<>(" + + "new Identity<>(new Identity<>(x))))\n" + + " .equals(new Compose>(" + + "trav.traverse(f, x -> new Identity<>(x))" + + ".fmap(t -> t.traverse(g, x -> new Identity<>(x))))))"); + } +}

the {@link Profunctor} bound + * @param the {@link Functor} bound + * @param the left side of the output profunctor + * @param the right side's functor embedding of the output profunctor + * @param the left side of the input profunctor + * @param the right side's functor embedding of the input profunctor + */ +@FunctionalInterface +public interface Optic

+ * For more information, read about the + * state monad. + * + * @param the state type + * @param the result type + */ +public final class State implements + MonadRec>, + MonadReader>, + MonadWriter> { + + private final StateT, A> stateFn; + + private State(StateT, A> stateFn) { + this.stateFn = stateFn; + } + + /** + * Run the stateful computation, returning a {@link Tuple2} of the result and the final state. + * + * @param s the initial state + * @return a {@link Tuple2} of the result and the final state. + */ + public Tuple2 run(S s) { + return stateFn.>>runStateT(s).runIdentity(); + } + + /** + * Run the stateful computation, returning the result. + * + * @param s the initial state + * @return the result + */ + public A eval(S s) { + return run(s)._1(); + } + + /** + * Run the stateful computation, returning the final state. + * + * @param s the initial state + * @return the final state + */ + public S exec(S s) { + return run(s)._2(); + } + + /** + * Map both the result and the final state to a new result and final state. + * + * @param fn the mapping function + * @param the new state type + * @return the mapped {@link State} + */ + public State mapState(Fn1, ? extends Tuple2> fn) { + return state(s -> fn.apply(run(s))); + } + + /** + * Map the final state to a new final state using the provided function. + * + * @param fn the state-mapping function + * @return the mapped {@link State} + */ + public State withState(Fn1 fn) { + return state(s -> run(fn.apply(s))); + } + + /** + * {@inheritDoc} + */ + @Override + public State local(Fn1 fn) { + return state(s -> run(fn.apply(s))); + } + + /** + * {@inheritDoc} + */ + @Override + public State> listens(Fn1 fn) { + return state(s -> run(s).biMapL(both(id(), constantly(fn.apply(s))))); + } + + /** + * {@inheritDoc} + */ + @Override + public State censor(Fn1 fn) { + return local(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public State flatMap(Fn1>> f) { + return state(s -> run(s).into((a, s2) -> f.apply(a).>coerce().run(s2))); + } + + /** + * {@inheritDoc} + */ + @Override + public State pure(B b) { + return state(s -> tuple(b, s)); + } + + /** + * {@inheritDoc} + */ + @Override + public State fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public State zip(Applicative, State> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, State>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public State discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public State discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public State trampolineM(Fn1, State>> fn) { + return state(fn1(this::run).fmap(trampoline(into((a, s) -> fn.apply(a) + .>>coerce().run(s) + .into((aOrB, s_) -> aOrB.biMap(a_ -> tuple(a_, s_), b -> tuple(b, s_))))))); + } + + /** + * Create a {@link State} that simply returns back the initial state as both the result and the final state + * + * @param the state and result type + * @return the new {@link State} instance + */ + @SuppressWarnings("RedundantTypeArguments") + public static State get() { + return state(Tuple2::fill); + } + + /** + * Create a {@link State} that ignores its initial state, returning a {@link Unit} result and s as its + * final state. + * + * @param s the final state + * @param the state type + * @return the new {@link State} instance + */ + public static State put(S s) { + return modify(constantly(s)); + } + + /** + * Create a {@link State} that maps its initial state into its result, but leaves the initial state unchanged. + * + * @param fn the mapping function + * @param the state type + * @param the result type + * @return the new {@link State} instance + */ + public static State gets(Fn1 fn) { + return state(both(fn, id())); + } + + /** + * Create a {@link State} that maps its initial state into its final state, returning a {@link Unit} result type. + * + * @param fn the mapping function + * @param the state type + * @return the new {@link State} instance + */ + public static State modify(Fn1 fn) { + return state(both(constantly(UNIT), fn)); + } + + /** + * Create a {@link State} that returns a as its result and its initial state as its final state. + * + * @param a the result + * @param the state type + * @param the result type + * @return the new {@link State} instance + */ + public static State state(A a) { + return gets(constantly(a)); + } + + /** + * Create a {@link State} from stateFn, a function that maps an initial state into a result and a final + * state. + * + * @param stateFn the state function + * @param the state type + * @param the result type + * @return the new {@link State} instance + */ + public static State state(Fn1> stateFn) { + return new State<>(stateT(s -> new Identity<>(stateFn.apply(s)))); + } + + /** + * The canonical {@link Pure} instance for {@link State}. + * + * @param the state type + * @return the {@link Pure} instance + */ + public static Pure> pureState() { + return new Pure>() { + @Override + public State checkedApply(A a) throws Throwable { + return state(s -> tuple(a, s)); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Tagged.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Tagged.java new file mode 100644 index 000000000..4231f2959 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Tagged.java @@ -0,0 +1,178 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Cocartesian; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.choice.Choice2.b; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; + +/** + * Like {@link Const}, but the phantom parameter is in the contravariant position, and the value is in covariant + * position. + * + * @param the phantom type + * @param the value type + */ +public final class Tagged implements + MonadRec>, + Traversable>, Cocartesian> { + + private final B b; + + public Tagged(B b) { + this.b = b; + } + + /** + * Extract the contained value. + * + * @return the value + */ + public B unTagged() { + return b; + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged flatMap(Fn1>> f) { + return f.apply(b).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged trampolineM(Fn1, Tagged>> fn) { + return new Tagged<>(trampoline(b -> fn.apply(b).>>coerce().unTagged(), + unTagged())); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged pure(C c) { + return new Tagged<>(c); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged zip(Applicative, Tagged> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravC extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return fn.apply(b) + .fmap(c -> Downcast.>>downcast(new Tagged<>(c))) + .coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged, Choice2> cocartesian() { + return new Tagged<>(b(b)); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged diMap(Fn1 lFn, Fn1 rFn) { + return new Tagged<>(rFn.apply(b)); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged diMapL(Fn1 fn) { + return (Tagged) Cocartesian.super.diMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged diMapR(Fn1 fn) { + return (Tagged) Cocartesian.super.diMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Tagged contraMap(Fn1 fn) { + return (Tagged) Cocartesian.super.contraMap(fn); + } + + @Override + public boolean equals(Object other) { + return other instanceof Tagged && Objects.equals(b, ((Tagged) other).b); + } + + @Override + public int hashCode() { + return Objects.hash(b); + } + + @Override + public String toString() { + return "Tagged{b=" + b + '}'; + } + + /** + * The canonical {@link Pure} instance for {@link Tagged}. + * + * @param the phantom type + * @return the {@link Pure} instance + */ + public static Pure> pureTagged() { + return Tagged::new; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java new file mode 100644 index 000000000..c4c6d752d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java @@ -0,0 +1,185 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.MonadWriter; +import com.jnape.palatable.lambda.monad.transformer.builtin.WriterT; +import com.jnape.palatable.lambda.monoid.Monoid; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; + +/** + * The lazy writer monad, a monad capturing some accumulation (eventually to be folded in terms of a given monoid) and + * a value. Note that unlike the {@link State} monad, the {@link Writer} monad does not allow the value to be fully + * derived from the accumulation. + * + * @param the accumulation type + * @param the value type + */ +public final class Writer implements + MonadWriter>, + MonadRec> { + + private final Fn1, ? extends Tuple2> writerFn; + + private Writer(Fn1, ? extends Tuple2> writerFn) { + this.writerFn = writerFn; + } + + /** + * Given a {@link Monoid} for the accumulation, run the computation represented by this {@link Writer}, accumulate + * the written output in terms of the {@link Monoid}, and produce the accumulation and the value. + * + * @param monoid the accumulation {@link Monoid} + * @return the accumulation with the value + */ + public Tuple2 runWriter(Monoid monoid) { + return writerFn.apply(monoid); + } + + /** + * {@inheritDoc} + */ + @Override + public Writer> listens(Fn1 fn) { + return new Writer<>(monoid -> runWriter(monoid).into((a, w) -> tuple(both(constantly(a), fn, w), w))); + } + + /** + * {@inheritDoc} + */ + @Override + public Writer censor(Fn1 fn) { + return new Writer<>(monoid -> runWriter(monoid).fmap(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + public Writer trampolineM(Fn1, Writer>> fn) { + return new Writer<>(monoid -> trampoline(into((a, w) -> fn.apply(a).>>coerce() + .runWriter(monoid) + .fmap(monoid.apply(w)) + .into((aOrB, w_) -> aOrB.biMap(a_ -> tuple(a_, w_), b -> tuple(b, w_)))), runWriter(monoid))); + } + + /** + * {@inheritDoc} + */ + @Override + public Writer flatMap(Fn1>> f) { + return new Writer<>(monoid -> writerFn.apply(monoid) + .into((a, w) -> f.apply(a).>coerce().runWriter(monoid).fmap(monoid.apply(w)))); + } + + /** + * {@inheritDoc} + */ + @Override + public Writer pure(B b) { + return listen(b); + } + + /** + * {@inheritDoc} + */ + @Override + public Writer fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Writer zip(Applicative, Writer> appFn) { + return MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Writer>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(MonadRec>::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + public Writer discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Writer discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); + } + + /** + * Construct a {@link Writer} from an accumulation. + * + * @param w the accumulation + * @param the accumulation type + * @return the {@link Writer} + */ + public static Writer tell(W w) { + return writer(tuple(UNIT, w)); + } + + /** + * Construct a {@link Writer} from a value. + * + * @param a the output value + * @param the accumulation type + * @param the value type + * @return the {@link Writer} + */ + public static Writer listen(A a) { + return Writer.pureWriter().apply(a); + } + + /** + * Construct a {@link Writer} from an accumulation and a value. + * + * @param aw the output value and accumulation + * @param the accumulation type + * @param the value type + * @return the {@link WriterT} + */ + public static Writer writer(Tuple2 aw) { + return new Writer<>(constantly(aw)); + } + + /** + * The canonical {@link Pure} instance for {@link Writer}. + * + * @param the accumulation type + * @return the {@link Pure} instance + */ + public static Pure> pureWriter() { + return new Pure>() { + @Override + public Writer checkedApply(A a) { + return new Writer<>(monoid -> tuple(a, monoid.identity())); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/ImmutableQueue.java b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableQueue.java new file mode 100644 index 000000000..dd605015e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableQueue.java @@ -0,0 +1,126 @@ +package com.jnape.palatable.lambda.internal; + +import com.jnape.palatable.lambda.adt.Maybe; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; + +/** + * Internal API. Use at your own peril. + */ +public abstract class ImmutableQueue implements Iterable { + + public abstract ImmutableQueue pushFront(A a); + + public abstract ImmutableQueue pushBack(A a); + + public abstract Maybe head(); + + public abstract ImmutableQueue tail(); + + public abstract ImmutableQueue concat(ImmutableQueue other); + + public final boolean isEmpty() { + return head().fmap(constantly(false)).orElse(true); + } + + public static ImmutableQueue singleton(A a) { + return new NonEmpty<>(ImmutableStack.empty().push(a), ImmutableStack.empty()); + } + + @Override + public Iterator iterator() { + return new Iterator() { + private ImmutableQueue queue = ImmutableQueue.this; + + @Override + public boolean hasNext() { + return !queue.isEmpty(); + } + + @Override + public A next() { + A next = queue.head().orElseThrow(NoSuchElementException::new); + queue = queue.tail(); + return next; + } + }; + } + + @SuppressWarnings("unchecked") + public static ImmutableQueue empty() { + return (ImmutableQueue) Empty.INSTANCE; + } + + private static final class Empty extends ImmutableQueue { + private static final Empty INSTANCE = new Empty<>(); + + @Override + public ImmutableQueue pushFront(A a) { + return new NonEmpty<>(ImmutableStack.empty().push(a), ImmutableStack.empty()); + } + + @Override + public ImmutableQueue pushBack(A a) { + return pushFront(a); + } + + @Override + public ImmutableQueue concat(ImmutableQueue other) { + return other; + } + + @Override + public Maybe head() { + return Maybe.nothing(); + } + + @Override + public ImmutableQueue tail() { + return this; + } + } + + private static final class NonEmpty extends ImmutableQueue { + private final ImmutableStack outbound; + private final ImmutableStack inbound; + + private NonEmpty(ImmutableStack outbound, ImmutableStack inbound) { + this.outbound = outbound; + this.inbound = inbound; + } + + @Override + public ImmutableQueue pushFront(A a) { + return new NonEmpty<>(outbound.push(a), inbound); + } + + @Override + public ImmutableQueue pushBack(A a) { + return new NonEmpty<>(outbound, inbound.push(a)); + } + + @Override + public ImmutableQueue concat(ImmutableQueue other) { + return new NonEmpty<>(outbound, foldLeft(ImmutableStack::push, inbound, other)); + } + + @Override + public Maybe head() { + return outbound.head(); + } + + @Override + public ImmutableQueue tail() { + ImmutableStack outTail = outbound.tail(); + if (!outTail.isEmpty()) + return new NonEmpty<>(outTail, inbound); + + ImmutableStack newOutbound = foldLeft(ImmutableStack::push, ImmutableStack.empty(), inbound); + return newOutbound.isEmpty() ? empty() : new NonEmpty<>(newOutbound, ImmutableStack.empty()); + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/ImmutableStack.java b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableStack.java new file mode 100644 index 000000000..cc6e7175b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableStack.java @@ -0,0 +1,84 @@ +package com.jnape.palatable.lambda.internal; + +import com.jnape.palatable.lambda.adt.Maybe; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * Internal API. Use at your own peril. + */ +public abstract class ImmutableStack implements Iterable { + + public final ImmutableStack push(A a) { + return new Node<>(a, this); + } + + public abstract Maybe head(); + + public abstract ImmutableStack tail(); + + public final boolean isEmpty() { + return head().fmap(constantly(false)).orElse(true); + } + + @Override + public Iterator iterator() { + return new Iterator() { + private ImmutableStack stack = ImmutableStack.this; + + @Override + public boolean hasNext() { + return !stack.isEmpty(); + } + + @Override + public A next() { + A next = stack.head().orElseThrow(NoSuchElementException::new); + stack = stack.tail(); + return next; + } + }; + } + + @SuppressWarnings("unchecked") + public static ImmutableStack empty() { + return (ImmutableStack) Empty.INSTANCE; + } + + private static final class Empty extends ImmutableStack { + private static final Empty INSTANCE = new Empty<>(); + + @Override + public Maybe head() { + return Maybe.nothing(); + } + + @Override + public ImmutableStack tail() { + return this; + } + } + + private static final class Node extends ImmutableStack { + private final A head; + private final ImmutableStack tail; + + public Node(A head, ImmutableStack tail) { + this.head = head; + this.tail = tail; + } + + @Override + public Maybe head() { + return Maybe.just(head); + } + + @Override + public ImmutableStack tail() { + return tail; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/Runtime.java b/src/main/java/com/jnape/palatable/lambda/internal/Runtime.java new file mode 100644 index 000000000..a280744fe --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/Runtime.java @@ -0,0 +1,12 @@ +package com.jnape.palatable.lambda.internal; + +public final class Runtime { + + private Runtime() { + } + + @SuppressWarnings("unchecked") + public static RuntimeException throwChecked(Throwable t) throws T { + throw (T) t; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/CombinatorialIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIterator.java similarity index 91% rename from src/main/java/com/jnape/palatable/lambda/iterators/CombinatorialIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIterator.java index 5e4a4dcd4..11922226a 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/CombinatorialIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.adt.hlist.Tuple2; @@ -8,7 +8,7 @@ import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; -public class CombinatorialIterator extends ImmutableIterator> { +public final class CombinatorialIterator extends ImmutableIterator> { private final Iterator asIterator; private final Iterator bsIterator; private final ListIterator doublyLinkedBsIterator; diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterable.java new file mode 100644 index 000000000..f1ff3a779 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterable.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.internal.ImmutableQueue; + +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; + +public final class ConcatenatingIterable implements Iterable { + + private final ImmutableQueue> iterables; + + public ConcatenatingIterable(Iterable xs, Iterable ys) { + if (xs instanceof ConcatenatingIterable) { + ImmutableQueue> iterables = ((ConcatenatingIterable) xs).iterables; + this.iterables = ys instanceof ConcatenatingIterable + ? iterables.concat(((ConcatenatingIterable) ys).iterables) + : iterables.pushBack(ys); + } else { + iterables = ys instanceof ConcatenatingIterable + ? ((ConcatenatingIterable) ys).iterables.pushFront(xs) + : ImmutableQueue.>empty().pushFront(ys).pushFront(xs); + } + } + + @Override + public Iterator iterator() { + return flatten(iterables).iterator(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ConsingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConsingIterator.java similarity index 75% rename from src/main/java/com/jnape/palatable/lambda/iterators/ConsingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ConsingIterator.java index c8dee5b3b..b560974bb 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ConsingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConsingIterator.java @@ -1,15 +1,16 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn0; import java.util.Iterator; import java.util.NoSuchElementException; -import java.util.function.Supplier; public final class ConsingIterator implements Iterator { - private final A head; - private final Supplier> asSupplier; - private Iterator asIterator; - private boolean iteratedHead; + private final A head; + private final Fn0> asSupplier; + private Iterator asIterator; + private boolean iteratedHead; public ConsingIterator(A head, Iterable as) { this.head = head; @@ -23,7 +24,7 @@ public boolean hasNext() { return true; if (asIterator == null) - asIterator = asSupplier.get(); + asIterator = asSupplier.apply(); return asIterator.hasNext(); } diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterable.java new file mode 100644 index 000000000..3218892b2 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterable.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Iterator; + +public final class CyclicIterable implements Iterable { + private final Iterable as; + + public CyclicIterable(Iterable as) { + while (as instanceof CyclicIterable) { + as = ((CyclicIterable) as).as; + } + this.as = as; + } + + @Override + public Iterator iterator() { + return new CyclicIterator<>(as.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/CyclicIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterator.java similarity index 87% rename from src/main/java/com/jnape/palatable/lambda/iterators/CyclicIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterator.java index 4aa462bb2..827652a44 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/CyclicIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterator.java @@ -1,10 +1,10 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import java.util.ArrayList; import java.util.Iterator; import java.util.ListIterator; -public class CyclicIterator extends InfiniteIterator { +public final class CyclicIterator extends InfiniteIterator { private final Iterator iterator; private final ListIterator doublyLinkedIterator; diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterable.java new file mode 100644 index 000000000..19758a6df --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterable.java @@ -0,0 +1,24 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.HashMap; +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; + +public final class DistinctIterable implements Iterable { + + private final Iterable as; + + public DistinctIterable(Iterable as) { + while (as instanceof DistinctIterable) { + as = ((DistinctIterable) as).as; + } + this.as = as; + } + + @Override + public Iterator iterator() { + HashMap known = new HashMap<>(); + return filter(a -> known.putIfAbsent(a, true) == null, as).iterator(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterable.java new file mode 100644 index 000000000..1d9534b17 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterable.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Iterator; + +public final class DroppingIterable implements Iterable { + private final int n; + private final Iterable as; + + public DroppingIterable(int n, Iterable as) { + while (as instanceof DroppingIterable) { + DroppingIterable nested = (DroppingIterable) as; + as = nested.as; + n += nested.n; + } + this.as = as; + this.n = n; + } + + @Override + public Iterator iterator() { + return new DroppingIterator<>(n, as.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/DroppingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterator.java similarity index 66% rename from src/main/java/com/jnape/palatable/lambda/iterators/DroppingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterator.java index 9beeead50..5b758695b 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/DroppingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterator.java @@ -1,9 +1,9 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; -public class DroppingIterator extends ImmutableIterator { +public final class DroppingIterator extends ImmutableIterator { private final Integer n; private final Iterator asIterator; private boolean dropped; @@ -16,7 +16,9 @@ public DroppingIterator(Integer n, Iterator asIterator) { @Override public boolean hasNext() { - dropIfNecessary(); + if (!dropped) { + drop(); + } return asIterator.hasNext(); } @@ -28,13 +30,10 @@ public A next() { return asIterator.next(); } - private void dropIfNecessary() { - if (!dropped) { - int i = 0; - while (i++ < n && asIterator.hasNext()) - asIterator.next(); - dropped = true; - } + private void drop() { + int i = 0; + while (i++ < n && asIterator.hasNext()) + asIterator.next(); + dropped = true; } - } diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterable.java new file mode 100644 index 000000000..7accde2f8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterable.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.All.all; +import static java.util.Collections.singletonList; + +public final class FilteringIterable implements Iterable { + private final List> predicates; + private final Iterable as; + + public FilteringIterable(Fn1 predicate, Iterable as) { + List> predicates = new ArrayList<>(singletonList(predicate)); + while (as instanceof FilteringIterable) { + FilteringIterable nested = (FilteringIterable) as; + predicates.addAll(0, nested.predicates); + as = nested.as; + } + this.predicates = predicates; + this.as = as; + } + + @Override + public Iterator iterator() { + Fn1 metaPredicate = a -> all(p -> p.apply(a), predicates); + return new FilteringIterator<>(metaPredicate, as.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/FilteringIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterator.java similarity index 64% rename from src/main/java/com/jnape/palatable/lambda/iterators/FilteringIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterator.java index b95f39eda..4a9346d40 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/FilteringIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterator.java @@ -1,15 +1,16 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; import java.util.Iterator; import java.util.NoSuchElementException; -import java.util.function.Function; -public class FilteringIterator extends ImmutableIterator { +public final class FilteringIterator extends ImmutableIterator { - private final Function predicate; - private final RewindableIterator rewindableIterator; + private final Fn1 predicate; + private final RewindableIterator rewindableIterator; - public FilteringIterator(Function predicate, Iterator iterator) { + public FilteringIterator(Fn1 predicate, Iterator iterator) { this.predicate = predicate; rewindableIterator = new RewindableIterator<>(iterator); } diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIterator.java new file mode 100644 index 000000000..f02759374 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIterator.java @@ -0,0 +1,29 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class FlatteningIterator extends ImmutableIterator { + private final Iterator> xss; + private Iterator xs; + + public FlatteningIterator(Iterator> xss) { + this.xss = xss; + } + + @Override + public boolean hasNext() { + while ((xs == null || !xs.hasNext()) && xss.hasNext()) + xs = xss.next().iterator(); + + return xs != null && xs.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + + return xs.next(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/GroupingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/GroupingIterator.java similarity index 78% rename from src/main/java/com/jnape/palatable/lambda/iterators/GroupingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/GroupingIterator.java index 310324b75..c21d9950c 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/GroupingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/GroupingIterator.java @@ -1,10 +1,10 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -public class GroupingIterator extends ImmutableIterator> { +public final class GroupingIterator extends ImmutableIterator> { private final Integer k; private final Iterator asIterator; @@ -21,7 +21,7 @@ public boolean hasNext() { @Override public Iterable next() { List group = new ArrayList<>(); - int i = 0; + int i = 0; while (i++ < k && asIterator.hasNext()) group.add(asIterator.next()); return group; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ImmutableIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableIterator.java similarity index 81% rename from src/main/java/com/jnape/palatable/lambda/iterators/ImmutableIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableIterator.java index cef1ff254..cc41f275d 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ImmutableIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/InfiniteIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/InfiniteIterator.java similarity index 73% rename from src/main/java/com/jnape/palatable/lambda/iterators/InfiniteIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/InfiniteIterator.java index b85b1d235..f136da18d 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/InfiniteIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/InfiniteIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; public abstract class InfiniteIterator extends ImmutableIterator { @Override diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/InitIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/InitIterator.java new file mode 100644 index 000000000..31eed515f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/InitIterator.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class InitIterator extends ImmutableIterator { + private final Iterator asIterator; + private A queued; + + public InitIterator(Iterable as) { + asIterator = as.iterator(); + } + + @Override + public boolean hasNext() { + if (queued == null) + if (asIterator.hasNext()) + queued = asIterator.next(); + + return asIterator.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + + A next = queued; + queued = asIterator.next(); + return next; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/IterationInterruptedException.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/IterationInterruptedException.java new file mode 100644 index 000000000..c52797198 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/IterationInterruptedException.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.builtin.fn4.RateLimit; + +import java.util.Iterator; + +/** + * An exception thrown when a thread is interrupted while an {@link Iterator} was blocked. + * + * @see RateLimit + */ +@SuppressWarnings("serial") +public final class IterationInterruptedException extends RuntimeException { + + public IterationInterruptedException(InterruptedException cause) { + super(cause); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterable.java new file mode 100644 index 000000000..3d2f1ba65 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterable.java @@ -0,0 +1,35 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +import static java.util.Collections.singletonList; + +public final class MappingIterable implements Iterable { + private final Iterable as; + private final List> mappers; + + @SuppressWarnings("unchecked") + public MappingIterable(Fn1 fn, Iterable as) { + List> mappers = new ArrayList<>(singletonList(fn)); + while (as instanceof MappingIterable) { + MappingIterable nested = (MappingIterable) as; + as = (Iterable) nested.as; + mappers.addAll(0, nested.mappers); + } + this.as = as; + this.mappers = mappers; + } + + @Override + @SuppressWarnings("unchecked") + public Iterator iterator() { + Fn1 fnComposedOnTheHeap = a -> foldLeft((x, fn) -> ((Fn1) fn).apply(x), + a, mappers); + return new MappingIterator<>((Fn1) fnComposedOnTheHeap, as.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterator.java new file mode 100644 index 000000000..6fb64fd90 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterator.java @@ -0,0 +1,26 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.Iterator; + +public final class MappingIterator extends ImmutableIterator { + + private final Fn1 function; + private final Iterator iterator; + + public MappingIterator(Fn1 function, Iterator iterator) { + this.function = function; + this.iterator = iterator; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public B next() { + return function.apply(iterator.next()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterable.java new file mode 100644 index 000000000..ea7b68bc7 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterable.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.ImmutableQueue; + +import java.util.Iterator; + +public final class PredicatedDroppingIterable implements Iterable { + private final ImmutableQueue> predicates; + private final Iterable as; + + public PredicatedDroppingIterable(Fn1 predicate, Iterable as) { + ImmutableQueue> predicates = ImmutableQueue.singleton(predicate); + while (as instanceof PredicatedDroppingIterable) { + PredicatedDroppingIterable nested = (PredicatedDroppingIterable) as; + as = nested.as; + predicates = nested.predicates.concat(predicates); + } + this.predicates = predicates; + this.as = as; + } + + @Override + public Iterator iterator() { + return new PredicatedDroppingIterator<>(predicates, as.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterator.java new file mode 100644 index 000000000..a97b513ac --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterator.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.ImmutableQueue; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class PredicatedDroppingIterator extends ImmutableIterator { + private final Iterator> predicates; + private final RewindableIterator rewindableIterator; + + public PredicatedDroppingIterator(ImmutableQueue> predicates, Iterator asIterator) { + this.predicates = predicates.iterator(); + rewindableIterator = new RewindableIterator<>(asIterator); + } + + @Override + public boolean hasNext() { + dropElementsIfNecessary(); + return rewindableIterator.hasNext(); + } + + @Override + public A next() { + if (hasNext()) + return rewindableIterator.next(); + + throw new NoSuchElementException(); + } + + private void dropElementsIfNecessary() { + while (predicates.hasNext() && rewindableIterator.hasNext()) { + Fn1 predicate = predicates.next(); + boolean predicateDone = false; + + while (rewindableIterator.hasNext() && !predicateDone) { + if (!predicate.apply(rewindableIterator.next())) { + rewindableIterator.rewind(); + predicateDone = true; + } + } + + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterable.java new file mode 100644 index 000000000..de9c7682e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterable.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.All.all; +import static java.util.Collections.singletonList; + +public final class PredicatedTakingIterable implements Iterable { + private final List> predicates; + private final Iterable as; + + public PredicatedTakingIterable(Fn1 predicate, Iterable as) { + List> predicates = new ArrayList<>(singletonList(predicate)); + while (as instanceof PredicatedTakingIterable) { + PredicatedTakingIterable nested = (PredicatedTakingIterable) as; + predicates.addAll(0, nested.predicates); + as = nested.as; + } + this.predicates = predicates; + this.as = as; + } + + @Override + public Iterator iterator() { + Fn1 metaPredicate = a -> all(p -> p.apply(a), predicates); + return new PredicatedTakingIterator<>(metaPredicate, as.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterator.java similarity index 62% rename from src/main/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterator.java index c269ad72b..9760450b0 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterator.java @@ -1,16 +1,16 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; import java.util.Iterator; import java.util.NoSuchElementException; -import java.util.function.Function; -public class PredicatedTakingIterator extends ImmutableIterator { - private final Function predicate; - private final RewindableIterator rewindableIterator; - private boolean stillTaking; +public final class PredicatedTakingIterator extends ImmutableIterator { + private final Fn1 predicate; + private final RewindableIterator rewindableIterator; + private boolean stillTaking; - public PredicatedTakingIterator(Function predicate, - Iterator asIterator) { + public PredicatedTakingIterator(Fn1 predicate, Iterator asIterator) { this.predicate = predicate; rewindableIterator = new RewindableIterator<>(asIterator); stillTaking = true; diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/PrependingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PrependingIterator.java new file mode 100644 index 000000000..8914c2aca --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PrependingIterator.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class PrependingIterator extends ImmutableIterator { + + private final A antecedent; + private final Iterator iterator; + private boolean prependNext; + + public PrependingIterator(A antecedent, Iterator iterator) { + this.antecedent = antecedent; + this.iterator = iterator; + prependNext = true; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public A next() { + if (!iterator.hasNext()) + throw new NoSuchElementException(); + + return (prependNext = !prependNext) ? iterator.next() : antecedent; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterable.java new file mode 100644 index 000000000..3a7c856d4 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterable.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.functions.Fn0; + +import java.time.Duration; +import java.time.Instant; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public final class RateLimitingIterable implements Iterable { + private final Iterable as; + private final Set>> rateLimits; + + public RateLimitingIterable(Iterable as, Set>> rateLimits) { + Set>> combinedRateLimits = new HashSet<>(rateLimits); + if (as instanceof RateLimitingIterable) { + RateLimitingIterable inner = (RateLimitingIterable) as; + combinedRateLimits.addAll(inner.rateLimits); + as = inner.as; + } + this.rateLimits = combinedRateLimits; + this.as = as; + } + + @Override + public Iterator iterator() { + return new RateLimitingIterator<>(as.iterator(), rateLimits); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterator.java new file mode 100644 index 000000000..1046f406c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterator.java @@ -0,0 +1,81 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.adt.Try; +import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.functions.Fn0; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import static com.jnape.palatable.lambda.adt.Try.failure; +import static com.jnape.palatable.lambda.adt.Try.trying; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; +import static com.jnape.palatable.lambda.functions.builtin.fn2.GTE.gte; +import static com.jnape.palatable.lambda.functions.builtin.fn2.LT.lt; +import static com.jnape.palatable.lambda.functions.builtin.fn2.LTE.lte; +import static com.jnape.palatable.lambda.monad.Monad.join; +import static com.jnape.palatable.lambda.semigroup.builtin.Max.max; +import static java.lang.Thread.sleep; +import static java.util.Collections.emptyList; + +public final class RateLimitingIterator implements Iterator { + private final Iterator asIterator; + private final Set>> rateLimits; + private final Map>, List> timeSlicesByRateLimit; + + public RateLimitingIterator(Iterator asIterator, Set>> rateLimits) { + this.asIterator = asIterator; + this.rateLimits = rateLimits; + timeSlicesByRateLimit = new HashMap<>(); + } + + @Override + public boolean hasNext() { + return asIterator.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + awaitNextTimeSlice(); + return asIterator.next(); + } + + private void awaitNextTimeSlice() { + rateLimits.forEach(rateLimit -> { + awaitNextTimeSliceForRateLimit(rateLimit); + List timeSlicesForRateLimit = timeSlicesByRateLimit.getOrDefault(rateLimit, new ArrayList<>()); + timeSlicesForRateLimit.add(rateLimit._3().apply()); + timeSlicesByRateLimit.put(rateLimit, timeSlicesForRateLimit); + }); + } + + private void awaitNextTimeSliceForRateLimit(Tuple3> rateLimit) { + while (rateLimitExhaustedInTimeSlice(rateLimit)) { + join(trying(() -> sleep(0)) + .fmap(Try::success) + .catching(InterruptedException.class, t -> failure(new IterationInterruptedException(t)))) + .orThrow(); + } + } + + private boolean rateLimitExhaustedInTimeSlice(Tuple3> rateLimit) { + List timeSlicesForRateLimit = timeSlicesByRateLimit.getOrDefault(rateLimit, emptyList()); + return rateLimit.into((limit, duration, instantSupplier) -> { + Instant timeSliceEnd = instantSupplier.apply(); + Instant previousTimeSliceEnd = timeSliceEnd.minus(duration); + timeSlicesForRateLimit.removeIf(lt(previousTimeSliceEnd).toPredicate()); + return max(0L, limit - size(filter(mark -> lte(mark, previousTimeSliceEnd) && gte(mark, timeSliceEnd), timeSlicesForRateLimit))) == 0; + }); + } + +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/RepetitiousIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIterator.java similarity index 57% rename from src/main/java/com/jnape/palatable/lambda/iterators/RepetitiousIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIterator.java index da3c73cbe..55652d4c4 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/RepetitiousIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIterator.java @@ -1,6 +1,6 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; -public class RepetitiousIterator extends InfiniteIterator { +public final class RepetitiousIterator extends InfiniteIterator { private final A value; diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterable.java new file mode 100644 index 000000000..29aff81db --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterable.java @@ -0,0 +1,24 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Iterator; + +public final class ReversingIterable implements Iterable { + private final Iterable as; + private final boolean reverse; + + public ReversingIterable(Iterable as) { + boolean reverse = true; + while (as instanceof ReversingIterable) { + ReversingIterable nested = (ReversingIterable) as; + as = nested.as; + reverse = !nested.reverse; + } + this.as = as; + this.reverse = reverse; + } + + @Override + public Iterator iterator() { + return reverse ? new ReversingIterator<>(as.iterator()) : as.iterator(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ReversingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterator.java similarity index 87% rename from src/main/java/com/jnape/palatable/lambda/iterators/ReversingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterator.java index fd042ee9a..70e1baa60 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ReversingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterator.java @@ -1,10 +1,10 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import java.util.ArrayList; import java.util.Iterator; import java.util.ListIterator; -public class ReversingIterator extends ImmutableIterator { +public final class ReversingIterator extends ImmutableIterator { private final Iterator as; private final ListIterator reversingIterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/RewindableIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java similarity index 86% rename from src/main/java/com/jnape/palatable/lambda/iterators/RewindableIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java index 99f96cb6a..092e7e98b 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/RewindableIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java @@ -1,9 +1,9 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; -public class RewindableIterator extends ImmutableIterator { +public final class RewindableIterator extends ImmutableIterator { private final Iterator asIterator; private final Cache cache; @@ -54,9 +54,7 @@ public A retrieve() { if (cache == null) throw new NoSuchElementException("Cache is empty."); - A cache = this.cache; - this.cache = null; - return cache; + return this.cache; } public boolean isEmpty() { diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ScanningIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ScanningIterator.java similarity index 61% rename from src/main/java/com/jnape/palatable/lambda/iterators/ScanningIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ScanningIterator.java index aaaa43fb7..43fc4ece5 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ScanningIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ScanningIterator.java @@ -1,16 +1,17 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn2; import java.util.Iterator; import java.util.NoSuchElementException; -import java.util.function.BiFunction; public final class ScanningIterator extends ImmutableIterator { - private final BiFunction scanner; - private final Iterator asIterator; - private B b; + private final Fn2 scanner; + private final Iterator asIterator; + private B b; - public ScanningIterator(BiFunction scanner, B b, + public ScanningIterator(Fn2 scanner, B b, Iterator asIterator) { this.scanner = scanner; this.b = b; diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterable.java new file mode 100644 index 000000000..9a0118200 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterable.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Collections; +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; +import static com.jnape.palatable.lambda.monoid.builtin.Concat.concat; + +public final class SnocIterable implements Iterable { + private final Iterable as; + private final Iterable snocs; + + public SnocIterable(A a, Iterable as) { + Iterable snocs = cons(a, Collections::emptyIterator); + while (as instanceof SnocIterable) { + SnocIterable nested = ((SnocIterable) as); + as = nested.as; + snocs = concat(nested.snocs, snocs); + } + this.as = as; + this.snocs = snocs; + } + + @Override + public Iterator iterator() { + return new SnocIterator<>(as.iterator(), snocs.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterator.java new file mode 100644 index 000000000..e3c336a91 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterator.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class SnocIterator implements Iterator { + + private final Iterator as; + private final Iterator snocs; + + public SnocIterator(Iterator as, Iterator snocs) { + this.as = as; + this.snocs = snocs; + } + + @Override + public boolean hasNext() { + return as.hasNext() || snocs.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + + return as.hasNext() ? as.next() : snocs.next(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterable.java new file mode 100644 index 000000000..f228b596a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterable.java @@ -0,0 +1,25 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Iterator; + +import static java.lang.Math.min; + +public final class TakingIterable implements Iterable { + private final int n; + private final Iterable as; + + public TakingIterable(int n, Iterable as) { + while (as instanceof TakingIterable) { + TakingIterable nested = (TakingIterable) as; + n = min(n, nested.n); + as = nested.as; + } + this.n = n; + this.as = as; + } + + @Override + public Iterator iterator() { + return new TakingIterator<>(n, as.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/TakingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterator.java similarity index 83% rename from src/main/java/com/jnape/palatable/lambda/iterators/TakingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterator.java index 8e659dc68..900f7ddb7 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/TakingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterator.java @@ -1,9 +1,9 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; -public class TakingIterator extends ImmutableIterator { +public final class TakingIterator extends ImmutableIterator { private final int n; private final Iterator iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java new file mode 100644 index 000000000..3b2a7b37f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java @@ -0,0 +1,68 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.internal.ImmutableQueue; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Not.not; +import static com.jnape.palatable.lambda.io.IO.io; + +public final class TrampoliningIterator implements Iterator { + private final Fn1>> fn; + private final A a; + + private ImmutableQueue>> remaining; + private B b; + + public TrampoliningIterator(Fn1>> fn, A a) { + this.fn = fn; + this.a = a; + } + + @Override + public boolean hasNext() { + queueNextIfPossible(); + return b != null; + } + + @Override + public B next() { + if (!hasNext()) + throw new NoSuchElementException(); + B next = b; + b = null; + return next; + } + + private void queueNextIfPossible() { + if (remaining == null) + pruneAfter(() -> remaining = ImmutableQueue.>>empty() + .pushFront(fn.apply(a).iterator())); + + while (b == null && remaining.head().match(constantly(false), constantly(true))) { + tickNext(); + } + } + + private void tickNext() { + pruneAfter(() -> remaining.head().orElseThrow(NoSuchElementException::new).next()) + .match(a -> io(() -> { + pruneAfter(() -> remaining = remaining.pushFront(fn.apply(a).iterator())); + }), b -> io(() -> { + this.b = b; + })).unsafePerformIO(); + } + + private R pruneAfter(Fn0 fn) { + R r = fn.apply(); + while (remaining.head().match(constantly(false), not(Iterator::hasNext))) { + remaining = remaining.tail(); + } + return r; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIterator.java new file mode 100644 index 000000000..7ed719046 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIterator.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.NoSuchElementException; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +public final class UnfoldingIterator extends ImmutableIterator { + private final Fn1>> function; + private B seed; + private Maybe> maybeAcc; + + public UnfoldingIterator(Fn1>> function, B seed) { + this.function = function; + this.seed = seed; + } + + @Override + public boolean hasNext() { + if (maybeAcc == null) + maybeAcc = function.apply(seed); + + return maybeAcc.fmap(constantly(true)).orElse(false); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + + Tuple2 acc = maybeAcc.orElseThrow(NoSuchElementException::new); + A next = acc._1(); + seed = acc._2(); + maybeAcc = null; + return next; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterable.java new file mode 100644 index 000000000..fc4c6ed60 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterable.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Distinct.distinct; + +public final class UnioningIterable implements Iterable { + + private final ConcatenatingIterable elements; + + public UnioningIterable(Iterable xs, Iterable ys) { + elements = new ConcatenatingIterable<>(xs instanceof UnioningIterable ? ((UnioningIterable) xs).elements : xs, + ys instanceof UnioningIterable ? ((UnioningIterable) ys).elements : ys); + } + + @Override + public Iterator iterator() { + return distinct(elements).iterator(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/ZippingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ZippingIterator.java new file mode 100644 index 000000000..107a6ec08 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ZippingIterator.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.Iterator; + +public final class ZippingIterator extends ImmutableIterator { + private final Fn2 zipper; + private final Iterator asIterator; + private final Iterator bsIterator; + + public ZippingIterator(Fn2 zipper, Iterator asIterator, + Iterator bsIterator) { + this.asIterator = asIterator; + this.bsIterator = bsIterator; + this.zipper = zipper; + } + + @Override + public boolean hasNext() { + return asIterator.hasNext() && bsIterator.hasNext(); + } + + @Override + public C next() { + return zipper.apply(asIterator.next(), bsIterator.next()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/io/IO.java b/src/main/java/com/jnape/palatable/lambda/io/IO.java new file mode 100644 index 000000000..6af4dc1b1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/io/IO.java @@ -0,0 +1,473 @@ +package com.jnape.palatable.lambda.io; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Try; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.LazyRec; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functions.specialized.SideEffect; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadError; +import com.jnape.palatable.lambda.monad.MonadRec; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.choice.Choice2.a; +import static com.jnape.palatable.lambda.adt.choice.Choice2.b; +import static com.jnape.palatable.lambda.functions.Fn0.fn0; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.monad.Monad.join; +import static java.lang.Thread.interrupted; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static java.util.concurrent.ForkJoinPool.commonPool; + +/** + * A {@link Monad} representing some side-effecting computation to be performed. Note that because {@link IO} inherently + * offers an interface supporting parallelism, the optimal execution strategy for any given {@link IO} is encoded in + * its composition. + * + * @param the result type + */ +public abstract class IO implements MonadRec>, MonadError> { + + private IO() { + } + + /** + * Run the effect represented by this {@link IO} instance, blocking the current thread until the effect terminates. + * + * @return the result of the effect + */ + public abstract A unsafePerformIO(); + + /** + * Returns a {@link CompletableFuture} representing the result of this eventual effect. By default, this will + * immediately run the effect in terms of the implicit {@link Executor} available to {@link CompletableFuture} + * (usually the {@link java.util.concurrent.ForkJoinPool}). Note that specific {@link IO} constructions may allow + * this method to delegate to externally-managed {@link CompletableFuture} instead of synthesizing their own. + * + * @return the {@link CompletableFuture} representing this {@link IO}'s eventual result + * @see IO#unsafePerformAsyncIO(Executor) + */ + public final CompletableFuture unsafePerformAsyncIO() { + return unsafePerformAsyncIO(commonPool()); + } + + /** + * Returns a {@link CompletableFuture} representing the result of this eventual effect. By default, this will + * immediately run the effect in terms of the provided {@link Executor}. Note that specific {@link IO} + * constructions may allow this method to delegate to externally-managed {@link CompletableFuture} instead of + * synthesizing their own. + * + * @param executor the {@link Executor} to run the {@link CompletableFuture} from + * @return the {@link CompletableFuture} representing this {@link IO}'s eventual result + * @see IO#unsafePerformAsyncIO() + */ + public abstract CompletableFuture unsafePerformAsyncIO(Executor executor); + + /** + * Given a function from any {@link Throwable} to the result type A, if this {@link IO} successfully + * yields a result, return it; otherwise, map the {@link Throwable} to the result type and return that. + * + * @param recoveryFn the recovery function + * @return the guarded {@link IO} + * @deprecated in favor of canonical {@link IO#catchError(Fn1)} + */ + @Deprecated + public final IO exceptionally(Fn1 recoveryFn) { + return catchError(t -> io(recoveryFn.apply(t))); + } + + /** + * Return an {@link IO} that will run ensureIO strictly after running this {@link IO} regardless of + * whether this {@link IO} terminates normally, analogous to a finally block. + * + * @param ensureIO the {@link IO} to ensure runs strictly after this {@link IO} + * @return the combined {@link IO} + */ + public final IO ensuring(IO ensureIO) { + return join(fmap(a -> ensureIO.fmap(constantly(a))) + .catchError(t1 -> ensureIO + .fmap(constantly(IO.throwing(t1))) + .catchError(t2 -> io(io(() -> { + t1.addSuppressed(t2); + throw t1; + }))))); + } + + /** + * Return a safe {@link IO} that will never throw by lifting the result of this {@link IO} into {@link Either}, + * catching any {@link Throwable} and wrapping it in a {@link Either#left(Object) left}. + * + * @return the safe {@link IO} + */ + public final IO> safe() { + return fmap(Either::right).catchError(t -> io(left(t))); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO pure(B b) { + return io(b); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO fmap(Fn1 fn) { + return MonadError.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO zip(Applicative, IO> appFn) { + return new Compose<>(this, a((IO>) appFn)); + } + + /** + * {@inheritDoc} + */ + @Override + public final Lazy> lazyZip(Lazy, IO>> lazyAppFn) { + return MonadError.super.lazyZip(lazyAppFn).>fmap(Monad>::coerce).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO discardL(Applicative> appB) { + return MonadError.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO discardR(Applicative> appB) { + return MonadError.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO flatMap(Fn1>> f) { + @SuppressWarnings({"unchecked", "RedundantCast"}) + Choice2, Fn1>> composition = b((Fn1>) (Object) f); + return new Compose<>(this, composition); + } + + /** + * {@inheritDoc} + */ + @Override + public IO trampolineM(Fn1, IO>> fn) { + return flatMap(a -> fn.apply(a).>>coerce().flatMap(aOrB -> aOrB.match( + a_ -> io(a_).trampolineM(fn), + IO::io))); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO throwError(Throwable throwable) { + return IO.throwing(throwable); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO catchError(Fn1>> recoveryFn) { + return new IO() { + @Override + public A unsafePerformIO() { + return Try.trying(IO.this::unsafePerformIO) + .catchError(t -> Try.trying(recoveryFn.apply(t).>coerce()::unsafePerformIO)) + .orThrow(); + } + + @Override + public CompletableFuture unsafePerformAsyncIO(Executor executor) { + return IO.this.unsafePerformAsyncIO(executor) + .handle((a, t) -> t == null + ? completedFuture(a) + : recoveryFn.apply(t).>coerce().unsafePerformAsyncIO(executor)) + .thenCompose(f -> f); + } + }; + } + + /** + * Produce an {@link IO} that throws the given {@link Throwable} when executed. + * + * @param t the {@link Throwable} + * @param any result type + * @return the {@link IO} + */ + public static IO throwing(Throwable t) { + return io(() -> {throw t;}); + } + + /** + * Wrap the given {@link IO} in an {@link IO} that first checks if the {@link Thread#currentThread() thread} the + * {@link IO} runs on is {@link Thread#interrupted() interrupted}. If it is, an {@link InterruptedException} is + * thrown; otherwise the given {@link IO} is executed as usual. Note that for {@link IO}s supporting parallelism, + * the thread that is checked for interruption may not necessarily be the same thread that the {@link IO} ultimately + * runs on. + * + * @param io the {@link IO} to wrap + * @param the {@link IO} result type + * @return an {@link IO} that first checks for {@link Thread#interrupted() thread interrupts} + */ + public static IO interruptible(IO io) { + return join(io(() -> { + if (interrupted()) + throw new InterruptedException(); + return io; + })); + } + + /** + * Synchronize the given + * {@link IO} using the provided lock object. Note that to ensure that the entirety of the {@link IO}'s computation + * actually runs inside the synchronized region, the {@link IO} is executed + * {@link IO#unsafePerformIO() synchronously} inside the synchronized block regardless of the caller's chosen + * execution strategy. + * + * @param lock the lock object + * @param io the {@link IO} + * @param the {@link IO} result type + * @return the synchronized {@link IO} + */ + public static IO monitorSync(Object lock, IO io) { + return io(() -> { + synchronized (lock) { + return io.unsafePerformIO(); + } + }); + } + + /** + * Fuse all fork opportunities of a given {@link IO} such that, unless it is {@link IO#pin(IO, Executor) pinned} + * (or is originally {@link IO#externallyManaged(Fn0) externally managed}), no parallelism will be used when + * running it, regardless of what semantics are used when it is executed. + * + * @param io the {@link IO} + * @param the {@link IO} result type + * @return the fused {@link IO} + * @see IO#pin(IO, Executor) + */ + public static IO fuse(IO io) { + return io(io::unsafePerformIO); + } + + /** + * Pin an {@link IO} to an {@link Executor} such that regardless of what future decisions are made, when it runs, it + * will run using whatever parallelism is supported by the {@link Executor}'s threading model. Note that if this + * {@link IO} has already been pinned (or is originally {@link IO#externallyManaged(Fn0) externally managed}), + * pinning to an additional {@link Executor} has no meaningful effect. + * + * @param io the {@link IO} + * @param executor the {@link Executor} + * @param the {@link IO} result type + * @return the {@link IO} pinned to the {@link Executor} + * @see IO#fuse(IO) + */ + public static IO pin(IO io, Executor executor) { + return IO.externallyManaged(() -> io.unsafePerformAsyncIO(executor)); + } + + /** + * Given an {@link IO}, return an {@link IO} that wraps it, caches its first successful result, and guarantees that + * no subsequent interactions will happen with it afterwards, returning the cached result thereafter. Note that if + * the underlying {@link IO} throws, the failure will not be cached, so subsequent interactions with the memoized + * {@link IO} will again call through to the delegate until it completes normally. + * + * @param io the delegate {@link IO} + * @param the return type + * @return the memoized {@link IO} + */ + public static IO memoize(IO io) { + class Ref { + A value; + boolean computed; + } + Ref ref = new Ref(); + return join(io(() -> { + if (!ref.computed) { + return monitorSync(ref, io(() -> { + if (!ref.computed) { + ref.value = io.unsafePerformIO(); + ref.computed = true; + } + })).flatMap(constantly(io(() -> ref.value))); + } + return io(ref.value); + })); + } + + /** + * Static factory method for creating an {@link IO} that just returns a when performed. + * + * @param a the result + * @param the result type + * @return the {@link IO} + */ + public static IO io(A a) { + return new IO() { + @Override + public A unsafePerformIO() { + return a; + } + + @Override + public CompletableFuture unsafePerformAsyncIO(Executor executor) { + return completedFuture(a); + } + }; + } + + /** + * Static factory method for coercing a lambda to an {@link IO}. + * + * @param fn0 the lambda to coerce + * @param the result type + * @return the {@link IO} + */ + public static IO io(Fn0 fn0) { + return new IO() { + @Override + public A unsafePerformIO() { + return fn0.apply(); + } + + @Override + public CompletableFuture unsafePerformAsyncIO(Executor executor) { + return supplyAsync(fn0::apply, executor); + } + }; + } + + /** + * Static factory method for creating an {@link IO} that runs a {@link SideEffect} and returns {@link Unit}. + * + * @param sideEffect the {@link SideEffect} + * @return the {@link IO} + */ + public static IO io(SideEffect sideEffect) { + return io(fn0(() -> { + sideEffect.Ω(); + return UNIT; + })); + } + + /** + * Static factory method for creating an {@link IO} from an externally managed source of + * {@link CompletableFuture completable futures}. + *

- * This is intentionally an interface so user-defined implementations are possible; however, it's important to note that - * all hopes of type-safety hinge on equality being implemented such that no two TypeSafeKeys with differing parameters - * may be considered equal. Reference equality is used here as the default, as that is sufficient. + * This is intentionally an interface so user-defined implementations are possible; however, it's important to note + * that all hopes of type-safety hinge on equality being implemented such that no two {@link TypeSafeKey}s with + * differing value-type parameters may be considered equal. Reference equality is used here as the default, as that is + * sufficient. * - * @param The type of the value that this key maps to inside an HMap + * @param The raw type of the value that this key maps to inside an {@link HMap} + * @param The mapped type of the value that this key maps to inside an {@link HMap} */ -public interface TypeSafeKey { +public interface TypeSafeKey extends Iso.Simple { + + @Override + default TypeSafeKey discardR(Applicative> appB) { + Iso.Simple discarded = Iso.Simple.super.discardR(appB); + return new TypeSafeKey() { + @Override + public >, + CoF extends Functor>, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return discarded.apply(pafb); + } + + @Override + public int hashCode() { + return TypeSafeKey.this.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return TypeSafeKey.this.equals(obj); + } + }; + } + + /** + * Left-to-right composition of this {@link TypeSafeKey} with some other {@link Iso}. Because the first parameter + * fundamentally represents an already stored value type, this is the only composition that is possible for + * {@link TypeSafeKey}, which is why only this (and not {@link Iso#compose(Optic)}) is overridden. + *