[Grace-core] Yet Another Modules Proposal (rev2)

James Noble kjx at ecs.vuw.ac.nz
Wed Dec 7 21:06:56 PST 2011


Another attempt at a module proposal. Based on James' & Kim's
proposals & the minigrace implementation - this one seems 
close to workable!   There really isn't much of it, which I like anyway.

Towards the end there is a list of "Outstanding Issues"
and at the very end, a first attempt at describing how the module
system works to provide dialects (aka language levels)



============================================================
Module Definitions

- Each module resides in its own file

- A module can either be an object, or a type.

- The module comprises all declarations in the file, and
  the module is named (in some sense) after the filename.

- If all declarations are types, the module is a type, otherwise it is an object

- "self" inside the "top level" of a module is bound to the module
  object itself.

- private/confidential features of modules / objects will not be exported
      (thanks to Grace's normal semantics for object access, 
       because they are all the semantics we have!)


============================================================
Using Modules 

- to import a module "moduleName" as locally named "mod",
    you do something like

     def mod = platform.moduleName

- the result is the module object is now bound to the name "mod" 

- All import sites get the same module object bound.

- you can access public features of the module with requests
    "collections.List"  for a list class say, or "reflection.Mirror" 
    (because each module is just a normal  object)  

- platform is a _very magical_ object which finds and binds
  definitions (and their types).   As in Newspeak, it's fundamentally
  defined outside the language.

- but you can *think* of platform simply as a very big object that has one 
   constant definition for each possible module that holds the module object...

