Watch video

Video tutorial

StateT

advanced

StateT also known as the State monad transformer allows to compute inside the context when State is nested in a different monad.

One issue we face with monads is that they don’t compose. This can cause your code to get really hairy when trying to combine structures like Either and State. But there’s a simple solution, and we’re going to explain how you can use Monad Transformers to alleviate this problem.

For our purposes here, we’re going to transform a monad that serves as a container that represents branching as an an error (left) or state (right) where computation can be performed. Given that both State<S, A> and Either<L, R> would be examples of datatypes that provide instances for the Monad typeclasses.

Because monads don’t compose, we may end up with nested structures such as Either<Error, State<Either<Error, State<S, Unit>>, Unit>> when using Either and State together. Using Monad Transformers can help us to reduce this boilerplate.

In the most basic of scenarios, we’ll only be dealing with one monad at a time making our lives nice and easy. However, it’s not uncommon to get into scenarios where some function calls will return Either<Error, A>, and others will return State<S, A>.

So let’s rewrite the example of State docs, but instead of representing the Stack as an optional NonEmptyList let’s represent it as a List.


import arrow.core.Tuple2
import arrow.core.toT

typealias Stack = List<String>

fun pop(stack: Stack): Tuple2<Stack, String> = stack.first().let {
  stack.drop(1) toT it
}

fun push(s: String, stack: Stack): Tuple2<Stack, Unit> =
  listOf(s, *stack.toTypedArray()) toT Unit

fun stackOperations(stack: Stack): Tuple2<Stack, String> {
  val (s1, _) = push("a", stack)
  val (s2, _) = pop(s1)
  return pop(s2)
}

import arrow.core.Tuple2
import arrow.core.toT

typealias Stack = List<String>

fun pop(stack: Stack): Tuple2<Stack, String> = stack.first().let {
  stack.drop(1) toT it
}

fun push(s: String, stack: Stack): Tuple2<Stack, Unit> =
  listOf(s, *stack.toTypedArray()) toT Unit

fun stackOperations(stack: Stack): Tuple2<Stack, String> {
  val (s1, _) = push("a", stack)
  val (s2, _) = pop(s1)
  return pop(s2)
}

fun main() {
  //sampleStart
  val value = stackOperations(listOf("hello", "world", "!"))
  //sampleEnd
  println("value=$value")
}

But if we now pop an empty Stack it will result in java.util.NoSuchElementException: List is empty..


import arrow.core.Tuple2
import arrow.core.toT

typealias Stack = List<String>

fun pop(stack: Stack): Tuple2<Stack, String> = stack.first().let {
  stack.drop(1) toT it
}

fun push(s: String, stack: Stack): Tuple2<Stack, Unit> =
  listOf(s, *stack.toTypedArray()) toT Unit

fun stackOperations(stack: Stack): Tuple2<Stack, String> {
  val (s1, _) = push("a", stack)
  val (s2, _) = pop(s1)
  return pop(s2)
}

