[Grace-core] Modules & Inheritance

Kim Bruce kim at cs.pomona.edu
Thu Oct 18 11:10:36 PDT 2012


Modules and Dialects:
 
Sorry to join this late.  A new grandchild and midterms have kept me busy the last few days.  However, I think these questions are important and I worry that attempts at uniformity and using a small number of notions is making our life harder rather than easier.
 
Let me run through a few basics that I think are important.  We have been treating modules as objects, which has the advantage of not introducing new notions, but there are some issues that I don't believe we have explicitly addressed.  It also leads to the other complications that have been discussed in the last few days, though I think there are ways around them.  (I'll come back to dialects later, but this should be related as we seem to be addressing dialogs as specialized modules.)
 
Somewhat independently we have been thinking of program units (possibly files, but maybe not) as implicitly defined objects.  Thus we think of all top-level definitions as being part of this top-most objects.  This is convenient when we are thinking of the object on its own, but likely has ramifications when we think about importing libraries or using them as dialects.
 
First, one thing that I don’t think we’ve every made clear.  Type definitions should not be considered components of objects.  Objects have defs, variables, and methods, but not type components.  This is important for keeping us out of dependent type systems like Scala.  I don’t want to have to type-check code like
    method m(o:SomeType) -> OtherType {
         var x: o.t
        …
   }
Type definitions have static scope like other definitions, but are otherwise treated mainly as abbreviations.  Hopefully this is not controversial.  Yet, as you'll see, this has major implications if we want to treat modules as objects.

The bottom line for me is that type expressions need to be statically determinable.

So, what is the meaning of a module?  We could handle everything except the types as an object with all of the other components of the module.  This is useful as there might be top-level methods (or other definitions) that either explicitly or implicitly refer to a "self" at the top-level.  I'd hate to have an unbound self bound as result of an import into another environment (module).  So perhaps a module could consist of a top-level object and a set of type definitions.
 
Let’s talk about importing from modules.  My personal preference would be for modules to have export lists that limit would could be exported.  Haskell is a good example of a language with export lists.  The syntax could be something like
   module SomeModule(ids to be exported) {
   ...
   }
Here the list of ids to be exported would include type defs as well as defs, vars, classes, and methods that are defined at the top level

On the other hand, we could just export all public top-level definitions (including type defs).  It occurs to me that top level features labeled confidential would have module scope, so that might not be so bad.  However, I like the idea of listing exports as it provides good documentation of what is being exported.  (With an industrial-strength language I'd want there to be separate interfaces, where we could associate different interfaces for different clients, but with a teaching language, I think this is sufficient.)

Now, I believe we had talked about imports as providing qualified names:

   def myMod = platform.SomeModule
   def obj = myMod.objFeature   // extract an exported feature from the object corresponding to the module.
   // question:  How would we write this for a class?  class myClass = ...?

Here is where the problem of not having types as parts of objects shows up.  If myMod includes the type definitions in SomeModule then it is no longer a legal object.  Perhaps that use of platform just gives the object and leaves out the type definitions.  Then we need a different (but related) way of extracting the type definitions.  I don't particularly like the following, but I don't have anything better at the moment.  If we want to get the definition of stackType from SomeModule, we could write:

   type stackType = platform.SomeModule.stackType

It seems strange not have to add something onto the object-extracting declaration as well:

   def myMod = platform.SomeModule.object

but that seems clunky.  Suggestions??

In essence what is being suggested here is that a module is an object plus a collection of type definitions.  

Again, the problem we need to solve here is that modules include type definitions as well as other features to be imported.  Types are not part of objects, so just wrapping everything up as part of an object doesn't work.  The main issue here seems notational.  ... and notice that there is no problem here importing multiple modules into another.  Treating importing as inheritance seems way too complicated to even consider because of the problems with multiple inheritance.
--------------------------

Now, finally lets get to dialects.  These strike me as special kinds of modules that are used to define the language.  In particular, we want the items made available in the dialects to be used without a module name prefix as they will include our control constructs, for example.  Like regular modules, dialects will include types, so we have the same issues as above with trying to understand dialects as objects.

It seems to me that the simplest approach is to write a dialect as a module, but have the import work differently:

#dialect SomeDialect

would behave like import except that the system would add the shortened names automatically.  Thus rather than having to write

   def for = platform.SomeDialect.for

the compiler would automatically insert those definitions for everything to be exported from the module, including types.  That is a kind of macro capability, but it seems to me to be relatively innocuous.  This way anyone could write their own dialect.  Similarly it would be easy to build a dag of dialects because the dialects would just be modules.  We could write a module baby, then a module stateful that imports baby, a module classy that also imports baby, and then a module basic that imports both stateful and classy.  When used as a dialect, basic would make available all of the features desired.

Let me know if I missed anything important -- and what you think.

Kim



On Oct 17, 2012, at 2:17 PM, James Noble wrote:

> Hi all
> 
> [for those of you on grace-core, you're probably lacking some context.
> The discussion started about language levels, but has .... widened somewhat. 
> Since it's interesting, I'm trying to move it to grace-core & stop manually cc:ing people]
> 
>>> Also where does the "dialect" come from? 
>>> How are names like "if" resolved?
>> 
>> 	That's the same question, I think.   We have been seduced by the ability to introduce what are effectively global names by making receiver-less requests go up the lexical hierarchy of objects.    
> 
> well sure - that's what nesting does.  Although they're not properly global names:
> they're names defined outside the module: precisely where they are defined is what
> I'm trying to tie down.
> 
>>> A separate one-level mechanism? Something else?
>> 
>> 	A separate mechanism.  It would need to be multilevel, so that a "statement" (like if) introduced by a dialect would be available though any number of nested scopes.   Such a mechanism might be like procedures with lexical scopes, which,
> 
> How is this different from the nesting we get with nested objects? 
> If objects can be part of that nesting, then it seems the same as what we have:
> if object's can't be - then do we have object nesting, or do we only have top-level objects?
> 
> I think the _topology_ is mostly independent of the question of whether we have a syntax for "implicit requests"
> or the semantics of those or any other kind of request. 
> 
>> incidentally, would also give us (the effect of) private methods.
> 
> we don't have "private methods" in the Pascal sense, because we don't allow methods to be nested inside methods.
> We could. That wouldn't give object-private methods though - or if it does, I don't see how. 
> 
> 
>>> again - because as well as being explicit, we want good code to look better
>>> than not so good code.
>> 
>> I think that you are confusing "looking good" with "being concise".  
> 
> generally in programming, smaller and more concise is better than long or more wordy.
> certainly "good code shorter than bad code" was one of our principles.
> 
> another (famously) was "no implicits" - so perhaps we should get rid of "implicit requests" purely on that principle... 
> 
>> I've been using Smalltalk for 19 years now, and I think that Smalltalk code looks good.  self in Smalltalk is always explicit.
> 
> Right - but I spent N years using self, where self is almost always implicit, 
> and while I think Smalltalk code looks good, I think Self code looks better :-)
> 
> of course I'm hoping Grace code will look better still: 
> but I'm aware that is as much about me (or us) developing an aesthetic than anything
> 
>>  I'be been trying to read Java for 18 years, and all of the implicit magic (implicit this, open imports, for syntax, exception handling) that has been introduced to try and make Java programs shorter just makes them unreadable — to me.
> 
> :-)
> 
> J



More information about the Grace-core mailing list