According to the cats official document: https://typelevel.org/cats-effect/typeclasses/liftio.html, if we want lift something from IO to other container, you should implement LiftIO trait, but the example explicitly run unsafeRunXXX
method to fetch out the effect, I'm wondering is it the only way for the transformation?
IO
is suspending side-effects, and its type tells you what value you'll get if you run all computations (both side-effectful and pure), if there will be no error to throw in the end (they can be handled along the way).
As such, it is impossible to obtain the value without running the calculation. So, basically any useful translation from IO[A]
to F[A]
would have to call some .unsafeXXX
somewhere. The unsafe
part doesn't mean, that you shouldn't use it - it means that you need to know what you are doing as the moment you run it, the result returned will be side-effecting, can fail and in general you give up on referential transparency.
That it why this it used in internally in IOApp
(it uses it at the end of the world, where you want to have your results computed). You can also translate IO
into another F
without loosing referential transparency, if you know that this F
:
So basically, it is another implementation of the same concept - see: SyncIO
, Coeval
, Task
, ZIO...
// example: Async can be used to translate IO into F
def IO2F[F[_]: Async]: IO ~> F = new (IO ~> F) {
def apply[A](ioa: IO[A]): F[A] = Async[F].async(ioa.unsafeRunAsync)
}
You could also do things like IO[A] => Either[Throwable, A]
or IO[A] => Option[A]
, or IO[A] => Future[A]
but then you have to remember that each time you run such evaluation, you are starting side effects immediately.
Long story short: of you want to translate IO
into something else, you have to use some .unsafeXXX
somewhere and this is not bad, unsafe
is just a reminder that you have to be careful, that's it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With