Skip to content

Instantly share code, notes, and snippets.

@travisbrown
Forked from kevinmeredith/MonadADT.scala
Last active April 28, 2017 02:34
Show Gist options
  • Save travisbrown/867230e3beaa1053281f to your computer and use it in GitHub Desktop.
Save travisbrown/867230e3beaa1053281f to your computer and use it in GitHub Desktop.
Sequencing through Monad with ADT
import scalaz._, Scalaz._
sealed trait Status
case object First extends Status
case object Second extends Status
case object Third extends Status
type StringOr[A] = String \/ A
def f = IndexedStateT.constantIndexedStateT[StringOr, Unit, First.type, Unit](())(First)
def g = IndexedStateT[StringOr, First.type, Second.type, Unit](_ => (Second, ()).right)
def h = IndexedStateT[StringOr, Second.type, Third.type, Unit](_ => (Third, ()).right)
for {
a <- f
b <- g
c <- h
} yield c
/* Doesn't compile:
for {
a <- g
b <- f
c <- h
} yield c
*/
// Given the following ADT:
sealed trait Status
case object One extends Status
case object Two extends Status
case object Three extends Status
// They represent states. The natural progression, for this
// example, is : One -> Two -> Three
// Then, the functions that'll be used in the below monadic chaining.
// Observe that, it's possible, for an error to occur during f, g, or h
def f: Either[String, Status] = Right(One)
def g(x: Status): Either[String, Status] = Right(Two)
def h(x: Status): Either[String, Status] = Right(Three)
// Lastly, a for-comprehension that sequences through f, g and h.
// Please ignore the fact that g and h both discard their input
scala> for {
| a <- f.right
| b <- g(a).right
| c <- h(b).right
| } yield c
res4: scala.util.Either[String,Status] = Right(Three)
// From a type perspectice, `g` could return `Right(One)`. It would not
// make sense per this domain (where all we know is One -> Two -> Three),
// but of course One, Two and Three constitute Status.
// Can I do better here? I wrote a service that's similar to
// this example, but I'm finding myself adding tests (with a test service)
// where `g` returns Right(One). As a result, `h` will return
// Left("Invalid state. Got One, but expected Two.")
@kevinmeredith
Copy link

hey Travis - what about this?

scala> case object One
defined object One

scala> case object Two
defined object Two

scala> def f: Either[String, One.type] = Right(One)
f: Either[String,One.type]

scala> def g: Either[String, Two.type] = Right(Two)
g: Either[String,Two.type]

scala> for { 
     |   a <- f.right
     |   b <- g(a).right
     | } yield b
res2: scala.util.Either[String,Two.type] = Right(Two)

A colleague pointed me to this approach.

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