[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