diff --git a/docs/datatypes.md b/docs/datatypes.md deleted file mode 100644 index b151b8ebe5..0000000000 --- a/docs/datatypes.md +++ /dev/null @@ -1,5 +0,0 @@ -{% laika.title = "Data Types" %} - -@:navigationTree { - entries = [ { target = "datatypes", depth = 2 } ] -} diff --git a/docs/datatypes/directory.conf b/docs/datatypes/directory.conf index b44c76219c..b7b01bd51c 100644 --- a/docs/datatypes/directory.conf +++ b/docs/datatypes/directory.conf @@ -2,24 +2,18 @@ laika.title = Data Types laika.navigationOrder = [ chain.md const.md - contt.md either.md - eithert.md eval.md freeapplicative.md freemonad.md functionk.md id.md ior.md - iort.md kleisli.md nel.md - nested.md oneand.md - optiont.md state.md statet.md validated.md writer.md - writert.md ] \ No newline at end of file diff --git a/docs/datatypes/writer.md b/docs/datatypes/writer.md index 7d1cb39f4d..c6b3bdf73b 100644 --- a/docs/datatypes/writer.md +++ b/docs/datatypes/writer.md @@ -125,9 +125,9 @@ type Writer[L, V] = WriterT[Id, L, V] So, all the [Operations](#operations) defined in the previous section are actually coming from the [WriterT -datatype](writert.md) +datatype](../monadtransformers/writert.md) -Most of the [`WriterT`](writert.md) functions require a +Most of the [`WriterT`](../monadtransformers/writert.md) functions require a [`Functor[F]`](../typeclasses/functor.md) or [`Monad[F]`](../typeclasses/monad.md) instance. However, Cats provides all the necessary instances for the diff --git a/docs/directory.conf b/docs/directory.conf index 7632d07504..da69be8691 100644 --- a/docs/directory.conf +++ b/docs/directory.conf @@ -1,7 +1,5 @@ laika.navigationOrder = [ index.md - typeclasses.md - datatypes.md algebra.md alleycats.md motivations.md @@ -17,4 +15,5 @@ laika.navigationOrder = [ typelevelEcosystem.md typeclasses datatypes + monadtransformers ] \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md index 8531131f7f..963ed7c799 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -68,7 +68,7 @@ It's really common to have a `List` of values with types like `Option`, `Either` ## Where is ListT? -There are monad transformers for various types, such as [OptionT](datatypes/optiont.md), so people often wonder why there isn't a `ListT`. For example, in the following example, people might reach for `ListT` to simplify making nested `map` and `exists` calls: +There are monad transformers for various types, such as [OptionT](monadtransformers/optiont.md), so people often wonder why there isn't a `ListT`. For example, in the following example, people might reach for `ListT` to simplify making nested `map` and `exists` calls: ```scala mdoc:reset:silent val l: Option[List[Int]] = Some(List(1, 2, 3, 4, 5)) diff --git a/docs/guidelines.md b/docs/guidelines.md index 0a731cfc76..cf7291eae1 100644 --- a/docs/guidelines.md +++ b/docs/guidelines.md @@ -109,7 +109,7 @@ abstract class KleisliInstance1 { We can introduce new type classes for the sake of adding laws that don't apply to the parent type class, e.g. `CommutativeSemigroup` and `CommutativeArrow`. -### Applicative instances for monad transformers +### Applicative instances for monad transformers We explicitly don't provide an instance of `Applicative` for e.g. `EitherT[F, String, *]` given an `Applicative[F]`. An attempt to construct one without a proper `Monad[F]` instance would be inconsistent in `ap` with the provided `Monad` instance diff --git a/docs/imports.md b/docs/imports.md index 866a9986e1..c712c25c9a 100644 --- a/docs/imports.md +++ b/docs/imports.md @@ -8,7 +8,7 @@ import cats.data._ import cats.syntax.all._ ``` -The `cats._` import brings in quite a few [type classes](typeclasses.md) (similar to interfaces) such as [Monad](typeclasses/monad.md), [Semigroup](typeclasses/semigroup.md), and [Foldable](typeclasses/foldable.md). Instead of the entire `cats` package, you can import only the types that you need, for example: +The `cats._` import brings in quite a few [type classes](typeclasses/index.md) (similar to interfaces) such as [Monad](typeclasses/monad.md), [Semigroup](typeclasses/semigroup.md), and [Foldable](typeclasses/foldable.md). Instead of the entire `cats` package, you can import only the types that you need, for example: ```scala mdoc:reset:silent import cats.Monad diff --git a/docs/index.md b/docs/index.md index a6f5e443d0..f2841ac585 100644 --- a/docs/index.md +++ b/docs/index.md @@ -65,7 +65,7 @@ We thankfully accept one-t -### Getting Started +## Getting Started Cats is available for [Scala.js](http://www.scala-js.org/) and [Scala Native](https://www.scala-native.org/), as well as the standard JVM runtime. @@ -130,7 +130,7 @@ into larger problems. If you are being harassed, please contact one of [us](#maintainers) immediately so that we can support you. -### Binary compatibility and versioning +## Binary Compatibility and Versioning After `1.0.0` release, we [decided](https://github.com/typelevel/cats/issues/1233) to use *MAJOR.MINOR.PATCH* [Semantic Versioning 2.0.0](http://semver.org/) @@ -157,11 +157,11 @@ Any binary breaking changes will require a *MAJOR* version bump, which we will b cautious about. We will also consider using `organization` and package name for major versioning in the future. But that decision is yet to be made. -### Adopters +## Adopters A (non-exhaustive) list of companies that use Cats in production is featured on the [Adopters page]. Don't see yours? [You can add it in a PR!](https://github.com/typelevel/cats/edit/main/ADOPTERS.md) And if you can, consider [supporting us](https://opencollective.com/typelevel). -### Maintainers +## Maintainers The current maintainers (people who can merge pull requests) are: @@ -194,7 +194,7 @@ wait for more). For typos, documentation improvements or minor build fix we relax this to a single sign-off. More detail in the [process document](https://github.com/typelevel/cats/blob/main/PROCESS.md). -### Copyright and License +## Copyright and License All code is available to you under the MIT license, available at http://opensource.org/licenses/mit-license.php and also in the diff --git a/docs/jump_start_guide.md b/docs/jump_start_guide.md index 52cdf09747..d72e0f1059 100644 --- a/docs/jump_start_guide.md +++ b/docs/jump_start_guide.md @@ -216,205 +216,6 @@ def computeOverValue3: Future[Option[Int]] = valueOpt.flatTraverse(compute) ``` and that solves our problem for good. - -# Monad Transformers - -## OptionT - -```scala mdoc:silent -import cats.data.OptionT -``` - -An instance of [`OptionT[F, A]`](datatypes/optiont.md) can be thought of as a wrapper over `F[Option[A]]` -which adds a couple of useful methods specific to nested types that aren't available in `F` or `Option` itself. -Most typically, your `F` will be `Future` (or sometimes slick's `DBIO`, but this requires having an implementation of Cats type classes like `Functor` or `Monad` for `DBIO`). -Wrappers such as `OptionT` are generally known as _monad transformers_. - -A quite common pattern is mapping the inner value stored inside an instance of `F[Option[A]]` to an instance of `F[Option[B]]` with a function of type `A => B`. -This can be done with rather verbose syntax like: -```scala mdoc:silent -lazy val resultFuture: Future[Option[Int]] = ??? - -def mappedResultFuture: Future[Option[String]] = resultFuture.map { maybeValue => - maybeValue.map { value => - // Do something with the value and return String - ??? - } -} -``` - -With the use of `OptionT`, this can be simplified as follows: - -```scala mdoc:silent -def mappedResultFuture2: OptionT[Future, String] = OptionT(resultFuture).map { value => - // Do something with the value and return String - ??? -} -``` - -The above `map` will return a value of type `OptionT[Future, String]`. - -To get the underlying `Future[Option[String]]` value, simply call `.value` on the `OptionT` instance. -It's also a viable solution to fully switch to `OptionT[Future, A]` in method parameter/return types and completely (or almost completely) ditch `Future[Option[A]]` in type declarations. - -There are several ways to construct an `OptionT` instance. -The method headers in the table below are slightly simplified: the type parameters and type classes required by each method are skipped. - -| Method | Takes | Returns | -| :---: | :---: | :---: | -| `OptionT.apply` or `OptionT(...)` | `F[Option[A]]` | `OptionT[F, A]` | -| `OptionT.fromOption` | `Option[A]` | `OptionT[F, A]` | -| `OptionT.liftF` | `F[A]` | `OptionT[F, A]` | -| `OptionT.pure` | `A` | `OptionT[F, A]` | - -In production code you'll most commonly use the `OptionT(...)` syntax in order to wrap an instance of `Future[Option[A]]` into `OptionT[F, A]`. -The other methods, in turn, prove useful to set up `OptionT`-typed dummy values in unit tests. - -We have already come across one of `OptionT`'s methods, namely `map`. -There are several other methods available and they mostly differ by the signature of the function they accept as the parameter. -As was the case with the previous table, the expected type classes are skipped. - -| Method | Takes | Returns -| :---: | :---: | :---: | -| `map[B]` | `A => B` | `OptionT[F, B]` | -| `subflatMap[B]` | `A => Option[B]` | `OptionT[F, B]` | -| `semiflatMap[B]` | `A => F[B]` | `OptionT[F, B]` | -| `flatMapF[B]` | `A => F[Option[B]]` | `OptionT[F, B]` | -| `flatMap[B]` | `A => OptionT[F, B]` | `OptionT[F, B]` | - -In practice, you're most likely to use `map` and `semiflatMap`. - -As is always the case with `flatMap` and `map`, you can use it not only explicitly, but also under the hood in `for` comprehensions, as in the example below: - -```scala mdoc:silent -class Money { /* ... */ } - -def findUserById(userId: Long): OptionT[Future, User] = { /* ... */ ??? } - -def findAccountById(accountId: Long): OptionT[Future, Account] = { /* ... */ ??? } - -def getReservedFundsForAccount(account: Account): OptionT[Future, Money] = { /* ... */ ??? } - -def getReservedFundsForUser(userId: Long): OptionT[Future, Money] = for { - user <- findUserById(userId) - account <- findAccountById(user.accountId) - funds <- getReservedFundsForAccount(account) -} yield funds -``` - -The `OptionT[Future, Money]` instance returned by `getReservedFundsForUser` will enclose a `None` value if any of the three composed methods returns an `OptionT` corresponding to `None`. -Otherwise, if the result of all three calls contains `Some`, the final outcome will also contain `Some`. - - -## EitherT - -```scala mdoc:silent -import cats.data.EitherT -``` - -[`EitherT[F, A, B]`](datatypes/eithert.md) is the monad transformer for `Either` — you can think of it as a wrapper over a `F[Either[A, B]]` value. - -Just as in the above section, I simplified the method headers, skipping type parameters or their context bounds and lower bounds. - -Let's have a quick look at how to create an `EitherT` instance: - -| Method | Takes | Returns | -| :---: | :---: | :---: | -| `EitherT.apply` or `EitherT(...)` | `F[Either[A, B]]` | `EitherT[F, A, B]` | -| `EitherT.fromEither` | `Either[A, B]` | `EitherT[F, A, B]` (wraps the provided `Either` value into `F`) | -| `EitherT.right` or `EitherT.liftF` | `F[B]` | `EitherT[F, A, B]` (wraps value inside `F[B]` into `Right`) | -| `EitherT.left` | `F[A]` | `EitherT[F, A, B]` (wraps value inside `F[B]` into `Left`) | -| `EitherT.pure` | `A` | `EitherT[F, A, B]` (wraps value into `Right` and then into `F`) | - -Another useful way to construct an `EitherT` instance is to use `OptionT`'s methods `toLeft` and `toRight`: - -```scala mdoc:silent -abstract class BaseException(message: String) extends Exception(message) - -case class UserNotFoundException(message: String) extends BaseException(message) - -def getUserById(userId: Int): Future[Option[User]] = { /* ... */ ??? } - -def ensureUserExists(userId: Int): EitherT[Future, BaseException, User] = { - OptionT(getUserById(userId)) - .toRight(left = UserNotFoundException(s"user not found, userId=$userId")) -} -``` - -`toRight` is pretty analogous to the method `Either.fromOption` mentioned before: just as `fromOption` built an `Either` from an `Option`, `toRight` creates an `EitherT` from an `OptionT`. -If the original `OptionT` stores `Some` value, it will be wrapped into `Right`; otherwise the value provided as the `left` parameter will be wrapped into a `Left`. -To provide the `left` value within the monad, there is corresponding `toRightF` method. - -`toLeft` is `toRight`'s counterpart which wraps the `Some` value into `Left` and transforms `None` into `Right` enclosing the provided `right` value. -This is less commonly used in practice, but can serve e.g. for enforcing uniqueness checks in code. -We return `Left` if the value has been found, and `Right` if it doesn't yet exist in the system. - -The methods available in `EitherT` are pretty similar to those we've seen in `OptionT`, but there are some notable differences. - -You might get into some confusion at first when it comes to e.g. `map`. -In the case of `OptionT`, it was pretty obvious what should be done: `map` should go over the `Option` enclosed within `Future`, and then map the enclosed `Option` itself. -This is slightly less obvious in case of `EitherT`: should it map over both `Left` and `Right` values, or only the `Right` value? - -The answer is that `EitherT` is _right-biased_, therefore plain `map` actually deals with the `Right` value. -This is unlike `Either` in the Scala standard library up to 2.11, which is in turn _unbiased_: there's no `map` available in `Either`, only for its left and right projections. - -Having said that, let's take a quick look at the right-biased methods that `EitherT` offers: - -| Method | Takes | Returns | -| :---: | --- | --- | -| `map[D]` | `B => D` | `EitherT[F, A, D]` | -| `subflatMap[D]` | `B => Either[A, D]` | `EitherT[F, A, D]` | -| `semiflatMap[D]` | `B => F[D]` | `EitherT[F, A, D]` | -| `flatMapF[D]` | `B => F[Either[A, D]]` | `EitherT[F, A, D]` | -| `flatMap[D]` | `B => EitherT[F, A, D]` | `EitherT[F, A, D]` | - -As a side note, there are also certain methods in `EitherT` (that you're likely to need at some point) which map over the `Left` value, like `leftMap`, or over both `Left` and `Right` values, like `fold` or `bimap`. - -`EitherT` is very useful for fail-fast chained verifications: - -```scala mdoc:silent - -case class Item(state: String) -class ItemOrder { /* ... */ } - -case class ItemNotFoundException(message: String) extends BaseException(message) -case class InvalidItemStateException(message: String) extends BaseException(message) - -def getItemById(itemId: Int): Future[Option[Item]] = { /* .. */ ??? } - -def ensureItemExists(itemId: Int): EitherT[Future, BaseException, Item] = { - OptionT(getItemById(itemId)) - .toRight(ItemNotFoundException(s"item not found, itemId = $itemId")) -} - -def ensureItemStateIs(actual: String, expected: String): EitherT[Future, BaseException, Unit] = { - // Returns a Unit value wrapped into Right and then into Future if condition is true, - // otherwise the provided exception wrapped into Left and then into Future. - EitherT.cond(actual == expected, (), InvalidItemStateException(s"actual=$actual, expected=$expected")) -} - -def placeOrderForItem(userId: Int, itemId: Int, count: Int): Future[ItemOrder] = { /* ... */ ??? } - -def buyItem(userId: Int, itemId: Int, count: Int): EitherT[Future, BaseException, ItemOrder] = { - for { - user <- ensureUserExists(userId) - item <- ensureItemExists(itemId) - _ <- ensureItemStateIs(item.state, "AVAILABLE_IN_STOCK") - // EitherT.liftF is necessary to make EitherT[Future, BaseException, ItemOrder] out of Future[ItemOrder] - placedOrder <- EitherT.liftF(placeOrderForItem(userId, itemId, count)) - } yield placedOrder -} -``` - -In the above example, we're running various checks against the item one by one. -If any of the checks fails, the resulting `EitherT` will contain a `Left` value. -Otherwise, if all of the checks yield a `Right` (of course we mean a `Right` wrapped into an `EitherT`), then the final outcome will also contain `Right`. -This is a fail-fast behavior: we're effectively stopping the `for` comprehension flow at the first `Left`-ish result. - -If you're instead looking for validation that accumulates the errors (e.g. when dealing with user-provided form data), `cats.data.Validated` may be a good choice. - - - # Common issues The Cats type class instances for standard library types are available in implicit scope and hence no longer have to be imported. If anything doesn't compile as expected, first make sure all the required Cats syntax implicits are in the scope — try importing ```cats.syntax.all._``` and see if the problem persists. The only exception is here is Cat's own ```Order``` and ```PartialOrder``` type classes which are available by importing ```cats.implicits._```. diff --git a/docs/datatypes/contt.md b/docs/monadtransformers/contt.md similarity index 99% rename from docs/datatypes/contt.md rename to docs/monadtransformers/contt.md index 503bd31f78..df6b24269c 100644 --- a/docs/datatypes/contt.md +++ b/docs/monadtransformers/contt.md @@ -30,7 +30,7 @@ def updateUser(persistToDatabase: User => Eval[UserUpdateResult]) ``` (Note: We will be using `Eval` throughout the examples on this page. If you are not -familiar with `Eval`, it's worth reading [the Eval documentation](eval.md) first.) +familiar with `Eval`, it's worth reading [the Eval documentation](../datatypes/eval.md) first.) Our `updateUser` function takes in an existing user and some updates to perform. It sanitises the inputs and updates the user model, but it delegates the diff --git a/docs/monadtransformers/directory.conf b/docs/monadtransformers/directory.conf new file mode 100644 index 0000000000..a4691c09f0 --- /dev/null +++ b/docs/monadtransformers/directory.conf @@ -0,0 +1,11 @@ +laika.title = Monad Transformers +laika.navigationOrder = [ + index.md + contt.md + eithert.md + iort.md + nested.md + optiont.md + statet.md + writert.md +] \ No newline at end of file diff --git a/docs/datatypes/eithert.md b/docs/monadtransformers/eithert.md similarity index 92% rename from docs/datatypes/eithert.md rename to docs/monadtransformers/eithert.md index e724d693f0..8a9140dd7e 100644 --- a/docs/datatypes/eithert.md +++ b/docs/monadtransformers/eithert.md @@ -2,8 +2,14 @@ API Documentation: @:api(cats.data.EitherT) +@:callout(warning) + +`EitherT` can interact poorly with more powerful effect types that provide concurrent computation, such as Cats Effect's `IO` or fs2's `Stream`. Because `IO` already provides its own error channel, `EitherT[IO, Throwable]` can lead to confusing behavior; prefer `IO`'s own error channel instead of carrying a second throwable error channel. + +@:@ + `Either` can be used for error handling in most situations. However, when -`Either` is placed into effectful types such as `Option` or`Future`, a large +`Either` is placed into effectful types such as `Option`, a large amount of boilerplate is required to handle errors. For example, consider the following program: @@ -65,8 +71,7 @@ handle the errors will increase dramatically. `EitherT[F[_], A, B]` is a lightweight wrapper for `F[Either[A, B]]` that makes it easy to compose `Either`s and `F`s together. To use `EitherT`, values of `Either`, `F`, `A`, and `B` are first converted into `EitherT`, and the -resulting `EitherT` values are then composed using combinators. For example, the -asynchronous division program can be rewritten as follows: +resulting `EitherT` values are then composed using combinators. For example, the asynchronous division program can be rewritten as follows: ```scala mdoc:nest import cats.data.EitherT diff --git a/docs/monadtransformers/index.md b/docs/monadtransformers/index.md new file mode 100644 index 0000000000..d2420f6a89 --- /dev/null +++ b/docs/monadtransformers/index.md @@ -0,0 +1,110 @@ +# Overview + +Monad transformers are data types used to simplify the composition of monads. For example: + +## OptionT + +```scala mdoc:silent +import cats.data.OptionT +``` + +An instance of [`OptionT[F, A]`](optiont.md) can be thought of as a wrapper over `F[Option[A]]` +which adds a couple of useful methods specific to nested types that aren't available in `F` or `Option` itself. +Most typically, your `F` will be `IO` from [Cats Effect](https://typelevel.org/cats-effect), or something like [doobie](https://typelevel.org/doobie/index.html)'s `ConnectionIO`). +Wrappers such as `OptionT` are generally known as _monad transformers_. + +A quite common pattern is mapping the inner value stored inside an instance of `F[Option[A]]` to an instance of `F[Option[B]]` with a function of type `A => B`. +This can be done with rather verbose syntax like: +```scala mdoc:silent +import scala.util.Try + +lazy val resultTry: Try[Option[Int]] = ??? + +def mappedResultTry: Try[Option[String]] = resultTry.map { maybeValue => + maybeValue.map { value => + // Do something with the value and return String + ??? + } +} +``` + +With the use of `OptionT`, this can be simplified as follows: + +```scala mdoc:silent +import cats.data.OptionT + +def mappedResultTry2: OptionT[Try, String] = OptionT(resultTry).map { value => + // Do something with the value and return String + ??? +} +``` + +The above `map` will return a value of type `OptionT[Try, String]`. +To get the underlying `Try[Option[String]]` value, simply call `.value` on the `OptionT` instance. + +There are several ways to construct an `OptionT` instance. +The method headers in the table below are slightly simplified: the type parameters and type classes required by each method are skipped. + +| Method | Takes | Returns | +| :---: | :---: | :---: | +| `OptionT.apply` or `OptionT(...)` | `F[Option[A]]` | `OptionT[F, A]` | +| `OptionT.fromOption` | `Option[A]` | `OptionT[F, A]` | +| `OptionT.liftF` | `F[A]` | `OptionT[F, A]` | +| `OptionT.pure` | `A` | `OptionT[F, A]` | + +In production code you'll most commonly use the `OptionT(...)` syntax in order to wrap an instance of `F[Option[A]]` into `OptionT[F, A]`. +The other methods, in turn, prove useful to set up `OptionT`-typed dummy values in unit tests. + +We have already come across one of `OptionT`'s methods, namely `map`. +There are several other methods available and they mostly differ by the signature of the function they accept as the parameter. +As was the case with the previous table, the expected type classes are skipped. + +| Method | Takes | Returns +| :---: | :---: | :---: | +| `map[B]` | `A => B` | `OptionT[F, B]` | +| `subflatMap[B]` | `A => Option[B]` | `OptionT[F, B]` | +| `semiflatMap[B]` | `A => F[B]` | `OptionT[F, B]` | +| `flatMapF[B]` | `A => F[Option[B]]` | `OptionT[F, B]` | +| `flatMap[B]` | `A => OptionT[F, B]` | `OptionT[F, B]` | + +In practice, you're most likely to use `map` and `semiflatMap`. + +As is always the case with `flatMap` and `map`, you can use it not only explicitly, but also under the hood in `for` comprehensions, as in the example below: + +```scala mdoc:silent + +case class User(accountId: Long) { /* ... */ } +class Account { /* ... */ } +class Money { /* ... */ } + +def findUserById(userId: Long): Try[Option[User]] = { /* ... */ ??? } + +def findAccountById(accountId: Long): Try[Option[Account]] = { /* ... */ ??? } + +def getReservedFundsForAccount(account: Account): Try[Option[Money]] = { /* ... */ ??? } + +def getReservedFundsForUser(userId: Long): Try[Option[Money]] = ( + for { + user <- OptionT(findUserById(userId)) + account <- OptionT(findAccountById(user.accountId)) + funds <- OptionT(getReservedFundsForAccount(account)) + } yield funds +).value +``` + +The `OptionT[Try, Money]` instance returned by `getReservedFundsForUser` will enclose a `None` value if any of the three composed methods returns an `OptionT` corresponding to `None`. +Otherwise, if the result of all three calls contains `Some`, the final outcome will also contain `Some`. + +## Pitfalls + +In principle, monad transformers can be stacked to create data types with complex behavior. +While powerful, this can lead to unwieldy type signatures and poor type inference. +The [Cats MTL](https://typelevel.org/cats-mtl/) project provides a capability based approach to extend effect types without requiring as much boilerplate. + +Some monad transformers can also interact poorly with more powerful effect types that provide concurrent computation, such as Cats Effect's `IO` or fs2's `Stream`. You should avoid `StateT` and `WriterT` and use the concurrent data types provided by those libraries instead. Because `IO` already provides its own error channel, `EitherT[IO, Throwable]` can also lead to confusing behavior; prefer `IO`'s own error channel instead. + +## Recommended Usage + +To minimize these pitfalls, monad transformers should only be used as locally-scoped utilities to reduce boilerplate. +Do not include monad transformers in public method signatures. +Calling code can always use monad transformers themselves. \ No newline at end of file diff --git a/docs/datatypes/iort.md b/docs/monadtransformers/iort.md similarity index 100% rename from docs/datatypes/iort.md rename to docs/monadtransformers/iort.md diff --git a/docs/datatypes/nested.md b/docs/monadtransformers/nested.md similarity index 100% rename from docs/datatypes/nested.md rename to docs/monadtransformers/nested.md diff --git a/docs/datatypes/optiont.md b/docs/monadtransformers/optiont.md similarity index 100% rename from docs/datatypes/optiont.md rename to docs/monadtransformers/optiont.md diff --git a/docs/datatypes/statet.md b/docs/monadtransformers/statet.md similarity index 96% rename from docs/datatypes/statet.md rename to docs/monadtransformers/statet.md index b275a463f9..9a08fc9327 100644 --- a/docs/datatypes/statet.md +++ b/docs/monadtransformers/statet.md @@ -22,6 +22,12 @@ state to an existing computation in the context of `F`. This definition could be confusing, but it will become clear after learning the `State` monad and by the example below. +@:callout(warning) + +`StateT` interacts poorly with more powerful effect types that provide concurrent computation, such as Cats Effect's `IO` or fs2's `Stream`. You should instead use the concurrent data types provided by those libraries. + +@:@ + ## StateT and State Relationship `StateT` is a monad transformer for diff --git a/docs/datatypes/writert.md b/docs/monadtransformers/writert.md similarity index 89% rename from docs/datatypes/writert.md rename to docs/monadtransformers/writert.md index d793d2cea9..024832f547 100644 --- a/docs/datatypes/writert.md +++ b/docs/monadtransformers/writert.md @@ -3,16 +3,22 @@ API Documentation: @:api(cats.data.WriterT) `WriterT[F[_], L, V]` is a type wrapper on an `F[(L, -V)]`. Speaking technically, it is a monad transformer for [`Writer`](writer.md), +V)]`. Speaking technically, it is a monad transformer for [`Writer`](../datatypes/writer.md), but you don't need to know what that means for it to be useful. +@:callout(warning) + +`WriterT` interacts poorly with more powerful effect types that provide concurrent computation, such as Cats Effect's `IO` or fs2's `Stream`. You should instead use the concurrent data types provided by those libraries. + +@:@ + ## Composition `WriterT` can be more convenient to work with than using `F[Writer[L, V]]` directly, because it exposes operations that allow -you to work with the values of the inner [`Writer`](writer.md) (`L` and -`V`) abstracting both the `F` and [`Writer`](writer.md). +you to work with the values of the inner [`Writer`](../datatypes/writer.md) (`L` and +`V`) abstracting both the `F` and [`Writer`](../datatypes/writer.md). For example, `map` allow you to transform the inner `V` value, getting back a `WriterT` that wraps around it. @@ -25,13 +31,13 @@ WriterT[Option, String, Int](Some(("value", 10))).map(x => x * x) Plus, when composing multiple `WriterT` computations, those will be composed following the same behaviour of a -[`Writer`](writer.md) and the +[`Writer`](../datatypes/writer.md) and the generic `F`. Let's see two examples with `Option` and `Either`: if one of the computations has a `None` or a `Left`, the whole computation will return a `None` or a `Left` since the way the two types compose typically behaves that way. Moreover, when the computation succeed, the logging side of the -[`Writer`](writer.md)s will be +[`Writer`](../datatypes/writer.md)s will be combined. ```scala mdoc:silent @@ -75,17 +81,17 @@ for { Just for completeness, we can have a look at the same example, but with -[`Validated`](validated.md) +[`Validated`](../datatypes/validated.md) since it as a slightly different behaviour than `Either`. Instead of short-circuiting when the first error is encountered, -[`Validated`](validated.md) +[`Validated`](../datatypes/validated.md) will accumulate all the errors. In the following example, you can see how this behaviour is respected when -[`Validated`](validated.md) is +[`Validated`](../datatypes/validated.md) is wrapped as the `F` type of a `WriterT`. In addition, notice how `flatMap` and for comprehension can't be used in this case, since -[`Validated`](validated.md) +[`Validated`](../datatypes/validated.md) only extends [`Applicative`](../typeclasses/applicative.md), but not [`Monad`](../typeclasses/monad.md). ```scala mdoc:silent @@ -147,7 +153,7 @@ to fill the `L` value not specified in the input. `put[F[_], L, V](v: V)(l: L)(implicit applicativeF: Applicative[F]): WriterT[F, L, V]` : As soon as there is an [`Applicative`](../typeclasses/applicative.md) instance of `F`, this function -creates the datatype starting from the inner [`Writer`](writer.md)'s values. +creates the datatype starting from the inner [`Writer`](../datatypes/writer.md)'s values. ```scala mdoc:nest WriterT.put[Option, String, Int](123)("initial value") @@ -163,14 +169,14 @@ creates the datatype starting from the inner [`Writer`](writer.md)'s values. ## Operations In the [Writer -definition](writer.md#definition) +definition](../datatypes/writer.md#definition) section, we showed how it is actually a `WriterT`. Therefore, all the operations described into [Writer -operations](writer.md#operations) +operations](../datatypes/writer.md#operations) are valid for `WriterT` as well. The only aspect we want to remark here is the following sentence from -[`Writer`](writer.md)'s page: +[`Writer`](../datatypes/writer.md)'s page: > Most of the `WriterT` functions require a `Functor[F]` or > `Monad[F]` instance. However, Cats provides all the necessary diff --git a/docs/motivations.md b/docs/motivations.md index bab6174237..81b088710b 100644 --- a/docs/motivations.md +++ b/docs/motivations.md @@ -15,7 +15,7 @@ page](CONTRIBUTING.md) to find out ways to give us feedback. #### Modularity We are trying to make the library modular. It will have a tight -core which will contain only the [type classes](typeclasses.md), +core which will contain only the [type classes](typeclasses/index.md), the bare minimum of data structures that are needed to support them, and type class instances for those data structures and standard library types. diff --git a/docs/typeclasses/directory.conf b/docs/typeclasses/directory.conf index 08270eb62b..6fd578269a 100644 --- a/docs/typeclasses/directory.conf +++ b/docs/typeclasses/directory.conf @@ -1,5 +1,6 @@ laika.title = Type Classes laika.navigationOrder = [ + index.md alternative.md applicative.md applicativemonaderror.md diff --git a/docs/typeclasses.md b/docs/typeclasses/index.md similarity index 99% rename from docs/typeclasses.md rename to docs/typeclasses/index.md index 85a4d32f69..d792bba2b9 100644 --- a/docs/typeclasses.md +++ b/docs/typeclasses/index.md @@ -1,4 +1,4 @@ -# Type Classes +# Overview Type classes are a powerful tool used in functional programming to enable ad-hoc polymorphism, more commonly known as overloading. Where many object-oriented languages leverage subtyping for polymorphic code, functional programming tends towards a combination of parametric polymorphism (think type parameters, like Java generics) @@ -223,7 +223,7 @@ val result = Monoid[Int].combine(sumLeft, sumRight) Cats provides laws for type classes via the `kernel-laws` and `laws` modules which makes law checking type class instances easy. -You can find out more about law testing [here](typeclasses/lawtesting.md). +You can find out more about law testing [here](lawtesting.md). ## Type classes in Cats diff --git a/docs/typeclasses/lawtesting.md b/docs/typeclasses/lawtesting.md index f4a8ab68ab..c9a6fa37c5 100644 --- a/docs/typeclasses/lawtesting.md +++ b/docs/typeclasses/lawtesting.md @@ -1,6 +1,6 @@ # Law testing -[Laws](../typeclasses.md#laws) are an important part of cats. +[Laws](../typeclasses/index.md#laws) are an important part of cats. Cats uses [discipline](https://github.com/typelevel/discipline) to define type class laws and the [ScalaCheck](https://github.com/rickynils/scalacheck) tests based on them. diff --git a/docs/typeclasses/monad.md b/docs/typeclasses/monad.md index 080f0a3986..b675a3e798 100644 --- a/docs/typeclasses/monad.md +++ b/docs/typeclasses/monad.md @@ -158,7 +158,7 @@ implicit def optionTMonad[F[_]](implicit F: Monad[F]): Monad[OptionT[F, *]] = { This sort of construction is called a monad transformer. -Cats has an [`OptionT`](../datatypes/optiont.md) monad transformer, which adds a lot of useful functions to the simple implementation above. +Cats has an [`OptionT`](../monadtransformers/optiont.md) monad transformer, which adds a lot of useful functions to the simple implementation above. ## FlatMap - a weakened Monad A closely related type class is `FlatMap` which is identical to `Monad`, minus the `pure`