Skip to content

Instantly share code, notes, and snippets.

@igstan
Last active August 12, 2021 16:28
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 igstan/05ba68cdb6f90674b6ba08c6bf608e6c to your computer and use it in GitHub Desktop.
Save igstan/05ba68cdb6f90674b6ba08c6bf608e6c to your computer and use it in GitHub Desktop.
import cats.implicits._
import cats.Monad
import cats.data.State
//
// Two capability/tagless final traits: Foo & Bar
//
trait Foo[F[_]] {
def foo: F[Unit]
}
trait Bar[F[_]] {
def bar: F[Unit]
}
//
// Two isolated stateful implementations. Each of them counts the number
// of calls to that particular method.
//
object CountedFoo extends Foo[State[Int, *]] {
override def foo: State[Int, Unit] =
State.modify(_ + 1)
}
object CountedBar extends Bar[State[Int, *]] {
override def bar: State[Int, Unit] =
State.modify(_ + 1)
}
object StateComposition {
// Mixed usage of the two traits.
def abstractUsage[F[_]: Monad](foo: Foo[F], bar: Bar[F]): F[Unit] =
foo.foo >> foo.foo >> foo.foo >> bar.bar
def main(args: Array[String]): Unit = {
//
// Q: how would you compose CountedFoo and CountedBar so that they
// can be used together inside `abstractUsage`.
//
type Composed[A] = ???
def composed: Foo[Composed] with Bar[Composed] = ???
val result = abstractUsage(composed, composed)
}
// I was thinking of doing something like this, but I'm wondering what's
// a better way of doing it.
final case class Counters(foo: Int, bar: Int)
type ComposedCounters[A] = State[Counters, A]
object Composed extends Foo[ComposedCounters] with Bar[ComposedCounters] {
override def foo: ComposedCounters[Unit] =
State.modify { counters =>
val fooState = CountedFoo.foo.runS(counters.foo).value
counters.copy(foo = fooState)
}
override def bar: ComposedCounters[Unit] =
State.modify { counters =>
val barState = CountedBar.bar.runS(counters.bar).value
counters.copy(bar = barState)
}
}
}
@igstan
Copy link
Author

igstan commented Aug 12, 2021

Improved implementation of Composed using IndexedStateT.transformS:

final case class Counters(foo: Int, bar: Int) {
  def withFoo(foo: Int): Counters = copy(foo = foo)
  def withBar(bar: Int): Counters = copy(bar = bar)
}

type ComposedCounters[A] = State[Counters, A]

object Composed extends Foo[ComposedCounters] with Bar[ComposedCounters] {
  override def foo: ComposedCounters[Unit] =
    CountedFoo.foo.transformS[Counters](_.foo, _.withFoo(_))

  override def bar: ComposedCounters[Unit] =
    CountedBar.bar.transformS[Counters](_.bar, _.withBar(_))
}

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