stackOperations(listOf())
javax.script.ScriptException: java.util.NoSuchElementException: List is empty.
	at kotlin.collections.CollectionsKt___CollectionsKt.first(_Collections.kt:196)
	at Line_3.pop(Line_3.kts:7)
	at Line_3.stackOperations(Line_3.kts:17)
	at org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase.compileAndEval(KotlinJsr223JvmScriptEngineBase.kt:65)
	at org.jetbrains.kotlin.cli.common.repl.KotlinJsr223JvmScriptEngineBase.eval(KotlinJsr223JvmScriptEngineBase.kt:31)
	at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
	at arrow.ank.InterpreterKt$interpreter$1$compileCode$2.invoke(interpreter.kt:176)
	at arrow.ank.InterpreterKt$interpreter$1$compileCode$2.invoke(interpreter.kt:85)
	at kotlin.sequences.TransformingIndexedSequence$iterator$1.next(Sequences.kt:196)
	at arrow.core.SequenceK.foldLeft(SequenceK.kt:88)
	at arrow.core.extensions.SequenceKFoldable$DefaultImpls.foldLeft(sequenceK.kt:135)
	at arrow.core.extensions.sequence.foldable.SequenceKFoldableKt$foldable_singleton$1.foldLeft(SequenceKFoldable.kt:312)
	at arrow.core.extensions.sequence.foldable.SequenceKFoldableKt.foldLeft(SequenceKFoldable.kt:30)
	at arrow.ank.InterpreterKt$interpreter$1.replaceAnkToLang(interpreter.kt:222)
	at arrow.ank.AnkKt$ank$$inlined$with$lambda$1$5$1$1.invokeSuspend(ank.kt:52)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.signal(IORunLoop.kt:414)
	at arrow.fx.IORunLoop$RestartCallback.resumeWith(IORunLoop.kt:445)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:114)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:402)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:227)
	at arrow.fx.IORunLoop.access$loop(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$suspendAsync$1.invoke(IORunLoop.kt:145)
	at arrow.fx.IORunLoop$suspendAsync$1.invoke(IORunLoop.kt:21)
	at arrow.fx.IORunLoop$RestartCallback.start(IORunLoop.kt:397)
	at arrow.fx.IORunLoop.loop(IORunLoop.kt:218)
	at arrow.fx.IORunLoop.start(IORunLoop.kt:24)
	at arrow.fx.IO.unsafeRunAsync(IO.kt:796)
	at arrow.fx.internal.Platform.unsafeResync(Utils.kt:156)
	at arrow.fx.IO$Async.unsafeRunTimedTotal$arrow_fx(IO.kt:1017)
	at arrow.fx.IO.unsafeRunTimed(IO.kt:862)
	at arrow.fx.IO.unsafeRunSync(IO.kt:851)
	at arrow.fx.extensions.IOUnsafeRun$DefaultImpls.runBlocking(io.kt:245)
	at arrow.fx.extensions.io.unsafeRun.IOUnsafeRunKt$unsafeRun_singleton$1.runBlocking(IOUnsafeRun.kt:21)
	at arrow.fx.extensions.io.unsafeRun.IOUnsafeRunKt.runBlocking(IOUnsafeRun.kt:31)
	at arrow.ank.main$main$1.invokeSuspend(main.kt:13)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:128)
	at arrow.unsafe.invoke(unsafe.kt:30)
	at arrow.ank.main.main(main.kt:12)

Luckily Arrow offers some nice solutions Functional Error Handling docs. Now we can model our error domain with ease.


import arrow.core.Either
import arrow.core.Tuple2
import arrow.core.flatMap
import arrow.core.left
import arrow.core.right
import arrow.core.toT

typealias Stack = List<String>
typealias StackEmpty = StackError.StackEmpty

sealed class StackError {
  object StackEmpty : StackError()
}

fun popE(stack: Stack): Either<StackError, Tuple2<Stack, String>> =
  if (stack.isEmpty()) StackEmpty.left()
  else stack.first().let {
    stack.drop(1) toT it
  }.right()

fun pushE(s: String, stack: Stack): Either<StackError, Tuple2<Stack, Unit>> =
  (listOf(s, *stack.toTypedArray()) toT Unit).right()

fun stackOperationsE(stack: Stack): Either<StackError, Tuple2<Stack, String>> {
  return pushE("a", stack).flatMap { (s1, _) ->
    popE(s1).flatMap { (s2, _) ->
      popE(s2)
    }
  }
}

import arrow.core.Either
import arrow.core.Tuple2
import arrow.core.flatMap
import arrow.core.left
import arrow.core.right
import arrow.core.toT

typealias Stack = List<String>
typealias StackEmpty = StackError.StackEmpty

sealed class StackError {
  object StackEmpty : StackError()
}

fun popE(stack: Stack): Either<StackError, Tuple2<Stack, String>> =
  if (stack.isEmpty()) StackEmpty.left()
  else stack.first().let {
    stack.drop(1) toT it
  }.right()

fun pushE(s: String, stack: Stack): Either<StackError, Tuple2<Stack, Unit>> =
  (listOf(s, *stack.toTypedArray()) toT Unit).right()

