scalacats-effect

Given `() => IO[T]`, can I obtain `IO[() => T]`?


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?


Solution

  • 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:

    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:

    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.