Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
RChain Scala Educational Series: Tagless-final (ZIO)

RChain Scala Educational Series

Beautiful, Simple, Testable Functional Effects for Scala (tagless-final :))

Excellent post by John A De Goes about the critique of tagless-final style of abstracting over program effects and program execution (control flow).
https://degoes.net/articles/zio-environment

In RChain source code tagless-final style is used (mostly) and knowing what are the limitations and where is the hidden complexity, can be very useful. Notes here should help in easier understanding of the points presented in the post.

There are two popular libraries for functional programming in Scala cats and scalaz. Because cats is used in RChain it's also in this examples.

First three programs are completely the same, just written with the different Scala syntax. It shows why for ... yield (monadic) comprehension is useful to "transform" nesting to a sequence similar to imperative programs.
In program4 control effect we have is only Applicative so getStrLn cannot give the result to the next instruction, data flow is static.

import cats.{Applicative, Monad}
import cats.syntax.all._

// Our effect interface / language
trait Console[F[_]] {
  def putStrLn(line: String): F[Unit]

  def getStrLn: F[String]
}

// Some Scala machinery to support use of `Console[F].<method>` in program
object Console {
  def apply[F[_]](implicit instance: Console[F]): Console[F] = instance
}

// Different variants of "combined" program

def program1[F[_]: Monad: Console]: F[String] =
    for {
      _    <- Console[F].putStrLn("Good morning, what's your name?")
      name <- Console[F].getStrLn
      _    <- Console[F].putStrLn(s"Great to meet you, $name")
    } yield name

def program2[F[_]: Monad: Console]: F[String] =
    Console[F].putStrLn("Good morning, what's your name?").flatMap { _ =>
      Console[F].getStrLn.flatMap { name =>
        Console[F].putStrLn(s"Great to meet you, $name").flatMap { _ =>
          name.pure[F]
        }
      }
    }

def program3[F[_]: Monad: Console]: F[String] =
    Console[F].putStrLn("Good morning, what's your name?") *>
      Console[F].getStrLn.flatMap { name =>
        // For the last step we can just use map
        Console[F].putStrLn(s"Great to meet you, $name").map { _ =>
          name
        }
      }

def program4[F[_]: Applicative: Console]: F[String] =
    // We cannot use `flatMap` because it's defined on Monad
    Console[F].putStrLn("Good morning, what's your name?") *>
      // Result from `getStrLn` will be returned, as arrows are pointing
      Console[F].getStrLn <*
      // Results from both `putStrLn` are ignored
      Console[F].putStrLn(s"Great to meet you")

Links and videos

Monad in cats - sequential execution flow.
https://typelevel.org/cats/typeclasses/monad.html

Effects in cats. Monad + suspend + concurrent execution flow.
https://typelevel.org/cats-effect/typeclasses/

Lambda World 2018 - A roadtrip with monads: from MTL, through tagless to BIO - Paweł Szulc
https://youtu.be/eth4y015BCU

Advanced Tagless Final - Saying farewell to Free (Luka Jacobowitz)
https://youtu.be/E9iRYNuTIYA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.