{"action":"created","comment":{"url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/comments/27516714","id":27516714,"diff_hunk":"@@ -0,0 +1,303 @@\n+---\n+comments: true\n+date: 2015-03-24 16:10:00\n+layout: post\n+slug: {functional-programming-on-frontend-with-react-clojurescript-reagent}\n+title: Functional programming on frontend with React & ClojureScript\n+summary: Lately all of my frontend work was done mostly using React. I really like React's functional approach to DOM manipulation and since I am fan of FP and interested in using functional languages I decided to give a try to ClojureScript and see how it might play together with React. In fact, those 2 technologies are playing together very nicely and enable you to build scalable and well performing UIs in functional way.\n+author: Marek TOMAS\n+tags:\n+- ClojureScript\n+- JavaScript\n+- Frontend\n+---\n+\n+## <a name=\"second\" href=\"#second\">Introduction</a>\n+I have been working and evaluating popular JavaScript frameworks such as AngularJS, Polymer, Ember and also emerging WebComponents for quite some time. In this blogpost I will try to explain why I found React to be most profound framework for building scalable and well performing web applications with taste of functional programming. In this tutorial we will introduce basics of ClojureScript as well and show how React plays nicely with ClojureScript through one of its wrappers called Reagent. Working app is included as well.\n+\n+## <a name=\"second\" href=\"#second\">What makes UI hard ?</a>\n+\n+One of the biggest and most common problems in the frontend web development is state management.\n+\n+\n+\n+Traditional approaches for managing state changes in UI, for example in AngularJS involve keeping track of changes in underlying data models and syncing those changes with DOM to keep UI up to-to-date.\n+\n+In general, it is a challenging task to manage state transitions that are responses to every possible user interaction. Coding in sense of transitions instead of states can be complex. The number of branches and checks we need to perform grows exponentially with the number of view states to manage. This basically means that developer (or framework) is responsible for handling every DOM mutation associated with user triggered interaction that changes state of the app. This is both error prone and hard to manage when complexity of your app grows over time.\n+\n+## <a name=\"second\" href=\"#second\">React - high level overview</a>\n+\n+### What is React ?\n+React is addressing the aforementioned challenges in a very neat and profound way. React authors define it as a **JavaScript library for creating UIs and addressing problem of building large applications with data that changes over time**. As already stated, mutable state is very complex thing to manage and reason about. So how is React approach to managing UI state different compared to others frameworks and libraries out there ?\n+\n+### Functional programming to the rescue\n+React brings the very basic essence of functional programming to the table. It provides abstractions such as components which are basically (pure) functions and get you away from imperatively touching the DOM.\n+\n+Basically, in terms of functional programming, you write [idempotent](http://en.wikipedia.org/wiki/Idempotence), composable functions. Data is coming to your functions as input which emit tree like representations as return values. When data changes, functions are re-run again with new data as input. React diffs the result of previous function call with new one and it effectively calculates the difference between the tree structures. From higher level perspective, React is a function which takes two DOMs and generates a list of DOM operations to be applied to the DOM, i.e., it is [referentially transparent](http://en.wikipedia.org/wiki/Referential_transparency_%28computer_science%29).\n+\n+### React basics\n+Now let's see what React does in the browser with DOM. Diffing between tree representations is done internally by React through *indirection mechanism* called virtual DOM which mirrors the real DOM. Every time when input data changes new virtual DOM is generated and differences between them are translated into batch operations applied to real DOM. In React you work with [Components](http://facebook.github.io/react/docs/tutorial.html#your-first-component) which are reusable, basic building blocks of UI. In React, you simply update component internal state and then its UI is re-rendered accordingly. Let's see some practical code example to explain the idea of React components little bit more.\n+\n+### React code sample\n+\n+{% highlight javascript %}\n+\n+var Timer = React.createClass({\n+\n+  getDefaultProps: function() {\n+    return {label: \"Timer\"}\n+  },\n+\n+  getInitialState: function() {\n+    return {secondsElapsed: 0};\n+  },\n+\n+  tick: function() {\n+    this.setState({secondsElapsed: this.state.secondsElapsed + 1});\n+  },\n+\n+  componentDidMount: function() {\n+    this.interval = setInterval(this.tick, 1000);\n+  },\n+\n+  componentWillUnmount: function() {\n+    clearInterval(this.interval);\n+  },\n+\n+  render: function() {\n+    return (\n+      <div>\n+      \t<strong>{this.props.label}</strong>\n+        <br/>\n+\t      Seconds Elapsed: {this.state.secondsElapsed}\n+      </div>\n+    );\n+  }\n+});\n+\n+React.render(<Timer label={\"Sample timer\"}/>, mountNode);\n+{% endhighlight %}\n+\n+This is example of stateful component. To allow reuse of components each of the components has its own unique state accessed via *this.state*. When using stateful components, initial state is defined through *getInitialState* method. In case component’s state changes (next timer tick in this example), markup will be updated by re-invoking *render()* function.\n+\n+This component renders itself also based on properties. Properties are imutable data tjat turn dull components into configurable interface elements. In component instance they are accessed via *this.props*. Similarly as for state, it is possible to define default properties via *getDefaultProps* method. In this example, properties are passed via JavaScript syntax extension called JSX that looks similar to XML and can be found at the very bottom of code sample and also in *render* method. React provides tools that translate this JSX syntax into native JavaScript. Reasons for introducing JSX syntax are that it is more familiar for casual developers such as designers and brings natural way of expressing larger trees of components.\n+\n+Declaring hierarchy of components that should be returned as a result of calling *render* function on some arbitrary component might look for example like this:\n+\n+{%highlight xml %}\n+render: function() {\n+        return  (<Form>\n+                   <FormRow>\n+                     <FormLabel />\n+                     <FormInput />\n+                   </FormRow>\n+                 </Form>)\n+}\n+{% endhighlight %}\n+(assuming that corresponding React classes are present in JavaScript execution context and named exactly the same as nodes in outlined XML tree)\n+\n+This syntax shows how it is possible to compose component trees in very readable and intuitive way. JSX transformer takes care of replacing XML literals with proper native JavaScript calls.\n+\n+React API also provides possibility to hook in your code in methods executed at specific points of [component lifecycle](http://facebook.github.io/react/docs/component-specs.html). In case of Timer example we use [getDefaultProps](https://facebook.github.io/react/docs/component-specs.html#getdefaultprops), [getInitialState](http://facebook.github.io/react/docs/component-specs.html#getinitialstate), [componentDidMount](http://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount), [componentWillUnmount](http://facebook.github.io/react/docs/component-specs.html#unmounting-componentwillunmount) and [render](http://facebook.github.io/react/docs/component-specs.html#render) lifecycle methods.\n+\n+\n+React makes rendering of UI as simple as defining a function. Since React follows the functional paradigm, it is no surprise that the ClojureScript community has embraced React with open arms. In next chapter of this blogpost we will have a look at ClojureScript and one of its React wrappers called Reagent and will get our hands dirty with building sample app.\n+\n+\n+## <a name=\"second\" href=\"#second\">React meets ClojureScript</a>\n+\n+### ClojureScript 101\n+\n+For those of you who are not much familiar with ClojureScript, I strongly recommend to read [rationale](https://github.com/clojure/clojurescript/wiki/Rationale) which contains nice summary of what is ClojureScript about:\n+\n+> ClojureScript seeks to address the weak link in the client/embedded application development story by replacing JavaScript with Clojure, a robust, concise and powerful programming language. In its implementation, ClojureScript adopts the strategy of the Google Closure library and compiler, and is able to effectively leverage both tools, gaining a large, production-quality library and whole-program optimization. ClojureScript brings the rich data structure set, functional programming, macros, reader, destructuring, polymorphism constructs, state discipline and many other features of Clojure to every place JavaScript reaches.\n+\n+ClojureScript is a simple language that favors a functional style of programing and is based on small numbers of fundamental concepts. For those exposed mostly to imperative, object-oriented languages some of the concepts might seem unfamiliar at first glance. However, learning those concepts gives you another powerful programming tools to your hands.\n+\n+Another specific thing about ClojureScript is its syntax which mimics Clojure (Lisp dialect). Instead of going through syntax and basics of the language here in this blogpost I rather recommend you to go through this [concise guide of ClojureScript](https://github.com/shaunlebron/ClojureScript-Syntax-in-15-minutes) which I found very helpful.\n+\n+After grasping basics you should be ready to read code samples in the following chapter. ClojureScript syntax actually enables you to write very concise representations of component hierarchies using Reagent library. I found Reagent to be the simplest and most intuitive ClojureScript wrapper to start with. I recommend you to [watch out for Om](https://github.com/omcljs/om) which gets lot of attention these days and bundles few great ideas together.\n+\n+\n+### Reagent - simple React wrapper\n+\n+Reagent is a library that provides minimalistic interface between React and ClojureScript. It allows you to define React UIs of arbitrary complexity using only plain ClojureScript functions & data and describe it using [Hiccup-like syntax](https://github.com/weavejester/hiccup) which is very concise and easy to read. Your application is built together only using bunch of very basic but powerful concepts.\n+\n+Let's see how to define basic component using Reagent:\n+\n+{%highlight clojure%}\n+(defn hello-world-component []\n+  (defn hello [name]\n+    [:div \"hello \" name]))\n+{%endhighlight%}\n+\n+This component can be mount to DOM node like this:\n+\n+{%highlight clojure%}\n+(reagent/render [hello-component \"Rich\"] (.-body js/document))\n+{%endhighlight%}\n+\n+Component composition is done in a very easy and concise way. You can also define accompanying CSS classes to components and pass children components as params as you do in React.\n+\n+{%highlight clojure%}\n+(defn page [body]\n+  [:div.page\n+    [:div.header \"Page header\"]\n+    body\n+    [:div.footer \"Page footer\"]])\n+{%endhighlight%}\n+\n+In ClojureScript, almost everything is immutable. To accomplish mutability ClojureScript uses concept of *atoms*. Atoms are references to objects which needs to be dereferenced in order to obtain object instance. Think about them as similar thing as pointers in C.\n+\n+Common and viable practice is to define all of your application state in one *atom* which might be done like this.\n+\n+{%highlight clojure%}\n+(def state (atom {:data [] :number-of-steps 0}))\n+{%endhighlight%}\n+\n+Manipulating atoms is done through side effecting functions (suffixed with !). Reagent uses it's own version of atom. Any component that uses an atom is automagically re-rendered when it's value changes. This allows for more complex binding scenarios than in typical use of React. Distinction between props and state is gone and you are free to use Reagent atoms in any way you prefer. Let's revisit our Timer component - this time little bit simplified.\n+\n+{%highlight clojure%}\n+(defn timer-component []\n+  (let [seconds-elapsed (atom 0)]\n+    (fn []\n+      (js/setTimeout #(swap! seconds-elapsed inc) 1000)\n+      [:div\n+       \"Seconds Elapsed: \" @seconds-elapsed])))\n+{%endhighlight%}\n+\n+Number of elapsed seconds is kept in its own atom - in this case unique state of component. Most common way to mutate atom value is to use built-in ClojureScript function *swap!*.\n+\n+Call to swap! accepts function as argument that is applied to value of atom and stored.\n+\n+{%highlight clojure%}\n+swap! seconds-elapsed inc\n+{%endhighlight%}\n+\n+Those are the basic building blocks of Reagent which let you build well performing UIs.\n+\n+## A word about performance\n+\n+React itself is very fast. Reagent is able to be even faster because of optimizations done in ClojureScript. All of the actions that might trigger re-rendering components (derefing atom, changing args passed to component or its state) benefit from fast pointer comparisons between changed ClojureScript data structures and trigger re-rendering of components only when it is really needed. This means you have to care about performance rarely (for example when displaying long list of items) and define UI as you feel fit.\n+\n+### <a name=\"third\" href=\"#third\">Sample app\n+\n+By grasping basics and knowing benefits of using ClojureScript and Reagent tigether you should be ready to understand code of sample basic application Pexeso. Whole source code [can be found on GitHub](https://github.com/mtomas/clojurescript-reagent-pexeso) and you are encouraged to follow and experiment with this example.\n+\n+You can see working [demo deployed on Heroku](https://cljspexeso.herokuapp.com/) as well.\n+\n+\n+<p align=\"center\">\n+<a href=\"https://cljspexeso.herokuapp.com/\">\n+  <img src=\"https://raw.githubusercontent.com/mtomas/clojurescript-reagent-pexeso/master/screens/screen.png\" />\n+</a>\n+</p>\n+\n+## Pexeso walkthrough\n+\n+To showcase Reagent together with ClojureScript I created a sample application Pexeso. App is meant to be played by one player and includes all of the described basic concepts.\n+\n+In this blogpost I won't go into details of setting up a ClojureScript project. The project requires [Leiningen build tool](http://leiningen.org/) and you will need to have it installed before continuing.\n+\n+Most convenient option for you (if you are Mac user) might be to install it using *brew* package manager.\n+\n+{% highlight bash %}\n+$ brew install leiningen\n+{% endhighlight %}\n+\n+\n+I decided to use excellent [Reagent Leiningen template](https://github.com/reagent-project/reagent-template) for projects using Reagent. This template packages everything needed for production ready ClojureScript applications. It comes up with nice development environment that allows you to do instant subsecond builds of your whole app and see them immediately swapped in the browser resulting in very efficient workflow for learning and trying things out.\n+\n+Whole application logic except template stuff has around ~100 LOC of code. Let's go through most important parts of it.\n+\n+\n+We will start by defining global state that keeps track of cards:\n+\n+{% highlight clojure%}\n+(def state (atom {:cards [] :last-symbol nil}))\n+{% endhighlight %}\n+\n+\n+Main UI is very simple, it basically consists of board of cards and symbol of last revealed card.\n+\n+{% highlight clojure%}\n+(defn home-page []\n+  [:div.pexeso\n+    [:h2 \"Pexeso\"]\n+    [:div.status (if (= (matched-cards-count) board-size)\n+        \"Game is finished, congratulations !\"\n+    )]\n+    [:br]\n+\n+    [:button.button {:onClick start-game} \"Restart game\"]\n+\n+    [:div.board\n+        (doall (for [card-state (@state :cards)] (card card-state)))\n+    ]])\n+{% endhighlight %}\n+\n+\n+You might wonder about **doall** function that wraps for comprehension. Point here is that we have to eagerly evaluate lazily generated sequence of atoms that hold cards' state. Otherwise Reagent would be unable to dereference them properly which would lead into inconsistencies. State of the card is then passed as an argument to function creating React components. Internals of component can be followed by reading the comments starting with **;**.\n+\n+{%highlight clojurescript %}\n+(defn card [card-state]\n+  (letfn [(handle-card-click! [event]\n+    ; pair of cards was revealed, now let's go for another pair step\n+    (if (= (revealed-cards-count) 2)\n+      (hide-nonmatch!)\n+    )\n+\n+    ;reveal next card in step\n+    (reveal-card card-state)\n+\n+    ;if 2 of cards are revealed, we have to check parity\n+    (if (= (revealed-cards-count) 2)\n+      (do\n+        (if (= (get @state :last-symbol) (get @card-state :symbol))\n+          (do\n+            (mark-match! (get @card-state :symbol))\n+          )\n+        )\n+      )\n+    )\n+\n+    ;let's remember last symbol to make comparison in subsequent steps\n+    (swap! state assoc :last-symbol (@card-state :symbol))\n+  )]\n+\n+  [:div.card\n+    {\n+      :onClick handle-card-click!\n+      :key (.random js/Math)\n+      :class (if (@card-state :matched) \"card-matched\" \"card\")\n+    }\n+    [:span.card-value\n+      {\n+        :class (if (@card-state :visible) \"card-value\" \"card-value-hidden\")\n+      }\n+      (@card-state :symbol)\n+    ]\n+  ]\n+  )\n+ )\n+{% endhighlight %}\n+\n+That is pretty much all worth elaborating that relates to logic of the game. [Whole source](https://github.com/mtomas/clojurescript-reagent-pexeso) is available on GitHub and I encourage you to try to implement some basic features - such as number of steps done to complete the game or even try to come up with feature that enables you to replay steps of finished game.\n+\n+## <a name=\"summary\" href=\"#summary\">Summary</a>\n+\n+As you can see picking up the stellar combo ClojureScript + Reagent is very simple and requires very little code to get things done. Things like atoms simplify your life and enable you to create more complex binding schemas. Functional code makes your frontend code easier to reason about and might be even more performing than your current implementations using plain React.\n+\n+ClojureScript has matured into production-ready language and it allows build better, more reusable and solid frontend code in functional manner and with fewer lines than in native JavaScript.\n+\n+I believe you might greatly benefit from using those technologies while building sophisticated web applications . If you are interested in ClojureScript and have been looking at it from distance I hope that I piqued your interest and wish you good luck in your learning endeavours.\n+\n+Feel free to express your questions, comments or opinions in the comments.\n+\n+## <a name=\"links\" href=\"#links\">Links</a>\n+- [http://swannodette.github.io/2013/11/07/clojurescript-101/](http://swannodette.github.io/2013/11/07/clojurescript-101/)\n+- [http://www.mattgreer.org/articles/reagent-rocks/#on-to-reagent](http://www.mattgreer.org/articles/reagent-rocks/#on-to-reagent)\n+- [http://reagent-project.github.io/](http://reagent-project.github.io/)\n+- [http://www.slideshare.net/InfoQ/the-immutable-frontend-in-clojurescript?qid=145eca11-81e3-4199-9da6-e67e73420f51&v=default&b=&from_search=1](http://www.slideshare.net/InfoQ/the-immutable-frontend-in-clojurescript?qid=145eca11-81e3-4199-9da6-e67e73420f51&v=default&b=&from_search=1)\n+- [http://www.lispcast.com/react-another-level-of-indirection](http://www.lispcast.com/react-another-level-of-indirection)\n+- [http://getprismatic.com/story/1405451329953](http://getprismatic.com/story/1405451329953)","path":"_posts/24-03-2015-clojurescript-reactjs-reagent.md","position":303,"original_position":303,"commit_id":"cfc07bf6d661e37dfb7e5eecf51cfc96289e3f67","original_commit_id":"cfc07bf6d661e37dfb7e5eecf51cfc96289e3f67","user":{"login":"pjazdzewski1990","id":1136843,"avatar_url":"https://avatars.githubusercontent.com/u/1136843?v=3","gravatar_id":"","url":"https://api.github.com/users/pjazdzewski1990","html_url":"https://github.com/pjazdzewski1990","followers_url":"https://api.github.com/users/pjazdzewski1990/followers","following_url":"https://api.github.com/users/pjazdzewski1990/following{/other_user}","gists_url":"https://api.github.com/users/pjazdzewski1990/gists{/gist_id}","starred_url":"https://api.github.com/users/pjazdzewski1990/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pjazdzewski1990/subscriptions","organizations_url":"https://api.github.com/users/pjazdzewski1990/orgs","repos_url":"https://api.github.com/users/pjazdzewski1990/repos","events_url":"https://api.github.com/users/pjazdzewski1990/events{/privacy}","received_events_url":"https://api.github.com/users/pjazdzewski1990/received_events","type":"User","site_admin":false},"body":"If possible link directly","created_at":"2015-03-31T19:58:12Z","updated_at":"2015-03-31T19:58:12Z","html_url":"https://github.com/ScalaConsultants/ScalaConsultants.github.io/pull/40#discussion_r27516714","pull_request_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/40","_links":{"self":{"href":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/comments/27516714"},"html":{"href":"https://github.com/ScalaConsultants/ScalaConsultants.github.io/pull/40#discussion_r27516714"},"pull_request":{"href":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/40"}}},"pull_request":{"url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/40","id":32356883,"html_url":"https://github.com/ScalaConsultants/ScalaConsultants.github.io/pull/40","diff_url":"https://github.com/ScalaConsultants/ScalaConsultants.github.io/pull/40.diff","patch_url":"https://github.com/ScalaConsultants/ScalaConsultants.github.io/pull/40.patch","issue_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/issues/40","number":40,"state":"open","locked":false,"title":"first draft","user":{"login":"mtomas","id":390629,"avatar_url":"https://avatars.githubusercontent.com/u/390629?v=3","gravatar_id":"","url":"https://api.github.com/users/mtomas","html_url":"https://github.com/mtomas","followers_url":"https://api.github.com/users/mtomas/followers","following_url":"https://api.github.com/users/mtomas/following{/other_user}","gists_url":"https://api.github.com/users/mtomas/gists{/gist_id}","starred_url":"https://api.github.com/users/mtomas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mtomas/subscriptions","organizations_url":"https://api.github.com/users/mtomas/orgs","repos_url":"https://api.github.com/users/mtomas/repos","events_url":"https://api.github.com/users/mtomas/events{/privacy}","received_events_url":"https://api.github.com/users/mtomas/received_events","type":"User","site_admin":false},"body":"review needed fellaz","created_at":"2015-03-31T19:28:45Z","updated_at":"2015-03-31T19:58:12Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1a7a4dc5741387f25fb031c7f52f6993bb61d764","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/40/commits","review_comments_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/40/comments","review_comment_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/issues/40/comments","statuses_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/statuses/cfc07bf6d661e37dfb7e5eecf51cfc96289e3f67","head":{"label":"ScalaConsultants:react-clojurescript","ref":"react-clojurescript","sha":"cfc07bf6d661e37dfb7e5eecf51cfc96289e3f67","user":{"login":"ScalaConsultants","id":6207831,"avatar_url":"https://avatars.githubusercontent.com/u/6207831?v=3","gravatar_id":"","url":"https://api.github.com/users/ScalaConsultants","html_url":"https://github.com/ScalaConsultants","followers_url":"https://api.github.com/users/ScalaConsultants/followers","following_url":"https://api.github.com/users/ScalaConsultants/following{/other_user}","gists_url":"https://api.github.com/users/ScalaConsultants/gists{/gist_id}","starred_url":"https://api.github.com/users/ScalaConsultants/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ScalaConsultants/subscriptions","organizations_url":"https://api.github.com/users/ScalaConsultants/orgs","repos_url":"https://api.github.com/users/ScalaConsultants/repos","events_url":"https://api.github.com/users/ScalaConsultants/events{/privacy}","received_events_url":"https://api.github.com/users/ScalaConsultants/received_events","type":"Organization","site_admin":false},"repo":{"id":15366666,"name":"ScalaConsultants.github.io","full_name":"ScalaConsultants/ScalaConsultants.github.io","owner":{"login":"ScalaConsultants","id":6207831,"avatar_url":"https://avatars.githubusercontent.com/u/6207831?v=3","gravatar_id":"","url":"https://api.github.com/users/ScalaConsultants","html_url":"https://github.com/ScalaConsultants","followers_url":"https://api.github.com/users/ScalaConsultants/followers","following_url":"https://api.github.com/users/ScalaConsultants/following{/other_user}","gists_url":"https://api.github.com/users/ScalaConsultants/gists{/gist_id}","starred_url":"https://api.github.com/users/ScalaConsultants/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ScalaConsultants/subscriptions","organizations_url":"https://api.github.com/users/ScalaConsultants/orgs","repos_url":"https://api.github.com/users/ScalaConsultants/repos","events_url":"https://api.github.com/users/ScalaConsultants/events{/privacy}","received_events_url":"https://api.github.com/users/ScalaConsultants/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ScalaConsultants/ScalaConsultants.github.io","description":"Scalac blog","fork":false,"url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io","forks_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/forks","keys_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/teams","hooks_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/hooks","issue_events_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/events","assignees_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/tags","blobs_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/languages","stargazers_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/stargazers","contributors_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/contributors","subscribers_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/subscribers","subscription_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/subscription","commits_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/merges","archive_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/downloads","issues_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/labels{/name}","releases_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/releases{/id}","created_at":"2013-12-21T22:28:57Z","updated_at":"2015-03-27T15:31:41Z","pushed_at":"2015-03-31T19:28:45Z","git_url":"git://github.com/ScalaConsultants/ScalaConsultants.github.io.git","ssh_url":"git@github.com:ScalaConsultants/ScalaConsultants.github.io.git","clone_url":"https://github.com/ScalaConsultants/ScalaConsultants.github.io.git","svn_url":"https://github.com/ScalaConsultants/ScalaConsultants.github.io","homepage":null,"size":2896,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":8,"mirror_url":null,"open_issues_count":1,"forks":8,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"ScalaConsultants:master","ref":"master","sha":"0466e9340e435f072435693c7ade60d64deb7ac3","user":{"login":"ScalaConsultants","id":6207831,"avatar_url":"https://avatars.githubusercontent.com/u/6207831?v=3","gravatar_id":"","url":"https://api.github.com/users/ScalaConsultants","html_url":"https://github.com/ScalaConsultants","followers_url":"https://api.github.com/users/ScalaConsultants/followers","following_url":"https://api.github.com/users/ScalaConsultants/following{/other_user}","gists_url":"https://api.github.com/users/ScalaConsultants/gists{/gist_id}","starred_url":"https://api.github.com/users/ScalaConsultants/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ScalaConsultants/subscriptions","organizations_url":"https://api.github.com/users/ScalaConsultants/orgs","repos_url":"https://api.github.com/users/ScalaConsultants/repos","events_url":"https://api.github.com/users/ScalaConsultants/events{/privacy}","received_events_url":"https://api.github.com/users/ScalaConsultants/received_events","type":"Organization","site_admin":false},"repo":{"id":15366666,"name":"ScalaConsultants.github.io","full_name":"ScalaConsultants/ScalaConsultants.github.io","owner":{"login":"ScalaConsultants","id":6207831,"avatar_url":"https://avatars.githubusercontent.com/u/6207831?v=3","gravatar_id":"","url":"https://api.github.com/users/ScalaConsultants","html_url":"https://github.com/ScalaConsultants","followers_url":"https://api.github.com/users/ScalaConsultants/followers","following_url":"https://api.github.com/users/ScalaConsultants/following{/other_user}","gists_url":"https://api.github.com/users/ScalaConsultants/gists{/gist_id}","starred_url":"https://api.github.com/users/ScalaConsultants/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ScalaConsultants/subscriptions","organizations_url":"https://api.github.com/users/ScalaConsultants/orgs","repos_url":"https://api.github.com/users/ScalaConsultants/repos","events_url":"https://api.github.com/users/ScalaConsultants/events{/privacy}","received_events_url":"https://api.github.com/users/ScalaConsultants/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ScalaConsultants/ScalaConsultants.github.io","description":"Scalac blog","fork":false,"url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io","forks_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/forks","keys_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/teams","hooks_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/hooks","issue_events_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/events","assignees_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/tags","blobs_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/languages","stargazers_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/stargazers","contributors_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/contributors","subscribers_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/subscribers","subscription_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/subscription","commits_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/merges","archive_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/downloads","issues_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/labels{/name}","releases_url":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/releases{/id}","created_at":"2013-12-21T22:28:57Z","updated_at":"2015-03-27T15:31:41Z","pushed_at":"2015-03-31T19:28:45Z","git_url":"git://github.com/ScalaConsultants/ScalaConsultants.github.io.git","ssh_url":"git@github.com:ScalaConsultants/ScalaConsultants.github.io.git","clone_url":"https://github.com/ScalaConsultants/ScalaConsultants.github.io.git","svn_url":"https://github.com/ScalaConsultants/ScalaConsultants.github.io","homepage":null,"size":2896,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":8,"mirror_url":null,"open_issues_count":1,"forks":8,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/40"},"html":{"href":"https://github.com/ScalaConsultants/ScalaConsultants.github.io/pull/40"},"issue":{"href":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/issues/40"},"comments":{"href":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/issues/40/comments"},"review_comments":{"href":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/40/comments"},"review_comment":{"href":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/pulls/40/commits"},"statuses":{"href":"https://api.github.com/repos/ScalaConsultants/ScalaConsultants.github.io/statuses/cfc07bf6d661e37dfb7e5eecf51cfc96289e3f67"}}}}