{"action":"created","comment":{"url":"https://api.github.com/repos/typelevel/cats-effect/pulls/comments/170480748","pull_request_review_id":99169921,"id":170480748,"diff_hunk":"@@ -19,41 +19,151 @@ package effect\n \n import simulacrum._\n import cats.data.{EitherT, OptionT, StateT, WriterT}\n-import cats.effect.internals.Callback\n import scala.annotation.implicitNotFound\n-import scala.concurrent.ExecutionContext\n import scala.util.Either\n \n /**\n- * A monad that can describe asynchronous or synchronous computations that\n- * produce exactly one result.\n+ * A monad that can describe asynchronous or synchronous computations\n+ * that produce exactly one result.\n+ *\n+ * ==On Asynchrony==\n+ *\n+ * An asynchronous task represents logic that executes independent of\n+ * the main program flow, or current callstack. It can be a task whose\n+ * result gets computed on another thread, or on some other machine on\n+ * the network.\n+ *\n+ * In terms of types, normally asynchronous processes are represented as:\n+ * {{{\n+ *   (A => Unit) => Unit\n+ * }}}\n+ *\n+ * This signature can be recognized in the \"Observer pattern\" described\n+ * in the \"Gang of Four\", although it should be noted that without\n+ * an `onComplete` event (like in Rx Observable pattern) you can't\n+ * detect completion in case this callback can be called zero or\n+ * multiple times.\n+ *\n+ * Some abstractions allow for signaling an error condition\n+ * (e.g. `MonadError` data types), so this would be a signature\n+ * that's closer to Scala's `Future#onComplete`:\n+ * {{{\n+ *   (Either[Throwable, A] => Unit) => Unit\n+ * }}}\n+ *\n+ * And many times the abstractions built to deal with asynchronous tasks\n+ * also provide a way to cancel such processes, to be used in race\n+ * conditions in order to cleanup resources early:\n+ * {{{\n+ *   (A => Unit) => Cancelable\n+ * }}}\n+ *\n+ * This is approximately the signature of JavaScript's `setTimeout`,\n+ * which will return a \"task ID\" that can be used to cancel it.\n+ *\n+ * ==Async Type-class==\n+ *\n+ * This type-class allows the modeling of data types that:\n+ *\n+ *  1. can start asynchronous processes\n+ *  2. emit exactly one result on completion\n+ *  3. can end in error\n+ *  4. are optionally cancellable\n+ *\n+ * Therefore the signature exposed by the\n+ * [[Async!.cancelable cancelable]] builder is this:\n+ * {{{\n+ *   (Either[Throwable, A] => Unit) => F[Unit]\n+ * }}}\n+ *\n+ * `F[Unit]` is used to represent a cancellation action which\n+ * will send a signal to the producer, that may observe it and\n+ * cancel the asynchronous process.\n+ *\n+ * But it can also model simple asynchronous processes that\n+ * cannot be cancelled, via the [[Async!.async async]] builder:\n+ * {{{\n+ *   (Either[Throwable, A] => Unit) => Unit\n+ * }}}\n+ *\n+ * The ability to cancel the async process is optional. For\n+ * `F` data types that cannot be cancelled, the `cancelable`\n+ * builder should be equivalent to this:\n+ * \n+ * {{{\n+ *   Async[F].cancelable { cb =>\n+ *     k(cb)\n+ *     IO.unit\n+ *   }\n+ * }}}\n  */\n @typeclass\n @implicitNotFound(\"\"\"Cannot find implicit value for Async[${F}].\n Building this implicit value might depend on having an implicit\n s.c.ExecutionContext in scope, a Strategy or some equivalent type.\"\"\")\n trait Async[F[_]] extends Sync[F] with LiftIO[F] {\n-\n   /**\n-   * Creates an `F[A]` instance from a provided function\n-   * that will have a callback injected for signaling the\n-   * final result of an asynchronous process.\n+   * Creates a simple, non-cancelable `F[A]` instance that\n+   * executes an asynchronous process on evaluation.\n+   *\n+   * The given function is being injected with a side-effectful\n+   * callback for signaling the final result of an asynchronous\n+   * process.\n+   *\n+   * In case of cancellable `F` data types, this should be\n+   * equivalent with:\n+   *\n+   * {{{\n+   *   Async[F].cancelable { cb =>\n+   *     k(cb)\n+   *     IO.unit\n+   *   }\n+   * }}}\n    *\n    * @param k is a function that should be called with a\n    *       callback for signaling the result once it is ready\n    */\n   def async[A](k: (Either[Throwable, A] => Unit) => Unit): F[A]\n \n   /**\n-   * @see [[IO#shift]]\n+   * Creates a cancelable `F[A]` instance that executes an\n+   * asynchronous process on evaluation.\n+   *\n+   * This builder accepts a registration function that is\n+   * being injected with a side-effectful callback, to be called\n+   * when the asynchronous process is complete with a final result.\n+   *\n+   * The registration function is also supposed to return\n+   * an `IO[Unit]` that captures the logic necessary for\n+   * cancelling the asynchronous process, for as long as it\n+   * is still active.\n+   *\n+   * In case of non-cancellable `F` data types, the returned\n+   * `IO[Unit]` can be ignored.\n+   *\n+   * Example:\n+   *\n+   * {{{\n+   *   import java.util.concurrent.ScheduledExecutorService\n+   *   import scala.concurrent.duration._\n+   *\n+   *   def sleep[F[_]](d: FiniteDuration)\n+   *     (implicit F: Async[F], ec: ScheduledExecutorService): F[A] = {\n+   *\n+   *     F.cancelable { cb =>\n+   *       // Note the callback is pure, so we need to trigger evaluation\n+   *       val run = new Runnable { def run() = cb(Right(())).unsafeRunSync }\n+   *\n+   *       // Schedules task to run after delay\n+   *       val future = ec.schedule(run, d.length, d.unit)\n+   *\n+   *       // Cancellation logic, suspended in IO\n+   *       IO(future.cancel())\n+   *     }\n+   *   }\n+   * }}}\n    */\n-  def shift(implicit ec: ExecutionContext): F[Unit] = {\n-    async { (cb: Either[Throwable, Unit] => Unit) =>\n-      ec.execute(new Runnable {\n-        def run() = cb(Callback.rightUnit)\n-      })\n-    }\n-  }\n+  def cancelable[A](k: (Either[Throwable, A] => Unit) => IO[Unit]): F[A]","path":"core/shared/src/main/scala/cats/effect/Async.scala","position":171,"original_position":164,"commit_id":"9633ed62ea50486b337c61af0e8377aed4e74159","original_commit_id":"6a14b0d7e6ef35463aec4db9953bfcbc910f4c1f","user":{"login":"johnynek","id":67958,"avatar_url":"https://avatars0.githubusercontent.com/u/67958?v=4","gravatar_id":"","url":"https://api.github.com/users/johnynek","html_url":"https://github.com/johnynek","followers_url":"https://api.github.com/users/johnynek/followers","following_url":"https://api.github.com/users/johnynek/following{/other_user}","gists_url":"https://api.github.com/users/johnynek/gists{/gist_id}","starred_url":"https://api.github.com/users/johnynek/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/johnynek/subscriptions","organizations_url":"https://api.github.com/users/johnynek/orgs","repos_url":"https://api.github.com/users/johnynek/repos","events_url":"https://api.github.com/users/johnynek/events{/privacy}","received_events_url":"https://api.github.com/users/johnynek/received_events","type":"User","site_admin":false},"body":"I'm not excited about cancellation being optional, but also always on Async. Can we not have another layer? Either use AsyncStart or AsyncCancelable?","created_at":"2018-02-26T02:03:48Z","updated_at":"2018-02-26T02:06:24Z","html_url":"https://github.com/typelevel/cats-effect/pull/126#discussion_r170480748","pull_request_url":"https://api.github.com/repos/typelevel/cats-effect/pulls/126","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/typelevel/cats-effect/pulls/comments/170480748"},"html":{"href":"https://github.com/typelevel/cats-effect/pull/126#discussion_r170480748"},"pull_request":{"href":"https://api.github.com/repos/typelevel/cats-effect/pulls/126"}},"in_reply_to_id":170434377},"pull_request":{"url":"https://api.github.com/repos/typelevel/cats-effect/pulls/126","id":171195061,"html_url":"https://github.com/typelevel/cats-effect/pull/126","diff_url":"https://github.com/typelevel/cats-effect/pull/126.diff","patch_url":"https://github.com/typelevel/cats-effect/pull/126.patch","issue_url":"https://api.github.com/repos/typelevel/cats-effect/issues/126","number":126,"state":"open","locked":false,"title":"Type-class hierarchy changes for cancelation","user":{"login":"alexandru","id":11753,"avatar_url":"https://avatars2.githubusercontent.com/u/11753?v=4","gravatar_id":"","url":"https://api.github.com/users/alexandru","html_url":"https://github.com/alexandru","followers_url":"https://api.github.com/users/alexandru/followers","following_url":"https://api.github.com/users/alexandru/following{/other_user}","gists_url":"https://api.github.com/users/alexandru/gists{/gist_id}","starred_url":"https://api.github.com/users/alexandru/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexandru/subscriptions","organizations_url":"https://api.github.com/users/alexandru/orgs","repos_url":"https://api.github.com/users/alexandru/repos","events_url":"https://api.github.com/users/alexandru/events{/privacy}","received_events_url":"https://api.github.com/users/alexandru/received_events","type":"User","site_admin":false},"body":"This PR is supposed to come after https://github.com/typelevel/cats-effect/pull/121 is merged.\r\n\r\nChanges:\r\n\r\n1. modified `Async` to be able to express `IO.cancelable`\r\n2. adds `AsyncStart` to be able to express `IO#start`\r\n3. modifies `Effect` to inherit `AsyncStart` and to be able to express `runCancelable`\r\n4. adds `runCancelable` on `IO` as the safe analogue to `unsafeRunCancelable`\r\n5. removes `Async#shift` because it's lawless, moved to the `Async` companion as a simple utility (don't see any potential for optimizations depending on `F`)\r\n6. fixes #127, fixes #128, fixes #129\r\n\r\nDiagram (accounting for `Bracket` in #113):\r\n\r\n<img src=\"https://user-images.githubusercontent.com/11753/36636630-eabe5058-19d2-11e8-949e-e5ae75b6898f.png\" width=200>\r\n\r\n## FAQ\r\n\r\n### Do we want cancellation for every Effect?\r\n\r\nNo, but the idea I'm having here is that cancellation is basically a no-op for `Effect` data types  that are not capable of cancellation. All the provided laws thus far take this into account. And for example we don't test that the provided cancellation token is actually triggered but we do test that:\r\n\r\n1. triggering the token returned by `runCancellable` can be done synchronously (with `.unsafeRunSync`)\r\n2. `start.flatMap(_.cancel)` never back-pressures, being always equivalent with `IO.unit`\r\n3. the result of triggering that token on `runCancellable` is triggering the same side effect as `start(fa).flatMap(_.cancel)`\r\n\r\nTo me this is a reasonable set of laws. And even if the reference `IO` no longer behaves like a non-cancellable effect, in the tests suite I introduced this type, which is tested against all laws of `Effect`:\r\n\r\n```scala\r\ntype LTask[+A] = ExecutionContext => Future[A]\r\n```\r\n\r\nSee [implementation](https://github.com/typelevel/cats-effect/pull/126/files#diff-e7d0839e02ddc830027ae3673ca1506f).\r\n\r\nPS: I found bugs #127, #128 and #129 this way 😉\r\n\r\n### How is polymorphic code impacted?\r\n\r\nThis is where it gets tricky. Normally with Monix's `Task` and the upcoming `IO` we can express this operation safely, because the returned task is cancelable and so we can cleanup resources in race conditions:\r\n\r\n```scala\r\ndef sleep(timespan: FiniteDuration): F[Unit]\r\n```\r\n\r\nHowever, with the non-cancelable `IO` that we have on master, users need to choose either to:\r\n\r\n1. block indefinitely (e.g. current implementation of `parMap2` for `IO`)\r\n2. have resource leakage (e.g. what usually happens with Scala's `Future` in common use by beginners)\r\n3. expose a cancellation token\r\n\r\nSo what you get in solutions like FS2 is actually a tuple that can be used to block on the result, or to cancel it:\r\n\r\n```scala\r\ndef sleep(timespan: FiniteDuration): F[(F[Unit], F[Unit])]\r\n```\r\n\r\nThis is actually no different than what we get with `start`, that result having the shape of our new `Fiber`:\r\n\r\n```scala\r\ndef sleep(timespan: FiniteDuration): F[Fiber[F, Unit]]\r\n```\r\n\r\nBut unfortunately we can't go from `F[A]` to `F[Fiber[F, A]]` unless `F` is cancelable. So polymorphic code that needs to be safe will have to return that `Fiber` result *everywhere* it matters and to bind it around in order to combine it with other fibers.\r\n\r\nAnd then the problem is that race conditions, like `parMap2` when one of the tasks is a failure, might choose to block for both tasks due to safety reasons, so there are instances in which, even if you have a `Fiber` in your hand, you can't actually use it. Ditto, you can't describe a safe `IO.race` without that `IO` being cancelable and you no longer have `Fiber` results that you can use. \r\n\r\nAnother problem is that a `CancelableAsync <: Async` probably violates Liskov's substitution principle.\r\n\r\n### Why is Async.cancelable not on AsyncStart?\r\n\r\nBecause the difference between `cancelable` and `start` is precisely the difference between `async` and `runAsync`.  It's basically a different level. You can't express `start` in terms of `cancelable`.\r\n\r\nYou can express `start` in terms of `cancelable` + `runCancelable` + some knowledge about the data type (as can be see by the implementations of `EitherT`, `StateT` and `WriterT`). \r\n\r\nAnd actually you can have a generic `start` in terms of `cancelable` + `runCancelable`, but it can lose info when it comes to these monad transformers — but that's besides the point, given that all implementations worked, that's good enough for `Effect <: AsyncStart`.\r\n\r\n### Why isn't AsyncStart embedded in Effect?\r\n\r\nBecause Monix's `Task` can express `start` without a `Scheduler`, but it needs a `Scheduler` for `runAsync` / `runCancelable`.\r\n\r\nThis highlights the fact that `start` is less powerful, sort of like a lighter version of `runAsync` / `runCancelable`.\r\n\r\n### Should we provide different type-classes?\r\n\r\nWe could provide a different path. But we would need both `Async` and `Effect`. And that may be overkill. Just to make this clear, here's how the hierarchy would look like:\r\n\r\n<img src=\"https://user-images.githubusercontent.com/11753/36636653-66bc0254-19d3-11e8-9b6c-b370506b8c99.png\" width=200>\r\n\r\nThis hierarchy seems too complex to me IMO.\r\n\r\nAnd here's the thing — if the reference `cats.effect.IO` is going to be cancelable and Monix's `Task` already is cancelable and the upcoming Scalaz 8 `IO` is cancelable, then we are left without data types that are not cancelable.\r\n\r\nAnd the big consumers of these type classes, e.g. Monix, FS2, Http4s, etc. will get optimized for cancelability, given the reference `IO` is cancelable. And this changes signatures. See my thoughts on \"*how is polymorphic code impacted?*\".","created_at":"2018-02-24T20:04:20Z","updated_at":"2018-02-26T02:06:24Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9b0639a2f448976971c245fa2b878446d0816af1","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":847764952,"url":"https://api.github.com/repos/typelevel/cats-effect/labels/breaking%20change","name":"breaking change","color":"333333","default":false},{"id":579526313,"url":"https://api.github.com/repos/typelevel/cats-effect/labels/enhancement","name":"enhancement","color":"84b6eb","default":true}],"milestone":{"url":"https://api.github.com/repos/typelevel/cats-effect/milestones/2","html_url":"https://github.com/typelevel/cats-effect/milestone/2","labels_url":"https://api.github.com/repos/typelevel/cats-effect/milestones/2/labels","id":3141377,"number":2,"title":"0.10","description":"The highlight of this release will be the cancelable `IO` along with the required type class changes.\r\n\r\nSetting the deadline to one month from now because:\r\n\r\n1. we should not drag this on forever\r\n2. would like this to be tested and in the hands of users at ScalaDays and the Typelevel Summit from Berlin, in May","creator":{"login":"alexandru","id":11753,"avatar_url":"https://avatars2.githubusercontent.com/u/11753?v=4","gravatar_id":"","url":"https://api.github.com/users/alexandru","html_url":"https://github.com/alexandru","followers_url":"https://api.github.com/users/alexandru/followers","following_url":"https://api.github.com/users/alexandru/following{/other_user}","gists_url":"https://api.github.com/users/alexandru/gists{/gist_id}","starred_url":"https://api.github.com/users/alexandru/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexandru/subscriptions","organizations_url":"https://api.github.com/users/alexandru/orgs","repos_url":"https://api.github.com/users/alexandru/repos","events_url":"https://api.github.com/users/alexandru/events{/privacy}","received_events_url":"https://api.github.com/users/alexandru/received_events","type":"User","site_admin":false},"open_issues":3,"closed_issues":1,"state":"open","created_at":"2018-02-25T10:24:33Z","updated_at":"2018-02-25T10:50:37Z","due_on":"2018-03-26T07:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/typelevel/cats-effect/pulls/126/commits","review_comments_url":"https://api.github.com/repos/typelevel/cats-effect/pulls/126/comments","review_comment_url":"https://api.github.com/repos/typelevel/cats-effect/pulls/comments{/number}","comments_url":"https://api.github.com/repos/typelevel/cats-effect/issues/126/comments","statuses_url":"https://api.github.com/repos/typelevel/cats-effect/statuses/9633ed62ea50486b337c61af0e8377aed4e74159","head":{"label":"alexandru:cancel-types1","ref":"cancel-types1","sha":"9633ed62ea50486b337c61af0e8377aed4e74159","user":{"login":"alexandru","id":11753,"avatar_url":"https://avatars2.githubusercontent.com/u/11753?v=4","gravatar_id":"","url":"https://api.github.com/users/alexandru","html_url":"https://github.com/alexandru","followers_url":"https://api.github.com/users/alexandru/followers","following_url":"https://api.github.com/users/alexandru/following{/other_user}","gists_url":"https://api.github.com/users/alexandru/gists{/gist_id}","starred_url":"https://api.github.com/users/alexandru/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexandru/subscriptions","organizations_url":"https://api.github.com/users/alexandru/orgs","repos_url":"https://api.github.com/users/alexandru/repos","events_url":"https://api.github.com/users/alexandru/events{/privacy}","received_events_url":"https://api.github.com/users/alexandru/received_events","type":"User","site_admin":false},"repo":{"id":89021735,"name":"cats-effect","full_name":"alexandru/cats-effect","owner":{"login":"alexandru","id":11753,"avatar_url":"https://avatars2.githubusercontent.com/u/11753?v=4","gravatar_id":"","url":"https://api.github.com/users/alexandru","html_url":"https://github.com/alexandru","followers_url":"https://api.github.com/users/alexandru/followers","following_url":"https://api.github.com/users/alexandru/following{/other_user}","gists_url":"https://api.github.com/users/alexandru/gists{/gist_id}","starred_url":"https://api.github.com/users/alexandru/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexandru/subscriptions","organizations_url":"https://api.github.com/users/alexandru/orgs","repos_url":"https://api.github.com/users/alexandru/repos","events_url":"https://api.github.com/users/alexandru/events{/privacy}","received_events_url":"https://api.github.com/users/alexandru/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/alexandru/cats-effect","description":"The IO monad for cats","fork":true,"url":"https://api.github.com/repos/alexandru/cats-effect","forks_url":"https://api.github.com/repos/alexandru/cats-effect/forks","keys_url":"https://api.github.com/repos/alexandru/cats-effect/keys{/key_id}","collaborators_url":"https://api.github.com/repos/alexandru/cats-effect/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/alexandru/cats-effect/teams","hooks_url":"https://api.github.com/repos/alexandru/cats-effect/hooks","issue_events_url":"https://api.github.com/repos/alexandru/cats-effect/issues/events{/number}","events_url":"https://api.github.com/repos/alexandru/cats-effect/events","assignees_url":"https://api.github.com/repos/alexandru/cats-effect/assignees{/user}","branches_url":"https://api.github.com/repos/alexandru/cats-effect/branches{/branch}","tags_url":"https://api.github.com/repos/alexandru/cats-effect/tags","blobs_url":"https://api.github.com/repos/alexandru/cats-effect/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/alexandru/cats-effect/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/alexandru/cats-effect/git/refs{/sha}","trees_url":"https://api.github.com/repos/alexandru/cats-effect/git/trees{/sha}","statuses_url":"https://api.github.com/repos/alexandru/cats-effect/statuses/{sha}","languages_url":"https://api.github.com/repos/alexandru/cats-effect/languages","stargazers_url":"https://api.github.com/repos/alexandru/cats-effect/stargazers","contributors_url":"https://api.github.com/repos/alexandru/cats-effect/contributors","subscribers_url":"https://api.github.com/repos/alexandru/cats-effect/subscribers","subscription_url":"https://api.github.com/repos/alexandru/cats-effect/subscription","commits_url":"https://api.github.com/repos/alexandru/cats-effect/commits{/sha}","git_commits_url":"https://api.github.com/repos/alexandru/cats-effect/git/commits{/sha}","comments_url":"https://api.github.com/repos/alexandru/cats-effect/comments{/number}","issue_comment_url":"https://api.github.com/repos/alexandru/cats-effect/issues/comments{/number}","contents_url":"https://api.github.com/repos/alexandru/cats-effect/contents/{+path}","compare_url":"https://api.github.com/repos/alexandru/cats-effect/compare/{base}...{head}","merges_url":"https://api.github.com/repos/alexandru/cats-effect/merges","archive_url":"https://api.github.com/repos/alexandru/cats-effect/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/alexandru/cats-effect/downloads","issues_url":"https://api.github.com/repos/alexandru/cats-effect/issues{/number}","pulls_url":"https://api.github.com/repos/alexandru/cats-effect/pulls{/number}","milestones_url":"https://api.github.com/repos/alexandru/cats-effect/milestones{/number}","notifications_url":"https://api.github.com/repos/alexandru/cats-effect/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/alexandru/cats-effect/labels{/name}","releases_url":"https://api.github.com/repos/alexandru/cats-effect/releases{/id}","deployments_url":"https://api.github.com/repos/alexandru/cats-effect/deployments","created_at":"2017-04-21T20:37:21Z","updated_at":"2017-04-21T20:37:23Z","pushed_at":"2018-02-25T16:23:24Z","git_url":"git://github.com/alexandru/cats-effect.git","ssh_url":"git@github.com:alexandru/cats-effect.git","clone_url":"https://github.com/alexandru/cats-effect.git","svn_url":"https://github.com/alexandru/cats-effect","homepage":"","size":586,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"typelevel:next","ref":"next","sha":"6a5e2a539df42c9b54cdd5a06cbc0f551f19d994","user":{"login":"typelevel","id":3731824,"avatar_url":"https://avatars0.githubusercontent.com/u/3731824?v=4","gravatar_id":"","url":"https://api.github.com/users/typelevel","html_url":"https://github.com/typelevel","followers_url":"https://api.github.com/users/typelevel/followers","following_url":"https://api.github.com/users/typelevel/following{/other_user}","gists_url":"https://api.github.com/users/typelevel/gists{/gist_id}","starred_url":"https://api.github.com/users/typelevel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/typelevel/subscriptions","organizations_url":"https://api.github.com/users/typelevel/orgs","repos_url":"https://api.github.com/users/typelevel/repos","events_url":"https://api.github.com/users/typelevel/events{/privacy}","received_events_url":"https://api.github.com/users/typelevel/received_events","type":"Organization","site_admin":false},"repo":{"id":87653068,"name":"cats-effect","full_name":"typelevel/cats-effect","owner":{"login":"typelevel","id":3731824,"avatar_url":"https://avatars0.githubusercontent.com/u/3731824?v=4","gravatar_id":"","url":"https://api.github.com/users/typelevel","html_url":"https://github.com/typelevel","followers_url":"https://api.github.com/users/typelevel/followers","following_url":"https://api.github.com/users/typelevel/following{/other_user}","gists_url":"https://api.github.com/users/typelevel/gists{/gist_id}","starred_url":"https://api.github.com/users/typelevel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/typelevel/subscriptions","organizations_url":"https://api.github.com/users/typelevel/orgs","repos_url":"https://api.github.com/users/typelevel/repos","events_url":"https://api.github.com/users/typelevel/events{/privacy}","received_events_url":"https://api.github.com/users/typelevel/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/typelevel/cats-effect","description":"The IO monad for cats","fork":false,"url":"https://api.github.com/repos/typelevel/cats-effect","forks_url":"https://api.github.com/repos/typelevel/cats-effect/forks","keys_url":"https://api.github.com/repos/typelevel/cats-effect/keys{/key_id}","collaborators_url":"https://api.github.com/repos/typelevel/cats-effect/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/typelevel/cats-effect/teams","hooks_url":"https://api.github.com/repos/typelevel/cats-effect/hooks","issue_events_url":"https://api.github.com/repos/typelevel/cats-effect/issues/events{/number}","events_url":"https://api.github.com/repos/typelevel/cats-effect/events","assignees_url":"https://api.github.com/repos/typelevel/cats-effect/assignees{/user}","branches_url":"https://api.github.com/repos/typelevel/cats-effect/branches{/branch}","tags_url":"https://api.github.com/repos/typelevel/cats-effect/tags","blobs_url":"https://api.github.com/repos/typelevel/cats-effect/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/typelevel/cats-effect/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/typelevel/cats-effect/git/refs{/sha}","trees_url":"https://api.github.com/repos/typelevel/cats-effect/git/trees{/sha}","statuses_url":"https://api.github.com/repos/typelevel/cats-effect/statuses/{sha}","languages_url":"https://api.github.com/repos/typelevel/cats-effect/languages","stargazers_url":"https://api.github.com/repos/typelevel/cats-effect/stargazers","contributors_url":"https://api.github.com/repos/typelevel/cats-effect/contributors","subscribers_url":"https://api.github.com/repos/typelevel/cats-effect/subscribers","subscription_url":"https://api.github.com/repos/typelevel/cats-effect/subscription","commits_url":"https://api.github.com/repos/typelevel/cats-effect/commits{/sha}","git_commits_url":"https://api.github.com/repos/typelevel/cats-effect/git/commits{/sha}","comments_url":"https://api.github.com/repos/typelevel/cats-effect/comments{/number}","issue_comment_url":"https://api.github.com/repos/typelevel/cats-effect/issues/comments{/number}","contents_url":"https://api.github.com/repos/typelevel/cats-effect/contents/{+path}","compare_url":"https://api.github.com/repos/typelevel/cats-effect/compare/{base}...{head}","merges_url":"https://api.github.com/repos/typelevel/cats-effect/merges","archive_url":"https://api.github.com/repos/typelevel/cats-effect/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/typelevel/cats-effect/downloads","issues_url":"https://api.github.com/repos/typelevel/cats-effect/issues{/number}","pulls_url":"https://api.github.com/repos/typelevel/cats-effect/pulls{/number}","milestones_url":"https://api.github.com/repos/typelevel/cats-effect/milestones{/number}","notifications_url":"https://api.github.com/repos/typelevel/cats-effect/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/typelevel/cats-effect/labels{/name}","releases_url":"https://api.github.com/repos/typelevel/cats-effect/releases{/id}","deployments_url":"https://api.github.com/repos/typelevel/cats-effect/deployments","created_at":"2017-04-08T17:55:13Z","updated_at":"2018-02-25T14:57:14Z","pushed_at":"2018-02-25T16:23:39Z","git_url":"git://github.com/typelevel/cats-effect.git","ssh_url":"git@github.com:typelevel/cats-effect.git","clone_url":"https://github.com/typelevel/cats-effect.git","svn_url":"https://github.com/typelevel/cats-effect","homepage":"","size":538,"stargazers_count":242,"watchers_count":242,"language":"Scala","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":36,"mirror_url":null,"archived":false,"open_issues_count":22,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0"},"forks":36,"open_issues":22,"watchers":242,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/typelevel/cats-effect/pulls/126"},"html":{"href":"https://github.com/typelevel/cats-effect/pull/126"},"issue":{"href":"https://api.github.com/repos/typelevel/cats-effect/issues/126"},"comments":{"href":"https://api.github.com/repos/typelevel/cats-effect/issues/126/comments"},"review_comments":{"href":"https://api.github.com/repos/typelevel/cats-effect/pulls/126/comments"},"review_comment":{"href":"https://api.github.com/repos/typelevel/cats-effect/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/typelevel/cats-effect/pulls/126/commits"},"statuses":{"href":"https://api.github.com/repos/typelevel/cats-effect/statuses/9633ed62ea50486b337c61af0e8377aed4e74159"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"johnynek"}}