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
Should we define instances for () => Future[A] ? #169
Comments
I agree. I don't like it when we shame certain types we don't like even though they may be commonly used. As long as the intended use is lawful, let's do it. |
I agree. I think it carries its weight educationally, if not practically. |
I don't know if the left flatMap law will pass. tailRecM works here, but requiring the left flatmap to pass may require returning something like: case class FlatMap[A, B](fn0: () => Future[A], fmfn: A => (() => Future[B])) extends Function0[Future[B]] {
// trampoline inside apply |
I'm 👎 (mild) why not provide instances for |
We do have those instances for EitherT over Eval, which is basically isomorphic, though. We could probably avoid the stack safety issues @johnynek mentioned if we used |
That sounds dodgier actually. Anyway don't take my opinion as a blocker, I don't really care that much as long as the laws pass. |
providing a Sync instance for |
I know I've been a little silent recently, but I think it's worth weighing in on this one… If the I'm very 👎 on this. If I write a function that expects an It is possible to write a lawful instance for |
Thankfully an instance for |
Ah, I misread! My bad! :-) Sorry about that. |
object Problem {
import cats._, implicits._
import scala.concurrent._
import cats.effect._
type LTask[A] = () => Future[A]
implicit def effInsLT(implicit ec: ExecutionContext): Sync[LTask] =
new Sync[LTask] {
def pure[A](x: A): LTask[A] =
() => Future.successful(x)
def raiseError[A](e: Throwable): LTask[A] =
() => Future.failed(e)
def suspend[A](thunk: => LTask[A]): LTask[A] =
() => Future.successful(()).flatMap(_ => thunk())
def flatMap[A, B](fa: LTask[A])(f: A => LTask[B]): LTask[B] =
() =>
Future.successful(()).flatMap { _ =>
fa().flatMap { a => f(a)() }
}
def tailRecM[A, B](a: A)(f: A => LTask[Either[A, B]]): LTask[B] =
flatMap(f(a)) {
case Left(a) => tailRecM(a)(f)
case Right(b) => pure(b)
}
def handleErrorWith[A](fa: LTask[A])(f: Throwable => LTask[A]): LTask[A] =
() => {
Future.successful(()).flatMap { _ =>
fa().recoverWith {
case err: ExecutionException if err.getCause ne null =>
f(err.getCause)()
case err =>
f(err)()
}
}
}
}
import ExecutionContext.Implicits.global
def foo[F[_]: Sync] = Sync[F].delay { println("hello") } // lawful code
def bar[F[_]: Comonad]: F[_] => Int = fa => {
fa.extract
1
} //lawful code
def baz = bar[Function0] //lawful code
def ooops = baz(foo[LTask]) //lawful code...with side effects!
} scala> Problem.ooops
hello
res1: Int = 1 |
Yeah, |
So OK, the use-case is for people that aren't into FP, abusing Due to being harder to remove things, than it is to add, we'll not do this for now, deferring this to the moment users will begin screaming for a That said, whenever @djspiewak goes silent, I'll just invoke |
Yeah, ironically the Function0 was the problem here. We have elsewhere assumes evaluation of he Function is pure. We shouldn’t change that here. Very arguably Eval has the same issue. I’m on my phone but I think it defines a Comonad as well. |
I've actually implemented this so that I can use tagless-final libraries in |
I think an opaque newtype around |
This is kind of my feeling on this. It's very easy to convert a |
Yes and no. TaglessFinalLibrary.someMethod(
IO.fromFuture(IO(
thisMethodReturnsAFuture()
))
).unsafeToFuture() isn't rocket surgery, but it does bear some explanation compared to TaglessFinalLibrary.someMethod(
() => thisMethodReturnsAFuture()
)() if nothing else, it breaks the flow of a developer who isn't familiar with |
@SystemFw Maybe a new type will solve problem with final class LazyFuture[T](unsafeRun: () => Future[T]) extends AnyVal |
I'm not sure it would be compatible with context propagation based on |
Thread local shenanigans are almost always incompatible with any asynchronous code, regardless of the framework. Under certain circumstances, some tricks can be used to get around it (I did this for Hadoop and Scalaz Task a number of years ago), but it's flaky and bespoke. Honestly, frameworks need to simply stop doing this kind of thing. There's no defense for it (there are better alternatives), and the only thing that makes this sort of thing work with async is Loom, and even then only if used in a certain way. |
@djspiewak a little off topic, but
any suggestions? I've been using monix local which works... sometimes. |
Depends on what you're doing. Threading fiber state is literally just The broader observation is that we have an effect context, and we can use that to do fancy things; we don't have to rely on automagical things like locals. |
Sync
,Async
andEffect
instances are possible for() => Future[A]
.Should we provide them?
In fact we have such instances used in testing, see:
https://github.com/typelevel/cats-effect/blob/v0.10/laws/shared/src/test/scala/cats/effect/LTask.scala
I believe we should.
The text was updated successfully, but these errors were encountered: