-
Notifications
You must be signed in to change notification settings - Fork 17
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 Cats Async, ApplicativeAsk and ArrowChoice instances #13
Changes from 1 commit
1affa4d
883844f
676fe05
cacd020
f093ec3
dd74123
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package arrows.stdlib | ||
|
||
import cats.{ Applicative, StackSafeMonad } | ||
import cats.effect.{ Async, ExitCase } | ||
import cats.arrow.ArrowChoice | ||
import cats.kernel.{ Monoid, Semigroup } | ||
import cats.mtl.ApplicativeAsk | ||
|
||
import scala.concurrent.Promise | ||
import scala.util.control.NonFatal | ||
import scala.util.{ Failure, Success } | ||
|
||
object Cats extends ArrowInstances | ||
|
||
sealed abstract class ArrowInstances extends ArrowInstances1 { | ||
|
||
implicit def catsMonadForArrow[E]: Async[Arrow[E, ?]] = new Async[Arrow[E, ?]] with StackSafeMonad[Arrow[E, ?]] { | ||
|
||
def flatMap[A, B](fa: Arrow[E, A])(f: A => Arrow[E, B]): Arrow[E, B] = | ||
Arrow[E].flatMap(e => fa(e).flatMap(a => f(a)(e))) | ||
|
||
def raiseError[A](e: Throwable): Arrow[E, A] = Arrow.failed[A](e) | ||
|
||
def handleErrorWith[A](fa: Arrow[E, A])(f: Throwable => Arrow[E, A]): Arrow[E, A] = | ||
Arrow[E].flatMap(e => fa.recoverWith { case t: Throwable => f(t)(e) }(e)) | ||
|
||
def pure[A](x: A): Arrow[E, A] = Arrow.successful(x) | ||
|
||
def suspend[A](thunk: => Arrow[E, A]): Arrow[E, A] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Arrows aren't strict so I'm not sure I understand why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's the contract for |
||
Arrow[E].flatMap(e => try { thunk(e) } catch { case NonFatal(t) => Arrow.failed[A](t)(e) }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you don't need to catch here, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, cool thanks! :) |
||
|
||
override def delay[A](thunk: => A): Arrow[E, A] = | ||
Arrow[E].flatMap(e => try { Arrow.successful(thunk) } catch { case NonFatal(t) => Arrow.failed[A](t)(e) }) | ||
|
||
def bracketCase[A, B](acquire: Arrow[E, A])(use: A => Arrow[E, B])(release: (A, ExitCase[Throwable]) => Arrow[E, Unit]): Arrow[E, B] = Arrow[E].flatMap(e => | ||
acquire.flatMap(a => use(a).transformWith { | ||
case Success(b) => release(a, ExitCase.complete)(e).map(_ => b) | ||
case Failure(t) => release(a, ExitCase.error(t))(e).flatMap(_ => Task.failed[B](t)) | ||
}(e))(e)) | ||
|
||
def async[A](k: (Either[Throwable, A] => Unit) => Unit): Arrow[E, A] = Arrow[E].flatMap { _ => | ||
val promise = Promise[A] | ||
|
||
k { | ||
case Left(t) => promise.failure(t) | ||
case Right(a) => promise.success(a) | ||
} | ||
|
||
Task.async(promise.future) | ||
} | ||
|
||
def asyncF[A](k: (Either[Throwable, A] => Unit) => Arrow[E, Unit]): Arrow[E, A] = Arrow[E].flatMap { e => | ||
val promise = Promise[A] | ||
|
||
k { | ||
case Left(t) => promise.failure(t) | ||
case Right(a) => promise.success(a) | ||
}.flatMap(_ => Task.async(promise.future))(e) | ||
} | ||
|
||
override def map[A, B](fa: Arrow[E, A])(f: A => B): Arrow[E, B] = fa.map(f) | ||
} | ||
|
||
implicit val catsArrowChoiceForArrow: ArrowChoice[Arrow] = new ArrowChoice[Arrow] { | ||
def choose[A, B, C, D](f: Arrow[A, C])(g: Arrow[B, D]): Arrow[Either[A, B], Either[C, D]] = | ||
Arrow[Either[A, B]].flatMap { | ||
case Left(a) => f(a).map(Left(_)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The bytecode for this transformation will be large. What do you think about optimizing it? Something like // In a stable scope
private final object ToLeft {
private final val instance: Any => Left[Any] = Left(_)
def apply[T]() = instance.asInstanceOf[T => Left[T]]
}
private final object ToRight {
private final val instance: Any => Right[Any] = Right(_)
def apply[T]() = instance.asInstanceOf[T => Right[T]]
} and then Arrow[Either[A, B]].flatMap { a =>
if(a.isLeft) f(a).map(toLeft)
else f(a).map(toRight)
} |
||
case Right(b) => g(b).map(Right(_)) | ||
} | ||
|
||
def lift[A, B](f: A => B): Arrow[A, B] = Arrow[A].map(f) | ||
|
||
def first[A, B, C](fa: Arrow[A, B]): Arrow[(A, C), (B, C)] = | ||
Arrow[(A, C)].flatMap { case (a, c) => fa(a).map(_ -> c) } | ||
|
||
def compose[A, B, C](f: Arrow[B, C], g: Arrow[A, B]): Arrow[A, C] = g.andThen(f) | ||
} | ||
|
||
implicit def catsApplicativeAskForArrow[E]: ApplicativeAsk[Arrow[E, ?], E] = new ApplicativeAsk[Arrow[E, ?], E] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could it be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can't be a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could cache it and cast: private final val catsApplicativeAskForArrowInstance: ApplicativeAsk[Arrow[Any, ?], Any] ...
implicit def catsApplicativeAskForArrow[E] = catsApplicativeAskForArrowInstance.asInstanceOf[ApplicativeAsk[Arrow[E, ?], E]] |
||
val applicative: Applicative[Arrow[E, ?]] = catsMonadForArrow[E] | ||
|
||
def ask: Arrow[E, E] = Arrow[E] | ||
|
||
def reader[A](f: E => A): Arrow[E, A] = ask.map(f) | ||
|
||
} | ||
|
||
implicit def catsMonoidForArrow[A, B](implicit B0: Monoid[B]): Monoid[Arrow[A, B]] = | ||
new Monoid[Arrow[A, B]] with ArrowSemigroup[A, B] { | ||
implicit def B: Semigroup[B] = B0 | ||
|
||
def empty: Arrow[A, B] = Arrow.successful(Monoid[B].empty) | ||
} | ||
} | ||
|
||
sealed abstract class ArrowInstances1 { | ||
implicit def catsSemigroupForArrow[A, B](implicit B0: Semigroup[B]): Semigroup[Arrow[A, B]] = new ArrowSemigroup[A, B] { | ||
implicit def B: Semigroup[B] = B0 | ||
} | ||
} | ||
|
||
trait ArrowSemigroup[A, B] extends Semigroup[Arrow[A, B]] { | ||
implicit def B: Semigroup[B] | ||
|
||
def combine(x: Arrow[A, B], y: Arrow[A, B]): Arrow[A, B] = | ||
Arrow[A].flatMap(a => x.flatMap(b => y(a).map(b2 => B.combine(b, b2)))(a)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,12 +25,17 @@ lazy val `arrows` = | |
`arrows-stdlib-jvm`, | ||
`arrows-stdlib-js`, | ||
`arrows-twitter`, | ||
`arrows-stdlib-cats-jvm`, | ||
`arrows-stdlib-cats-js`, | ||
`arrows-benchmark` | ||
) | ||
.dependsOn( | ||
`arrows-stdlib-jvm`, | ||
`arrows-stdlib-js`, | ||
`arrows-twitter`, | ||
`arrows-twitter`, | ||
`arrows-stdlib-cats-jvm`, | ||
`arrows-stdlib-cats-js`, | ||
`arrows-benchmark` | ||
) | ||
|
||
|
@@ -50,6 +55,29 @@ lazy val `arrows-stdlib` = | |
lazy val `arrows-stdlib-jvm` = `arrows-stdlib`.jvm | ||
lazy val `arrows-stdlib-js` = `arrows-stdlib`.js.settings(test := {}) | ||
|
||
lazy val `arrows-stdlib-cats` = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the convention for these sub-projects? What is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
crossProject.crossType(superPure) | ||
.settings(commonSettings) | ||
.settings( | ||
crossScalaVersions := Seq("2.12.5"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we are at |
||
name := "arrows-stdlib-cats", | ||
libraryDependencies ++= List( | ||
"org.typelevel" %%% "cats-effect" % "1.0.0-RC3", | ||
"org.typelevel" %%% "cats-effect-laws" % "1.0.0-RC3", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only need |
||
"org.typelevel" %%% "cats-mtl-core" % "0.2.3", | ||
"org.typelevel" %%% "cats-mtl-laws" % "0.2.3", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here too, I think we only need |
||
compilerPlugin("org.spire-math" %%% "kind-projector" % "0.9.7"), | ||
"org.typelevel" %%% "cats-testkit" % "1.2.0" % "test", | ||
), | ||
scoverage.ScoverageKeys.coverageMinimum := 60, | ||
scoverage.ScoverageKeys.coverageFailOnMinimum := false) | ||
.jsSettings( | ||
coverageExcludedPackages := ".*" | ||
).dependsOn(`arrows-stdlib`) | ||
|
||
lazy val `arrows-stdlib-cats-jvm` = `arrows-stdlib-cats`.jvm | ||
lazy val `arrows-stdlib-cats-js` = `arrows-stdlib-cats`.js.settings(test := {}) | ||
|
||
lazy val `arrows-twitter` = project | ||
.settings(commonSettings) | ||
.settings( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this use
Task
instead ofArrow
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that would constraint it unnecessary, An
Arrow[E, A]
is just as much valid as aTask[A]
. Though forEffect
it does need to beTask
, as you can't implementrunAsync
otherwise. ForAsync
we can do it for anyArrow
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arrow is more general than monad, so I don't think it's a good fit to a monad typeclass. Also, the fixed
Unit
input is important for performance. This has much better performance:than
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right yeah, that's why I need to write the
Effect
type forTask
, but we should still provide all the instances that make sense :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, we also have
Async
instances forKleisli[F, R, A]
whereF
has anAsync
instance itself. SinceArrow
is roughly equivalent to that, it stands to reason that we should provide that too :)