4 ms·
I agree in that easy parsing is a good quality of languages. But i also think a language can have a rather rich (i.e. big) syntax that's also straightforward to
by epidemian 11y ago
I agree in that easy parsing is a good quality of languages. But i also think a language can have a rather rich (i.e. big) syntax that's also straightforward to parse. That is, that the dichotomy of either having a very lean syntax like Scheme or a bloated and complicated syntax like Ruby is not so clear-cut.
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 :)
- qwertyuiop924 11y agoThis is what I mean. Syntactic ambiguity is evil, and pretty common. Even C has it (google "lexer hack"). And I definitely think that Scheme could do with a bit more syntax. Some of the whitespace indent extensions (WISP, for example) are nice, and I could do with hash table literals. So I'm off to go write those...:-D
- saurik 11y agoFWIW, as someone who does a lot of work with parsers, that block case is a bad example, as it is not at all ambiguous and does not make the language more difficult to parse. C and C++ have tons of things that make them frustrating or even nearly impossible to parse: if you just write down the naive description of this feature from Ruby you will see there are no ambiguities and it never even requires more than a single lookahead token to decide if you are using a hash or a block argument. A very similar example from JavaScript between and object literal and a block statement is actually a legitimate issue, but this feature of Ruby is so benign you can probably parse it correctly dropped into the middle of the file at the { with nothing but a single token of look-behind. The "splat" example, though, is spot on: there you have a context sensitivity. (Not so much in the [/" one, though: this does make the language more frustrating to parse, but only because the language is clearly defined over characters instead of tokens; but it is still a better example than the block argument.)
- epidemian 11y agoThanks for the clarification :) (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).