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

Ior syntax #1540

merged 19 commits into from
Apr 4, 2017

Conversation

leandrob13
Copy link
Contributor

@leandrob13 leandrob13 commented Feb 19, 2017

Added syntax for Ior like mentioned on issue #1539

This pull request includes ior syntax support for:

  • Validated
  • Option
  • Ior

@codecov-io
Copy link

codecov-io commented Feb 19, 2017

Codecov Report

Merging #1540 into master will increase coverage by 0.02%.
The diff coverage is 100%.

@@            Coverage Diff            @@
##           master   #1540      +/-   ##
=========================================
+ Coverage   92.48%   92.5%   +0.02%     
=========================================
  Files         246     247       +1     
  Lines        3885    3896      +11     
  Branches      133     129       -4     
=========================================
+ Hits         3593    3604      +11     
  Misses        292     292
Impacted Files Coverage Δ
core/src/main/scala/cats/syntax/list.scala 100% <ø> (ø)
core/src/main/scala/cats/data/package.scala 83.33% <ø> (ø)
core/src/main/scala/cats/syntax/ior.scala 100% <100%> (ø)
core/src/main/scala/cats/syntax/option.scala 100% <100%> (ø)
core/src/main/scala/cats/data/Ior.scala 98.94% <100%> (+0.03%)
core/src/main/scala/cats/data/Validated.scala 100% <100%> (ø)
core/src/main/scala/cats/data/Kleisli.scala 92.59% <0%> (+0.09%)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ecdd27f...ef82db6. Read the comment docs.

Copy link
Member

@ChristopherDavenport ChristopherDavenport left a comment

Choose a reason for hiding this comment

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

In general I like the idea. I don't see any glaringly obvious problems.

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

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

Choose a reason for hiding this comment

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

What is s used for here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ChristopherDavenport That just slipped, I will remove it. Thanks!

* 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.

@@ -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.

@@ -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.

👍

@@ -76,6 +76,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.

👍

* res0: Ior[String, String] = Right(hello)
* }}}
*/
def rightIor[B]: Ior[B, A] = Ior.right(a)
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.

@@ -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

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.

* 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.

}


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.

* res1: IorNel[String, Int] = Right(1)
* }}}
*/
def toIorNel[B](ifEmpty: => B): IorNel[A, B] =
Copy link
Contributor

Choose a reason for hiding this comment

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

The intention of this isn't very clear. I mean if you read a code result1.toIorNel(1), it's not obvious that the result1 is the error list while 1 is the valid result. Users will have to read the code here to understand the intention. I am not sure about such syntax addition.

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 are right, I didn't make it clear. This is motivated due to a use case I find frequently. If there is some validations made and were returned as a List, meaning that if it is empty everything went well and if it is not then there are errors present, I had to do something like this:

errorList match {
  case Nil => Valid("this is valid")
  case err :: errs => Invalid(NoneEmptyList(err, errs))
}

I found convenient to have a syntax that helped me with this for ValidatedNel and IorNel.

I do agree with you that if there is not consensus on this I can roll it back and maybe try it again on another pull request when I can make an example illustrating the use of what I proposed. I'll wait for your thoughts on this.

* }}}
*/
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.

@kailuowang
Copy link
Contributor

thanks very much @leandrob13, I like most of the PR, there are some syntax that I am not sure about. But I am more a minimalist when it comes to API design. So if other people see values in those syntax that I am not sure about I can be persuaded. Or if you want to separate those to another PR I'll gladly give my thumbs up on this one.

@leandrob13
Copy link
Contributor Author

@kailuowang I agree with you, my specific comments are above. Waiting for your feedback to decide what to do next.

Copy link
Contributor

@kailuowang kailuowang left a comment

Choose a reason for hiding this comment

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

Thanks very much! 👍

}


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 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

}


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.

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.

* 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 think you are right, rightIorNel leftIorNel look fine to me now.

@leandrob13
Copy link
Contributor Author

@kailuowang Thanks! I will follow your advice and prepare another PR to illustrate better the proposals I made. I was actually thinking of a reduceable for the list of iors reduce proposal and thanks to your hint I might work on that too.

@peterneyens
Copy link
Collaborator

Merging with two 👍

@peterneyens peterneyens merged commit e77bb99 into typelevel:master Apr 4, 2017
@kailuowang kailuowang modified the milestone: 1.0.0-MF Apr 26, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants