Skip to content

Instantly share code, notes, and snippets.

@programaker
Created October 21, 2021 17:26
Show Gist options
  • Select an option

  • Save programaker/9203b464e324ee0c4291af4e4461250c to your computer and use it in GitHub Desktop.

Select an option

Save programaker/9203b464e324ee0c4291af4e4461250c to your computer and use it in GitHub Desktop.

Revisions

  1. programaker created this gist Oct 21, 2021.
    72 changes: 72 additions & 0 deletions chainingTapping.scala
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,72 @@
    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()