I have an object
object Producer {
def apply() :IO[Foo] = ???
}
It is in code I do not control. I am composing (flat mapping) several functions returning IO
. One of them hides a Ref
, and exposes update
method compatible with it:
object State {
def update(update: Bar => Bar) :IO[Bar] = ???
}
This one I do control, although introducing significant changes would cause a large refactor.
Problem:
for {
bar <- State.update { arg => ??? }
} yield ()
In some cases, but not all, I need a Foo
to produce a new Bar
inside the lambda passed to State.update
.
Can it be done without always evaluating foo <- Producer
as the first step?
One way of thinking of IO[A]
is that it is () => A
(or () => Future[A]
).
So () => IO[T]
and IO[() => T]
are kinda the same idea, just with a different ergonomics (and different ways of evaluating what's inside). So you should be able to go from one into another and back e.g. with:
val a: () => IO[A] = ...
val b: IO[() => A] = IO { () => a.runUnsafeSync }
val c: IO[() => A] = ...
val d: () => IO[A] = () => c.map(_())
However, the devil is in the details:
runUnsafeSync
will not propagate cancellation from the outer IO to the inner IOIOLocal
so making a bullet-proof conversion this way would be... rather difficult.
And hardly ever it would be useful as with ergonomics there comes a convention:
IO[A]
suggest a side-effect (or at least a possibility of it)X => non-IO
suggest pure computation - take X
, then return sth without mutation nor talking to external world() =>
provides no new information, so such pure function would have to be constant (it might be a lazy value basically)so if we followed the convention, we could convert fa: () => IO[A]
into IO[() => A]
by doing
IO.defer {
fa()
}.map { a =>
// a is a constant value, so function we created is constant
// and everything that obtains it through e.g. map/flatMap
// would get the same value no matter how many times it would be called.
// However, evaluation whole IO twice could result in 2 functions
// returning different constant values.
() => a
}
It should make the conversion trivial, but it would also mean that all these () =>
in the context of IO[A]
are kinda pointless. Unless you need to do them to adapt your code to some external interface which expects () => F[A]
or F[() => A]
, it's just noise.