It's all about small stuff like: `async fn` (Rust) vs `fn async` (Inko), skipping parens for 0-arity functions in Inko, `::` vs `.`, `Generic<T>` vs `Generic[T]`.
It's all about small stuff like: `async fn` (Rust) vs `fn async` (Inko), skipping parens for 0-arity functions in Inko, `::` vs `.`, `Generic<T>` vs `Generic[T]`.
When you see an async, you only know you're parsing a fn while fn is the only thing in the language that can be async.
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). 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/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.
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
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.
The getter/setter pattern is also bad, but a little less so since it's exceedingly rare any real code will get executed there.