Skip to content

Instantly share code, notes, and snippets.

@programaker
Created October 21, 2021 17:26
Show Gist options
  • Save programaker/9203b464e324ee0c4291af4e4461250c to your computer and use it in GitHub Desktop.
Save programaker/9203b464e324ee0c4291af4e4461250c to your computer and use it in GitHub Desktop.
Chaining & tapping
import cats.effect.std.{Console, Random}
import cats.effect.{IO, Sync}
import scala.util.{Random => RandomStd}
/*
* Std lib has added `tap` and `pipe` functions for all types.
* With `tap` we can use a function to inspect the value without changing it.
* `pipe` is the equivalent of `|>` from F# or Elixir; it sends the value to a function.
* */
def impureFn(start: Int, end: Int): Int = {
import scala.util.chaining._
RandomStd
.between(start, end)
.tap(i => println(s">>> Starting with $i"))
.pipe(_ * 2)
.pipe(_ + 10)
.tap(i => println(s">>> Final result $i"))
}
///
/*
* The FP version of `tap` is also known as `k-combinator` or Kestrel:
* https://github.com/osxhacker/foundation/blob/master/models/core/src/main/scala/com/github/osxhacker/foundation/models/core/functional/Kestrel.scala
* https://github.com/osxhacker/foundation/blob/master/models/core/src/test/scala/com/github/osxhacker/foundation/models/core/functional/KestrelSpec.scala
*
* However, cats decided to not add a `kestrel`/`tapM` function, adding `flatTap` instead.
* The reasoning is that `flatTap` is more principled; by accepting an effectful function,
* it would encourage the use of purely functional console/log solutions:
* https://github.com/typelevel/cats/issues/1559
* */
def pureFn[F[_]: Console: Sync](start: Int, end: Int): F[Int] = {
import cats.syntax.flatMap._
import cats.syntax.functor._
Random.scalaUtilRandom[F]
.flatMap(_.betweenInt(start, end))
.flatTap(i => Console[F].println(s">=> Starting with $i"))
.map(_ * 2)
.map(_ + 10)
.flatTap(i => Console[F].println(s">=> Final result $i"))
}
/*
* We can still cheat `flatTap` using `println(..).pure` or `log.info(..).pure` =}
* But avoid that! Prefer a purely functional console/log!
* */
def semiPureFn[F[_]: Sync](start: Int, end: Int): F[Int] = {
import cats.syntax.flatMap._
import cats.syntax.functor._
import cats.syntax.applicative._
Random.scalaUtilRandom[F]
.flatMap(_.betweenInt(start, end))
.flatTap(i => println(s">-> Starting with $i").pure)
.map(_ * 2)
.map(_ + 10)
.flatTap(i => println(s">-> Final result $i").pure)
}
///
import cats.effect.unsafe.implicits.global
val start = 1
val end = 1_000_000
val out1 = impureFn(start, end)
val out2 = pureFn[IO](start, end).unsafeRunSync()
val out3 = semiPureFn[IO](start, end).unsafeRunSync()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment