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

Ior syntax #1540

Merged
merged 19 commits into from
Apr 4, 2017
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

final def toOption: Option[B] = right
final def toList: List[B] = right.toList

Expand Down Expand Up @@ -198,6 +201,9 @@ 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 rightNel[A, B](b: B): IorNel[A, B] = right(NonEmptyList.of(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] = Ior.Both(NonEmptyList.of(a), NonEmptyList.of(b))

/**
* Create an `Ior` from two Options if at least one of them is defined.
Expand Down
10 changes: 10 additions & 0 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


/**
* Convert this value to a single element List if it is Valid,
* otherwise return an empty List
Expand Down Expand Up @@ -405,4 +410,9 @@ private[data] trait ValidatedFunctions {
* 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 an `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], NonEmptyList[A]]
Copy link
Contributor Author

@leandrob13 leandrob13 Feb 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about this definition now. Maybe it should be similar to ValidatedNel

type IorNel[+B, +A] = Ior[NonEmptyList[B], A]

I decided to define it with a NonEmptyList on the right also because I saw some utility on the combine case (or use of Ior's append function to be exact which can be accessed through semigroup syntax function |+|). But when comparing the combine for Either and Validated I think I got it wrong. Ior's flatmap does accumulate errors only if the case is Ior.Both, so it seems appropiate to leave it similar to ValidatedNel's type definition. I would love some feedback on this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I cannot see a use-case for IorNel as it was before; did you have one in mind? That might help to decide.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@edmundnoble I am going to change it to type IorNel[+B, +A] = Ior[NonEmptyList[B], A] to keep consistency with ValidatedNel. I do have a use case for my original proposal but that is when you get to combine Iors with the |+| operator. My main argument to change it is that in the flatMap you get tu accumulate errors on the left side of Both so it is the natural usage of it.


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 MonadCombineSyntax
with MonadErrorSyntax
Expand Down
158 changes: 158 additions & 0 deletions core/src/main/scala/cats/syntax/ior.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package cats.syntax

import cats.data.{Ior, IorNel}

trait IorSyntax {
implicit def catsSyntaxIorId[A](a: A): IorIdOps[A] = new IorIdOps(a)
implicit def catsSyntaxListIorNel[A, B](list: List[IorNel[A, B]]): IorNelListOps[A, B] =
new IorNelListOps(list)
}

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)
Copy link
Member

@ChristopherDavenport ChristopherDavenport Feb 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context in Either instances uses asRight so perhaps asRightIor. Depends on what sort of syntax maintainers would like to see but asRightIor makes more sense to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only that way because of the Either.right projection ambiguity. The lack of such functions on Ior suggests to me that this way would be nicest.


/**
* 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)

/**
* Wrap a value in the right side of `Ior.Both`.
*
* Example:
* {{{
* scala> import cats.data.Ior
* scala> import cats.implicits._
*
* scala> "hello".putRightIor("error")
* res0: Ior[String, String] = Both(error,hello)
* }}}
*/
def putRightIor[B](left: B): Ior[B, A] = Ior.both(left, a)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about the necessity of these syntax:
putRightIor putLeftIor rightIorNel leftIorNel putRightIorNel putLeftIorNel
I am just not sure if the convenience of these methods overrides the cost of having them on everything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kailuowang in this case, I wanted to offer in the syntax the put functions that helped build Both type just for convenience. I really found useful the invalidNel syntax of validated so I wanted to be able to use something similar when I was working with Ior, that is why I added rightIorNel-leftIorNel. This means that you also object to Ior.rightNel-Ior.leftNel functions on the companion object?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right, rightIorNel leftIorNel look fine to me now.


/**
* Wrap a value in the left side of `Ior.Both`.
*
* Example:
* {{{
* scala> import cats.data.Ior
* scala> import cats.implicits._
*
* scala> "error".putLeftIor("hello")
* res0: Ior[String, String] = Both(error,hello)
* }}}
*/
def putLeftIor[B](right: B): Ior[A, B] = Ior.both(a, right)

/**
* Wrap a value in a NonEmptyList in `Ior.Right`.
*
* Example:
* {{{
* scala> import cats.data.IorNel
* scala> import cats.implicits._
*
* scala> "hello".rightIorNel[String]
* res0: IorNel[String, String] = Right(NonEmptyList(hello))
* }}}
*/
def rightIorNel[B]: IorNel[B, A] = Ior.rightNel(a)

/**
* Wrap a value in a NonEmptyList in `Ior.Left`.
*
* Example:
* {{{
* scala> import cats.data.IorNel
* scala> import cats.implicits._
*
* scala> "error".leftIorNel[String]
* res0: IorNel[String, String] = Left(NonEmptyList(error))
* }}}
*/
def leftIorNel[B]: IorNel[A, B] = Ior.leftNel(a)

/**
* Wrap a value in a NonEmptyList in the right side of `Ior.Both`.
*
* Example:
* {{{
* scala> import cats.data.IorNel
* scala> import cats.implicits._
*
* scala> "hello".putRightIorNel[String]("error")
* res0: IorNel[String, String] = Both(NonEmptyList(error),NonEmptyList(hello))
* }}}
*/
def putRightIorNel[B](left: B): IorNel[B, A] = Ior.bothNel(left, a)

/**
* Wrap a value in a NonEmptyList in the left side of `Ior.Both`.
*
* Example:
* {{{
* scala> import cats.data.IorNel
* scala> import cats.implicits._
*
* scala> "I got it wrong".putLeftIorNel[String]("hello")
* res0: IorNel[String, String] = Both(NonEmptyList(I got it wrong),NonEmptyList(hello))
* }}}
*/
def putLeftIorNel[B](right: B): IorNel[A, B] = Ior.bothNel(a, right)
}


