{"action":"created","comment":{"url":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/comments/82479604","pull_request_review_id":3387035,"id":82479604,"diff_hunk":"@@ -0,0 +1,413 @@\n+---\n+layout: post\n+title: EDSLs as functions\n+\n+meta:\n+  nav: blog\n+  author: adelbertc\n+  pygments: true\n+---\n+\n+*This is the second of a series of articles on \"Monadic EDSLs in Scala.\"*\n+\n+Perhaps the most direct way to start writing an EDSL is to start writing\n+functions. Let's say we want a language for talking about sets of integers.\n+\n+```scala\n+trait SetLang {\n+  def add(i: Int, set: Set[Int]): Set[Int]\n+  def remove(i: Int, set: Set[Int]): Set[Int]\n+  def exists(i: Int, set: Set[Int]): Boolean\n+}\n+```\n+\n+This works.. to the extent that we want only to work with\n+`scala.collection.Set`s. As it stands we cannot talk about\n+other implementations of sets, bloom filters, or sets controlled by\n+remote programs. Our language isn't *abstract* enough, so let's remove\n+all traces of `Set`.\n+\n+```scala\n+trait SetLang[F[_]] {\n+  def add(i: Int, set: F[Int]): F[Int]\n+  def remove(i: Int, set: F[Int]): F[Int]\n+  def exists(i: Int, set: F[Int]): Boolean\n+\n+  // Given unknown F we no longer know how to create an empty set\n+  // so we add the capability to our language\n+  def empty: F[Int]\n+}\n+```\n+\n+We've parameterized our language with a [higher-kinded type][hkt] which\n+represents the context of our set. We can then write mini-programs which talks\n+about sets without regards to `F`, which could be `scala.collection.Set`,\n+`java.util.Set`, or `type RemoteSet[A] = Future[Set[A]]`.\n+\n+```scala\n+def program[F[_]](lang: SetLang[F]): Boolean = {\n+  import lang._\n+  exists(10, remove(5, add(10, add(5, empty))))\n+}\n+```\n+\n+Interpretation of our language is straightforward, by implementing `SetLang` and\n+passing in the corresponding implementation to `program`.\n+\n+Our language is still limited. `F` is arbitrarily designated to\n+represent only the set as opposed to a more general \"context\". Consider the\n+behavior of `exists` if `F = RemoteSet`. The set is presumably stored on some\n+concurrent process, yet accessing the set is synchronous. It's also tedious\n+to thread the set through each method manually. We solve both problems by\n+generalizing `F` to represent a computational context which has access to a\n+set (think `Set[Int] => A` or `Set[Int] => Future[A]`).\n+\n+```scala\n+trait SetLang[F[_]] {\n+  def add(i: Int): F[Unit]\n+  def remove(i: Int): F[Unit]\n+  def exists(i: Int): F[Boolean]\n+\n+  // No longer need `empty` since the \"context\" has it already\n+}\n+\n+```\n+\n+Our new encoding introduces a new but very important problem: how do we\n+compose? In the previous encoding we could add and then remove by threading\n+the set through. With this more general context, there is no way to do\n+that.\n+\n+Fortunately we are now in a position to leverage a powerful tool:\n+[monads][monads]. By extending our set language with monadic powers,\n+we recover composition in an elegant way. The [Cats][cats] library is used\n+for demonstration purposes, but discussion applies equally to [Scalaz][scalaz].\n+\n+```scala\n+import cats.Monad\n+import cats.implicits._\n+\n+trait SetLang[F[_]] {\n+  def monad: Monad[F]\n+\n+  def add(i: Int): F[Unit]\n+  def remove(i: Int): F[Unit]\n+  def exists(i: Int): F[Boolean]\n+}\n+\n+def program[F[_]](lang: SetLang[F]): F[Boolean] = {\n+  import lang._\n+  implicit val monadInstance = monad\n+  for {\n+    _ <- add(5)\n+    _ <- add(10)\n+    _ <- remove(5)\n+    b <- exists(10)\n+  } yield b\n+}\n+```\n+\n+See [this post][typeClassSubType] for why the `Monad` instance is defined as a\n+member as opposed to through inheritance.\n+\n+Defining an interpreter requires identifying a target context. Since the context\n+computes values while updating state, this suggests the state monad.\n+\n+```scala\n+import cats.data.State\n+\n+object ScalaSet extends SetLang[State[Set[Int], ?]] {\n+  val monad = Monad[State[Set[Int], ?]]\n+  def add(i: Int): State[Set[Int], Unit] =\n+    State.modify(_ + i)\n+  def remove(i: Int): State[Set[Int], Unit] =\n+    State.modify(_ - i)\n+  def exists(i: Int): State[Set[Int], Boolean] =\n+    State.inspect(_(i))\n+}\n+```\n+\n+```scala\n+val state = program[State[Set[Int], ?]](ScalaSet)\n+// state: cats.data.StateT[cats.Eval,scala.collection.immutable.Set[Int],Boolean] = cats.data.StateT@5718268a\n+\n+state.run(Set.empty).value\n+// res4: (scala.collection.immutable.Set[Int], Boolean) = (Set(10),true)\n+```\n+\n+Note that using `program` does not require any context-specific knowledge -\n+we could just as well define another interpreter, perhaps one that does\n+logging, and interpret the program that way.\n+\n+```scala\n+import cats.data.StateT\n+import scala.concurrent.{ExecutionContext, Future}\n+\n+// Asynchronous state\n+def AsyncSet(implicit ec: ExecutionContext): SetLang[StateT[Future, Set[Int], ?]] =\n+  new SetLang[StateT[Future, Set[Int], ?]] {\n+    val monad = Monad[StateT[Future, Set[Int], ?]]\n+    def add(i: Int): StateT[Future, Set[Int], Unit] =\n+      StateT.modify(_ + i)\n+    def remove(i: Int): StateT[Future, Set[Int], Unit] =\n+      StateT.modify(_ - i)\n+    def exists(i: Int): StateT[Future, Set[Int], Boolean] =\n+      StateT.inspect(_(i))\n+  }\n+```\n+\n+```scala\n+// No changes to `program` required\n+val result = program(AsyncSet(ExecutionContext.global))\n+// result: cats.data.StateT[scala.concurrent.Future,scala.collection.immutable.Set[Int],Boolean] = cats.data.StateT@24ca528d\n+```\n+\n+Our `SetLang` captures the *structure* of a computation, but leaves open\n+its *interpretation*.\n+\n+# Monad transformers and classes\n+\n+Our set language started with simple methods and is now fully generic and\n+composable. As it turns out our `SetLang` encoding often referred to as\n+[MTL-style][mtl].\n+\n+## Monads in monads\n+\n+Among the motivations for monad classes is to remove the need to specify\n+monad transformer stacks. The following example is adapted from\n+[Functional Programming with Overloading and Higher-Order Polymorphism][mtlPaper]\n+by Professor Mark P. Jones.\n+\n+Consider a program that is open to failure and computes with some state. This\n+suggests a combinator of `Either` and `State`, which in turn suggests their\n+corresponding monad transformers. All that is left is to decide which\n+transformer to use.\n+\n+```scala\n+type App1[A] = EitherT[State[S, ?], Error, A]\n+            // State[S, Either[Error, A]]\n+            // S => (S, Either[Error, A])\n+\n+type App2[A] = StateT[Either[Error, ?], S, A]\n+            // S => Either[Error, (S, A)]\n+```\n+\n+While `App1` and `App2` are both valid compositions, the\n+semantics of the compositions differ. `App1` describes a program where\n+the computation of a *value* at each transition may fail whereas `App2`\n+describes a program where the *entire* transition may fail.\n+\n+We can abstract away the difference by creating a type class which provides\n+the relevant operations we need.\n+\n+```scala\n+trait MonadError[F[_], E] {\n+  def monad: Monad[F]\n+\n+  def raiseError[A](e: E): F[A]\n+  def handleErrorWith[A](fa: F[A])(f: E => F[A]): F[A]\n+}\n+\n+trait MonadState[F[_], S] {\n+  def monad: Monad[F]\n+\n+  def get: F[S]\n+  def set(s: S): F[Unit]\n+}\n+```\n+\n+You can imagine similar type classes for the `Reader` and `Writer` data types.\n+These type classes are provided in both [Cats][cats] and [Scalaz][scalaz],\n+[with some caveats][typeClassSubType].\n+\n+With these type classes in place we can write functions against these as opposed\n+to specific transformer stacks. Furthermore our functions can specify exactly\n+what operations they need which aids correctness and [parametricity][parametricity].\n+\n+```scala\n+import cats.{MonadError, MonadState}\n+import cats.data.{EitherT, State, StateT}\n+\n+def program[F[_]](implicit F0: MonadError[F, String],\n+                           F1: MonadState[F, Int]): F[Int] =\n+  F0.flatMap(F1.get) { i =>\n+    F0.raiseError[Int](\"fail\")\n+  }\n+```\n+\n+Our program can then be instantiated with either `App1` or `App2`.\n+\n+```scala\n+import cats.implicits._\n+\n+// At the time of this writing Cats does not have these instances\n+// so they are defined here.\n+//\n+// Additionally, both Cats and Scalaz 7 have encoding issues\n+// with these MTL type classes which requires us to redefine Monad when\n+// defining MonadState instances, despite there already being one.\n+implicit def eitherTMonadState[F[_], E, S](implicit F: MonadState[F, S]): MonadState[EitherT[F, E, ?], S] =\n+  new MonadState[EitherT[F, E, ?], S] {\n+    def get: EitherT[F, E, S] =\n+      EitherT(F.get.map(Right(_)))\n+\n+    def set(s: S): EitherT[F, E, Unit] =\n+      EitherT(F.set(s).map(Right(_)))\n+\n+    def flatMap[A, B](fa: EitherT[F, E, A])\n+                     (f: A => EitherT[F, E, B]): EitherT[F, E, B] =\n+      fa.flatMap(f)\n+\n+    def pure[A](x: A): EitherT[F, E, A] =\n+      EitherT.pure(x)\n+\n+    def tailRecM[A, B](a: A)(f: A => EitherT[F, E, Either[A, B]]): EitherT[F, E, B] =\n+      EitherT.catsDataMonadErrorForEitherT[F, E].tailRecM(a)(f)\n+  }\n+\n+implicit def stateTMonadError[F[_], E, S](implicit F: MonadError[F, E]): MonadError[StateT[F, S, ?], E] =\n+  new MonadError[StateT[F, S, ?], E] {\n+    def handleErrorWith[A](fa: StateT[F, S, A])(f: E => StateT[F, S, A]): StateT[F, S, A] =\n+      StateT[F, S, A] { (s: S) =>\n+        val state: F[(S, A)] = fa.run(s)\n+        F.handleErrorWith(state)(e => f(e).run(s))\n+      }\n+\n+    def raiseError[A](e: E): StateT[F, S, A] =\n+      StateT.lift(F.raiseError(e))\n+\n+    def flatMap[A, B](fa: StateT[F, S, A])(f: A => StateT[F, S, B]): StateT[F, S, B] =\n+      fa.flatMap(f)\n+\n+    def pure[A](x: A): StateT[F, S, A] = StateT.pure(x)\n+\n+    def tailRecM[A, B](a: A)(f: A => StateT[F, S, Either[A, B]]): StateT[F, S, B] =\n+      StateT.catsDataMonadStateForStateT[F, S].tailRecM(a)(f)\n+  }\n+\n+type App1[A] = EitherT[State[Int, ?], String, A]\n+\n+type App2[A] = StateT[Either[String, ?], Int, A]\n+```\n+\n+```scala\n+val app1 = program[App1]\n+// app1: App1[Int] = EitherT(cats.data.StateT@33ffe601)\n+\n+val app2 = program[App2]\n+// app2: App2[Int] = cats.data.StateT@7f432a18\n+```\n+\n+# Composing languages\n+\n+From one angle we can view our set language, or more generally any EDSL\n+in this style, an effect like `MonadError` and `MonadState`. From another\n+angle we can view `MonadError` and `MonadState` as EDSLs that talk about errors\n+and stateful computations. We can eliminate the distinctions by renaming\n+`SetLang` to `MonadSet` and treating it as a type class.\n+\n+```scala\n+import cats.Monad\n+import cats.implicits._\n+\n+trait MonadSet[F[_]] {\n+  def monad: Monad[F]\n+\n+  def add(i: Int): F[Unit]\n+  def remove(i: Int): F[Unit]\n+  def exists(i: Int): F[Boolean]\n+}\n+```\n+\n+Composing multiple languages then becomes adding constraints to functions, and\n+interpretation becomes type class instances.\n+\n+```scala\n+trait MonadCalc[F[_]] {\n+  def monad: Monad[F]\n+\n+  def lit(i: Int): F[Int]\n+  def plus(l: F[Int], r: F[Int]): F[Int]\n+}\n+\n+def setProgram[F[_]: MonadSet](i: Int): F[Boolean] =\n+  implicitly[MonadSet[F]].exists(i)\n+\n+def calcProgram[F[_]: MonadCalc]: F[Int] = {\n+  val calc = implicitly[MonadCalc[F]]\n+  calc.plus(calc.lit(1), calc.lit(2))\n+}\n+\n+def composedProgram[F[_]: MonadCalc: MonadSet]: F[Boolean] = {\n+  implicit val monad: Monad[F] = implicitly[MonadCalc[F]].monad\n+  for {\n+    i <- calcProgram[F]\n+    b <- setProgram(i)\n+  } yield b\n+}\n+\n+// Instance\n+\n+type SetState[A] = State[Set[Int], A]\n+implicit val stateInstance: MonadSet[SetState] with MonadCalc[SetState] =","path":"_posts/2016-10-12-edsls-part-2.md","position":352,"original_position":352,"commit_id":"0e7c0dd75a8b8dc862b5a2eefee7bb68caf102ec","original_commit_id":"0e7c0dd75a8b8dc862b5a2eefee7bb68caf102ec","user":{"login":"larsrh","id":923079,"avatar_url":"https://avatars.githubusercontent.com/u/923079?v=3","gravatar_id":"","url":"https://api.github.com/users/larsrh","html_url":"https://github.com/larsrh","followers_url":"https://api.github.com/users/larsrh/followers","following_url":"https://api.github.com/users/larsrh/following{/other_user}","gists_url":"https://api.github.com/users/larsrh/gists{/gist_id}","starred_url":"https://api.github.com/users/larsrh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/larsrh/subscriptions","organizations_url":"https://api.github.com/users/larsrh/orgs","repos_url":"https://api.github.com/users/larsrh/repos","events_url":"https://api.github.com/users/larsrh/events{/privacy}","received_events_url":"https://api.github.com/users/larsrh/received_events","type":"User","site_admin":false},"body":"Here, you have successfully modularised and composed programs, but not the underlying implementations. It could be worth mentioning that you can also split up this instance into two.","created_at":"2016-10-07T22:17:26Z","updated_at":"2016-10-07T22:17:58Z","html_url":"https://github.com/typelevel/typelevel.github.com/pull/120#discussion_r82479604","pull_request_url":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/120","_links":{"self":{"href":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/comments/82479604"},"html":{"href":"https://github.com/typelevel/typelevel.github.com/pull/120#discussion_r82479604"},"pull_request":{"href":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/120"}}},"pull_request":{"url":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/120","id":88489050,"html_url":"https://github.com/typelevel/typelevel.github.com/pull/120","diff_url":"https://github.com/typelevel/typelevel.github.com/pull/120.diff","patch_url":"https://github.com/typelevel/typelevel.github.com/pull/120.patch","issue_url":"https://api.github.com/repos/typelevel/typelevel.github.com/issues/120","number":120,"state":"open","locked":false,"title":"New blog post: EDSLs as functions","user":{"login":"adelbertc","id":1332980,"avatar_url":"https://avatars.githubusercontent.com/u/1332980?v=3","gravatar_id":"","url":"https://api.github.com/users/adelbertc","html_url":"https://github.com/adelbertc","followers_url":"https://api.github.com/users/adelbertc/followers","following_url":"https://api.github.com/users/adelbertc/following{/other_user}","gists_url":"https://api.github.com/users/adelbertc/gists{/gist_id}","starred_url":"https://api.github.com/users/adelbertc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adelbertc/subscriptions","organizations_url":"https://api.github.com/users/adelbertc/orgs","repos_url":"https://api.github.com/users/adelbertc/repos","events_url":"https://api.github.com/users/adelbertc/events{/privacy}","received_events_url":"https://api.github.com/users/adelbertc/received_events","type":"User","site_admin":false},"body":"Scheduled for Wednesday October 12.\r\n\r\ncc @S11001001 ","created_at":"2016-10-07T18:51:22Z","updated_at":"2016-10-07T22:17:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"20e416bd1a5e71da52b23490b649976a0a54170c","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/120/commits","review_comments_url":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/120/comments","review_comment_url":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/comments{/number}","comments_url":"https://api.github.com/repos/typelevel/typelevel.github.com/issues/120/comments","statuses_url":"https://api.github.com/repos/typelevel/typelevel.github.com/statuses/0e7c0dd75a8b8dc862b5a2eefee7bb68caf102ec","head":{"label":"adelbertc:edsl-pt-2","ref":"edsl-pt-2","sha":"0e7c0dd75a8b8dc862b5a2eefee7bb68caf102ec","user":{"login":"adelbertc","id":1332980,"avatar_url":"https://avatars.githubusercontent.com/u/1332980?v=3","gravatar_id":"","url":"https://api.github.com/users/adelbertc","html_url":"https://github.com/adelbertc","followers_url":"https://api.github.com/users/adelbertc/followers","following_url":"https://api.github.com/users/adelbertc/following{/other_user}","gists_url":"https://api.github.com/users/adelbertc/gists{/gist_id}","starred_url":"https://api.github.com/users/adelbertc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adelbertc/subscriptions","organizations_url":"https://api.github.com/users/adelbertc/orgs","repos_url":"https://api.github.com/users/adelbertc/repos","events_url":"https://api.github.com/users/adelbertc/events{/privacy}","received_events_url":"https://api.github.com/users/adelbertc/received_events","type":"User","site_admin":false},"repo":{"id":34679746,"name":"typelevel.github.com","full_name":"adelbertc/typelevel.github.com","owner":{"login":"adelbertc","id":1332980,"avatar_url":"https://avatars.githubusercontent.com/u/1332980?v=3","gravatar_id":"","url":"https://api.github.com/users/adelbertc","html_url":"https://github.com/adelbertc","followers_url":"https://api.github.com/users/adelbertc/followers","following_url":"https://api.github.com/users/adelbertc/following{/other_user}","gists_url":"https://api.github.com/users/adelbertc/gists{/gist_id}","starred_url":"https://api.github.com/users/adelbertc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adelbertc/subscriptions","organizations_url":"https://api.github.com/users/adelbertc/orgs","repos_url":"https://api.github.com/users/adelbertc/repos","events_url":"https://api.github.com/users/adelbertc/events{/privacy}","received_events_url":"https://api.github.com/users/adelbertc/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/adelbertc/typelevel.github.com","description":"Web site of typelevel.scala","fork":true,"url":"https://api.github.com/repos/adelbertc/typelevel.github.com","forks_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/forks","keys_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/keys{/key_id}","collaborators_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/teams","hooks_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/hooks","issue_events_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/issues/events{/number}","events_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/events","assignees_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/assignees{/user}","branches_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/branches{/branch}","tags_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/tags","blobs_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/git/refs{/sha}","trees_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/git/trees{/sha}","statuses_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/statuses/{sha}","languages_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/languages","stargazers_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/stargazers","contributors_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/contributors","subscribers_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/subscribers","subscription_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/subscription","commits_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/commits{/sha}","git_commits_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/git/commits{/sha}","comments_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/comments{/number}","issue_comment_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/issues/comments{/number}","contents_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/contents/{+path}","compare_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/compare/{base}...{head}","merges_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/merges","archive_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/downloads","issues_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/issues{/number}","pulls_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/pulls{/number}","milestones_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/milestones{/number}","notifications_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/labels{/name}","releases_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/releases{/id}","deployments_url":"https://api.github.com/repos/adelbertc/typelevel.github.com/deployments","created_at":"2015-04-27T17:07:37Z","updated_at":"2015-04-27T17:07:37Z","pushed_at":"2016-10-07T18:50:58Z","git_url":"git://github.com/adelbertc/typelevel.github.com.git","ssh_url":"git@github.com:adelbertc/typelevel.github.com.git","clone_url":"https://github.com/adelbertc/typelevel.github.com.git","svn_url":"https://github.com/adelbertc/typelevel.github.com","homepage":"http://typelevel.org","size":6252,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"typelevel:master","ref":"master","sha":"d36062c73d1c48785183eb55c4c86bf3f05f1efc","user":{"login":"typelevel","id":3731824,"avatar_url":"https://avatars.githubusercontent.com/u/3731824?v=3","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":8810137,"name":"typelevel.github.com","full_name":"typelevel/typelevel.github.com","owner":{"login":"typelevel","id":3731824,"avatar_url":"https://avatars.githubusercontent.com/u/3731824?v=3","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/typelevel.github.com","description":"Web site of typelevel.scala","fork":false,"url":"https://api.github.com/repos/typelevel/typelevel.github.com","forks_url":"https://api.github.com/repos/typelevel/typelevel.github.com/forks","keys_url":"https://api.github.com/repos/typelevel/typelevel.github.com/keys{/key_id}","collaborators_url":"https://api.github.com/repos/typelevel/typelevel.github.com/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/typelevel/typelevel.github.com/teams","hooks_url":"https://api.github.com/repos/typelevel/typelevel.github.com/hooks","issue_events_url":"https://api.github.com/repos/typelevel/typelevel.github.com/issues/events{/number}","events_url":"https://api.github.com/repos/typelevel/typelevel.github.com/events","assignees_url":"https://api.github.com/repos/typelevel/typelevel.github.com/assignees{/user}","branches_url":"https://api.github.com/repos/typelevel/typelevel.github.com/branches{/branch}","tags_url":"https://api.github.com/repos/typelevel/typelevel.github.com/tags","blobs_url":"https://api.github.com/repos/typelevel/typelevel.github.com/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/typelevel/typelevel.github.com/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/typelevel/typelevel.github.com/git/refs{/sha}","trees_url":"https://api.github.com/repos/typelevel/typelevel.github.com/git/trees{/sha}","statuses_url":"https://api.github.com/repos/typelevel/typelevel.github.com/statuses/{sha}","languages_url":"https://api.github.com/repos/typelevel/typelevel.github.com/languages","stargazers_url":"https://api.github.com/repos/typelevel/typelevel.github.com/stargazers","contributors_url":"https://api.github.com/repos/typelevel/typelevel.github.com/contributors","subscribers_url":"https://api.github.com/repos/typelevel/typelevel.github.com/subscribers","subscription_url":"https://api.github.com/repos/typelevel/typelevel.github.com/subscription","commits_url":"https://api.github.com/repos/typelevel/typelevel.github.com/commits{/sha}","git_commits_url":"https://api.github.com/repos/typelevel/typelevel.github.com/git/commits{/sha}","comments_url":"https://api.github.com/repos/typelevel/typelevel.github.com/comments{/number}","issue_comment_url":"https://api.github.com/repos/typelevel/typelevel.github.com/issues/comments{/number}","contents_url":"https://api.github.com/repos/typelevel/typelevel.github.com/contents/{+path}","compare_url":"https://api.github.com/repos/typelevel/typelevel.github.com/compare/{base}...{head}","merges_url":"https://api.github.com/repos/typelevel/typelevel.github.com/merges","archive_url":"https://api.github.com/repos/typelevel/typelevel.github.com/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/typelevel/typelevel.github.com/downloads","issues_url":"https://api.github.com/repos/typelevel/typelevel.github.com/issues{/number}","pulls_url":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls{/number}","milestones_url":"https://api.github.com/repos/typelevel/typelevel.github.com/milestones{/number}","notifications_url":"https://api.github.com/repos/typelevel/typelevel.github.com/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/typelevel/typelevel.github.com/labels{/name}","releases_url":"https://api.github.com/repos/typelevel/typelevel.github.com/releases{/id}","deployments_url":"https://api.github.com/repos/typelevel/typelevel.github.com/deployments","created_at":"2013-03-15T22:19:50Z","updated_at":"2016-10-03T23:13:31Z","pushed_at":"2016-10-07T18:51:23Z","git_url":"git://github.com/typelevel/typelevel.github.com.git","ssh_url":"git@github.com:typelevel/typelevel.github.com.git","clone_url":"https://github.com/typelevel/typelevel.github.com.git","svn_url":"https://github.com/typelevel/typelevel.github.com","homepage":"http://typelevel.org","size":6260,"stargazers_count":14,"watchers_count":14,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":30,"mirror_url":null,"open_issues_count":8,"forks":30,"open_issues":8,"watchers":14,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/120"},"html":{"href":"https://github.com/typelevel/typelevel.github.com/pull/120"},"issue":{"href":"https://api.github.com/repos/typelevel/typelevel.github.com/issues/120"},"comments":{"href":"https://api.github.com/repos/typelevel/typelevel.github.com/issues/120/comments"},"review_comments":{"href":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/120/comments"},"review_comment":{"href":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/typelevel/typelevel.github.com/pulls/120/commits"},"statuses":{"href":"https://api.github.com/repos/typelevel/typelevel.github.com/statuses/0e7c0dd75a8b8dc862b5a2eefee7bb68caf102ec"}}}}
{"actor":{"display_login":"larsrh"}}