Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

New post: Web framework 2 [draft] #22

Merged
merged 5 commits into from

3 participants

@alco
Collaborator

This a draft of the new post in the Building a Web Framework series. I would appreciate feedback on the post structure and the volume of the material, i.e. is it too much or too little? Is the narrative to concise? Is there too much code?

Please don't suggest any typographic or spelling corrections yet. I will finish the post and review it myself tomorrow. After that, I'll send another ping for proof reading.

@rafaelfranca
Collaborator

I think we can split this post in two. The "Building a Client API" and the "Bonus" sections can be in the next blog post.

The narrative is concise but I think that we have too much code and text to digest in a single blog post.

_posts/2012-05-04-web-framework-2.markdown
((23 lines not shown))
+
+## Unexpected Requests ##
+
+Our server can currently handle only those requests that we have explicitly coded. If we try to send it a different kind of request, it'll crash:
+
+ iex> HelloServer.handle :oops, "/", nil
+ ** (FunctionClauseError) no function clause matching: HelloServer.handle(:oops, "/", nil)
+
+To avoid this, we'll add a catch-all default handler to _feb.ex_:
+
+ defmacro default_handle do
+ quote do
+ def handle(method, path, data // "")
+ def handle(method, path, data) do
+ # Allow only the listed methods
+ if not (method in [:get, :post, :delete]) do
@josevalim Owner

Is :put out on purpose?

@alco Collaborator
alco added a note

Yes, I only included the minimum set of methods to build a key-value store introduced at the end.

@josevalim Owner

So we should implement the delete macro in Feb, right? It may be a good opportunity to quickly review the macros of the previous chapter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
_posts/2012-05-04-web-framework-2.markdown
((53 lines not shown))
+
+I've also introduced a new function that automates error reporting a bit. Here's how it's implemented:
+
+ # Return a { :error, <binary> } tuple with error description
+ def format_error(code) do
+ { :error, case code do
+ match: 400
+ "400 Bad Request"
+ match: 404
+ "404 Not Found"
+ else:
+ "503 Internal Server Error"
+ end }
+ end
+
+Lastly, I've changed the `import` statement in the `__using__` macro to the following:
@josevalim Owner

Where is this statement? We should mention the file we are changing. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
_posts/2012-05-04-web-framework-2.markdown
((76 lines not shown))
+We'll add a call to the `default_handle` method at the end of our server definition in _server.ex_. This will make sure that this handler is going to be invoked only after every other clause was tried and failed to match the arguments.
+
+Now let's test the new handler.
+
+ $ make
+ $ iex
+ iex> HelloServer.handle :oops, "/"
+ {:error,"400 Bad Request"}
+
+ iex> HelloServer.handle :get, "wrong_path"
+ {:error,"400 Bad Request"}
+
+That's better.
+
+
+## URL Queries ##
@josevalim Owner

Maybe we should simply use the URI module that comes with Elixir?

@alco Collaborator
alco added a note

URI does not help here:

iex> URI.parse("/?search=hello&find=chuck%20norris")
{URI.Info,nil,nil,nil,"/",nil,"search=hello&find=chuck%20norris",nil,nil,nil}
@josevalim Owner

Yeah, I will add a URI.decode_query so you can do:

URI.decode_query URI.parse(uri).query

It will have the advantage of also handling encoded chars.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
_posts/2012-05-04-web-framework-2.markdown
((61 lines not shown))
+ match: 404
+ "404 Not Found"
+ else:
+ "503 Internal Server Error"
+ end }
+ end
+
+Lastly, I've changed the `import` statement in the `__using__` macro to the following:
+
+ import Feb
+ # was
+ # import Feb, only: [get: 2, post: 2]
+
+This will allow us to add new functions and make them available to the client code automatically.
+
+We'll add a call to the `default_handle` method at the end of our server definition in _server.ex_. This will make sure that this handler is going to be invoked only after every other clause was tried and failed to match the arguments.
@josevalim Owner

Instead of forcing users to call default_handle manually, instead we could use a Module.add_compile_callback to automatically add the default_handle before compiling the module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@josevalim
Owner

Great work! I have added some punctual suggestions in the comments.

However this blog post is mixing different topics into one, which makes it feels a bit heavy. One suggestion is to break it into 2 or 3 parts. For this next part, we could talk about two things:

1) The default handle mechanism

2) Adding a query argument to get/post and friends

Then we finish by saying that in the next blog post, we will (for example) do URI parsing and improve testing.

Related to code, here are a few things that could be done differently:

1) The default_handle could be added automatically, without requiring the user to type it at the end. I have done it in dynamo:

https://github.com/josevalim/dynamo/blob/master/lib/dynamo/router.ex#L20
https://github.com/josevalim/dynamo/blob/master/lib/dynamo/router.ex#L39

2) For the URI parsing, I would prefer if we could use the existing URI module that ships with Elixir. I/we can do any changes or improvements that are required;

3) In the get "/foo", query syntax, we are passing the query as the name of the variable we want the query dictionary. One option is to pass this variable implicitly and simply say that the variable query will be available in the function body.

Also, I would like to suggest to show the code of the server we built in the previous blog post at the top. I believe that many people who will see this blog post haven't seen the previous one, so showing "what we are building" will be a compelling reason for them to stay and read the series. At the end, we could also wrap up showing the final code of the feb.ex or at least a link to github.

Finally, we should rename all mentions of methods to functions. I have missed this detail in the previous blog post, but I think we can go back and fix it. :)

@alco
Collaborator

Thanks for your suggestions, @rafaelfranca and @josevalim. I don't want to make things too granular by cutting the post up into many little ones. I'm trying to build a complete functional piece in each post so that readers have something to play with in the end. So, for instance, adding a query argument to the get macro and implementing query parsing must be done within a single post to get a working thing.

_posts/2012-05-04-web-framework-2.markdown
((170 lines not shown))
+
+ iex> HelloServer.handle :get, "/idontcare"
+ :ok
+
+
+## Building a Client API ##
+
+Up until now we have been calling the `HelloServer.handle` method manually. This kind of defeats the purpose of having a useful abstraction for our web framework. Let's take a brief detour and build a client API that'll provide a more natural way for sending requests to the server. Plus, we'll run the server in a separate Erlang process so all communication with it is going to be performed via message passing. The exact messaging protocol is what we'll hide behind a few methods in our API (one for each HTTP verb).
+
+Before we do that, let's implement the messaging part first. Remember that at the very beginning we have defined a `start` method. Now it's time to review it and put it to use. Here's what the new implementation looks like:
+
+ # feb.ex
+
+ def start(module) do
+ IO.puts "Executing Feb.start"
+ pid = spawn __MODULE__, :init, [module]
@josevalim Owner

Maybe you would want to use spawn_link? So you the parent process crashes if the webserver crashes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@josevalim
Owner

@alco good point. so what do you think about:

1) Improving the URI module so we can use it and reduce the amount of code we need to write;

2) Moving the part that implements URI parsing after we add query to get/post and friends. Today, we talk about macros, then URI parsing and then macros again. If we could talk about macros and just then URI parsing, it would remove some back and forth;

3) From the testing part on, it could optionally go to another blog post;

@alco
Collaborator

1) Improving the URI module so we can use it and reduce the amount of code we need to write

