gracelang / minigrace

Self-hosting compiler for the Grace programming language
39 stars 22 forks source link

Compiler hangs when compiling circular imports #285

Open IsaacOscar opened 5 years ago

IsaacOscar commented 5 years ago

If I have a file a.grace with:

import "b" as b

and a file b.grace with

import "a" as a

Compiling either one of them with mgc causes the compiler to hang without printing anything.

I understand that the grace spec forbids such modules, but I am curiouse as to why. Why not just make them syntax sugar for objects, e.g:

my above code could be treated as equivalent to

def b = object { 
   def a = outer
   /*rest of b.grace*/ }
/*rest of a.grace*/
kjx commented 5 years ago

Good question! although I note Kernan says

CyclicImportError: R2011: Module "a" transitively imports itself. The chain of imports a -> b -> a cannot be satisfied as it would require accessing a before it was ready.

I generally try to check language level things across both kernan and e.g. amg.

We're pretty keen on fixed, comprehensive order of evaluation. This would change the order of evaluation depending on which file was run first. Also, modules are nested inside dialects, and your transformation doesn't account for those.

IsaacOscar commented 5 years ago

Yes I see the problem with respect to evaluation order. I was thinking of this myself, and came up with a requires "x" as b stateement, thats like an import, but will ensure that "x" is evaluated first. (if it can't be, e.g. you have cyclic dependencencies of requires, then you will get an error). This is important if you want a module's initialisation code to read a def declared by b, naturally you would want b to be initilised first. However, often a module just contains a bunch of methods and type-declarations; but no real initilisation code. This is where it's particular usefull to suport recursive modules. One option is to not initilise a modules variables until after everything that "import"s it has been initilised. Then you can get an error message if a module depends on it's imports being evaluated first, when they should be using "require" first.

As for other side-effects (e.g. I/O), I don't personally see the problem with the evaluation order depending on which file is compiled first, as one is likely to compile a project from the same main file each time.

As for dialects, that's a good point. Perhaps we could add support for dialects in objects? In which case outer would correspond to the dialect, and outer.outer would correspond to the enclosing object (since you're not supposed to acccess the dialect of a dialect).

kjx commented 5 years ago

As for dialects, that's a good point. Perhaps we could add support for dialects in objects?

yeah. the syntax is (mostly) clear. the fun is actually the interaction of two or more dialects - how much of the enclosing scope or dialect do you keep? Currently that's a low priority for me, but there's certainly research around dialects (like making them go fast in Moth without hardcoding everything). One could look at this as part of that I'm sure...

In which case outer would correspond to the dialect, and outer.outer would correspond to the enclosing object (since you're not supposed to acccess the dialect of a dialect).

We still hates outer my precious!
(see https://github.com/gracelang/language/issues/140#issuecomment-357420869)

IsaacOscar commented 5 years ago

Why not self1 for outer? And self2 for outer.outer, etc? In fact my type-checker is using a similar representation for it's ast: self == outer0, outer == outer1, outer.outer == outer2. Etc. (Thus I do not support constructs like object{}.outer, which one may interpret as equivalent to self).

Of course this1 would be even better!

kjx commented 5 years ago

Why not self1 for outer? And self2 for outer.outer, etc?

we still hates outer my precioussssss

(Thus I do not support constructs like object{}.outer, which one may interpret as equivalent to self).

you can't have x.outer any more than you can have x.self.

IsaacOscar commented 5 years ago

Sure, encapsulation and all that... but x.outer makes perfect sense! Actually, I think .. is a better syntax, instead of self.foo, you just write .foo, and instead of outer.foo, write ..foo. If you're going to have a language with nested objects, why wouldn't you wan't to easily access and call methods on your enclosing object?

As for the linked issue, I don't even see why you need confidential at all... Isn't lexical scoping sufficent? E.g.:

method C {
     def my_field = /*some computation*/
     object {
              method public_method { my_field + 2 }}

Now the result of C has it's private state (i.e. my_field) perfectly encapsulated.

IsaacOscar commented 5 years ago

Also, if you allow def's to be recursive, then then only reason you'd ever need self/outer is to disambigoute shaddowing:

method bar { ... }
method C {
    def res = object {
             method foo { ...; return res }
             method bar { outer.bar } }
    res }

As a side note, if you allow def x = e to evaluate to x (instead of done) the last line above won't be needed

kjx commented 5 years ago

Sure, encapsulation and all that... but x.outer makes perfect sense!

it makes sense, but it's still a bad idea.

If it's not the syntax, what is you're problem with outer? If you're going to have a language with nested objects, why wouldn't you have it?

because - with a semantic definition for nesting, you don't need it.

O'CAML names "self" for each object explicitly with extra syntax.

In Grace you can name objects internally or externally

def externalName = object { def internalName = self ...

if encapsulation is calculated on the dynamic relationships between objects, then you don't need special syntax. I think a type checker would still need to track those relationships, ditto a VM, although caching may fix things.

for what it's worth, this is again research - James

apblack commented 5 years ago

This conversation seems to have gone off the rails. Outer is not a message, and never has been. object{}.outer is not allowed. If you think that we should change the language to make it a message, open an issue on the language issue tracker, so that I can remind you that an object built using inherit or use has more than one outer.

Then delete this comment and all the garbage about outer.

apblack commented 5 years ago

I understand that the grace spec forbids such modules, but I am curious as to why

The “why” is that the original implementor of Grace was adamant that recursive dependencies are a bad idea. Certainly they are confusing to novices, but we do have use-cases (#282) in setting up the environment where they can be useful. As the OP points out, recursive objects are actually rather common, and attempts to read the value of an uninitialised def or var var are already caught.