As a newbie coming into Elixir without either Ruby or Erlang background, I didn't find anything in the syntax to be a real pain point (although understanding optional parenthesis usage in CoffeeScript did help).
As a newbie coming into Elixir without either Ruby or Erlang background, I didn't find anything in the syntax to be a real pain point (although understanding optional parenthesis usage in CoffeeScript did help).
I have mentioned before this is often the most unrewarding aspect of designing a language because, it doesn't matter what you do, you will always get an opposing opinion. Here are some examples of what I have heard or read multiple times throughout the years:
* Using the brace syntax is seen as catering to common languages (like C and Java) which would arguably cause a lot of confusion when added on top of a functional language
* Going with Lisp is always a matter of love or hate. Some people will love it and some people is going to really hate it
* The same with space-based indentation. A lot of people praise its conciseness, a lot of people curse the code being extremely hard to move around (this was honestly my second choice but it would get complex inside quoted expressions)
* The do-end blocks gets some praise for being readable (less punctuation characters) but also a bad rap for being verbose
To be clear, I am not calling you out, the point is exactly that everyone will have their preferences and if I was not writing this comment to you, I'd definitely be writing it to someone else. :)
However, it's quite obvious that you are hugely influenced by Ruby's syntax. What I don't understand is why, in copying Ruby's overall flavour of syntax, you decided to make it a little worse.
My hypothesis is that you discovered that this syntax allowed an elegant, unifying structure to the internal implementation, which is fine — but as a user, it comes across as an annoying wart. The parser should know perfectly well that after "def" comes a function name, so why does it need the "do" to demarcate the function body? It would have been just as ugly in Ruby, which goes for terseness in the common case (eg., "if" can take a "then").
Criticisms aside, I should add that this is the only thing so far that has annoyed me about Elixir.
Elixir's do/end blocks actually enhance the syntax it inherits from Ruby by making it very, very consistent. Defining any type of entity takes a do/end block - def, defmodule, defmacro, defprotocol, defimpl, and probably some that I have forgotten.
Your hypothesis is almost right. The important bit is that it is not about an internal structure, it is about a public structure that is accessible to macros. It is about the language AST.
Before there was syntax, I had defined I would like to have Lisp-style macros in Elixir. For that we need to have a regular syntax because when you are composing AST nodes you don't see keywords.
As an example, imagine you have code generation where you may generate a function called `add` and we treat `def` as a keyword. You would do something like:
quote do
def add(x, y)
x + y
end
end
Now let's say the function may be public or private. You would try to write it like this: kind = :def # or :defp
quote do
unquote(kind)(add(x, y))
x + y
end
end
And now the parser would be unable to parse the code above because there is no longer a `def` or `defp` keywords where the parser would know it could skip the `do` bit. Therefore having an uniform syntax means you can generate and transform code without worrying about special cases, without evaluating strings and without resorting to special functions/methods for dynamic code definition. You can just compose code!I also believe this matters a lot to the end-user, positively, for two reasons.
1. Consistency. The language is consistent and you can extend it in a consistent fashion. For example, Elixir code is written inside modules. `defmodule` is not a keyword, it is an identifier as any other in the language:
defmodule MyModule do
end
We also have defprotocol, which is implemented as a macro, and looks exactly the same: defprotocol MyProtocol do
end
If we made defmodule a keyword, we would put ourselves into a corner as we would either have to make defprotocol a keyword too (for consistency) or have defmodule and defprotocol looking slightly different (causing confusion).2. The second reason is that enabling such AST macros allows us to extend the language in different and interesting ways you can't do cleanly with other languages. One example I like to give in talks is the assert macro in our ExUnit test framework:
assert foo == bar
Different to many unit test frameworks, we don't need `assert_equal`, `assert_more_than` and friends. As a macro, `assert` navigates the AST, which is quite uniform, and extract information from the code.Another example I gave at my talk at ElixirConf is that we could even extend language constructs like our for comprehensions. Someone can implement a `parallel` macro that transforms a regular for comprehension into one that runs in multiple Elixir processes (leveraging multicore):
parallel for user <- users do
fetch_user_data(user)
end
This only works if parallel can look at the code and transform it in multiple ways.TLDR: The unifying syntax is an important part of Elixir macro system which brings consistency and provides an extensible foundation to the language that can be leveraged by its users.