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

WIP: Support for fs2.Task dropping own Catchable #323

Merged
merged 10 commits into from
Sep 6, 2016

Conversation

guersam
Copy link
Contributor

@guersam guersam commented Aug 12, 2016

An attempt to fix #311.

Although I didn't mean to touch all these stuff at first, eventually I ended up with dropping doobie.util.catchable.Catchable in favor of fs.util.Catchable fighting a lot of ambiguous implicit errors. But probably there would be a much simpler way...

Changes made (which can be reverted) or things to discuss:

  • Replace most cats compat utils with fs2-cats
  • Replace cats.data.Xor with scala.util.Either to support fs2.util.Catchable (related discussion: data.Xor and instances/either.scala: Either is now right-biased cats#1192)
  • Use fs2.util.Suspendable instead of Capture[fs2.Task]
  • Or revive doobie.util.catchable.Catchable and directly support only for fs2.Task?
  • Drop IOLite?
  • Move fs2.util.Suspendable instance for cats.data.Kleisli into fs2-cats
  • Decide which typeclasses (especially Monad) to support, of cats, or of fs2?

TODO:

  • Align yax directives in a consistent way
  • Update docs

| implicit val Catchable${sname}IO: Catchable[${sname}IO] =
| new Catchable[${sname}IO] {
| def attempt[A](f: ${sname}IO[A]): ${sname}IO[Throwable \\/ A] = ${sname.toLowerCase}.attempt(f)
| def fail[A](err: Throwable): ${sname}IO[A] = ${sname.toLowerCase}.delay(throw err)
| }
|#-scalaz
|#+cats
|#+fs2
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think cats directive is unnecessary here. Right?

@guersam
Copy link
Contributor Author

guersam commented Aug 12, 2016

The second commit is slightly out of scope. I just found unnecessary diffs caused by varying method order with environments annoying.

@guersam guersam changed the title WIP: Support fs2.util.{Catchable, Task} dropping own Catchable WIP: Support for fs2.Task dropping own Catchable Aug 12, 2016
@tpolecat
Copy link
Member

Yeah the ever-changing codegen is irritating. I will look at this more closely this weekend. It seems like a big change but it certainly makes sense to get rid of our own Catchable if one is provided by fs2.

@guersam
Copy link
Contributor Author

guersam commented Aug 15, 2016

I have been struggling to combine f.u.Catchable and d.u.Capture into f.u.Effect, but got no clue to implement def unsafeRunAsync[A](fa: F[A])(cb: Attempt[A] => Unit): Unit for Free algebras and Kleisli[M[_]: Effect, E, A]. Is it possible at all?

@mpilquist
Copy link
Member

mpilquist commented Aug 15, 2016

FWIW, I think we could introduce the following to FS2:

  trait Suspendable[F[_]] extends Monad[F] {
    def suspend[A](fa: => F[A]): F[A]
    def delay[A](a: => A): F[A]
  }

  trait Effect[F] extends Catchable[F] with Suspendable[F] {
    def unsafeRunAsync[A](fa: F[A])(cb: Attempt[A] => Unit): Unit
  }

@guersam
Copy link
Contributor Author

guersam commented Aug 15, 2016

@mpilquist That's perfect, I managed to write a modified version having every unsafeRunAsync to be ???, which somehow passes all tests. Suspendable must work with it out of the box.

@tpolecat
Copy link
Member

@guersam thanks for your continued hacking on this. I didn't have any time over the weekend to get into it but I will catch up soon.

@guersam
Copy link
Contributor Author

guersam commented Aug 15, 2016

@tpolecat No problem, and sorry about some inessential diffs which will make the review harder. As noted above I didn't expect the changes to be this huge 😨

mpilquist added a commit to mpilquist/fs2 that referenced this pull request Aug 16, 2016
This PR moves `suspend` and `delay` from the `Effect` type class to a
new `Suspendable` type class. There are two motivations for doing this:

 - Exposing `unsafeRunAsync` to any function that requires `suspend`
   or `delay` gives up a lot of parametricity.

 - Some type constructors have `Suspendable` instances but do not
   support value extraction. This came up in the [port of Doobie to
   FS2](typelevel/doobie#323 (comment)).

This [came up on Gitter a while back as
well](http://www.gitterforum.com/discussion/scalaz-scalaz-stream?page=143).
@tpolecat
Copy link
Member

Ok, so here are a few thoughts.

Drop IOLite?

This is really irritating because scalaz Task has .unsafePerformSync and fs2 has .unsafeRunSync which means every exec in the book (and there are a lot) needs a yax directive.

Move fs2.util.Suspendable instance for cats.data.Kleisli into fs2-cats

👍

Decide which typeclasses (especially Monad) to support, of cats, or of fs2?

My instinct is to support only cats typeclasses, with the exception of fs2's Suspendable and Catchable which have no equivalent in cats (yet ... they're talking about it).


So I'm wondering if this is too much to do for 0.3.1 … I was kind of thinking of just providing instances for fs2 Task and calling it good for now.

@guersam
Copy link
Contributor Author

guersam commented Aug 20, 2016

Sounds reasonable. A little thought about cats/fs2 typeclass support:

  1. From usability perspective, when dealing with ConnectionIO direct support for cats typeclasses would usually be the better bet.
  2. Regarding maintainability, fs2 typeclasses will give us more consistency if we eventually will replace scalaz-stream with fs2 (compile for scalaz+fs2? #310).
  3. I'm not sure about performance, because that whichever we choose to support for, it involves typeclass conversion anyway. I guess (and hope) the effect will be negligible though.

Breaking the changes smaller for 0.3.1 will also be fine. Although there's no official release for cats/fs2 yet, there's good reason for keep migration steps small enough. In my case, there are actually no big differences since I'm not using the APIs lower than ConnectionIO directly.

@guersam
Copy link
Contributor Author

guersam commented Aug 20, 2016

BTW, is there a reason for the lack of automatic Free generation for postgres classes and jdbc drivermanager? I found it a bit tiring modifying all of them by hand, even for a small change.

@tpolecat
Copy link
Member

is there a reason for the lack of automatic Free generation for postgres classes and jdbc drivermanager?

I think there was a reason but I don't remember. Opened #329 to follow up.

So for 0.3.1 maybe do the following and push off generalizing support for fs2 typeclasses

  • Update to latest cats/fs2 (new cats out later tonight)
  • Add doobie Catchable and Capture instances for fs2 Task

I really doubt we will get any actual cats+fs2 users since both libraries are in so much flux at the moment, so I think we have time to generalize later. I'd just like to get a release out that people can play with.

What do you think?

@guersam
Copy link
Contributor Author

guersam commented Aug 21, 2016

Agreed. I'm willing to port to my scalaz-stream codebase to cats/fs2 to get rid of mixed imports from scalaz and cats. I'll handle #329 first and then add missing instances for fs2.Task.

@guersam
Copy link
Contributor Author

guersam commented Aug 22, 2016

After second trial, I still find it harder than expected to add a few fs2.Task instances, because that fs2.util.Catchable is already a monad so that it always ends up with dropping own doobie.util.Catchable and touch here and there to avoid ambiguous/diverging implicit caused by the compatibility utils.

In this nature, it's getting more and more natural to me to fully support fs2.util.Suspendable from the initial cats/fs2 release, but if it feels like a too big change for 0.3.1 I'm okay to wait for 0.4.0-M1.

@tpolecat
Copy link
Member

Ok, I will take a look tomorrow and see if I run into the same issue.

@guersam
Copy link
Contributor Author

guersam commented Aug 23, 2016

Latest build failures seem like the cache problem anticipated in #318. @tpolecat Could you flush the cache for this PR once?

@tpolecat
Copy link
Member

I don't know how to flush the cache but I restarted that build.

@guersam
Copy link
Contributor Author

guersam commented Aug 24, 2016

Cache settings will appear in the travis repo page: https://docs.travis-ci.com/user/caching/#Clearing-Caches

Tried to do this by myself but I don't have the permission. Wish that travis allows PR openers to clear their PR specific cache.

@tpolecat
Copy link
Member

Ah ok, thanks. Cleared and restarted the build.

@tpolecat
Copy link
Member

Looks like it worked.

@guersam
Copy link
Contributor Author

guersam commented Aug 24, 2016

Thanks :)

On Wed, Aug 24, 2016, 9:14 AM Rob Norris notifications@github.com wrote:

Looks like it worked.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#323 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AA7JoECgWif7cLoY3eBI9SMaMvUJ722uks5qi4z3gaJpZM4Ji1m1
.

@guersam guersam mentioned this pull request Sep 2, 2016
@@ -152,17 +154,17 @@ class FreeGen(managed: List[Class[_]], log: Logger) {
// Ctor values for all methods in of A plus superclasses, interfaces, etc.
def ctors[A](implicit ev: ClassTag[A]): List[Ctor] =
methods(ev.runtimeClass).groupBy(_.getName).toList.flatMap { case (n, ms) =>
ms.toList.zipWithIndex.map {
ms.toList.sortBy(_.getGenericParameterTypes.map(toScalaType).mkString(",")).zipWithIndex.map {
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for this change, I should have done this a long time ago. Stuff was getting shuffled around every time I ran freegen.

@tpolecat
Copy link
Member

tpolecat commented Sep 5, 2016

Ok, I think this looks great. Huge amount of work. I'm fine with foldMapUnsafe for now; we can add the other constraint later if we want to.

Is there anything else that needs to be done in this PR before merging? I'd prefer to get this in and then follow up as needed rather than keep this open longer. What do you think?

@adelbertc
Copy link
Contributor

I would hold off on adding the RecursiveTailRecM stuff, I'm becoming convinced that maybe we should just have it on Monad proper. I don't really like the marker trait and reading about the experience here made me reopen this typelevel/cats#1278

@tpolecat
Copy link
Member

tpolecat commented Sep 5, 2016

@adelbertc thanks, that was my instinct too. Given the volatility we should do the simplest thing that works.

@guersam
Copy link
Contributor Author

guersam commented Sep 5, 2016

Although some docs (especially the book) should be updated to catch up these changes, I'd like merging this for now too.

@tpolecat
Copy link
Member

tpolecat commented Sep 6, 2016

Alright let's do it.

@tpolecat tpolecat merged commit f7bcbce into typelevel:series/0.3.x Sep 6, 2016
@macalinao
Copy link

How does this work with monix.Task? The docs mention this briefly but I get the following error:

[error] could not find implicit value for evidence parameter of type fs2.util.Catchable[monix.eval.Task]
[error]     DriverManagerTransactor[Task](

@adelbertc
Copy link
Contributor

@macalinao You will need to define an instance of Catchable[monix.eval.Task]. Catchable looks like:

trait Catchable[F[_]] extends Monad[F] {
  /** Lifts a pure exception in to the error mode of the `F` effect. */
  def fail[A](err: Throwable): F[A]
  /** Provides access to the error in `fa`, if present, by wrapping it in a `Left`. */
  def attempt[A](fa: F[A]): F[Attempt[A]]
}

@tpolecat
Copy link
Member

tpolecat commented Mar 12, 2017

You'll need instancs of fs2.util.Catchable and fs2.util.Suspendable (or scalaz.Catchable and doobie.util.Capture if you're using the scalaz version of doobie).

@macalinao
Copy link

macalinao commented Mar 12, 2017

I see, thanks. For future readers, here's my typeclass:

  implicit object taskCatchable extends Catchable[Task] with Suspendable[Task] {
    def pure[A](a: A): Task[A] = Task.pure(a)

    def flatMap[A, B](a: Task[A])(f: A => Task[B]): Task[B] =
      a.flatMap(f)

    def fail[A](err: Throwable): Task[A] =
      Task.raiseError(err)

    def attempt[A](fa: Task[A]): Task[Attempt[A]] =
      fa.materialize.map(_.toEither)

    def suspend[A](fa: => Task[A]): Task[A] =
      Task.suspend(fa)
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants