MonadDefer

intermediate

MonadDefer is a typeclass to abstract over computations that cause side effects. This means that the computations are defered until they’re are asked to be performed synchronously. Without effect suspension the effects would otherwise run immediately.

val now = IO.applicative().just(println("eager side effect"))
// Print: "eager side effect"

now.unsafeRunAsync { }
// Nothing, the effect has run already

val later = IO.monadDefer().invoke { println("lazy side effect") }
// Nothing, the effect is deferred until executed

later.unsafeRunAsync { }
// Print: "lazy side effect"

Main Combinators

All the new combinators added by MonadDefer are constructors. MonadDefer also includes all combinators present in MonadError.

invoke

Receives a function returning A. The instance is responsible of evaluating the function lazily.

IO.monadDefer().invoke { 1 }

As it captures exceptions, invoke() is the simplest way of wrapping existing synchronous APIs.

fun <F> getSongUrlAsync(SC: MonadDefer<F>) =
  SC { getSongUrl() }

val songIO: IOOf<Url> = getSongUrlAsync(IO.monadDefer())
val songDeferred: DeferredKOf<Url> = getSongUrlAsync(DeferredK.monadDefer())

defer

Receives a function returning Kind<F, A>. The instance is responsible of creating and running the returned datatype lazily.

IO.monadDefer().defer { IO.just(1) }

This can be used to wrap synchronous APIs that already return the expected datatype, forcing them to be run lazily.

lazy

Suspends a function returning Unit. Useful in cases like Monad Comprehension where you’d want to defer the start of the comprehension until the datatype is run without needing to use suspend.

val SC = IO.monadDefer()

val result = SC.binding {
  println("Print: now")
  val result = just(1).bind()
  result + 1
}

//Print: now

val lazyResult = SC.binding {
  SC.lazy().bind()
  println("Print: lazy")
  val result = eagerIO().bind()
  result + 1
}

//Nothing here!

lazyResult
  .unsafeRunAsync { }

//Print: lazy

deferUnsafe

Takes as a parameter a function that returns Either<Throwable, A>. The left side of the Either represents an error in the execution. This function is assumed to never throw any internal exceptions.

IO.async()
  .deferUnsafe { throw RuntimeException() }
  .unsafeRunSync()
// ERROR!! The program crashes

deferUnsafe() exists for performance purposes when throwing can be avoided.

Laws

Arrow provides MonadDeferLaws in the form of test cases for internal verification of lawful instances and third party apps creating their own MonadDefer instances.

Data Types

The following data types in Arrow provide instances that adhere to the MonadDefer type class.