final class IorNelListOps[A, B](val list: List[IorNel[A, B]]) extends AnyVal {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this can be further abstracted. I am also not sure having syntax for such a specific type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kailuowang you mean that those function can be added to the Ior datatype?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just feel this thing is too specific,
F[G[A, B]] => G[H[A], H[B]]. if F is a Functor and Reducable I feel like that want to see this function on a type class or companion obect

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I am over thinking, maybe being able to reduce a list of Iors conveniently has good value. Generally speaking, I feel a good part of your PR can get easy consensus and this is a place I am less sure.


/**
* Returns single combined IorNel by reducing a list of IorNel
*
* Example:
* {{{
* scala> import cats.data.IorNel
* scala> import cats.implicits._
*
* scala> List("hello".rightIorNel[String], "error".leftIorNel[String]).reduceToIorNel
* res0: IorNel[String, String] = Both(NonEmptyList(error),NonEmptyList(hello))
* }}}
*/
def reduceToIorNel: IorNel[A, B] = list reduce (_ append _)

/**
* Returns an Option of a single combined IorNel by reducing a list of IorNel
*
* Example:
* {{{
* scala> import cats.data.IorNel
* scala> import cats.implicits._
*
* scala> List("hello".rightIorNel[String], "error".leftIorNel[String]).reduceToOptionIorNel
* res0: Option[IorNel[String, String]] = Some(Both(NonEmptyList(error),NonEmptyList(hello)))
*
* scala> List.empty[IorNel[String, String]].reduceToOptionIorNel
* res1: Option[IorNel[String, String]] = None
* }}}
*/
def reduceToOptionIorNel: Option[IorNel[A, B]] = list reduceOption (_ append _)
}
81 changes: 80 additions & 1 deletion core/src/main/scala/cats/syntax/list.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,91 @@
package cats
package syntax

import cats.data.NonEmptyList
import cats.data.Validated.{Invalid, Valid}
import cats.data.{Ior, IorNel, NonEmptyList, ValidatedNel}

trait ListSyntax {
implicit def catsSyntaxList[A](la: List[A]): ListOps[A] = new ListOps(la)
}

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)

/**
* Returns a IorNel from a List
*
* Example:
* {{{
* scala> import cats.data.IorNel
* scala> import cats.implicits._
*
* scala> val result1: List[Int] = List(1, 2)
* scala> result1.toRightIorNel("error!")
* res0: IorNel[String, Int] = Right(NonEmptyList(1, 2))
*
* scala> val result2: List[Int] = List.empty[Int]
* scala> result2.toRightIorNel("error!")
* res1: IorNel[String, Int] = Left(NonEmptyList(error!))
* }}}
*/
def toRightIorNel[B](ifEmpty: => B): IorNel[B, A] =
toNel.fold[IorNel[B, A]](Ior.leftNel(ifEmpty))(Ior.right)

/**
* Returns a IorNel from a List
*
* Example:
* {{{
* scala> import cats.data.IorNel
* scala> import cats.implicits._
*
* scala> val result1: List[String] = List("error 1", "error 2")
* scala> result1.toLeftIorNel(1)
* res0: IorNel[String, Int] = Left(NonEmptyList(error 1, error 2))
*
* scala> val result2: List[String] = List.empty[String]
* scala> result2.toLeftIorNel(1)
* res1: IorNel[String, Int] = Right(NonEmptyList(1))
* }}}
*/
def toLeftIorNel[B](ifEmpty: => B): IorNel[A, B] =
toNel.fold[IorNel[A, B]](Ior.rightNel(ifEmpty))(Ior.left)

/**
* Returns a ValidatedNel from a List
*
* Example:
* {{{
* scala> import cats.data.ValidatedNel
* scala> import cats.implicits._
*
* scala> val result1: List[String] = List("error 1", "error 2")
* scala> result1.toValidatedNel(1)
* res0: ValidatedNel[String, Int] = Invalid(NonEmptyList(error 1, error 2))
*
* scala> val result2: List[String] = List.empty[String]
* scala> result2.toValidatedNel(1)
* res1: ValidatedNel[String, Int] = Valid(1)
* }}}
*/
def toValidatedNel[B](ifEmpty: => B): ValidatedNel[A, B] =
toNel.fold[ValidatedNel[A, B]](Valid(ifEmpty))(Invalid(_))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

}
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 monadCombine extends MonadCombineSyntax
object monadError extends MonadErrorSyntax
Expand Down
Loading