Comment by lerno
7 days ago
`::` simplifies the module vs identifier resolution.
In C3 there is something called "path shortening", allowing you to use `foo::bar()` in place of something like `std::baz::foo::bar()`. To do something similar with `.` is problematic, because you don't know where the path ends. Is `foo.baz.bar()` referring to `foo::baz::bar()` or `foo::baz.bar()` or `foo.baz.bar()`?
I would just explicit require pulling `foo` into scope, like Rust does. Though in fairness Rust also uses :: for scope anyway and I don't think it's as bad as sfpotter says.
Sure, but in practice I believe most developers would find it intuitive to just type . everywhere.
It feels more lightweight and consistent, and collisions aren’t super common once you adopt some conventions.
It’s a tradeoff for sure, but this preference comes from having lived in both worlds.
> Sure, but in practice I believe most developers would find it intuitive to just type . everywhere.
Yeah, but in practice I try not to produce write-only code.
Code is for reading more than for writing, hence make it easier to read. If it becomes easier to write as a side-effect, then so be it.
I’ve lived in both and I prefer ::.
I prefer a world where there is no distinction between modules (or namespaces) and object, so '.' it is. (and I'm almost exclusively a C++ programmer).
N=1; I find :: a lot more obvious.
> `::` simplifies the module vs identifier resolution
The identifier on the right is looked up in the scope of the identifier on the left. If it resolves to a module, then it's a module. If it resolves to a function, then it's a function. If the left side is a pointer (not a symbol with a scope) then the right side resolves to a member.
It also makes refactoring much easier - changing a pointer to a reference does not require a global search/replace of -> with .
C3 has "path shortening", so for example given `open(...)` in std::io::file is usually used as `file::open(...)`. If we would to write this as `file.open(...)`. Consider now the case of mistyping `open`: `file.openn(...)`. Is this (A) mistyping the function open in module `std::io::file` or is it (B) the global/local `file` is missing from the current scope?
Also, "io", "file", "random" etc are commonly used variables, so the issue with shadowing is real.
`File file = file::open(...)` is completely unambiguous and fine. `File file = file.open(...)` on the other hand would be bad.
If the language had flat modules, or no path shortening, then it would be possible.
> Is this (A) mistyping the function open in module `std::io::file` or is it (B) the global/local `file` is missing from the current scope?
D uses a spell checker for undefined identifiers, and the dictionary is all the identifiers in scope. It has about a 50% success rate in guessing which identifier was meant, which is quite good.
> Also, "io", "file", "random" etc are commonly used variables, so the issue with shadowing is real.
If the same identifier is accessible through multiple lookup paths, an error is issued. If a local variable shadows a variable in an outer scope, and error is issued.
We've developed this over several years, and it works quite well.
Path shortening can be done with:
or:
2 replies →
I feel like path shortening is the issue, and IMO it's an unnecessary feature. I don't think most programmers are bothered by the need to explicitly import what they use. I'd personally prefer to explicitly import what I use, and refer to it in whatever way the import statement would imply.
In Rust where modules share a namespace with other identifiers, I just pick different variable names, or write my imports so they don't conflict. It's not that big a deal.
2 replies →
This is the kind of thing I don't want to have to think about as a programmer. The compiler should just make it work.
But you have to. Ambiguities hint at lack of redundancies that also affects code reading. When you quickly scan something like `file.open` vs `file::open` the former is also more unclear to a reader without more context.