Skip to content

Commit

Permalink
Give NonEmptyChain more presence (#2431)
Browse files Browse the repository at this point in the history
* Give NonEmptyChain more presence

* Small tweak
  • Loading branch information
Luka Jacobowitz authored Sep 1, 2018
1 parent 8da9f79 commit 380f721
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 8 deletions.
9 changes: 9 additions & 0 deletions core/src/main/scala/cats/data/EitherT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {
def toValidatedNel(implicit F: Functor[F]): F[ValidatedNel[A, B]] =
F.map(value)(_.toValidatedNel)

def toValidatedNec(implicit F: Functor[F]): F[ValidatedNec[A, B]] =
F.map(value)(_.toValidatedNec)

/** Run this value as a `[[Validated]]` against the function and convert it back to an `[[EitherT]]`.
*
* The [[Applicative]] instance for `EitherT` "fails fast" - it is often useful to "momentarily" have
Expand Down Expand Up @@ -274,6 +277,12 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {
*/
def toNestedValidatedNel(implicit F: Functor[F]): Nested[F, ValidatedNel[A, ?], B] =
Nested[F, ValidatedNel[A, ?], B](F.map(value)(_.toValidatedNel))

/**
* Transform this `EitherT[F, A, B]` into a `[[Nested]][F, ValidatedNec[A, ?], B]`.
*/
def toNestedValidatedNec(implicit F: Functor[F]): Nested[F, ValidatedNec[A, ?], B] =
Nested[F, ValidatedNec[A, ?], B](F.map(value)(_.toValidatedNec))
}

object EitherT extends EitherTInstances {
Expand Down
37 changes: 36 additions & 1 deletion core/src/main/scala/cats/data/Validated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable {
}
}

object Validated extends ValidatedInstances with ValidatedFunctions{
object Validated extends ValidatedInstances with ValidatedFunctions with ValidatedFunctionsBinCompat0 {
final case class Valid[+A](a: A) extends Validated[Nothing, A]
final case class Invalid[+E](e: E) extends Validated[E, Nothing]

Expand Down Expand Up @@ -602,3 +602,38 @@ private[data] trait ValidatedFunctions {
final def condNel[A, B](test: Boolean, b: => B, a: => A): ValidatedNel[A, B] =
if (test) validNel(b) else invalidNel(a)
}

private[data] trait ValidatedFunctionsBinCompat0 {


/**
* Converts a `B` to a `ValidatedNec[A, B]`.
*
* For example:
* {{{
* scala> Validated.validNec[IllegalArgumentException, String]("Hello world")
* res0: ValidatedNec[IllegalArgumentException, String] = Valid(Hello world)
* }}}
*/
def validNec[A, B](b: B): ValidatedNec[A, B] = Validated.Valid(b)



/**
* Converts an `A` to a `ValidatedNec[A, B]`.
*
* For example:
* {{{
* scala> Validated.invalidNec[IllegalArgumentException, String](new IllegalArgumentException("Argument is nonzero"))
* res0: ValidatedNec[IllegalArgumentException, String] = Invalid(Chain(java.lang.IllegalArgumentException: Argument is nonzero))
* }}}
*/
def invalidNec[A, B](a: A): ValidatedNec[A, B] = Validated.Invalid(NonEmptyChain.one(a))

/**
* If the condition is satisfied, return the given `B` as valid NEC,
* otherwise return the given `A` as invalid NEC.
*/
final def condNec[A, B](test: Boolean, b: => B, a: => A): ValidatedNec[A, B] =
if (test) validNec(b) else invalidNec(a)
}
3 changes: 3 additions & 0 deletions core/src/main/scala/cats/data/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ package object data {
type ValidatedNel[+E, +A] = Validated[NonEmptyList[E], A]
type IorNel[+B, +A] = Ior[NonEmptyList[B], A]
type EitherNel[+E, +A] = Either[NonEmptyList[E], A]
type ValidatedNec[+E, +A] = Validated[NonEmptyChain[E], A]
type IorNec[+B, +A] = Ior[NonEmptyChain[B], A]
type EitherNec[+E, +A] = Either[NonEmptyChain[E], A]

def NonEmptyStream[A](head: A, tail: Stream[A] = Stream.empty): NonEmptyStream[A] =
OneAnd(head, tail)
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,6 @@ trait AllSyntaxBinCompat1

trait AllSyntaxBinCompat2
extends ParallelTraverseSyntax
with EitherSyntaxBinCompat0
with ListSyntaxBinCompat0
with ValidatedSyntaxBincompat0
46 changes: 45 additions & 1 deletion core/src/main/scala/cats/syntax/either.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package cats
package syntax

import cats.data.{EitherT, Ior, NonEmptyList, Validated, ValidatedNel}
import cats.data._

import scala.reflect.ClassTag
import scala.util.{Failure, Success, Try}
import EitherSyntax._
Expand Down Expand Up @@ -374,6 +375,49 @@ final class EitherIdOps[A](val obj: A) extends AnyVal {

}

trait EitherSyntaxBinCompat0 {
implicit final def catsSyntaxEitherBinCompat0[A, B](eab: Either[A, B]): EitherOpsBinCompat0[A, B] =
new EitherOpsBinCompat0(eab)

implicit final def catsSyntaxEitherIdBinCompat0[A](a: A): EitherIdOpsBinCompat0[A] =
new EitherIdOpsBinCompat0(a)
}

final class EitherIdOpsBinCompat0[A](val value: A) extends AnyVal {
/**
* Wrap a value to a left EitherNec
*
* For example:
* {{{
* scala> import cats.implicits._, cats.data.NonEmptyChain
* scala> "Err".leftNec[Int]
* res0: Either[NonEmptyChain[String], Int] = Left(Chain(Err))
* }}}
*/
def leftNec[B]: Either[NonEmptyChain[A], B] = Left(NonEmptyChain.one(value))

/**
* Wrap a value to a right EitherNec
*
* For example:
* {{{
* scala> import cats.implicits._, cats.data.NonEmptyChain
* scala> 1.rightNec[String]
* res0: Either[NonEmptyChain[String], Int] = Right(1)
* }}}
*/
def rightNec[B]: Either[NonEmptyChain[B], A] = Right(value)
}

final class EitherOpsBinCompat0[A, B](val value: Either[A, B]) extends AnyVal {
/** Returns a [[cats.data.ValidatedNec]] representation of this disjunction with the `Left` value
* as a single element on the `Invalid` side of the [[cats.data.NonEmptyList]]. */
def toValidatedNec: ValidatedNec[A, B] = value match {
case Left(a) => Validated.invalidNec(a)
case Right(b) => Validated.valid(b)
}
}

/** Convenience methods to use `Either` syntax inside `Either` syntax definitions. */
private[cats] object EitherUtil {
def leftCast[A, B, C](right: Right[A, B]): Either[C, B] =
Expand Down
29 changes: 28 additions & 1 deletion core/src/main/scala/cats/syntax/list.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package syntax

import scala.collection.immutable.SortedMap
import cats.data.NonEmptyList
import cats.data.{NonEmptyChain, NonEmptyList}

trait ListSyntax {
implicit final def catsSyntaxList[A](la: List[A]): ListOps[A] = new ListOps(la)
Expand Down Expand Up @@ -49,3 +49,30 @@ final class ListOps[A](val la: List[A]) extends AnyVal {
toNel.fold(SortedMap.empty[B, NonEmptyList[A]])(_.groupBy(f))
}
}

trait ListSyntaxBinCompat0 {
implicit final def catsSyntaxListBinCompat0[A](la: List[A]): ListOpsBinCompat0[A] = new ListOpsBinCompat0(la)
}

final class ListOpsBinCompat0[A](val la: List[A]) extends AnyVal {

/**
* Groups elements inside this `List` according to the `Order` of the keys
* produced by the given mapping function.
*
* {{{
* scala> import cats.data.NonEmptyChain
* scala> import scala.collection.immutable.SortedMap
* scala> import cats.implicits._
*
* scala> val list = List(12, -2, 3, -5)
*
* scala> list.groupByNec(_ >= 0)
* res0: SortedMap[Boolean, NonEmptyChain[Int]] = Map(false -> Chain(-2, -5), true -> Chain(12, 3))
* }}}
*/
def groupByNec[B](f: A => B)(implicit B: Order[B]): SortedMap[B, NonEmptyChain[A]] = {
implicit val ordering = B.toOrdering
NonEmptyChain.fromSeq(la).fold(SortedMap.empty[B, NonEmptyChain[A]])(_.groupBy(f).toSortedMap)
}
}
6 changes: 3 additions & 3 deletions core/src/main/scala/cats/syntax/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ package object syntax {
object contravariant extends ContravariantSyntax
object contravariantSemigroupal extends ContravariantSemigroupalSyntax
object contravariantMonoidal extends ContravariantMonoidalSyntax
object either extends EitherSyntax
object either extends EitherSyntax with EitherSyntaxBinCompat0
object eq extends EqSyntax
object flatMap extends FlatMapSyntax
object foldable extends FoldableSyntax
object functor extends FunctorSyntax
object group extends GroupSyntax
object invariant extends InvariantSyntax
object ior extends IorSyntax
object list extends ListSyntax
object list extends ListSyntax with ListSyntaxBinCompat0
object monad extends MonadSyntax
object monadError extends MonadErrorSyntax
object monoid extends MonoidSyntax
Expand All @@ -51,7 +51,7 @@ package object syntax {
object traverse extends TraverseSyntax
object nonEmptyTraverse extends NonEmptyTraverseSyntax
object unorderedTraverse extends UnorderedTraverseSyntax
object validated extends ValidatedSyntax with ValidatedExtensionSyntax
object validated extends ValidatedSyntax with ValidatedExtensionSyntax with ValidatedSyntaxBincompat0
object vector extends VectorSyntax
object writer extends WriterSyntax
object set extends SetSyntax
Expand Down
33 changes: 32 additions & 1 deletion core/src/main/scala/cats/syntax/validated.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package syntax

import cats.data.{ Validated, ValidatedNel }
import cats.data.{Validated, ValidatedNec, ValidatedNel}

trait ValidatedSyntax {
implicit final def catsSyntaxValidatedId[A](a: A): ValidatedIdSyntax[A] = new ValidatedIdSyntax(a)
Expand All @@ -23,3 +23,34 @@ final class ValidatedExtension[E, A](val self: Validated[E, A]) extends AnyVal {
def liftTo[F[_]](implicit F: ApplicativeError[F, E]): F[A] =
new ApplicativeErrorExtensionOps(F).fromValidated(self)
}

trait ValidatedSyntaxBincompat0 {
implicit final def catsSyntaxValidatedIdBinCompat0[A](a: A): ValidatedIdOpsBinCompat0[A] =
new ValidatedIdOpsBinCompat0(a)
}

final class ValidatedIdOpsBinCompat0[A](val a: A) extends AnyVal {
/**
* Wrap a value to a valid ValidatedNec
*
* For example:
* {{{
* scala> import cats.implicits._, cats.data._
* scala> 1.validNec[String]
* res0: Validated[NonEmptyChain[String], Int] = Valid(1)
* }}}
*/
def validNec[B]: ValidatedNec[B, A] = Validated.Valid(a)

/**
* Wrap a value to an invalid ValidatedNec
*
* For example:
* {{{
* scala> import cats.implicits._, cats.data._
* scala> "Err".invalidNec[Int]
* res0: Validated[NonEmptyChain[String], Int] = Invalid(Chain(Err))
* }}}
*/
def invalidNec[B]: ValidatedNec[A, B] = Validated.invalidNec(a)
}
1 change: 1 addition & 0 deletions tests/src/test/scala/cats/tests/EitherSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ class EitherSuite extends CatsSuite {
x.isLeft should === (x.toList.isEmpty)
x.isLeft should === (x.toValidated.isInvalid)
x.isLeft should === (x.toValidatedNel.isInvalid)
x.isLeft should === (x.toValidatedNec.isInvalid)
Option(x.isLeft) should === (x.toEitherT[Option].isLeft)
}
}
Expand Down
12 changes: 12 additions & 0 deletions tests/src/test/scala/cats/tests/EitherTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ class EitherTSuite extends CatsSuite {
}
}

test("toValidatedNec") {
forAll { (eithert: EitherT[List, String, Int]) =>
eithert.toValidatedNec.map(_.toEither.leftMap(_.head)) should === (eithert.value)
}
}

test("toNested") {
forAll { (eithert: EitherT[List, String, Int]) =>
eithert.toNested.value should === (eithert.value)
Expand All @@ -159,6 +165,12 @@ class EitherTSuite extends CatsSuite {
}
}

test("toNestedValidatedNec") {
forAll { (eithert: EitherT[List, String, Int]) =>
eithert.toNestedValidatedNec.value should === (eithert.value.map(_.toValidatedNec))
}
}

test("withValidated") {
forAll { (eithert: EitherT[List, String, Int], f: String => Char, g: Int => Double) =>
eithert.withValidated(_.bimap(f, g)) should === (eithert.bimap(f, g))
Expand Down
9 changes: 8 additions & 1 deletion tests/src/test/scala/cats/tests/SyntaxSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package tests
import scala.collection.immutable.SortedSet
import scala.collection.immutable.SortedMap
import cats.arrow.Compose
import cats.data.{Binested, Nested, NonEmptyList, NonEmptySet}
import cats.data.{Binested, Nested, NonEmptyChain, NonEmptyList, NonEmptySet}
import cats.instances.AllInstances
import cats.syntax.{AllSyntax, AllSyntaxBinCompat}

Expand Down Expand Up @@ -377,5 +377,12 @@ object SyntaxSuite extends AllSyntaxBinCompat with AllInstances with AllSyntax {
val grouped: SortedMap[B, NonEmptyList[A]] = list.groupByNel(f)
}

def testNonEmptyChain[A, B: Order] : Unit = {
val f = mock[A => B]
val list = mock[List[A]]

val grouped: SortedMap[B, NonEmptyChain[A]] = list.groupByNec(f)
}

}

6 changes: 6 additions & 0 deletions tests/src/test/scala/cats/tests/ValidatedSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,12 @@ class ValidatedSuite extends CatsSuite {
}
}

test("condNec consistent with Either.cond + toValidatedNec") {
forAll { (cond: Boolean, s: String, i: Int) =>
Validated.condNec(cond, s, i) should === (Either.cond(cond, s, i).toValidatedNec)
}
}

test("liftTo consistent with direct to Option") {
forAll { (v: Validated[Unit, Int]) =>
v.liftTo[Option] shouldBe v.toOption
Expand Down

0 comments on commit 380f721

Please sign in to comment.