Mercerenies / tfactor

0 stars 0 forks source link

No More Aliases? #23

Open Mercerenies opened 4 years ago

Mercerenies commented 4 years ago

Here's an interesting question: why do we have an alias system? Seriously. Let's pretend for the moment that #2 is implemented, so we have a private keyword. alias is really just a private synonym. open is really just a private include. The implicit Prelude open could be a private implicit Prelude include (probably with the ability to make it explicit, so you can hide names if you so desire). Modulo some minor duplicate name issues, the behaviors are pretty much equivalent. The only thing we'd have to really implement additionally is the implicit open on the current module and its parents. But even that can probably be done with some atQIdResourceGeneral manipulation (now that all lookups go through that function, in one way or another).

Now, let me justify why I think it's worth considering getting rid of the alias system altogether. It was one of the first parts of this thing I wrote, before all the module shenanigans. It parses strictly top-to-bottom, whereas the rest of the system has an explicit load order. It makes a ton of assumptions about the way the rest of the system works, in order to guess at which names exist or will be in scope, which is a massive violation of DRY. And, largely as a consequence of all the previous reasons, it's just clunky and a mess to deal with. The original idea was that we do alias resolution first so we don't have to think about it anytime later in the process, but the result has just been a messy, monolithic early pass that has to approximate the rest of the system in order to be fully correct.

Mercerenies commented 4 years ago

I think the problem is unsolvable the way I've posed it. With the current alias system, we're left to guess using incomplete information, and we'll never be able to get it 100% right because the module load phase is going to (obviously) depend on what names alias resolution returns, and alias resolution has to guess at the result of module loading. Even if there was a fixed point (which I'm not confident there is), and even if it was unique (which I'm very unconvinced of), the result would be an unintuitive mess of guess-and-check.

Now, if we get rid of the alias system and keep the module resolution system the way it is, we have a similar problem. What order do we load modules in? Until we load modules, we can't be 100% certain of what a name resolves to (since aliases and especially open can't fully resolve yet), and until we resolve aliases, we can't know exactly how to load modules. Again, mutual dependence. Again, ugly solution, if one even exists at all.

I think I'm starting to understand why languages like OCaml disable this kind of load reordering. It's feasible in simpler systems like Java or C#, where there's no real equivalent of a "functor", every binding is late, and the types are pretty explicit. But in a language like this where we really know nothing from the beginning, it's infeasible.

So... what does that mean for this project? To start with, none of this affects the typechecker. As far as I can tell, mutual recursion at the term level is still fine, and the final load phase resolves it for macros. Since we require top-level type signatures, that problem is solvable. But we don't require the same for modules. So my thought is this. We evaluate modules strictly in the order we see them. If I see a mod declaration, I go inside and evaluate it, making all the resources along the way, top-to-bottom. If I see a synonym, I evaluate it based on what I know of the system right then and there. If I see include, the thing I'm including had better exist already.

If you ever have need for mutual recursion, then we allow something akin to C++ forward declarations. Namely, you can declare that a module will soon satisfy a given trait, without actually defining the module yet. That, pragmatically, can be implemented with placeholder resources, which will be filled in later and will err if they aren't replaced correctly or if they aren't replaced at all. Yes, it's imperative, and yes it's much less pretty than the original system I had envisioned. But it's simple, it's intuitive, and it's not really that restrictive. And it's solvable. That's a big one.