The difficulty of parsing Ruby's syntax (both for machines and for humans) comes from some unfortunate design decisions, not so much from its vastness. For example, using the same symbols, {}, to denote hash literals and blocks makes parsing/understanding the syntax of method calls more difficult:
def foo(*args)
puts "args: #{args}, block_given?: #{block_given?}"
end
# Parentheses can be omitted when calling methods:
foo # args: [], block_given?: false
foo 42 # args: [42], block_given?: false
foo 42, "bar" # args: [42, "bar"], block_given?: false
# *Except* when the first argument is a hash. This:
foo({}) # args: [{}], block_given?: false
# Means something completely different than this:
foo {} # args: [], block_given?: true
# But if the hash is not the first argument then it's OK, you can omit parens
# again :3
foo 42, {} # args: [42, {}], block_given?: false
This is not a problem of having dedicated syntax for blocks and hash literals. It's a problem of having chosen conflicting syntactic forms for them. If blocks could only be denoted with do/end, or with a different set of symbols than hash literals, this would not be a problem.And this is just one example of conflicting syntactic forms. In Ruby, sadly, there many cases of this. E.g., using * both for multiplication and for argument "splatting" means that
a * b
is not the same as a *b
when `a` is a method, but actually is the same when `a` is a local variable; or `foo "bar"` and `foo"bar"` being two perfectly valid ways of calling foo with "bar" as an argument, but `foo [42]` and `foo[42]` not being two perfectly valid ways of calling foo with [42] as an argument; and probably more.So, as long as syntactic forms are carefully chosen so that they don't have weird and conflicting edge cases between each other, i think a language can have a rather big syntax without it being difficult to parse/understand.
Disclaimer: i love Ruby; it's my bread-and-butter language. But i wish it didn't have as many syntax quirks as it does :)
(And sorry for not having replied earlier, i seems HN doesn't notify about replies any more.)
The point i was trying to make was mostly about the difficulty of understanding the syntax (i.e. "parsing" for humans), not so much about syntax ambiguity or having to use a more sophisticated parsing strategy. The comments on the code represent the "rules" that a human might follow to try and make sense of that syntax. They are not as simple as they could be had Ruby only allows blocks to be denoted with do/end.
However, about the complexity on the parser itself, doesn't this case of block syntax need at least some dedicated logic to take care of ´foo {}´ not parsing the {} part as a hash literal expression but rather as a block? (e.g., maybe adding some explicit priories to the parser rules, or making the lexer add hidden parentheses so that the parser sees ´foo() {}´ and doesn't have to make that distinction).
Though I'm not sure if that falls under syntax or semantics for purposes of this discussion.
Confirmation bias.
https://en.wikipedia.org/wiki/Modula-3
...knows that one can have key features of C++ for systems programming with an easy-to-parse, safer, and [for a time] more efficient language. The SPIN OS team even modified it with type-safe linking so one could hotload code into the OS. What I saw there was "hotload code into running system" w/ associated benefits during updates. Or safer, better JIT. ;)
Anyway, combined with LISP-style macros & irritating parts removed (eg uppercase, verbosity), you get a system language that blows C++ away in productivity (esp w/ DSL's), compiler efficiency, maintenance, and possibly run-time efficiency. The world might have went in a different direction but they better be honest about parsing pain they created. And all the productivity opportunities lost due to that.
The syntax of older-style BASIC, for example, hews close to the assembly parsing model, just adding a few special cases for different keywords. Algol syntax has a tradition of design based around algorithm description first, with generalized power second. The Forth or Lisp approaches, because they are very generic and powerful, naturally pull those languages in the direction of extensive metaprogramming.
In that sense one can see that the more sugary syntaxes treat the programmer more like an end-user, because they direct the workflow. Generalized ones encourage a DIY approach to everything. There are tradeoffs to each strategy.
In this case, "easy parsing" is OK, but not when it conflicts with "tried and true".
Choosing Lisp as your "tried and true" is acceptable, though...
Simpler general parsing (eg Lisp) is more powerful, but that's a negative for learnability of the language.