Skip to content
Ary Borenszweig edited this page Aug 8, 2015 · 17 revisions

Why isn't the language indentation based?

Apart from the "Crystal has Ruby-inspired syntax" reason, there are more reasons:

  1. If you copy and paste a snippet of code, you have to manually re-indent the code for it to work. This slows you down if you just wanted to do a quick test.
  2. If you want to comment some code, for example comment an if condition, you have to re-indent its body. Later you want to uncomment the if and you'll need to re-indent the body. This slows you down and it's cumbersome.
  3. Macros become harder to write. Consider the json_mapping macro. It defines defs, uses case ... when ... else ... end without having to bother whether the generated code will be indented. Without end, the user would have to correctly indent the lines that would be generated.
  4. If you want a template language like ERB or ECR for a language that doesn't care about whitespace, you'll have to put those end to signal where conditions/loops/blocks end.
  5. Right now you can do: [1, 2, 3].select { |x| x.even? }.map { |x| x.to_s }. Or you can do it with do .. end. How would you chain calls in an indentation-based language? Usage of { ... } is not valid, only indentation should be used to match code blocks.
  6. Assuming one day we have a REPL, in which you tend to write code quickly, it's tedious and bug-prone to match indentation, because whitespace is basically invisible.

Because of all the above reasons, know that the end keyword is here forever: there's no point in trying to suggest changing the language to an indentation-based one.

Why don't you add syntax for XYZ?

Before suggesting syntax additions, ask this question:

  • Can it be currently done with the current syntax?

If the answer is "yes", there's probably no need to add new syntax for something that can be already done. Adding syntax means we have to be sure it doesn't conflict with the existing syntax. all users will have to learn something new, and it needs to be documented.

Maybe the current syntax is long to write or involves a couple of composed methods, but we should favor method composition instead of specific rules for specific problems.

Why trailing while/until is not supported, unlike Ruby?

In Ruby a trailing while comes in two flavors:

# This one first checks <condition> and then executes <code>
<code> while <condition>

# This one first executes <code> and then checks <condition>
begin
  <code>
end while <condition>

We find this logic confusing, and even Matz regrets it.

We had four options:

  1. Keep the same semantic as Ruby.
  2. Unify the semantic of both constructs.
  3. Disallow the second construct (which Matz seems to regret)
  4. Disallow both constructs.

We didn't want Option 1 because that would be to keep a mistake.

Option 2 is the worst choice because it will be surprising for those who come from Ruby: their code will compile fine but behave in a different way.

Option 3 sounds good, but for someone learning Crystal without previous Ruby experience, we think it might be confusing. Imagine you don't know Ruby's semantic and you see this:

<code> if <condition>

Here, it doesn't make sense to execute <code> without first checking <condition>. However, if we change the if to a while:

<code> while <condition>

there are now two possibilities: execute <code> and then check the <condition> (in a loop), or check the <condition> and then execute <code> (in a loop). And <code> comes before <condition>, so you might consider that possibility.

So, to remove all ambiguity, so that programmers don't have to stop thinking about what happens first, we decided to go with Option 4.

You can always replace this:

# Ruby
<code> while <condition>

with this:

# Crystal and Ruby
while <condition>
  <code> 
end

and this:

# Ruby
begin
  <code>
end while <condition>

with this:

# Crystal and Ruby
loop do
  <code> 
  break if <condition>
end

while is used much less frequently than if, so we think this is the correct choice.

As a bonus, the compiler's code and logic becomes simpler, because there's only one mode of operation for while, and there are less things to learn.