{"action":"created","comment":{"url":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/comments/94199611","pull_request_review_id":14730672,"id":94199611,"diff_hunk":"@@ -0,0 +1,456 @@\n+---\n+layout: post\n+title:  More Dots: Syntactic Loop Fusion in Julia\n+author: <a href=\"http://math.mit.edu/~stevenj\">Steven G. Johnson</a>\n+---\n+\n+After a [lengthy design process](https://github.com/JuliaLang/julia/issues/8450) and [preliminary foundations in Julia 0.5](http://julialang.org/blog/2016/10/julia-0.5-highlights#vectorized-function-calls), Julia 0.6 includes new facilities for writing code in the \"vectorized\"\n+style (familiar from Matlab, Numpy, R, etcetera) while avoiding the\n+overhead that this style of programming usually imposes: multiple\n+vectorized operations can now be \"fused\" into a single loop, without\n+allocating any extraneous temporary arrays.\n+\n+This is best illustrated with an example.  Suppose we have\n+a function `f(x) = 3x^2 + 5x + 2` that evaluates a polynomial,\n+and we want to evaluate `f(2x^2 + 6x^3 - sqrt(x))` for a whole array `X`,\n+storing the result in-place in `X`.  You can now do:\n+\n+```jl\n+X .= f.(2 .* X.^2 .+ 6 .* X.^3 .- sqrt.(X))\n+```\n+\n+and the whole computation will be *fused* into a single loop, operating in-place,\n+and performance will be comparable to the hand-written\n+\"devectorized\" loop:\n+\n+```jl\n+for i in eachindex(X)\n+    x = X[i]\n+    X[i] = f(2x^2 + 6x^3 - sqrt(x))\n+end\n+```\n+\n+(Of course, like all Julia code, to get good performance both of these snippets should be executed inside some function, not in global scope.)\n+\n+In this blog post, we delve into some of the details of this new development, in order to answer questions that often arise when this feature is presented:\n+\n+1. What is the overhead of traditional \"vectorized\" code?  Isn't vectorized code supposed to be fast already?\n+\n+2. Why are all these dots necessary?  Couldn't Julia just optimize \"ordinary\" vector code?\n+\n+3. Is this something unique to Julia, or can other languages do the same thing?\n+\n+The short answers are:\n+\n+1. [Ordinary vectorized code is fast, but not as fast as a hand-written loop](http://www.johnmyleswhite.com/notebook/2013/12/22/the-relationship-between-vectorized-and-devectorized-code/)\n+  (assuming loops are efficiently compiled, as in Julia)\n+  because each vectorized operation generates a new temporary array and\n+  executes a separate loop, leading to a lot of overhead when multiple\n+  vectorized operations are combined.\n+\n+2. The dots allow Julia to recognize the \"vectorized\" nature of the\n+  operations at a *syntactic* level (before e.g. the type of `x` is known),\n+  and hence the loop fusion is a *syntactic guarantee*, not a\n+  compiler optimization that may or may not occur for carefully written code.  They also allow the *caller* to \"vectorize\" *any* function, rather than relying on the function author.\n+\n+3. Other languages have implemented loop fusion for vectorized operations,\n+  but typically for only a small set of types and operations/functions that\n+  are known to the compiler.  Julia's ability to do it generically, even\n+  for *user-defined* array types and functions/operators, is unusual\n+  and relies in part on the syntax choices above and on its ability to efficiently\n+  compile higher-order functions.\n+\n+## Isn't vectorized code already fast?\n+\n+To explore this question (also discussed\n+[in this blog post](http://www.johnmyleswhite.com/notebook/2013/12/22/the-relationship-between-vectorized-and-devectorized-code/)), let's begin by rewriting the code above in a more traditional vectorized style, without\n+so many dots, such as you might use in Julia 0.4 or in other languages\n+(most famously Matlab, Python/Numpy, or R).   \n+\n+```jl\n+X = f(2 * X.^2 + 6 * X.^3 - sqrt(X))\n+```\n+\n+Of course, this assumes that the functions `sqrt` and `f` are \"vectorized,\"\n+i.e. that they accept vector arguments `X` and compute the\n+function elementwise.  This is true of `sqrt` in Julia 0.4, but it\n+means that we have to rewrite our function `f` from above in a vectorized style, as\n+e.g. `f(x) = 3x.^2 + 5x + 2` (changing `f` to use the elementwise operator `.^` because\n+`array^scalar` is not defined).   (If we cared a lot about efficiency, we\n+might instead define a special method `f(X::AbstractArray) = map(f, X)`\n+or use the `@vectorize_1arg f Number` macro defined in Julia 0.4.)\n+\n+### Which functions are vectorized?\n+\n+As an aside, this example illustrates an annoyance with the vectorized style:\n+you have to *decide in advance* whether a given function `f(x)`\n+will also be applied elementwise to arrays, and either\n+write it specially or define a corresponding elementwise method.\n+\n+(Even if a function *accepts* an array argument `x` — our function `f`\n+accepts any `x` type, and in Matlab or R there is no distinction between\n+a scalar and a 1-element array — that doesn't mean it will *work* elementwise\n+for an array unless you write the function with that in mind.)\n+\n+For library functions like `sqrt`, this means that the library authors\n+have to guess at which functions should have vectorized methods, and users\n+have to guess at what vaguely defined subset of library functions work\n+for vectors.\n+\n+One possible solution is to vectorize *every function automatically*.   The\n+language [Chapel](https://en.wikipedia.org/wiki/Chapel_%28programming_language%29)\n+does this: every function `f(x...)` implicitly\n+defines a function `f(x::Array...)` that evaluates `map(f, x...)`\n+[(Chamberlain et al, 2011)](http://pgas11.rice.edu/papers/ChamberlainEtAl-Chapel-Iterators-PGAS11.pdf).\n+This could be implemented in Julia as well via\n+function-call overloading [(Bezanson, 2015: chapter 4)](https://github.com/JeffBezanson/phdthesis/blob/master/main.pdf),\n+but we chose to go in a different direction.\n+\n+Instead, starting in Julia 0.5, *any* function `f(x)` can be applied elementwise\n+to an array `X` with the [\"dot call\" syntax `f.(X)`](http://docs.julialang.org/en/stable/manual/functions/#dot-syntax-for-vectorizing-functions).\n+Thus, the *caller* decides which functions to vectorize.  In Julia 0.6,\n+\"traditionally\" vectorized library functions like `sqrt(X)` are [deprecated](https://github.com/JuliaLang/julia/pull/17302) in\n+favor of `sqrt.(X)`, and dot operators\n+like `x .+ y` are [now equivalent](https://github.com/JuliaLang/julia/pull/17623) to\n+dot calls `(+).(x,y)`.   (Unlike Chapel's implicit vectorization, Julia's\n+`f.(x...)` syntax corresponds to `broadcast(f, x...)` rather than `map`,\n+which allows you to combine arrays of different shapes/dimensions.)\n+From the standpoint of the programmer, this adds a certain amount of\n+clarity because it indicates explicitly when an elementwise operation\n+is occuring.  From the standpoint of the compiler, dot-call syntax\n+enables the *syntactic loop fusion* optimization described in more detail\n+below, which we think is an overwhelming advantage of this style.\n+\n+### Why vectorized code is fast\n+\n+In many dynamic languages for technical computing, vectorization is\n+seen as a key (often *the* key) performance optimization.   It allows\n+your code to take advantage of highly optimized (perhaps even parallelized)\n+library routines for basic operations like `scalar*array` or `sqrt(array)`.\n+Those functions, in turn, are usually implemented in a low-level language\n+like C or Fortran.   Writing your own \"devectorized\" loops, in contrast,\n+is too slow, unless you are willing to drop down to a low-level language\n+yourself, because the semantics of those languages make it hard to\n+compile them to efficient code in general.\n+\n+Thanks to Julia's design, a properly written devectorized loop in Julia\n+has performance comparable to C or Fortran, so there is no *necessity*\n+of vectorizing, but of course vectorization may still be convenient for some problems.\n+And vectorized operations like `scalar*array` or `sqrt(array)` are still fast in Julia\n+(calling optimized library routines, albeit ones written in Julia itself).\n+\n+Furthermore, if your problem involves a function that does not have a pre-written,\n+highly optimized, vectorized, library routine in Julia, and does not\n+decompose easily into existing vectorized building blocks like `scalar*array`, then\n+you can write your own building block without dropping down to a low-level language.\n+(If all the performance-critical code you will ever need already existed in the\n+form of optimized library routines, programming would be a lot easier!)\n+\n+### Why vectorized code is not as fast as it could be\n+\n+There is a tension between two general principles in computing: on\n+the one hand, *re-using* highly optimized code is good for\n+performance; on the other other hand, optimized code that is *specialized*\n+for your problem can usually beat general-purpose functions.\n+This is illustrated nicely by the traditional vectorized version of our code above:\n+\n+```jl\n+f(x) = 3x.^2 + 5x + 2\n+X = f(2 * X.^2 + 6 * X.^3 - sqrt(X))\n+```\n+\n+Each of the operations like `X.^2`  and `5*X` *individually*\n+calls highly optimized functions, but their *combination*\n+leaves a lot of performance on the table when `X` is an array.   To see that,\n+you have to realize that this code is equivalent to:\n+\n+```\n+tmp1 = X.^2\n+tmp2 = 2*tmp1\n+tmp3 = X.^3\n+tmp4 = 6 * tmp3\n+tmp5 = tmp2 + tmp4\n+tmp6 = sqrt(X)\n+tmp7 = tmp5 - tmp7\n+X = f(tmp7)\n+```\n+\n+That is, each of these vectorized operations allocates a separate\n+temporary array, and is a separate library call with its own inner\n+loop.  Both of these properties are bad for performance.\n+\n+First, eight arrays are allocated (`tmp1` through `tmp7`, plus another\n+for the result of `f(tmp7)`, and another four are allocated\n+internally by `f(tmp7)` for the same reasons, for *12 arrays in all*.\n+The resulting `X = ...` expression does *not* update `X` in-place, but\n+rather makes the variable `X` \"point\" to a new array returned by `f(tmp7)`,\n+discarding the old array `X`.   All of these extra arrays are eventually\n+deallocated by Julia's garbage collector, but in the meantime it wastes\n+a lot of memory (more than an order of magnitude!)\n+\n+If the array `X` is small, then the performance cost of allocating\n+these temporary arrays is significant: heap allocation is expensive.\n+If the array `X` is large enough, then the time for the heap allocation\n+and garbage collection may be negligible, but you pay a *different* performance price\n+from the fact that you have 12 loops (12 passes over memory) compared\n+to one, in part because of the loss of [memory locality](https://en.wikipedia.org/wiki/Locality_of_reference).\n+\n+In particular, reading or writing data in main computer memory (RAM) is much slower than performing scalar arithmetic operations like `+` and `*`, so computer hardware stores recently used data in a [cache](https://en.wikipedia.org/wiki/Cache_%28computing%29): a small amount\n+of much faster memory.  Furthermore, there is a hierarchy of smaller,\n+faster caches, culminating in the [register memory](https://en.wikipedia.org/wiki/Processor_register)\n+of the CPU itself.   This means that, for good performance, you should\n+load each data item `x = X[i]` *once* (so that it goes into cache, or into a register for small enough types), and\n+then perform several operations like `f(2x^2 + 6x^3 - sqrt(x))` on `x`\n+while you still have fast access to it, before loading the next datum;\n+this is called \"temporal locality.\"   The traditional vectorized code\n+discards this potential locality: each `X[i]` is loaded once for a\n+single small operation like `2*X[i]`, writing the result out to a temporary\n+array before immediately reading the next `X[i]`.\n+\n+On a typical modern computer, therefore, the traditional vectorized\n+code `X = f(2 * X.^2 + 6 * X.^3 - sqrt(X))` turns out to be **almost 10× slower** than\n+the devectorized or fused-vectorized versions of the same code at the\n+beginning of this article for `X = rand(10^6)`.   This is not unique to Julia!  Such\n+vectorized code is suboptimal\n+in any language unless the language's compiler can automatically\n+fuse all of these loops (even ones that appear inside function calls),\n+which rarely happens for the reasons described below.\n+\n+## Why does Julia need dots to fuse the loops?\n+\n+You might look at an expression like `2 * X.^2 + 6 * X.^3 - sqrt(X)` and\n+think that it is \"obvious\" that it could be combined into a single loop\n+over `X`.  Why can't Julia's compiler be smart enough to recognize this?\n+\n+The thing that you need to realize is that, in Julia, there is nothing\n+particularly special about `+` or `sqrt` — they are arbitrary functions\n+and could do *anything*.   `X + Y` could send an email or open\n+a plotting window, for all the compiler knows.   To figure out that it\n+could fuse e.g. `2*X + Y` into a single loop, allocating a single\n+array for the result, the compiler would need to:\n+\n+1. Once the types of `X` and `Y` are determined, figure out what `*` and `+` functions\n+  to call.  (Julia already does this, when type inference succeeds.)\n+\n+2. Look inside of those functions, realize that they are elementwise loops over `X`\n+  and `Y`, and realize that they are [pure](https://en.wikipedia.org/wiki/Pure_function)\n+  (e.g. `2*X` has no side-effects like modifying `Y`).  Also, it needs to infer\n+  not only the purity of expressions like `X[i]` (which are calls to a function `getindex(X, i)`\n+  that is \"just another function\" to the compiler), but to detect\n+  what data dependencies they imply.\n+\n+The key difficulty is the second step: looking at an arbitrary function and\n+figuring out that it is a pure elementwise loop or data access turns out to be a\n+very hard problem in general.  If fusion is viewed as a compiler *optimization*,\n+then the compiler is only free to fuse if it can *prove* that fusion *won't\n+change the results*, which requires the detection of purity and other data-dependency\n+analyses.\n+\n+In contrast, when the Julia compiler sees an expression like `2 .* X .+ Y`,\n+it knows just from the *syntax* (the \"spelling\") that these are elementwise\n+operations, and it *guarantees* that it will *always* fuse into a single\n+loop, freeing it from the need to prove purity.  This is what we\n+term **syntactic loop fusion**, described in more detail below.\n+\n+### A halfway solution: Loop fusion for a few operations/types\n+\n+One approach that may occur to you, and which has been implemented in a\n+variety of languages (e.g. [Kennedy & McKinley, 1993](http://dl.acm.org/citation.cfm?id=665526);\n+[Veldhuizen, 1995](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.43.248);\n+[Lewis et al., 1998](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46.6627);\n+[Chakravarty & Keller, 2001](http://dl.acm.org/citation.cfm?id=507661);\n+[Manjikian & Abdelrahman, 2002](http://ieeexplore.ieee.org.libproxy.mit.edu/document/577265/);\n+[Sarkar, 2010](http://ieeexplore.ieee.org/document/5389392/);\n+[Prasad et al., 2011](http://dl.acm.org/citation.cfm?id=1993517);\n+[Wu et al., 2012](http://dl.acm.org/citation.cfm?id=2457490)), is to only\n+perform loop fusion for *a few \"built-in\" types and operations* that the\n+compiler can be designed to recognize. In Julia, for example, we could\n+potentially build the compiler to recognize that it can fuse\n+`*`, `+`, `.^`, and similar operations for the built-in `Array` type,\n+but only for elements that are one of a few built-in numeric types (`Float64`, `Int`, etc.).\n+These operations are common enough that this may still be a worthwhile optimization\n+for Julia to implement at some point!\n+\n+However, even though Julia will certainly implement additional compiler\n+optimizations as time passes, one of the key principles of Julia's design\n+is to \"build in\" as little as possible into the core language, implementing\n+as much of possible of Julia *in Julia* itself [(Bezanson, 2015)](https://github.com/JeffBezanson/phdthesis/blob/master/main.pdf).\n+Put another way, the same *optimizations should be just as available to user-defined\n+types and functions* as to the \"built-in\" functions of Julia's standard library\n+(`Base`).  You should be able to define your own array types\n+(e.g. via the [StaticArrays](https://github.com/JuliaArrays/StaticArrays.jl)\n+package or [PETSc arrays](https://github.com/JuliaParallel/PETSc.jl))\n+and functions (such as our `f` above), and have them be capable of fusing vectorized operations.\n+\n+### Syntactic loop fusion in Julia\n+\n+In contrast, Julia's approach is quite simple and general: the caller\n+indicates, by adding dots, which function calls and operators are\n+intended to be applied elementwise (specifically, as `broadcast` calls).\n+The compiler notices these dots at *parse time* (or technically\n+at \"lowering\" time, but in any case long before it knows\n+the types of the variables etc.), and transforms them into calls to\n+`broadcast`.  Moreover, it guarantees that *nested* \"dot calls\" will\n+*always* be fused into a single broadcast call, i.e. a single loop.\n+\n+Put another way, `f.(g.(x .+ 1))` is treated by Julia as simply\n+[syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar) for","path":"blog/_posts/2016-12-xx-moredots.md","position":297,"original_position":297,"commit_id":"e746e5b54283ac21268d6b72093edac6eb4d3243","original_commit_id":"157ee18cdd4aeb84b9ab0328b12fda10cbc77270","user":{"login":"stevengj","id":2913679,"avatar_url":"https://avatars.githubusercontent.com/u/2913679?v=3","gravatar_id":"","url":"https://api.github.com/users/stevengj","html_url":"https://github.com/stevengj","followers_url":"https://api.github.com/users/stevengj/followers","following_url":"https://api.github.com/users/stevengj/following{/other_user}","gists_url":"https://api.github.com/users/stevengj/gists{/gist_id}","starred_url":"https://api.github.com/users/stevengj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stevengj/subscriptions","organizations_url":"https://api.github.com/users/stevengj/orgs","repos_url":"https://api.github.com/users/stevengj/repos","events_url":"https://api.github.com/users/stevengj/events{/privacy}","received_events_url":"https://api.github.com/users/stevengj/received_events","type":"User","site_admin":false},"body":"as merely?","created_at":"2016-12-30T03:10:16Z","updated_at":"2016-12-30T03:10:16Z","html_url":"https://github.com/JuliaLang/julialang.github.com/pull/480#discussion_r94199611","pull_request_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/480","_links":{"self":{"href":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/comments/94199611"},"html":{"href":"https://github.com/JuliaLang/julialang.github.com/pull/480#discussion_r94199611"},"pull_request":{"href":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/480"}}},"pull_request":{"url":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/480","id":99594364,"html_url":"https://github.com/JuliaLang/julialang.github.com/pull/480","diff_url":"https://github.com/JuliaLang/julialang.github.com/pull/480.diff","patch_url":"https://github.com/JuliaLang/julialang.github.com/pull/480.patch","issue_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/issues/480","number":480,"state":"open","locked":false,"title":"blog post on syntactic loop fusion in Julia 0.6","user":{"login":"stevengj","id":2913679,"avatar_url":"https://avatars.githubusercontent.com/u/2913679?v=3","gravatar_id":"","url":"https://api.github.com/users/stevengj","html_url":"https://github.com/stevengj","followers_url":"https://api.github.com/users/stevengj/followers","following_url":"https://api.github.com/users/stevengj/following{/other_user}","gists_url":"https://api.github.com/users/stevengj/gists{/gist_id}","starred_url":"https://api.github.com/users/stevengj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stevengj/subscriptions","organizations_url":"https://api.github.com/users/stevengj/orgs","repos_url":"https://api.github.com/users/stevengj/repos","events_url":"https://api.github.com/users/stevengj/events{/privacy}","received_events_url":"https://api.github.com/users/stevengj/received_events","type":"User","site_admin":false},"body":"Since I commonly get questions on this, I thought it would be useful to have a blog post on the upcoming \"syntactic loop fusion\" (i.e. dot calls) feature of Julia 0.6.\r\n\r\ncc: @Sacha0, @yuyichao, @pabloferz","created_at":"2016-12-28T21:27:38Z","updated_at":"2016-12-30T03:10:16Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e8ee84198f896341d3af672b4a8aed223beab161","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/480/commits","review_comments_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/480/comments","review_comment_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/comments{/number}","comments_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/issues/480/comments","statuses_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/statuses/e746e5b54283ac21268d6b72093edac6eb4d3243","head":{"label":"JuliaLang:moredots","ref":"moredots","sha":"e746e5b54283ac21268d6b72093edac6eb4d3243","user":{"login":"JuliaLang","id":743164,"avatar_url":"https://avatars.githubusercontent.com/u/743164?v=3","gravatar_id":"","url":"https://api.github.com/users/JuliaLang","html_url":"https://github.com/JuliaLang","followers_url":"https://api.github.com/users/JuliaLang/followers","following_url":"https://api.github.com/users/JuliaLang/following{/other_user}","gists_url":"https://api.github.com/users/JuliaLang/gists{/gist_id}","starred_url":"https://api.github.com/users/JuliaLang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JuliaLang/subscriptions","organizations_url":"https://api.github.com/users/JuliaLang/orgs","repos_url":"https://api.github.com/users/JuliaLang/repos","events_url":"https://api.github.com/users/JuliaLang/events{/privacy}","received_events_url":"https://api.github.com/users/JuliaLang/received_events","type":"Organization","site_admin":false},"repo":{"id":2885268,"name":"julialang.github.com","full_name":"JuliaLang/julialang.github.com","owner":{"login":"JuliaLang","id":743164,"avatar_url":"https://avatars.githubusercontent.com/u/743164?v=3","gravatar_id":"","url":"https://api.github.com/users/JuliaLang","html_url":"https://github.com/JuliaLang","followers_url":"https://api.github.com/users/JuliaLang/followers","following_url":"https://api.github.com/users/JuliaLang/following{/other_user}","gists_url":"https://api.github.com/users/JuliaLang/gists{/gist_id}","starred_url":"https://api.github.com/users/JuliaLang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JuliaLang/subscriptions","organizations_url":"https://api.github.com/users/JuliaLang/orgs","repos_url":"https://api.github.com/users/JuliaLang/repos","events_url":"https://api.github.com/users/JuliaLang/events{/privacy}","received_events_url":"https://api.github.com/users/JuliaLang/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/JuliaLang/julialang.github.com","description":"Julia Project web site.","fork":false,"url":"https://api.github.com/repos/JuliaLang/julialang.github.com","forks_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/forks","keys_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/keys{/key_id}","collaborators_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/teams","hooks_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/hooks","issue_events_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/issues/events{/number}","events_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/events","assignees_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/assignees{/user}","branches_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/branches{/branch}","tags_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/tags","blobs_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/git/refs{/sha}","trees_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/git/trees{/sha}","statuses_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/statuses/{sha}","languages_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/languages","stargazers_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/stargazers","contributors_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/contributors","subscribers_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/subscribers","subscription_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/subscription","commits_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/commits{/sha}","git_commits_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/git/commits{/sha}","comments_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/comments{/number}","issue_comment_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/issues/comments{/number}","contents_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/contents/{+path}","compare_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/compare/{base}...{head}","merges_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/merges","archive_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/downloads","issues_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/issues{/number}","pulls_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls{/number}","milestones_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/milestones{/number}","notifications_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/labels{/name}","releases_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/releases{/id}","deployments_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/deployments","created_at":"2011-11-30T18:22:15Z","updated_at":"2016-12-13T23:43:52Z","pushed_at":"2016-12-30T03:09:10Z","git_url":"git://github.com/JuliaLang/julialang.github.com.git","ssh_url":"git@github.com:JuliaLang/julialang.github.com.git","clone_url":"https://github.com/JuliaLang/julialang.github.com.git","svn_url":"https://github.com/JuliaLang/julialang.github.com","homepage":"http://julialang.org/","size":9713,"stargazers_count":75,"watchers_count":75,"language":"TeX","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":162,"mirror_url":null,"open_issues_count":17,"forks":162,"open_issues":17,"watchers":75,"default_branch":"master"}},"base":{"label":"JuliaLang:master","ref":"master","sha":"6478b6e782ba92f866d241f249716e0b5e57e836","user":{"login":"JuliaLang","id":743164,"avatar_url":"https://avatars.githubusercontent.com/u/743164?v=3","gravatar_id":"","url":"https://api.github.com/users/JuliaLang","html_url":"https://github.com/JuliaLang","followers_url":"https://api.github.com/users/JuliaLang/followers","following_url":"https://api.github.com/users/JuliaLang/following{/other_user}","gists_url":"https://api.github.com/users/JuliaLang/gists{/gist_id}","starred_url":"https://api.github.com/users/JuliaLang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JuliaLang/subscriptions","organizations_url":"https://api.github.com/users/JuliaLang/orgs","repos_url":"https://api.github.com/users/JuliaLang/repos","events_url":"https://api.github.com/users/JuliaLang/events{/privacy}","received_events_url":"https://api.github.com/users/JuliaLang/received_events","type":"Organization","site_admin":false},"repo":{"id":2885268,"name":"julialang.github.com","full_name":"JuliaLang/julialang.github.com","owner":{"login":"JuliaLang","id":743164,"avatar_url":"https://avatars.githubusercontent.com/u/743164?v=3","gravatar_id":"","url":"https://api.github.com/users/JuliaLang","html_url":"https://github.com/JuliaLang","followers_url":"https://api.github.com/users/JuliaLang/followers","following_url":"https://api.github.com/users/JuliaLang/following{/other_user}","gists_url":"https://api.github.com/users/JuliaLang/gists{/gist_id}","starred_url":"https://api.github.com/users/JuliaLang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JuliaLang/subscriptions","organizations_url":"https://api.github.com/users/JuliaLang/orgs","repos_url":"https://api.github.com/users/JuliaLang/repos","events_url":"https://api.github.com/users/JuliaLang/events{/privacy}","received_events_url":"https://api.github.com/users/JuliaLang/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/JuliaLang/julialang.github.com","description":"Julia Project web site.","fork":false,"url":"https://api.github.com/repos/JuliaLang/julialang.github.com","forks_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/forks","keys_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/keys{/key_id}","collaborators_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/teams","hooks_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/hooks","issue_events_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/issues/events{/number}","events_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/events","assignees_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/assignees{/user}","branches_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/branches{/branch}","tags_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/tags","blobs_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/git/refs{/sha}","trees_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/git/trees{/sha}","statuses_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/statuses/{sha}","languages_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/languages","stargazers_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/stargazers","contributors_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/contributors","subscribers_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/subscribers","subscription_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/subscription","commits_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/commits{/sha}","git_commits_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/git/commits{/sha}","comments_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/comments{/number}","issue_comment_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/issues/comments{/number}","contents_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/contents/{+path}","compare_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/compare/{base}...{head}","merges_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/merges","archive_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/downloads","issues_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/issues{/number}","pulls_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls{/number}","milestones_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/milestones{/number}","notifications_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/labels{/name}","releases_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/releases{/id}","deployments_url":"https://api.github.com/repos/JuliaLang/julialang.github.com/deployments","created_at":"2011-11-30T18:22:15Z","updated_at":"2016-12-13T23:43:52Z","pushed_at":"2016-12-30T03:09:10Z","git_url":"git://github.com/JuliaLang/julialang.github.com.git","ssh_url":"git@github.com:JuliaLang/julialang.github.com.git","clone_url":"https://github.com/JuliaLang/julialang.github.com.git","svn_url":"https://github.com/JuliaLang/julialang.github.com","homepage":"http://julialang.org/","size":9713,"stargazers_count":75,"watchers_count":75,"language":"TeX","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":162,"mirror_url":null,"open_issues_count":17,"forks":162,"open_issues":17,"watchers":75,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/480"},"html":{"href":"https://github.com/JuliaLang/julialang.github.com/pull/480"},"issue":{"href":"https://api.github.com/repos/JuliaLang/julialang.github.com/issues/480"},"comments":{"href":"https://api.github.com/repos/JuliaLang/julialang.github.com/issues/480/comments"},"review_comments":{"href":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/480/comments"},"review_comment":{"href":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/JuliaLang/julialang.github.com/pulls/480/commits"},"statuses":{"href":"https://api.github.com/repos/JuliaLang/julialang.github.com/statuses/e746e5b54283ac21268d6b72093edac6eb4d3243"}}}}
{"actor":{"display_login":"stevengj"}}