Skip to content

Commit

Permalink
Add docs for Ior (#1822)
Browse files Browse the repository at this point in the history
* Add Ior docs

* Add menu entry

* Replace Xor reference

* Switch to Nel#one

* Ior.leftNel

* Add note to IorNel
  • Loading branch information
LukaJCB authored and peterneyens committed Sep 20, 2017
1 parent 5a3d1eb commit de7b7e1
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/src/main/resources/microsite/data/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ options:
url: datatypes/id.html
menu_type: data

- title: Ior
url: datatypes/ior.html
menu_type: data

- title: Kleisli
url: datatypes/kleisli.html
menu_type: data
Expand Down
126 changes: 126 additions & 0 deletions docs/src/main/tut/datatypes/ior.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
layout: docs
title: "Ior"
section: "data"
source: "core/src/main/scala/cats/data/Ior.scala"
scaladoc: "#cats.data.Ior"
---
# Ior

`Ior` represents an inclusive-or relationship between two data types.
This makes it very similar to the [`Either`](either.html) data type, which represents an "exclusive-or" relationship.
What this means, is that an `Ior[A, B]` (also written as `A Ior B`) can contain either an `A`, a `B`, or both an `A` and `B`.
Another similarity to `Either` is that `Ior` is right-biased,
which means that the `map` and `flatMap` functions will work on the right side of the `Ior`, in our case the `B` value.
You can see this in the function signature of `map`:

```scala
def map[B, C](fa: A Ior B)(f: B => C): A Ior C
```

We can create `Ior` values using `Ior.left`, `Ior.right` and `Ior.both`:

```tut
import cats.data._
val right = Ior.right[String, Int](3)
val left = Ior.left[String, Int]("Error")
val both = Ior.both("Warning", 3)
```

Cats also offers syntax enrichment for `Ior`. The `leftIor` and `rightIor` functions can be imported from `cats.syntax.ior._`:

```tut
import cats.syntax.ior._
val right = 3.rightIor
val left = "Error".leftIor
```


When we look at the `Monad` or `Applicative` instances of `Ior`, we can see that they actually requires a `Semigroup` instance on the left side.
This is because `Ior` will actually accumulate failures on the left side, very similar to how the [`Validated`](validated.html) data type does.
This means we can accumulate data on the left side while also being able to short-circuit upon the first right-side-only value.
For example, sometimes, we might want to accumulate warnings together with a valid result and only halt the computation on a "hard error"
Here's an example of how we might be able to do that:

```tut:silent
import cats.implicits._
import cats.data.{ NonEmptyList => Nel }
type Failures = Nel[String]
case class Username(value: String) extends AnyVal
case class Password(value: String) extends AnyVal
case class User(name: Username, pw: Password)
def validateUsername(u: String): Failures Ior Username = {
if (u.isEmpty)
Nel.one("Can't be empty").leftIor
else if (u.contains("."))
Ior.both(Nel.one("Dot in name is deprecated"), Username(u))
else
Username(u).rightIor
}
def validatePassword(p: String): Failures Ior Password = {
if (p.length < 8)
Nel.one("Password too short").leftIor
else if (p.length < 10)
Ior.both(Nel.one("Password should be longer"), Password(p))
else
Password(p).rightIor
}
def validateUser(name: String, password: String): Failures Ior User =
(validateUsername(name), validatePassword(password)).mapN(User)
```

Now we're able to validate user data and also accumulate non-fatal warnings:

```tut
validateUser("John", "password12")
validateUser("john.doe", "password")
validateUser("jane", "short")
```

To extract the values, we can use the `fold` method, which expects a function for each case the `Ior` can represent:

```tut
validateUser("john.doe", "password").fold(
errorNel => s"Error: ${errorNel.head}",
user => s"Success: $user",
(warnings, user) => s"Warning: ${user.name.value}; The following warnings occurred: ${warnings.show}"
)
```
Similar to [Validated](validated.html), there is also a type alias for using a `NonEmptyList` on the left side.

```tut:silent
type IorNel[B, A] = Ior[NonEmptyList[B], A]
```


```tut
val left: IorNel[String, Int] = Ior.leftNel("Error")
```


We can also convert our `Ior` to `Either`, `Validated` or `Option`.
All of these conversions will discard the left side value if both are available:

```tut
Ior.both("Warning", 42).toEither
```

0 comments on commit de7b7e1

Please sign in to comment.