fun stackOperationsE(stack: Stack): Either<StackError, Tuple2<Stack, String>> {
  return pushE("a", stack).flatMap { (s1, _) ->
    popE(s1).flatMap { (s2, _) ->
      popE(s2)
    }
  }
}

fun main() {
  val value =
    //sampleStart
    stackOperationsE(listOf("hello", "world", "!"))
  //sampleEnd
  println(value)
}

import arrow.core.Either
import arrow.core.Tuple2
import arrow.core.flatMap
import arrow.core.left
import arrow.core.right
import arrow.core.toT

typealias Stack = List<String>
typealias StackEmpty = StackError.StackEmpty

sealed class StackError {
  object StackEmpty : StackError()
}

fun popE(stack: Stack): Either<StackError, Tuple2<Stack, String>> =
  if (stack.isEmpty()) StackEmpty.left()
  else stack.first().let {
    stack.drop(1) toT it
  }.right()

fun pushE(s: String, stack: Stack): Either<StackError, Tuple2<Stack, Unit>> =
  (listOf(s, *stack.toTypedArray()) toT Unit).right()

fun stackOperationsE(stack: Stack): Either<StackError, Tuple2<Stack, String>> {
  return pushE("a", stack).flatMap { (s1, _) ->
    popE(s1).flatMap { (s2, _) ->
      popE(s2)
    }
  }
}

fun main() {
  val value =
    //sampleStart
    stackOperationsE(listOf())
  //sampleEnd
  println(value)
}

As is immediately clear, this code while properly modelling the errors, has become more complex but our signature now represents a simple Stack as a List with an error domain. Let’s refactor our manual state management in the form of (S) -> Tuple2<S, A> to State.

So what we want is a return type that represents Either a StackError or a certain State of Stack. When working with State we don’t pass around Stack anymore, so there is no parameter to check if the Stack is empty.


import arrow.core.ForId
import arrow.mtl.StateT

fun _popS(): Either<StackError, StateT<ForId, Stack, String>> = TODO()

The only thing we can do is handle this with StateT. We want to wrap State with Either. EitherKindPartial is an alias that helps us to fix StackError as the left type parameter for Either<L, R>.


import arrow.core.Either
import arrow.core.EitherPartialOf
import arrow.core.extensions.either.monad.monad
import arrow.core.extensions.either.monadError.monadError
import arrow.core.left
import arrow.core.right
import arrow.core.toT
import arrow.mtl.StateT
import arrow.mtl.runM
import arrow.mtl.fix

typealias Stack = List<String>
typealias StackEmpty = StackError.StackEmpty

sealed class StackError {
  object StackEmpty : StackError()
}
//sampleStart
fun popS() = StateT<EitherPartialOf<StackError>, Stack, String>(Either.monad()) { stack: Stack ->
  if (stack.isEmpty()) StackEmpty.left()
  else stack.first().let {
    stack.drop(1) toT it
  }.right()
}

fun pushS(s: String) = StateT<EitherPartialOf<StackError>, Stack, Unit>(Either.monad()) { stack: Stack ->
  (listOf(s, *stack.toTypedArray()) toT Unit).right()
}

fun stackOperationsS(): StateT<EitherPartialOf<StackError>, Stack, String> =
  pushS("a").flatMap(Either.monad()) { _ ->
    popS().flatMap(Either.monad()) { _ ->
      popS()
    }
  }.fix()
  
fun main() {
  val value = stackOperationsS().runM(Either.monad<StackError>(), listOf("hello", "world", "!"))
  println(value)
}
//sampleEnd

import arrow.core.Either
import arrow.core.EitherPartialOf
import arrow.core.extensions.either.monad.monad
import arrow.core.extensions.either.monadError.monadError
import arrow.core.left
import arrow.core.right
import arrow.core.toT
import arrow.mtl.StateT
import arrow.mtl.runM
import arrow.mtl.fix

typealias Stack = List<String>
typealias StackEmpty = StackError.StackEmpty

sealed class StackError {
  object StackEmpty : StackError()
}

