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 ee919be2e..3aecf9b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,170 @@ # 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` @@ -16,7 +177,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - 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 @@ -27,11 +191,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [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***: `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***: `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` @@ -133,14 +299,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### 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`) +- ***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`) +- 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` @@ -206,7 +375,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `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 +- `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 @@ -316,7 +486,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `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` +- `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 @@ -326,14 +497,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `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 +- `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 +- `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` @@ -387,7 +560,8 @@ 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 ### Changed @@ -426,7 +600,14 @@ 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-3.2.0...HEAD +[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 diff --git a/LICENSE b/LICENSE index b35067105..fb5630322 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 John Napier (jnape) +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 65501c222..7d84c6295 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ λ ====== -[![Build Status](https://img.shields.io/travis/palatable/lambda/master.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 @@ -26,7 +28,8 @@ Functional patterns for Java - [CoProducts](#coproducts) - [Either](#either) - [Lenses](#lenses) - - [Notes](#notes) + - [Notes](#notes) + - [Ecosystem](#ecosystem) - [License](#license) Background @@ -57,14 +60,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 3.2.0 + 5.4.0 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '3.2.0' +compile group: 'com.jnape.palatable', name: 'lambda', version: '5.4.0' ``` Examples @@ -72,22 +75,22 @@ compile group: 'com.jnape.palatable', name: 'lambda', version: '3.2.0' 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) - .andThen(filter(x -> x % 2 == 0)) - .andThen(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: @@ -103,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] ``` @@ -113,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: @@ -122,9 +125,9 @@ Let's compose two functions: Fn1 add = x -> x + 1; Fn1 subtract = x -> x -1; -Fn1 noOp = add.andThen(subtract); +Fn1 noOp = add.fmap(subtract); // same as -Fn1 alsoNoOp = subtract.compose(add); +Fn1 alsoNoOp = subtract.contraMap(add); ``` And partially apply some: @@ -141,7 +144,7 @@ And have fun with 3s: ```Java Iterable> multiplesOf3InGroupsOf3 = - take(3, 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]] ``` @@ -173,7 +176,7 @@ Check out the [semigroup](https://palatable.github.io/lambda/javadoc/com/jnape/p ```Java Monoid multiply = monoid((x, y) -> x * y, 1); -multiple.reduceLeft(emptyList()); //-> 1 +multiply.reduceLeft(emptyList()); //-> 1 multiply.reduceLeft(asList(1, 2, 3)); //-> 6 multiply.foldMap(Integer::parseInt, asList("1", "2", "3")); //-> also 6 ``` @@ -736,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 145522c52..bae03b1e3 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 3.3.0 + 5.5.1-SNAPSHOT jar Lambda @@ -53,10 +53,9 @@ - 3.1 - 1.2 + 1.3.0 3.3 - 1.3 + 2.1 3.1.1 @@ -67,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 @@ -81,12 +82,6 @@ ${traitor.version} test - - org.apache.commons - commons-lang3 - ${commons-lang3.version} - test - @@ -108,6 +103,13 @@ org.apache.maven.plugins maven-jar-plugin ${maven-jar-plugin.version} + + + + com.jnape.palatable.lambda + + + 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 00f3bf787..a26b6a42e 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -2,24 +2,31 @@ import com.jnape.palatable.lambda.adt.choice.Choice3; import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; -import com.jnape.palatable.lambda.functions.builtin.fn2.Peek; -import com.jnape.palatable.lambda.functions.builtin.fn2.Peek2; -import com.jnape.palatable.lambda.functions.specialized.checked.CheckedFn1; -import com.jnape.palatable.lambda.functions.specialized.checked.CheckedRunnable; -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.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; /** @@ -30,7 +37,12 @@ * @param The left parameter type * @param The right parameter type */ -public abstract class Either implements CoProduct2>, Monad>, Traversable>, Bifunctor { +public abstract class Either implements + CoProduct2>, + MonadError>, + MonadRec>, + Traversable>, + Bifunctor> { private Either() { } @@ -52,7 +64,7 @@ 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()); } @@ -63,7 +75,7 @@ public final R recover(Function recoveryFn) { * @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); } @@ -71,13 +83,13 @@ 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()); } @@ -88,13 +100,13 @@ public final R orThrow(Function th *

* 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 filter(pred, __ -> leftSupplier.get()); + public final Either filter(Fn1 pred, Fn0 leftFn0) { + return filter(pred, __ -> leftFn0.apply()); } /** @@ -106,8 +118,8 @@ public final Either filter(Function pred, Su * @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(Function pred, - Function leftFn) { + public final Either filter(Fn1 pred, + Fn1 leftFn) { return flatMap(r -> pred.apply(r) ? right(r) : left(leftFn.apply(r))); } @@ -123,8 +135,19 @@ public final Either filter(Function pred, * @return the Either resulting from applying rightFn to this right value, or this left value if left */ @Override - public Either flatMap(Function>> rightFn) { - return match(Either::left, rightFn.andThen(Applicative::coerce)); + @SuppressWarnings("RedundantTypeArguments") + public Either flatMap(Fn1>> rightFn) { + return match(Either::left, rightFn.fmap(Monad>::coerce)); + } + + /** + * {@inheritDoc} + */ + @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)))); } @Override @@ -144,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)))), @@ -156,22 +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.peek(rightConsumer, this); + @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 Peek2.peek2(leftConsumer, rightConsumer, this); + @Deprecated + public Either peek(Fn1> leftEffect, Fn1> rightEffect) { + return match(leftEffect, rightEffect).fmap(constantly(this)).unsafePerformIO(); } /** @@ -179,68 +209,124 @@ 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 */ @Override - public abstract V match(Function leftFn, Function rightFn); + public abstract V match(Fn1 leftFn, Fn1 rightFn); + /** + * {@inheritDoc} + */ @Override public Choice3 diverge() { return match(Choice3::a, Choice3::b); } + /** + * {@inheritDoc} + */ @Override - public final Either fmap(Function fn) { - return Monad.super.fmap(fn).coerce(); + public final Either fmap(Fn1 fn) { + return MonadError.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final Either biMapL(Function fn) { - return (Either) Bifunctor.super.biMapL(fn); + public final Either biMapL(Fn1 fn) { + return (Either) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final Either biMapR(Function fn) { - return (Either) Bifunctor.super.biMapR(fn); + public final Either biMapR(Fn1 fn) { + return (Either) Bifunctor.super.biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public final Either biMap(Function leftFn, - Function rightFn) { + public final Either biMap(Fn1 leftFn, + Fn1 rightFn) { return match(l -> left(leftFn.apply(l)), r -> right(rightFn.apply(r))); } + /** + * {@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 final Either zip(Applicative, Either> appFn) { - return appFn.>>coerce().flatMap(this::biMapR); + 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 Monad.super.discardL(appB).coerce(); + return MonadError.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public final Either discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadError.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Either throwError(L l) { + return left(l); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public final >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, - Function pure) { - return (AppTrav) match(l -> pure.apply((TravB) left(l)), r -> fn.apply(r).fmap(Either::right)); + @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(); } /** @@ -257,71 +343,65 @@ public final Maybe toMaybe() { * Convert a {@link Maybe}<R> into an Either<L, R>, supplying the left value from * leftFn in the case of {@link Maybe#nothing()}. * - * @param maybe the maybe - * @param leftFn the supplier to use for left values - * @param the left parameter type - * @param the right parameter type + * @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 fromMaybe(Maybe maybe, Supplier leftFn) { + public static Either fromMaybe(Maybe maybe, Fn0 leftFn0) { return maybe.>fmap(Either::right) - .orElseGet(() -> left(leftFn.get())); + .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 */ - public static Either trying(CheckedSupplier supplier, - Function leftFn) { - return Try.trying(supplier::get).toEither(leftFn); + 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 CheckedRunnable}, returning {@link Unit} in a right value. If the runnable throws + * 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 runnable the runnable - * @param leftFn a function mapping E to L - * @param the most contravariant exception that the runnable might throw - * @param the left parameter type + * @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(CheckedRunnable runnable, - Function leftFn) { - return Try.trying(runnable).toEither(leftFn); + public static Either trying(SideEffect sideEffect, Fn1 leftFn) { + return Try.trying(sideEffect).toEither(leftFn); } /** - * Attempt to execute the {@link CheckedRunnable}, returning {@link Unit} in a right value. If the runnable throws + * 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 runnable the runnable - * @param the left parameter type (the most contravariant exception that runnable might throw) + * @param sideEffect the runnable * @return {@link Unit} as a right value, or a left value of the thrown exception */ - public static Either trying(CheckedRunnable runnable) { - return trying(runnable, id()); + public static Either trying(SideEffect sideEffect) { + return trying(sideEffect, id()); } /** @@ -348,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; @@ -356,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); } @@ -386,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 index ad0b81ad9..dc5595983 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java @@ -5,23 +5,31 @@ 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.builtin.fn2.Peek; -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.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 java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; 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 @@ -30,7 +38,11 @@ * @param the optional parameter type * @see Optional */ -public abstract class Maybe implements CoProduct2>, Monad, Traversable { +public abstract class Maybe implements + CoProduct2>, + MonadError>, + MonadRec>, + Traversable> { private Maybe() { } @@ -38,11 +50,11 @@ private Maybe() { /** * If the value is present, return it; otherwise, return the value supplied by otherSupplier. * - * @param otherSupplier the supplier for the other value + * @param otherFn0 the supplier for the other value * @return this value, or the supplied other value */ - public final A orElseGet(Supplier otherSupplier) { - return match(__ -> otherSupplier.get(), id()); + public final A orElseGet(Fn0 otherFn0) { + return match(__ -> otherFn0.apply(), id()); } /** @@ -64,10 +76,10 @@ public final A orElse(A other) { * @return the value, if present * @throws E the throwable, if the value is absent */ - public final A orElseThrow(Supplier throwableSupplier) throws E { - return orElseGet((CheckedSupplier) () -> { - throw throwableSupplier.get(); - }); + public final A orElseThrow(Fn0 throwableSupplier) throws E { + return orElseGet(fn0(() -> { + throw throwableSupplier.apply(); + })); } /** @@ -77,20 +89,36 @@ public final A orElseThrow(Supplier throwableSupplier) * @param predicate the predicate to apply to the possibly absent value * @return maybe the present value that satisfied the predicate */ - public final Maybe filter(Function 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 lSupplier the supplier for the left value - * @param the left parameter type + * @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(Supplier lSupplier) { - return fmap(Either::right).orElseGet(() -> left(lSupplier.get())); + public final Either toEither(Fn0 lFn0) { + return fmap(Either::right).orElseGet(() -> left(lFn0.apply())); } /** @@ -121,40 +149,85 @@ public final Maybe pure(B b) { * {@link Maybe#nothing}. */ @Override - public final Maybe fmap(Function fn) { - return Monad.super.fmap(fn).coerce(); + 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 zip(Applicative, Maybe> appFn) { - return Monad.super.zip(appFn).coerce(); + public final Maybe discardL(Applicative> appB) { + return MonadError.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public final Maybe discardL(Applicative appB) { - return Monad.super.discardL(appB).coerce(); + public final Maybe discardR(Applicative> appB) { + return MonadError.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ + @SuppressWarnings("RedundantTypeArguments") @Override - public final Maybe discardR(Applicative appB) { - return Monad.super.discardR(appB).coerce(); + public final Maybe flatMap(Fn1>> f) { + return match(constantly(nothing()), f.fmap(Monad>::coerce)); } + /** + * {@inheritDoc} + */ @Override - public final Maybe flatMap(Function> f) { - return match(constantly(nothing()), f.andThen(Applicative::coerce)); + 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); @@ -163,18 +236,20 @@ public Choice2 invert() { /** * If this value is present, accept it by consumer; otherwise, do nothing. * - * @param consumer the consumer + * @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 */ - public final Maybe peek(Consumer consumer) { - return Peek.peek(consumer, this); + @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 , AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, - Function pure) { + 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)); } @@ -236,17 +311,26 @@ public static Maybe just(A a) { */ @SuppressWarnings("unchecked") public static Maybe nothing() { - return Nothing.INSTANCE; + 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 static final Nothing INSTANCE = new Nothing<>(); private Nothing() { } @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return aFn.apply(UNIT); } @@ -265,7 +349,7 @@ private Just(A a) { } @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return bFn.apply(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 index f4ffc5846..4fc4cc27b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/These.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/These.java @@ -3,17 +3,24 @@ 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 java.util.function.Function; 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 @@ -23,7 +30,11 @@ * @param the first possible type * @param the second possible type */ -public abstract class These implements CoProduct3, These>, Monad>, Bifunctor>, Traversable> { +public abstract class These implements + CoProduct3, These>, + MonadRec>, + Bifunctor>, + Traversable> { private These() { } @@ -32,8 +43,8 @@ private These() { * {@inheritDoc} */ @Override - public final These biMap(Function lFn, - Function rFn) { + 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)))); } @@ -41,8 +52,25 @@ public final These biMap(Function lFn, * {@inheritDoc} */ @Override - public final These flatMap(Function>> f) { - return match(These::a, b -> f.apply(b).coerce(), into((a, b) -> f.apply(b).>coerce().biMapL(constantly(a)))); + 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))); } /** @@ -53,47 +81,58 @@ 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 - @SuppressWarnings("unchecked") - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return match(a -> pure.apply((TravB) a(a)), - 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())); + public final These biMapL(Fn1 fn) { + return (These) Bifunctor.super.biMapL(fn); } /** * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") - public final These biMapL(Function fn) { - return (These) Bifunctor.super.biMapL(fn); + public final These biMapR(Fn1 fn) { + return (These) Bifunctor.super.biMapR(fn); } /** * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") - public final These biMapR(Function fn) { - return (These) Bifunctor.super.biMapR(fn); + public final These fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); } /** * {@inheritDoc} */ @Override - public final These fmap(Function fn) { - return Monad.super.fmap(fn).coerce(); + public final These zip(Applicative, These> appFn) { + return MonadRec.super.zip(appFn).coerce(); } /** * {@inheritDoc} */ @Override - public final These zip(Applicative, These> appFn) { - return Monad.super.zip(appFn).coerce(); + public Lazy> lazyZip( + Lazy, These>> lazyAppFn) { + return projectA().>>fmap(a -> lazy(a(a))) + .orElseGet(() -> MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce)); } /** @@ -101,7 +140,7 @@ public final These zip(Applicative, T */ @Override public final These discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -109,7 +148,7 @@ public final These discardL(Applicative> appB) { */ @Override public final These discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -150,6 +189,35 @@ 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; @@ -159,8 +227,8 @@ private _A(A a) { } @Override - public R match(Function aFn, Function bFn, - Function, ? extends R> cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1, ? extends R> cFn) { return aFn.apply(a); } @@ -188,8 +256,8 @@ private _B(B b) { } @Override - public R match(Function aFn, Function bFn, - Function, ? extends R> cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1, ? extends R> cFn) { return bFn.apply(b); } @@ -217,8 +285,8 @@ private Both(Tuple2 tuple) { } @Override - public R match(Function aFn, Function bFn, - Function, ? extends R> cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1, ? extends R> cFn) { return cFn.apply(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 index d629a967b..90cd28091 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Try.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Try.java @@ -1,33 +1,44 @@ package com.jnape.palatable.lambda.adt; 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.CheckedRunnable; -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.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.BoundedBifunctor; +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.function.Function; 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.builtin.fn2.Peek2.peek2; +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 {@link Throwable} type that may have been thrown by the expression * @param the possibly successful expression result * @see Either */ -public abstract class Try implements Monad>, Traversable>, BoundedBifunctor>, CoProduct2> { +public abstract class Try implements + MonadError>, + MonadRec>, + Traversable>, + CoProduct2> { private Try() { } @@ -35,14 +46,14 @@ 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 - * @param the {@link Throwable} (sub)type * @return a new {@link Try} instance around either the original successful result or the mapped result */ - @SuppressWarnings("unchecked") - public final Try catching(Class throwableType, Function recoveryFn) { - return catching(throwableType::isInstance, t -> recoveryFn.apply((S) t)); + public final Try catching(Class throwableType, + Fn1 recoveryFn) { + return catching(throwableType::isInstance, t -> recoveryFn.apply(Downcast.downcast(t))); } /** @@ -52,8 +63,8 @@ public final Try catching(Class throwableType, Function catching(Function predicate, - Function recoveryFn) { + public final Try catching(Fn1 predicate, + Fn1 recoveryFn) { return match(t -> predicate.apply(t) ? success(recoveryFn.apply(t)) : failure(t), Try::success); } @@ -66,15 +77,18 @@ public final Try catching(Function predicate * 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 runnable the runnable block of code to execute + * @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(CheckedRunnable runnable) { - return match(t -> peek2(t::addSuppressed, __ -> {}, trying(runnable)) - .biMapL(constantly(t)) - .flatMap(constantly(failure(t))), - a -> trying(runnable).fmap(constantly(a))); + 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))); } /** @@ -84,7 +98,7 @@ public final Try ensuring(CheckedRunnable runnable) { * @param fn the function mapping the potential {@link Throwable} T to A * @return a success value */ - public final A recover(Function fn) { + public final A recover(Fn1 fn) { return match(fn, id()); } @@ -95,17 +109,35 @@ public final A recover(Function fn) { * @param fn the function mapping the potential A to T * @return a failure value */ - public final T forfeit(Function fn) { + 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 possible failure + * @throws T the transformation output */ - public abstract A orThrow() throws T; + 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 @@ -123,7 +155,7 @@ public final Maybe toMaybe() { * * @return {@link Either} the success value or the {@link Throwable} */ - public final Either toEither() { + public final Either toEither() { return toEither(id()); } @@ -131,89 +163,129 @@ public final Either toEither() { * 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 fn the mapping function * @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(Function fn) { - return match(fn.andThen(Either::left), Either::right); + public final Either toEither(Fn1 fn) { + return match(fn.fmap(Either::left), Either::right); } + /** + * {@inheritDoc} + */ @Override - public Try fmap(Function fn) { - return Monad.super.fmap(fn).coerce(); + public Try throwError(Throwable throwable) { + return failure(throwable); } + /** + * {@inheritDoc} + */ @Override - public Try flatMap(Function>> f) { - return match(Try::failure, a -> f.apply(a).coerce()); + 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 pure(B b) { - return success(b); + public Try flatMap(Fn1>> f) { + return match(Try::failure, a -> f.apply(a).coerce()); } + /** + * {@inheritDoc} + */ @Override - public Try zip(Applicative, Try> appFn) { - return Monad.super.zip(appFn).coerce(); + public Try pure(B b) { + return success(b); } + /** + * {@inheritDoc} + */ @Override - public Try discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + public Try zip(Applicative, Try> appFn) { + return MonadError.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Try discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + 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 - @SuppressWarnings("unchecked") - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return match(t -> pure.apply((TravB) failure(t)), - a -> fn.apply(a).fmap(Try::success).fmap(Applicative::coerce).coerce()); + public Try discardL(Applicative> appB) { + return MonadError.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Try biMap(Function lFn, - Function rFn) { - return match(t -> failure(lFn.apply(t)), a -> success(rFn.apply(a))); + public Try discardR(Applicative> appB) { + return MonadError.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Try biMapL(Function fn) { - return (Try) BoundedBifunctor.super.biMapL(fn); + 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 - public Try biMapR(Function fn) { - return (Try) BoundedBifunctor.super.biMapR(fn); + @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 failure parameter type * @param the success parameter type * @return a success value of a */ - public static Try success(A a) { + public static Try success(A a) { return new Success<>(a); } /** * Static factory method for creating a failure value. * - * @param t the wrapped {@link Throwable} - * @param the failure parameter type + * @param t the {@link Throwable} * @param the success parameter type * @return a failure value of t */ - public static Try failure(T t) { + public static Try failure(Throwable t) { return new Failure<>(t); } @@ -221,37 +293,34 @@ public static Try failure(T t) { * Execute supplier, returning a success A or a failure of the thrown {@link Throwable}. * * @param supplier the supplier - * @param the possible {@link Throwable} type * @param the possible success type * @return a new {@link Try} around either a successful A result or the thrown {@link Throwable} */ - @SuppressWarnings("unchecked") - public static Try trying(CheckedSupplier supplier) { + public static Try trying(Fn0 supplier) { try { - return success(supplier.get()); + return success(supplier.apply()); } catch (Throwable t) { - return failure((T) t); + return failure(t); } } /** * Execute runnable, returning a success {@link Unit} or a failure of the thrown {@link Throwable}. * - * @param runnable the runnable - * @param the possible {@link Throwable} type + * @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(CheckedRunnable runnable) { + public static Try trying(SideEffect sideEffect) { return trying(() -> { - runnable.run(); + IO.io(sideEffect).unsafePerformIO(); return UNIT; }); } /** - * Given a {@link CheckedSupplier}<{@link AutoCloseable}> aSupplier and a - * {@link Function} fn, apply fn to the result of aSupplier, ensuring - * that the result has its {@link AutoCloseable#close() close} method invoked, regardless of the outcome. + * 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 @@ -269,78 +338,88 @@ public static Try trying(CheckedRunnable runna * * try-with-resources, introduced in Java 7. * - * @param aSupplier the resource supplier - * @param fn the function body - * @param the resource type - * @param the function return type + * @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 */ - public static Try withResources( - CheckedSupplier aSupplier, - CheckedFn1> fn) { + @SuppressWarnings("try") + public static Try withResources( + Fn0 fn0, + Fn1> fn) { return trying(() -> { - try (A resource = aSupplier.get()) { - return fn.apply(resource).biMap(upcast(), upcast()); + try (A resource = fn0.apply()) { + return fn.apply(resource).fmap(upcast()); } }).flatMap(id()); } /** - * Convenience overload of {@link Try#withResources(CheckedSupplier, CheckedFn1) withResources} that cascades - * dependent resource creation via nested calls. + * Convenience overload of {@link Try#withResources(Fn0, Fn1) withResources} that cascades dependent resource + * creation via nested calls. * - * @param aSupplier 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 + * @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( - CheckedSupplier aSupplier, - CheckedFn1 bFn, - CheckedFn1> fn) { - return withResources(aSupplier, a -> withResources(() -> bFn.apply(a), fn::apply)); + 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(CheckedSupplier, CheckedFn1, CheckedFn1) withResources} that + * Convenience overload of {@link Try#withResources(Fn0, Fn1, Fn1) withResources} that * cascades * two dependent resource creations via nested calls. * - * @param aSupplier 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 + * @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( - CheckedSupplier aSupplier, - CheckedFn1 bFn, - CheckedFn1 cFn, - CheckedFn1> fn) { - return withResources(aSupplier, bFn, b -> withResources(() -> cFn.apply(b), fn::apply)); + 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 T t; + private static final class Failure extends Try { + private final Throwable t; - private Failure(T t) { + private Failure(Throwable t) { this.t = t; } @Override - public A orThrow() throws T { - throw t; + public A orThrow(Fn1 fn) throws T { + throw fn.apply(t); } @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return aFn.apply(t); } @@ -362,7 +441,7 @@ public String toString() { } } - private static final class Success extends Try { + private static final class Success extends Try { private final A a; private Success(A a) { @@ -370,12 +449,12 @@ private Success(A a) { } @Override - public A orThrow() throws T { + public A orThrow(Fn1 fn) { return a; } @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return bFn.apply(a); } 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 969da832d..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 @@ -5,15 +5,23 @@ 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}. Unlike {@link Either}, there is no concept of "success" or @@ -26,8 +34,8 @@ */ public abstract class Choice2 implements CoProduct2>, - Monad>, - Bifunctor, + MonadRec>, + Bifunctor>, Traversable> { private Choice2() { @@ -43,71 +51,125 @@ 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 Monad.super.fmap(fn).coerce(); + 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 Choice2 zip(Applicative, Choice2> appFn) { - return appFn.>>coerce() - .match(Choice2::a, this::biMapR); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public Choice2 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public final Choice2 flatMap(Function>> f) { + 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 - @SuppressWarnings("unchecked") - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return match(a -> pure.apply((TravB) a(a)), - b -> fn.apply(b).fmap(Choice2::b).fmap(Applicative::coerce).coerce()); + 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()); } /** @@ -134,6 +196,16 @@ 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; @@ -143,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); } @@ -175,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 1f8aa3226..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 @@ -5,15 +5,23 @@ 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}. @@ -26,7 +34,7 @@ */ public abstract class Choice3 implements CoProduct3>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -43,72 +51,129 @@ 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 Monad.super.fmap(fn).coerce(); + 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 appFn.>>coerce() - .match(Choice3::a, Choice3::b, this::biMapR); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public Choice3 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Choice3 flatMap(Function>> f) { + public Choice3 flatMap(Fn1>> f) { return match(Choice3::a, Choice3::b, c -> f.apply(c).coerce()); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return match(a -> pure.apply((TravB) Choice3.a(a)).coerce(), - b -> pure.apply((TravB) Choice3.b(b)).coerce(), - c -> fn.apply(c).fmap(Choice3::c).fmap(Applicative::coerce).coerce()); + 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(); } /** @@ -150,6 +215,17 @@ 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; @@ -159,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); } @@ -192,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); } @@ -225,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); } @@ -248,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 494638330..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 @@ -5,15 +5,23 @@ 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}. @@ -27,7 +35,7 @@ */ public abstract class Choice4 implements CoProduct4>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -44,76 +52,133 @@ 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 Monad.super.fmap(fn).coerce(); + 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 Choice4 zip(Applicative, Choice4> appFn) { - return appFn.>>coerce() - .match(Choice4::a, Choice4::b, Choice4::c, this::biMapR); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public Choice4 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Choice4 flatMap(Function>> f) { + 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 - @SuppressWarnings("unchecked") - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return match(a -> pure.apply((TravB) Choice4.a(a)).coerce(), - b -> pure.apply((TravB) Choice4.b(b)).coerce(), - c -> pure.apply((TravB) Choice4.c(c)), - d -> fn.apply(d).fmap(Choice4::d).fmap(Applicative::coerce).coerce()); + 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(); } /** @@ -172,6 +237,18 @@ 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; @@ -181,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); } @@ -214,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); } @@ -247,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); } @@ -280,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 5954d4ae2..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 @@ -5,15 +5,22 @@ 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.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}. @@ -28,7 +35,7 @@ */ public abstract class Choice5 implements CoProduct5>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -45,78 +52,137 @@ public Tuple5, Maybe, Maybe, Maybe, Maybe> project() { return into5(HList::tuple, CoProduct5.super.project()); } + /** + * {@inheritDoc} + */ @Override public Choice6 diverge() { return match(Choice6::a, Choice6::b, Choice6::c, Choice6::d, Choice6::e); } + /** + * {@inheritDoc} + */ @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 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(Function fn) { - return Monad.super.fmap(fn).coerce(); + 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 - 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 appFn.>>coerce() - .match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, this::biMapR); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public Choice5 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Choice5 flatMap(Function>> f) { + public Choice5 flatMap(Fn1>> f) { return match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, e -> f.apply(e).coerce()); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return match(a -> pure.apply((TravB) Choice5.a(a)).coerce(), - b -> pure.apply((TravB) Choice5.b(b)).coerce(), - c -> pure.apply((TravB) Choice5.c(c)), - d -> pure.apply((TravB) Choice5.d(d)), - e -> fn.apply(e).fmap(Choice5::e).fmap(Applicative::coerce).coerce()); + 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(); } /** @@ -194,6 +260,19 @@ 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; @@ -203,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); } @@ -237,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); } @@ -271,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); } @@ -305,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); } @@ -339,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 index 8076383b8..ec0eacfc5 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice6.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice6.java @@ -5,15 +5,22 @@ 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 java.util.function.Function; 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}. @@ -29,7 +36,7 @@ */ public abstract class Choice6 implements CoProduct6>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -46,82 +53,141 @@ public Tuple6, Maybe, Maybe, Maybe, Maybe, Maybe> projec 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(Function> convergenceFn) { - return match(Choice5::a, - Choice5::b, - Choice5::c, - Choice5::d, - Choice5::e, - convergenceFn.andThen(cp5 -> cp5.match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, Choice5::e))); + 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(Function fn) { - return Monad.super.fmap(fn).coerce(); + public Choice6 fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Choice6 biMapL(Function fn) { - return (Choice6) Bifunctor.super.biMapL(fn); + public Choice6 biMapL(Fn1 fn) { + return (Choice6) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Choice6 biMapR(Function fn) { - return (Choice6) Bifunctor.super.biMapR(fn); + public Choice6 biMapR(Fn1 fn) { + return (Choice6) Bifunctor.super.biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public Choice6 biMap(Function lFn, - Function rFn) { + 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 appFn.>>coerce() - .match(Choice6::a, Choice6::b, Choice6::c, Choice6::d, Choice6::e, this::biMapR); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public Choice6 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Choice6 flatMap( - Function>> fn) { + 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 - @SuppressWarnings("unchecked") - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return match(a -> pure.apply((TravB) Choice6.a(a)).coerce(), - b -> pure.apply((TravB) Choice6.b(b)).coerce(), - c -> pure.apply((TravB) Choice6.c(c)), - d -> pure.apply((TravB) Choice6.d(d)), - e -> pure.apply((TravB) Choice6.e(e)), - f -> fn.apply(f).fmap(Choice6::f).fmap(Applicative::coerce).coerce()); + 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(); } /** @@ -220,6 +286,20 @@ 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; @@ -229,9 +309,9 @@ private _A(A a) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { return aFn.apply(a); } @@ -261,9 +341,9 @@ private _B(B b) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { return bFn.apply(b); } @@ -293,9 +373,9 @@ private _C(C c) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { return cFn.apply(c); } @@ -325,9 +405,9 @@ private _D(D d) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { return dFn.apply(d); } @@ -357,9 +437,9 @@ private _E(E e) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { return eFn.apply(e); } @@ -389,9 +469,9 @@ private _F(F f) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { return fFn.apply(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 index bd7f3b65f..bf0131e84 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice7.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice7.java @@ -5,15 +5,22 @@ 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 java.util.function.Function; 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}. @@ -30,7 +37,7 @@ */ public abstract class Choice7 implements CoProduct7>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -47,85 +54,146 @@ public Tuple7, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe< 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( - Function> convergenceFn) { - return match(Choice6::a, - Choice6::b, - Choice6::c, - Choice6::d, - Choice6::e, - Choice6::f, - convergenceFn.andThen(cp6 -> cp6.match(Choice6::a, Choice6::b, Choice6::c, Choice6::d, Choice6::e, Choice6::f))); + 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(Function fn) { - return Monad.super.fmap(fn).coerce(); + public Choice7 fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Choice7 biMapL(Function fn) { - return (Choice7) Bifunctor.super.biMapL(fn); + public Choice7 biMapL(Fn1 fn) { + return (Choice7) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Choice7 biMapR(Function fn) { - return (Choice7) Bifunctor.super.biMapR(fn); + public Choice7 biMapR(Fn1 fn) { + return (Choice7) Bifunctor.super.biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public Choice7 biMap(Function lFn, - Function rFn) { + 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 appFn.>>coerce() - .match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, Choice7::f, this::biMapR); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public Choice7 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public Choice7 flatMap( - Function>> fn) { + 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 - @SuppressWarnings("unchecked") - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return match(a -> pure.apply((TravB) Choice7.a(a)).coerce(), - b -> pure.apply((TravB) Choice7.b(b)).coerce(), - c -> pure.apply((TravB) Choice7.c(c)), - d -> pure.apply((TravB) Choice7.d(d)), - e -> pure.apply((TravB) Choice7.e(e)), - f -> pure.apply((TravB) Choice7.f(f)), - g -> fn.apply(g).fmap(Choice7::g).fmap(Applicative::coerce).coerce()); + 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(); } /** @@ -247,6 +315,21 @@ 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; @@ -256,10 +339,10 @@ private _A(A a) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { return aFn.apply(a); } @@ -289,10 +372,10 @@ private _B(B b) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { return bFn.apply(b); } @@ -322,10 +405,10 @@ private _C(C c) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { return cFn.apply(c); } @@ -355,10 +438,10 @@ private _D(D d) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { return dFn.apply(d); } @@ -388,10 +471,10 @@ private _E(E e) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { return eFn.apply(e); } @@ -421,10 +504,10 @@ private _F(F f) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { return fFn.apply(f); } @@ -454,10 +537,10 @@ private _G(G g) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { return gFn.apply(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 index 9755c5050..edd220a7d 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice8.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice8.java @@ -5,15 +5,22 @@ 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 java.util.function.Function; 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}. @@ -30,7 +37,7 @@ */ public abstract class Choice8 implements CoProduct8>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -47,82 +54,142 @@ public Tuple8, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe< return into8(HList::tuple, CoProduct8.super.project()); } + /** + * {@inheritDoc} + */ @Override public Choice7 converge( - Function> convergenceFn) { - return match(Choice7::a, - Choice7::b, - Choice7::c, - Choice7::d, - Choice7::e, - Choice7::f, - Choice7::g, - convergenceFn.andThen(cp7 -> cp7.match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, Choice7::f, Choice7::g))); + 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(Function fn) { - return Monad.super.fmap(fn).coerce(); + public Choice8 fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Choice8 biMapL(Function fn) { - return (Choice8) Bifunctor.super.biMapL(fn); + public Choice8 biMapL(Fn1 fn) { + return (Choice8) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Choice8 biMapR(Function fn) { - return (Choice8) Bifunctor.super.biMapR(fn); + public Choice8 biMapR(Fn1 fn) { + return (Choice8) Bifunctor.super.biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public Choice8 biMap(Function lFn, - Function rFn) { + 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 appFn.>>coerce() - .match(Choice8::a, Choice8::b, Choice8::c, Choice8::d, Choice8::e, Choice8::f, Choice8::g, this::biMapR); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public Choice8 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public Choice8 flatMap( - Function>> fn) { + 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 - @SuppressWarnings("unchecked") - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return match(a -> pure.apply((TravB) Choice8.a(a)).coerce(), - b -> pure.apply((TravB) Choice8.b(b)).coerce(), - c -> pure.apply((TravB) Choice8.c(c)), - d -> pure.apply((TravB) Choice8.d(d)), - e -> pure.apply((TravB) Choice8.e(e)), - f -> pure.apply((TravB) Choice8.f(f)), - g -> pure.apply((TravB) Choice8.g(g)), - h -> fn.apply(h).fmap(Choice8::h).fmap(Applicative::coerce).coerce()); + 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(); } /** @@ -269,6 +336,21 @@ 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 { @@ -279,10 +361,10 @@ private _A(A a) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { return aFn.apply(a); } @@ -312,10 +394,10 @@ private _B(B b) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { return bFn.apply(b); } @@ -345,10 +427,10 @@ private _C(C c) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { return cFn.apply(c); } @@ -378,10 +460,10 @@ private _D(D d) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { return dFn.apply(d); } @@ -411,10 +493,10 @@ private _E(E e) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { return eFn.apply(e); } @@ -444,10 +526,10 @@ private _F(F f) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { return fFn.apply(f); } @@ -477,10 +559,10 @@ private _G(G g) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { return gFn.apply(g); } @@ -510,10 +592,10 @@ private _H(H h) { } @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { return hFn.apply(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 5ad1a9908..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 @@ -6,8 +6,6 @@ import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functions.Fn1; -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; @@ -32,12 +30,12 @@ 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 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 @@ -63,8 +61,8 @@ public interface CoProduct2> { 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); } }; @@ -107,7 +105,7 @@ default Maybe projectB() { default CoProduct2> invert() { return new CoProduct2>() { @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return CoProduct2.this.match(bFn, aFn); } }; @@ -118,14 +116,13 @@ public R match(Function aFn, Function 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 - * @param result type * @return the result of applying the appropriate morphism to this coproduct */ @SuppressWarnings("unchecked") - default R embed(Function aFn, - Function bFn) { + 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 23a97ac9c..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,11 +1,10 @@ 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.product.Product3; import com.jnape.palatable.lambda.functions.Fn1; -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; @@ -27,15 +26,14 @@ 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 to this coproduct's unwrapped value - * @see CoProduct2#match(Function, Function) + * @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. @@ -47,8 +45,8 @@ R match(Function aFn, Function 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); } }; @@ -68,18 +66,8 @@ public R match(Function aFn, Function> 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); + Fn1> convergenceFn) { + return match(Choice2::a, Choice2::b, convergenceFn::apply); } /** @@ -126,16 +114,15 @@ default Maybe projectC() { * 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 - * @param result type * @return the result of applying the appropriate morphism to this coproduct */ @SuppressWarnings("unchecked") - default R embed(Function aFn, - Function bFn, - Function cFn) { + default R embed(Fn1 aFn, Fn1 bFn, + Fn1 cFn) { return this.>match(constantly(fn1(aFn)), constantly(fn1(bFn)), constantly(fn1(cFn))) 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 2ec2e113d..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,11 +1,10 @@ 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.product.Product4; import com.jnape.palatable.lambda.functions.Fn1; -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; @@ -28,18 +27,18 @@ 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. @@ -51,9 +50,9 @@ R match(Function aFn, 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); } }; @@ -68,26 +67,8 @@ 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); + Fn1> convergenceFn) { + return match(Choice3::a, Choice3::b, Choice3::c, convergenceFn::apply); } /** @@ -144,18 +125,18 @@ default Maybe projectD() { * 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 - * @param result type * @return the result of applying the appropriate morphism to this coproduct */ @SuppressWarnings("unchecked") - default R embed(Function aFn, - Function bFn, - Function cFn, - Function dFn) { + default R embed(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn) { return this.>match(constantly(fn1(aFn)), constantly(fn1(bFn)), constantly(fn1(cFn)), 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 b90aab2f5..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,11 +1,10 @@ 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.product.Product5; import com.jnape.palatable.lambda.functions.Fn1; -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; @@ -29,20 +28,20 @@ public interface CoProduct5 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(Function aFn, - Function bFn, - Function cFn, - Function dFn, - Function eFn); + R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn, + Fn1 dFn, + Fn1 eFn); /** * Diverge this coproduct by introducing another possible type that it could represent. @@ -54,9 +53,9 @@ R match(Function aFn, default CoProduct6> diverge() { return new CoProduct6>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { return CoProduct5.this.match(aFn, bFn, cFn, dFn, eFn); } }; @@ -70,32 +69,8 @@ public R match(Function aFn, Function> 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); + Fn1> convergenceFn) { + return match(Choice4::a, Choice4::b, Choice4::c, Choice4::d, convergenceFn::apply); } /** @@ -162,20 +137,20 @@ default Maybe projectE() { * 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 - * @param result type * @return the result of applying the appropriate morphism to this coproduct */ @SuppressWarnings("unchecked") - default R embed(Function aFn, - Function bFn, - Function cFn, - Function dFn, - Function eFn) { + 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)), 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 index a3aa40163..d8d8d2301 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct6.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct6.java @@ -5,8 +5,6 @@ import com.jnape.palatable.lambda.adt.product.Product6; import com.jnape.palatable.lambda.functions.Fn1; -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; @@ -31,22 +29,22 @@ public interface CoProduct6 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 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, - Function eFn, - Function fFn); + 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. @@ -58,10 +56,10 @@ R match(Function aFn, default CoProduct7> diverge() { return new CoProduct7>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + 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); } }; @@ -75,7 +73,7 @@ public R match(Function aFn, Function> converge( - Function> convergenceFn) { + Fn1> convergenceFn) { return match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, Choice5::e, convergenceFn::apply); } @@ -153,22 +151,22 @@ default Maybe projectF() { * 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 - * @param result type * @return the result of applying the appropriate morphism to this coproduct */ @SuppressWarnings("unchecked") - default R embed(Function aFn, - Function bFn, - Function cFn, - Function dFn, - Function eFn, - Function fFn) { + 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)), 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 index 18086211c..0f7647f96 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct7.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct7.java @@ -5,8 +5,6 @@ import com.jnape.palatable.lambda.adt.product.Product7; import com.jnape.palatable.lambda.functions.Fn1; -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; @@ -32,6 +30,7 @@ public interface CoProduct7 result type * @param aFn morphism A -> R * @param bFn morphism B -> R * @param cFn morphism C -> R @@ -39,17 +38,16 @@ public interface CoProduct7E -> R * @param fFn morphism F -> R * @param gFn morphism G -> 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, - Function eFn, - Function fFn, - Function gFn); + 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. @@ -61,10 +59,10 @@ R match(Function aFn, default CoProduct8> diverge() { return new CoProduct8>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + 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); } }; @@ -78,7 +76,7 @@ public R match(Function aFn, Function> converge( - Function> convergenceFn) { + Fn1> convergenceFn) { return match(Choice6::a, Choice6::b, Choice6::c, Choice6::d, Choice6::e, Choice6::f, convergenceFn::apply); } @@ -166,6 +164,7 @@ default Maybe projectG() { * 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 @@ -173,17 +172,16 @@ default Maybe projectG() { * @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 - * @param result type * @return the result of applying the appropriate morphism to this coproduct */ @SuppressWarnings("unchecked") - default R embed(Function aFn, - Function bFn, - Function cFn, - Function dFn, - Function eFn, - Function fFn, - Function gFn) { + 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)), 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 index 776f067e8..9653e178a 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct8.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct8.java @@ -5,8 +5,6 @@ import com.jnape.palatable.lambda.adt.product.Product8; import com.jnape.palatable.lambda.functions.Fn1; -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; @@ -33,6 +31,7 @@ public interface CoProduct8 result type * @param aFn morphism A -> R * @param bFn morphism B -> R * @param cFn morphism C -> R @@ -41,18 +40,17 @@ public interface CoProduct8F -> R * @param gFn morphism G -> R * @param hFn morphism H -> 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, - Function eFn, - Function fFn, - Function gFn, - Function hFn); + 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 @@ -62,8 +60,9 @@ R match(Function aFn, * @return a {@link CoProduct7}<A, B, C, D, E, F, G> */ default CoProduct7> converge( - Function> convergenceFn) { - return match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, Choice7::f, Choice7::g, convergenceFn::apply); + Fn1> convergenceFn) { + return match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, Choice7::f, Choice7::g, + convergenceFn::apply); } /** @@ -160,6 +159,7 @@ default Maybe projectH() { * 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 @@ -168,18 +168,17 @@ default Maybe projectH() { * @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 - * @param result type * @return the result of applying the appropriate morphism to this coproduct */ @SuppressWarnings("unchecked") - default R embed(Function aFn, - Function bFn, - Function cFn, - Function dFn, - Function eFn, - Function fFn, - Function gFn, - Function hFn) { + 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)), 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 bfb0b8f79..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; /** @@ -34,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,7 +65,7 @@ public static HNil nil() { * @return the newly created HList */ public static HCons cons(Head head, Tail tail) { - return new HCons<>(head, tail); + return Downcast., HCons>downcast(tail.cons(head)); } /** @@ -87,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); } @@ -104,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); } @@ -123,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); } @@ -144,7 +143,6 @@ 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); } @@ -167,7 +165,6 @@ public static <_1, _2, _3, _4, _5> Tuple5<_1, _2, _3, _4, _5> tuple(_1 _1, _2 _2 * @return the 6-element HList * @see Tuple6 */ - @SuppressWarnings("JavaDoc") 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); @@ -193,7 +190,6 @@ public static <_1, _2, _3, _4, _5, _6> Tuple6<_1, _2, _3, _4, _5, _6> tuple(_1 _ * @return the 7-element HList * @see Tuple7 */ - @SuppressWarnings("JavaDoc") 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); @@ -221,7 +217,6 @@ public static <_1, _2, _3, _4, _5, _6, _7> Tuple7<_1, _2, _3, _4, _5, _6, _7> tu * @return the 8-element HList * @see Tuple8 */ - @SuppressWarnings("JavaDoc") 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) { @@ -269,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); } 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 0a8143d84..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,11 +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.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. @@ -18,63 +23,128 @@ * @see Tuple4 * @see Tuple5 */ -public class SingletonHList<_1> extends HCons<_1, HNil> implements Monad<_1, SingletonHList>, Traversable<_1, SingletonHList> { +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(Function fn) { - return Monad.super.<_1Prime>fmap(fn).coerce(); + 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 Monad.super.zip(appFn).coerce(); + Applicative, SingletonHList> appFn) { + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override - public <_1Prime> SingletonHList<_1Prime> discardL(Applicative<_1Prime, SingletonHList> appB) { - return Monad.super.discardL(appB).coerce(); + public <_1Prime> Lazy> lazyZip( + Lazy, SingletonHList>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_1Prime, SingletonHList>::coerce); } + /** + * {@inheritDoc} + */ @Override - public <_1Prime> SingletonHList<_1> discardR(Applicative<_1Prime, SingletonHList> appB) { - return Monad.super.discardR(appB).coerce(); + public <_1Prime> SingletonHList<_1Prime> discardL(Applicative<_1Prime, SingletonHList> appB) { + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public <_1Prime> SingletonHList<_1Prime> flatMap(Function> f) { + 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 - @SuppressWarnings("unchecked") - public , AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { + 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. + * 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(Function fn) { + 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 621537198..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,16 +1,27 @@ 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.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.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. @@ -26,9 +37,9 @@ */ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements Product2<_1, _2>, - Map.Entry<_1, _2>, - Monad<_2, Tuple2<_1, ?>>, - Bifunctor<_1, _2, Tuple2>, + MonadRec<_2, Tuple2<_1, ?>>, + MonadWriter<_1, _2, Tuple2<_1, ?>>, + Bifunctor<_1, _2, Tuple2>, Traversable<_2, Tuple2<_1, ?>> { private final _1 _1; @@ -37,100 +48,204 @@ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements 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); } + /** + * Snoc an element onto the back of this {@link Tuple2}. + * + * @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; } + /** + * {@inheritDoc} + */ @Override public _2 _2() { return _2; } + /** + * {@inheritDoc} + */ @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 Monad.super.<_2Prime>fmap(fn).coerce(); + 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 Monad.super.zip(appFn).coerce(); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_2Prime> Tuple2<_1, _2> discardR(Applicative<_2Prime, Tuple2<_1, ?>> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public <_2Prime> Tuple2<_1, _2Prime> flatMap(Function>> f) { + public <_2Prime> Tuple2<_1, _2Prime> flatMap(Fn1>> f) { return pure(f.apply(_2).>coerce()._2()); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_2Prime, App extends Applicative, TravB extends Traversable<_2Prime, Tuple2<_1, ?>>, AppB extends Applicative<_2Prime, App>, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { + 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. * @@ -153,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 32d8a5359..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,15 +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.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.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. @@ -26,7 +33,7 @@ */ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements Product3<_1, _2, _3>, - Monad<_3, Tuple3<_1, _2, ?>>, + MonadRec<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>>, Traversable<_3, Tuple3<_1, _2, ?>> { @@ -37,103 +44,191 @@ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements 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); } + /** + * Snoc an element onto the back of this {@link Tuple3}. + * + * @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; } + /** + * {@inheritDoc} + */ @Override public _2 _2() { return _2; } + /** + * {@inheritDoc} + */ @Override public _3 _3() { return _3; } + /** + * {@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} + */ @Override public Tuple3<_2, _1, _3> invert() { return tuple(_2, _1, _3); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_3Prime> Tuple3<_1, _2, _3Prime> fmap(Function fn) { - return (Tuple3<_1, _2, _3Prime>) Monad.super.fmap(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()); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_3Prime> Tuple3<_1, _2, _3> discardR(Applicative<_3Prime, Tuple3<_1, _2, ?>> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_3Prime> Tuple3<_1, _2, _3Prime> flatMap( - Function>> f) { + 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 - @SuppressWarnings("unchecked") - public <_3Prime, App extends Applicative, TravB extends Traversable<_3Prime, Tuple3<_1, _2, ?>>, AppB extends Applicative<_3Prime, App>, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { + 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. * @@ -145,4 +240,34 @@ public <_3Prime> Tuple3<_1, _2, _3Prime> flatMap( public static 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 af141c9f3..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,15 +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.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.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. @@ -27,7 +34,7 @@ */ public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implements Product4<_1, _2, _3, _4>, - Monad<_4, Tuple4<_1, _2, _3, ?>>, + MonadRec<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>>, Traversable<_4, Tuple4<_1, _2, _3, ?>> { @@ -39,116 +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); } + /** + * Snoc an element onto the back of this {@link Tuple4}. + * + * @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; } + /** + * {@inheritDoc} + */ @Override public _2 _2() { return _2; } + /** + * {@inheritDoc} + */ @Override public _3 _3() { return _3; } + /** + * {@inheritDoc} + */ @Override public _4 _4() { return _4; } + /** + * {@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} + */ @Override public Tuple4<_2, _3, _1, _4> rotateL3() { return tuple(_2, _3, _1, _4); } + /** + * {@inheritDoc} + */ @Override public Tuple4<_3, _1, _2, _4> rotateR3() { return tuple(_3, _1, _2, _4); } + /** + * {@inheritDoc} + */ @Override public Tuple4<_2, _1, _3, _4> invert() { return tuple(_2, _1, _3, _4); } + /** + * {@inheritDoc} + */ @Override - public <_4Prime> Tuple4<_1, _2, _3, _4Prime> fmap(Function fn) { - return (Tuple4<_1, _2, _3, _4Prime>) Monad.super.<_4Prime>fmap(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> Tuple4<_1, _2, _3Prime, _4> biMapL(Function fn) { + 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(Function fn) { + 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(Function lFn, - Function rFn) { + 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()); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_4Prime> Tuple4<_1, _2, _3, _4> discardR(Applicative<_4Prime, Tuple4<_1, _2, _3, ?>> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_4Prime> Tuple4<_1, _2, _3, _4Prime> flatMap( - Function>> f) { + Fn1>> f) { return pure(f.apply(_4).>coerce()._4); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_4Prime, App extends Applicative, TravB extends Traversable<_4Prime, Tuple4<_1, _2, _3, ?>>, AppB extends Applicative<_4Prime, App>, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { + 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. * @@ -160,4 +267,36 @@ public <_4Prime> Tuple4<_1, _2, _3, _4Prime> flatMap( public static 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 65dfb3c7a..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,15 +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.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. @@ -28,7 +35,7 @@ */ public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> implements Product5<_1, _2, _3, _4, _5>, - Monad<_5, Tuple5<_1, _2, _3, _4, ?>>, + MonadRec<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>>, Traversable<_5, Tuple5<_1, _2, _3, _4, ?>> { @@ -41,134 +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> Tuple6<_0, _1, _2, _3, _4, _5> cons(_0 _0) { return new Tuple6<>(_0, this); } + /** + * Snoc an element onto the back of this {@link Tuple5}. + * + * @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; } + /** + * {@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 Tuple5<_2, _3, _4, _5, _1> rotateL5() { return tuple(_2, _3, _4, _5, _1); } + /** + * {@inheritDoc} + */ @Override public Tuple5<_5, _1, _2, _3, _4> rotateR5() { return tuple(_5, _1, _2, _3, _4); } + /** + * {@inheritDoc} + */ @Override 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(Function fn) { - return Monad.super.<_5Prime>fmap(fn).coerce(); + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> fmap(Fn1 fn) { + return MonadRec.super.<_5Prime>fmap(fn).coerce(); } + /** + * {@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 <_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 Monad.super.zip(appFn).coerce(); + 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 Monad.super.discardL(appB).coerce(); + 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 Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> flatMap( - Function>> f) { + 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 - @SuppressWarnings("unchecked") - public <_5Prime, App extends Applicative, TravB extends Traversable<_5Prime, Tuple5<_1, _2, _3, _4, ?>>, AppB extends Applicative<_5Prime, App>, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { + 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. * @@ -180,4 +294,38 @@ public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> flatMap( public static 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 index 0f3046ace..1034e3b6b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java @@ -1,15 +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.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 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 6-element tuple product type, implemented as a specialized HList. Supports random access. @@ -30,7 +37,7 @@ */ 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>, - Monad<_6, Tuple6<_1, _2, _3, _4, _5, ?>>, + MonadRec<_6, Tuple6<_1, _2, _3, _4, _5, ?>>, Bifunctor<_5, _6, Tuple6<_1, _2, _3, _4, ?, ?>>, Traversable<_6, Tuple6<_1, _2, _3, _4, _5, ?>> { @@ -44,152 +51,269 @@ public class Tuple6<_1, _2, _3, _4, _5, _6> extends HCons<_1, Tuple5<_2, _3, _4, 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(); + _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(Function fn) { - return Monad.super.<_6Prime>fmap(fn).coerce(); + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> fmap(Fn1 fn) { + return MonadRec.super.<_6Prime>fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_5Prime> Tuple6<_1, _2, _3, _4, _5Prime, _6> biMapL(Function fn) { - return (Tuple6<_1, _2, _3, _4, _5Prime, _6>) Bifunctor.super.biMapL(fn); + 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 - @SuppressWarnings("unchecked") - public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> biMapR(Function fn) { - return (Tuple6<_1, _2, _3, _4, _5, _6Prime>) Bifunctor.super.biMapR(fn); + 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( - Function lFn, - Function rFn) { + 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 Monad.super.zip(appFn).coerce(); + 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 Monad.super.discardL(appB).coerce(); + 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 Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> flatMap( - Function>> f) { + Fn1>> f) { return pure(f.apply(_6).>coerce()._6()); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_6Prime, App extends Applicative, TravB extends Traversable<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>>, AppB extends Applicative<_6Prime, App>, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { + 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. * @@ -201,4 +325,41 @@ public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> flatMap( 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 index 1910381d1..cdfe552d6 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java @@ -1,15 +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.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 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 7-element tuple product type, implemented as a specialized HList. Supports random access. @@ -32,7 +39,7 @@ */ 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>, - Monad<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, + 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, ?>> { @@ -47,169 +54,296 @@ public class Tuple7<_1, _2, _3, _4, _5, _6, _7> extends HCons<_1, Tuple6<_2, _3, 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(); + _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(Function fn) { - return Monad.super.<_7Prime>fmap(fn).coerce(); + public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> fmap(Fn1 fn) { + return MonadRec.super.<_7Prime>fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_6Prime> Tuple7<_1, _2, _3, _4, _5, _6Prime, _7> biMapL(Function fn) { - return (Tuple7<_1, _2, _3, _4, _5, _6Prime, _7>) Bifunctor.super.biMapL(fn); + 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 - @SuppressWarnings("unchecked") - public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> biMapR(Function fn) { - return (Tuple7<_1, _2, _3, _4, _5, _6, _7Prime>) Bifunctor.super.biMapR(fn); + 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( - Function lFn, - Function rFn) { + 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 Monad.super.zip(appFn).coerce(); + 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 Monad.super.discardL(appB).coerce(); + 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 Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> flatMap( - Function>> f) { + Fn1>> f) { return pure(f.apply(_7).>coerce()._7()); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_7Prime, App extends Applicative, TravB extends Traversable<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, AppB extends Applicative<_7Prime, App>, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { + 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. * @@ -221,4 +355,43 @@ public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> flatMap( 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 index 30a9eba61..079a30611 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java @@ -1,15 +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.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 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; /** * An 8-element tuple product type, implemented as a specialized HList. Supports random access. @@ -34,7 +41,7 @@ */ 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>, - Monad<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, + 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, ?>> { @@ -50,185 +57,323 @@ public class Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> extends HCons<_1, Tuple7<_2, 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(); + _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(Function fn) { - return Monad.super.<_8Prime>fmap(fn).coerce(); + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> fmap(Fn1 fn) { + return MonadRec.super.<_8Prime>fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_7Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8> biMapL(Function fn) { - return (Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8>) Bifunctor.super.biMapL(fn); + 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 - @SuppressWarnings("unchecked") - public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> biMapR(Function fn) { - return (Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime>) Bifunctor.super.biMapR(fn); + 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( - Function lFn, - Function rFn) { + 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 Monad.super.zip(appFn).coerce(); + 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 Monad.super.discardL(appB).coerce(); + 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 Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> flatMap( - Function>> f) { + 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 - @SuppressWarnings("unchecked") - public <_8Prime, App extends Applicative, TravB extends Traversable<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, AppB extends Applicative<_8Prime, App>, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { + 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. * @@ -240,4 +385,48 @@ public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> flatMap( 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 df5cf50d7..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 @@ -2,6 +2,7 @@ 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; @@ -17,7 +18,7 @@ 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.lens.functions.View.view; +import static com.jnape.palatable.lambda.optics.functions.View.view; import static java.util.Collections.emptyMap; /** @@ -27,13 +28,13 @@ * @see TypeSafeKey * @see com.jnape.palatable.lambda.adt.hlist.HList */ -public final 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; } @@ -45,9 +46,8 @@ private HMap(Map table) { * @param the value type * @return Maybe the value at this key */ - @SuppressWarnings("unchecked") public Maybe get(TypeSafeKey key) { - return maybe((A) table.get(key)).fmap(view(key)); + return maybe(Downcast.downcast(table.get(key))).fmap(view(key)); } /** @@ -59,7 +59,8 @@ public Maybe get(TypeSafeKey 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.")); + return get(key).orElseThrow(() -> new NoSuchElementException("Demanded value for key " + key + + ", but couldn't find one.")); } /** @@ -90,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); } @@ -100,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)); } @@ -114,6 +115,15 @@ 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. *

@@ -122,7 +132,7 @@ public HMap removeAll(HMap hMap) { * * @return a {@link Set} of all the mapped keys */ - public Set keys() { + public Set> keys() { return new HashSet<>(table.keySet()); } @@ -141,12 +151,12 @@ public Collection 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(); } @@ -171,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); } 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 index a7e3eb371..61c9b181a 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java @@ -12,10 +12,13 @@ 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.lens.Lens; +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.lens.lenses.HMapLens.valueAt; +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 @@ -25,36 +28,104 @@ * @param the {@link HList} of values to focus on * @see TypeSafeKey */ -public interface Schema> extends Lens.Simple> { +public interface Schema extends Lens.Simple> { - @SuppressWarnings("unchecked") + /** + * 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) { - return 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))) - ::apply; + 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); + } + }; } - @SuppressWarnings("unchecked") + /** + * 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) { - return valueAt(key) + Lens>, Maybe>> lens = valueAt(key) .mapA(ma -> ma.fmap(HList::singletonHList)) - .>>mapB(maybeSingletonA -> maybeSingletonA.fmap(HCons::head)) - ::apply; + .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, @@ -62,7 +133,21 @@ static Schema> schema(TypeSafeKey aKey, 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, @@ -71,6 +156,23 @@ static Schema> schema(TypeSafeKey aK 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, @@ -80,6 +182,25 @@ static Schema> schema(TypeSafeKey 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, @@ -90,6 +211,27 @@ static Schema> schema(TypeSafe 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, 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 8bae70949..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 @@ -3,8 +3,8 @@ 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.lens.Iso; -import com.jnape.palatable.lambda.lens.LensLike; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Optic; import java.util.Objects; @@ -24,13 +24,17 @@ public interface TypeSafeKey extends Iso.Simple { @Override - default TypeSafeKey discardR(Applicative> appB) { + default TypeSafeKey discardR(Applicative> appB) { Iso.Simple discarded = Iso.Simple.super.discardR(appB); return new TypeSafeKey() { @Override - public

, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( - PAFB pafb) { - return discarded.apply(pafb); + 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 @@ -48,7 +52,7 @@ public boolean equals(Object 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(Iso)}) is overridden. + * {@link TypeSafeKey}, which is why only this (and not {@link Iso#compose(Optic)}) is overridden. *

* 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, @@ -64,9 +68,13 @@ default TypeSafeKey andThen(Iso.Simple f) { Iso.Simple composed = Iso.Simple.super.andThen(f); return new TypeSafeKey() { @Override - public

, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( + default >, + CoF extends Functor>, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply( PAFB pafb) { return (PSFT) pafb; } 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 index 4c3d64da0..0a76df761 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/product/Product2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product2.java @@ -1,9 +1,9 @@ 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; -import java.util.function.BiFunction; /** * The minimal shape of the combination of two potentially distinctly typed values, supporting destructuring via @@ -35,11 +35,11 @@ public interface Product2<_1, _2> extends Map.Entry<_1, _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 fn the function to apply * @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(BiFunction fn) { + default R into(Fn2 fn) { return fn.apply(_1(), _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 index 063456453..18da0491f 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/product/Product3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product3.java @@ -30,7 +30,7 @@ public interface Product3<_1, _2, _3> extends Product2<_1, _2> { * @return the result of applying the destructured product to the function */ default R into(Fn3 fn) { - return Product2.super.into(fn.toBiFunction()).apply(_3()); + return Product2.super.into(fn).apply(_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 index 2703f8229..87a33376d 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/product/Product4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product4.java @@ -1,6 +1,7 @@ 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; /** @@ -31,7 +32,7 @@ public interface Product4<_1, _2, _3, _4> extends Product3<_1, _2, _3> { * @return the result of applying the destructured product to the function */ default R into(Fn4 fn) { - return Product3.super.into(fn).apply(_4()); + return Product3.super.>into(fn).apply(_4()); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Effect.java b/src/main/java/com/jnape/palatable/lambda/functions/Effect.java index 5c36ff861..648c42eab 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Effect.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Effect.java @@ -1,74 +1,116 @@ 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 java.util.function.Function; -import static com.jnape.palatable.lambda.functions.Fn0.fn0; -import static com.jnape.palatable.lambda.functions.IO.io; +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 - * @see Consumer */ @FunctionalInterface -public interface Effect extends Fn1>, Consumer { +public interface Effect extends Fn1> { @Override - default IO apply(A a) { - return io(fn0(() -> accept(a))); + 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 Effect diMapL(Function fn) { - return effect(Fn1.super.diMapL(fn)); + 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 contraMap(Function fn) { - return effect(Fn1.super.contraMap(fn)); + default Effect diMapL(Fn1 fn) { + return effect(Fn1.super.diMapL(fn)); } + /** + * {@inheritDoc} + */ @Override - default Effect compose(Function before) { - return effect(Fn1.super.compose(before)); + default Effect contraMap(Fn1 fn) { + return effect(Fn1.super.contraMap(fn)); } + /** + * {@inheritDoc} + */ @Override default Effect discardR(Applicative> appB) { return effect(Fn1.super.discardR(appB)); } - @Override - default Effect andThen(Consumer after) { - return Consumer.super.andThen(after)::accept; + /** + * 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)); } /** - * Static factory method to aid in inference. + * Create an {@link Effect} from a {@link SideEffect}; * - * @param effect the effect - * @param the effect argument type - * @return the effect + * @param sideEffect the {@link SideEffect} + * @param any desired input type + * @return the {@link Effect} */ - static Effect effect(Consumer effect) { - return effect::accept; + static Effect effect(SideEffect sideEffect) { + return effect(constantly(io(sideEffect))); } /** - * Create an {@link Effect} from a {@link Runnable}; + * Create an {@link Effect} that accepts an input and does nothing; * - * @param runnable the runnable - * @return the effect + * @param any desired input type + * @return the noop {@link Effect} */ - static Effect effect(Runnable runnable) { - return effect(constantly(io(runnable))); + @SuppressWarnings("unused") + static Effect noop() { + return effect(NOOP); } /** @@ -78,7 +120,7 @@ static Effect effect(Runnable runnable) { * @param the effect argument type * @return the effect */ - static Effect effect(Fn1> fn) { - return a -> fn.apply(a).unsafePerformIO(); + 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 index 84eb3e4a8..3352046fe 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn0.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn0.java @@ -5,43 +5,64 @@ import com.jnape.palatable.lambda.monad.Monad; import java.util.concurrent.Callable; -import java.util.function.Function; import java.util.function.Supplier; import static com.jnape.palatable.lambda.adt.Unit.UNIT; -import static com.jnape.palatable.lambda.functions.IO.io; /** * A function taking "no arguments", implemented as an {@link Fn1}<{@link Unit}, A>. * * @param the result type * @see Fn1 - * @see Supplier * @see Callable */ @FunctionalInterface -public interface Fn0 extends Fn1, Supplier, Callable { +public interface Fn0 extends Fn1 { - A apply(); + A checkedApply() throws Throwable; /** - * Invoke this function with {@link Unit}. + * Convenience method for applying this {@link Fn0} without providing an explicit {@link Unit}. * - * @param unit the only allowed input - * @return the result value + * @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 apply(Unit unit) { - return apply(); + default A checkedApply(Unit unit) throws Throwable { + return checkedApply(); } @Override - default Fn0 flatMap(Function>> f) { + default Fn0 flatMap(Fn1>> f) { return Fn1.super.flatMap(f).thunk(UNIT); } @Override - default Fn0 fmap(Function f) { + default Fn0 fmap(Fn1 f) { return Fn1.super.fmap(f).thunk(UNIT); } @@ -51,7 +72,7 @@ default Fn0 pure(B b) { } @Override - default Fn0 zip(Applicative, Fn1> appFn) { + default Fn0 zip(Applicative, Fn1> appFn) { return Fn1.super.zip(appFn).thunk(UNIT); } @@ -71,30 +92,10 @@ default Fn0 discardR(Applicative> appB) { } @Override - default Fn0 diMapR(Function fn) { + default Fn0 diMapR(Fn1 fn) { return Fn1.super.diMapR(fn).thunk(UNIT); } - @Override - default Fn1 compose(Function before) { - return Fn1.super.compose(before)::apply; - } - - @Override - default Fn0 andThen(Function after) { - return Fn1.super.andThen(after).thunk(UNIT); - } - - @Override - default A get() { - return apply(); - } - - @Override - default A call() { - return apply(); - } - /** * Convenience method for converting a {@link Supplier} to an {@link Fn0}. * @@ -102,39 +103,41 @@ default A call() { * @param the output type * @return the {@link Fn0} */ - static Fn0 fn0(Supplier supplier) { + static Fn0 fromSupplier(Supplier supplier) { return supplier::get; } /** - * Static factory method for coercing a lambda to an {@link Fn0}. + * Convenience method for converting a {@link Callable} to an {@link Fn0}. * - * @param fn the lambda to coerce - * @param the output type + * @param callable the callable + * @param the output type * @return the {@link Fn0} */ - static Fn0 fn0(Fn0 fn) { - return fn; + static Fn0 fromCallable(Callable callable) { + return callable::call; } /** - * Static factory method for adapting a {@link Runnable} to an {@link Fn0}<{@link Unit}>. + * Static factory method for coercing a lambda to an {@link Fn0}. * - * @param runnable the {@link Runnable} + * @param fn the lambda to coerce + * @param the output type * @return the {@link Fn0} */ - static Fn0 fn0(Runnable runnable) { - return io(runnable)::unsafePerformIO; + static Fn0 fn0(Fn0 fn) { + return fn; } /** - * Static factory method for adapting a {@link Function} to an {@link Fn0}. + * Static factory method for adapting an {@link Fn1}<Unit, A> to an + * {@link Fn0}<A>. * - * @param fn the {@link Function} + * @param fn the {@link Fn1} * @param the output type * @return the {@link Fn0} */ - static Fn0 fn0(Function fn) { + 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 c9fa0ee26..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,15 +1,26 @@ package com.jnape.palatable.lambda.functions; +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.Strong; +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.BiFunction; import java.util.function.Function; -import static com.jnape.palatable.lambda.functions.Fn2.fn2; +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 @@ -19,15 +30,35 @@ * @param The result type */ @FunctionalInterface -public interface Fn1 extends Monad>, Strong, 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); + } + } + + /** + * Invoke this function with the given argument, potentially throwing any {@link Throwable}. + * + * @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 @@ -47,24 +78,60 @@ default Fn0 thunk(A a) { * @return the widened function */ default Fn2 widen() { - return fn2(constantly(this)); + 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 flatMap(Function>> f) { + default Fn1 local(Fn1 fn) { + return contraMap(fn); + } + + /** + * {@inheritDoc} + */ + @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 */ @Override - default Fn1 fmap(Function f) { - return Monad.super.fmap(f).coerce(); + default Fn1 fmap(Fn1 f) { + return a -> f.apply(apply(a)); } /** @@ -79,8 +146,8 @@ default Fn1 pure(C c) { * {@inheritDoc} */ @Override - default Fn1 zip(Applicative, Fn1> appFn) { - return a -> appFn.>>coerce().apply(a).apply(apply(a)); + default Fn1 zip(Applicative, Fn1> appFn) { + return MonadRec.super.zip(appFn).coerce(); } /** @@ -88,7 +155,23 @@ default Fn1 zip(Applicative, Fn1 Fn1 zip(Fn2 appFn) { - return zip((Fn1>) (Object) 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)); } /** @@ -96,7 +179,7 @@ default Fn1 zip(Fn2 appFn) { */ @Override default Fn1 discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -104,7 +187,7 @@ default Fn1 discardL(Applicative> appB) { */ @Override default Fn1 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -116,8 +199,8 @@ default Fn1 discardR(Applicative> appB) { * @return an {@link Fn1}<Z, B> */ @Override - default Fn1 diMapL(Function fn) { - return (Fn1) Strong.super.diMapL(fn); + default Fn1 diMapL(Fn1 fn) { + return (Fn1) Cartesian.super.diMapL(fn); } /** @@ -129,8 +212,8 @@ default Fn1 diMapL(Function fn) { * @return an {@link Fn1}<A, C> */ @Override - default Fn1 diMapR(Function fn) { - return (Fn1) Strong.super.diMapR(fn); + default Fn1 diMapR(Fn1 fn) { + return (Fn1) Cartesian.super.diMapR(fn); } /** @@ -143,8 +226,8 @@ default Fn1 diMapR(Function fn) { * @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; } /** @@ -154,43 +237,45 @@ default Fn1 diMap(Function lFn, Function Fn1, Tuple2> strengthen() { + default Fn1, Tuple2> cartesian() { return t -> t.fmap(this); } + /** + * {@inheritDoc} + */ + @Override default Fn1> carry() { - return (Fn1>) Strong.super.carry(); + return (Fn1>) Cartesian.super.carry(); } + /** + * Choose between either applying this function or returning back a different result altogether. + * + * @param the potentially different result + * @return teh strengthened {@link Fn1} + */ @Override - default Fn1 contraMap(Function fn) { - return (Fn1) Strong.super.contraMap(fn); + default Fn1, Choice2> cocartesian() { + return a -> a.fmap(this); } /** - * Override of {@link Function#compose(Function)}, returning an instance of {@link Fn1} for compatibility. - * Right-to-left composition. + * Choose between a successful result b or returning back the input, a. * - * @param before the function who's return value is this function's argument - * @param the new argument type - * @return an {@link Fn1}<Z, B> + * @return an {@link Fn1} that chooses between its input (in case of failure) or its output. */ @Override - default Fn1 compose(Function before) { - return z -> apply(before.apply(z)); + default Fn1> choose() { + return a -> Either.trying(() -> apply(a), constantly(a)).match(Choice2::a, Choice2::b); } /** - * Right-to-left composition between different arity functions. Preserves highest arity in the return type, - * specialized to lambda types (in this case, {@link BiFunction} -> {@link Fn2}). - * - * @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> + * {@inheritDoc} */ - default Fn2 compose(BiFunction before) { - return compose(fn2(before)); + @Override + default Fn1 contraMap(Fn1 fn) { + return (Fn1) Cartesian.super.contraMap(fn); } /** @@ -202,45 +287,69 @@ default Fn2 compose(BiFunction Fn2 compose(Fn2 before) { - return fn2(before.fmap(this::compose))::apply; + return curried(before.fmap(this::contraMap))::apply; } /** - * Left-to-right composition between different arity functions. Preserves highest arity in the return type, - * specialized to lambda types (in this case, {@link BiFunction} -> {@link Fn2}). + * 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(BiFunction after) { + default Fn2 andThen(Fn2 after) { return (a, c) -> after.apply(apply(a), c); } + default Fn1 self() { + return this; + } + /** - * Override of {@link Function#andThen(Function)}, returning an instance of {@link Fn1} for compatibility. - * Left-to-right composition. + * Static factory method for avoid explicit casting when using method references as {@link Fn1}s. * - * @param after the function to invoke on this function's return value - * @param the new result type - * @return an {@link Fn1}<A, C> + * @param fn the function to adapt + * @param the input type + * @param the output type + * @return the {@link Fn1} */ - @Override - default Fn1 andThen(Function after) { - return a -> after.apply(apply(a)); + static Fn1 fn1(Fn1 fn) { + return fn::apply; } /** - * 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. + * Static factory method for wrapping a java {@link Function} in an {@link Fn1}. * - * @param function the function to adapt - * @param the input argument type + * @param function the function + * @param the input type * @param the output type * @return the {@link Fn1} */ - static Fn1 fn1(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 dd116a80f..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,11 +1,13 @@ 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 java.util.function.Function; +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; @@ -23,6 +25,8 @@ @FunctionalInterface public interface Fn2 extends Fn1> { + C checkedApply(A a, B b) throws Throwable; + /** * Invoke this function with the given arguments. * @@ -30,26 +34,28 @@ 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 Fn3 widen() { - return fn3(constantly(this)); + default Fn1 checkedApply(A a) throws Throwable { + return b -> checkedApply(a, b); } /** - * Same as normal composition, except that the result is an instance of {@link Fn2} for convenience. - * - * @param before the function who's return value is this function's argument - * @param the new argument type - * @return an {@link Fn2}<Z, B, C> + * {@inheritDoc} */ @Override - default Fn2 compose(Function before) { - return fn2(Fn1.super.compose(before)); + default Fn3 widen() { + return fn3(constantly(this)); } /** @@ -91,34 +97,40 @@ default BiFunction toBiFunction() { return this::apply; } + /** + * {@inheritDoc} + */ @Override default Fn2 discardR(Applicative> appB) { - return fn2(Fn1.super.discardR(appB)); - } - - @Override - default Fn2 diMapL(Function fn) { - return fn2(Fn1.super.diMapL(fn)); + return curried(Fn1.super.discardR(appB)); } + /** + * {@inheritDoc} + */ @Override - default Fn2 contraMap(Function fn) { - return fn2(Fn1.super.contraMap(fn)); + default Fn2 diMapL(Fn1 fn) { + return curried(Fn1.super.diMapL(fn)); } + /** + * {@inheritDoc} + */ @Override - default Fn3 compose(BiFunction before) { - return fn3(Fn1.super.compose(before)); + 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}. Useful for avoid explicit casting when - * using method references as {@link Fn2}s. + * Static factory method for wrapping a {@link BiFunction} in an {@link Fn2}. * * @param biFunction the biFunction to adapt * @param the first input argument type @@ -126,7 +138,7 @@ default Fn3 compose(Fn2 be * @param the output type * @return the {@link Fn2} */ - static Fn2 fn2(BiFunction biFunction) { + static Fn2 fromBiFunction(BiFunction biFunction) { return biFunction::apply; } @@ -139,7 +151,33 @@ static Fn2 fn2(BiFunction * @param the output type * @return the {@link Fn2} */ - static Fn2 fn2(Fn1> curriedFn1) { + 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 9178520dc..881d848ff 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn3.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn3.java @@ -2,9 +2,7 @@ import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functor.Applicative; - -import java.util.function.BiFunction; -import java.util.function.Function; +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; @@ -21,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. * @@ -29,7 +29,21 @@ 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} @@ -89,20 +103,15 @@ default Fn3 discardR(Applicative> appB) { } @Override - default Fn3 diMapL(Function fn) { + default Fn3 diMapL(Fn1 fn) { return fn3(Fn2.super.diMapL(fn)); } @Override - default Fn3 contraMap(Function fn) { + default Fn3 contraMap(Fn1 fn) { return fn3(Fn2.super.contraMap(fn)); } - @Override - default Fn4 compose(BiFunction before) { - return fn4(Fn2.super.compose(before)); - } - @Override default Fn4 compose(Fn2 before) { return fn4(Fn2.super.compose(before)); 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 4f9ea02de..f2e9f7d47 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn4.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn4.java @@ -2,9 +2,7 @@ import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functor.Applicative; - -import java.util.function.BiFunction; -import java.util.function.Function; +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; @@ -22,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. * @@ -31,7 +31,21 @@ 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} @@ -104,20 +118,15 @@ default Fn4 discardR(Applicative> appB) { } @Override - default Fn4 diMapL(Function fn) { + default Fn4 diMapL(Fn1 fn) { return fn4(Fn3.super.diMapL(fn)); } @Override - default Fn4 contraMap(Function fn) { + default Fn4 contraMap(Fn1 fn) { return fn4(Fn3.super.contraMap(fn)); } - @Override - default Fn5 compose(BiFunction before) { - return fn5(Fn3.super.compose(before)); - } - @Override default Fn5 compose(Fn2 before) { return fn5(Fn3.super.compose(before)); diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java index f37f664e0..c89b8dadd 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java @@ -2,9 +2,7 @@ import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functor.Applicative; - -import java.util.function.BiFunction; -import java.util.function.Function; +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; @@ -23,6 +21,8 @@ @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. * @@ -33,7 +33,22 @@ public interface Fn5 extends Fn4> { * @param e the fifth argument * @return the result of the function application */ - F apply(A a, B b, C c, D d, E e); + 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} @@ -120,20 +135,15 @@ default Fn5 discardR(Applicative> appB) { } @Override - default Fn5 diMapL(Function fn) { + default Fn5 diMapL(Fn1 fn) { return fn5(Fn4.super.diMapL(fn)); } @Override - default Fn5 contraMap(Function fn) { + default Fn5 contraMap(Fn1 fn) { return fn5(Fn4.super.contraMap(fn)); } - @Override - default Fn6 compose(BiFunction before) { - return fn6(Fn4.super.compose(before)); - } - @Override default Fn6 compose(Fn2 before) { return fn6(Fn4.super.compose(before)); diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java index 69c6619cb..4e7bbea89 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java @@ -2,9 +2,7 @@ import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functor.Applicative; - -import java.util.function.BiFunction; -import java.util.function.Function; +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; @@ -24,6 +22,8 @@ @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. * @@ -35,7 +35,22 @@ public interface Fn6 extends Fn5> * @param f the sixth argument * @return the result of the function application */ - G apply(A a, B b, C c, D d, E e, F f); + 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} @@ -137,20 +152,15 @@ default Fn6 discardR(Applicative> appB) { } @Override - default Fn6 diMapL(Function fn) { + default Fn6 diMapL(Fn1 fn) { return fn6(Fn5.super.diMapL(fn)); } @Override - default Fn6 contraMap(Function fn) { + default Fn6 contraMap(Fn1 fn) { return fn6(Fn5.super.contraMap(fn)); } - @Override - default Fn7 compose(BiFunction before) { - return fn7(Fn5.super.compose(before)); - } - @Override default Fn7 compose(Fn2 before) { return fn7(Fn5.super.compose(before)); diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java index 90feae545..15f7a3be9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java @@ -2,15 +2,13 @@ import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functor.Applicative; - -import java.util.function.BiFunction; -import java.util.function.Function; +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 six arguments. Defined in terms of {@link Fn6}, so similarly auto-curried. + * 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 @@ -25,6 +23,8 @@ @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. * @@ -37,7 +37,21 @@ public interface Fn7 extends Fn6 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} @@ -155,20 +169,15 @@ default Fn7 discardR(Applicative> appB) } @Override - default Fn7 diMapL(Function fn) { + default Fn7 diMapL(Fn1 fn) { return fn7(Fn6.super.diMapL(fn)); } @Override - default Fn7 contraMap(Function fn) { + default Fn7 contraMap(Fn1 fn) { return fn7(Fn6.super.contraMap(fn)); } - @Override - default Fn8 compose(BiFunction before) { - return fn8(Fn6.super.compose(before)); - } - @Override default Fn8 compose(Fn2 before) { return fn8(Fn6.super.compose(before)); @@ -249,7 +258,7 @@ static Fn7 fn7(Fn4 the first input argument type * @param the second input argument type * @param the third input argument type @@ -267,7 +276,7 @@ static Fn7 fn7(Fn5 the first input argument type * @param the second input argument type * @param the third input argument type diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java index 9d5020687..60f3a4a3c 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java @@ -2,12 +2,10 @@ import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functor.Applicative; - -import java.util.function.BiFunction; -import java.util.function.Function; +import com.jnape.palatable.lambda.internal.Runtime; /** - * A function taking six arguments. Defined in terms of {@link Fn7}, so similarly auto-curried. + * 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 @@ -23,6 +21,8 @@ @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. * @@ -36,7 +36,21 @@ public interface Fn8 extends Fn7 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. @@ -163,21 +177,15 @@ default Fn8 discardR(Applicative> ap } @Override - default Fn8 diMapL(Function fn) { + default Fn8 diMapL(Fn1 fn) { return fn8(Fn7.super.diMapL(fn)); } @Override - default Fn8 contraMap(Function fn) { + default Fn8 contraMap(Fn1 fn) { return fn8(Fn7.super.contraMap(fn)); } - @Override - default Fn8> compose( - BiFunction before) { - return Fn7.super.compose(before); - } - @Override default Fn8> compose(Fn2 before) { return Fn7.super.compose(before); @@ -266,7 +274,7 @@ static Fn8 fn8( /** * Static factory method for wrapping a curried {@link Fn5} in an {@link Fn8}. * - * @param curriedFn5 the curried fn4 to adapt + * @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 @@ -286,7 +294,7 @@ static Fn8 fn8( /** * Static factory method for wrapping a curried {@link Fn6} in an {@link Fn8}. * - * @param curriedFn6 the curried fn4 to adapt + * @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 @@ -306,7 +314,7 @@ static Fn8 fn8( /** * Static factory method for wrapping a curried {@link Fn7} in an {@link Fn8}. * - * @param curriedFn7 the curried fn4 to adapt + * @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 diff --git a/src/main/java/com/jnape/palatable/lambda/functions/IO.java b/src/main/java/com/jnape/palatable/lambda/functions/IO.java deleted file mode 100644 index cb0ce4c07..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/IO.java +++ /dev/null @@ -1,244 +0,0 @@ -package com.jnape.palatable.lambda.functions; - -import com.jnape.palatable.lambda.adt.Try; -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.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.function.Function; -import java.util.function.Supplier; - -import static com.jnape.palatable.lambda.adt.Unit.UNIT; -import static com.jnape.palatable.lambda.functions.specialized.checked.CheckedSupplier.checked; -import static java.util.concurrent.CompletableFuture.supplyAsync; - -/** - * 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 interface IO extends Monad> { - - /** - * Run the effect represented by this {@link IO} instance, blocking the current thread until the effect terminates. - * - * @return the result of the effect - */ - 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) - */ - default CompletableFuture unsafePerformAsyncIO() { - return supplyAsync(this::unsafePerformIO); - } - - /** - * 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() - */ - default CompletableFuture unsafePerformAsyncIO(Executor executor) { - return supplyAsync(this::unsafePerformIO, 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} - */ - default IO exceptionally(Function recoveryFn) { - return new IO() { - @Override - public A unsafePerformIO() { - return Try.trying(IO.this::unsafePerformIO).recover(recoveryFn); - } - - @Override - public CompletableFuture unsafePerformAsyncIO() { - return IO.this.unsafePerformAsyncIO().exceptionally(recoveryFn::apply); - } - - @Override - public CompletableFuture unsafePerformAsyncIO(Executor executor) { - return IO.this.unsafePerformAsyncIO(executor).exceptionally(recoveryFn::apply); - } - }; - } - - /** - * {@inheritDoc} - */ - @Override - default IO flatMap(Function>> f) { - return new IO() { - @Override - public B unsafePerformIO() { - return f.apply(IO.this.unsafePerformIO()).>coerce().unsafePerformIO(); - } - - @Override - public CompletableFuture unsafePerformAsyncIO() { - return IO.this.unsafePerformAsyncIO() - .thenCompose(a -> f.apply(a).>coerce().unsafePerformAsyncIO()); - } - - @Override - public CompletableFuture unsafePerformAsyncIO(Executor executor) { - return IO.this.unsafePerformAsyncIO(executor) - .thenCompose(a -> f.apply(a).>coerce().unsafePerformAsyncIO(executor)); - } - }; - } - - /** - * {@inheritDoc} - */ - @Override - default IO pure(B b) { - return () -> b; - } - - /** - * {@inheritDoc} - */ - @Override - default IO fmap(Function fn) { - return Monad.super.fmap(fn).coerce(); - } - - /** - * {@inheritDoc} - */ - @Override - default IO zip(Applicative, IO> appFn) { - IO ioA = this; - IO> ioF = appFn.coerce(); - return new IO() { - @Override - public B unsafePerformIO() { - return ioF.unsafePerformIO().apply(ioA.unsafePerformIO()); - } - - @Override - public CompletableFuture unsafePerformAsyncIO() { - return ioF.unsafePerformAsyncIO().thenCompose(ioA.unsafePerformAsyncIO()::thenApply); - } - - @Override - public CompletableFuture unsafePerformAsyncIO(Executor executor) { - return ioF.unsafePerformAsyncIO(executor).thenCompose(ioA.unsafePerformAsyncIO(executor)::thenApply); - } - }; - } - - /** - * {@inheritDoc} - */ - @Override - default IO discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); - } - - /** - * {@inheritDoc} - */ - @Override - default IO discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); - } - - /** - * Static factory method for coercing a lambda to an {@link IO}. - * - * @param io the lambda to coerce - * @param the result type - * @return the {@link IO} - */ - static IO io(IO io) { - return io; - } - - /** - * 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} - */ - static IO io(A a) { - return io(() -> a); - } - - /** - * Static factory method for creating an {@link IO} that runs runnable and returns {@link Unit}. - * - * @param runnable the {@link Runnable} - * @return the {@link IO} - */ - static IO io(Runnable runnable) { - return io(() -> { - runnable.run(); - return UNIT; - }); - } - - /** - * Static factory method for creating an {@link IO} from an {@link Fn1}<{@link Unit}, A>. - * - * @param fn1 the {@link Fn1} - * @param the result type - * @return the {@link IO} - */ - static IO io(Fn1 fn1) { - return io(() -> fn1.apply(UNIT)); - } - - /** - * Static factory method for creating an {@link IO} from an externally managed source of - * {@link CompletableFuture completable futures}. - *

- * 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} - */ - static IO externallyManaged(Supplier> supplier) { - return new IO() { - @Override - public A unsafePerformIO() { - return checked(() -> unsafePerformAsyncIO().get()).get(); - } - - @Override - public CompletableFuture unsafePerformAsyncIO() { - return supplier.get(); - } - - @Override - public CompletableFuture unsafePerformAsyncIO(Executor executor) { - return supplier.get(); - } - }; - } -} 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 index 64b9e0a4c..9833e2a32 100644 --- 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 @@ -15,20 +15,20 @@ * @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 static final CatMaybes INSTANCE = new CatMaybes<>(); private CatMaybes() { } @Override - public Iterable apply(Iterable> maybes) { + public Iterable checkedApply(Iterable> maybes) { return flatten(map(m -> m.>fmap(Collections::singletonList) .orElse(Collections::emptyIterator), maybes)); } @SuppressWarnings("unchecked") public static CatMaybes catMaybes() { - return INSTANCE; + return (CatMaybes) INSTANCE; } public static Iterable catMaybes(Iterable> 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 index e97fe056f..3154e07ea 100644 --- 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 @@ -25,13 +25,13 @@ */ public final class Coalesce implements Fn1>, Either, Iterable>> { - private static final Coalesce INSTANCE = new Coalesce(); + private static final Coalesce INSTANCE = new Coalesce<>(); private Coalesce() { } @Override - public Either, Iterable> apply(Iterable> eithers) { + 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, @@ -42,7 +42,7 @@ public Either, Iterable> apply(Iterable> eithers) { @SuppressWarnings("unchecked") public static Coalesce coalesce() { - return INSTANCE; + return (Coalesce) INSTANCE; } public static Either, Iterable> coalesce(Iterable> 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 652b5470d..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.iteration.CyclicIterable; +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) { + 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 index 8b39ac2a4..30b97dec4 100644 --- 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 @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iteration.DistinctIterable; +import com.jnape.palatable.lambda.internal.iteration.DistinctIterable; /** * Return an {@link Iterable} of the distinct values from the given input {@link Iterable}. @@ -9,19 +9,19 @@ * @param the Iterable element type */ public final class Distinct implements Fn1, Iterable> { - private static final Distinct INSTANCE = new Distinct(); + private static final Distinct INSTANCE = new Distinct<>(); private Distinct() { } @Override - public Iterable apply(Iterable as) { + public Iterable checkedApply(Iterable as) { return new DistinctIterable<>(as); } @SuppressWarnings("unchecked") public static Distinct distinct() { - return INSTANCE; + return (Distinct) INSTANCE; } public static Iterable distinct(Iterable 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 index 9906aa803..6ea6e6254 100644 --- 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 @@ -9,22 +9,22 @@ */ public final class Empty implements Predicate> { - private static final Empty INSTANCE = new Empty(); + private static final Empty INSTANCE = new Empty<>(); private Empty() { } @Override - public Boolean apply(Iterable as) { + public Boolean checkedApply(Iterable as) { return !as.iterator().hasNext(); } @SuppressWarnings("unchecked") public static Empty empty() { - return INSTANCE; + return (Empty) INSTANCE; } public static Boolean empty(Iterable as) { - return Empty.empty().test(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 index 4c88bdec8..812452965 100644 --- 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 @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iteration.FlatteningIterator; +import com.jnape.palatable.lambda.internal.iteration.FlatteningIterator; /** * Given a nested {@link Iterable} of {@link Iterable}s, return a lazily flattening {@link Iterable} @@ -10,19 +10,19 @@ * @param the nested Iterable element type */ public final class Flatten implements Fn1>, Iterable> { - private static final Flatten INSTANCE = new Flatten(); + private static final Flatten INSTANCE = new Flatten<>(); private Flatten() { } @Override - public Iterable apply(Iterable> iterables) { + public Iterable checkedApply(Iterable> iterables) { return () -> new FlatteningIterator<>(iterables.iterator()); } @SuppressWarnings("unchecked") public static Flatten flatten() { - return INSTANCE; + return (Flatten) INSTANCE; } public static Iterable flatten(Iterable> 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 index 511b90b4e..1c12a04fa 100644 --- 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 @@ -1,23 +1,27 @@ 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 static final Force INSTANCE = new Force<>(); private Force() { } @Override @SuppressWarnings("StatementWithEmptyBody") - public Iterable apply(Iterable as) { + public Iterable checkedApply(Iterable as) { for (A ignored : as) { } return as; @@ -25,7 +29,7 @@ public Iterable apply(Iterable as) { @SuppressWarnings("unchecked") public static Force force() { - return INSTANCE; + return (Force) INSTANCE; } public static Iterable force(Iterable 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 e8abf632b..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 @@ -16,20 +16,20 @@ */ 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 Maybe apply(Iterable as) { + public Maybe checkedApply(Iterable as) { Iterator iterator = as.iterator(); return iterator.hasNext() ? just(iterator.next()) : nothing(); } @SuppressWarnings("unchecked") public static Head head() { - return INSTANCE; + return (Head) INSTANCE; } public static Maybe head(Iterable 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 index ced05d274..66cea88bf 100644 --- 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 @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iteration.InitIterator; +import com.jnape.palatable.lambda.internal.iteration.InitIterator; /** * Given an {@link Iterable}<A>, produce an @@ -11,19 +11,19 @@ */ public final class Init implements Fn1, Iterable> { - private static final Init INSTANCE = new Init(); + private static final Init INSTANCE = new Init<>(); private Init() { } @Override - public Iterable apply(Iterable as) { + public Iterable checkedApply(Iterable as) { return () -> new InitIterator<>(as); } @SuppressWarnings("unchecked") public static Init init() { - return INSTANCE; + return (Init) INSTANCE; } public static Iterable init(Iterable 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 index 52c0413b6..846762200 100644 --- 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 @@ -19,19 +19,19 @@ */ public final class Inits implements Fn1, Iterable>> { - private static final Inits INSTANCE = new Inits(); + private static final Inits INSTANCE = new Inits<>(); private Inits() { } @Override - public Iterable> apply(Iterable as) { - return scanLeft(Snoc.snoc().flip().toBiFunction(), Collections::emptyIterator, as); + public Iterable> checkedApply(Iterable as) { + return scanLeft(Snoc.snoc().flip(), Collections::emptyIterator, as); } @SuppressWarnings("unchecked") public static Inits inits() { - return INSTANCE; + return (Inits) INSTANCE; } public static Iterable> inits(Iterable 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 4710f7900..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 @@ -13,13 +13,13 @@ */ 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 Maybe apply(Iterable as) { + public Maybe checkedApply(Iterable as) { A last = null; for (A a : as) { last = a; @@ -29,7 +29,7 @@ public Maybe apply(Iterable as) { @SuppressWarnings("unchecked") public static Last last() { - return INSTANCE; + return (Last) INSTANCE; } public static Maybe last(Iterable 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 index 47c4d3ed8..ce2d2e2a3 100644 --- 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 @@ -12,19 +12,19 @@ */ public final class Magnetize implements Fn1, Iterable>> { - private static final Magnetize INSTANCE = new Magnetize(); + private static final Magnetize INSTANCE = new Magnetize<>(); private Magnetize() { } @Override - public Iterable> apply(Iterable as) { - return magnetizeBy(eq().toBiFunction(), as); + public Iterable> checkedApply(Iterable as) { + return magnetizeBy(eq(), as); } @SuppressWarnings("unchecked") public static Magnetize magnetize() { - return INSTANCE; + return (Magnetize) INSTANCE; } public static Iterable> magnetize(Iterable 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 index 5f0bb8691..e148fadcb 100644 --- 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 @@ -1,36 +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; -import java.util.function.Function; - /** * Negate a predicate function. * * @param the input argument type */ -public final class Not implements BiPredicate, A> { - private static final Not INSTANCE = new Not(); +public final class Not implements BiPredicate, A> { + private static final Not INSTANCE = new Not<>(); private Not() { } @Override - public Boolean apply(Function pred, A a) { + public Boolean checkedApply(Fn1 pred, A a) { return !pred.apply(a); } @SuppressWarnings("unchecked") public static Not not() { - return INSTANCE; + return (Not) INSTANCE; } - public static Predicate not(Function pred) { + public static Predicate not(Fn1 pred) { return Not.not().apply(pred); } - public static Boolean not(Function pred, A a) { + 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 index 8a9062942..6fea549c8 100644 --- 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 @@ -14,13 +14,13 @@ * @param the {@link Iterable} element type */ public final class Occurrences implements Fn1, Map> { - private static final Occurrences INSTANCE = new Occurrences(); + private static final Occurrences INSTANCE = new Occurrences<>(); private Occurrences() { } @Override - public Map apply(Iterable as) { + public Map checkedApply(Iterable as) { return foldLeft((occurrences, a) -> { occurrences.put(a, occurrences.getOrDefault(a, 0L) + 1); return occurrences; @@ -29,7 +29,7 @@ public Map apply(Iterable as) { @SuppressWarnings("unchecked") public static Occurrences occurrences() { - return INSTANCE; + return (Occurrences) INSTANCE; } public static Map occurrences(Iterable 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 4286dbace..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.iteration.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 7b50cecbc..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.iteration.ReversingIterable; +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) { + 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 index 96fb7056d..da00cfaaf 100644 --- 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 @@ -19,19 +19,19 @@ */ public final class Sort> implements Fn1, List> { - private static final Sort INSTANCE = new Sort(); + private static final Sort INSTANCE = new Sort<>(); private Sort() { } @Override - public List apply(Iterable as) { + public List checkedApply(Iterable as) { return sortBy(id(), as); } @SuppressWarnings("unchecked") public static > Sort sort() { - return INSTANCE; + return (Sort) INSTANCE; } public static > List sort(Iterable 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 8b954fe07..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 @@ -12,19 +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) { + 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 index cbdc65589..7179d1f31 100644 --- 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 @@ -22,19 +22,19 @@ */ public final class Tails implements Fn1, Iterable>> { - private static final Tails INSTANCE = new Tails(); + private static final Tails INSTANCE = new Tails<>(); private Tails() { } @Override - public Iterable> apply(Iterable as) { + 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 INSTANCE; + return (Tails) INSTANCE; } public static Iterable> tails(Iterable 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 index 7c6c807bf..dc27d9bb3 100644 --- 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 @@ -16,19 +16,19 @@ */ public final class Uncons implements Fn1, Maybe>>> { - private static final Uncons INSTANCE = new Uncons(); + private static final Uncons INSTANCE = new Uncons<>(); private Uncons() { } @Override - public Maybe>> apply(Iterable as) { + public Maybe>> checkedApply(Iterable as) { return head(as).fmap(a -> tuple(a, tail(as))); } @SuppressWarnings("unchecked") public static Uncons uncons() { - return INSTANCE; + return (Uncons) INSTANCE; } public static Maybe>> uncons(Iterable 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 index 08f6fe519..f4f187021 100644 --- 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 @@ -22,19 +22,19 @@ */ public final class Upcast implements Fn1 { - private static final Upcast INSTANCE = new Upcast<>(); + private static final Upcast INSTANCE = new Upcast<>(); private Upcast() { } @Override - public B apply(A a) { + public B checkedApply(A a) { return a; } @SuppressWarnings("unchecked") public static Upcast upcast() { - return INSTANCE; + return (Upcast) INSTANCE; } public static B upcast(A 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 2aeb00eca..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< @SuppressWarnings("unchecked") public static All all() { - return INSTANCE; + return (All) INSTANCE; } - public static Fn1, ? extends 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 index f51050eae..b830a7e9c 100644 --- 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 @@ -3,7 +3,7 @@ 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.IO; +import com.jnape.palatable.lambda.io.IO; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; @@ -13,21 +13,21 @@ * * @param the input and output */ -public final class Alter implements Fn2, A, IO> { +public final class Alter implements Fn2>, A, IO> { - private static final Alter INSTANCE = new Alter(); + private static final Alter INSTANCE = new Alter<>(); private Alter() { } @Override - public IO apply(Effect effect, A a) { + public IO checkedApply(Fn1> effect, A a) { return effect.fmap(io -> io.fmap(constantly(a))).apply(a); } @SuppressWarnings("unchecked") public static Alter alter() { - return INSTANCE; + return (Alter) INSTANCE; } public static Fn1> alter(Effect effect) { 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 6285084f5..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 @@ -1,10 +1,9 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.functions.specialized.Predicate; -import java.util.function.Function; - /** * Eagerly apply a predicate to each element in an Iterable, returning true if any element * satisfies the predicate, and false otherwise. This method short-circuits on the first true @@ -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< @SuppressWarnings("unchecked") public static Any any() { - return INSTANCE; + return (Any) INSTANCE; } - public static Predicate> 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 index 04bce60f5..0d22f7216 100644 --- 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 @@ -4,8 +4,6 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn3; -import java.util.function.Function; - /** * Given two functions f and g, produce a * {@link Fn1}<A, {@link Tuple2}<B, C>> (the dual application of both functions). @@ -14,38 +12,33 @@ * @param the first function return type * @param the second function return type */ -public final class Both implements Fn3, Function, A, Tuple2> { +public final class Both implements + Fn3, Fn1, A, Tuple2> { - private static final Both INSTANCE = new Both(); + private static final Both INSTANCE = new Both<>(); private Both() { } @Override - public Tuple2 apply(Function f, - Function g, - A a) { - return Tuple2.fill(a).biMap(f, g); + 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 INSTANCE; + return (Both) INSTANCE; } - public static Fn1, Fn1>> both( - Function f) { + public static Fn1, Fn1>> both(Fn1 f) { return Both.both().apply(f); } - public static Fn1> both(Function f, - Function g) { + public static Fn1> both(Fn1 f, Fn1 g) { return Both.both(f).apply(g); } - public static Tuple2 both(Function f, - Function g, - A a) { + 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 aa883f86c..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.iteration.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 index 132dec5c7..3a91ade4c 100644 --- 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 @@ -18,19 +18,19 @@ */ public final class CmpEq> implements BiPredicate { - private static final CmpEq INSTANCE = new CmpEq(); + private static final CmpEq INSTANCE = new CmpEq<>(); private CmpEq() { } @Override - public Boolean apply(A x, A y) { + public Boolean checkedApply(A x, A y) { return cmpEqBy(id(), x, y); } @SuppressWarnings("unchecked") public static > CmpEq cmpEq() { - return INSTANCE; + return (CmpEq) INSTANCE; } public static > Predicate cmpEq(A x) { 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 bee418871..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.iteration.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 index 777279597..38735673c 100644 --- 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 @@ -23,13 +23,13 @@ */ public final class Difference implements Fn2, Iterable, Iterable> { - private static final Difference INSTANCE = new Difference(); + private static final Difference INSTANCE = new Difference<>(); private Difference() { } @Override - public Iterable apply(Iterable xs, Iterable ys) { + public Iterable checkedApply(Iterable xs, Iterable ys) { return () -> { if (empty(xs)) return xs.iterator(); @@ -45,7 +45,7 @@ public Iterable apply(Iterable xs, Iterable ys) { @SuppressWarnings("unchecked") public static Difference difference() { - return INSTANCE; + return (Difference) INSTANCE; } public static Fn1, Iterable> difference(Iterable xs) { 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 c286d2da6..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.iteration.DroppingIterable; +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) { + 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 560f35c5c..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.iteration.PredicatedDroppingIterable; - -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) { + 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 fd0c70e7a..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.iteration.FilteringIterable; - -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) { + 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 716a4f3e8..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 @@ -4,8 +4,6 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -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; @@ -18,28 +16,28 @@ * * @param the Iterable element type */ -public final class Find implements Fn2, Iterable, Maybe> { +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 Maybe apply(Function predicate, Iterable 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, Maybe> find(Function predicate) { + public static Fn1, Maybe> find(Fn1 predicate) { return Find.find().apply(predicate); } - public static Maybe 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 index 3c45e24cd..09d1be4ae 100644 --- 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 @@ -8,8 +8,8 @@ import static com.jnape.palatable.lambda.functions.builtin.fn3.GTBy.gtBy; /** - * Given two {@link Comparable} values of type A, return true if the first value is strictly - * greater than the second value; otherwise, return false. + * 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 @@ -17,19 +17,19 @@ */ public final class GT> implements BiPredicate { - private static final GT INSTANCE = new GT(); + private static final GT INSTANCE = new GT<>(); private GT() { } @Override - public Boolean apply(A y, A x) { + public Boolean checkedApply(A y, A x) { return gtBy(id(), y, x); } @SuppressWarnings("unchecked") public static > GT gt() { - return INSTANCE; + return (GT) INSTANCE; } public static > Predicate gt(A y) { 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 index 23069dd03..89413cea6 100644 --- 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 @@ -8,8 +8,8 @@ import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEBy.gteBy; /** - * Given two {@link Comparable} values of type A, return true if the first value is greater - * than or equal to the second value according to {@link Comparable#compareTo(Object)}; otherwise, return false. + * 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 @@ -17,19 +17,19 @@ */ public final class GTE> implements BiPredicate { - private static final GTE INSTANCE = new GTE(); + private static final GTE INSTANCE = new GTE<>(); private GTE() { } @Override - public Boolean apply(A y, A x) { + public Boolean checkedApply(A y, A x) { return gteBy(id(), y, x); } @SuppressWarnings("unchecked") public static > GTE gte() { - return INSTANCE; + return (GTE) INSTANCE; } public static > Predicate gte(A y) { 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 index 1ef163a74..42e61912a 100644 --- 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 @@ -7,7 +7,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; @@ -20,15 +19,15 @@ * @param the Map value type * @see InGroupsOf */ -public final class GroupBy implements Fn2, Iterable, Map>> { +public final class GroupBy implements Fn2, Iterable, Map>> { - private static final GroupBy INSTANCE = new GroupBy(); + private static final GroupBy INSTANCE = new GroupBy<>(); private GroupBy() { } @Override - public Map> apply(Function keyFn, Iterable vs) { + public Map> checkedApply(Fn1 keyFn, Iterable vs) { return foldLeft((m, v) -> { m.computeIfAbsent(keyFn.apply(v), __ -> new ArrayList<>()).add(v); return m; @@ -37,14 +36,14 @@ public Map> apply(Function keyFn, Iterable @SuppressWarnings("unchecked") public static GroupBy groupBy() { - return INSTANCE; + return (GroupBy) INSTANCE; } - public static Fn1, Map>> groupBy(Function keyFn) { + public static Fn1, Map>> groupBy(Fn1 keyFn) { return GroupBy.groupBy().apply(keyFn); } - public static Map> groupBy(Function keyFn, Iterable vs) { + 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 da93304c3..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.iteration.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 68d7bc54d..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 @@ -7,27 +7,27 @@ 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 empty - * Iterable is left untouched. + * 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 index 5fa399bb1..523c08543 100644 --- 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 @@ -4,39 +4,38 @@ import com.jnape.palatable.lambda.functions.Fn2; import java.util.Map; -import java.util.function.BiFunction; /** - * Given a {@link BiFunction}<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. + * 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> { +public final class Into implements Fn2, Map.Entry, C> { - private static final Into INSTANCE = new Into(); + private static final Into INSTANCE = new Into<>(); private Into() { } @Override - public C apply(BiFunction fn, Map.Entry entry) { + public C checkedApply(Fn2 fn, Map.Entry entry) { return fn.apply(entry.getKey(), entry.getValue()); } @SuppressWarnings("unchecked") public static Into into() { - return INSTANCE; + return (Into) INSTANCE; } public static Fn1, C> into( - BiFunction fn) { + Fn2 fn) { return Into.into().apply(fn); } - public static C into(BiFunction 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 index e965ee544..3b574146b 100644 --- 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 @@ -4,34 +4,32 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import java.util.function.Function; - /** - * Given a {@link Function}<A, B> and a {@link SingletonHList}<A>, - * pop the head and apply it to the function, returning the result. + * 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> { +public final class Into1 implements Fn2, SingletonHList, B> { - private static final Into1 INSTANCE = new Into1(); + private static final Into1 INSTANCE = new Into1<>(); @Override - public B apply(Function fn, SingletonHList singletonHList) { + public B checkedApply(Fn1 fn, SingletonHList singletonHList) { return fn.apply(singletonHList.head()); } @SuppressWarnings("unchecked") public static Into1 into1() { - return INSTANCE; + return (Into1) INSTANCE; } - public static Fn1, B> into1(Function fn) { + public static Fn1, B> into1(Fn1 fn) { return Into1.into1().apply(fn); } - public static B into1(Function fn, SingletonHList singletonHList) { + 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 index 9572d3f8d..d58969d57 100644 --- 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 @@ -16,16 +16,16 @@ */ public final class Into3 implements Fn2, Product3, D> { - private static final Into3 INSTANCE = new Into3(); + private static final Into3 INSTANCE = new Into3<>(); @Override - public D apply(Fn3 fn, Product3 product) { - return product.into(fn); + public D checkedApply(Fn3 fn, Product3 product) { + return product.into(fn); } @SuppressWarnings("unchecked") public static Into3 into3() { - return INSTANCE; + return (Into3) INSTANCE; } public static Fn1, D> into3(Fn3 fn) { 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 index 9cd0f37e0..14dba6f90 100644 --- 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 @@ -17,16 +17,17 @@ */ public final class Into4 implements Fn2, Product4, E> { - private static final Into4 INSTANCE = new Into4(); + private static final Into4 INSTANCE = new Into4<>(); @Override - public E apply(Fn4 fn, Product4 product) { + public E checkedApply(Fn4 fn, + Product4 product) { return product.into(fn); } @SuppressWarnings("unchecked") public static Into4 into4() { - return INSTANCE; + return (Into4) INSTANCE; } public static Fn1, E> into4( 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 index a55294f10..8cbdf25f5 100644 --- 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 @@ -18,17 +18,17 @@ */ public final class Into5 implements Fn2, Product5, F> { - private static final Into5 INSTANCE = new Into5(); + private static final Into5 INSTANCE = new Into5<>(); @Override - public F apply(Fn5 fn, - Product5 product) { + public F checkedApply(Fn5 fn, + Product5 product) { return product.into(fn); } @SuppressWarnings("unchecked") public static Into5 into5() { - return INSTANCE; + return (Into5) INSTANCE; } public static Fn1, F> into5( 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 index 9caf25a5b..0c59771f2 100644 --- 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 @@ -20,17 +20,17 @@ */ public final class Into6 implements Fn2, Product6, G> { - private static final Into6 INSTANCE = new Into6(); + private static final Into6 INSTANCE = new Into6<>(); @Override - public G apply(Fn6 fn, - Product6 product) { + public G checkedApply(Fn6 fn, + Product6 product) { return product.into(fn); } @SuppressWarnings("unchecked") public static Into6 into6() { - return INSTANCE; + return (Into6) INSTANCE; } public static Fn1, G> into6( 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 index 3ea4d0ebd..612068814 100644 --- 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 @@ -21,17 +21,18 @@ */ public final class Into7 implements Fn2, Product7, H> { - private static final Into7 INSTANCE = new Into7(); + private static final Into7 INSTANCE = new Into7<>(); @Override - public H apply(Fn7 fn, - Product7 product) { + public H checkedApply( + Fn7 fn, + Product7 product) { return product.into(fn); } @SuppressWarnings("unchecked") public static Into7 into7() { - return INSTANCE; + return (Into7) INSTANCE; } public static Fn1, H> into7( 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 index a04d17660..fdad33a72 100644 --- 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 @@ -22,10 +22,10 @@ */ public final class Into8 implements Fn2, Product8, I> { - private static final Into8 INSTANCE = new Into8(); + private static final Into8 INSTANCE = new Into8<>(); @Override - public I apply( + public I checkedApply( Fn8 fn, Product8 product) { return product.into(fn); @@ -33,7 +33,7 @@ public I apply( @SuppressWarnings("unchecked") public static Into8 into8() { - return INSTANCE; + return (Into8) INSTANCE; } public static Fn1, I> into8( 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 d948bb12f..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.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) { + 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 index d6a1d6d99..359d59b66 100644 --- 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 @@ -8,8 +8,8 @@ import static com.jnape.palatable.lambda.functions.builtin.fn3.LTBy.ltBy; /** - * Given two {@link Comparable} values of type A, return true if the first value is strictly - * less than the second value; otherwise, return false. + * 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 @@ -17,19 +17,19 @@ */ public final class LT> implements BiPredicate { - private static final LT INSTANCE = new LT(); + private static final LT INSTANCE = new LT<>(); private LT() { } @Override - public Boolean apply(A y, A x) { + public Boolean checkedApply(A y, A x) { return ltBy(id(), y, x); } @SuppressWarnings("unchecked") public static > LT lt() { - return INSTANCE; + return (LT) INSTANCE; } public static > Predicate lt(A y) { 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 index 19d2424c4..85f07b61e 100644 --- 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 @@ -8,8 +8,8 @@ import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEBy.lteBy; /** - * Given two {@link Comparable} values of type A, return true if the first value is less than - * or equal to the second value according to {@link Comparable#compareTo(Object)} otherwise, return false. + * 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 @@ -17,19 +17,19 @@ */ public final class LTE> implements BiPredicate { - private static final LTE INSTANCE = new LTE(); + private static final LTE INSTANCE = new LTE<>(); private LTE() { } @Override - public Boolean apply(A y, A x) { + public Boolean checkedApply(A y, A x) { return lteBy(id(), y, x); } @SuppressWarnings("unchecked") public static > LTE lte() { - return INSTANCE; + return (LTE) INSTANCE; } public static > Predicate lte(A y) { 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 index ef94c372a..43697b8a5 100644 --- 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 @@ -4,7 +4,6 @@ import com.jnape.palatable.lambda.functions.Fn2; import java.util.Collections; -import java.util.function.BiFunction; import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; @@ -25,15 +24,16 @@ * * @param the {@link Iterable} element type */ -public final class MagnetizeBy implements Fn2, Iterable, Iterable>> { +public final class MagnetizeBy implements Fn2, Iterable, Iterable>> { - private static final MagnetizeBy INSTANCE = new MagnetizeBy(); + private static final MagnetizeBy INSTANCE = new MagnetizeBy<>(); private MagnetizeBy() { } @Override - public Iterable> apply(BiFunction predicate, Iterable as) { + 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) @@ -45,16 +45,16 @@ public Iterable> apply(BiFunction MagnetizeBy magnetizeBy() { - return INSTANCE; + return (MagnetizeBy) INSTANCE; } public static Fn1, Iterable>> magnetizeBy( - BiFunction predicate) { + Fn2 predicate) { return MagnetizeBy.magnetizeBy().apply(predicate); } public static Iterable> magnetizeBy( - BiFunction predicate, + 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 2b48c2a07..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.iteration.MappingIterable; - -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) { + 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 5a9a17148..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 @@ -6,7 +6,6 @@ import com.jnape.palatable.lambda.functions.Fn2; import java.util.Collections; -import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; @@ -24,16 +23,16 @@ * @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) { + 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))) @@ -42,16 +41,16 @@ public Tuple2, Iterable> apply(Function 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 index 59ee59cda..c793b45b7 100644 --- 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 @@ -1,44 +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 java.util.function.Consumer; -import java.util.function.Function; +import com.jnape.palatable.lambda.io.IO; /** - * Given a {@link Consumer}, "peek" at the value contained inside a {@link Functor} via - * {@link Functor#fmap(Function)}, applying the {@link Consumer} to the contained value, if there is one. + * 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 */ -public final class Peek> implements Fn2, FA, FA> { - private static final Peek INSTANCE = new Peek<>(); +@Deprecated +public final class Peek> implements Fn2>, FA, FA> { + private static final Peek INSTANCE = new Peek<>(); private Peek() { } @Override @SuppressWarnings("unchecked") - public FA apply(Consumer consumer, FA fa) { + public FA checkedApply(Fn1> effect, FA fa) { return (FA) fa.fmap(a -> { - consumer.accept(a); + effect.apply(a).unsafePerformIO(); return a; - }); + }).coerce(); } @SuppressWarnings("unchecked") public static > Peek peek() { - return INSTANCE; + return (Peek) INSTANCE; } - public static > Fn1 peek(Consumer consumer) { - return Peek.peek().apply(consumer); + public static > Fn1 peek(Fn1> effect) { + return Peek.peek().apply(effect); } - public static > FA peek(Consumer consumer, FA fa) { - return Peek.peek(consumer).apply(fa); + 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 index d544ac7d7..bcc98eb55 100644 --- 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 @@ -1,61 +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 java.util.function.Consumer; -import java.util.function.Function; +import com.jnape.palatable.lambda.io.IO; /** - * Given two {@link Consumer}s, "peek" at the values contained inside a {@link Bifunctor} via - * {@link Bifunctor#biMap(Function, Function)}, applying the {@link Consumer}s to the contained values, - * if there are any. + * 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 */ -public final class Peek2> implements Fn3, Consumer, FAB, FAB> { - private static final Peek2 INSTANCE = new Peek2<>(); +@Deprecated +public final class Peek2> implements + Fn3>, Fn1>, FAB, FAB> { + private static final Peek2 INSTANCE = new Peek2<>(); private Peek2() { } @Override @SuppressWarnings("unchecked") - public FAB apply(Consumer aConsumer, Consumer bConsumer, FAB fab) { + public FAB checkedApply(Fn1> effectA, Fn1> effectB, FAB fab) { return (FAB) fab.biMap(a -> { - aConsumer.accept(a); + effectA.apply(a).unsafePerformIO(); return a; }, b -> { - bConsumer.accept(b); + effectB.apply(b).unsafePerformIO(); return b; }); } @SuppressWarnings("unchecked") public static > Peek2 peek2() { - return INSTANCE; + return (Peek2) INSTANCE; } - public static > Fn2, FAB, FAB> peek2( - Consumer aConsumer) { - return Peek2.peek2().apply(aConsumer); + public static > + Fn2>, FAB, FAB> peek2(Fn1> effectA) { + return Peek2.peek2().apply(effectA); } public static > Fn1 peek2( - Consumer aConsumer, - Consumer bConsumer) { - return Peek2.peek2(aConsumer).apply(bConsumer); + Fn1> effectA, + Fn1> effectB) { + return Peek2.peek2(effectA).apply(effectB); } public static > FAB peek2( - Consumer aConsumer, - Consumer bConsumer, + Fn1> effectA, + Fn1> effectB, FAB fab) { - return Peek2.peek2(aConsumer, bConsumer).apply(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 4c7f26f2a..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,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iteration.PrependingIterator; +import com.jnape.palatable.lambda.internal.iteration.PrependingIterator; /** * Lazily prepend each value with of the Iterable with the supplied separator value. An empty @@ -13,19 +13,19 @@ */ 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) { + 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 c25b988a4..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 @@ -6,14 +6,13 @@ import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; import java.util.Iterator; -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 {@link Iterable}<A> and a {@link BiFunction}<A, A, A>, iteratively + * 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. @@ -25,29 +24,29 @@ * @see ReduceRight * @see FoldLeft */ -public final class ReduceLeft implements Fn2, Iterable, Maybe> { +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 Maybe apply(BiFunction fn, Iterable as) { + public Maybe checkedApply(Fn2 fn, Iterable as) { Iterator iterator = as.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, Maybe> reduceLeft(BiFunction fn) { + public static Fn1, Maybe> reduceLeft(Fn2 fn) { return ReduceLeft.reduceLeft().apply(fn); } - public static Maybe 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 432ac0f56..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 @@ -5,13 +5,11 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight; -import java.util.function.BiFunction; - import static com.jnape.palatable.lambda.functions.builtin.fn1.Reverse.reverse; import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft.reduceLeft; /** - * Given an {@link Iterable}<A> and a {@link BiFunction}<A, A, A>, iteratively + * 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. @@ -23,28 +21,28 @@ * @see ReduceLeft * @see FoldRight */ -public final class ReduceRight implements Fn2, Iterable, Maybe> { +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 Maybe 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, Maybe> reduceRight(BiFunction fn) { + public static Fn1, Maybe> reduceRight(Fn2 fn) { return ReduceRight.reduceRight().apply(fn); } - public static Maybe 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 index d810f4938..98d436461 100644 --- 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 @@ -13,19 +13,19 @@ */ public final class Replicate implements Fn2> { - private static final Replicate INSTANCE = new Replicate(); + private static final Replicate INSTANCE = new Replicate<>(); private Replicate() { } @Override - public Iterable apply(Integer n, A a) { + public Iterable checkedApply(Integer n, A a) { return take(n, repeat(a)); } @SuppressWarnings("unchecked") public static Replicate replicate() { - return INSTANCE; + return (Replicate) INSTANCE; } public static Fn1> replicate(Integer n) { 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 index 36b394970..bed18e1aa 100644 --- 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 @@ -8,7 +8,6 @@ import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Map; -import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; @@ -25,77 +24,72 @@ * @param the Traversable element type * @param the Applicative unification parameter * @param the Traversable unification parameter - * @param the Applicative instance wrapped in the input Traversable * @param the Traversable instance wrapped in the output Applicative * @param the concrete parametrized output Applicative type - * @param the concrete parametrized input Traversable type */ -public final class Sequence, +public final class Sequence, Trav extends Traversable, TravA extends Traversable, - AppTrav extends Applicative, - TravApp extends Traversable> implements Fn2, AppTrav> { + AppTrav extends Applicative> implements + Fn2, Trav>, Fn1, AppTrav> { - private static final Sequence INSTANCE = new Sequence(); + private static final Sequence INSTANCE = new Sequence<>(); private Sequence() { } @Override - public AppTrav apply(TravApp traversable, Function pure) { + public AppTrav checkedApply(Traversable, Trav> traversable, + Fn1 pure) { return traversable.traverse(id(), pure); } @SuppressWarnings("unchecked") - public static , + public static , Trav extends Traversable, TravA extends Traversable, - AppTrav extends Applicative, - TravApp extends Traversable> Sequence sequence() { - return INSTANCE; + AppTrav extends Applicative> Sequence sequence() { + return (Sequence) INSTANCE; } - public static , + public static , Trav extends Traversable, TravA extends Traversable, - AppTrav extends Applicative, - TravApp extends Traversable> Fn1, AppTrav> sequence( - TravApp traversable) { - return Sequence.sequence().apply(traversable); + AppTrav extends Applicative> Fn1, AppTrav> sequence( + Traversable, Trav> traversable) { + return Sequence.sequence().apply(traversable); } - public static , Trav extends Traversable, TravA extends Traversable, - AppA extends Applicative, - AppTrav extends Applicative, - TravApp extends Traversable> AppTrav sequence(TravApp traversable, - Function pure) { - return Sequence.sequence(traversable).apply(pure); + 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>, IterableApp extends Iterable> - Fn1, ? extends AppIterable>, AppIterable> sequence(IterableApp iterableApp) { - return pure -> (AppIterable) Sequence., AppA, Applicative, App>, LambdaIterable>sequence( + 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>, IterableApp extends Iterable> - AppIterable sequence(IterableApp iterableApp, Function, ? extends AppIterable> pure) { - return Sequence.sequence(iterableApp).apply(pure); + 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>, MapApp extends Map> - Fn1, ? extends AppMap>, AppMap> sequence(MapApp mapApp) { - return pure -> (AppMap) Sequence., LambdaMap, AppB, Applicative, App>, LambdaMap>sequence( + 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>, MapApp extends Map> - AppMap sequence(MapApp mapApp, Function, ? extends AppMap> pure) { - return Sequence.sequence(mapApp).apply(pure); + 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 index 337d59788..1a65422d6 100644 --- 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 @@ -21,13 +21,13 @@ */ public final class Slide implements Fn2, Iterable>> { - private static final Slide INSTANCE = new Slide<>(); + private static final Slide INSTANCE = new Slide<>(); private Slide() { } @Override - public Iterable> apply(Integer k, Iterable as) { + public Iterable> checkedApply(Integer k, Iterable as) { if (k == 0) throw new IllegalArgumentException("k must be greater than 0"); @@ -36,7 +36,7 @@ public Iterable> apply(Integer k, Iterable as) { @SuppressWarnings("unchecked") public static Slide slide() { - return INSTANCE; + return (Slide) INSTANCE; } public static Fn1, Iterable>> slide(Integer k) { 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 index 6437c0c57..bfd8007bc 100644 --- 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 @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iteration.SnocIterable; +import com.jnape.palatable.lambda.internal.iteration.SnocIterable; /** * Opposite of {@link Cons}: lazily append an element to the end of the given {@link Iterable}. @@ -15,19 +15,19 @@ */ public final class Snoc implements Fn2, Iterable> { - private static final Snoc INSTANCE = new Snoc(); + private static final Snoc INSTANCE = new Snoc<>(); private Snoc() { } @Override - public Iterable apply(A a, Iterable as) { + public Iterable checkedApply(A a, Iterable as) { return new SnocIterable<>(a, as); } @SuppressWarnings("unchecked") public static Snoc snoc() { - return INSTANCE; + return (Snoc) INSTANCE; } public static Fn1, Iterable> snoc(A a) { 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 index b92628141..13ea9d0df 100644 --- 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 @@ -5,7 +5,6 @@ import com.jnape.palatable.lambda.functions.builtin.fn1.Sort; import java.util.List; -import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn2.SortWith.sortWith; import static java.util.Comparator.comparing; @@ -20,29 +19,28 @@ * @see Sort * @see SortWith */ -public final class SortBy> implements Fn2, Iterable, List> { +public final class SortBy> implements Fn2, Iterable, List> { - private static final SortBy INSTANCE = new SortBy(); + private static final SortBy INSTANCE = new SortBy<>(); private SortBy() { } @Override - public List apply(Function fn, Iterable as) { - return sortWith(comparing(fn), as); + public List checkedApply(Fn1 fn, Iterable as) { + return sortWith(comparing(fn.toFunction()), as); } @SuppressWarnings("unchecked") public static > SortBy sortBy() { - return INSTANCE; + return (SortBy) INSTANCE; } - public static > Fn1, List> sortBy( - Function fn) { + public static > Fn1, List> sortBy(Fn1 fn) { return SortBy.sortBy().apply(fn); } - public static > List sortBy(Function fn, Iterable as) { + 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 index 813fb2e53..2c93239ee 100644 --- 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 @@ -21,13 +21,13 @@ */ public final class SortWith implements Fn2, Iterable, List> { - private static final SortWith INSTANCE = new SortWith(); + private static final SortWith INSTANCE = new SortWith<>(); private SortWith() { } @Override - public List apply(Comparator comparator, Iterable as) { + public List checkedApply(Comparator comparator, Iterable as) { List result = toCollection(ArrayList::new, as); result.sort(comparator); return result; @@ -35,7 +35,7 @@ public List apply(Comparator comparator, Iterable as) { @SuppressWarnings("unchecked") public static SortWith sortWith() { - return INSTANCE; + return (SortWith) INSTANCE; } public static Fn1, List> sortWith(Comparator comparator) { 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 index aea792d08..966b6518d 100644 --- 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 @@ -4,8 +4,6 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import java.util.function.Function; - import static com.jnape.palatable.lambda.functions.builtin.fn2.DropWhile.dropWhile; import static com.jnape.palatable.lambda.functions.builtin.fn2.TakeWhile.takeWhile; @@ -15,29 +13,30 @@ * * @param the {@link Iterable} element type */ -public final class Span implements Fn2, Iterable, Tuple2, Iterable>> { +public final class Span implements + Fn2, Iterable, Tuple2, Iterable>> { - private static final Span INSTANCE = new Span(); + private static final Span INSTANCE = new Span<>(); private Span() { } @Override - public Tuple2, Iterable> apply(Function predicate, Iterable as) { + public Tuple2, Iterable> checkedApply(Fn1 predicate, Iterable as) { return Tuple2.fill(as).biMap(takeWhile(predicate), dropWhile(predicate)); } @SuppressWarnings("unchecked") public static Span span() { - return INSTANCE; + return (Span) INSTANCE; } public static Fn1, Tuple2, Iterable>> span( - Function predicate) { + Fn1 predicate) { return Span.span().apply(predicate); } - public static Tuple2, Iterable> span(Function 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 ed10f75bc..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.iteration.TakingIterable; +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) { + 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 8285d953c..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.iteration.PredicatedTakingIterable; - -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) { + 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 index e12ae85b1..cecaa1543 100644 --- 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 @@ -16,14 +16,14 @@ */ public final class ToArray implements Fn2, Iterable, A[]> { - private static final ToArray INSTANCE = new ToArray<>(); + private static final ToArray INSTANCE = new ToArray<>(); private ToArray() { } @Override @SuppressWarnings("unchecked") - public A[] apply(Class arrayType, Iterable as) { + 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); @@ -37,7 +37,7 @@ public A[] apply(Class arrayType, Iterable as) { @SuppressWarnings("unchecked") public static ToArray toArray() { - return INSTANCE; + return (ToArray) INSTANCE; } public static Fn1, A[]> toArray(Class arrayType) { 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 1522720aa..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,48 +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) { + public M checkedApply(Fn0 mFn0, Iterable> entries) { return foldLeft((m, kv) -> { m.put(kv.getKey(), kv.getValue()); return m; - }, mSupplier.get(), entries); + }, 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, + public static > M toMap(Fn0 mFn0, Iterable> entries) { - return toMap(mSupplier).apply(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 9a2cd1b15..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 @@ -4,9 +4,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.iteration.UnfoldingIterator; - -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 {@link Maybe}<{@link @@ -29,28 +27,28 @@ * @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 index 70dac2bce..ccb98929f 100644 --- 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 @@ -14,19 +14,19 @@ */ public final class Between> implements Fn3 { - private static final Between INSTANCE = new Between<>(); + private static final Between INSTANCE = new Between<>(); private Between() { } @Override - public Boolean apply(A lower, A upper, A a) { + public Boolean checkedApply(A lower, A upper, A a) { return clamp(lower, upper, a).equals(a); } @SuppressWarnings("unchecked") public static > Between between() { - return INSTANCE; + return (Between) INSTANCE; } public static > BiPredicate between(A lower) { 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 index 62d0c21de..3343afae4 100644 --- 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 @@ -17,19 +17,19 @@ */ public final class Clamp> implements Fn3 { - private static final Clamp INSTANCE = new Clamp<>(); + private static final Clamp INSTANCE = new Clamp<>(); private Clamp() { } @Override - public A apply(A lower, A upper, A a) { + 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 INSTANCE; + return (Clamp) INSTANCE; } public static > Fn2 clamp(A lower) { 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 index 156c51413..a6e04baa9 100644 --- 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 @@ -1,11 +1,14 @@ 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 java.util.function.Function; +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 @@ -15,45 +18,46 @@ * @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> { +public final class CmpEqBy> implements Fn3, A, A, Boolean> { - private static final CmpEqBy INSTANCE = new CmpEqBy(); + private static final CmpEqBy INSTANCE = new CmpEqBy<>(); private CmpEqBy() { } @Override - public Boolean apply(Function compareFn, A x, A y) { - return compareFn.apply(x).compareTo(compareFn.apply(y)) == 0; + public Boolean checkedApply(Fn1 compareFn, A x, A y) { + return cmpEqWith(comparing(compareFn.toFunction()), x, y); } @Override - public BiPredicate apply(Function compareFn) { + public BiPredicate apply(Fn1 compareFn) { return Fn3.super.apply(compareFn)::apply; } @Override - public Predicate apply(Function compareFn, A x) { - return Fn3.super.apply(compareFn, x)::apply; + public Predicate apply(Fn1 compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); } @SuppressWarnings("unchecked") public static > CmpEqBy cmpEqBy() { - return INSTANCE; + return (CmpEqBy) INSTANCE; } - public static > BiPredicate cmpEqBy(Function compareFn) { + public static > BiPredicate cmpEqBy(Fn1 compareFn) { return CmpEqBy.cmpEqBy().apply(compareFn); } - public static > Predicate cmpEqBy(Function compareFn, A x) { + public static > Predicate cmpEqBy(Fn1 compareFn, A x) { return CmpEqBy.cmpEqBy(compareFn).apply(x); } - public static > Boolean cmpEqBy(Function compareFn, A x, A y) { + 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 index 4428784e1..caddec12e 100644 --- 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 @@ -1,58 +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 java.util.function.Function; +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 first value is strictly greater than the second value in + * 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> { +public final class GTBy> implements Fn3, A, A, Boolean> { - private static final GTBy INSTANCE = new GTBy(); + private static final GTBy INSTANCE = new GTBy<>(); private GTBy() { } @Override - public Boolean apply(Function compareFn, A y, A x) { - return compareFn.apply(x).compareTo(compareFn.apply(y)) > 0; + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return gtWith(comparing(compareFn.toFunction()), y, x); } @Override - public BiPredicate apply(Function compareFn) { + public BiPredicate apply(Fn1 compareFn) { return Fn3.super.apply(compareFn)::apply; } @Override - public Predicate apply(Function compareFn, A x) { - return Fn3.super.apply(compareFn, x)::apply; + public Predicate apply(Fn1 compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); } @SuppressWarnings("unchecked") public static > GTBy gtBy() { - return INSTANCE; + return (GTBy) INSTANCE; } - public static > BiPredicate gtBy(Function fn) { + public static > BiPredicate gtBy(Fn1 fn) { return GTBy.gtBy().apply(fn); } - public static > Predicate gtBy(Function fn, A y) { + public static > Predicate gtBy(Fn1 fn, A y) { return GTBy.gtBy(fn).apply(y); } - public static > Boolean gtBy(Function fn, A y, A x) { + 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 index 2c5d8ae26..7a1b4acad 100644 --- 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 @@ -1,61 +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 java.util.function.Function; - -import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; +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 first value is greater than or equal to the second value in + * 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> { +public final class GTEBy> implements Fn3, A, A, Boolean> { - private static final GTEBy INSTANCE = new GTEBy(); + private static final GTEBy INSTANCE = new GTEBy<>(); private GTEBy() { } @Override - public Boolean apply(Function compareFn, A y, A x) { - return GTBy.gtBy(compareFn).or(cmpEqBy(compareFn)).apply(y, x); + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return gteWith(comparing(compareFn.toFunction()), y, x); } @Override - public BiPredicate apply(Function compareFn) { + public BiPredicate apply(Fn1 compareFn) { return Fn3.super.apply(compareFn)::apply; } @Override - public Predicate apply(Function compareFn, A y) { - return Fn3.super.apply(compareFn, y)::apply; + public Predicate apply(Fn1 compareFn, A y) { + return predicate(Fn3.super.apply(compareFn, y)); } @SuppressWarnings("unchecked") public static > GTEBy gteBy() { - return INSTANCE; + return (GTEBy) INSTANCE; } - public static > BiPredicate gteBy(Function fn) { + public static > BiPredicate gteBy(Fn1 fn) { return GTEBy.gteBy().apply(fn); } - public static > Predicate gteBy(Function fn, A y) { + public static > Predicate gteBy(Fn1 fn, A y) { return GTEBy.gteBy(fn).apply(y); } - public static > Boolean gteBy(Function fn, A y, A x) { + 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 index 220a600e8..05e163844 100644 --- 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 @@ -1,15 +1,18 @@ 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 java.util.function.Function; +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 first value is strictly less than the second value in terms + * 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 @@ -17,42 +20,42 @@ * @see LT * @see GTBy */ -public final class LTBy> implements Fn3, A, A, Boolean> { +public final class LTBy> implements Fn3, A, A, Boolean> { - private static final LTBy INSTANCE = new LTBy(); + private static final LTBy INSTANCE = new LTBy<>(); private LTBy() { } @Override - public Boolean apply(Function compareFn, A y, A x) { - return compareFn.apply(x).compareTo(compareFn.apply(y)) < 0; + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return ltWith(comparing(compareFn.toFunction()), y, x); } @Override - public BiPredicate apply(Function compareFn) { + public BiPredicate apply(Fn1 compareFn) { return Fn3.super.apply(compareFn)::apply; } @Override - public Predicate apply(Function compareFn, A y) { - return Fn3.super.apply(compareFn, y)::apply; + public Predicate apply(Fn1 compareFn, A y) { + return predicate(Fn3.super.apply(compareFn, y)); } @SuppressWarnings("unchecked") public static > LTBy ltBy() { - return INSTANCE; + return (LTBy) INSTANCE; } - public static > BiPredicate ltBy(Function fn) { + public static > BiPredicate ltBy(Fn1 fn) { return LTBy.ltBy().apply(fn); } - public static > Predicate ltBy(Function fn, A y) { + public static > Predicate ltBy(Fn1 fn, A y) { return LTBy.ltBy(fn).apply(y); } - public static > Boolean ltBy(Function fn, A y, A x) { + 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 index a83fdc9c5..4c377d583 100644 --- 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 @@ -1,17 +1,17 @@ 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 java.util.function.Function; - -import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; +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 first value is less than or equal to the second value in + * 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. * @@ -20,42 +20,42 @@ * @see LTE * @see GTEBy */ -public final class LTEBy> implements Fn3, A, A, Boolean> { +public final class LTEBy> implements Fn3, A, A, Boolean> { - private static final LTEBy INSTANCE = new LTEBy(); + private static final LTEBy INSTANCE = new LTEBy<>(); private LTEBy() { } @Override - public Boolean apply(Function compareFn, A y, A x) { - return LTBy.ltBy(compareFn).or(cmpEqBy(compareFn)).apply(y, x); + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return lteWith(comparing(compareFn.toFunction()), y, x); } @Override - public BiPredicate apply(Function compareFn) { + public BiPredicate apply(Fn1 compareFn) { return Fn3.super.apply(compareFn)::apply; } @Override - public Predicate apply(Function compareFn, A y) { + public Predicate apply(Fn1 compareFn, A y) { return Fn3.super.apply(compareFn, y)::apply; } @SuppressWarnings("unchecked") public static > LTEBy lteBy() { - return INSTANCE; + return (LTEBy) INSTANCE; } - public static > BiPredicate lteBy(Function fn) { + public static > BiPredicate lteBy(Fn1 fn) { return LTEBy.lteBy().apply(fn); } - public static > Predicate lteBy(Function fn, A y) { + public static > Predicate lteBy(Fn1 fn, A y) { return LTEBy.lteBy(fn).apply(y); } - public static > Boolean lteBy(Function fn, A y, A x) { + 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 index 7ba773691..0badec8f5 100644 --- 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 @@ -5,56 +5,50 @@ import com.jnape.palatable.lambda.functions.Fn3; import com.jnape.palatable.lambda.functor.Applicative; -import java.util.function.BiFunction; - /** - * Lift into and apply a {@link BiFunction} to two {@link Applicative} values, returning the result inside the same + * 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 unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type + * @param the applicative witness * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA2, - AppB extends Applicative, - AppC extends Applicative> implements Fn3, AppA, AppB, AppC> { +public final class LiftA2, AppC extends Applicative> implements + Fn3, Applicative, Applicative, AppC> { - private static final LiftA2 INSTANCE = new LiftA2(); + private static final LiftA2 INSTANCE = new LiftA2<>(); private LiftA2() { } @Override - public AppC apply(BiFunction fn, AppA appA, AppB appB) { - return appB.zip(appA.fmap(Fn2.fn2(fn))).coerce(); + 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 , AppB extends Applicative, AppC extends Applicative> LiftA2 liftA2() { - return INSTANCE; + public static , AppC extends Applicative> + LiftA2 liftA2() { + return (LiftA2) INSTANCE; } - public static , AppB extends Applicative, AppC extends Applicative> Fn2 liftA2( - BiFunction fn) { - return LiftA2.liftA2().apply(fn); + public static , AppC extends Applicative> + Fn2, Applicative, AppC> liftA2(Fn2 fn) { + return LiftA2.liftA2().apply(fn); } - public static , AppB extends Applicative, AppC extends Applicative> Fn1 liftA2( - BiFunction fn, - AppA appA) { - return LiftA2.liftA2(fn).apply(appA); + public static , AppC extends Applicative> + Fn1, AppC> liftA2(Fn2 fn, Applicative appA) { + return LiftA2.liftA2(fn).apply(appA); } - public static , AppB extends Applicative, AppC extends Applicative> AppC liftA2( - BiFunction fn, - AppA appA, - AppB appB) { - return LiftA2.liftA2(fn, appA).apply(appB); + 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 9cc780a15..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.iteration.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 index cdf33a8d7..c23277e63 100644 --- 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 @@ -4,8 +4,6 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; -import java.util.function.Function; - import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; @@ -20,15 +18,15 @@ * * @param the input and output type */ -public final class Times implements Fn3, A, A> { +public final class Times implements Fn3, A, A> { - private static final Times INSTANCE = new Times(); + private static final Times INSTANCE = new Times<>(); private Times() { } @Override - public A apply(Integer n, Function fn, A a) { + public A checkedApply(Integer n, Fn1 fn, A a) { if (n < 0) throw new IllegalStateException("n must not be less than 0"); @@ -37,18 +35,18 @@ public A apply(Integer n, Function fn, A a) { @SuppressWarnings("unchecked") public static Times times() { - return INSTANCE; + return (Times) INSTANCE; } - public static Fn2, A, A> times(Integer n) { + public static Fn2, A, A> times(Integer n) { return Times.times().apply(n); } - public static Fn1 times(Integer n, Function fn) { + public static Fn1 times(Integer n, Fn1 fn) { return Times.times(n).apply(fn); } - public static A times(Integer n, Function fn, A a) { + 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 75266b884..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.iteration.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 index d67440595..70336b79a 100644 --- 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 @@ -5,46 +5,43 @@ import com.jnape.palatable.lambda.functions.Fn3; import com.jnape.palatable.lambda.functions.Fn4; -import java.util.function.Function; +public final class IfThenElse implements + Fn4, Fn1, Fn1, A, B> { -public final class IfThenElse implements Fn4, Function, Function, A, B> { - - private static final IfThenElse INSTANCE = new IfThenElse(); + private static final IfThenElse INSTANCE = new IfThenElse<>(); private IfThenElse() { } @Override - public B apply(Function predicate, Function thenCase, - Function elseCase, A a) { + 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 INSTANCE; + return (IfThenElse) INSTANCE; } - public static Fn3, Function, A, B> ifThenElse( - Function predicate) { + public static Fn3, Fn1, A, B> ifThenElse( + Fn1 predicate) { return IfThenElse.ifThenElse().apply(predicate); } - public static Fn2, A, B> ifThenElse( - Function predicate, Function thenCase) { + public static Fn2, A, B> ifThenElse(Fn1 predicate, + Fn1 thenCase) { return IfThenElse.ifThenElse(predicate).apply(thenCase); } - public static Fn1 ifThenElse( - Function predicate, Function thenCase, - Function elseCase) { + public static Fn1 ifThenElse(Fn1 predicate, + Fn1 thenCase, + Fn1 elseCase) { return IfThenElse.ifThenElse(predicate, thenCase).apply(elseCase); } - public static B ifThenElse( - Function predicate, Function thenCase, - Function elseCase, - A a) { + 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 index a34a1aaae..9000ad81a 100644 --- 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 @@ -14,74 +14,49 @@ * @param the function's second argument type * @param the function's third argument type * @param the function's return type - * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type - * @param the inferred third applicative argument type + * @param the applicative witness * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA3, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> implements Fn4, AppA, AppB, AppC, AppD> { +public final class LiftA3, AppD extends Applicative> implements + Fn4, Applicative, Applicative, Applicative, AppD> { - private static final LiftA3 INSTANCE = new LiftA3(); + private static final LiftA3 INSTANCE = new LiftA3<>(); private LiftA3() { } @Override - public AppD apply(Fn3 fn, AppA appA, AppB appB, AppC appC) { - return appC.zip(appB.zip(appA.fmap(fn))).coerce(); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> LiftA3 liftA3() { - return INSTANCE; + public static , AppD extends Applicative> + LiftA3 liftA3() { + return (LiftA3) INSTANCE; } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> Fn3 liftA3(Fn3 fn) { - return LiftA3.liftA3().apply(fn); + public static , AppD extends Applicative> + Fn3, Applicative, Applicative, AppD> liftA3(Fn3 fn) { + return LiftA3.liftA3().apply(fn); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> Fn2 liftA3(Fn3 fn, AppA appA) { - return LiftA3.liftA3(fn).apply(appA); + public static , AppD extends Applicative> + Fn2, Applicative, AppD> liftA3(Fn3 fn, Applicative appA) { + return LiftA3.liftA3(fn).apply(appA); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> Fn1 liftA3(Fn3 fn, AppA appA, AppB appB) { - return LiftA3.liftA3(fn, appA).apply(appB); + public static , AppD extends Applicative> + Fn1, AppD> liftA3(Fn3 fn, Applicative appA, Applicative appB) { + return LiftA3.liftA3(fn, appA).apply(appB); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> AppD liftA3(Fn3 fn, AppA appA, AppB appB, - AppC appC) { - return LiftA3.liftA3(fn, appA, appB).apply(appC); + 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 index 4c7ad843e..53d9b7189 100644 --- 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 @@ -1,35 +1,38 @@ 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.iteration.IterationInterruptedException; -import com.jnape.palatable.lambda.iteration.RateLimitingIterable; +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 java.util.function.Supplier; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static java.util.Collections.singleton; /** - * Given a {@link Supplier} 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 Supplier} to advance time. + * 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);
+ * 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> instantSupplier = Clock.systemUTC()::instant;
- * Fn1<Iterable<Integer>, Iterable<Integer>> tenPerSecond = rateLimit(instantSupplier, 10L, Duration.ofSeconds(1));
- * Fn1<Iterable<Integer>, Iterable<Integer>> oneHundredEveryTwoMinutes = rateLimit(instantSupplier, 100L, Duration.ofMinutes(2));
+ * 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);
  * 
@@ -45,41 +48,39 @@ * * @param
the {@link Iterable} element type */ -public final class RateLimit implements Fn4, Long, Duration, Iterable, Iterable> { +public final class RateLimit implements Fn4, Long, Duration, Iterable, Iterable> { - private static final RateLimit INSTANCE = new RateLimit(); + private static final RateLimit INSTANCE = new RateLimit<>(); private RateLimit() { } @Override - public Iterable apply(Supplier instantSupplier, Long limit, Duration duration, Iterable as) { + 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, instantSupplier))); + return new RateLimitingIterable<>(as, singleton(tuple(limit, duration, instantFn0))); } @SuppressWarnings("unchecked") public static RateLimit rateLimit() { - return INSTANCE; + return (RateLimit) INSTANCE; } - public static Fn3, Iterable> rateLimit(Supplier instantSupplier) { - return RateLimit.rateLimit().apply(instantSupplier); + public static Fn3, Iterable> rateLimit(Fn0 instantFn0) { + return RateLimit.rateLimit().apply(instantFn0); } - public static Fn2, Iterable> rateLimit(Supplier instantSupplier, Long limit) { - return RateLimit.rateLimit(instantSupplier).apply(limit); + public static Fn2, Iterable> rateLimit(Fn0 instantFn0, Long limit) { + return RateLimit.rateLimit(instantFn0).apply(limit); } - public static Fn1, Iterable> rateLimit(Supplier instantSupplier, Long limit, - Duration duration) { - return RateLimit.rateLimit(instantSupplier, limit).apply(duration); + public static Fn1, Iterable> rateLimit(Fn0 instantFn0, Long limit, Duration duration) { + return RateLimit.rateLimit(instantFn0, limit).apply(duration); } - public static Iterable rateLimit(Supplier instantSupplier, Long limit, Duration duration, - Iterable as) { - return RateLimit.rateLimit(instantSupplier, limit, duration).apply(as); + 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 index 099d519ca..9132376dc 100644 --- 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 @@ -16,93 +16,64 @@ * @param the function's third argument type * @param the function's fourth argument type * @param the function's return type - * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type - * @param the inferred third applicative argument type - * @param the inferred fourth applicative argument type + * @param the applicative witness * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA4, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> implements Fn5, AppA, AppB, AppC, AppD, AppE> { +public final class LiftA4, AppE extends Applicative> implements + Fn5, Applicative, Applicative, Applicative, Applicative, + AppE> { - private static final LiftA4 INSTANCE = new LiftA4(); + private static final LiftA4 INSTANCE = new LiftA4<>(); private LiftA4() { } @Override - public AppE apply(Fn4 fn, AppA appA, AppB appB, AppC appC, AppD appD) { - return appD.zip(appC.zip(appB.zip(appA.fmap(fn)))).coerce(); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> LiftA4 liftA4() { - return INSTANCE; + public static , AppE extends Applicative> + LiftA4 liftA4() { + return (LiftA4) INSTANCE; } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> Fn4 liftA4(Fn4 fn) { - return LiftA4.liftA4().apply(fn); + public static , AppE extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppE> liftA4( + Fn4 fn) { + return LiftA4.liftA4().apply(fn); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> Fn3 liftA4(Fn4 fn, AppA appA) { - return LiftA4.liftA4(fn).apply(appA); + public static , AppE extends Applicative> + Fn3, Applicative, Applicative, AppE> liftA4(Fn4 fn, + Applicative appA) { + return LiftA4.liftA4(fn).apply(appA); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> Fn2 liftA4(Fn4 fn, AppA appA, - AppB appB) { - return LiftA4.liftA4(fn, appA).apply(appB); + public static , AppE extends Applicative> + Fn2, Applicative, AppE> liftA4(Fn4 fn, + Applicative appA, + Applicative appB) { + return LiftA4.liftA4(fn, appA).apply(appB); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> Fn1 liftA4(Fn4 fn, AppA appA, AppB appB, - AppC appC) { - return LiftA4.liftA4(fn, appA, appB).apply(appC); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> AppE liftA4(Fn4 fn, AppA appA, AppB appB, - AppC appC, AppD appD) { - return LiftA4.liftA4(fn, appA, appB, appC).apply(appD); + 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 index f32d44569..7fd7c8f4f 100644 --- 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 @@ -18,115 +18,76 @@ * @param the function's fourth argument type * @param the function's fifth argument type * @param the function's return type - * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type - * @param the inferred third applicative argument type - * @param the inferred fourth applicative argument type - * @param the inferred fifth applicative argument type + * @param the applicative witness * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA5, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> implements Fn6, AppA, AppB, AppC, AppD, AppE, AppF> { +public final class LiftA5, AppF extends Applicative> + implements Fn6, Applicative, Applicative, Applicative, + Applicative, Applicative, AppF> { - private static final LiftA5 INSTANCE = new LiftA5(); + private static final LiftA5 INSTANCE = new LiftA5<>(); private LiftA5() { } @Override - public AppF apply(Fn5 fn, AppA appA, AppB appB, AppC appC, AppD appD, AppE appE) { - return appE.zip(appD.zip(appC.zip(appB.zip(appA.fmap(fn))))).coerce(); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> LiftA5 liftA5() { - return INSTANCE; + public static , AppF extends Applicative> + LiftA5 liftA5() { + return (LiftA5) INSTANCE; } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> Fn5 liftA5(Fn5 fn) { - return LiftA5.liftA5().apply(fn); + public static , AppF extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppF> + liftA5(Fn5 fn) { + return LiftA5.liftA5().apply(fn); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> Fn4 liftA5(Fn5 fn, - AppA appA) { - return LiftA5.liftA5(fn).apply(appA); + public static , AppF extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppF> + liftA5(Fn5 fn, + Applicative appA) { + return LiftA5.liftA5(fn).apply(appA); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> Fn3 liftA5(Fn5 fn, AppA appA, - AppB appB) { - return LiftA5.liftA5(fn, appA).apply(appB); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> Fn2 liftA5(Fn5 fn, AppA appA, - AppB appB, - AppC appC) { - return LiftA5.liftA5(fn, appA, appB).apply(appC); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> Fn1 liftA5(Fn5 fn, AppA appA, AppB appB, - AppC appC, AppD appD) { - return LiftA5.liftA5(fn, appA, appB, appC).apply(appD); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> AppF liftA5(Fn5 fn, AppA appA, AppB appB, - AppC appC, AppD appD, AppE appE) { - return LiftA5.liftA5(fn, appA, appB, appC, appD).apply(appE); + 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 index 72da45314..fcc7934c9 100644 --- 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 @@ -20,143 +20,98 @@ * @param the function's fifth argument type * @param the function's sixth argument type * @param the function's return type - * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type - * @param the inferred third applicative argument type - * @param the inferred fourth applicative argument type - * @param the inferred fifth applicative argument type - * @param the inferred sixth applicative argument type + * @param the applicative witness * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA6, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> implements Fn7, AppA, AppB, AppC, AppD, AppE, AppF, AppG> { +public final class LiftA6, AppG extends Applicative> + implements Fn7, + Applicative, + Applicative, + Applicative, + Applicative, + Applicative, + Applicative, + AppG> { - private static final LiftA6 INSTANCE = new LiftA6(); + private static final LiftA6 INSTANCE = new LiftA6<>(); private LiftA6() { } @Override - public AppG apply(Fn6 fn, AppA appA, AppB appB, AppC appC, AppD appD, AppE appE, - AppF appF) { - return appF.zip(appE.zip(appD.zip(appC.zip(appB.zip(appA.fmap(fn)))))).coerce(); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> LiftA6 liftA6() { - return INSTANCE; + public static , AppG extends Applicative> + LiftA6 liftA6() { + return (LiftA6) INSTANCE; } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn6 liftA6( - Fn6 fn) { - return LiftA6.liftA6().apply(fn); + public static , AppG extends Applicative> + Fn6, Applicative, Applicative, Applicative, Applicative, + Applicative, AppG> liftA6(Fn6 fn) { + return LiftA6.liftA6().apply(fn); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn5 liftA6( - Fn6 fn, - AppA appA) { - return LiftA6.liftA6(fn).apply(appA); + public static , AppG extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppG> + liftA6(Fn6 fn, Applicative appA) { + return LiftA6.liftA6(fn).apply(appA); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn4 liftA6(Fn6 fn, - AppA appA, - AppB appB) { - return LiftA6.liftA6(fn, appA).apply(appB); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn3 liftA6(Fn6 fn, AppA appA, - AppB appB, - AppC appC) { - return LiftA6.liftA6(fn, appA, appB).apply(appC); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn2 liftA6(Fn6 fn, AppA appA, - AppB appB, - AppC appC, AppD appD) { - return LiftA6.liftA6(fn, appA, appB, appC).apply(appD); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn1 liftA6(Fn6 fn, AppA appA, AppB appB, - AppC appC, AppD appD, AppE appE) { - return LiftA6.liftA6(fn, appA, appB, appC, appD).apply(appE); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> AppG liftA6(Fn6 fn, AppA appA, AppB appB, - AppC appC, AppD appD, AppE appE, AppF appF) { - return LiftA6.liftA6(fn, appA, appB, appC, appD, appE).apply(appF); + 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 index 6d508f06d..6f80b7d37 100644 --- 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 @@ -23,164 +23,106 @@ * @param the function's seventh argument type * @param the function's return type * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type - * @param the inferred third applicative argument type - * @param the inferred fourth applicative argument type - * @param the inferred fifth applicative argument type - * @param the inferred sixth applicative argument type - * @param the inferred seventh applicative argument type * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA7, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> implements Fn8, AppA, AppB, AppC, AppD, AppE, AppF, AppG, AppH> { +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 static final LiftA7 INSTANCE = new LiftA7<>(); private LiftA7() { } @Override - public AppH apply(Fn7 fn, AppA appA, AppB appB, AppC appC, AppD appD, AppE appE, - AppF appF, AppG appG) { - return appG.zip(appF.zip(appE.zip(appD.zip(appC.zip(appB.zip(appA.fmap(fn))))))).coerce(); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> LiftA7 liftA7() { - return INSTANCE; + public static , AppH extends Applicative> + LiftA7 liftA7() { + return (LiftA7) INSTANCE; } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn7 liftA7( - Fn7 fn) { - return LiftA7.liftA7().apply(fn); + public static , AppH extends Applicative> + Fn7, Applicative, Applicative, Applicative, Applicative, + Applicative, Applicative, AppH> liftA7(Fn7 fn) { + return LiftA7.liftA7().apply(fn); } - public static , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn6 liftA7( - Fn7 fn, AppA appA) { - return LiftA7.liftA7(fn).apply(appA); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn5 liftA7( - Fn7 fn, AppA appA, AppB appB) { - return LiftA7.liftA7(fn, appA).apply(appB); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn4 liftA7(Fn7 fn, - AppA appA, AppB appB, - AppC appC) { - return LiftA7.liftA7(fn, appA, appB).apply(appC); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn3 liftA7(Fn7 fn, - AppA appA, AppB appB, AppC appC, - AppD appD) { - return LiftA7.liftA7(fn, appA, appB, appC).apply(appD); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn2 liftA7(Fn7 fn, AppA appA, - AppB appB, AppC appC, AppD appD, AppE appE) { - return LiftA7.liftA7(fn, appA, appB, appC, appD).apply(appE); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn1 liftA7(Fn7 fn, AppA appA, - AppB appB, AppC appC, AppD appD, AppE appE, - AppF appF) { - return LiftA7.liftA7(fn, appA, appB, appC, appD, appE).apply(appF); + 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 , - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> AppH liftA7(Fn7 fn, AppA appA, AppB appB, - AppC appC, AppD appD, AppE appE, AppF appF, AppG appG) { - return LiftA7.liftA7(fn, appA, appB, appC, appD, appE, appF).apply(appG); + 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 index 7da780a1e..2fd54d791 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java @@ -1,13 +1,17 @@ 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 java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; /** * Specialized {@link CoProduct2} representing the possible results of a primitive recursive function. @@ -18,81 +22,152 @@ * @param the recursive function's output type * @see Trampoline */ -public abstract class RecursiveResult implements CoProduct2>, Bifunctor>, Monad>, Traversable> { +public abstract class RecursiveResult implements + CoProduct2>, + Bifunctor>, + MonadRec>, + Traversable> { private RecursiveResult() { } + /** + * {@inheritDoc} + */ @Override public RecursiveResult invert() { return match(RecursiveResult::terminate, RecursiveResult::recurse); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public RecursiveResult biMapL(Function fn) { - return (RecursiveResult) Bifunctor.super.biMapL(fn); + public RecursiveResult biMapL(Fn1 fn) { + return (RecursiveResult) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public RecursiveResult biMapR(Function fn) { - return (RecursiveResult) Bifunctor.super.biMapR(fn); + public RecursiveResult biMapR(Fn1 fn) { + return (RecursiveResult) Bifunctor.super.biMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public RecursiveResult biMap(Function lFn, - Function rFn) { + public RecursiveResult biMap(Fn1 lFn, + Fn1 rFn) { return match(a -> recurse(lFn.apply(a)), b -> terminate(rFn.apply(b))); } + /** + * {@inheritDoc} + */ @Override - public RecursiveResult flatMap( - Function>> f) { + 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(Function fn) { - return Monad.super.fmap(fn).coerce(); + public RecursiveResult fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - public RecursiveResult zip( - Applicative, RecursiveResult> appFn) { - return Monad.super.zip(appFn).coerce(); + public RecursiveResult zip(Applicative, RecursiveResult> appFn) { + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override public RecursiveResult discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public RecursiveResult discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return match(__ -> pure.apply(coerce()), b -> fn.apply(b).fmap(this::pure).fmap(Applicative::coerce).coerce()); + 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; @@ -101,7 +176,7 @@ private Recurse(A a) { } @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return aFn.apply(a); } @@ -131,7 +206,7 @@ private Terminate(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/functions/recursion/Trampoline.java b/src/main/java/com/jnape/palatable/lambda/functions/recursion/Trampoline.java index a4efb94cc..6637219ec 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/recursion/Trampoline.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/recursion/Trampoline.java @@ -7,25 +7,23 @@ import com.jnape.palatable.lambda.functions.recursion.RecursiveResult.Recurse; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult.Terminate; -import java.util.function.Function; - /** - * Given a {@link Function}<A, {@link CoProduct2}<A, B, ?>> (analogous to "recurse" and - * "return" tail position instructions, respectively), produce a {@link Function}<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). + * 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> { +public final class Trampoline implements Fn2>, A, B> { - private static final Trampoline INSTANCE = new Trampoline<>(); + private static final Trampoline INSTANCE = new Trampoline<>(); @Override - public B apply(Function> fn, A a) { + public B checkedApply(Fn1> fn, A a) { RecursiveResult next = fn.apply(a); while (next instanceof Recurse) next = fn.apply(((Recurse) next).a); @@ -34,14 +32,14 @@ public B apply(Function> fn, A a) { @SuppressWarnings("unchecked") public static Trampoline trampoline() { - return INSTANCE; + return (Trampoline) INSTANCE; } - public static Fn1 trampoline(Function> fn) { + public static Fn1 trampoline(Fn1> fn) { return Trampoline.trampoline().apply(fn); } - public static B trampoline(Function> fn, A a) { + 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 352de9585..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,22 +1,37 @@ package com.jnape.palatable.lambda.functions.specialized; 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 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 8d28fb290..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,42 +1,41 @@ package com.jnape.palatable.lambda.functions.specialized; import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; - -import java.util.function.Function; +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 Fn2.super.flip()::apply; + default BiPredicate discardR(Applicative> appB) { + return Fn2.super.discardR(appB)::apply; } /** @@ -51,7 +50,7 @@ default BiPredicate flip() { * {@inheritDoc} */ @Override - default BiPredicate diMapL(Function fn) { + default BiPredicate diMapL(Fn1 fn) { return Fn2.super.diMapL(fn)::apply; } @@ -59,42 +58,57 @@ default BiPredicate diMapL(Function fn) { * {@inheritDoc} */ @Override - default Fn2 contraMap(Function fn) { + default Fn2 contraMap(Fn1 fn) { return Fn2.super.contraMap(fn)::apply; } /** - * Override of {@link java.util.function.BiPredicate#and(java.util.function.BiPredicate)}, returning an instance of - * BiPredicate for compatibility. Left-to-right composition. + * 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 a2a16d899..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 @@ -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) { @@ -24,9 +34,4 @@ default BiSemigroupFactory flip() { 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 index 433ef410f..ad66e4036 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java @@ -4,18 +4,16 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.monad.Monad; -import java.util.function.Function; - /** * 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(Function)}. + * 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> extends Fn1 { +public interface Kleisli, MB extends Monad> extends Fn1 { /** * Left-to-right composition of two compatible {@link Kleisli} arrows, yielding a new {@link Kleisli} arrow. @@ -25,6 +23,7 @@ public interface Kleisli> extends * @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(); } @@ -37,18 +36,11 @@ default > Kleisli andThen(Kleisli 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 compose(Function before) { - return Fn1.super.compose(before)::apply; - } - /** * {@inheritDoc} */ @@ -61,7 +53,7 @@ default Kleisli discardR(Applicative> appB) { * {@inheritDoc} */ @Override - default Kleisli contraMap(Function fn) { + default Kleisli contraMap(Fn1 fn) { return Fn1.super.contraMap(fn)::apply; } @@ -69,7 +61,7 @@ default Kleisli contraMap(Function fn) * {@inheritDoc} */ @Override - default Kleisli diMapL(Function fn) { + default Kleisli diMapL(Fn1 fn) { return Fn1.super.diMapL(fn)::apply; } @@ -83,8 +75,8 @@ default Kleisli diMapL(Function fn) { * @param the returned {@link Monad} instance * @return the function adapted as a {@link Kleisli} arrow */ - static > Kleisli kleisli( - Function fn) { + 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/Noop.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Noop.java deleted file mode 100644 index 62c5192f4..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Noop.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized; - -import com.jnape.palatable.lambda.functions.Effect; - -/** - * As the name might suggest, this is an {@link Effect} that, *ahem*, has no effect. - * - * @param the argument type - */ -public final class Noop implements Effect { - private static final Noop INSTANCE = new Noop(); - - private Noop() { - } - - @Override - public void accept(A a) { - } - - /** - * Static factory method that returns the singleton {@link Noop} instance. - * - * @param the argument type - * @return the singleton {@link Noop} instance - */ - @SuppressWarnings("unchecked") - public static Noop noop() { - return INSTANCE; - } -} 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 2138db821..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,96 +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 - default Predicate compose(Function before) { - return Fn1.super.compose(before)::apply; + default Predicate contraMap(Fn1 fn) { + return Fn1.super.contraMap(fn)::apply; } /** * {@inheritDoc} */ @Override - default Predicate diMapL(Function fn) { - return Fn1.super.diMapL(fn)::apply; + default BiPredicate widen() { + return Fn1.super.widen()::checkedApply; } /** * {@inheritDoc} */ @Override - default Predicate contraMap(Function fn) { - return Fn1.super.contraMap(fn)::apply; + 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); } /** - * Static factory method to create a predicate from a function. + * 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 function + * @param predicate the {@link Fn1} * @param the input type * @return the predicate */ - static Predicate predicate(Function 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/CheckedEffect.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedEffect.java deleted file mode 100644 index a4dc0c820..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedEffect.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -import com.jnape.palatable.lambda.adt.Unit; -import com.jnape.palatable.lambda.functions.Effect; -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functor.Applicative; -import com.jnape.palatable.lambda.functions.IO; - -import java.util.function.Consumer; -import java.util.function.Function; - -import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; -import static com.jnape.palatable.lambda.functions.IO.io; - -/** - * Specialized {@link Effect} that can throw any {@link Throwable}. - * - * @param The {@link Throwable} type - * @param The input type - * @see CheckedRunnable - * @see CheckedFn1 - * @see Effect - */ -@FunctionalInterface -public interface CheckedEffect extends Effect, CheckedFn1> { - - /** - * {@inheritDoc} - */ - @Override - default void accept(A a) { - try { - checkedAccept(a); - } catch (Throwable t) { - throw throwChecked(t); - } - } - - /** - * {@inheritDoc} - */ - @Override - default IO apply(A a) { - return io(() -> accept(a)); - } - - /** - * {@inheritDoc} - */ - @Override - default IO checkedApply(A a) throws T { - return apply(a); - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedEffect diMapL(Function fn) { - return Effect.super.diMapL(fn)::accept; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedEffect contraMap(Function fn) { - return Effect.super.contraMap(fn)::accept; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedEffect compose(Function before) { - return Effect.super.compose(before)::accept; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedEffect discardR(Applicative> appB) { - return Effect.super.discardR(appB)::accept; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedEffect andThen(Consumer after) { - return Effect.super.andThen(after)::accept; - } - - /** - * A version of {@link Effect#accept} that can throw checked exceptions. - * - * @param a the effect argument - * @throws T any exception that can be thrown by this method - */ - void checkedAccept(A a) throws T; - - /** - * Convenience static factory method for constructing a {@link CheckedEffect} without an explicit cast or type - * attribution at the call site. - * - * @param checkedEffect the checked effect - * @param the inferred Throwable type - * @param the input type - * @return the checked effect - */ - static CheckedEffect checked(CheckedEffect checkedEffect) { - return checkedEffect; - } -} 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 4b5bd93a7..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -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.functor.Applicative; -import com.jnape.palatable.lambda.monad.Monad; - -import java.util.function.Function; - -import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; - -/** - * Specialized {@link Fn1} that can throw any {@link Throwable}. - * - * @param The {@link Throwable} type - * @param The input type - * @param The output type - * @see CheckedSupplier - * @see CheckedRunnable - * @see Fn1 - */ -@FunctionalInterface -public interface CheckedFn1 extends Fn1 { - - /** - * {@inheritDoc} - */ - @Override - default B apply(A a) { - try { - return checkedApply(a); - } catch (Throwable t) { - throw throwChecked(t); - } - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 fmap(Function f) { - return Fn1.super.fmap(f)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 flatMap(Function>> f) { - return Fn1.super.flatMap(f).>coerce()::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 discardL(Applicative> appB) { - return Fn1.super.discardL(appB)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 discardR(Applicative> appB) { - return Fn1.super.discardR(appB)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 zip(Applicative, Fn1> appFn) { - return Fn1.super.zip(appFn)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 zip(Fn2 appFn) { - return Fn1.super.zip(appFn).coerce(); - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 diMapL(Function fn) { - return Fn1.super.diMapL(fn).coerce(); - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 diMapR(Function fn) { - return Fn1.super.diMapR(fn).coerce(); - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 diMap(Function lFn, - Function rFn) { - return Fn1.super.diMap(lFn, rFn)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1, Tuple2> strengthen() { - return Fn1.super.strengthen()::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1> carry() { - return Fn1.super.carry()::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 contraMap(Function fn) { - return Fn1.super.contraMap(fn).coerce(); - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 compose(Function before) { - return Fn1.super.compose(before)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedFn1 andThen(Function after) { - return Fn1.super.andThen(after)::apply; - } - - /** - * A version of {@link Fn1#apply} that can throw checked exceptions. - * - * @param a the function argument - * @return the application of the argument to the function - * @throws T any exception that can be thrown by this method - */ - B checkedApply(A a) throws T; - - /** - * Convenience static factory method for constructing a {@link CheckedFn1} without an explicit cast or type - * attribution at the call site. - * - * @param checkedFn1 the checked fn1 - * @param the inferred Throwable type - * @param the input type - * @param the output type - * @return the checked fn1 - */ - static CheckedFn1 checked(CheckedFn1 checkedFn1) { - return checkedFn1; - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java deleted file mode 100644 index 7eb794917..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -import com.jnape.palatable.lambda.adt.Unit; -import com.jnape.palatable.lambda.functions.IO; - -import static com.jnape.palatable.lambda.adt.Unit.UNIT; -import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; - -/** - * Specialized {@link Runnable} that can throw any {@link Throwable}. - * - * @param The {@link Throwable} type - * @see CheckedSupplier - * @see CheckedFn1 - */ -@FunctionalInterface -public interface CheckedRunnable extends Runnable, IO { - - /** - * A version of {@link Runnable#run()} that can throw checked exceptions. - * - * @throws T any exception that can be thrown by this method - */ - void checkedRun() throws T; - - @Override - default void run() { - try { - checkedRun(); - } catch (Throwable t) { - throw throwChecked(t); - } - } - - @Override - default Unit unsafePerformIO() { - run(); - return UNIT; - } - - /** - * Convert this {@link CheckedRunnable} to a {@link CheckedSupplier} that returns {@link Unit}. - * - * @return the checked supplier - */ - default CheckedSupplier toSupplier() { - return () -> { - run(); - return UNIT; - }; - } - - /** - * Convenience static factory method for constructing a {@link CheckedRunnable} without an explicit cast or type - * attribution at the call site. - * - * @param runnable the checked runnable - * @param the inferred Throwable type - * @return the checked runnable - */ - static CheckedRunnable checked(CheckedRunnable runnable) { - return runnable::run; - } - -} 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 627da8c91..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -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.Fn2; -import com.jnape.palatable.lambda.functor.Applicative; -import com.jnape.palatable.lambda.monad.Monad; - -import java.util.function.Function; -import java.util.function.Supplier; - -import static com.jnape.palatable.lambda.adt.Unit.UNIT; -import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; - -/** - * Specialized {@link Supplier} that can throw any {@link Throwable}. - * - * @param The {@link Throwable} type - * @param The return type - * @see CheckedFn1 - * @see CheckedRunnable - */ -@FunctionalInterface -public interface CheckedSupplier extends Supplier, CheckedFn1 { - - /** - * A version of {@link Supplier#get()} that can throw checked exceptions. - * - * @return the supplied result - * @throws T any exception that can be thrown by this method - */ - A checkedGet() throws T; - - /** - * Convert this {@link CheckedSupplier} to a {@link CheckedRunnable}. - * - * @return the checked runnable - */ - default CheckedRunnable toRunnable() { - return this::get; - } - - @Override - default A checkedApply(Unit unit) throws T { - return checkedGet(); - } - - /** - * {@inheritDoc} - */ - @Override - default A get() { - try { - return checkedGet(); - } catch (Throwable t) { - throw throwChecked(t); - } - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedSupplier fmap(Function f) { - return CheckedFn1.super.fmap(f).thunk(UNIT)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedSupplier flatMap(Function>> f) { - return CheckedFn1.super.flatMap(f).thunk(UNIT)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedSupplier discardL(Applicative> appB) { - return CheckedFn1.super.discardL(appB).thunk(UNIT)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedSupplier discardR(Applicative> appB) { - return CheckedFn1.super.discardR(appB).thunk(UNIT)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedSupplier zip(Applicative, Fn1> appFn) { - return CheckedFn1.super.zip(appFn).thunk(UNIT)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedSupplier zip(Fn2 appFn) { - return CheckedFn1.super.zip(appFn).thunk(UNIT)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedSupplier diMapR(Function fn) { - return CheckedFn1.super.diMapR(fn).thunk(UNIT)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedSupplier> carry() { - return CheckedFn1.super.carry().thunk(UNIT)::apply; - } - - /** - * {@inheritDoc} - */ - @Override - default CheckedSupplier andThen(Function after) { - return CheckedFn1.super.andThen(after).thunk(UNIT)::apply; - } - - /** - * Convenience static factory method for constructing a {@link CheckedSupplier} without an explicit cast or type - * attribution at the call site. - * - * @param supplier the checked supplier - * @param the inferred Throwable type - * @param the supplier return type - * @return the checked supplier - */ - static CheckedSupplier checked(CheckedSupplier supplier) { - return supplier::get; - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java index c8eb56d7c..9cf2125e1 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.functor; -import java.util.function.Function; +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; @@ -26,7 +27,7 @@ * @param The type of the parameter * @param The unification parameter to more tightly type-constrain Applicatives to themselves */ -public interface Applicative extends Functor { +public interface Applicative> extends Functor { /** * Lift the value b into this applicative functor. @@ -45,11 +46,22 @@ public interface Applicative extends Functor * @param the resulting applicative parameter type * @return the mapped applicative */ - Applicative zip(Applicative, App> appFn); + Applicative zip(Applicative, App> appFn); - @Override - default Applicative fmap(Function fn) { - return zip(pure(fn)); + /** + * 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); } /** @@ -61,7 +73,7 @@ default Applicative fmap(Function fn) { * @return appB */ default Applicative discardL(Applicative appB) { - return appB.zip(fmap(constantly(id()))); + return zip(appB.fmap(constantly())); } /** @@ -73,17 +85,14 @@ default Applicative discardL(Applicative appB) { * @return this Applicative */ default Applicative discardR(Applicative appB) { - return appB.zip(fmap(constantly())); + return zip(appB.fmap(constantly(id()))); } /** - * Convenience method for coercing this applicative instance into another concrete type. Unsafe. - * - * @param the concrete applicative instance to coerce this applicative to - * @return the coerced applicative + * {@inheritDoc} */ - @SuppressWarnings("unchecked") - default > Concrete coerce() { - return (Concrete) this; + @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 695c71a8f..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; @@ -17,7 +17,7 @@ * @see com.jnape.palatable.lambda.adt.hlist.Tuple2 */ @FunctionalInterface -public interface Bifunctor extends BoundedBifunctor { +public interface Bifunctor> extends BoundedBifunctor { /** * Covariantly map over the left parameter. @@ -26,7 +26,7 @@ public interface Bifunctor extends BoundedBifunctor< * @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()); } @@ -38,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); } @@ -52,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 index 02fc7775f..057c3bd29 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/BoundedBifunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/BoundedBifunctor.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; @@ -16,29 +16,32 @@ * @see Bifunctor */ @FunctionalInterface -public interface BoundedBifunctor { +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 fn the mapping function * @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( - Function fn) { + default BoundedBifunctor biMapL(Fn1 fn) { return biMap(fn, id()); } /** * Covariantly map the right parameter into a value that is covariant to ContraB. * - * @param fn the mapping function * @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( - Function fn) { + default BoundedBifunctor biMapR(Fn1 fn) { return biMap(id(), fn); } @@ -53,6 +56,6 @@ default BoundedBifunctor biMapR( * @return a bifunctor over C (the new left parameter type) and D (the new right parameter type) */ BoundedBifunctor biMap( - Function lFn, - Function rFn); + 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 index 07d548d1a..c596b36b0 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Contravariant.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Contravariant.java @@ -1,20 +1,21 @@ package com.jnape.palatable.lambda.functor; -import java.util.function.Function; +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 { +public interface Contravariant> { /** * Contravariantly map A <- B. @@ -23,5 +24,5 @@ public interface Contravariant { * @param the new parameter type * @return the mapped Contravariant functor instance */ - Contravariant contraMap(Function fn); + 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 769aae816..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. @@ -20,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 @@ -30,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 0e7235b44..b9518d3e4 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java @@ -1,9 +1,6 @@ package com.jnape.palatable.lambda.functor; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.lens.Lens; - -import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; @@ -19,10 +16,10 @@ * @see Bifunctor * @see Contravariant * @see Fn1 - * @see Lens + * @see com.jnape.palatable.lambda.optics.Optic */ @FunctionalInterface -public interface Profunctor extends Contravariant> { +public interface Profunctor> extends Contravariant> { /** * Dually map contravariantly over the left parameter and covariantly over the right parameter. This is isomorphic @@ -34,7 +31,7 @@ public interface Profunctor extends Contravariant Profunctor diMap(Function lFn, Function rFn); + Profunctor diMap(Fn1 lFn, Fn1 rFn); /** * Contravariantly map over the left parameter. @@ -43,7 +40,7 @@ public interface Profunctor extends Contravariant Profunctor diMapL(Function fn) { + default Profunctor diMapL(Fn1 fn) { return diMap(fn, id()); } @@ -55,12 +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); } + /** + * {@inheritDoc} + */ @Override - default Profunctor contraMap(Function fn) { + default Profunctor contraMap(Fn1 fn) { return diMapL(fn); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Strong.java b/src/main/java/com/jnape/palatable/lambda/functor/Strong.java deleted file mode 100644 index c6b4929c2..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functor/Strong.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jnape.palatable.lambda.functor; - -import com.jnape.palatable.lambda.adt.hlist.Tuple2; - -import java.util.function.Function; - -/** - * "Strong" {@link Profunctor profunctors} are profunctors that can be "strengthened" to preserve the pairing of an - * arbitrary type under dimap (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 - */ -public interface Strong extends Profunctor { - - /** - * Pair some type C to this profunctor's carrier types. - * - * @param the paired type - * @return the strengthened profunctor - */ - Strong, Tuple2, S> strengthen(); - - /** - * 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 Strong, S> carry() { - return this.strengthen().contraMap(Tuple2::fill); - } - - @Override - Strong diMap(Function lFn, Function rFn); - - @Override - default Strong diMapL(Function fn) { - return (Strong) Profunctor.super.diMapL(fn); - } - - @Override - default Strong diMapR(Function fn) { - return (Strong) Profunctor.super.diMapR(fn); - } - - @Override - default Strong contraMap(Function fn) { - return (Strong) Profunctor.super.contraMap(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 index 40a732247..b0f0dff76 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java @@ -1,9 +1,12 @@ 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 java.util.function.Function; + +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 @@ -13,7 +16,8 @@ * @param The inner applicative * @param The carrier type */ -public final class Compose implements Applicative> { +public final class Compose, G extends Applicative, A> implements + Applicative> { private final Applicative, F> fga; @@ -21,30 +25,65 @@ public Compose(Applicative, F> fga) { this.fga = fga; } + @SuppressWarnings("RedundantTypeArguments") public , FGA extends Applicative> FGA getCompose() { - return fga.fmap(Applicative::coerce).coerce(); + return fga.fmap(Applicative::coerce).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Compose fmap(Function fn) { + 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)))); + 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(); @@ -64,4 +103,23 @@ public int hashCode() { 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 6de15b769..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,12 +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.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; /** * A (surprisingly useful) functor over some phantom type B, retaining a value of type A that @@ -17,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 Monad>, Bifunctor, Traversable> { +public final class Const implements + MonadRec>, + Bifunctor>, + Traversable> { private final A a; @@ -43,81 +49,102 @@ public A runConst() { * @return a Const over A (the same value) and C (the new phantom parameter) */ @Override - public Const fmap(Function fn) { - return Monad.super.fmap(fn).coerce(); + public Const fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override @SuppressWarnings("unchecked") public Const pure(C c) { return (Const) this; } + /** + * {@inheritDoc} + */ @Override - public Const zip(Applicative, Const> appFn) { - return Monad.super.zip(appFn).coerce(); + 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 Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public Const discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override @SuppressWarnings("unchecked") - public Const flatMap(Function>> f) { + public Const flatMap(Fn1>> f) { return (Const) this; } + /** + * {@inheritDoc} + */ @Override - public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { + @SuppressWarnings("unchecked") + public Const trampolineM(Fn1, Const>> fn) { + return (Const) this; + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { return pure.apply(coerce()); } /** - * 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 - @SuppressWarnings("unchecked") - public Const biMapL(Function fn) { - return (Const) Bifunctor.super.biMapL(fn); + public Const biMapL(Fn1 fn) { + return (Const) Bifunctor.super.biMapL(fn); } /** - * 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 biMapR(Fn1 fn) { + return (Const) Bifunctor.super.biMapR(fn); } /** - * 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 Const biMap(Function lFn, - Function rFn) { + public Const biMap(Fn1 lFn, + Fn1 rFn) { return new Const<>(lFn.apply(a)); } @@ -137,4 +164,20 @@ public String toString() { "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 index 6a317784c..f04050368 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Exchange.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Exchange.java @@ -1,9 +1,8 @@ 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.lens.Iso; - -import java.util.function.Function; +import com.jnape.palatable.lambda.optics.Iso; /** * A profunctor used to extract the isomorphic functions an {@link Iso} is composed of. @@ -14,40 +13,62 @@ * @param the larger viewed value of an {@link Iso} */ public final class Exchange implements Profunctor> { - private final Function sa; - private final Function bt; + private final Fn1 sa; + private final Fn1 bt; - public Exchange(Function sa, Function bt) { + public Exchange(Fn1 sa, Fn1 bt) { this.sa = sa; this.bt = bt; } - public Function sa() { + /** + * Extract the mapping S -> A. + * + * @return an {@link Fn1}<S, A> + */ + public Fn1 sa() { return sa; } - public Function bt() { + /** + * Extract the mapping B -> T. + * + * @return an {@link Fn1}<B, T> + */ + public Fn1 bt() { return bt; } + /** + * {@inheritDoc} + */ @Override - public Exchange diMap(Function lFn, - Function rFn) { - return new Exchange<>(lFn.andThen(sa), bt.andThen(rFn)); + public Exchange diMap(Fn1 lFn, + Fn1 rFn) { + return new Exchange<>(lFn.fmap(sa), bt.fmap(rFn)); } + /** + * {@inheritDoc} + */ @Override - public Exchange diMapL(Function fn) { + public Exchange diMapL(Fn1 fn) { return (Exchange) Profunctor.super.diMapL(fn); } + /** + * {@inheritDoc} + */ @Override - public Exchange diMapR(Function fn) { + public Exchange diMapR(Fn1 fn) { return (Exchange) Profunctor.super.diMapR(fn); } + /** + * {@inheritDoc} + */ @Override - public Exchange contraMap(Function fn) { + 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 68e223c40..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,18 +1,23 @@ 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.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.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 Monad, Traversable { +public final class Identity implements MonadRec>, Traversable> { private final A a; @@ -33,7 +38,7 @@ public A runIdentity() { * {@inheritDoc} */ @Override - public Identity flatMap(Function> f) { + public Identity flatMap(Fn1>> f) { return f.apply(runIdentity()).coerce(); } @@ -41,37 +46,71 @@ public Identity flatMap(Function> * {@inheritDoc} */ @Override - public Identity fmap(Function fn) { - return Monad.super.fmap(fn).coerce(); + 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)); + public Identity zip(Applicative, Identity> appFn) { + return new Identity<>(appFn.>>coerce().runIdentity().apply(a)); } + /** + * {@inheritDoc} + */ @Override - public Identity discardL(Applicative appB) { - return Monad.super.discardL(appB).coerce(); + public Lazy> lazyZip( + Lazy, Identity>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); } + /** + * {@inheritDoc} + */ @Override - public Identity discardR(Applicative appB) { - return Monad.super.discardR(appB).coerce(); + 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 , AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { + 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 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); @@ -88,4 +127,13 @@ public String toString() { "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/iteration/MappingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/MappingIterable.java deleted file mode 100644 index 1730a6838..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iteration/MappingIterable.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jnape.palatable.lambda.iteration; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.function.Function; - -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(Function 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() { - Function fnComposedOnTheHeap = o -> foldLeft((x, fn) -> fn.apply(x), o, mappers); - return new MappingIterator<>(fnComposedOnTheHeap, as.iterator()); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterable.java deleted file mode 100644 index bd3aa87b6..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterable.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.jnape.palatable.lambda.iteration; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.function.Function; - -import static com.jnape.palatable.lambda.functions.builtin.fn2.Any.any; -import static java.util.Collections.singletonList; - -public final class PredicatedDroppingIterable implements Iterable { - private final List> predicates; - private final Iterable as; - - public PredicatedDroppingIterable(Function predicate, Iterable as) { - List> predicates = new ArrayList<>(singletonList(predicate)); - - while (as instanceof PredicatedDroppingIterable) { - PredicatedDroppingIterable nested = (PredicatedDroppingIterable) as; - as = nested.as; - predicates.addAll(0, nested.predicates); - } - this.predicates = predicates; - this.as = as; - } - - @Override - public Iterator iterator() { - Function metaPredicate = a -> any(p -> p.apply(a), predicates); - return new PredicatedDroppingIterator<>(metaPredicate, as.iterator()); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterator.java deleted file mode 100644 index 7fe16c64b..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterator.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jnape.palatable.lambda.iteration; - -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.function.Function; - -public final 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/lens/Iso.java b/src/main/java/com/jnape/palatable/lambda/lens/Iso.java deleted file mode 100644 index bff042842..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/Iso.java +++ /dev/null @@ -1,319 +0,0 @@ -package com.jnape.palatable.lambda.lens; - -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.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.lens.functions.Over; -import com.jnape.palatable.lambda.lens.functions.Set; -import com.jnape.palatable.lambda.lens.functions.View; -import com.jnape.palatable.lambda.monad.Monad; - -import java.util.function.Function; - -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.lens.Iso.Simple.adapt; -import static com.jnape.palatable.lambda.lens.functions.View.view; - -/** - * An {@link Iso} (short for "isomorphism") is an invertible {@link Lens}: a {@link LensLike} encoding of a - * bi-directional focusing of two types, and like {@link Lens}es, can be {@link View}ed, - * {@link Set}, and {@link Over}ed. - *

- * 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 a {@link Lens}<String, String, Integer, - * Integer>, and can be {@link Iso#mirror}ed and viewed as a {@link Lens}<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 - */ -@FunctionalInterface -public interface Iso extends LensLike { - -

, FT extends Functor, - PAFB extends Profunctor, - PSFT extends Profunctor> PSFT apply(PAFB pafb); - - @Override - default , FB extends Functor> FT apply( - Function fn, S s) { - return this., Fn1>apply(fn1(fn)).apply(s); - } - - /** - * Convert this {@link Iso} into a {@link Lens}. - * - * @return the equivalent lens - */ - default Lens toLens() { - return new Lens() { - @Override - public , FB extends Functor> FT apply( - Function fn, S s) { - return Iso.this.apply(fn, s); - } - }; - } - - /** - * 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())); - } - - @Override - default Iso fmap(Function fn) { - return LensLike.super.fmap(fn).coerce(); - } - - @Override - default Iso pure(U u) { - return iso(view(this), constantly(u)); - } - - @Override - default Iso zip(Applicative, LensLike> appFn) { - return LensLike.super.zip(appFn).coerce(); - } - - @Override - default Iso discardL(Applicative> appB) { - return LensLike.super.discardL(appB).coerce(); - } - - @Override - default Iso discardR(Applicative> appB) { - return LensLike.super.discardR(appB).coerce(); - } - - @Override - default Iso flatMap(Function>> fn) { - return unIso().fmap(bt -> Fn2.fn2(fn1(bt.andThen(fn.>andThen(Applicative::coerce)) - .andThen(Iso::unIso) - .andThen(Tuple2::_2) - .andThen(Fn1::fn1)))) - .fmap(Fn2::uncurry) - .fmap(bbu -> bbu.diMapL(Tuple2::fill)) - .into(Iso::iso); - } - - @Override - default Iso diMapL(Function fn) { - return LensLike.super.diMapL(fn).coerce(); - } - - @Override - default Iso diMapR(Function fn) { - return LensLike.super.diMapR(fn).coerce(); - } - - @Override - default Iso diMap(Function lFn, - Function rFn) { - return LensLike.super.diMap(lFn, rFn).coerce(); - } - - @Override - default Iso contraMap(Function fn) { - return LensLike.super.contraMap(fn).coerce(); - } - - @Override - default Iso mapS(Function fn) { - return unIso().biMapL(f -> f.compose(fn)).into(Iso::iso); - } - - @Override - default Iso mapT(Function fn) { - return unIso().biMapR(f -> f.andThen(fn)).into(Iso::iso); - } - - @Override - default Iso mapA(Function fn) { - return unIso().biMapL(f -> f.andThen(fn)).into(Iso::iso); - } - - @Override - default Iso mapB(Function fn) { - return unIso().biMapR(f -> f.compose(fn)).into(Iso::iso); - } - - @Override - default Lens andThen(LensLike f) { - return toLens().andThen(f); - } - - /** - * Left-to-right composition of {@link Iso}. - * - * @param f the iso to apply after this one - * @param the smaller type the first larger type can be viewed as - * @param the smaller type that can be viewed as the second larger type - * @return the composed {@link Iso} - */ - default Iso andThen(Iso f) { - return unIso().into((sa, bt) -> f.unIso().into((ac, db) -> iso(sa.andThen(ac), db.andThen(bt)))); - } - - /** - * Right-to-left composition of {@link Iso}. - * - * @param g the iso to apply before this one - * @param the larger type that can be viewed as the first smaller type - * @param the larger type the second smaller type can be viewed as - * @return the composed {@link Iso} - */ - default Iso compose(Iso g) { - return g.andThen(this); - } - - @Override - default Lens compose(LensLike f) { - return toLens().compose(f); - } - - /** - * 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(Function f, - Function g) { - return new Iso() { - @Override - @SuppressWarnings("unchecked") - public

, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( - PAFB pafb) { - return (PSFT) pafb.diMap(f, fb -> (FT) fb.fmap(g)); - } - }; - } - - /** - * 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(Function f, Function g) { - return adapt(iso(f, g)); - } - - /** - * 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, LensLike.Simple { - - /** - * 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 - */ - 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 - */ - default Iso.Simple andThen(Iso.Simple f) { - return adapt(f.compose(this)); - } - - @Override - default Iso.Simple mirror() { - return adapt(Iso.super.mirror()); - } - - @Override - default Lens.Simple toLens() { - return Lens.Simple.adapt(Iso.super.toLens()); - } - - @Override - default Iso.Simple discardR(Applicative> appB) { - return adapt(Iso.super.discardR(appB)); - } - - @Override - default Lens.Simple compose(LensLike.Simple g) { - return toLens().compose(g); - } - - @Override - default Lens.Simple andThen(LensLike.Simple f) { - return toLens().andThen(f); - } - - /** - * Adapt an {@link Iso} with the right variance to an {@link Iso.Simple}. - * - * @param iso the iso - * @param S/T - * @param A/B - * @return the simple iso - */ - @SuppressWarnings("unchecked") - static Iso.Simple adapt(Iso iso) { - return iso::apply; - } - } -} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/lens/LensLike.java b/src/main/java/com/jnape/palatable/lambda/lens/LensLike.java deleted file mode 100644 index 5551e172a..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/LensLike.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.jnape.palatable.lambda.lens; - -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.monad.Monad; - -import java.util.function.Function; - -/** - * The generic supertype of all types that can be treated as lenses but should preserve type-specific return types in - * overrides. This type only exists to appease Java's unfortunate parametric type hierarchy constraints. If you're here, - * you're probably looking for {@link Lens} or {@link Iso}. - * - * @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 concrete lens subtype - * @see Lens - * @see Iso - */ -public interface LensLike extends Monad>, Profunctor> { - - , FB extends Functor> FT apply( - Function fn, S s); - - /** - * 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 - */ - LensLike compose(LensLike g); - - /** - * 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 - */ - LensLike andThen(LensLike f); - - /** - * 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 - */ - LensLike mapS(Function fn); - - /** - * 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 - */ - LensLike mapT(Function 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 - */ - LensLike mapA(Function fn); - - /** - * 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 - */ - LensLike mapB(Function fn); - - @Override - LensLike flatMap(Function>> f); - - @Override - LensLike pure(U u); - - @Override - default LensLike fmap(Function fn) { - return Monad.super.fmap(fn).coerce(); - } - - @Override - default LensLike zip( - Applicative, LensLike> appFn) { - return Monad.super.zip(appFn).coerce(); - } - - @Override - default LensLike discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); - } - - @Override - default LensLike discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); - } - - @Override - default LensLike diMapL(Function fn) { - return (LensLike) Profunctor.super.diMapL(fn); - } - - @Override - default LensLike diMapR(Function fn) { - return (LensLike) Profunctor.super.diMapR(fn); - } - - @Override - default LensLike diMap(Function lFn, - Function rFn) { - return this.mapS(lFn).mapT(rFn); - } - - @Override - default LensLike contraMap(Function fn) { - return (LensLike) Profunctor.super.contraMap(fn); - } - - /** - * A simpler type signature for lenses where S/T and A/B are equivalent. - * - * @param the "larger" type - * @param the "smaller" type - * @param the concrete lens subtype - */ - interface Simple extends LensLike { - - /** - * Compose two simple lenses from right to left. - * - * @param g the other simple lens - * @param the other simple lens' larger type - * @return the composed simple lens - */ - LensLike.Simple compose(LensLike.Simple g); - - /** - * Compose two simple lenses from left to right. - * - * @param f the other simple lens - * @param the other simple lens' smaller type - * @return the composed simple lens - */ - LensLike.Simple andThen(LensLike.Simple f); - } -} 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 5c1de0104..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.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.LensLike; - -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(LensLike lens, Function fn, S s) { - return lens., Identity>apply(fn.andThen((Function>) Identity::new), s).runIdentity(); - } - - @SuppressWarnings("unchecked") - public static Over over() { - return (Over) INSTANCE; - } - - public static Fn2, S, T> over(LensLike lens) { - return Over.over().apply(lens); - } - - public static Fn1 over(LensLike lens, Function fn) { - return over(lens).apply(fn); - } - - public static T over(LensLike 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 07c97b804..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.LensLike; - -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(LensLike lens, B b, S s) { - return over(lens, constantly(b), s); - } - - @SuppressWarnings("unchecked") - public static Set set() { - return INSTANCE; - } - - public static Fn2 set(LensLike lens) { - return Set.set().apply(lens); - } - - public static Fn1 set(LensLike lens, B b) { - return set(lens).apply(b); - } - - public static T set(LensLike lens, B b, S s) { - return set(lens, b).apply(s); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Under.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Under.java deleted file mode 100644 index 11c37a1e5..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/Under.java +++ /dev/null @@ -1,52 +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.lens.Iso; -import com.jnape.palatable.lambda.lens.LensLike; - -import java.util.function.Function; - -/** - * 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). - *

- * Note this is only possible for {@link Iso}s and not general {@link LensLike}s because of the mandatory need for the - * correspondence B -> T. - * - * @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, Function, B, A> { - - private static final Under INSTANCE = new Under(); - - private Under() { - } - - @Override - public A apply(Iso iso, Function fn, B b) { - return iso.unIso().into((sa, bt) -> bt.fmap(fn).fmap(sa)).apply(b); - } - - @SuppressWarnings("unchecked") - public static Under under() { - return INSTANCE; - } - - public static Fn2, B, A> under(Iso iso) { - return Under.under().apply(iso); - } - - public static Fn1 under(Iso iso, Function fn) { - return under(iso).apply(fn); - } - - public static A under(Iso iso, Function fn, B b) { - return under(iso, fn).apply(b); - } -} 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 a6d1cb883..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.LensLike; - -/** - * 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(LensLike lens, S s) { - return lens., Const, Const>apply(Const::new, s).runConst(); - } - - @SuppressWarnings("unchecked") - public static View view() { - return INSTANCE; - } - - public static Fn1 view(LensLike lens) { - return View.view().apply(lens); - } - - public static A view(LensLike lens, S s) { - return view(lens).apply(s); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java index 4c3e2e18d..1c5645905 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java @@ -1,16 +1,16 @@ 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 java.util.function.Function; +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(Function)}, a binding operation that maps the carrier value to a new monad instance in the same + * {@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: @@ -26,7 +26,7 @@ * @param the type of the parameter * @param the unification parameter to more tightly type-constrain Monads to themselves */ -public interface Monad extends Applicative { +public interface Monad> extends Applicative { /** * Chain dependent computations that may continue or short-circuit based on previous results. @@ -35,7 +35,7 @@ public interface Monad extends Applicative { * @param the resulting monad parameter type * @return the new monad instance */ - Monad flatMap(Function> f); + Monad flatMap(Fn1> f); /** * {@inheritDoc} @@ -47,16 +47,25 @@ public interface Monad extends Applicative { * {@inheritDoc} */ @Override - default Monad fmap(Function fn) { - return flatMap(fn.andThen(this::pure)); + 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 Monad zip(Applicative, M> appFn) { - return appFn., M>>coerce().flatMap(this::fmap); + default Lazy> lazyZip( + Lazy, M>> lazyAppFn) { + return Applicative.super.lazyZip(lazyAppFn).fmap(Applicative::coerce); } /** @@ -76,7 +85,7 @@ default Monad discardR(Applicative appB) { } /** - * Convenience static method equivalent to {@link Monad#flatMap(Function) flatMap}{@link Id#id() (id())}; + * Convenience static method equivalent to {@link Monad#flatMap(Fn1) flatMap}{@link Id#id() (id())}; * * @param mma the outer monad * @param the monad type @@ -84,7 +93,7 @@ default Monad discardR(Applicative appB) { * @param the nested monad * @return the nested monad */ - static > MA join(Monad mma) { + 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 6cdf12b1e..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,18 +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. @@ -57,15 +58,15 @@ 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 FoldLeft.foldLeft(this.toBiFunction(), identity(), map(fn, bs)); + default A foldMap(Fn1 fn, Iterable bs) { + return FoldLeft.foldLeft(this, identity(), map(fn, bs)); } /** @@ -80,8 +81,8 @@ default A foldLeft(A a, Iterable as) { * {@inheritDoc} */ @Override - default A foldRight(A a, Iterable as) { - return flip().foldMap(id(), reverse(cons(a, as))); + default Lazy foldRight(A a, Iterable as) { + return lazy(() -> flip().foldMap(id(), cons(a, reverse(as)))); } /** @@ -108,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 index 6cce48bfd..219763fd3 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/AddAll.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/AddAll.java @@ -1,13 +1,12 @@ 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 java.util.function.Function; -import java.util.function.Supplier; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; @@ -19,23 +18,23 @@ * * @see Monoid */ -public final class AddAll> implements MonoidFactory, C> { +public final class AddAll> implements MonoidFactory, C> { - private static final AddAll INSTANCE = new AddAll(); + private static final AddAll INSTANCE = new AddAll<>(); private AddAll() { } @Override - public Monoid apply(Supplier cSupplier) { + public Monoid checkedApply(Fn0 cFn0) { return new Monoid() { @Override public C identity() { - return cSupplier.get(); + return cFn0.apply(); } @Override - public C apply(C xs, C ys) { + public C checkedApply(C xs, C ys) { C c = identity(); c.addAll(xs); c.addAll(ys); @@ -43,7 +42,7 @@ public C apply(C xs, C ys) { } @Override - public C foldMap(Function fn, Iterable bs) { + public C foldMap(Fn1 fn, Iterable bs) { return FoldLeft.foldLeft((x, y) -> { x.addAll(y); return x; @@ -54,18 +53,18 @@ public C foldMap(Function fn, Iterable bs) { @SuppressWarnings("unchecked") public static > AddAll addAll() { - return INSTANCE; + return (AddAll) INSTANCE; } - public static > Monoid addAll(Supplier collectionSupplier) { - return AddAll.addAll().apply(collectionSupplier); + public static > Monoid addAll(Fn0 collectionFn0) { + return AddAll.addAll().apply(collectionFn0); } - public static > Fn1 addAll(Supplier collectionSupplier, C xs) { - return addAll(collectionSupplier).apply(xs); + public static > Fn1 addAll(Fn0 collectionFn0, C xs) { + return addAll(collectionFn0).apply(xs); } - public static > C addAll(Supplier collectionSupplier, C xs, C ys) { - return addAll(collectionSupplier, xs).apply(ys); + 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 3465ca0f8..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 @@ -4,8 +4,6 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.monoid.Monoid; -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.Not.not; import static com.jnape.palatable.lambda.functions.builtin.fn2.Find.find; @@ -29,20 +27,15 @@ 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(Function fn, Iterable bs) { + public Boolean foldMap(Fn1 fn, Iterable bs) { return find(not(fn), bs).fmap(constantly(false)).orElse(true); } - @Override - public boolean test(Boolean x, Boolean y) { - return apply(x, y); - } - @Override public And flip() { return this; 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 index ca27bb28f..dbffbe748 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Compose.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Compose.java @@ -6,8 +6,8 @@ import com.jnape.palatable.lambda.semigroup.Semigroup; import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; +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; @@ -24,20 +24,20 @@ */ public final class Compose implements MonoidFactory, CompletableFuture> { - private static final Compose INSTANCE = new Compose(); + private static final Compose INSTANCE = new Compose<>(); private Compose() { } @Override - public Monoid> apply(Monoid aMonoid) { + public Monoid> checkedApply(Monoid aMonoid) { return monoid(com.jnape.palatable.lambda.semigroup.builtin.Compose.compose(aMonoid), - (Supplier>) () -> completedFuture(aMonoid.identity())); + fn0(() -> completedFuture(aMonoid.identity()))); } @SuppressWarnings("unchecked") public static Compose compose() { - return INSTANCE; + return (Compose) INSTANCE; } public static Monoid> compose(Monoid aMonoid) { 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 2d164bbdc..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,11 +1,10 @@ package com.jnape.palatable.lambda.monoid.builtin; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iteration.ConcatenatingIterable; +import com.jnape.palatable.lambda.internal.iteration.ConcatenatingIterable; import com.jnape.palatable.lambda.monoid.Monoid; import java.util.Collections; -import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; @@ -17,7 +16,7 @@ */ public final class Concat implements Monoid> { - private static final Concat INSTANCE = new Concat(); + private static final Concat INSTANCE = new Concat<>(); private Concat() { } @@ -28,18 +27,18 @@ public Iterable identity() { } @Override - public Iterable apply(Iterable xs, Iterable ys) { + public Iterable checkedApply(Iterable xs, Iterable ys) { return new ConcatenatingIterable<>(xs, ys); } @Override - public Iterable foldMap(Function> fn, Iterable bs) { + public Iterable foldMap(Fn1> fn, Iterable bs) { return flatten(map(fn, bs)); } @SuppressWarnings("unchecked") public static Concat concat() { - return INSTANCE; + return (Concat) INSTANCE; } public static Fn1, Iterable> concat(Iterable xs) { 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 cb3b65f4c..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 @@ -4,8 +4,6 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.monoid.Monoid; -import java.util.function.Function; - 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; @@ -23,7 +21,7 @@ */ public final class First implements Monoid> { - private static final First INSTANCE = new First(); + private static final First INSTANCE = new First<>(); private First() { } @@ -34,18 +32,18 @@ public Maybe identity() { } @Override - public Maybe apply(Maybe x, Maybe y) { + public Maybe checkedApply(Maybe x, Maybe y) { return x.fmap(Maybe::just).orElse(y); } @Override - public Maybe foldMap(Function> fn, Iterable bs) { + 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, Maybe> first(Maybe x) { 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 bb039c09a..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 @@ -19,7 +19,7 @@ * @see Maybe */ public final class Last implements Monoid> { - private static final Last INSTANCE = new Last(); + private static final Last INSTANCE = new Last<>(); private Last() { } @@ -30,13 +30,13 @@ public Maybe identity() { } @Override - public Maybe apply(Maybe x, Maybe y) { + 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, Maybe> last(Maybe x) { 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 5026328fb..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 @@ -29,20 +29,20 @@ */ 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 index 104d6f7d5..0f5c5fcc9 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeMaps.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeMaps.java @@ -1,5 +1,6 @@ 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; @@ -8,7 +9,6 @@ import java.util.Map; import java.util.function.BiFunction; -import java.util.function.Supplier; /** * A {@link Monoid} instance formed by {@link java.util.Map#merge(Object, Object, BiFunction)} and a semigroup over @@ -19,43 +19,43 @@ * @see Monoid * @see java.util.Map */ -public final class MergeMaps implements BiMonoidFactory>, Semigroup, Map> { +public final class MergeMaps implements BiMonoidFactory>, Semigroup, Map> { - private static final MergeMaps INSTANCE = new MergeMaps(); + private static final MergeMaps INSTANCE = new MergeMaps<>(); private MergeMaps() { } @Override - public Monoid> apply(Supplier> mSupplier, Semigroup semigroup) { + public Monoid> checkedApply(Fn0> mFn0, Semigroup semigroup) { return Monoid.>monoid((x, y) -> { - Map copy = mSupplier.get(); + Map copy = mFn0.apply(); copy.putAll(x); y.forEach((k, v) -> copy.merge(k, v, semigroup.toBiFunction())); return copy; - }, mSupplier); + }, mFn0); } @SuppressWarnings("unchecked") public static MergeMaps mergeMaps() { - return INSTANCE; + return (MergeMaps) INSTANCE; } - public static MonoidFactory, Map> mergeMaps(Supplier> mSupplier) { - return MergeMaps.mergeMaps().apply(mSupplier); + public static MonoidFactory, Map> mergeMaps(Fn0> mFn0) { + return MergeMaps.mergeMaps().apply(mFn0); } - public static Monoid> mergeMaps(Supplier> mSupplier, Semigroup semigroup) { - return mergeMaps(mSupplier).apply(semigroup); + public static Monoid> mergeMaps(Fn0> mFn0, Semigroup semigroup) { + return mergeMaps(mFn0).apply(semigroup); } - public static Fn1, Map> mergeMaps(Supplier> mSupplier, Semigroup semigroup, + public static Fn1, Map> mergeMaps(Fn0> mFn0, Semigroup semigroup, Map x) { - return mergeMaps(mSupplier, semigroup).apply(x); + return mergeMaps(mFn0, semigroup).apply(x); } - public static Map mergeMaps(Supplier> mSupplier, Semigroup semigroup, Map x, + public static Map mergeMaps(Fn0> mFn0, Semigroup semigroup, Map x, Map y) { - return mergeMaps(mSupplier, semigroup, x).apply(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 a8157d0fb..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 @@ -4,8 +4,6 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.monoid.Monoid; -import java.util.function.Function; - import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.Find.find; @@ -28,18 +26,13 @@ public Boolean identity() { } @Override - public Boolean apply(Boolean x, Boolean y) { + public Boolean checkedApply(Boolean x, Boolean y) { return x || y; } @Override - public boolean test(Boolean x, Boolean y) { - return apply(x, y); - } - - @Override - public Boolean foldMap(Function fn, Iterable bs) { - return find(fn::apply, bs).fmap(constantly(true)).orElse(false); + public Boolean foldMap(Fn1 fn, Iterable bs) { + return find(fn, bs).fmap(constantly(true)).orElse(false); } @Override 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 cccf15d1b..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 @@ -29,20 +29,20 @@ */ public final class Present implements MonoidFactory, Maybe> { - private static final Present INSTANCE = new Present<>(); + private static final Present INSTANCE = new Present<>(); private Present() { } @Override - public Monoid> apply(Semigroup aSemigroup) { + 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 INSTANCE; + return (Present) INSTANCE; } public static Monoid> present(Semigroup semigroup) { 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 f357d5da4..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,20 +29,20 @@ */ 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 Monoid> rightAny(Monoid 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 index 8e3c0d869..7533f9c41 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RunAll.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RunAll.java @@ -1,12 +1,12 @@ package com.jnape.palatable.lambda.monoid.builtin; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.IO; 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.functions.IO.io; +import static com.jnape.palatable.lambda.io.IO.io; import static com.jnape.palatable.lambda.monoid.Monoid.monoid; /** @@ -17,20 +17,20 @@ */ public final class RunAll implements MonoidFactory, IO> { - private static final RunAll INSTANCE = new RunAll(); + private static final RunAll INSTANCE = new RunAll<>(); private RunAll() { } @Override - public Monoid> apply(Monoid monoid) { + 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 INSTANCE; + return (RunAll) INSTANCE; } public static Monoid> runAll(Monoid monoid) { 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 index d119e0a50..283bd2980 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Union.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Union.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.builtin.fn1.Distinct; -import com.jnape.palatable.lambda.iteration.UnioningIterable; +import com.jnape.palatable.lambda.internal.iteration.UnioningIterable; import com.jnape.palatable.lambda.monoid.Monoid; import java.util.Collections; @@ -15,7 +15,7 @@ */ public final class Union implements Monoid> { - private static final Union INSTANCE = new Union(); + private static final Union INSTANCE = new Union<>(); private Union() { } @@ -26,13 +26,13 @@ public Iterable identity() { } @Override - public Iterable apply(Iterable xs, Iterable ys) { + public Iterable checkedApply(Iterable xs, Iterable ys) { return new UnioningIterable<>(xs, ys); } @SuppressWarnings("unchecked") public static Union union() { - return INSTANCE; + return (Union) INSTANCE; } public static Fn1, Iterable> union(Iterable xs) { 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 index 860df1e6f..8ed724ad6 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java @@ -26,15 +26,10 @@ public Boolean identity() { } @Override - public Boolean apply(Boolean x, Boolean y) { + public Boolean checkedApply(Boolean x, Boolean y) { return x ^ y; } - @Override - public boolean test(Boolean x, Boolean y) { - return apply(x, y); - } - @Override public Xor flip() { return this; 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/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/optics/Lens.java similarity index 52% rename from src/main/java/com/jnape/palatable/lambda/lens/Lens.java rename to src/main/java/com/jnape/palatable/lambda/optics/Lens.java index 3f832d351..f2b0c23d6 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/Lens.java @@ -1,20 +1,26 @@ -package com.jnape.palatable.lambda.lens; +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 java.util.function.BiFunction; -import java.util.function.Function; - -import static com.jnape.palatable.lambda.lens.Iso.iso; -import static com.jnape.palatable.lambda.lens.Lens.Simple.adapt; -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.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. @@ -135,79 +141,138 @@ * @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 LensLike { +public interface Lens extends + Optic, Functor, S, T, A, B>, + MonadRec>, + Profunctor> { + /** + * {@inheritDoc} + */ @Override - default Lens fmap(Function fn) { - return LensLike.super.fmap(fn).coerce(); + 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, LensLike> appFn) { - return LensLike.super.zip(appFn).coerce(); + default Lens zip(Applicative, Lens> appFn) { + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override - default Lens discardL(Applicative> appB) { - return LensLike.super.discardL(appB).coerce(); + default Lens discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - default Lens discardR(Applicative> appB) { - return LensLike.super.discardR(appB).coerce(); + default Lens discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - default Lens flatMap(Function>> f) { + 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(Function fn) { - return LensLike.super.diMapL(fn).coerce(); + default Lens diMapL(Fn1 fn) { + return (Lens) Profunctor.super.diMapL(fn); } + /** + * {@inheritDoc} + */ @Override - default Lens diMapR(Function fn) { - return LensLike.super.diMapR(fn).coerce(); + default Lens diMapR(Fn1 fn) { + return (Lens) Profunctor.super.diMapR(fn); } + /** + * {@inheritDoc} + */ @Override - default Lens diMap(Function lFn, - Function rFn) { - return LensLike.super.diMap(lFn, rFn).coerce(); + default Lens diMap(Fn1 lFn, Fn1 rFn) { + return this.mapS(lFn).mapT(rFn); } + /** + * {@inheritDoc} + */ @Override - default Lens contraMap(Function fn) { - return LensLike.super.contraMap(fn).coerce(); + default Lens contraMap(Fn1 fn) { + return (Lens) Profunctor.super.contraMap(fn); } + /** + * {@inheritDoc} + */ @Override - default Lens mapS(Function fn) { - return lens(view(this).compose(fn), (r, b) -> set(this, b, fn.apply(r))); + default Lens mapS(Fn1 fn) { + return lens(Optic.super.mapS(fn)); } + /** + * {@inheritDoc} + */ @Override - default Lens mapT(Function fn) { - return fmap(fn); + default Lens mapT(Fn1 fn) { + return lens(Optic.super.mapT(fn)); } + /** + * {@inheritDoc} + */ @Override - default Lens mapA(Function fn) { - return andThen(lens(fn, (a, b) -> b)); + default Lens mapA(Fn1 fn) { + return lens(Optic.super.mapA(fn)); } + /** + * {@inheritDoc} + */ @Override - default Lens mapB(Function fn) { - return lens(view(this), (s, z) -> set(this, fn.apply(z), s)); + default Lens mapB(Fn1 fn) { + return lens(Optic.super.mapB(fn)); } /** @@ -220,14 +285,20 @@ default Iso toIso(S s) { return iso(view(this), set(this).flip().apply(s)); } + /** + * {@inheritDoc} + */ @Override - default Lens andThen(LensLike f) { - return lens(view(this).fmap(view(f)), (q, b) -> over(this, set(f, b), q)); + default Lens andThen(Optic, ? super Functor, A, B, C, D> f) { + return lens(Optic.super.andThen(f)); } + /** + * {@inheritDoc} + */ @Override - default Lens compose(LensLike g) { - return lens(view(g).fmap(view(this)), (q, b) -> over(g, set(this, b), q)); + default Lens compose(Optic, ? super Functor, R, U, S, T> g) { + return lens(Optic.super.compose(g)); } /** @@ -241,15 +312,39 @@ default Lens compose(LensLike g) { * @param the type of the "smaller" update value * @return the lens */ - static Lens lens(Function getter, - BiFunction setter) { + 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 - @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)); + public >, + CoF extends Functor>, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return optic.apply(pafb); } }; } @@ -263,8 +358,8 @@ public , FB extends Functor> F * @param the type of both "smaller" values * @return the lens */ - static Lens.Simple simpleLens(Function getter, - BiFunction setter) { + static Lens.Simple simpleLens(Fn1 getter, + Fn2 setter) { return adapt(lens(getter, setter)); } @@ -282,7 +377,7 @@ static Lens.Simple simpleLens(Function gett * @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::compose).apply(s)); + return lens(Both.both(view(f), view(g)), (s, cd) -> cd.biMap(set(f), set(g)).into(Fn1::contraMap).apply(s)); } /** @@ -299,6 +394,24 @@ static Lens.Simple> both(Lens.Simple f, Lens.Sim 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. @@ -307,14 +420,22 @@ static Lens.Simple> both(Lens.Simple f, Lens.Sim * @param the type of both "smaller" values */ @FunctionalInterface - interface Simple extends Lens, LensLike.Simple { + interface Simple extends Lens, Optic.Simple, Functor, S, A> { - default Lens.Simple compose(LensLike.Simple g) { - return Lens.Simple.adapt(Lens.super.compose(g)); + /** + * {@inheritDoc} + */ + @Override + default Lens.Simple andThen(Optic.Simple, ? super Functor, A, B> f) { + return Lens.Simple.adapt(Lens.super.andThen(f)); } - default Lens.Simple andThen(LensLike.Simple 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)); } /** @@ -325,9 +446,17 @@ default Lens.Simple andThen(LensLike.Simple f) { * @param A/B * @return the simple lens */ - @SuppressWarnings("unchecked") - static Lens.Simple adapt(Lens lens) { - return lens::apply; + 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); + } + }; } /** 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

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

, 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 82% 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 b43c0e596..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); } @@ -40,8 +40,7 @@ public static > Lens.Simple asCopy(Function< * @param the type of the collection * @return a lens that focuses on a Collection as a Set */ - public static > Lens.Simple> asSet( - Function copyFn) { + public static > Lens.Simple> asSet(Fn1 copyFn) { return simpleLens(HashSet::new, (xsL, xsS) -> { Set missing = new HashSet<>(xsS); missing.removeAll(xsL); @@ -63,9 +62,10 @@ public static > Lens.Simple> asSet( * @param the type of the collection * @return a lens that focuses on a Collection as a stream. */ + @SuppressWarnings("RedundantTypeArguments") public static > Lens.Simple> asStream( - Function copyFn) { - return simpleLens(Collection::stream, (xsL, xsS) -> { + Fn1 copyFn) { + return simpleLens(Collection::stream, (xsL, xsS) -> { CX updated = copyFn.apply(xsL); updated.clear(); xsS.forEach(updated::add); diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/EitherLens.java similarity index 83% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java rename to src/main/java/com/jnape/palatable/lambda/optics/lenses/EitherLens.java index 50b840e5c..8463d0eb1 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/EitherLens.java @@ -1,14 +1,14 @@ -package com.jnape.palatable.lambda.lens.lenses; +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.lens.Lens; +import com.jnape.palatable.lambda.optics.Lens; -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; /** - * Lenses that operate on {@link Either}s. + * Lenses for {@link Either}. */ public final class EitherLens { @@ -26,7 +26,7 @@ private EitherLens() { * @param the right parameter type * @return a lens that focuses on right values */ - public static Lens.Simple, Maybe> right() { + public static Lens.Simple, Maybe> _right() { return simpleLens(CoProduct2::projectB, (lOrR, maybeR) -> maybeR.>fmap(Either::right).orElse(lOrR)); } @@ -41,7 +41,7 @@ public static Lens.Simple, Maybe> right() { * @param the right parameter type * @return a lens that focuses on left values */ - public static Lens.Simple, Maybe> left() { + 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 90% 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 bc0ce452b..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,12 +1,12 @@ -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. diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/HMapLens.java similarity index 81% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java rename to src/main/java/com/jnape/palatable/lambda/optics/lenses/HMapLens.java index c7fd953da..f98446254 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/HMapLens.java @@ -1,11 +1,11 @@ -package com.jnape.palatable.lambda.lens.lenses; +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.lens.Lens; +import com.jnape.palatable.lambda.optics.Lens; -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; /** * Lenses that operate on {@link HMap}s. diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/IterableLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/IterableLens.java similarity index 79% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/IterableLens.java rename to src/main/java/com/jnape/palatable/lambda/optics/lenses/IterableLens.java index cccf68730..b0f125c14 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/IterableLens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/IterableLens.java @@ -1,18 +1,18 @@ -package com.jnape.palatable.lambda.lens.lenses; +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.lens.Iso; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Lens; -import static com.jnape.palatable.lambda.functions.Fn2.fn2; +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.lens.Iso.simpleIso; -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.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. @@ -45,7 +45,7 @@ public static Lens.Simple, Maybe> head() { * @return a lens focusing on the tail of an {@link Iterable} */ public static Lens.Simple, Iterable> tail() { - return simpleLens(Tail::tail, fn2(Head.head().andThen(o -> o.fmap(cons()).orElse(id()))).toBiFunction()); + return simpleLens(Tail::tail, curried(Head.head().fmap(o -> o.fmap(cons()).orElse(id())))); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/ListLens.java similarity index 88% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java rename to src/main/java/com/jnape/palatable/lambda/optics/lenses/ListLens.java index b7857df2a..383c1a226 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/ListLens.java @@ -1,7 +1,7 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.optics.lenses; import com.jnape.palatable.lambda.adt.Maybe; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.optics.Lens; import java.util.ArrayList; import java.util.List; @@ -9,9 +9,9 @@ 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.lens.Lens.simpleLens; -import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.unLiftA; -import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.unLiftB; +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; /** @@ -73,8 +73,7 @@ public static Lens.Simple, Maybe> elementAt(int 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 unLiftB(unLiftA(elementAt(index), defaultValue))::apply; + return Lens.Simple.adapt(unLiftB(unLiftA(elementAt(index), defaultValue))); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/MapLens.java similarity index 82% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java rename to src/main/java/com/jnape/palatable/lambda/optics/lenses/MapLens.java index e6df4f236..729ca0b23 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/MapLens.java @@ -1,12 +1,12 @@ -package com.jnape.palatable.lambda.lens.lenses; +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.IO; import com.jnape.palatable.lambda.functions.builtin.fn2.Filter; -import com.jnape.palatable.lambda.lens.Iso; -import com.jnape.palatable.lambda.lens.Lens; +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; @@ -14,19 +14,21 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.function.Function; 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.lens.Lens.Simple.adapt; -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.MaybeLens.unLiftA; -import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.unLiftB; +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. @@ -47,7 +49,7 @@ private MapLens() { * @return a lens that focuses on copies of maps as a specific subtype */ public static , K, V> Lens, M, M, M> asCopy( - Function, ? extends M> copyFn) { + Fn1, ? extends M> copyFn) { return lens(copyFn, (__, copy) -> copy); } @@ -66,18 +68,18 @@ public static Lens.Simple, Map> asCopy() { * 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 - * @param k the key to focus on - * @param copyFn the copy function * @return a lens that focuses on the value at key, as a {@link Maybe} */ public static , K, V> Lens, M, Maybe, Maybe> valueAt( - Function, ? extends M> copyFn, K k) { + Fn1, ? extends M> copyFn, K k) { return lens(m -> maybe(m.get(k)), (m, maybeV) -> maybeV - .>>fmap(v -> alter(updated -> updated.put(k, v))) - .orElse(alter(updated -> updated.remove(k))) + .>>fmap(v -> alter(effect(updated -> io(() -> updated.put(k, v))))) + .orElse(alter(updated -> io(sideEffect(() -> updated.remove(k))))) .apply(copyFn.apply(m)) .unsafePerformIO()); } @@ -119,9 +121,9 @@ public static Lens.Simple, V> valueAt(K k, V defaultValue) { */ 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(); + 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)); @@ -142,11 +144,11 @@ public static Lens.Simple, Set> keys() { */ 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); + Map updated = new HashMap<>(m); + Set valueSet = new HashSet<>(vs); Set matchingKeys = Filter.>filter(kv -> valueSet.contains(kv.getValue())) - .andThen(map(Map.Entry::getKey)) - .andThen(toCollection(HashSet::new)) + .fmap(map(Map.Entry::getKey)) + .fmap(toCollection(HashSet::new)) .apply(updated.entrySet()); updated.keySet().retainAll(matchingKeys); return updated; diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/MaybeLens.java similarity index 97% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java rename to src/main/java/com/jnape/palatable/lambda/optics/lenses/MaybeLens.java index 0917b07ae..5bb9f93da 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/MaybeLens.java @@ -1,9 +1,9 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.optics.lenses; import com.jnape.palatable.lambda.adt.Maybe; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.optics.Lens; -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; /** * Lenses that operate on {@link Maybe}. diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/SetLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/SetLens.java similarity index 73% rename from src/main/java/com/jnape/palatable/lambda/lens/lenses/SetLens.java rename to src/main/java/com/jnape/palatable/lambda/optics/lenses/SetLens.java index b48ceea24..75c7ab756 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/SetLens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/SetLens.java @@ -1,12 +1,12 @@ -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.HashSet; import java.util.Set; -import java.util.function.Function; -import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; /** * Lenses that operate on {@link Set}s. @@ -26,8 +26,8 @@ private SetLens() { * @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( - Function copyFn, A a) { + public static > Lens.Simple contains(Fn1 copyFn, + A a) { return simpleLens(setA -> setA.contains(a), (setA, include) -> { SetA copy = copyFn.apply(setA); @@ -38,8 +38,8 @@ public static > Lens.Simple contains( } /** - * A lens that focuses on whether a {@link Set} contains some value a. Like {@link #contains(Function, - * Object)} but with an implicit copy function that produces {@link HashSet}s. + * 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 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 b82aea644..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,8 +38,8 @@ 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); } /** 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 index 9c5392860..222a5e7dc 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java @@ -2,11 +2,21 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2; +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 @@ -25,23 +35,23 @@ */ public final class Absent implements SemigroupFactory, Maybe> { - private static final Absent INSTANCE = new Absent<>(); + private static final Absent INSTANCE = new Absent<>(); private Absent() { } @Override - public Semigroup> apply(Semigroup aSemigroup) { - return LiftA2., Maybe, Maybe>liftA2(aSemigroup.toBiFunction())::apply; + public Semigroup> checkedApply(Semigroup aSemigroup) { + return shortCircuitSemigroup(aSemigroup); } @SuppressWarnings("unchecked") public static Absent absent() { - return INSTANCE; + return (Absent) INSTANCE; } - public static Semigroup> absent(Semigroup semigroup) { - return Absent.absent().apply(semigroup); + public static Semigroup> absent(Semigroup aSemigroup) { + return shortCircuitSemigroup(aSemigroup); } public static Fn1, Maybe> absent(Semigroup aSemigroup, Maybe x) { @@ -51,4 +61,34 @@ public static Fn1, Maybe> absent(Semigroup aSemigroup, Maybe< 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 index 9857ab413..cba9a218f 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Compose.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Compose.java @@ -20,19 +20,19 @@ */ public final class Compose implements SemigroupFactory, CompletableFuture> { - private static final Compose INSTANCE = new Compose(); + private static final Compose INSTANCE = new Compose<>(); private Compose() { } @Override - public Semigroup> apply(Semigroup aSemigroup) { + 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 INSTANCE; + return (Compose) INSTANCE; } public static Semigroup> compose(Semigroup aSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersection.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Intersection.java similarity index 78% rename from src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersection.java rename to src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Intersection.java index 178b7be8c..cf33e9b3a 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersection.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Intersection.java @@ -1,8 +1,8 @@ -package com.jnape.palatable.lambda.functions.builtin.fn2; +package com.jnape.palatable.lambda.semigroup.builtin; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.Fn2; 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; @@ -16,21 +16,21 @@ * * @param the {@link Iterable} element type */ -public final class Intersection implements Fn2, Iterable, Iterable> { +public final class Intersection implements Semigroup> { - private static final Intersection INSTANCE = new Intersection(); + private static final Intersection INSTANCE = new Intersection<>(); private Intersection() { } @Override - public Iterable apply(Iterable xs, Iterable ys) { + 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 INSTANCE; + return (Intersection) INSTANCE; } public static Fn1, Iterable> intersection(Iterable xs) { 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 b8955b2e0..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) { + 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 b93133143..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,13 +29,13 @@ */ 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) { + public Semigroup> checkedApply(Semigroup lSemigroup) { return (x, y) -> x.match(xL -> y.match(yL -> left(lSemigroup.apply(xL, yL)), yR -> left(xL)), xR -> y); @@ -43,7 +43,7 @@ public Semigroup> apply(Semigroup lSemigroup) { @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 index 3687a5187..a9cc3db66 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Max.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Max.java @@ -20,19 +20,19 @@ */ public final class Max> implements Semigroup { - private static final Max INSTANCE = new Max(); + private static final Max INSTANCE = new Max<>(); private Max() { } @Override - public A apply(A x, A y) { + public A checkedApply(A x, A y) { return maxBy(id(), x, y); } @SuppressWarnings("unchecked") public static > Max max() { - return INSTANCE; + return (Max) INSTANCE; } public static > Fn1 max(A x) { 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 index 5e6cbbe5c..2abee980e 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java @@ -4,8 +4,6 @@ import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; import com.jnape.palatable.lambda.semigroup.Semigroup; -import java.util.function.Function; - import static com.jnape.palatable.lambda.functions.builtin.fn3.LTBy.ltBy; /** @@ -20,35 +18,35 @@ * @param the value type * @param the mapped comparison type * @see Max + * @see MaxWith * @see MinBy */ -public final class MaxBy> implements SemigroupFactory, A> { +public final class MaxBy> implements SemigroupFactory, A> { - private static final MaxBy INSTANCE = new MaxBy(); + private static final MaxBy INSTANCE = new MaxBy<>(); private MaxBy() { } @Override - public Semigroup apply(Function compareFn) { + public Semigroup checkedApply(Fn1 compareFn) { return (x, y) -> ltBy(compareFn, y, x) ? y : x; } @SuppressWarnings("unchecked") public static > MaxBy maxBy() { - return INSTANCE; + return (MaxBy) INSTANCE; } - public static > Semigroup maxBy( - Function compareFn) { + public static > Semigroup maxBy(Fn1 compareFn) { return MaxBy.maxBy().apply(compareFn); } - public static > Fn1 maxBy(Function compareFn, A x) { + public static > Fn1 maxBy(Fn1 compareFn, A x) { return MaxBy.maxBy(compareFn).apply(x); } - public static > A maxBy(Function compareFn, A x, A y) { + 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: + *

+ * + * @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 index 742055ed5..747bd5d25 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Min.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Min.java @@ -20,19 +20,19 @@ */ public final class Min> implements Semigroup { - private static final Min INSTANCE = new Min(); + private static final Min INSTANCE = new Min<>(); private Min() { } @Override - public A apply(A x, A y) { + public A checkedApply(A x, A y) { return minBy(id(), x, y); } @SuppressWarnings("unchecked") public static > Min min() { - return INSTANCE; + return (Min) INSTANCE; } public static > Fn1 min(A x) { 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 index 3a7ea9a71..07ee12999 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinBy.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinBy.java @@ -4,8 +4,6 @@ import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; import com.jnape.palatable.lambda.semigroup.Semigroup; -import java.util.function.Function; - import static com.jnape.palatable.lambda.functions.builtin.fn3.GTBy.gtBy; /** @@ -22,33 +20,32 @@ * @see Min * @see MaxBy */ -public final class MinBy> implements SemigroupFactory, A> { +public final class MinBy> implements SemigroupFactory, A> { - private static final MinBy INSTANCE = new MinBy(); + private static final MinBy INSTANCE = new MinBy<>(); private MinBy() { } @Override - public Semigroup apply(Function compareFn) { + public Semigroup checkedApply(Fn1 compareFn) { return (x, y) -> gtBy(compareFn, y, x) ? y : x; } @SuppressWarnings("unchecked") public static > MinBy minBy() { - return INSTANCE; + return (MinBy) INSTANCE; } - public static > Semigroup minBy( - Function compareFn) { + public static > Semigroup minBy(Fn1 compareFn) { return MinBy.minBy().apply(compareFn); } - public static > Fn1 minBy(Function compareFn, A x) { + public static > Fn1 minBy(Fn1 compareFn, A x) { return MinBy.minBy(compareFn).apply(x); } - public static > A minBy(Function compareFn, A x, A y) { + 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 00f528054..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) { + public Semigroup> checkedApply(Semigroup rSemigroup) { return (x, y) -> x.match(constantly(y), xR -> y.match(constantly(right(xR)), - rSemigroup.apply(xR).andThen(Either::right))); + 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 index 5ae09b8ac..f84d0243a 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RunAll.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RunAll.java @@ -1,8 +1,8 @@ package com.jnape.palatable.lambda.semigroup.builtin; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.IO; import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.io.IO; import com.jnape.palatable.lambda.semigroup.Semigroup; /** @@ -13,19 +13,19 @@ */ public final class RunAll implements SemigroupFactory, IO> { - private static final RunAll INSTANCE = new RunAll(); + private static final RunAll INSTANCE = new RunAll<>(); private RunAll() { } @Override - public Semigroup> apply(Semigroup semigroup) { + public Semigroup> checkedApply(Semigroup semigroup) { return (ioX, ioY) -> ioY.zip(ioX.fmap(semigroup)); } @SuppressWarnings("unchecked") public static RunAll runAll() { - return INSTANCE; + return (RunAll) INSTANCE; } public static Semigroup> runAll(Semigroup semigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java index 8a40c9cb5..0134549b1 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java @@ -1,16 +1,23 @@ 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 java.util.function.Function; 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.functions.builtin.fn3.FoldRight.foldRight; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static java.util.Collections.emptyList; import static java.util.Collections.singleton; @@ -20,7 +27,10 @@ * @param the {@link Iterable} element type * @see LambdaMap */ -public final class LambdaIterable implements Monad, Traversable { +public final class LambdaIterable implements + MonadRec>, + Traversable> { + private final Iterable as; @SuppressWarnings("unchecked") @@ -37,11 +47,17 @@ public Iterable unwrap() { return as; } + /** + * {@inheritDoc} + */ @Override - public LambdaIterable fmap(Function fn) { + public LambdaIterable fmap(Fn1 fn) { return wrap(map(fn, as)); } + /** + * {@inheritDoc} + */ @Override public LambdaIterable pure(B b) { return wrap(singleton(b)); @@ -58,40 +74,80 @@ public LambdaIterable pure(B b) { * @return the zipped LambdaIterable */ @Override - public LambdaIterable zip(Applicative, LambdaIterable> appFn) { - return Monad.super.zip(appFn).coerce(); + 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 Monad.super.discardL(appB).coerce(); + public LambdaIterable discardL(Applicative> appB) { + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public LambdaIterable discardR(Applicative appB) { - return Monad.super.discardR(appB).coerce(); + public LambdaIterable discardR(Applicative> appB) { + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public LambdaIterable flatMap(Function> f) { + 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 , AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, - Function pure) { - return foldRight((a, appTrav) -> (AppTrav) appTrav.zip(fn.apply(a).fmap(b -> bs -> (TravB) wrap(cons(b, ((LambdaIterable) bs).unwrap())))), - (AppTrav) pure.apply((TravB) LambdaIterable.empty()), - as); + 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(); + Iterator ys = ((LambdaIterable) other).as.iterator(); while (xs.hasNext() && ys.hasNext()) if (!Objects.equals(xs.next(), ys.next())) @@ -127,4 +183,18 @@ public static LambdaIterable wrap(Iterable as) { 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 index 20205a419..f8e8c7fd2 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaMap.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaMap.java @@ -1,6 +1,6 @@ package com.jnape.palatable.lambda.traversable; -import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; @@ -8,9 +8,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.function.Function; 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; @@ -40,20 +40,21 @@ public Map unwrap() { } @Override - public LambdaMap fmap(Function fn) { + 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 >, AppC extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return foldLeft(Fn2., AppTrav>fn2(appTrav -> into((k, appV) -> (AppTrav) appTrav.zip(appV.fmap(v -> m -> { + 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 (TravC) m; - })))).toBiFunction(), + return m; + })))), pure.apply((TravC) LambdaMap.wrap(new HashMap<>())), - this.fmap(fn).unwrap().entrySet()); + this.fmap(fn).unwrap().entrySet()); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java index aff6884da..54caa1317 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java @@ -1,11 +1,10 @@ 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; -import java.util.function.Function; - /** * 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 @@ -30,27 +29,29 @@ * @param The type of the parameter * @param The unification parameter */ -public interface Traversable extends Functor { +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 fn the function to apply - * @param pure the applicative pure function * @param the resulting element type * @param the result applicative type * @param this Traversable instance over B - * @param the result applicative 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 */ - , AppB extends Applicative, + , TravB extends Traversable, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure); + Fn1> fn, Fn1 pure); + /** + * {@inheritDoc} + */ @Override - default Traversable fmap(Function fn) { + 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 e43e2438f..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,5 +1,6 @@ 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; @@ -11,11 +12,10 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; 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.fromMaybe; import static com.jnape.palatable.lambda.adt.Either.left; @@ -23,10 +23,12 @@ 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 { @@ -34,14 +36,25 @@ public class EitherTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @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)); @@ -50,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")); @@ -59,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)); @@ -68,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)); @@ -81,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)); @@ -92,7 +105,7 @@ public void filterLiftsRight() { @Test public void filterSupportsFunctionFromRToL() { - Either left = left("foo"); + Either left = left("foo"); Either right = right(1); assertThat(left.filter(x -> true, Object::toString), is(left)); @@ -103,7 +116,7 @@ public void filterSupportsFunctionFromRToL() { @Test public void monadicFlatMapLiftsRightAndFlattensBackToEither() { - Either left = left("foo"); + Either left = left("foo"); Either right = right(1); assertThat(left.flatMap(r -> right(r + 1)), is(left("foo"))); @@ -112,14 +125,14 @@ public void monadicFlatMapLiftsRightAndFlattensBackToEither() { @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)); @@ -129,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")); @@ -144,7 +157,7 @@ public void toMaybeMapsEitherToOptional() { @Test public void fromMaybeMapsMaybeToEither() { - Maybe just = just(1); + Maybe just = just(1); Maybe nothing = nothing(); assertThat(fromMaybe(just, () -> "fail"), is(right(1))); @@ -153,8 +166,8 @@ public void fromMaybeMapsMaybeToEither() { @Test public void fromMaybeDoesNotEvaluateLeftFnForRight() { - Maybe just = just(1); - AtomicInteger atomicInteger = new AtomicInteger(0); + Maybe just = just(1); + AtomicInteger atomicInteger = new AtomicInteger(0); fromMaybe(just, atomicInteger::incrementAndGet); assertThat(atomicInteger.get(), is(0)); @@ -194,33 +207,16 @@ public void monadTryingWithRunnable() { } @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 lazyZip() { + assertEquals(right(2), right(1).lazyZip(lazy(right(x -> x + 1))).value()); + assertEquals(left("foo"), left("foo").lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); } @Test - public void dyadicPeekDuallyLiftsIO() { - Either left = left("foo"); - Either right = right(1); - - AtomicReference stringRef = new AtomicReference<>(); - AtomicInteger intRef = new AtomicInteger(); - - left.peek(stringRef::set, intRef::set); - assertEquals("foo", stringRef.get()); - assertEquals(0, intRef.get()); - - right.peek(stringRef::set, intRef::set); - assertEquals("foo", stringRef.get()); - assertEquals(1, intRef.get()); + 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 index 1e747dc34..6668dbc19 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java @@ -10,30 +10,39 @@ 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 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.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}) + @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); @@ -93,16 +102,6 @@ public void fromEither() { assertEquals(nothing(), Maybe.fromEither(left("failure"))); } - @Test - public void peek() { - AtomicInteger ref = new AtomicInteger(0); - assertEquals(just(1), just(1).peek(__ -> ref.incrementAndGet())); - assertEquals(1, ref.get()); - - assertEquals(nothing(), nothing().peek(__ -> ref.incrementAndGet())); - assertEquals(1, ref.get()); - } - @Test public void justOrThrow() { just(1).orElseThrow(IllegalStateException::new); @@ -116,7 +115,7 @@ public void nothingOrThrow() { @Test public void divergesIntoChoice3() { assertEquals(Choice3.a(UNIT), nothing().diverge()); - assertEquals(Choice3.b(1), just(1).diverge()); + assertEquals(Choice3.b(1), just(1).diverge()); } @Test @@ -130,4 +129,18 @@ 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 index ba44c9caf..e9f764d49 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java @@ -3,23 +3,60 @@ 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, TraversableLaws.class, BifunctorLaws.class}) + @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 index f0c7cb043..dd4ff5dfb 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java @@ -10,6 +10,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; import java.io.IOException; @@ -23,10 +24,13 @@ 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; @@ -35,21 +39,31 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static testsupport.matchers.LeftMatcher.isLeftThat; +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}) - public Subjects> testSubject() { + @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()) + Try caught = Try.failure(new RuntimeException()) .catching(__ -> false, r -> "caught first") .catching(__ -> true, r -> "caught second"); @@ -58,7 +72,7 @@ public void catchingWithGenericPredicate() { @Test public void catchingIsANoOpForSuccess() { - Try caught = Try.success("success") + Try caught = success("success") .catching(__ -> true, __ -> "caught"); assertEquals(success("success"), caught); @@ -66,7 +80,7 @@ public void catchingIsANoOpForSuccess() { @Test public void firstMatchingCatchBlockWins() { - Try caught = Try.failure(new IllegalStateException()) + Try caught = Try.failure(new IllegalStateException()) .catching(__ -> true, __ -> "first") .catching(__ -> true, __ -> "second"); @@ -75,7 +89,7 @@ public void firstMatchingCatchBlockWins() { @Test public void catchBasedOnExceptionType() { - Try caught = Try.failure(new IllegalStateException()) + Try caught = Try.failure(new IllegalStateException()) .catching(IllegalArgumentException.class, __ -> "illegal argument exception") .catching(IllegalStateException.class, __ -> "illegal state exception") .catching(RuntimeException.class, __ -> "runtime exception"); @@ -86,7 +100,7 @@ public void catchBasedOnExceptionType() { @Test public void ensureIfSuccess() { AtomicInteger invocations = new AtomicInteger(0); - Try.success(1).ensuring((invocations::incrementAndGet)); + success(1).ensuring((invocations::incrementAndGet)); assertEquals(1, invocations.get()); } @@ -100,9 +114,9 @@ public void ensureIfFailure() { @Test public void exceptionThrownInEnsuringBlockIsCaught() { IllegalStateException expected = new IllegalStateException(); - assertEquals(Try.failure(expected), Try.success(1).ensuring(() -> {throw expected;})); + assertEquals(Try.failure(expected), success(1).ensuring(() -> {throw expected;})); - Either actual = Try.failure(new IllegalArgumentException()) + Either actual = Try.failure(new IllegalArgumentException()) .ensuring(() -> { throw expected;}) .toEither(); assertThat(actual, isLeftThat(instanceOf(IllegalArgumentException.class))); @@ -112,14 +126,14 @@ public void exceptionThrownInEnsuringBlockIsCaught() { @Test public void forfeitEnsuresFailure() { IllegalStateException expected = new IllegalStateException(); - assertEquals(expected, Try.failure(expected).forfeit(__ -> new IllegalArgumentException())); - assertEquals(expected, Try.success(1).forfeit(__ -> expected)); + 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))); + assertEquals((Integer) 1, Try.success(1).recover(constantly(1))); + assertEquals((Integer) 1, Try.failure(new IllegalArgumentException()).recover(constantly(1))); } @Test @@ -133,13 +147,13 @@ public void orThrow() throws Throwable { @Test public void toMaybe() { - assertEquals(just("foo"), Try.success("foo").toMaybe()); + assertEquals(just("foo"), success("foo").toMaybe()); assertEquals(nothing(), Try.failure(new IllegalStateException()).toMaybe()); } @Test public void toEither() { - assertEquals(right("foo"), Try.success("foo").toEither()); + assertEquals(right("foo"), success("foo").toEither()); IllegalStateException exception = new IllegalStateException(); assertEquals(left(exception), Try.failure(exception).toEither()); @@ -147,7 +161,7 @@ public void toEither() { @Test public void toEitherWithLeftMappingFunction() { - assertEquals(right(1), Try.success(1).toEither(__ -> "fail")); + assertEquals(right(1), success(1).toEither(__ -> "fail")); assertEquals(left("fail"), Try.failure(new IllegalStateException("fail")).toEither(Throwable::getMessage)); } @@ -162,39 +176,39 @@ public void tryingCatchesAnyThrowableThrownDuringEvaluation() { @Test public void withResourcesCleansUpAutoCloseableInSuccessCase() { AtomicBoolean closed = new AtomicBoolean(false); - assertEquals(Try.success(1), Try.withResources(() -> () -> closed.set(true), resource -> success(1))); + assertEquals(success(1), withResources(() -> (AutoCloseable) () -> closed.set(true), resource -> success(1))); assertTrue(closed.get()); } @Test public void withResourcesCleansUpAutoCloseableInFailureCase() { - AtomicBoolean closed = new AtomicBoolean(false); + AtomicBoolean closed = new AtomicBoolean(false); RuntimeException exception = new RuntimeException(); - assertEquals(Try.failure(exception), Try.withResources(() -> () -> closed.set(true), - resource -> { throw exception; })); + 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), Try.withResources(() -> { throw ioException; }, resource -> success(1))); + assertEquals(Try.failure(ioException), withResources(() -> { throw ioException; }, resource -> success(1))); } @Test public void withResourcesExposesResourceCloseFailure() { IOException ioException = new IOException(); - assertEquals(Try.failure(ioException), Try.withResources(() -> () -> { throw ioException; }, - resource -> success(1))); + 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 = Try.withResources(() -> () -> { throw nestedIOException; }, - resource -> { throw rootException; }); - Exception thrown = failure.recover(id()); + 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()); @@ -203,10 +217,53 @@ public void withResourcesPreservesSuppressedExceptionThrownDuringClose() { @Test public void cascadingWithResourcesClosesInInverseOrder() { List closeMessages = new ArrayList<>(); - assertEquals(success(1), Try.withResources(() -> (AutoCloseable) () -> closeMessages.add("close a"), - a -> () -> closeMessages.add("close b"), - b -> () -> closeMessages.add("close c"), - c -> success(1))); + 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 49b489192..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 @@ -6,14 +6,11 @@ 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.TraversableLaws; +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; @@ -29,14 +26,33 @@ public void setUp() { b = b(true); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @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 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 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 1d04ec9a5..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 @@ -6,15 +6,12 @@ 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.TraversableLaws; +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; @@ -32,7 +29,7 @@ public void setUp() { c = Choice3.c(true); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class, MonadRecLaws.class}) public Subjects> testSubjects() { return subjects(a("foo"), b(1), c(true)); } @@ -50,4 +47,21 @@ public void divergeStaysInChoice() { assertEquals(Choice4.b("two"), b.diverge()); assertEquals(Choice4.c(true), c.diverge()); } + + @Test + 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 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 0b70141e7..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 @@ -10,12 +10,14 @@ 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; @@ -35,24 +37,49 @@ public void setUp() { d = d(4D); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @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 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 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 d0ea885e5..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 @@ -6,17 +6,10 @@ 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.TraversableLaws; - -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 testsupport.traits.*; + +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; @@ -38,7 +31,12 @@ public void setUp() { e = e('z'); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @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)); } @@ -60,4 +58,27 @@ public void divergeStaysInChoice() { 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 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 index 2262884b5..1d761f0ee 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice6Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice6Test.java @@ -1,6 +1,7 @@ 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; @@ -11,16 +12,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -import java.util.function.Function; - 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; @@ -44,14 +45,20 @@ public void setUp() { f = f(5L); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @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() { - Function> convergenceFn = f -> Choice5.b(f.toString()); + Fn1> convergenceFn = f -> Choice5.b(f.toString()); assertEquals(Choice5.a(1), a.converge(convergenceFn)); assertEquals(Choice5.b("two"), b.converge(convergenceFn)); @@ -70,4 +77,31 @@ public void divergeStaysInChoice() { 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 index 27bf111d0..fd2dc7637 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice7Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice7Test.java @@ -1,6 +1,7 @@ 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; @@ -11,10 +12,9 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -import java.util.function.Function; - 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; @@ -22,6 +22,7 @@ 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; @@ -47,14 +48,20 @@ public void setUp() { g = g(6F); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @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() { - Function> convergenceFn = g -> Choice6.b(g.toString()); + Fn1> convergenceFn = g -> Choice6.b(g.toString()); assertEquals(Choice6.a(1), a.converge(convergenceFn)); assertEquals(Choice6.b("two"), b.converge(convergenceFn)); @@ -75,4 +82,34 @@ public void divergeStaysInChoice() { 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 index 24fede69b..acabc9f78 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice8Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice8Test.java @@ -1,6 +1,7 @@ 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; @@ -11,10 +12,9 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -import java.util.function.Function; - 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; @@ -23,6 +23,7 @@ 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; @@ -50,14 +51,21 @@ public void setUp() { h = h((short) 7); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @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() { - Function> convergenceFn = h -> Choice7.b(h.toString()); + Fn1> convergenceFn = + h -> Choice7.b(h.toString()); assertEquals(Choice7.a(1), a.converge(convergenceFn)); assertEquals(Choice7.b("two"), b.converge(convergenceFn)); @@ -68,4 +76,37 @@ public void convergeStaysInChoice() { 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 8b3d8f978..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,11 +1,10 @@ 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.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; @@ -21,13 +20,13 @@ public class CoProduct2Test { public void setUp() { 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>() { @Override - public R match(Function aFn, Function bFn) { + public R match(Fn1 aFn, Fn1 bFn) { return bFn.apply(true); } }; 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 49af8665c..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 @@ -3,11 +3,10 @@ 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.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; @@ -24,22 +23,22 @@ public class CoProduct3Test { public void setUp() { 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>() { @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>() { @Override - public R match(Function aFn, Function bFn, - Function cFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn) { return cFn.apply(true); } }; @@ -61,7 +60,7 @@ public void diverge() { @Test public void converge() { - Function> convergenceFn = x -> x ? Choice2.a(-1) : Choice2.b("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())); 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 840416088..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 @@ -3,11 +3,10 @@ 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.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; @@ -25,29 +24,29 @@ public class CoProduct4Test { public void setUp() { a = 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 aFn.apply(1); } }; b = 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 bFn.apply("two"); } }; c = 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 cFn.apply(true); } }; d = 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 dFn.apply(4D); } }; @@ -71,11 +70,12 @@ public void diverge() { @Test public void converge() { - Function> convergenceFn = x -> x.equals(1d) - ? Choice3.a(1) - : x.equals(2d) - ? Choice3.b("b") - : Choice3.c(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())); 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 14cccc369..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 @@ -3,11 +3,10 @@ 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.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; @@ -26,41 +25,41 @@ public class CoProduct5Test { public void setUp() { 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>() { @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>() { @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>() { @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>() { @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'); } }; @@ -86,13 +85,13 @@ public void diverge() { @Test public void converge() { - Function> convergenceFn = x -> x.equals('a') - ? Choice4.a(1) - : x.equals('b') - ? Choice4.b("b") - : x.equals('c') - ? Choice4.c(false) - : Choice4.d(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())); 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 index 8c38290e7..924e0507f 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct6Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct6Test.java @@ -3,11 +3,10 @@ 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 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; @@ -27,49 +26,49 @@ public class CoProduct6Test { public void setUp() { a = new CoProduct6>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn) { return fFn.apply(5L); } }; @@ -97,7 +96,7 @@ public void diverge() { @Test public void converge() { - Function> convergenceFn = x -> + Fn1> convergenceFn = x -> x.equals(1L) ? Choice5.a(1) : x.equals(2L) 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 index 1ae73c596..84999f677 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct7Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct7Test.java @@ -3,11 +3,10 @@ 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 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; @@ -28,64 +27,64 @@ public class CoProduct7Test { public void setUp() { a = new CoProduct7>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn) { return gFn.apply(6f); } }; @@ -115,7 +114,7 @@ public void diverge() { @Test public void converge() { - Function> convergenceFn = x -> + Fn1> convergenceFn = x -> x.equals(1f) ? Choice6.a(1) : x.equals(2f) 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 index e85f3152a..e5620b3c0 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct8Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct8Test.java @@ -3,11 +3,10 @@ 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 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; @@ -29,73 +28,73 @@ public class CoProduct8Test { public void setUp() { a = new CoProduct8>() { @Override - public R match(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + 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(Function aFn, Function bFn, - Function cFn, Function dFn, - Function eFn, Function fFn, - Function gFn, Function hFn) { + public R match(Fn1 aFn, Fn1 bFn, + Fn1 cFn, Fn1 dFn, + Fn1 eFn, Fn1 fFn, + Fn1 gFn, Fn1 hFn) { return hFn.apply((short) 7); } }; @@ -115,7 +114,7 @@ public void match() { @Test public void converge() { - Function> convergenceFn = x -> + Fn1> convergenceFn = x -> x.equals((short) 1) ? Choice7.a(1) : x.equals((short) 2) 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 56d3c19d5..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 @@ -8,10 +8,13 @@ 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) @@ -24,8 +27,8 @@ public void setUp() { singletonHList = new SingletonHList<>(1); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class}) - public SingletonHList testSubject() { + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) + public SingletonHList testSubject() { return singletonHList("one"); } @@ -44,8 +47,19 @@ public void cons() { assertEquals(new Tuple2<>("0", singletonHList), singletonHList.cons("0")); } + @Test + 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 a5e86f825..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,5 +1,6 @@ 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; @@ -9,14 +10,21 @@ 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 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.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; @@ -33,8 +41,15 @@ public void setUp() { tuple2 = new Tuple2<>(1, new SingletonHList<>(2)); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) - public Tuple2 testSubject() { + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + MonadWriterLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple2 testSubject() { return tuple("one", 2); } @@ -48,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()); @@ -62,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(); @@ -93,6 +118,7 @@ public void setValueIsNotSupported() { } @Test + @SuppressWarnings("serial") public void staticFactoryMethodFromMapEntry() { Map.Entry stringIntEntry = new HashMap() {{ put("string", 1); @@ -103,15 +129,28 @@ public void staticFactoryMethodFromMapEntry() { @Test public void zipPrecedence() { - Tuple2 a = tuple("foo", 1); - Tuple2> b = tuple("bar", x -> x + 1); - assertEquals(tuple("bar", 2), a.zip(b)); + 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); - Function> b = x -> tuple("bar", x + 1); + 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 0730a97a9..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,5 +1,6 @@ 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; @@ -9,11 +10,17 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -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.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; @@ -30,8 +37,14 @@ public void setUp() { tuple3 = new Tuple3<>(1, new Tuple2<>("2", new SingletonHList<>('3'))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) - public Tuple3 testSubject() { + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple3 testSubject() { return tuple("one", 2, 3d); } @@ -50,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()); @@ -59,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(); @@ -83,15 +102,34 @@ public void fill() { @Test public void zipPrecedence() { - Tuple3 a = tuple("foo", 1, 2); - Tuple3> b = tuple("bar", 2, x -> x + 1); + 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); - Function> b = x -> tuple("bar", 2, x + 1); + 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 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 44a6ba8dc..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,5 +1,6 @@ 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; @@ -9,11 +10,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -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.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; @@ -30,8 +36,14 @@ public void setUp() { tuple4 = new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>('3', new SingletonHList<>(false)))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) - public Tuple4 testSubject() { + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple4 testSubject() { return tuple("one", 2, 3d, 4f); } @@ -50,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()); @@ -60,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(); @@ -86,15 +103,34 @@ public void fill() { @Test public void zipPrecedence() { - Tuple4 a = tuple("foo", 1, 2, 3); - Tuple4> b = tuple("foo", 1, 2, x -> x + 1); + 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); - Function> b = x -> tuple("bar", 2, 3, x + 1); + 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 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 248dd03e1..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,6 +1,7 @@ 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; @@ -10,11 +11,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -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.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; @@ -31,8 +37,14 @@ 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, BifunctorLaws.class, TraversableLaws.class}) - public Tuple5 testSubject() { + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Tuple5 testSubject() { return tuple("one", 2, 3d, 4f, '5'); } @@ -51,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()); @@ -62,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(); @@ -90,15 +107,36 @@ public void fill() { @Test public void zipPrecedence() { - Tuple5 a = tuple("foo", 1, 2, 3, 4); - Tuple5> b = tuple("bar", 2, 3, 4, x -> x + 1); - assertEquals(tuple("bar", 2, 3, 4, 5), a.zip(b)); + 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); - Function> b = x -> tuple("bar", 2, 3, 4, x + 1); + 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 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 index bd31aca1a..880eee1e4 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java @@ -1,6 +1,7 @@ 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; @@ -10,11 +11,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -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.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; @@ -31,8 +37,14 @@ 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, BifunctorLaws.class, TraversableLaws.class}) - public Tuple6 testSubject() { + @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); } @@ -52,6 +64,11 @@ 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()); @@ -64,8 +81,8 @@ public void accessors() { @Test public void randomAccess() { - Tuple5 spiedTail = spy(tuple("second", "third", "fourth", "fifth", "sixth")); - Tuple6 tuple6 = new Tuple6<>("first", spiedTail); + 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(); @@ -94,15 +111,36 @@ public void fill() { @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("bar", 2, 3, 4, 5, 6), a.zip(b)); + 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); - Function> b = x -> tuple("bar", 2, 3, 4, 5, x + 1); + 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 index a62a36691..d5b13fd24 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java @@ -1,6 +1,7 @@ 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; @@ -10,11 +11,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -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.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; @@ -31,8 +37,14 @@ 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, BifunctorLaws.class, TraversableLaws.class}) - public Tuple7 testSubject() { + @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); } @@ -52,6 +64,11 @@ 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()); @@ -65,8 +82,8 @@ public void accessors() { @Test public void randomAccess() { - Tuple6 spiedTail = spy(tuple("second", "third", "fourth", "fifth", "sixth", "seventh")); - Tuple7 tuple7 = new Tuple7<>("first", spiedTail); + 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(); @@ -97,15 +114,37 @@ public void into() { @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("bar", 2, 3, 4, 5, 6, 7), a.zip(b)); + 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); - Function> b = x -> tuple("bar", 2, 3, 4, 5, 6, x + 1); + 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 index c19ee8a58..ced5531b5 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java @@ -1,6 +1,7 @@ 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; @@ -10,11 +11,18 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -import java.util.function.Function; +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; @@ -28,11 +36,19 @@ public class Tuple8Test { @Before public void setUp() { - tuple8 = new Tuple8<>((short) 65535, new Tuple7<>((byte) 127, new Tuple6<>(2.0f, new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L)))))))); + 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, BifunctorLaws.class, TraversableLaws.class}) - public Tuple8 testSubject() { + @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); } @@ -43,8 +59,9 @@ public void head() { @Test public void tail() { - assertEquals(new Tuple7<>((byte) 127, new Tuple6<>(2.0f, new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L))))))), - tuple8.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 @@ -52,6 +69,15 @@ 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()); @@ -66,8 +92,10 @@ public void accessors() { @Test public void randomAccess() { - Tuple7 spiedTail = spy(tuple("second", "third", "fourth", "fifth", "sixth", "seventh", "eighth")); - Tuple8 tuple8 = new Tuple8<>("first", spiedTail); + 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(); @@ -94,21 +122,46 @@ public void fill() { @Test public void into() { - Tuple8 tuple = tuple("foo", 1, 2.0d, false, 3f, (short) 4, (byte) 5, 6L); + 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("bar", 2, 3, 4, 5, 6, 7, 8), a.zip(b)); + 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); - Function> b = x -> tuple("bar", 2, 3, 4, 5, 6, 7, x + 1); + 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 0c93ecb3f..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 @@ -13,7 +13,7 @@ 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.lens.Iso.simpleIso; +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; @@ -39,11 +39,19 @@ public void getForAbsentKey() { .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)); + 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)); @@ -54,7 +62,8 @@ public void storesTypeSafeKeyBaseValue() { 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(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)); @@ -82,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); @@ -132,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"); @@ -156,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", @@ -191,14 +201,14 @@ public void values() { @Test public void convenienceStaticFactoryMethods() { - 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(); + 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); @@ -263,7 +273,6 @@ public void convenienceStaticFactoryMethods() { } @Test - @SuppressWarnings("EqualsWithItself") public void equality() { assertEquals(emptyHMap(), emptyHMap()); 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 index 5d76e50cd..d5cf6500c 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hmap/SchemaTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/SchemaTest.java @@ -9,7 +9,7 @@ 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.lens.functions.View.view; +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; @@ -18,14 +18,14 @@ 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(); + 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, 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 b9bb7e802..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 @@ -6,7 +6,7 @@ import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; -import static com.jnape.palatable.lambda.lens.Iso.simpleIso; +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; @@ -24,9 +24,9 @@ public void lensLawfulness() { @Test public void compositionMapsOriginalValueInAndOutOfHMap() { - TypeSafeKey.Simple stringKey = typeSafeKey(); - TypeSafeKey intKey = stringKey.andThen(simpleIso(Integer::parseInt, Object::toString)); - HMap map = emptyHMap().put(stringKey, "123"); + 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)); @@ -40,16 +40,16 @@ public void compositionMapsOriginalValueInAndOutOfHMap() { @Test public void discardRPreservesTypeSafeKey() { - TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey.Simple stringKey = typeSafeKey(); TypeSafeKey discardedKey = stringKey.discardR(simpleIso(id(), id())); - HMap map = emptyHMap().put(stringKey, "123"); + HMap map = emptyHMap().put(stringKey, "123"); assertEquals(just("123"), map.get(discardedKey)); } @Test public void defaultEquality() { - TypeSafeKey.Simple keyA = typeSafeKey(); + TypeSafeKey.Simple keyA = typeSafeKey(); TypeSafeKey mappedKeyA = keyA.andThen(simpleIso(id(), id())); assertEquals(keyA, keyA); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java b/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java index 9f5dabd60..456d2619c 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java @@ -1,17 +1,27 @@ 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.adt.Unit.UNIT; 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 { @@ -19,33 +29,48 @@ public class EffectTest { public void covariantReturns() { List results = new ArrayList<>(); - Effect effect = results::add; - Effect diMapL = effect.diMapL(Object::toString); - Effect contraMap = effect.contraMap(Object::toString); - Effect compose = effect.compose(Object::toString); + Effect effect = fromConsumer(results::add); + Effect diMapL = effect.diMapL(Object::toString); + Effect contraMap = effect.contraMap(Object::toString); Effect stringEffect = effect.discardR(constantly("1")); - Effect andThen = effect.andThen(effect); - effect.accept("1"); - diMapL.accept("2"); - contraMap.accept("3"); - compose.accept("4"); - stringEffect.accept("5"); - andThen.accept("6"); + 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)); - assertEquals(asList("1", "2", "3", "4", "5", "6", "6"), results); + assertThat(alter(inc.andThen(inc), counter).fmap(AtomicInteger::get), + yieldsValue(equalTo(2))); } @Test public void staticFactoryMethods() { AtomicInteger counter = new AtomicInteger(); - Effect runnableEffect = effect(counter::incrementAndGet); - runnableEffect.apply(UNIT).unsafePerformIO(); - assertEquals(1, counter.get()); + Effect sideEffect = effect(counter::incrementAndGet); + assertThat(sideEffect.apply("foo").flatMap(constantly(io(counter::get))), + yieldsValue(equalTo(1))); - Effect fnEffect = effect(AtomicInteger::incrementAndGet); - fnEffect.apply(counter).unsafePerformIO(); - assertEquals(2, counter.get()); + 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 index d04084205..87af14209 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn0Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn0Test.java @@ -1,21 +1,39 @@ package com.jnape.palatable.lambda.functions; -import com.jnape.palatable.traitor.annotations.TestTraits; -import com.jnape.palatable.traitor.runners.Traits; -import org.junit.runner.RunWith; -import testsupport.EqualityAwareFn0; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; - -import static com.jnape.palatable.lambda.adt.Unit.UNIT; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; - -@RunWith(Traits.class) +import org.junit.Test; + +import java.util.concurrent.Callable; +import java.util.function.Supplier; + +import static org.junit.Assert.assertEquals; + public class Fn0Test { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class}) - public Fn0 testSubject() { - return new EqualityAwareFn0<>(constantly(1).thunk(UNIT)); + @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 bd3237f2c..047aea1db 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -4,25 +4,38 @@ import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.EqualityAwareFn1; 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 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 { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class}) - public Fn1 testSubject() { - return new EqualityAwareFn1<>("1", Integer::parseInt); + @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 @@ -35,9 +48,11 @@ public void profunctorProperties() { } @Test - public void fn1() { - Function parseInt = Integer::parseInt; - assertEquals((Integer) 1, Fn1.fn1(parseInt).apply("1")); + 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 @@ -49,13 +64,13 @@ public void thunk() { @Test public void widen() { Fn1 addOne = x -> x + 1; - assertEquals(just(4), reduceLeft(addOne.widen().toBiFunction(), asList(1, 2, 3))); + assertEquals(just(4), reduceLeft(addOne.widen(), asList(1, 2, 3))); } @Test - public void strengthen() { + public void cartesian() { Fn1 add1 = x -> x + 1; - assertEquals(tuple("a", 2), add1.strengthen().apply(tuple("a", 1))); + assertEquals(tuple("a", 2), add1.cartesian().apply(tuple("a", 1))); } @Test @@ -63,4 +78,36 @@ 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))); + } + + @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 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 89cf70a03..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,13 +2,14 @@ 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; -import static org.junit.Assert.assertTrue; public class Fn2Test { @@ -30,24 +31,33 @@ public void uncurries() { assertThat(CHECK_LENGTH.uncurry().apply(tuple("abc", 3)), is(true)); } - @Test - @SuppressWarnings("ConstantConditions") - public void composePreservesTypeSpecificity() { - assertTrue(CHECK_LENGTH.compose(Object::toString) instanceof Fn2); - } - @Test public void toBiFunction() { BiFunction biFunction = CHECK_LENGTH.toBiFunction(); assertEquals(true, biFunction.apply("abc", 3)); } + @Test + 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.fn2(biFunction).apply("foo %s", "bar")); + assertEquals("foo bar", Fn2.fromBiFunction(biFunction).apply("foo %s", "bar")); + } - Fn1> curriedFn1 = (x) -> (y) -> String.format(x, y); - assertEquals("foo bar", Fn2.fn2(curriedFn1).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/IOTest.java b/src/test/java/com/jnape/palatable/lambda/functions/IOTest.java deleted file mode 100644 index cab2595c0..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/IOTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.jnape.palatable.lambda.functions; - -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.EqualityAwareIO; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.function.Function; - -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.IO.externallyManaged; -import static com.jnape.palatable.lambda.functions.IO.io; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler; -import static com.jnape.palatable.lambda.functions.specialized.checked.CheckedFn1.checked; -import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.concurrent.Executors.newFixedThreadPool; -import static java.util.concurrent.ForkJoinPool.commonPool; -import static org.junit.Assert.assertEquals; - -@RunWith(Traits.class) -public class IOTest { - - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class}) - public IO testSubject() { - return new EqualityAwareIO<>(io(1)); - } - - @Test - public void staticFactoryMethods() { - assertEquals((Integer) 1, io(1).unsafePerformIO()); - assertEquals((Integer) 1, io(() -> 1).unsafePerformIO()); - assertEquals((Integer) 1, io(fn0(() -> 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(checked(__ -> { - advanceFirst.countDown(); - advanceSecond.await(); - return a; - })); - IO>> ioF = io(checked(__ -> { - 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 exceptionallyRecoversThrowableToResult() { - IO io = io(() -> { throw new UnsupportedOperationException("foo"); }); - assertEquals("foo", io.exceptionally(Throwable::getMessage).unsafePerformIO()); - - IO externallyManaged = externallyManaged(() -> new CompletableFuture() {{ - completeExceptionally(new UnsupportedOperationException("foo")); - }}).exceptionally(e -> e.getCause().getMessage()); - assertEquals("foo", externallyManaged.unsafePerformIO()); - } - - @Test - public void exceptionallyRescuesFutures() { - ExecutorService executor = newFixedThreadPool(2); - - IO io = io(() -> { throw new UnsupportedOperationException("foo"); }); - assertEquals("foo", io.exceptionally(e -> e.getCause().getMessage()).unsafePerformAsyncIO().join()); - assertEquals("foo", io.exceptionally(e -> e.getCause().getMessage()).unsafePerformAsyncIO(executor).join()); - - IO externallyManaged = externallyManaged(() -> new CompletableFuture() {{ - completeExceptionally(new UnsupportedOperationException("foo")); - }}).exceptionally(e -> e.getCause().getMessage()); - assertEquals("foo", externallyManaged.unsafePerformIO()); - - } -} \ 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 index 68db636c1..6502e6104 100644 --- 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 @@ -8,10 +8,10 @@ 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; -import static testsupport.matchers.LeftMatcher.isLeftThat; -import static testsupport.matchers.RightMatcher.isRightThat; public class CoalesceTest { 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 index 4755af103..b535e381c 100644 --- 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 @@ -19,7 +19,7 @@ public class DistinctTest { @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Distinct testSubject() { + public Distinct testSubject() { return distinct(); } 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/FlattenTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/FlattenTest.java index d1889525b..8138aaed0 100644 --- 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 @@ -28,7 +28,7 @@ public class FlattenTest { @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class}) public Fn1, Iterable> testSubject() { - return Flatten.flatten().compose(Map.>map(Collections::singletonList)); + return Flatten.flatten().contraMap(Map.>map(Collections::singletonList)); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ForceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ForceTest.java deleted file mode 100644 index 394d85ddd..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ForceTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.jnape.palatable.lambda.functions.builtin.fn1; - -import org.junit.Test; - -import java.util.concurrent.atomic.AtomicInteger; - -import static com.jnape.palatable.lambda.functions.builtin.fn1.Force.force; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; - -public class ForceTest { - - @Test - public void performsAnySideEffects() { - AtomicInteger counter = new AtomicInteger(); - Iterable ints = map(x -> { - counter.incrementAndGet(); - return x; - }, asList(1, 2, 3)); - - assertEquals(0, counter.get()); - - force(ints); - force(ints); - - assertEquals(6, counter.get()); - } -} \ 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 index dde20b9fe..b27b65903 100644 --- 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 @@ -23,7 +23,7 @@ public class InitTest { @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, Laziness.class, ImmutableIteration.class, FiniteIteration.class}) - public Fn1 testSubject() { + public Fn1, ? extends Iterable> testSubject() { return init(); } 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 index 191425e2c..50b417d58 100644 --- 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 @@ -22,7 +22,7 @@ public class InitsTest { @TestTraits({Laziness.class, EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Fn1 testSubject() { + public Fn1, ? extends Iterable> testSubject() { return inits(); } 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 index 87f5a983e..cec328d11 100644 --- 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 @@ -26,7 +26,6 @@ public Fn1, Iterable>> testSubject() { } @Test - @SuppressWarnings("unchecked") public void magnetizesElementsByPredicateOutcome() { assertThat(magnetize(asList(1, 1, 2, 3, 3, 3, 2, 2, 1)), contains(iterates(1, 1), 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 index bb11d5782..290fa2736 100644 --- 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 @@ -2,17 +2,18 @@ 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 java.util.Collections.emptyMap; import static org.junit.Assert.assertEquals; public class OccurrencesTest { @Test + @SuppressWarnings("serial") public void occurrencesOfIndividualElements() { assertEquals(new HashMap() {{ put("foo", 2L); @@ -23,6 +24,6 @@ public void occurrencesOfIndividualElements() { @Test public void emptyIterableHasNoOccurrences() { - assertEquals(emptyMap(), occurrences(emptyList())); + 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 308b9123e..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,13 +24,13 @@ 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")); 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/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 index 45b3a1b25..a384d21cc 100644 --- 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 @@ -27,7 +27,7 @@ public class TailsTest { @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Laziness.class}) - public Fn1 testSubject() { + public Fn1, ? extends Iterable> testSubject() { return tails(); } 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 index b3c8ece22..01abfdcd2 100644 --- 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 @@ -19,13 +19,13 @@ public class UnconsTest { @TestTraits({EmptyIterableSupport.class}) - public Uncons testSubject() { + public Uncons testSubject() { return uncons(); } @Test public void nonEmptyIterable() { - Iterable numbers = asList(1, 2, 3); + Iterable numbers = asList(1, 2, 3); Tuple2> headAndTail = uncons(numbers).orElseThrow(AssertionError::new); assertEquals((Integer) 1, headAndTail._1()); 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 index a93d83537..2a5fa7e55 100644 --- 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 @@ -11,8 +11,8 @@ public class UpcastTest { @Test @SuppressWarnings("unused") public void castsUp() { - Upcast upcast = upcast(); - Iterable strings = asList("foo", "bar"); - Iterable charSequences = map(upcast, strings); + 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 c266a4bfb..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,7 +17,7 @@ @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, ? extends Boolean> createTestSubject() { 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 index 73e11e800..5baa26fa1 100644 --- 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 @@ -4,17 +4,22 @@ 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.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +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<>(); - assertSame(input, alter(xs -> xs.add("foo"), input).unsafePerformIO()); - assertEquals(singletonList("foo"), input); + 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 ff632730b..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/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/DropWhileTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java index e168d749a..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 @@ -15,7 +15,6 @@ import java.util.List; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Force.force; import static com.jnape.palatable.lambda.functions.builtin.fn2.DropWhile.dropWhile; import static java.util.Arrays.asList; import static org.junit.Assert.assertThat; @@ -50,14 +49,20 @@ public void dropsNoElementsIfPredicateImmediatelyFails() { public void deforestingExecutesPredicatesInOrder() { List innerInvocations = new ArrayList<>(); List outerInvocations = new ArrayList<>(); - force(dropWhile(y -> { + dropWhile(y -> { outerInvocations.add(y); return true; }, dropWhile(x -> { innerInvocations.add(x); return x > 2; - }, asList(1, 2, 3)))); - assertThat(innerInvocations, iterates(1, 2, 3)); - assertThat(outerInvocations, iterates(1, 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 27e5fa0bd..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 @@ -15,7 +15,6 @@ import java.util.List; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Force.force; import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; import static java.util.Arrays.asList; import static org.junit.Assert.assertThat; @@ -25,7 +24,7 @@ public class FilterTest { @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Fn1 testSubject() { + public Fn1, ?> testSubject() { return filter(constantly(true)); } @@ -42,13 +41,13 @@ public void filtersOutMatchingElements() { public void deforestingExecutesPredicatesInOrder() { List innerInvocations = new ArrayList<>(); List outerInvocations = new ArrayList<>(); - force(filter(y -> { + filter(y -> { outerInvocations.add(y); return true; }, filter(x -> { innerInvocations.add(x); return x % 2 == 0; - }, asList(1, 2, 3)))); + }, 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/GroupByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupByTest.java index 02f8e8bf2..5a9401099 100644 --- 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 @@ -2,6 +2,7 @@ import org.junit.Test; +import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -9,10 +10,10 @@ 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.emptyMap; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; +@SuppressWarnings("serial") public class GroupByTest { @Test @@ -25,6 +26,6 @@ public void grouping() { @Test public void emptyIterableProducesEmptyMap() { - assertEquals(emptyMap(), groupBy(id(), emptyList())); + 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/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/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 index 1995332f2..25b0723eb 100644 --- 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 @@ -11,11 +11,10 @@ import testsupport.traits.InfiniteIterableSupport; import testsupport.traits.Laziness; -import java.util.function.BiFunction; - 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; @@ -31,16 +30,15 @@ public class MagnetizeByTest { @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Laziness.class}) public Fn1, Iterable>> testSubject() { - return magnetizeBy(eq().toBiFunction()); + return magnetizeBy(eq()); } @Test @SuppressWarnings("unchecked") public void magnetizesElementsByPredicateOutcome() { - BiFunction lte = (x, y) -> x <= y; - assertThat(magnetizeBy(lte, emptyList()), isEmpty()); - assertThat(magnetizeBy(lte, singletonList(1)), contains(iterates(1))); - assertThat(magnetizeBy(lte, asList(1, 2, 3, 2, 2, 3, 2, 1)), + 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), 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 92a6e725b..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 @@ -21,7 +21,7 @@ public class MapTest { @TestTraits({Laziness.class, EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Fn1 createTraitsTestSubject() { + 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 05366dfb4..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 @@ -29,14 +29,14 @@ public class PartitionTest { @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Subjects> createTraitsTestSubject() { - return subjects(partition(constantly(a(1))).andThen(Tuple2::_1), - partition(constantly(b(1))).andThen(Tuple2::_2)); + public Subjects, ?>> createTraitsTestSubject() { + return subjects(partition(constantly(a(1))).fmap(Tuple2::_1), + partition(constantly(b(1))).fmap(Tuple2::_2)); } @Test public void partitionsIterableIntoAsAndBs() { - Iterable strings = asList("one", "two", "three", "four", "five"); + 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")); @@ -45,8 +45,8 @@ public void partitionsIterableIntoAsAndBs() { @Test public void infiniteListSupport() { - Iterable> coproducts = cycle(a("left"), b(1)); - Tuple2, Iterable> partition = partition(id(), coproducts); + 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/Peek2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek2Test.java deleted file mode 100644 index af16097b3..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek2Test.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.jnape.palatable.lambda.functions.builtin.fn2; - -import com.jnape.palatable.lambda.adt.Either; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; -import org.junit.Test; - -import java.util.concurrent.atomic.AtomicInteger; - -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.functions.builtin.fn2.Peek2.peek2; -import static org.junit.Assert.assertEquals; - -public class Peek2Test { - - @Test - public void peeksAtBothBifunctorValues() { - AtomicInteger counter = new AtomicInteger(0); - Tuple2 tuple = tuple(1, 2); - assertEquals(tuple, peek2(__ -> counter.incrementAndGet(), __ -> counter.incrementAndGet(), tuple)); - assertEquals(2, counter.get()); - } - - @Test - public void followsSameConventionsAsBimap() { - AtomicInteger counter = new AtomicInteger(0); - Either either = right(1); - peek2(__ -> counter.incrementAndGet(), __ -> counter.incrementAndGet(), either); - assertEquals(1, counter.get()); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PeekTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PeekTest.java deleted file mode 100644 index 337899584..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PeekTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.jnape.palatable.lambda.functions.builtin.fn2; - -import com.jnape.palatable.lambda.adt.Maybe; -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.functions.builtin.fn2.Peek.peek; -import static org.junit.Assert.assertEquals; - -public class PeekTest { - - @Test - public void appliesConsumerToCarrierValue() { - AtomicInteger counter = new AtomicInteger(0); - Maybe maybeString = just("foo"); - assertEquals(maybeString, peek(x -> counter.incrementAndGet(), maybeString)); - assertEquals(1, counter.get()); - } - - @Test - public void onlyAppliesIfFmapWould() { - AtomicInteger counter = new AtomicInteger(0); - Maybe.nothing().fmap(__ -> counter.incrementAndGet()); - assertEquals(0, counter.get()); - } -} \ 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 index e51dfbaa1..cec572406 100644 --- 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 @@ -2,11 +2,12 @@ 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.function.Function; +import java.util.Map; import static com.jnape.palatable.lambda.adt.Either.right; import static com.jnape.palatable.lambda.adt.Maybe.just; @@ -22,8 +23,8 @@ public class SequenceTest { @Test public void naturality() { - Function, Either> t = id -> right(id.runIdentity()); - Either> traversable = right(new Identity<>(1)); + 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)); @@ -69,5 +70,8 @@ public void compilation() { 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 index 6736b37ad..258e221b0 100644 --- 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 @@ -45,8 +45,8 @@ public void kMustBeGreaterThan0() { @Test public void stackSafety() { - Integer stackBlowingNumber = 50000; - Iterable> xss = slide(2, take(stackBlowingNumber, iterate(x -> x + 1, 1))); + 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/TakeWhileTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java index bfa4e7000..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 @@ -15,7 +15,6 @@ import java.util.List; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Force.force; import static com.jnape.palatable.lambda.functions.builtin.fn2.TakeWhile.takeWhile; import static java.util.Arrays.asList; import static org.junit.Assert.assertThat; @@ -33,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)); } @@ -55,13 +54,13 @@ public void takesNoElementsIfPredicateImmediatelyFails() { public void deforestingExecutesPredicatesInOrder() { List innerInvocations = new ArrayList<>(); List outerInvocations = new ArrayList<>(); - force(takeWhile(y -> { + takeWhile(y -> { outerInvocations.add(y); return true; }, takeWhile(x -> { innerInvocations.add(x); return x < 3; - }, asList(1, 2, 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/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 95b0cf7c1..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 @@ -23,7 +23,7 @@ public class UnfoldrTest { @TestTraits({Laziness.class, InfiniteIteration.class, ImmutableIteration.class}) - public Fn1 createTestSubject() { + public Fn1, ? extends Iterable> createTestSubject() { return unfoldr(x -> just(tuple(x, x))); } 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/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/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/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/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 index 1e575879d..f775e60ec 100644 --- 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 @@ -2,10 +2,9 @@ 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 java.util.function.BiFunction; - 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; @@ -17,7 +16,7 @@ public class LiftA2Test { @Test public void inference() { - BiFunction add = (x, y) -> x + y; + Fn2 add = Integer::sum; Maybe a = liftA2(add, just(1), just(2)); assertEquals(just(3), a); @@ -31,7 +30,7 @@ public void inference() { Maybe d = liftA2(add, nothing(), nothing()); assertEquals(nothing(), d); - Either e = liftA2(add, Either.right(1), right(2)); + Either e = liftA2(add, Either.right(1), right(2)); assertEquals(right(3), e); Either f = liftA2(add, left("error"), right(2)); 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 index 70e1af6ab..f1e0895c6 100644 --- 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 @@ -11,9 +11,9 @@ public class IfThenElseTest { @Test public void standardLogic() { - Predicate even = x -> x % 2 == 0; - Fn1 inc = x -> x + 1; - Fn1 dec = x -> x - 1; + 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)); 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 index 0459a8be1..b91b92b08 100644 --- 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 @@ -1,8 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn4; -import com.jnape.palatable.lambda.adt.Try; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iteration.IterationInterruptedException; +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; @@ -17,8 +16,10 @@ 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; @@ -55,18 +56,17 @@ public void zeroDurationJustIteratesElements() { @Test public void limitPerDurationIsHonoredAccordingToClock() { Duration duration = Duration.ofMillis(10); - long limit = 2L; + 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); + Thread testThread = Thread.currentThread(); + CountDownLatch latch = new CountDownLatch(1); new Thread(() -> { - Try.trying(latch::await).biMapL(AssertionError::new) - .orThrow(); + trying(sideEffect(latch::await)).orThrow(); testThread.interrupt(); }) {{ start(); 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 index 0d1851d34..bd131e897 100644 --- 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 @@ -10,6 +10,7 @@ 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))); + 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 index a247e5620..76ed29162 100644 --- 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 @@ -10,6 +10,8 @@ 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))); + 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 index 73297afde..f81bd6ef0 100644 --- 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 @@ -10,6 +10,7 @@ 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))); + 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 index b46f91f5d..d8a156a9a 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java @@ -3,21 +3,30 @@ 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}) + @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 index 035358e6a..5104aa824 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/recursion/TrampolineTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/recursion/TrampolineTest.java @@ -1,11 +1,11 @@ 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 java.util.function.Function; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; @@ -17,7 +17,8 @@ public class TrampolineTest { - private static final Function, RecursiveResult, BigInteger>> FACTORIAL = + 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 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 4a19bbba8..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 @@ -13,44 +13,44 @@ 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 @@ -58,4 +58,18 @@ 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 index bd40b2dca..8be6844fe 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/specialized/KleisliTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/specialized/KleisliTest.java @@ -9,8 +9,8 @@ public class KleisliTest { - private static final Kleisli> G = kleisli(i -> new Identity<>(i.toString())); - private static final Kleisli> F = kleisli(s -> new Identity<>(parseInt(s))); + 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() { 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/functions/specialized/checked/CheckedEffectTest.java b/src/test/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedEffectTest.java deleted file mode 100644 index 8472dc067..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedEffectTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; - -public class CheckedEffectTest { - - @Test - public void assignment() { - List results = new ArrayList<>(); - - CheckedEffect effect = results::add; - CheckedEffect diMapL = effect.diMapL(Object::toString); - CheckedEffect contraMap = effect.contraMap(Object::toString); - CheckedEffect compose = effect.compose(Object::toString); - CheckedEffect stringEffect = effect.discardR(constantly("1")); - CheckedEffect andThen = effect.andThen(effect); - - effect.accept("1"); - diMapL.accept("2"); - contraMap.accept("3"); - compose.accept("4"); - stringEffect.accept("5"); - andThen.accept("6"); - - assertEquals(asList("1", "2", "3", "4", "5", "6", "6"), results); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1Test.java b/src/test/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1Test.java deleted file mode 100644 index a8818d684..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1Test.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -import com.jnape.palatable.lambda.adt.hlist.Tuple2; -import org.junit.Test; - -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 org.junit.Assert.assertEquals; - -public class CheckedFn1Test { - - @Test - public void assignment() { - CheckedFn1 parseInt = Integer::parseInt; - CheckedFn1 fmap = parseInt.fmap(Object::toString); - CheckedFn1 flatMap = parseInt.flatMap(i -> constantly(i + "")); - CheckedFn1 discardL = parseInt.discardL(constantly("0")); - CheckedFn1 discardR = parseInt.discardR(constantly("0")); - CheckedFn1 zipApp = parseInt.zip(constantly(id())); - CheckedFn1 zipF = parseInt.zip(constantly()); - CheckedFn1 diMapL = parseInt.diMapL(id()); - CheckedFn1 diMapR = parseInt.diMapR(id()); - CheckedFn1 diMap = parseInt.diMap(id(), id()); - CheckedFn1, Tuple2> strengthen = parseInt.strengthen(); - CheckedFn1> carry = parseInt.carry(); - CheckedFn1 contraMap = parseInt.contraMap(id()); - CheckedFn1 compose = parseInt.compose(id()); - CheckedFn1 andThen = parseInt.andThen(id()); - - assertEquals((Integer) 1, parseInt.apply("1")); - assertEquals("1", fmap.apply("1")); - assertEquals("1", flatMap.apply("1")); - assertEquals("0", discardL.apply("1")); - assertEquals((Integer) 1, discardR.apply("1")); - assertEquals((Integer) 1, zipApp.apply("1")); - assertEquals("1", zipF.apply("1")); - assertEquals((Integer) 1, diMapL.apply("1")); - assertEquals((Integer) 1, diMapR.apply("1")); - assertEquals((Integer) 1, diMap.apply("1")); - assertEquals(tuple("foo", 1), strengthen.apply(tuple("foo", "1"))); - assertEquals(tuple("1", 1), carry.apply("1")); - assertEquals((Integer) 1, contraMap.apply("1")); - assertEquals((Integer) 1, compose.apply("1")); - assertEquals((Integer) 1, andThen.apply("1")); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplierTest.java b/src/test/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplierTest.java deleted file mode 100644 index 804b77ca0..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplierTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -import com.jnape.palatable.lambda.adt.Unit; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; -import com.jnape.palatable.lambda.functions.builtin.fn1.Constantly; -import org.junit.Test; - -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.specialized.checked.CheckedSupplier.checked; -import static org.junit.Assert.assertEquals; - -public class CheckedSupplierTest { - - @Test - public void assignment() { - CheckedSupplier intSupplier = checked(() -> 1); - CheckedSupplier fmap = intSupplier.fmap(Object::toString); - @SuppressWarnings("RedundantTypeArguments") - CheckedSupplier flatMap = intSupplier.flatMap(Constantly::constantly); - CheckedSupplier discardL = intSupplier.discardL(constantly(1)); - CheckedSupplier discardR = intSupplier.discardR(constantly(2)); - CheckedSupplier zipA = intSupplier.zip(constantly(id())); - CheckedSupplier zipF = intSupplier.zip(constantly()); - CheckedSupplier diMapR = intSupplier.diMapR(id()); - CheckedSupplier> carry = intSupplier.carry(); - CheckedSupplier andThen = intSupplier.andThen(id()); - - assertEquals((Integer) 1, intSupplier.get()); - assertEquals("1", fmap.get()); - assertEquals((Integer) 1, flatMap.get()); - assertEquals((Integer) 1, discardL.get()); - assertEquals((Integer) 1, discardR.get()); - assertEquals((Integer) 1, zipA.get()); - assertEquals(UNIT, zipF.get()); - assertEquals((Integer) 1, diMapR.get()); - assertEquals(tuple(UNIT, 1), carry.get()); - assertEquals((Integer) 1, andThen.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 ce89ba26e..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 0f7358250..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 index 3cc6198fc..3fea2229f 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java @@ -9,15 +9,20 @@ 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 testSubject() { + public Compose, Identity, Integer> testSubject() { return new Compose<>(new Identity<>(new Identity<>(1))); } @@ -26,4 +31,20 @@ 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 45588a462..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 @@ -2,18 +2,35 @@ 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 { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) - public Const testSubject() { + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) + public Const testSubject() { return new Const<>(1); } + + @Test + 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 7a46afd1d..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 @@ -2,17 +2,28 @@ 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}) - public Identity testSubject() { + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) + public Identity testSubject() { return new Identity<>(""); } + + @Test + 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/iteration/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/iteration/CombinatorialIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIteratorTest.java index 7678bd2c4..9e52df9b3 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/CombinatorialIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIteratorTest.java @@ -1,10 +1,10 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/ConcatenatingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterableTest.java similarity index 94% rename from src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterableTest.java index c82efdc70..92ffd28b9 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ConsingIteratorTest.java similarity index 80% rename from src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ConsingIteratorTest.java index 68f99d943..85f6c6ba0 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ConsingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +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; @@ -51,9 +52,10 @@ public void doesNotHaveNextIfNoElementsLeft() { @Test public void stackSafety() { Integer stackBlowingNumber = 10_000; - Iterable ints = foldRight((x, acc) -> () -> new ConsingIterator<>(x, acc), - (Iterable) Collections.emptyList(), - take(stackBlowingNumber, iterate(x -> x + 1, 1))); + 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/iteration/CyclicIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterableTest.java similarity index 88% rename from src/test/java/com/jnape/palatable/lambda/iteration/CyclicIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterableTest.java index 66938afa1..a4ce12dce 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/CyclicIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/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/iteration/CyclicIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIteratorTest.java index ba7e84485..5af519d32 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/CyclicIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CyclicIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/DistinctIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterableTest.java similarity index 88% rename from src/test/java/com/jnape/palatable/lambda/iteration/DistinctIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterableTest.java index 69a2bd252..ae6d53ce5 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/DistinctIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterableTest.java similarity index 88% rename from src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterableTest.java index 52ab7899f..cccdd2878 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIteratorTest.java similarity index 87% rename from src/test/java/com/jnape/palatable/lambda/iteration/DroppingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIteratorTest.java index 0e28a2dd2..0abdb857e 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIteratorTest.java @@ -1,10 +1,10 @@ -package com.jnape.palatable.lambda.iteration; +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; @@ -17,7 +17,7 @@ public class DroppingIteratorTest { @Mock private Iterator iterator; - private DroppingIterator droppingIterator; + private DroppingIterator droppingIterator; @Before public void setUp() { diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/FilteringIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterableTest.java similarity index 91% rename from src/test/java/com/jnape/palatable/lambda/iteration/FilteringIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterableTest.java index 7d1c645d6..b2f40b516 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/FilteringIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/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/iteration/FilteringIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIteratorTest.java index 5f2c3cb7e..68f3ab567 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/FilteringIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FilteringIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIteratorTest.java similarity index 96% rename from src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIteratorTest.java index 7288e8fbc..ce286ecd2 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/GroupingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/GroupingIteratorTest.java similarity index 93% rename from src/test/java/com/jnape/palatable/lambda/iteration/GroupingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/GroupingIteratorTest.java index 0caab11a5..d4e1df3ab 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/GroupingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/GroupingIteratorTest.java @@ -1,10 +1,10 @@ -package com.jnape.palatable.lambda.iteration; +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; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/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/iteration/ImmutableIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ImmutableIteratorTest.java index d1ffc6ebd..15369f60a 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/ImmutableIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ImmutableIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/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/iteration/InfiniteIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/InfiniteIteratorTest.java index 9d9f8abd5..a2df8b32c 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/InfiniteIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/InfiniteIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/InitIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/InitIteratorTest.java similarity index 94% rename from src/test/java/com/jnape/palatable/lambda/iteration/InitIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/InitIteratorTest.java index 04c512cb3..99f12dee2 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/InitIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/InitIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/MappingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIterableTest.java similarity index 91% rename from src/test/java/com/jnape/palatable/lambda/iteration/MappingIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIterableTest.java index fdddf37e0..b86b95650 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/MappingIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/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/iteration/MappingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIteratorTest.java index c4522f8c1..23abb2ce7 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/MappingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/PredicatedDroppingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterableTest.java similarity index 91% rename from src/test/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterableTest.java index 2202c2b98..55d6a3ef1 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/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/iteration/PredicatedDroppingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java index 68f933a9a..44fcde9f1 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java @@ -1,15 +1,18 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/PredicatedTakingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterableTest.java similarity index 91% rename from src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterableTest.java index bb8738cc5..e819c90f2 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIteratorTest.java similarity index 78% rename from src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIteratorTest.java index 833ad43c8..aae11165b 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.specialized.Predicate; import org.junit.Test; @@ -16,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(); @@ -32,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(); @@ -48,14 +48,14 @@ public void doesNotHaveNextIfTakenAllElements() { @Test(expected = NoSuchElementException.class) public void throwsExceptionIfNextAfterFailedPredicate() { - Iterable words = singletonList("no"); + Iterable words = singletonList("no"); PredicatedTakingIterator predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator()); predicatedTakingIterator.next(); } @Test public void takesEverythingIfPredicateNeverFails() { - Iterable words = singletonList("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/iteration/PrependingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PrependingIteratorTest.java similarity index 96% rename from src/test/java/com/jnape/palatable/lambda/iteration/PrependingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/PrependingIteratorTest.java index 84bdebc1a..4e922aa43 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/PrependingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PrependingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import org.junit.Rule; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/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/iteration/RepetitiousIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIteratorTest.java index bcf9d2628..f99823f25 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/RepetitiousIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/ReversingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterableTest.java similarity index 90% rename from src/test/java/com/jnape/palatable/lambda/iteration/ReversingIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterableTest.java index e70b7cbd9..44ea4f446 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/ReversingIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/ReversingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIteratorTest.java similarity index 89% rename from src/test/java/com/jnape/palatable/lambda/iteration/ReversingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIteratorTest.java index 754345bfb..dd31b4bf7 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/ReversingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIteratorTest.java @@ -1,10 +1,10 @@ -package com.jnape.palatable.lambda.iteration; +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; @@ -22,7 +22,7 @@ public class ReversingIteratorTest { @Mock private Iterator iterator; - private ReversingIterator reversingIterator; + private ReversingIterator reversingIterator; @Before public void setUp() { @@ -47,6 +47,7 @@ public void reversesIterator() { } @Test + @SuppressWarnings("ResultOfMethodCallIgnored") public void doesNotReverseUntilNextIsCalled() { reversingIterator.hasNext(); verify(iterator, never()).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/iteration/RewindableIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java similarity index 85% rename from src/test/java/com/jnape/palatable/lambda/iteration/RewindableIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java index 3e1498b90..f07fe55a5 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/RewindableIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java @@ -1,15 +1,16 @@ -package com.jnape.palatable.lambda.iteration; +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; 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; @@ -18,7 +19,7 @@ public class RewindableIteratorTest { @Mock private Iterator iterator; - private RewindableIterator rewindableIterator; + private RewindableIterator rewindableIterator; @Before public void setUp() { @@ -51,13 +52,14 @@ 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 diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/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/iteration/ScanningIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ScanningIteratorTest.java index cde0c2f1b..43d24fad0 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/ScanningIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ScanningIteratorTest.java @@ -1,9 +1,9 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/SnocIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIterableTest.java similarity index 89% rename from src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIterableTest.java index 258e872a3..6451e8b4f 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIteratorTest.java similarity index 95% rename from src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIteratorTest.java index 6fca49817..96080f32b 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/SnocIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/TakingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIterableTest.java similarity index 90% rename from src/test/java/com/jnape/palatable/lambda/iteration/TakingIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIterableTest.java index b1bd84454..e72b6f060 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/TakingIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/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/iteration/TakingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIteratorTest.java index 9f17bcf4d..b405da88b 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/TakingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIteratorTest.java @@ -1,11 +1,11 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/UnfoldingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIteratorTest.java similarity index 96% rename from src/test/java/com/jnape/palatable/lambda/iteration/UnfoldingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIteratorTest.java index 68fa1ec9c..4b1199565 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/UnfoldingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/UnioningIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterableTest.java similarity index 94% rename from src/test/java/com/jnape/palatable/lambda/iteration/UnioningIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterableTest.java index dddec5ad9..b28bc2b61 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/UnioningIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterableTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/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/iteration/ZippingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/internal/iteration/ZippingIteratorTest.java index f88705a91..9fb639a6a 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/ZippingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ZippingIteratorTest.java @@ -1,13 +1,13 @@ -package com.jnape.palatable.lambda.iteration; +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/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 1282b4f66..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,6 +1,7 @@ package com.jnape.palatable.lambda.monoid; import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import org.junit.Test; import java.util.List; @@ -10,24 +11,32 @@ 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); + 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)); } 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 index 8a997382b..c1aaf214f 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AddAllTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AddAllTest.java @@ -13,6 +13,7 @@ public class AddAllTest { @Test + @SuppressWarnings("serial") public void monoid() { Monoid> addAll = addAll(HashSet::new); 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 d78aaf702..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 @@ -27,7 +27,7 @@ public void monoid() { @Test(timeout = 500) public void shortCircuiting() { Iterable bools = cons(false, repeat(true)); - And and = and(); + And and = and(); assertEquals(false, and.foldLeft(false, bools)); assertEquals(false, and.foldLeft(true, bools)); 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 index d3bf689db..faca56af2 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java @@ -15,7 +15,7 @@ public class ComposeTest { @Test public void monoid() throws ExecutionException, InterruptedException { - Monoid addition = Monoid.monoid((x, y) -> x + y, 0); + Monoid addition = Monoid.monoid(Integer::sum, 0); CompletableFuture failedFuture = new CompletableFuture() {{ completeExceptionally(new RuntimeException()); 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 76675e52e..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 @@ -29,7 +29,7 @@ public void monoid() { @Test(timeout = 500) public void shortCircuiting() { Iterable> maybeInts = repeat(just(1)); - First first = First.first(); + First first = First.first(); assertEquals(just(1), first.foldLeft(nothing(), maybeInts)); assertEquals(just(1), first.foldLeft(just(1), maybeInts)); 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 index be871be21..fe27f4833 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java @@ -18,7 +18,8 @@ import static org.junit.Assert.assertTrue; public class MergeMapsTest { - private static final Semigroup ADD = (x, y) -> x + y; + private static final Semigroup ADD = Integer::sum; + private Monoid> merge; @Before @@ -36,8 +37,8 @@ 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))); + 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))); + 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/OrTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java index d421c1e5d..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 @@ -27,7 +27,7 @@ public void monoid() { @Test(timeout = 500) public void shortCircuiting() { Iterable bools = cons(true, repeat(false)); - Or or = or(); + Or or = or(); assertEquals(true, or.foldLeft(false, bools)); assertEquals(true, or.foldLeft(true, bools)); 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 98b1f952d..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 @@ -12,8 +12,8 @@ public class PresentTest { @Test public void monoid() { - Present present = present(); - Semigroup addition = (x, y) -> x + y; + Present present = present(); + Semigroup addition = Integer::sum; assertEquals(just(3), present.apply(addition, just(1), just(2))); assertEquals(just(1), present.apply(addition, nothing(), just(1))); 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 bd664b01d..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 index 782ee334f..c7460e53e 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RunAllTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RunAllTest.java @@ -3,16 +3,18 @@ import com.jnape.palatable.lambda.monoid.Monoid; import org.junit.Test; -import static com.jnape.palatable.lambda.functions.IO.io; +import static com.jnape.palatable.lambda.io.IO.io; import static com.jnape.palatable.lambda.monoid.builtin.RunAll.runAll; -import static org.junit.Assert.assertEquals; +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((x, y) -> x + y, 0); - assertEquals((Integer) 3, runAll(add).apply(io(1), io(2)).unsafePerformIO()); - assertEquals((Integer) 0, runAll(add).identity().unsafePerformIO()); + 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/lens/IsoTest.java b/src/test/java/com/jnape/palatable/lambda/optics/IsoTest.java similarity index 67% rename from src/test/java/com/jnape/palatable/lambda/lens/IsoTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/IsoTest.java index 016de15ee..8c2bdb79f 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/IsoTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/IsoTest.java @@ -1,24 +1,26 @@ -package com.jnape.palatable.lambda.lens; +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.EqualityAwareIso; 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.lens.Iso.iso; -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.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 { @@ -26,9 +28,9 @@ 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}) - public Iso, Integer, Double> testSubject() { - return new EqualityAwareIso<>("123", 1.23d, ISO); + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Equivalence, Integer, Double>> testSubject() { + return equivalence(ISO, iso -> view(iso, "123")); } @Test @@ -54,4 +56,11 @@ public void mapsIndividuallyOverParameters() { 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/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/LensTest.java similarity index 58% rename from src/test/java/com/jnape/palatable/lambda/lens/LensTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/LensTest.java index 12d94b30c..8283cd4d4 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/LensTest.java @@ -1,17 +1,19 @@ -package com.jnape.palatable.lambda.lens; +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.EqualityAwareLens; 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; @@ -19,11 +21,11 @@ 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.lens.Lens.both; -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.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; +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; @@ -31,27 +33,34 @@ 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)); + 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}) - public Lens, List, Integer, String> testSubject() { - return new EqualityAwareLens<>(emptyMap(), lens(m -> m.get("foo"), (m, s) -> singletonList(m.get(s)))); + @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>apply(s -> new Identity<>(s.length()), asList("foo", "bar", "baz")).runIdentity(); + 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>apply(s -> new Const<>(s.length()), asList("foo", "bar", "baz")).runConst(); + 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); } @@ -65,10 +74,12 @@ public void mapsIndividuallyOverParameters() { .mapB((Maybe maybeI) -> maybeI.orElse(-1)); assertEquals(just(true), - theGambit.>, Identity>>apply( - maybeC -> new Identity<>(maybeC.fmap(c -> parseInt(Character.toString(c)))), - just("321")).runIdentity() - ); + theGambit., Identity, Identity>, Identity>, + Fn1, Identity>>, + Fn1, Identity>>>apply( + maybeC -> new Identity<>(maybeC.fmap(c -> parseInt(Character.toString(c))))) + .apply(just("321")) + .runIdentity()); } @Test @@ -87,9 +98,9 @@ public void andThenComposesInReverse() { @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); + 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")); @@ -97,7 +108,7 @@ public void bothSplitsFocusBetweenLenses() { @Test public void bothForSimpleLenses() { - Lens.Simple stringToInt = simpleLens(Integer::parseInt, (s, i) -> s + i.toString()); + 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")); 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/lens/functions/UnderTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/UnderTest.java similarity index 68% rename from src/test/java/com/jnape/palatable/lambda/lens/functions/UnderTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/functions/UnderTest.java index 58d44f19f..15827500b 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/functions/UnderTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/UnderTest.java @@ -1,14 +1,14 @@ -package com.jnape.palatable.lambda.lens.functions; +package com.jnape.palatable.lambda.optics.functions; -import com.jnape.palatable.lambda.lens.Iso; +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.lens.Iso.iso; -import static com.jnape.palatable.lambda.lens.functions.Under.under; +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; 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/lens/lenses/CollectionLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/CollectionLensTest.java similarity index 80% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/lenses/CollectionLensTest.java index 07d88c2de..1c13ef7c9 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/CollectionLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/CollectionLensTest.java @@ -1,6 +1,6 @@ -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.optics.Lens; import org.junit.Test; import java.util.ArrayList; @@ -8,10 +8,10 @@ import java.util.List; 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 com.jnape.palatable.lambda.lens.lenses.CollectionLens.asCopy; -import static com.jnape.palatable.lambda.lens.lenses.CollectionLens.asSet; +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; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/EitherLensTest.java similarity index 83% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/lenses/EitherLensTest.java index 08bf1d3e3..ef9aee18d 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/EitherLensTest.java @@ -1,23 +1,23 @@ -package com.jnape.palatable.lambda.lens.lenses; +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.lens.Lens; +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.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; +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(); + Lens.Simple, Maybe> right = EitherLens._right(); assertEquals(just(1), view(right, right(1))); assertEquals(nothing(), view(right, left("fail"))); @@ -29,7 +29,7 @@ public void rightFocusesOnRightValues() { @Test public void leftFocusesOnLeftValues() { - Lens.Simple, Maybe> left = EitherLens.left(); + Lens.Simple, Maybe> left = EitherLens._left(); assertEquals(just("fail"), view(left, left("fail"))); assertEquals(nothing(), view(left, right(1))); diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/HListLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/HListLensTest.java similarity index 90% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/HListLensTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/lenses/HListLensTest.java index 6296ff3ef..09c8de717 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/HListLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/HListLensTest.java @@ -1,11 +1,11 @@ -package com.jnape.palatable.lambda.lens.lenses; +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.lens.lenses.HListLens.elementAt; +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; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/HMapLensTest.java similarity index 96% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/lenses/HMapLensTest.java index 66073427e..94041fb6e 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/HMapLensTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.optics.lenses; import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/IterableLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/IterableLensTest.java similarity index 87% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/IterableLensTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/lenses/IterableLensTest.java index 561bc4d99..3353c9eed 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/IterableLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/IterableLensTest.java @@ -1,17 +1,17 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.optics.lenses; import com.jnape.palatable.lambda.adt.Maybe; -import com.jnape.palatable.lambda.lens.Iso; -import com.jnape.palatable.lambda.lens.Lens; +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.lens.Iso.simpleIso; -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.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; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/ListLensTest.java similarity index 79% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/lenses/ListLensTest.java index 78949c068..9a283f71c 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/ListLensTest.java @@ -1,16 +1,16 @@ -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.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.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.ListLens.asCopy; -import static com.jnape.palatable.lambda.lens.lenses.ListLens.elementAt; +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; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/MapLensTest.java similarity index 94% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/lenses/MapLensTest.java index 7a83e934a..5a97e5b4a 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/MapLensTest.java @@ -1,6 +1,6 @@ -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.optics.Lens; import org.junit.Test; import java.util.Collection; @@ -11,11 +11,11 @@ import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; -import static com.jnape.palatable.lambda.lens.Iso.iso; -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 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; @@ -29,6 +29,7 @@ import static testsupport.assertion.LensAssert.assertLensLawfulness; import static testsupport.matchers.IterableMatcher.iterates; +@SuppressWarnings("serial") public class MapLensTest { @Test diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MaybeLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/MaybeLensTest.java similarity index 80% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/MaybeLensTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/lenses/MaybeLensTest.java index 445aa8a73..4542be84b 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MaybeLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/MaybeLensTest.java @@ -1,19 +1,19 @@ -package com.jnape.palatable.lambda.lens.lenses; +package com.jnape.palatable.lambda.optics.lenses; import com.jnape.palatable.lambda.adt.Maybe; -import com.jnape.palatable.lambda.lens.Lens; +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.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.MaybeLens.liftA; -import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.liftB; -import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.liftS; -import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.liftT; +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; diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/SetLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/SetLensTest.java similarity index 92% rename from src/test/java/com/jnape/palatable/lambda/lens/lenses/SetLensTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/lenses/SetLensTest.java index 9421e50f6..6beff4b53 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/SetLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/SetLensTest.java @@ -1,11 +1,11 @@ -package com.jnape.palatable.lambda.lens.lenses; +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.lens.functions.Set.set; +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; 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 index 7df46e6e2..9d3dc4524 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java @@ -1,10 +1,17 @@ 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; @@ -12,12 +19,84 @@ public class AbsentTest { @Test public void semigroup() { - Absent absent = absent(); - Semigroup addition = (x, y) -> x + y; + 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); - assertEquals(just(3), absent.apply(addition, just(1), just(2))); - assertEquals(nothing(), absent.apply(addition, nothing(), just(1))); - assertEquals(nothing(), absent.apply(addition, just(1), nothing())); - assertEquals(nothing(), absent.apply(addition, nothing(), nothing())); + 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/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/functions/builtin/fn2/IntersectionTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/IntersectionTest.java similarity index 92% rename from src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntersectionTest.java rename to src/test/java/com/jnape/palatable/lambda/semigroup/builtin/IntersectionTest.java index 13c16ace3..0ecd33c71 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntersectionTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/IntersectionTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.functions.builtin.fn2; +package com.jnape.palatable.lambda.semigroup.builtin; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; @@ -10,7 +10,7 @@ import testsupport.traits.InfiniteIterableSupport; import testsupport.traits.Laziness; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Intersection.intersection; +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; 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/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/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 index c8840da69..2733690c4 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java @@ -1,17 +1,17 @@ package com.jnape.palatable.lambda.semigroup.builtin; -import com.jnape.palatable.lambda.semigroup.Semigroup; import org.junit.Test; -import static com.jnape.palatable.lambda.functions.IO.io; +import static com.jnape.palatable.lambda.io.IO.io; import static com.jnape.palatable.lambda.semigroup.builtin.RunAll.runAll; -import static org.junit.Assert.assertEquals; +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() { - Semigroup add = (x, y) -> x + y; - assertEquals((Integer) 3, runAll(add).apply(io(1), io(2)).unsafePerformIO()); + 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 index 9947e7f3a..ede9fde89 100644 --- a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java @@ -1,5 +1,7 @@ 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; @@ -8,29 +10,102 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -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.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}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class, MonadLaws.class, MonadRecLaws.class}) public Subjects> testSubject() { - return subjects(LambdaIterable.empty(), LambdaIterable.wrap(singleton(1)), LambdaIterable.wrap(replicate(100, 1))); + 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> fns = LambdaIterable.wrap(asList(x -> x + 1, x -> x - 1)); - LambdaIterable xs = LambdaIterable.wrap(asList(1, 2, 3)); - assertThat(xs.zip(fns).unwrap(), iterates(2, 3, 4, 0, 1, 2)); + 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 index 9cffe9efb..2be8673f7 100644 --- a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaMapTest.java +++ b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaMapTest.java @@ -15,6 +15,7 @@ @RunWith(Traits.class) public class LambdaMapTest { + @SuppressWarnings("serial") @TestTraits({FunctorLaws.class, TraversableLaws.class}) public Subjects> testSubject() { return subjects(LambdaMap.empty(), 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/EqualityAwareFn0.java b/src/test/java/testsupport/EqualityAwareFn0.java deleted file mode 100644 index 17b3318d5..000000000 --- a/src/test/java/testsupport/EqualityAwareFn0.java +++ /dev/null @@ -1,72 +0,0 @@ -package testsupport; - -import com.jnape.palatable.lambda.adt.Unit; -import com.jnape.palatable.lambda.functions.Fn0; -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functor.Applicative; -import com.jnape.palatable.lambda.monad.Monad; - -import java.util.function.Function; - -import static com.jnape.palatable.lambda.adt.Unit.UNIT; -import static java.util.Objects.hash; - -public final class EqualityAwareFn0 implements Fn0 { - private final Fn0 fn; - - public EqualityAwareFn0(Fn0 fn) { - this.fn = fn; - } - - @Override - public A apply() { - return fn.apply(); - } - - @Override - public A apply(Unit unit) { - return apply(); - } - - @Override - public EqualityAwareFn0 flatMap(Function>> f) { - return new EqualityAwareFn0<>(fn.flatMap(f)); - } - - @Override - public EqualityAwareFn0 fmap(Function f) { - return new EqualityAwareFn0<>(fn.fmap(f)); - } - - @Override - public EqualityAwareFn0 zip(Applicative, Fn1> appFn) { - return new EqualityAwareFn0<>(fn.zip(appFn)); - } - - @Override - public EqualityAwareFn0 pure(B b) { - return new EqualityAwareFn0<>(fn.pure(b)); - } - - - @Override - public EqualityAwareFn0 discardL(Applicative> appB) { - return new EqualityAwareFn0<>(fn.discardL(appB)); - } - - @Override - public EqualityAwareFn0 discardR(Applicative> appB) { - return new EqualityAwareFn0<>(fn.discardR(appB)); - } - - @Override - @SuppressWarnings("unchecked") - public boolean equals(Object other) { - return other instanceof Fn0 && ((Fn0) other).apply(UNIT).equals(apply(UNIT)); - } - - @Override - public int hashCode() { - return hash(fn); - } -} diff --git a/src/test/java/testsupport/EqualityAwareFn1.java b/src/test/java/testsupport/EqualityAwareFn1.java deleted file mode 100644 index 464962d19..000000000 --- a/src/test/java/testsupport/EqualityAwareFn1.java +++ /dev/null @@ -1,55 +0,0 @@ -package testsupport; - -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functor.Applicative; -import com.jnape.palatable.lambda.monad.Monad; - -import java.util.function.Function; - -import static java.util.Objects.hash; - -public final class EqualityAwareFn1 implements Fn1 { - private final A a; - private final Fn1 fn; - - public EqualityAwareFn1(A a, Fn1 fn) { - this.a = a; - this.fn = fn; - } - - @Override - public B apply(A a) { - return fn.apply(a); - } - - @Override - public EqualityAwareFn1 flatMap(Function>> f) { - return new EqualityAwareFn1<>(a, fn.flatMap(f)); - } - - @Override - public EqualityAwareFn1 fmap(Function f) { - return new EqualityAwareFn1<>(a, fn.fmap(f)); - } - - @Override - public EqualityAwareFn1 zip(Applicative, Fn1> appFn) { - return new EqualityAwareFn1<>(a, fn.zip(appFn)); - } - - @Override - public EqualityAwareFn1 pure(C c) { - return new EqualityAwareFn1<>(a, fn.pure(c)); - } - - @Override - @SuppressWarnings("unchecked") - public boolean equals(Object other) { - return other instanceof Fn1 && ((Fn1) other).apply(a).equals(apply(a)); - } - - @Override - public int hashCode() { - return hash(a, fn); - } -} diff --git a/src/test/java/testsupport/EqualityAwareIO.java b/src/test/java/testsupport/EqualityAwareIO.java deleted file mode 100644 index 822cfebf7..000000000 --- a/src/test/java/testsupport/EqualityAwareIO.java +++ /dev/null @@ -1,64 +0,0 @@ -package testsupport; - -import com.jnape.palatable.lambda.functions.IO; -import com.jnape.palatable.lambda.functor.Applicative; -import com.jnape.palatable.lambda.monad.Monad; - -import java.util.function.Function; - -import static java.util.Objects.hash; - -public final class EqualityAwareIO implements IO { - private final IO io; - - public EqualityAwareIO(IO io) { - this.io = io; - } - - @Override - public A unsafePerformIO() { - return io.unsafePerformIO(); - } - - @Override - public EqualityAwareIO flatMap(Function>> f) { - return new EqualityAwareIO<>(io.flatMap(f)); - } - - @Override - public EqualityAwareIO fmap(Function f) { - return new EqualityAwareIO<>(io.fmap(f)); - } - - @Override - public EqualityAwareIO zip(Applicative, IO> appFn) { - return new EqualityAwareIO<>(io.zip(appFn)); - } - - @Override - public EqualityAwareIO pure(B b) { - return new EqualityAwareIO<>(io.pure(b)); - } - - - @Override - public EqualityAwareIO discardL(Applicative> appB) { - return new EqualityAwareIO<>(io.discardL(appB)); - } - - @Override - public EqualityAwareIO discardR(Applicative> appB) { - return new EqualityAwareIO<>(io.discardR(appB)); - } - - @Override - @SuppressWarnings("unchecked") - public boolean equals(Object other) { - return other instanceof IO && ((IO) other).unsafePerformIO().equals(unsafePerformIO()); - } - - @Override - public int hashCode() { - return hash(io); - } -} diff --git a/src/test/java/testsupport/EqualityAwareIso.java b/src/test/java/testsupport/EqualityAwareIso.java deleted file mode 100644 index 0efc7f039..000000000 --- a/src/test/java/testsupport/EqualityAwareIso.java +++ /dev/null @@ -1,72 +0,0 @@ -package testsupport; - -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.lens.Iso; -import com.jnape.palatable.lambda.lens.LensLike; -import com.jnape.palatable.lambda.monad.Monad; - -import java.util.Objects; -import java.util.function.Function; - -import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; -import static com.jnape.palatable.lambda.lens.functions.View.view; - -public final class EqualityAwareIso implements Iso { - private final S s; - private final B b; - private final Iso iso; - - public EqualityAwareIso(S s, B b, Iso iso) { - this.s = s; - this.b = b; - this.iso = iso; - } - - @Override - public

, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( - PAFB pafb) { - return iso.apply(pafb); - } - - @Override - public , FB extends Functor> FT apply( - Function fn, S s) { - return iso.apply(fn, s); - } - - @Override - public EqualityAwareIso fmap(Function fn) { - return new EqualityAwareIso<>(s, b, iso.fmap(fn)); - } - - @Override - public EqualityAwareIso pure(U u) { - return new EqualityAwareIso<>(s, b, iso.pure(u)); - } - - @Override - public EqualityAwareIso zip( - Applicative, LensLike> appFn) { - return new EqualityAwareIso<>(s, b, iso.zip(appFn)); - } - - @Override - public EqualityAwareIso flatMap( - Function>> fn) { - return new EqualityAwareIso<>(s, b, iso.flatMap(fn)); - } - - @Override - @SuppressWarnings("unchecked") - public boolean equals(Object other) { - if (other instanceof EqualityAwareIso) { - Iso that = (EqualityAwareIso) other; - Boolean sameForward = both(view(this), view(that)).apply(s).into(Objects::equals); - Boolean sameReverse = both(view(this.mirror()), view(that.mirror())).apply(b).into(Objects::equals); - return sameForward && sameReverse; - } - return false; - } -} diff --git a/src/test/java/testsupport/EqualityAwareLens.java b/src/test/java/testsupport/EqualityAwareLens.java deleted file mode 100644 index e8a8cb52f..000000000 --- a/src/test/java/testsupport/EqualityAwareLens.java +++ /dev/null @@ -1,57 +0,0 @@ -package testsupport; - -import com.jnape.palatable.lambda.functor.Applicative; -import com.jnape.palatable.lambda.functor.Functor; -import com.jnape.palatable.lambda.functor.builtin.Const; -import com.jnape.palatable.lambda.lens.Lens; -import com.jnape.palatable.lambda.lens.LensLike; -import com.jnape.palatable.lambda.monad.Monad; - -import java.util.Objects; -import java.util.function.Function; - -public final class EqualityAwareLens implements Lens { - private final S s; - private final Lens lens; - - public EqualityAwareLens(S s, Lens lens) { - this.s = s; - this.lens = lens; - } - - @Override - public , FB extends Functor> FT apply( - Function fn, S s) { - return lens.apply(fn, s); - } - - @Override - public EqualityAwareLens flatMap( - Function>> f) { - return new EqualityAwareLens<>(s, lens.flatMap(f)); - } - - @Override - public EqualityAwareLens fmap(Function fn) { - return new EqualityAwareLens<>(s, lens.fmap(fn)); - } - - @Override - public EqualityAwareLens pure(U u) { - return new EqualityAwareLens<>(s, lens.pure(u)); - } - - @Override - public EqualityAwareLens zip( - Applicative, LensLike> appFn) { - return new EqualityAwareLens<>(s, lens.zip(appFn)); - } - - @Override - @SuppressWarnings("unchecked") - public boolean equals(Object other) { - return other instanceof Lens - && Objects.equals(((Lens) other)., Const, Const>apply(Const::new, s).runConst(), - this., Const, Const>apply(Const::new, s).runConst()); - } -} 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 e1ab75465..a39803ee1 100644 --- a/src/test/java/testsupport/applicatives/InvocationRecordingBifunctor.java +++ b/src/test/java/testsupport/applicatives/InvocationRecordingBifunctor.java @@ -1,24 +1,24 @@ 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 InvocationRecordingBifunctor biMap(Function lFn, - Function rFn) { + public InvocationRecordingBifunctor biMap(Fn1 lFn, + Fn1 rFn) { leftFn.set(lFn); rightFn.set(rFn); return (InvocationRecordingBifunctor) this; diff --git a/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java b/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java index 4f440bb38..403dee83c 100644 --- a/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java +++ b/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java @@ -1,24 +1,24 @@ 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 InvocationRecordingProfunctor diMap(Function lFn, - Function rFn) { + public InvocationRecordingProfunctor diMap(Fn1 lFn, + Fn1 rFn) { leftFn.set(lFn); rightFn.set(rFn); return (InvocationRecordingProfunctor) this; diff --git a/src/test/java/testsupport/assertion/LensAssert.java b/src/test/java/testsupport/assertion/LensAssert.java index e89678afb..a64e80fd5 100644 --- a/src/test/java/testsupport/assertion/LensAssert.java +++ b/src/test/java/testsupport/assertion/LensAssert.java @@ -2,10 +2,13 @@ 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.lens.LensLike; +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; @@ -15,21 +18,24 @@ 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.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; +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(LensLike lens, Iterable ss, Iterable bs) { + 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))) - .peek(failures -> {throw new AssertionError("Lens law failures\n\n" + failures);}); + .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, 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 5313e70af..ac1faee71 100644 --- a/src/test/java/testsupport/matchers/IterableMatcher.java +++ b/src/test/java/testsupport/matchers/IterableMatcher.java @@ -5,9 +5,9 @@ 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> { @@ -19,7 +19,7 @@ private IterableMatcher(Iterable expected) { @Override public boolean matches(Object actual) { - return actual instanceof Iterable && iterablesIterateSameElementsInOrder(expected, (Iterable) actual); + return actual instanceof Iterable && iterablesIterateSameElementsInOrder(expected, (Iterable) actual); } @Override @@ -32,36 +32,36 @@ public void describeMismatch(Object item, Description description) { if (item instanceof Iterable) { if (description.toString().endsWith("but: ")) description.appendText("was "); - description.appendText("<").appendText(stringify((Iterable) item)).appendText(">"); + 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()) @@ -71,10 +71,15 @@ private String stringify(Iterable iterable) { } @SafeVarargs + @SuppressWarnings("varargs") public static IterableMatcher iterates(E... es) { return new IterableMatcher<>(asList(es)); } + 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/LeftMatcher.java b/src/test/java/testsupport/matchers/LeftMatcher.java deleted file mode 100644 index 7982965a8..000000000 --- a/src/test/java/testsupport/matchers/LeftMatcher.java +++ /dev/null @@ -1,42 +0,0 @@ -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.functions.builtin.fn1.Constantly.constantly; - -public final class LeftMatcher extends TypeSafeMatcher> { - - private final Matcher lMatcher; - - private LeftMatcher(Matcher lMatcher) { - this.lMatcher = lMatcher; - } - - @Override - protected boolean matchesSafely(Either actual) { - return actual.match(lMatcher::matches, constantly(false)); - } - - @Override - public void describeTo(Description description) { - description.appendText("Left value of "); - lMatcher.describeTo(description); - } - - @Override - protected void describeMismatchSafely(Either item, Description mismatchDescription) { - mismatchDescription.appendText("was "); - item.peek(l -> { - mismatchDescription.appendText("Left value of "); - lMatcher.describeMismatch(l, mismatchDescription); - }, - r -> mismatchDescription.appendValue(item)); - } - - public static LeftMatcher isLeftThat(Matcher lMatcher) { - return new LeftMatcher<>(lMatcher); - } -} diff --git a/src/test/java/testsupport/matchers/LensMatcher.java b/src/test/java/testsupport/matchers/LensMatcher.java deleted file mode 100644 index c25f1fa86..000000000 --- a/src/test/java/testsupport/matchers/LensMatcher.java +++ /dev/null @@ -1,59 +0,0 @@ -package testsupport.matchers; - -import com.jnape.palatable.lambda.adt.hlist.Tuple2; -import com.jnape.palatable.lambda.lens.Lens; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; - -import java.util.HashSet; - -import static com.jnape.palatable.lambda.functions.builtin.fn1.Empty.empty; -import static com.jnape.palatable.lambda.functions.builtin.fn2.All.all; -import static com.jnape.palatable.lambda.functions.builtin.fn2.CartesianProduct.cartesianProduct; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; -import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; - -public class LensMatcher extends BaseMatcher> { - - private final Iterable> combinations; - - private LensMatcher(Iterable> combinations) { - this.combinations = combinations; - } - - @Override - @SuppressWarnings("unchecked") - public boolean matches(Object other) { - if (!(other instanceof Lens)) - return false; - - Lens lens = (Lens) other; - return youGetBackWhatYouPutIn(lens) - && puttingBackWhatYouGotChangesNothing(lens) - && settingTwiceIsEquivalentToSettingOnce(lens); - } - - @Override - public void describeTo(Description description) { - throw new UnsupportedOperationException(); - } - - private boolean youGetBackWhatYouPutIn(Lens lens) { - return all(into((s, b) -> view(lens, set(lens, b, s)).equals(b)), combinations); - } - - private boolean puttingBackWhatYouGotChangesNothing(Lens lens) { - return all(into((s, b) -> set(lens, view(lens, s), s).equals(s)), combinations); - } - - private boolean settingTwiceIsEquivalentToSettingOnce(Lens lens) { - return all(into((s, b) -> set(lens, b, set(lens, b, s)).equals(set(lens, b, s))), combinations); - } - - public static LensMatcher isLawfulForAllSAndB(Iterable ss, Iterable bs) { - return new LensMatcher<>(cartesianProduct(ss, bs)); - } -} diff --git a/src/test/java/testsupport/matchers/RightMatcher.java b/src/test/java/testsupport/matchers/RightMatcher.java deleted file mode 100644 index 08965bf2f..000000000 --- a/src/test/java/testsupport/matchers/RightMatcher.java +++ /dev/null @@ -1,42 +0,0 @@ -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.functions.builtin.fn1.Constantly.constantly; - -public final class RightMatcher extends TypeSafeMatcher> { - - private final Matcher rMatcher; - - private RightMatcher(Matcher rMatcher) { - this.rMatcher = rMatcher; - } - - @Override - protected boolean matchesSafely(Either actual) { - return actual.match(constantly(false), rMatcher::matches); - } - - @Override - public void describeTo(Description description) { - description.appendText("Right value of "); - rMatcher.describeTo(description); - } - - @Override - protected void describeMismatchSafely(Either item, Description mismatchDescription) { - mismatchDescription.appendText("was "); - item.peek(l -> mismatchDescription.appendValue(item), - r -> { - mismatchDescription.appendText("Right value of "); - rMatcher.describeMismatch(r, mismatchDescription); - }); - } - - public static RightMatcher isRightThat(Matcher rMatcher) { - return new RightMatcher<>(rMatcher); - } -} 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/traits/ApplicativeLaws.java b/src/test/java/testsupport/traits/ApplicativeLaws.java index 6255798ce..a193889fd 100644 --- a/src/test/java/testsupport/traits/ApplicativeLaws.java +++ b/src/test/java/testsupport/traits/ApplicativeLaws.java @@ -1,99 +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 com.jnape.palatable.traitor.traits.Trait; import java.util.Random; -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.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; -import static java.util.function.Function.identity; -public class ApplicativeLaws implements Trait> { +public class ApplicativeLaws> implements EquivalenceTrait> { @Override - public void test(Applicative applicative) { + 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(applicative), + .>, Maybe>>foldMap( + f -> f.apply(equivalence), asList(this::testIdentity, this::testComposition, this::testHomomorphism, this::testInterchange, this::testDiscardL, - this::testDiscardR) - ) - .peek(s -> { - throw new AssertionError("The following Applicative laws did not hold for instance of " + applicative.getClass() + ": \n\t - " + s); - }); + 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(Applicative applicative) { - Applicative v = applicative.pure(1); - Applicative, App> pureId = v.pure(identity()); - return v.zip(pureId).equals(v) - ? nothing() - : just("identity (v.zip(pureId).equals(v))"); + 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(Applicative applicative) { - Random random = new Random(); - Integer firstInt = random.nextInt(100); + private Maybe testComposition(Equivalence> equivalence) { + Random random = new Random(); + Integer firstInt = random.nextInt(100); Integer secondInt = random.nextInt(100); - Function, ? extends Function, ? extends Function>> compose = x -> x::compose; - Applicative, App> u = applicative.pure(x -> x + firstInt); - Applicative, App> v = applicative.pure(x -> x + secondInt); - Applicative w = applicative.pure("result: "); - - Applicative, ? extends Function, ? extends Function>>, App> pureCompose = u.pure(compose); - return w.zip(v.zip(u.zip(pureCompose))).equals(w.zip(v).zip(u)) - ? nothing() - : just("composition (w.zip(v.zip(u.zip(pureCompose))).equals((w.zip(v)).zip(u)))"); + 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(Applicative applicative) { - Function f = x -> x + 1; - int x = 1; + private Maybe testHomomorphism(Equivalence> equivalence) { + Fn1 f = x -> x + 1; + int x = 1; - Applicative pureX = applicative.pure(x); - Applicative, App> pureF = applicative.pure(f); - Applicative pureFx = applicative.pure(f.apply(x)); - return pureX.zip(pureF).equals(pureFx) - ? nothing() - : just("homomorphism (pureX.zip(pureF).equals(pureFx))"); + 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(Applicative applicative) { - Applicative, App> u = applicative.pure(x -> x + 1); + private Maybe testInterchange(Equivalence> equivalence) { int y = 1; - - Applicative pureY = applicative.pure(y); - return pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))) - ? nothing() - : just("interchange (pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))))"); + 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(Applicative applicative) { - Applicative u = applicative.pure("u"); - Applicative v = applicative.pure("v"); - - return u.discardL(v).equals(v.zip(u.zip(applicative.pure(constantly(identity()))))) - ? nothing() - : just("discardL u.discardL(v).equals(v.zip(u.zip(applicative.pure(constantly(identity())))))"); + 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(Applicative applicative) { - Applicative u = applicative.pure("u"); - Applicative v = applicative.pure("v"); + 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()))))"); + } - return u.discardR(v).equals(v.zip(u.zip(applicative.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 index b8bb42856..ba5f88d7e 100644 --- a/src/test/java/testsupport/traits/BifunctorLaws.java +++ b/src/test/java/testsupport/traits/BifunctorLaws.java @@ -1,48 +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 com.jnape.palatable.traitor.traits.Trait; - -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.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; import static java.util.Arrays.asList; -public class BifunctorLaws implements Trait> { +public class BifunctorLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return Bifunctor.class; + } @Override - public void test(Bifunctor bifunctor) { + public void test(Equivalence> equivalence) { Present.present((x, y) -> x + "\n\t - " + y) - ., Maybe>>foldMap( - f -> f.apply(bifunctor), + .>, Maybe>>foldMap( + f -> f.apply(equivalence), asList(this::testLeftIdentity, this::testRightIdentity, this::testMutualIdentity) ) - .peek(s -> { - throw new AssertionError("The following Bifunctor laws did not hold for instance of " + bifunctor.getClass() + ": \n\t - " + s); - }); + .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(Bifunctor bifunctor) { - return bifunctor.biMapL(id()).equals(bifunctor) - ? nothing() - : just("left identity (bifunctor.biMapL(id()).equals(bifunctor))"); + 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(Bifunctor bifunctor) { - return bifunctor.biMapR(id()).equals(bifunctor) - ? nothing() - : just("right identity (bifunctor.biMapR(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(Bifunctor bifunctor) { - return bifunctor.biMapL(id()).biMapR(id()).equals(bifunctor.biMap(id(), id())) - ? nothing() - : just("mutual identity (bifunctor.biMapL(id()).biMapR(id()).equals(bifunctor.biMap(id(),id()))"); + 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/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 index a6b5f3d0e..0371b1c81 100644 --- a/src/test/java/testsupport/traits/FunctorLaws.java +++ b/src/test/java/testsupport/traits/FunctorLaws.java @@ -1,44 +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 com.jnape.palatable.traitor.traits.Trait; - -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.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; -import static java.util.function.Function.identity; -public class FunctorLaws implements Trait> { +public class FunctorLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return Functor.class; + } @Override - public void test(Functor f) { + public void test(Equivalence> equivalence) { Present.present((x, y) -> x + "\n\t - " + y) - ., Maybe>>foldMap( - fn -> fn.apply(f), + .>, Maybe>>foldMap( + fn -> fn.apply(equivalence), asList(this::testIdentity, this::testComposition)) - .peek(s -> { - throw new AssertionError("The following Functor laws did not hold for instance of " + f.getClass() + ": \n\t - " + s); - }); + .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(Functor f) { - return f.fmap(identity()).equals(f) - ? nothing() - : just("identity (f.fmap(identity()).equals(f))"); + 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(Functor functor) { - Functor subject = functor.fmap(constantly(1)); - Function f = x -> x * 3; - Function g = x -> x - 2; - return subject.fmap(f.compose(g)).equals(subject.fmap(g).fmap(f)) - ? nothing() - : just("composition (functor.fmap(f.compose(g)).equals(functor.fmap(g).fmap(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 index 038fc6789..eda3b45c2 100644 --- a/src/test/java/testsupport/traits/MonadLaws.java +++ b/src/test/java/testsupport/traits/MonadLaws.java @@ -2,61 +2,65 @@ 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 com.jnape.palatable.traitor.traits.Trait; - -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.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.io.IO.throwing; import static com.jnape.palatable.lambda.monad.Monad.join; import static java.util.Arrays.asList; -public class MonadLaws implements Trait> { +public class MonadLaws> implements EquivalenceTrait> { @Override - public void test(Monad m) { + 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(m), asList( + .>, Maybe>>foldMap(f -> f.apply(equivalence), asList( this::testLeftIdentity, this::testRightIdentity, this::testAssociativity, this::testJoin)) - .peek(s -> { - throw new AssertionError("The following Monad laws did not hold for instance of " + m.getClass() + ": \n\t - " + s); - }); + .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(Monad m) { + private Maybe testLeftIdentity(Equivalence> equivalence) { Object a = new Object(); - Fn1> fn = id().andThen(m::pure); - return m.pure(a).flatMap(fn).equals(fn.apply(a)) + 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(Monad m) { - return m.flatMap(m::pure).equals(m) + 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(Monad m) { - Fn1> f = constantly(m.pure(new Object())); - Function> g = constantly(m.pure(new Object())); - return m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g))) + 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(Monad m) { - Monad, M> mma = m.pure(m.fmap(upcast())); - boolean equals = mma.flatMap(id()).equals(join(mma)); - return equals + 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 index 8ce0ab0f0..3b85202ba 100644 --- a/src/test/java/testsupport/traits/TraversableLaws.java +++ b/src/test/java/testsupport/traits/TraversableLaws.java @@ -2,67 +2,85 @@ 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 com.jnape.palatable.traitor.traits.Trait; - -import java.util.function.Function; 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; -@SuppressWarnings("Convert2MethodRef") -public class TraversableLaws implements Trait> { +public class TraversableLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return Traversable.class; + } @Override - public void test(Traversable traversable) { + public void test(Equivalence> equivalence) { Present.present((x, y) -> x + "\n\t - " + y) - ., Maybe>>foldMap( - f -> f.apply(traversable), + .>, Maybe>>foldMap( + f -> f.apply(equivalence), asList(this::testNaturality, this::testIdentity, this::testComposition) ) - .peek(s -> { - throw new AssertionError("The following Traversable laws did not hold for instance of " + traversable.getClass() + ": \n\t - " + s); - }); + .match(IO::io, + s -> throwing(new AssertionError("The following Traversable laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); } - private Maybe testNaturality(Traversable trav) { - Function> f = Identity::new; - Function, Either> t = id -> right(id.runIdentity()); + @SuppressWarnings("unchecked") + private Maybe testNaturality(Equivalence> equivalence) { + Fn1> f = Identity::new; + Fn1, Either> t = id -> right(id.runIdentity()); - Function, Applicative, Identity>> pureFn = x -> new Identity<>(x); - Function, Applicative, Either>> pureFn2 = x -> right(x); + Fn1, Identity>> pureFn = Identity::new; + Fn1, Either>> pureFn2 = Either::right; - return t.apply(trav.traverse(f, pureFn).fmap(id()).coerce()) - .equals(trav.traverse(t.compose(f), pureFn2).fmap(id()).coerce()) - ? nothing() - : just("naturality (t.apply(trav.traverse(f, pureFn).fmap(id()).coerce())\n" + - " .equals(trav.traverse(t.compose(f), pureFn2).fmap(id()).coerce()))"); + 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(Traversable trav) { - return trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new Identity<>(trav)) - ? nothing() - : just("identity (trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new Identity<>(trav))"); + 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))"); } - @SuppressWarnings("unchecked") - private Maybe testComposition(Traversable trav) { - Function> f = Identity::new; - Function> g = x -> new Identity<>(x); + private Maybe testComposition(Equivalence> equivalence) { + Fn1> f = Identity::new; + Fn1>> g = Identity::new; - return trav.traverse(f.andThen(x -> x.fmap(g)).andThen(Compose::new), x -> new Compose<>(new Identity<>(new Identity<>(x)))) - .equals(new Compose<>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> t.traverse(g, x -> new Identity<>(x))))) - ? nothing() - : just("compose (trav.traverse(f.andThen(x -> x.fmap(g)).andThen(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))))))"); + 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))))))"); } }

+ * 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/iteration/ImmutableQueue.java b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableQueue.java similarity index 67% rename from src/main/java/com/jnape/palatable/lambda/iteration/ImmutableQueue.java rename to src/main/java/com/jnape/palatable/lambda/internal/ImmutableQueue.java index 5a734a88c..dd605015e 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableQueue.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableQueue.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal; import com.jnape.palatable.lambda.adt.Maybe; @@ -8,22 +8,29 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; -abstract class ImmutableQueue implements Iterable { +/** + * Internal API. Use at your own peril. + */ +public abstract class ImmutableQueue implements Iterable { - abstract ImmutableQueue pushFront(A a); + public abstract ImmutableQueue pushFront(A a); - abstract ImmutableQueue pushBack(A a); + public abstract ImmutableQueue pushBack(A a); - abstract Maybe head(); + public abstract Maybe head(); - abstract ImmutableQueue tail(); + public abstract ImmutableQueue tail(); - abstract ImmutableQueue concat(ImmutableQueue other); + public abstract ImmutableQueue concat(ImmutableQueue other); - final boolean isEmpty() { + 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() { @@ -45,34 +52,34 @@ public A next() { @SuppressWarnings("unchecked") public static ImmutableQueue empty() { - return Empty.INSTANCE; + return (ImmutableQueue) Empty.INSTANCE; } private static final class Empty extends ImmutableQueue { - private static final Empty INSTANCE = new Empty(); + private static final Empty INSTANCE = new Empty<>(); @Override - ImmutableQueue pushFront(A a) { + public ImmutableQueue pushFront(A a) { return new NonEmpty<>(ImmutableStack.empty().push(a), ImmutableStack.empty()); } @Override - ImmutableQueue pushBack(A a) { + public ImmutableQueue pushBack(A a) { return pushFront(a); } @Override - ImmutableQueue concat(ImmutableQueue other) { + public ImmutableQueue concat(ImmutableQueue other) { return other; } @Override - Maybe head() { + public Maybe head() { return Maybe.nothing(); } @Override - ImmutableQueue tail() { + public ImmutableQueue tail() { return this; } } @@ -87,27 +94,27 @@ private NonEmpty(ImmutableStack outbound, ImmutableStack inbound) { } @Override - ImmutableQueue pushFront(A a) { + public ImmutableQueue pushFront(A a) { return new NonEmpty<>(outbound.push(a), inbound); } @Override - ImmutableQueue pushBack(A a) { + public ImmutableQueue pushBack(A a) { return new NonEmpty<>(outbound, inbound.push(a)); } @Override - ImmutableQueue concat(ImmutableQueue other) { + public ImmutableQueue concat(ImmutableQueue other) { return new NonEmpty<>(outbound, foldLeft(ImmutableStack::push, inbound, other)); } @Override - Maybe head() { + public Maybe head() { return outbound.head(); } @Override - ImmutableQueue tail() { + public ImmutableQueue tail() { ImmutableStack outTail = outbound.tail(); if (!outTail.isEmpty()) return new NonEmpty<>(outTail, inbound); diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableStack.java b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableStack.java similarity index 71% rename from src/main/java/com/jnape/palatable/lambda/iteration/ImmutableStack.java rename to src/main/java/com/jnape/palatable/lambda/internal/ImmutableStack.java index 619619efb..cc6e7175b 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableStack.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableStack.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal; import com.jnape.palatable.lambda.adt.Maybe; @@ -7,17 +7,20 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; -abstract class ImmutableStack implements Iterable { +/** + * Internal API. Use at your own peril. + */ +public abstract class ImmutableStack implements Iterable { - final ImmutableStack push(A a) { + public final ImmutableStack push(A a) { return new Node<>(a, this); } - abstract Maybe head(); + public abstract Maybe head(); - abstract ImmutableStack tail(); + public abstract ImmutableStack tail(); - final boolean isEmpty() { + public final boolean isEmpty() { return head().fmap(constantly(false)).orElse(true); } @@ -42,19 +45,19 @@ public A next() { @SuppressWarnings("unchecked") public static ImmutableStack empty() { - return Empty.INSTANCE; + return (ImmutableStack) Empty.INSTANCE; } private static final class Empty extends ImmutableStack { - private static final Empty INSTANCE = new Empty(); + private static final Empty INSTANCE = new Empty<>(); @Override - Maybe head() { + public Maybe head() { return Maybe.nothing(); } @Override - ImmutableStack tail() { + public ImmutableStack tail() { return this; } } @@ -69,12 +72,12 @@ public Node(A head, ImmutableStack tail) { } @Override - Maybe head() { + public Maybe head() { return Maybe.just(head); } @Override - ImmutableStack tail() { + public ImmutableStack tail() { return tail; } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/Runtime.java b/src/main/java/com/jnape/palatable/lambda/internal/Runtime.java similarity index 60% rename from src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/Runtime.java rename to src/main/java/com/jnape/palatable/lambda/internal/Runtime.java index 1546e811b..a280744fe 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/Runtime.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/Runtime.java @@ -1,6 +1,9 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; +package com.jnape.palatable.lambda.internal; -class Runtime { +public final class Runtime { + + private Runtime() { + } @SuppressWarnings("unchecked") public static RuntimeException throwChecked(Throwable t) throws T { diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/CombinatorialIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIterator.java similarity index 96% rename from src/main/java/com/jnape/palatable/lambda/iteration/CombinatorialIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIterator.java index 219582f4a..11922226a 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/CombinatorialIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.adt.hlist.Tuple2; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterable.java similarity index 89% rename from src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterable.java index f7ae08023..f1ff3a779 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterable.java @@ -1,4 +1,6 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.internal.ImmutableQueue; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/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/iteration/ConsingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ConsingIterator.java index d35d92c0b..b560974bb 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ConsingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConsingIterator.java @@ -1,15 +1,16 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/CyclicIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterable.java similarity index 88% rename from src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterable.java index 6d08dbcf2..3218892b2 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterable.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterator.java similarity index 94% rename from src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterator.java index 2705ffa06..827652a44 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/CyclicIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/DistinctIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterable.java similarity index 91% rename from src/main/java/com/jnape/palatable/lambda/iteration/DistinctIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterable.java index 447b76168..19758a6df 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/DistinctIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DistinctIterable.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.HashMap; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterable.java similarity index 91% rename from src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterable.java index b545a296f..1d9534b17 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterable.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterator.java similarity index 93% rename from src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterator.java index 5f69eb643..5b758695b 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/DroppingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterable.java similarity index 55% rename from src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterable.java index 1b9f315ff..7accde2f8 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterable.java @@ -1,19 +1,20 @@ -package com.jnape.palatable.lambda.iteration; +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 java.util.function.Function; 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; + private final List> predicates; + private final Iterable as; - public FilteringIterable(Function predicate, Iterable as) { - List> predicates = new ArrayList<>(singletonList(predicate)); + 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); @@ -25,7 +26,7 @@ public FilteringIterable(Function predicate, Itera @Override public Iterator iterator() { - Function metaPredicate = a -> all(p -> p.apply(a), predicates); + 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/iteration/FilteringIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterator.java similarity index 70% rename from src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterator.java index 5b9358c01..4a9346d40 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FilteringIterator.java @@ -1,15 +1,16 @@ -package com.jnape.palatable.lambda.iteration; +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 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/iteration/FlatteningIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIterator.java similarity index 92% rename from src/main/java/com/jnape/palatable/lambda/iteration/FlatteningIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIterator.java index 40458cd48..f02759374 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/FlatteningIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/FlatteningIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/GroupingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/GroupingIterator.java similarity index 89% rename from src/main/java/com/jnape/palatable/lambda/iteration/GroupingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/GroupingIterator.java index fdaf95ebe..c21d9950c 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/GroupingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/GroupingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.ArrayList; import java.util.Iterator; @@ -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/iteration/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/iteration/ImmutableIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableIterator.java index 7252d2313..cc41f275d 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/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/iteration/InfiniteIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/InfiniteIterator.java index 88b7c640d..f136da18d 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/InfiniteIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/InfiniteIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; public abstract class InfiniteIterator extends ImmutableIterator { @Override diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/InitIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/InitIterator.java similarity index 92% rename from src/main/java/com/jnape/palatable/lambda/iteration/InitIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/InitIterator.java index 0b1daa4e9..31eed515f 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/InitIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/InitIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/IterationInterruptedException.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/IterationInterruptedException.java similarity index 82% rename from src/main/java/com/jnape/palatable/lambda/iteration/IterationInterruptedException.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/IterationInterruptedException.java index 69b26f335..c52797198 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/IterationInterruptedException.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/IterationInterruptedException.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.builtin.fn4.RateLimit; @@ -9,6 +9,7 @@ * * @see RateLimit */ +@SuppressWarnings("serial") public final class IterationInterruptedException extends RuntimeException { public IterationInterruptedException(InterruptedException 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/iteration/MappingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterator.java similarity index 53% rename from src/main/java/com/jnape/palatable/lambda/iteration/MappingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterator.java index 0d336fa89..6fb64fd90 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/MappingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterator.java @@ -1,14 +1,15 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; import java.util.Iterator; -import java.util.function.Function; public final class MappingIterator extends ImmutableIterator { - private final Function function; - private final Iterator iterator; + private final Fn1 function; + private final Iterator iterator; - public MappingIterator(Function function, Iterator iterator) { + public MappingIterator(Fn1 function, Iterator iterator) { this.function = function; this.iterator = iterator; } 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/iteration/PredicatedTakingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterable.java similarity index 56% rename from src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterable.java index 14201849d..de9c7682e 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterable.java @@ -1,19 +1,20 @@ -package com.jnape.palatable.lambda.iteration; +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 java.util.function.Function; 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; + private final List> predicates; + private final Iterable as; - public PredicatedTakingIterable(Function predicate, Iterable as) { - List> predicates = new ArrayList<>(singletonList(predicate)); + 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); @@ -25,7 +26,7 @@ public PredicatedTakingIterable(Function predicate @Override public Iterator iterator() { - Function metaPredicate = a -> all(p -> p.apply(a), predicates); + 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/iteration/PredicatedTakingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterator.java similarity index 67% rename from src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterator.java index 370ef1e84..9760450b0 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterator.java @@ -1,16 +1,16 @@ -package com.jnape.palatable.lambda.iteration; +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 final class PredicatedTakingIterator extends ImmutableIterator { - private final Function predicate; - private final RewindableIterator rewindableIterator; - private boolean stillTaking; + 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/iteration/PrependingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PrependingIterator.java similarity index 93% rename from src/main/java/com/jnape/palatable/lambda/iteration/PrependingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/PrependingIterator.java index 53755c0f2..8914c2aca 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/PrependingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PrependingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/RateLimitingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterable.java similarity index 66% rename from src/main/java/com/jnape/palatable/lambda/iteration/RateLimitingIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterable.java index 27427b2cb..3a7c856d4 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/RateLimitingIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterable.java @@ -1,20 +1,20 @@ -package com.jnape.palatable.lambda.iteration; +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; -import java.util.function.Supplier; public final class RateLimitingIterable implements Iterable { - private final Iterable as; - private final Set>> rateLimits; + private final Iterable as; + private final Set>> rateLimits; - public RateLimitingIterable(Iterable as, Set>> rateLimits) { - Set>> combinedRateLimits = new HashSet<>(rateLimits); + public RateLimitingIterable(Iterable as, Set>> rateLimits) { + Set>> combinedRateLimits = new HashSet<>(rateLimits); if (as instanceof RateLimitingIterable) { RateLimitingIterable inner = (RateLimitingIterable) as; combinedRateLimits.addAll(inner.rateLimits); diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/RateLimitingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterator.java similarity index 71% rename from src/main/java/com/jnape/palatable/lambda/iteration/RateLimitingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterator.java index f7b622bb8..1046f406c 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/RateLimitingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterator.java @@ -1,6 +1,8 @@ -package com.jnape.palatable.lambda.iteration; +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; @@ -11,24 +13,25 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; -import java.util.function.Supplier; +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; + private final Iterator asIterator; + private final Set>> rateLimits; + private final Map>, List> timeSlicesByRateLimit; - public RateLimitingIterator(Iterator asIterator, Set>> rateLimits) { + public RateLimitingIterator(Iterator asIterator, Set>> rateLimits) { this.asIterator = asIterator; this.rateLimits = rateLimits; timeSlicesByRateLimit = new HashMap<>(); @@ -51,23 +54,26 @@ private void awaitNextTimeSlice() { rateLimits.forEach(rateLimit -> { awaitNextTimeSliceForRateLimit(rateLimit); List timeSlicesForRateLimit = timeSlicesByRateLimit.getOrDefault(rateLimit, new ArrayList<>()); - timeSlicesForRateLimit.add(rateLimit._3().get()); + timeSlicesForRateLimit.add(rateLimit._3().apply()); timeSlicesByRateLimit.put(rateLimit, timeSlicesForRateLimit); }); } - private void awaitNextTimeSliceForRateLimit(Tuple3> rateLimit) { + private void awaitNextTimeSliceForRateLimit(Tuple3> rateLimit) { while (rateLimitExhaustedInTimeSlice(rateLimit)) { - trying(() -> sleep(0)).biMapL(IterationInterruptedException::new).orThrow(); + join(trying(() -> sleep(0)) + .fmap(Try::success) + .catching(InterruptedException.class, t -> failure(new IterationInterruptedException(t)))) + .orThrow(); } } - private boolean rateLimitExhaustedInTimeSlice(Tuple3> rateLimit) { + private boolean rateLimitExhaustedInTimeSlice(Tuple3> rateLimit) { List timeSlicesForRateLimit = timeSlicesByRateLimit.getOrDefault(rateLimit, emptyList()); return rateLimit.into((limit, duration, instantSupplier) -> { - Instant timeSliceEnd = instantSupplier.get(); + Instant timeSliceEnd = instantSupplier.apply(); Instant previousTimeSliceEnd = timeSliceEnd.minus(duration); - timeSlicesForRateLimit.removeIf(lt(previousTimeSliceEnd)); + 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/iteration/RepetitiousIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIterator.java similarity index 81% rename from src/main/java/com/jnape/palatable/lambda/iteration/RepetitiousIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIterator.java index 51a6715a5..55652d4c4 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/RepetitiousIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; public final class RepetitiousIterator extends InfiniteIterator { diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterable.java similarity index 92% rename from src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterable.java index df4c25e45..29aff81db 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterable.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterator.java similarity index 94% rename from src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterator.java index 428898058..70e1baa60 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ReversingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/RewindableIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java similarity index 91% rename from src/main/java/com/jnape/palatable/lambda/iteration/RewindableIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java index d4de2c5d7..092e7e98b 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/RewindableIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; @@ -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/iteration/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/iteration/ScanningIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ScanningIterator.java index d195b14b3..43fc4ece5 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ScanningIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ScanningIterator.java @@ -1,16 +1,17 @@ -package com.jnape.palatable.lambda.iteration; +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/iteration/SnocIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterable.java similarity index 93% rename from src/main/java/com/jnape/palatable/lambda/iteration/SnocIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterable.java index a16947b4a..9a0118200 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterable.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Collections; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterator.java similarity index 91% rename from src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterator.java index 7c084c7ab..e3c336a91 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/SnocIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/TakingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterable.java similarity index 91% rename from src/main/java/com/jnape/palatable/lambda/iteration/TakingIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterable.java index ce67f8e89..f228b596a 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/TakingIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterable.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/TakingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterator.java similarity index 92% rename from src/main/java/com/jnape/palatable/lambda/iteration/TakingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterator.java index 3b558d68e..900f7ddb7 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/TakingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TakingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; 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/iteration/UnfoldingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIterator.java similarity index 58% rename from src/main/java/com/jnape/palatable/lambda/iteration/UnfoldingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIterator.java index 418121c7c..7ed719046 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/UnfoldingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIterator.java @@ -1,19 +1,19 @@ -package com.jnape.palatable.lambda.iteration; +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 java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; public final class UnfoldingIterator extends ImmutableIterator { - private final Function>> function; - private B seed; - private Maybe> maybeAcc; + private final Fn1>> function; + private B seed; + private Maybe> maybeAcc; - public UnfoldingIterator(Function>> function, B seed) { + public UnfoldingIterator(Fn1>> function, B seed) { this.function = function; this.seed = seed; } @@ -27,13 +27,12 @@ public boolean hasNext() { } @Override - @SuppressWarnings("ConstantConditions") public A next() { if (!hasNext()) throw new NoSuchElementException(); - Tuple2 acc = maybeAcc.orElseThrow(NoSuchElementException::new); - A next = acc._1(); + 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/iteration/UnioningIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterable.java similarity index 92% rename from src/main/java/com/jnape/palatable/lambda/iteration/UnioningIterable.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterable.java index 641ab8255..fc4c6ed60 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/UnioningIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterable.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/ZippingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ZippingIterator.java similarity index 54% rename from src/main/java/com/jnape/palatable/lambda/iteration/ZippingIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/ZippingIterator.java index 1b0909fd4..107a6ec08 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ZippingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ZippingIterator.java @@ -1,14 +1,15 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn2; import java.util.Iterator; -import java.util.function.BiFunction; public final class ZippingIterator extends ImmutableIterator { - private final BiFunction zipper; - private final Iterator asIterator; - private final Iterator bsIterator; + private final Fn2 zipper; + private final Iterator asIterator; + private final Iterator bsIterator; - public ZippingIterator(BiFunction zipper, Iterator asIterator, + public ZippingIterator(Fn2 zipper, Iterator asIterator, Iterator bsIterator) { this.asIterator = asIterator; this.bsIterator = bsIterator; 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}. + *

, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( - PAFB pafb) { - return composed.apply(pafb); + 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 @@ -93,6 +101,11 @@ static Simple typeSafeKey() { public boolean equals(Object obj) { return obj instanceof Simple ? this == obj : Objects.equals(obj, this); } + + @Override + public int hashCode() { + return super.hashCode(); + } }; } @@ -105,7 +118,12 @@ interface Simple extends TypeSafeKey { @Override @SuppressWarnings("unchecked") - default