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/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 000000000..2f7615ccb --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,30 @@ +name: Java CI + +on: [push] + +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 diff --git a/.gitignore b/.gitignore index 6b65bb21d..a0b059d5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/* target/* *.iml -.java-version \ No newline at end of file +.java-version +.DS_Store diff --git a/.travis.yml b/.travis.yml index 8ea4e5783..39074daea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ +dist: trusty language: java jdk: - oraclejdk8 - - oraclejdk11 + - openjdk11 diff --git a/CHANGELOG.md b/CHANGELOG.md index cc0afb897..b29aef81a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,46 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### 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 +- `Force`, in favor if traversing into an `IO` and explicitly 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 @@ -76,11 +116,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [3.2.0] - 2018-12-08 ### Changed -- ***Breaking Change***: `Difference` and `Intersection` no longer instances of `Semigroup` and moved to `functions.builtin.fn2` package +- ***Breaking Change***: `Difference` and `Intersection` no longer instances of `Semigroup` and moved to + `functions.builtin.fn2` package - ***Breaking Change***: `Absent` moved to `semigroup.builtin` package - ***Breaking Change***: `Effect#accept()` is now the required method to implement in the functional interface - ***Breaking Change***: `Fn0#apply()` is now the required method to implement in the functional interface -- ***Breaking Change***: `GTBy`, `GT`, `LTBy`, `LT`, `GTEBy`, `GTE`, `LTEBy`, and `LTE` take the right-hand side first for more intuitive partial application +- ***Breaking Change***: `GTBy`, `GT`, `LTBy`, `LT`, `GTEBy`, `GTE`, `LTEBy`, and `LTE` take the right-hand side first + for more intuitive partial application - ***Breaking Change***: `Effect` now returns an `IO` - `RightAny` overload returns `Monoid` - monoids now all fold with respect to `foldMap` @@ -182,14 +224,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Changed - ***Breaking Change***: `Sequence` now has two more type parameters to aid in inference - ***Breaking Change***: `Traversable#traverse` now has three more type parameters to aid in inference -- ***Breaking Change***: `Monad#zip` now forces `m a -> b` before `m a` in default `Applicative#zip` implementation; this is only breaking for types that are sensitive to computation order (the resulting values are the same) -- ***Breaking Change***: `TypeSafeKey` is now dually parametric (single parameter analog is preserved in `TypeSafeKey.Simple`) +- ***Breaking Change***: `Monad#zip` now forces `m a -> b` before `m a` in default `Applicative#zip` implementation; + this is only breaking for types that are sensitive to computation order (the resulting values are the same) +- ***Breaking Change***: `TypeSafeKey` is now dually parametric (single parameter analog is preserved in + `TypeSafeKey.Simple`) - `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` - `Peek2` now accepts the more general `BoundedBifunctor` - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations - `Into3-8` now supports functions with parameter variance - `HListLens#tail` is now covariant in `Tail` parameter -- More functions now automatically deforest nested calls (`concat` `cons`, `cycle`, `distinct`, `drop`, `dropwhile`, `filter`, `map`, `reverse`, `snoc`, `take`, `takewhile`, `tail`) +- More functions now automatically deforest nested calls (`concat` `cons`, `cycle`, `distinct`, `drop`, `dropwhile`, + `filter`, `map`, `reverse`, `snoc`, `take`, `takewhile`, `tail`) - `Flatten` calls `Iterator#hasNext` less aggressively, allowing for better laziness - `Lens` subtypes `LensLike` - `View`/`Set`/`Over` now only require `LensLike` @@ -255,7 +300,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `CollectionLens#asSet(Function)`, a proper analog of `CollectionLens#asSet()` that uses defensive copies - `CollectionLens#asStream(Function)`, a proper analog of `CollectionLens#asStream()` that uses defensive copies - Explicitly calling attention to all unlawful lenses in their documentation -- `Peek` and `Peek2`, for "peeking" at the value contained inside any given `Functor` or `Bifunctor` with given side-effects +- `Peek` and `Peek2`, for "peeking" at the value contained inside any given `Functor` or `Bifunctor` with given + side-effects - `Trampoline` and `RecursiveResult` for modeling primitive tail-recursive functions that can be trampolined ### Removed @@ -365,7 +411,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Const` supports value equality - `partition` now only requires iterables of `CoProudct2` - `CoProductN`s receive a unification parameter, which trickles down to `Either` and `Choice`s -- `Concat` now represents a monoid for `Iterable`; previous `Concat` semigroup and monoid renamed to more appropriate `AddAll` +- `Concat` now represents a monoid for `Iterable`; previous `Concat` semigroup and monoid renamed to more appropriate + `AddAll` - `Lens` is now an instance of `Profunctor` ### Added @@ -375,14 +422,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `empty`, used to test if an Iterable is empty - `groupBy`, for folding an Iterable into a Map given a key function - `Applicative` arrives; all functors gain applicative properties -- `Traversable` arrives; `SingletonHList`, `Tuple*`, `Choice*`, `Either`, `Identity`, and `Const` gain traversable properties +- `Traversable` arrives; `SingletonHList`, `Tuple*`, `Choice*`, `Either`, `Identity`, and `Const` gain traversable + properties - `TraversableOptional` and `TraversableIterable` for adapting `Optional` and `Iterable`, respectively, to `Traversable` - `sequence` for wrapping a traversable in an applicative during traversal - `Compose`, an applicative functor that represents type-level functor composition ## [1.5.6] - 2017-02-11 ### Changed -- `CoProductN.[a-e]()` static factory methods moved to equivalent `ChoiceN` class. Coproduct interfaces now solely represent methods, no longer have anonymous implementations, and no longer require a `Functor` constraint +- `CoProductN.[a-e]()` static factory methods moved to equivalent `ChoiceN` class. Coproduct interfaces now solely + represent methods, no longer have anonymous implementations, and no longer require a `Functor` constraint ### Added - `ChoiceN` types, representing concrete coproduct implementations that are also `Functor` and `BiFunctor` @@ -436,7 +485,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [1.4] - 2016-08-08 ### Changed -- All function input values become `java.util.function` types, and all function output values remain lambda types, for better compatibility +- All function input values become `java.util.function` types, and all function output values remain lambda types, for + better compatibility ## [1.3] - 2016-07-31 ### Changed @@ -475,8 +525,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Monadic/Dyadic/TriadicFunction`, `Predicate`, `Tuple2`, `Tuple3` - `Functor`, `BiFunctor`, `ProFunctor` -[Unreleased]: https://github.com/palatable/lambda/compare/lambda-3.3.0...HEAD -[3.3.0]: https://github.com/palatable/lambda/compare/lambda-3.2.0...3.3.0 +[Unreleased]: https://github.com/palatable/lambda/compare/lambda-4.0.0...HEAD +[4.0.0]: https://github.com/palatable/lambda/compare/lambda-3.3.0...lambda-4.0.0 +[3.3.0]: https://github.com/palatable/lambda/compare/lambda-3.2.0...lambda-3.3.0 [3.2.0]: https://github.com/palatable/lambda/compare/lambda-3.1.0...lambda-3.2.0 [3.1.0]: https://github.com/palatable/lambda/compare/lambda-3.0.3...lambda-3.1.0 [3.0.3]: https://github.com/palatable/lambda/compare/lambda-3.0.2...lambda-3.0.3 diff --git a/LICENSE b/LICENSE index b35067105..fb5630322 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 John Napier (jnape) +Copyright (c) 2019 John Napier (jnape) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 476f89ebc..8e2641d63 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ λ ====== -[![Build Status](https://img.shields.io/travis/palatable/lambda/master.svg)](https://travis-ci.org/palatable/lambda) +[![Build Status](https://travis-ci.com/palatable/lambda.svg?branch=master)](https://travis-ci.com/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 at https://gitter.im/palatable/lambda](https://badges.gitter.im/palatable/lambda.svg)](https://gitter.im/palatable/lambda?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Floobits Status](https://floobits.com/jnape/lambda.svg)](https://floobits.com/jnape/lambda/redirect) Functional patterns for Java @@ -58,14 +60,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 3.3.0 + 4.0.0 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '3.3.0' +compile group: 'com.jnape.palatable', name: 'lambda', version: '4.0.0' ``` Examples @@ -73,22 +75,22 @@ compile group: 'com.jnape.palatable', name: 'lambda', version: '3.3.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: @@ -104,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] ``` @@ -114,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: @@ -123,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: @@ -142,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]] ``` @@ -174,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 ``` diff --git a/pom.xml b/pom.xml index ddf6ce35a..c767905b6 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 4.0.0 + 5.0.0 jar Lambda @@ -53,9 +53,9 @@ - 1.2 + 1.3.0 3.3 - 1.3 + 2.1 3.1.1 @@ -66,13 +66,14 @@ org.hamcrest - hamcrest-all - ${hamcrest-all.version} + hamcrest + ${hamcrest.version} test org.mockito - mockito-all + mockito-core + 2.28.2 com.jnape.palatable 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 29e421570..a26b6a42e 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -5,21 +5,28 @@ 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.builtin.fn2.Peek; -import com.jnape.palatable.lambda.functions.builtin.fn2.Peek2; +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 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; /** @@ -32,7 +39,8 @@ */ public abstract class Either implements CoProduct2>, - Monad>, + MonadError>, + MonadRec>, Traversable>, Bifunctor> { @@ -132,6 +140,16 @@ public Either flatMap(Fn1>::coerce)); } + /** + * {@inheritDoc} + */ + @Override + public Either trampolineM(Fn1, Either>> fn) { + return match(Either::left, trampoline(a -> fn.apply(a).>>coerce() + .match(l -> terminate(left(l)), + aOrB -> aOrB.fmap(Either::right)))); + } + @Override public final Either invert() { return match(Either::right, Either::left); @@ -164,9 +182,13 @@ public final Either merge(Fn2 leftFn, * * @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 */ + @Deprecated public Either peek(Fn1> effect) { - return Peek.peek(effect, this); + return match(l -> io(Either.left(l)), + r -> effect.apply(r).fmap(constantly(this))) + .unsafePerformIO(); } /** @@ -175,9 +197,11 @@ public Either peek(Fn1> effect) { * @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 */ + @Deprecated public Either peek(Fn1> leftEffect, Fn1> rightEffect) { - return Peek2.peek2(leftEffect, rightEffect, this); + return match(leftEffect, rightEffect).fmap(constantly(this)).unsafePerformIO(); } /** @@ -206,7 +230,7 @@ public Choice3 diverge() { */ @Override public final Either fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadError.super.fmap(fn).coerce(); } /** @@ -247,7 +271,7 @@ public final Either pure(R2 r2) { */ @Override public final Either zip(Applicative, Either> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadError.super.zip(appFn).coerce(); } /** @@ -265,7 +289,7 @@ public Lazy> lazyZip( */ @Override public final Either discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadError.super.discardL(appB).coerce(); } /** @@ -273,18 +297,36 @@ public final Either discardL(Applicative> appB) { */ @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("RedundantTypeArguments") + public Either catchError(Fn1>> recoveryFn) { + return match(recoveryFn.fmap(Monad>::coerce), Either::right); } /** * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") public final , TravB extends Traversable>, AppTrav extends Applicative> AppTrav traverse(Fn1> fn, Fn1 pure) { - return (AppTrav) match(l -> pure.apply((TravB) left(l)), r -> fn.apply(r).fmap(Either::right)); + return match(l -> pure.apply(Either.left(l).coerce()), + r -> fn.apply(r).>fmap(Either::right).fmap(Functor::coerce)) + .coerce(); } /** @@ -386,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; 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 4cc726673..dc5595983 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java @@ -7,12 +7,15 @@ 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.builtin.fn2.Peek; +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; @@ -23,7 +26,10 @@ 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 @@ -34,7 +40,8 @@ */ public abstract class Maybe implements CoProduct2>, - Monad>, + MonadError>, + MonadRec>, Traversable> { private Maybe() { @@ -86,6 +93,22 @@ 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. @@ -127,7 +150,7 @@ public final Maybe pure(B b) { */ @Override public final Maybe fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadError.super.fmap(fn).coerce(); } /** @@ -135,7 +158,7 @@ public final Maybe fmap(Fn1 fn) { */ @Override public final Maybe zip(Applicative, Maybe> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadError.super.zip(appFn).coerce(); } /** @@ -156,7 +179,7 @@ public Lazy> lazyZip(Lazy Maybe discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadError.super.discardL(appB).coerce(); } /** @@ -164,7 +187,7 @@ public final Maybe discardL(Applicative> appB) { */ @Override public final Maybe discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadError.super.discardR(appB).coerce(); } /** @@ -176,6 +199,16 @@ 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} */ @@ -205,9 +238,11 @@ public Choice2 invert() { * * @param effect the consumer * @return the same Maybe instance + * @deprecated in favor of {@link Maybe#match(Fn1, Fn1) matching} into an {@link IO} and explicitly running it */ + @Deprecated public final Maybe peek(Fn1> effect) { - return Peek.peek(effect, this); + return match(constantly(io(this)), a -> effect.apply(a).fmap(constantly(this))).unsafePerformIO(); } @Override @@ -279,6 +314,15 @@ public static Maybe nothing() { return (Maybe) Nothing.INSTANCE; } + /** + * The canonical {@link Pure} instance for {@link Maybe}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureMaybe() { + return Maybe::just; + } + private static final class Nothing extends Maybe { private static final Nothing INSTANCE = new Nothing<>(); 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 5f8e67270..6721d33e8 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/These.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/These.java @@ -4,10 +4,14 @@ 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; @@ -15,6 +19,7 @@ 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; /** @@ -27,7 +32,7 @@ */ public abstract class These implements CoProduct3, These>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -48,7 +53,24 @@ public final These biMap(Fn1 lFn, */ @Override public final These flatMap(Fn1>> f) { - return match(These::a, b -> f.apply(b).coerce(), into((a, b) -> f.apply(b).>coerce().biMapL(constantly(a)))); + 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))); } /** @@ -59,32 +81,32 @@ public final These pure(C c) { return match(a -> both(a, c), b -> b(c), into((a, b) -> both(a, c))); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") - public , TravB extends Traversable>, - AppTrav extends Applicative> - AppTrav traverse(Fn1> fn, Fn1 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 , 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 final These biMapL(Fn1 fn) { - return (These) Bifunctor.super.biMapL(fn); + return (These) Bifunctor.super.biMapL(fn); } /** * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") public final These biMapR(Fn1 fn) { - return (These) Bifunctor.super.biMapR(fn); + return (These) Bifunctor.super.biMapR(fn); } /** @@ -92,7 +114,7 @@ public final These biMapR(Fn1 fn) { */ @Override public final These fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -100,14 +122,17 @@ public final These fmap(Fn1 fn) { */ @Override public final These zip(Applicative, These> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override public Lazy> lazyZip( Lazy, These>> lazyAppFn) { return projectA().>>fmap(a -> lazy(a(a))) - .orElseGet(() -> Monad.super.lazyZip(lazyAppFn).fmap(Monad>::coerce)); + .orElseGet(() -> MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce)); } /** @@ -115,7 +140,7 @@ public Lazy> lazyZip( */ @Override public final These discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -123,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(); } /** @@ -164,6 +189,16 @@ public static These both(A a, B b) { return new Both<>(tuple(a, 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; 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 bdc2ec040..90cd28091 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Try.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Try.java @@ -3,11 +3,16 @@ import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; import com.jnape.palatable.lambda.functions.Fn0; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functions.specialized.SideEffect; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.io.IO; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadError; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; @@ -17,6 +22,8 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static com.jnape.palatable.lambda.internal.Runtime.throwChecked; @@ -27,7 +34,11 @@ * @param the possibly successful expression result * @see Either */ -public abstract class Try implements Monad>, Traversable>, CoProduct2> { +public abstract class Try implements + MonadError>, + MonadRec>, + Traversable>, + CoProduct2> { private Try() { } @@ -40,10 +51,9 @@ private Try() { * @param recoveryFn the function mapping the {@link Throwable} to the result * @return a new {@link Try} instance around either the original successful result or the mapped result */ - @SuppressWarnings("unchecked") public final Try catching(Class throwableType, Fn1 recoveryFn) { - return catching(throwableType::isInstance, t -> recoveryFn.apply((S) t)); + return catching(throwableType::isInstance, t -> recoveryFn.apply(Downcast.downcast(t))); } /** @@ -72,13 +82,13 @@ public final Try catching(Fn1 predicate * rules above */ public final Try ensuring(SideEffect sideEffect) { - return match(t -> trying(sideEffect) - .>fmap(constantly(failure(t))) - .recover(t2 -> { - t.addSuppressed(t2); - return failure(t); - }), - a -> trying(sideEffect).fmap(constantly(a))); + return this.>match(t -> trying(sideEffect) + .>fmap(constantly(failure(t))) + .recover(t2 -> { + t.addSuppressed(t2); + return failure(t); + }), + a -> trying(sideEffect).fmap(constantly(a))); } /** @@ -129,7 +139,6 @@ public final A orThrow() throws T { */ public abstract A orThrow(Fn1 fn) throws T; - /** * If this is a success, wrap the value in a {@link Maybe#just} and return it. Otherwise, return {@link * Maybe#nothing()}. @@ -162,12 +171,28 @@ public final Either toEither(Fn1 fn) { return match(fn.fmap(Either::left), Either::right); } + /** + * {@inheritDoc} + */ + @Override + public Try throwError(Throwable throwable) { + return failure(throwable); + } + + /** + * {@inheritDoc} + */ + @Override + public Try catchError(Fn1>> recoveryFn) { + return match(t -> recoveryFn.apply(t).coerce(), Try::success); + } + /** * {@inheritDoc} */ @Override public Try fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadError.super.fmap(fn).coerce(); } /** @@ -191,7 +216,7 @@ public Try pure(B b) { */ @Override public Try zip(Applicative, Try> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadError.super.zip(appFn).coerce(); } /** @@ -208,7 +233,7 @@ public Lazy> lazyZip(Lazy Try discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadError.super.discardL(appB).coerce(); } /** @@ -216,7 +241,18 @@ public Try discardL(Applicative> appB) { */ @Override public Try discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadError.super.discardR(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Try trampolineM(Fn1, Try>> fn) { + return flatMap(trampoline(a -> fn.apply(a).>>coerce().match( + t -> terminate(failure(t)), + aOrB -> aOrB.fmap(Try::success) + ))); } /** @@ -361,6 +397,15 @@ public static 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; 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 7366881c2..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 @@ -6,16 +6,21 @@ 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 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; /** @@ -29,7 +34,7 @@ */ public abstract class Choice2 implements CoProduct2>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -67,7 +72,7 @@ public Choice2 invert() { */ @Override public final Choice2 fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -108,14 +113,14 @@ public Choice2 pure(C c) { */ @Override public Choice2 zip(Applicative, Choice2> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** * {@inheritDoc} */ @Override - public Lazy>> lazyZip( + public Lazy> lazyZip( Lazy, Choice2>> lazyAppFn) { return match(a -> lazy(a(a)), b -> lazyAppFn.fmap(choiceF -> choiceF.fmap(f -> f.apply(b)).coerce())); @@ -126,7 +131,7 @@ public Choice2 zip(Applicative, Choice2 Choice2 discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -134,7 +139,7 @@ public Choice2 discardL(Applicative> appB) { */ @Override public Choice2 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -149,11 +154,21 @@ public final Choice2 flatMap(Fn1 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((TravB) a(a)), + return match(a -> pure.apply(Choice2.a(a).coerce()), b -> fn.apply(b).>fmap(Choice2::b).fmap(Functor::coerce).coerce()); } @@ -181,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; 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 5e4388419..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 @@ -6,16 +6,21 @@ 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 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; /** @@ -29,7 +34,7 @@ */ public abstract class Choice3 implements CoProduct3>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -67,7 +72,7 @@ public final Choice2 converge(Fn1 */ @Override public final Choice3 fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -108,7 +113,7 @@ public Choice3 pure(D d) { */ @Override public Choice3 zip(Applicative, Choice3> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -127,7 +132,7 @@ public Lazy> lazyZip( */ @Override public Choice3 discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -135,7 +140,7 @@ public Choice3 discardL(Applicative> appB) { */ @Override public Choice3 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -150,13 +155,25 @@ public Choice3 flatMap(Fn1 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((TravB) Choice3.a(a)).coerce(), - b -> pure.apply((TravB) Choice3.b(b)).coerce(), - c -> fn.apply(c).>fmap(Choice3::c).fmap(Functor::coerce).coerce()); + 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(); } /** @@ -198,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; @@ -296,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 4f79f3d5e..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 @@ -6,16 +6,21 @@ 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 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; /** @@ -30,7 +35,7 @@ */ public abstract class Choice4 implements CoProduct4>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -69,7 +74,7 @@ public Choice3 converge(Fn1 */ @Override public final Choice4 fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -107,7 +112,7 @@ public Choice4 pure(E e) { */ @Override public Choice4 zip(Applicative, Choice4> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -127,7 +132,7 @@ public Lazy> lazyZip( */ @Override public Choice4 discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -135,7 +140,7 @@ public Choice4 discardL(Applicative> appB */ @Override public Choice4 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -150,14 +155,30 @@ public Choice4 flatMap(Fn1 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((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(Functor::coerce).coerce()); + 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(); } /** @@ -216,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; 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 8714e4395..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 @@ -6,15 +6,20 @@ 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 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; /** @@ -30,7 +35,7 @@ */ public abstract class Choice5 implements CoProduct5>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -69,7 +74,7 @@ public Choice4 converge(Fn1 Choice5 fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -110,7 +115,7 @@ public Choice5 pure(F f) { */ @Override public Choice5 zip(Applicative, Choice5> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -131,7 +136,7 @@ public Lazy> lazyZip( */ @Override public Choice5 discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -139,7 +144,7 @@ public Choice5 discardL(Applicative */ @Override public Choice5 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -154,16 +159,30 @@ public Choice5 flatMap(Fn1 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((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)), + 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()); + .fmap(Applicative::coerce)) + .coerce(); } /** @@ -241,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; 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 ff2ba8df5..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 @@ -6,15 +6,20 @@ import com.jnape.palatable.lambda.adt.hlist.HList; import com.jnape.palatable.lambda.adt.hlist.Tuple6; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import static com.jnape.palatable.lambda.functions.builtin.fn2.Into6.into6; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** @@ -31,7 +36,7 @@ */ public abstract class Choice6 implements CoProduct6>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -70,25 +75,23 @@ public Choice5 converge(Fn1 Choice6 fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") public Choice6 biMapL(Fn1 fn) { - return (Choice6) Bifunctor.super.biMapL(fn); + return (Choice6) Bifunctor.super.biMapL(fn); } /** * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") public Choice6 biMapR(Fn1 fn) { - return (Choice6) Bifunctor.super.biMapR(fn); + return (Choice6) Bifunctor.super.biMapR(fn); } /** @@ -114,7 +117,7 @@ public Choice6 pure(G g) { @Override public Choice6 zip( Applicative, Choice6> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -136,7 +139,7 @@ public Lazy> lazyZip( */ @Override public Choice6 discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -144,7 +147,7 @@ public Choice6 discardL(Applicative Choice6 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -159,17 +162,32 @@ public Choice6 flatMap(Fn1 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((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)), + 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()); + .fmap(Applicative::coerce)) + .coerce(); } /** @@ -268,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; 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 590796133..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 @@ -6,15 +6,20 @@ import com.jnape.palatable.lambda.adt.hlist.HList; import com.jnape.palatable.lambda.adt.hlist.Tuple7; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import static com.jnape.palatable.lambda.functions.builtin.fn2.Into7.into7; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** @@ -32,7 +37,7 @@ */ public abstract class Choice7 implements CoProduct7>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -72,7 +77,7 @@ public Choice6 converge(Fn1 Choice7 fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -114,7 +119,7 @@ public Choice7 pure(H h) { @Override public Choice7 zip( Applicative, Choice7> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -137,7 +142,7 @@ public Lazy> lazyZip( */ @Override public Choice7 discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -145,7 +150,7 @@ public Choice7 discardL(Applicative Choice7 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -161,18 +166,34 @@ public Choice7 flatMap( * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") + public Choice7 trampolineM( + Fn1, Choice7>> fn) { + return flatMap(trampoline(g -> fn.apply(g).>>coerce().match( + a -> terminate(a(a)), + b -> terminate(b(b)), + c -> terminate(c(c)), + d -> terminate(d(d)), + e -> terminate(e(e)), + f -> terminate(f(f)), + gRec -> gRec.fmap(Choice7::g)))); + } + + /** + * {@inheritDoc} + */ + @Override public , TravB extends Traversable>, AppTrav extends Applicative> AppTrav traverse(Fn1> fn, Fn1 pure) { - return match(a -> pure.apply((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)), + 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()); + .fmap(Applicative::coerce)) + .coerce(); } /** @@ -294,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; 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 cfdb71587..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 @@ -6,15 +6,20 @@ import com.jnape.palatable.lambda.adt.hlist.HList; import com.jnape.palatable.lambda.adt.hlist.Tuple8; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import static com.jnape.palatable.lambda.functions.builtin.fn2.Into8.into8; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** @@ -32,7 +37,7 @@ */ public abstract class Choice8 implements CoProduct8>, - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -65,7 +70,7 @@ public Choice7 converge( */ @Override public Choice8 fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -107,7 +112,7 @@ public Choice8 pure(I i) { @Override public Choice8 zip( Applicative, Choice8> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -131,7 +136,7 @@ public Lazy> lazyZip( */ @Override public Choice8 discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -139,7 +144,7 @@ public Choice8 discardL(Applicative Choice8 discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -155,19 +160,36 @@ public Choice8 flatMap( * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") + 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((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)), + 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()); + .fmap(Applicative::coerce)) + .coerce(); } /** @@ -314,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 { 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 176745aba..927f27ae3 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 @@ -3,11 +3,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 static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; + /** * A singleton HList. Supports random access. * @@ -19,55 +24,91 @@ * @see Tuple5 */ public class SingletonHList<_1> extends HCons<_1, HNil> implements - Monad<_1, SingletonHList>, + 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); } + /** + * {@inheritDoc} + */ @Override public <_1Prime> SingletonHList<_1Prime> fmap(Fn1 fn) { - return Monad.super.<_1Prime>fmap(fn).coerce(); + 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(); + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_1Prime> Lazy> lazyZip( Lazy, SingletonHList>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad<_1Prime, SingletonHList>::coerce); + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_1Prime, SingletonHList>::coerce); } + /** + * {@inheritDoc} + */ @Override public <_1Prime> SingletonHList<_1Prime> discardL(Applicative<_1Prime, SingletonHList> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_1Prime> SingletonHList<_1> discardR(Applicative<_1Prime, SingletonHList> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_1Prime> SingletonHList<_1Prime> flatMap(Fn1>> f) { return f.apply(head()).coerce(); } + /** + * {@inheritDoc} + */ + @Override + public <_1Prime> SingletonHList<_1Prime> trampolineM( + Fn1, SingletonHList>> fn) { + return fmap(trampoline(head -> fn.apply(head).>>coerce().head())); + } + + /** + * {@inheritDoc} + */ @Override public , TravB extends Traversable>, AppTrav extends Applicative> AppTrav traverse(Fn1> fn, @@ -85,4 +126,13 @@ AppTrav extends Applicative> AppTrav traverse(Fn1 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 78bf332c3..726335f6d 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,17 +1,27 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Head; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.MonadWriter; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Map; import 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. @@ -27,8 +37,8 @@ */ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements Product2<_1, _2>, - Map.Entry<_1, _2>, - Monad<_2, Tuple2<_1, ?>>, + MonadRec<_2, Tuple2<_1, ?>>, + MonadWriter<_1, _2, Tuple2<_1, ?>>, Bifunctor<_1, _2, Tuple2>, Traversable<_2, Tuple2<_1, ?>> { @@ -41,96 +51,173 @@ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements _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); } + /** + * {@inheritDoc} + */ @Override public _1 _1() { return _1; } + /** + * {@inheritDoc} + */ @Override public _2 _2() { return _2; } + /** + * {@inheritDoc} + */ @Override public _1 getKey() { return _1(); } + /** + * {@inheritDoc} + */ @Override public _2 getValue() { return _2(); } + /** + * {@inheritDoc} + */ @Override public _2 setValue(_2 value) { throw new UnsupportedOperationException(); } + /** + * {@inheritDoc} + */ @Override public Tuple2<_2, _1> invert() { return tuple(_2, _1); } + /** + * {@inheritDoc} + */ @Override public <_2Prime> Tuple2<_1, _2Prime> fmap(Fn1 fn) { - return Monad.super.<_2Prime>fmap(fn).coerce(); + return MonadRec.super.<_2Prime>fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public <_1Prime> Tuple2<_1Prime, _2> biMapL(Fn1 fn) { - return (Tuple2<_1Prime, _2>) Bifunctor.super.biMapL(fn); + return (Tuple2<_1Prime, _2>) Bifunctor.super.<_1Prime>biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public <_2Prime> Tuple2<_1, _2Prime> biMapR(Fn1 fn) { - return (Tuple2<_1, _2Prime>) Bifunctor.super.biMapR(fn); + return (Tuple2<_1, _2Prime>) Bifunctor.super.<_2Prime>biMapR(fn); } + /** + * {@inheritDoc} + */ @Override 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(); + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_2Prime> Lazy> lazyZip( Lazy, Tuple2<_1, ?>>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad<_2Prime, Tuple2<_1, ?>>::coerce); + 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(Fn1>> f) { return pure(f.apply(_2).>coerce()._2()); } + /** + * {@inheritDoc} + */ + @Override + public <_2Prime> Tuple2<_1, _2Prime> trampolineM( + Fn1, Tuple2<_1, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce()._2())); + } + + /** + * {@inheritDoc} + */ @Override public <_2Prime, App extends Applicative, TravB extends Traversable<_2Prime, Tuple2<_1, ?>>, AppTrav extends Applicative> AppTrav traverse( @@ -161,4 +248,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 0e99a88b9..e32eac589 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java @@ -1,15 +1,22 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product3; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A 3-element tuple product type, implemented as a specialized HList. Supports random access. @@ -26,7 +33,7 @@ */ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements Product3<_1, _2, _3>, - Monad<_3, Tuple3<_1, _2, ?>>, + MonadRec<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>>, Traversable<_3, Tuple3<_1, _2, ?>> { @@ -41,98 +48,158 @@ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements _3 = tail._2(); } + /** + * {@inheritDoc} + */ @Override public <_0> Tuple4<_0, _1, _2, _3> cons(_0 _0) { return new Tuple4<>(_0, this); } + /** + * {@inheritDoc} + */ @Override public _1 _1() { return _1; } + /** + * {@inheritDoc} + */ @Override public _2 _2() { return _2; } + /** + * {@inheritDoc} + */ @Override public _3 _3() { return _3; } + /** + * {@inheritDoc} + */ @Override public Tuple3<_2, _3, _1> rotateL3() { return tuple(_2, _3, _1); } + /** + * {@inheritDoc} + */ @Override public Tuple3<_3, _1, _2> rotateR3() { return tuple(_3, _1, _2); } + /** + * {@inheritDoc} + */ @Override public Tuple3<_2, _1, _3> invert() { return tuple(_2, _1, _3); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public <_3Prime> Tuple3<_1, _2, _3Prime> fmap(Fn1 fn) { - return (Tuple3<_1, _2, _3Prime>) Monad.super.fmap(fn); + return (Tuple3<_1, _2, _3Prime>) MonadRec.super.<_3Prime>fmap(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public <_2Prime> Tuple3<_1, _2Prime, _3> biMapL(Fn1 fn) { - return (Tuple3<_1, _2Prime, _3>) Bifunctor.super.biMapL(fn); + return (Tuple3<_1, _2Prime, _3>) Bifunctor.super.<_2Prime>biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public <_3Prime> Tuple3<_1, _2, _3Prime> biMapR(Fn1 fn) { - return (Tuple3<_1, _2, _3Prime>) Bifunctor.super.biMapR(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()::apply); } + /** + * {@inheritDoc} + */ @Override public <_3Prime> Lazy> lazyZip( Lazy, Tuple3<_1, _2, ?>>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad<_3Prime, Tuple3<_1, _2, ?>>::coerce); + 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( Fn1>> f) { return pure(f.apply(_3).>coerce()._3); } + /** + * {@inheritDoc} + */ + @Override + public <_3Prime> Tuple3<_1, _2, _3Prime> trampolineM( + Fn1, Tuple3<_1, _2, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce()._3())); + } + + /** + * {@inheritDoc} + */ @Override public <_3Prime, App extends Applicative, TravB extends Traversable<_3Prime, Tuple3<_1, _2, ?>>, AppTrav extends Applicative> AppTrav traverse( @@ -152,4 +219,34 @@ AppTrav extends Applicative> AppTrav traverse( 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 1e99d3fd5..3e74da859 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java @@ -1,15 +1,22 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product4; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A 4-element tuple product type, implemented as a specialized HList. Supports random access. @@ -27,7 +34,7 @@ */ public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implements Product4<_1, _2, _3, _4>, - Monad<_4, Tuple4<_1, _2, _3, ?>>, + MonadRec<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>>, Traversable<_4, Tuple4<_1, _2, _3, ?>> { @@ -44,110 +51,182 @@ public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implem _4 = tail._3(); } + /** + * {@inheritDoc} + */ @Override public <_0> Tuple5<_0, _1, _2, _3, _4> cons(_0 _0) { return new Tuple5<>(_0, this); } + /** + * {@inheritDoc} + */ @Override public _1 _1() { return _1; } + /** + * {@inheritDoc} + */ @Override public _2 _2() { return _2; } + /** + * {@inheritDoc} + */ @Override public _3 _3() { return _3; } + /** + * {@inheritDoc} + */ @Override public _4 _4() { return _4; } + /** + * {@inheritDoc} + */ @Override public Tuple4<_2, _3, _4, _1> rotateL4() { return tuple(_2, _3, _4, _1); } + /** + * {@inheritDoc} + */ @Override public Tuple4<_4, _1, _2, _3> rotateR4() { return tuple(_4, _1, _2, _3); } + /** + * {@inheritDoc} + */ @Override public Tuple4<_2, _3, _1, _4> rotateL3() { return tuple(_2, _3, _1, _4); } + /** + * {@inheritDoc} + */ @Override public Tuple4<_3, _1, _2, _4> rotateR3() { return tuple(_3, _1, _2, _4); } + /** + * {@inheritDoc} + */ @Override public Tuple4<_2, _1, _3, _4> invert() { return tuple(_2, _1, _3, _4); } + /** + * {@inheritDoc} + */ @Override public <_4Prime> Tuple4<_1, _2, _3, _4Prime> fmap(Fn1 fn) { - return (Tuple4<_1, _2, _3, _4Prime>) Monad.super.<_4Prime>fmap(fn); + return (Tuple4<_1, _2, _3, _4Prime>) MonadRec.super.<_4Prime>fmap(fn); } + /** + * {@inheritDoc} + */ @Override public <_3Prime> Tuple4<_1, _2, _3Prime, _4> biMapL(Fn1 fn) { return (Tuple4<_1, _2, _3Prime, _4>) Bifunctor.super.<_3Prime>biMapL(fn); } + /** + * {@inheritDoc} + */ @Override public <_4Prime> Tuple4<_1, _2, _3, _4Prime> biMapR(Fn1 fn) { return (Tuple4<_1, _2, _3, _4Prime>) Bifunctor.super.<_4Prime>biMapR(fn); } + /** + * {@inheritDoc} + */ @Override public <_3Prime, _4Prime> Tuple4<_1, _2, _3Prime, _4Prime> biMap(Fn1 lFn, Fn1 rFn) { return new Tuple4<>(_1(), tail().biMap(lFn, rFn)); } + /** + * {@inheritDoc} + */ @Override public <_4Prime> Tuple4<_1, _2, _3, _4Prime> pure(_4Prime _4Prime) { return tuple(_1, _2, _3, _4Prime); } + /** + * {@inheritDoc} + */ @Override public <_4Prime> Tuple4<_1, _2, _3, _4Prime> zip( Applicative, Tuple4<_1, _2, _3, ?>> appFn) { return biMapR(appFn.>>coerce()._4()::apply); } + /** + * {@inheritDoc} + */ @Override public <_4Prime> Lazy> lazyZip( Lazy, Tuple4<_1, _2, _3, ?>>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad<_4Prime, Tuple4<_1, _2, _3, ?>>::coerce); + 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( Fn1>> f) { return pure(f.apply(_4).>coerce()._4); } + /** + * {@inheritDoc} + */ + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> trampolineM( + Fn1, Tuple4<_1, _2, _3, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce()._4())); + } + + /** + * {@inheritDoc} + */ @Override public <_4Prime, App extends Applicative, TravB extends Traversable<_4Prime, Tuple4<_1, _2, _3, ?>>, AppTrav extends Applicative> AppTrav traverse( @@ -167,4 +246,36 @@ AppTrav extends Applicative> AppTrav traverse( 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 0292831d2..c87ee564b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java @@ -1,15 +1,22 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product5; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A 5-element tuple product type, implemented as a specialized HList. Supports random access. @@ -28,7 +35,7 @@ */ public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> implements Product5<_1, _2, _3, _4, _5>, - Monad<_5, Tuple5<_1, _2, _3, _4, ?>>, + MonadRec<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>>, Traversable<_5, Tuple5<_1, _2, _3, _4, ?>> { @@ -47,127 +54,206 @@ public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5> _5 = tail._4(); } + /** + * {@inheritDoc} + */ @Override public <_0> Tuple6<_0, _1, _2, _3, _4, _5> cons(_0 _0) { return new Tuple6<>(_0, this); } + /** + * {@inheritDoc} + */ @Override public _1 _1() { return _1; } + /** + * {@inheritDoc} + */ @Override public _2 _2() { return _2; } + /** + * {@inheritDoc} + */ @Override public _3 _3() { return _3; } + /** + * {@inheritDoc} + */ @Override public _4 _4() { return _4; } + /** + * {@inheritDoc} + */ @Override public _5 _5() { return _5; } + /** + * {@inheritDoc} + */ @Override public Tuple5<_2, _3, _4, _5, _1> rotateL5() { return tuple(_2, _3, _4, _5, _1); } + /** + * {@inheritDoc} + */ @Override public Tuple5<_5, _1, _2, _3, _4> rotateR5() { return tuple(_5, _1, _2, _3, _4); } + /** + * {@inheritDoc} + */ @Override public Tuple5<_2, _3, _4, _1, _5> rotateL4() { return tuple(_2, _3, _4, _1, _5); } + /** + * {@inheritDoc} + */ @Override public Tuple5<_4, _1, _2, _3, _5> rotateR4() { return tuple(_4, _1, _2, _3, _5); } + /** + * {@inheritDoc} + */ @Override public Tuple5<_2, _3, _1, _4, _5> rotateL3() { return tuple(_2, _3, _1, _4, _5); } + /** + * {@inheritDoc} + */ @Override public Tuple5<_3, _1, _2, _4, _5> rotateR3() { return tuple(_3, _1, _2, _4, _5); } + /** + * {@inheritDoc} + */ @Override public Tuple5<_2, _1, _3, _4, _5> invert() { return tuple(_2, _1, _3, _4, _5); } + /** + * {@inheritDoc} + */ @Override public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> fmap(Fn1 fn) { - return Monad.super.<_5Prime>fmap(fn).coerce(); + return MonadRec.super.<_5Prime>fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public <_4Prime> Tuple5<_1, _2, _3, _4Prime, _5> biMapL(Fn1 fn) { - return (Tuple5<_1, _2, _3, _4Prime, _5>) Bifunctor.super.biMapL(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(Fn1 fn) { - return (Tuple5<_1, _2, _3, _4, _5Prime>) Bifunctor.super.biMapR(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(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(); + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_5Prime> Lazy> lazyZip( Lazy, Tuple5<_1, _2, _3, _4, ?>>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad<_5Prime, Tuple5<_1, _2, _3, _4, ?>>::coerce); + 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( Fn1>> f) { return pure(f.apply(_5).>coerce()._5()); } + /** + * {@inheritDoc} + */ + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> trampolineM( + Fn1, Tuple5<_1, _2, _3, _4, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce()._5())); + } + + /** + * {@inheritDoc} + */ @Override public <_5Prime, App extends Applicative, TravB extends Traversable<_5Prime, Tuple5<_1, _2, _3, _4, ?>>, AppTrav extends Applicative> AppTrav traverse( @@ -187,4 +273,38 @@ AppTrav extends Applicative> AppTrav traverse( 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 86991a8ae..6d07b503b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java @@ -1,15 +1,22 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product6; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A 6-element tuple product type, implemented as a specialized HList. Supports random access. @@ -30,7 +37,7 @@ */ public class Tuple6<_1, _2, _3, _4, _5, _6> extends HCons<_1, Tuple5<_2, _3, _4, _5, _6>> implements Product6<_1, _2, _3, _4, _5, _6>, - Monad<_6, Tuple6<_1, _2, _3, _4, _5, ?>>, + MonadRec<_6, Tuple6<_1, _2, _3, _4, _5, ?>>, Bifunctor<_5, _6, Tuple6<_1, _2, _3, _4, ?, ?>>, Traversable<_6, Tuple6<_1, _2, _3, _4, _5, ?>> { @@ -51,103 +58,161 @@ public class Tuple6<_1, _2, _3, _4, _5, _6> extends HCons<_1, Tuple5<_2, _3, _4, _6 = tail._5(); } + /** + * {@inheritDoc} + */ @Override public <_0> Tuple7<_0, _1, _2, _3, _4, _5, _6> cons(_0 _0) { return new Tuple7<>(_0, this); } + /** + * {@inheritDoc} + */ @Override public _1 _1() { return _1; } + /** + * {@inheritDoc} + */ @Override public _2 _2() { return _2; } + /** + * {@inheritDoc} + */ @Override public _3 _3() { return _3; } + /** + * {@inheritDoc} + */ @Override public _4 _4() { return _4; } + /** + * {@inheritDoc} + */ @Override public _5 _5() { return _5; } + /** + * {@inheritDoc} + */ @Override public _6 _6() { return _6; } + /** + * {@inheritDoc} + */ @Override public Tuple6<_2, _3, _4, _5, _6, _1> rotateL6() { return tuple(_2, _3, _4, _5, _6, _1); } + /** + * {@inheritDoc} + */ @Override public Tuple6<_6, _1, _2, _3, _4, _5> rotateR6() { return tuple(_6, _1, _2, _3, _4, _5); } + /** + * {@inheritDoc} + */ @Override public Tuple6<_2, _3, _4, _5, _1, _6> rotateL5() { return tuple(_2, _3, _4, _5, _1, _6); } + /** + * {@inheritDoc} + */ @Override public Tuple6<_5, _1, _2, _3, _4, _6> rotateR5() { return tuple(_5, _1, _2, _3, _4, _6); } + /** + * {@inheritDoc} + */ @Override public Tuple6<_2, _3, _4, _1, _5, _6> rotateL4() { return tuple(_2, _3, _4, _1, _5, _6); } + /** + * {@inheritDoc} + */ @Override public Tuple6<_4, _1, _2, _3, _5, _6> rotateR4() { return tuple(_4, _1, _2, _3, _5, _6); } + /** + * {@inheritDoc} + */ @Override public Tuple6<_2, _3, _1, _4, _5, _6> rotateL3() { return tuple(_2, _3, _1, _4, _5, _6); } + /** + * {@inheritDoc} + */ @Override public Tuple6<_3, _1, _2, _4, _5, _6> rotateR3() { return tuple(_3, _1, _2, _4, _5, _6); } + /** + * {@inheritDoc} + */ @Override public Tuple6<_2, _1, _3, _4, _5, _6> invert() { return tuple(_2, _1, _3, _4, _5, _6); } + /** + * {@inheritDoc} + */ @Override public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> fmap(Fn1 fn) { - return Monad.super.<_6Prime>fmap(fn).coerce(); + return MonadRec.super.<_6Prime>fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public <_5Prime> Tuple6<_1, _2, _3, _4, _5Prime, _6> biMapL(Fn1 fn) { - return (Tuple6<_1, _2, _3, _4, _5Prime, _6>) Bifunctor.super.biMapL(fn); + return (Tuple6<_1, _2, _3, _4, _5Prime, _6>) Bifunctor.super.<_5Prime>biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> biMapR(Fn1 fn) { - return (Tuple6<_1, _2, _3, _4, _5, _6Prime>) Bifunctor.super.biMapR(fn); + return (Tuple6<_1, _2, _3, _4, _5, _6Prime>) Bifunctor.super.<_6Prime>biMapR(fn); } + /** + * {@inheritDoc} + */ @Override public <_5Prime, _6Prime> Tuple6<_1, _2, _3, _4, _5Prime, _6Prime> biMap( Fn1 lFn, @@ -155,40 +220,71 @@ public <_5Prime, _6Prime> Tuple6<_1, _2, _3, _4, _5Prime, _6Prime> biMap( 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(); + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_6Prime> Lazy> lazyZip( Lazy, Tuple6<_1, _2, _3, _4, _5, ?>>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>>::coerce); + 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( Fn1>> f) { return pure(f.apply(_6).>coerce()._6()); } + /** + * {@inheritDoc} + */ + @Override + public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> trampolineM( + Fn1, Tuple6<_1, _2, _3, _4, _5, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce() + ._6())); + } + + /** + * {@inheritDoc} + */ @Override public <_6Prime, App extends Applicative, TravB extends Traversable<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>>, AppTrav extends Applicative> AppTrav traverse( @@ -208,4 +304,41 @@ AppTrav extends Applicative> AppTrav traverse( 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 60d42664c..a3e162f73 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java @@ -1,15 +1,22 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product7; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A 7-element tuple product type, implemented as a specialized HList. Supports random access. @@ -32,7 +39,7 @@ */ public class Tuple7<_1, _2, _3, _4, _5, _6, _7> extends HCons<_1, Tuple6<_2, _3, _4, _5, _6, _7>> implements Product7<_1, _2, _3, _4, _5, _6, _7>, - Monad<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, + MonadRec<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, Bifunctor<_6, _7, Tuple7<_1, _2, _3, _4, _5, ?, ?>>, Traversable<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>> { @@ -55,118 +62,185 @@ public class Tuple7<_1, _2, _3, _4, _5, _6, _7> extends HCons<_1, Tuple6<_2, _3, _7 = tail._6(); } + /** + * {@inheritDoc} + */ @Override public <_0> Tuple8<_0, _1, _2, _3, _4, _5, _6, _7> cons(_0 _0) { return new Tuple8<>(_0, this); } + /** + * {@inheritDoc} + */ @Override public _1 _1() { return _1; } + /** + * {@inheritDoc} + */ @Override public _2 _2() { return _2; } + /** + * {@inheritDoc} + */ @Override public _3 _3() { return _3; } + /** + * {@inheritDoc} + */ @Override public _4 _4() { return _4; } + /** + * {@inheritDoc} + */ @Override public _5 _5() { return _5; } + /** + * {@inheritDoc} + */ @Override public _6 _6() { return _6; } + /** + * {@inheritDoc} + */ @Override public _7 _7() { return _7; } + /** + * {@inheritDoc} + */ @Override public Tuple7<_2, _3, _4, _5, _6, _7, _1> rotateL7() { return tuple(_2, _3, _4, _5, _6, _7, _1); } + /** + * {@inheritDoc} + */ @Override public Tuple7<_7, _1, _2, _3, _4, _5, _6> rotateR7() { return tuple(_7, _1, _2, _3, _4, _5, _6); } + /** + * {@inheritDoc} + */ @Override public Tuple7<_2, _3, _4, _5, _6, _1, _7> rotateL6() { return tuple(_2, _3, _4, _5, _6, _1, _7); } + /** + * {@inheritDoc} + */ @Override public Tuple7<_6, _1, _2, _3, _4, _5, _7> rotateR6() { return tuple(_6, _1, _2, _3, _4, _5, _7); } + /** + * {@inheritDoc} + */ @Override public Tuple7<_2, _3, _4, _5, _1, _6, _7> rotateL5() { return tuple(_2, _3, _4, _5, _1, _6, _7); } + /** + * {@inheritDoc} + */ @Override public Tuple7<_5, _1, _2, _3, _4, _6, _7> rotateR5() { return tuple(_5, _1, _2, _3, _4, _6, _7); } + /** + * {@inheritDoc} + */ @Override public Tuple7<_2, _3, _4, _1, _5, _6, _7> rotateL4() { return tuple(_2, _3, _4, _1, _5, _6, _7); } + /** + * {@inheritDoc} + */ @Override public Tuple7<_4, _1, _2, _3, _5, _6, _7> rotateR4() { return tuple(_4, _1, _2, _3, _5, _6, _7); } + /** + * {@inheritDoc} + */ @Override public Tuple7<_2, _3, _1, _4, _5, _6, _7> rotateL3() { return tuple(_2, _3, _1, _4, _5, _6, _7); } + /** + * {@inheritDoc} + */ @Override public Tuple7<_3, _1, _2, _4, _5, _6, _7> rotateR3() { return tuple(_3, _1, _2, _4, _5, _6, _7); } + /** + * {@inheritDoc} + */ @Override public Tuple7<_2, _1, _3, _4, _5, _6, _7> invert() { return tuple(_2, _1, _3, _4, _5, _6, _7); } + /** + * {@inheritDoc} + */ @Override public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> fmap(Fn1 fn) { - return Monad.super.<_7Prime>fmap(fn).coerce(); + return MonadRec.super.<_7Prime>fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public <_6Prime> Tuple7<_1, _2, _3, _4, _5, _6Prime, _7> biMapL(Fn1 fn) { - return (Tuple7<_1, _2, _3, _4, _5, _6Prime, _7>) Bifunctor.super.biMapL(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(Fn1 fn) { - return (Tuple7<_1, _2, _3, _4, _5, _6, _7Prime>) Bifunctor.super.biMapR(fn); + return (Tuple7<_1, _2, _3, _4, _5, _6, _7Prime>) Bifunctor.super.<_7Prime>biMapR(fn); } + /** + * {@inheritDoc} + */ @Override public <_6Prime, _7Prime> Tuple7<_1, _2, _3, _4, _5, _6Prime, _7Prime> biMap( Fn1 lFn, @@ -174,41 +248,72 @@ public <_6Prime, _7Prime> Tuple7<_1, _2, _3, _4, _5, _6Prime, _7Prime> biMap( 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(); + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_7Prime> Lazy> lazyZip( Lazy, Tuple7<_1, _2, _3, _4, _5, _6, ?>>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>>::coerce); + 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( Fn1>> f) { return pure(f.apply(_7).>coerce()._7()); } + /** + * {@inheritDoc} + */ + @Override + public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> trampolineM( + Fn1, Tuple7<_1, _2, _3, _4, _5, _6, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x).>>coerce() + ._7())); + } + + /** + * {@inheritDoc} + */ @Override public <_7Prime, App extends Applicative, TravB extends Traversable<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, @@ -229,4 +334,43 @@ AppTrav extends Applicative> AppTrav traverse( 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 81054635e..8a8001e24 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java @@ -1,15 +1,22 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product8; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Into; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * An 8-element tuple product type, implemented as a specialized HList. Supports random access. @@ -34,7 +41,7 @@ */ public class Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> extends HCons<_1, Tuple7<_2, _3, _4, _5, _6, _7, _8>> implements Product8<_1, _2, _3, _4, _5, _6, _7, _8>, - Monad<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, + MonadRec<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, Bifunctor<_7, _8, Tuple8<_1, _2, _3, _4, _5, _6, ?, ?>>, Traversable<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>> { @@ -59,133 +66,209 @@ public class Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> extends HCons<_1, Tuple7<_2, _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); } + /** + * {@inheritDoc} + */ @Override public _1 _1() { return _1; } + /** + * {@inheritDoc} + */ @Override public _2 _2() { return _2; } + /** + * {@inheritDoc} + */ @Override public _3 _3() { return _3; } + /** + * {@inheritDoc} + */ @Override public _4 _4() { return _4; } + /** + * {@inheritDoc} + */ @Override public _5 _5() { return _5; } + /** + * {@inheritDoc} + */ @Override public _6 _6() { return _6; } + /** + * {@inheritDoc} + */ @Override public _7 _7() { return _7; } + /** + * {@inheritDoc} + */ @Override public _8 _8() { return _8; } + /** + * {@inheritDoc} + */ @Override public Tuple8<_2, _3, _4, _5, _6, _7, _8, _1> rotateL8() { return tuple(_2, _3, _4, _5, _6, _7, _8, _1); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_8, _1, _2, _3, _4, _5, _6, _7> rotateR8() { return tuple(_8, _1, _2, _3, _4, _5, _6, _7); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_2, _3, _4, _5, _6, _7, _1, _8> rotateL7() { return tuple(_2, _3, _4, _5, _6, _7, _1, _8); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_7, _1, _2, _3, _4, _5, _6, _8> rotateR7() { return tuple(_7, _1, _2, _3, _4, _5, _6, _8); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_2, _3, _4, _5, _6, _1, _7, _8> rotateL6() { return tuple(_2, _3, _4, _5, _6, _1, _7, _8); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_6, _1, _2, _3, _4, _5, _7, _8> rotateR6() { return tuple(_6, _1, _2, _3, _4, _5, _7, _8); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_2, _3, _4, _5, _1, _6, _7, _8> rotateL5() { return tuple(_2, _3, _4, _5, _1, _6, _7, _8); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_5, _1, _2, _3, _4, _6, _7, _8> rotateR5() { return tuple(_5, _1, _2, _3, _4, _6, _7, _8); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_2, _3, _4, _1, _5, _6, _7, _8> rotateL4() { return tuple(_2, _3, _4, _1, _5, _6, _7, _8); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_4, _1, _2, _3, _5, _6, _7, _8> rotateR4() { return tuple(_4, _1, _2, _3, _5, _6, _7, _8); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_2, _3, _1, _4, _5, _6, _7, _8> rotateL3() { return tuple(_2, _3, _1, _4, _5, _6, _7, _8); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_3, _1, _2, _4, _5, _6, _7, _8> rotateR3() { return tuple(_3, _1, _2, _4, _5, _6, _7, _8); } + /** + * {@inheritDoc} + */ @Override public Tuple8<_2, _1, _3, _4, _5, _6, _7, _8> invert() { return tuple(_2, _1, _3, _4, _5, _6, _7, _8); } + /** + * {@inheritDoc} + */ @Override public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> fmap(Fn1 fn) { - return Monad.super.<_8Prime>fmap(fn).coerce(); + return MonadRec.super.<_8Prime>fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") 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.biMapL(fn); + return (Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8>) Bifunctor.super.<_7Prime>biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> biMapR(Fn1 fn) { - return (Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime>) Bifunctor.super.biMapR(fn); + return (Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime>) Bifunctor.super.<_8Prime>biMapR(fn); } + /** + * {@inheritDoc} + */ @Override public <_7Prime, _8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8Prime> biMap( Fn1 lFn, @@ -193,42 +276,74 @@ public <_7Prime, _8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7Prime, _8Prime> biMap 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(); + return MonadRec.super.zip(appFn).coerce(); } + /** + * {@inheritDoc} + */ @Override public <_8Prime> Lazy> lazyZip( Lazy, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>::coerce); + 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( Fn1>> f) { return pure(f.apply(_8).>coerce()._8()); } + /** + * {@inheritDoc} + */ + @Override + public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> trampolineM( + Fn1, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>> fn) { + return fmap(trampoline(x -> fn.apply(x) + .>>coerce() + ._8())); + } + + /** + * {@inheritDoc} + */ @Override public <_8Prime, App extends Applicative, TravB extends Traversable<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, @@ -249,4 +364,48 @@ AppTrav extends Applicative> AppTrav traverse( 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 e1677d49f..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; @@ -45,9 +46,8 @@ private HMap(Map, Object> table) { * @param the value type * @return Maybe the value at this key */ - @SuppressWarnings("unchecked") public Maybe get(TypeSafeKey key) { - return maybe((A) table.get(key)).fmap(view(key)); + return maybe(Downcast.downcast(table.get(key))).fmap(view(key)); } /** @@ -59,7 +59,8 @@ public Maybe get(TypeSafeKey key) { * @throws NoSuchElementException if the key is unmapped */ public V demand(TypeSafeKey key) throws NoSuchElementException { - return get(key).orElseThrow(() -> new NoSuchElementException("Demanded value for key " + key + ", but couldn't find one.")); + return get(key).orElseThrow(() -> new NoSuchElementException("Demanded value for key " + key + + ", but couldn't find one.")); } /** @@ -114,6 +115,15 @@ public HMap removeAll(HMap hMap) { return alter(t -> t.keySet().removeAll(hMap.table.keySet())); } + /** + * Test whether this {@link HMap} is empty. + * + * @return true if the {@link HMap} is empty; false otherwise. + */ + public boolean isEmpty() { + return table.isEmpty(); + } + /** * Retrieve all the mapped keys. *

diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java index f72b9f8b4..61c9b181a 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/Schema.java @@ -11,8 +11,8 @@ 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.Fn1; 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; @@ -28,8 +28,16 @@ * @param the {@link HList} of values to focus on * @see TypeSafeKey */ -public interface Schema> extends Lens.Simple> { +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)) @@ -39,37 +47,85 @@ default > Schema add(TypeSafeKe 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) { + 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) { + 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, @@ -77,7 +133,21 @@ static Schema> schema(TypeSafeKey aKey, return schema(bKey, cKey, dKey).add(aKey); } - + /** + * Create a {@link Schema} from five {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @return the {@link Schema} + */ static Schema> schema(TypeSafeKey aKey, TypeSafeKey bKey, TypeSafeKey cKey, @@ -86,6 +156,23 @@ static Schema> schema(TypeSafeKey aK return schema(bKey, cKey, dKey, eKey).add(aKey); } + /** + * Create a {@link Schema} from six {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param fKey the sixth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @param the type of value the sixth key focuses on + * @return the {@link Schema} + */ static Schema> schema(TypeSafeKey aKey, TypeSafeKey bKey, TypeSafeKey cKey, @@ -95,6 +182,25 @@ static Schema> schema(TypeSafeKey the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @param the type of value the sixth key focuses on + * @param the type of value the seventh key focuses on + * @return the {@link Schema} + */ static Schema> schema(TypeSafeKey aKey, TypeSafeKey bKey, TypeSafeKey cKey, @@ -105,6 +211,27 @@ static Schema> schema(TypeSafe return schema(bKey, cKey, dKey, eKey, fKey, gKey).add(aKey); } + /** + * Create a {@link Schema} from eight {@link TypeSafeKey TypeSafeKeys}. + * + * @param aKey the first {@link TypeSafeKey} + * @param bKey the second {@link TypeSafeKey} + * @param cKey the third {@link TypeSafeKey} + * @param dKey the fourth {@link TypeSafeKey} + * @param eKey the fifth {@link TypeSafeKey} + * @param fKey the sixth {@link TypeSafeKey} + * @param gKey the seventh {@link TypeSafeKey} + * @param hKey the eighth {@link TypeSafeKey} + * @param the type of value the first key focuses on + * @param the type of value the second key focuses on + * @param the type of value the third key focuses on + * @param the type of value the fourth key focuses on + * @param the type of value the fifth key focuses on + * @param the type of value the sixth key focuses on + * @param the type of value the seventh key focuses on + * @param the type of value the eighth key focuses on + * @return the {@link Schema} + */ static Schema> schema(TypeSafeKey aKey, TypeSafeKey bKey, TypeSafeKey cKey, diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java index ce9361693..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 @@ -23,14 +23,17 @@ */ public interface TypeSafeKey extends Iso.Simple { - @Override default TypeSafeKey discardR(Applicative> appB) { Iso.Simple discarded = Iso.Simple.super.discardR(appB); return new TypeSafeKey() { @Override - public >, CoF extends Functor>, FB extends Functor, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( - PAFB pafb) { + public >, + CoF extends Functor>, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { return discarded.apply(pafb); } @@ -61,12 +64,16 @@ public boolean equals(Object obj) { * @return the new {@link TypeSafeKey} */ @Override - default TypeSafeKeyandThen(Iso.Simple f) { + default TypeSafeKey andThen(Iso.Simple f) { Iso.Simple composed = Iso.Simple.super.andThen(f); return new TypeSafeKey() { @Override - public >, CoF extends Functor>, FB extends Functor, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( - PAFB pafb) { + public >, + CoF extends Functor>, + FB extends Functor, + FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb) { return composed.apply(pafb); } 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 d08195f42..3f2be1e92 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -3,17 +3,24 @@ import com.jnape.palatable.lambda.adt.Either; import com.jnape.palatable.lambda.adt.choice.Choice2; import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.builtin.fn1.Constantly; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Cartesian; import com.jnape.palatable.lambda.functor.Cocartesian; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.internal.Runtime; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadReader; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.MonadWriter; import java.util.function.Function; import static com.jnape.palatable.lambda.functions.Fn2.curried; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** * A function taking a single argument. This is the core function type that all other function types extend and @@ -24,7 +31,9 @@ */ @FunctionalInterface public interface Fn1 extends - Monad>, + MonadRec>, + MonadReader>, + MonadWriter>, Cartesian>, Cocartesian> { @@ -81,6 +90,30 @@ 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} */ @@ -114,7 +147,7 @@ default Fn1 pure(C c) { */ @Override default Fn1 zip(Applicative, Fn1> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -130,7 +163,15 @@ default Fn1 zip(Fn2 appFn) { */ @Override default Lazy> lazyZip(Lazy, Fn1>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + 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)); } /** @@ -138,7 +179,7 @@ default Lazy> lazyZip(Lazy Fn1 discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -146,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(); } /** @@ -284,4 +325,14 @@ static Fn1 fn1(Fn1 fn) { 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; + } } 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 0528ff71a..0da885cdf 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.functions; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.internal.Runtime; import java.util.function.BiFunction; +import 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; @@ -153,6 +155,19 @@ 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. * diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java index 5eb0b2cc4..b830a7e9c 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Alter.java @@ -13,7 +13,7 @@ * * @param the input and output */ -public final class Alter implements Fn2, A, IO> { +public final class Alter implements Fn2>, A, IO> { private static final Alter INSTANCE = new Alter<>(); @@ -21,7 +21,7 @@ private Alter() { } @Override - public IO checkedApply(Effect effect, A a) { + public IO checkedApply(Fn1> effect, A a) { return effect.fmap(io -> io.fmap(constantly(a))).apply(a); } 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 e4c5aeda1..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 ==. * @@ -17,7 +19,7 @@ private Eq() { @Override public Boolean checkedApply(A x, A y) { - return x == null ? y == null : x.equals(y); + return Objects.equals(x, y); } @SuppressWarnings("unchecked") 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 4ff11cdec..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,8 +7,8 @@ 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 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 index 7ebb8fe6f..c1b7253d2 100644 --- 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 @@ -1,11 +1,11 @@ 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.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; -import static com.jnape.palatable.lambda.monad.Monad.join; /** * Given a {@link Fn2} that receives a recursive function and an input and yields a {@link Lazy lazy} result, and an @@ -26,7 +26,8 @@ * @param the input type * @param the output type */ -public final class LazyRec implements Fn2>, A, Lazy>, A, Lazy> { +public final class LazyRec implements + Fn2, Lazy>, A, Lazy>, A, Lazy> { private static final LazyRec INSTANCE = new LazyRec<>(); @@ -34,8 +35,8 @@ private LazyRec() { } @Override - public Lazy checkedApply(Fn2>, A, Lazy> fn, A a) { - return join(lazy(() -> fn.apply(nextA -> apply(fn, nextA), a))); + public Lazy checkedApply(Fn2, Lazy>, A, Lazy> fn, A a) { + return lazy(a).flatMap(fn.apply(lazyRec(fn))); } @SuppressWarnings("unchecked") @@ -43,11 +44,12 @@ public static LazyRec lazyRec() { return (LazyRec) INSTANCE; } - public static Fn1> lazyRec(Fn2>, A, Lazy> fn) { - return LazyRec.lazyRec().apply(fn); + public static Kleisli, Lazy> lazyRec( + Fn2, Lazy>, A, Lazy> fn) { + return kleisli(LazyRec.lazyRec().apply(fn)); } - public static Lazy lazyRec(Fn2>, A, Lazy> fn, A a) { + 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/Peek.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek.java index bea6bb1c5..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 @@ -12,7 +12,9 @@ * * @param the functor parameter type * @param the functor type + * @deprecated in favor of producing an {@link IO} from the given {@link Functor} and explicitly running it */ +@Deprecated public final class Peek> implements Fn2>, FA, FA> { private static final Peek INSTANCE = new Peek<>(); 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 3114f26c1..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 @@ -15,7 +15,9 @@ * @param the bifunctor's first parameter type * @param the bifunctor's second parameter type * @param the bifunctor type + * @deprecated in favor of producing an {@link IO} from the given {@link BoundedBifunctor} and explicitly running it */ +@Deprecated public final class Peek2> implements Fn3>, Fn1>, FAB, FAB> { private static final Peek2 INSTANCE = new Peek2<>(); 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/fn3/LiftA2.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2.java index e83048652..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 @@ -12,54 +12,43 @@ * @param the function's first argument type * @param the function's second argument type * @param the function's return type - * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type + * @param the applicative witness * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA2, - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative> implements - Fn3, AppA, AppB, AppC> { +public final class LiftA2, AppC extends Applicative> implements + Fn3, Applicative, Applicative, AppC> { - private static final LiftA2 INSTANCE = new LiftA2<>(); + private static final LiftA2 INSTANCE = new LiftA2<>(); private LiftA2() { } @Override - public AppC checkedApply(Fn2 fn, AppA appA, AppB appB) { - return appB.zip(appA.fmap(fn)).coerce(); + public AppC checkedApply(Fn2 fn, + Applicative appA, + Applicative appB) { + return appA.zip(appB.fmap(b -> a -> fn.apply(a, b))).coerce(); } @SuppressWarnings("unchecked") - public static , AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative> LiftA2 liftA2() { - return (LiftA2) INSTANCE; + public static , AppC extends Applicative> + LiftA2 liftA2() { + return (LiftA2) INSTANCE; } - public static , AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative> Fn2 liftA2( - Fn2 fn) { - return LiftA2.liftA2().apply(fn); + public static , AppC extends Applicative> + Fn2, Applicative, AppC> liftA2(Fn2 fn) { + return LiftA2.liftA2().apply(fn); } - public static , AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative> Fn1 liftA2(Fn2 fn, - AppA appA) { - return LiftA2.liftA2(fn).apply(appA); + public static , AppC extends Applicative> + Fn1, AppC> liftA2(Fn2 fn, Applicative appA) { + return LiftA2.liftA2(fn).apply(appA); } - public static , AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative> AppC liftA2(Fn2 fn, - AppA appA, - AppB appB) { - return LiftA2.liftA2(fn, appA).apply(appB); + public static , AppC extends Applicative> + AppC liftA2(Fn2 fn, Applicative appA, Applicative appB) { + return LiftA2.liftA2(fn, appA).apply(appB); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3.java index b8ccd2527..9000ad81a 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/LiftA3.java @@ -14,74 +14,49 @@ * @param the function's second argument type * @param the function's third argument type * @param the function's return type - * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type - * @param the inferred third applicative argument type + * @param the applicative witness * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA3, - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> implements Fn4, AppA, AppB, AppC, AppD> { +public final class LiftA3, AppD extends Applicative> implements + Fn4, Applicative, Applicative, Applicative, AppD> { - private static final LiftA3 INSTANCE = new LiftA3<>(); + private static final LiftA3 INSTANCE = new LiftA3<>(); private LiftA3() { } @Override - public AppD checkedApply(Fn3 fn, AppA appA, AppB appB, AppC appC) { - return appC.zip(appB.zip(appA.fmap(fn))).coerce(); + public AppD checkedApply(Fn3 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return appA.zip(appB.zip(appC.fmap(c -> b -> a -> fn.apply(a, b, c)))).coerce(); } @SuppressWarnings("unchecked") - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> LiftA3 liftA3() { - return (LiftA3) INSTANCE; + public static , AppD extends Applicative> + LiftA3 liftA3() { + return (LiftA3) INSTANCE; } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> Fn3 liftA3(Fn3 fn) { - return LiftA3.liftA3().apply(fn); + public static , AppD extends Applicative> + Fn3, Applicative, Applicative, AppD> liftA3(Fn3 fn) { + return LiftA3.liftA3().apply(fn); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> Fn2 liftA3(Fn3 fn, AppA appA) { - return LiftA3.liftA3(fn).apply(appA); + public static , AppD extends Applicative> + Fn2, Applicative, AppD> liftA3(Fn3 fn, Applicative appA) { + return LiftA3.liftA3(fn).apply(appA); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> Fn1 liftA3(Fn3 fn, AppA appA, AppB appB) { - return LiftA3.liftA3(fn, appA).apply(appB); + public static , AppD extends Applicative> + Fn1, AppD> liftA3(Fn3 fn, Applicative appA, Applicative appB) { + return LiftA3.liftA3(fn, appA).apply(appB); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative> AppD liftA3(Fn3 fn, AppA appA, AppB appB, - AppC appC) { - return LiftA3.liftA3(fn, appA, appB).apply(appC); + public static , AppD extends Applicative> + AppD liftA3(Fn3 fn, Applicative appA, Applicative appB, Applicative appC) { + return LiftA3.liftA3(fn, appA, appB).apply(appC); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4.java index 408f94652..9132376dc 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn5/LiftA4.java @@ -16,93 +16,64 @@ * @param the function's third argument type * @param the function's fourth argument type * @param the function's return type - * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type - * @param the inferred third applicative argument type - * @param the inferred fourth applicative argument type + * @param the applicative witness * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA4, - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> implements Fn5, AppA, AppB, AppC, AppD, AppE> { +public final class LiftA4, AppE extends Applicative> implements + Fn5, Applicative, Applicative, Applicative, Applicative, + AppE> { - private static final LiftA4 INSTANCE = new LiftA4<>(); + private static final LiftA4 INSTANCE = new LiftA4<>(); private LiftA4() { } @Override - public AppE checkedApply(Fn4 fn, AppA appA, AppB appB, AppC appC, AppD appD) { - return appD.zip(appC.zip(appB.zip(appA.fmap(fn)))).coerce(); + public AppE checkedApply(Fn4 fn, Applicative appA, Applicative appB, + Applicative appC, Applicative appD) { + return appA.zip(appB.zip(appC.zip(appD.fmap(d -> c -> b -> a -> fn.apply(a, b, c, d))))).coerce(); } @SuppressWarnings("unchecked") - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> LiftA4 liftA4() { - return (LiftA4) INSTANCE; + public static , AppE extends Applicative> + LiftA4 liftA4() { + return (LiftA4) INSTANCE; } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> Fn4 liftA4(Fn4 fn) { - return LiftA4.liftA4().apply(fn); + public static , AppE extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppE> liftA4( + Fn4 fn) { + return LiftA4.liftA4().apply(fn); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> Fn3 liftA4(Fn4 fn, AppA appA) { - return LiftA4.liftA4(fn).apply(appA); + public static , AppE extends Applicative> + Fn3, Applicative, Applicative, AppE> liftA4(Fn4 fn, + Applicative appA) { + return LiftA4.liftA4(fn).apply(appA); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> Fn2 liftA4(Fn4 fn, AppA appA, - AppB appB) { - return LiftA4.liftA4(fn, appA).apply(appB); + public static , AppE extends Applicative> + Fn2, Applicative, AppE> liftA4(Fn4 fn, + Applicative appA, + Applicative appB) { + return LiftA4.liftA4(fn, appA).apply(appB); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> Fn1 liftA4(Fn4 fn, AppA appA, AppB appB, - AppC appC) { - return LiftA4.liftA4(fn, appA, appB).apply(appC); + public static , AppE extends Applicative> + Fn1, AppE> liftA4(Fn4 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA4.liftA4(fn, appA, appB).apply(appC); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative> AppE liftA4(Fn4 fn, AppA appA, AppB appB, - AppC appC, AppD appD) { - return LiftA4.liftA4(fn, appA, appB, appC).apply(appD); + public static , AppE extends Applicative> + AppE liftA4(Fn4 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA4.liftA4(fn, appA, appB, appC).apply(appD); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5.java index 4711de30a..7fd7c8f4f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5.java @@ -18,115 +18,76 @@ * @param the function's fourth argument type * @param the function's fifth argument type * @param the function's return type - * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type - * @param the inferred third applicative argument type - * @param the inferred fourth applicative argument type - * @param the inferred fifth applicative argument type + * @param the applicative witness * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA5, - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> implements Fn6, AppA, AppB, AppC, AppD, AppE, AppF> { +public final class LiftA5, AppF extends Applicative> + implements Fn6, Applicative, Applicative, Applicative, + Applicative, Applicative, AppF> { - private static final LiftA5 INSTANCE = new LiftA5<>(); + private static final LiftA5 INSTANCE = new LiftA5<>(); private LiftA5() { } @Override - public AppF checkedApply(Fn5 fn, AppA appA, AppB appB, AppC appC, AppD appD, AppE appE) { - return appE.zip(appD.zip(appC.zip(appB.zip(appA.fmap(fn))))).coerce(); + public AppF checkedApply(Fn5 fn, Applicative appA, Applicative appB, + Applicative appC, Applicative appD, Applicative appE) { + return appA.zip(appB.zip(appC.zip(appD.zip(appE.fmap(e -> d -> c -> b -> a -> fn.apply(a, b, c, d, e)))))) + .coerce(); } @SuppressWarnings("unchecked") - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> LiftA5 liftA5() { - return (LiftA5) INSTANCE; + public static , AppF extends Applicative> + LiftA5 liftA5() { + return (LiftA5) INSTANCE; } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> Fn5 liftA5(Fn5 fn) { - return LiftA5.liftA5().apply(fn); + public static , AppF extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppF> + liftA5(Fn5 fn) { + return LiftA5.liftA5().apply(fn); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> Fn4 liftA5(Fn5 fn, - AppA appA) { - return LiftA5.liftA5(fn).apply(appA); + public static , AppF extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppF> + liftA5(Fn5 fn, + Applicative appA) { + return LiftA5.liftA5(fn).apply(appA); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> Fn3 liftA5(Fn5 fn, AppA appA, - AppB appB) { - return LiftA5.liftA5(fn, appA).apply(appB); + public static , AppF extends Applicative> + Fn3, Applicative, Applicative, AppF> liftA5(Fn5 fn, + Applicative appA, + Applicative appB) { + return LiftA5.liftA5(fn, appA).apply(appB); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> Fn2 liftA5(Fn5 fn, AppA appA, - AppB appB, - AppC appC) { - return LiftA5.liftA5(fn, appA, appB).apply(appC); + public static , AppF extends Applicative> + Fn2, Applicative, AppF> liftA5(Fn5 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA5.liftA5(fn, appA, appB).apply(appC); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> Fn1 liftA5(Fn5 fn, AppA appA, AppB appB, - AppC appC, AppD appD) { - return LiftA5.liftA5(fn, appA, appB, appC).apply(appD); + public static , AppF extends Applicative> + Fn1, AppF> liftA5(Fn5 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA5.liftA5(fn, appA, appB, appC).apply(appD); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative> AppF liftA5(Fn5 fn, AppA appA, AppB appB, - AppC appC, AppD appD, AppE appE) { - return LiftA5.liftA5(fn, appA, appB, appC, appD).apply(appE); + public static , AppF extends Applicative> + AppF liftA5(Fn5 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE) { + return LiftA5.liftA5(fn, appA, appB, appC, appD).apply(appE); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6.java index 90234684d..fcc7934c9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6.java @@ -20,145 +20,98 @@ * @param the function's fifth argument type * @param the function's sixth argument type * @param the function's return type - * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type - * @param the inferred third applicative argument type - * @param the inferred fourth applicative argument type - * @param the inferred fifth applicative argument type - * @param the inferred sixth applicative argument type + * @param the applicative witness * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA6, - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> implements - Fn7, AppA, AppB, AppC, AppD, AppE, AppF, AppG> { +public final class LiftA6, AppG extends Applicative> + implements Fn7, + Applicative, + Applicative, + Applicative, + Applicative, + Applicative, + Applicative, + AppG> { - private static final LiftA6 INSTANCE = new LiftA6<>(); + private static final LiftA6 INSTANCE = new LiftA6<>(); private LiftA6() { } @Override - public AppG checkedApply(Fn6 fn, AppA appA, AppB appB, AppC appC, AppD appD, AppE appE, - AppF appF) { - return appF.zip(appE.zip(appD.zip(appC.zip(appB.zip(appA.fmap(fn)))))).coerce(); + public AppG checkedApply(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF) { + return appA.zip(appB.zip(appC.zip(appD.zip(appE.zip(appF.fmap( + f -> e -> d -> c -> b -> a -> fn.apply(a, b, c, d, e, f))))))).coerce(); } - @SuppressWarnings("unchecked") - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> - LiftA6 liftA6() { - return (LiftA6) INSTANCE; + public static , AppG extends Applicative> + LiftA6 liftA6() { + return (LiftA6) INSTANCE; } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn6 liftA6( - Fn6 fn) { - return LiftA6.liftA6().apply(fn); + public static , AppG extends Applicative> + Fn6, Applicative, Applicative, Applicative, Applicative, + Applicative, AppG> liftA6(Fn6 fn) { + return LiftA6.liftA6().apply(fn); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn5 liftA6( - Fn6 fn, - AppA appA) { - return LiftA6.liftA6(fn).apply(appA); + public static , AppG extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppG> + liftA6(Fn6 fn, Applicative appA) { + return LiftA6.liftA6(fn).apply(appA); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn4 liftA6(Fn6 fn, - AppA appA, - AppB appB) { - return LiftA6.liftA6(fn, appA).apply(appB); + public static , AppG extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppG> + liftA6(Fn6 fn, + Applicative appA, + Applicative appB) { + return LiftA6.liftA6(fn, appA).apply(appB); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn3 liftA6(Fn6 fn, AppA appA, - AppB appB, - AppC appC) { - return LiftA6.liftA6(fn, appA, appB).apply(appC); + public static , AppG extends Applicative> + Fn3, Applicative, Applicative, AppG> liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA6.liftA6(fn, appA, appB).apply(appC); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn2 liftA6(Fn6 fn, AppA appA, - AppB appB, - AppC appC, AppD appD) { - return LiftA6.liftA6(fn, appA, appB, appC).apply(appD); + public static , AppG extends Applicative> + Fn2, Applicative, AppG> liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA6.liftA6(fn, appA, appB, appC).apply(appD); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> Fn1 liftA6(Fn6 fn, AppA appA, AppB appB, - AppC appC, AppD appD, AppE appE) { - return LiftA6.liftA6(fn, appA, appB, appC, appD).apply(appE); + public static , AppG extends Applicative> + Fn1, AppG> liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE) { + return LiftA6.liftA6(fn, appA, appB, appC, appD).apply(appE); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative> AppG liftA6(Fn6 fn, AppA appA, AppB appB, - AppC appC, AppD appD, AppE appE, AppF appF) { - return LiftA6.liftA6(fn, appA, appB, appC, appD, appE).apply(appF); + public static , AppG extends Applicative> + AppG liftA6(Fn6 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF) { + return LiftA6.liftA6(fn, appA, appB, appC, appD, appE).apply(appF); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7.java index 90eab07a5..6f80b7d37 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7.java @@ -23,164 +23,106 @@ * @param the function's seventh argument type * @param the function's return type * @param the applicative unification type - * @param the inferred first applicative argument type - * @param the inferred second applicative argument type - * @param the inferred third applicative argument type - * @param the inferred fourth applicative argument type - * @param the inferred fifth applicative argument type - * @param the inferred sixth applicative argument type - * @param the inferred seventh applicative argument type * @param the inferred applicative return type * @see Applicative#zip(Applicative) */ -public final class LiftA7, - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> implements Fn8, AppA, AppB, AppC, AppD, AppE, AppF, AppG, AppH> { +public final class LiftA7, AppH extends Applicative> + implements Fn8, Applicative, Applicative, Applicative, + Applicative, Applicative, Applicative, Applicative, AppH> { - private static final LiftA7 INSTANCE = new LiftA7<>(); + private static final LiftA7 INSTANCE = new LiftA7<>(); private LiftA7() { } @Override - public AppH checkedApply(Fn7 fn, AppA appA, AppB appB, AppC appC, AppD appD, AppE appE, - AppF appF, AppG appG) { - return appG.zip(appF.zip(appE.zip(appD.zip(appC.zip(appB.zip(appA.fmap(fn))))))).coerce(); + public AppH checkedApply(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF, + Applicative appG) { + return appA.zip(appB.zip(appC.zip(appD.zip(appE.zip(appF.zip(appG.fmap( + g -> f -> e -> d -> c -> b -> a -> fn.apply(a, b, c, d, e, f, g)))))))).coerce(); } @SuppressWarnings("unchecked") - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> LiftA7 liftA7() { - return (LiftA7) INSTANCE; + public static , AppH extends Applicative> + LiftA7 liftA7() { + return (LiftA7) INSTANCE; } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn7 liftA7( - Fn7 fn) { - return LiftA7.liftA7().apply(fn); + public static , AppH extends Applicative> + Fn7, Applicative, Applicative, Applicative, Applicative, + Applicative, Applicative, AppH> liftA7(Fn7 fn) { + return LiftA7.liftA7().apply(fn); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn6 liftA7( - Fn7 fn, AppA appA) { - return LiftA7.liftA7(fn).apply(appA); + public static , AppH extends Applicative> + Fn6, Applicative, Applicative, Applicative, Applicative, + Applicative, AppH> liftA7(Fn7 fn, + Applicative appA) { + return LiftA7.liftA7(fn).apply(appA); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn5 liftA7( - Fn7 fn, AppA appA, AppB appB) { - return LiftA7.liftA7(fn, appA).apply(appB); + public static , AppH extends Applicative> + Fn5, Applicative, Applicative, Applicative, Applicative, AppH> + liftA7(Fn7 fn, + Applicative appA, + Applicative appB) { + return LiftA7.liftA7(fn, appA).apply(appB); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn4 liftA7(Fn7 fn, - AppA appA, AppB appB, - AppC appC) { - return LiftA7.liftA7(fn, appA, appB).apply(appC); + public static , AppH extends Applicative> + Fn4, Applicative, Applicative, Applicative, AppH> liftA7( + Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC) { + return LiftA7.liftA7(fn, appA, appB).apply(appC); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn3 liftA7(Fn7 fn, - AppA appA, AppB appB, AppC appC, - AppD appD) { - return LiftA7.liftA7(fn, appA, appB, appC).apply(appD); + public static , AppH extends Applicative> + Fn3, Applicative, Applicative, AppH> liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD) { + return LiftA7.liftA7(fn, appA, appB, appC).apply(appD); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn2 liftA7(Fn7 fn, AppA appA, - AppB appB, AppC appC, AppD appD, AppE appE) { - return LiftA7.liftA7(fn, appA, appB, appC, appD).apply(appE); + public static , AppH extends Applicative> + Fn2, Applicative, AppH> liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE) { + return LiftA7.liftA7(fn, appA, appB, appC, appD).apply(appE); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> Fn1 liftA7(Fn7 fn, AppA appA, - AppB appB, AppC appC, AppD appD, AppE appE, - AppF appF) { - return LiftA7.liftA7(fn, appA, appB, appC, appD, appE).apply(appF); + public static , AppH extends Applicative> + Fn1, AppH> liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF) { + return LiftA7.liftA7(fn, appA, appB, appC, appD, appE).apply(appF); } - public static , - AppA extends Applicative, - AppB extends Applicative, - AppC extends Applicative, - AppD extends Applicative, - AppE extends Applicative, - AppF extends Applicative, - AppG extends Applicative, - AppH extends Applicative> AppH liftA7(Fn7 fn, AppA appA, AppB appB, - AppC appC, AppD appD, AppE appE, AppF appF, AppG appG) { - return LiftA7.liftA7(fn, appA, appB, appC, appD, appE, appF).apply(appG); + public static , AppH extends Applicative> + AppH liftA7(Fn7 fn, + Applicative appA, + Applicative appB, + Applicative appC, + Applicative appD, + Applicative appE, + Applicative appF, + Applicative appG) { + return LiftA7.liftA7(fn, appA, appB, appC, appD, appE, appF).apply(appG); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java b/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java index c21c62908..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 @@ -2,13 +2,17 @@ import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; + /** * Specialized {@link CoProduct2} representing the possible results of a primitive recursive function. * Used by {@link Trampoline} to cheat around {@link CoProduct2#match} and quickly unpack values via @@ -18,64 +22,110 @@ * @param the recursive function's output type * @see Trampoline */ -public abstract class RecursiveResult implements CoProduct2>, Bifunctor>, Monad>, Traversable> { +public abstract class RecursiveResult implements + CoProduct2>, + Bifunctor>, + MonadRec>, + Traversable> { private RecursiveResult() { } + /** + * {@inheritDoc} + */ @Override public RecursiveResult invert() { return match(RecursiveResult::terminate, RecursiveResult::recurse); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public RecursiveResult biMapL(Fn1 fn) { - return (RecursiveResult) Bifunctor.super.biMapL(fn); + return (RecursiveResult) Bifunctor.super.biMapL(fn); } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") public RecursiveResult biMapR(Fn1 fn) { - return (RecursiveResult) Bifunctor.super.biMapR(fn); + return (RecursiveResult) Bifunctor.super.biMapR(fn); } + /** + * {@inheritDoc} + */ @Override public RecursiveResult biMap(Fn1 lFn, Fn1 rFn) { return match(a -> recurse(lFn.apply(a)), b -> terminate(rFn.apply(b))); } + /** + * {@inheritDoc} + */ @Override public RecursiveResult flatMap(Fn1>> f) { return match(RecursiveResult::recurse, b -> f.apply(b).coerce()); } + /** + * {@inheritDoc} + */ @Override public RecursiveResult pure(C c) { return terminate(c); } + /** + * {@inheritDoc} + */ @Override public RecursiveResult fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override public RecursiveResult zip(Applicative, RecursiveResult> appFn) { - return Monad.super.zip(appFn).coerce(); + 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 + 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, @@ -84,14 +134,40 @@ AppTrav extends Applicative> AppTrav traverse(Fn1 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; 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/Pure.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Pure.java index 77d3bd91a..797a2e8aa 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Pure.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Pure.java @@ -4,6 +4,8 @@ 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. * @@ -16,7 +18,7 @@ public interface Pure> { default > FA apply(A a) { try { - @SuppressWarnings("unchecked") FA fa = (FA) checkedApply(a); + @SuppressWarnings("unchecked") FA fa = downcast(checkedApply(a)); return fa; } catch (Throwable t) { throw Runtime.throwChecked(t); @@ -33,4 +35,16 @@ public interface 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/functor/Applicative.java b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java index 4dd634e13..9cf2125e1 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java @@ -64,11 +64,6 @@ default Lazy> lazyZip( return lazyAppFn.fmap(this::zip); } - @Override - default Applicative fmap(Fn1 fn) { - return zip(pure(fn)); - } - /** * Sequence both this Applicative and appB, discarding this Applicative's * result and returning appB. This is generally useful for sequentially performing side-effects. @@ -78,7 +73,7 @@ default Applicative fmap(Fn1 fn) { * @return appB */ default Applicative discardL(Applicative appB) { - return appB.zip(fmap(constantly(id()))); + return zip(appB.fmap(constantly())); } /** @@ -90,6 +85,14 @@ default Applicative discardL(Applicative appB) { * @return this Applicative */ default Applicative discardR(Applicative appB) { - return appB.zip(fmap(constantly())); + return zip(appB.fmap(constantly(id()))); + } + + /** + * {@inheritDoc} + */ + @Override + default Applicative fmap(Fn1 fn) { + return zip(pure(fn)); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Cartesian.java b/src/main/java/com/jnape/palatable/lambda/functor/Cartesian.java index e3ac2783b..8c28dc54f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Cartesian.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Cartesian.java @@ -33,19 +33,31 @@ 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/builtin/Compose.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java index a9a50eb91..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,6 +1,7 @@ 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; @@ -102,4 +103,23 @@ public int hashCode() { public String toString() { return "Compose{fga=" + fga + '}'; } + + /** + * The canonical {@link Pure} instance for {@link Compose}. + * + * @param pureF the {@link Pure} constructor for the outer {@link Applicative} + * @param pureG the {@link Pure} constructor for the inner {@link Applicative} + * @param the outer {@link Applicative} type + * @param the inner {@link Applicative} type + * @return the {@link Pure} instance + */ + public static , G extends Applicative> Pure> pureCompose( + Pure pureF, Pure pureG) { + return new Pure>() { + @Override + public Compose checkedApply(A a) throws Throwable { + return new Compose<>(pureF., Applicative, F>>apply(pureG.apply(a))); + } + }; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index 73e974ede..dfc29b1c6 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -1,9 +1,12 @@ 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; @@ -18,7 +21,7 @@ * @param the right (phantom) parameter type */ public final class Const implements - Monad>, + MonadRec>, Bifunctor>, Traversable> { @@ -47,9 +50,12 @@ public A runConst() { */ @Override public Const fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } + /** + * {@inheritDoc} + */ @Override @SuppressWarnings("unchecked") public Const pure(C c) { @@ -61,7 +67,7 @@ public Const pure(C c) { */ @Override public Const zip(Applicative, Const> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -70,7 +76,7 @@ public Const zip(Applicative, Const> @Override public Lazy> lazyZip( Lazy, Const>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); } /** @@ -78,7 +84,7 @@ public Lazy> lazyZip( */ @Override public Const discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -86,7 +92,7 @@ public Const discardL(Applicative> appB) { */ @Override public Const discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -98,6 +104,15 @@ public Const flatMap(Fn1>> f return (Const) this; } + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public Const trampolineM(Fn1, Const>> fn) { + return (Const) this; + } + /** * {@inheritDoc} */ @@ -120,9 +135,8 @@ public Const biMapL(Fn1 fn) { * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") public Const biMapR(Fn1 fn) { - return (Const) Bifunctor.super.biMapR(fn); + return (Const) Bifunctor.super.biMapR(fn); } /** @@ -150,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/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java index dba617294..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 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; @@ -42,7 +47,7 @@ public Identity flatMap(Fn1>> f */ @Override public Identity fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -67,7 +72,7 @@ public Identity zip(Applicative, Identity> @Override public Lazy> lazyZip( Lazy, Identity>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); } /** @@ -75,7 +80,7 @@ public Lazy> lazyZip( */ @Override public Identity discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -83,7 +88,7 @@ public Identity discardL(Applicative> appB) { */ @Override public Identity discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -97,6 +102,15 @@ AppTrav extends Applicative> AppTrav traverse(Fn1 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); @@ -113,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 index fff6acc72..83ec47711 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Lazy.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Lazy.java @@ -3,8 +3,11 @@ 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; @@ -21,7 +24,7 @@ * * @param the value type */ -public abstract class Lazy implements Monad>, Traversable> { +public abstract class Lazy implements MonadRec>, Traversable> { private Lazy() { } @@ -68,7 +71,7 @@ public final Lazy pure(B b) { */ @Override public final Lazy fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -76,7 +79,7 @@ public final Lazy fmap(Fn1 fn) { */ @Override public Lazy zip(Applicative, Lazy> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -84,7 +87,7 @@ public Lazy zip(Applicative, Lazy> appFn) */ @Override public final Lazy discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -92,7 +95,16 @@ public final Lazy discardL(Applicative> appB) { */ @Override public final Lazy discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + 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 @@ -132,6 +144,15 @@ 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; 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 index f1a4b3243..3c8ce7576 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Market.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Market.java @@ -3,16 +3,22 @@ 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. @@ -23,7 +29,7 @@ * @param the guaranteed output */ public final class Market implements - Monad>, + MonadRec>, Cocartesian> { private final Fn1 bt; @@ -71,6 +77,23 @@ public Market flatMap(Fn1 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} */ @@ -131,4 +154,21 @@ public Market diMapR(Fn1 fn) { 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 index ed3f17470..5caa98d18 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/State.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/State.java @@ -1,19 +1,26 @@ package com.jnape.palatable.lambda.functor.builtin; 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.adt.product.Product2; 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. @@ -24,11 +31,14 @@ * @param the state type * @param the result type */ -public final class State implements Monad> { +public final class State implements + MonadRec>, + MonadReader>, + MonadWriter> { - private final Fn1> stateFn; + private final StateT, A> stateFn; - private State(Fn1> stateFn) { + private State(StateT, A> stateFn) { this.stateFn = stateFn; } @@ -39,7 +49,7 @@ private State(Fn1> stateFn) { * @return a {@link Tuple2} of the result and the final state. */ public Tuple2 run(S s) { - return stateFn.apply(s).into(HList::tuple); + return stateFn.>>runStateT(s).runIdentity(); } /** @@ -66,10 +76,10 @@ public S exec(S s) { * Map both the result and the final state to a new result and final state. * * @param fn the mapping function - * @param the potentially new final state type + * @param the new state type * @return the mapped {@link State} */ - public State mapState(Fn1, ? extends Product2> fn) { + public State mapState(Fn1, ? extends Tuple2> fn) { return state(s -> fn.apply(run(s))); } @@ -83,6 +93,30 @@ 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} */ @@ -104,7 +138,7 @@ public State pure(B b) { */ @Override public State fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -112,7 +146,7 @@ public State fmap(Fn1 fn) { */ @Override public State zip(Applicative, State> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -121,7 +155,7 @@ public State zip(Applicative, State> @Override public Lazy> lazyZip( Lazy, State>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); } /** @@ -129,7 +163,7 @@ public Lazy> lazyZip( */ @Override public State discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -137,7 +171,17 @@ public State discardR(Applicative> appB) { */ @Override public State discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + 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_))))))); } /** @@ -148,7 +192,7 @@ public State discardL(Applicative> appB) { */ @SuppressWarnings("RedundantTypeArguments") public static State get() { - return new State<>(Tuple2::fill); + return state(Tuple2::fill); } /** @@ -160,7 +204,7 @@ public static State get() { * @return the new {@link State} instance */ public static State put(S s) { - return new State<>(constantly(tuple(UNIT, s))); + return modify(constantly(s)); } /** @@ -186,6 +230,18 @@ 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. @@ -195,19 +251,22 @@ public static State modify(Fn1 fn) { * @param the result type * @return the new {@link State} instance */ - public static State state(Fn1> stateFn) { - return new State<>(stateFn.fmap(into(HList::tuple))); + public static State state(Fn1> stateFn) { + return new State<>(stateT(s -> new Identity<>(stateFn.apply(s)))); } /** - * Create a {@link State} that returns a as its result and its initial state as its final state. + * The canonical {@link Pure} instance for {@link State}. * - * @param a the result * @param the state type - * @param the result type - * @return the new {@link State} instance + * @return the {@link Pure} instance */ - public static State state(A a) { - return gets(constantly(a)); + 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 index c601e6d65..4231f2959 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Tagged.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Tagged.java @@ -2,11 +2,19 @@ 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 @@ -15,7 +23,10 @@ * @param the phantom type * @param the value type */ -public final class Tagged implements Monad>, Cocartesian> { +public final class Tagged implements + MonadRec>, + Traversable>, Cocartesian> { + private final B b; public Tagged(B b) { @@ -39,6 +50,15 @@ public Tagged flatMap(Fn1>> 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} */ @@ -52,7 +72,7 @@ public Tagged pure(C c) { */ @Override public Tagged fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -60,7 +80,7 @@ public Tagged fmap(Fn1 fn) { */ @Override public Tagged zip(Applicative, Tagged> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -68,7 +88,7 @@ public Tagged zip(Applicative, Tagged Tagged discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -76,7 +96,19 @@ public Tagged discardL(Applicative> appB) { */ @Override public Tagged discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + 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(); } /** @@ -118,4 +150,29 @@ public Tagged diMapR(Fn1 fn) { 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/internal/iteration/GroupingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/GroupingIterator.java index 08d2f8c3f..c21d9950c 100644 --- a/src/main/java/com/jnape/palatable/lambda/internal/iteration/GroupingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/GroupingIterator.java @@ -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/internal/iteration/TrampoliningIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java new file mode 100644 index 000000000..fa0350d08 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java @@ -0,0 +1,67 @@ +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 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/io/IO.java b/src/main/java/com/jnape/palatable/lambda/io/IO.java index c86fcbc59..6af4dc1b1 100644 --- a/src/main/java/com/jnape/palatable/lambda/io/IO.java +++ b/src/main/java/com/jnape/palatable/lambda/io/IO.java @@ -4,29 +4,31 @@ 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.adt.hlist.Tuple2; 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.LinkedList; 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.hlist.HList.tuple; +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.fn2.Into.into; -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.functions.recursion.Trampoline.trampoline; +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; @@ -38,7 +40,7 @@ * * @param the result type */ -public abstract class IO implements Monad> { +public abstract class IO implements MonadRec>, MonadError> { private IO() { } @@ -81,19 +83,11 @@ public final CompletableFuture unsafePerformAsyncIO() { * * @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 new IO() { - @Override - public A unsafePerformIO() { - return Try.trying(IO.this::unsafePerformIO).recover(recoveryFn); - } - - @Override - public CompletableFuture unsafePerformAsyncIO(Executor executor) { - return IO.this.unsafePerformAsyncIO(executor).exceptionally(recoveryFn::apply); - } - }; + return catchError(t -> io(recoveryFn.apply(t))); } /** @@ -105,11 +99,12 @@ public CompletableFuture unsafePerformAsyncIO(Executor executor) { */ public final IO ensuring(IO ensureIO) { return join(fmap(a -> ensureIO.fmap(constantly(a))) - .exceptionally(t -> join(ensureIO.>fmap(constantly(io(() -> {throw t;}))) - .exceptionally(t2 -> io(() -> { - t.addSuppressed(t2); - throw t; - }))))); + .catchError(t1 -> ensureIO + .fmap(constantly(IO.throwing(t1))) + .catchError(t2 -> io(io(() -> { + t1.addSuppressed(t2); + throw t1; + }))))); } /** @@ -119,7 +114,7 @@ public final IO ensuring(IO ensureIO) { * @return the safe {@link IO} */ public final IO> safe() { - return fmap(Either::right).exceptionally(Either::left); + return fmap(Either::right).catchError(t -> io(left(t))); } /** @@ -135,7 +130,7 @@ public final IO pure(B b) { */ @Override public final IO fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadError.super.fmap(fn).coerce(); } /** @@ -143,19 +138,15 @@ public final IO fmap(Fn1 fn) { */ @Override public final IO zip(Applicative, IO> appFn) { - @SuppressWarnings("unchecked") - IO source = (IO) this; - @SuppressWarnings("unchecked") - IO> zip = (IO>) (Object) appFn; - return new Compose<>(source, a(zip)); + return new Compose<>(this, a((IO>) appFn)); } /** * {@inheritDoc} */ @Override - public Lazy> lazyZip(Lazy, IO>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + public final Lazy> lazyZip(Lazy, IO>> lazyAppFn) { + return MonadError.super.lazyZip(lazyAppFn).>fmap(Monad>::coerce).coerce(); } /** @@ -163,7 +154,7 @@ public Lazy> lazyZip(Lazy IO discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadError.super.discardL(appB).coerce(); } /** @@ -171,7 +162,7 @@ public final IO discardL(Applicative> appB) { */ @Override public final IO discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadError.super.discardR(appB).coerce(); } /** @@ -179,11 +170,51 @@ public final IO discardR(Applicative> appB) { */ @Override public final IO flatMap(Fn1>> f) { - @SuppressWarnings("unchecked") - IO source = (IO) this; @SuppressWarnings({"unchecked", "RedundantCast"}) - Fn1> flatMap = (Fn1>) (Object) f; - return new Compose<>(source, Choice2.b(flatMap)); + 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); + } + }; } /** @@ -197,6 +228,104 @@ 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. * @@ -278,62 +407,67 @@ public CompletableFuture unsafePerformAsyncIO(Executor executor) { }; } + /** + * 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) { + 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() { - @SuppressWarnings("unchecked") - A result = (A) trampoline(into((source, compositions) -> { - Object res = source.unsafePerformIO(); - return compositions.isEmpty() - ? terminate(res) - : compositions.pop().match( - zip -> recurse(tuple(io(zip.unsafePerformIO().apply(res)), compositions)), - flatMap -> { - IO next = flatMap.apply(res); - return (next instanceof Compose) - ? recurse(((Compose) next).deforest(compositions)) - : recurse(tuple(next, compositions)); - }); - }), deforest(new LinkedList<>())); - - return result; + 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) { - @SuppressWarnings("unchecked") - CompletableFuture future = (CompletableFuture) deforest(new LinkedList<>()) - .into((source, compositions) -> foldLeft( - (ioFuture, composition) -> composition - .match(zip -> zip.unsafePerformAsyncIO(executor) - .thenComposeAsync(f -> ioFuture.thenApply(f.toFunction()), executor), - flatMap -> ioFuture.thenComposeAsync(obj -> flatMap.apply(obj) - .unsafePerformAsyncIO(executor), executor)), - source.unsafePerformAsyncIO(executor), - compositions)); - return future; - } - - private Tuple2, LinkedList>, Fn1>>>> - deforest(LinkedList>, Fn1>>> branches) { - Tuple2, LinkedList>, Fn1>>>> args = - tuple(this, branches); - return trampoline(into((source, compositions) -> { - IO leaf = source.source; - compositions.push(source.composition); - return leaf instanceof Compose - ? recurse(tuple((Compose) leaf, compositions)) - : terminate(tuple(leaf, compositions)); - }), args); + 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/monad/Monad.java b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java index 769218304..1c5645905 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java @@ -56,7 +56,7 @@ default Monad fmap(Fn1 fn) { */ @Override default Monad zip(Applicative, M> appFn) { - return appFn., M>>coerce().flatMap(this::fmap); + return flatMap(a -> appFn., M>>coerce().fmap(f -> f.apply(a))); } /** 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..9eada32da --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/SafeT.java @@ -0,0 +1,300 @@ +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 body.resume() + .match(mBodyA -> appFn.>>coerce().body.resume() + .match(mBodyF -> new SafeT<>(Body.more(mBodyA.zip(mBodyF.fmap( + bodyF -> bodyA -> new SafeT<>(bodyA, pureM).zip(new SafeT<>(bodyF, pureM)).body))), pureM), + f -> new SafeT<>(Body.more(mBodyA.fmap(b -> Body.suspend( + b, a -> Body.done(f.apply(a))))), pureM)), + a -> appFn.>>coerce().body.resume() + .match(mBodyF -> new SafeT<>(new Body.More<>(mBodyF.fmap( + body -> new SafeT<>(body, pureM).fmap(f -> f.apply(a)).body)), pureM), + f -> pure(f.apply(a)))); + } + + /** + * {@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<>(new Body.More<>(ma.fmap(Body.Done::new)), 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 index 17b6dfd4e..71ed2b24b 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/MonadT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/MonadT.java @@ -2,100 +2,103 @@ 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; /** - * An interface representing a {@link Monad} transformer. + * The generic type representing a {@link Monad} transformer, exposing the argument {@link Monad} as a type parameter. *

- * While any two {@link com.jnape.palatable.lambda.functor.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}, provided that they are embedded inside the other {@link Monad}. When this is the case, they can offer - * implementations of {@link Monad#pure pure} and {@link Monad#flatMap(Fn1) flatMap} for free, simply by relying - * on the outer {@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. + * 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 a particular encoding of monadic composition. Because this general composition - * of a particular {@link Monad} with any other {@link Monad} relies on privileged knowledge about the embedded - * {@link Monad}, the {@link MonadT transformer} representing this compositions is described from the embedded - * {@link Monad monad's} perspective (e.g. {@link MaybeT} describing the embedding - * {@link Monad}<{@link com.jnape.palatable.lambda.adt.Maybe}<A>>). - *

- * Additionally, monad transformers connected by compatible {@link Monad monads} also compose. When two or more monad - * transformers are composed, this is generally referred to as a "monad transformer stack". + * 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 outer {@link Monad monad} - * @param the inner {@link Monad monad} - * @param the carrier type + * @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, G extends Monad, A> - extends Monad> { +public interface MonadT, A, MT extends MonadT, T extends MonadT> + extends MonadBase, Monad, MonadRec { /** - * Extract out the composed monad out of this transformer. - * - * @param the inferred embedded monad - * @param the inferred composed monad - * @return the composed monad + * {@inheritDoc} */ - , FGA extends Monad> FGA run(); + @Override + > MonadT lift(MonadRec mb); /** * {@inheritDoc} */ @Override - MonadT flatMap(Fn1>> f); + MonadT flatMap(Fn1> f); /** * {@inheritDoc} */ @Override - MonadT pure(B b); + MonadT pure(B b); /** * {@inheritDoc} */ @Override - default MonadT fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + default MonadT fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); } /** * {@inheritDoc} */ @Override - default MonadT zip(Applicative, MonadT> appFn) { - return Monad.super.zip(appFn).coerce(); + default MonadT zip(Applicative, MT> appFn) { + return MonadRec.super.zip(appFn).coerce(); } /** * {@inheritDoc} */ @Override - default Lazy> lazyZip( - Lazy, MonadT>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + default Lazy> lazyZip( + Lazy, MT>> lazyAppFn) { + return MonadRec.super.lazyZip(lazyAppFn).fmap(Applicative::coerce); } /** * {@inheritDoc} */ @Override - default MonadT discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + default MonadT discardL(Applicative appB) { + return MonadRec.super.discardL(appB).coerce(); } /** * {@inheritDoc} */ @Override - default MonadT discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + 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 index d8a139f39..340edd066 100644 --- 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 @@ -2,47 +2,65 @@ 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.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} + * @param the outer {@link Monad stack-safe monad} * @param the left type * @param the right type */ -public final class EitherT, L, R> implements MonadT, R> { +public final class EitherT, L, R> implements + Bifunctor>, + MonadT, EitherT> { - private final Monad, M> melr; + private final MonadRec, M> melr; - private EitherT(Monad, 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 >, FGA extends Monad> FGA run() { - return melr.fmap(Either::coerce).coerce(); + public > EitherT lift(MonadRec mb) { + return EitherT.liftEitherT().apply(mb); } /** * {@inheritDoc} */ @Override - public EitherT flatMap(Fn1, ?>>> f) { + public EitherT flatMap(Fn1>> f) { return eitherT(melr.flatMap(lr -> lr.match(l -> melr.pure(left(l)), - r -> f.apply(r).>coerce().run()))); + r -> f.apply(r).>coerce().runEitherT()))); } /** @@ -66,7 +84,7 @@ public EitherT fmap(Fn1 fn) { */ @Override public EitherT zip( - Applicative, MonadT, ?>> appFn) { + Applicative, EitherT> appFn) { return MonadT.super.zip(appFn).coerce(); } @@ -75,12 +93,11 @@ public EitherT zip( */ @Override public Lazy> lazyZip( - Lazy, MonadT, ?>>> lazyAppFn) { + Lazy, EitherT>> lazyAppFn) { return new Compose<>(melr) .lazyZip(lazyAppFn.fmap(maybeT -> new Compose<>( maybeT.>>coerce() - .>, - Monad>, M>>run()))) + .>, M>>runEitherT()))) .fmap(compose -> eitherT(compose.getCompose())); } @@ -88,7 +105,7 @@ public Lazy> lazyZip( * {@inheritDoc} */ @Override - public EitherT discardL(Applicative, ?>> appB) { + public EitherT discardL(Applicative> appB) { return MonadT.super.discardL(appB).coerce(); } @@ -96,10 +113,49 @@ public EitherT discardL(Applicative, ? * {@inheritDoc} */ @Override - public EitherT discardR(Applicative, ?>> appB) { + public EitherT discardR(Applicative> appB) { return MonadT.super.discardR(appB).coerce(); } + /** + * {@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); @@ -125,7 +181,40 @@ public String toString() { * @param the right type * @return the {@link EitherT} */ - public static , L, R> EitherT eitherT(Monad, M> melr) { + 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 index a3e87e3fc..42f6f326c 100644 --- 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 @@ -1,11 +1,15 @@ 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; @@ -13,31 +17,58 @@ /** * A {@link MonadT monad transformer} for {@link Identity}. * - * @param the outer {@link Monad} + * @param the outer {@link Monad stack-safe monad} * @param the carrier type */ -public final class IdentityT, A> implements MonadT, A> { +public final class IdentityT, A> implements + MonadT, IdentityT> { - private final Monad, M> mia; + private final MonadRec, M> mia; - private IdentityT(Monad, 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 >, FGA extends Monad> FGA run() { - return mia.fmap(Identity::coerce).coerce(); + public IdentityT flatMap(Fn1>> f) { + return identityT(mia.flatMap(identityA -> f.apply(identityA.runIdentity()) + .>coerce() + .runIdentityT())); } /** * {@inheritDoc} */ @Override - public IdentityT flatMap(Fn1, ?>>> f) { - return identityT(mia.flatMap(identityA -> f.apply(identityA.runIdentity()).>coerce().run())); + 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)); } /** @@ -60,7 +91,7 @@ public IdentityT fmap(Fn1 fn) { * {@inheritDoc} */ @Override - public IdentityT zip(Applicative, MonadT, ?>> appFn) { + public IdentityT zip(Applicative, IdentityT> appFn) { return MonadT.super.zip(appFn).coerce(); } @@ -69,12 +100,11 @@ public IdentityT zip(Applicative, MonadT Lazy> lazyZip( - Lazy, MonadT, ?>>> lazyAppFn) { + Lazy, IdentityT>> lazyAppFn) { return new Compose<>(mia) .lazyZip(lazyAppFn.fmap(maybeT -> new Compose<>( maybeT.>>coerce() - .>, - Monad>, M>>run()))) + .>, M>>runIdentityT()))) .fmap(compose -> identityT(compose.getCompose())); } @@ -82,7 +112,7 @@ public Lazy> lazyZip( * {@inheritDoc} */ @Override - public IdentityT discardL(Applicative, ?>> appB) { + public IdentityT discardL(Applicative> appB) { return MonadT.super.discardL(appB).coerce(); } @@ -90,7 +120,7 @@ public IdentityT discardL(Applicative, ?>> ap * {@inheritDoc} */ @Override - public IdentityT discardR(Applicative, ?>> appB) { + public IdentityT discardR(Applicative> appB) { return MonadT.super.discardR(appB).coerce(); } @@ -118,7 +148,37 @@ public String toString() { * @param the carrier type * @return the new {@link IdentityT}. */ - public static , A> IdentityT identityT(Monad, M> mia) { + 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/LazyT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/LazyT.java index d35ddc215..5af654230 100644 --- 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 @@ -1,11 +1,14 @@ 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.Functor; 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; @@ -13,33 +16,55 @@ import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; /** - * A {@link MonadT monad transformer} for {@link Lazy}. Note that {@link LazyT#flatMap(Fn1)} must force its value. + * 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} + * @param the outer {@link Monad stack-safe monad} * @param the carrier type */ -public class LazyT, A> implements MonadT, A> { +public final class LazyT, A> implements + MonadT, LazyT> { - private final Monad, M> mla; + private final MonadRec, M> mla; - private LazyT(Monad, 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 >, FGA extends Monad> FGA run() { - return mla.fmap(Functor::coerce).coerce(); + public LazyT flatMap(Fn1>> f) { + return new LazyT<>(mla.flatMap(lazyA -> f.apply(lazyA.value()).>coerce().runLazyT())); } /** * {@inheritDoc} */ @Override - public LazyT flatMap(Fn1, ?>>> f) { - return new LazyT<>(mla.flatMap(lazyA -> f.apply(lazyA.value())., B>>coerce().run())); + 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)))); } /** @@ -62,7 +87,7 @@ public LazyT fmap(Fn1 fn) { * {@inheritDoc} */ @Override - public LazyT zip(Applicative, MonadT, ?>> appFn) { + public LazyT zip(Applicative, LazyT> appFn) { return MonadT.super.zip(appFn).coerce(); } @@ -71,12 +96,11 @@ public LazyT zip(Applicative, MonadT Lazy> lazyZip( - Lazy, MonadT, ?>>> lazyAppFn) { + Lazy, LazyT>> lazyAppFn) { return new Compose<>(mla) .lazyZip(lazyAppFn.fmap(lazyT -> new Compose<>( lazyT.>>coerce() - .>, - Monad>, M>>run()))) + .>, M>>runLazyT()))) .fmap(compose -> lazyT(compose.getCompose())); } @@ -84,7 +108,7 @@ public Lazy> lazyZip( * {@inheritDoc} */ @Override - public LazyT discardL(Applicative, ?>> appB) { + public LazyT discardL(Applicative> appB) { return MonadT.super.discardL(appB).coerce(); } @@ -92,7 +116,7 @@ public LazyT discardL(Applicative, ?>> appB) { * {@inheritDoc} */ @Override - public LazyT discardR(Applicative, ?>> appB) { + public LazyT discardR(Applicative> appB) { return MonadT.super.discardR(appB).coerce(); } @@ -120,7 +144,37 @@ public String toString() { * @param the carrier type * @return the new {@link LazyT} */ - public static , A> LazyT lazyT(Monad, M> mla) { + 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 index 7f1cf151f..3a6cad1f8 100644 --- 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 @@ -2,10 +2,14 @@ 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.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; @@ -13,27 +17,39 @@ 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.recursion.RecursiveResult.terminate; /** * A {@link MonadT monad transformer} for {@link Maybe}. * - * @param the outer {@link Monad} + * @param the outer {@link Monad stack-safe monad} * @param the carrier type */ -public final class MaybeT, A> implements MonadT, A> { +public final class MaybeT, A> implements + MonadT, MaybeT> { - private final Monad, M> mma; + private final MonadRec, M> mma; - private MaybeT(Monad, 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(); + } + /** * {@inheritDoc} */ @Override - public >, FGA extends Monad> FGA run() { - return mma.fmap(Applicative::coerce).coerce(); + public > MaybeT lift(MonadRec mb) { + return liftMaybeT().apply(mb); } /** @@ -56,7 +72,7 @@ public MaybeT pure(B b) { * {@inheritDoc} */ @Override - public MaybeT zip(Applicative, MonadT, ?>> appFn) { + public MaybeT zip(Applicative, MaybeT> appFn) { return MonadT.super.zip(appFn).coerce(); } @@ -65,11 +81,11 @@ public MaybeT zip(Applicative, MonadT Lazy> lazyZip( - Lazy, MonadT, ?>>> lazyAppFn) { + Lazy, MaybeT>> lazyAppFn) { return new Compose<>(mma) .lazyZip(lazyAppFn.fmap(maybeT -> new Compose<>( maybeT.>>coerce() - .>, Monad>, M>>run()))) + .>, M>>runMaybeT()))) .fmap(compose -> maybeT(compose.getCompose())); } @@ -77,17 +93,31 @@ public Lazy> lazyZip( * {@inheritDoc} */ @Override - public MaybeT flatMap(Fn1, ?>>> f) { + public MaybeT flatMap(Fn1>> f) { return maybeT(mma.flatMap(ma -> ma .match(constantly(mma.pure(nothing())), - a -> f.apply(a).>coerce().run()))); + 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) { + public MaybeT discardL(Applicative> appB) { return MonadT.super.discardL(appB).coerce(); } @@ -95,7 +125,7 @@ public MaybeT discardL(Applicative, ?>> appB) { * {@inheritDoc} */ @Override - public MaybeT discardR(Applicative, ?>> appB) { + public MaybeT discardR(Applicative> appB) { return MonadT.super.discardR(appB).coerce(); } @@ -125,7 +155,38 @@ public String toString() { * @param the carrier type * @return the {@link MaybeT} */ - public static , A> MaybeT maybeT(Monad, M> mma) { + 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..2af18e16d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java @@ -0,0 +1,240 @@ +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())); + } + + /** + * {@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 MonadT.super.fmap(fn).coerce(); + } + + /** + * {@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(); + } + + /** + * 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..907d71d51 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java @@ -0,0 +1,314 @@ +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 MonadT.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public StateT zip(Applicative, StateT> appFn) { + return stateT(s -> runStateT(s) + .zip(appFn.>>coerce() + .runStateT(s) + .fmap(Tuple2::_1) + .fmap(f -> t -> t.biMapL(f)))); + } + + /** + * {@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) throws Throwable { + 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..da80ad984 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java @@ -0,0 +1,216 @@ +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.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 strict transformer for a {@link Tuple2} holding a value and an accumulation. + * + * @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 Fn1, ? extends MonadRec, M>> writerFn; + + private WriterT(Fn1, ? extends MonadRec, M>> writerFn) { + 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 Monad} result + * @return the accumulation with the result + */ + public , M>> MAW runWriterT(Monoid monoid) { + return writerFn.apply(monoid).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT> listens(Fn1 fn) { + return new WriterT<>(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<>(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<>(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<>(m -> runWriterT(m).pure(tuple(b, m.identity()))); + } + + /** + * {@inheritDoc} + */ + @Override + public WriterT flatMap(Fn1>> f) { + return new WriterT<>(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<>(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<>(monoid -> ma.fmap(a -> tuple(a, monoid.identity()))); + } + + /** + * Lift a value and an accumulation embedded in a {@link Monad} into a {@link WriterT}. + * + * @param mwa 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> mwa) { + return new WriterT<>(constantly(mwa)); + } + + /** + * 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) throws Throwable { + 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/optics/Iso.java b/src/main/java/com/jnape/palatable/lambda/optics/Iso.java index feb59212c..04c21ccfb 100644 --- a/src/main/java/com/jnape/palatable/lambda/optics/Iso.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/Iso.java @@ -3,12 +3,15 @@ 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; @@ -55,7 +58,7 @@ @FunctionalInterface public interface Iso extends Optic, Functor, S, T, A, B>, - Monad>, + MonadRec>, Profunctor> { /** @@ -96,7 +99,7 @@ default Iso mirror() { */ @Override default Iso fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -112,7 +115,7 @@ default Iso pure(U u) { */ @Override default Iso zip(Applicative, Iso> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -120,7 +123,7 @@ default Iso zip(Applicative, Iso Iso discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -128,16 +131,16 @@ default Iso discardL(Applicative> appB) { */ @Override default Iso discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** * {@inheritDoc} */ @Override + @SuppressWarnings("RedundantTypeArguments") default Iso flatMap(Fn1>> fn) { - //noinspection RedundantTypeArguments - return unIso().fmap(bt -> Fn2.curried( + return unIso().>fmap(bt -> Fn2.curried( fn1(bt.fmap(fn.>fmap(Monad>::coerce)) .fmap(Iso::unIso) .fmap(Tuple2::_2) @@ -147,6 +150,18 @@ default Iso flatMap(Fn1 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} */ @@ -279,6 +294,24 @@ static Iso.Simple simpleIso(Fn1 f, Fn1 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. diff --git a/src/main/java/com/jnape/palatable/lambda/optics/Lens.java b/src/main/java/com/jnape/palatable/lambda/optics/Lens.java index 48e16495c..f2b0c23d6 100644 --- a/src/main/java/com/jnape/palatable/lambda/optics/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/Lens.java @@ -4,11 +4,19 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.builtin.fn2.Both; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Cartesian; import com.jnape.palatable.lambda.functor.Functor; import com.jnape.palatable.lambda.functor.Profunctor; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.Fn2.curried; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; import static com.jnape.palatable.lambda.optics.Iso.iso; import static com.jnape.palatable.lambda.optics.Lens.Simple.adapt; import static com.jnape.palatable.lambda.optics.functions.Set.set; @@ -138,8 +146,8 @@ */ @FunctionalInterface public interface Lens extends - Optic, Functor, S, T, A, B>, - Monad>, + Optic, Functor, S, T, A, B>, + MonadRec>, Profunctor> { /** @@ -147,7 +155,7 @@ public interface Lens extends */ @Override default Lens fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -163,7 +171,7 @@ default Lens pure(U u) { */ @Override default Lens zip(Applicative, Lens> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -171,7 +179,7 @@ default Lens zip(Applicative, Lens Lens discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -179,7 +187,7 @@ default Lens discardL(Applicative> appB) { */ @Override default Lens discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -191,6 +199,18 @@ default Lens flatMap(Fn1 set(f.apply(set(this, b, s)).>coerce(), b, s)); } + /** + * {@inheritDoc} + */ + @Override + default Lens trampolineM( + Fn1, Lens>> fn) { + return lens(view(this), + curried(set(this).flip().flatMap(bt -> fn1(s -> bt + .trampolineM(t -> set(fn.apply(t)., A, B>>coerce()) + .flip().apply(s)))))); + } + /** * {@inheritDoc} */ @@ -269,7 +289,7 @@ default Iso toIso(S s) { * {@inheritDoc} */ @Override - default Lens andThen(Optic, ? super Functor, A, B, C, D> f) { + default Lens andThen(Optic, ? super Functor, A, B, C, D> f) { return lens(Optic.super.andThen(f)); } @@ -277,7 +297,7 @@ default Lens andThen(Optic, ? super Functor * {@inheritDoc} */ @Override - default Lens compose(Optic, ? super Functor, R, U, S, T> g) { + default Lens compose(Optic, ? super Functor, R, U, S, T> g) { return lens(Optic.super.compose(g)); } @@ -294,13 +314,14 @@ default Lens compose(Optic, ? super Functor */ static Lens lens(Fn1 getter, Fn2 setter) { - return lens(Optic., Functor, + return lens(Optic., Functor, S, T, A, B, Functor>, Functor>, - Fn1>>, - Fn1>>>optic(afb -> s -> afb.apply(getter.apply(s)) - .fmap(b -> setter.apply(s, b)))); + 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)))))); } /** @@ -313,11 +334,14 @@ static Lens lens(Fn1 getter, * @param the type of the "smaller" update value * @return the {@link Lens} */ - static Lens lens(Optic, ? super Functor, S, T, A, B> optic) { + static Lens lens( + Optic, ? super Functor, S, T, A, B> optic) { return new Lens() { @Override - public >, CoF extends Functor>, - FB extends Functor, FT extends Functor, + public >, + CoF extends Functor>, + FB extends Functor, + FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply(PAFB pafb) { return optic.apply(pafb); @@ -370,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. @@ -378,13 +420,13 @@ static Lens.Simple> both(Lens.Simple f, Lens.Sim * @param the type of both "smaller" values */ @FunctionalInterface - interface Simple extends Lens, Optic.Simple, Functor, S, A> { + interface Simple extends Lens, Optic.Simple, Functor, S, A> { /** * {@inheritDoc} */ @Override - default Lens.Simple andThen(Optic.Simple, ? super Functor, A, B> f) { + default Lens.Simple andThen(Optic.Simple, ? super Functor, A, B> f) { return Lens.Simple.adapt(Lens.super.andThen(f)); } @@ -392,7 +434,7 @@ default Lens.Simple andThen(Optic.Simple, ? super Fu * {@inheritDoc} */ @Override - default Lens.Simple compose(Optic.Simple, ? super Functor, R, S> g) { + default Lens.Simple compose(Optic.Simple, ? super Functor, R, S> g) { return Lens.Simple.adapt(Lens.super.compose(g)); } @@ -404,10 +446,11 @@ default Lens.Simple compose(Optic.Simple, ? super Fu * @param A/B * @return the simple lens */ - static Lens.Simple adapt(Optic, ? super Functor, S, S, A, A> lens) { + static Lens.Simple adapt( + Optic, ? super Functor, S, S, A, A> lens) { return new Lens.Simple() { @Override - public >, + public >, CoF extends Functor>, FB extends Functor, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply(PAFB 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 index fb6ec0e59..d0dc2d625 100644 --- a/src/main/java/com/jnape/palatable/lambda/optics/Optic.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/Optic.java @@ -22,6 +22,18 @@ @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, diff --git a/src/main/java/com/jnape/palatable/lambda/optics/Prism.java b/src/main/java/com/jnape/palatable/lambda/optics/Prism.java index d89ad3bfb..698860cbf 100644 --- a/src/main/java/com/jnape/palatable/lambda/optics/Prism.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/Prism.java @@ -6,6 +6,7 @@ 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; @@ -15,6 +16,7 @@ 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; @@ -24,9 +26,11 @@ 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: @@ -55,7 +59,7 @@ @FunctionalInterface public interface Prism extends ProtoOptic, S, T, A, B>, - Monad>, + MonadRec>, Profunctor> { /** @@ -64,10 +68,7 @@ public interface Prism extends * @return a {@link Tuple2 tuple} of the two mappings encapsulated by this {@link Prism} */ default Tuple2, Fn1>> unPrism() { - return Tuple2.fill(this., Identity, Identity, Identity, - Market>, Market>>apply( - new Market<>(Identity::new, Either::right)).fmap(Identity::runIdentity)) - .biMap(Market::bt, Market::sta); + return both(Re.re().fmap(view()), matching(), this); } /** @@ -84,6 +85,54 @@ public interface Prism extends 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} */ @@ -97,9 +146,11 @@ default Prism pure(U u) { */ @Override default Prism flatMap(Fn1>> f) { - return unPrism().into((bt, seta) -> 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))); + 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))); } /** @@ -107,7 +158,7 @@ default Prism flatMap(Fn1 Prism fmap(Fn1 fn) { - return Monad.super.fmap(fn).coerce(); + return MonadRec.super.fmap(fn).coerce(); } /** @@ -115,7 +166,7 @@ default Prism fmap(Fn1 fn) { */ @Override default Prism zip(Applicative, Prism> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -124,7 +175,7 @@ default Prism zip(Applicative, Prism @Override default Lazy> lazyZip( Lazy, Prism>> lazyAppFn) { - return Monad.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); } /** @@ -132,7 +183,7 @@ default Lazy> lazyZip( */ @Override default Prism discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -140,7 +191,20 @@ default Prism discardL(Applicative> appB) { */ @Override default Prism discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + 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()); } /** @@ -264,10 +328,38 @@ static Prism.Simple simplePrism(Fn1> 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); + } - static Prism.Simple fromPartial(Fn1 partialSa, - Fn1 as) { - return adapt(prism(partialSa.diMap(downcast(), upcast()).choose(), as)); + /** + * 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)); + } + }; } /** @@ -322,5 +414,22 @@ 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 index 57fc5ffbc..5904ea4be 100644 --- a/src/main/java/com/jnape/palatable/lambda/optics/ProtoOptic.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/ProtoOptic.java @@ -1,14 +1,18 @@ 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}. + * 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 @@ -30,6 +34,9 @@ public interface ProtoOptic

, S, T, A, B> */ > Optic toOptic(Pure pure); + /** + * {@inheritDoc} + */ @Override default , CoF extends Functor>, FB extends Functor, FT extends Functor, @@ -37,4 +44,92 @@ public interface ProtoOptic

, S, T, A, B> 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/Pre.java b/src/main/java/com/jnape/palatable/lambda/optics/functions/Pre.java index 977788eac..f7d3c4095 100644 --- a/src/main/java/com/jnape/palatable/lambda/optics/functions/Pre.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/functions/Pre.java @@ -3,6 +3,7 @@ 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; @@ -19,42 +20,41 @@ * @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 implements Fn1, ? super Const, ?>, S, T, A, B>, - Optic, Const, ?>, S, T, Maybe, B>> { +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 static final Pre INSTANCE = new Pre<>(); private Pre() { } @Override - public Optic, Const, ?>, S, T, Maybe, B> checkedApply( - Optic, ? super Const, ?>, S, T, A, B> optic) { - Optic, ? super Const, ?>, S, T, Maybe, B> mappedOptic = optic.mapA(Maybe::just); + 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 Pre pre() { - return (Pre) INSTANCE; + public static

, S, T, A, B> Pre pre() { + return (Pre) INSTANCE; } @SuppressWarnings("overloads") - public static Optic, Const, ?>, S, T, Maybe, B> pre( - Optic, ? super Const, ?>, S, T, A, B> optic) { - return Pre.pre().apply(optic); + 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 Optic, Const, ?>, S, T, Maybe, B> pre( - ProtoOptic, S, T, A, B> protoOptic) { - Optic, Const, ?>, S, T, A, B> optic = protoOptic - .toOptic(new Pure, ?>>() { - @Override - public Const, X> checkedApply(X x) { - return new Const<>(nothing()); - } - }); - return pre(optic); + 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 index eeb36f5b8..a948a0170 100644 --- a/src/main/java/com/jnape/palatable/lambda/optics/functions/Re.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/functions/Re.java @@ -1,6 +1,7 @@ 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; @@ -18,7 +19,8 @@ * @param the value to read from */ public final class Re implements - Fn1, ? super Identity, S, T, A, B>, Optic, Const, B, B, T, T>> { + Fn1, ? super Identity, S, T, A, B>, + Optic, Const, B, B, T, T>> { private static final Re INSTANCE = new Re<>(); @@ -26,14 +28,17 @@ private Re() { } @Override - public Optic, Const, B, B, T, T> checkedApply( + public Optic, Const, B, B, T, T> checkedApply( Optic, ? super Identity, S, T, A, B> optic) { - return Optic., Const, B, B, T, T, + return Optic., Const, B, B, T, T, Const, Const, - Fn1>, - Fn1>>optic(pafb -> b -> new Const<>(optic., Identity, Identity, - Identity, Tagged>, - Tagged>>apply(new Tagged<>(new Identity<>(b))).unTagged().runIdentity())); + 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") @@ -41,7 +46,7 @@ public static Re re() { return (Re) INSTANCE; } - public static Optic, Const, B, B, T, T> re( + 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/prisms/UUIDPrism.java b/src/main/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrism.java index c9a70080e..1a030b654 100644 --- a/src/main/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrism.java +++ b/src/main/java/com/jnape/palatable/lambda/optics/prisms/UUIDPrism.java @@ -4,10 +4,13 @@ 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() { } @@ -17,6 +20,6 @@ private UUIDPrism() { * @return the {@link Prism} */ public static Prism.Simple uuid() { - return Prism.fromPartial(UUID::fromString, UUID::toString); + return fromPartial(UUID::fromString, UUID::toString); } } diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java index f39fd05cd..a692eac77 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java @@ -32,7 +32,7 @@ private Absent() { @Override public Semigroup> checkedApply(Semigroup aSemigroup) { - return LiftA2., Maybe, Maybe, Maybe>liftA2(aSemigroup)::apply; + return LiftA2., Maybe>liftA2(aSemigroup)::apply; } @SuppressWarnings("unchecked") 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 6fe5f4a38..9859dac85 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java @@ -3,9 +3,13 @@ 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; @@ -23,7 +27,10 @@ * @param the {@link Iterable} element type * @see LambdaMap */ -public final class LambdaIterable implements Monad>, Traversable> { +public final class LambdaIterable implements + MonadRec>, + Traversable> { + private final Iterable as; @SuppressWarnings("unchecked") @@ -68,7 +75,7 @@ public LambdaIterable pure(B b) { */ @Override public LambdaIterable zip(Applicative, LambdaIterable> appFn) { - return Monad.super.zip(appFn).coerce(); + return MonadRec.super.zip(appFn).coerce(); } /** @@ -79,7 +86,7 @@ public Lazy> lazyZip( Lazy, LambdaIterable>> lazyAppFn) { return Empty.empty(as) ? lazy(LambdaIterable.empty()) - : Monad.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + : MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); } /** @@ -87,7 +94,7 @@ public Lazy> lazyZip( */ @Override public LambdaIterable discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + return MonadRec.super.discardL(appB).coerce(); } /** @@ -95,7 +102,7 @@ public LambdaIterable discardL(Applicative> appB) { */ @Override public LambdaIterable discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + return MonadRec.super.discardR(appB).coerce(); } /** @@ -106,6 +113,19 @@ public LambdaIterable flatMap(Fn1 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} */ @@ -164,4 +184,18 @@ public static LambdaIterable wrap(Iterable as) { public static LambdaIterable empty() { return wrap(emptyList()); } + + /** + * The canonical {@link Pure} instance for {@link LambdaIterable}. + * + * @return the {@link Pure} instance + */ + public static Pure> pureLambdaIterable() { + return new Pure>() { + @Override + public LambdaIterable checkedApply(A a) throws Throwable { + return wrap(singleton(a)); + } + }; + } } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 37371ead6..50921161d 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -12,10 +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 static com.jnape.palatable.lambda.adt.Either.fromMaybe; import static com.jnape.palatable.lambda.adt.Either.left; @@ -23,12 +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.functions.Effect.fromConsumer; 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 { @@ -36,11 +36,22 @@ 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"); @@ -195,37 +206,6 @@ public void monadTryingWithRunnable() { assertEquals(left(expected), Either.trying(() -> {throw expected;})); } - @Test - public void monadicPeekLiftsIOToTheRight() { - Either left = left("foo"); - Either right = right(1); - - AtomicInteger intRef = new AtomicInteger(); - - left.peek(fromConsumer(intRef::set)); - assertEquals(0, intRef.get()); - - right.peek(fromConsumer(intRef::set)); - assertEquals(1, intRef.get()); - } - - @Test - public void dyadicPeekDuallyLiftsIO() { - Either left = left("foo"); - Either right = right(1); - - AtomicReference stringRef = new AtomicReference<>(); - AtomicInteger intRef = new AtomicInteger(); - - left.peek(fromConsumer(stringRef::set), fromConsumer(intRef::set)); - assertEquals("foo", stringRef.get()); - assertEquals(0, intRef.get()); - - right.peek(fromConsumer(stringRef::set), fromConsumer(intRef::set)); - assertEquals("foo", stringRef.get()); - assertEquals(1, intRef.get()); - } - @Test public void lazyZip() { assertEquals(right(2), right(1).lazyZip(lazy(right(x -> x + 1))).value()); @@ -233,4 +213,10 @@ public void lazyZip() { throw new AssertionError(); })).value()); } + + @Test + public void staticPure() { + Either either = Either.pureEither().apply(1); + assertEquals(right(1), either); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java b/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java index 0548e74f4..6668dbc19 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java @@ -10,33 +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.lambda.io.IO.io; 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); @@ -96,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(constantly(io(ref::incrementAndGet)))); - assertEquals(1, ref.get()); - - assertEquals(nothing(), nothing().peek(constantly(io(ref::incrementAndGet)))); - assertEquals(1, ref.get()); - } - @Test public void justOrThrow() { just(1).orElseThrow(IllegalStateException::new); @@ -119,7 +115,7 @@ public void nothingOrThrow() { @Test public void divergesIntoChoice3() { assertEquals(Choice3.a(UNIT), nothing().diverge()); - assertEquals(Choice3.b(1), just(1).diverge()); + assertEquals(Choice3.b(1), just(1).diverge()); } @Test @@ -141,4 +137,10 @@ public void lazyZip() { 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 c4484c1a3..36a195011 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java @@ -9,6 +9,7 @@ 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.These.a; @@ -21,7 +22,12 @@ @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)); } @@ -29,11 +35,17 @@ public Subjects> testSubject() { @Test public void lazyZip() { assertEquals(b(2), b(1).lazyZip(lazy(b(x -> x + 1))).value()); - assertEquals(b(2), b(1).lazyZip(lazy(both("foo", x -> x + 1))).value()); - assertEquals(both("bar", 2), both("foo", 1).lazyZip(lazy(both("bar", 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); + } } \ 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 ee6ad0dd7..1d4ed3b0f 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java @@ -10,6 +10,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; import java.io.IOException; @@ -23,8 +24,10 @@ 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; @@ -37,6 +40,7 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static testsupport.assertion.MonadErrorAssert.assertLaws; import static testsupport.matchers.LeftMatcher.isLeftThat; @RunWith(Traits.class) @@ -44,11 +48,19 @@ public class TryTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @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(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()) @@ -164,7 +176,7 @@ public void tryingCatchesAnyThrowableThrownDuringEvaluation() { @Test public void withResourcesCleansUpAutoCloseableInSuccessCase() { AtomicBoolean closed = new AtomicBoolean(false); - assertEquals(success(1), Try.withResources(() -> () -> closed.set(true), resource -> success(1))); + assertEquals(success(1), withResources(() -> (AutoCloseable) () -> closed.set(true), resource -> success(1))); assertTrue(closed.get()); } @@ -172,30 +184,30 @@ public void withResourcesCleansUpAutoCloseableInSuccessCase() { public void withResourcesCleansUpAutoCloseableInFailureCase() { AtomicBoolean closed = new AtomicBoolean(false); RuntimeException exception = new RuntimeException(); - assertEquals(Try.failure(exception), Try.withResources(() -> () -> closed.set(true), - resource -> { throw exception; })); + assertEquals(Try.failure(exception), withResources(() -> (AutoCloseable) () -> closed.set(true), + resource -> { throw exception; })); assertTrue(closed.get()); } @Test public void withResourcesExposesResourceCreationFailure() { IOException ioException = new IOException(); - assertEquals(Try.failure(ioException), Try.withResources(() -> { throw ioException; }, resource -> success(1))); + assertEquals(Try.failure(ioException), withResources(() -> { throw ioException; }, resource -> success(1))); } @Test public void withResourcesExposesResourceCloseFailure() { IOException ioException = new IOException(); - assertEquals(Try.failure(ioException), Try.withResources(() -> () -> { throw ioException; }, - resource -> success(1))); + assertEquals(Try.failure(ioException), withResources(() -> (AutoCloseable) () -> { throw ioException; }, + resource -> success(1))); } @Test public void withResourcesPreservesSuppressedExceptionThrownDuringClose() { RuntimeException rootException = new RuntimeException(); IOException nestedIOException = new IOException(); - Try failure = Try.withResources(() -> () -> { throw nestedIOException; }, - resource -> { throw rootException; }); + Try failure = withResources(() -> (AutoCloseable) () -> { throw nestedIOException; }, + resource -> { throw rootException; }); Throwable thrown = failure.recover(id()); assertEquals(thrown, rootException); @@ -205,10 +217,10 @@ public void withResourcesPreservesSuppressedExceptionThrownDuringClose() { @Test public void cascadingWithResourcesClosesInInverseOrder() { List closeMessages = new ArrayList<>(); - assertEquals(success(1), Try.withResources(() -> (AutoCloseable) () -> closeMessages.add("close a"), - a -> () -> closeMessages.add("close b"), - b -> () -> closeMessages.add("close c"), - c -> success(1))); + assertEquals(success(1), withResources(() -> (AutoCloseable) () -> closeMessages.add("close a"), + a -> () -> closeMessages.add("close b"), + b -> () -> closeMessages.add("close c"), + c -> success(1))); assertEquals(asList("close c", "close b", "close a"), closeMessages); } @@ -248,4 +260,10 @@ public void orThrowCanTransformFirst() { 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 ea4c27160..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,11 +6,7 @@ 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; @@ -30,15 +26,20 @@ 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 @@ -48,4 +49,10 @@ public void lazyZip() { 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 2deefbb91..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,11 +6,7 @@ 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; @@ -33,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)); } @@ -62,4 +58,10 @@ public void lazyZip() { 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 1e20850d6..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,6 +10,7 @@ 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; @@ -36,25 +37,30 @@ 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 @@ -70,4 +76,10 @@ public void lazyZip() { 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 bfccafcb9..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,9 @@ 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.Choice5.a; -import static com.jnape.palatable.lambda.adt.choice.Choice5.b; -import static com.jnape.palatable.lambda.adt.choice.Choice5.c; -import static com.jnape.palatable.lambda.adt.choice.Choice5.d; -import static com.jnape.palatable.lambda.adt.choice.Choice5.e; +import static com.jnape.palatable.lambda.adt.choice.Choice5.*; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; @@ -39,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)); } @@ -78,4 +75,10 @@ public void lazyZip() { 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 f818d4fd7..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 @@ -12,6 +12,7 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice6.a; @@ -44,7 +45,13 @@ 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)); } @@ -90,4 +97,11 @@ public void lazyZip() { 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 62f2e52d0..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 @@ -12,6 +12,7 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice7.a; @@ -47,7 +48,13 @@ 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)); } @@ -98,4 +105,11 @@ public void lazyZip() { 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 2b3d4dbe7..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 @@ -12,6 +12,7 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice8.a; @@ -50,7 +51,13 @@ 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)); } @@ -95,4 +102,11 @@ public void lazyZip() { 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/hlist/SingletonHListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java index aae10e80e..57a9f85b7 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,12 @@ 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.SingletonHList.pureSingletonHList; import static org.junit.Assert.assertEquals; @RunWith(Traits.class) @@ -24,7 +26,7 @@ public void setUp() { singletonHList = new SingletonHList<>(1); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) public SingletonHList testSubject() { return singletonHList("one"); } @@ -48,4 +50,10 @@ public void cons() { 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 75d4bcbe2..32483a168 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 @@ -10,13 +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 static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple2.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.only; import static org.mockito.Mockito.spy; @@ -33,7 +41,14 @@ public void setUp() { tuple2 = new Tuple2<>(1, new SingletonHList<>(2)); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + MonadWriterLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) public Tuple2 testSubject() { return tuple("one", 2); } @@ -106,7 +121,7 @@ public void staticFactoryMethodFromMapEntry() { public void zipPrecedence() { Tuple2 a = tuple("foo", 1); Tuple2> b = tuple("bar", x -> x + 1); - assertEquals(tuple("bar", 2), a.zip(b)); + assertEquals(tuple("foo", 2), a.zip(b)); } @Test @@ -115,4 +130,17 @@ public void flatMapPrecedence() { 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 d52b407bb..9ac57fcfb 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 @@ -10,9 +10,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple3.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.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; @@ -29,7 +36,13 @@ public void setUp() { tuple3 = new Tuple3<>(1, new Tuple2<>("2", new SingletonHList<>('3'))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) public Tuple3 testSubject() { return tuple("one", 2, 3d); } @@ -58,8 +71,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(); @@ -93,4 +106,17 @@ public void flatMapPrecedence() { 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); + } } 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 4e0fa9659..b2a1d6319 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 @@ -10,9 +10,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple4.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -29,7 +36,13 @@ 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}) + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) public Tuple4 testSubject() { return tuple("one", 2, 3d, 4f); } @@ -96,4 +109,17 @@ public void flatMapPrecedence() { 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); + } } \ 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 45e4edc67..83b300b97 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 @@ -11,9 +11,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple5.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -30,7 +37,13 @@ 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}) + @TestTraits({ + FunctorLaws.class, + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + BifunctorLaws.class, + TraversableLaws.class}) public Tuple5 testSubject() { return tuple("one", 2, 3d, 4f, '5'); } @@ -93,7 +106,7 @@ public void zipPrecedence() { 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)); + assertEquals(tuple("foo", 1, 2, 3, 5), a.zip(b)); } @Test @@ -102,4 +115,17 @@ public void flatMapPrecedence() { 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); + } } \ 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 1fb7ab578..902b4b254 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 @@ -11,9 +11,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple6.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -30,7 +37,13 @@ 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}) + @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); } @@ -97,7 +110,7 @@ public void zipPrecedence() { 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)); + assertEquals(tuple("foo", 1, 2, 3, 4, 6), a.zip(b)); } @Test @@ -106,4 +119,17 @@ public void flatMapPrecedence() { 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); + } } \ 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 6203fe15e..0e8a68aec 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 @@ -11,9 +11,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.Tuple7.pureTuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -30,7 +37,13 @@ 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}) + @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); } @@ -100,7 +113,7 @@ public void zipPrecedence() { 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)); + assertEquals(tuple("foo", 1, 2, 3, 4, 5, 7), a.zip(b)); } @Test @@ -109,4 +122,18 @@ public void flatMapPrecedence() { 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); + } } \ 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 6c788a83d..4d2a8cb76 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 @@ -11,9 +11,16 @@ import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.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; @@ -27,10 +34,18 @@ 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}) + @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); } @@ -42,8 +57,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 @@ -65,8 +81,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(); @@ -93,7 +111,8 @@ 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)); } @@ -103,7 +122,7 @@ public void zipPrecedence() { = 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)); + assertEquals(tuple("foo", 1, 2, 3, 4, 5, 6, 8), a.zip(b)); } @Test @@ -114,4 +133,18 @@ public void flatMapPrecedence() { 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); + } } \ 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 f9e849f78..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 @@ -39,11 +39,19 @@ public void getForAbsentKey() { .get(typeSafeKey())); } + @Test + public void isEmpty() { + assertTrue(emptyHMap().isEmpty()); + assertFalse(singletonHMap(typeSafeKey(), "foo").isEmpty()); + } + @Test public void storesTypeSafeKeyBaseValue() { TypeSafeKey.Simple stringKey = typeSafeKey(); - TypeSafeKey longKey = stringKey.andThen(simpleIso(Long::parseLong, String::valueOf)); - TypeSafeKey bigIntegerKey = longKey.andThen(simpleIso(BigInteger::valueOf, BigInteger::longValue)); + TypeSafeKey longKey = stringKey.andThen(simpleIso(Long::parseLong, + String::valueOf)); + TypeSafeKey bigIntegerKey = longKey.andThen(simpleIso(BigInteger::valueOf, + BigInteger::longValue)); HMap hMap = singletonHMap(stringKey, "1"); assertEquals(just("1"), hMap.get(stringKey)); @@ -54,7 +62,8 @@ public void storesTypeSafeKeyBaseValue() { assertEquals(emptyHMap().put(longKey, 1L).get(longKey), emptyHMap().put(stringKey, "1").get(longKey)); assertEquals(emptyHMap().put(stringKey, "1").get(stringKey), emptyHMap().put(longKey, 1L).get(stringKey)); - assertEquals(emptyHMap().put(stringKey, "1").get(stringKey), emptyHMap().put(bigIntegerKey, ONE).get(stringKey)); + assertEquals(emptyHMap().put(stringKey, "1").get(stringKey), + emptyHMap().put(bigIntegerKey, ONE).get(stringKey)); assertEquals(singletonHMap(stringKey, "1"), singletonHMap(longKey, 1L)); assertEquals(singletonHMap(stringKey, "1"), singletonHMap(bigIntegerKey, ONE)); @@ -82,9 +91,9 @@ public void put() { @Test public void putAll() { - TypeSafeKey stringKey1 = typeSafeKey(); - TypeSafeKey stringKey2 = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); HMap left = hMap(stringKey1, "string value", intKey, 1); @@ -132,9 +141,9 @@ public void removeAll() { @Test public void containsKey() { - TypeSafeKey stringKey1 = typeSafeKey(); - TypeSafeKey stringKey2 = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); HMap hMap = singletonHMap(stringKey1, "string"); @@ -158,8 +167,8 @@ public void demandForAbsentKey() { @Test @SuppressWarnings("serial") public void toMap() { - TypeSafeKey stringKey = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); assertEquals(new HashMap, Object>() {{ put(stringKey, "string"); @@ -192,14 +201,14 @@ public void values() { @Test public void convenienceStaticFactoryMethods() { - TypeSafeKey.Simple stringKey = typeSafeKey(); - TypeSafeKey.Simple intKey = typeSafeKey(); - TypeSafeKey.Simple floatKey = typeSafeKey(); - TypeSafeKey.Simple byteKey = typeSafeKey(); - TypeSafeKey.Simple shortKey = typeSafeKey(); - TypeSafeKey.Simple longKey = typeSafeKey(); - TypeSafeKey.Simple doubleKey = typeSafeKey(); - TypeSafeKey.Simple charKey = typeSafeKey(); + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey.Simple intKey = typeSafeKey(); + TypeSafeKey.Simple floatKey = typeSafeKey(); + TypeSafeKey.Simple byteKey = typeSafeKey(); + TypeSafeKey.Simple shortKey = typeSafeKey(); + TypeSafeKey.Simple longKey = typeSafeKey(); + TypeSafeKey.Simple doubleKey = typeSafeKey(); + TypeSafeKey.Simple charKey = typeSafeKey(); HMap m1 = emptyHMap().put(stringKey, "string value"); HMap m2 = m1.put(intKey, 1); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/SchemaTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/SchemaTest.java index 3fdbde74f..d5cf6500c 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hmap/SchemaTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/SchemaTest.java @@ -18,14 +18,14 @@ public class SchemaTest { @Test public void extractsValuesAtKeysFromMap() { - TypeSafeKey.Simple byteKey = typeSafeKey(); - TypeSafeKey.Simple shortKey = typeSafeKey(); - TypeSafeKey.Simple intKey = typeSafeKey(); - TypeSafeKey.Simple longKey = typeSafeKey(); - TypeSafeKey.Simple floatKey = typeSafeKey(); - TypeSafeKey.Simple doubleKey = typeSafeKey(); - TypeSafeKey.Simple charKey = typeSafeKey(); - TypeSafeKey.Simple booleanKey = typeSafeKey(); + TypeSafeKey.Simple byteKey = typeSafeKey(); + TypeSafeKey.Simple shortKey = typeSafeKey(); + TypeSafeKey.Simple intKey = typeSafeKey(); + TypeSafeKey.Simple longKey = typeSafeKey(); + TypeSafeKey.Simple floatKey = typeSafeKey(); + TypeSafeKey.Simple doubleKey = typeSafeKey(); + TypeSafeKey.Simple charKey = typeSafeKey(); + TypeSafeKey.Simple booleanKey = typeSafeKey(); HMap m = hMap(byteKey, (byte) 1, shortKey, (short) 2, diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java index f03acea5b..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 @@ -24,9 +24,9 @@ public void lensLawfulness() { @Test public void compositionMapsOriginalValueInAndOutOfHMap() { - TypeSafeKey.Simple stringKey = typeSafeKey(); - TypeSafeKey intKey = stringKey.andThen(simpleIso(Integer::parseInt, Object::toString)); - HMap map = emptyHMap().put(stringKey, "123"); + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey intKey = stringKey.andThen(simpleIso(Integer::parseInt, Object::toString)); + HMap map = emptyHMap().put(stringKey, "123"); assertEquals(just("123"), map.get(stringKey)); assertEquals(just(123), map.get(intKey)); @@ -40,16 +40,16 @@ public void compositionMapsOriginalValueInAndOutOfHMap() { @Test public void discardRPreservesTypeSafeKey() { - TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey.Simple stringKey = typeSafeKey(); TypeSafeKey discardedKey = stringKey.discardR(simpleIso(id(), id())); - HMap map = emptyHMap().put(stringKey, "123"); + HMap map = emptyHMap().put(stringKey, "123"); assertEquals(just("123"), map.get(discardedKey)); } @Test public void defaultEquality() { - TypeSafeKey.Simple keyA = typeSafeKey(); + TypeSafeKey.Simple keyA = typeSafeKey(); TypeSafeKey mappedKeyA = keyA.andThen(simpleIso(id(), id())); assertEquals(keyA, keyA); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java b/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java index 0609643f5..456d2619c 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/EffectTest.java @@ -12,11 +12,16 @@ 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 { @@ -25,16 +30,17 @@ 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 diMapL = effect.diMapL(Object::toString); + Effect contraMap = effect.contraMap(Object::toString); Effect stringEffect = effect.discardR(constantly("1")); - effect.apply("1").unsafePerformIO(); - diMapL.apply("2").unsafePerformIO(); - contraMap.apply("3").unsafePerformIO(); - stringEffect.apply("4").unsafePerformIO(); - - assertEquals(asList("1", "2", "3", "4"), results); + 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 @@ -42,8 +48,8 @@ public void andThen() { AtomicInteger counter = new AtomicInteger(); Effect inc = c -> io(sideEffect(c::incrementAndGet)); - inc.andThen(inc).apply(counter).unsafePerformIO(); - assertEquals(2, counter.get()); + assertThat(alter(inc.andThen(inc), counter).fmap(AtomicInteger::get), + yieldsValue(equalTo(2))); } @Test @@ -51,12 +57,12 @@ public void staticFactoryMethods() { AtomicInteger counter = new AtomicInteger(); Effect sideEffect = effect(counter::incrementAndGet); - sideEffect.apply("foo").unsafePerformIO(); - assertEquals(1, counter.get()); + assertThat(sideEffect.apply("foo").flatMap(constantly(io(counter::get))), + yieldsValue(equalTo(1))); Effect fnEffect = Effect.fromConsumer(AtomicInteger::incrementAndGet); - fnEffect.apply(counter).unsafePerformIO(); - assertEquals(2, counter.get()); + assertThat(fnEffect.apply(counter).flatMap(constantly(io(counter::get))), + yieldsValue(equalTo(2))); } @Test 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 85db3d418..39400c6b0 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -4,10 +4,13 @@ import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.EquatableM; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.MonadReaderLaws; +import testsupport.traits.MonadWriterLaws; import java.util.function.Function; @@ -20,13 +23,19 @@ 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 EquatableM, ?> testSubject() { - return new EquatableM<>(fn1(Integer::parseInt), f -> f.apply("1")); + @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 @@ -90,4 +99,10 @@ public void toFunction() { 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")); + } } 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 71e3a65e3..97a65cd99 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java @@ -2,9 +2,11 @@ import org.junit.Test; +import java.util.Map; import java.util.function.BiFunction; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -52,4 +54,10 @@ public void fromBiFunction() { BiFunction biFunction = String::format; assertEquals("foo bar", Fn2.fromBiFunction(biFunction).apply("foo %s", "bar")); } + + @Test + public void curry() { + Fn1, String> uncurried = into((a, b) -> a + b); + assertEquals("foobar", Fn2.curry(uncurried).apply("foo", "bar")); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DowncastTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DowncastTest.java index b21e30b73..585419e1b 100644 --- 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 @@ -13,16 +13,16 @@ public class DowncastTest { @SuppressWarnings("unused") public void safeDowncast() { CharSequence charSequence = "123"; - String s = downcast(charSequence); + String s = downcast(charSequence); Functor> maybeInt = nothing(); - Maybe cast = downcast(maybeInt); + Maybe cast = downcast(maybeInt); } @Test(expected = ClassCastException.class) @SuppressWarnings({"JavacQuirks", "unused"}) public void unsafeDowncast() { CharSequence charSequence = "123"; - Integer explosion = downcast(charSequence); + Integer explosion = downcast(charSequence); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/MagnetizeTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/MagnetizeTest.java 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/ReverseTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ReverseTest.java index 434ed4ba2..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 @@ -30,7 +30,7 @@ public Reverse createTestSubject() { @Test public void iteratesElementsOfAnIterableBackwards() { - Iterable words = asList("the", "rain", "in", "Spain"); + Iterable words = asList("the", "rain", "in", "Spain"); Iterable reversed = reverse(words); assertThat(reversed, iterates("Spain", "in", "rain", "the")); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SizeTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SizeTest.java index a0039dc3e..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 { @@ -23,12 +22,18 @@ 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/UnconsTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UnconsTest.java index 115ba8164..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 @@ -25,7 +25,7 @@ public Uncons testSubject() { @Test public void nonEmptyIterable() { - Iterable numbers = asList(1, 2, 3); + Iterable numbers = asList(1, 2, 3); Tuple2> headAndTail = uncons(numbers).orElseThrow(AssertionError::new); assertEquals((Integer) 1, headAndTail._1()); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UpcastTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UpcastTest.java index a93d83537..2a5fa7e55 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UpcastTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UpcastTest.java @@ -11,8 +11,8 @@ public class UpcastTest { @Test @SuppressWarnings("unused") public void castsUp() { - Upcast upcast = upcast(); - Iterable strings = asList("foo", "bar"); - Iterable charSequences = map(upcast, strings); + Upcast upcast = upcast(); + Iterable strings = asList("foo", "bar"); + Iterable charSequences = map(upcast, strings); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java index 9bff92fa4..5baa26fa1 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/AlterTest.java @@ -7,15 +7,19 @@ import static com.jnape.palatable.lambda.functions.Effect.fromConsumer; import static com.jnape.palatable.lambda.functions.builtin.fn2.Alter.alter; import static java.util.Collections.singletonList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; public class AlterTest { @Test public void altersInput() { ArrayList input = new ArrayList<>(); - assertSame(input, alter(fromConsumer(xs -> xs.add("foo")), input).unsafePerformIO()); - assertEquals(singletonList("foo"), input); + assertThat(alter(fromConsumer(xs -> xs.add("foo")), input), + yieldsValue(allOf(sameInstance(input), + equalTo(singletonList("foo"))))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/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/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/PartitionTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java index ed75f2568..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 @@ -36,7 +36,7 @@ public class PartitionTest { @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 3b0860b22..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek2Test.java +++ /dev/null @@ -1,33 +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.Effect.fromConsumer; -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(fromConsumer(__ -> counter.incrementAndGet()), - fromConsumer(__ -> counter.incrementAndGet()), tuple)); - assertEquals(2, counter.get()); - } - - @Test - public void followsSameConventionsAsBimap() { - AtomicInteger counter = new AtomicInteger(0); - Either either = right(1); - peek2(fromConsumer(__ -> counter.incrementAndGet()), fromConsumer(__ -> 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 5ab1fba4f..000000000 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PeekTest.java +++ /dev/null @@ -1,29 +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.Effect.fromConsumer; -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(fromConsumer(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/SlideTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SlideTest.java index 6736b37ad..258e221b0 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SlideTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SlideTest.java @@ -45,8 +45,8 @@ public void kMustBeGreaterThan0() { @Test public void stackSafety() { - Integer stackBlowingNumber = 50000; - Iterable> xss = slide(2, take(stackBlowingNumber, iterate(x -> x + 1, 1))); + int stackBlowingNumber = 50_000; + Iterable> xss = slide(2, take(stackBlowingNumber, iterate(x -> x + 1, 1))); assertThat(drop(stackBlowingNumber - 2, xss), iterates(asList(49999, 50000))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhileTest.java index bfa4e7000..30a4172b3 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 @@ -33,7 +33,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)); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UntilTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UntilTest.java new file mode 100644 index 000000000..90c63dbee --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UntilTest.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Until.until; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; + +public class UntilTest { + + @Test + public void repeatedlyExecutesUntilPredicateMatches() { + AtomicInteger counter = new AtomicInteger(0); + assertThat(until(x -> x == 10, io(counter::getAndIncrement)), + yieldsValue(equalTo(10))); + } + + @Test + public void predicateThatImmediatelyMatchesDoesNotChangeIO() { + assertThat(until(constantly(true), io(0)), yieldsValue(equalTo(0))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ZipTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ZipTest.java index 69f1545d7..ae7aa9d38 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ZipTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ZipTest.java @@ -27,9 +27,8 @@ public Fn1, Iterable>> createTestSubject } @Test - @SuppressWarnings("unchecked") public void zipsTwoIterablesTogether() { - Iterable odds = asList(1, 3, 5); + Iterable odds = asList(1, 3, 5); Iterable evens = asList(2, 4, 6); Iterable> numbers = zip(odds, evens); diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BracketTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/BracketTest.java index 389ca5508..9e2f373a1 100644 --- 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 @@ -6,11 +6,15 @@ 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.fail; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.throwsException; +import static testsupport.matchers.IOMatcher.yieldsValue; public class BracketTest { @@ -26,7 +30,7 @@ public void cleanupHappyPath() { IO hashIO = bracket(io(() -> count), c -> io(c::incrementAndGet), c -> io(c::hashCode)); assertEquals(0, count.get()); - assertEquals((Integer) count.hashCode(), hashIO.unsafePerformIO()); + assertThat(hashIO, yieldsValue(equalTo(count.hashCode()))); assertEquals(1, count.get()); } @@ -35,28 +39,19 @@ public void cleanupSadPath() { IllegalStateException thrown = new IllegalStateException("kaboom"); IO hashIO = bracket(io(count), c -> io(c::incrementAndGet), c -> io(() -> {throw thrown;})); - try { - hashIO.unsafePerformIO(); - fail("Expected exception to be raised"); - } catch (IllegalStateException actual) { - assertEquals(thrown, actual); - assertEquals(1, count.get()); - } + assertThat(hashIO, throwsException(equalTo(thrown))); + assertEquals(1, count.get()); } @Test public void cleanupOnlyRunsIfInitialIORuns() { IllegalStateException thrown = new IllegalStateException("kaboom"); IO hashIO = bracket(io(() -> {throw thrown;}), - __ -> io(count::incrementAndGet), - __ -> io(count::incrementAndGet)); - try { - hashIO.unsafePerformIO(); - fail("Expected exception to be raised"); - } catch (IllegalStateException actual) { - assertEquals(thrown, actual); - assertEquals(0, count.get()); - } + constantly(io(count::incrementAndGet)), + constantly(io(count::incrementAndGet))); + + assertThat(hashIO, throwsException(equalTo(thrown))); + assertEquals(0, count.get()); } @Test @@ -64,14 +59,10 @@ public void errorsInCleanupAreAddedToBodyErrors() { IllegalStateException bodyError = new IllegalStateException("kaboom"); IllegalStateException cleanupError = new IllegalStateException("KABOOM"); IO hashIO = bracket(io(count), - c -> io(() -> {throw cleanupError;}), - c -> io(() -> {throw bodyError;})); - try { - hashIO.unsafePerformIO(); - fail("Expected exception to be raised"); - } catch (IllegalStateException actual) { - assertEquals(bodyError, actual); - assertArrayEquals(new Throwable[]{cleanupError}, actual.getSuppressed()); - } + 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/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/fn6/LiftA5Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5Test.java index 0d1851d34..bd131e897 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn6/LiftA5Test.java @@ -10,6 +10,7 @@ public class LiftA5Test { @Test public void lifting() { - assertEquals(just(15), liftA5((a, b, c, d, e) -> a + b + c + d + e, just(1), just(2), just(3), just(4), just(5))); + assertEquals(just(15), + liftA5((a, b, c, d, e) -> a + b + c + d + e, just(1), just(2), just(3), just(4), just(5))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6Test.java index a247e5620..76ed29162 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn7/LiftA6Test.java @@ -10,6 +10,8 @@ public class LiftA6Test { @Test public void lifting() { - assertEquals(just(21), liftA6((a, b, c, d, e, f) -> a + b + c + d + e + f, just(1), just(2), just(3), just(4), just(5), just(6))); + assertEquals(just(21), + liftA6((a, b, c, d, e, f) -> a + b + c + d + e + f, + just(1), just(2), just(3), just(4), just(5), just(6))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7Test.java index 73297afde..f81bd6ef0 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn8/LiftA7Test.java @@ -10,6 +10,7 @@ public class LiftA7Test { @Test public void lifting() { - assertEquals(just(28), liftA7((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g, just(1), just(2), just(3), just(4), just(5), just(6), just(7))); + assertEquals(just(28), liftA7((a, b, c, d, e, f, g) -> a + b + c + d + e + f + g, + just(1), just(2), just(3), just(4), just(5), just(6), just(7))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java b/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java index b46f91f5d..d8a156a9a 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResultTest.java @@ -3,21 +3,30 @@ import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; @RunWith(Traits.class) public class RecursiveResultTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) public Subjects> testSubject() { return subjects(recurse("foo"), terminate(1)); } + + @Test + public void staticPure() { + RecursiveResult recursiveResult = RecursiveResult.pureRecursiveResult().apply(1); + assertEquals(terminate(1), recursiveResult); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java index dc994a3b0..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 @@ -12,6 +12,9 @@ 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; @@ -38,4 +41,10 @@ public void lazyZip() { 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 da90a8592..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}) + @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 242a5aaa0..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}) + @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 index b169b7870..45785399b 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/LazyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/LazyTest.java @@ -5,26 +5,29 @@ import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.EquatableM; 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}) - public EquatableM, Integer> testSubject() { - return new EquatableM<>(lazy(0), Lazy::value); + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Equivalence> testSubject() { + return equivalence(lazy(0), Lazy::value); } @Test @@ -63,4 +66,10 @@ public Lazy checkedApply(Lazy lazyX) { } }.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 index d4009918e..9814d5ee0 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/MarketTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/MarketTest.java @@ -3,27 +3,39 @@ 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.EquatableM; 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}) - public Subjects, String>> testSubject() { + @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(new EquatableM<>(market, m -> both(m.bt(), m.sta(), "123")), - new EquatableM<>(market, m -> both(m.bt(), m.sta(), "foo"))); + 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 index f2071a27e..fba168ecd 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/StateTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/StateTest.java @@ -1,28 +1,37 @@ package com.jnape.palatable.lambda.functor.builtin; 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.Test; import org.junit.runner.RunWith; -import testsupport.EquatableM; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadReaderLaws; +import testsupport.traits.MonadWriterLaws; +import testsupport.traits.MonadRecLaws; 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.adt.product.Product2.product; import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; import static org.junit.Assert.assertEquals; +import static testsupport.traits.Equivalence.equivalence; @RunWith(Traits.class) public class StateTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class}) - public EquatableM, Unit> testSubject() { - return new EquatableM<>(State.get(), state -> state.run(UNIT).into(HList::tuple)); + @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 @@ -60,7 +69,7 @@ public void modify() { @Test public void state() { assertEquals(tuple(1, UNIT), State.state(1).run(UNIT)); - assertEquals(tuple(1, -1), State.state(x -> product(x + 1, x - 1)).run(0)); + assertEquals(tuple(1, -1), State.state(x -> tuple(x + 1, x - 1)).run(0)); } @Test @@ -69,6 +78,14 @@ public void stateAccumulation() { assertEquals(tuple(0, 1), counter.run(0)); } + @Test + public void zipOrdering() { + Tuple2 result = State.state(s -> tuple(0, s + "1")) + .zip(State.>state(s -> tuple(x -> x + 1, s + "2"))) + .run("_"); + assertEquals(tuple(1, "_12"), result); + } + @Test public void withState() { State modified = State.get().withState(x -> x + 1); @@ -77,7 +94,13 @@ public void withState() { @Test public void mapState() { - State modified = State.get().mapState(into((a, s) -> product(a + 1, s + 2))); + State modified = State.get().mapState(into((a, s) -> tuple(a + 1, s + 2))); assertEquals(tuple(1, 2), modified.run(0)); } + + @Test + public void staticPure() { + State state = State.pureState().apply(1); + assertEquals(tuple(1, "foo"), state.run("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/internal/iteration/CombinatorialIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIteratorTest.java index 639136dd7..9e52df9b3 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/CombinatorialIteratorTest.java @@ -4,7 +4,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.util.Iterator; @@ -46,7 +46,6 @@ public void doesNotHaveNextIfMoreAsButNoBs() { @Test public void doesNotHaveNextIfNoAsButMoreBs() { when(as.hasNext()).thenReturn(false); - when(bs.hasNext()).thenReturn(true); assertThat(combinatorialIterator.hasNext(), is(false)); } diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIteratorTest.java index b13dc33d1..0abdb857e 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/DroppingIteratorTest.java @@ -4,7 +4,7 @@ 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/internal/iteration/GroupingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/GroupingIteratorTest.java index 0288574d2..d4e1df3ab 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/GroupingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/GroupingIteratorTest.java @@ -4,7 +4,7 @@ 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/internal/iteration/MappingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIteratorTest.java index 396982cdc..23abb2ce7 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/MappingIteratorTest.java @@ -13,8 +13,8 @@ public class MappingIteratorTest { @Test public void nextProducesMappedResult() { - Fn1 stringToLength = String::length; - List words = asList("foo", "bar"); + Fn1 stringToLength = String::length; + List words = asList("foo", "bar"); MappingIterator mappingIterator = new MappingIterator<>(stringToLength, words.iterator()); assertThat(mappingIterator.next(), is(3)); diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java index bb3afa82f..7fb0613c3 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java @@ -5,7 +5,7 @@ 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; diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIteratorTest.java index 70ba8953f..aae11165b 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedTakingIteratorTest.java @@ -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/internal/iteration/ReversingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIteratorTest.java index 1fd9afce1..dd31b4bf7 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ReversingIteratorTest.java @@ -4,7 +4,7 @@ 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/internal/iteration/RewindableIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java index 532397934..33bda1ad4 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java @@ -4,7 +4,7 @@ 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; diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIteratorTest.java index d22dd8138..b405da88b 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TakingIteratorTest.java @@ -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(); diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIteratorTest.java new file mode 100644 index 000000000..81b37122c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIteratorTest.java @@ -0,0 +1,72 @@ +package com.jnape.palatable.lambda.internal.iteration; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class TrampoliningIteratorTest { + + @Test + public void hasNextIfAnyTerminateInstructions() { + TrampoliningIterator it = new TrampoliningIterator<>(x -> singleton(terminate(x + 1)), 0); + assertTrue(it.hasNext()); + assertEquals(1, it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void hasNextIfTerminateInterleavedBeforeRecurse() { + TrampoliningIterator it = new TrampoliningIterator<>( + x -> x < 3 + ? asList(terminate(x), recurse(x + 1)) + : emptyList(), + 0); + assertTrue(it.hasNext()); + assertEquals(0, it.next()); + assertTrue(it.hasNext()); + assertEquals(1, it.next()); + assertTrue(it.hasNext()); + assertEquals(2, it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void hasNextIfTerminateInterleavedAfterRecurse() { + TrampoliningIterator it = new TrampoliningIterator<>( + x -> x < 3 + ? asList(recurse(x + 1), terminate(x)) + : emptyList(), + 0); + assertTrue(it.hasNext()); + assertEquals(2, it.next()); + assertTrue(it.hasNext()); + assertEquals(1, it.next()); + assertTrue(it.hasNext()); + assertEquals(0, it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void doesNotHaveNextIfEmptyInitialResult() { + TrampoliningIterator it = new TrampoliningIterator<>(constantly(emptyList()), 0); + assertFalse(it.hasNext()); + } + + @Test + public void doesNotHaveNextIfNoTerminateInstruction() { + TrampoliningIterator it = new TrampoliningIterator<>( + x -> x < 3 + ? singleton(recurse(x + 1)) + : emptyList(), + 0); + assertFalse(it.hasNext()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/ZippingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ZippingIteratorTest.java index 96e655db0..9fb639a6a 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/ZippingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/ZippingIteratorTest.java @@ -5,7 +5,7 @@ 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; @@ -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 index 2c86683e3..723abb4fc 100644 --- a/src/test/java/com/jnape/palatable/lambda/io/IOTest.java +++ b/src/test/java/com/jnape/palatable/lambda/io/IOTest.java @@ -1,17 +1,26 @@ 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.EquatableM; 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; @@ -21,24 +30,51 @@ 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 { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class}) - public EquatableM, Integer> testSubject() { - return new EquatableM<>(io(1), IO::unsafePerformIO); + 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 @@ -102,85 +138,39 @@ public void delegatesToExternallyManagedFuture() { } @Test - public void exceptionallyRecoversThrowableToResult() { - IO io = io(() -> { throw new UnsupportedOperationException("foo"); }); - assertEquals("foo", io.exceptionally(Throwable::getMessage).unsafePerformIO()); + 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")); - }}).exceptionally(e -> e.getCause().getMessage()); + }}).catchError(e -> io(() -> e.getCause().getMessage())); assertEquals("foo", externallyManaged.unsafePerformIO()); } @Test - public void exceptionallyRescuesFutures() { - ExecutorService executor = newFixedThreadPool(2); - - IO io = io(() -> { throw new UnsupportedOperationException("foo"); }); - assertEquals("foo", io.exceptionally(e -> e.getCause().getMessage()).unsafePerformAsyncIO().join()); - assertEquals("foo", io.exceptionally(e -> e.getCause().getMessage()).unsafePerformAsyncIO(executor).join()); - - IO externallyManaged = externallyManaged(() -> new CompletableFuture() {{ - completeExceptionally(new UnsupportedOperationException("foo")); - }}).exceptionally(e -> e.getCause().getMessage()); - assertEquals("foo", externallyManaged.unsafePerformIO()); - } + public void catchAndRethrow() { + IllegalStateException expected = new IllegalStateException("expected"); + IO catchAndRethrow = IO.throwing(expected) + .catchError(IO::throwing); - @Test - public void linearSyncStackSafety() { - assertEquals(STACK_EXPLODING_NUMBER, - times(STACK_EXPLODING_NUMBER, f -> f.fmap(x -> x + 1), io(0)).unsafePerformIO()); - assertEquals(STACK_EXPLODING_NUMBER, - times(STACK_EXPLODING_NUMBER, f -> f.zip(f.pure(x -> x + 1)), io(0)).unsafePerformIO()); - assertEquals((Integer) 0, - times(STACK_EXPLODING_NUMBER, f -> f.pure(0).discardR(f), io(0)).unsafePerformIO()); - assertEquals((Integer) 1, - times(STACK_EXPLODING_NUMBER, f -> f.pure(1).discardR(f), io(0)).unsafePerformIO()); - assertEquals((Integer) 0, - times(STACK_EXPLODING_NUMBER, f -> f.pure(1).discardL(f), io(0)).unsafePerformIO()); - assertEquals(STACK_EXPLODING_NUMBER, - times(STACK_EXPLODING_NUMBER, f -> f.flatMap(x -> f.pure(x + 1)), io(0)).unsafePerformIO()); - } - - @Test - public void recursiveSyncFlatMapStackSafety() { - assertEquals(STACK_EXPLODING_NUMBER, - new Fn1, IO>() { - @Override - public IO checkedApply(IO a) { - return a.flatMap(x -> x < STACK_EXPLODING_NUMBER ? apply(io(x + 1)) : io(x)); - } - }.apply(io(0)).unsafePerformIO()); - - } - - @Test - public void linearAsyncStackSafety() { - assertEquals(STACK_EXPLODING_NUMBER, - times(STACK_EXPLODING_NUMBER, f -> f.fmap(x -> x + 1), io(0)).unsafePerformAsyncIO().join()); - assertEquals(STACK_EXPLODING_NUMBER, - times(STACK_EXPLODING_NUMBER, f -> f.zip(f.pure(x -> x + 1)), io(0)).unsafePerformAsyncIO() - .join()); - assertEquals((Integer) 0, - times(STACK_EXPLODING_NUMBER, f -> f.pure(0).discardR(f), io(0)).unsafePerformAsyncIO().join()); - assertEquals((Integer) 1, - times(STACK_EXPLODING_NUMBER, f -> f.pure(1).discardR(f), io(0)).unsafePerformAsyncIO().join()); - assertEquals((Integer) 0, - times(STACK_EXPLODING_NUMBER, f -> f.pure(1).discardL(f), io(0)).unsafePerformAsyncIO().join()); - assertEquals(STACK_EXPLODING_NUMBER, - times(STACK_EXPLODING_NUMBER, f -> f.flatMap(x -> f.pure(x + 1)), io(0)).unsafePerformAsyncIO() - .join()); - } + try { + catchAndRethrow.unsafePerformIO(); + } catch (Exception actual) { + assertSame(expected, actual); + } - @Test - public void recursiveAsyncFlatMapStackSafety() { - assertEquals(STACK_EXPLODING_NUMBER, - new Fn1, IO>() { - @Override - public IO checkedApply(IO a) { - return a.flatMap(x -> x < STACK_EXPLODING_NUMBER ? apply(io(x + 1)) : io(x)); - } - }.apply(io(0)).unsafePerformAsyncIO().join()); + try { + catchAndRethrow.unsafePerformAsyncIO().join(); + } catch (CompletionException actual) { + assertEquals(expected, actual.getCause()); + } } @Test @@ -243,4 +233,281 @@ public void throwing() { 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/monad/SafeTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/SafeTTest.java new file mode 100644 index 000000000..17206c5fa --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/SafeTTest.java @@ -0,0 +1,76 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.adt.Unit; +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.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 java.util.concurrent.CountDownLatch; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.SafeT.safeT; +import static java.util.concurrent.Executors.newFixedThreadPool; +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(new Identity<>(STACK_EXPLODING_NUMBER), + times(STACK_EXPLODING_NUMBER, + safeT -> safeT.zip(safeT(new Identity<>(x -> x + 1))), + safeT(new Identity<>(0))).runSafeT()); + + } + + @Test(timeout = 500) + public void compositionallyPreservesZip() { + CountDownLatch latch = new CountDownLatch(2); + IO countDownAndAwait = io(() -> { + latch.countDown(); + latch.await(); + }); + + safeT(countDownAndAwait) + .discardL(safeT(countDownAndAwait)) + .>runSafeT() + .unsafePerformAsyncIO(newFixedThreadPool(2)) + .join(); + } +} \ 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 index b5d717b69..3fdd45f03 100644 --- 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 @@ -7,13 +7,16 @@ 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 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.monad.transformer.builtin.EitherT.eitherT; import static com.jnape.palatable.traitor.framework.Subjects.subjects; @@ -22,7 +25,12 @@ @RunWith(Traits.class) public class EitherTTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class}) + @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)))); } @@ -36,4 +44,11 @@ public void lazyZip() { throw new AssertionError(); })).value()); } + + @Test + public void staticPure() { + EitherT, String, Integer> eitherT = EitherT., String>pureEitherT(pureIdentity()) + .apply(1); + assertEquals(eitherT(new Identity<>(right(1))), eitherT); + } } \ 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 index fb9a6629d..0d31de5d8 100644 --- 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 @@ -9,17 +9,20 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; 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.monad.transformer.builtin.IdentityT.identityT; +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}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) public IdentityT, Integer> testSubject() { return identityT(just(new Identity<>(1))); } @@ -34,4 +37,10 @@ public void lazyZip() { throw new AssertionError(); })).value()); } + + @Test + public void staticPure() { + IdentityT, Integer> identityT = pureIdentityT(pureMaybe()).apply(1); + assertEquals(identityT(just(new Identity<>(1))), identityT); + } } \ 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 index 87dffb7e6..8daa73bbe 100644 --- 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 @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.monad.transformer.builtin; import com.jnape.palatable.lambda.adt.Maybe; +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; @@ -8,17 +9,20 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; 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.monad.transformer.builtin.LazyT.lazyT; +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}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) public LazyT, Integer> testSubject() { return lazyT(just(lazy(1))); } @@ -33,4 +37,10 @@ public void lazyZip() { throw new AssertionError(); })).value()); } + + @Test + public void staticPure() { + LazyT, Integer> lazyT = pureLazyT(pureIdentity()).apply(1); + assertEquals(lazyT(new Identity<>(lazy(1))), lazyT); + } } \ 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 index 9a5368cdc..cba72968a 100644 --- 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 @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.monad.transformer.builtin; import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.functor.builtin.Identity; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; @@ -9,20 +10,23 @@ import testsupport.traits.ApplicativeLaws; 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.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.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; @RunWith(Traits.class) public class MaybeTTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) public Subjects, Integer>> testSubject() { return subjects(maybeT(right(just(1))), maybeT(right(nothing())), @@ -38,4 +42,10 @@ public void lazyZip() { throw new AssertionError(); })).value()); } + + @Test + public void staticPure() { + MaybeT, Integer> maybeT = pureMaybeT(pureIdentity()).apply(1); + assertEquals(maybeT(new Identity<>(just(1))), maybeT); + } } \ 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..55ee2eec7 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java @@ -0,0 +1,60 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +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 testsupport.traits.MonadReaderLaws; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +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 staticPure() { + ReaderT, Integer> readerT = + ReaderT.>pureReaderT(pureIdentity()).apply(1); + assertEquals(new Identity<>(1), readerT.runReaderT("foo")); + } +} \ 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..f36d660ef --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java @@ -0,0 +1,121 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +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 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.functor.builtin.Identity.pureIdentity; +import static org.junit.Assert.assertEquals; +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 + "_"))); + + assertEquals(new Identity<>("__"), stateT.execT("_")); + assertEquals(new Identity<>(1), stateT.evalT("_")); + } + + @Test + public void mapStateT() { + StateT, Integer> stateT = + StateT.stateT(str -> new Identity<>(tuple(str.length(), str + "_"))); + assertEquals(just(tuple(4, "ABC_")), + stateT.mapStateT(id -> id.>>coerce() + .runIdentity() + .into((x, str) -> just(tuple(x + 1, str.toUpperCase())))) + .>>runStateT("abc")); + } + + @Test + public void zipping() { + assertEquals(new Identity<>(tuple(4, "final state: FOO")), + StateT.>modify(s -> new Identity<>(s.toUpperCase())) + .discardL(StateT.gets(s -> new Identity<>(s.length()))) + .flatMap(x -> StateT.stateT(s -> new Identity<>(tuple(x + 1, "final state: " + s)))) + .>>runStateT("foo")); + } + + @Test + public void withStateT() { + StateT, Integer> stateT = + StateT.stateT(str -> new Identity<>(tuple(str.length(), str + "_"))); + assertEquals(new Identity<>(tuple(3, "ABC_")), + stateT.withStateT(str -> new Identity<>(str.toUpperCase())).runStateT("abc")); + } + + @Test + public void get() { + assertEquals(new Identity<>(tuple("state", "state")), + StateT.>get(pureIdentity()).runStateT("state")); + } + + @Test + public void gets() { + assertEquals(new Identity<>(tuple(5, "state")), + StateT., Integer>gets(s -> new Identity<>(s.length())).runStateT("state")); + } + + @Test + public void put() { + assertEquals(new Identity<>(tuple(UNIT, 1)), StateT.put(new Identity<>(1)).runStateT(0)); + } + + @Test + public void modify() { + assertEquals(new Identity<>(tuple(UNIT, 1)), + StateT.>modify(x -> new Identity<>(x + 1)).runStateT(0)); + } + + @Test + public void stateT() { + assertEquals(new Identity<>(tuple(0, "_")), + StateT., Integer>stateT(new Identity<>(0)).runStateT("_")); + assertEquals(new Identity<>(tuple(1, "_1")), + StateT., Integer>stateT(s -> new Identity<>(tuple(s.length(), s + "1"))) + .runStateT("_")); + } + + @Test + public void staticPure() { + assertEquals(new Identity<>(tuple(1, "foo")), + StateT.>pureStateT(pureIdentity()) + ., Integer>>apply(1) + .>>runStateT("foo")); + } + + @Test + public void staticLift() { + assertEquals(new Identity<>(tuple(1, "foo")), + StateT.liftStateT()., StateT, Integer>>apply(new Identity<>(1)) + .>>runStateT("foo")); + } +} \ 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..a1e3f0531 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterTTest.java @@ -0,0 +1,71 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +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.MonadRecLaws; +import testsupport.traits.MonadWriterLaws; + +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.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.monad.transformer.builtin.WriterT.writerT; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; +import static org.junit.Assert.assertEquals; +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() { + Identity> result = writerT(new Identity<>(tuple(1, "foo"))) + .discardR(WriterT.tell(new Identity<>("bar"))) + .flatMap(x -> writerT(new Identity<>(tuple(x + 1, "baz")))) + .runWriterT(join()); + + assertEquals(new Identity<>(tuple(2, "foobarbaz")), result); + } + + @Test + public void tell() { + assertEquals(new Identity<>(tuple(UNIT, "")), + WriterT.tell(new Identity<>("")).runWriterT(join())); + } + + @Test + public void listen() { + assertEquals(new Identity<>(tuple(1, "")), + WriterT., Integer>listen(new Identity<>(1)).runWriterT(join())); + } + + @Test + public void staticPure() { + WriterT, Integer> apply = WriterT.>pureWriterT(pureIdentity()).apply(1); + assertEquals(new Identity<>(tuple(1, "")), + apply.runWriterT(join())); + } + + @Test + public void staticLift() { + WriterT, Integer> apply = WriterT.liftWriterT().apply(new Identity<>(1)); + assertEquals(new Identity<>(tuple(1, "")), + apply.runWriterT(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..87cfced3e 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java @@ -15,19 +15,19 @@ 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 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/AndTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AndTest.java index d78aaf702..25b10aca9 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AndTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AndTest.java @@ -27,7 +27,7 @@ public void monoid() { @Test(timeout = 500) public void shortCircuiting() { Iterable bools = cons(false, repeat(true)); - And and = and(); + And and = and(); assertEquals(false, and.foldLeft(false, bools)); assertEquals(false, and.foldLeft(true, bools)); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java index 1e0f36e9e..5c42de619 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/CollapseTest.java @@ -5,14 +5,15 @@ import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.monoid.builtin.Collapse.collapse; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; import static org.junit.Assert.assertEquals; public class CollapseTest { @Test public void monoid() { - Monoid join = Monoid.monoid((x, y) -> x + y, ""); - Monoid add = Monoid.monoid((x, y) -> x + y, 0); + Monoid join = join(); + Monoid add = Monoid.monoid(Integer::sum, 0); Collapse collapse = collapse(); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java index d3bf689db..faca56af2 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ComposeTest.java @@ -15,7 +15,7 @@ public class ComposeTest { @Test public void monoid() throws ExecutionException, InterruptedException { - Monoid addition = Monoid.monoid((x, y) -> x + y, 0); + Monoid addition = Monoid.monoid(Integer::sum, 0); CompletableFuture failedFuture = new CompletableFuture() {{ completeExceptionally(new RuntimeException()); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java index 76675e52e..4b5ee5960 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java @@ -29,7 +29,7 @@ public void monoid() { @Test(timeout = 500) public void shortCircuiting() { Iterable> maybeInts = repeat(just(1)); - First first = First.first(); + First first = First.first(); assertEquals(just(1), first.foldLeft(nothing(), maybeInts)); assertEquals(just(1), first.foldLeft(just(1), maybeInts)); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java index dc6692ed0..24426ca71 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LeftAnyTest.java @@ -13,7 +13,7 @@ public class LeftAnyTest { @Test public void monoid() { LeftAny leftAny = leftAny(); - Monoid join = Monoid.monoid((x, y) -> x + y, ""); + Monoid join = Monoid.monoid((x, y) -> x + y, ""); assertEquals(left(""), leftAny.apply(join).identity()); assertEquals(left("foo"), leftAny.apply(join).apply(left("foo"), right(1))); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java index be871be21..fe27f4833 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeMapsTest.java @@ -18,7 +18,8 @@ import static org.junit.Assert.assertTrue; public class MergeMapsTest { - private static final Semigroup ADD = (x, y) -> x + y; + private static final Semigroup ADD = Integer::sum; + private Monoid> merge; @Before @@ -36,8 +37,8 @@ public void monoid() { assertEquals(singletonMap("foo", 1), merge.apply(emptyMap(), singletonMap("foo", 1))); assertEquals(singletonMap("foo", 1), merge.apply(singletonMap("foo", 1), emptyMap())); assertEquals(singletonMap("foo", 2), - merge.apply(singletonMap("foo", 1), singletonMap("foo", 1))); + merge.apply(singletonMap("foo", 1), singletonMap("foo", 1))); assertEquals(toMap(HashMap::new, asList(tuple("foo", 1), tuple("bar", 1))), - merge.apply(singletonMap("foo", 1), singletonMap("bar", 1))); + merge.apply(singletonMap("foo", 1), singletonMap("bar", 1))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java index d421c1e5d..0e22e20bd 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/OrTest.java @@ -27,7 +27,7 @@ public void monoid() { @Test(timeout = 500) public void shortCircuiting() { Iterable bools = cons(true, repeat(false)); - Or or = or(); + Or or = or(); assertEquals(true, or.foldLeft(false, bools)); assertEquals(true, or.foldLeft(true, bools)); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java index 98b1f952d..49eca1df6 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java @@ -12,8 +12,8 @@ public class PresentTest { @Test public void monoid() { - Present present = present(); - Semigroup addition = (x, y) -> x + y; + Present present = present(); + Semigroup addition = Integer::sum; assertEquals(just(3), present.apply(addition, just(1), just(2))); assertEquals(just(1), present.apply(addition, nothing(), just(1))); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java index bd664b01d..5d3f90215 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java @@ -19,8 +19,8 @@ public void identity() { @Test public void monoid() { - TypeSafeKey stringKey = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); HMap x = singletonHMap(stringKey, "string"); HMap y = singletonHMap(intKey, 1); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RightAnyTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RightAnyTest.java index 96a4d4e8a..730438ace 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RightAnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RightAnyTest.java @@ -13,7 +13,7 @@ public class RightAnyTest { @Test public void monoid() { RightAny rightAny = rightAny(); - Monoid add = Monoid.monoid((x, y) -> x + y, 0); + Monoid add = Monoid.monoid(Integer::sum, 0); assertEquals(right(0), rightAny.apply(add).identity()); assertEquals(right(1), rightAny.apply(add).apply(right(1), left("foo"))); diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RunAllTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RunAllTest.java index c0e9f6d17..c7460e53e 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RunAllTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/RunAllTest.java @@ -5,14 +5,16 @@ import static com.jnape.palatable.lambda.io.IO.io; import static com.jnape.palatable.lambda.monoid.builtin.RunAll.runAll; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; public class RunAllTest { @Test public void monoid() { - Monoid add = Monoid.monoid((x, y) -> x + y, 0); - assertEquals((Integer) 3, runAll(add).apply(io(1), io(2)).unsafePerformIO()); - assertEquals((Integer) 0, runAll(add).identity().unsafePerformIO()); + Monoid add = Monoid.monoid(Integer::sum, 0); + assertThat(runAll(add).apply(io(1), io(2)), yieldsValue(equalTo(3))); + assertThat(runAll(add).identity(), yieldsValue(equalTo(0))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/optics/IsoTest.java b/src/test/java/com/jnape/palatable/lambda/optics/IsoTest.java index 91c181029..8c2bdb79f 100644 --- a/src/test/java/com/jnape/palatable/lambda/optics/IsoTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/IsoTest.java @@ -5,10 +5,11 @@ import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.EquatableM; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import java.util.List; @@ -19,6 +20,7 @@ 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 EquatableM, List> testSubject() { - return new EquatableM<>(ISO, iso -> view(iso, "123")); + @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/optics/LensTest.java b/src/test/java/com/jnape/palatable/lambda/optics/LensTest.java index f5b1cbf2a..8283cd4d4 100644 --- a/src/test/java/com/jnape/palatable/lambda/optics/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/LensTest.java @@ -9,10 +9,11 @@ import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.EquatableM; 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; @@ -32,6 +33,7 @@ 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 { @@ -41,10 +43,9 @@ public class LensTest { 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 EquatableM, ?, Integer, String>, List> testSubject() { - return new EquatableM<>(lens(m -> m.get("foo"), (m, s) -> singletonList(m.get(s))), - lens -> view(lens, emptyMap())); + @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 @@ -97,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")); @@ -107,7 +108,7 @@ public void bothSplitsFocusBetweenLenses() { @Test public void bothForSimpleLenses() { - Lens.Simple stringToInt = simpleLens(Integer::parseInt, (s, i) -> s + i.toString()); + Lens.Simple stringToInt = simpleLens(Integer::parseInt, (s, i) -> s + i.toString()); Lens.Simple stringToChar = simpleLens(s -> s.charAt(0), (s, c) -> s + c.toString()); assertEquals(tuple(3, '3'), view(both(stringToInt, stringToChar), "3")); diff --git a/src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java b/src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java index b2080bb0b..22e2c5541 100644 --- a/src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java +++ b/src/test/java/com/jnape/palatable/lambda/optics/PrismTest.java @@ -7,31 +7,38 @@ import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.EquatableM; 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}) - public EquatableM, String> testSubject() { - return new EquatableM<>(Prism.fromPartial(Integer::parseInt, Object::toString), - prism -> matching(prism, "foo")); + @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 @@ -60,4 +67,35 @@ public void unPrismExtractsMappings() { 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/semigroup/builtin/AbsentTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java index 7df46e6e2..5ef25395e 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java @@ -12,8 +12,8 @@ public class AbsentTest { @Test public void semigroup() { - Absent absent = absent(); - Semigroup addition = (x, y) -> x + y; + Absent absent = absent(); + Semigroup addition = Integer::sum; assertEquals(just(3), absent.apply(addition, just(1), just(2))); assertEquals(nothing(), absent.apply(addition, nothing(), just(1))); 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/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/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/RightAnyTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java index ec234090e..46d8e5f5a 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RightAnyTest.java @@ -13,7 +13,7 @@ public class RightAnyTest { @Test public void semigroup() { RightAny rightAny = rightAny(); - Semigroup add = (x, y) -> x + y; + Semigroup add = Integer::sum; assertEquals(right(1), rightAny.apply(add).apply(right(1), left("foo"))); assertEquals(right(1), rightAny.apply(add).apply(left("foo"), right(1))); diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java index 329fe8429..2733690c4 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/RunAllTest.java @@ -1,17 +1,17 @@ package com.jnape.palatable.lambda.semigroup.builtin; -import com.jnape.palatable.lambda.semigroup.Semigroup; import org.junit.Test; import static com.jnape.palatable.lambda.io.IO.io; import static com.jnape.palatable.lambda.semigroup.builtin.RunAll.runAll; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IOMatcher.yieldsValue; public class RunAllTest { @Test public void semigroup() { - Semigroup add = (x, y) -> x + y; - assertEquals((Integer) 3, runAll(add).apply(io(1), io(2)).unsafePerformIO()); + assertThat(runAll(Integer::sum).apply(io(1), io(2)), yieldsValue(equalTo(3))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java index 163fa5f20..ede9fde89 100644 --- a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java @@ -10,6 +10,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.Maybe.just; @@ -19,12 +20,16 @@ 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; @@ -33,16 +38,46 @@ @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(), 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 = wrap(asList(x -> x + 1, x -> x - 1)); LambdaIterable xs = wrap(asList(1, 2, 3)); - assertThat(xs.zip(fns).unwrap(), iterates(2, 3, 4, 0, 1, 2)); + LambdaIterable> fns = wrap(asList(x -> x + 1, x -> x - 1)); + assertThat(xs.zip(fns).unwrap(), iterates(2, 0, 3, 1, 4, 2)); } @Test @@ -67,4 +102,10 @@ public void lazyZip() { 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/testsupport/EquatableM.java b/src/test/java/testsupport/EquatableM.java deleted file mode 100644 index a046e3fa6..000000000 --- a/src/test/java/testsupport/EquatableM.java +++ /dev/null @@ -1,63 +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.Objects; - -public final class EquatableM, A> implements Monad> { - - private final Monad ma; - private final Fn1 equatable; - - public EquatableM(Monad ma, Fn1 equatable) { - this.ma = ma; - this.equatable = equatable; - } - - @Override - public EquatableM flatMap(Fn1>> f) { - return new EquatableM<>(ma.flatMap(f.fmap(x -> x.>coerce().ma)), equatable); - } - - @Override - public EquatableM pure(B b) { - return new EquatableM<>(ma.pure(b), equatable); - } - - @Override - public EquatableM fmap(Fn1 fn) { - return new EquatableM<>(ma.fmap(fn), equatable); - } - - @Override - public EquatableM zip(Applicative, EquatableM> appFn) { - return new EquatableM<>(ma.zip(appFn.>>coerce().ma), equatable); - } - - @Override - public EquatableM discardL(Applicative> appB) { - return new EquatableM<>(ma.discardL(appB.>coerce().ma), equatable); - } - - @Override - public EquatableM discardR(Applicative> appB) { - return new EquatableM<>(ma.discardR(appB.>coerce().ma), equatable); - } - - @Override - @SuppressWarnings("unchecked") - public boolean equals(Object other) { - if (other instanceof EquatableM) { - EquatableM that = (EquatableM) other; - return Objects.equals(equatable.apply((M) ma), that.equatable.apply((M) that.ma)); - } - return false; - } - - @Override - public int hashCode() { - return super.hashCode(); - } -} diff --git a/src/test/java/testsupport/assertion/LensAssert.java b/src/test/java/testsupport/assertion/LensAssert.java index 2c174cada..a64e80fd5 100644 --- a/src/test/java/testsupport/assertion/LensAssert.java +++ b/src/test/java/testsupport/assertion/LensAssert.java @@ -34,7 +34,8 @@ public static void assertLensLawfulness(Optic, Functor< .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 -> IO.throwing(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 index 9794678de..443130fb4 100644 --- a/src/test/java/testsupport/assertion/PrismAssert.java +++ b/src/test/java/testsupport/assertion/PrismAssert.java @@ -42,7 +42,8 @@ public static void assertPrismLawfulness(Prism prism, 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))) - .peek(failures -> IO.throwing(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/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/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/LeftMatcher.java b/src/test/java/testsupport/matchers/LeftMatcher.java index fa57261fe..34ea33079 100644 --- a/src/test/java/testsupport/matchers/LeftMatcher.java +++ b/src/test/java/testsupport/matchers/LeftMatcher.java @@ -30,11 +30,12 @@ public void describeTo(Description description) { @Override protected void describeMismatchSafely(Either item, Description mismatchDescription) { mismatchDescription.appendText("was "); - item.peek(l -> io(() -> { - mismatchDescription.appendText("Left value of "); - lMatcher.describeMismatch(l, mismatchDescription); - }), - r -> io(() -> mismatchDescription.appendValue(item))); + item.match(l -> io(() -> { + mismatchDescription.appendText("Left value of "); + lMatcher.describeMismatch(l, mismatchDescription); + }), + r -> io(() -> mismatchDescription.appendValue(item))) + .unsafePerformIO(); } public static LeftMatcher isLeftThat(Matcher lMatcher) { diff --git a/src/test/java/testsupport/matchers/RightMatcher.java b/src/test/java/testsupport/matchers/RightMatcher.java index cce44fe03..1eafc0ab1 100644 --- a/src/test/java/testsupport/matchers/RightMatcher.java +++ b/src/test/java/testsupport/matchers/RightMatcher.java @@ -30,11 +30,12 @@ public void describeTo(Description description) { @Override protected void describeMismatchSafely(Either item, Description mismatchDescription) { mismatchDescription.appendText("was "); - item.peek(l -> io(() -> mismatchDescription.appendValue(item)), - r -> io(() -> { - mismatchDescription.appendText("Right value of "); - rMatcher.describeMismatch(r, mismatchDescription); - })); + item.match(l -> io(() -> mismatchDescription.appendValue(item)), + r -> io(() -> { + mismatchDescription.appendText("Right value of "); + rMatcher.describeMismatch(r, mismatchDescription); + })) + .unsafePerformIO(); } public static RightMatcher isRightThat(Matcher rMatcher) { diff --git a/src/test/java/testsupport/matchers/ZeroInvocationsMatcher.java b/src/test/java/testsupport/matchers/ZeroInvocationsMatcher.java index 3757eef2c..9c360867c 100644 --- a/src/test/java/testsupport/matchers/ZeroInvocationsMatcher.java +++ b/src/test/java/testsupport/matchers/ZeroInvocationsMatcher.java @@ -5,10 +5,10 @@ import org.hamcrest.Matcher; import org.mockito.exceptions.misusing.NotAMockException; import org.mockito.exceptions.verification.NoInteractionsWanted; -import org.mockito.internal.util.MockUtil; import org.mockito.invocation.Invocation; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.internal.util.MockUtil.getInvocationContainer; public class ZeroInvocationsMatcher extends BaseMatcher { @Override @@ -26,7 +26,7 @@ public boolean matches(Object item) { @Override public void describeMismatch(Object item, final Description description) { description.appendText("had these: "); - for (Invocation invocation : new MockUtil().getMockHandler(item).getInvocationContainer().getInvocations()) + for (Invocation invocation : getInvocationContainer(item).getInvocations()) description.appendText(invocation.toString()); } diff --git a/src/test/java/testsupport/traits/ApplicativeLaws.java b/src/test/java/testsupport/traits/ApplicativeLaws.java index 1d5c0904f..a193889fd 100644 --- a/src/test/java/testsupport/traits/ApplicativeLaws.java +++ b/src/test/java/testsupport/traits/ApplicativeLaws.java @@ -5,7 +5,6 @@ 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; @@ -14,94 +13,93 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.io.IO.throwing; import static java.util.Arrays.asList; -public class ApplicativeLaws> implements 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, - this::testLazyZip) - ) - .peek(s -> IO.throwing(new AssertionError("The following Applicative laws did not hold for instance of " - + applicative.getClass() + ": \n\t - " + s))); + 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(id()); - return 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) { + private Maybe testComposition(Equivalence> equivalence) { Random random = new Random(); Integer firstInt = random.nextInt(100); Integer secondInt = random.nextInt(100); - Fn1, - Fn1, - Fn1>> compose = x -> x::contraMap; - Applicative, App> u = applicative.pure(x -> x + firstInt); - Applicative, App> v = applicative.pure(x -> x + secondInt); - Applicative w = applicative.pure("result: "); - - return w.zip(v.zip(u.zip(u.pure(compose)))).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) { + 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) + 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); - int y = 1; - - Applicative pureY = applicative.pure(y); - return pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))) + private Maybe testInterchange(Equivalence> equivalence) { + int y = 1; + return equivalence.invMap(app -> app.pure(y).zip(app.pure(x -> x + 1))) + .equals(equivalence.invMap(app -> app.>pure(x -> x + 1) + .zip(app.pure(f -> f.apply(y))))) ? nothing() : just("interchange (pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))))"); } - private Maybe testDiscardL(Applicative applicative) { - Applicative u = applicative.pure("u"); - Applicative v = applicative.pure("v"); - - return u.discardL(v).equals(v.zip(u.zip(applicative.pure(constantly(id()))))) + 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"); - - return u.discardR(v).equals(v.zip(u.zip(applicative.pure(constantly())))) + private Maybe testDiscardR(Equivalence> equivalence) { + return equivalence.invMap(app -> app.pure("u").discardR(app.pure("v"))) + .equals(equivalence.invMap(app -> app.pure("v").zip(app.pure("u").zip(app.pure(constantly()))))) ? nothing() : just("discardR u.discardR(v).equals(v.zip(u.zip(applicative.pure(constantly()))))"); } - private Maybe testLazyZip(Applicative applicative) { - return applicative.lazyZip(lazy(applicative.pure(id()))).value().equals(applicative.zip(applicative.pure(id()))) + 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 d682435e6..ba5f88d7e 100644 --- a/src/test/java/testsupport/traits/BifunctorLaws.java +++ b/src/test/java/testsupport/traits/BifunctorLaws.java @@ -5,42 +5,50 @@ 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 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 void test(Bifunctor bifunctor) { + public Class> type() { + return Bifunctor.class; + } + + @Override + public void test(Equivalence> equivalence) { Present.present((x, y) -> x + "\n\t - " + y) - ., Maybe>>foldMap( - f -> f.apply(bifunctor), + .>, Maybe>>foldMap( + f -> f.apply(equivalence), asList(this::testLeftIdentity, this::testRightIdentity, this::testMutualIdentity) ) - .peek(s -> IO.throwing(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) + 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) + 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())) + 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/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/FunctorLaws.java b/src/test/java/testsupport/traits/FunctorLaws.java index 5fc7d5937..0371b1c81 100644 --- a/src/test/java/testsupport/traits/FunctorLaws.java +++ b/src/test/java/testsupport/traits/FunctorLaws.java @@ -5,38 +5,45 @@ 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 static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; import static java.util.Arrays.asList; -public class FunctorLaws> implements Trait> { +public class FunctorLaws> implements EquivalenceTrait> { @Override - public void test(Functor f) { + public Class> type() { + return Functor.class; + } + + @Override + public void test(Equivalence> equivalence) { Present.present((x, y) -> x + "\n\t - " + y) - ., Maybe>>foldMap( - fn -> fn.apply(f), + .>, Maybe>>foldMap( + fn -> fn.apply(equivalence), asList(this::testIdentity, this::testComposition)) - .peek(s -> IO.throwing(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(id()).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)); - Fn1 f = x -> x * 3; - Fn1 g = x -> x - 2; - return subject.fmap(f.contraMap(g)).equals(subject.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/MonadLaws.java b/src/test/java/testsupport/traits/MonadLaws.java index 2ff5a6e77..eda3b45c2 100644 --- a/src/test/java/testsupport/traits/MonadLaws.java +++ b/src/test/java/testsupport/traits/MonadLaws.java @@ -5,56 +5,62 @@ 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 static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; +import static com.jnape.palatable.lambda.io.IO.throwing; import static com.jnape.palatable.lambda.monad.Monad.join; import static java.util.Arrays.asList; -public class MonadLaws> implements Trait> { +public class MonadLaws> implements EquivalenceTrait> { @Override - public void test(Monad m) { + public Class> type() { + return Monad.class; + } + + @Override + public void test(Equivalence> equivalence) { Present.present((x, y) -> x + "\n\t - " + y) - ., Maybe>>foldMap(f -> f.apply(m), asList( + .>, Maybe>>foldMap(f -> f.apply(equivalence), asList( this::testLeftIdentity, this::testRightIdentity, this::testAssociativity, this::testJoin)) - .peek(s -> IO.throwing(new AssertionError("The following Monad laws did not hold for instance of " + - m.getClass() + ": \n\t - " + s))); + .match(IO::io, + s -> throwing(new AssertionError("The following Monad laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); } - private Maybe testLeftIdentity(Monad m) { - Object a = new Object(); - Fn1> fn = id().fmap(m::pure); - return m.pure(a).flatMap(fn).equals(fn.apply(a)) + private Maybe testLeftIdentity(Equivalence> equivalence) { + Object a = new Object(); + return equivalence.invMap(m -> m.pure(a.hashCode())) + .equals(equivalence.invMap(m -> m.pure(a.hashCode()))) ? nothing() : just("left identity (m.pure(a).flatMap(fn).equals(fn.apply(a)))"); } - private Maybe testRightIdentity(Monad m) { - return m.flatMap(m::pure).equals(m) + private Maybe testRightIdentity(Equivalence> equivalence) { + return equivalence.invMap(m -> m.flatMap(m::pure)).equals(equivalence) ? nothing() : just("right identity: (m.flatMap(m::pure).equals(m))"); } - private Maybe testAssociativity(Monad m) { - Fn1> f = constantly(m.pure(new Object())); - Fn1> g = constantly(m.pure(new Object())); - return m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g))) + private Maybe testAssociativity(Equivalence> equivalence) { + Object a = new Object(); + Object b = new Object(); + return equivalence.invMap(m -> m.flatMap(constantly(m.pure(a))).flatMap(constantly(m.pure(b)))) + .equals(equivalence.invMap(m -> m.flatMap(__ -> m.pure(a).flatMap(constantly(m.pure(b)))))) ? nothing() : just("associativity: (m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g))))"); } - private Maybe testJoin(Monad m) { - Monad, M> mma = m.pure(m.fmap(upcast())); - boolean equals = mma.flatMap(id()).equals(join(mma)); - return equals + private Maybe testJoin(Equivalence> equivalence) { + return equivalence.invMap(m -> m.pure(m).flatMap(id())) + .equals(equivalence.invMap(m -> join(m.pure(m)))) ? nothing() : just("join: (m.pure(m).flatMap(id())).equals(Monad.join(m.pure(m)))"); } diff --git a/src/test/java/testsupport/traits/MonadReaderLaws.java b/src/test/java/testsupport/traits/MonadReaderLaws.java new file mode 100644 index 000000000..2553500ae --- /dev/null +++ b/src/test/java/testsupport/traits/MonadReaderLaws.java @@ -0,0 +1,40 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadReader; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static java.util.Collections.singletonList; + +public class MonadReaderLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return MonadReader.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + f -> f.apply(equivalence), + singletonList(this::testLocalIdentity) + ) + .match(IO::io, + s -> throwing(new AssertionError("The following MonadReader laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testLocalIdentity(Equivalence> equivalence) { + return equivalence.invMap(mr -> mr.local(id())).equals(equivalence) + ? nothing() + : just("local identity (mr.local(id()).equals(mr))"); + } +} diff --git a/src/test/java/testsupport/traits/MonadRecLaws.java b/src/test/java/testsupport/traits/MonadRecLaws.java new file mode 100644 index 000000000..00f96eb45 --- /dev/null +++ b/src/test/java/testsupport/traits/MonadRecLaws.java @@ -0,0 +1,43 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static java.util.Collections.singletonList; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; + +public class MonadRecLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return MonadRec.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + fn -> fn.apply(equivalence), + singletonList(this::testStackSafety)) + .match(IO::io, + s -> IO.throwing(new AssertionError("The following MonadRec laws did not hold for instance of " + + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testStackSafety(Equivalence> equivalence) { + return equivalence.invMap(mr -> mr.pure(0) + .trampolineM(x -> mr.pure(x < STACK_EXPLODING_NUMBER + ? RecursiveResult.recurse(x + 1) + : RecursiveResult.terminate(x)))) + .equals(equivalence.invMap(mr -> mr.pure(STACK_EXPLODING_NUMBER))) + ? nothing() + : just("stack-safety (m.pure(" + STACK_EXPLODING_NUMBER + ").equals(m.pure(0).trampolineM(f)))"); + } +} diff --git a/src/test/java/testsupport/traits/MonadWriterLaws.java b/src/test/java/testsupport/traits/MonadWriterLaws.java new file mode 100644 index 000000000..8024535b8 --- /dev/null +++ b/src/test/java/testsupport/traits/MonadWriterLaws.java @@ -0,0 +1,48 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadWriter; +import com.jnape.palatable.lambda.monoid.builtin.Present; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.io.IO.throwing; +import static java.util.Arrays.asList; + +public class MonadWriterLaws> implements EquivalenceTrait> { + + @Override + public Class> type() { + return MonadWriter.class; + } + + @Override + public void test(Equivalence> equivalence) { + Present.present((x, y) -> x + "\n\t - " + y) + .>, Maybe>>foldMap( + f -> f.apply(equivalence), asList( + this::testCensor, + this::testListens) + ) + .match(IO::io, + s -> throwing(new AssertionError("The following MonadWriter laws did not hold for instance" + + " of " + equivalence + ": \n\t - " + s))) + .unsafePerformIO(); + } + + private Maybe testCensor(Equivalence> equivalence) { + return equivalence.invMap(mw -> mw.censor(id())).equals(equivalence) + ? nothing() + : just("censor (mw.censor(id()).equals(mw))"); + } + + private Maybe testListens(Equivalence> equivalence) { + return equivalence.invMap(mw -> mw.listens(id()).fmap(Tuple2::_1)).equals(equivalence) + ? nothing() + : just("censor (mw.censor(id()).equals(mw))"); + } +} diff --git a/src/test/java/testsupport/traits/TraversableLaws.java b/src/test/java/testsupport/traits/TraversableLaws.java index c0a6c4968..3b85202ba 100644 --- a/src/test/java/testsupport/traits/TraversableLaws.java +++ b/src/test/java/testsupport/traits/TraversableLaws.java @@ -9,60 +9,78 @@ 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 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 void test(Traversable traversable) { + public Class> type() { + return Traversable.class; + } + + @Override + public void test(Equivalence> equivalence) { Present.present((x, y) -> x + "\n\t - " + y) - ., Maybe>>foldMap( - f -> f.apply(traversable), + .>, Maybe>>foldMap( + f -> f.apply(equivalence), asList(this::testNaturality, this::testIdentity, this::testComposition) ) - .peek(s -> IO.throwing(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) { + @SuppressWarnings("unchecked") + private Maybe testNaturality(Equivalence> equivalence) { Fn1> f = Identity::new; Fn1, Either> t = id -> right(id.runIdentity()); - Fn1, Applicative, Identity>> pureFn = - x -> new Identity<>(x); - Fn1, 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.contraMap(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()))"); + " .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)) + private Maybe testIdentity(Equivalence> equivalence) { + Traversable trav = equivalence.getValue(); + return trav.traverse(Identity::new, Identity::new).fmap(equivalence::swap) + .equals(new Identity<>(trav).fmap(equivalence::swap)) ? nothing() : just("identity (trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new Identity<>(trav))"); } - private Maybe testComposition(Traversable trav) { + private Maybe testComposition(Equivalence> equivalence) { Fn1> f = Identity::new; - Fn1>> g = x -> new Identity<>(x); + Fn1>> g = Identity::new; - return trav.traverse(f.fmap(x -> x.fmap(g)).fmap(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))))) + 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))))))"); + : 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))))))"); } }