|
| 1 | +import cats.data.{EitherT, OptionT, Writer} |
| 2 | +import cats.syntax.applicative.catsSyntaxApplicativeId // pure |
| 3 | +import scala.concurrent.ExecutionContext.Implicits.global |
| 4 | +import scala.concurrent.duration.* |
| 5 | +import scala.concurrent.{Await, Future} |
| 6 | + |
| 7 | +// 10.2 A Transformative Example |
| 8 | +/* |
| 9 | +we build ListOption from the inside out: we pass List, the type of the outer monad, |
| 10 | +as a parameter to OptionT, the transformer for the inner monad. |
| 11 | + */ |
| 12 | +type ListOption[A] = OptionT[List, A] |
| 13 | + |
| 14 | +// We can create instances of ListOption using the OptionT constructor, or more conveniently using pure |
| 15 | +val result1: ListOption[Int] = OptionT(List(Option(10))) |
| 16 | +val result2: ListOption[Int] = 32.pure[ListOption] |
| 17 | + |
| 18 | +result1.flatMap { (x: Int) => |
| 19 | + result2.map { (y: Int) => |
| 20 | + x + y |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +// 10.3.2 Building Monad Stacks |
| 25 | + |
| 26 | +// Alias Either to a type constructor with one parameter |
| 27 | +type ErrorOr[A] = Either[String, A] |
| 28 | + |
| 29 | +// Build our final monad stack using OptionT |
| 30 | +type ErrorOrOption[A] = OptionT[ErrorOr, A] |
| 31 | + |
| 32 | +val a = 10.pure[ErrorOrOption] |
| 33 | +val b = 32.pure[ErrorOrOption] |
| 34 | + |
| 35 | +val c = a.flatMap(x => b.map(y => x + y)) |
| 36 | + |
| 37 | +type FutureEither[A] = EitherT[Future, String, A] |
| 38 | + |
| 39 | +type FutureEitherOption[A] = OptionT[FutureEither, A] |
| 40 | + |
| 41 | +val futureEitherOr: FutureEitherOption[Int] = |
| 42 | + for |
| 43 | + a <- 10.pure[FutureEitherOption] |
| 44 | + b <- 32.pure[FutureEitherOption] |
| 45 | + yield a + b |
| 46 | + |
| 47 | +// 10.3.3 Constructing and Unpacking Instances |
| 48 | +val intermediate = futureEitherOr.value |
| 49 | + |
| 50 | +val stack = intermediate.value |
| 51 | + |
| 52 | +Await.result(stack, 1.second) |
| 53 | + |
| 54 | +// 10.3.5 Usage Patterns |
| 55 | +type Logged[A] = Writer[List[String], A] |
| 56 | + |
| 57 | +// Methods generally return untransformed stacks |
| 58 | +def parseNumber(str: String): Logged[Option[Int]] = |
| 59 | + util.Try(str.toInt).toOption match |
| 60 | + case Some(num) => Writer(List(s"Read $str"), Some(num)) |
| 61 | + case None => Writer(List(s"Failed on $str"), None) |
| 62 | + |
| 63 | +// Consumers use monad transformers locally to simplify composition |
| 64 | +def addAll(a: String, b: String, c: String): Logged[Option[Int]] = |
| 65 | + val result = for |
| 66 | + a <- OptionT(parseNumber(a)) |
| 67 | + b <- OptionT(parseNumber(b)) |
| 68 | + c <- OptionT(parseNumber(c)) |
| 69 | + yield a + b + c |
| 70 | + |
| 71 | + result.value |
| 72 | + |
| 73 | +// This approach doesn't force OptionT on other users' code: |
| 74 | +addAll("1", "2", "3") |
| 75 | +addAll("1", "a", "3") |
0 commit comments