Skip to content

Commit

Permalink
Ior syntax (#1540)
Browse files Browse the repository at this point in the history
* Added Ior syntax

* Removed examples on ior syntax comments

* Removed toValidatedNel functions

* Added syntax to list for IorNel and ValidatedNel

* Added test for toValidated function

* Added newline to end of ior.scala file

* Added toIor functions to option syntax

* Added fromIor to ValidatedFunctions and toIor function to Validated datatype

* Corrected error on duplicated implicit defs on syntax.ior.scala

* Added tests for new functions in Validated and Ior

* Added examples to list syntax functions

* Corrected errors on list syntax examples

* Added corrected examples to ior syntax

* Corrected names for IorNel syntax functions

* Redifined the IorNel type alias

* Fixed test descriptions on IorTests

* Removed some functions from ior syntax object, deleted functions from list syntax object
  • Loading branch information
leandrob13 authored and peterneyens committed Apr 4, 2017
1 parent 388acd1 commit e77bb99
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 16 deletions.
9 changes: 7 additions & 2 deletions core/src/main/scala/cats/data/Ior.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package cats
package data

import cats.data.Validated.{Invalid, Valid}
import cats.functor.Bifunctor

import scala.annotation.tailrec

/** Represents a right-biased disjunction that is either an `A`, or a `B`, or both an `A` and a `B`.
Expand Down Expand Up @@ -47,6 +49,7 @@ sealed abstract class Ior[+A, +B] extends Product with Serializable {
final def unwrap: Either[Either[A, B], (A, B)] = fold(a => Left(Left(a)), b => Left(Right(b)), (a, b) => Right((a, b)))

final def toEither: Either[A, B] = fold(Left(_), Right(_), (_, b) => Right(b))
final def toValidated: Validated[A, B] = fold(Invalid(_), Valid(_), (_, b) => Valid(b))
final def toOption: Option[B] = right
final def toList: List[B] = right.toList

Expand Down Expand Up @@ -102,7 +105,7 @@ sealed abstract class Ior[+A, +B] extends Product with Serializable {
fold(identity, ev, (_, b) => ev(b))

// scalastyle:off cyclomatic.complexity
final def append[AA >: A, BB >: B](that: AA Ior BB)(implicit AA: Semigroup[AA], BB: Semigroup[BB]): AA Ior BB = this match {
final def combine[AA >: A, BB >: B](that: AA Ior BB)(implicit AA: Semigroup[AA], BB: Semigroup[BB]): AA Ior BB = this match {
case Ior.Left(a1) => that match {
case Ior.Left(a2) => Ior.Left(AA.combine(a1, a2))
case Ior.Right(b2) => Ior.Both(a1, b2)
Expand Down Expand Up @@ -150,7 +153,7 @@ private[data] sealed abstract class IorInstances extends IorInstances0 {
}

implicit def catsDataSemigroupForIor[A: Semigroup, B: Semigroup]: Semigroup[Ior[A, B]] = new Semigroup[Ior[A, B]] {
def combine(x: Ior[A, B], y: Ior[A, B]) = x.append(y)
def combine(x: Ior[A, B], y: Ior[A, B]) = x.combine(y)
}

implicit def catsDataMonadForIor[A: Semigroup]: Monad[A Ior ?] = new Monad[A Ior ?] {
Expand Down Expand Up @@ -198,6 +201,8 @@ private[data] sealed trait IorFunctions {
def left[A, B](a: A): A Ior B = Ior.Left(a)
def right[A, B](b: B): A Ior B = Ior.Right(b)
def both[A, B](a: A, b: B): A Ior B = Ior.Both(a, b)
def leftNel[A, B](a: A): IorNel[A, B] = left(NonEmptyList.of(a))
def bothNel[A, B](a: A, b: B): IorNel[A, B] = both(NonEmptyList.of(a), b)

/**
* Create an `Ior` from two Options if at least one of them is defined.
Expand Down
14 changes: 12 additions & 2 deletions core/src/main/scala/cats/data/Validated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable {
*/
def toOption: Option[A] = fold(_ => None, Some.apply)

/**
* Returns Valid values wrapped in Ior.Right, and None for Ior.Left values
*/
def toIor: Ior[E, A] = fold(Ior.left, Ior.right)

/**
* Convert this value to a single element List if it is Valid,
* otherwise return an empty List
Expand Down Expand Up @@ -430,13 +435,18 @@ private[data] trait ValidatedFunctions {
}

/**
* Converts an `Either[A, B]` to an `Validated[A, B]`.
* Converts an `Either[A, B]` to a `Validated[A, B]`.
*/
def fromEither[A, B](e: Either[A, B]): Validated[A, B] = e.fold(invalid, valid)

/**
* Converts an `Option[B]` to an `Validated[A, B]`, where the provided `ifNone` values is returned on
* Converts an `Option[B]` to a `Validated[A, B]`, where the provided `ifNone` values is returned on
* the invalid of the `Validated` when the specified `Option` is `None`.
*/
def fromOption[A, B](o: Option[B], ifNone: => A): Validated[A, B] = o.fold(invalid[A, B](ifNone))(valid)

/**
* Converts an `Ior[A, B]` to a `Validated[A, B]`.
*/
def fromIor[A, B](ior: Ior[A, B]): Validated[A, B] = ior.fold(invalid, valid, (_, b) => valid(b))
}
1 change: 1 addition & 0 deletions core/src/main/scala/cats/data/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cats
package object data {
type NonEmptyStream[A] = OneAnd[Stream, A]
type ValidatedNel[+E, +A] = Validated[NonEmptyList[E], A]
type IorNel[+B, +A] = Ior[NonEmptyList[B], A]

def NonEmptyStream[A](head: A, tail: Stream[A] = Stream.empty): NonEmptyStream[A] =
OneAnd(head, tail)
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ trait AllSyntax
with FunctorFilterSyntax
with GroupSyntax
with InvariantSyntax
with IorSyntax
with ListSyntax
with MonadSyntax
with MonadCombineSyntax
Expand Down
37 changes: 37 additions & 0 deletions core/src/main/scala/cats/syntax/ior.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cats.syntax

import cats.data.Ior

trait IorSyntax {
implicit def catsSyntaxIorId[A](a: A): IorIdOps[A] = new IorIdOps(a)
}

final class IorIdOps[A](val a: A) extends AnyVal {
/**
* Wrap a value in `Ior.Right`.
*
* Example:
* {{{
* scala> import cats.data.Ior
* scala> import cats.implicits._
*
* scala> "hello".rightIor[String]
* res0: Ior[String, String] = Right(hello)
* }}}
*/
def rightIor[B]: Ior[B, A] = Ior.right(a)

/**
* Wrap a value in `Ior.Left`.
*
* Example:
* {{{
* scala> import cats.data.Ior
* scala> import cats.implicits._
*
* scala> "error".leftIor[String]
* res0: Ior[String, String] = Left(error)
* }}}
*/
def leftIor[B]: Ior[A, B] = Ior.left(a)
}
18 changes: 18 additions & 0 deletions core/src/main/scala/cats/syntax/list.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ trait ListSyntax {
}

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

/**
* Returns an Option of NonEmptyList from a List
*
* Example:
* {{{
* scala> import cats.data.NonEmptyList
* scala> import cats.implicits._
*
* scala> val result1: List[Int] = List(1, 2)
* scala> result1.toNel
* res0: Option[NonEmptyList[Int]] = Some(NonEmptyList(1, 2))
*
* scala> val result2: List[Int] = List.empty[Int]
* scala> result2.toNel
* res1: Option[NonEmptyList[Int]] = None
* }}}
*/
def toNel: Option[NonEmptyList[A]] = NonEmptyList.fromList(la)
def groupByNel[B](f: A => B): Map[B, NonEmptyList[A]] =
toNel.fold(Map.empty[B, NonEmptyList[A]])(_.groupBy(f))
Expand Down
42 changes: 41 additions & 1 deletion core/src/main/scala/cats/syntax/option.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.{Ior, Validated, ValidatedNel}

trait OptionSyntax {
final def none[A]: Option[A] = Option.empty[A]
Expand Down Expand Up @@ -112,6 +112,46 @@ final class OptionOps[A](val oa: Option[A]) extends AnyVal {
*/
def toValidNel[B](b: => B): ValidatedNel[B, A] = oa.fold[ValidatedNel[B, A]](Validated.invalidNel(b))(Validated.Valid(_))

/**
* If the `Option` is a `Some`, return its value in a [[cats.data.Ior.Right]].
* If the `Option` is `None`, wrap the provided `B` value in a [[cats.data.Ior.Left]]
*
* Example:
* {{{
* scala> import cats.data.Ior
* scala> import cats.implicits._
*
* scala> val result1: Option[Int] = Some(3)
* scala> result1.toRightIor("error!")
* res0: Ior[String, Int] = Right(3)
*
* scala> val result2: Option[Int] = None
* scala> result2.toRightIor("error!")
* res1: Ior[String, Int] = Left(error!)
* }}}
*/
def toRightIor[B](b: => B): Ior[B, A] = oa.fold[Ior[B, A]](Ior.Left(b))(Ior.Right(_))

/**
* If the `Option` is a `Some`, return its value in a [[cats.data.Ior.Left]].
* If the `Option` is `None`, wrap the provided `B` value in a [[cats.data.Ior.Right]]
*
* Example:
* {{{
* scala> import cats.data.Ior
* scala> import cats.implicits._
*
* scala> val result1: Option[String] = Some("error!")
* scala> result1.toLeftIor(3)
* res0: Ior[String, Int] = Left(error!)
*
* scala> val result2: Option[String] = None
* scala> result2.toLeftIor(3)
* res1: Ior[String, Int] = Right(3)
* }}}
*/
def toLeftIor[B](b: => B): Ior[A, B] = oa.fold[Ior[A, B]](Ior.Right(b))(Ior.Left(_))

/**
* If the `Option` is a `Some`, return its value. If the `Option` is `None`,
* return the `empty` value for `Monoid[A]`.
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package object syntax {
object functorFilter extends FunctorFilterSyntax
object group extends GroupSyntax
object invariant extends InvariantSyntax
object ior extends IorSyntax
object list extends ListSyntax
object monad extends MonadSyntax
object monadCombine extends MonadCombineSyntax
Expand Down
30 changes: 24 additions & 6 deletions tests/src/test/scala/cats/tests/IorTests.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package cats
package tests

import cats.data.Ior
import cats.data.{Ior, NonEmptyList}
import cats.kernel.laws.GroupLaws
import cats.laws.discipline.{BifunctorTests, CartesianTests, MonadTests, SerializableTests, TraverseTests}
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.{BifunctorTests, CartesianTests, MonadTests, SerializableTests, TraverseTests}
import org.scalacheck.Arbitrary._

class IorTests extends CatsSuite {
Expand Down Expand Up @@ -153,15 +153,15 @@ class IorTests extends CatsSuite {
}
}

test("append left") {
test("combine left") {
forAll { (i: Int Ior String, j: Int Ior String) =>
i.append(j).left should === (i.left.map(_ + j.left.getOrElse(0)).orElse(j.left))
i.combine(j).left should === (i.left.map(_ + j.left.getOrElse(0)).orElse(j.left))
}
}

test("append right") {
test("combine right") {
forAll { (i: Int Ior String, j: Int Ior String) =>
i.append(j).right should === (i.right.map(_ + j.right.getOrElse("")).orElse(j.right))
i.combine(j).right should === (i.right.map(_ + j.right.getOrElse("")).orElse(j.right))
}
}

Expand Down Expand Up @@ -198,6 +198,24 @@ class IorTests extends CatsSuite {
}
}

test("toValidated consistent with right") {
forAll { (x: Int Ior String) =>
x.toValidated.toOption should === (x.right)
}
}

test("leftNel") {
forAll { (x: String) =>
Ior.leftNel(x).left should === (Some(NonEmptyList.of(x)))
}
}

test("bothNel") {
forAll { (x: Int, y: String) =>
Ior.bothNel(y, x).onlyBoth should === (Some((NonEmptyList.of(y), x)))
}
}

test("getOrElse consistent with Option getOrElse") {
forAll { (x: Int Ior String, default: String) =>
x.getOrElse(default) should === (x.toOption.getOrElse(default))
Expand Down
22 changes: 17 additions & 5 deletions tests/src/test/scala/cats/tests/ValidatedTests.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package cats
package tests

import cats.data.{EitherT, NonEmptyList, Validated, ValidatedNel}
import cats.data.Validated.{Valid, Invalid}
import cats.laws.discipline.{BitraverseTests, TraverseTests, ApplicativeErrorTests, SerializableTests, CartesianTests}
import cats.data._
import cats.data.Validated.{Invalid, Valid}
import cats.laws.discipline.{ApplicativeErrorTests, BitraverseTests, CartesianTests, SerializableTests, TraverseTests}
import org.scalacheck.Arbitrary._
import cats.laws.discipline.{SemigroupKTests}
import cats.laws.discipline.SemigroupKTests
import cats.laws.discipline.arbitrary._
import cats.kernel.laws.{OrderLaws, GroupLaws}
import cats.kernel.laws.{GroupLaws, OrderLaws}

import scala.util.Try

Expand Down Expand Up @@ -187,6 +187,18 @@ class ValidatedTests extends CatsSuite {
}
}

test("fromIor consistent with Ior.toValidated"){
forAll { (i: Ior[String, Int]) =>
Validated.fromIor(i) should === (i.toValidated)
}
}

test("toIor then fromEither is identity") {
forAll { (v: Validated[String, Int]) =>
Validated.fromIor(v.toIor) should === (v)
}
}

test("isValid after combine, iff both are valid") {
forAll { (lhs: Validated[Int, String], rhs: Validated[Int, String]) =>
lhs.combine(rhs).isValid should === (lhs.isValid && rhs.isValid)
Expand Down

0 comments on commit e77bb99

Please sign in to comment.