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 3273cf69d..3aecf9b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,315 @@ # 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] -_No changes_ + +### Added +- `ReaderT#ask`, a static factory method for returning an identity `ReaderT` + +### Fixed +- nested `DropWhile`s no longer incorrectly deforest using disjunction + +## [5.4.0] - 2021-09-17 + +### Changed +- `Absent` folds short-circuit on the first `nothing()` +- `EitherMatcher#isLeftThat/isRightThat` support contravariant bounds on their delegates + +### Added +- `IterateT#runStep`, a method used to run a single step of an IterateT without the contractual guarantee of emitting a + value or reaching the end +- `These#fromMaybes :: Maybe a -> Maybe b -> Maybe (These a b)` +- `EitherMatcher#isLeftOf/isRightOf` for asserting equality + +### Fixed +- `WriterT` now keeps an immediate reference to the embedded monad's `pure` + +## [5.3.0] - 2020-12-07 + +### Changed +- `IterateT#unfold` now only computes a single `Pure` for the given input +- `ReaderT#fmap` and `StateT#fmap` avoid unnecessary calls to `pure` +- `MaybeT` implements `MonadError` + +### Added +- `$`, function application represented as a higher-order `Fn2` +- `Fn1#withSelf`, a static method for constructing a self-referencing `Fn1` +- `HNil/SingletonHList/TupleX#snoc`, a method to add a new last element (append to a tuple) +- `Tuple2-8#init`, for populating a `TupleN` with all but the last element + +### Fixed +- `IterateT#trampolineM` now yields and stages all recursive result values, rather + than prematurely terminating on the first termination result +- `IterateT#flatMap` is now stack-safe regardless of how many consecutive empty `IterateT`s + are returned and regardless of whether the monad is strict or lazy or internally trampolined + +## [5.2.0] - 2020-02-12 + +### Changed +- `HList#cons` static factory method auto-promotes to specialized `HList` if there is one +- `EitherT` gains a `MonadError` instance + +### Added +- `MergeHMaps`, a `Monoid` that merges `HMap`s by merging the values via key-specified `Semigroup`s +- `Id#id` overload that accepts an argument and returns it +- `MaybeT#or`, choose the first `MaybeT` that represents an effect around `just` a value +- `MaybeT#filter`, filter a `Maybe` inside an effect +- `StateMatcher, StateTMatcher, WriterTMatcher` +- `ReaderT#and`, category composition between `ReaderT` instances: `(a -> m b) -> (b -> m c) -> (a -> m c)` +- `IterateT`, [`ListT` done right](https://wiki.haskell.org/ListT_done_right) +- `Comparison`, a type-safe sum of `LT`, `EQ`, and `GT` orderings +- `Compare`, a function taking a `Comparator` and returning a `Comparison` +- `Min/Max/...With` variants for inequality testing with a `Comparator` + +## [5.1.0] - 2019-10-13 +### Changed +- All monad transformers that can support composable parallelism do support it + +### Added +- `Writer`, the writer monad +- `EndoK`, a monoid formed under endomorphism for any monad +- `AutoBracket`, a specialized form of `Bracket` for `AutoCloseable` that closes the resource during cleanup + +### Fixed +- `SafeT#zip` is now stack-safe regardless of the underlying monad's `zip` implementation + +### Deprecated +- `Force`, in favor if traversing into an `IO` and explicitly running it + +## [5.0.0] - 2019-09-23 +### Changed +- ***Breaking Change***: `MonadT` is now witnessed by a parameter for better subtyping, and no longer requires a common + `run` interface; each `run` method is now `runXXXT()`, where `XXX` is the name of the transformer in question +- ***Breaking Change***: `Applicative#zip` and derivatives evaluate from left to right now across the board. +- ***Breaking Change***: `testsupport.EquatableM` replaced with `Equivalence` +- `Alter` now merely requires an `Fn1` instead of an explicit `Effect` +- `IO` now internally trampolines all forms of composition, including `lazyZip`; + sequencing very large iterables of `IO` will work, if you have the heap, and + retain parallelization inflection points + +### Added +- `MonadRec`, monads that support a stack-safe `trampolineM` method with defaults for all exported monads +- `MonadError`, monads that can be thrown to and caught from, with defaults for `IO`, `Either`, `Maybe`, and `Try` +- `MonadBase`, an interface representing lifting infrastructure for `Monad`s +- `MonadReader` and `MonadWriter`, general interfaces for reading from an environment and accumulating results +- `SafeT`, a stack-safe monad transformer for any `MonadRec` +- `ReaderT`, the transformer for the reader monad +- `WriterT`, a monad transformer for an accumulation and a value +- `StateT`, the `State` monad transformer +- `Lift`, an existentially-quantified lifting function for some `MonadBase` type +- `IO#interruptible`, for wrapping an `IO` in a thread interruption check +- `IO#monitorSync`, for wrapping an `IO` in a `synchronized` block on a given lock object +- `IO#pin`, for pinning an `IO` to an `Executor` without yet executing it +- `IO#fuse`, for fusing the fork opportunities of a given `IO` into a single linearized `IO` +- `IO#memoize`, for memoizing an `IO` by caching its first successful result +- `Until`, for repeatedly executing an `IO` until its result matches a predicate +- `Optic#andThen`, `Optic#compose`, and other defaults added +- `Prism#andThen`, `Prism#compose` begets another `Prism` +- `Prism#fromPartial` public interfaces +- `Tuple2-8#fromIterable`, for populating a `TupleN` with the first `N` elements of an `Iterable` +- `Fn2#curry`, for converting an `Fn1,C>` to an `Fn2` +- `EquivalenceTrait`, a traitor `Trait` to make it easier to test properties of type-classes with a separate equivalence + relation + +### Deprecated +- `Peek`, `Peek2`, `Maybe#peek`, and `Either#peek` in favor of explicitly matching into `IO` and running it +- `IO#exceptionally` in favor of `IO#catchError` (from `MonadError`) + +## [4.0.0] - 2019-05-20 +### Changed +- ***Breaking Change***: `IO` is now sealed and moved to its own package. Most previous constructions using the static + factory methods should continue to work (by simply targeting `Supplier` now instead of an + anonymous `IO`), but some might need to be reworked, and subtyping is obviously no longer + supported. +- ***Breaking Change***: Breaking all dependency on `java.util.function` types across the board. All `Fn*` types target + methods now support throwing `Throwable`; `apply` is now defaulted and will simply bypass javac + to throw checked exceptions as if they were unchecked. All `Checked` variants have been + eliminated as a consequence, as they are no longer necessary. Also, straggler functions like + `Partial2/3` that only existed to aid in partial application of non-curried functions are now + superfluous, and have also been eliminated. +- ***Breaking Change***: `FoldRight` now requires `Lazy` as part of its interface to support short-circuiting operations +- ***Breaking Change***: Eliminated all raw types and java11 warnings. This required using capture in unification + parameters for Functor and friends, so nearly every functor's type-signature changed. +- ***Breaking Change***: `Strong` is now called `Cartesian` to better reflect the type of strength +- ***Breaking Change***: new Optic type hierarchy more faithfully encodes profunctor constraints on optics, new `Optic` + type is now the supertype of `Lens` and `Iso`, and `lens` package has been moved to `optics` +- ***Breaking Change***: `Try` and `Either` no longer preserve `Throwable` type since it was inherently not type-safe + anyway; Try is therefore no longer a `Bifunctor`, and `orThrow` can be used to declare checked + exceptions that could be caught by corresponding catch blocks +- `IO` is now stack-safe, regardless of whether the composition nests linearly or recursively + +### Added +- `Lazy`, a monad supporting stack-safe lazy evaluation +- `LazyRec`, a function for writing stack-safe recursive algorithms embedded in `Lazy` +- `Applicative#lazyZip`, for zipping two applicatives in a way that might not require evaluation of one applicative +- `MonadT`, a general interface representing monad transformers +- `MaybeT`, a monad transformer for `Maybe` +- `EitherT`, a monad transformer for `Either` +- `IdentityT`, a monad transformer for `Identity` +- `LazyT`, a monad transformer for `Lazy` +- `Endo`, a monoid formed by `Fn1` under composition +- `State`, the state `Monad` +- `Downcast`, a function supporting unchecked down-casting +- `Cocartesian`, profunctorial strength in cocartesian coproduct terms +- `Prism`, an `Optic` that is nearly an `Iso` but can fail in one direction +- `Market`, `Tagged`, profunctors supporting optics +- `Re` for viewing an `Optic` in one direction reliably +- `Pre` for viewing at most one value from an `Optic` in one direction +- `SideEffect`, for representing side-effects runnable by `IO` +- `IO#safe`, mapping an `IO` to an `IO>` that will never throw +- `IO#ensuring`, like `finally` semantics for `IO`s +- `IO#throwing`, for producing an `IO` that will throw a given `Throwable` when executed +- `Bracket`, for bracketing an `IO` operation with a mapping operation and a cleanup operation + +## [3.3.0] - 2019-02-18 +### Added +- `MergeMaps`, a `Monoid` on `Map` formed by `Map#merge` +- `CheckedEffect` is now a `CheckedFn1` +- `CheckedSupplier` is now a `CheckedFn1` +- `CheckedFn1` now overrides all possible methods with covariant return type +- `MapLens#asCopy` has overload taking copy function +- `MapLens#valueAt` has overload taking copy function +- `SortWith` for sorting an `Iterable` given a `Comparator` over its elements +- `IO#externallyManaged`, for supplying an `IO` with externally-managed futures +- test jar is now published +- `Monad#join` static alias for `flatMap(id())` +- `Effect#effect` static factory method taking `Fn1` +- `IO#unsafePerformAsyncIO` overloads for running `IO`s asynchronously +- `IO`s automatically encode parallelism in composition +- `IO#exceptionally` for recovering from failure during `IO` operation +- `Optic`, a generic supertype for all profunctor optics + +### Fixed +- issue where certain ways to compose `Effect`s unintentionally nullified the effect + +### Removed +- `AddAll` semigroup, deprecated in previous release +- Dyadic `Either#flatMap()`, deprecated in previous release + +## [3.2.0] - 2018-12-08 +### Changed +- ***Breaking Change***: `Difference` and `Intersection` no longer instances of `Semigroup` and moved to + `functions.builtin.fn2` package +- ***Breaking Change***: `Absent` moved to `semigroup.builtin` package +- ***Breaking Change***: `Effect#accept()` is now the required method to implement in the functional interface +- ***Breaking Change***: `Fn0#apply()` is now the required method to implement in the functional interface +- ***Breaking Change***: `GTBy`, `GT`, `LTBy`, `LT`, `GTEBy`, `GTE`, `LTEBy`, and `LTE` take the right-hand side first + for more intuitive partial application +- ***Breaking Change***: `Effect` now returns an `IO` +- `RightAny` overload returns `Monoid` +- monoids now all fold with respect to `foldMap` +- monoid folding now implicitly starts with the identity, regardless of iterable population +- `Concat` monoid can now fold infinite iterables +- all `Function` are now `Function` for better compatibility +- `Either#diverge` returns a `Choice3` +- `Maybe` is now a `CoProduct2` of `Unit` and `A` +- `Fn0` now additionally implements `Callable` +- `CheckedRunnable` is an `IO` + +### Added +- `Predicate#predicate` static factory method +- `Product2-8` left/right rotation methods +- `Tuple2-8` specializations of left/right product rotation +- `CheckedEffect`, an `Effect` variant that can throw checked exceptions +- `CheckedFn1#checked`, convenience static factory method to aid inference +- `LiftA3-8`, higher-arity analogs to `LiftA2` +- `Alter`, for applying an `Effect` to an input and returning it, presumably altered +- `Clamp`, for clamping a value between two bounds +- `Between`, for determining if a value is in a closed interval +- `Strong`, profunctor strength +- `IO` monad +- `RunAll` semigroup and monoid instance for `IO` + +### Deprecated +- `AddAll` semigroup, in favor of the monoid that no longer mutates any argument +- Dyadic `Either#flatMap()`, in favor of `Either#match` + +## [3.1.0] - 2018-07-16 +### Added +- `Fn3-8` static factory overloads to aid in coercing lambdas +- Adding composition guarantees to `LensLike` +- `CmpEqBy`, `CmpEq`, `GTBy`, `GT`, `LTBy`, `LT`, `GTEBy`, `GTE`, `LTEBy`, and `LTE` inequality checks +- `MinBy`, `MaxBy`, `Min`, and `Max` semigroups +- `Product2-8` interfaces, representing general product types +- `Union`, a monoid that behaves like a lazy set union on `Iterable`s +- `Difference`, a semigroup that behaves like a partially lazy set difference on `Iterable`s +- `LambdaMap`, extension point for `j.u.Map`, similar to `LambdaIterable` +- `Sequence#sequence` overloads for `j.u.Map` that traverse via intermediate `LambdaMap` instances +- `Intersection`, a semigroup that behaves like a lazy set intersection on `Iterable`s +- `Fn0`, a function from `Unit` to some value +- `Fn1#thunk`, producing an `Fn0` +- `Absent`, a monoid over `Maybe` that is absence biased +- `RateLimit`, a function that iterates elements from an `Iterable` according to some rate limit +- `Try#withResources`, `Try`'s expression analog to Java 7's try-with-resources statement +- `Occurrences`, for counting the occurrences of the members of an `Iterable` +- `Effect`, an `Fn0` returning `UNIT` +- `Noop`, a no-op `Effect` +- `Fn1#widen`, add an ignored argument to the beginning of any function to raise its arity by one + +### Changed +- `Tuple2-8` now implement `Product2-8` +- `Into` now accepts `Map.Entry` +- `Into3-8` now accept a product of the same cardinality, instead of requiring a tuple +- `CoProduct2-8#project` now return generalized products +- `Choice2-8#project` return tuples +- `liftA2` receives more parameters to aid inference +- `Compose#getCompose` now supports inference + +### Removed +- `MapLens#mappingValues`, deprecated in a prior release +- `CollectionLens#asSet`, deprecated in a prior release +- `CollectionLens#asStream`, deprecated in a prior release + +## [3.0.3] - 2018-05-27 +### Added +- `Lens#toIso`, for converting a lens to an iso +- `HMap#hMap` overloads up to 8 bindings deep +- `Schema`, schemas for extracting multiple values from `HMap`s by aggregating `TypeSafeKey`s + +### Fixed +- Deforested iterables execute in intended nesting order, where essential + +## [3.0.2] - 2018-05-21 +### Added +- `IterableLens#mapping`, an `Iso` that maps values + +### Changed +- `TypeSafeKey.Simple` now has a default `#apply` implementation + +### Fixed +- mapped `TypeSafeKey` instances can be used for initial put in an `HMap`, and the base key can be used to retrieve them +- Merged pull request fixing issue storing values at mapped `TypeSafeKey` in `singletonHMap` + +## [3.0.1] - 2018-05-13 +### Changed +- `ToMap` accepts an `Iterable` covariant in `Map.Entry` +- `RecursiveResult#invert` is also a `RecursiveResult` +- `First`/`And`/`Or` monoids all utilize short-circuiting +- `Monoid#foldLeft/foldRight` delegate to `Monoid#reduceLeft/reduceRight`, respectively + +### Added +- `Upcast` for safely casting up a type hierarchy +- `SetLens`, lenses operating on `Set`s +- `ToArray`, for converting `Iterable` to `A[]` ## [3.0.0] - 2018-05-04 ### Changed - ***Breaking Change***: `Sequence` now has two more type parameters to aid in inference - ***Breaking Change***: `Traversable#traverse` now has three more type parameters to aid in inference -- ***Breaking Change***: `Monad#zip` now forces `m a -> b` before `m a` in default `Applicative#zip` implementation; this is only breaking for types that are sensitive to computation order (the resulting values are the same) -- ***Breaking Change***: `TypeSafeKey` is now dually parametric (single parameter analog is preserved in `TypeSafeKey.Simple`) +- ***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` @@ -83,7 +375,8 @@ _No changes_ - `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 @@ -193,7 +486,8 @@ _No changes_ - `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 @@ -203,14 +497,16 @@ _No changes_ - `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` @@ -264,7 +560,8 @@ _No changes_ ## [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 @@ -303,7 +600,19 @@ _No changes_ - `Monadic/Dyadic/TriadicFunction`, `Predicate`, `Tuple2`, `Tuple3` - `Functor`, `BiFunctor`, `ProFunctor` -[Unreleased]: https://github.com/palatable/lambda/compare/lambda-3.0.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 +[3.0.2]: https://github.com/palatable/lambda/compare/lambda-3.0.1...lambda-3.0.2 +[3.0.1]: https://github.com/palatable/lambda/compare/lambda-3.0.0...lambda-3.0.1 [3.0.0]: https://github.com/palatable/lambda/compare/lambda-2.1.1...lambda-3.0.0 [2.1.1]: https://github.com/palatable/lambda/compare/lambda-2.1.0...lambda-2.1.1 [2.1.0]: https://github.com/palatable/lambda/compare/lambda-2.0.0...lambda-2.1.0 diff --git a/LICENSE b/LICENSE index 89c5bf75e..fb5630322 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 palatable +Copyright (c) 2019 John Napier (jnape) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 5d8eff2e7..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.0.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.0.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.0.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 ``` @@ -444,8 +447,7 @@ Examples of traversable functors include: - `Choice*` - `Either` - `Const` and `Identity` -- `TraversableIterable` for wrapping `Iterable` in an instance of `Traversable` -- `TraversableOptional` for wrapping `Optional` in an instance of `Traversable` +- `LambdaIterable` for wrapping `Iterable` in an instance of `Traversable` In addition to implementing `fmap` from `Functor`, implementing a traversable functor involves providing an implementation of `traverse`. @@ -737,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 75fbb10f8..bae03b1e3 100644 --- a/pom.xml +++ b/pom.xml @@ -9,12 +9,12 @@ lambda - 3.0.1-SNAPSHOT + 5.5.1-SNAPSHOT jar Lambda - Functional patterns for Java 8 + Functional patterns for Java http://www.github.com/palatable/lambda @@ -53,10 +53,10 @@ - 3.1 - 1.2 + 1.3.0 3.3 - 1.3 + 2.1 + 3.1.1 @@ -66,13 +66,15 @@ org.hamcrest - hamcrest-all - ${hamcrest-all.version} + hamcrest + ${hamcrest.version} test org.mockito - mockito-all + mockito-core + 2.28.2 + test com.jnape.palatable @@ -80,12 +82,6 @@ ${traitor.version} test - - org.apache.commons - commons-lang3 - ${commons-lang3.version} - test - @@ -103,6 +99,25 @@ + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + com.jnape.palatable.lambda + + + + + + + test-jar + + + + diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 0fbcf2dd5..a26b6a42e 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -1,24 +1,32 @@ package com.jnape.palatable.lambda.adt; +import com.jnape.palatable.lambda.adt.choice.Choice3; import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; -import com.jnape.palatable.lambda.functions.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; /** @@ -29,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() { } @@ -51,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()); } @@ -62,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); } @@ -70,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()); } @@ -87,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()); } /** @@ -105,7 +118,8 @@ public final Either filter(Function pred, Supplier * @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))); } @@ -121,29 +135,24 @@ public final Either filter(Function pred, Function Either flatMap(Function>> rightFn) { - return flatMap(Either::left, rightFn.andThen(Applicative::coerce)); + @SuppressWarnings("RedundantTypeArguments") + public Either flatMap(Fn1>> rightFn) { + return match(Either::left, rightFn.fmap(Monad>::coerce)); } /** - * If a right value, apply rightFn to the unwrapped right value and return the resulting - * Either; otherwise, apply the unwrapped left value to leftFn and return the resulting - * Either. - * - * @param leftFn the function to apply if a left value - * @param rightFn the function to apply if a right value - * @param the new left parameter type - * @param the new right parameter type - * @return the result of either rightFn or leftFn, depending on whether this is a right or a left + * {@inheritDoc} */ - public final Either flatMap(Function> leftFn, - Function> rightFn) { - return match(leftFn, rightFn); + @Override + public Either trampolineM(Fn1, Either>> fn) { + return match(Either::left, trampoline(a -> fn.apply(a).>>coerce() + .match(l -> terminate(left(l)), + aOrB -> aOrB.fmap(Either::right)))); } @Override public final Either invert() { - return flatMap(Either::right, Either::left); + return match(Either::right, Either::left); } /** @@ -158,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)))), @@ -170,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(); } /** @@ -193,63 +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 appFn.>>coerce().flatMap(this::biMapR); + public final Either zip(Applicative, Either> appFn) { + return MonadError.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Either>> lazyAppFn) { + return match(l -> lazy(left(l)), + r -> lazyAppFn.fmap(eitherLF -> eitherLF.fmap(f -> f.apply(r)).coerce())); + } + + /** + * {@inheritDoc} + */ @Override public final Either discardL(Applicative> appB) { - return 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(); } /** @@ -266,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()); } /** @@ -357,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; @@ -365,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); } @@ -395,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 93a700061..dc5595983 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java @@ -1,19 +1,35 @@ package com.jnape.palatable.lambda.adt; -import com.jnape.palatable.lambda.functions.builtin.fn2.Peek; -import com.jnape.palatable.lambda.functions.specialized.checked.CheckedSupplier; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.adt.choice.Choice3; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.io.IO; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadError; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import java.util.Optional; -import 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 @@ -22,17 +38,24 @@ * @param the optional parameter type * @see Optional */ -public abstract class Maybe implements Monad, Traversable { +public abstract class Maybe implements + CoProduct2>, + MonadError>, + MonadRec>, + Traversable> { + private Maybe() { } /** * If the value is present, return it; otherwise, return the value supplied by otherSupplier. * - * @param otherSupplier the supplier for the other value + * @param otherFn0 the supplier for the other value * @return this value, or the supplied other value */ - public abstract A orElseGet(Supplier otherSupplier); + public final A orElseGet(Fn0 otherFn0) { + return match(__ -> otherFn0.apply(), id()); + } /** * If the value is present, return it; otherwise, return other. @@ -53,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(); + })); } /** @@ -66,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())); } /** @@ -110,36 +149,108 @@ 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 Monad.super.zip(appFn).coerce(); + 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 final Maybe discardL(Applicative appB) { - return Monad.super.discardL(appB).coerce(); + 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 discardR(Applicative appB) { - return Monad.super.discardR(appB).coerce(); + public final Maybe discardL(Applicative> appB) { + return MonadError.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public abstract Maybe flatMap(Function> f); + public final Maybe discardR(Applicative> appB) { + return MonadError.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("RedundantTypeArguments") + @Override + public final Maybe flatMap(Fn1>> f) { + return match(constantly(nothing()), f.fmap(Monad>::coerce)); + } + + /** + * {@inheritDoc} + */ + @Override + public Maybe trampolineM(Fn1, Maybe>> fn) { + return match(constantly(nothing()), trampoline(a -> fn.apply(a).>>coerce() + .match(constantly(terminate(nothing())), + aOrB -> aOrB.fmap(Maybe::just)))); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice3 diverge() { + return match(Choice3::a, Choice3::b); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple2, Maybe> project() { + return CoProduct2.super.project().into(HList::tuple); + } + + /** + * {@inheritDoc} + */ + @Override + public Choice2 invert() { + return match(Choice2::b, Choice2::a); + } /** * If this value is present, accept it by consumer; otherwise, do nothing. * - * @param 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 , 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)); } /** @@ -192,80 +303,69 @@ public static Maybe just(A a) { return new Just<>(a); } + /** + * Return nothing. + * + * @param the type of the value, if there was one + * @return nothing + */ @SuppressWarnings("unchecked") public static Maybe nothing() { - return Nothing.INSTANCE; + return (Maybe) Nothing.INSTANCE; } - private static final class Just extends Maybe { - - private final A a; - - private Just(A a) { - this.a = a; - } - - @Override - public A orElseGet(Supplier otherSupplier) { - return a; - } - - @Override - public Maybe flatMap(Function> f) { - return f.apply(a).coerce(); - } + /** + * The canonical {@link Pure} instance for {@link Maybe}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureMaybe() { + return Maybe::just; + } - @Override - @SuppressWarnings("unchecked") - public , - AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return fn.apply(a).fmap(Just::new).fmap(Applicative::coerce).coerce(); - } + private static final class Nothing extends Maybe { + private static final Nothing INSTANCE = new Nothing<>(); - @Override - public boolean equals(Object other) { - return other instanceof Just && Objects.equals(this.a, ((Just) other).a); + private Nothing() { } @Override - public int hashCode() { - return Objects.hash(a); + public R match(Fn1 aFn, Fn1 bFn) { + return aFn.apply(UNIT); } @Override public String toString() { - return "Just " + a; + return "Nothing"; } } - private static final class Nothing extends Maybe { - private static final Nothing INSTANCE = new Nothing(); + private static final class Just extends Maybe { - private Nothing() { + private final A a; + + private Just(A a) { + this.a = a; } @Override - @SuppressWarnings("unchecked") - public Maybe flatMap(Function> f) { - return nothing(); + public R match(Fn1 aFn, Fn1 bFn) { + return bFn.apply(a); } @Override - @SuppressWarnings("unchecked") - public , AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( - Function fn, Function pure) { - return pure.apply((TravB) nothing()); + public boolean equals(Object other) { + return other instanceof Just && Objects.equals(this.a, ((Just) other).a); } @Override - public A orElseGet(Supplier otherSupplier) { - return otherSupplier.get(); + public int hashCode() { + return Objects.hash(a); } @Override public String toString() { - return "Nothing"; + return "Just " + a; } } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/These.java b/src/main/java/com/jnape/palatable/lambda/adt/These.java 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 0a2169f09..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,31 +1,44 @@ package com.jnape.palatable.lambda.adt; import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; -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.fn2.Peek2.peek2; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.internal.Runtime.throwChecked; /** * A {@link Monad} of the evaluation outcome of an expression that might throw. Try/catch/finally semantics map to * trying/catching/ensuring, respectively. * - * @param the {@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() { } @@ -33,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))); } /** @@ -50,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); } @@ -64,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))); } /** @@ -82,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()); } @@ -93,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 the possible failure + * @throws T anything that the call site may want to explicitly catch or indicate could be thrown */ - public abstract A orThrow() throws T; + public final A orThrow() throws T { + try { + return orThrow(id()); + } catch (Throwable t) { + throw throwChecked(t); + } + } + + /** + * If this is a success value, return it. Otherwise, transform the captured failure with fn and throw + * the result. + * + * @param fn the {@link Throwable} transformation + * @param the type of the thrown {@link Throwable} + * @return possibly the success value + * @throws T the transformation output + */ + public abstract A orThrow(Fn1 fn) throws T; /** * If this is a success, wrap the value in a {@link Maybe#just} and return it. Otherwise, return {@link @@ -121,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()); } @@ -129,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 pure(B b) { - return success(b); + public Try fmap(Fn1 fn) { + return MonadError.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Try zip(Applicative, Try> appFn) { - return Monad.super.zip(appFn).coerce(); + public Try flatMap(Fn1>> f) { + return match(Try::failure, a -> f.apply(a).coerce()); } + /** + * {@inheritDoc} + */ @Override - public Try discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + public Try pure(B b) { + return success(b); } + /** + * {@inheritDoc} + */ @Override - public Try discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + public Try zip(Applicative, Try> appFn) { + return MonadError.super.zip(appFn).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 Lazy> lazyZip(Lazy, Try>> lazyAppFn) { + return match(f -> lazy(failure(f)), + s -> lazyAppFn.fmap(tryF -> tryF.fmap(f -> f.apply(s)).coerce())); } + /** + * {@inheritDoc} + */ @Override - public Try biMap(Function lFn, - Function rFn) { - return match(t -> failure(lFn.apply(t)), a -> success(rFn.apply(a))); + public Try discardL(Applicative> appB) { + return MonadError.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Try biMapL(Function fn) { - return (Try) BoundedBifunctor.super.biMapL(fn); + public Try discardR(Applicative> appB) { + return MonadError.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override - public Try biMapR(Function fn) { - return (Try) BoundedBifunctor.super.biMapR(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 + @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); } @@ -219,47 +293,133 @@ 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; }); } - private static final class Failure extends Try { - private final T t; + /** + * Given a {@link Fn0}<{@link AutoCloseable}> aSupplier and an {@link Fn1} + * fn, apply fn to the result of aSupplier, ensuring that the result has its + * {@link AutoCloseable#close() close} method invoked, regardless of the outcome. + *

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

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

+ * This is {@link Try}'s equivalent of + * + * try-with-resources, introduced in Java 7. + * + * @param fn0 the resource supplier + * @param fn the function body + * @param the resource type + * @param the function return type + * @return a {@link Try} representing the result of the function's application to the resource + */ + @SuppressWarnings("try") + public static Try withResources( + Fn0 fn0, + Fn1> fn) { + return trying(() -> { + try (A resource = fn0.apply()) { + return fn.apply(resource).fmap(upcast()); + } + }).flatMap(id()); + } + + /** + * Convenience overload of {@link Try#withResources(Fn0, Fn1) withResources} that cascades dependent resource + * creation via nested calls. + * + * @param fn0 the first resource supplier + * @param bFn the dependent resource function + * @param fn the function body + * @param the first resource type + * @param the second resource type + * @param the function return type + * @return a {@link Try} representing the result of the function's application to the dependent resource + */ + public static Try withResources( + Fn0 fn0, + Fn1 bFn, + Fn1> fn) { + return withResources(fn0, a -> withResources(() -> bFn.apply(a), fn::apply)); + } + + /** + * Convenience overload of {@link Try#withResources(Fn0, Fn1, Fn1) withResources} that + * cascades + * two dependent resource creations via nested calls. + * + * @param fn0 the first resource supplier + * @param bFn the second resource function + * @param cFn the final resource function + * @param fn the function body + * @param the first resource type + * @param the second resource type + * @param the final resource type + * @param the function return type + * @return a {@link Try} representing the result of the function's application to the final dependent resource + */ + public static Try withResources( + Fn0 fn0, + Fn1 bFn, + Fn1 cFn, + Fn1> fn) { + return withResources(fn0, bFn, b -> withResources(() -> cFn.apply(b), fn::apply)); + } + + /** + * The canonical {@link Pure} instance for {@link Try}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureTry() { + return Try::success; + } + + private static final class Failure extends Try { + private final Throwable t; - private Failure(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); } @@ -281,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) { @@ -289,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 5276adb04..067762f16 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java @@ -1,14 +1,27 @@ package com.jnape.palatable.lambda.adt.choice; import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** * Canonical ADT representation of {@link CoProduct2}. Unlike {@link Either}, there is no concept of "success" or @@ -19,76 +32,144 @@ * @see Either * @see Choice3 */ -public abstract class Choice2 implements CoProduct2>, Monad>, Bifunctor, Traversable> { +public abstract class Choice2 implements + CoProduct2>, + MonadRec>, + Bifunctor>, + Traversable> { private Choice2() { } + /** + * Specialize this choice's projection to a {@link Tuple2}. + * + * @return a {@link Tuple2} + */ + @Override + public Tuple2, Maybe> project() { + return into(HList::tuple, CoProduct2.super.project()); + } + + /** + * {@inheritDoc} + */ @Override public final Choice3 diverge() { return match(Choice3::a, Choice3::b); } + /** + * {@inheritDoc} + */ @Override public Choice2 invert() { return match(Choice2::b, Choice2::a); } + /** + * {@inheritDoc} + */ @Override - public final Choice2 fmap(Function fn) { - return 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 appFn.>>coerce() - .match(Choice2::a, this::biMapR); + public Choice2 zip(Applicative, Choice2> appFn) { + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Choice2>> lazyAppFn) { + return match(a -> lazy(a(a)), + b -> lazyAppFn.fmap(choiceF -> choiceF.fmap(f -> f.apply(b)).coerce())); + } + + /** + * {@inheritDoc} + */ @Override public Choice2 discardL(Applicative> appB) { - return 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 - @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 Choice2 trampolineM(Fn1, Choice2>> fn) { + return match(Choice2::a, + trampoline(b -> fn.apply(b).>>coerce() + .match(a -> terminate(a(a)), + bOrC -> bOrC.fmap(Choice2::b)))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(a -> pure.apply(Choice2.a(a).coerce()), + b -> fn.apply(b).>fmap(Choice2::b).fmap(Functor::coerce).coerce()); } /** @@ -115,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; @@ -124,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); } @@ -156,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 8cc0abba5..8116344e5 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java @@ -1,14 +1,27 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into3.into3; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** * Canonical ADT representation of {@link CoProduct3}. @@ -21,79 +34,146 @@ */ public abstract class Choice3 implements CoProduct3>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { private Choice3() { } + /** + * Specialize this choice's projection to a {@link Tuple3}. + * + * @return a {@link Tuple3} + */ + @Override + public Tuple3, Maybe, Maybe> project() { + return into3(HList::tuple, CoProduct3.super.project()); + } + + /** + * {@inheritDoc} + */ @Override public final Choice4 diverge() { return match(Choice4::a, Choice4::b, Choice4::c); } + /** + * {@inheritDoc} + */ @Override - public final Choice2 converge(Function> convergenceFn) { - return match(Choice2::a, Choice2::b, convergenceFn.andThen(cp2 -> cp2.match(Choice2::a, Choice2::b))); + public final Choice2 converge(Fn1> convergenceFn) { + return match(Choice2::a, Choice2::b, convergenceFn.fmap(cp2 -> cp2.match(Choice2::a, Choice2::b))); } + /** + * {@inheritDoc} + */ @Override - public final Choice3 fmap(Function fn) { - return 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 MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ @Override - public Choice3 zip(Applicative, Choice3> appFn) { - return appFn.>>coerce() - .match(Choice3::a, Choice3::b, this::biMapR); + 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(); } /** @@ -135,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; @@ -144,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); } @@ -177,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); } @@ -210,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); } @@ -233,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 9a604fa71..7f5afb2e6 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java @@ -1,14 +1,27 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; import com.jnape.palatable.lambda.adt.coproduct.CoProduct4; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple4; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into4.into4; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** * Canonical ADT representation of {@link CoProduct4}. @@ -22,83 +35,150 @@ */ public abstract class Choice4 implements CoProduct4>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { private Choice4() { } + /** + * Specialize this choice's projection to a {@link Tuple4}. + * + * @return a {@link Tuple4} + */ + @Override + public Tuple4, Maybe, Maybe, Maybe> project() { + return into4(HList::tuple, CoProduct4.super.project()); + } + + /** + * {@inheritDoc} + */ @Override public Choice5 diverge() { return match(Choice5::a, Choice5::b, Choice5::c, Choice5::d); } + /** + * {@inheritDoc} + */ @Override - public Choice3 converge(Function> convergenceFn) { - return match(Choice3::a, - Choice3::b, - Choice3::c, - convergenceFn.andThen(cp3 -> cp3.match(Choice3::a, Choice3::b, Choice3::c))); + public Choice3 converge(Fn1> convergenceFn) { + return match(Choice3::a, Choice3::b, Choice3::c, + convergenceFn.fmap(cp3 -> cp3.match(Choice3::a, Choice3::b, Choice3::c))); } + /** + * {@inheritDoc} + */ @Override - public final Choice4 fmap(Function fn) { - return 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 appFn.>>coerce() - .match(Choice4::a, Choice4::b, Choice4::c, this::biMapR); + public Choice4 zip(Applicative, Choice4> appFn) { + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, Choice4>> lazyAppFn) { + return match(a -> lazy(a(a)), + b -> lazy(b(b)), + c -> lazy(c(c)), + d -> lazyAppFn.fmap(choiceF -> choiceF.fmap(f -> f.apply(d)).coerce())); + } + + /** + * {@inheritDoc} + */ @Override public Choice4 discardL(Applicative> appB) { - return 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 - @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 Choice4 trampolineM( + Fn1, Choice4>> fn) { + return match(Choice4::a, + Choice4::b, + Choice4::c, + trampoline(d -> fn.apply(d).>>coerce() + .match(a -> terminate(a(a)), + b -> terminate(b(b)), + c -> terminate(c(c)), + dOrE -> dOrE.fmap(Choice4::d)))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(a -> pure.apply(Choice4.a(a).coerce()), + b -> pure.apply(Choice4.b(b).coerce()), + c -> pure.apply(Choice4.c(c).coerce()), + d -> fn.apply(d).>fmap(Choice4::d).fmap(Functor::coerce)) + .coerce(); } /** @@ -157,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; @@ -166,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); } @@ -199,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); } @@ -232,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); } @@ -265,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 758aa2a00..4a085c94a 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java @@ -1,14 +1,26 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct4; import com.jnape.palatable.lambda.adt.coproduct.CoProduct5; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple5; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.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}. @@ -23,85 +35,154 @@ */ public abstract class Choice5 implements CoProduct5>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { private Choice5() { } + /** + * Specialize this choice's projection to a {@link Tuple5}. + * + * @return a {@link Tuple5} + */ + @Override + 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 MonadRec.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ @Override - public Choice5 zip(Applicative, Choice5> appFn) { - return appFn.>>coerce() - .match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, this::biMapR); + 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(); } /** @@ -179,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; @@ -188,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); } @@ -222,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); } @@ -256,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); } @@ -290,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); } @@ -324,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 9286e9abf..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 @@ -1,14 +1,26 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct5; import com.jnape.palatable.lambda.adt.coproduct.CoProduct6; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple6; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import 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}. @@ -24,89 +36,158 @@ */ public abstract class Choice6 implements CoProduct6>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { private Choice6() { } + /** + * Specialize this choice's projection to a {@link Tuple6}. + * + * @return a {@link Tuple6} + */ + @Override + public Tuple6, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + return into6(HList::tuple, CoProduct6.super.project()); + } + + /** + * {@inheritDoc} + */ @Override public Choice7 diverge() { return match(Choice7::a, Choice7::b, Choice7::c, Choice7::d, Choice7::e, Choice7::f); } + /** + * {@inheritDoc} + */ @Override - public Choice5 converge(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 - @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 Choice6 trampolineM( + Fn1, Choice6>> fn) { + return flatMap(trampoline(f -> fn.apply(f).>>coerce().match( + a -> terminate(Choice6.a(a)), + b -> terminate(Choice6.b(b)), + c -> terminate(Choice6.c(c)), + d -> terminate(Choice6.d(d)), + e -> terminate(Choice6.e(e)), + fRec -> fRec.fmap(Choice6::f)))); + } + + /** + * {@inheritDoc} + */ + @Override + public , TravB extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return match(a -> pure.apply(Choice6.a(a).coerce()), + b -> pure.apply(Choice6.b(b).coerce()), + c -> pure.apply(Choice6.c(c).coerce()), + d -> pure.apply(Choice6.d(d).coerce()), + e -> pure.apply(Choice6.e(e).coerce()), + f -> fn.apply(f).>fmap(Choice6::f) + .fmap(Applicative::coerce)) + .coerce(); } /** @@ -205,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; @@ -214,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); } @@ -246,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); } @@ -278,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); } @@ -310,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); } @@ -342,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); } @@ -374,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 18d9bdc9e..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 @@ -1,14 +1,26 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct6; import com.jnape.palatable.lambda.adt.coproduct.CoProduct7; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple7; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import 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}. @@ -25,92 +37,163 @@ */ public abstract class Choice7 implements CoProduct7>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { private Choice7() { } + /** + * Specialize this choice's projection to a {@link Tuple7}. + * + * @return a {@link Tuple7} + */ + @Override + public Tuple7, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + return into7(HList::tuple, CoProduct7.super.project()); + } + + /** + * {@inheritDoc} + */ @Override public Choice8 diverge() { return match(Choice8::a, Choice8::b, Choice8::c, Choice8::d, Choice8::e, Choice8::f, Choice8::g); } + /** + * {@inheritDoc} + */ @Override - public Choice6 converge( - 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(); } /** @@ -232,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; @@ -241,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); } @@ -274,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); } @@ -307,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); } @@ -340,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); } @@ -373,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); } @@ -406,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); } @@ -439,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 e7ec41a49..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 @@ -1,14 +1,26 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.coproduct.CoProduct7; import com.jnape.palatable.lambda.adt.coproduct.CoProduct8; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.Tuple8; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; -import 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}. @@ -25,89 +37,159 @@ */ public abstract class Choice8 implements CoProduct8>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { private Choice8() { } + /** + * Specialize this choice's projection to a {@link Tuple8}. + * + * @return a {@link Tuple8} + */ + @Override + public Tuple8, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + return into8(HList::tuple, CoProduct8.super.project()); + } + + /** + * {@inheritDoc} + */ @Override public Choice7 converge( - 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(); } /** @@ -254,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 { @@ -264,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); } @@ -297,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); } @@ -330,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); } @@ -363,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); } @@ -396,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); } @@ -429,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); } @@ -462,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); } @@ -495,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 5aadd08a0..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 @@ -3,11 +3,9 @@ import com.jnape.palatable.lambda.adt.Either; import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.choice.Choice2; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import 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; @@ -20,8 +18,9 @@ *

* Learn more about Coproducts. * - * @param the first possible type - * @param the second possible type + * @param the first possible type + * @param the second possible type + * @param the recursive type of this coproduct (used for embedding) * @see Choice2 * @see Either */ @@ -31,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 @@ -62,26 +61,26 @@ 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); } }; } /** - * Project this coproduct onto a tuple, such that the slot in the tuple that corresponds to this coproduct's value - * is present, while the other slots are absent. + * Project this coproduct onto a product, such that the index in the product that corresponds to this coproduct's + * value is present, while the other indices are absent. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection */ - default Tuple2, Maybe> project() { + default Product2, Maybe> project() { return match(a -> tuple(just(a), nothing()), b -> tuple(nothing(), just(b))); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ @@ -90,7 +89,7 @@ default Maybe projectA() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ @@ -106,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); } }; @@ -117,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 6937d4735..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.hlist.Tuple3; +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; @@ -15,9 +14,10 @@ /** * A generalization of the coproduct of three types. * - * @param the first possible type - * @param the second possible type - * @param the third possible type + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the recursive type of this coproduct (used for embedding) * @see CoProduct2 */ @FunctionalInterface @@ -26,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. @@ -46,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); } }; @@ -67,34 +66,24 @@ 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); } /** - * Project this coproduct onto a tuple. + * Project this coproduct onto a product. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection * @see CoProduct2#project() */ - default Tuple3, Maybe, Maybe> project() { + default Product3, Maybe, Maybe> project() { return match(a -> tuple(just(a), nothing(), nothing()), b -> tuple(nothing(), just(b), nothing()), c -> tuple(nothing(), nothing(), just(c))); } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ @@ -103,7 +92,7 @@ default Maybe projectA() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ @@ -112,7 +101,7 @@ default Maybe projectB() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. * * @return an optional value representing the projection of the "c" type index */ @@ -125,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 96939fba9..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.hlist.Tuple4; +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; @@ -15,10 +14,11 @@ /** * A generalization of the coproduct of four types. * - * @param the first possible type - * @param the second possible type - * @param the third possible type - * @param the fourth possible type + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the recursive type of this coproduct (used for embedding) * @see CoProduct2 */ @FunctionalInterface @@ -27,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. @@ -50,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); } }; @@ -67,35 +67,17 @@ 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); } /** - * Project this coproduct onto a tuple. + * Project this coproduct onto a product. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection * @see CoProduct2#project() */ - default Tuple4, Maybe, Maybe, Maybe> project() { + default Product4, Maybe, Maybe, Maybe> project() { return match(a -> tuple(just(a), nothing(), nothing(), nothing()), b -> tuple(nothing(), just(b), nothing(), nothing()), c -> tuple(nothing(), nothing(), just(c), nothing()), @@ -103,7 +85,7 @@ default Tuple4, Maybe, Maybe, Maybe> project() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ @@ -112,7 +94,7 @@ default Maybe projectA() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ @@ -121,7 +103,7 @@ default Maybe projectB() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. * * @return an optional value representing the projection of the "c" type index */ @@ -130,7 +112,7 @@ default Maybe projectC() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fourth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fourth slot value. * * @return an optional value representing the projection of the "d" type index */ @@ -143,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 ffaf5ef1b..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.hlist.Tuple5; +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; @@ -15,11 +14,12 @@ /** * A generalization of the coproduct of five types. * - * @param the first possible type - * @param the second possible type - * @param the third possible type - * @param the fourth possible type - * @param the fifth possible type + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the recursive type of this coproduct (used for embedding) * @see CoProduct2 */ @FunctionalInterface @@ -28,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. @@ -53,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); } }; @@ -69,41 +69,17 @@ 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); } /** - * Project this coproduct onto a tuple. + * Project this coproduct onto a product. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection * @see CoProduct2#project() */ - default Tuple5, Maybe, Maybe, Maybe, Maybe> project() { + default Product5, Maybe, Maybe, Maybe, Maybe> project() { return match(a -> tuple(just(a), nothing(), nothing(), nothing(), nothing()), b -> tuple(nothing(), just(b), nothing(), nothing(), nothing()), c -> tuple(nothing(), nothing(), just(c), nothing(), nothing()), @@ -112,7 +88,7 @@ default Tuple5, Maybe, Maybe, Maybe, Maybe> project() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ @@ -121,7 +97,7 @@ default Maybe projectA() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ @@ -130,7 +106,7 @@ default Maybe projectB() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. * * @return an optional value representing the projection of the "c" type index */ @@ -139,7 +115,7 @@ default Maybe projectC() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fourth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fourth slot value. * * @return an optional value representing the projection of the "d" type index */ @@ -148,7 +124,7 @@ default Maybe projectD() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fifth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fifth slot value. * * @return an optional value representing the projection of the "e" type index */ @@ -161,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 947036ce1..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 @@ -2,11 +2,9 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.choice.Choice5; -import com.jnape.palatable.lambda.adt.hlist.Tuple6; +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; @@ -16,12 +14,13 @@ /** * A generalization of the coproduct of six types. * - * @param the first possible type - * @param the second possible type - * @param the third possible type - * @param the fourth possible type - * @param the fifth possible type - * @param the sixth possible type + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the recursive type of this coproduct (used for embedding) * @see CoProduct2 */ @FunctionalInterface @@ -30,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. @@ -57,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); } }; @@ -74,17 +73,17 @@ 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); } /** - * Project this coproduct onto a tuple. + * Project this coproduct onto a product. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection * @see CoProduct2#project() */ - default Tuple6, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + default Product6, Maybe, Maybe, Maybe, Maybe, Maybe> project() { return match(a -> tuple(just(a), nothing(), nothing(), nothing(), nothing(), nothing()), b -> tuple(nothing(), just(b), nothing(), nothing(), nothing(), nothing()), c -> tuple(nothing(), nothing(), just(c), nothing(), nothing(), nothing()), @@ -94,7 +93,7 @@ default Tuple6, Maybe, Maybe, Maybe, Maybe, Maybe> proje } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ @@ -103,7 +102,7 @@ default Maybe projectA() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ @@ -112,7 +111,7 @@ default Maybe projectB() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. * * @return an optional value representing the projection of the "c" type index */ @@ -121,7 +120,7 @@ default Maybe projectC() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fourth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fourth slot value. * * @return an optional value representing the projection of the "d" type index */ @@ -130,7 +129,7 @@ default Maybe projectD() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fifth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fifth slot value. * * @return an optional value representing the projection of the "e" type index */ @@ -139,7 +138,7 @@ default Maybe projectE() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the sixth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the sixth slot value. * * @return an optional value representing the projection of the "f" type index */ @@ -152,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 3b57fce9b..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 @@ -2,11 +2,9 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.choice.Choice6; -import com.jnape.palatable.lambda.adt.hlist.Tuple7; +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; @@ -16,13 +14,14 @@ /** * A generalization of the coproduct of seven types. * - * @param the first possible type - * @param the second possible type - * @param the third possible type - * @param the fourth possible type - * @param the fifth possible type - * @param the sixth possible type - * @param the seventh possible type + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the recursive type of this coproduct (used for embedding) * @see CoProduct2 */ @FunctionalInterface @@ -31,6 +30,7 @@ public interface CoProduct7 result type * @param aFn morphism A -> R * @param bFn morphism B -> R * @param cFn morphism C -> R @@ -38,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. @@ -60,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); } }; @@ -77,17 +76,17 @@ 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); } /** - * Project this coproduct onto a tuple. + * Project this coproduct onto a product. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection * @see CoProduct2#project() */ - default Tuple7, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + default Product7, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe> project() { return match(a -> tuple(just(a), nothing(), nothing(), nothing(), nothing(), nothing(), nothing()), b -> tuple(nothing(), just(b), nothing(), nothing(), nothing(), nothing(), nothing()), c -> tuple(nothing(), nothing(), just(c), nothing(), nothing(), nothing(), nothing()), @@ -98,7 +97,7 @@ default Tuple7, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ @@ -107,7 +106,7 @@ default Maybe projectA() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ @@ -116,7 +115,7 @@ default Maybe projectB() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. * * @return an optional value representing the projection of the "c" type index */ @@ -125,7 +124,7 @@ default Maybe projectC() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fourth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fourth slot value. * * @return an optional value representing the projection of the "d" type index */ @@ -134,7 +133,7 @@ default Maybe projectD() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fifth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fifth slot value. * * @return an optional value representing the projection of the "e" type index */ @@ -143,7 +142,7 @@ default Maybe projectE() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the sixth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the sixth slot value. * * @return an optional value representing the projection of the "f" type index */ @@ -152,7 +151,7 @@ default Maybe projectF() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the seventh slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the seventh slot value. * * @return an optional value representing the projection of the "g" type index */ @@ -165,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 @@ -172,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 3a55b2c24..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 @@ -2,11 +2,9 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.choice.Choice7; -import com.jnape.palatable.lambda.adt.hlist.Tuple8; +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; @@ -16,14 +14,15 @@ /** * A generalization of the coproduct of eight types. * - * @param the first possible type - * @param the second possible type - * @param the third possible type - * @param the fourth possible type - * @param the fifth possible type - * @param the sixth possible type - * @param the seventh possible type - * @param the eighth possible type + * @param the first possible type + * @param the second possible type + * @param the third possible type + * @param the fourth possible type + * @param the fifth possible type + * @param the sixth possible type + * @param the seventh possible type + * @param the eighth possible type + * @param the recursive type of this coproduct (used for embedding) * @see CoProduct2 */ @FunctionalInterface @@ -32,6 +31,7 @@ public interface CoProduct8 result type * @param aFn morphism A -> R * @param bFn morphism B -> R * @param cFn morphism C -> R @@ -40,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 @@ -61,17 +60,18 @@ 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); } /** - * Project this coproduct onto a tuple. + * Project this coproduct onto a product. * - * @return a tuple of the coproduct projection + * @return a product of the coproduct projection * @see CoProduct2#project() */ - default Tuple8, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe> project() { + default Product8, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe> project() { return match(a -> tuple(just(a), nothing(), nothing(), nothing(), nothing(), nothing(), nothing(), nothing()), b -> tuple(nothing(), just(b), nothing(), nothing(), nothing(), nothing(), nothing(), nothing()), c -> tuple(nothing(), nothing(), just(c), nothing(), nothing(), nothing(), nothing(), nothing()), @@ -83,7 +83,7 @@ default Tuple8, Maybe, Maybe, Maybe, Maybe, Maybe, Maybe } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the first slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the first slot value. * * @return an optional value representing the projection of the "a" type index */ @@ -92,7 +92,7 @@ default Maybe projectA() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the second slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the second slot value. * * @return an optional value representing the projection of the "b" type index */ @@ -101,7 +101,7 @@ default Maybe projectB() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the third slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the third slot value. * * @return an optional value representing the projection of the "c" type index */ @@ -110,7 +110,7 @@ default Maybe projectC() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fourth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fourth slot value. * * @return an optional value representing the projection of the "d" type index */ @@ -119,7 +119,7 @@ default Maybe projectD() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the fifth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the fifth slot value. * * @return an optional value representing the projection of the "e" type index */ @@ -128,7 +128,7 @@ default Maybe projectE() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the sixth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the sixth slot value. * * @return an optional value representing the projection of the "f" type index */ @@ -137,7 +137,7 @@ default Maybe projectF() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the seventh slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the seventh slot value. * * @return an optional value representing the projection of the "g" type index */ @@ -146,7 +146,7 @@ default Maybe projectG() { } /** - * Convenience method for projecting this coproduct onto a tuple and then extracting the eighth slot value. + * Convenience method for projecting this coproduct onto a product and then extracting the eighth slot value. * * @return an optional value representing the projection of the "h" type index */ @@ -159,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 @@ -167,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 173bed228..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,30 +1,46 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Head; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.MonadWriter; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A 2-element tuple product type, implemented as a specialized HList. Supports random access. * * @param <_1> The first slot element type * @param <_2> The second slot element type + * @see Product2 * @see HList * @see SingletonHList * @see Tuple3 * @see Tuple4 * @see Tuple5 */ -public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> - implements Map.Entry<_1, _2>, Monad<_2, Tuple2<_1, ?>>, Bifunctor<_1, _2, Tuple2>, Traversable<_2, Tuple2<_1, ?>> { +public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements + Product2<_1, _2>, + MonadRec<_2, Tuple2<_1, ?>>, + MonadWriter<_1, _2, Tuple2<_1, ?>>, + Bifunctor<_1, _2, Tuple2>, + Traversable<_2, Tuple2<_1, ?>> { private final _1 _1; private final _2 _2; @@ -32,115 +48,204 @@ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> Tuple2(_1 _1, SingletonHList<_2> tail) { super(_1, tail); this._1 = _1; - _2 = tail.head(); + _2 = tail.head(); } + /** + * {@inheritDoc} + */ + @Override + public <_3> Tuple2<_1, Tuple2<_2, _3>> listens(Fn1 fn) { + return fmap(both(id(), constantly(fn.apply(_1)))); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple2<_1, _2> censor(Fn1 fn) { + return biMapL(fn); + } + + /** + * {@inheritDoc} + */ @Override public <_0> Tuple3<_0, _1, _2> cons(_0 _0) { return new Tuple3<>(_0, this); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple2}. * - * @return the head element + * @param _3 the new last element + * @param <_3> the new last element type + * @return the new {@link Tuple3} */ + public <_3> Tuple3<_1, _2, _3> snoc(_3 _3) { + return tuple(_1, _2, _3); + } + + /** + * {@inheritDoc} + */ + @Override public _1 _1() { return _1; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Destructure and apply this tuple to a function accepting the same number of arguments as this tuple's - * slots. This can be thought of as a kind of dual to uncurrying a function and applying a tuple to it. - * - * @param fn the function to apply - * @param the return type of the function - * @return the result of applying the destructured tuple to the function + * {@inheritDoc} */ - public R into(BiFunction fn) { - return fn.apply(_1, _2); - } - @Override public _1 getKey() { return _1(); } + /** + * {@inheritDoc} + */ @Override public _2 getValue() { return _2(); } + /** + * {@inheritDoc} + */ @Override public _2 setValue(_2 value) { throw new UnsupportedOperationException(); } + /** + * {@inheritDoc} + */ @Override - public <_2Prime> Tuple2<_1, _2Prime> fmap(Function fn) { - return Monad.super.<_2Prime>fmap(fn).coerce(); + public Tuple2<_2, _1> invert() { + return tuple(_2, _1); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_1Prime> Tuple2<_1Prime, _2> biMapL(Function fn) { - return (Tuple2<_1Prime, _2>) Bifunctor.super.biMapL(fn); + public <_2Prime> Tuple2<_1, _2Prime> fmap(Fn1 fn) { + return MonadRec.super.<_2Prime>fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_2Prime> Tuple2<_1, _2Prime> biMapR(Function fn) { - return (Tuple2<_1, _2Prime>) Bifunctor.super.biMapR(fn); + public <_1Prime> Tuple2<_1Prime, _2> biMapL(Fn1 fn) { + return (Tuple2<_1Prime, _2>) Bifunctor.super.<_1Prime>biMapL(fn); } + /** + * {@inheritDoc} + */ + @Override + 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 + public <_2Prime> Tuple2<_1, _2Prime> trampolineM( + Fn1, Tuple2<_1, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>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, 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. * @@ -163,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 55ddb87b5..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.functions.Fn3; +import com.jnape.palatable.lambda.adt.product.Product3; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.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. @@ -17,14 +24,19 @@ * @param <_1> The first slot element type * @param <_2> The second slot element type * @param <_3> The third slot element type + * @see Product3 * @see HList * @see SingletonHList * @see Tuple2 * @see Tuple4 * @see Tuple5 */ -public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> - implements Monad<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>>, Traversable<_3, Tuple3<_1, _2, ?>> { +public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements + Product3<_1, _2, _3>, + MonadRec<_3, Tuple3<_1, _2, ?>>, + Bifunctor<_2, _3, Tuple3<_1, ?, ?>>, + Traversable<_3, Tuple3<_1, _2, ?>> { + private final _1 _1; private final _2 _2; private final _3 _3; @@ -32,113 +44,191 @@ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> Tuple3(_1 _1, Tuple2<_2, _3> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); + _2 = tail._1(); + _3 = tail._2(); } + /** + * {@inheritDoc} + */ @Override public <_0> Tuple4<_0, _1, _2, _3> cons(_0 _0) { return new Tuple4<>(_0, this); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple3}. * - * @return the head element + * @param _4 the new last element + * @param <_4> the new last element type + * @return the new {@link Tuple4} */ + public <_4> Tuple4<_1, _2, _3, _4> snoc(_4 _4) { + return tuple(_1, _2, _3, _4); + } + + /** + * {@inheritDoc} + */ + @Override public _1 _1() { return _1; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Retrieve the third element in constant time. - * - * @return the third element + * {@inheritDoc} */ + @Override public _3 _3() { return _3; } /** - * Destructure and apply this tuple to a function accepting the same number of arguments as this tuple's - * slots. - * - * @param fn the function to apply - * @param the return type of the function - * @return the result of applying the destructured tuple to the function - * @see Tuple2#into + * {@inheritDoc} */ - public R into(Fn3 fn) { - return fn.apply(_1, _2, _3); + @Override + public Tuple3<_2, _3, _1> rotateL3() { + return tuple(_2, _3, _1); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_3Prime> Tuple3<_1, _2, _3Prime> fmap(Function fn) { - return (Tuple3<_1, _2, _3Prime>) Monad.super.fmap(fn); + public Tuple3<_3, _1, _2> rotateR3() { + return tuple(_3, _1, _2); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_2Prime> Tuple3<_1, _2Prime, _3> biMapL(Function fn) { - return (Tuple3<_1, _2Prime, _3>) Bifunctor.super.biMapL(fn); + public Tuple3<_2, _1, _3> invert() { + return tuple(_2, _1, _3); } + /** + * {@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> fmap(Fn1 fn) { + return (Tuple3<_1, _2, _3Prime>) MonadRec.super.<_3Prime>fmap(fn); } + /** + * {@inheritDoc} + */ @Override - public <_2Prime, _3Prime> Tuple3<_1, _2Prime, _3Prime> biMap(Function lFn, - Function rFn) { + public <_2Prime> Tuple3<_1, _2Prime, _3> biMapL(Fn1 fn) { + return (Tuple3<_1, _2Prime, _3>) Bifunctor.super.<_2Prime>biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + 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(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 - @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> Tuple3<_1, _2, _3Prime> trampolineM( + Fn1, Tuple3<_1, _2, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce()._3())); + } + + /** + * {@inheritDoc} + */ + @Override + public <_3Prime, App extends Applicative, TravB extends Traversable<_3Prime, Tuple3<_1, _2, ?>>, + AppTrav extends Applicative> AppTrav traverse( + Fn1> fn, + Fn1 pure) { return fn.apply(_3).fmap(_3Prime -> fmap(constantly(_3Prime))).fmap(Applicative::coerce).coerce(); } + /** + * Returns a {@link Tuple2}<_1, _2> of all the elements of this + * {@link Tuple3}<_1, _2, _3> except the last. + * + * @return The {@link Tuple2}<_1, _2> representing all but the last element + */ + public Tuple2<_1, _2> init() { + return rotateR3().tail(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * @@ -150,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 92347c62a..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.functions.Fn4; +import com.jnape.palatable.lambda.adt.product.Product4; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.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. @@ -18,14 +25,19 @@ * @param <_2> The second slot element type * @param <_3> The third slot element type * @param <_4> The fourth slot element type + * @see Product4 * @see HList * @see SingletonHList * @see Tuple2 * @see Tuple3 * @see Tuple5 */ -public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> - implements Monad<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>>, Traversable<_4, Tuple4<_1, _2, _3, ?>> { +public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implements + Product4<_1, _2, _3, _4>, + MonadRec<_4, Tuple4<_1, _2, _3, ?>>, + Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>>, + Traversable<_4, Tuple4<_1, _2, _3, ?>> { + private final _1 _1; private final _2 _2; private final _3 _3; @@ -34,123 +46,216 @@ public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> Tuple4(_1 _1, Tuple3<_2, _3, _4> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); } + /** + * {@inheritDoc} + */ @Override public <_0> Tuple5<_0, _1, _2, _3, _4> cons(_0 _0) { return new Tuple5<>(_0, this); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple4}. * - * @return the head element + * @param _5 the new last element + * @param <_5> the new last element type + * @return the new {@link Tuple5} */ + public <_5> Tuple5<_1, _2, _3, _4, _5> snoc(_5 _5) { + return tuple(_1, _2, _3, _4, _5); + } + + /** + * {@inheritDoc} + */ + @Override public _1 _1() { return _1; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Retrieve the third element in constant time. - * - * @return the third element + * {@inheritDoc} */ + @Override public _3 _3() { return _3; } /** - * Retrieve the fourth element in constant time. - * - * @return the fourth element + * {@inheritDoc} */ + @Override public _4 _4() { return _4; } /** - * Destructure and apply this tuple to a function accepting the same number of arguments as this tuple's - * slots. - * - * @param fn the function to apply - * @param the return type of the function - * @return the result of applying the destructured tuple to the function - * @see Tuple2#into + * {@inheritDoc} */ - public R into(Fn4 fn) { - return fn.apply(_1, _2, _3, _4); + @Override + public Tuple4<_2, _3, _4, _1> rotateL4() { + return tuple(_2, _3, _4, _1); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_4Prime> Tuple4<_1, _2, _3, _4Prime> fmap(Function fn) { - return (Tuple4<_1, _2, _3, _4Prime>) Monad.super.fmap(fn); + 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(Fn1 fn) { + return (Tuple4<_1, _2, _3, _4Prime>) MonadRec.super.<_4Prime>fmap(fn); + } + + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_3Prime> Tuple4<_1, _2, _3Prime, _4> biMapL(Function fn) { - return (Tuple4<_1, _2, _3Prime, _4>) Bifunctor.super.biMapL(fn); + public <_3Prime> Tuple4<_1, _2, _3Prime, _4> biMapL(Fn1 fn) { + return (Tuple4<_1, _2, _3Prime, _4>) Bifunctor.super.<_3Prime>biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public <_4Prime> Tuple4<_1, _2, _3, _4Prime> biMapR(Function fn) { - return (Tuple4<_1, _2, _3, _4Prime>) Bifunctor.super.biMapR(fn); + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> 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. * @@ -162,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 d087d4a48..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.functions.Fn5; +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. @@ -19,14 +26,19 @@ * @param <_3> The third slot element type * @param <_4> The fourth slot element type * @param <_5> The fifth slot element type + * @see Product5 * @see HList * @see SingletonHList * @see Tuple2 * @see Tuple3 * @see Tuple4 */ -public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> - implements Monad<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>>, Traversable<_5, Tuple5<_1, _2, _3, _4, ?>> { +public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> implements + Product5<_1, _2, _3, _4, _5>, + MonadRec<_5, Tuple5<_1, _2, _3, _4, ?>>, + Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>>, + Traversable<_5, Tuple5<_1, _2, _3, _4, ?>> { + private final _1 _1; private final _2 _2; private final _3 _3; @@ -36,132 +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); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple5}. * - * @return the head element + * @param _6 the new last element + * @param <_6> the new last element type + * @return the new {@link Tuple6} + */ + public <_6> Tuple6<_1, _2, _3, _4, _5, _6> snoc(_6 _6) { + return tuple(_1, _2, _3, _4, _5, _6); + } + + /** + * {@inheritDoc} */ + @Override public _1 _1() { return _1; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Retrieve the third element in constant time. - * - * @return the third element + * {@inheritDoc} */ + @Override public _3 _3() { return _3; } /** - * Retrieve the fourth element in constant time. - * - * @return the fourth element + * {@inheritDoc} */ + @Override public _4 _4() { return _4; } /** - * Retrieve the fifth element in constant time. - * - * @return the fifth element + * {@inheritDoc} */ + @Override public _5 _5() { return _5; } /** - * Destructure and apply this tuple to a function accepting the same number of arguments as this tuple's - * slots. - * - * @param fn the function to apply - * @param the return type of the function - * @return the result of applying the destructured tuple to the function - * @see Tuple2#into + * {@inheritDoc} + */ + @Override + public 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} */ - public R into(Fn5 fn) { - return fn.apply(_1, _2, _3, _4, _5); + @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. * @@ -173,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 2d7e21ab6..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.functions.Fn6; +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. @@ -20,6 +27,7 @@ * @param <_4> The fourth slot element type * @param <_5> The fifth slot element type * @param <_6> The sixth slot element type + * @see Product6 * @see HList * @see SingletonHList * @see Tuple2 @@ -27,8 +35,12 @@ * @see Tuple4 * @see Tuple5 */ -public class Tuple6<_1, _2, _3, _4, _5, _6> extends HCons<_1, Tuple5<_2, _3, _4, _5, _6>> - implements Monad<_6, Tuple6<_1, _2, _3, _4, _5, ?>>, Bifunctor<_5, _6, Tuple6<_1, _2, _3, _4, ?, ?>>, Traversable<_6, Tuple6<_1, _2, _3, _4, _5, ?>> { +public class Tuple6<_1, _2, _3, _4, _5, _6> extends HCons<_1, Tuple5<_2, _3, _4, _5, _6>> implements + Product6<_1, _2, _3, _4, _5, _6>, + MonadRec<_6, Tuple6<_1, _2, _3, _4, _5, ?>>, + Bifunctor<_5, _6, Tuple6<_1, _2, _3, _4, ?, ?>>, + Traversable<_6, Tuple6<_1, _2, _3, _4, _5, ?>> { + private final _1 _1; private final _2 _2; private final _3 _3; @@ -39,144 +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); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple6}. * - * @return the head element + * @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; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Retrieve the third element in constant time. - * - * @return the third element + * {@inheritDoc} */ + @Override public _3 _3() { return _3; } /** - * Retrieve the fourth element in constant time. - * - * @return the fourth element + * {@inheritDoc} */ + @Override public _4 _4() { return _4; } /** - * Retrieve the fifth element in constant time. - * - * @return the fifth element + * {@inheritDoc} */ + @Override public _5 _5() { return _5; } /** - * Retrieve the sixth element in constant time. - * - * @return the sixth element + * {@inheritDoc} */ + @Override public _6 _6() { return _6; } /** - * Destructure and apply this tuple to a function accepting the same number of arguments as this tuple's - * slots. - * - * @param fn the function to apply - * @param the return type of the function - * @return the result of applying the destructured tuple to the function - * @see Tuple2#into + * {@inheritDoc} + */ + @Override + public 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} */ - public R into(Fn6 fn) { - return fn.apply(_1, _2, _3, _4, _5, _6); + @Override + public Tuple6<_3, _1, _2, _4, _5, _6> rotateR3() { + return tuple(_3, _1, _2, _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 Tuple6<_2, _1, _3, _4, _5, _6> invert() { + return tuple(_2, _1, _3, _4, _5, _6); } + /** + * {@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 <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> fmap(Fn1 fn) { + return MonadRec.super.<_6Prime>fmap(fn).coerce(); } + /** + * {@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 <_5Prime> Tuple6<_1, _2, _3, _4, _5Prime, _6> biMapL(Fn1 fn) { + return (Tuple6<_1, _2, _3, _4, _5Prime, _6>) Bifunctor.super.<_5Prime>biMapL(fn); } + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> biMapR(Fn1 fn) { + return (Tuple6<_1, _2, _3, _4, _5, _6Prime>) Bifunctor.super.<_6Prime>biMapR(fn); + } + + /** + * {@inheritDoc} + */ @Override public <_5Prime, _6Prime> Tuple6<_1, _2, _3, _4, _5Prime, _6Prime> biMap( - 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. * @@ -188,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 28f47406c..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.functions.Fn7; +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. @@ -21,6 +28,7 @@ * @param <_5> The fifth slot element type * @param <_6> The sixth slot element type * @param <_7> The seventh slot element type + * @see Product7 * @see HList * @see SingletonHList * @see Tuple2 @@ -29,8 +37,12 @@ * @see Tuple5 * @see Tuple6 */ -public class Tuple7<_1, _2, _3, _4, _5, _6, _7> extends HCons<_1, Tuple6<_2, _3, _4, _5, _6, _7>> - implements Monad<_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, ?>> { +public class Tuple7<_1, _2, _3, _4, _5, _6, _7> extends HCons<_1, Tuple6<_2, _3, _4, _5, _6, _7>> implements + Product7<_1, _2, _3, _4, _5, _6, _7>, + MonadRec<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, + Bifunctor<_6, _7, Tuple7<_1, _2, _3, _4, _5, ?, ?>>, + Traversable<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>> { + private final _1 _1; private final _2 _2; private final _3 _3; @@ -42,156 +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); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple7}. * - * @return the head element + * @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; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Retrieve the third element in constant time. - * - * @return the third element + * {@inheritDoc} */ + @Override public _3 _3() { return _3; } /** - * Retrieve the fourth element in constant time. - * - * @return the fourth element + * {@inheritDoc} */ + @Override public _4 _4() { return _4; } /** - * Retrieve the fifth element in constant time. - * - * @return the fifth element + * {@inheritDoc} */ + @Override public _5 _5() { return _5; } /** - * Retrieve the sixth element in constant time. - * - * @return the sixth element + * {@inheritDoc} */ + @Override public _6 _6() { return _6; } /** - * Retrieve the seventh element in constant time. - * - * @return the seventh element + * {@inheritDoc} */ + @Override public _7 _7() { return _7; } /** - * Destructure and apply this tuple to a function accepting the same number of arguments as this tuple's - * slots. - * - * @param fn the function to apply - * @param the return type of the function - * @return the result of applying the destructured tuple to the function - * @see Tuple2#into + * {@inheritDoc} + */ + @Override + public 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} */ - public R into( - Fn7 fn) { - return fn.apply(_1, _2, _3, _4, _5, _6, _7); + @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. * @@ -203,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 1226cc395..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.functions.Fn8; +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. @@ -22,6 +29,7 @@ * @param <_6> The sixth slot element type * @param <_7> The seventh slot element type * @param <_8> The eighth slot element type + * @see Product8 * @see HList * @see SingletonHList * @see Tuple2 @@ -31,8 +39,12 @@ * @see Tuple6 * @see Tuple7 */ -public class Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> extends HCons<_1, Tuple7<_2, _3, _4, _5, _6, _7, _8>> - implements Monad<_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, ?>> { +public class Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> extends HCons<_1, Tuple7<_2, _3, _4, _5, _6, _7, _8>> implements + Product8<_1, _2, _3, _4, _5, _6, _7, _8>, + MonadRec<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, + Bifunctor<_7, _8, Tuple8<_1, _2, _3, _4, _5, _6, ?, ?>>, + Traversable<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>> { + private final _1 _1; private final _2 _2; private final _3 _3; @@ -45,166 +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); } /** - * Retrieve the first (head) element in constant time. + * Snoc an element onto the back of this {@link Tuple8}. * - * @return the head element + * @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; } /** - * Retrieve the second element in constant time. - * - * @return the second element + * {@inheritDoc} */ + @Override public _2 _2() { return _2; } /** - * Retrieve the third element in constant time. - * - * @return the third element + * {@inheritDoc} */ + @Override public _3 _3() { return _3; } /** - * Retrieve the fourth element in constant time. - * - * @return the fourth element + * {@inheritDoc} */ + @Override public _4 _4() { return _4; } /** - * Retrieve the fifth element in constant time. - * - * @return the fifth element + * {@inheritDoc} */ + @Override public _5 _5() { return _5; } /** - * Retrieve the sixth element in constant time. - * - * @return the sixth element + * {@inheritDoc} */ + @Override public _6 _6() { return _6; } /** - * Retrieve the seventh element in constant time. - * - * @return the seventh element + * {@inheritDoc} */ + @Override public _7 _7() { return _7; } /** - * Retrieve the eighth element in constant time. - * - * @return the eighth element + * {@inheritDoc} */ + @Override public _8 _8() { return _8; } /** - * Destructure and apply this tuple to a function accepting the same number of arguments as this tuple's - * slots. - * - * @param fn the function to apply - * @param the return type of the function - * @return the result of applying the destructured tuple to the function - * @see Tuple2#into + * {@inheritDoc} + */ + @Override + public 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} */ - public R into( - Fn8 fn) { - return fn.apply(_1, _2, _3, _4, _5, _6, _7, _8); + @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 <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> fmap(Function fn) { - return Monad.super.<_8Prime>fmap(fn).coerce(); + public Tuple8<_7, _1, _2, _3, _4, _5, _6, _8> rotateR7() { + return tuple(_7, _1, _2, _3, _4, _5, _6, _8); } + /** + * {@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 Tuple8<_2, _3, _4, _5, _6, _1, _7, _8> rotateL6() { + return tuple(_2, _3, _4, _5, _6, _1, _7, _8); } + /** + * {@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 Tuple8<_6, _1, _2, _3, _4, _5, _7, _8> rotateR6() { + return tuple(_6, _1, _2, _3, _4, _5, _7, _8); } + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _3, _4, _5, _1, _6, _7, _8> rotateL5() { + return tuple(_2, _3, _4, _5, _1, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_5, _1, _2, _3, _4, _6, _7, _8> rotateR5() { + return tuple(_5, _1, _2, _3, _4, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _3, _4, _1, _5, _6, _7, _8> rotateL4() { + return tuple(_2, _3, _4, _1, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_4, _1, _2, _3, _5, _6, _7, _8> rotateR4() { + return tuple(_4, _1, _2, _3, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _3, _1, _4, _5, _6, _7, _8> rotateL3() { + return tuple(_2, _3, _1, _4, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_3, _1, _2, _4, _5, _6, _7, _8> rotateR3() { + return tuple(_3, _1, _2, _4, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_2, _1, _3, _4, _5, _6, _7, _8> invert() { + return tuple(_2, _1, _3, _4, _5, _6, _7, _8); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> fmap(Fn1 fn) { + return MonadRec.super.<_8Prime>fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8> biMapL(Fn1 fn) { + return (Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8>) Bifunctor.super.<_7Prime>biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> biMapR(Fn1 fn) { + return (Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime>) Bifunctor.super.<_8Prime>biMapR(fn); + } + + /** + * {@inheritDoc} + */ @Override public <_7Prime, _8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8Prime> biMap( - 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. * @@ -216,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 83fc47f11..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,9 +18,8 @@ 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; -import static java.util.Collections.singletonMap; /** * An immutable heterogeneous mapping from a parametrized type-safe key to any value, supporting a minimal mapping @@ -28,13 +28,13 @@ * @see TypeSafeKey * @see com.jnape.palatable.lambda.adt.hlist.HList */ -public class HMap implements Iterable> { +public final class HMap implements Iterable, Object>> { private static final HMap EMPTY = new HMap(emptyMap()); - private final Map table; + private final Map, Object> table; - private HMap(Map table) { + private HMap(Map, Object> table) { this.table = table; } @@ -46,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)); } /** @@ -60,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.")); } /** @@ -91,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); } @@ -101,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)); } @@ -115,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. *

@@ -123,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()); } @@ -142,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(); } @@ -172,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); } @@ -196,7 +205,7 @@ public static HMap emptyHMap() { * @return a singleton HMap */ public static HMap singletonHMap(TypeSafeKey key, V value) { - return new HMap(singletonMap(key, value)); + return emptyHMap().put(key, value); } /** @@ -232,6 +241,194 @@ public static HMap hMap(TypeSafeKey key1, V1 value1, public static HMap hMap(TypeSafeKey key1, V1 value1, TypeSafeKey key2, V2 value2, TypeSafeKey key3, V3 value3) { - return hMap(key1, value1, key2, value2).put(key3, value3); + return hMap(key1, value1, + key2, value2) + .put(key3, value3); } + + /** + * Static factory method for creating an HMap from four given associations. + * + * @param key1 the first mapped key + * @param value1 the value mapped at key1 + * @param key2 the second mapped key + * @param value2 the value mapped at key2 + * @param key3 the third mapped key + * @param value3 the value mapped at key3 + * @param key4 the fourth mapped key + * @param value4 the value mapped at key4 + * @param value1's type + * @param value2's type + * @param value3's type + * @param value4's type + * @return an HMap with the given associations + */ + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3, + TypeSafeKey key4, V4 value4) { + return hMap(key1, value1, + key2, value2, + key3, value3) + .put(key4, value4); + } + + /** + * Static factory method for creating an HMap from five given associations. + * + * @param key1 the first mapped key + * @param value1 the value mapped at key1 + * @param key2 the second mapped key + * @param value2 the value mapped at key2 + * @param key3 the third mapped key + * @param value3 the value mapped at key3 + * @param key4 the fourth mapped key + * @param value4 the value mapped at key4 + * @param key5 the fifth mapped key + * @param value5 the value mapped at key5 + * @param value1's type + * @param value2's type + * @param value3's type + * @param value4's type + * @param value5's type + * @return an HMap with the given associations + */ + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3, + TypeSafeKey key4, V4 value4, + TypeSafeKey key5, V5 value5) { + return hMap(key1, value1, + key2, value2, + key3, value3, + key4, value4) + .put(key5, value5); + } + + /** + * Static factory method for creating an HMap from six given associations. + * + * @param key1 the first mapped key + * @param value1 the value mapped at key1 + * @param key2 the second mapped key + * @param value2 the value mapped at key2 + * @param key3 the third mapped key + * @param value3 the value mapped at key3 + * @param key4 the fourth mapped key + * @param value4 the value mapped at key4 + * @param key5 the fifth mapped key + * @param value5 the value mapped at key5 + * @param key6 the sixth mapped key + * @param value6 the value mapped at key6 + * @param value1's type + * @param value2's type + * @param value3's type + * @param value4's type + * @param value5's type + * @param value6's type + * @return an HMap with the given associations + */ + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3, + TypeSafeKey key4, V4 value4, + TypeSafeKey key5, V5 value5, + TypeSafeKey key6, V6 value6) { + return hMap(key1, value1, + key2, value2, + key3, value3, + key4, value4, + key5, value5) + .put(key6, value6); + } + + /** + * Static factory method for creating an HMap from seven given associations. + * + * @param key1 the first mapped key + * @param value1 the value mapped at key1 + * @param key2 the second mapped key + * @param value2 the value mapped at key2 + * @param key3 the third mapped key + * @param value3 the value mapped at key3 + * @param key4 the fourth mapped key + * @param value4 the value mapped at key4 + * @param key5 the fifth mapped key + * @param value5 the value mapped at key5 + * @param key6 the sixth mapped key + * @param value6 the value mapped at key6 + * @param key7 the seventh mapped key + * @param value7 the value mapped at key7 + * @param value1's type + * @param value2's type + * @param value3's type + * @param value4's type + * @param value5's type + * @param value6's type + * @param value7's type + * @return an HMap with the given associations + */ + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3, + TypeSafeKey key4, V4 value4, + TypeSafeKey key5, V5 value5, + TypeSafeKey key6, V6 value6, + TypeSafeKey key7, V7 value7) { + return hMap(key1, value1, + key2, value2, + key3, value3, + key4, value4, + key5, value5, + key6, value6) + .put(key7, value7); + } + + /** + * Static factory method for creating an HMap from eight given associations. + * + * @param key1 the first mapped key + * @param value1 the value mapped at key1 + * @param key2 the second mapped key + * @param value2 the value mapped at key2 + * @param key3 the third mapped key + * @param value3 the value mapped at key3 + * @param key4 the fourth mapped key + * @param value4 the value mapped at key4 + * @param key5 the fifth mapped key + * @param value5 the value mapped at key5 + * @param key6 the sixth mapped key + * @param value6 the value mapped at key6 + * @param key7 the seventh mapped key + * @param value7 the value mapped at key7 + * @param key8 the eighth mapped key + * @param value8 the value mapped at key8 + * @param value1's type + * @param value2's type + * @param value3's type + * @param value4's type + * @param value5's type + * @param value6's type + * @param value7's type + * @param value8's type + * @return an HMap with the given associations + */ + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3, + TypeSafeKey key4, V4 value4, + TypeSafeKey key5, V5 value5, + TypeSafeKey key6, V6 value6, + TypeSafeKey key7, V7 value7, + TypeSafeKey key8, V8 value8) { + return hMap(key1, value1, + key2, value2, + key3, value3, + key4, value4, + key5, value5, + key6, value6, + key7, value7) + .put(key8, value8); + } + } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java new file mode 100644 index 000000000..61c9b181a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java @@ -0,0 +1,245 @@ +package com.jnape.palatable.lambda.adt.hmap; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.HList; +import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.adt.hlist.SingletonHList; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.adt.hlist.Tuple4; +import com.jnape.palatable.lambda.adt.hlist.Tuple5; +import com.jnape.palatable.lambda.adt.hlist.Tuple6; +import com.jnape.palatable.lambda.adt.hlist.Tuple7; +import com.jnape.palatable.lambda.adt.hlist.Tuple8; +import com.jnape.palatable.lambda.functions.builtin.fn2.Both; +import com.jnape.palatable.lambda.functor.Cartesian; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.optics.Lens; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.optics.lenses.HMapLens.valueAt; + +/** + * A lens that focuses on the {@link HList heterogeneous list} of values pointed at by one or more + * {@link TypeSafeKey typesafe keys} that must all exist in the same {@link HMap} to be collectively extracted. Note + * that if any of the keys is absent in the map, the result will be {@link Maybe#nothing()}. + * + * @param the {@link HList} of values to focus on + * @see TypeSafeKey + */ +public interface Schema extends Lens.Simple> { + + /** + * Add a new {@link TypeSafeKey} to the head of this {@link Schema}. + * + * @param key the new head key + * @param the value the head key focuses on + * @param the new {@link HCons} of values + * @return the updated {@link Schema} + */ + @SuppressWarnings({"unchecked", "RedundantTypeArguments"}) + default > Schema add(TypeSafeKey key) { + Lens, Maybe> lens = Lens.both(this, valueAt(key)) + .>mapA(into((maybeValues, maybeA) -> maybeValues + .zip(maybeA.fmap(a -> values -> (NewValues) values.cons(a))))) + .>mapB(Both.both(maybeNewValues -> maybeNewValues.fmap(HCons::tail), + maybeNewValues -> maybeNewValues.fmap(HCons::head))); + return new Schema() { + @Override + public >, + CoF extends Functor>, + FB extends Functor, ? extends CoF>, + FT extends Functor, + PAFB extends Profunctor, FB, ? extends CoP>, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return lens.apply(pafb); + } + }; + } + + /** + * Create a {@link Schema} from a single {@link TypeSafeKey}. + * + * @param key the {@link TypeSafeKey} + * @param the type of value the key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey key) { + Lens>, Maybe>> lens = valueAt(key) + .mapA(ma -> ma.fmap(HList::singletonHList)) + .mapB(maybeSingletonA -> maybeSingletonA.fmap(HCons::head)); + return new Schema>() { + @Override + public >, + CoF extends Functor>, + FB extends Functor>, ? extends CoF>, + FT extends Functor, + PAFB extends Profunctor>, FB, ? extends CoP>, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { + return lens.apply(pafb); + } + }; + } + + /** + * Create a {@link Schema} from two {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey) { + return schema(bKey).add(aKey); + } + + /** + * Create a {@link Schema} from three {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey) { + return schema(bKey, cKey).add(aKey); + } + + /** + * Create a {@link Schema} from four {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey, + TypeSafeKey dKey) { + return schema(bKey, cKey, dKey).add(aKey); + } + + /** + * Create a {@link Schema} from five {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey, + TypeSafeKey dKey, + TypeSafeKey eKey) { + return schema(bKey, cKey, dKey, eKey).add(aKey); + } + + /** + * Create a {@link Schema} from six {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param fKey the sixth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @param the type of value the sixth key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey, + TypeSafeKey dKey, + TypeSafeKey eKey, + TypeSafeKey fKey) { + return schema(bKey, cKey, dKey, eKey, fKey).add(aKey); + } + + /** + * Create a {@link Schema} from seven {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param fKey the sixth {@link TypeSafeKey} + * @param gKey the seventh {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @param the type of value the sixth key focuses on + * @param the type of value the seventh key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey, + TypeSafeKey dKey, + TypeSafeKey eKey, + TypeSafeKey fKey, + TypeSafeKey gKey) { + return schema(bKey, cKey, dKey, eKey, fKey, gKey).add(aKey); + } + + /** + * Create a {@link Schema} from eight {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param fKey the sixth {@link TypeSafeKey} + * @param gKey the seventh {@link TypeSafeKey} + * @param hKey the eighth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @param the type of value the sixth key focuses on + * @param the type of value the seventh key focuses on + * @param the type of value the eighth key focuses on + * @return the {@link Schema} + */ + static Schema> schema(TypeSafeKey aKey, + TypeSafeKey bKey, + TypeSafeKey cKey, + TypeSafeKey dKey, + TypeSafeKey eKey, + TypeSafeKey fKey, + TypeSafeKey gKey, + TypeSafeKey hKey) { + return schema(bKey, cKey, dKey, eKey, fKey, gKey, hKey).add(aKey); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java index a71e4c04c..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,10 @@ 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; /** * An interface representing a parametrized key for use in {@link HMap}s. Additionally, every {@link TypeSafeKey} is an @@ -22,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 @@ -46,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, @@ -62,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( - PAFB pafb) { - return (PSFT) pafb; + public boolean equals(Object obj) { + return obj instanceof Simple ? this == obj : Objects.equals(obj, this); + } + + @Override + public int hashCode() { + return super.hashCode(); } }; } @@ -102,5 +115,17 @@ public

, FT ex * @param The type of the value that this key maps to inside an {@link HMap} */ interface Simple extends TypeSafeKey { + + @Override + @SuppressWarnings("unchecked") + default >, + CoF extends Functor>, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply( + PAFB pafb) { + return (PSFT) pafb; + } } } \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product2.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product2.java new file mode 100644 index 000000000..0a76df761 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product2.java @@ -0,0 +1,92 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.Map; + +/** + * The minimal shape of the combination of two potentially distinctly typed values, supporting destructuring via + * explicitly named indexing methods, as well as via a combining function. + *

+ * For more information, read about products. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @see Tuple2 + */ +public interface Product2<_1, _2> extends Map.Entry<_1, _2> { + + /** + * Retrieve the first element. + * + * @return the first element + */ + _1 _1(); + + /** + * Retrieve the second element. + * + * @return the second element + */ + _2 _2(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param the return type of the function + * @param fn the function to apply + * @return the result of applying the destructured product to the function + */ + default R into(Fn2 fn) { + return fn.apply(_1(), _2()); + } + + /** + * Rotate the first two slots of this product. + * + * @return the rotated product + */ + default Product2<_2, _1> invert() { + return into((_1, _2) -> product(_2, _1)); + } + + @Override + default _1 getKey() { + return _1(); + } + + @Override + default _2 getValue() { + return _2(); + } + + @Override + default _2 setValue(_2 value) { + throw new UnsupportedOperationException(); + } + + /** + * Static factory method for creating a generic {@link Product2}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @return the {@link Product2} + */ + static <_1, _2> Product2<_1, _2> product(_1 _1, _2 _2) { + return new Product2<_1, _2>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product3.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product3.java new file mode 100644 index 000000000..18da0491f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product3.java @@ -0,0 +1,88 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.functions.Fn3; + +/** + * A product with three values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @see Product2 + * @see Tuple3 + */ +public interface Product3<_1, _2, _3> extends Product2<_1, _2> { + + /** + * Retrieve the third element. + * + * @return the third element + */ + _3 _3(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into(Fn3 fn) { + return Product2.super.into(fn).apply(_3()); + } + + /** + * Rotate the first three values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product3<_2, _3, _1> rotateL3() { + return into((_1, _2, _3) -> product(_2, _3, _1)); + } + + /** + * Rotate the first three values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product3<_3, _1, _2> rotateR3() { + return into((_1, _2, _3) -> product(_3, _1, _2)); + } + + @Override + default Product3<_2, _1, _3> invert() { + return into((_1, _2, _3) -> product(_2, _1, _3)); + } + + /** + * Static factory method for creating a generic {@link Product3}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @return the {@link Product3} + */ + static <_1, _2, _3> Product3<_1, _2, _3> product(_1 _1, _2 _2, _3 _3) { + return new Product3<_1, _2, _3>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product4.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product4.java new file mode 100644 index 000000000..87a33376d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product4.java @@ -0,0 +1,107 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple4; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn4; + +/** + * A product with four values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @param <_4> The fourth element type + * @see Product2 + * @see Tuple4 + */ +public interface Product4<_1, _2, _3, _4> extends Product3<_1, _2, _3> { + + /** + * Retrieve the fourth element. + * + * @return the fourth element + */ + _4 _4(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into(Fn4 fn) { + return Product3.super.>into(fn).apply(_4()); + } + + /** + * Rotate the first four values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product4<_2, _3, _4, _1> rotateL4() { + return into((_1, _2, _3, _4) -> product(_2, _3, _4, _1)); + } + + /** + * Rotate the first four values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product4<_4, _1, _2, _3> rotateR4() { + return into((_1, _2, _3, _4) -> product(_4, _1, _2, _3)); + } + + @Override + default Product4<_2, _3, _1, _4> rotateL3() { + return into((_1, _2, _3, _4) -> product(_2, _3, _1, _4)); + } + + @Override + default Product4<_3, _1, _2, _4> rotateR3() { + return into((_1, _2, _3, _4) -> product(_3, _1, _2, _4)); + } + + @Override + default Product4<_2, _1, _3, _4> invert() { + return into((_1, _2, _3, _4) -> product(_2, _1, _3, _4)); + } + + /** + * Static factory method for creating a generic {@link Product4}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param _4 the fourth slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @param <_4> the fourth slot type + * @return the {@link Product4} + */ + static <_1, _2, _3, _4> Product4<_1, _2, _3, _4> product(_1 _1, _2 _2, _3 _3, _4 _4) { + return new Product4<_1, _2, _3, _4>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + + @Override + public _4 _4() { + return _4; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product5.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product5.java new file mode 100644 index 000000000..864671bbe --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product5.java @@ -0,0 +1,125 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple5; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn5; + +/** + * A product with five values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @param <_4> The fourth element type + * @param <_5> The fifth element type + * @see Product2 + * @see Tuple5 + */ +public interface Product5<_1, _2, _3, _4, _5> extends Product4<_1, _2, _3, _4> { + + /** + * Retrieve the fifth element. + * + * @return the fifth element + */ + _5 _5(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into(Fn5 fn) { + return Product4.super.>into(fn).apply(_5()); + } + + /** + * Rotate the first five values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product5<_2, _3, _4, _5, _1> rotateL5() { + return into((_1, _2, _3, _4, _5) -> product(_2, _3, _4, _5, _1)); + } + + /** + * Rotate the first five values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product5<_5, _1, _2, _3, _4> rotateR5() { + return into((_1, _2, _3, _4, _5) -> product(_5, _1, _2, _3, _4)); + } + + @Override + default Product5<_2, _3, _4, _1, _5> rotateL4() { + return into((_1, _2, _3, _4, _5) -> product(_2, _3, _4, _1, _5)); + } + + @Override + default Product5<_4, _1, _2, _3, _5> rotateR4() { + return into((_1, _2, _3, _4, _5) -> product(_4, _1, _2, _3, _5)); + } + + @Override + default Product5<_2, _3, _1, _4, _5> rotateL3() { + return into((_1, _2, _3, _4, _5) -> product(_2, _3, _1, _4, _5)); + } + + @Override + default Product5<_3, _1, _2, _4, _5> rotateR3() { + return into((_1, _2, _3, _4, _5) -> product(_3, _1, _2, _4, _5)); + } + + @Override + default Product5<_2, _1, _3, _4, _5> invert() { + return into((_1, _2, _3, _4, _5) -> product(_2, _1, _3, _4, _5)); + } + + /** + * Static factory method for creating a generic {@link Product5}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param _4 the fourth slot + * @param _5 the fifth slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @param <_4> the fourth slot type + * @param <_5> the fifth slot type + * @return the {@link Product5} + */ + static <_1, _2, _3, _4, _5> Product5<_1, _2, _3, _4, _5> product(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5) { + return new Product5<_1, _2, _3, _4, _5>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + + @Override + public _4 _4() { + return _4; + } + + @Override + public _5 _5() { + return _5; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product6.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product6.java new file mode 100644 index 000000000..2ea16a9a5 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product6.java @@ -0,0 +1,144 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple6; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn6; + +/** + * A product with six values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @param <_4> The fourth element type + * @param <_5> The fifth element type + * @param <_6> The sixth element type + * @see Product2 + * @see Tuple6 + */ +public interface Product6<_1, _2, _3, _4, _5, _6> extends Product5<_1, _2, _3, _4, _5> { + + /** + * Retrieve the sixth element. + * + * @return the sixth element + */ + _6 _6(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into(Fn6 fn) { + return Product5.super.>into(fn).apply(_6()); + } + + /** + * Rotate the first six values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product6<_2, _3, _4, _5, _6, _1> rotateL6() { + return into((_1, _2, _3, _4, _5, _6) -> product(_2, _3, _4, _5, _6, _1)); + } + + /** + * Rotate the first six values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product6<_6, _1, _2, _3, _4, _5> rotateR6() { + return into((_1, _2, _3, _4, _5, _6) -> product(_6, _1, _2, _3, _4, _5)); + } + + @Override + default Product6<_2, _3, _4, _5, _1, _6> rotateL5() { + return into((_1, _2, _3, _4, _5, _6) -> product(_2, _3, _4, _5, _1, _6)); + } + + @Override + default Product6<_5, _1, _2, _3, _4, _6> rotateR5() { + return into((_1, _2, _3, _4, _5, _6) -> product(_5, _1, _2, _3, _4, _6)); + } + + @Override + default Product6<_2, _3, _4, _1, _5, _6> rotateL4() { + return into((_1, _2, _3, _4, _5, _6) -> product(_2, _3, _4, _1, _5, _6)); + } + + @Override + default Product6<_4, _1, _2, _3, _5, _6> rotateR4() { + return into((_1, _2, _3, _4, _5, _6) -> product(_4, _1, _2, _3, _5, _6)); + } + + @Override + default Product6<_2, _3, _1, _4, _5, _6> rotateL3() { + return into((_1, _2, _3, _4, _5, _6) -> product(_2, _3, _1, _4, _5, _6)); + } + + @Override + default Product6<_3, _1, _2, _4, _5, _6> rotateR3() { + return into((_1, _2, _3, _4, _5, _6) -> product(_3, _1, _2, _4, _5, _6)); + } + + @Override + default Product6<_2, _1, _3, _4, _5, _6> invert() { + return into((_1, _2, _3, _4, _5, _6) -> product(_2, _1, _3, _4, _5, _6)); + } + + /** + * Static factory method for creating a generic {@link Product6}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param _4 the fourth slot + * @param _5 the fifth slot + * @param _6 the sixth slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @param <_4> the fourth slot type + * @param <_5> the fifth slot type + * @param <_6> the sixth slot type + * @return the {@link Product6} + */ + static <_1, _2, _3, _4, _5, _6> Product6<_1, _2, _3, _4, _5, _6> product(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, + _6 _6) { + return new Product6<_1, _2, _3, _4, _5, _6>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + + @Override + public _4 _4() { + return _4; + } + + @Override + public _5 _5() { + return _5; + } + + @Override + public _6 _6() { + return _6; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product7.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product7.java new file mode 100644 index 000000000..75b9fc1d8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product7.java @@ -0,0 +1,163 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple7; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn7; + +/** + * A product with seven values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @param <_4> The fourth element type + * @param <_5> The fifth element type + * @param <_6> The sixth element type + * @param <_7> The seventh element type + * @see Product2 + * @see Tuple7 + */ +public interface Product7<_1, _2, _3, _4, _5, _6, _7> extends Product6<_1, _2, _3, _4, _5, _6> { + + /** + * Retrieve the seventh element. + * + * @return the seventh element + */ + _7 _7(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into( + Fn7 fn) { + return Product6.super.>into(fn).apply(_7()); + } + + /** + * Rotate the first seven values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product7<_2, _3, _4, _5, _6, _7, _1> rotateL7() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _3, _4, _5, _6, _7, _1)); + } + + /** + * Rotate the first seven values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product7<_7, _1, _2, _3, _4, _5, _6> rotateR7() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_7, _1, _2, _3, _4, _5, _6)); + } + + @Override + default Product7<_2, _3, _4, _5, _6, _1, _7> rotateL6() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _3, _4, _5, _6, _1, _7)); + } + + @Override + default Product7<_6, _1, _2, _3, _4, _5, _7> rotateR6() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_6, _1, _2, _3, _4, _5, _7)); + } + + @Override + default Product7<_2, _3, _4, _5, _1, _6, _7> rotateL5() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _3, _4, _5, _1, _6, _7)); + } + + @Override + default Product7<_5, _1, _2, _3, _4, _6, _7> rotateR5() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_5, _1, _2, _3, _4, _6, _7)); + } + + @Override + default Product7<_2, _3, _4, _1, _5, _6, _7> rotateL4() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _3, _4, _1, _5, _6, _7)); + } + + @Override + default Product7<_4, _1, _2, _3, _5, _6, _7> rotateR4() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_4, _1, _2, _3, _5, _6, _7)); + } + + @Override + default Product7<_2, _3, _1, _4, _5, _6, _7> rotateL3() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _3, _1, _4, _5, _6, _7)); + } + + @Override + default Product7<_3, _1, _2, _4, _5, _6, _7> rotateR3() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_3, _1, _2, _4, _5, _6, _7)); + } + + @Override + default Product7<_2, _1, _3, _4, _5, _6, _7> invert() { + return into((_1, _2, _3, _4, _5, _6, _7) -> product(_2, _1, _3, _4, _5, _6, _7)); + } + + /** + * Static factory method for creating a generic {@link Product7}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param _4 the fourth slot + * @param _5 the fifth slot + * @param _6 the sixth slot + * @param _7 the seventh slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @param <_4> the fourth slot type + * @param <_5> the fifth slot type + * @param <_6> the sixth slot type + * @param <_7> the seventh slot type + * @return the {@link Product7} + */ + static <_1, _2, _3, _4, _5, _6, _7> Product7<_1, _2, _3, _4, _5, _6, _7> product(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, + _6 _6, _7 _7) { + return new Product7<_1, _2, _3, _4, _5, _6, _7>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + + @Override + public _4 _4() { + return _4; + } + + @Override + public _5 _5() { + return _5; + } + + @Override + public _6 _6() { + return _6; + } + + @Override + public _7 _7() { + return _7; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/product/Product8.java b/src/main/java/com/jnape/palatable/lambda/adt/product/Product8.java new file mode 100644 index 000000000..2b51e1bf0 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/product/Product8.java @@ -0,0 +1,182 @@ +package com.jnape.palatable.lambda.adt.product; + +import com.jnape.palatable.lambda.adt.hlist.Tuple8; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn8; + +/** + * A product with eight values. + * + * @param <_1> The first element type + * @param <_2> The second element type + * @param <_3> The third element type + * @param <_4> The fourth element type + * @param <_5> The fifth element type + * @param <_6> The sixth element type + * @param <_7> The seventh element type + * @param <_8> The eighth element type + * @see Product2 + * @see Tuple8 + */ +public interface Product8<_1, _2, _3, _4, _5, _6, _7, _8> extends Product7<_1, _2, _3, _4, _5, _6, _7> { + + /** + * Retrieve the eighth element. + * + * @return the eighth element + */ + _8 _8(); + + /** + * Destructure and apply this product to a function accepting the same number of arguments as this product's + * slots. This can be thought of as a kind of dual to uncurrying a function and applying a product to it. + * + * @param fn the function to apply + * @param the return type of the function + * @return the result of applying the destructured product to the function + */ + default R into( + Fn8 fn) { + return Product7.super.>into(fn).apply(_8()); + } + + /** + * Rotate all eight values of this product one slot to the left. + * + * @return the left-rotated product + */ + default Product8<_2, _3, _4, _5, _6, _7, _8, _1> rotateL8() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _4, _5, _6, _7, _8, _1)); + } + + /** + * Rotate all eight values of this product one slot to the right. + * + * @return the right-rotated product + */ + default Product8<_8, _1, _2, _3, _4, _5, _6, _7> rotateR8() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_8, _1, _2, _3, _4, _5, _6, _7)); + } + + @Override + default Product8<_2, _3, _4, _5, _6, _7, _1, _8> rotateL7() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _4, _5, _6, _7, _1, _8)); + } + + @Override + default Product8<_7, _1, _2, _3, _4, _5, _6, _8> rotateR7() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_7, _1, _2, _3, _4, _5, _6, _8)); + } + + @Override + default Product8<_2, _3, _4, _5, _6, _1, _7, _8> rotateL6() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _4, _5, _6, _1, _7, _8)); + } + + @Override + default Product8<_6, _1, _2, _3, _4, _5, _7, _8> rotateR6() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_6, _1, _2, _3, _4, _5, _7, _8)); + } + + @Override + default Product8<_2, _3, _4, _5, _1, _6, _7, _8> rotateL5() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _4, _5, _1, _6, _7, _8)); + } + + @Override + default Product8<_5, _1, _2, _3, _4, _6, _7, _8> rotateR5() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_5, _1, _2, _3, _4, _6, _7, _8)); + } + + @Override + default Product8<_2, _3, _4, _1, _5, _6, _7, _8> rotateL4() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _4, _1, _5, _6, _7, _8)); + } + + @Override + default Product8<_4, _1, _2, _3, _5, _6, _7, _8> rotateR4() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_4, _1, _2, _3, _5, _6, _7, _8)); + } + + @Override + default Product8<_2, _3, _1, _4, _5, _6, _7, _8> rotateL3() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _3, _1, _4, _5, _6, _7, _8)); + } + + @Override + default Product8<_3, _1, _2, _4, _5, _6, _7, _8> rotateR3() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_3, _1, _2, _4, _5, _6, _7, _8)); + } + + @Override + default Product8<_2, _1, _3, _4, _5, _6, _7, _8> invert() { + return into((_1, _2, _3, _4, _5, _6, _7, _8) -> product(_2, _1, _3, _4, _5, _6, _7, _8)); + } + + /** + * Static factory method for creating a generic {@link Product8}. + * + * @param _1 the first slot + * @param _2 the second slot + * @param _3 the third slot + * @param _4 the fourth slot + * @param _5 the fifth slot + * @param _6 the sixth slot + * @param _7 the seventh slot + * @param _8 the eighth slot + * @param <_1> the first slot type + * @param <_2> the second slot type + * @param <_3> the third slot type + * @param <_4> the fourth slot type + * @param <_5> the fifth slot type + * @param <_6> the sixth slot type + * @param <_7> the seventh slot type + * @param <_8> the eighth slot type + * @return the {@link Product8} + */ + static <_1, _2, _3, _4, _5, _6, _7, _8> Product8<_1, _2, _3, _4, _5, _6, _7, _8> product(_1 _1, _2 _2, _3 _3, _4 _4, + _5 _5, _6 _6, _7 _7, + _8 _8) { + return new Product8<_1, _2, _3, _4, _5, _6, _7, _8>() { + @Override + public _1 _1() { + return _1; + } + + @Override + public _2 _2() { + return _2; + } + + @Override + public _3 _3() { + return _3; + } + + @Override + public _4 _4() { + return _4; + } + + @Override + public _5 _5() { + return _5; + } + + @Override + public _6 _6() { + return _6; + } + + @Override + public _7 _7() { + return _7; + } + + @Override + public _8 _8() { + return _8; + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Effect.java b/src/main/java/com/jnape/palatable/lambda/functions/Effect.java new file mode 100644 index 000000000..648c42eab --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/Effect.java @@ -0,0 +1,126 @@ +package com.jnape.palatable.lambda.functions; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.specialized.SideEffect; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.internal.Runtime; +import com.jnape.palatable.lambda.io.IO; + +import java.util.function.Consumer; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.specialized.SideEffect.NOOP; +import static com.jnape.palatable.lambda.io.IO.io; + +/** + * A function returning "no result", and therefore only useful as a side-effect. + * + * @param the argument type + * @see Fn0 + */ +@FunctionalInterface +public interface Effect extends Fn1> { + + @Override + IO checkedApply(A a) throws Throwable; + + /** + * Convert this {@link Effect} to a java {@link Consumer} + * + * @return the {@link Consumer} + */ + default Consumer toConsumer() { + return a -> apply(a).unsafePerformIO(); + } + + /** + * {@inheritDoc} + */ + @Override + default IO apply(A a) { + try { + return checkedApply(a); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * Left-to-right composition of {@link Effect Effects}. + * + * @param effect the other {@link Effect} + * @return the composed {@link Effect} + */ + default Effect andThen(Effect effect) { + return a -> apply(a).flatMap(constantly(effect.apply(a))); + } + + /** + * {@inheritDoc} + */ + @Override + default Effect diMapL(Fn1 fn) { + return effect(Fn1.super.diMapL(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Effect contraMap(Fn1 fn) { + return effect(Fn1.super.contraMap(fn)); + } + + /** + * {@inheritDoc} + */ + @Override + default Effect discardR(Applicative> appB) { + return effect(Fn1.super.discardR(appB)); + } + + /** + * Static factory method to create an {@link Effect} from a java {@link Consumer}. + * + * @param consumer the {@link Consumer} + * @param the input type + * @return the {@link Effect} + */ + static Effect fromConsumer(Consumer consumer) { + return a -> io(() -> consumer.accept(a)); + } + + /** + * Create an {@link Effect} from a {@link SideEffect}; + * + * @param sideEffect the {@link SideEffect} + * @param any desired input type + * @return the {@link Effect} + */ + static Effect effect(SideEffect sideEffect) { + return effect(constantly(io(sideEffect))); + } + + /** + * Create an {@link Effect} that accepts an input and does nothing; + * + * @param any desired input type + * @return the noop {@link Effect} + */ + @SuppressWarnings("unused") + static Effect noop() { + return effect(NOOP); + } + + /** + * Create an {@link Effect} from an {@link Fn1} that yields an {@link IO}. + * + * @param fn the function + * @param the effect argument type + * @return the effect + */ + static Effect effect(Fn1> fn) { + return fn.fmap(io -> io.fmap(constantly(UNIT)))::apply; + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn0.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn0.java new file mode 100644 index 000000000..3352046fe --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn0.java @@ -0,0 +1,143 @@ +package com.jnape.palatable.lambda.functions; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; + +import java.util.concurrent.Callable; +import java.util.function.Supplier; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; + +/** + * A function taking "no arguments", implemented as an {@link Fn1}<{@link Unit}, A>. + * + * @param the result type + * @see Fn1 + * @see Callable + */ +@FunctionalInterface +public interface Fn0 extends Fn1 { + + A checkedApply() throws Throwable; + + /** + * Convenience method for applying this {@link Fn0} without providing an explicit {@link Unit}. + * + * @return the result + */ + default A apply() { + return apply(UNIT); + } + + /** + * Convert this {@link Fn0} to a java {@link Supplier} + * + * @return the {@link Supplier} + */ + default Supplier toSupplier() { + return this::apply; + } + + /** + * Convert this {@link Fn0} to a java {@link Callable} + * + * @return the {@link Callable} + */ + default Callable toCallable() { + return this::apply; + } + + /** + * {@inheritDoc} + */ + @Override + default A checkedApply(Unit unit) throws Throwable { + return checkedApply(); + } + + @Override + default Fn0 flatMap(Fn1>> f) { + return Fn1.super.flatMap(f).thunk(UNIT); + } + + @Override + default Fn0 fmap(Fn1 f) { + return Fn1.super.fmap(f).thunk(UNIT); + } + + @Override + default Fn0 pure(B b) { + return Fn1.super.pure(b).thunk(UNIT); + } + + @Override + default Fn0 zip(Applicative, Fn1> appFn) { + return Fn1.super.zip(appFn).thunk(UNIT); + } + + @Override + default Fn0 zip(Fn2 appFn) { + return Fn1.super.zip(appFn).thunk(UNIT); + } + + @Override + default Fn0 discardL(Applicative> appB) { + return Fn1.super.discardL(appB).thunk(UNIT); + } + + @Override + default Fn0 discardR(Applicative> appB) { + return Fn1.super.discardR(appB).thunk(UNIT); + } + + @Override + default Fn0 diMapR(Fn1 fn) { + return Fn1.super.diMapR(fn).thunk(UNIT); + } + + /** + * Convenience method for converting a {@link Supplier} to an {@link Fn0}. + * + * @param supplier the supplier + * @param the output type + * @return the {@link Fn0} + */ + static Fn0 fromSupplier(Supplier supplier) { + return supplier::get; + } + + /** + * Convenience method for converting a {@link Callable} to an {@link Fn0}. + * + * @param callable the callable + * @param the output type + * @return the {@link Fn0} + */ + static Fn0 fromCallable(Callable callable) { + return callable::call; + } + + /** + * Static factory method for coercing a lambda to an {@link Fn0}. + * + * @param fn the lambda to coerce + * @param the output type + * @return the {@link Fn0} + */ + static Fn0 fn0(Fn0 fn) { + return fn; + } + + /** + * Static factory method for adapting an {@link Fn1}<Unit, A> to an + * {@link Fn0}<A>. + * + * @param fn the {@link Fn1} + * @param the output type + * @return the {@link Fn0} + */ + static Fn0 fn0(Fn1 fn) { + return fn0(() -> fn.apply(UNIT)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java index 608d57fd5..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,13 +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.Profunctor; +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 @@ -17,31 +30,108 @@ * @param The result type */ @FunctionalInterface -public interface Fn1 extends Monad>, Profunctor, Function { +public interface Fn1 extends + MonadRec>, + MonadReader>, + MonadWriter>, + Cartesian>, + Cocartesian> { /** - * Invoke this function with the given argument. + * Invoke this function explosively with the given argument. * * @param a the argument * @return the result of the function application */ - B apply(A a); + default B apply(A a) { + try { + return checkedApply(a); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * 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 + * argument now, but deferring application until a later time. + * + * @param a the argument + * @return an {@link Fn0} + */ + default Fn0 thunk(A a) { + return () -> apply(a); + } + + /** + * Widen this function's argument list by prepending an ignored argument of any type to the front. + * + * @param the new first argument type + * @return the widened function + */ + default Fn2 widen() { + return curried(constantly(this)); + } + + /** + * Convert this {@link Fn1} to a java {@link Function}. + * + * @return the {@link Function} + */ + default Function toFunction() { + return this::apply; + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 local(Fn1 fn) { + return contraMap(fn); + } + + /** + * {@inheritDoc} + */ + @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(Function>> f) { + 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)); } /** @@ -56,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(); } /** @@ -65,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)); } /** @@ -73,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(); } /** @@ -81,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(); } /** @@ -93,8 +199,8 @@ default Fn1 discardR(Applicative> appB) { * @return an {@link Fn1}<Z, B> */ @Override - default Fn1 diMapL(Function fn) { - return (Fn1) Profunctor.super.diMapL(fn); + default Fn1 diMapL(Fn1 fn) { + return (Fn1) Cartesian.super.diMapL(fn); } /** @@ -106,8 +212,8 @@ default Fn1 diMapL(Function fn) { * @return an {@link Fn1}<A, C> */ @Override - default Fn1 diMapR(Function fn) { - return (Fn1) Profunctor.super.diMapR(fn); + default Fn1 diMapR(Fn1 fn) { + return (Fn1) Cartesian.super.diMapR(fn); } /** @@ -120,39 +226,56 @@ 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; + } + + /** + * Pair a value with the input to this function, and preserve the paired value through to the output. + * + * @param the paired value + * @return the strengthened {@link Fn1} + */ + @Override + default Fn1, Tuple2> cartesian() { + return t -> t.fmap(this); } + /** + * {@inheritDoc} + */ @Override - default Fn1 contraMap(Function fn) { - return (Fn1) Profunctor.super.contraMap(fn); + default Fn1> carry() { + return (Fn1>) Cartesian.super.carry(); } /** - * Override of {@link Function#compose(Function)}, returning an instance of {@link Fn1} for compatibility. - * Right-to-left composition. + * Choose between either applying this function or returning back a different result altogether. * - * @param before the function who's return value is this function's argument - * @param the new argument type - * @return an {@link Fn1}<Z, B> + * @param the potentially different result + * @return teh strengthened {@link Fn1} */ @Override - default Fn1 compose(Function before) { - return z -> apply(before.apply(z)); + default Fn1, Choice2> cocartesian() { + return a -> a.fmap(this); } /** - * 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}). + * Choose between a successful result b or returning back the input, a. * - * @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> + * @return an {@link Fn1} that chooses between its input (in case of failure) or its output. */ - default Fn2 compose(BiFunction before) { - return compose(fn2(before)); + @Override + default Fn1> choose() { + return a -> Either.trying(() -> apply(a), constantly(a)).match(Choice2::a, Choice2::b); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 contraMap(Fn1 fn) { + return (Fn1) Cartesian.super.contraMap(fn); } /** @@ -164,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 712676d66..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,12 +1,15 @@ 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; /** * A function taking two arguments. @@ -22,6 +25,8 @@ @FunctionalInterface public interface Fn2 extends Fn1> { + C checkedApply(A a, B b) throws Throwable; + /** * Invoke this function with the given arguments. * @@ -29,18 +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); + } + } /** - * 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 Fn1 checkedApply(A a) throws Throwable { + return b -> checkedApply(a, b); + } + + /** + * {@inheritDoc} */ @Override - default Fn2 compose(Function before) { - return fn2(Fn1.super.compose(before)); + default Fn3 widen() { + return fn3(constantly(this)); } /** @@ -64,11 +79,11 @@ default Fn2 flip() { } /** - * Returns an {@link Fn1} that takes the arguments as a {@link Tuple2}<A, B>. + * Returns an {@link Fn1} that takes the arguments as a {@link Product2}<A, B>. * - * @return an {@link Fn1} taking a {@link Tuple2} + * @return an {@link Fn1} taking a {@link Product2} */ - default Fn1, C> uncurry() { + default Fn1, C> uncurry() { return (ab) -> apply(ab._1(), ab._2()); } @@ -82,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 @@ -117,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; } @@ -130,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 34d725aed..881d848ff 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn3.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn3.java @@ -1,12 +1,11 @@ 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 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; /** * A function taking three arguments. Defined in terms of {@link Fn2}, so similarly auto-curried. @@ -20,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. * @@ -28,7 +29,29 @@ public interface Fn3 extends Fn2> { * @param c the third argument * @return the result of the function application */ - D apply(A a, B b, C c); + default D apply(A a, B b, C c) { + try { + return checkedApply(a, b, c); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 checkedApply(A a, B b) throws Throwable { + return c -> checkedApply(a, b, c); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn4 widen() { + return fn4(constantly(this)); + } /** * Partially apply this function by taking its first argument. @@ -64,13 +87,13 @@ default Fn3 flip() { } /** - * Returns an {@link Fn2} that takes the first two arguments as a {@link Tuple2}<A, B> and the + * Returns an {@link Fn2} that takes the first two arguments as a {@link Product2}<A, B> and the * third argument. * - * @return an {@link Fn2} taking a {@link Tuple2} and the third argument + * @return an {@link Fn2} taking a {@link Product2} and the third argument */ @Override - default Fn2, C, D> uncurry() { + default Fn2, C, D> uncurry() { return (ab, c) -> apply(ab._1(), ab._2(), c); } @@ -80,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)); @@ -126,4 +144,18 @@ static Fn3 fn3(Fn1> curriedFn1) { static Fn3 fn3(Fn2> curriedFn2) { return (a, b, c) -> curriedFn2.apply(a, b).apply(c); } + + /** + * Static factory method for coercing a lambda to an {@link Fn3}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the output type + * @return the {@link Fn3} + */ + static Fn3 fn3(Fn3 fn) { + return fn; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn4.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn4.java index e98bb9da9..f2e9f7d47 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn4.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn4.java @@ -1,12 +1,11 @@ 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 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; /** * A function taking four arguments. Defined in terms of {@link Fn3}, so similarly auto-curried. @@ -21,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. * @@ -30,7 +31,29 @@ public interface Fn4 extends Fn3> { * @param d the fourth argument * @return the result of the function application */ - E apply(A a, B b, C c, D d); + default E apply(A a, B b, C c, D d) { + try { + return checkedApply(a, b, c, d); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * {@inheritDoc} + */ + @Override + default Fn1 checkedApply(A a, B b, C c) throws Throwable { + return d -> checkedApply(a, b, c, d); + } + + /** + * {@inheritDoc} + */ + @Override + default Fn5 widen() { + return fn5(constantly(this)); + } /** * Partially apply this function by taking its first argument. @@ -79,13 +102,13 @@ default Fn4 flip() { } /** - * Returns an {@link Fn3} that takes the first two arguments as a {@link Tuple2}<A, B> and the + * Returns an {@link Fn3} that takes the first two arguments as a {@link Product2}<A, B> and the * third and fourth arguments. * - * @return an {@link Fn3} taking a {@link Tuple2} and the third and fourth arguments + * @return an {@link Fn3} taking a {@link Product2} and the third and fourth arguments */ @Override - default Fn3, C, D, E> uncurry() { + default Fn3, C, D, E> uncurry() { return (ab, c, d) -> apply(ab._1(), ab._2(), c, d); } @@ -95,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)); @@ -158,4 +176,19 @@ static Fn4 fn4(Fn2> curriedFn2 static Fn4 fn4(Fn3> curriedFn3) { return (a, b, c, d) -> curriedFn3.apply(a, b, c).apply(d); } + + /** + * Static factory method for coercing a lambda to an {@link Fn4}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the output type + * @return the {@link Fn4} + */ + static Fn4 fn4(Fn4 fn) { + return fn; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java index 7e8b5abdf..c89b8dadd 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn5.java @@ -1,12 +1,11 @@ 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 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; /** * A function taking five arguments. Defined in terms of {@link Fn4}, so similarly auto-curried. @@ -22,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. * @@ -32,7 +33,30 @@ 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} + */ + @Override + default Fn6 widen() { + return fn6(constantly(this)); + } /** * Partially apply this function by taking its first argument. @@ -95,13 +119,13 @@ default Fn5 flip() { } /** - * Returns an {@link Fn4} that takes the first two arguments as a {@link Tuple2}<A, B> and the + * Returns an {@link Fn4} that takes the first two arguments as a {@link Product2}<A, B> and the * remaining arguments. * - * @return an {@link Fn4} taking a {@link Tuple2} and the remaining arguments + * @return an {@link Fn4} taking a {@link Product2} and the remaining arguments */ @Override - default Fn4, C, D, E, F> uncurry() { + default Fn4, C, D, E, F> uncurry() { return (ab, c, d, e) -> apply(ab._1(), ab._2(), c, d, e); } @@ -111,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)); @@ -193,4 +212,20 @@ static Fn5 fn5(Fn3> c static Fn5 fn5(Fn4> curriedFn4) { return (a, b, c, d, e) -> curriedFn4.apply(a, b, c, d).apply(e); } + + /** + * Static factory method for coercing a lambda to an {@link Fn5}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the output type + * @return the {@link Fn5} + */ + static Fn5 fn5(Fn5 fn) { + return fn; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java index e81331b16..4e7bbea89 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn6.java @@ -1,12 +1,11 @@ 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 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; /** * A function taking six arguments. Defined in terms of {@link Fn5}, so similarly auto-curried. @@ -23,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. * @@ -34,7 +35,30 @@ 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} + */ + @Override + default Fn7 widen() { + return fn7(constantly(this)); + } /** * Partially apply this function by taking its first argument. @@ -112,13 +136,13 @@ default Fn6 flip() { } /** - * Returns an {@link Fn5} that takes the first two arguments as a {@link Tuple2}<A, B> and the + * Returns an {@link Fn5} that takes the first two arguments as a {@link Product2}<A, B> and the * remaining arguments. * - * @return an {@link Fn5} taking a {@link Tuple2} and the remaining arguments + * @return an {@link Fn5} taking a {@link Product2} and the remaining arguments */ @Override - default Fn5, C, D, E, F, G> uncurry() { + default Fn5, C, D, E, F, G> uncurry() { return (ab, c, d, e, f) -> apply(ab._1(), ab._2(), c, d, e, f); } @@ -128,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)); @@ -231,4 +250,21 @@ static Fn6 fn6(Fn4 Fn6 fn6(Fn5> curriedFn5) { return (a, b, c, d, e, f) -> curriedFn5.apply(a, b, c, d, e).apply(f); } + + /** + * Static factory method for coercing a lambda to an {@link Fn6}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the output type + * @return the {@link Fn6} + */ + static Fn6 fn6(Fn6 fn) { + return fn; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java index 20ad5d8e5..15f7a3be9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java @@ -1,15 +1,14 @@ package com.jnape.palatable.lambda.functions; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functor.Applicative; - -import 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 @@ -24,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. * @@ -36,7 +37,29 @@ 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} + */ + @Override + default Fn8 widen() { + return fn8(constantly(this)); + } /** * Partially apply this function by taking its first argument. @@ -130,13 +153,13 @@ default Fn7 flip() { } /** - * Returns an {@link Fn6} that takes the first two arguments as a {@link Tuple2}<A, B> and the + * Returns an {@link Fn6} that takes the first two arguments as a {@link Product2}<A, B> and the * remaining arguments. * - * @return an {@link Fn6} taking a {@link Tuple2} and the remaining arguments + * @return an {@link Fn6} taking a {@link Product2} and the remaining arguments */ @Override - default Fn6, C, D, E, F, G, H> uncurry() { + default Fn6, C, D, E, F, G, H> uncurry() { return (ab, c, d, e, f, g) -> apply(ab._1(), ab._2(), c, d, e, f, g); } @@ -146,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)); @@ -240,7 +258,7 @@ static Fn7 fn7(Fn4 the first input argument type * @param the second input argument type * @param the third input argument type @@ -258,7 +276,7 @@ static Fn7 fn7(Fn5 the first input argument type * @param the second input argument type * @param the third input argument type @@ -272,4 +290,22 @@ static Fn7 fn7(Fn5 Fn7 fn7(Fn6> curriedFn6) { return (a, b, c, d, e, f, g) -> curriedFn6.apply(a, b, c, d, e, f).apply(g); } + + /** + * Static factory method for coercing a lambda to an {@link Fn7}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the output type + * @return the {@link Fn7} + */ + static Fn7 fn7(Fn7 fn) { + return fn; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java index 6f80beb9f..60f3a4a3c 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java @@ -1,13 +1,11 @@ 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 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. @@ -147,13 +161,13 @@ default Fn8 flip() { } /** - * Returns an {@link Fn7} that takes the first two arguments as a {@link Tuple2}<A, B> and the + * Returns an {@link Fn7} that takes the first two arguments as a {@link Product2}<A, B> and the * remaining arguments. * - * @return an {@link Fn7} taking a {@link Tuple2} and the remaining arguments + * @return an {@link Fn7} taking a {@link Product2} and the remaining arguments */ @Override - default Fn7, C, D, E, F, G, H, I> uncurry() { + default Fn7, C, D, E, F, G, H, I> uncurry() { return (ab, c, d, e, f, g, h) -> apply(ab._1(), ab._2(), c, d, e, f, g, h); } @@ -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 @@ -322,4 +330,23 @@ static Fn8 fn8( Fn7> curriedFn7) { return (a, b, c, d, e, f, g, h) -> curriedFn7.apply(a, b, c, d, e, f, g).apply(h); } + + /** + * Static factory method for coercing a lambda to an {@link Fn8}. + * + * @param fn the lambda to coerce + * @param the first input argument type + * @param the second input argument type + * @param the third input argument type + * @param the fourth input argument type + * @param the fifth input argument type + * @param the sixth input argument type + * @param the seventh input argument type + * @param the eighth input argument type + * @param the output type + * @return the {@link Fn8} + */ + static Fn8 fn8(Fn8 fn) { + return fn; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybes.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybes.java 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 5f8718c8b..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 new file mode 100644 index 000000000..6fea549c8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Occurrences.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.HashMap; +import java.util.Map; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; + +/** + * Given an {@link Iterable}<A>, return a {@link Map}<A, Long> representing each + * unique element in the {@link Iterable} paired with its number of occurrences. + * + * @param the {@link Iterable} element type + */ +public final class Occurrences implements Fn1, Map> { + private static final Occurrences INSTANCE = new Occurrences<>(); + + private Occurrences() { + } + + @Override + public Map checkedApply(Iterable as) { + return foldLeft((occurrences, a) -> { + occurrences.put(a, occurrences.getOrDefault(a, 0L) + 1); + return occurrences; + }, new HashMap<>(), as); + } + + @SuppressWarnings("unchecked") + public static Occurrences occurrences() { + return (Occurrences) INSTANCE; + } + + public static Map occurrences(Iterable as) { + return Occurrences.occurrences().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Repeat.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Repeat.java index 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 ce297dd9b..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 @@ -1,6 +1,8 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.SortBy; +import com.jnape.palatable.lambda.functions.builtin.fn2.SortWith; import java.util.List; @@ -12,23 +14,24 @@ * this is both eager and monolithic. * * @param the input Iterable and output List element type - * @see com.jnape.palatable.lambda.functions.builtin.fn2.SortBy + * @see SortBy + * @see SortWith */ 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 new file mode 100644 index 000000000..f4f187021 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Upcast.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +/** + * Upcast a value of type B to a value of type A that B extends. This is + * principally useful when dealing with parametric types that are invariant in their parameters and a cast is + * necessary for compatibility purposes. + *

+ * Example: + *

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

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

+ * {@code
+ * List> fns     = asList(x -> x + 1, x -> x, x -> x - 1);
+ * List               args    = asList(0, 1, 2);
+ * Iterable           results = zipWith($(), fns, args); // [1, 1, 1]
+ * }
+ * 
+ * + * @param the applied {@link Fn1 Fn1's} input type + * @param the applied {@link Fn1 Fn1's} output type + */ +public final class $ implements Fn2, A, B> { + private static final $ INSTANCE = new $<>(); + + private $() { + } + + @Override + public B checkedApply(Fn1 fn, A a) { + return fn.apply(a); + } + + @SuppressWarnings("unchecked") + public static $ $() { + return ($) INSTANCE; + } + + public static Fn1 $(Fn1 fn) { + return $.$().apply(fn); + } + + public static B $(Fn1 fn, A a) { + return $.$(fn).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java index 53ecac095..26d76101f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java @@ -3,8 +3,6 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.specialized.BiPredicate; -import java.util.function.Function; - /** * Eagerly apply a predicate to each element in an Iterable, returning true if every element * satisfies the predicate, and false otherwise. This method short-circuits on the first false @@ -13,15 +11,15 @@ * @param The input Iterable element type * @see Any */ -public final class All implements BiPredicate, Iterable> { +public final class All implements BiPredicate, Iterable> { - private static final All INSTANCE = new All(); + private static final All INSTANCE = new All<>(); private All() { } @Override - public Boolean apply(Function predicate, Iterable as) { + public Boolean checkedApply(Fn1 predicate, Iterable as) { for (A a : as) if (!predicate.apply(a)) return false; @@ -31,14 +29,14 @@ public Boolean apply(Function predicate, Iterable as) { @SuppressWarnings("unchecked") public static All all() { - return INSTANCE; + return (All) INSTANCE; } - public static Fn1, Boolean> all(Function predicate) { + public static Fn1, ? extends Boolean> all(Fn1 predicate) { return All.all().apply(predicate); } - public static Boolean all(Function predicate, Iterable as) { + public static Boolean all(Fn1 predicate, Iterable as) { return All.all(predicate).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java new file mode 100644 index 000000000..b830a7e9c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Effect; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.io.IO; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * Given an {@link Effect}<A> and some A, produce an {@link IO} that, when run, performs + * the effect on A and returns it. + * + * @param the input and output + */ +public final class Alter implements Fn2>, A, IO> { + + private static final Alter INSTANCE = new Alter<>(); + + private Alter() { + } + + @Override + public IO checkedApply(Fn1> effect, A a) { + return effect.fmap(io -> io.fmap(constantly(a))).apply(a); + } + + @SuppressWarnings("unchecked") + public static Alter alter() { + return (Alter) INSTANCE; + } + + public static Fn1> alter(Effect effect) { + return Alter.alter().apply(effect); + } + + public static IO alter(Effect effect, A a) { + return Alter.alter(effect).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java index fa8db83b4..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 as) { @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 new file mode 100644 index 000000000..3a91ade4c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CmpEq.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; + +/** + * Given two {@link Comparable} values of type A, return true if the first value is strictly + * equal to the second value (according to {@link Comparable#compareTo(Object)}; otherwise, return false. + * + * @param the value type + * @see CmpEqBy + * @see LT + * @see GT + */ +public final class CmpEq> implements BiPredicate { + + private static final CmpEq INSTANCE = new CmpEq<>(); + + private CmpEq() { + } + + @Override + public Boolean checkedApply(A x, A y) { + return cmpEqBy(id(), x, y); + } + + @SuppressWarnings("unchecked") + public static > CmpEq cmpEq() { + return (CmpEq) INSTANCE; + } + + public static > Predicate cmpEq(A x) { + return CmpEq.cmpEq().apply(x); + } + + public static > Boolean cmpEq(A x, A y) { + return cmpEq(x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Cons.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Cons.java index 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 new file mode 100644 index 000000000..38735673c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Difference.java @@ -0,0 +1,58 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn1.Distinct; + +import java.util.HashSet; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Distinct.distinct; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Empty.empty; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; + +/** + * Given two {@link Iterable Iterables} xs and ys, return the {@link Distinct distinct} + * elements of xs that are not in ys. Note that this is not symmetric + * difference. + *

+ * This operation preserves order, so the resulting elements from xs are iterated in the order that + * they uniquely occur in. + * + * @param the {@link Iterable} element type + */ +public final class Difference implements Fn2, Iterable, Iterable> { + + private static final Difference INSTANCE = new Difference<>(); + + private Difference() { + } + + @Override + public Iterable checkedApply(Iterable xs, Iterable ys) { + return () -> { + if (empty(xs)) + return xs.iterator(); + + if (empty(ys)) + return distinct(xs).iterator(); + + //todo: a pre-order depth-first fold of the expression tree would make this stack-safe + HashSet uniqueYs = toCollection(HashSet::new, ys); + return distinct(filter(a -> !uniqueYs.contains(a), xs)).iterator(); + }; + } + + @SuppressWarnings("unchecked") + public static Difference difference() { + return (Difference) INSTANCE; + } + + public static Fn1, Iterable> difference(Iterable xs) { + return Difference.difference().apply(xs); + } + + public static Iterable difference(Iterable xs, Iterable ys) { + return difference(xs).apply(ys); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Drop.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Drop.java index 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 9b87e63b0..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 787978318..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 e1bb57be7..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 new file mode 100644 index 000000000..09d1be4ae --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GT.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.builtin.fn3.GTBy; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTBy.gtBy; + +/** + * Given two {@link Comparable} values of type A, return true if the second value is strictly + * greater than the first value; otherwise, return false. + * + * @param the value type + * @see GTBy + * @see LT + */ +public final class GT> implements BiPredicate { + + private static final GT INSTANCE = new GT<>(); + + private GT() { + } + + @Override + public Boolean checkedApply(A y, A x) { + return gtBy(id(), y, x); + } + + @SuppressWarnings("unchecked") + public static > GT gt() { + return (GT) INSTANCE; + } + + public static > Predicate gt(A y) { + return GT.gt().apply(y); + } + + public static > Boolean gt(A y, A x) { + return gt(y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTE.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTE.java new file mode 100644 index 000000000..89413cea6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTE.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.builtin.fn3.GTEBy; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEBy.gteBy; + +/** + * Given two {@link Comparable} values of type A, return true if the second value is greater + * than or equal to the first value according to {@link Comparable#compareTo(Object)}; otherwise, return false. + * + * @param the value type + * @see GTEBy + * @see LTE + */ +public final class GTE> implements BiPredicate { + + private static final GTE INSTANCE = new GTE<>(); + + private GTE() { + } + + @Override + public Boolean checkedApply(A y, A x) { + return gteBy(id(), y, x); + } + + @SuppressWarnings("unchecked") + public static > GTE gte() { + return (GTE) INSTANCE; + } + + public static > Predicate gte(A y) { + return GTE.gte().apply(y); + } + + public static > Boolean gte(A y, A x) { + return gte(y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java index 11dc659bc..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,44 +7,43 @@ 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; /** - * Given an Iterable<V> vs and a key function V -> K f, fold - * vs into a Map<K, List<V>> by applying f to each element of + * Given an Iterable<V> vs and a key function V -> K f, + * fold vs into a Map<K, List<V>> by applying f to each element of * vs, retaining values that map to the same key in a list, in the order they were iterated in. * * @param the Map key type * @param the Map value type * @see InGroupsOf */ -public 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; - }, new HashMap>(), vs); + }, new HashMap<>(), vs); } @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 c3c8381ed..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 @@ -1,41 +1,42 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import java.util.function.BiFunction; +import java.util.Map; /** - * Given a {@link BiFunction}<A, B, C> and a {@link Tuple2}<A, B>, destructure the - * tuple and apply the slots 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, Tuple2, 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, Tuple2 tuple) { - return tuple.into(fn); + 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) { + public static Fn1, C> into( + Fn2 fn) { return Into.into().apply(fn); } - public static C into(BiFunction fn, Tuple2 tuple) { - return Into.into(fn).apply(tuple); + 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 1ab7e302e..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 @@ -1,38 +1,39 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.adt.product.Product3; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; /** - * Given an {@link Fn3}<A, B, C, D> and a {@link Tuple3}<A, B, C>, destructure the - * tuple and apply the slots as arguments to the function, returning the result. + * Given an {@link Fn3}<A, B, C, D> and a {@link Product3}<A, B, C>, destructure + * the product and apply the slots as arguments to the function, returning the result. * * @param the first argument type * @param the second argument type * @param the third argument type * @param the result type */ -public final class Into3 implements Fn2, Tuple3, D> { +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, Tuple3 tuple) { - return tuple.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) { + public static Fn1, D> into3(Fn3 fn) { return Into3.into3().apply(fn); } - public static D into3(Fn3 fn, Tuple3 tuple) { - return Into3.into3(fn).apply(tuple); + public static D into3(Fn3 fn, + Product3 product) { + return Into3.into3(fn).apply(product); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4.java index 240c0eb58..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 @@ -1,13 +1,13 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.hlist.Tuple4; +import com.jnape.palatable.lambda.adt.product.Product4; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn4; /** - * Given an {@link Fn4}<A, B, C, D, E> and a {@link Tuple4}<A, B, C, D>, - * destructure the tuple and apply the slots as arguments to the function, returning the result. + * Given an {@link Fn4}<A, B, C, D, E> and a {@link Product4}<A, B, C, D>, + * destructure the product and apply the slots as arguments to the function, returning the result. * * @param the first argument type * @param the second argument type @@ -15,27 +15,28 @@ * @param the fourth argument type * @param the result type */ -public final class Into4 implements Fn2, Tuple4, E> { +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, Tuple4 tuple) { - return tuple.into(fn); + 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( + public static Fn1, E> into4( Fn4 fn) { return Into4.into4().apply(fn); } public static E into4(Fn4 fn, - Tuple4 tuple) { - return Into4.into4(fn).apply(tuple); + Product4 product) { + return Into4.into4(fn).apply(product); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5.java index f74b6966c..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 @@ -1,13 +1,13 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.hlist.Tuple5; +import com.jnape.palatable.lambda.adt.product.Product5; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn5; /** - * Given an {@link Fn5}<A, B, C, D, E, F> and a {@link Tuple5}<A, B, C, D, E>, - * destructure the tuple and apply the slots as arguments to the function, returning the result. + * Given an {@link Fn5}<A, B, C, D, E, F> and a {@link Product5}<A, B, C, D, E>, + * destructure the product and apply the slots as arguments to the function, returning the result. * * @param the first argument type * @param the second argument type @@ -16,28 +16,28 @@ * @param the fifth argument type * @param the result type */ -public final class Into5 implements Fn2, Tuple5, F> { +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, - Tuple5 tuple) { - return tuple.into(fn); + 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( + public static Fn1, F> into5( Fn5 fn) { return Into5.into5().apply(fn); } public static F into5(Fn5 fn, - Tuple5 tuple) { - return Into5.into5(fn).apply(tuple); + Product5 product) { + return Into5.into5(fn).apply(product); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6.java index 50e68ef70..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 @@ -1,14 +1,14 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.hlist.Tuple6; +import com.jnape.palatable.lambda.adt.product.Product6; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn6; /** * Given an {@link Fn6}<A, B, C, D, E, F, G> and a - * {@link Tuple6}<A, B, C, D, E, F>, destructure the tuple and apply the slots as arguments to the - * function, returning the result. + * {@link Product6}<A, B, C, D, E, F>, destructure the product and apply the slots as arguments to + * the function, returning the result. * * @param the first argument type * @param the second argument type @@ -18,29 +18,29 @@ * @param the sixth argument type * @param the result type */ -public final class Into6 implements Fn2, Tuple6, G> { +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, - Tuple6 tuple) { - return tuple.into(fn); + 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( + public static Fn1, G> into6( Fn6 fn) { return Into6.into6().apply(fn); } public static G into6( Fn6 fn, - Tuple6 tuple) { - return Into6.into6(fn).apply(tuple); + Product6 product) { + return Into6.into6(fn).apply(product); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7.java index c814d4b3e..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 @@ -1,14 +1,14 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.hlist.Tuple7; +import com.jnape.palatable.lambda.adt.product.Product7; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn7; /** * Given an {@link Fn7}<A, B, C, D, E, F, G, H> and a - * {@link Tuple7}<A, B, C, D, E, F, G>, destructure the tuple and apply the slots as arguments to the - * function, returning the result. + * {@link Product7}<A, B, C, D, E, F, G>, destructure the product and apply the slots as arguments to + * the function, returning the result. * * @param the first argument type * @param the second argument type @@ -19,29 +19,30 @@ * @param the seventh argument type * @param the result type */ -public final class Into7 implements Fn2, Tuple7, H> { +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, - Tuple7 tuple) { - return tuple.into(fn); + 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( + public static Fn1, H> into7( Fn7 fn) { return Into7.into7().apply(fn); } public static H into7( Fn7 fn, - Tuple7 tuple) { - return Into7.into7(fn).apply(tuple); + Product7 product) { + return Into7.into7(fn).apply(product); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8.java index 6ac77230b..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 @@ -1,14 +1,14 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.hlist.Tuple8; +import com.jnape.palatable.lambda.adt.product.Product8; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn8; /** * Given an {@link Fn8}<A, B, C, D, E, F, G, H, I> and a - * {@link Tuple8}<A, B, C, D, E, F, G, H>, destructure the tuple and apply the slots as arguments to - * the function, returning the result. + * {@link Product8}<A, B, C, D, E, F, G, H>, destructure the product and apply the slots as arguments + * to the function, returning the result. * * @param the first argument type * @param the second argument type @@ -20,30 +20,30 @@ * @param the eighth argument type * @param the result type */ -public final class Into8 implements Fn2, Tuple8, I> { +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, - Tuple8 tuple) { - return tuple.into(fn); + Product8 product) { + return product.into(fn); } @SuppressWarnings("unchecked") public static Into8 into8() { - return INSTANCE; + return (Into8) INSTANCE; } - public static Fn1, I> into8( + public static Fn1, I> into8( Fn8 fn) { return Into8.into8().apply(fn); } public static I into8( Fn8 fn, - Tuple8 tuple) { - return Into8.into8(fn).apply(tuple); + Product8 product) { + return Into8.into8(fn).apply(product); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Iterate.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Iterate.java index 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 new file mode 100644 index 000000000..359d59b66 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LT.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.builtin.fn3.LTBy; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTBy.ltBy; + +/** + * Given two {@link Comparable} values of type A, return true if the second value is strictly + * less than the first value; otherwise, return false. + * + * @param the value type + * @see LTBy + * @see GT + */ +public final class LT> implements BiPredicate { + + private static final LT INSTANCE = new LT<>(); + + private LT() { + } + + @Override + public Boolean checkedApply(A y, A x) { + return ltBy(id(), y, x); + } + + @SuppressWarnings("unchecked") + public static > LT lt() { + return (LT) INSTANCE; + } + + public static > Predicate lt(A y) { + return LT.lt().apply(y); + } + + public static > Boolean lt(A y, A x) { + return lt(y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTE.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTE.java new file mode 100644 index 000000000..85f07b61e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTE.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.builtin.fn3.LTEBy; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEBy.lteBy; + +/** + * Given two {@link Comparable} values of type A, return true if the second value is less than + * or equal to the first value according to {@link Comparable#compareTo(Object)} otherwise, return false. + * + * @param the value typ + * @see LTEBy + * @see GTE + */ +public final class LTE> implements BiPredicate { + + private static final LTE INSTANCE = new LTE<>(); + + private LTE() { + } + + @Override + public Boolean checkedApply(A y, A x) { + return lteBy(id(), y, x); + } + + @SuppressWarnings("unchecked") + public static > LTE lte() { + return (LTE) INSTANCE; + } + + public static > Predicate lte(A y) { + return LTE.lte().apply(y); + } + + public static > Boolean lte(A y, A x) { + return lte(y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRec.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRec.java new file mode 100644 index 000000000..c1b7253d2 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRec.java @@ -0,0 +1,55 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.specialized.Kleisli; +import com.jnape.palatable.lambda.functor.builtin.Lazy; + +import static com.jnape.palatable.lambda.functions.specialized.Kleisli.kleisli; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + +/** + * Given a {@link Fn2} that receives a recursive function and an input and yields a {@link Lazy lazy} result, and an + * input, produce a {@link Lazy lazy} result that, when forced, will recursively invoke the function until it terminates + * in a stack-safe way. + *

+ * Example: + *

+ * {@code
+ * Lazy lazyFactorial = lazyRec((fact, x) -> x.equals(ONE)
+ *                                                       ? lazy(x)
+ *                                                       : fact.apply(x.subtract(ONE)).fmap(y -> y.multiply(x)),
+ *                                                  BigInteger.valueOf(50_000));
+ * BigInteger value = lazyFactorial.value(); // 3.34732050959714483691547609407148647791277322381045 x 10^213236
+ * }
+ * 
+ * + * @param the input type + * @param the output type + */ +public final class LazyRec implements + Fn2, Lazy>, A, Lazy>, A, Lazy> { + + private static final LazyRec INSTANCE = new LazyRec<>(); + + private LazyRec() { + } + + @Override + public Lazy checkedApply(Fn2, Lazy>, A, Lazy> fn, A a) { + return lazy(a).flatMap(fn.apply(lazyRec(fn))); + } + + @SuppressWarnings("unchecked") + public static LazyRec lazyRec() { + return (LazyRec) INSTANCE; + } + + public static Kleisli, Lazy> lazyRec( + Fn2, Lazy>, A, Lazy> fn) { + return kleisli(LazyRec.lazyRec().apply(fn)); + } + + public static Lazy lazyRec(Fn2, Lazy>, A, Lazy> fn, A a) { + return lazyRec(fn).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java index a7e512cc7..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 pre @SuppressWarnings("unchecked") public static 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 198e9feb9..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 @@ -4,9 +4,10 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.traversable.LambdaIterable; +import com.jnape.palatable.lambda.traversable.LambdaMap; import com.jnape.palatable.lambda.traversable.Traversable; -import java.util.function.Function; +import java.util.Map; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; @@ -23,64 +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>> + Fn1, ? extends AppMap>, AppMap> sequence(Map> mapApp) { + return pure -> (AppMap) Sequence., LambdaMap, Applicative, App>>sequence( + LambdaMap.wrap(mapApp), x -> pure.apply(x.unwrap()).fmap(LambdaMap::wrap)) + .fmap(LambdaMap::unwrap); + } + + public static , AppMap extends Applicative, App>> + AppMap sequence(Map> mapApp, Fn1, ? extends AppMap> pure) { + return Sequence.sequence(mapApp).apply(pure); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Slide.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Slide.java index 69e1ec16e..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 @@ -14,20 +14,20 @@ * Iterable} by one element at a time, returning an {@link Iterable}<{@link Iterable}<A>>. *

* Example: - *

+ * * slide(2, asList(1, 2, 3, 4, 5)); // [[1, 2], [2, 3], [3, 4], [4, 5]] * * @param the Iterable element type */ public final class Slide implements Fn2, Iterable>> { - private static final Slide INSTANCE = new Slide<>(); + private 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 de395356d..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 @@ -2,12 +2,11 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn1.Sort; -import java.util.ArrayList; import java.util.List; -import java.util.function.Function; -import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; +import static com.jnape.palatable.lambda.functions.builtin.fn2.SortWith.sortWith; import static java.util.Comparator.comparing; /** @@ -17,33 +16,31 @@ * * @param the input Iterable and output List element type * @param the mapped Comparable type - * @see com.jnape.palatable.lambda.functions.builtin.fn1.Sort + * @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) { - List result = toCollection(ArrayList::new, as); - result.sort(comparing(fn)); - return result; + 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 new file mode 100644 index 000000000..2c93239ee --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortWith.java @@ -0,0 +1,48 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn1.Sort; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; + +/** + * Given an {@link Iterable} and a {@link java.util.Comparator} over the {@link Iterable} element type, produce a + * sorted {@link List} of the original elements based on sorting applied by the {@link java.util.Comparator}. Note that + * this is both eager and monolithic. + * + * @param the input Iterable and output List element type + * @see Sort + * @see SortBy + */ +public final class SortWith implements Fn2, Iterable, List> { + + private static final SortWith INSTANCE = new SortWith<>(); + + private SortWith() { + } + + @Override + public List checkedApply(Comparator comparator, Iterable as) { + List result = toCollection(ArrayList::new, as); + result.sort(comparator); + return result; + } + + @SuppressWarnings("unchecked") + public static SortWith sortWith() { + return (SortWith) INSTANCE; + } + + public static Fn1, List> sortWith(Comparator comparator) { + return SortWith.sortWith().apply(comparator); + } + + public static List sortWith(Comparator comparator, Iterable as) { + return SortWith.sortWith(comparator).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Span.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Span.java index cd41bc74a..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,28 +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) { + public static Fn1, Tuple2, Iterable>> span( + 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 0b7bc7621..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 new file mode 100644 index 000000000..cecaa1543 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToArray.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.lang.reflect.Array; +import java.util.Collection; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; + +/** + * Write all the elements of an {@link Iterable} directly into an array of the specified type. If the {@link Iterable} + * is an instance of {@link Collection}, use {@link Collection#toArray(Object[])}. + * + * @param the {@link Iterable} element type + */ +public final class ToArray implements Fn2, Iterable, A[]> { + + private static final ToArray INSTANCE = new ToArray<>(); + + private ToArray() { + } + + @Override + @SuppressWarnings("unchecked") + public A[] checkedApply(Class arrayType, Iterable as) { + A[] array = (A[]) Array.newInstance(arrayType.getComponentType(), size(as).intValue()); + if (as instanceof Collection) + return ((Collection) as).toArray(array); + + int index = 0; + for (A a : as) { + array[index++] = a; + } + return array; + } + + @SuppressWarnings("unchecked") + public static ToArray toArray() { + return (ToArray) INSTANCE; + } + + public static Fn1, A[]> toArray(Class arrayType) { + return ToArray.toArray().apply(arrayType); + } + + public static A[] toArray(Class arrayType, Iterable as) { + return toArray(arrayType).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollection.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollection.java index b9b46b893..535d90e0a 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollection.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollection.java @@ -1,43 +1,43 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.functions.Fn0; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import java.util.Collection; -import java.util.function.Supplier; /** - * Given a {@link Supplier} of some {@link Collection} C, create an instance of C and add - * all of the elements in the provided Iterable to the instance. Note that instances of C - * must support {@link Collection#add} (which is to say, must not throw on invocation). + * Given an {@link Fn0} of some {@link Collection} C, create an instance of C and add all of + * the elements in the provided Iterable to the instance. Note that instances of C must + * support {@link Collection#add} (which is to say, must not throw on invocation). * * @param the iterable element type * @param the resulting collection type */ -public final class ToCollection> implements Fn2, Iterable, C> { +public final class ToCollection> implements Fn2, Iterable, C> { - private static final ToCollection INSTANCE = new ToCollection(); + private static final ToCollection INSTANCE = new ToCollection<>(); private ToCollection() { } @Override - public C apply(Supplier cSupplier, Iterable as) { - C c = cSupplier.get(); + public C checkedApply(Fn0 cFn0, Iterable as) { + C c = cFn0.apply(); as.forEach(c::add); return c; } @SuppressWarnings("unchecked") public static > ToCollection toCollection() { - return INSTANCE; + return (ToCollection) INSTANCE; } - public static > Fn1, C> toCollection(Supplier cSupplier) { - return ToCollection.toCollection().apply(cSupplier); + public static > Fn1, C> toCollection(Fn0 cFn0) { + return ToCollection.toCollection().apply(cFn0); } - public static > C toCollection(Supplier cSupplier, Iterable as) { - return toCollection(cSupplier).apply(as); + public static > C toCollection(Fn0 cFn0, Iterable as) { + return toCollection(cFn0).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMap.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMap.java index fa69c61c5..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,47 +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, Iterable> entries) { - return toMap(mSupplier).apply(entries); + public static > M toMap(Fn0 mFn0, + Iterable> entries) { + return toMap(mFn0).apply(entries); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Tupler2.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Tupler2.java index de1b4a283..939518d38 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Tupler2.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Tupler2.java @@ -15,19 +15,19 @@ */ public final class Tupler2 implements Fn2> { - private static final Tupler2 INSTANCE = new Tupler2(); + private static final Tupler2 INSTANCE = new Tupler2<>(); private Tupler2() { } @Override - public Tuple2 apply(A a, B b) { + public Tuple2 checkedApply(A a, B b) { return tuple(a, b); } @SuppressWarnings("unchecked") public static Tupler2 tupler() { - return INSTANCE; + return (Tupler2) INSTANCE; } public static Fn1> tupler(A a) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java index 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 new file mode 100644 index 000000000..ccb98929f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Between.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Clamp.clamp; + +/** + * Given two bounds and a value, return whether or not the value is greater than or equal to the lower bound and less + * than or equal to the upper bound. + * + * @param the bounds and input type + */ +public final class Between> implements Fn3 { + + private static final Between INSTANCE = new Between<>(); + + private Between() { + } + + @Override + public Boolean checkedApply(A lower, A upper, A a) { + return clamp(lower, upper, a).equals(a); + } + + @SuppressWarnings("unchecked") + public static > Between between() { + return (Between) INSTANCE; + } + + public static > BiPredicate between(A lower) { + return Between.between().apply(lower)::apply; + } + + public static > Predicate between(A lower, A upper) { + return between(lower).apply(upper); + } + + public static > Boolean between(A lower, A upper, A a) { + return between(lower, upper).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Bracket.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Bracket.java new file mode 100644 index 000000000..59e6f512d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Bracket.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.Monad; + +/** + * Given an {@link IO} that yields some type A, a cleanup operation to run if a value of that type could be + * provisioned, and a kleisli arrow from that type to a new {@link IO} of type B, produce an + * {@link IO}<B> that, when run, will provision the A, + * {@link Monad#flatMap(Fn1) flatMap} it to B, and clean up the original value if it was produced in the + * first place. + * + * @param the initial value to map and clean up + * @param the resulting type + */ +public final class Bracket implements + Fn3, Fn1>, Fn1>, IO> { + + private static final Bracket INSTANCE = new Bracket<>(); + + private Bracket() { + } + + @Override + public IO checkedApply(IO io, Fn1> cleanupIO, + Fn1> bodyIO) throws Throwable { + return io.flatMap(a -> bodyIO.apply(a).ensuring(cleanupIO.apply(a))); + } + + @SuppressWarnings("unchecked") + public static Bracket bracket() { + return (Bracket) INSTANCE; + } + + public static Fn2>, Fn1>, IO> bracket( + IO io) { + return Bracket.bracket().apply(io); + } + + public static Fn1>, IO> bracket( + IO io, Fn1> cleanupIO) { + return Bracket.bracket(io).apply(cleanupIO); + } + + public static IO bracket(IO io, Fn1> cleanupIO, + Fn1> bodyIO) { + return Bracket.bracket(io, cleanupIO).apply(bodyIO); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Clamp.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Clamp.java new file mode 100644 index 000000000..3343afae4 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Clamp.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; + +import static com.jnape.palatable.lambda.semigroup.builtin.Max.max; +import static com.jnape.palatable.lambda.semigroup.builtin.Min.min; + +/** + * Given two bounds and a value, "clamp" the value between the bounds via the following algorithm: + * - if the value is strictly less than the lower bound, return the lower bound + * - if the value is strictly greater than the upper bound, return the upper bound + * - otherwise, return the value + * + * @param the bounds and input type + */ +public final class Clamp> implements Fn3 { + + private static final Clamp INSTANCE = new Clamp<>(); + + private Clamp() { + } + + @Override + public A checkedApply(A lower, A upper, A a) { + return max(min(lower, upper)).fmap(min(max(lower, upper))).apply(a); + } + + @SuppressWarnings("unchecked") + public static > Clamp clamp() { + return (Clamp) INSTANCE; + } + + public static > Fn2 clamp(A lower) { + return Clamp.clamp().apply(lower); + } + + public static > Fn1 clamp(A lower, A upper) { + return clamp(lower).apply(upper); + } + + public static > A clamp(A lower, A upper, A a) { + return clamp(lower, upper).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java new file mode 100644 index 000000000..a6e04baa9 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java @@ -0,0 +1,63 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.CmpEq; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqWith.cmpEqWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B and two values + * of type A, return true if the first value is strictly equal to the second value (according + * to {@link Comparable#compareTo(Object)} in terms of their mapped B results; otherwise, return false. + * + * @param the value type + * @param the mapped comparison type + * @see CmpEq + * @see CmpEqWith + * @see LTBy + * @see GTBy + */ +public final class CmpEqBy> implements Fn3, A, A, Boolean> { + + private static final CmpEqBy INSTANCE = new CmpEqBy<>(); + + private CmpEqBy() { + } + + @Override + public Boolean checkedApply(Fn1 compareFn, A x, A y) { + return cmpEqWith(comparing(compareFn.toFunction()), x, y); + } + + @Override + public BiPredicate apply(Fn1 compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Fn1 compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static > CmpEqBy cmpEqBy() { + return (CmpEqBy) INSTANCE; + } + + public static > BiPredicate cmpEqBy(Fn1 compareFn) { + return CmpEqBy.cmpEqBy().apply(compareFn); + } + + public static > Predicate cmpEqBy(Fn1 compareFn, A x) { + return CmpEqBy.cmpEqBy(compareFn).apply(x); + } + + public static > Boolean cmpEqBy(Fn1 compareFn, A x, A y) { + return cmpEqBy(compareFn, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java new file mode 100644 index 000000000..ff95cd2fc --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.equal; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, return + * true if the first value is strictly equal to the second value (according to + * {@link Comparator#compare(Object, Object)} otherwise, return false. + * + * @param the value type + * @see CmpEqBy + * @see LTBy + * @see GTBy + * @see Compare + */ +public final class CmpEqWith implements Fn3, A, A, Boolean> { + + private static final CmpEqWith INSTANCE = new CmpEqWith<>(); + + private CmpEqWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(equal()); + } + + @SuppressWarnings("unchecked") + public static CmpEqWith cmpEqWith() { + return (CmpEqWith) INSTANCE; + } + + public static BiPredicate cmpEqWith(Comparator comparator) { + return CmpEqWith.cmpEqWith().apply(comparator); + } + + public static Predicate cmpEqWith(Comparator comparator, A x) { + return CmpEqWith.cmpEqWith(comparator).apply(x); + } + + public static Boolean cmpEqWith(Comparator comparator, A x, A y) { + return cmpEqWith(comparator, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java new file mode 100644 index 000000000..b388feca9 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java @@ -0,0 +1,56 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.ordering.ComparisonRelation; + +import java.util.Comparator; + +/** + * Given a {@link Comparator} from some type A and two values of type A, return a + * {@link ComparisonRelation} of the first value with reference to the second value (according to + * {@link Comparator#compare(Object, Object)}. The order of parameters is flipped with respect to + * {@link Comparator#compare(Object, Object)} for more idiomatic partial application. + *

+ * Example: + *

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

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

+ * Example: + *

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

* For more information, read about Catamorphisms. @@ -23,32 +36,41 @@ * @param The accumulation type * @see FoldLeft */ -public final class FoldRight implements Fn3, B, Iterable, B> { +public final class FoldRight implements + Fn3, ? extends Lazy>, Lazy, Iterable, Lazy> { - private static final FoldRight INSTANCE = new FoldRight(); + private static final FoldRight INSTANCE = new FoldRight<>(); private FoldRight() { } @Override - public B apply(BiFunction fn, B acc, Iterable as) { - return foldLeft((b, a) -> fn.apply(a, b), acc, reverse(as)); + public Lazy checkedApply(Fn2, ? extends Lazy> fn, Lazy acc, + Iterable as) { + return lazyRec((f, lazyIt) -> lazyIt.flatMap(it -> it.hasNext() + ? fn.apply(it.next(), f.apply(lazy(it))) + : acc), + lazy(as::iterator)); } @SuppressWarnings("unchecked") public static FoldRight foldRight() { - return INSTANCE; + return (FoldRight) INSTANCE; } - public static Fn2, B> foldRight(BiFunction fn) { + public static Fn2, Iterable, Lazy> foldRight( + Fn2, ? extends Lazy> fn) { return FoldRight.foldRight().apply(fn); } - public static Fn1, B> foldRight(BiFunction fn, B acc) { + public static Fn1, Lazy> foldRight( + Fn2, ? extends Lazy> fn, + Lazy acc) { return FoldRight.foldRight(fn).apply(acc); } - public static B foldRight(BiFunction fn, B acc, Iterable as) { + public static Lazy foldRight(Fn2, ? extends Lazy> fn, Lazy acc, + Iterable as) { return FoldRight.foldRight(fn, acc).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTBy.java new file mode 100644 index 000000000..caddec12e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTBy.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.GT; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B and two values + * of type A, return true if the second value is strictly greater than the first value in + * terms of their mapped B results; otherwise, return false. + * + * @param the value type + * @param the mapped comparison type + * @see GT + * @see GTWith + * @see LTBy + */ +public final class GTBy> implements Fn3, A, A, Boolean> { + + private static final GTBy INSTANCE = new GTBy<>(); + + private GTBy() { + } + + @Override + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return gtWith(comparing(compareFn.toFunction()), y, x); + } + + @Override + public BiPredicate apply(Fn1 compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Fn1 compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static > GTBy gtBy() { + return (GTBy) INSTANCE; + } + + public static > BiPredicate gtBy(Fn1 fn) { + return GTBy.gtBy().apply(fn); + } + + public static > Predicate gtBy(Fn1 fn, A y) { + return GTBy.gtBy(fn).apply(y); + } + + public static > Boolean gtBy(Fn1 fn, A y, A x) { + return gtBy(fn, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEBy.java new file mode 100644 index 000000000..7a1b4acad --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEBy.java @@ -0,0 +1,63 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.GTE; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEWith.gteWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B and two values + * of type A, return true if the second value is greater than or equal to the first value in + * terms of their mapped B results according to {@link Comparable#compareTo(Object)}; otherwise, return + * false. + * + * @param the value type + * @param the mapped comparison type + * @see GTE + * @see GTEWith + * @see LTEBy + */ +public final class GTEBy> implements Fn3, A, A, Boolean> { + + private static final GTEBy INSTANCE = new GTEBy<>(); + + private GTEBy() { + } + + @Override + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return gteWith(comparing(compareFn.toFunction()), y, x); + } + + @Override + public BiPredicate apply(Fn1 compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Fn1 compareFn, A y) { + return predicate(Fn3.super.apply(compareFn, y)); + } + + @SuppressWarnings("unchecked") + public static > GTEBy gteBy() { + return (GTEBy) INSTANCE; + } + + public static > BiPredicate gteBy(Fn1 fn) { + return GTEBy.gteBy().apply(fn); + } + + public static > Predicate gteBy(Fn1 fn, A y) { + return GTEBy.gteBy(fn).apply(y); + } + + public static > Boolean gteBy(Fn1 fn, A y, A x) { + return gteBy(fn, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java new file mode 100644 index 000000000..29a5dfe82 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.GTE; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, + * return true if the second value is greater than or equal to the first value in + * terms of their mapped B results according to {@link Comparator#compare(Object, Object)}; + * otherwise, return false. + * + * @param the value type + * @see GTE + * @see GTEBy + * @see LTEWith + */ +public final class GTEWith implements Fn3, A, A, Boolean> { + + private static final GTEWith INSTANCE = new GTEWith<>(); + + private GTEWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return !ltWith(comparator, a, a2); + } + + @SuppressWarnings("unchecked") + public static GTEWith gteWith() { + return (GTEWith) INSTANCE; + } + + public static BiPredicate gteWith(Comparator comparator) { + return GTEWith.gteWith().apply(comparator); + } + + public static Predicate gteWith(Comparator comparator, A y) { + return GTEWith.gteWith(comparator).apply(y); + } + + public static Boolean gteWith(Comparator comparator, A y, A x) { + return gteWith(comparator, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java new file mode 100644 index 000000000..d65bec00f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.GT; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.greaterThan; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, + * return true if the second value is strictly greater than the first value in + * terms of their mapped B results; otherwise, return false. + * + * @param the value type + * @see GT + * @see GTBy + * @see LTWith + */ +public final class GTWith implements Fn3, A, A, Boolean> { + + private static final GTWith INSTANCE = new GTWith<>(); + + private GTWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static GTWith gtWith() { + return (GTWith) INSTANCE; + } + + public static BiPredicate gtWith(Comparator comparator) { + return GTWith.gtWith().apply(comparator); + } + + public static Predicate gtWith(Comparator comparator, A y) { + return GTWith.gtWith(comparator).apply(y); + } + + public static Boolean gtWith(Comparator comparator, A y, A x) { + return gtWith(comparator, y).apply(x); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(greaterThan()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTBy.java new file mode 100644 index 000000000..05e163844 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTBy.java @@ -0,0 +1,61 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.LT; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B and two values + * of type A, return true if the second value is strictly less than the first value in terms + * of their mapped B results; otherwise, return false. + * + * @param the value type + * @param the mapped comparison type + * @see LT + * @see GTBy + */ +public final class LTBy> implements Fn3, A, A, Boolean> { + + private static final LTBy INSTANCE = new LTBy<>(); + + private LTBy() { + } + + @Override + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return ltWith(comparing(compareFn.toFunction()), y, x); + } + + @Override + public BiPredicate apply(Fn1 compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Fn1 compareFn, A y) { + return predicate(Fn3.super.apply(compareFn, y)); + } + + @SuppressWarnings("unchecked") + public static > LTBy ltBy() { + return (LTBy) INSTANCE; + } + + public static > BiPredicate ltBy(Fn1 fn) { + return LTBy.ltBy().apply(fn); + } + + public static > Predicate ltBy(Fn1 fn, A y) { + return LTBy.ltBy(fn).apply(y); + } + + public static > Boolean ltBy(Fn1 fn, A y, A x) { + return ltBy(fn, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEBy.java new file mode 100644 index 000000000..4c377d583 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEBy.java @@ -0,0 +1,61 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.LTE; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEWith.lteWith; +import static java.util.Comparator.comparing; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B and two values + * of type A, return true if the second value is less than or equal to the first value in + * terms of their mapped B results according to {@link Comparable#compareTo(Object)}; otherwise, return + * false. + * + * @param the value type + * @param the mapped comparison type + * @see LTE + * @see GTEBy + */ +public final class LTEBy> implements Fn3, A, A, Boolean> { + + private static final LTEBy INSTANCE = new LTEBy<>(); + + private LTEBy() { + } + + @Override + public Boolean checkedApply(Fn1 compareFn, A y, A x) { + return lteWith(comparing(compareFn.toFunction()), y, x); + } + + @Override + public BiPredicate apply(Fn1 compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Fn1 compareFn, A y) { + return Fn3.super.apply(compareFn, y)::apply; + } + + @SuppressWarnings("unchecked") + public static > LTEBy lteBy() { + return (LTEBy) INSTANCE; + } + + public static > BiPredicate lteBy(Fn1 fn) { + return LTEBy.lteBy().apply(fn); + } + + public static > Predicate lteBy(Fn1 fn, A y) { + return LTEBy.lteBy(fn).apply(y); + } + + public static > Boolean lteBy(Fn1 fn, A y, A x) { + return lteBy(fn, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java new file mode 100644 index 000000000..403f5c15f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.LTE; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, + * return true if the second value is less than or equal to the first value in + * terms of their mapped B results according to {@link Comparator#compare(Object, Object)}; + * otherwise, return false. + * + * @param the value type + * @see LTE + * @see LTEBy + * @see GTEWith + */ +public final class LTEWith implements Fn3, A, A, Boolean> { + + private static final LTEWith INSTANCE = new LTEWith<>(); + + private LTEWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return !gtWith(comparator, a, a2); + } + + @SuppressWarnings("unchecked") + public static LTEWith lteWith() { + return (LTEWith) INSTANCE; + } + + public static BiPredicate lteWith(Comparator comparator) { + return LTEWith.lteWith().apply(comparator); + } + + public static Predicate lteWith(Comparator comparator, A y) { + return LTEWith.lteWith(comparator).apply(y); + } + + public static Boolean lteWith(Comparator comparator, A y, A x) { + return lteWith(comparator, y).apply(x); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java new file mode 100644 index 000000000..14652a4c6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.LT; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.lessThan; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a comparator for some type A and two values of type A, + * return true if the second value is strictly less than than the first value in + * terms of their mapped B results; otherwise, return false. + * + * @param the value type + * @see LT + * @see LTBy + * @see GTWith + */ +public final class LTWith implements Fn3, A, A, Boolean> { + + private static final LTWith INSTANCE = new LTWith<>(); + + private LTWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static LTWith ltWith() { + return (LTWith) INSTANCE; + } + + public static BiPredicate ltWith(Comparator comparator) { + return LTWith.ltWith().apply(comparator); + } + + public static Predicate ltWith(Comparator comparator, A y) { + return LTWith.ltWith(comparator).apply(y); + } + + public static Boolean ltWith(Comparator comparator, A y, A x) { + return ltWith(comparator, y).apply(x); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(lessThan()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2.java index c3dd1742b..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,52 +5,50 @@ import com.jnape.palatable.lambda.functions.Fn3; import com.jnape.palatable.lambda.functor.Applicative; -import java.util.function.BiFunction; - -import static com.jnape.palatable.lambda.functions.Fn2.fn2; - /** - * Lift into and apply a {@link BiFunction} to two {@link Applicative} values, returning the result inside the same - * {@link Applicative} context. Equivalent ot appB.zip(appA.fmap(fn)). + * 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 function's first argument type + * @param the function's second argument type + * @param the function's return type + * @param the applicative witness + * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA2 implements Fn3, - Applicative, Applicative, Applicative> { +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 Applicative apply(BiFunction fn, - Applicative appA, - Applicative appB) { - return appB.zip(appA.fmap(fn2(fn))); + 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 LiftA2 liftA2() { - return INSTANCE; + public static , AppC extends Applicative> + LiftA2 liftA2() { + return (LiftA2) INSTANCE; } - public static Fn2, Applicative, Applicative> 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 Fn1, Applicative> liftA2( - BiFunction fn, Applicative 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 Applicative liftA2( - BiFunction fn, Applicative appA, Applicative 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 2227c584e..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; @@ -15,20 +13,20 @@ * n times, returning the result. *

* Example: - *

+ * * times(3, x -> x + 1, 0); // 3 * * @param the input and output type */ -public final class Times implements Fn3, A, A> { +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 fe353a497..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 new file mode 100644 index 000000000..9000ad81a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn3} to three {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's third argument type + * @param the function's return type + * @param the applicative witness + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA3, AppD extends Applicative> implements + Fn4, Applicative, Applicative, Applicative, AppD> { + + private static final LiftA3 INSTANCE = new LiftA3<>(); + + private LiftA3() { + } + + @Override + public AppD checkedApply(Fn3 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return appA.zip(appB.zip(appC.fmap(c -> b -> a -> fn.apply(a, b, c)))).coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppD extends Applicative> + LiftA3 liftA3() { + return (LiftA3) INSTANCE; + } + + public static , AppD extends Applicative> + Fn3, Applicative, Applicative, AppD> liftA3(Fn3 fn) { + return LiftA3.liftA3().apply(fn); + } + + public static , AppD extends Applicative> + Fn2, Applicative, AppD> liftA3(Fn3 fn, Applicative appA) { + return LiftA3.liftA3(fn).apply(appA); + } + + public static , AppD extends Applicative> + Fn1, AppD> liftA3(Fn3 fn, Applicative appA, Applicative appB) { + return LiftA3.liftA3(fn, appA).apply(appB); + } + + public static , AppD extends Applicative> + AppD liftA3(Fn3 fn, Applicative appA, Applicative appB, Applicative appC) { + return LiftA3.liftA3(fn, appA, appB).apply(appC); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimit.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimit.java new file mode 100644 index 000000000..53d9b7189 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimit.java @@ -0,0 +1,86 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.internal.iteration.IterationInterruptedException; +import com.jnape.palatable.lambda.internal.iteration.RateLimitingIterable; + +import java.time.Duration; +import java.time.Instant; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static java.util.Collections.singleton; + +/** + * Given an {@link Fn0} of {@link Instant Instants} (presumably backed by a clock), a limit, a + * {@link Duration}, and an {@link Iterable} as, return an {@link Iterable} that iterates as + * according to the threshold specified by the limit per duration, using the {@link Fn0} to advance time. + *

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


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

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

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

+ * Note that the returned {@link Iterable} will never iterate faster than the specified rate limit, but the earliest + * the next element is available will be dependent on the precision of the underlying instant supplier as well as any + * overhead involved in producing the element from the original {@link Iterable}. + * + * @param the {@link Iterable} element type + */ +public final class RateLimit implements Fn4, Long, Duration, Iterable, Iterable> { + + private static final RateLimit INSTANCE = new RateLimit<>(); + + private RateLimit() { + } + + @Override + public Iterable checkedApply(Fn0 instantFn0, Long limit, Duration duration, Iterable as) { + if (limit < 1) + throw new IllegalArgumentException("Limit must be greater than 0: " + limit); + + return new RateLimitingIterable<>(as, singleton(tuple(limit, duration, instantFn0))); + } + + @SuppressWarnings("unchecked") + public static RateLimit rateLimit() { + return (RateLimit) INSTANCE; + } + + public static Fn3, Iterable> rateLimit(Fn0 instantFn0) { + return RateLimit.rateLimit().apply(instantFn0); + } + + public static Fn2, Iterable> rateLimit(Fn0 instantFn0, Long limit) { + return RateLimit.rateLimit(instantFn0).apply(limit); + } + + public static Fn1, Iterable> rateLimit(Fn0 instantFn0, Long limit, Duration duration) { + return RateLimit.rateLimit(instantFn0, limit).apply(duration); + } + + public static Iterable rateLimit(Fn0 instantFn0, Long limit, Duration duration, Iterable as) { + return RateLimit.rateLimit(instantFn0, limit, duration).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4.java new file mode 100644 index 000000000..9132376dc --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4.java @@ -0,0 +1,79 @@ +package com.jnape.palatable.lambda.functions.builtin.fn5; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functions.Fn5; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn4} to four {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's third argument type + * @param the function's fourth argument type + * @param the function's return type + * @param the applicative witness + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA4, AppE extends Applicative> implements + Fn5, Applicative, Applicative, Applicative, Applicative, + AppE> { + + private static final LiftA4 INSTANCE = new LiftA4<>(); + + private LiftA4() { + } + + @Override + public AppE checkedApply(Fn4 fn, Applicative appA, Applicative appB, + Applicative appC, Applicative appD) { + return appA.zip(appB.zip(appC.zip(appD.fmap(d -> c -> b -> a -> fn.apply(a, b, c, d))))).coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppE extends Applicative> + LiftA4 liftA4() { + return (LiftA4) INSTANCE; + } + + public static , AppE extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppE> liftA4( + Fn4 fn) { + return LiftA4.liftA4().apply(fn); + } + + public static , AppE extends Applicative> + Fn3, Applicative, Applicative, AppE> liftA4(Fn4 fn, + Applicative appA) { + return LiftA4.liftA4(fn).apply(appA); + } + + public static , AppE extends Applicative> + Fn2, Applicative, AppE> liftA4(Fn4 fn, + Applicative appA, + Applicative appB) { + return LiftA4.liftA4(fn, appA).apply(appB); + } + + public static , AppE extends Applicative> + Fn1, AppE> liftA4(Fn4 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA4.liftA4(fn, appA, appB).apply(appC); + } + + public static , AppE extends Applicative> + AppE liftA4(Fn4 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA4.liftA4(fn, appA, appB, appC).apply(appD); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5.java new file mode 100644 index 000000000..7fd7c8f4f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5.java @@ -0,0 +1,93 @@ +package com.jnape.palatable.lambda.functions.builtin.fn6; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functions.Fn5; +import com.jnape.palatable.lambda.functions.Fn6; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn5} to five {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's third argument type + * @param the function's fourth argument type + * @param the function's fifth argument type + * @param the function's return type + * @param the applicative witness + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA5, AppF extends Applicative> + implements Fn6, Applicative, Applicative, Applicative, + Applicative, Applicative, AppF> { + + private static final LiftA5 INSTANCE = new LiftA5<>(); + + private LiftA5() { + } + + @Override + public AppF checkedApply(Fn5 fn, Applicative appA, Applicative appB, + Applicative appC, Applicative appD, Applicative appE) { + return appA.zip(appB.zip(appC.zip(appD.zip(appE.fmap(e -> d -> c -> b -> a -> fn.apply(a, b, c, d, e)))))) + .coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppF extends Applicative> + LiftA5 liftA5() { + return (LiftA5) INSTANCE; + } + + public static , AppF extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppF> + liftA5(Fn5 fn) { + return LiftA5.liftA5().apply(fn); + } + + public static , AppF extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppF> + liftA5(Fn5 fn, + Applicative appA) { + return LiftA5.liftA5(fn).apply(appA); + } + + public static , AppF extends Applicative> + Fn3, Applicative, Applicative, AppF> liftA5(Fn5 fn, + Applicative appA, + Applicative appB) { + return LiftA5.liftA5(fn, appA).apply(appB); + } + + public static , AppF extends Applicative> + Fn2, Applicative, AppF> liftA5(Fn5 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA5.liftA5(fn, appA, appB).apply(appC); + } + + public static , AppF extends Applicative> + Fn1, AppF> liftA5(Fn5 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA5.liftA5(fn, appA, appB, appC).apply(appD); + } + + public static , AppF extends Applicative> + AppF liftA5(Fn5 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE) { + return LiftA5.liftA5(fn, appA, appB, appC, appD).apply(appE); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6.java new file mode 100644 index 000000000..fcc7934c9 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6.java @@ -0,0 +1,117 @@ +package com.jnape.palatable.lambda.functions.builtin.fn7; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functions.Fn5; +import com.jnape.palatable.lambda.functions.Fn6; +import com.jnape.palatable.lambda.functions.Fn7; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn6} to six {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's third argument type + * @param the function's fourth argument type + * @param the function's fifth argument type + * @param the function's sixth argument type + * @param the function's return type + * @param the applicative witness + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA6, AppG extends Applicative> + implements Fn7, + Applicative, + Applicative, + Applicative, + Applicative, + Applicative, + Applicative, + AppG> { + + private static final LiftA6 INSTANCE = new LiftA6<>(); + + private LiftA6() { + } + + @Override + public AppG checkedApply(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF) { + return appA.zip(appB.zip(appC.zip(appD.zip(appE.zip(appF.fmap( + f -> e -> d -> c -> b -> a -> fn.apply(a, b, c, d, e, f))))))).coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppG extends Applicative> + LiftA6 liftA6() { + return (LiftA6) INSTANCE; + } + + public static , AppG extends Applicative> + Fn6, Applicative, Applicative, Applicative, Applicative, + Applicative, AppG> liftA6(Fn6 fn) { + return LiftA6.liftA6().apply(fn); + } + + public static , AppG extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppG> + liftA6(Fn6 fn, Applicative appA) { + return LiftA6.liftA6(fn).apply(appA); + } + + public static , AppG extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppG> + liftA6(Fn6 fn, + Applicative appA, + Applicative appB) { + return LiftA6.liftA6(fn, appA).apply(appB); + } + + public static , AppG extends Applicative> + Fn3, Applicative, Applicative, AppG> liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA6.liftA6(fn, appA, appB).apply(appC); + } + + public static , AppG extends Applicative> + Fn2, Applicative, AppG> liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA6.liftA6(fn, appA, appB, appC).apply(appD); + } + + public static , AppG extends Applicative> + Fn1, AppG> liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE) { + return LiftA6.liftA6(fn, appA, appB, appC, appD).apply(appE); + } + + public static , AppG extends Applicative> + AppG liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF) { + return LiftA6.liftA6(fn, appA, appB, appC, appD, appE).apply(appF); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7.java new file mode 100644 index 000000000..6f80b7d37 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7.java @@ -0,0 +1,128 @@ +package com.jnape.palatable.lambda.functions.builtin.fn8; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functions.Fn5; +import com.jnape.palatable.lambda.functions.Fn6; +import com.jnape.palatable.lambda.functions.Fn7; +import com.jnape.palatable.lambda.functions.Fn8; +import com.jnape.palatable.lambda.functor.Applicative; + +/** + * Lift into and apply an {@link Fn7} to seven {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's third argument type + * @param the function's fourth argument type + * @param the function's fifth argument type + * @param the function's sixth argument type + * @param the function's seventh argument type + * @param the function's return type + * @param the applicative unification type + * @param the inferred applicative return type + * @see Applicative#zip(Applicative) + */ +public final class LiftA7, AppH extends Applicative> + implements Fn8, Applicative, Applicative, Applicative, + Applicative, Applicative, Applicative, Applicative, AppH> { + + private static final LiftA7 INSTANCE = new LiftA7<>(); + + private LiftA7() { + } + + @Override + public AppH checkedApply(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF, + Applicative appG) { + return appA.zip(appB.zip(appC.zip(appD.zip(appE.zip(appF.zip(appG.fmap( + g -> f -> e -> d -> c -> b -> a -> fn.apply(a, b, c, d, e, f, g)))))))).coerce(); + } + + @SuppressWarnings("unchecked") + public static , AppH extends Applicative> + LiftA7 liftA7() { + return (LiftA7) INSTANCE; + } + + public static , AppH extends Applicative> + Fn7, Applicative, Applicative, Applicative, Applicative, + Applicative, Applicative, AppH> liftA7(Fn7 fn) { + return LiftA7.liftA7().apply(fn); + } + + public static , AppH extends Applicative> + Fn6, Applicative, Applicative, Applicative, Applicative, + Applicative, AppH> liftA7(Fn7 fn, + Applicative appA) { + return LiftA7.liftA7(fn).apply(appA); + } + + public static , AppH extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppH> + liftA7(Fn7 fn, + Applicative appA, + Applicative appB) { + return LiftA7.liftA7(fn, appA).apply(appB); + } + + public static , AppH extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppH> liftA7( + Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA7.liftA7(fn, appA, appB).apply(appC); + } + + public static , AppH extends Applicative> + Fn3, Applicative, Applicative, AppH> liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA7.liftA7(fn, appA, appB, appC).apply(appD); + } + + public static , AppH extends Applicative> + Fn2, Applicative, AppH> liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE) { + return LiftA7.liftA7(fn, appA, appB, appC, appD).apply(appE); + } + + public static , AppH extends Applicative> + Fn1, AppH> liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF) { + return LiftA7.liftA7(fn, appA, appB, appC, appD, appE).apply(appF); + } + + public static , AppH extends Applicative> + AppH liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF, + Applicative appG) { + return LiftA7.liftA7(fn, appA, appB, appC, appD, appE, appF).apply(appG); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java b/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java new file mode 100644 index 000000000..0c4bb93c3 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java @@ -0,0 +1,102 @@ +package com.jnape.palatable.lambda.functions.ordering; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn3.Compare; + +import java.util.Comparator; + +/** + * Specialized {@link CoProduct3} representing the possible results of a ordered comparison. + * Used by {@link Compare} as the result of a comparison. + * + * @see Compare + */ +public abstract class ComparisonRelation + implements CoProduct3< + ComparisonRelation.LessThan, + ComparisonRelation.Equal, + ComparisonRelation.GreaterThan, + ComparisonRelation> { + + private ComparisonRelation() { + } + + /** + * Return a comparison relation from the result of a {@link Comparator} or {@link Comparable} result + * + * @param signifier The result of {@link Comparator#compare(Object, Object)} or {@link Comparable#compareTo(Object)} + * @return The intended {@link ComparisonRelation} of the signifier + */ + public static ComparisonRelation fromInt(int signifier) { + return signifier > 0 ? greaterThan() : signifier == 0 ? equal() : lessThan(); + } + + public static GreaterThan greaterThan() { + return GreaterThan.INSTANCE; + } + + public static LessThan lessThan() { + return LessThan.INSTANCE; + } + + public static Equal equal() { + return Equal.INSTANCE; + } + + public static final class LessThan extends ComparisonRelation { + private static final LessThan INSTANCE = new LessThan(); + + private LessThan() { + } + + @Override + public String toString() { + return "LessThan"; + } + + @Override + public R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn) { + return aFn.apply(this); + } + } + + public static final class Equal extends ComparisonRelation { + private static final Equal INSTANCE = new Equal(); + + private Equal() { + } + + public String toString() { + return "Equal"; + } + + @Override + public R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn) { + return bFn.apply(this); + } + } + + public static final class GreaterThan extends ComparisonRelation { + private static final GreaterThan INSTANCE = new GreaterThan(); + + private GreaterThan() { + } + + @Override + public String toString() { + return "GreaterThan"; + } + + @Override + public R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn) { + return cFn.apply(this); + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java b/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java index 555af77e5..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,76 +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 - @SuppressWarnings("unchecked") - public RecursiveResult biMapL(Function fn) { - return (RecursiveResult) Bifunctor.super.biMapL(fn); + public RecursiveResult invert() { + return match(RecursiveResult::terminate, RecursiveResult::recurse); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public RecursiveResult biMapR(Function fn) { - return (RecursiveResult) Bifunctor.super.biMapR(fn); + public RecursiveResult biMapL(Fn1 fn) { + return (RecursiveResult) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - public RecursiveResult biMap(Function lFn, - Function rFn) { + public RecursiveResult biMapR(Fn1 fn) { + return (RecursiveResult) Bifunctor.super.biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public RecursiveResult biMap(Fn1 lFn, + Fn1 rFn) { return match(a -> recurse(lFn.apply(a)), b -> terminate(rFn.apply(b))); } + /** + * {@inheritDoc} + */ @Override - public RecursiveResult flatMap( - 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; @@ -96,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); } @@ -126,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 fee6e7339..198316f00 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiMonoidFactory.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiMonoidFactory.java @@ -1,26 +1,41 @@ package com.jnape.palatable.lambda.functions.specialized; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.internal.Runtime; import com.jnape.palatable.lambda.monoid.Monoid; @FunctionalInterface public interface BiMonoidFactory extends BiSemigroupFactory { @Override - default MonoidFactory apply(A a) { - return b -> apply(a, b); + Monoid checkedApply(A a, B b) throws Throwable; + + @Override + default MonoidFactory checkedApply(A a) throws Throwable { + return b -> checkedApply(a, b); + } + + @Override + default Monoid apply(A a, B b) { + try { + return checkedApply(a, b); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } } @Override - Monoid apply(A a, B b); + default MonoidFactory apply(A a) { + return b -> apply(a, b); + } @Override default BiMonoidFactory flip() { - return (b, a) -> apply(a, b); + return (b, a) -> checkedApply(a, b); } @Override - default MonoidFactory, C> uncurry() { + default MonoidFactory, C> uncurry() { return ab -> apply(ab._1()).apply(ab._2()); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiPredicate.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiPredicate.java index b7fe21c35..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,49 +1,48 @@ package com.jnape.palatable.lambda.functions.specialized; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; - -import 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; } /** * {@inheritDoc} */ @Override - default Predicate> uncurry() { + default Predicate> uncurry() { return Fn2.super.uncurry()::apply; } @@ -51,7 +50,7 @@ default Predicate> uncurry() { * {@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 d7266132c..5cf0a4a17 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiSemigroupFactory.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiSemigroupFactory.java @@ -1,6 +1,6 @@ package com.jnape.palatable.lambda.functions.specialized; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functions.Fn4; import com.jnape.palatable.lambda.semigroup.Semigroup; @@ -8,7 +8,17 @@ public interface BiSemigroupFactory extends Fn4 { @Override - Semigroup apply(A a, B b); + Semigroup checkedApply(A a, B b) throws Throwable; + + @Override + default C checkedApply(A a, B b, C c, C d) throws Throwable { + return checkedApply(a, b).checkedApply(c, d); + } + + @Override + default Semigroup apply(A a, B b) { + return Fn4.super.apply(a, b)::apply; + } @Override default SemigroupFactory apply(A a) { @@ -21,12 +31,7 @@ default BiSemigroupFactory flip() { } @Override - default SemigroupFactory, C> uncurry() { + default SemigroupFactory, C> uncurry() { return ab -> apply(ab._1(), ab._2()); } - - @Override - default C apply(A a, B b, C c, C d) { - return apply(a).apply(b).apply(c, d); - } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java 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/Predicate.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java index 89e3e5e93..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,85 +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; } /** - * Override of {@link java.util.function.Predicate#and(java.util.function.Predicate)}, returning an instance of - * Predicate for compatibility. Left-to-right composition. + * {@inheritDoc} + */ + @Override + default BiPredicate compose(Fn2 before) { + return Fn1.super.compose(before)::apply; + } + + /** + * Left-to-right short-circuiting logical conjunction. * * @param other the predicate to test if this one succeeds * @return a predicate representing the conjunction of this predicate and other */ - @Override - default Predicate and(java.util.function.Predicate other) { - return a -> apply(a) && other.test(a); + default Predicate and(Predicate other) { + return a -> apply(a) && other.apply(a); } /** - * Override of {@link java.util.function.Predicate#or(java.util.function.Predicate)}, returning an instance of - * Predicate for compatibility. Left-to-right composition. + * Left-to-right short-circuiting logical disjunction. * * @param other the predicate to test if this one fails * @return a predicate representing the disjunction of this predicate and other */ - @Override - default Predicate or(java.util.function.Predicate other) { - return a -> apply(a) || other.test(a); + default Predicate or(Predicate other) { + return a -> apply(a) || other.apply(a); } /** - * Override of {@link java.util.function.Predicate#negate()}, returning an instance of Predicate for - * compatibility. + * Logical negation. * * @return the negation of this predicate */ - @Override default Predicate negate() { return a -> !apply(a); } + + /** + * Convert this {@link Predicate} to a java {@link java.util.function.Predicate}. + * + * @return the {@link java.util.function.Predicate} + */ + default java.util.function.Predicate toPredicate() { + return this::apply; + } + + /** + * Static factory method to create a predicate from an {@link Fn1}. + * + * @param predicate the {@link Fn1} + * @param the input type + * @return the predicate + */ + static Predicate predicate(Fn1 predicate) { + return predicate::apply; + } + + /** + * Create a {@link Predicate} from a java {@link java.util.function.Predicate}. + * + * @param predicate the java {@link java.util.function.Predicate} + * @param the input type + * @return the {@link Predicate} + */ + static Predicate fromPredicate(java.util.function.Predicate predicate) { + return predicate::test; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Pure.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Pure.java new file mode 100644 index 000000000..797a2e8aa --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Pure.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.internal.Runtime; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; + +/** + * Generalized, portable {@link Applicative#pure(Object)}, with a loosened {@link Functor} constraint. + * + * @param the {@link Functor} to lift into + */ +@FunctionalInterface +public interface Pure> { + + Functor checkedApply(A a) throws Throwable; + + default > FA apply(A a) { + try { + @SuppressWarnings("unchecked") FA fa = downcast(checkedApply(a)); + return fa; + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + } + + /** + * Static method to aid inference. + * + * @param pure the {@link Pure} + * @param the {@link Functor} witness + * @return the {@link Pure} + */ + static > Pure pure(Pure pure) { + return pure; + } + + /** + * Extract an {@link Applicative Applicative's} {@link Applicative#pure(Object) pure} implementation to an instance + * of {@link Pure}. + * + * @param app the {@link Applicative} + * @param the witness + * @return the {@link Pure} + */ + static > Pure of(Applicative app) { + return app::pure; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/SemigroupFactory.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/SemigroupFactory.java index 4a47c277c..6d49d6a55 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/SemigroupFactory.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/SemigroupFactory.java @@ -7,10 +7,15 @@ public interface SemigroupFactory extends Fn3 { @Override - Semigroup apply(A a); + Semigroup checkedApply(A a) throws Throwable; @Override - default B apply(A a, B b, B c) { - return apply(a).apply(b, c); + default Semigroup apply(A a) { + return Fn3.super.apply(a)::apply; + } + + @Override + default B checkedApply(A a, B b, B c) throws Throwable { + return checkedApply(a).checkedApply(b, c); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/SideEffect.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/SideEffect.java new file mode 100644 index 000000000..6ef2092a1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/SideEffect.java @@ -0,0 +1,59 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.internal.Runtime; +import com.jnape.palatable.lambda.io.IO; + +/** + * An interface used to represent an effect that requires no input and produces no output, and therefore is only + * perceivable through inspection of some unreported state. Only exists because Java target-type inference requires an + * interface, or else this would all be internal, hence the inconveniently-named Ω. + *

+ * Ω should *never* be called directly. + * + * @see IO#io(SideEffect) + */ +public interface SideEffect { + + /** + * A no-op {@link SideEffect} + */ + SideEffect NOOP = () -> {}; + + @SuppressWarnings("NonAsciiCharacters") + void Ω() throws Throwable; + + /** + * Convert this {@link SideEffect} to a java {@link Runnable}. + * + * @return the {@link Runnable} + */ + default Runnable toRunnable() { + return () -> { + try { + Ω(); + } catch (Throwable t) { + throw Runtime.throwChecked(t); + } + }; + } + + /** + * Create a {@link SideEffect} from a java {@link Runnable}. + * + * @param runnable the {@link Runnable} + * @return the {@link SideEffect} + */ + static SideEffect fromRunnable(Runnable runnable) { + return runnable::run; + } + + /** + * Static factory method to aid in inference. + * + * @param sideEffect the {@link SideEffect} + * @return the {@link SideEffect} + */ + static SideEffect sideEffect(SideEffect sideEffect) { + return sideEffect; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java deleted file mode 100644 index 3d5a9176c..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -import com.jnape.palatable.lambda.functions.Fn1; - -import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; - -/** - * Specialized {@link Fn1} that can throw 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 { - - @Override - default B apply(A a) { - try { - return checkedApply(a); - } catch (Throwable t) { - throw throwChecked(t); - } - } - - /** - * 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; -} 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 2552c746e..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -import com.jnape.palatable.lambda.adt.Unit; - -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 { - - @Override - default void run() { - try { - checkedRun(); - } catch (Throwable t) { - throw throwChecked(t); - } - } - - /** - * 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; - - /** - * 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 b32f1e4b8..000000000 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.jnape.palatable.lambda.functions.specialized.checked; - -import java.util.function.Supplier; - -import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; - -/** - * Specialized {@link Supplier} that can throw any {@link Throwable}. - * - * @param The {@link Throwable} type - * @param The return type - * @see CheckedFn1 - * @see CheckedRunnable - */ -@FunctionalInterface -public interface CheckedSupplier extends Supplier { - - @Override - default A get() { - try { - return checkedGet(); - } catch (Throwable t) { - throw throwChecked(t); - } - } - - /** - * A version of {@link Supplier#get()} that can throw checked exceptions. - * - * @return the supplied result - * @throws 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; - } - - /** - * 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 224963a83..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(zip(pure(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/builtin/Compose.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java index 568988a9d..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,38 +16,74 @@ * @param The inner applicative * @param The carrier type */ -public class Compose implements Applicative> { +public final class Compose, G extends Applicative, A> implements + Applicative> { - private final Applicative, F> fga; + private final Applicative, F> fga; - public Compose(Applicative, F> fga) { + public Compose(Applicative, F> fga) { this.fga = fga; } - public Applicative, F> getCompose() { - return fga; + @SuppressWarnings("RedundantTypeArguments") + public , FGA extends Applicative> FGA getCompose() { + 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(); @@ -62,8 +101,25 @@ public int hashCode() { @Override public String toString() { - return "Compose{" + - "fga=" + fga + - '}'; + 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 1a9439d6b..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,43 +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 - @SuppressWarnings("unchecked") - public Exchange diMapL(Function fn) { - return (Exchange) Profunctor.super.diMapL(fn); + public Exchange diMapL(Fn1 fn) { + return (Exchange) Profunctor.super.diMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Exchange diMapR(Function fn) { - return (Exchange) Profunctor.super.diMapR(fn); + public Exchange diMapR(Fn1 fn) { + return (Exchange) Profunctor.super.diMapR(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public Exchange contraMap(Function fn) { - return (Exchange) Profunctor.super.contraMap(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/MappingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/MappingIterator.java deleted file mode 100644 index 82624603f..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iteration/MappingIterator.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.jnape.palatable.lambda.iteration; - -import java.util.Iterator; -import java.util.function.Function; - -public class MappingIterator extends ImmutableIterator { - - private final Function function; - private final Iterator iterator; - - public MappingIterator(Function function, Iterator iterator) { - this.function = function; - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public B next() { - return function.apply(iterator.next()); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterable.java deleted file mode 100644 index e4b2ca429..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(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 e94b97d5d..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 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/iteration/ZippingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/ZippingIterator.java deleted file mode 100644 index 147079e80..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ZippingIterator.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.jnape.palatable.lambda.iteration; - -import java.util.Iterator; -import java.util.function.BiFunction; - -public class ZippingIterator extends ImmutableIterator { - private final BiFunction zipper; - private final Iterator asIterator; - private final Iterator bsIterator; - - public ZippingIterator(BiFunction zipper, Iterator asIterator, - Iterator bsIterator) { - this.asIterator = asIterator; - this.bsIterator = bsIterator; - this.zipper = zipper; - } - - @Override - public boolean hasNext() { - return asIterator.hasNext() && bsIterator.hasNext(); - } - - @Override - public C next() { - return zipper.apply(asIterator.next(), bsIterator.next()); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Iso.java b/src/main/java/com/jnape/palatable/lambda/lens/Iso.java deleted file mode 100644 index 56caf718e..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/Iso.java +++ /dev/null @@ -1,298 +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); - } - - /** - * 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); - } - - /** - * 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 - */ - interface Simple extends Iso, LensLike.Simple { - - @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)); - } - - /** - * 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)); - } - - /** - * 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 7b8b699f6..000000000 --- a/src/main/java/com/jnape/palatable/lambda/lens/LensLike.java +++ /dev/null @@ -1,121 +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); - - /** - * 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 { - } -} 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 11b18d84c..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,13 +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 com.jnape.palatable.lambda.functor.builtin.Lazy; -import java.util.function.Function; +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: @@ -23,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. @@ -32,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} @@ -44,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 appFn., M>>coerce().flatMap(ab -> fmap(ab::apply)); + default Monad zip(Applicative, M> appFn) { + return flatMap(a -> appFn., M>>coerce().fmap(f -> f.apply(a))); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip( + Lazy, M>> lazyAppFn) { + return Applicative.super.lazyZip(lazyAppFn).fmap(Applicative::coerce); } /** @@ -71,4 +83,17 @@ default Monad discardL(Applicative appB) { default Monad discardR(Applicative appB) { return Applicative.super.discardR(appB).coerce(); } + + /** + * Convenience static method equivalent to {@link Monad#flatMap(Fn1) flatMap}{@link Id#id() (id())}; + * + * @param mma the outer monad + * @param the monad type + * @param the nested type parameter + * @param the nested monad + * @return the nested monad + */ + static , A, MA extends Monad> MA join(Monad mma) { + return mma.flatMap(id()).coerce(); + } } \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/monad/MonadBase.java b/src/main/java/com/jnape/palatable/lambda/monad/MonadBase.java new file mode 100644 index 000000000..36798e8eb --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/MonadBase.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.monad; + +/** + * A type into which a {@link MonadRec} is embedded whilst internally preserving the {@link MonadRec} structure. + * + * @param the {@link MonadRec} embedded in this {@link MonadBase} + * @param the carrier type + * @param the witness + */ +@SuppressWarnings("unused") +public interface MonadBase, A, MB extends MonadBase> { + + /** + * Lift a new argument {@link MonadRec} into this {@link MonadBase}. + * + * @param nc the argument {@link MonadRec} + * @param the {@link MonadRec} carrier type + * @param the argument {@link MonadRec} witness + * @return the new {@link MonadBase} + */ + > MonadBase lift(MonadRec nc); +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/MonadError.java b/src/main/java/com/jnape/palatable/lambda/monad/MonadError.java new file mode 100644 index 000000000..bcecdcad8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/MonadError.java @@ -0,0 +1,96 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Try; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.io.IO; + +/** + * An interface for {@link Monad monads} that can be interrupted with some type of error. The type of error is fully + * dictated by the instance of {@link MonadError} and is not necessarily analogous to Java {@link Exception exceptions} + * or even {@link Throwable}. For instance, {@link IO} can be thrown any {@link Throwable}, where as {@link Either} can + * only be "thrown" a value of its {@link Either#left(Object) left} type. + * + * @param the error type + * @param the {@link Monad} witness + * @param the carrier + * @see IO + * @see Either + * @see Try + * @see Maybe + */ +public interface MonadError> extends Monad { + + /** + * Throw an error value of type E into the {@link Monad monad}. + * + * @param e the error type + * @return the {@link Monad monad} + */ + MonadError throwError(E e); + + /** + * Catch any {@link MonadError#throwError(Object) thrown} errors inside the {@link Monad} and resume normal + * operations. + * + * @param recoveryFn the catch function + * @return the recovered {@link Monad} + */ + MonadError catchError(Fn1> recoveryFn); + + /** + * {@inheritDoc} + */ + @Override + MonadError flatMap(Fn1> f); + + /** + * {@inheritDoc} + */ + @Override + MonadError pure(B b); + + /** + * {@inheritDoc} + */ + @Override + default MonadError fmap(Fn1 fn) { + return Monad.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadError zip(Applicative, M> appFn) { + return Monad.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip( + Lazy, M>> lazyAppFn) { + return Monad.super.lazyZip(lazyAppFn).fmap(Monad::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadError discardL(Applicative appB) { + return Monad.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadError discardR(Applicative appB) { + return Monad.super.discardR(appB).coerce(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/MonadReader.java b/src/main/java/com/jnape/palatable/lambda/monad/MonadReader.java new file mode 100644 index 000000000..17fd1eddd --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/MonadReader.java @@ -0,0 +1,83 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Contravariant; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.functor.builtin.Lazy; + +/** + * A monad that is capable of reading an environment R and producing a lifted value A. This + * is strictly less powerful than an {@link Fn1}, loosening the requirement on {@link Contravariant} (and therefore + * {@link Profunctor} constraints), so is more generally applicable, offering instead a {@link MonadReader#local(Fn1)} + * mechanism for modifying the environment *after* reading it but before running the effect (as opposed to + * {@link Contravariant} functors which may modify their inputs *before* running their effects, and may therefore alter + * the input types). + * + * @param the environment + * @param the output + * @param the witness + */ +public interface MonadReader> extends Monad { + + /** + * Modify this {@link MonadReader MonadReader's} environment after reading it but before running the effect. + * + * @param fn the modification function + * @return the {@link MonadReader} with a modified environment + */ + MonadReader local(Fn1 fn); + + /** + * {@inheritDoc} + */ + @Override + MonadReader flatMap(Fn1> f); + + /** + * {@inheritDoc} + */ + @Override + MonadReader pure(B b); + + /** + * {@inheritDoc} + */ + @Override + default MonadReader fmap(Fn1 fn) { + return Monad.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadReader zip(Applicative, MR> appFn) { + return Monad.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Lazy> lazyZip( + Lazy, MR>> lazyAppFn) { + return Monad.super.lazyZip(lazyAppFn).fmap(Applicative::coerce); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadReader discardL(Applicative appB) { + return Monad.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default MonadReader discardR(Applicative appB) { + return Monad.super.discardR(appB).coerce(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/MonadRec.java b/src/main/java/com/jnape/palatable/lambda/monad/MonadRec.java new file mode 100644 index 000000000..b55fb5aa8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/MonadRec.java @@ -0,0 +1,95 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.transformer.MonadT; +import com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT; + +/** + * A class of {@link Monad monads} that offer a stack-safe interface for performing arbitrarily many + * {@link Monad#flatMap(Fn1) flatmap-like} operations via {@link MonadRec#trampolineM(Fn1)}. + *

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

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

+ * Example: + *

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

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

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

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

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

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


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

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

- * For the {@link Semigroup}, see {@link com.jnape.palatable.lambda.semigroup.builtin.AddAll}. + * Note that the result is a new collection, and the inputs to this monoid are left unmodified. * * @see Monoid */ -public final class AddAll> implements MonoidFactory, C> { +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) { - Semigroup semigroup = com.jnape.palatable.lambda.semigroup.builtin.AddAll.addAll(); - return monoid(semigroup, cSupplier); + public Monoid checkedApply(Fn0 cFn0) { + return new Monoid() { + @Override + public C identity() { + return cFn0.apply(); + } + + @Override + public C checkedApply(C xs, C ys) { + C c = identity(); + c.addAll(xs); + c.addAll(ys); + return c; + } + + @Override + public C foldMap(Fn1 fn, Iterable bs) { + return FoldLeft.foldLeft((x, y) -> { + x.addAll(y); + return x; + }, identity(), map(fn, bs)); + } + }; } @SuppressWarnings("unchecked") public static > AddAll addAll() { - return 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 3b7610510..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,13 +4,17 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.monoid.Monoid; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Not.not; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Find.find; + /** * A {@link Monoid} instance formed by Boolean. Equivalent to logical &&. * * @see Or * @see Monoid */ -public class And implements Monoid, BiPredicate { +public final class And implements Monoid, BiPredicate { private static final And INSTANCE = new And(); @@ -23,13 +27,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); + public Boolean foldMap(Fn1 fn, Iterable bs) { + return find(not(fn), bs).fmap(constantly(false)).orElse(true); } @Override 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 ec38b568b..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,14 @@ 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 static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; + /** * The {@link Monoid} instance formed under concatenation for an arbitrary {@link Iterable}. * @@ -13,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() { } @@ -24,13 +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(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 a933c86a8..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 @@ -5,6 +5,9 @@ import com.jnape.palatable.lambda.monoid.Monoid; import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.CatMaybes.catMaybes; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; /** * A {@link Monoid} instance formed by {@link Maybe}<A>. The application to two {@link Maybe} values @@ -18,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() { } @@ -29,13 +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(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 d5fa005a6..ca2f4634b 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAll.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAll.java @@ -27,22 +27,22 @@ * @see RightAll * @see Either */ -public class LeftAll implements MonoidFactory, Either> { +public final class LeftAll implements MonoidFactory, Either> { - private static final LeftAll INSTANCE = new LeftAll(); + private static final LeftAll INSTANCE = new LeftAll<>(); private LeftAll() { } @Override - public Monoid> apply(Monoid lMonoid) { + public Monoid> checkedApply(Monoid lMonoid) { Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.LeftAll.leftAll(lMonoid); return Monoid.>monoid(semigroup, () -> left(lMonoid.identity())); } @SuppressWarnings("unchecked") public static LeftAll leftAll() { - return INSTANCE; + return (LeftAll) INSTANCE; } public static Monoid> leftAll(Monoid lMonoid) { diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAny.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAny.java index a9035d67c..461738188 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAny.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/LeftAny.java @@ -29,20 +29,20 @@ */ public final class LeftAny implements MonoidFactory, Either> { - private static final LeftAny INSTANCE = new LeftAny(); + private static final LeftAny INSTANCE = new LeftAny<>(); private LeftAny() { } @Override - public Monoid> apply(Monoid lMonoid) { + public Monoid> checkedApply(Monoid lMonoid) { Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.LeftAny.leftAny(lMonoid); return Monoid.>monoid(semigroup, () -> left(lMonoid.identity())); } @SuppressWarnings("unchecked") public static LeftAny leftAny() { - return INSTANCE; + return (LeftAny) INSTANCE; } public static Monoid> leftAny(Monoid lMonoid) { diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Merge.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Merge.java index 798b6041a..83b3afa41 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Merge.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Merge.java @@ -22,20 +22,20 @@ */ public final class Merge implements BiMonoidFactory, Monoid, Either> { - private static final Merge INSTANCE = new Merge(); + private static final Merge INSTANCE = new Merge<>(); private Merge() { } @Override - public Monoid> apply(Semigroup lSemigroup, Monoid rMonoid) { + public Monoid> checkedApply(Semigroup lSemigroup, Monoid rMonoid) { Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.Merge.merge(lSemigroup, rMonoid); return Monoid.>monoid(semigroup, () -> right(rMonoid.identity())); } @SuppressWarnings("unchecked") public static Merge merge() { - return INSTANCE; + return (Merge) INSTANCE; } public static MonoidFactory, Either> merge(Semigroup lSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java new file mode 100644 index 000000000..fc2f28c95 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java @@ -0,0 +1,81 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hmap.HMap; +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.maybe; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.monoid.builtin.Last.last; +import static com.jnape.palatable.lambda.monoid.builtin.Present.present; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.lenses.MapLens.valueAt; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; + +/** + * A {@link Monoid} instance formed by merging {@link HMap HMaps} using the chosen + * {@link TypeSafeKey} -> {@link Semigroup} + * {@link MergeHMaps#key(TypeSafeKey, Semigroup) mappings}, defaulting to {@link Last} in case no + * {@link Semigroup} has been chosen for a given {@link TypeSafeKey}. + */ +public final class MergeHMaps implements Monoid { + + private final Map, Fn2> bindings; + private final Φ> defaultBinding; + + private MergeHMaps(Map, Fn2> bindings, + Φ> defaultBinding) { + this.bindings = bindings; + this.defaultBinding = defaultBinding; + } + + public MergeHMaps key(TypeSafeKey key, Semigroup semigroup) { + return new MergeHMaps(set(valueAt(key), just(merge(key, present(semigroup))), bindings), defaultBinding); + } + + @Override + public HMap identity() { + return HMap.emptyHMap(); + } + + @Override + public HMap checkedApply(HMap x, HMap y) throws Throwable { + return reduceLeft(asList(x, y)); + } + + @Override + public HMap foldMap(Fn1 fn, Iterable bs) { + return FoldLeft.foldLeft((acc, m) -> FoldLeft.foldLeft((result, k) -> maybe(bindings.get(k)) + .orElseGet(() -> defaultBinding.eliminate(k)) + .apply(result, m), acc, m.keys()), identity(), map(fn, bs)); + } + + public static MergeHMaps mergeHMaps() { + return new MergeHMaps(emptyMap(), new Φ>() { + @Override + public Fn2 eliminate(TypeSafeKey key) { + return merge(key, last()); + } + }); + } + + private static Fn2 merge(TypeSafeKey key, Semigroup> semigroup) { + return (x, y) -> semigroup.apply(x.get(key), y.get(key)) + .fmap(a -> x.put(key, a)) + .orElse(x); + } + + @SuppressWarnings({"NonAsciiCharacters"}) + private interface Φ { + R eliminate(TypeSafeKey key); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeMaps.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeMaps.java new file mode 100644 index 000000000..0f5c5fcc9 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeMaps.java @@ -0,0 +1,61 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.BiMonoidFactory; +import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Map; +import java.util.function.BiFunction; + +/** + * A {@link Monoid} instance formed by {@link java.util.Map#merge(Object, Object, BiFunction)} and a semigroup over + * V. Combines together multiple maps using the provided semigroup for key collisions. + * + * @param The key parameter type of the Map + * @param The value parameter type of the Map + * @see Monoid + * @see java.util.Map + */ +public final class MergeMaps implements BiMonoidFactory>, Semigroup, Map> { + + private static final MergeMaps INSTANCE = new MergeMaps<>(); + + private MergeMaps() { + } + + @Override + public Monoid> checkedApply(Fn0> mFn0, Semigroup semigroup) { + return Monoid.>monoid((x, y) -> { + Map copy = mFn0.apply(); + copy.putAll(x); + y.forEach((k, v) -> copy.merge(k, v, semigroup.toBiFunction())); + return copy; + }, mFn0); + } + + @SuppressWarnings("unchecked") + public static MergeMaps mergeMaps() { + return (MergeMaps) INSTANCE; + } + + public static MonoidFactory, Map> mergeMaps(Fn0> mFn0) { + return MergeMaps.mergeMaps().apply(mFn0); + } + + public static Monoid> mergeMaps(Fn0> mFn0, Semigroup semigroup) { + return mergeMaps(mFn0).apply(semigroup); + } + + public static Fn1, Map> mergeMaps(Fn0> mFn0, Semigroup semigroup, + Map x) { + return mergeMaps(mFn0, semigroup).apply(x); + } + + public static Map mergeMaps(Fn0> mFn0, Semigroup semigroup, Map x, + Map y) { + return mergeMaps(mFn0, semigroup, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Or.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Or.java index e4bc56b8c..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,13 +4,16 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.monoid.Monoid; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Find.find; + /** * A {@link Monoid} instance formed by Boolean. Equivalent to logical ||. * * @see And * @see Monoid */ -public class Or implements Monoid, BiPredicate { +public final class Or implements Monoid, BiPredicate { private static final Or INSTANCE = new Or(); @@ -23,13 +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); + 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 aa1433c77..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 @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; import com.jnape.palatable.lambda.monoid.Monoid; import com.jnape.palatable.lambda.semigroup.Semigroup; +import com.jnape.palatable.lambda.semigroup.builtin.Absent; import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.monoid.Monoid.monoid; @@ -23,24 +24,25 @@ * * @param the Maybe value parameter type * @see Monoid + * @see Absent * @see Maybe */ 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 ad812cffd..277ec5163 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RightAny.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RightAny.java @@ -29,23 +29,23 @@ */ public final class RightAny implements MonoidFactory, Either> { - private static final RightAny INSTANCE = new RightAny(); + private static final RightAny INSTANCE = new RightAny<>(); private RightAny() { } @Override - public Monoid> apply(Monoid rMonoid) { + public Monoid> checkedApply(Monoid rMonoid) { Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.RightAny.rightAny(rMonoid); return Monoid.>monoid(semigroup, () -> right(rMonoid.identity())); } @SuppressWarnings("unchecked") public static RightAny rightAny() { - return INSTANCE; + return (RightAny) INSTANCE; } - public static Semigroup> rightAny(Monoid rMonoid) { + public static Monoid> rightAny(Monoid rMonoid) { return RightAny.rightAny().apply(rMonoid); } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RunAll.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RunAll.java new file mode 100644 index 000000000..7533f9c41 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/RunAll.java @@ -0,0 +1,47 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monoid.Monoid.monoid; + +/** + * Run {@link IO} operations, aggregating their results in terms of the provided {@link Monoid}. + * + * @param the {@link IO} result + * @see com.jnape.palatable.lambda.semigroup.builtin.RunAll + */ +public final class RunAll implements MonoidFactory, IO> { + + private static final RunAll INSTANCE = new RunAll<>(); + + private RunAll() { + } + + @Override + public Monoid> checkedApply(Monoid monoid) { + Semigroup> semigroup = com.jnape.palatable.lambda.semigroup.builtin.RunAll.runAll(monoid); + return monoid(semigroup, io(monoid.identity())); + } + + @SuppressWarnings("unchecked") + public static RunAll runAll() { + return (RunAll) INSTANCE; + } + + public static Monoid> runAll(Monoid monoid) { + return RunAll.runAll().apply(monoid); + } + + public static Fn1, IO> runAll(Monoid monoid, IO x) { + return runAll(monoid).apply(x); + } + + public static IO runAll(Monoid monoid, IO x, IO y) { + return runAll(monoid, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Trivial.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Trivial.java new file mode 100644 index 000000000..bf77b65f1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Trivial.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Constantly; +import com.jnape.palatable.lambda.monoid.Monoid; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; + +/** + * The trivial {@link Unit} {@link Monoid} formed under {@link Constantly constantly}. + */ +public final class Trivial implements Monoid { + + private static final Trivial INSTANCE = new Trivial(); + + private Trivial() { + } + + @Override + public Unit identity() { + return UNIT; + } + + @Override + public Unit checkedApply(Unit x, Unit y) throws Throwable { + return y; + } + + public static Trivial trivial() { + return INSTANCE; + } + + public static Fn1 trivial(Unit x) { + return trivial().apply(x); + } + + public static Unit trivial(Unit x, Unit y) { + return trivial(x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Union.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Union.java new file mode 100644 index 000000000..283bd2980 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Union.java @@ -0,0 +1,45 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Distinct; +import com.jnape.palatable.lambda.internal.iteration.UnioningIterable; +import com.jnape.palatable.lambda.monoid.Monoid; + +import java.util.Collections; + +/** + * Given two {@link Iterable Iterables} xs and ys, return the {@link Concat concatenation} of + * the {@link Distinct distinct} elements of both xs and ys. + * + * @param the {@link Iterable} element type + */ +public final class Union implements Monoid> { + + private static final Union INSTANCE = new Union<>(); + + private Union() { + } + + @Override + public Iterable identity() { + return Collections::emptyIterator; + } + + @Override + public Iterable checkedApply(Iterable xs, Iterable ys) { + return new UnioningIterable<>(xs, ys); + } + + @SuppressWarnings("unchecked") + public static Union union() { + return (Union) INSTANCE; + } + + public static Fn1, Iterable> union(Iterable xs) { + return Union.union().apply(xs); + } + + public static Iterable union(Iterable xs, Iterable ys) { + return union(xs).apply(ys); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java index a2fe7abaf..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 @@ -13,7 +13,7 @@ * @see Or * @see And */ -public class Xor implements Monoid, BiPredicate { +public final class Xor implements Monoid, BiPredicate { private static final Xor INSTANCE = new Xor(); @@ -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 50% 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 9b0f54774..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,19 +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.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. @@ -134,103 +141,164 @@ * @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 diMapL(Function fn) { - return LensLike.super.diMapL(fn).coerce(); + 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 diMapR(Function fn) { - return LensLike.super.diMapR(fn).coerce(); + default Lens diMapL(Fn1 fn) { + return (Lens) Profunctor.super.diMapL(fn); } + /** + * {@inheritDoc} + */ @Override - default Lens diMap(Function lFn, - Function rFn) { - return LensLike.super.diMap(lFn, rFn).coerce(); + default Lens diMapR(Fn1 fn) { + return (Lens) Profunctor.super.diMapR(fn); } + /** + * {@inheritDoc} + */ @Override - default Lens contraMap(Function fn) { - return LensLike.super.contraMap(fn).coerce(); + default Lens diMap(Fn1 lFn, Fn1 rFn) { + return this.mapS(lFn).mapT(rFn); } + /** + * {@inheritDoc} + */ @Override - default Lens mapS(Function fn) { - return lens(view(this).compose(fn), (r, b) -> set(this, b, fn.apply(r))); + default Lens contraMap(Fn1 fn) { + return (Lens) Profunctor.super.contraMap(fn); } + /** + * {@inheritDoc} + */ @Override - default Lens mapT(Function fn) { - return fmap(fn); + default Lens mapS(Fn1 fn) { + return lens(Optic.super.mapS(fn)); } + /** + * {@inheritDoc} + */ @Override - default Lens mapA(Function fn) { - return andThen(lens(fn, (a, b) -> b)); + default Lens mapT(Fn1 fn) { + return lens(Optic.super.mapT(fn)); } + /** + * {@inheritDoc} + */ @Override - default Lens mapB(Function fn) { - return lens(view(this), (s, z) -> set(this, fn.apply(z), s)); + default Lens mapA(Fn1 fn) { + return lens(Optic.super.mapA(fn)); } /** - * 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 + * {@inheritDoc} */ - default Lens andThen(Lens f) { - return f.compose(this); + @Override + default Lens mapB(Fn1 fn) { + return lens(Optic.super.mapB(fn)); } /** - * Right-to-left composition of lenses. Requires compatibility between A and B. + * Produce an {@link Iso} from this {@link Lens} by providing a default S value. * - * @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 + * @param s the default S + * @return an {@link Iso} + */ + default Iso toIso(S s) { + return iso(view(this), set(this).flip().apply(s)); + } + + /** + * {@inheritDoc} */ - default Lens compose(Lens g) { - return lens(view(g).fmap(view(this)), (q, b) -> over(g, set(this, b), q)); + @Override + default Lens andThen(Optic, ? super Functor, A, B, C, D> f) { + return lens(Optic.super.andThen(f)); + } + + /** + * {@inheritDoc} + */ + @Override + default Lens compose(Optic, ? super Functor, R, U, S, T> g) { + return lens(Optic.super.compose(g)); } /** @@ -244,15 +312,39 @@ default Lens compose(Lens 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); } }; } @@ -266,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)); } @@ -285,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)); } /** @@ -302,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. @@ -310,28 +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> { /** - * 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 + * {@inheritDoc} */ - default Lens.Simple compose(Lens.Simple g) { - return Lens.Simple.adapt(Lens.super.compose(g)); + @Override + default Lens.Simple andThen(Optic.Simple, ? super Functor, A, B> f) { + return Lens.Simple.adapt(Lens.super.andThen(f)); } /** - * 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 + * {@inheritDoc} */ - default Lens.Simple andThen(Lens.Simple f) { - return f.compose(this); + @Override + default Lens.Simple compose(Optic.Simple, ? super Functor, R, S> g) { + return Lens.Simple.adapt(Lens.super.compose(g)); } /** @@ -342,9 +446,17 @@ default Lens.Simple andThen(Lens.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 57% 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 70af23f35..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,27 +27,10 @@ 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); } - /** - * Convenience static factory method for creating a lens that focuses on an arbitrary {@link Collection} as a - * {@link Set}. - * - * @param the collection element type - * @param the type of the collection - * @return a lens that focuses on a Collection as a Set - * @deprecated in favor of lawful {@link CollectionLens#asSet(Function)} - */ - @Deprecated - public static > Lens.Simple> asSet() { - return simpleLens(HashSet::new, (xsL, xsS) -> { - xsL.retainAll(xsS); - return xsL; - }); - } - /** * Convenience static factory method for creating a lens that focuses on an arbitrary {@link Collection} as a * {@link Set}. @@ -57,8 +40,7 @@ public static > Lens.Simple> asSet() { * @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); @@ -69,23 +51,6 @@ public static > Lens.Simple> asSet( }); } - /** - * Convenience static factory method for creating a lens that focuses on a Collection as a Stream. - * - * @param the collection element type - * @param the type of the collection - * @return a lens that focuses on a Collection as a stream. - * @deprecated in favor of lawful {@link CollectionLens#asStream(Function)} - */ - @Deprecated - public static > Lens.Simple> asStream() { - return simpleLens(Collection::stream, (xsL, xsS) -> { - xsL.clear(); - xsS.forEach(xsL::add); - return xsL; - }); - } - /** * Convenience static factory method for creating a lens that focuses on a Collection as a Stream. *

@@ -97,9 +62,10 @@ public static > Lens.Simple> asStream( * @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 54% 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 0c765505d..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,14 +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.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.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.optics.Iso.simpleIso; +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; +import static com.jnape.palatable.lambda.optics.functions.View.view; /** * Lenses that operate on {@link Iterable}s. @@ -41,6 +45,18 @@ 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())))); + } + + /** + * An iso focusing on the mapped values of an {@link Iterable}. + * + * @param abIso the iso from A to B + * @param the unmapped {@link Iterable} element type + * @param the mapped {@link Iterable} element type + * @return an iso that maps {@link Iterable}<A> to {@link Iterable}<B> + */ + public static Iso.Simple, Iterable> mapping(Iso abIso) { + return simpleIso(map(view(abIso)), map(view(abIso.mirror()))); } } diff --git a/src/main/java/com/jnape/palatable/lambda/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 61% 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 cb393ac1f..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,10 +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.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; @@ -12,18 +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.builtin.fn2.Eq.eq; +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.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. @@ -33,6 +38,21 @@ public final class MapLens { private MapLens() { } + /** + * A lens that focuses on a copy of a {@link Map} as a subtype M. Useful for composition to avoid + * mutating a map reference. + * + * @param the map subtype + * @param the key type + * @param the value type + * @param copyFn the copy function + * @return a lens that focuses on copies of maps as a specific subtype + */ + public static , K, V> Lens, M, M, M> asCopy( + Fn1, ? extends M> copyFn) { + return lens(copyFn, (__, copy) -> copy); + } + /** * A lens that focuses on a copy of a Map. Useful for composition to avoid mutating a map reference. * @@ -41,7 +61,27 @@ private MapLens() { * @return a lens that focuses on copies of maps */ public static Lens.Simple, Map> asCopy() { - return simpleLens(HashMap::new, (__, copy) -> copy); + return adapt(asCopy(HashMap::new)); + } + + /** + * A lens that focuses on a value at a key in a map, as a {@link Maybe}, and produces a subtype M on + * the way back out. + * + * @param copyFn the copy function + * @param k the key to focus on + * @param the map subtype + * @param the key type + * @param the value type + * @return a lens that focuses on the value at key, as a {@link Maybe} + */ + public static , K, V> Lens, M, Maybe, Maybe> valueAt( + Fn1, ? extends M> copyFn, K k) { + return lens(m -> maybe(m.get(k)), (m, maybeV) -> maybeV + .>>fmap(v -> alter(effect(updated -> io(() -> updated.put(k, v))))) + .orElse(alter(updated -> io(sideEffect(() -> updated.remove(k))))) + .apply(copyFn.apply(m)) + .unsafePerformIO()); } /** @@ -53,16 +93,7 @@ public static Lens.Simple, Map> asCopy() { * @return a lens that focuses on the value at key, as a {@link Maybe} */ public static Lens.Simple, Maybe> valueAt(K k) { - return simpleLens(m -> maybe(m.get(k)), (m, maybeV) -> { - Map updated = new HashMap<>(m); - return maybeV.fmap(v -> { - updated.put(k, v); - return updated; - }).orElseGet(() -> { - updated.remove(k); - return updated; - }); - }); + return adapt(valueAt(HashMap::new, k)); } /** @@ -77,7 +108,6 @@ public static Lens.Simple, Maybe> valueAt(K k) { * @param the value type * @return a lens that focuses on the value at the key */ - @SuppressWarnings("unchecked") public static Lens.Simple, V> valueAt(K k, V defaultValue) { return adapt(unLiftB(unLiftA(valueAt(k), defaultValue))); } @@ -91,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)); @@ -114,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; @@ -128,6 +158,9 @@ public static Lens.Simple, Collection> values() { /** * A lens that focuses on the inverse of a map (keys and values swapped). In the case of multiple equal values * becoming keys, the last one wins. + *

+ * Note that this lens is very likely to NOT be lawful, since "you get back what you put in" will fail for any keys + * that map to the same value. * * @param the key type * @param the value type @@ -146,41 +179,15 @@ public static Lens.Simple, Map> inverted() { }); } - /** - * A lens that focuses on a map while mapping its values with the mapping function. - *

- * Note that this lens is NOT lawful, since "you get back what you put in" fails for all values B that - * do not map from the current values in S (new mappings cannot be preserved as the inversion of - * fn is not known). - * - * @param fn the mapping function - * @param the key type - * @param the unfocused map value type - * @param the focused map value types - * @return a lens that focuses on a map while mapping its values - * @deprecated in favor of the lawful (and far more rational) {@link MapLens#mappingValues(Iso)} - */ - @Deprecated - public static Lens.Simple, Map> mappingValues(Function fn) { - return simpleLens(m -> toMap(HashMap::new, map(t -> t.biMapR(fn), map(Tuple2::fromEntry, m.entrySet()))), - (s, b) -> { - Set retainKeys = Filter.>filter(kv -> eq(fn.apply(kv.getValue()), b.get(kv.getKey()))) - .andThen(map(Map.Entry::getKey)) - .andThen(toCollection(HashSet::new)) - .apply(s.entrySet()); - Map copy = new HashMap<>(s); - copy.keySet().retainAll(retainKeys); - return copy; - }); - } - /** * A lens that focuses on a map while mapping its values with the mapping {@link Iso}. + *

+ * Note that for this lens to be lawful, iso must be lawful. * * @param iso the mapping {@link Iso} * @param the key type * @param the unfocused map value type - * @param the focused map value types + * @param the focused map value type * @return a lens that focuses on a map while mapping its values */ public static Lens.Simple, Map> mappingValues(Iso iso) { 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/optics/lenses/SetLens.java b/src/main/java/com/jnape/palatable/lambda/optics/lenses/SetLens.java new file mode 100644 index 000000000..75c7ab756 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/lenses/SetLens.java @@ -0,0 +1,51 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.optics.Lens; + +import java.util.HashSet; +import java.util.Set; + +import static com.jnape.palatable.lambda.optics.Lens.simpleLens; + +/** + * Lenses that operate on {@link Set}s. + */ +public final class SetLens { + + private SetLens() { + } + + /** + * A lens that focuses on whether a {@link Set} contains some value a. Note that copyFn is + * used to avoid mutating the {@link Set} in question. + * + * @param copyFn the copy function + * @param a the value in question + * @param the value type + * @param the set to focus on + * @return a lens that focuses on a value's inclusion in a given {@link Set} + */ + public static > Lens.Simple contains(Fn1 copyFn, + A a) { + return simpleLens(setA -> setA.contains(a), + (setA, include) -> { + SetA copy = copyFn.apply(setA); + if (include) copy.add(a); + else copy.remove(a); + return copy; + }); + } + + /** + * A lens that focuses on whether a {@link Set} contains some value a. Like + * {@link #contains(Fn1, Object)} but with an implicit copy function that produces {@link HashSet}s. + * + * @param a the value in question + * @param the value type + * @return a lens that focuses on a value's inclusion in a given {@link Set} + */ + public static Lens.Simple, Boolean> contains(A a) { + return contains(HashSet::new, a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/prisms/EitherPrism.java b/src/main/java/com/jnape/palatable/lambda/optics/prisms/EitherPrism.java new file mode 100644 index 000000000..462f30b0a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/prisms/EitherPrism.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.optics.Prism; + +import static com.jnape.palatable.lambda.optics.Prism.simplePrism; + +/** + * {@link Prism Prisms} for {@link Either}. + */ +public final class EitherPrism { + + private EitherPrism() { + } + + /** + * A {@link Prism} that focuses on the {@link Either#left(Object) left} value of an {@link Either}. + * + * @param the left type + * @param the right type + * @return the {@link Prism} + */ + public static Prism.Simple, L> _left() { + return simplePrism(CoProduct2::projectA, Either::left); + } + + /** + * A {@link Prism} that focuses on the {@link Either#right(Object) right} value of an {@link Either}. + * + * @param the left type + * @param the right type + * @return the {@link Prism} + */ + public static Prism.Simple, R> _right() { + return simplePrism(CoProduct2::projectB, Either::right); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/prisms/MapPrism.java b/src/main/java/com/jnape/palatable/lambda/optics/prisms/MapPrism.java new file mode 100644 index 000000000..2d1ee7930 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/prisms/MapPrism.java @@ -0,0 +1,49 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.optics.Prism; + +import java.util.HashMap; +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.Maybe.maybe; +import static com.jnape.palatable.lambda.optics.Prism.Simple.adapt; +import static com.jnape.palatable.lambda.optics.Prism.prism; +import static java.util.Collections.singletonMap; + +/** + * {@link Prism Prisms} for {@link Map Maps}. + */ +public final class MapPrism { + private MapPrism() { + } + + /** + * A {@link Prism} that focuses on the value at a key in a {@link Map}, and produces an instance of M + * on the way back out. + * + * @param copyFn the copy function + * @param k the key to focus on + * @param the {@link Map} subtype + * @param the key type + * @param the value type + * @return the {@link Prism} + */ + public static , K, V> Prism, M, V, V> valueAt(Fn1, M> copyFn, K k) { + return prism(m -> maybe(m.get(k)).toEither(copyFn.thunk(m)), + v -> copyFn.apply(singletonMap(k, v))); + } + + /** + * A {@link Prism} that focuses on the value at a key in a {@link Map} making no guarantees about the {@link Map} + * interface. + * + * @param k the key to focus on + * @param the key type + * @param the value type + * @return the {@link Prism} + */ + public static Prism.Simple, V> valueAt(K k) { + return adapt(valueAt(HashMap::new, k)); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/prisms/MaybePrism.java b/src/main/java/com/jnape/palatable/lambda/optics/prisms/MaybePrism.java new file mode 100644 index 000000000..eaf5ed0f1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/prisms/MaybePrism.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.optics.Prism; + +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.optics.Prism.prism; +import static com.jnape.palatable.lambda.optics.Prism.simplePrism; + +/** + * {@link Prism Prisms} for {@link Maybe}. + */ +public final class MaybePrism { + + private MaybePrism() { + } + + /** + * A {@link Prism} that focuses on present values in a {@link Maybe}. + * + * @param {@link Maybe} the input value + * @param {@link Maybe} the output value + * @return the {@link Prism} + */ + public static Prism, Maybe, A, B> _just() { + return prism(maybeA -> maybeA.toEither(Maybe::nothing), Maybe::just); + } + + /** + * A {@link Prism} that focuses on absent values in a {@link Maybe}, for symmetry. + * + * @param {@link Maybe} the input and output value + * @return the {@link Prism} + */ + public static Prism.Simple, Unit> _nothing() { + return simplePrism(CoProduct2::projectA, constantly(nothing())); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrism.java b/src/main/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrism.java new file mode 100644 index 000000000..1a030b654 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrism.java @@ -0,0 +1,25 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import com.jnape.palatable.lambda.optics.Prism; + +import java.util.UUID; + +import static com.jnape.palatable.lambda.optics.Prism.Simple.fromPartial; + +/** + * {@link Prism Prisms} for {@link UUID}. + */ +public final class UUIDPrism { + + private UUIDPrism() { + } + + /** + * A {@link Prism} that focuses on a {@link String} as a {@link UUID}. + * + * @return the {@link Prism} + */ + public static Prism.Simple uuid() { + return fromPartial(UUID::fromString, UUID::toString); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java b/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java index 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 new file mode 100644 index 000000000..222a5e7dc --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java @@ -0,0 +1,94 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2.liftA2; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + +/** + * A {@link Semigroup} instance formed by {@link Maybe}<A> and a semigroup over A. The + * application to two {@link Maybe} values is absence-biased, such that for a given {@link Maybe} x + * and y: + *

+ * + * @param the Maybe value parameter type + * @see Semigroup + * @see Present + * @see Maybe + */ +public final class Absent implements SemigroupFactory, Maybe> { + + private static final Absent INSTANCE = new Absent<>(); + + private Absent() { + } + + @Override + public Semigroup> checkedApply(Semigroup aSemigroup) { + return shortCircuitSemigroup(aSemigroup); + } + + @SuppressWarnings("unchecked") + public static Absent absent() { + return (Absent) INSTANCE; + } + + public static Semigroup> absent(Semigroup aSemigroup) { + return shortCircuitSemigroup(aSemigroup); + } + + public static Fn1, Maybe> absent(Semigroup aSemigroup, Maybe x) { + return absent(aSemigroup).apply(x); + } + + public static Maybe absent(Semigroup semigroup, Maybe x, Maybe y) { + return absent(semigroup, x).apply(y); + } + + private static Semigroup> shortCircuitSemigroup(Semigroup aSemigroup) { + return new Semigroup>() { + @Override + public Maybe checkedApply(Maybe maybeX, Maybe maybeY) { + return liftA2(aSemigroup, maybeX, maybeY); + } + + @Override + public Maybe foldLeft(Maybe acc, Iterable> maybes) { + return trampoline( + into((res, it) -> res.equals(nothing()) || !it.hasNext() + ? terminate(res) + : recurse(tuple(liftA2(aSemigroup, res, it.next()), it))), + tuple(acc, maybes.iterator())); + } + + @Override + public Lazy> foldRight(Maybe accumulation, Iterable> as) { + boolean shouldShortCircuit = accumulation == nothing(); + if (shouldShortCircuit) + return lazy(accumulation); + return FoldRight.foldRight( + (maybeX, acc) -> maybeX.lazyZip(acc.fmap(maybeY -> maybeY.fmap(aSemigroup.flip()))), + lazy(accumulation), + as + ); + } + }; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/AddAll.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/AddAll.java deleted file mode 100644 index 557f9454b..000000000 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/AddAll.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.jnape.palatable.lambda.semigroup.builtin; - -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.monoid.Monoid; -import com.jnape.palatable.lambda.semigroup.Semigroup; - -import java.util.Collection; - -/** - * The {@link Semigroup} instance formed under mutative concatenation for an arbitrary {@link Collection}. The - * collection subtype (C) must support {@link Collection#addAll(Collection)}. - *

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

+ * + * @param the value type + * @see MaxBy + * @see Min + */ +public final class Max> implements Semigroup { + + private static final Max INSTANCE = new Max<>(); + + private Max() { + } + + @Override + public A checkedApply(A x, A y) { + return maxBy(id(), x, y); + } + + @SuppressWarnings("unchecked") + public static > Max max() { + return (Max) INSTANCE; + } + + public static > Fn1 max(A x) { + return Max.max().apply(x); + } + + public static > A max(A x, A y) { + return max(x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java new file mode 100644 index 000000000..2abee980e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTBy.ltBy; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B, produce a + * {@link Semigroup} over A that chooses between two values x and y via the + * following rules: + *
    + *
  • If x is strictly less than y in terms of B, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @param the mapped comparison type + * @see Max + * @see MaxWith + * @see MinBy + */ +public final class MaxBy> implements SemigroupFactory, A> { + + private static final MaxBy INSTANCE = new MaxBy<>(); + + private MaxBy() { + } + + @Override + public Semigroup checkedApply(Fn1 compareFn) { + return (x, y) -> ltBy(compareFn, y, x) ? y : x; + } + + @SuppressWarnings("unchecked") + public static > MaxBy maxBy() { + return (MaxBy) INSTANCE; + } + + public static > Semigroup maxBy(Fn1 compareFn) { + return MaxBy.maxBy().apply(compareFn); + } + + public static > Fn1 maxBy(Fn1 compareFn, A x) { + return MaxBy.maxBy(compareFn).apply(x); + } + + public static > A maxBy(Fn1 compareFn, A x, A y) { + return maxBy(compareFn, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java new file mode 100644 index 000000000..ed3f07373 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; + +/** + * Given a comparator for some type A, produce a {@link Semigroup} over A that chooses + * between two values x and y via the following rules: + *
    + *
  • If x is strictly less than y in terms of B, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @see Max + * @see MaxBy + * @see MinWith + */ +public final class MaxWith implements SemigroupFactory, A> { + + private static final MaxWith INSTANCE = new MaxWith<>(); + + private MaxWith() { + } + + @SuppressWarnings("unchecked") + public static MaxWith maxWith() { + return (MaxWith) INSTANCE; + } + + public static Semigroup maxWith(Comparator compareFn) { + return MaxWith.maxWith().apply(compareFn); + } + + public static Fn1 maxWith(Comparator compareFn, A x) { + return MaxWith.maxWith(compareFn).apply(x); + } + + public static A maxWith(Comparator compareFn, A x, A y) { + return maxWith(compareFn, x).apply(y); + } + + @Override + public Semigroup checkedApply(Comparator comparator) { + return (x, y) -> ltWith(comparator, y, x) ? y : x; + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Merge.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Merge.java index 75bd81ffd..833e1aa57 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Merge.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Merge.java @@ -20,19 +20,19 @@ */ public final class Merge implements BiSemigroupFactory, Semigroup, Either> { - private static final Merge INSTANCE = new Merge(); + private static final Merge INSTANCE = new Merge<>(); private Merge() { } @Override - public Semigroup> apply(Semigroup lSemigroup, Semigroup rSemigroup) { + public Semigroup> checkedApply(Semigroup lSemigroup, Semigroup rSemigroup) { return (x, y) -> x.merge(lSemigroup::apply, rSemigroup::apply, y); } @SuppressWarnings("unchecked") public static Merge merge() { - return INSTANCE; + return (Merge) INSTANCE; } public static SemigroupFactory, Either> merge(Semigroup lSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Min.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Min.java new file mode 100644 index 000000000..747bd5d25 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Min.java @@ -0,0 +1,45 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.semigroup.builtin.MinBy.minBy; + +/** + * A {@link Semigroup} over A that chooses between two values x and y via the + * following rules: + *
    + *
  • If x is strictly greater than y, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @see MinBy + * @see Max + */ +public final class Min> implements Semigroup { + + private static final Min INSTANCE = new Min<>(); + + private Min() { + } + + @Override + public A checkedApply(A x, A y) { + return minBy(id(), x, y); + } + + @SuppressWarnings("unchecked") + public static > Min min() { + return (Min) INSTANCE; + } + + public static > Fn1 min(A x) { + return Min.min().apply(x); + } + + public static > A min(A x, A y) { + return min(x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinBy.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinBy.java new file mode 100644 index 000000000..07ee12999 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinBy.java @@ -0,0 +1,51 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTBy.gtBy; + +/** + * Given a mapping function from some type A to some {@link Comparable} type B, produce a + * {@link Semigroup} over A that chooses between two values x and y via the + * following rules: + *
    + *
  • If x is strictly greater than y in terms of B, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @param the mapped comparison type + * @see Min + * @see MaxBy + */ +public final class MinBy> implements SemigroupFactory, A> { + + private static final MinBy INSTANCE = new MinBy<>(); + + private MinBy() { + } + + @Override + public Semigroup checkedApply(Fn1 compareFn) { + return (x, y) -> gtBy(compareFn, y, x) ? y : x; + } + + @SuppressWarnings("unchecked") + public static > MinBy minBy() { + return (MinBy) INSTANCE; + } + + public static > Semigroup minBy(Fn1 compareFn) { + return MinBy.minBy().apply(compareFn); + } + + public static > Fn1 minBy(Fn1 compareFn, A x) { + return MinBy.minBy(compareFn).apply(x); + } + + public static > A minBy(Fn1 compareFn, A x, A y) { + return minBy(compareFn, x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java new file mode 100644 index 000000000..05eec2528 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; + +/** + * Given a comparator for some type A, produce a {@link Semigroup} over A that chooses + * between two values x and y via the following rules: + *
    + *
  • If x is strictly greater than y in terms of B, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @see Min + * @see MinBy + * @see MaxBy + */ +public final class MinWith implements SemigroupFactory, A> { + + private static final MinWith INSTANCE = new MinWith<>(); + + private MinWith() { + } + + @SuppressWarnings("unchecked") + public static MinWith minWith() { + return (MinWith) INSTANCE; + } + + public static Semigroup minWith(Comparator compareFn) { + return MinWith.minWith().apply(compareFn); + } + + public static Fn1 minWith(Comparator compareFn, A x) { + return MinWith.minWith(compareFn).apply(x); + } + + public static A minWith(Comparator compareFn, A x, A y) { + return minWith(compareFn, x).apply(y); + } + + @Override + public Semigroup checkedApply(Comparator comparator) { + return (x, y) -> gtWith(comparator, y, x) ? y : x; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAll.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAll.java index 7f1bdab5b..5bb6fa018 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAll.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAll.java @@ -29,19 +29,19 @@ */ public final class RightAll implements SemigroupFactory, Either> { - private static final RightAll INSTANCE = new RightAll(); + private static final RightAll INSTANCE = new RightAll<>(); private RightAll() { } @Override - public Semigroup> apply(Semigroup rSemigroup) { + public Semigroup> checkedApply(Semigroup rSemigroup) { return (eitherX, eitherY) -> eitherX.flatMap(xR -> eitherY.flatMap(yR -> right(rSemigroup.apply(xR, yR)))); } @SuppressWarnings("unchecked") public static RightAll rightAll() { - return INSTANCE; + return (RightAll) INSTANCE; } public static Semigroup> rightAll(Semigroup rSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAny.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAny.java index 04531cbfb..8dcc439a3 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAny.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RightAny.java @@ -30,21 +30,21 @@ */ public final class RightAny implements SemigroupFactory, Either> { - private static final RightAny INSTANCE = new RightAny(); + private static final RightAny INSTANCE = new RightAny<>(); private RightAny() { } @Override - public Semigroup> apply(Semigroup rSemigroup) { - return (x, y) -> x.flatMap(constantly(y), - xR -> y.flatMap(constantly(right(xR)), - rSemigroup.apply(xR).andThen(Either::right))); + public Semigroup> checkedApply(Semigroup rSemigroup) { + return (x, y) -> x.match(constantly(y), + xR -> y.match(constantly(right(xR)), + rSemigroup.apply(xR).fmap(Either::right))); } @SuppressWarnings("unchecked") public static RightAny rightAny() { - return INSTANCE; + return (RightAny) INSTANCE; } public static Semigroup> rightAny(Semigroup rSemigroup) { diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RunAll.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RunAll.java new file mode 100644 index 000000000..f84d0243a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/RunAll.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +/** + * Run {@link IO} operations, aggregating their results in terms of the provided {@link Semigroup}. + * + * @param the {@link IO} result + * @see com.jnape.palatable.lambda.monoid.builtin.RunAll + */ +public final class RunAll implements SemigroupFactory, IO> { + + private static final RunAll INSTANCE = new RunAll<>(); + + private RunAll() { + } + + @Override + public Semigroup> checkedApply(Semigroup semigroup) { + return (ioX, ioY) -> ioY.zip(ioX.fmap(semigroup)); + } + + @SuppressWarnings("unchecked") + public static RunAll runAll() { + return (RunAll) INSTANCE; + } + + public static Semigroup> runAll(Semigroup semigroup) { + return RunAll.runAll().apply(semigroup); + } + + public static Fn1, IO> runAll(Semigroup semigroup, IO ioX) { + return runAll(semigroup).apply(ioX); + } + + public static IO runAll(Semigroup semigroup, IO ioX, IO ioY) { + return runAll(semigroup, ioX).apply(ioY); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java index 682fd81bf..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,29 +1,36 @@ 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.CartesianProduct.cartesianProduct; import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; -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.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; /** - * Wrap an {@link Iterable} in a {@link Traversable} such that {@link Traversable#traverse(Function, Function)} applies - * its computation against each element of the wrapped {@link Iterable}. Returns the result of pure if the - * wrapped {@link Iterable} is empty. + * Extension point for {@link Iterable} to adapt lambda core types like {@link Monad} and {@link Traversable}. * - * @param the Iterable element type + * @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") @@ -34,17 +41,23 @@ private LambdaIterable(Iterable as) { /** * Unwrap the underlying {@link Iterable}. * - * @return the wrapped Iterable + * @return the wrapped {@link Iterable} */ 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)); @@ -61,42 +74,80 @@ public LambdaIterable pure(B b) { * @return the zipped LambdaIterable */ @Override - @SuppressWarnings("Convert2MethodRef") - public LambdaIterable zip(Applicative, LambdaIterable> appFn) { - return wrap(map(into((f, x) -> f.apply(x)), - cartesianProduct(appFn.>>coerce().unwrap(), as))); + 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())) @@ -113,23 +164,37 @@ public int hashCode() { } /** - * Wrap an {@link Iterable} in a TraversableIterable. + * Wrap an {@link Iterable} in a {@link LambdaIterable}. * * @param as the Iterable * @param the Iterable element type - * @return the Iterable wrapped in a TraversableIterable + * @return the Iterable wrapped in a {@link LambdaIterable} */ public static LambdaIterable wrap(Iterable as) { return new LambdaIterable<>(as); } /** - * Construct an empty TraversableIterable by wrapping {@link java.util.Collections#emptyList()}. + * Construct an empty {@link LambdaIterable} by wrapping {@link java.util.Collections#emptyList()}. * * @param the Iterable element type - * @return a TraversableIterable wrapping Collections.emptyList() + * @return an empty {@link LambdaIterable} */ public static LambdaIterable empty() { return wrap(emptyList()); } + + /** + * The canonical {@link Pure} instance for {@link LambdaIterable}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureLambdaIterable() { + return new Pure>() { + @Override + public LambdaIterable checkedApply(A a) throws Throwable { + return wrap(singleton(a)); + } + }; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaMap.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaMap.java new file mode 100644 index 000000000..f8e8c7fd2 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaMap.java @@ -0,0 +1,97 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn2.curried; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToMap.toMap; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +import static java.util.Collections.emptyMap; + +/** + * Extension point for {@link Map} to adapt lambda core types like {@link Functor} and {@link Traversable}. + * + * @param the {@link Map} element type + * @see LambdaIterable + */ +public final class LambdaMap implements Functor>, Traversable> { + private final Map map; + + private LambdaMap(Map map) { + this.map = map; + } + + /** + * Unwrap the underlying {@link Map}. + * + * @return the wrapped {@link Map} + */ + public Map unwrap() { + return map; + } + + @Override + public LambdaMap fmap(Fn1 fn) { + return wrap(toMap(HashMap::new, map(entry -> tuple(entry.getKey(), fn.apply(entry.getValue())), map.entrySet()))); + } + + @Override + @SuppressWarnings("unchecked") + public , TravC extends Traversable>, + AppTrav extends Applicative> AppTrav traverse(Fn1> fn, + Fn1 pure) { + return foldLeft(curried(appTrav -> into((k, appV) -> (AppTrav) appTrav.zip(appV.fmap(v -> m -> { + ((LambdaMap) m).unwrap().put(k, v); + return m; + })))), + pure.apply((TravC) LambdaMap.wrap(new HashMap<>())), + this.fmap(fn).unwrap().entrySet()); + } + + @Override + public boolean equals(Object other) { + return other instanceof LambdaMap && Objects.equals(map, ((LambdaMap) other).map); + } + + @Override + public int hashCode() { + return Objects.hash(map); + } + + @Override + public String toString() { + return "LambdaMap{map=" + map + '}'; + } + + /** + * Wrap a {@link Map} in a {@link LambdaMap}. + * + * @param map the {@link Map} + * @param the key type + * @param the value type + * @return the {@link Map} wrapped in a {@link LambdaMap} + */ + public static LambdaMap wrap(Map map) { + return new LambdaMap<>(map); + } + + /** + * Construct an empty {@link LambdaMap} by wrapping {@link Collections#emptyMap()} + * + * @param the key type + * @param the value type + * @return an empty {@link LambdaMap} + */ + public static LambdaMap empty() { + return wrap(emptyMap()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java 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 a06282ba6..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,32 +116,23 @@ 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"))); assertThat(right.flatMap(r -> right(r + 1)), is(right(2))); } - @Test - public void dyadicFlatMapDuallyLiftsAndFlattensBackToEither() { - Either left = left("foo"); - Either right = right(1); - - assertThat(left.flatMap(l -> left(l + "bar"), r -> right(r + 1)), is(left("foobar"))); - assertThat(right.flatMap(l -> left(l + "bar"), r -> right(r + 1)), is(right(2))); - } - @Test public void 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)); @@ -138,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")); @@ -153,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))); @@ -162,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)); @@ -203,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 9205db543..6668dbc19 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java @@ -1,5 +1,7 @@ package com.jnape.palatable.lambda.adt; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.adt.choice.Choice3; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; @@ -8,28 +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); @@ -89,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); @@ -108,4 +111,36 @@ public void justOrThrow() { public void nothingOrThrow() { nothing().orElseThrow(IllegalStateException::new); } + + @Test + public void divergesIntoChoice3() { + assertEquals(Choice3.a(UNIT), nothing().diverge()); + assertEquals(Choice3.b(1), just(1).diverge()); + } + + @Test + public void projectsIntoTuple2() { + assertEquals(tuple(just(UNIT), nothing()), nothing().project()); + assertEquals(tuple(nothing(), just(1)), just(1).project()); + } + + @Test + public void invertsIntoChoice2() { + assertEquals(Choice2.b(UNIT), nothing().invert()); + assertEquals(Choice2.a(1), just(1).invert()); + } + + @Test + public void lazyZip() { + assertEquals(just(2), just(1).lazyZip(lazy(() -> just(x -> x + 1))).value()); + assertEquals(nothing(), nothing().lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + Maybe maybe = pureMaybe().apply(1); + assertEquals(just(1), maybe); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java 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 362671cf6..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,8 +10,13 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static com.jnape.palatable.lambda.adt.Either.left; @@ -19,29 +24,46 @@ import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.Try.failure; +import static com.jnape.palatable.lambda.adt.Try.pureTry; import static com.jnape.palatable.lambda.adt.Try.success; import static com.jnape.palatable.lambda.adt.Try.trying; +import static com.jnape.palatable.lambda.adt.Try.withResources; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static testsupport.matchers.LeftMatcher.isLeftThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static testsupport.assertion.MonadErrorAssert.assertLaws; +import static testsupport.matchers.EitherMatcher.isLeftThat; @RunWith(Traits.class) public class TryTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class}) - 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"); @@ -50,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); @@ -58,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"); @@ -67,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"); @@ -78,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()); } @@ -92,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))); @@ -104,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 @@ -125,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()); @@ -139,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)); } @@ -150,4 +172,98 @@ public void tryingCatchesAnyThrowableThrownDuringEvaluation() { assertEquals(success("foo"), trying(() -> "foo")); } + + @Test + public void withResourcesCleansUpAutoCloseableInSuccessCase() { + AtomicBoolean closed = new AtomicBoolean(false); + assertEquals(success(1), withResources(() -> (AutoCloseable) () -> closed.set(true), resource -> success(1))); + assertTrue(closed.get()); + } + + @Test + public void withResourcesCleansUpAutoCloseableInFailureCase() { + AtomicBoolean closed = new AtomicBoolean(false); + RuntimeException exception = new RuntimeException(); + assertEquals(Try.failure(exception), withResources(() -> (AutoCloseable) () -> closed.set(true), + resource -> { throw exception; })); + assertTrue(closed.get()); + } + + @Test + public void withResourcesExposesResourceCreationFailure() { + IOException ioException = new IOException(); + assertEquals(Try.failure(ioException), withResources(() -> { throw ioException; }, resource -> success(1))); + } + + @Test + public void withResourcesExposesResourceCloseFailure() { + IOException ioException = new IOException(); + assertEquals(Try.failure(ioException), withResources(() -> (AutoCloseable) () -> { throw ioException; }, + resource -> success(1))); + } + + @Test + public void withResourcesPreservesSuppressedExceptionThrownDuringClose() { + RuntimeException rootException = new RuntimeException(); + IOException nestedIOException = new IOException(); + Try failure = withResources(() -> (AutoCloseable) () -> { throw nestedIOException; }, + resource -> { throw rootException; }); + Throwable thrown = failure.recover(id()); + + assertEquals(thrown, rootException); + assertArrayEquals(new Throwable[]{nestedIOException}, thrown.getSuppressed()); + } + + @Test + public void cascadingWithResourcesClosesInInverseOrder() { + List closeMessages = new ArrayList<>(); + assertEquals(success(1), withResources(() -> (AutoCloseable) () -> closeMessages.add("close a"), + a -> () -> closeMessages.add("close b"), + b -> () -> closeMessages.add("close c"), + c -> success(1))); + assertEquals(asList("close c", "close b", "close a"), closeMessages); + } + + @Test + public void lazyZip() { + assertEquals(success(2), success(1).lazyZip(lazy(success(x -> x + 1))).value()); + IllegalStateException e = new IllegalStateException(); + assertEquals(failure(e), failure(e).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void orThrowCanStillThrowCheckedExceptions() { + try { + Try.trying(() -> { + throw new RuntimeException(); + }).orThrow(); + fail("Expected RuntimeException to be thrown, but nothing was"); + } catch (IOException ioException) { + fail("Expected thrown exception to not be IOException, but merely proving it can still be caught"); + } catch (Exception expected) { + } + } + + @Test + public void orThrowCanTransformFirst() { + try { + Try.trying(() -> { + throw new IllegalStateException(); + }).orThrow(IllegalArgumentException::new); + fail("Expected RuntimeException to be thrown, but nothing was"); + } catch (IllegalStateException ioException) { + fail("Expected thrown exception to not be IllegalStateException, but it was"); + } catch (IllegalArgumentException expected) { + } catch (Exception e) { + fail("A different exception altogether was thrown."); + } + } + + @Test + public void staticPure() { + Try try_ = pureTry().apply(1); + assertEquals(success(1), try_); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java index 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 de58ff042..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; @@ -22,32 +21,32 @@ public class CoProduct4Test { private CoProduct4 d; @Before - public void setUp() throws Exception { + 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 36f9a44cc..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; @@ -29,12 +37,19 @@ public class Tuple2Test { private Tuple2 tuple2; @Before - public void setUp() throws Exception { + public void setUp() { tuple2 = new Tuple2<>(1, new SingletonHList<>(2)); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, 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 8495b059f..5064c8048 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java @@ -2,6 +2,7 @@ import org.junit.Test; +import java.math.BigInteger; import java.util.HashMap; import java.util.NoSuchElementException; @@ -12,6 +13,8 @@ import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; import static com.jnape.palatable.lambda.adt.hmap.HMap.singletonHMap; import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static com.jnape.palatable.lambda.optics.Iso.simpleIso; +import static java.math.BigInteger.ONE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -36,6 +39,37 @@ 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)); + + HMap hMap = singletonHMap(stringKey, "1"); + assertEquals(just("1"), hMap.get(stringKey)); + assertEquals(just(1L), hMap.get(longKey)); + assertEquals(just(ONE), hMap.get(bigIntegerKey)); + + assertNotEquals(typeSafeKey(), typeSafeKey()); + + assertEquals(emptyHMap().put(longKey, 1L).get(longKey), emptyHMap().put(stringKey, "1").get(longKey)); + assertEquals(emptyHMap().put(stringKey, "1").get(stringKey), emptyHMap().put(longKey, 1L).get(stringKey)); + assertEquals(emptyHMap().put(stringKey, "1").get(stringKey), + emptyHMap().put(bigIntegerKey, ONE).get(stringKey)); + + assertEquals(singletonHMap(stringKey, "1"), singletonHMap(longKey, 1L)); + assertEquals(singletonHMap(stringKey, "1"), singletonHMap(bigIntegerKey, ONE)); + assertEquals(singletonHMap(longKey, 1L), singletonHMap(bigIntegerKey, ONE)); + } + @Test public void getForPresentKeyWithNullValue() { TypeSafeKey stringKey = typeSafeKey(); @@ -57,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); @@ -107,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"); @@ -131,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", @@ -166,22 +201,78 @@ public void values() { @Test public void convenienceStaticFactoryMethods() { - TypeSafeKey stringKey = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); - TypeSafeKey floatKey = typeSafeKey(); - assertEquals(emptyHMap().put(stringKey, "string value"), + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey.Simple intKey = typeSafeKey(); + TypeSafeKey.Simple floatKey = typeSafeKey(); + TypeSafeKey.Simple byteKey = typeSafeKey(); + TypeSafeKey.Simple shortKey = typeSafeKey(); + TypeSafeKey.Simple longKey = typeSafeKey(); + TypeSafeKey.Simple doubleKey = typeSafeKey(); + TypeSafeKey.Simple charKey = typeSafeKey(); + + HMap m1 = emptyHMap().put(stringKey, "string value"); + HMap m2 = m1.put(intKey, 1); + HMap m3 = m2.put(floatKey, 1f); + HMap m4 = m3.put(byteKey, (byte) 1); + HMap m5 = m4.put(shortKey, (short) 1); + HMap m6 = m5.put(longKey, 1L); + HMap m7 = m6.put(doubleKey, 1D); + HMap m8 = m7.put(charKey, '1'); + + assertEquals(m1, singletonHMap(stringKey, "string value")); - assertEquals(emptyHMap().put(stringKey, "string value").put(intKey, 1), + + assertEquals(m2, hMap(stringKey, "string value", intKey, 1)); - assertEquals(emptyHMap().put(stringKey, "string value").put(intKey, 1).put(floatKey, 1f), + + assertEquals(m3, hMap(stringKey, "string value", intKey, 1, floatKey, 1f)); + + assertEquals(m4, + hMap(stringKey, "string value", + intKey, 1, + floatKey, 1f, + byteKey, (byte) 1)); + + assertEquals(m5, + hMap(stringKey, "string value", + intKey, 1, + floatKey, 1f, + byteKey, (byte) 1, + shortKey, (short) 1)); + + assertEquals(m6, + hMap(stringKey, "string value", + intKey, 1, + floatKey, 1f, + byteKey, (byte) 1, + shortKey, (short) 1, + longKey, 1L)); + + assertEquals(m7, + hMap(stringKey, "string value", + intKey, 1, + floatKey, 1f, + byteKey, (byte) 1, + shortKey, (short) 1, + longKey, 1L, + doubleKey, 1D)); + + assertEquals(m8, + hMap(stringKey, "string value", + intKey, 1, + floatKey, 1f, + byteKey, (byte) 1, + shortKey, (short) 1, + longKey, 1L, + doubleKey, 1D, + charKey, '1')); } @Test - @SuppressWarnings("EqualsWithItself") public void equality() { 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 new file mode 100644 index 000000000..d5cf6500c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/SchemaTest.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.adt.hmap; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; +import static com.jnape.palatable.lambda.adt.hmap.Schema.schema; +import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + +public class SchemaTest { + + @Test + public void extractsValuesAtKeysFromMap() { + TypeSafeKey.Simple byteKey = typeSafeKey(); + TypeSafeKey.Simple shortKey = typeSafeKey(); + TypeSafeKey.Simple intKey = typeSafeKey(); + TypeSafeKey.Simple longKey = typeSafeKey(); + TypeSafeKey.Simple floatKey = typeSafeKey(); + TypeSafeKey.Simple doubleKey = typeSafeKey(); + TypeSafeKey.Simple charKey = typeSafeKey(); + TypeSafeKey.Simple booleanKey = typeSafeKey(); + + HMap m = hMap(byteKey, (byte) 1, + shortKey, (short) 2, + intKey, 3, + longKey, 4L, + floatKey, 5F, + doubleKey, 6D, + charKey, '7', + booleanKey, true); + + assertLensLawfulness(schema(byteKey, shortKey, intKey, longKey, floatKey, doubleKey, charKey, booleanKey), + asList(emptyHMap(), + m), + asList(nothing(), + just(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, '7', true)))); + } + + @Test + public void extractsNothingIfAnyKeysMissing() { + assertEquals(nothing(), view(schema(typeSafeKey()), emptyHMap())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java index 577902c8c..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,9 +6,10 @@ 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; import static testsupport.assertion.LensAssert.assertLensLawfulness; @@ -23,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)); @@ -39,10 +40,35 @@ 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 mappedKeyA = keyA.andThen(simpleIso(id(), id())); + + assertEquals(keyA, keyA); + assertEquals(keyA, mappedKeyA); + assertEquals(mappedKeyA, keyA); + assertEquals(keyA.hashCode(), mappedKeyA.hashCode()); + + TypeSafeKey.Simple keyB = typeSafeKey(); + assertNotEquals(keyA, keyB); + assertNotEquals(keyB, keyA); + assertNotEquals(keyB, mappedKeyA); + assertNotEquals(mappedKeyA, keyB); + + TypeSafeKey differentMappedKeyA = keyA.andThen(simpleIso(id(), id())); + assertEquals(keyA, differentMappedKeyA); + assertEquals(differentMappedKeyA, keyA); + assertEquals(mappedKeyA, differentMappedKeyA); + assertEquals(differentMappedKeyA, mappedKeyA); + assertEquals(keyA.hashCode(), differentMappedKeyA.hashCode()); + assertEquals(mappedKeyA.hashCode(), differentMappedKeyA.hashCode()); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product2Test.java new file mode 100644 index 000000000..4fe4837e0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product2Test.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product2.product; +import static org.junit.Assert.assertEquals; + +public class Product2Test { + + private Product2 product; + + @Before + public void setUp() { + product = product("a", "b"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + } + + @Test + public void invert() { + assertEquals("ba", product.invert().into((a, b) -> a + b)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product3Test.java new file mode 100644 index 000000000..f6533e682 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product3Test.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product3.product; +import static org.junit.Assert.assertEquals; + +public class Product3Test { + + private Product3 product; + + @Before + public void setUp() { + product = product("a", "b", "c"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + } + + @Test + public void rotations() { + assertEquals("bac", product.invert().into((a, b, c) -> a + b + c)); + assertEquals("bca", product.rotateL3().into((a, b, c) -> a + b + c)); + assertEquals("cab", product.rotateR3().into((a, b, c) -> a + b + c)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product4Test.java new file mode 100644 index 000000000..12d6b3c19 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product4Test.java @@ -0,0 +1,34 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product4.product; +import static org.junit.Assert.assertEquals; + +public class Product4Test { + + private Product4 product; + + @Before + public void setUp() { + product = product("a", "b", "c", "d"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + assertEquals("d", product._4()); + } + + @Test + public void rotations() { + assertEquals("bacd", product.invert().into((a, b, c, d) -> a + b + c + d)); + assertEquals("bcad", product.rotateL3().into((a, b, c, d) -> a + b + c + d)); + assertEquals("cabd", product.rotateR3().into((a, b, c, d) -> a + b + c + d)); + assertEquals("bcda", product.rotateL4().into((a, b, c, d) -> a + b + c + d)); + assertEquals("dabc", product.rotateR4().into((a, b, c, d) -> a + b + c + d)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product5Test.java new file mode 100644 index 000000000..63723741f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product5Test.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product5.product; +import static org.junit.Assert.assertEquals; + +public class Product5Test { + + private Product5 product; + + @Before + public void setUp() { + product = product("a", "b", "c", "d", "e"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + assertEquals("d", product._4()); + assertEquals("e", product._5()); + } + + @Test + public void rotations() { + assertEquals("bacde", product.invert().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("bcade", product.rotateL3().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("cabde", product.rotateR3().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("bcdae", product.rotateL4().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("dabce", product.rotateR4().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("bcdea", product.rotateL5().into((a, b, c, d, e) -> a + b + c + d + e)); + assertEquals("eabcd", product.rotateR5().into((a, b, c, d, e) -> a + b + c + d + e)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product6Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product6Test.java new file mode 100644 index 000000000..aa6e0ceb7 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product6Test.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product6.product; +import static org.junit.Assert.assertEquals; + +public class Product6Test { + + private Product6 product; + + @Before + public void setUp() { + product = product("a", "b", "c", "d", "e", "f"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + assertEquals("d", product._4()); + assertEquals("e", product._5()); + assertEquals("f", product._6()); + } + + @Test + public void rotations() { + assertEquals("bacdef", product.invert().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("bcadef", product.rotateL3().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("cabdef", product.rotateR3().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("bcdaef", product.rotateL4().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("dabcef", product.rotateR4().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("bcdeaf", product.rotateL5().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("eabcdf", product.rotateR5().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("bcdefa", product.rotateL6().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + assertEquals("fabcde", product.rotateR6().into((a, b, c, d, e, f) -> a + b + c + d + e + f)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product7Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product7Test.java new file mode 100644 index 000000000..a5cd04b5f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product7Test.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product7.product; +import static org.junit.Assert.assertEquals; + +public class Product7Test { + + private Product7 product; + + @Before + public void setUp() { + product = product("a", "b", "c", "d", "e", "f", "g"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + assertEquals("d", product._4()); + assertEquals("e", product._5()); + assertEquals("f", product._6()); + assertEquals("g", product._7()); + } + + @Test + public void rotations() { + assertEquals("bacdefg", product.invert().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("bcadefg", product.rotateL3().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("cabdefg", product.rotateR3().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("bcdaefg", product.rotateL4().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("dabcefg", product.rotateR4().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("bcdeafg", product.rotateL5().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("eabcdfg", product.rotateR5().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("bcdefag", product.rotateL6().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("fabcdeg", product.rotateR6().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("bcdefga", product.rotateL7().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + assertEquals("gabcdef", product.rotateR7().into((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/product/Product8Test.java b/src/test/java/com/jnape/palatable/lambda/adt/product/Product8Test.java new file mode 100644 index 000000000..64360b763 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/product/Product8Test.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.adt.product; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.product.Product8.product; +import static org.junit.Assert.assertEquals; + +public class Product8Test { + + private Product8 product; + + @Before + public void setUp() { + product = product("a", "b", "c", "d", "e", "f", "g", "h"); + } + + @Test + public void staticFactoryMethod() { + assertEquals("a", product._1()); + assertEquals("b", product._2()); + assertEquals("c", product._3()); + assertEquals("d", product._4()); + assertEquals("e", product._5()); + assertEquals("f", product._6()); + assertEquals("g", product._7()); + assertEquals("h", product._8()); + } + + @Test + public void rotations() { + assertEquals("bacdefgh", product.invert().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcadefgh", product.rotateL3().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("cabdefgh", product.rotateR3().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcdaefgh", product.rotateL4().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("dabcefgh", product.rotateR4().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcdeafgh", product.rotateL5().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("eabcdfgh", product.rotateR5().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcdefagh", product.rotateL6().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("fabcdegh", product.rotateR6().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcdefgah", product.rotateL7().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("gabcdefh", product.rotateR7().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("bcdefgha", product.rotateL8().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + assertEquals("habcdefg", product.rotateR8().into((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java b/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java new file mode 100644 index 000000000..456d2619c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java @@ -0,0 +1,76 @@ +package com.jnape.palatable.lambda.functions; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.io.IO; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import static com.jnape.palatable.lambda.functions.Effect.effect; +import static com.jnape.palatable.lambda.functions.Effect.fromConsumer; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Alter.alter; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; +import static com.jnape.palatable.lambda.functions.specialized.SideEffect.sideEffect; +import static com.jnape.palatable.lambda.io.IO.io; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class EffectTest { + + @Test + public void covariantReturns() { + List results = new ArrayList<>(); + + Effect effect = fromConsumer(results::add); + Effect diMapL = effect.diMapL(Object::toString); + Effect contraMap = effect.contraMap(Object::toString); + Effect stringEffect = effect.discardR(constantly("1")); + + assertThat(sequence(asList(effect.apply("1"), + diMapL.apply("2"), + contraMap.apply("3"), + stringEffect.apply("4")), + IO::io) + .fmap(constantly(results)), + yieldsValue(equalTo(asList("1", "2", "3", "4")))); + } + + @Test + public void andThen() { + AtomicInteger counter = new AtomicInteger(); + Effect inc = c -> io(sideEffect(c::incrementAndGet)); + + assertThat(alter(inc.andThen(inc), counter).fmap(AtomicInteger::get), + yieldsValue(equalTo(2))); + } + + @Test + public void staticFactoryMethods() { + AtomicInteger counter = new AtomicInteger(); + + Effect sideEffect = effect(counter::incrementAndGet); + assertThat(sideEffect.apply("foo").flatMap(constantly(io(counter::get))), + yieldsValue(equalTo(1))); + + Effect fnEffect = Effect.fromConsumer(AtomicInteger::incrementAndGet); + assertThat(fnEffect.apply(counter).flatMap(constantly(io(counter::get))), + yieldsValue(equalTo(2))); + } + + @Test + public void toConsumer() { + @SuppressWarnings("RedundantTypeArguments") Effect> addFoo = l -> IO.io(() -> l.add("foo")); + Consumer> consumer = addFoo.toConsumer(); + ArrayList list = new ArrayList<>(); + consumer.accept(list); + assertEquals(singletonList("foo"), list); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn0Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn0Test.java new file mode 100644 index 000000000..87af14209 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn0Test.java @@ -0,0 +1,39 @@ +package com.jnape.palatable.lambda.functions; + +import org.junit.Test; + +import java.util.concurrent.Callable; +import java.util.function.Supplier; + +import static org.junit.Assert.assertEquals; + +public class Fn0Test { + + @Test + public void fromSupplier() { + Supplier supplier = () -> 1; + Fn0 fn0 = Fn0.fromSupplier(supplier); + assertEquals((Integer) 1, fn0.apply()); + } + + @Test + public void fromCallable() { + Callable callable = () -> 1; + Fn0 fn0 = Fn0.fromCallable(callable); + assertEquals((Integer) 1, fn0.apply()); + } + + @Test + public void toSupplier() { + Fn0 fn0 = () -> 1; + Supplier supplier = fn0.toSupplier(); + assertEquals((Integer) 1, supplier.get()); + } + + @Test + public void toCallable() throws Exception { + Fn0 fn0 = () -> 1; + Callable callable = fn0.toCallable(); + assertEquals((Integer) 1, callable.call()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java index fa3881d40..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,21 +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 @@ -31,8 +48,66 @@ 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 + public void thunk() { + Fn1 toString = Object::toString; + assertEquals("1", toString.thunk(1).apply()); + } + + @Test + public void widen() { + Fn1 addOne = x -> x + 1; + assertEquals(just(4), reduceLeft(addOne.widen(), asList(1, 2, 3))); + } + + @Test + public void cartesian() { + Fn1 add1 = x -> x + 1; + assertEquals(tuple("a", 2), add1.cartesian().apply(tuple("a", 1))); + } + + @Test + public void carry() { + Fn1 add1 = x -> x + 1; + assertEquals(tuple(1, 2), add1.carry().apply(1)); + } + + @Test + public void cocartesian() { + Fn1 add1 = x -> x + 1; + assertEquals(a("foo"), add1.cocartesian().apply(a("foo"))); + assertEquals(b(2), add1.cocartesian().apply(b(1))); + } + + @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/Fn3Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java index 31f00e249..78ec9a225 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn3Test.java @@ -3,6 +3,7 @@ import org.junit.Test; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn3.fn3; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -31,11 +32,13 @@ public void uncurries() { } @Test - public void fn3() { + public void staticFactoryMethods() { Fn1> fn1 = a -> (b, c) -> a + b + c; - assertEquals("abc", Fn3.fn3(fn1).apply("a", "b", "c")); + assertEquals("abc", fn3(fn1).apply("a", "b", "c")); Fn2> fn2 = (a, b) -> c -> a + b + c; - assertEquals("abc", Fn3.fn3(fn2).apply("a", "b", "c")); + assertEquals("abc", fn3(fn2).apply("a", "b", "c")); + + assertEquals("abc", Fn3.fn3((a, b, c) -> a + b + c).apply("a", "b", "c")); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn4Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn4Test.java index f241fc88a..5808dac74 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn4Test.java @@ -3,6 +3,7 @@ import org.junit.Test; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn4.fn4; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -31,14 +32,16 @@ public void uncurries() { } @Test - public void fn4() { + public void staticFactoryMethods() { Fn1> fn1 = a -> (b, c, d) -> a + b + c + d; - assertEquals("abcd", Fn4.fn4(fn1).apply("a", "b", "c", "d")); + assertEquals("abcd", fn4(fn1).apply("a", "b", "c", "d")); Fn2> fn2 = (a, b) -> (c, d) -> a + b + c + d; - assertEquals("abcd", Fn4.fn4(fn2).apply("a", "b", "c", "d")); + assertEquals("abcd", fn4(fn2).apply("a", "b", "c", "d")); Fn3> fn3 = (a, b, c) -> (d) -> a + b + c + d; - assertEquals("abcd", Fn4.fn4(fn3).apply("a", "b", "c", "d")); + assertEquals("abcd", fn4(fn3).apply("a", "b", "c", "d")); + + assertEquals("abcd", Fn4.fn4((a, b, c, d) -> a + b + c + d).apply("a", "b", "c", "d")); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn5Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn5Test.java index bb5d2021d..f7f8097b8 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn5Test.java @@ -3,6 +3,7 @@ import org.junit.Test; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn5.fn5; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -32,17 +33,19 @@ public void uncurries() { } @Test - public void fn5() { + public void staticFactoryMethods() { Fn1> fn1 = a -> (b, c, d, e) -> a + b + c + d + e; - assertEquals("abcde", Fn5.fn5(fn1).apply("a", "b", "c", "d", "e")); + assertEquals("abcde", fn5(fn1).apply("a", "b", "c", "d", "e")); Fn2> fn2 = (a, b) -> (c, d, e) -> a + b + c + d + e; - assertEquals("abcde", Fn5.fn5(fn2).apply("a", "b", "c", "d", "e")); + assertEquals("abcde", fn5(fn2).apply("a", "b", "c", "d", "e")); Fn3> fn3 = (a, b, c) -> (d, e) -> a + b + c + d + e; - assertEquals("abcde", Fn5.fn5(fn3).apply("a", "b", "c", "d", "e")); + assertEquals("abcde", fn5(fn3).apply("a", "b", "c", "d", "e")); Fn4> fn4 = (a, b, c, d) -> (e) -> a + b + c + d + e; - assertEquals("abcde", Fn5.fn5(fn4).apply("a", "b", "c", "d", "e")); + assertEquals("abcde", fn5(fn4).apply("a", "b", "c", "d", "e")); + + assertEquals("abcde", Fn5.fn5((a, b, c, d, e) -> a + b + c + d + e).apply("a", "b", "c", "d", "e")); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn6Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn6Test.java index e52b1b54f..2f9faaa2d 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn6Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn6Test.java @@ -33,7 +33,7 @@ public void uncurries() { } @Test - public void fn6() { + public void staticFactoryMethods() { Fn1> fn1 = a -> (b, c, d, e, f) -> a + b + c + d + e + f; assertEquals("abcdef", Fn6.fn6(fn1).apply("a", "b", "c", "d", "e", "f")); @@ -48,5 +48,7 @@ public void fn6() { Fn5> fn5 = (a, b, c, d, e) -> (f) -> a + b + c + d + e + f; assertEquals("abcdef", Fn6.fn6(fn5).apply("a", "b", "c", "d", "e", "f")); + + assertEquals("abcdef", Fn6.fn6((a, b, c, d, e, f) -> a + b + c + d + e + f).apply("a", "b", "c", "d", "e", "f")); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn7Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn7Test.java index 9baf0fe0b..ed361c26c 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn7Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn7Test.java @@ -34,7 +34,7 @@ public void uncurries() { } @Test - public void fn7() { + public void staticFactoryMethod() { Fn1> fn1 = a -> (b, c, d, e, f, g) -> a + b + c + d + e + f + g; assertEquals("abcdefg", Fn7.fn7(fn1).apply("a", "b", "c", "d", "e", "f", "g")); @@ -52,5 +52,7 @@ public void fn7() { Fn6> fn6 = (a, b, c, d, e, f) -> (g) -> a + b + c + d + e + f + g; assertEquals("abcdefg", Fn7.fn7(fn6).apply("a", "b", "c", "d", "e", "f", "g")); + + assertEquals("abcdefg", Fn7.fn7((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g).apply("a", "b", "c", "d", "e", "f", "g")); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn8Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn8Test.java index ea220e2f8..058168eb1 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn8Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn8Test.java @@ -3,6 +3,7 @@ import org.junit.Test; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn8.fn8; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -35,26 +36,28 @@ public void uncurries() { } @Test - public void fn8() { + public void staticFactoryMethods() { Fn1> fn1 = a -> (b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h; - assertEquals("abcdefgh", Fn8.fn8(fn1).apply("a", "b", "c", "d", "e", "f", "g", "h")); + assertEquals("abcdefgh", fn8(fn1).apply("a", "b", "c", "d", "e", "f", "g", "h")); Fn2> fn2 = (a, b) -> (c, d, e, f, g, h) -> a + b + c + d + e + f + g + h; - assertEquals("abcdefgh", Fn8.fn8(fn2).apply("a", "b", "c", "d", "e", "f", "g", "h")); + assertEquals("abcdefgh", fn8(fn2).apply("a", "b", "c", "d", "e", "f", "g", "h")); Fn3> fn3 = (a, b, c) -> (d, e, f, g, h) -> a + b + c + d + e + f + g + h; - assertEquals("abcdefgh", Fn8.fn8(fn3).apply("a", "b", "c", "d", "e", "f", "g", "h")); + assertEquals("abcdefgh", fn8(fn3).apply("a", "b", "c", "d", "e", "f", "g", "h")); Fn4> fn4 = (a, b, c, d) -> (e, f, g, h) -> a + b + c + d + e + f + g + h; - assertEquals("abcdefgh", Fn8.fn8(fn4).apply("a", "b", "c", "d", "e", "f", "g", "h")); + assertEquals("abcdefgh", fn8(fn4).apply("a", "b", "c", "d", "e", "f", "g", "h")); Fn5> fn5 = (a, b, c, d, e) -> (f, g, h) -> a + b + c + d + e + f + g + h; - assertEquals("abcdefgh", Fn8.fn8(fn5).apply("a", "b", "c", "d", "e", "f", "g", "h")); + assertEquals("abcdefgh", fn8(fn5).apply("a", "b", "c", "d", "e", "f", "g", "h")); Fn6> fn6 = (a, b, c, d, e, f) -> (g, h) -> a + b + c + d + e + f + g + h; - assertEquals("abcdefgh", Fn8.fn8(fn6).apply("a", "b", "c", "d", "e", "f", "g", "h")); + assertEquals("abcdefgh", fn8(fn6).apply("a", "b", "c", "d", "e", "f", "g", "h")); Fn7> fn7 = (a, b, c, d, e, f, g) -> (h) -> a + b + c + d + e + f + g + h; - assertEquals("abcdefgh", Fn8.fn8(fn7).apply("a", "b", "c", "d", "e", "f", "g", "h")); + assertEquals("abcdefgh", fn8(fn7).apply("a", "b", "c", "d", "e", "f", "g", "h")); + + assertEquals("abcdefgh", Fn8.fn8((a, b, c, d, e, f, g, h) -> a + b + c + d + e + f + g + h).apply("a", "b", "c", "d", "e", "f", "g", "h")); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/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 d428e7c17..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 @@ -18,6 +18,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertThat; import static testsupport.matchers.IterableMatcher.isEmpty; import static testsupport.matchers.IterableMatcher.iterates; @@ -27,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 @@ -40,4 +41,10 @@ public void flattensSparseIterableOfPopulatedIterables() { assertThat(flatten(asList(emptyList(), asList(1, 2, 3), emptyList(), emptyList(), singleton(4), asList(5, 6), emptyList())), iterates(1, 2, 3, 4, 5, 6)); } + + @Test + public void flattenMultipleLevelsOfNesting() { + assertThat(flatten(asList(asList(asList(1, 2, 3), asList(4, 5)), singletonList(asList(6, 7)))), + iterates(asList(1, 2, 3), asList(4, 5), asList(6, 7))); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/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 new file mode 100644 index 000000000..290fa2736 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/OccurrencesTest.java @@ -0,0 +1,29 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Occurrences.occurrences; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; + +public class OccurrencesTest { + + @Test + @SuppressWarnings("serial") + public void occurrencesOfIndividualElements() { + assertEquals(new HashMap() {{ + put("foo", 2L); + put("bar", 2L); + put("baz", 1L); + }}, occurrences(asList("foo", "bar", "foo", "baz", "bar"))); + } + + @Test + public void emptyIterableHasNoOccurrences() { + assertEquals(Collections.emptyMap(), occurrences(emptyList())); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/RepeatTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/RepeatTest.java index 80112707f..de34c53b4 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/RepeatTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/RepeatTest.java @@ -17,7 +17,7 @@ public class RepeatTest { @TestTraits({Laziness.class, ImmutableIteration.class, InfiniteIteration.class}) - public Repeat createTestSubject() { + public Repeat createTestSubject() { return repeat(); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ReverseTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ReverseTest.java index da6a94e45..39a133bcb 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ReverseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ReverseTest.java @@ -24,20 +24,20 @@ public class ReverseTest { @TestTraits({Laziness.class, ImmutableIteration.class, FiniteIteration.class, EmptyIterableSupport.class}) - public Reverse createTestSubject() { + public Reverse createTestSubject() { return reverse(); } @Test public void iteratesElementsOfAnIterableBackwards() { - Iterable words = asList("the", "rain", "in", "Spain"); + Iterable words = asList("the", "rain", "in", "Spain"); Iterable reversed = reverse(words); assertThat(reversed, iterates("Spain", "in", "rain", "the")); } @Test - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "ResultOfMethodCallIgnored"}) public void doesNotBeginReversingUntilIterated() { Iterable mockIterable = mock(Iterable.class); Iterator mockIterator = mock(Iterator.class); @@ -50,4 +50,9 @@ public void doesNotBeginReversingUntilIterated() { verify(mockIterator).hasNext(); verify(mockIterator, never()).next(); } + + @Test + public void doubleReverseIsNoOp() { + assertThat(reverse(reverse(asList(1, 2, 3))), iterates(1, 2, 3)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SizeTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SizeTest.java index 7ee079443..1a127054f 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SizeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SizeTest.java @@ -4,13 +4,12 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; public class SizeTest { @@ -21,13 +20,20 @@ public void countsElementsInIterable() { } @Test + @SuppressWarnings("serial") public void optimizesForCollections() { - Collection collection = spy(new ArrayList() {{ - add(1); - add(2); - add(3); - }}); - when(collection.iterator()).thenThrow(new IllegalStateException("should not be using the iterator")); + Collection collection = new ArrayList() { + @Override + public Iterator iterator() { + throw new IllegalStateException("should not be using the iterator"); + } + + { + add(1); + add(2); + add(3); + } + }; assertEquals((Long) 3L, size(collection)); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/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 new file mode 100644 index 000000000..2a5fa7e55 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UpcastTest.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static java.util.Arrays.asList; + +public class UpcastTest { + + @Test + @SuppressWarnings("unused") + public void castsUp() { + Upcast upcast = upcast(); + Iterable strings = asList("foo", "bar"); + Iterable charSequences = map(upcast, strings); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java new file mode 100644 index 000000000..166e99f29 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.Fn2.fn2; +import static com.jnape.palatable.lambda.functions.builtin.fn2.$.$; +import static org.junit.Assert.assertEquals; + +public class $Test { + + @Test + public void application() { + assertEquals((Integer) 1, $(x -> x + 1, 0)); + assertEquals((Integer) 1, $.$(x -> x + 1).apply(0)); + } + + @Test + public void curryingInference() { + assertEquals((Integer) 1, $($(fn2(Integer::sum), 0), 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AllTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AllTest.java index d746b0fc1..376295e5b 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AllTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AllTest.java @@ -7,8 +7,6 @@ import org.junit.runner.RunWith; import testsupport.traits.EmptyIterableSupport; -import java.util.function.Function; - import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.functions.builtin.fn2.All.all; @@ -19,10 +17,10 @@ @RunWith(Traits.class) public class AllTest { - private static final Function EVEN = x -> x.doubleValue() % 2 == 0; + private static final Fn1 EVEN = x -> x.doubleValue() % 2 == 0; @TestTraits({EmptyIterableSupport.class}) - public Fn1, Boolean> createTestSubject() { + public Fn1, ? extends Boolean> createTestSubject() { return all(constantly(true)); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java new file mode 100644 index 000000000..5baa26fa1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java @@ -0,0 +1,25 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import java.util.ArrayList; + +import static com.jnape.palatable.lambda.functions.Effect.fromConsumer; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Alter.alter; +import static java.util.Collections.singletonList; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class AlterTest { + + @Test + public void altersInput() { + ArrayList input = new ArrayList<>(); + assertThat(alter(fromConsumer(xs -> xs.add("foo")), input), + yieldsValue(allOf(sameInstance(input), + equalTo(singletonList("foo"))))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AnyTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AnyTest.java index aa8f8a118..ccf0451cd 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AnyTest.java @@ -7,8 +7,6 @@ import org.junit.runner.RunWith; import testsupport.traits.EmptyIterableSupport; -import java.util.function.Function; - import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.functions.builtin.fn2.Any.any; @@ -19,7 +17,7 @@ @RunWith(Traits.class) public class AnyTest { - public static final Function EVEN = x -> x % 2 == 0; + public static final Fn1 EVEN = x -> x % 2 == 0; @TestTraits({EmptyIterableSupport.class}) public Fn1, Boolean> createTestSubject() { diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AutoBracketTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AutoBracketTest.java new file mode 100644 index 000000000..b6cd3cf4a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AutoBracketTest.java @@ -0,0 +1,48 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.io.IO; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.AutoBracket.autoBracket; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.throwsException; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class AutoBracketTest { + + private AtomicInteger closedCounter; + private AutoCloseable autoCloseable; + + @Before + public void setUp() { + closedCounter = new AtomicInteger(0); + autoCloseable = closedCounter::incrementAndGet; + } + + @Test + public void closeWhenDone() { + IO bracketed = autoBracket(io(autoCloseable), closeable -> io(1)); + + assertEquals(0, closedCounter.get()); + assertThat(bracketed, yieldsValue(equalTo(1))); + assertEquals(1, closedCounter.get()); + } + + @Test + public void closeOnException() { + RuntimeException cause = new RuntimeException(); + + IO bracketed = autoBracket(io(autoCloseable), closeable -> IO.throwing(cause)); + + assertEquals(0, closedCounter.get()); + assertThat(bracketed, throwsException(equalTo(cause))); + assertEquals(1, closedCounter.get()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProductTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProductTest.java index b6b74e9e4..07780e82b 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProductTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProductTest.java @@ -18,7 +18,6 @@ import static testsupport.matchers.IterableMatcher.iterates; @RunWith(Traits.class) -@SuppressWarnings("unchecked") public class CartesianProductTest { @TestTraits({Laziness.class, ImmutableIteration.class, EmptyIterableSupport.class, FiniteIteration.class}) @@ -29,7 +28,7 @@ public Fn1, Iterable>> createTestSubjec @Test public void computesCartesianProductOfTwoEquallySizedIterables() { Iterable numbers = asList(1, 2, 3); - Iterable letters = asList("a", "b", "c"); + Iterable letters = asList("a", "b", "c"); assertThat( cartesianProduct(numbers, letters), @@ -50,7 +49,7 @@ public void computesCartesianProductOfTwoEquallySizedIterables() { @Test public void worksForTwoUnequallySizedIterables() { Iterable oneThroughThree = asList(1, 2, 3); - Iterable aAndB = asList("a", "b"); + Iterable aAndB = asList("a", "b"); assertThat( cartesianProduct(oneThroughThree, aAndB), diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CmpEqTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CmpEqTest.java new file mode 100644 index 000000000..05290241a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/CmpEqTest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.CmpEq.cmpEq; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CmpEqTest { + + @Test + public void comparisons() { + assertTrue(cmpEq(1, 1)); + assertFalse(cmpEq(1, 2)); + assertFalse(cmpEq(2, 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DifferenceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DifferenceTest.java new file mode 100644 index 000000000..2b1dd6ecc --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DifferenceTest.java @@ -0,0 +1,39 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Difference.difference; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class DifferenceTest { + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) + public Fn1, Iterable> testSubject() { + return Difference.difference().flip().apply(asList(1, 2, 3)); + } + + @Test + public void semigroup() { + assertThat(difference(emptyList(), emptyList()), isEmpty()); + assertThat(difference(asList(1, 2, 3), emptyList()), iterates(1, 2, 3)); + assertThat(difference(asList(1, 2, 2, 3), emptyList()), iterates(1, 2, 3)); + assertThat(difference(emptyList(), asList(1, 2, 3)), isEmpty()); + assertThat(difference(asList(1, 2, 3), singletonList(4)), iterates(1, 2, 3)); + assertThat(difference(asList(1, 2, 3), asList(2, 4)), iterates(1, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java index b57ae8085..ff4551a11 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java @@ -11,6 +11,9 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; +import java.util.ArrayList; +import java.util.List; + import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.DropWhile.dropWhile; import static java.util.Arrays.asList; @@ -41,4 +44,25 @@ public void dropsAllElementsIfPredicateNeverFails() { public void dropsNoElementsIfPredicateImmediatelyFails() { assertThat(dropWhile(constantly(false), asList(1, 2, 3)), iterates(1, 2, 3)); } + + @Test + public void deforestingExecutesPredicatesInOrder() { + List innerInvocations = new ArrayList<>(); + List outerInvocations = new ArrayList<>(); + dropWhile(y -> { + outerInvocations.add(y); + return true; + }, dropWhile(x -> { + innerInvocations.add(x); + return x > 2; + }, asList(1, 2, 3))).forEach(__ -> {}); + assertThat(innerInvocations, iterates(1)); + assertThat(outerInvocations, iterates(1, 2, 3)); + } + + @Test + public void eachLayerIsAppliedOnce() { + assertThat(dropWhile(i -> i % 2 == 0, dropWhile(i -> i % 2 == 1, asList(1, 2, 3))), + iterates(3)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FilterTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FilterTest.java index d718c55ef..cc589a984 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FilterTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FilterTest.java @@ -11,6 +11,9 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; +import java.util.ArrayList; +import java.util.List; + import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; import static java.util.Arrays.asList; @@ -21,7 +24,7 @@ public class FilterTest { @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Fn1 createTraitsTestSubject() { + public Fn1, ?> testSubject() { return filter(constantly(true)); } @@ -33,4 +36,19 @@ public void filtersOutMatchingElements() { iterates(2, 4, 6) ); } + + @Test + public void deforestingExecutesPredicatesInOrder() { + List innerInvocations = new ArrayList<>(); + List outerInvocations = new ArrayList<>(); + filter(y -> { + outerInvocations.add(y); + return true; + }, filter(x -> { + innerInvocations.add(x); + return x % 2 == 0; + }, asList(1, 2, 3))).forEach(__ -> {}); + assertThat(innerInvocations, iterates(1, 2, 3)); + assertThat(outerInvocations, iterates(2)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTETest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTETest.java new file mode 100644 index 000000000..c3fd1f89b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTETest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.GTE.gte; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTETest { + + @Test + public void comparisons() { + assertTrue(gte(1, 2)); + assertTrue(gte(1, 1)); + assertFalse(gte(2, 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTTest.java new file mode 100644 index 000000000..4f448b695 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GTTest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.GT.gt; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTTest { + + @Test + public void comparisons() { + assertTrue(gt(1, 2)); + assertFalse(gt(1, 1)); + assertFalse(gt(2, 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupByTest.java 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/LTETest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTETest.java new file mode 100644 index 000000000..8fd684b31 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTETest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.LTE.lte; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTETest { + + @Test + public void comparisons() { + assertTrue(lte(2, 1)); + assertTrue(lte(1, 1)); + assertFalse(lte(1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTTest.java new file mode 100644 index 000000000..2c2229aa2 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LTTest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.LT.lt; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTTest { + + @Test + public void comparisons() { + assertTrue(lt(2, 1)); + assertFalse(lt(1, 1)); + assertFalse(lt(1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRecTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRecTest.java new file mode 100644 index 000000000..8987fe32a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/LazyRecTest.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.LazyRec.lazyRec; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; + +public class LazyRecTest { + + @Test + public void recursivelyComputesValue() { + assertEquals(STACK_EXPLODING_NUMBER, + LazyRec.lazyRec((f, x) -> x < STACK_EXPLODING_NUMBER + ? f.apply(x + 1) + : lazy(x), + 0) + .value()); + } + + @Test + public void defersAllComputationUntilForced() { + AtomicBoolean invoked = new AtomicBoolean(false); + lazyRec((f, x) -> { + invoked.set(true); + return lazy(x); + }, + 0); + + assertFalse(invoked.get()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeByTest.java 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 73b45f407..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,17 +2,19 @@ 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; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static testsupport.matchers.IterableMatcher.iterates; @@ -21,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)); @@ -49,6 +51,12 @@ public void iterableSpecialization() { iterates(1, 2)); } + @Test + public void mapSpecialization() { + assertEquals(right(singletonMap("foo", 1)), + sequence(singletonMap("foo", right(1)), Either::right)); + } + @Test public void compilation() { Either> a = sequence(just(right(1)), Either::right); @@ -62,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/SnocTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SnocTest.java index 8615a5dd8..439a60dfd 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SnocTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SnocTest.java @@ -15,6 +15,7 @@ import static com.jnape.palatable.lambda.functions.builtin.fn2.Snoc.snoc; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static org.junit.Assert.assertThat; import static testsupport.matchers.IterableMatcher.iterates; @@ -35,4 +36,9 @@ public void appendToEmptyIterable() { public void appendToNonEmptyIterable() { assertThat(snoc(4, asList(1, 2, 3)), iterates(1, 2, 3, 4)); } + + @Test + public void deforestingOrder() { + assertThat(snoc(3, snoc(2, snoc(1, emptyList()))), iterates(1, 2, 3)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortWithTest.java new file mode 100644 index 000000000..d1b64b603 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortWithTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import org.junit.Test; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.SortWith.sortWith; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class SortWithTest { + + @Test + public void sortsWithGivenComparator() { + assertEquals(asList(tuple("bar", 4), tuple("baz", 3), tuple("foo", 1), tuple("foo", 2)), + sortWith(Comparator., String>comparing(Tuple2::_1) + .thenComparing(Tuple2::_2), + asList(tuple("foo", 1), tuple("foo", 2), tuple("bar", 4), tuple("baz", 3)))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java index 871bd3a6e..5e34c3403 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java @@ -11,6 +11,9 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; +import java.util.ArrayList; +import java.util.List; + import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.TakeWhile.takeWhile; import static java.util.Arrays.asList; @@ -29,7 +32,7 @@ public Fn1, Iterable> createTestObject() { @Test public void takesElementsWhilePredicateIsTrue() { Predicate lessThan3 = integer -> integer < 3; - Iterable numbers = takeWhile(lessThan3, asList(1, 2, 3, 4, 5)); + Iterable numbers = takeWhile(lessThan3, asList(1, 2, 3, 4, 5)); assertThat(numbers, iterates(1, 2)); } @@ -46,4 +49,19 @@ public void takesAllElementsIfPredicateNeverFails() { public void takesNoElementsIfPredicateImmediatelyFails() { assertThat(takeWhile(constantly(false), asList(1, 2, 3)), isEmpty()); } + + @Test + public void deforestingExecutesPredicatesInOrder() { + List innerInvocations = new ArrayList<>(); + List outerInvocations = new ArrayList<>(); + takeWhile(y -> { + outerInvocations.add(y); + return true; + }, takeWhile(x -> { + innerInvocations.add(x); + return x < 3; + }, asList(1, 2, 3))).forEach(__ -> {}); + assertThat(innerInvocations, iterates(1, 2, 3)); + assertThat(outerInvocations, iterates(1, 2)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToArrayTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToArrayTest.java new file mode 100644 index 000000000..0c4b3001e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToArrayTest.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import java.util.AbstractCollection; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToArray.toArray; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyIterator; +import static org.junit.Assert.assertArrayEquals; + +public class ToArrayTest { + + @Test + public void writesIterableToArray() { + assertArrayEquals(new Integer[]{1, 2, 3}, toArray(Integer[].class, asList(1, 2, 3))); + + List variance = asList(1, 2, 3); + assertArrayEquals(new Object[]{1, 2, 3}, toArray(Object[].class, variance)); + } + + @Test + public void usesCollectionToArrayIfPossible() { + Object sentinel = new Object(); + class CustomCollection extends AbstractCollection { + @Override + public Iterator iterator() { + return emptyIterator(); + } + + @Override + public int size() { + return 0; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + T[] result = Arrays.copyOf(a, 1); + result[0] = (T) sentinel; + return result; + } + } + + assertArrayEquals(new Object[]{sentinel}, toArray(Object[].class, new CustomCollection())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollectionTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollectionTest.java index 584dd876d..6188c6765 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollectionTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToCollectionTest.java @@ -3,8 +3,6 @@ import org.junit.Test; import java.util.ArrayList; -import java.util.Collection; -import java.util.function.Supplier; import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; import static java.util.Arrays.asList; @@ -14,7 +12,6 @@ public class ToCollectionTest { @Test public void convertsIterablesToCollectionInstance() { - Supplier> listFactory = ArrayList::new; - assertEquals(asList(1, 2, 3), toCollection(listFactory, asList(1, 2, 3))); + assertEquals(asList(1, 2, 3), toCollection(ArrayList::new, asList(1, 2, 3))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMapTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMapTest.java index 21ab43c8d..2573212f6 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMapTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMapTest.java @@ -13,6 +13,7 @@ public class ToMapTest { @Test + @SuppressWarnings("serial") public void collectsEntriesIntoMap() { Map expected = new HashMap() {{ put("foo", 1); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UnfoldrTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UnfoldrTest.java index 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/BetweenTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BetweenTest.java new file mode 100644 index 000000000..2638ee7f1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BetweenTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Between.between; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class BetweenTest { + + @Test + public void testsIfValueIsBetweenClosedBounds() { + assertFalse(between(1, 10, 0)); + assertTrue(between(1, 10, 1)); + assertTrue(between(1, 10, 5)); + assertTrue(between(1, 10, 10)); + assertFalse(between(1, 10, 11)); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BracketTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BracketTest.java new file mode 100644 index 000000000..9e2f373a1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BracketTest.java @@ -0,0 +1,68 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.io.IO; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Bracket.bracket; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.throwsException; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class BracketTest { + + private AtomicInteger count; + + @Before + public void setUp() { + count = new AtomicInteger(0); + } + + @Test + public void cleanupHappyPath() { + IO hashIO = bracket(io(() -> count), c -> io(c::incrementAndGet), c -> io(c::hashCode)); + + assertEquals(0, count.get()); + assertThat(hashIO, yieldsValue(equalTo(count.hashCode()))); + assertEquals(1, count.get()); + } + + @Test + public void cleanupSadPath() { + IllegalStateException thrown = new IllegalStateException("kaboom"); + IO hashIO = bracket(io(count), c -> io(c::incrementAndGet), c -> io(() -> {throw thrown;})); + + assertThat(hashIO, throwsException(equalTo(thrown))); + assertEquals(1, count.get()); + } + + @Test + public void cleanupOnlyRunsIfInitialIORuns() { + IllegalStateException thrown = new IllegalStateException("kaboom"); + IO hashIO = bracket(io(() -> {throw thrown;}), + constantly(io(count::incrementAndGet)), + constantly(io(count::incrementAndGet))); + + assertThat(hashIO, throwsException(equalTo(thrown))); + assertEquals(0, count.get()); + } + + @Test + public void errorsInCleanupAreAddedToBodyErrors() { + IllegalStateException bodyError = new IllegalStateException("kaboom"); + IllegalStateException cleanupError = new IllegalStateException("KABOOM"); + IO hashIO = bracket(io(count), + constantly(io(() -> {throw cleanupError;})), + constantly(io(() -> {throw bodyError;}))); + + assertThat(hashIO, throwsException(equalTo(bodyError))); + assertArrayEquals(new Throwable[]{cleanupError}, bodyError.getSuppressed()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/ClampTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/ClampTest.java new file mode 100644 index 000000000..9b2bedde4 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/ClampTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Clamp.clamp; +import static org.junit.Assert.assertEquals; + +public class ClampTest { + + @Test + public void clampsValueBetweenBounds() { + assertEquals((Integer) 5, clamp(1, 10, 5)); + assertEquals((Integer) 1, clamp(1, 10, -1)); + assertEquals((Integer) 10, clamp(1, 10, 11)); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqByTest.java new file mode 100644 index 000000000..4754ecf3e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqByTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CmpEqByTest { + + @Test + public void comparisons() { + assertTrue(cmpEqBy(id(), 1, 1)); + assertFalse(cmpEqBy(id(), 1, 2)); + assertFalse(cmpEqBy(id(), 2, 1)); + + assertTrue(cmpEqBy(String::length, "b", "a")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java new file mode 100644 index 000000000..8d062f8fd --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CmpEqWithTest { + @Test + public void comparisons() { + assertTrue(cmpEqBy(id(), 1, 1)); + assertFalse(cmpEqBy(id(), 1, 2)); + assertFalse(cmpEqBy(id(), 2, 1)); + + assertTrue(cmpEqBy(String::length, "b", "a")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java new file mode 100644 index 000000000..8817f94fc --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.equal; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.greaterThan; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.lessThan; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertEquals; + +public class CompareTest { + @Test + public void comparisons() { + assertEquals(equal(), compare(naturalOrder(), 1, 1)); + assertEquals(lessThan(), compare(naturalOrder(), 2, 1)); + assertEquals(greaterThan(), compare(naturalOrder(), 1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java index b40915da7..62f6d43c9 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java @@ -1,17 +1,22 @@ package com.jnape.palatable.lambda.functions.builtin.fn3; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.EmptyIterableSupport; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight.foldRight; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; import static testsupport.functions.ExplainFold.explainFold; @RunWith(Traits.class) @@ -19,14 +24,25 @@ public class FoldRightTest { @TestTraits({EmptyIterableSupport.class}) public Fn1, Iterable> createTestSubject() { - return foldRight((o, objects) -> objects, singletonList(new Object())); + return foldRight((o, objects) -> objects, lazy(singletonList(new Object()))).fmap(Lazy::value); } @Test public void foldRightAccumulatesRightToLeft() { - assertThat( - foldRight(explainFold(), "5", asList("1", "2", "3", "4")), - is("(1 + (2 + (3 + (4 + 5))))") + assertThat(foldRight((a, lazyAcc) -> lazyAcc.fmap(acc -> explainFold().apply(a, acc)), + lazy("5"), + asList("1", "2", "3", "4")) + .value(), + is("(1 + (2 + (3 + (4 + 5))))") ); } + + @Test + public void stackSafe() { + Lazy lazy = foldRight((x, lazyY) -> x < STACK_EXPLODING_NUMBER ? lazyY.fmap(y -> y) : lazy(x), + lazy(0), + iterate(x -> x + 1, 0)); + + assertEquals(STACK_EXPLODING_NUMBER, lazy.value()); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTByTest.java new file mode 100644 index 000000000..a04ffb0b6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTByTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTBy.gtBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTByTest { + + @Test + public void comparisons() { + assertTrue(gtBy(id(), 1, 2)); + assertFalse(gtBy(id(), 1, 1)); + assertFalse(gtBy(id(), 2, 1)); + + assertTrue(gtBy(String::length, "bb", "aaa")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEByTest.java new file mode 100644 index 000000000..438d0cb84 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEByTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEBy.gteBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTEByTest { + + @Test + public void comparisons() { + assertTrue(gteBy(id(), 1, 2)); + assertTrue(gteBy(id(), 1, 1)); + assertFalse(gteBy(id(), 2, 1)); + + assertTrue(gteBy(String::length, "b", "ab")); + assertTrue(gteBy(String::length, "bc", "ab")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java new file mode 100644 index 000000000..4ccaac4ec --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEWith.gteWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTEWithTest { + @Test + public void comparisons() { + assertTrue(gteWith(naturalOrder(), 1, 2)); + assertTrue(gteWith(naturalOrder(), 1, 1)); + assertFalse(gteWith(naturalOrder(), 2, 1)); + + assertTrue(gteWith(comparing(String::length), "b", "ab")); + assertTrue(gteWith(comparing(String::length), "bc", "ab")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java new file mode 100644 index 000000000..4520ee272 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertFalse; + +public class GTWithTest { + @Test + public void comparisons() { + assertTrue(gtWith(naturalOrder(), 1, 2)); + assertFalse(gtWith(naturalOrder(), 1, 1)); + assertFalse(gtWith(naturalOrder(), 2, 1)); + + assertTrue(gtWith(comparing(String::length), "bb", "aaa")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTByTest.java new file mode 100644 index 000000000..dc511d7cf --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTByTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTBy.ltBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTByTest { + + @Test + public void comparisons() { + assertTrue(ltBy(id(), 2, 1)); + assertFalse(ltBy(id(), 1, 1)); + assertFalse(ltBy(id(), 1, 2)); + + assertTrue(ltBy(String::length, "ab", "b")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEByTest.java new file mode 100644 index 000000000..122c76070 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEByTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEBy.lteBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTEByTest { + + @Test + public void comparisons() { + assertTrue(lteBy(id(), 2, 1)); + assertTrue(lteBy(id(), 1, 1)); + assertFalse(lteBy(id(), 1, 2)); + + assertTrue(lteBy(String::length, "ab", "b")); + assertTrue(lteBy(String::length, "ab", "bc")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java new file mode 100644 index 000000000..18f749c16 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEWith.lteWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTEWithTest { + @Test + public void comparisons() { + assertTrue(lteWith(naturalOrder(), 2, 1)); + assertTrue(lteWith(naturalOrder(), 1, 1)); + assertFalse(lteWith(naturalOrder(), 1, 2)); + + assertTrue(lteWith(comparing(String::length), "ab", "b")); + assertTrue(lteWith(comparing(String::length), "ab", "bc")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java new file mode 100644 index 000000000..dacdfd2ff --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTWithTest { + @Test + public void comparisons() { + assertTrue(ltWith(naturalOrder(), 2, 1)); + assertFalse(ltWith(naturalOrder(), 1, 1)); + assertFalse(ltWith(naturalOrder(), 1, 2)); + + assertTrue(ltWith(comparing(String::length), "ab", "b")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java index 93556ebe9..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 @@ -1,22 +1,45 @@ package com.jnape.palatable.lambda.functions.builtin.fn3; -import com.jnape.palatable.lambda.functor.builtin.Identity; +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.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2.liftA2; import static org.junit.Assert.assertEquals; public class LiftA2Test { @Test - public void liftsAndAppliesDyadicFunctionToTwoApplicatives() { - BiFunction add = (x, y) -> x + y; - assertEquals(right(3), liftA2(add, right(1), right(2)).coerce()); - assertEquals(tuple(1, 5), liftA2(add, tuple(1, 2), tuple(2, 3)).coerce()); - assertEquals(new Identity<>(3), liftA2(add, new Identity<>(1), new Identity<>(2))); + public void inference() { + Fn2 add = Integer::sum; + + Maybe a = liftA2(add, just(1), just(2)); + assertEquals(just(3), a); + + Maybe b = liftA2(add, just(1), nothing()); + assertEquals(nothing(), b); + + Maybe c = liftA2(add, nothing(), just(2)); + assertEquals(nothing(), c); + + Maybe d = liftA2(add, nothing(), nothing()); + assertEquals(nothing(), d); + + Either e = liftA2(add, Either.right(1), right(2)); + assertEquals(right(3), e); + + Either f = liftA2(add, left("error"), right(2)); + assertEquals(left("error"), f); + + Either g = liftA2(add, right(1), left("error")); + assertEquals(left("error"), g); + + Either h = liftA2(add, left("error"), left("another error")); + assertEquals(left("error"), h); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/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/LiftA3Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3Test.java new file mode 100644 index 000000000..e6f65f814 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn4.LiftA3.liftA3; +import static org.junit.Assert.assertEquals; + +public class LiftA3Test { + + @Test + public void lifting() { + assertEquals(just(6), liftA3((a, b, c) -> a + b + c, just(1), just(2), just(3))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimitTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimitTest.java new file mode 100644 index 000000000..b91b92b08 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/RateLimitTest.java @@ -0,0 +1,77 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.iteration.IterationInterruptedException; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.time.InstantRecordingClock; +import testsupport.traits.Deforesting; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; + +import static com.jnape.palatable.lambda.adt.Try.trying; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn4.RateLimit.rateLimit; +import static com.jnape.palatable.lambda.functions.specialized.SideEffect.sideEffect; +import static java.time.Clock.systemUTC; +import static java.time.Duration.ZERO; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; +import static testsupport.matchers.RateLimitedIterationMatcher.iteratesAccordingToRateLimit; + +@RunWith(Traits.class) +public class RateLimitTest { + + private InstantRecordingClock clock; + + @Before + public void setUp() throws Exception { + clock = new InstantRecordingClock(systemUTC()); + } + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, Deforesting.class}) + public Fn1, Iterable> testSubject() { + return rateLimit(systemUTC()::instant, 1L, ZERO); + } + + @Test(expected = IllegalArgumentException.class) + public void lessThanOneLimitIsInvalid() { + rateLimit(clock::instant, 0L, ZERO, emptyList()); + } + + @Test + public void zeroDurationJustIteratesElements() { + assertThat(rateLimit(clock::instant, 1L, ZERO, asList(1, 2, 3)), iterates(1, 2, 3)); + } + + @Test + public void limitPerDurationIsHonoredAccordingToClock() { + Duration duration = Duration.ofMillis(10); + long limit = 2L; + assertThat(rateLimit(clock::instant, limit, duration, asList(1, 2, 3, 4)), + iteratesAccordingToRateLimit(limit, duration, asList(1, 2, 3, 4), clock)); + } + + @Test(timeout = 100, expected = IterationInterruptedException.class) + public void rateLimitingDelayIsInterruptible() throws InterruptedException { + Thread testThread = Thread.currentThread(); + CountDownLatch latch = new CountDownLatch(1); + new Thread(() -> { + trying(sideEffect(latch::await)).orThrow(); + testThread.interrupt(); + }) {{ + start(); + }}; + + rateLimit(clock::instant, 1L, Duration.ofSeconds(1), repeat(1)).forEach(xs -> latch.countDown()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4Test.java new file mode 100644 index 000000000..784e04fb6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4Test.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn5; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn5.LiftA4.liftA4; +import static org.junit.Assert.assertEquals; + +public class LiftA4Test { + + @Test + public void lifting() { + assertEquals(just(10), liftA4((a, b, c, d) -> a + b + c + d, just(1), just(2), just(3), just(4))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5Test.java new file mode 100644 index 000000000..bd131e897 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5Test.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.functions.builtin.fn6; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn6.LiftA5.liftA5; +import static org.junit.Assert.assertEquals; + +public class LiftA5Test { + + @Test + public void lifting() { + assertEquals(just(15), + liftA5((a, b, c, d, e) -> a + b + c + d + e, just(1), just(2), just(3), just(4), just(5))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6Test.java new file mode 100644 index 000000000..76ed29162 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6Test.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.functions.builtin.fn7; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn7.LiftA6.liftA6; +import static org.junit.Assert.assertEquals; + +public class LiftA6Test { + + @Test + public void lifting() { + assertEquals(just(21), + liftA6((a, b, c, d, e, f) -> a + b + c + d + e + f, + just(1), just(2), just(3), just(4), just(5), just(6))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7Test.java new file mode 100644 index 000000000..f81bd6ef0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7Test.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.functions.builtin.fn8; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn8.LiftA7.liftA7; +import static org.junit.Assert.assertEquals; + +public class LiftA7Test { + + @Test + public void lifting() { + assertEquals(just(28), liftA7((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g, + just(1), just(2), just(3), just(4), just(5), just(6), just(7))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java b/src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java new file mode 100644 index 000000000..8b0083661 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.functions.ordering; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.equal; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.greaterThan; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.lessThan; +import static java.lang.Integer.MAX_VALUE; +import static java.lang.Integer.MIN_VALUE; +import static org.junit.Assert.assertEquals; + +public class ComparisonRelationTest { + @Test + public void fromInt() { + assertEquals(greaterThan(), ComparisonRelation.fromInt(1)); + assertEquals(greaterThan(), ComparisonRelation.fromInt(MAX_VALUE)); + + assertEquals(equal(), ComparisonRelation.fromInt(0)); + + assertEquals(lessThan(), ComparisonRelation.fromInt(-1)); + assertEquals(lessThan(), ComparisonRelation.fromInt(MIN_VALUE)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java b/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java index 49b086466..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 @@ -1,24 +1,32 @@ package com.jnape.palatable.lambda.functions.recursion; -import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; 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.traitor.framework.Subjects.subjects; 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 9aa9db414..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,23 +1,24 @@ package com.jnape.palatable.lambda.functions.recursion; import com.jnape.palatable.lambda.adt.hlist.Tuple2; -import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Test; import java.math.BigInteger; -import java.util.function.Function; +import java.util.Map; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; -import static java.math.BigInteger.ONE; -import static org.junit.Assert.assertEquals; import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static java.math.BigInteger.ONE; +import static org.junit.Assert.assertEquals; public class TrampolineTest { - private static final 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/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 144b71d47..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 @@ -1,16 +1,50 @@ package com.jnape.palatable.lambda.functor.builtin; +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.pureMaybe; +import static com.jnape.palatable.lambda.functor.builtin.Compose.pureCompose; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static org.junit.Assert.assertEquals; + @RunWith(Traits.class) public class ComposeTest { @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) - public Compose testSubject() { + public Compose, Identity, Integer> testSubject() { return new Compose<>(new Identity<>(new Identity<>(1))); } + + @Test + public void inference() { + Either> a = new Compose<>(right(just(1))).fmap(x -> x + 1).getCompose(); + assertEquals(right(just(2)), a); + } + + @Test + public void lazyZip() { + assertEquals(new Compose<>(right(just(2))), + new Compose<>(right(just(1))).lazyZip(lazy(new Compose<>(right(just(x -> x + 1))))).value()); + assertEquals(new Compose<>(left("foo")), + new Compose<>(left("foo")).lazyZip(lazy(() -> { + throw new AssertionError(); + })).value()); + } + + @Test + public void staticPure() { + Compose, Maybe, Integer> compose = pureCompose(pureIdentity(), pureMaybe()).apply(1); + assertEquals(new Compose<>(new Identity<>(just(1))), compose); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java index 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 61% 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 c9e9faf61..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,22 +1,15 @@ -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.monoid.builtin.Concat; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; -import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.Deforesting; -import static com.jnape.palatable.lambda.adt.Maybe.just; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; import static com.jnape.palatable.traitor.framework.Subjects.subjects; -import static java.util.Arrays.asList; import static java.util.Collections.emptyList; -import static org.junit.Assert.assertEquals; @RunWith(Traits.class) public class ConcatenatingIterableTest { @@ -28,10 +21,4 @@ public Subjects, Iterable>> testSubject() { xs -> new ConcatenatingIterable<>(repeat(1), xs), xs -> new ConcatenatingIterable<>(xs, repeat(1))); } - - @Test - public void stackSafety() { - Iterable xs = Concat.concat().reduceLeft(replicate(1_000_000, asList(1, 2, 3))); - assertEquals(just(3), last(xs)); - } } \ No newline at end of file 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/internal/iteration/UnioningIterableTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterableTest.java new file mode 100644 index 000000000..b28bc2b61 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterableTest.java @@ -0,0 +1,24 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Collections.emptyList; + +@RunWith(Traits.class) +public class UnioningIterableTest { + @TestTraits({Deforesting.class}) + public Subjects, Iterable>> testSubject() { + return subjects(xs -> new UnioningIterable<>(emptyList(), xs), + xs -> new UnioningIterable<>(xs, emptyList()), + xs -> new UnioningIterable<>(repeat(1), xs), + xs -> new UnioningIterable<>(xs, repeat(1))); + } + +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/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 8ae51c840..25b10aca9 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AndTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AndTest.java @@ -2,6 +2,9 @@ import org.junit.Test; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; import static com.jnape.palatable.lambda.monoid.builtin.And.and; import static org.junit.Assert.assertEquals; @@ -20,4 +23,15 @@ public void monoid() { assertEquals(false, and.apply(true, false)); assertEquals(false, and.apply(false, false)); } + + @Test(timeout = 500) + public void shortCircuiting() { + Iterable bools = cons(false, repeat(true)); + And and = and(); + + assertEquals(false, and.foldLeft(false, bools)); + assertEquals(false, and.foldLeft(true, bools)); + assertEquals(false, and.reduceLeft(bools)); + assertEquals(false, and.foldMap(id(), bools)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java index 1e0f36e9e..5c42de619 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java @@ -5,14 +5,15 @@ import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.monoid.builtin.Collapse.collapse; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; import static org.junit.Assert.assertEquals; public class CollapseTest { @Test public void monoid() { - Monoid join = Monoid.monoid((x, y) -> x + y, ""); - Monoid add = Monoid.monoid((x, y) -> x + y, 0); + Monoid join = join(); + Monoid add = Monoid.monoid(Integer::sum, 0); Collapse collapse = collapse(); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java 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 4851243ba..4b5ee5960 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java @@ -1,9 +1,12 @@ package com.jnape.palatable.lambda.monoid.builtin; +import com.jnape.palatable.lambda.adt.Maybe; import org.junit.Test; import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.monoid.builtin.First.first; import static org.junit.Assert.assertEquals; @@ -22,4 +25,15 @@ public void monoid() { assertEquals(just(2), first.apply(nothing(), just(2))); assertEquals(nothing(), first.apply(nothing(), nothing())); } + + @Test(timeout = 500) + public void shortCircuiting() { + Iterable> maybeInts = repeat(just(1)); + First first = First.first(); + + assertEquals(just(1), first.foldLeft(nothing(), maybeInts)); + assertEquals(just(1), first.foldLeft(just(1), maybeInts)); + assertEquals(just(1), first.reduceLeft(maybeInts)); + assertEquals(just(1), first.foldMap(id(), maybeInts)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java index dc6692ed0..24426ca71 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java @@ -13,7 +13,7 @@ public class LeftAnyTest { @Test public void monoid() { LeftAny leftAny = leftAny(); - Monoid join = Monoid.monoid((x, y) -> x + y, ""); + Monoid join = Monoid.monoid((x, y) -> x + y, ""); assertEquals(left(""), leftAny.apply(join).identity()); assertEquals(left("foo"), leftAny.apply(join).apply(left("foo"), right(1))); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java new file mode 100644 index 000000000..0bf2d978b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.singletonHMap; +import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; +import static com.jnape.palatable.lambda.monoid.builtin.MergeHMaps.mergeHMaps; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class MergeHMapsTest { + + @Test + public void allKeysAccountedFor() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + MergeHMaps mergeHMaps = mergeHMaps().key(stringKey, join()); + + assertEquals(emptyHMap(), mergeHMaps.apply(emptyHMap(), emptyHMap())); + assertEquals(singletonHMap(stringKey, "foo"), + mergeHMaps.apply(singletonHMap(stringKey, "foo"), emptyHMap())); + assertEquals(singletonHMap(stringKey, "foobar"), + mergeHMaps.apply(singletonHMap(stringKey, "foo"), + singletonHMap(stringKey, "bar"))); + } + + @Test + public void unaccountedForKeyUsesLastByDefault() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + + assertEquals(singletonHMap(stringKey, "foo"), + mergeHMaps().apply(singletonHMap(stringKey, "foo"), emptyHMap())); + assertEquals(singletonHMap(stringKey, "bar"), + mergeHMaps().apply(emptyHMap(), singletonHMap(stringKey, "bar"))); + assertEquals(singletonHMap(stringKey, "bar"), + mergeHMaps().apply(singletonHMap(stringKey, "foo"), singletonHMap(stringKey, "bar"))); + } + + @Test + public void sparseKeysAcrossMaps() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey.Simple intKey = typeSafeKey(); + TypeSafeKey.Simple boolKey = typeSafeKey(); + + MergeHMaps mergeHMaps = mergeHMaps() + .key(stringKey, join()) + .key(intKey, Integer::sum); + + assertEquals(hMap(stringKey, "foobar", + intKey, 3, + boolKey, false), + mergeHMaps.reduceLeft(asList(singletonHMap(stringKey, "foo"), + singletonHMap(intKey, 1), + singletonHMap(boolKey, true), + hMap(stringKey, "bar", + intKey, 2, + boolKey, false)))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java new file mode 100644 index 000000000..fe27f4833 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java @@ -0,0 +1,44 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToMap.toMap; +import static com.jnape.palatable.lambda.monoid.builtin.MergeMaps.mergeMaps; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class MergeMapsTest { + private static final Semigroup ADD = Integer::sum; + + private Monoid> merge; + + @Before + public void setUp() { + merge = mergeMaps(HashMap::new, ADD); + } + + @Test + public void identity() { + assertTrue(merge.identity().isEmpty()); + } + + @Test + public void monoid() { + assertEquals(singletonMap("foo", 1), merge.apply(emptyMap(), singletonMap("foo", 1))); + assertEquals(singletonMap("foo", 1), merge.apply(singletonMap("foo", 1), emptyMap())); + assertEquals(singletonMap("foo", 2), + merge.apply(singletonMap("foo", 1), singletonMap("foo", 1))); + assertEquals(toMap(HashMap::new, asList(tuple("foo", 1), tuple("bar", 1))), + merge.apply(singletonMap("foo", 1), singletonMap("bar", 1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeTest.java index 2a7fd462d..d64e26542 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeTest.java @@ -19,7 +19,7 @@ public class MergeTest { private Monoid> merge; @Before - public void setUp() throws Exception { + public void setUp() { merge = merge(JOIN, ADD); } diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java index 336a98e4d..0e22e20bd 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java @@ -2,6 +2,9 @@ import org.junit.Test; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; import static com.jnape.palatable.lambda.monoid.builtin.Or.or; import static org.junit.Assert.assertEquals; @@ -20,4 +23,15 @@ public void monoid() { assertEquals(true, or.apply(false, true)); assertEquals(false, or.apply(false, false)); } + + @Test(timeout = 500) + public void shortCircuiting() { + Iterable bools = cons(true, repeat(false)); + Or or = or(); + + assertEquals(true, or.foldLeft(false, bools)); + assertEquals(true, or.foldLeft(true, bools)); + assertEquals(true, or.reduceLeft(bools)); + assertEquals(true, or.foldMap(id(), bools)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java index 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 new file mode 100644 index 000000000..c7460e53e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RunAllTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.monoid.Monoid; +import org.junit.Test; + +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monoid.builtin.RunAll.runAll; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class RunAllTest { + + @Test + public void monoid() { + Monoid add = Monoid.monoid(Integer::sum, 0); + assertThat(runAll(add).apply(io(1), io(2)), yieldsValue(equalTo(3))); + assertThat(runAll(add).identity(), yieldsValue(equalTo(0))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/TrivialTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/TrivialTest.java new file mode 100644 index 000000000..da28c59f6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/TrivialTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.monoid.builtin.Trivial.trivial; +import static org.junit.Assert.assertEquals; + +public class TrivialTest { + + @Test + public void triviality() { + assertEquals(UNIT, trivial().identity()); + assertEquals(UNIT, trivial().apply(UNIT, UNIT)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/UnionTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/UnionTest.java new file mode 100644 index 000000000..5a423c679 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/UnionTest.java @@ -0,0 +1,44 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.monoid.builtin.Union.union; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class UnionTest { + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Deforesting.class}) + public Subjects, Iterable>> testSubject() { + return subjects(union(asList(1, 2, 3)), Union.union().flip().apply(asList(1, 2, 3))); + } + + @Test + public void monoid() { + assertThat(union().identity(), isEmpty()); + + assertThat(union(emptyList(), emptyList()), isEmpty()); + assertThat(union(asList(1, 2), emptyList()), iterates(1, 2)); + assertThat(union(emptyList(), singletonList(3)), iterates(3)); + assertThat(union(asList(1, 2), singletonList(3)), iterates(1, 2, 3)); + assertThat(union(asList(1, 2, 2), singletonList(3)), iterates(1, 2, 3)); + assertThat(union(asList(1, 2), asList(1, 2, 3)), iterates(1, 2, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/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 53% 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 2ab9e8028..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,10 +108,17 @@ 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")); assertEquals("133", set(both(stringToInt, stringToChar), tuple(3, '3'), "1")); } + + @Test + public void toIso() { + Iso, Set, String, Integer> iso = LENS.toIso(singletonList("")); + assertEquals("1", view(iso, asList("1", "2", "3"))); + assertEquals(singleton(1), view(iso.mirror(), 1)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/OpticTest.java b/src/test/java/com/jnape/palatable/lambda/optics/OpticTest.java new file mode 100644 index 000000000..00a550185 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/OpticTest.java @@ -0,0 +1,44 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Tagged; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.optics.Optic.optic; +import static org.junit.Assert.assertEquals; + +public class OpticTest { + + @Test + public void monomorphize() { + Optic, Identity, String, String, String, String> optic = optic(pafb -> pafb); + Fn1>, Tagged>> monomorphize = optic.monomorphize(); + assertEquals(new Identity<>("foo"), monomorphize.apply(new Tagged<>(new Identity<>("foo"))).unTagged()); + } + + @Test + public void reframe() { + Optic, Functor, String, String, String, String> optic = optic(pafb -> pafb); + Optic, Identity, String, String, String, String> reframed = Optic.reframe(optic); + assertEquals(new Identity<>("foo"), + reframed., Identity, Identity, Identity, + Fn1>, + Fn1>>apply(constantly(new Identity<>("foo"))).apply("bar")); + } + + @Test + public void adapt() { + Optic, Functor, String, String, String, String> optic = optic(pafb -> pafb); + Optic.Simple, Functor, String, String> simple = Optic.Simple.adapt(optic); + + assertEquals("foo", + simple., Identity, Identity, Identity, + Fn1>, + Fn1>>apply(constantly(new Identity<>("foo"))) + .apply("bar").runIdentity()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java new file mode 100644 index 000000000..22e2c5541 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java @@ -0,0 +1,101 @@ +package com.jnape.palatable.lambda.optics; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.optics.Prism.prism; +import static com.jnape.palatable.lambda.optics.Prism.simplePrism; +import static com.jnape.palatable.lambda.optics.functions.Matching.matching; +import static com.jnape.palatable.lambda.optics.functions.Pre.pre; +import static com.jnape.palatable.lambda.optics.functions.Re.re; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static testsupport.assertion.PrismAssert.assertPrismLawfulness; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class PrismTest { + + private static final Fn1 PARSE_INT = Fn1.fn1(Integer::parseInt); + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Equivalence> testSubject() { + return equivalence(Prism.fromPartial(Integer::parseInt, Object::toString), + prism -> matching(prism, "foo")); + } + + @Test + public void prismLaws() { + Prism prism = prism(PARSE_INT.choose(), Object::toString); + + assertEquals(just(1), view(pre(prism), view(re(prism), 1))); + assertEquals(just(123), view(pre(prism), "123").filter(a -> view(re(prism), a).equals("123"))); + assertEquals(left("foo"), matching(prism, "foo").match(t -> matching(prism, t), Either::right)); + } + + @Test + @SuppressWarnings("unused") + public void simplePrismInference() { + Prism.Simple simplePrism = simplePrism(PARSE_INT.choose().fmap(CoProduct2::projectB), + Object::toString); + } + + @Test + public void unPrismExtractsMappings() { + Prism prism = prism(PARSE_INT.choose(), Object::toString); + Fn1 is = prism.unPrism()._1(); + Fn1> sis = prism.unPrism()._2(); + + assertEquals("123", is.apply(123)); + assertEquals(right(123), sis.apply("123")); + assertEquals(left("foo"), sis.apply("foo")); + } + + @Test + public void andThen() { + Prism.Simple stringFloat = Prism.Simple.fromPartial(Float::parseFloat, Object::toString); + Prism.Simple floatInt = simplePrism(f -> just(f.intValue()).filter(i -> eq(i.floatValue(), f)), + Integer::floatValue); + Prism composed = stringFloat.andThen(floatInt); + + assertPrismLawfulness(composed, singletonList("1.2"), singletonList(1)); + assertPrismLawfulness(composed, singletonList("1.0"), singletonList(1)); + assertPrismLawfulness(composed, singletonList("foo"), emptyList()); + } + + @Test + public void composed() { + Prism.Simple stringFloat = Prism.Simple.fromPartial(Float::parseFloat, Object::toString); + Prism.Simple floatInt = simplePrism(f -> just(f.intValue()).filter(i -> eq(i.floatValue(), f)), + Integer::floatValue); + Prism composed = floatInt.compose(stringFloat); + + assertPrismLawfulness(composed, singletonList("1.2"), singletonList(1)); + assertPrismLawfulness(composed, singletonList("1.0"), singletonList(1)); + assertPrismLawfulness(composed, singletonList("foo"), emptyList()); + } + + @Test + public void staticPure() { + Prism prism = Prism.purePrism().apply('1'); + assertEquals(left('1'), matching(prism, "foo")); + assertEquals((Character) '1', set(prism, true, "bar")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/OverTest.java similarity index 68% rename from src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/functions/OverTest.java index e5a95cf4a..ef2adf7a6 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/functions/OverTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/OverTest.java @@ -1,13 +1,13 @@ -package com.jnape.palatable.lambda.lens.functions; +package com.jnape.palatable.lambda.optics.functions; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.optics.Lens; import org.junit.Test; import java.util.List; import java.util.Set; -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.functions.Over.over; +import static com.jnape.palatable.lambda.optics.Lens.lens; +import static com.jnape.palatable.lambda.optics.functions.Over.over; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/com/jnape/palatable/lambda/optics/functions/PreTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/PreTest.java new file mode 100644 index 000000000..d697108ee --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/PreTest.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Prism; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.optics.Iso.iso; +import static com.jnape.palatable.lambda.optics.Prism.prism; +import static com.jnape.palatable.lambda.optics.functions.Pre.pre; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.lang.Integer.parseInt; +import static org.junit.Assert.assertEquals; + +public class PreTest { + + @Test + public void focusOnAtMostOneValue() { + Iso iso = iso(Integer::parseInt, Object::toString); + Prism prism = prism(s -> Either.trying(() -> parseInt(s), + constantly(s)), + Object::toString); + assertEquals(just(1), view(pre(prism), "1")); + assertEquals(nothing(), view(pre(prism), "foo")); + assertEquals(just(1), view(pre(iso), "1")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/functions/ReTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/ReTest.java new file mode 100644 index 000000000..a6d4fb900 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/ReTest.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.optics.functions; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.optics.Iso; +import com.jnape.palatable.lambda.optics.Prism; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.optics.Iso.iso; +import static com.jnape.palatable.lambda.optics.Prism.prism; +import static com.jnape.palatable.lambda.optics.functions.Re.re; +import static com.jnape.palatable.lambda.optics.functions.View.view; +import static java.lang.Integer.parseInt; +import static org.junit.Assert.assertEquals; + +public class ReTest { + + @Test + public void flipAroundIsoAndPrism() { + Iso iso = iso(Integer::parseInt, Object::toString); + Prism prism = prism(s -> Either.trying(() -> parseInt(s), + constantly(s)), + Object::toString); + assertEquals("1", view(re(prism), 1)); + assertEquals("1", view(re(iso), 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java b/src/test/java/com/jnape/palatable/lambda/optics/functions/SetTest.java similarity index 67% rename from src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java rename to src/test/java/com/jnape/palatable/lambda/optics/functions/SetTest.java index 47fa8a959..11a6bdcc5 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/functions/SetTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/functions/SetTest.java @@ -1,12 +1,12 @@ -package com.jnape.palatable.lambda.lens.functions; +package com.jnape.palatable.lambda.optics.functions; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.optics.Lens; import org.junit.Test; import java.util.List; -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.optics.Lens.lens; +import static com.jnape.palatable.lambda.optics.functions.Set.set; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static org.junit.Assert.assertEquals; diff --git a/src/test/java/com/jnape/palatable/lambda/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 63% 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 ff5e30327..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,15 +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.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.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; @@ -51,4 +53,19 @@ public void tail() { assertThat(over(tail, map(x -> x + 1), emptyList()), isEmpty()); assertThat(over(tail, map(x -> x + 1), asList(1, 2, 3)), iterates(1, 3, 4)); } + + @Test + public void mapping() { + Iso.Simple, Iterable> iso = IterableLens.mapping(simpleIso(Integer::parseInt, Object::toString)); + + assertThat(view(iso, emptyList()), isEmpty()); + assertThat(view(iso, singletonList("1")), iterates(1)); + assertThat(view(iso, asList("1", "2", "3")), iterates(1, 2, 3)); + + assertThat(set(iso, emptyList(), emptyList()), isEmpty()); + assertThat(set(iso, singletonList(1), emptyList()), iterates("1")); + assertThat(set(iso, singletonList(2), singletonList("1")), iterates("2")); + assertThat(set(iso, asList(1, 2, 3), singletonList("1")), iterates("1", "2", "3")); + assertThat(set(iso, emptyList(), singletonList("1")), isEmpty()); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/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 72% 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 dc18edd90..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,21 +1,21 @@ -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; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; -import static com.jnape.palatable.lambda.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.lens.lenses.MapLens.valueAt; +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; @@ -27,11 +27,13 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertThat; import static testsupport.assertion.LensAssert.assertLensLawfulness; +import static testsupport.matchers.IterableMatcher.iterates; +@SuppressWarnings("serial") public class MapLensTest { @Test - public void asCopyFocusesOnMapThroughCopy() { + public void asCopy() { assertLensLawfulness(MapLens.asCopy(), asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ put("foo", 1); @@ -46,8 +48,29 @@ public void asCopyFocusesOnMapThroughCopy() { } @Test - public void valueAtFocusesOnValueAtKey() { - assertLensLawfulness(valueAt("foo"), + public void asCopyWithCopyFn() { + assertLensLawfulness(MapLens.asCopy(LinkedHashMap::new), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }})); + + assertThat(view(MapLens.asCopy(LinkedHashMap::new), new LinkedHashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}).keySet(), iterates("foo", "bar", "baz")); + } + + @Test + public void valueAt() { + assertLensLawfulness(MapLens.valueAt("foo"), asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ put("foo", 1); put("bar", 2); @@ -57,8 +80,20 @@ public void valueAtFocusesOnValueAtKey() { } @Test - public void valueAtWithDefaultValueFocusedOnValueAtKey() { - Lens.Simple, Integer> atFoo = valueAt("foo", -1); + public void valueAtWithCopyFn() { + assertLensLawfulness(MapLens.valueAt("foo"), + asList(emptyMap(), singletonMap("foo", 1), new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}), + asList(nothing(), just(1))); + } + + + @Test + public void valueAtWithDefaultValue() { + Lens.Simple, Integer> atFoo = MapLens.valueAt("foo", -1); assertEquals((Integer) 1, view(atFoo, new HashMap() {{ put("foo", 1); 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/optics/lenses/SetLensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/lenses/SetLensTest.java new file mode 100644 index 000000000..6beff4b53 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/lenses/SetLensTest.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.optics.lenses; + +import org.junit.Test; + +import java.util.HashSet; +import java.util.TreeSet; + +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + +public class SetLensTest { + + @Test + public void containsWithCopyFn() { + assertLensLawfulness(SetLens.contains(HashSet::new, 1), + asList(emptySet(), + singleton(1), + singleton(2), + new HashSet<>(asList(1, 2)), + new HashSet<>(asList(2, 3))), + asList(true, false)); + assertThat(set(SetLens.contains(TreeSet::new, 1), true, emptySet()), instanceOf(TreeSet.class)); + } + + @Test + public void containsWithoutCopyFn() { + assertLensLawfulness(SetLens.contains(1), + asList(emptySet(), + singleton(1), + singleton(2), + new HashSet<>(asList(1, 2)), + new HashSet<>(asList(2, 3))), + asList(true, false)); + assertThat(set(SetLens.contains(1), true, emptySet()), instanceOf(HashSet.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/prisms/EitherPrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/prisms/EitherPrismTest.java new file mode 100644 index 000000000..cb1e09357 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/prisms/EitherPrismTest.java @@ -0,0 +1,26 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static testsupport.assertion.PrismAssert.assertPrismLawfulness; + +public class EitherPrismTest { + + @Test + public void _right() { + assertPrismLawfulness(EitherPrism._right(), + asList(left("foo"), right(1)), + singleton(1)); + } + + @Test + public void _left() { + assertPrismLawfulness(EitherPrism._left(), + asList(left("foo"), right(1)), + singleton("foo")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/prisms/MapPrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/prisms/MapPrismTest.java new file mode 100644 index 000000000..7ba1dd09f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/prisms/MapPrismTest.java @@ -0,0 +1,33 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.LinkedHashMap; + +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static java.util.Collections.singletonMap; +import static testsupport.assertion.PrismAssert.assertPrismLawfulness; + +public class MapPrismTest { + + @Test + public void valueAtWithConstructor() { + assertPrismLawfulness(MapPrism.valueAt(LinkedHashMap::new, "foo"), + asList(new LinkedHashMap<>(), + new LinkedHashMap<>(singletonMap("foo", 1)), + new LinkedHashMap<>(singletonMap("bar", 2))), + singleton(1)); + } + + @Test + public void valueAtWithoutConstructor() { + assertPrismLawfulness(MapPrism.valueAt("foo"), + asList(new HashMap<>(), + new HashMap<>(singletonMap("foo", 1)), + new HashMap<>(singletonMap("bar", 2))), + singleton(1)); + } + +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/prisms/MaybePrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/prisms/MaybePrismTest.java new file mode 100644 index 000000000..5d772a6c5 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/prisms/MaybePrismTest.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static testsupport.assertion.PrismAssert.assertPrismLawfulness; + +public class MaybePrismTest { + + @Test + public void _just() { + assertPrismLawfulness(MaybePrism._just(), + asList(just(1), nothing()), + singleton(1)); + } + + @Test + public void _nothing() { + assertPrismLawfulness(MaybePrism._nothing(), + asList(just(1), nothing()), + singleton(UNIT)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrismTest.java new file mode 100644 index 000000000..24bf6421a --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrismTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.optics.prisms; + +import org.junit.Test; + +import java.util.UUID; + +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static testsupport.assertion.PrismAssert.assertPrismLawfulness; + +public class UUIDPrismTest { + + @Test + public void uuid() { + UUID uuid = UUID.randomUUID(); + assertPrismLawfulness(UUIDPrism.uuid(), + asList("", "123", uuid.toString()), + singleton(uuid)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java index 94697cda3..712c09e02 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java @@ -1,22 +1,22 @@ package com.jnape.palatable.lambda.semigroup; -import com.jnape.palatable.lambda.semigroup.Semigroup; import org.junit.Test; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static testsupport.functions.ExplainFold.explainFold; public class SemigroupTest { @Test public void foldLeft() { - Semigroup sum = (x, y) -> x + y; - assertEquals((Integer) 6, sum.foldLeft(0, asList(1, 2, 3))); + Semigroup foldFn = explainFold()::apply; + assertEquals("(((0 + 1) + 2) + 3)", foldFn.foldLeft("0", asList("1", "2", "3"))); } @Test public void foldRight() { - Semigroup sum = (x, y) -> x + y; - assertEquals((Integer) 6, sum.foldRight(0, asList(1, 2, 3))); + Semigroup foldFn = explainFold()::apply; + assertEquals("(1 + (2 + (3 + 0)))", foldFn.foldRight("0", asList("1", "2", "3")).value()); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java new file mode 100644 index 000000000..9d3dc4524 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java @@ -0,0 +1,102 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.builtin.fn1.Constantly; +import com.jnape.palatable.lambda.semigroup.Semigroup; +import org.junit.Test; + +import java.util.Arrays; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.semigroup.builtin.Absent.absent; +import static org.junit.Assert.assertEquals; + +public class AbsentTest { + + @Test + public void semigroup() { + Semigroup addition = Integer::sum; + + assertEquals(just(3), absent(addition, just(1), just(2))); + assertEquals(nothing(), absent(addition, nothing(), just(1))); + assertEquals(nothing(), absent(addition, just(1), nothing())); + assertEquals(nothing(), absent(addition, nothing(), nothing())); + } + + @Test + public void foldRight() { + Absent absent = absent(); + Semigroup addition = Integer::sum; + + assertEquals(just(3), absent.apply(addition).foldRight(just(0), Arrays.asList(just(1), just(2))).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(nothing(), just(1))).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(just(1), nothing())).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(nothing(), nothing())).value()); + } + + @Test + public void foldLeft() { + Absent absent = absent(); + Semigroup addition = Integer::sum; + + assertEquals(just(3), absent.apply(addition).foldLeft(just(0), Arrays.asList(just(1), just(2)))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(nothing(), just(1)))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(just(1), nothing()))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(nothing(), nothing()))); + } + + @Test(timeout = 200) + public void foldRightShortCircuit() { + Maybe result = Absent.absent(Constantly::constantly) + .foldRight(just(UNIT), repeat(nothing())).value(); + assertEquals(nothing(), result); + + result = Absent.absent(Constantly::constantly) + .foldRight(nothing(), repeat(just(UNIT))).value(); + assertEquals(nothing(), result); + } + + @Test(timeout = 200) + public void foldLeftShortCircuit() { + Maybe result = Absent.absent(Constantly::constantly) + .foldLeft(just(UNIT), repeat(nothing())); + assertEquals(nothing(), result); + + result = Absent.absent(Constantly::constantly) + .foldLeft(nothing(), repeat(just(UNIT))); + assertEquals(nothing(), result); + } + + @Test + public void foldLeftWorksForJusts() { + Maybe result = Absent.absent(Constantly::constantly) + .foldLeft(just(UNIT), Arrays.asList(just(UNIT), just(UNIT))); + assertEquals(just(UNIT), result); + } + + @Test(timeout = 200) + public void checkedApplyFoldRightShortCircuit() { + Maybe result = Absent.absent().checkedApply(Constantly::constantly) + .foldRight(just(UNIT), repeat(nothing())).value(); + assertEquals(nothing(), result); + + result = Absent.absent().checkedApply(Constantly::constantly) + .foldRight(nothing(), repeat(just(UNIT))).value(); + assertEquals(nothing(), result); + } + + @Test(timeout = 200) + public void checkedApplyFoldLeftShortCircuit() { + Maybe result = Absent.absent().checkedApply(Constantly::constantly) + .foldLeft(just(UNIT), repeat(nothing())); + assertEquals(nothing(), result); + + result = Absent.absent().checkedApply(Constantly::constantly) + .foldLeft(nothing(), repeat(just(UNIT))); + assertEquals(nothing(), result); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AddAllTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AddAllTest.java deleted file mode 100644 index f1840b5de..000000000 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AddAllTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.jnape.palatable.lambda.semigroup.builtin; - -import org.junit.Test; - -import java.util.HashSet; - -import static com.jnape.palatable.lambda.semigroup.builtin.AddAll.addAll; -import static java.util.Collections.singleton; -import static org.junit.Assert.assertEquals; - -public class AddAllTest { - - @Test - public void semigroup() { - assertEquals(new HashSet() {{ - add(1); - add(2); - }}, addAll(new HashSet<>(singleton(1)), new HashSet<>(singleton(2)))); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/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/semigroup/builtin/IntersectionTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/IntersectionTest.java new file mode 100644 index 000000000..0ecd33c71 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/IntersectionTest.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.semigroup.builtin.Intersection.intersection; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class IntersectionTest { + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class}) + public Fn1, Iterable> testSubject() { + return intersection(asList(0, 1, 2, 3)); + } + + @Test + public void intersectionOfEmptyOnEitherSideIsEmpty() { + assertThat(intersection(emptyList(), singletonList(1)), isEmpty()); + assertThat(intersection(singletonList(1), emptyList()), isEmpty()); + assertThat(intersection(emptyList(), emptyList()), isEmpty()); + } + + @Test + public void intersectionIsCommonElementsAcrossIterables() { + assertThat(intersection(asList(1, 2, 3), asList(1, 2, 3)), iterates(1, 2, 3)); + assertThat(intersection(asList(1, 2, 3), asList(2, 3, 4)), iterates(2, 3)); + assertThat(intersection(singletonList(1), singletonList(2)), isEmpty()); + assertThat(intersection(asList(1, 2, 3, 3), singletonList(3)), iterates(3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAnyTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAnyTest.java index 98026177b..2e0cd7ea0 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/LeftAnyTest.java @@ -13,7 +13,7 @@ public class LeftAnyTest { @Test public void semigroup() { LeftAny leftAny = leftAny(); - Semigroup join = (x, y) -> x + y; + Semigroup join = (x, y) -> x + y; assertEquals(left("foo"), leftAny.apply(join).apply(left("foo"), right(1))); assertEquals(left("foo"), leftAny.apply(join).apply(right(1), left("foo"))); diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxByTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxByTest.java new file mode 100644 index 000000000..a47fb0f30 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxByTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.semigroup.builtin.MaxBy.maxBy; +import static org.junit.Assert.assertEquals; + +public class MaxByTest { + + @Test + public void semigroup() { + assertEquals((Integer) 1, maxBy(id(), 1, 0)); + assertEquals((Integer) 1, maxBy(id(), 1, 1)); + assertEquals((Integer) 2, maxBy(id(), 1, 2)); + + assertEquals("ab", maxBy(String::length, "ab", "a")); + assertEquals("ab", maxBy(String::length, "ab", "cd")); + assertEquals("bc", maxBy(String::length, "a", "bc")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxTest.java new file mode 100644 index 000000000..5e18a7bcf --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.semigroup.builtin.Max.max; +import static org.junit.Assert.assertEquals; + +public class MaxTest { + + @Test + public void semigroup() { + assertEquals((Integer) 1, max(1, 0)); + assertEquals((Integer) 1, max(1, 1)); + assertEquals((Integer) 2, max(1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java new file mode 100644 index 000000000..49211aee0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.semigroup.builtin.MaxWith.maxWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertEquals; + +public class MaxWithTest { + @Test + public void semigroup() { + assertEquals((Integer) 1, maxWith(naturalOrder(), 1, 0)); + assertEquals((Integer) 1, maxWith(naturalOrder(), 1, 1)); + assertEquals((Integer) 2, maxWith(naturalOrder(), 1, 2)); + + assertEquals("ab", maxWith(comparing(String::length), "ab", "a")); + assertEquals("ab", maxWith(comparing(String::length), "ab", "cd")); + assertEquals("bc", maxWith(comparing(String::length), "a", "bc")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MergeTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MergeTest.java index c867c1302..5cd397cd4 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MergeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MergeTest.java @@ -6,6 +6,7 @@ import static com.jnape.palatable.lambda.adt.Either.left; import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; import static com.jnape.palatable.lambda.semigroup.builtin.Merge.merge; import static org.junit.Assert.assertEquals; @@ -13,8 +14,8 @@ public class MergeTest { @Test public void semigroup() { - Semigroup join = (x, y) -> x + y; - Semigroup add = (x, y) -> x + y; + Semigroup join = join(); + Semigroup add = Integer::sum; Semigroup> merge = merge(join, add); assertEquals(left("onetwo"), merge.apply(left("one"), left("two"))); diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinByTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinByTest.java new file mode 100644 index 000000000..5436b289e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinByTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.semigroup.builtin.MinBy.minBy; +import static org.junit.Assert.assertEquals; + +public class MinByTest { + + @Test + public void semigroup() { + assertEquals((Integer) 1, minBy(id(), 1, 2)); + assertEquals((Integer) 1, minBy(id(), 1, 1)); + assertEquals((Integer) 0, minBy(id(), 1, 0)); + + assertEquals("a", minBy(String::length, "a", "ab")); + assertEquals("ab", minBy(String::length, "ab", "cd")); + assertEquals("c", minBy(String::length, "ab", "c")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinTest.java new file mode 100644 index 000000000..22e4ee3a0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.semigroup.builtin.Min.min; +import static org.junit.Assert.assertEquals; + +public class MinTest { + + @Test + public void semigroup() { + assertEquals((Integer) 1, min(1, 2)); + assertEquals((Integer) 1, min(1, 1)); + assertEquals((Integer) 0, min(1, 0)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java new file mode 100644 index 000000000..e6bfb82bb --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.semigroup.builtin.MinWith.minWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertEquals; + +public class MinWithTest { + @Test + public void semigroup() { + assertEquals((Integer) 1, minWith(naturalOrder(), 1, 2)); + assertEquals((Integer) 1, minWith(naturalOrder(), 1, 1)); + assertEquals((Integer) 0, minWith(naturalOrder(), 1, 0)); + + assertEquals("a", minWith(comparing(String::length), "a", "ab")); + assertEquals("ab", minWith(comparing(String::length), "ab", "cd")); + assertEquals("c", minWith(comparing(String::length), "ab", "c")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java index ec234090e..46d8e5f5a 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java @@ -13,7 +13,7 @@ public class RightAnyTest { @Test public void semigroup() { RightAny rightAny = rightAny(); - Semigroup add = (x, y) -> x + y; + Semigroup add = Integer::sum; assertEquals(right(1), rightAny.apply(add).apply(right(1), left("foo"))); assertEquals(right(1), rightAny.apply(add).apply(left("foo"), right(1))); diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java new file mode 100644 index 000000000..2733690c4 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java @@ -0,0 +1,17 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.semigroup.builtin.RunAll.runAll; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class RunAllTest { + + @Test + public void semigroup() { + assertThat(runAll(Integer::sum).apply(io(1), io(2)), yieldsValue(equalTo(3))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java 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 new file mode 100644 index 000000000..2be8673f7 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaMapTest.java @@ -0,0 +1,29 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; + +import java.util.HashMap; + +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Collections.singletonMap; + +@RunWith(Traits.class) +public class LambdaMapTest { + + @SuppressWarnings("serial") + @TestTraits({FunctorLaws.class, TraversableLaws.class}) + public Subjects> testSubject() { + return subjects(LambdaMap.empty(), + LambdaMap.wrap(singletonMap(1, "foo")), + LambdaMap.wrap(new HashMap() {{ + put(1, "foo"); + put(2, "bar"); + put(3, "baz"); + }})); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java index f27c2b214..1bd8e5fa5 100644 --- a/src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java @@ -15,7 +15,7 @@ public class TraversableTest { @Test - public void compilation() { + public void inference() { Either> a = just(Either.right(1)).traverse(id(), Either::right); assertEquals(right(just(1)), a); 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/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/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/RateLimitedIterationMatcher.java b/src/test/java/testsupport/matchers/RateLimitedIterationMatcher.java new file mode 100644 index 000000000..c0f2d56ab --- /dev/null +++ b/src/test/java/testsupport/matchers/RateLimitedIterationMatcher.java @@ -0,0 +1,68 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.functions.builtin.fn2.Eq; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; +import testsupport.time.InstantRecordingClock; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.All.all; +import static com.jnape.palatable.lambda.functions.builtin.fn2.InGroupsOf.inGroupsOf; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Slide.slide; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Zip.zip; +import static java.time.Duration.between; + +public final class RateLimitedIterationMatcher extends TypeSafeMatcher> { + private final Iterable elements; + private final Duration delay; + private final InstantRecordingClock clock; + private final Long limit; + + public RateLimitedIterationMatcher(Long limit, Duration delay, Iterable elements, InstantRecordingClock clock) { + this.elements = elements; + this.delay = delay; + this.clock = clock; + this.limit = limit; + } + + @Override + protected boolean matchesSafely(Iterable xs) { + xs.forEach(__ -> clock.saveLastInstant()); + + Boolean enoughDelay = all(d -> d.toNanos() > delay.toNanos(), map(boundaries -> { + Iterator it = boundaries.iterator(); + Instant first = it.next(); + Instant second = it.next(); + return between(first, second); + }, slide(2, map(instants -> instants.iterator().next(), inGroupsOf(limit.intValue(), clock.instants()))))); + + Boolean sameElements = all(Eq.eq().uncurry(), zip(elements, xs)); + + return enoughDelay && sameElements; + } + + @Override + public void describeTo(Description description) { + description.appendText("Iterated elements " + toCollection(ArrayList::new, elements) + " with at least " + delay.toMillis() + "ms between groups of " + limit); + } + + @Override + protected void describeMismatchSafely(Iterable item, Description mismatchDescription) { + mismatchDescription.appendText("Iterated elements " + + toCollection(ArrayList::new, item) + + " with the following delays between groups: " + + toCollection(ArrayList::new, map(instants -> instants.iterator().next(), inGroupsOf(limit.intValue(), clock.instants())))); + } + + public static RateLimitedIterationMatcher iteratesAccordingToRateLimit(Long limit, Duration duration, + Iterable elements, + InstantRecordingClock clock) { + return new RateLimitedIterationMatcher<>(limit, duration, elements, clock); + } +} diff --git a/src/test/java/testsupport/matchers/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/time/InstantRecordingClock.java b/src/test/java/testsupport/time/InstantRecordingClock.java new file mode 100644 index 000000000..421971861 --- /dev/null +++ b/src/test/java/testsupport/time/InstantRecordingClock.java @@ -0,0 +1,42 @@ +package testsupport.time; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; + +public final class InstantRecordingClock extends Clock { + private final Clock clock; + private final List instants; + private Instant lastInstant; + + public InstantRecordingClock(Clock clock) { + this.clock = clock; + instants = new ArrayList<>(); + } + + @Override + public ZoneId getZone() { + return clock.getZone(); + } + + @Override + public Clock withZone(ZoneId zone) { + return new InstantRecordingClock(clock.withZone(zone)); + } + + @Override + public Instant instant() { + return lastInstant = clock.instant(); + } + + public Instant saveLastInstant() { + instants.add(lastInstant); + return lastInstant; + } + + public List instants() { + return instants; + } +} diff --git a/src/test/java/testsupport/traits/ApplicativeLaws.java b/src/test/java/testsupport/traits/ApplicativeLaws.java 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 da7ba9e66..eda3b45c2 100644 --- a/src/test/java/testsupport/traits/MonadLaws.java +++ b/src/test/java/testsupport/traits/MonadLaws.java @@ -2,51 +2,66 @@ 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.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 Class> type() { + return Monad.class; + } @Override - public void test(Monad m) { + 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)) - .peek(s -> { - throw new AssertionError("The following Monad laws did not hold for instance of " + m.getClass() + ": \n\t - " + s); - }); + this::testAssociativity, + this::testJoin)) + .match(IO::io, + s -> throwing(new AssertionError("The following Monad laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); } - private Maybe testLeftIdentity(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)) - ? nothing() - : just("left identity (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) - ? nothing() - : just("right identity: (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(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 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))) - ? nothing() - : just("associativity: (m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g))))"); + private Maybe testJoin(Equivalence> equivalence) { + return equivalence.invMap(m -> m.pure(m).flatMap(id())) + .equals(equivalence.invMap(m -> join(m.pure(m)))) + ? nothing() + : just("join: (m.pure(m).flatMap(id())).equals(Monad.join(m.pure(m)))"); } } diff --git a/src/test/java/testsupport/traits/MonadReaderLaws.java b/src/test/java/testsupport/traits/MonadReaderLaws.java new file mode 100644 index 000000000..2553500ae --- /dev/null +++ b/src/test/java/testsupport/traits/MonadReaderLaws.java @@ -0,0 +1,40 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadReader; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static java.util.Collections.singletonList; + +public class MonadReaderLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return MonadReader.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + f -> f.apply(equivalence), + singletonList(this::testLocalIdentity) + ) + .match(IO::io, + s -> throwing(new AssertionError("The following MonadReader laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testLocalIdentity(Equivalence> equivalence) { + return equivalence.invMap(mr -> mr.local(id())).equals(equivalence) + ? nothing() + : just("local identity (mr.local(id()).equals(mr))"); + } +} diff --git a/src/test/java/testsupport/traits/MonadRecLaws.java b/src/test/java/testsupport/traits/MonadRecLaws.java new file mode 100644 index 000000000..00f96eb45 --- /dev/null +++ b/src/test/java/testsupport/traits/MonadRecLaws.java @@ -0,0 +1,43 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static java.util.Collections.singletonList; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; + +public class MonadRecLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return MonadRec.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + fn -> fn.apply(equivalence), + singletonList(this::testStackSafety)) + .match(IO::io, + s -> IO.throwing(new AssertionError("The following MonadRec laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testStackSafety(Equivalence> equivalence) { + return equivalence.invMap(mr -> mr.pure(0) + .trampolineM(x -> mr.pure(x < STACK_EXPLODING_NUMBER + ? RecursiveResult.recurse(x + 1) + : RecursiveResult.terminate(x)))) + .equals(equivalence.invMap(mr -> mr.pure(STACK_EXPLODING_NUMBER))) + ? nothing() + : just("stack-safety (m.pure(" + STACK_EXPLODING_NUMBER + ").equals(m.pure(0).trampolineM(f)))"); + } +} diff --git a/src/test/java/testsupport/traits/MonadWriterLaws.java b/src/test/java/testsupport/traits/MonadWriterLaws.java new file mode 100644 index 000000000..8024535b8 --- /dev/null +++ b/src/test/java/testsupport/traits/MonadWriterLaws.java @@ -0,0 +1,48 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadWriter; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static java.util.Arrays.asList; + +public class MonadWriterLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return MonadWriter.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + f -> f.apply(equivalence), asList( + this::testCensor, + this::testListens) + ) + .match(IO::io, + s -> throwing(new AssertionError("The following MonadWriter laws did not hold for instance" + + " of " + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testCensor(Equivalence> equivalence) { + return equivalence.invMap(mw -> mw.censor(id())).equals(equivalence) + ? nothing() + : just("censor (mw.censor(id()).equals(mw))"); + } + + private Maybe testListens(Equivalence> equivalence) { + return equivalence.invMap(mw -> mw.listens(id()).fmap(Tuple2::_1)).equals(equivalence) + ? nothing() + : just("censor (mw.censor(id()).equals(mw))"); + } +} diff --git a/src/test/java/testsupport/traits/TraversableLaws.java b/src/test/java/testsupport/traits/TraversableLaws.java 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 66% 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 94d94d0b0..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,32 +94,32 @@ 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); - ImmutableStack newOutbound = foldLeft(ImmutableStack::push, ImmutableStack.empty(), inbound); + ImmutableStack newOutbound = foldLeft(ImmutableStack::push, ImmutableStack.empty(), inbound); return newOutbound.isEmpty() ? empty() : new NonEmpty<>(newOutbound, ImmutableStack.empty()); } } diff --git a/src/main/java/com/jnape/palatable/lambda/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 91% 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 1aefe0536..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; @@ -8,7 +8,7 @@ import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; -public class CombinatorialIterator extends ImmutableIterator> { +public final class CombinatorialIterator extends ImmutableIterator> { private final Iterator asIterator; private final Iterator bsIterator; private final ListIterator doublyLinkedBsIterator; diff --git a/src/main/java/com/jnape/palatable/lambda/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 87% 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 ba04f23b8..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,10 +1,10 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.ArrayList; import java.util.Iterator; import java.util.ListIterator; -public class CyclicIterator extends InfiniteIterator { +public final class CyclicIterator extends InfiniteIterator { private final Iterator iterator; private final ListIterator doublyLinkedIterator; diff --git a/src/main/java/com/jnape/palatable/lambda/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 86% 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 e0c543c3f..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,9 +1,9 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; -public class DroppingIterator extends ImmutableIterator { +public final class DroppingIterator extends ImmutableIterator { private final Integer n; private final Iterator asIterator; private boolean dropped; 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 51% 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 bd7be982c..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,22 +1,23 @@ -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(nested.predicates); + predicates.addAll(0, nested.predicates); as = nested.as; } this.predicates = predicates; @@ -25,7 +26,7 @@ public FilteringIterable(Function predicate, Iterable as) @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 64% 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 75f02db95..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 class FilteringIterator extends ImmutableIterator { +public final class FilteringIterator extends ImmutableIterator { - private final Function predicate; - private final RewindableIterator rewindableIterator; + private final Fn1 predicate; + private final RewindableIterator rewindableIterator; - public FilteringIterator(Function predicate, Iterator iterator) { + public FilteringIterator(Fn1 predicate, Iterator iterator) { this.predicate = predicate; rewindableIterator = new RewindableIterator<>(iterator); } diff --git a/src/main/java/com/jnape/palatable/lambda/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 78% 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 6960c87c8..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,10 +1,10 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -public class GroupingIterator extends ImmutableIterator> { +public final class GroupingIterator extends ImmutableIterator> { private final Integer k; private final Iterator asIterator; @@ -21,7 +21,7 @@ public boolean hasNext() { @Override public Iterable next() { List group = new ArrayList<>(); - int i = 0; + int i = 0; while (i++ < k && asIterator.hasNext()) group.add(asIterator.next()); return group; diff --git a/src/main/java/com/jnape/palatable/lambda/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 83% 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 e89ebf1fb..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,9 +1,9 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; -public class InitIterator extends ImmutableIterator { +public final class InitIterator extends ImmutableIterator { private final Iterator asIterator; private A queued; diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/IterationInterruptedException.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/IterationInterruptedException.java new file mode 100644 index 000000000..c52797198 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/IterationInterruptedException.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.builtin.fn4.RateLimit; + +import java.util.Iterator; + +/** + * An exception thrown when a thread is interrupted while an {@link Iterator} was blocked. + * + * @see RateLimit + */ +@SuppressWarnings("serial") +public final class IterationInterruptedException extends RuntimeException { + + public IterationInterruptedException(InterruptedException cause) { + super(cause); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterable.java new file mode 100644 index 000000000..3d2f1ba65 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterable.java @@ -0,0 +1,35 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +import static java.util.Collections.singletonList; + +public final class MappingIterable implements Iterable { + private final Iterable as; + private final List> mappers; + + @SuppressWarnings("unchecked") + public MappingIterable(Fn1 fn, Iterable as) { + List> mappers = new ArrayList<>(singletonList(fn)); + while (as instanceof MappingIterable) { + MappingIterable nested = (MappingIterable) as; + as = (Iterable) nested.as; + mappers.addAll(0, nested.mappers); + } + this.as = as; + this.mappers = mappers; + } + + @Override + @SuppressWarnings("unchecked") + public Iterator iterator() { + Fn1 fnComposedOnTheHeap = a -> foldLeft((x, fn) -> ((Fn1) fn).apply(x), + a, mappers); + return new MappingIterator<>((Fn1) fnComposedOnTheHeap, as.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterator.java new file mode 100644 index 000000000..6fb64fd90 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/MappingIterator.java @@ -0,0 +1,26 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.Iterator; + +public final class MappingIterator extends ImmutableIterator { + + private final Fn1 function; + private final Iterator iterator; + + public MappingIterator(Fn1 function, Iterator iterator) { + this.function = function; + this.iterator = iterator; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public B next() { + return function.apply(iterator.next()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterable.java new file mode 100644 index 000000000..ea7b68bc7 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterable.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.ImmutableQueue; + +import java.util.Iterator; + +public final class PredicatedDroppingIterable implements Iterable { + private final ImmutableQueue> predicates; + private final Iterable as; + + public PredicatedDroppingIterable(Fn1 predicate, Iterable as) { + ImmutableQueue> predicates = ImmutableQueue.singleton(predicate); + while (as instanceof PredicatedDroppingIterable) { + PredicatedDroppingIterable nested = (PredicatedDroppingIterable) as; + as = nested.as; + predicates = nested.predicates.concat(predicates); + } + this.predicates = predicates; + this.as = as; + } + + @Override + public Iterator iterator() { + return new PredicatedDroppingIterator<>(predicates, as.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterator.java new file mode 100644 index 000000000..a97b513ac --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterator.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.ImmutableQueue; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class PredicatedDroppingIterator extends ImmutableIterator { + private final Iterator> predicates; + private final RewindableIterator rewindableIterator; + + public PredicatedDroppingIterator(ImmutableQueue> predicates, Iterator asIterator) { + this.predicates = predicates.iterator(); + rewindableIterator = new RewindableIterator<>(asIterator); + } + + @Override + public boolean hasNext() { + dropElementsIfNecessary(); + return rewindableIterator.hasNext(); + } + + @Override + public A next() { + if (hasNext()) + return rewindableIterator.next(); + + throw new NoSuchElementException(); + } + + private void dropElementsIfNecessary() { + while (predicates.hasNext() && rewindableIterator.hasNext()) { + Fn1 predicate = predicates.next(); + boolean predicateDone = false; + + while (rewindableIterator.hasNext() && !predicateDone) { + if (!predicate.apply(rewindableIterator.next())) { + rewindableIterator.rewind(); + predicateDone = true; + } + } + + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIterable.java similarity index 52% 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 4c80ccdb8..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,23 +1,24 @@ -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); as = nested.as; - predicates.addAll(nested.predicates); } this.predicates = predicates; this.as = as; @@ -25,7 +26,7 @@ public PredicatedTakingIterable(Function predicate, Iterable @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 62% 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 a53684946..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 class PredicatedTakingIterator extends ImmutableIterator { - private final Function predicate; - private final RewindableIterator rewindableIterator; - private boolean stillTaking; +public final class PredicatedTakingIterator extends ImmutableIterator { + private final Fn1 predicate; + private final RewindableIterator rewindableIterator; + private boolean stillTaking; - public PredicatedTakingIterator(Function predicate, - Iterator asIterator) { + public PredicatedTakingIterator(Fn1 predicate, Iterator asIterator) { this.predicate = predicate; rewindableIterator = new RewindableIterator<>(asIterator); stillTaking = true; diff --git a/src/main/java/com/jnape/palatable/lambda/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/internal/iteration/RateLimitingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterable.java new file mode 100644 index 000000000..3a7c856d4 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterable.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.functions.Fn0; + +import java.time.Duration; +import java.time.Instant; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public final class RateLimitingIterable implements Iterable { + private final Iterable as; + private final Set>> rateLimits; + + public RateLimitingIterable(Iterable as, Set>> rateLimits) { + Set>> combinedRateLimits = new HashSet<>(rateLimits); + if (as instanceof RateLimitingIterable) { + RateLimitingIterable inner = (RateLimitingIterable) as; + combinedRateLimits.addAll(inner.rateLimits); + as = inner.as; + } + this.rateLimits = combinedRateLimits; + this.as = as; + } + + @Override + public Iterator iterator() { + return new RateLimitingIterator<>(as.iterator(), rateLimits); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterator.java new file mode 100644 index 000000000..1046f406c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RateLimitingIterator.java @@ -0,0 +1,81 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.adt.Try; +import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.functions.Fn0; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import static com.jnape.palatable.lambda.adt.Try.failure; +import static com.jnape.palatable.lambda.adt.Try.trying; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; +import static com.jnape.palatable.lambda.functions.builtin.fn2.GTE.gte; +import static com.jnape.palatable.lambda.functions.builtin.fn2.LT.lt; +import static com.jnape.palatable.lambda.functions.builtin.fn2.LTE.lte; +import static com.jnape.palatable.lambda.monad.Monad.join; +import static com.jnape.palatable.lambda.semigroup.builtin.Max.max; +import static java.lang.Thread.sleep; +import static java.util.Collections.emptyList; + +public final class RateLimitingIterator implements Iterator { + private final Iterator asIterator; + private final Set>> rateLimits; + private final Map>, List> timeSlicesByRateLimit; + + public RateLimitingIterator(Iterator asIterator, Set>> rateLimits) { + this.asIterator = asIterator; + this.rateLimits = rateLimits; + timeSlicesByRateLimit = new HashMap<>(); + } + + @Override + public boolean hasNext() { + return asIterator.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + awaitNextTimeSlice(); + return asIterator.next(); + } + + private void awaitNextTimeSlice() { + rateLimits.forEach(rateLimit -> { + awaitNextTimeSliceForRateLimit(rateLimit); + List timeSlicesForRateLimit = timeSlicesByRateLimit.getOrDefault(rateLimit, new ArrayList<>()); + timeSlicesForRateLimit.add(rateLimit._3().apply()); + timeSlicesByRateLimit.put(rateLimit, timeSlicesForRateLimit); + }); + } + + private void awaitNextTimeSliceForRateLimit(Tuple3> rateLimit) { + while (rateLimitExhaustedInTimeSlice(rateLimit)) { + join(trying(() -> sleep(0)) + .fmap(Try::success) + .catching(InterruptedException.class, t -> failure(new IterationInterruptedException(t)))) + .orThrow(); + } + } + + private boolean rateLimitExhaustedInTimeSlice(Tuple3> rateLimit) { + List timeSlicesForRateLimit = timeSlicesByRateLimit.getOrDefault(rateLimit, emptyList()); + return rateLimit.into((limit, duration, instantSupplier) -> { + Instant timeSliceEnd = instantSupplier.apply(); + Instant previousTimeSliceEnd = timeSliceEnd.minus(duration); + timeSlicesForRateLimit.removeIf(lt(previousTimeSliceEnd).toPredicate()); + return max(0L, limit - size(filter(mark -> lte(mark, previousTimeSliceEnd) && gte(mark, timeSliceEnd), timeSlicesForRateLimit))) == 0; + }); + } + +} diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/RepetitiousIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIterator.java similarity index 57% rename from src/main/java/com/jnape/palatable/lambda/iteration/RepetitiousIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/RepetitiousIterator.java index 7f43bed68..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,6 +1,6 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; -public class RepetitiousIterator extends InfiniteIterator { +public final class RepetitiousIterator extends InfiniteIterator { private final A value; diff --git a/src/main/java/com/jnape/palatable/lambda/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 87% 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 e676cfa11..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,10 +1,10 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.ArrayList; import java.util.Iterator; import java.util.ListIterator; -public class ReversingIterator extends ImmutableIterator { +public final class ReversingIterator extends ImmutableIterator { private final Iterator as; private final ListIterator reversingIterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/RewindableIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java similarity index 86% rename from src/main/java/com/jnape/palatable/lambda/iteration/RewindableIterator.java rename to src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java index 07b540704..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,9 +1,9 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; -public class RewindableIterator extends ImmutableIterator { +public final class RewindableIterator extends ImmutableIterator { private final Iterator asIterator; private final Cache cache; @@ -54,9 +54,7 @@ public A retrieve() { if (cache == null) throw new NoSuchElementException("Cache is empty."); - A cache = this.cache; - this.cache = null; - return cache; + return this.cache; } public boolean isEmpty() { diff --git a/src/main/java/com/jnape/palatable/lambda/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 83% 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 6cfedc82b..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,9 +1,9 @@ -package com.jnape.palatable.lambda.iteration; +package com.jnape.palatable.lambda.internal.iteration; import java.util.Iterator; import java.util.NoSuchElementException; -public class TakingIterator extends ImmutableIterator { +public final class TakingIterator extends ImmutableIterator { private final int n; private final Iterator iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java new file mode 100644 index 000000000..3b2a7b37f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java @@ -0,0 +1,68 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.internal.ImmutableQueue; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Not.not; +import static com.jnape.palatable.lambda.io.IO.io; + +public final class TrampoliningIterator implements Iterator { + private final Fn1>> fn; + private final A a; + + private ImmutableQueue>> remaining; + private B b; + + public TrampoliningIterator(Fn1>> fn, A a) { + this.fn = fn; + this.a = a; + } + + @Override + public boolean hasNext() { + queueNextIfPossible(); + return b != null; + } + + @Override + public B next() { + if (!hasNext()) + throw new NoSuchElementException(); + B next = b; + b = null; + return next; + } + + private void queueNextIfPossible() { + if (remaining == null) + pruneAfter(() -> remaining = ImmutableQueue.>>empty() + .pushFront(fn.apply(a).iterator())); + + while (b == null && remaining.head().match(constantly(false), constantly(true))) { + tickNext(); + } + } + + private void tickNext() { + pruneAfter(() -> remaining.head().orElseThrow(NoSuchElementException::new).next()) + .match(a -> io(() -> { + pruneAfter(() -> remaining = remaining.pushFront(fn.apply(a).iterator())); + }), b -> io(() -> { + this.b = b; + })).unsafePerformIO(); + } + + private R pruneAfter(Fn0 fn) { + R r = fn.apply(); + while (remaining.head().match(constantly(false), not(Iterator::hasNext))) { + remaining = remaining.tail(); + } + return r; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/UnfoldingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnfoldingIterator.java similarity index 53% 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 2c8790f1d..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 class UnfoldingIterator extends ImmutableIterator { - private final Function>> function; - private B seed; - private Maybe> maybeAcc; +public final class UnfoldingIterator extends ImmutableIterator { + 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/internal/iteration/UnioningIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterable.java new file mode 100644 index 000000000..fc4c6ed60 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/UnioningIterable.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Distinct.distinct; + +public final class UnioningIterable implements Iterable { + + private final ConcatenatingIterable elements; + + public UnioningIterable(Iterable xs, Iterable ys) { + elements = new ConcatenatingIterable<>(xs instanceof UnioningIterable ? ((UnioningIterable) xs).elements : xs, + ys instanceof UnioningIterable ? ((UnioningIterable) ys).elements : ys); + } + + @Override + public Iterator iterator() { + return distinct(elements).iterator(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/ZippingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ZippingIterator.java new file mode 100644 index 000000000..107a6ec08 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ZippingIterator.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.Iterator; + +public final class ZippingIterator extends ImmutableIterator { + private final Fn2 zipper; + private final Iterator asIterator; + private final Iterator bsIterator; + + public ZippingIterator(Fn2 zipper, Iterator asIterator, + Iterator bsIterator) { + this.asIterator = asIterator; + this.bsIterator = bsIterator; + this.zipper = zipper; + } + + @Override + public boolean hasNext() { + return asIterator.hasNext() && bsIterator.hasNext(); + } + + @Override + public C next() { + return zipper.apply(asIterator.next(), bsIterator.next()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/io/IO.java b/src/main/java/com/jnape/palatable/lambda/io/IO.java new file mode 100644 index 000000000..6af4dc1b1 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/io/IO.java @@ -0,0 +1,473 @@ +package com.jnape.palatable.lambda.io; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Try; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.LazyRec; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functions.specialized.SideEffect; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadError; +import com.jnape.palatable.lambda.monad.MonadRec; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.choice.Choice2.a; +import static com.jnape.palatable.lambda.adt.choice.Choice2.b; +import static com.jnape.palatable.lambda.functions.Fn0.fn0; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.monad.Monad.join; +import static java.lang.Thread.interrupted; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.supplyAsync; +import static java.util.concurrent.ForkJoinPool.commonPool; + +/** + * A {@link Monad} representing some side-effecting computation to be performed. Note that because {@link IO} inherently + * offers an interface supporting parallelism, the optimal execution strategy for any given {@link IO} is encoded in + * its composition. + * + * @param the result type + */ +public abstract class IO implements MonadRec>, MonadError> { + + private IO() { + } + + /** + * Run the effect represented by this {@link IO} instance, blocking the current thread until the effect terminates. + * + * @return the result of the effect + */ + public abstract A unsafePerformIO(); + + /** + * Returns a {@link CompletableFuture} representing the result of this eventual effect. By default, this will + * immediately run the effect in terms of the implicit {@link Executor} available to {@link CompletableFuture} + * (usually the {@link java.util.concurrent.ForkJoinPool}). Note that specific {@link IO} constructions may allow + * this method to delegate to externally-managed {@link CompletableFuture} instead of synthesizing their own. + * + * @return the {@link CompletableFuture} representing this {@link IO}'s eventual result + * @see IO#unsafePerformAsyncIO(Executor) + */ + public final CompletableFuture unsafePerformAsyncIO() { + return unsafePerformAsyncIO(commonPool()); + } + + /** + * Returns a {@link CompletableFuture} representing the result of this eventual effect. By default, this will + * immediately run the effect in terms of the provided {@link Executor}. Note that specific {@link IO} + * constructions may allow this method to delegate to externally-managed {@link CompletableFuture} instead of + * synthesizing their own. + * + * @param executor the {@link Executor} to run the {@link CompletableFuture} from + * @return the {@link CompletableFuture} representing this {@link IO}'s eventual result + * @see IO#unsafePerformAsyncIO() + */ + public abstract CompletableFuture unsafePerformAsyncIO(Executor executor); + + /** + * Given a function from any {@link Throwable} to the result type A, if this {@link IO} successfully + * yields a result, return it; otherwise, map the {@link Throwable} to the result type and return that. + * + * @param recoveryFn the recovery function + * @return the guarded {@link IO} + * @deprecated in favor of canonical {@link IO#catchError(Fn1)} + */ + @Deprecated + public final IO exceptionally(Fn1 recoveryFn) { + return catchError(t -> io(recoveryFn.apply(t))); + } + + /** + * Return an {@link IO} that will run ensureIO strictly after running this {@link IO} regardless of + * whether this {@link IO} terminates normally, analogous to a finally block. + * + * @param ensureIO the {@link IO} to ensure runs strictly after this {@link IO} + * @return the combined {@link IO} + */ + public final IO ensuring(IO ensureIO) { + return join(fmap(a -> ensureIO.fmap(constantly(a))) + .catchError(t1 -> ensureIO + .fmap(constantly(IO.throwing(t1))) + .catchError(t2 -> io(io(() -> { + t1.addSuppressed(t2); + throw t1; + }))))); + } + + /** + * Return a safe {@link IO} that will never throw by lifting the result of this {@link IO} into {@link Either}, + * catching any {@link Throwable} and wrapping it in a {@link Either#left(Object) left}. + * + * @return the safe {@link IO} + */ + public final IO> safe() { + return fmap(Either::right).catchError(t -> io(left(t))); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO pure(B b) { + return io(b); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO fmap(Fn1 fn) { + return MonadError.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO zip(Applicative, IO> appFn) { + return new Compose<>(this, a((IO>) appFn)); + } + + /** + * {@inheritDoc} + */ + @Override + public final Lazy> lazyZip(Lazy, IO>> lazyAppFn) { + return MonadError.super.lazyZip(lazyAppFn).>fmap(Monad>::coerce).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO discardL(Applicative> appB) { + return MonadError.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO discardR(Applicative> appB) { + return MonadError.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO flatMap(Fn1>> f) { + @SuppressWarnings({"unchecked", "RedundantCast"}) + Choice2, Fn1>> composition = b((Fn1>) (Object) f); + return new Compose<>(this, composition); + } + + /** + * {@inheritDoc} + */ + @Override + public IO trampolineM(Fn1, IO>> fn) { + return flatMap(a -> fn.apply(a).>>coerce().flatMap(aOrB -> aOrB.match( + a_ -> io(a_).trampolineM(fn), + IO::io))); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO throwError(Throwable throwable) { + return IO.throwing(throwable); + } + + /** + * {@inheritDoc} + */ + @Override + public final IO catchError(Fn1>> recoveryFn) { + return new IO() { + @Override + public A unsafePerformIO() { + return Try.trying(IO.this::unsafePerformIO) + .catchError(t -> Try.trying(recoveryFn.apply(t).>coerce()::unsafePerformIO)) + .orThrow(); + } + + @Override + public CompletableFuture unsafePerformAsyncIO(Executor executor) { + return IO.this.unsafePerformAsyncIO(executor) + .handle((a, t) -> t == null + ? completedFuture(a) + : recoveryFn.apply(t).>coerce().unsafePerformAsyncIO(executor)) + .thenCompose(f -> f); + } + }; + } + + /** + * Produce an {@link IO} that throws the given {@link Throwable} when executed. + * + * @param t the {@link Throwable} + * @param any result type + * @return the {@link IO} + */ + public static IO throwing(Throwable t) { + return io(() -> {throw t;}); + } + + /** + * Wrap the given {@link IO} in an {@link IO} that first checks if the {@link Thread#currentThread() thread} the + * {@link IO} runs on is {@link Thread#interrupted() interrupted}. If it is, an {@link InterruptedException} is + * thrown; otherwise the given {@link IO} is executed as usual. Note that for {@link IO}s supporting parallelism, + * the thread that is checked for interruption may not necessarily be the same thread that the {@link IO} ultimately + * runs on. + * + * @param io the {@link IO} to wrap + * @param the {@link IO} result type + * @return an {@link IO} that first checks for {@link Thread#interrupted() thread interrupts} + */ + public static IO interruptible(IO io) { + return join(io(() -> { + if (interrupted()) + throw new InterruptedException(); + return io; + })); + } + + /** + * Synchronize the given + * {@link IO} using the provided lock object. Note that to ensure that the entirety of the {@link IO}'s computation + * actually runs inside the synchronized region, the {@link IO} is executed + * {@link IO#unsafePerformIO() synchronously} inside the synchronized block regardless of the caller's chosen + * execution strategy. + * + * @param lock the lock object + * @param io the {@link IO} + * @param the {@link IO} result type + * @return the synchronized {@link IO} + */ + public static IO monitorSync(Object lock, IO io) { + return io(() -> { + synchronized (lock) { + return io.unsafePerformIO(); + } + }); + } + + /** + * Fuse all fork opportunities of a given {@link IO} such that, unless it is {@link IO#pin(IO, Executor) pinned} + * (or is originally {@link IO#externallyManaged(Fn0) externally managed}), no parallelism will be used when + * running it, regardless of what semantics are used when it is executed. + * + * @param io the {@link IO} + * @param the {@link IO} result type + * @return the fused {@link IO} + * @see IO#pin(IO, Executor) + */ + public static IO fuse(IO io) { + return io(io::unsafePerformIO); + } + + /** + * Pin an {@link IO} to an {@link Executor} such that regardless of what future decisions are made, when it runs, it + * will run using whatever parallelism is supported by the {@link Executor}'s threading model. Note that if this + * {@link IO} has already been pinned (or is originally {@link IO#externallyManaged(Fn0) externally managed}), + * pinning to an additional {@link Executor} has no meaningful effect. + * + * @param io the {@link IO} + * @param executor the {@link Executor} + * @param the {@link IO} result type + * @return the {@link IO} pinned to the {@link Executor} + * @see IO#fuse(IO) + */ + public static IO pin(IO io, Executor executor) { + return IO.externallyManaged(() -> io.unsafePerformAsyncIO(executor)); + } + + /** + * Given an {@link IO}, return an {@link IO} that wraps it, caches its first successful result, and guarantees that + * no subsequent interactions will happen with it afterwards, returning the cached result thereafter. Note that if + * the underlying {@link IO} throws, the failure will not be cached, so subsequent interactions with the memoized + * {@link IO} will again call through to the delegate until it completes normally. + * + * @param io the delegate {@link IO} + * @param the return type + * @return the memoized {@link IO} + */ + public static IO memoize(IO io) { + class Ref { + A value; + boolean computed; + } + Ref ref = new Ref(); + return join(io(() -> { + if (!ref.computed) { + return monitorSync(ref, io(() -> { + if (!ref.computed) { + ref.value = io.unsafePerformIO(); + ref.computed = true; + } + })).flatMap(constantly(io(() -> ref.value))); + } + return io(ref.value); + })); + } + + /** + * Static factory method for creating an {@link IO} that just returns a when performed. + * + * @param a the result + * @param the result type + * @return the {@link IO} + */ + public static IO io(A a) { + return new IO() { + @Override + public A unsafePerformIO() { + return a; + } + + @Override + public CompletableFuture unsafePerformAsyncIO(Executor executor) { + return completedFuture(a); + } + }; + } + + /** + * Static factory method for coercing a lambda to an {@link IO}. + * + * @param fn0 the lambda to coerce + * @param the result type + * @return the {@link IO} + */ + public static IO io(Fn0 fn0) { + return new IO() { + @Override + public A unsafePerformIO() { + return fn0.apply(); + } + + @Override + public CompletableFuture unsafePerformAsyncIO(Executor executor) { + return supplyAsync(fn0::apply, executor); + } + }; + } + + /** + * Static factory method for creating an {@link IO} that runs a {@link SideEffect} and returns {@link Unit}. + * + * @param sideEffect the {@link SideEffect} + * @return the {@link IO} + */ + public static IO io(SideEffect sideEffect) { + return io(fn0(() -> { + sideEffect.Ω(); + return UNIT; + })); + } + + /** + * Static factory method for creating an {@link IO} from an externally managed source of + * {@link CompletableFuture completable futures}. + *

, 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 @@ -88,10 +98,13 @@ public boolean equals(Object obj) { static Simple typeSafeKey() { return new TypeSafeKey.Simple() { @Override - @SuppressWarnings("unchecked") - public