[Grace-core] Modules: starting discussion

James Noble kjx at ecs.vuw.ac.nz
Tue Jun 26 23:17:27 PDT 2012


Hi all

Time to start thinking again about Modules.

There is the a good page on the wiki here: 
https://projects.cecs.pdx.edu:8443/~black/NewOOL/index.cgi/wiki/ModuleDesiderata

with some requirements

and this email below (which should be copied & formatted into a wiki page ideally)

is the state of the discussion at the end of last year, I think.

I added in some new comments with +++

From: Kim Bruce <kim at cs.pomona.edu>
Subject: Re: telecon this week? - wed 21 Dec 3pm Pacific, thurs 12 Dec noon NZ 
Date: 22 December 2011 11:42:06 AM NZDT
To: James Noble <kjx at ecs.vuw.ac.nz>
Cc: "Andrew P. Black" <black at cs.pdx.edu>, Michael Homer <Michael.Homer at ecs.vuw.ac.nz>, Timothy Jones <tim at zimothy.com>, Kim Bruce <kim at cs.pomona.edu>

Here are some embedded comments on James' e-mail on modules from 12/20.  My comments start with ***.  I didn't comment on annotations.

Kim


(I added in some responses with +++)

============================================================
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

*** Why can't it just be a collection of declarations?  Is there a disadvantage to that?
+++ We don't have "collection of declarations" in Grace (yet). Or rather,
         we've got objects & types that are ... collections of declarations.

- "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!)

*** Can we get definitions with module scope?
+++ they are private to the object that reifies the module

============================================================
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>

*** notation for below has to change with new notation for generics.

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).

*** I go back and forth on this.  Right now it seems OK to have var in 
modules.
+++ the real question underlyign this is are modules objects or classes.
         Gilad's Newspeak modules are fundamentally classes, and instantiating
         a module binds names via the constructor's "platform" parameter.
         (like "Object Algebras")

- Is it a problem that importing/abbreviating objects uses def; 
while importing types uses type (and methods would use method)?

*** I like using the words as it makes reading the code easier.
+++ OK...

- 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. 

*** I'm not sure I understand the proposal or why it might be needed.
+++ abbreviating an important method is messy at least, as you have to write 
        a forwarding method

- 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

*** Why not hide all that in the platform so we don't need to muck up the 
code.
+++ excellent idea!   that;s how it's supposed to work!

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

*** Having a module be a method doesn't make much sense to me (as opposed 
to having a method inside a module).  I'm not even sure we need to treat 
a module as an object or type.  Treating it as a class seems weird to me 
(though having a class at the top level is clearly fine).

+++ see above & Gilad's Newspeak paper

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

*** "SourceOfAllKnowledge" or "Linker"?

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

*** I'm not sure.  Any good reason to?
+++ I begin to think there are good reasons to import / inherit at lower leves
        (but that can be done in two stages)
        I big reason (again from Gilad) to import only at the top level
        is that it makes the dependencies of the module manifest.

- 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

*** Shouldn't matter
+++ indeed

============================================================
============================================================
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).  

*** What is the obvious reason?  Are we emulating Perl?
+++ you could put #/usr/bin/Grace and run scripts from the command line
         this should work now for a suitably horrible value of "/usr/bin/Grace"

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

** It depends on what we do with the rest of the annotations.  We should 
try to be consistent!

- #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?) 

*** Why not in the most advanced dialect so that we can use the full power 
of the language to design features.
+++ if we use inheritance to import dialects, how do we specify we *don't* want all the powerful features inherited?   
+++ this is an important question


============================================================
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 .... }
  }

*** Can't we have a standard prelude that gives definitions of imported 
things (e.g., implicitly in the Platform), but definitions can be replaced 
in the Platform if desired.  Because they are replaced in the platform, 
everything will have to be recompiled, making them compatible anywhere in 
the program.  The biggest problem with this is performance.  That 
recompile will require everything (including everything in the standard 
prelude) be recompiled.  Obviously this is not something you'll want to do 
often.  I certainly would never have students do it, though I might supply 
something already recompiled.  This does lead to the fact that we are 
going to need a very smart implicit "make" facility.


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?

*** Platform should be responsible for picking that up and there should be 
ways of specifying which definitions are used when there are ambiguities.




More information about the Grace-core mailing list