- because modules are just objects, we can structure the module name space
    by using objects, and write longer pathnames to get around (just like Self's namespace)


============================================================
Abbreviations 

- Following Oberon & Newspeak, we propose no special
  language support for unqualified imported names.

- Like Newspeak, if you want an unqualified name for an object
   as an abbreviation, or to rename, you can just write a def

      def list = platform.collections.List

  for a type, use 
   
     type Seq = platforms.collections.Sequence


============================================================
Separate Compilation

- it's up to the implementation to make "platform" work.

- we stick to an early decision that the distribution medium of Grace is source code.

- there is no separate construct for doing eager vs lazy linking
  (or "compile time" vs "link time" bindings)

- binding against types retrieved from platform may obviously change
  depending on context (i.e. which platform you get).
  This is just like Java - compile against Java 1.X, run against 1.Y, doesn't work.

- to get separate static type checking, either define your own
  types inline, or put them in a module that you supply. Then ensure
  the supplied types & objects are as you expect.

     type MyList = platform.thisApplication.types.List
     
     def List : { new -> MyList }  = platform.library.collections.List

    This will fail at compile/link time if platform.library.collections.List - presumably
     a List class object,  doesn't return something I recognize as a MyList in
     response to the "new" message. 



============================================================
Kim's example taken from his email: "A module proposal"
Date: 4 November 2011 7:33:26 AM NZDT

----------------------------------------------------------------
StackInterfaceModule.grace
--------------------------------
#lang Grace

   type Stack<T> = {
       push(_:T) -> Unit
       pop -> T
   }

   type StackFactory<T> = {
      new<T> -> Stack<T>
   }

   type StackMakerType<T> = {  				
       empty -> Stack<T>
   }

----------------------------------------------------------------
StackImplementationModule.grace
--------------------------------
#lang Grace

// import Stack, StackFactory, StackMakerType from StackInterfaceModule
type SI = platform.StackInterfaceModule
type Stack = SI.Stack
type StackFactory<T> = SI.StackFactory<T>
type StackMakerType<T> = SI.StackMakerType<T>

public class StackClass<T> implements Stack<T> {  // details omitted for brevity
    var rep:... := ...
    method push(e:T) -> Unit {...}
    method pop -> T {...}
}


public class StackMaker<T> implements StackMakerType<T> { 
    method empty -> Stack<T> { 
           return StackClass.new<T>
    }
}

----------------------------------------------------------------
StackUsingModule.grace
--------------------------------
#lang Grace

// import Stack, StackFactory, StackMakerType from StackInterfaceModule
type SI = platform.StackInterfaceModule
type Stack = SI.Stack
type StackFactory<T> = SI.StackFactory<T>
type StackMakerType<T> = SI.StackMakerType<T>

// import StackMaker from StackImplementationModule
def StackMaker = platform.StackImplementationModule.StackMaker 

method myStack -> Stack<Number> { StackMaker.new<Number>.empty } 



----------------------------------------------------------------
StackModifyingModule.grace
--------------------------------
#lang Grace

// import Stack, StackFactory from StackInterfaceModule
type Stack = platform.StackInterfaceModule.Stack
type StackFactory<T> = platform.StackInterfaceModule.StackFactory<T>

// import StackMaker, StackClass from StackImplementationModule
def SM = platform.StackImplementationModule
def StackMaker = SM.StackMaker 
def StackClass  = SM.StackClass

// note the above imports done the other way around to show that it can be done.

type CountingStack<T> = Stack<T> & { count -> Number }
 // minor modification to Stack - just use "&" to "add" a method to the type 

class CountingStackClass<T> implements CountingStack<T> {
          inherits StackClass.new<T> // permitted via "statically defines" 
      var count:= 0;
      override method push ...
     ...
}



============================================================
Outstanding Issues:

- currently minigrace modules run their code top-to-bottom.  The
  module file can also contain code other than definitions, which is
  executed on import. That code can invoke other methods and
  do pretty much anything. 

  This proposal generalizes this behaviour - initialization code can be anywhere
   within any object - including of course module objects.  Code in other
   object constructors is executed in order as the constructor is invoked,
   along with field initializers.

   In fact, that's the reason for relaxing this rule: so long as
   initializers can run init code --- so some evil like this is
   presumably always possible:

     def dummy = { any.code(at,all); 42 }.apply

   We should however have a good look at what Dart specifies here (wrt const) 

- should we require module objects to be (shallowly) immutable? 
    that is, forbid "var" in modules?   
    (if they're deeply immutable we probably can't define anything mutable).
 
- Is it a problem that importing/abbreviating objects uses def; 
   while importing types uses type (and methods would use method)?

- with all the forwarding going on, do we need general syntax for
  forwarding?

    forward foo()bar()baz() -> foo
    forward * -> foo 

   vs 

   import foo()bar()baz() from foo 
   import * from foo

  forward wouldn't also do the import from platform - 
    so it could be used wherever else forwarding was required.

  we all lean against this - hoping to avoid more "macros" and 
  avoid another "inheritance-like" mechanism. 


- we probably need some kind of convention about library module
  names/paths vs "modules in the current program" names/paths, vs
  modules to load from third parties over the web.

   see e.g:  http://docs.racket-lang.org/guide/module-basics.html
   or Dart's library import stuff.

   we could support e.g. platform.load("http://gracelang.org/modules/foo.grace")
	or just platform.org.gracelang.modules.foo

- can classes & methods be modules, or just objects & types?

- should we rename "platform" to something nicer? 
     "lobby"?  "platform"?   "Grace"? (a nod to "Smalltalk")

- it's probably good practice & good style only to import stuff at the top.
    does this need to be a language restriction? 

- if several module-files are to be distributed together, how does that work?
    zip?  tar?  cat??
     text stream with modules introduced by #module name directive




============================================================
============================================================
Using (and Specifying) Dialects

- Somehow, we have to define the dialect --- the language level ---
   used in a file. Stealing almost directly from Racket, I propose
   "#lang" at the top of a file to define the language used in the
   module.
   	
   (Note that we already allow # lines, e.g. #! at the top of files
   for the obvious reason, so I though I'd just steal that and make
   it #lang Grace).  

   (Note I wondered about lang("Grace") or something, that being an
   annotation) 

 - #lang Grace (or whatever) basically does "import Grace"  
 and then we resort to magic - via a range of defs/types/methods
 all public featues (including inherited features) of the dialect are imported
 into the module being defined - as if there was a series of defs/types/methods

  type String = platform.Grace.String
  def  UTF16String = platform.Grace.UTG16String
  method if(t)then(b1)else(b1)  {platform.Grace.if(t) then(b1) else(b2)} 

- a dialect is presumably a module in some very primitive dialect
  itself  (PreGrace? PrimitiveGrace? Primitive?) 



============================================================
Outstanding Issues for Dialects:

- How to handle binding (or rebinding) system classes --- the example
  of adding "titleCase" methods into string?  Should this be on the
  level of the dialect or anywhere --- e.g. (re)defining /
  furtherbinding a "String" class that is somehow picked up.

     class ConcreteString {
       inherits super.ConcreteString 

	method titleCase { return .... }
     }


  the question is: do instances of String created outside this module
  share that method?  If not how do the types work out?  Can you
  furtherbind types:

    type String = super.String & { titlecase -> SelfType }


- we also have the interesting issue of literals --- numbers,
  strings, blocks?, even objects? --- which (meta)class do they
  instantiate? GHC implements (and Newspeak suggests) calls to
  functions to make literals etc.

  http://haskell.org/ghc/docs/latest/html/users_guide/syntax-extns.html#rebindable-syntax

- what if two different dialects give incompatible redefinitions of
  the same library classes?  At least, how do we detect that?




More information about the Grace-core mailing list