Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefix all Cats instances with cats, add Alternative instance for partial.Result and PartialTransformer, change instances name to something which won't change as often #465

Merged
merged 2 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
package io.scalaland.chimney.cats

import _root_.cats.{~>, Applicative, CoflatMap, Eval, Monad, MonadError, Parallel, Traverse}
import _root_.cats.{~>, Alternative, Applicative, CoflatMap, Eval, Monad, MonadError, Parallel, Traverse}
import _root_.cats.arrow.FunctionK
import _root_.cats.data.{Chain, NonEmptyChain, NonEmptyList, Validated, ValidatedNec, ValidatedNel}
import _root_.cats.kernel.{Eq, Semigroup}
import io.scalaland.chimney.partial
import io.scalaland.chimney.partial.{AsResult, Result}
import io.scalaland.chimney.partial.AsResult

import language.implicitConversions

/** @since 1.0.0 */
trait CatsPartialResultImplicits {

/** @since 0.7.0 */
implicit final val monadErrorCoflatMapTraversePartialResult
: MonadError[partial.Result, partial.Result.Errors] & CoflatMap[partial.Result] & Traverse[partial.Result] =
new MonadError[partial.Result, partial.Result.Errors] with CoflatMap[partial.Result] with Traverse[partial.Result] {
implicit final val catsCovariantForPartialResult: MonadError[partial.Result, partial.Result.Errors] &
CoflatMap[partial.Result] &
Traverse[partial.Result] &
Alternative[partial.Result] =
new MonadError[partial.Result, partial.Result.Errors]
with CoflatMap[partial.Result]
with Traverse[partial.Result]
with Alternative[partial.Result] {
override def pure[A](x: A): partial.Result[A] = partial.Result.Value(x)

override def flatMap[A, B](fa: partial.Result[A])(f: A => partial.Result[B]): partial.Result[B] = fa.flatMap(f)
Expand Down Expand Up @@ -55,10 +60,14 @@ trait CatsPartialResultImplicits {
case partial.Result.Value(value) => f(value, lb)
case _ => lb
}

override def empty[A]: partial.Result[A] = partial.Result.fromEmpty[A]

override def combineK[A](x: partial.Result[A], y: partial.Result[A]): partial.Result[A] = x.orElse(y)
}

/** @since 1.0.0 */
implicit final val parallelSemigroupalPartialResult: Parallel[partial.Result] {
implicit final val catsParallelForPartialResult: Parallel[partial.Result] {
type F[A] = partial.Result[A]
} = new Parallel[partial.Result] {
override type F[A] = partial.Result[A]
Expand All @@ -73,69 +82,68 @@ trait CatsPartialResultImplicits {
partial.Result.map2[A => B, A, B](ff, fa, (f, a) => f(a), failFast = false)
}

override val monad: Monad[partial.Result] = monadErrorCoflatMapTraversePartialResult
override val monad: Monad[partial.Result] = catsCovariantForPartialResult
}

/** @since 0.7.0 */
implicit final val semigroupPartialResultErrors: Semigroup[partial.Result.Errors] =
implicit final val catsSemigroupForPartialResultErrors: Semigroup[partial.Result.Errors] =
Semigroup.instance(partial.Result.Errors.merge)

/** @since 1.0.0 */
implicit final def eqPartialResult[A: Eq]: Eq[partial.Result[A]] = {
case (partial.Result.Value(a1), partial.Result.Value(a2)) => Eq[A].eqv(a1, a2)
case (e1: partial.Result.Errors, e2: partial.Result.Errors) =>
e1.asErrorPathMessages.iterator.sameElements(e2.asErrorPathMessages.iterator)
case _ => false
implicit final def catsEqForPartialResult[A: Eq]: Eq[partial.Result[A]] = {
case (partial.Result.Value(a1), partial.Result.Value(a2)) => Eq[A].eqv(a1, a2)
case (e1: partial.Result.Errors, e2: partial.Result.Errors) => catsEqForPartialResultErrors.eqv(e1, e2)
case _ => false
}

/** @since 1.0.0 */
implicit final def eqPartialResultErrors: Eq[partial.Result.Errors] = (e1, e2) =>
implicit final val catsEqForPartialResultErrors: Eq[partial.Result.Errors] = (e1, e2) =>
e1.asErrorPathMessages.iterator.sameElements(e2.asErrorPathMessages.iterator)

/** @since 0.7.0 */
implicit final def catsPartialTransformerResultOps[A](ptr: partial.Result[A]): CatsPartialTransformerResultOps[A] =
new CatsPartialTransformerResultOps(ptr)

/** @since 1.0.0 */
implicit def validatedPartialResultErrorsAsResult[E <: partial.Result.Errors]: AsResult[Validated[E, *]] =
implicit def catsValidatedPartialResultErrorsAsResult[E <: partial.Result.Errors]: AsResult[Validated[E, *]] =
new AsResult[Validated[E, *]] {
def asResult[A](fa: Validated[E, A]): Result[A] = fa match {
def asResult[A](fa: Validated[E, A]): partial.Result[A] = fa match {
case Validated.Valid(a) => partial.Result.fromValue(a)
case Validated.Invalid(e) => e
}
}

/** @since 1.0.0 */
implicit def validatedNecPartialErrorAsResult[E <: partial.Error]: AsResult[ValidatedNec[E, *]] =
implicit def catsValidatedNecPartialErrorAsResult[E <: partial.Error]: AsResult[ValidatedNec[E, *]] =
new AsResult[ValidatedNec[E, *]] {
def asResult[A](fa: ValidatedNec[E, A]): Result[A] = fa match {
def asResult[A](fa: ValidatedNec[E, A]): partial.Result[A] = fa match {
case Validated.Valid(a) => partial.Result.fromValue(a)
case Validated.Invalid(e) => partial.Result.Errors(e.head, e.tail.toList*)
}
}

/** @since 1.0.0 */
implicit def validatedNelPartialErrorAsResult[E <: partial.Error]: AsResult[ValidatedNel[E, *]] =
implicit def catsValidatedNelPartialErrorAsResult[E <: partial.Error]: AsResult[ValidatedNel[E, *]] =
new AsResult[ValidatedNel[E, *]] {
def asResult[A](fa: ValidatedNel[E, A]): Result[A] = fa match {
def asResult[A](fa: ValidatedNel[E, A]): partial.Result[A] = fa match {
case Validated.Valid(a) => partial.Result.fromValue(a)
case Validated.Invalid(e) => partial.Result.Errors(e.head, e.tail*)
}
}

/** @since 1.0.0 */
implicit def validatedNecStringAsResult[E <: String]: AsResult[ValidatedNec[E, *]] =
implicit def catsValidatedNecStringAsResult[E <: String]: AsResult[ValidatedNec[E, *]] =
new AsResult[ValidatedNec[E, *]] {
def asResult[A](fa: ValidatedNec[E, A]): Result[A] = fa match {
def asResult[A](fa: ValidatedNec[E, A]): partial.Result[A] = fa match {
case Validated.Valid(a) => partial.Result.fromValue(a)
case Validated.Invalid(e) => partial.Result.fromErrorStrings(e.head, e.tail.toList*)
}
}

/** @since 1.0.0 */
implicit def validatedNelStringAsResult[E <: String]: AsResult[ValidatedNel[E, *]] =
implicit def catsValidatedNelStringAsResult[E <: String]: AsResult[ValidatedNel[E, *]] =
new AsResult[ValidatedNel[E, *]] {
def asResult[A](fa: ValidatedNel[E, A]): Result[A] = fa match {
def asResult[A](fa: ValidatedNel[E, A]): partial.Result[A] = fa match {
case Validated.Valid(a) => partial.Result.fromValue(a)
case Validated.Invalid(e) => partial.Result.fromErrorStrings(e.head, e.tail*)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.scalaland.chimney.cats

import _root_.cats.{~>, Applicative, CoflatMap, Contravariant, Monad, MonadError, Parallel}
import _root_.cats.{~>, Alternative, Applicative, CoflatMap, Contravariant, Monad, MonadError, Parallel}
import _root_.cats.arrow.{ArrowChoice, CommutativeArrow, FunctionK}
import io.scalaland.chimney.partial
import io.scalaland.chimney.PartialTransformer
Expand All @@ -9,7 +9,7 @@ import io.scalaland.chimney.PartialTransformer
trait CatsPartialTransformerImplicits {

/** @since 1.0.0 */
implicit final val commutativeArrowChoiceForPartialTransformer
implicit final val catsArrowForPartialTransformer
: ArrowChoice[PartialTransformer] & CommutativeArrow[PartialTransformer] =
new ArrowChoice[PartialTransformer] with CommutativeArrow[PartialTransformer] {
override def lift[A, B](f: A => B): PartialTransformer[A, B] = PartialTransformer.fromFunction(f)
Expand All @@ -35,9 +35,13 @@ trait CatsPartialTransformerImplicits {
}

/** @since 1.0.0 */
implicit final def monadErrorCoflatMapForPartialTransformer[Source]
: MonadError[PartialTransformer[Source, *], partial.Result.Errors] & CoflatMap[PartialTransformer[Source, *]] =
new MonadError[PartialTransformer[Source, *], partial.Result.Errors] with CoflatMap[PartialTransformer[Source, *]] {
implicit final def catsCovariantForPartialTransformer[Source]
: MonadError[PartialTransformer[Source, *], partial.Result.Errors] &
CoflatMap[PartialTransformer[Source, *]] &
Alternative[PartialTransformer[Source, *]] =
new MonadError[PartialTransformer[Source, *], partial.Result.Errors]
with CoflatMap[PartialTransformer[Source, *]]
with Alternative[PartialTransformer[Source, *]] {
override def pure[A](x: A): PartialTransformer[Source, A] = (_, _) => partial.Result.Value(x)

override def flatMap[A, B](fa: PartialTransformer[Source, A])(
Expand Down Expand Up @@ -78,10 +82,18 @@ trait CatsPartialTransformerImplicits {
)(
f: PartialTransformer[Source, A] => B
): PartialTransformer[Source, B] = (src, _) => partial.Result.fromCatching(f(fa))

override def empty[A]: PartialTransformer[Source, A] = (_, _) => partial.Result.fromEmpty[A]

override def combineK[A](
x: PartialTransformer[Source, A],
y: PartialTransformer[Source, A]
): PartialTransformer[Source, A] = (src, failFast) =>
x.transform(src, failFast).orElse(y.transform(src, failFast))
}

/** @since 1.0.0 */
implicit final def parallelForPartialTransformer[Source]: Parallel[PartialTransformer[Source, *]] {
implicit final def catsParallelForPartialTransformer[Source]: Parallel[PartialTransformer[Source, *]] {
type F[A] = PartialTransformer[Source, A]
} =
new Parallel[PartialTransformer[Source, *]] {
Expand All @@ -105,11 +117,11 @@ trait CatsPartialTransformerImplicits {
)
}

val monad: Monad[PartialTransformer[Source, *]] = monadErrorCoflatMapForPartialTransformer[Source]
val monad: Monad[PartialTransformer[Source, *]] = catsCovariantForPartialTransformer[Source]
}

/** @since 1.0.0 */
implicit final def contravariantForPartialTransformer[Target]: Contravariant[PartialTransformer[*, Target]] =
implicit final def catsContravariantForPartialTransformer[Target]: Contravariant[PartialTransformer[*, Target]] =
new Contravariant[PartialTransformer[*, Target]] {
def contramap[A, B](fa: PartialTransformer[A, Target])(f: B => A): PartialTransformer[B, Target] =
(b, failFast) => partial.Result.fromCatching(f(b)).flatMap(a => fa.transform(a, failFast))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import io.scalaland.chimney.Transformer
trait CatsTotalTransformerImplicits {

/** @since 1.0.0 */
implicit final val commutativeArrowChoiceForTransformer: ArrowChoice[Transformer] & CommutativeArrow[Transformer] =
implicit final val catsArrowForTransformer: ArrowChoice[Transformer] & CommutativeArrow[Transformer] =
new ArrowChoice[Transformer] with CommutativeArrow[Transformer] {
override def lift[A, B](f: A => B): Transformer[A, B] = f(_)

Expand All @@ -28,7 +28,7 @@ trait CatsTotalTransformerImplicits {
}

/** @since 1.0.0 */
implicit final def monadCoflatMapForTransformer[Source]
implicit final def catsCovariantForTransformer[Source]
: Monad[Transformer[Source, *]] & CoflatMap[Transformer[Source, *]] =
new Monad[Transformer[Source, *]] with CoflatMap[Transformer[Source, *]] {
override def pure[A](x: A): Transformer[Source, A] = _ => x
Expand Down Expand Up @@ -56,7 +56,7 @@ trait CatsTotalTransformerImplicits {
}

/** @since 1.0.0 */
implicit final def contravariantForTransformer[Target]: Contravariant[Transformer[*, Target]] =
implicit final def catsContravariantForTransformer[Target]: Contravariant[Transformer[*, Target]] =
new Contravariant[Transformer[*, Target]] {
def contramap[A, B](fa: Transformer[A, Target])(f: B => A): Transformer[B, Target] =
b => fa.transform(f(b))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class PartialResultLaws extends ChimneySpec with utils.ArbitraryUtils {
}

group(
"MonadError[partial.Result, partial.Result.Errors] & CoflatMap[partial.Result] & Traverse[partial.Result] should follow laws"
"MonadError[partial.Result, partial.Result.Errors] & CoflatMap[partial.Result] & Traverse[partial.Result] & Alternative[partial.Result] should follow laws"
) {
checkLawsAsTests(InvariantTests[partial.Result].invariant[Int, String, Double])
checkLawsAsTests(SemigroupalTests[partial.Result].semigroupal[Int, String, Double])
Expand All @@ -29,5 +29,8 @@ class PartialResultLaws extends ChimneySpec with utils.ArbitraryUtils {
UnorderedTraverseTests[partial.Result].unorderedTraverse[Int, Long, Double, Const[Unit, *], Const[Int, *]]
)
checkLawsAsTests(TraverseTests[partial.Result].traverse[Int, Long, Double, Byte, Const[Unit, *], Const[Int, *]])
checkLawsAsTests(SemigroupKTests[partial.Result].semigroupK[Int])
checkLawsAsTests(MonoidKTests[partial.Result].monoidK[Int])
checkLawsAsTests(AlternativeTests[partial.Result].alternative[Int, String, Double])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ class PartialTransformerLaws extends ChimneySpec with utils.ArbitraryUtils {
}

group(
"MonadError[Transformer[Source, *], partial.Result.Errors] & CoflatMap[Transformer[Source, *]] instance should follow laws"
"MonadError[PartialTransformer[Source, *], partial.Result.Errors] & CoflatMap[PartialTransformer[Source, *]] & Alternative[PartialTransformer[Source, *]] instance should follow laws"
) {
checkLawsAsTests(InvariantTests[PartialTransformer[String, *]].invariant[Int, String, Double])
checkLawsAsTests(SemigroupalTests[PartialTransformer[String, *]].semigroupal[Int, String, Double])
checkLawsAsTests(FunctorTests[PartialTransformer[String, *]].functor[Int, String, Double])
checkLawsAsTests(ApplicativeTests[PartialTransformer[String, *]].applicative[Int, String, Double])
checkLawsAsTests(FlatMapTests[PartialTransformer[String, *]].flatMap[Int, String, Double])
Expand All @@ -31,14 +32,17 @@ class PartialTransformerLaws extends ChimneySpec with utils.ArbitraryUtils {
MonadErrorTests[PartialTransformer[String, *], partial.Result.Errors].monadError[Int, String, Double]
)
checkLawsAsTests(CoflatMapTests[PartialTransformer[String, *]].coflatMap[Int, String, Double])
checkLawsAsTests(SemigroupKTests[PartialTransformer[String, *]].semigroupK[Int])
checkLawsAsTests(MonoidKTests[PartialTransformer[String, *]].monoidK[Int])
checkLawsAsTests(AlternativeTests[PartialTransformer[String, *]].alternative[Int, String, Double])
}

group("Parallel[Transformer[From, *]] instance should follow laws") {
group("Parallel[PartialTransformer[From, *]] instance should follow laws") {
checkLawsAsTests(NonEmptyParallelTests[PartialTransformer[String, *]].nonEmptyParallel[Int, String])
checkLawsAsTests(ParallelTests[PartialTransformer[String, *]].parallel[Int, String])
}

group("Contravariant[Transformer[*, To]] instance should follow laws") {
group("Contravariant[PartialTransformer[*, To]] instance should follow laws") {
checkLawsAsTests(ContravariantTests[PartialTransformer[*, String]].contravariant[Int, String, Double])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cats.Eq
import cats.data.Const
import cats.syntax.eq.*
import io.scalaland.chimney.{partial, ChimneySpec, PartialTransformer, Transformer}
import io.scalaland.chimney.cats.eqPartialResult
import io.scalaland.chimney.cats.catsEqForPartialResult
import org.scalacheck.{Arbitrary, Cogen}
import org.scalacheck.Test.check
import org.scalacheck.Prop.forAll
Expand Down
18 changes: 18 additions & 0 deletions chimney/src/main/scala/io/scalaland/chimney/partial/Result.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,24 @@ sealed trait Result[+A] {
case _: Result.Errors => this.asInstanceOf[Result[B]]
}

/** Prepends a [[io.scalaland.chimney.partial.PathElement]] to all errors represented by this result.
*
* @tparam B the element type of the returned result
* @param result lazy [[io.scalaland.chimney.partial.Result]] to compute as a fallback if this one has errors
* @return a [[io.scalaland.chimney.partial.Result]] with the first successful value or a failure combining errors
* from both results
*
* @since 1.0.0
*/
final def orElse[B >: A](result: => Result[B]): Result[B] = this match {
case _: Result.Value[?] => this
case e: Result.Errors =>
result match {
case r: Result.Value[?] => r
case e2: Result.Errors => Result.Errors.merge(e, e2)
}
}

/** Prepends a [[io.scalaland.chimney.partial.PathElement]] to all errors represented by this result.
*
* @param pathElement [[io.scalaland.chimney.partial.PathElement]] to be prepended
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,38 @@ class PartialResultSpec extends ChimneySpec {
result2.asErrorPathMessageStrings ==> Iterable("" -> """For input string: "error2"""")
}

test("orElse fallbacks on another Result on error, aggregating errors if both Results fail") {
var used = false
val result = partial.Result.fromValue(1).orElse {
used = true
partial.Result.fromValue(2)
}
result.asOption ==> Some(1)
result.asEither ==> Right(1)
result.asErrorPathMessageStrings ==> Iterable()
used ==> false

var used2 = false
val result2 = partial.Result.fromEmpty[Int].orElse {
used2 = true
partial.Result.fromValue(2)
}
result2.asOption ==> Some(2)
result2.asEither ==> Right(2)
result2.asErrorPathMessageStrings ==> Iterable()
used2 ==> true

var used3 = false
val result3 = partial.Result.fromEmpty[Int].orElse {
used3 = true
partial.Result.fromEmpty[Int]
}
result3.asOption ==> None
result3.asEither.isLeft ==> true
result3.asErrorPathMessageStrings ==> Iterable("" -> """empty value""", "" -> """empty value""")
used3 ==> true
}

test("fromFunction converts function into function returning Result") {
partial.Result.fromFunction[Int, Int](_ * 2).apply(3) ==> partial.Result.fromValue(6)
}
Expand Down
13 changes: 7 additions & 6 deletions docs/docs/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,20 +295,21 @@ Cats integration module contains the following utilities:
- for `Transformer` type class:
- `ArrowChoice[Transformer] & CommutativeArrow[Transformer]` (implementing also `Arrow`, `Choice`, `Category`,
`Compose`, `Strong`, `Profunctor`)
- `[Source] => Monad[Transformer[Source, *]] with CoflatMap[Transformer[Source, *]]` (implementing also `Monad`,
`Applicative`, `Functor`)
- `[Source] => Monad[Transformer[Source, *]] & CoflatMap[Transformer[Source, *]]`
(implementing also `Monad`, `Applicative`, `Functor`)
- `[Target] => Contravariant[Transformer[*, Target]]` (implementing also `Invariant`)
- for `PartialTransformer` type class:
- `ArrowChoice[PartialTransformer] & CommutativeArrow[PartialTransformer]` (implementing also `Arrow`, `Choice`,
`Category`,`Compose`, `Strong`, `Profunctor`)
- `[Source] => MonadError[PartialTransformer[Source, *], partial.Result.Errors] with CoflatMap[PartialTransformer[Source, *]]`
(implementing also `Monad`, `Applicative`, `Functor`, `ApplicativeError`)
- `[Source] => MonadError[PartialTransformer[Source, *], partial.Result.Errors] & CoflatMap[PartialTransformer[Source, *]] & Alternative[PartialTransformer[Source, *]]`
(implementing also `Monad`, `Applicative`, `Functor`, `ApplicativeError`, `NonEmptyAlternative`, `MonoidK`,
`SemigroupK`)
- `[Source] => Parallel[PartialTransformer[Source, *]]` (implementing also `NonEmptyParallel`)
- `[Target] => Contravariant[Transformer[*, Target]]` (implementing also `Invariant`)
- for `partial.Result` data type:
- `MonadError[partial.Result, partial.Result.Errors] & CoflatMap[partial.Result] & Traverse[partial.Result]`
- `MonadError[partial.Result, partial.Result.Errors] & CoflatMap[partial.Result] & Traverse[partial.Result] $ Alternative[partial.Result]`
(implementing also `Monad`, `Applicative`, `Functor`, `ApplicativeError`, `UnorderedTraverse`, `Foldable`,
`UnorderedFoldable`, `Invariant[partial.Result]`, `Semigriupal[partial.Result]`, ...)
`UnorderedFoldable`, `Invariant`, `Semigriupal`, `NonEmptyAlternative`, `SemigroupK`, `MonoidK`)
- `Parallel[partial.Result]` (implementing also`NonEmptyParallel`)
- `Semigroup[partial.Result.Errors]`

Expand Down
Loading