Makes sense. Here's my attempt at implementing decode_query -- elixir-lang/elixir#284 It doesn't currently handle the case of "=value" properly (because of issue #282).

@alco
Collaborator

Here's a final draft. I've incorporated your suggestions and reorganized the post a bit.

@josevalim josevalim commented on the diff
_posts/2012-05-04-web-framework-2.markdown
((242 lines not shown))
+ # A 2-argument handler that also receives a query along with the path
+ defmacro get(path, query, [do: code]) do
+ quote do
+ def handle(:get, unquote(path), unquote(query)) do
+ unquote(code)
+ end
+ end
+ end
+
+The only difference between this handler and the basic query-less GET handler is that we include the `query` argument provided by the user in the generated function definition. Remember that in our [POST handler](https://github.com/alco/web-framework/blob/master/1-macros/src/feb.ex#L49) we used a `quote` form with hygiene turned off in order to define an implicit `_data` variable. Our GET handlers could also receive such implicit argument if we turned the hygiene off for them. The reason I've chosen to handle the query explicitly in this case is to show you that there are multiple options available. You may choose whichever you like most.
+
+With this new clause in place we can add another `get` request definition in the `HelloServer` module:
+
+ # server.ex
+
+ get "/search", query do
@josevalim Owner

Can we add another example as well that uses my_query instead of query to make it super clear that we are passing the variable name as argument?

@alco Collaborator
alco added a note

Give me a second.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@josevalim josevalim commented on the diff
_posts/2012-05-04-web-framework-2.markdown
((157 lines not shown))
+ IO.puts "Got a GET request with query: #{_query}"
+ :ok
+ end
+
+You can see how this approach allows us to express the fact that "/kvstore" provides some kind of service with support for multiple methods. This skeleton could be used to build a REST API, for example. This time around we'll be using implicit variables for POST data and GET query.
+
+Let's think for a moment what the `multi_handle` macro should expand to. So far we've been expanding our `post` and `get` macros into one `handle` function that uses pattern-matching to dispatch to the appropriate handler based on the incoming request. There's no reason not to use the same approach for `multi_handle`. So here's what its implementation looks like:
+
+ defmacro multi_handle(path, blocks) do
+ # Remove the entry for `:do` which is nil in this case
+ blocks = Keyword.delete blocks, :do
+
+ # Iterate over each block in `blocks` and produce a separate `handle`
+ # clause for it
+ Enum.map blocks, fn ->
+ match: {:get, code}
@josevalim Owner

One option is to not match against each specific block but simply do this:

Enum.each blocks, fn(verb, code) ->
  quote hygiene: false do
    def handle(unquote(verb), unquote(path), _query) do
      unquote(code)
    end
  end
end

Then later if we want to support other verbs, we don't need to change anything here.

@alco Collaborator
alco added a note

The get and post handlers have different third argument. It is a minor difference in this case, but I would still like to keep this explicit for now. When it comes time to adding more verbs, I'll be able to refactor :)

@josevalim Owner

Sounds good!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@josevalim
Owner

Awesome! :+1:

@alco
Collaborator

Can we add another example as well that uses my_query instead of query to make it super clear that we are passing the variable name as argument?

Done.

@alco
Collaborator

I forgot I can merge it myself :)

@alco alco merged commit e2b5c04 into from
@josevalim
Owner

Going to tweet this tomorrow (monday) or tuesday. There is no audience on twitter today. :)

@alco alco deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 4, 2012
  1. @alco

    Fix a typo

    alco authored
Commits on May 5, 2012
  1. @alco
Commits on May 6, 2012
  1. @alco
  2. @alco

    Add acknowledgements

    alco authored
  3. @alco
