Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blast-hardcheese/8764f977ed7619ad8fa7c8ff3c610083 to your computer and use it in GitHub Desktop.
Save blast-hardcheese/8764f977ed7619ad8fa7c8ff3c610083 to your computer and use it in GitHub Desktop.

A problem I had with Free interpreters was seemingly being unable to call functions defined inside any given interface from sibling members to the same interface.

Recently, I converted https://github.com/twilio/guardrail to Tagless Final, and in so doing discovered a way to use sibling functions while preserving the ability to provide different implementations of those functions.

The following is a sample, note that the desired behaviour is that my overridden function in second is the desired behaviour for all attempted invocations, and in production code would be explicitly brought into scope right before I execute my initial function.

abstract class FooInterface[F[_]] {
  def doFoo(): F[Unit]
  def doBar(): F[Unit]
  def doBaz()(implicit impl: FooInterface[F]): F[Unit]
}

object first extends FooInterface[cats.Id] {
  def doFoo() = println(s"first")
  def doBar() = doFoo()
  def doBaz()(implicit impl: FooInterface[F]) = impl.doFoo()
}

implicit object second extends FooInterface[cats.Id] {
  def doFoo() = println(s"second")
  def doBar() = first.doBar()
  def doBaz()(implicit impl: FooInterface[F]) = first.doBaz()
}

first.doBar()  // prints first
second.doBar() // also prints first

// compare with:

first.doBaz()  // prints second
second.doBaz() // also prints second

This is useful, as so long as you make the implementation you want the only implicit interpreter, you won't accidentally completely embed one implementation without giving the ability to override.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment