Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IO#to conversion method #50

Merged
merged 7 commits into from
May 12, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/shared/src/main/scala/cats/effect/Effect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@ trait Effect[F[_]] extends Async[F] with LiftIO[F] {
})
}
}

override def liftIO[A](ioa: IO[A]): F[A] = {
// Implementation for `IO#to` depends on the `Async` type class,
// and not on `Effect`, so this shouldn't create a cyclic dependency
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's being overridden anyway.

ioa.to[F](this)
}
}
17 changes: 16 additions & 1 deletion core/shared/src/main/scala/cats/effect/IO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package effect

import cats.effect.internals.{AndThen, IOPlatform, NonFatal}
import scala.annotation.tailrec
import scala.annotation.unchecked.{uncheckedVariance => uV}
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.concurrent.duration._
import scala.util.{Left, Right}
Expand Down Expand Up @@ -380,6 +381,20 @@ sealed abstract class IO[+A] {
p.future
}

/**
* Converts the source `IO` into any `F` type that implements
* the [[cats.effect.Async Async]] type class.
*/
final def to[F[_]](implicit F: cats.effect.Async[F]): F[A @uV] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor sidebar: I would rather just write out the whole @uncheckedVariance here. I don't expect to use the annotation elsewhere, so the value of the alias is minimal and IMO not worth the obfuscation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, will fix.

F.suspend {
unsafeStep match {
case Pure(a) => F.pure(a)
case RaiseError(ex) => F.raiseError(ex)
case async =>
F.async(async.unsafeRunAsync)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should keep threading suspends, rather than just immediately jumping to async. It should be possible to convert an entirely synchronous IO in terms of entirely synchronous F constructions, even if the IO involves flatMap and suspend.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean. Doesn't unsafeStep eliminate Suspend and BindSuspend?

From what I've seen that branch is hit only if we have an Async or BindAsync state.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I just realized you're using unsafeStep. That branch will only be hit at an async point.

I guess it's sort of an open question whether it's better to collapse bind chains or carry them forward into the structure of the target monad. I was originally thinking of the latter implementation, but the former is possible. Both should be lawful.

}
}

override def toString = this match {
case Pure(a) => s"IO($a)"
case RaiseError(e) => s"IO(throw $e)"
Expand Down Expand Up @@ -426,7 +441,7 @@ private[effect] trait IOInstances extends IOLowPriorityInstances {

override def shift[A](ioa: IO[A])(implicit ec: ExecutionContext) = ioa.shift

def liftIO[A](ioa: IO[A]) = ioa
override def liftIO[A](ioa: IO[A]) = ioa
}

implicit def ioMonoid[A: Monoid]: Monoid[IO[A]] = new IOSemigroup[A] with Monoid[IO[A]] {
Expand Down
3 changes: 1 addition & 2 deletions laws/shared/src/test/scala/cats/effect/IOTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ object IOTests {
/** Implementation for testing default methods. */
val ioEffectDefaults = new Effect[IO] {
private val ref = implicitly[Effect[IO]]

def async[A](k: ((Either[Throwable, A]) => Unit) => Unit): IO[A] =
ref.async(k)
def raiseError[A](e: Throwable): IO[A] =
Expand All @@ -373,7 +374,5 @@ object IOTests {
ref.runAsync(fa)(cb)
def suspend[A](thunk: =>IO[A]): IO[A] =
ref.suspend(thunk)
def liftIO[A](ioa: IO[A]): IO[A] =
ref.liftIO(ioa)
}
}