3 ms·
I have mixed feelings on Rust's syntax, especially around generics, lifetimes, and the `modifier -> keyword` syntax (i.e. `async fn` or `pub fn`). For Inko, I w
by YorickPeterse 3y ago
I have mixed feelings on Rust's syntax, especially around generics, lifetimes, and the `modifier -> keyword` syntax (i.e. `async fn` or `pub fn`). For Inko, I wanted something that's easy to parse by hand, and no context specific parsing (e.g. `QUOTE -> something` being the start of a lifetime in one place, but a char literal in another place).
Another motivator for that is that years ago I worked on Rubinius for a while (an implementation of Ruby), and helped out with a parser for Ruby (https://github.com/whitequark/parser https://github.com/whitequark/parser). The Ruby developers really liked changing their already impossible syntax in even more impossible ways on a regular basis, making it a real challenge to provide syntax related tools that support multiple Ruby versions. I wanted to avoid making the same mistake with Inko, hence I'm actively trying to keep the syntax as simple as is reasonable.
As for the specific examples:
`fn async` means your parser only needs to look for `A | B | fn` in a certain scope, instead of `A | B | fn | async fn`. This cuts down the amount of repetition in the parser. An example is found at https://github.com/inko-lang/inko/blob/8f5ad1e56756fe00325a3b4ed0fc3c46e1d50e65/ast/src/parser.rs#L1152-L1169 https://github.com/inko-lang/inko/blob/8f5ad1e56756fe00325a3..., which parses the body of a class definition.
Skipping parentheses is directly lifted from Ruby, because I really like it. Older versions took this further by also letting you write `function arg1 arg2`, but I got rid of that to make parsing easier. It's especially nice so you can do things like `if foo.bar.baz? { ... }` instead of `if foo().bar().baz?()`, though I suspect opinions will differ on this :)
Until recently we did in fact use `::` as a namespace separator, but I changed that to `.` to keep things consistent with the call syntax, and because it removes the need for remembering "Oh for namespaces I need to use ::, but for calls .".
`[T]` for generics is because most editors automatically insert a closing `]` if you type `[`, but not when you type `<`. If they do, then trying to write `10<20` is annoying because you'd end up with `10<>20`. I also just like the way it looks more. The usual ambiguity issues surrounding `<>` (e.g. what leads to `foo::<T>()` in Rust) doesn't apply to Inko, because we don't allow generics in expressions (i.e. `Array[Int].with_capacity(42)` isn't valid syntax) in the first place.
- pzmarzly 3y agoAh that is indeed making the parser much nicer. Thanks for the explanation! Also, thanks a ton for your work on Ruby parser gem, so many awesome projects are using it now! https://rubygems.org/gems/parser/reverse_dependencies https://rubygems.org/gems/parser/reverse_dependencies
- Mawr 3y ago> Skipping parentheses is directly lifted from Ruby, because I really like it. Older versions took this further by also letting you write `function arg1 arg2`, but I got rid of that to make parsing easier. It's especially nice so you can do things like `if foo.bar.baz? { ... }` instead of `if foo().bar().baz?()`, though I suspect opinions will differ on this :) I've spent a little bit of time in Ruby land and such cute syntactical tricks tend to have limited benefits (none?), but significant downsides. This: foo.bar() communicates very clearly that arbitrary code will get executed now. But: foo.bar could be arbitrary code execution or a trivial and cheap field access. No way to know which without extra information. Reading code like this stutters; every instance of x.y requires pausing to look up type information. There's a second problem. Allowing: foo.bar but not: foo.bar x y applies special treatment to functions with zero arity. Is there a reason for that? As far as I know function arity carries no special significance. Consistency shouldn't be disturbed without a sound justification.
- karmakaze 3y agoThere's one advantage of foo.bar calling a function, it works well with referential transparency. Whether it's a property/value or function, only the result matters. I can't say it's a big difference though, I've only been mildly annoyed by having to change all call-sites when changing between them. Other languages allow code bodies for getters/setters (foo.bar = ...) so it still hides the call. For a C-style syntax language having the () seems less surprising.
- Mawr 3y agoWhen it comes to correctness, only the result matters, sure. But not so for performance and code understandability. Obscuring points of arbitrary code execution is awful. The getter/setter pattern is also bad, but a little less so since it's exceedingly rare any real code will get executed there.