fun popS() = StateT<EitherPartialOf<StackError>, Stack, String>(Either.monad()) { stack: Stack ->
  if (stack.isEmpty()) StackEmpty.left()
  else stack.first().let {
    stack.drop(1) toT it
  }.right()
}

fun pushS(s: String) = StateT<EitherPartialOf<StackError>, Stack, Unit>(Either.monad()) { stack: Stack ->
  (listOf(s, *stack.toTypedArray()) toT Unit).right()
}

fun stackOperationsS(): StateT<EitherPartialOf<StackError>, Stack, String> =
  pushS("a").flatMap(Either.monad()) { _ ->
    popS().flatMap(Either.monad()) { _ ->
      popS()
    }
  }.fix()

fun main() {
  val value =
    //sampleStart
    stackOperationsS().runM(Either.monad<StackError>(), listOf())
  //sampleEnd
  println(value)
}

While our code looks very similar to what we had before there are some key advantages. State management is now contained within State and we are dealing only with 1 monad instead of 2 nested monads so we can use monad bindings!


import arrow.core.Either
import arrow.core.EitherPartialOf
import arrow.core.extensions.either.monad.monad
import arrow.core.extensions.either.monadError.monadError
import arrow.core.left
import arrow.core.right
import arrow.core.toT
import arrow.mtl.StateT
import arrow.mtl.extensions.fx

typealias Stack = List<String>
typealias StackEmpty = StackError.StackEmpty

sealed class StackError {
  object StackEmpty : StackError()
}

fun popS() = StateT<EitherPartialOf<StackError>, Stack, String>(Either.monad()) { stack: Stack ->
  if (stack.isEmpty()) StackEmpty.left()
  else stack.first().let {
    stack.drop(1) toT it
  }.right()
}

fun pushS(s: String) = StateT<EitherPartialOf<StackError>, Stack, Unit>(Either.monad()) { stack: Stack ->
  (listOf(s, *stack.toTypedArray()) toT Unit).right()
}
//sampleStart
fun stackOperationsS2() =
  StateT.fx<EitherPartialOf<StackError>, Stack, String>(Either.monadError<StackError>()) {
    !pushS("a")
    !popS()
    val (string) = popS()
    string
  }

fun main() {
  val value = stackOperationsS2().runM(Either.monad<StackError>(), listOf("hello", "world", "!"))
  println(value)
}
//sampleEnd

import arrow.core.Either
import arrow.core.EitherPartialOf
import arrow.core.extensions.either.monad.monad
import arrow.core.extensions.either.monadError.monadError
import arrow.core.left
import arrow.core.right
import arrow.core.toT
import arrow.mtl.StateT
import arrow.mtl.extensions.fx

typealias Stack = List<String>
typealias StackEmpty = StackError.StackEmpty

sealed class StackError {
  object StackEmpty : StackError()
}

fun popS() = StateT<EitherPartialOf<StackError>, Stack, String>(Either.monad()) { stack: Stack ->
  if (stack.isEmpty()) StackEmpty.left()
  else stack.first().let {
    stack.drop(1) toT it
  }.right()
}

fun pushS(s: String) = StateT<EitherPartialOf<StackError>, Stack, Unit>(Either.monad()) { stack: Stack ->
  (listOf(s, *stack.toTypedArray()) toT Unit).right()
}
//sampleStart
fun stackOperationsS2() =
  StateT.fx<EitherPartialOf<StackError>, Stack, String>(Either.monadError<StackError>()) {
    !pushS("a")
    !popS()
    val (string) = popS()
    string
  }

fun main() {
  val value =
    //sampleStart
    stackOperationsS2().runM(Either.monad<StackError>(), listOf())
  //sampleEnd
  println(value)
}

Supported type classes

Module Type classes
arrow.mtl.typeclasses MonadState
arrow.typeclasses Applicative, ApplicativeError, Contravariant, Decidable, Divide, Divisible, Functor, Monad, MonadCombine, MonadError, MonadThrow, SemigroupK

Take a look at the EitherT docs or OptionT docs for an alternative version monad transformer for achieving different goals.