This page is out of date. Refresh to see the latest.
View
2  _posts/2012-04-21-hello-macros.markdown
@@ -209,7 +209,7 @@ Now let's try that again:
iex> handle :get, "/demo", nil
{:ok,"<html>\n<head>\n <title>Demo</title>\n</head>\n<body>\n <h1>Hello world!</h1>\n</body>\n</html>\n\n\n"}
-Awesome! This concludes the first part in the series. Don't forget to grab the code [from GitHub][3].If you have any questions or corrections, send a message to the [mailing list][4] or stop by the **#elixir-lang** channel on **irc.freenode.net**.
+Awesome! This concludes the first part in the series. Don't forget to grab the code [from GitHub][3]. If you have any questions or corrections, send a message to the [mailing list][4] or stop by the **#elixir-lang** channel on **irc.freenode.net**.
See you there.
View
311 _posts/2012-05-04-web-framework-2.markdown
@@ -0,0 +1,311 @@
+---
+layout: post
+title: Building a Web Framework. Part II
+author: Alexei Sholik
+category: "Web dev"
+excerpt: Last time we learned how to write macros that provide a useful abstraction for building a web server on top of the language. The server we built was able to serve static files and handle user's GET and POST requests. This week we're going to further extend our web framework, fix a couple of rough edges, and experiment with networking.
+---
+
+[Last time][1] we learned how to write macros that provide a useful abstraction on top of the language which we used for building a web server. The server we built was able to serve static files and handle user's GET and POST requests. Here's what it looked like:
+
+ defmodule HelloServer do
+ use Feb, root: "assets"
+
+ get "/" do
+ { :ok, "Hello world!" }
+ end
+
+ get "/demo", file: "demo.html"
+
+ post "/" do
+ { :ok, "You're posted!\nYour data: #{inspect _data}" }
+ end
+ end
+
+If you missed the previous post, I encourage you to [go back][1] to it and make sure you understand the concepts explained there.
+
+In this post we're going to fix a couple of rough edges and further extend our web framework. Specifically, we're going to implement URL query parsing and add a generic `multi_handle` macro that will complete our syntactic abstraction. From that point on we'll start looking at how to add networking to the framework to eventually be able to build a real website. This is what the code for our `HelloServer` is going to turn into by the end of this post:
+
+ defmodule HelloServer do
+ use Feb, root: "assets"
+
+ get "/" do
+ { :ok, "Hello world!" }
+ end
+
+ get "/demo", file: "demo.html"
+
+ post "/" do
+ { :ok, "You're posted!\nYour data: #{inspect _data}" }
+ end
+
+ ## New stuff below this line ##
+
+ multi_handle "/kvstore" do
+ post:
+ IO.puts "Got a POST request with data: #{inspect _data}"
+ :ok
+
+ get:
+ IO.puts "Got a GET request with query: #{inspect _query}"
+ :ok
+ end
+
+ get "/search", query do
+ search = Dict.get query, "q"
+ if search do
+ { :ok, "No items found for the query '#{search}'" }
+ else:
+ { :ok, "No query" }
+ end
+ end
+ end
+
+The entire source code for this and the past articles is available over at [GitHub][2]. To recap, we have two files in the _src_ directory: _feb.ex_ which contains the code for our `Feb` framework and _server.ex_ which is a simple server implementation (named `HelloServer`) based on the framework. To follow along with the post, I recommend keeping the code from the _1-macros_ directory open in a side window. I will be explaining the new stuff based on the code we have seen so far.
+
+ [1]: http://elixir-lang.org/blog/2012/04/21/hello-macros/
+ [2]: https://github.com/alco/web-framework
+
+
+## Unexpected Requests ##
+
+Our server can currently handle only those requests that are explicitly coded. If we try to send it a different kind of request, it'll crash:
+
+ iex> HelloServer.handle :oops, "/", nil
+ ** (FunctionClauseError) no function clause matching: HelloServer.handle(:oops, "/", nil)
+
+To avoid this, we'll add a catch-all default handler to _feb.ex_:
+
+ defmacro default_handle(_) do
+ quote do
+ def handle(method, path, data // "")
+ def handle(method, path, data) do
+ # Allow only the listed methods
+ if not (method in [:get, :post]) do
+ format_error(400)
+
+ # Path should always start with a slash (/)
+ elsif: not match?("/" <> _, path)
+ format_error(400)
+
+ # Otherwise, the request is assumed to be valid but the requested
+ # resource cannot be found
+ else:
+ format_error(404)
+ end
+ end
+ end
+ end
+
+I'll explain the ignored argument to this macro shortly. I've also introduced a new function that automates error reporting a bit. Here's how it's implemented in _feb.ex_:
+
+ # Return a { :error, <binary> } tuple with error description
+ def format_error(code) do
+ { :error, case code do
+ match: 400
+ "400 Bad Request"
+ match: 404
+ "404 Not Found"
+ else:
+ "503 Internal Server Error"
+ end }
+ end
+
+Lastly, I've changed the `import` statement in the `__using__` macro (again in _feb.ex_) to the following one:
+
+ import Feb
+ # was
+ # import Feb, only: [get: 2, post: 2]
+
+This will allow us to add new functions and macros and make them available to the client code automatically.
+
+To include the `default_handle` function in `HelloServer` we can add a call to it at the end of the module definition. But there is a better way, an automatic one. Elixir provides the function `Module.add_compile_callback/1` which is exactly what we need to set up our default handler and ensure that it is going to be invoked only after every other clause was tried and failed to match the arguments.
+
+We'll add the following line at the beginning the `__using__` macro definition in _feb.ex_:
+
+ defmacro __using__(module, opts) do
+ Module.add_compile_callback module, __MODULE__, :default_handle
+
+Elixir will call the `default_handle` macro as if it were placed right at the end of `HelloServer` definition and it will pass the module name as an argument (which we're not using here, so we're simply ignoring it).
+
+Now let's test the new handler.
+
+ $ make
+ $ iex
+ iex> HelloServer.handle :oops, "/"
+ {:error,"400 Bad Request"}
+
+ iex> HelloServer.handle :get, "wrong_path"
+ {:error,"400 Bad Request"}
+
+ iex> HelloServer.handle :get, "/404"
+ {:error,"404 Not Found"}
+
+That's better.
+
+
+## Generic Handle ##
+
+Let's add one more piece of sugar to our framework by allowing the users to write one function that will handle several HTTP methods, useful for defining various interactions with a single path spec. For instance:
+
+ multi_handle "/kvstore" do
+ post:
+ IO.puts "Got a POST request with data: #{_data}"
+ :ok
+
+ get:
+ IO.puts "Got a GET request with query: #{_query}"
+ :ok
+ end
+
+You can see how this approach allows us to express the fact that "/kvstore" provides some kind of service with support for multiple methods. This skeleton could be used to build a REST API, for example. This time around we'll be using implicit variables for POST data and GET query.
+
+Let's think for a moment what the `multi_handle` macro should expand to. So far we've been expanding our `post` and `get` macros into one `handle` function that uses pattern-matching to dispatch to the appropriate handler based on the incoming request. There's no reason not to use the same approach for `multi_handle`. So here's what its implementation looks like:
+
+ defmacro multi_handle(path, blocks) do
+ # Remove the entry for `:do` which is nil in this case
+ blocks = Keyword.delete blocks, :do
+
+ # Iterate over each block in `blocks` and produce a separate `handle`
+ # clause for it
+ Enum.map blocks, fn ->
+ match: {:get, code}
@josevalim Owner

One option is to not match against each specific block but simply do this:

Enum.each blocks, fn(verb, code) ->
  quote hygiene: false do
    def handle(unquote(verb), unquote(path), _query) do
      unquote(code)
    end
  end
end

Then later if we want to support other verbs, we don't need to change anything here.

@alco Collaborator
alco added a note

The get and post handlers have different third argument. It is a minor difference in this case, but I would still like to keep this explicit for now. When it comes time to adding more verbs, I'll be able to refactor :)

@josevalim Owner

Sounds good!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ quote hygiene: false do
+ def handle(:get, unquote(path), _query) do
+ unquote(code)
+ end
+ end
+
+ match: {:post, code}
+ quote hygiene: false do
+ def handle(:post, unquote(path), _data) do
+ unquote(code)
+ end
+ end
+ end
+ end
+
+When this macro is called, it'll get a list of the following form as its `blocks` argument:
+
+ [{:do, nil}, {:get, <user code>}, {:post, <user code>}]
+
+Because the `get:` block immediately follows the `do`, the latter gets no code and we can safely discard it. This is what we do at the beginning of our `multi_handle` macro. Next, we pick each code block in turn and emit a function definition with corresponding arguments. The code for each of the code blocks is similar to the GET and POST handlers we have defined earlier.
+
+Finally, let's test it in `iex`:
+
+ $ make
+ $ iex
+ iex> import HelloServer
+ []
+
+ iex> handle :get, "/kvstore"
+ Got a GET request with query: ""
+ :ok
+
+ iex> handle :post, "/kvstore"
+ Got a POST request with data: ""
+ :ok
+
+ iex> handle :post, "/kvstore", "secret"
+ Got a POST request with data: "secret"
+ :ok
+
+So far so good. Now let's add the ability to get the query from a URL.
+
+
+## URL Queries ##
+
+We'd like our server to be able to handle queries of the form `/search?q=donut`. The `URI` module which ships with Elixir has the right tools for the task: `parse` and `decode_query`. The first one parses a URI and stores it in a `URI.Info` record. The second one accepts a query string and returns a dict.
+
+We'll implement a `split_path` function in `Feb` that will return a tuple of the form `{ path, query }` where `query` is going to be an orddict. If the path does not contain a query, an empty orddict will be returned.
+
+ # Return { path, query } where `query` is an orddict.
+ def split_path(path_with_query) do
+ uri_info = URI.parse path_with_query
+ { uri_info.path, URI.decode_query(uri_info.query || "") }
+ end
+
+The code is pretty straightforward. Let's make sure that it works:
+
+ $ make
+ $ iex
+ iex> Feb.split_path "/search"
+ {"/search",{Orddict.Record,[]}}
+
+ iex> Feb.split_path "/search?q=hello&find=chuck%20norris"
+ {"/search",{Orddict.Record,[{"find","chuck norris"},{"q","hello"}]}}
+
+OK, so that's done. But we have no way of passing the query to the server. This is solved by adding another clause to the `get` macro as follows:
+
+ # feb.ex
+
+ # A 2-argument handler that also receives a query along with the path
+ defmacro get(path, query, [do: code]) do
+ quote do
+ def handle(:get, unquote(path), unquote(query)) do
+ unquote(code)
+ end
+ end
+ end
+
+The only difference between this handler and the basic query-less GET handler is that we include the `query` argument provided by the user in the generated function definition. Remember that in our [POST handler](https://github.com/alco/web-framework/blob/master/1-macros/src/feb.ex#L49) we used a `quote` form with hygiene turned off in order to define an implicit `_data` variable. Our GET handlers could also receive such implicit argument if we turned the hygiene off for them. The reason I've chosen to handle the query explicitly in this case is to show you that there are multiple options available. You may choose whichever you like most.
+
+With this new clause in place we can add another `get` request definition in the `HelloServer` module:
+
+ # server.ex
+
+ get "/search", query do
@josevalim Owner

Can we add another example as well that uses my_query instead of query to make it super clear that we are passing the variable name as argument?

@alco Collaborator
alco added a note

Give me a second.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ search = Dict.get query, "q"
+ if search do
+ { :ok, "No items found for the query '#{search}'" }
+ else:
+ { :ok, "No query" }
+ end
+ end
+
+Let's take a step back and look again at the definition for this `get` macro in _feb.ex_. It takes its second argument (the `query`) and puts it in place of the third argument in the definition of `handle`. The reason we're unquoting it (instead of simply writing `query`) is to allow the user to choose an arbitrary name for this argument. So, for instance, the following definition in `HelloServer` will work the same way:
+
+ get "/search", my_query do
+ # the query dict is now stored in `my_query`
+ IO.inspect my_query
+ end
+
+All that's left is to test the code:
+
+ $ make
+ $ iex
+ iex> { path, query } = Feb.split_path "/search?q=donut"
+ {"/search",{Orddict.Record,[{"q","donut"}]}}
+
+ iex> import HelloServer
+ []
+
+ iex> handle :get, path, query
+ {:ok,"No items found for the query 'donut'"}
+
+ iex> handle :get, "/search", Orddict.new
+ {:ok,"No query"}
+
+ # Let's also test our generic handler with a query
+ iex> handle :get, "/kvstore", URI.decode_query("key=value")
+ Got a GET request with query: {Orddict.Record,[{"key","value"}]}
+ :ok
+
+Great! With this we have completed the implementation of our simplistic DSL. Let's wrap up for this week and do a quick review of what we have learned.
+
+
+## Conclusion ##
+
+By this time you know how to use macros to your advantage by defining appropriate abstractions that allow writing code that's easy to grasp. Another benefit of this approach is that it hides implementation details so that they can be changed without touching the application-level code. We'll see an example of this in a future post.
+
+Next time, we'll implement a basic networking layer for our framework. It will serve as a basis for testing and adding support for the real HTTP protocol later on.
+
+---
+
+This concludes the second part in the series. Don't forget to grab the code [from GitHub][2]. I'd like to thank [@rafaelfranca][4] and [@josevalim][5] for their valuable suggestions. If you have any questions or corrections, send a message to the [mailing list][3] or join the the **#elixir-lang** channel on **irc.freenode.net**.
+
+See you there.
+
+ [3]: http://groups.google.com/group/elixir-lang-core
+ [4]: https://github.com/rafaelfranca
+ [5]: https://github.com/josevalim
Something went wrong with that request. Please try again.