[Grace-core] Dialect Design Proposal

James Noble kjx at ecs.vuw.ac.nz
Thu Nov 22 15:52:01 PST 2012


Michael (and Roma) and I talked yesterday afternoon.  Here's one
result, a design for dialects can working with scoping - treating a
dialect as the an "outermost lexical scope" that encloses a module
object.

DIALECTS

The basic idea is in Fig 1:

-------------- next part --------------
A non-text attachment was scrubbed...
Name: PastedGraphic-1.pdf
Type: application/pdf
Size: 205139 bytes
Desc: not available
URL: <https://mailhost.cecs.pdx.edu/mailman/private/grace-core/attachments/20121123/0bac7e4c/attachment-0005.pdf>
-------------- next part --------------

Here M1 is a module - an object build from a file - as currently with
the 3rd generation module design.  I'll generally draw modules as tall
thin rectangles.  M1 has objects nested inside it - nesting are lines
with no arrows typically running left to right.  So far so good.

The D1 is M1's dialect. D1 is an object (in fact it will typically
also be a module).  M1 declares this with the "Dialect" statement on the 
first line:

//in file M1
dialect "D1"

def A = ...
def B = ...
def C = ...

The "dialect "D1"" declaration loads the module named by the string
"D1" more or less as if it was in an import statement:

  import "D1" as _

Programs don't access the dialect directly, Rather, any outer request
or (most often) receiverless request in the module M1, or in any
objects nested inside that module, are resolved via lexical scope up
to the module object (the horizontal blue lines, looking up right to
left) and then the lookup proceeds just one more step (along the red
curly-arrowed link in Fig 1) to the current dialect. This is just like
any other request resolved by lexical nesting (say going from an
object nested inside a module to the module-object itself).  All the
definitions in the dialect must be found in the dialect object (D1) in
the diagram.  For a Standard Grace program, its dialect is simply the
Grace Standard Prelude object.


DEFINING DIALECTS

And that's pretty much it.  Yes, Smalltalkers might think of that link
as close to a meta-class pointer, but it's not really, it's much
simpler than that!  It's basically just nesting - lexical, but not
textual.  The only magic is that the curly dialect link is *not*
transitive.  This is because dialects need to be defined
somewhere. Fig2 shows two (red) dialects D1 and D2 defining some blue
modules - note that the two module-objects written in the D1 dialect
are linked to the same dialect-object: again, dialects are modules,
and each module is reified to only one module-object no matter how
many times it is imported into a program.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: PastedGraphic-2.pdf
Type: application/pdf
Size: 294532 bytes
Desc: not available
URL: <https://mailhost.cecs.pdx.edu/mailman/private/grace-core/attachments/20121123/0bac7e4c/attachment-0006.pdf>
-------------- next part --------------

D1 and D2 are modules - so they must be written in some dialect - in
the figure, D*. Generally, special purpose dialects will be less
powerful than the whole language, so there will be many definitions in
D* that shouldn't be in D1 or D2, but will be useful for _writing_ D1
and D2. This is why the dialect link is *not transitive*: the modules
using D1 and D2 find their definitions only in the D1 and D2
dialect-module-objects, and not in D*.

What about D* itself, presumably some maximal common instructors'
language --- well the recursion must stop somewhere. The cute trick
would be to have D* as it's own dialect, but unlike Smalltalk, that's
not necessary here: D* can access all of its own definitions directly,
so it can "be implemented in" the empty dialect, Dnull.


CHECKERS

As well as providing definitions, dialects may outlaw or control
particular features of the language, or more likely, give more and
better focussed error and warning messages. These can also be
implemented simply: because a dialect is a module, it can contain a
constraint checker function or object that can be invoked by the
compiler when modules that use that dialect are compiled. This
constraint checker (like JavaCop or Checkers) will be passed the AST
of the module and can do with it as it wishes :-)


AUXILIARY DEFINITIONS IN DIALECTS

The intransitive dialect link ensures a dialect only provides its own
features to its modules, rather than all the features of the dialect
in which it is itself defined. A dialect may also include auxiliary
definitions: say a "loopHelper" method to help define a while loop.
Unfortunately because dialects are lexically nested, making the
features "confidential" or even "private" (which we currently don't
support) won't help.  But once again Grace can come to its own rescue:
a module can define a checker function that disallows the code in its
modules access to any unwanted features from the dialect. Because
dialects are lexically scoped, all the code that could potentially
access the implementation feature must be within the lexical scope of
that dialect, and so will have been checked via the dialect's checkers.
A more advanced dialect writing dialect could provide a generic
checker and an annotation ("local" or "restricted" or "dialectual" or
something) that could make this look like a language feature.


AN ASIDE: GENERIC MODULES

Grace modules are objects, or rather, Grace modules are files* that
are reified into module-objects.  Because module-objects are objects,
rather than classes, they can't be instantiated repeatedly or
parameterised, rather they are just a single singleton object.  For
similar reasons, you can't really inherit _from_ modules either,
because, in the reverse concatenation semantics we've adopted,
inheritance extends the object you're inheriting from.  This affects
dialects because dialects are modules. But, clearly we will need to
build up dialects by inheritance, share features, etc. How can we do
that if we can't inherit from dialects?

While modules (dialects) cannot act as superclasses or superobjects
--- dialects and modules are most of all just Grace objects, and they
certainly can inherit features just like other objects, typically from
classes (or fresh objects, but let's not worry about that now), see
Fig 3.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: PastedGraphic-3.pdf
Type: application/pdf
Size: 262966 bytes
Desc: not available
URL: <https://mailhost.cecs.pdx.edu/mailman/private/grace-core/attachments/20121123/0bac7e4c/attachment-0007.pdf>
-------------- next part --------------

Here, module M0 contains a reusable class C0. This class has been
instantiated twice (CA and CB) and two module objects (MA and MB)
inherit the instances of the class.

//in M0
class C0.new {
  // body of class
}

//in MA
import "M0" as M0
inherit M0.C("A parameters") 

This design should also work with type-generic classes. 

Fig 4. shows a slight twist on this design: a module that extends one
of its own classes.  The point here is that the BG module can now be
used directly - including as a dialect - but that also contains an
class that can be extended or reused elsewhere.

//in BG
inherit CBG.new

class CBG.new {
 //put most of the definitions in here...
}




-------------- next part --------------
A non-text attachment was scrubbed...
Name: PastedGraphic-4.pdf
Type: application/pdf
Size: 219096 bytes
Desc: not available
URL: <https://mailhost.cecs.pdx.edu/mailman/private/grace-core/attachments/20121123/0bac7e4c/attachment-0008.pdf>
-------------- next part --------------


ASIDE: LOCAL DIALECTS

(Michael said: make this related work)

Sometimes, having all of a module in just one dialect, may be
overkill. It may be useful to employ smaller local DSLs to use in
particular places --- one for test cases, another for defining state
machines. We can extend this by making dialects object local:

//in file M1
dialect "D1"

def A = object { // in D1 dialect }
def T = object {
  dialect GraceTest
  // test cases
}
def B = object {
   def S object { 
      dialect StateMachine
      // state machine 
   }
}
def C = object { // in D1 dialect } 

This works because we interpret the dialect (red curly arrow) as
"lexically" scoped: the lexical lookup rule remains the same as
before: up the lexical scope until it reaches the module object and
then the lookup proceeds just one more step to the current dialect. 

-------------- next part --------------
A non-text attachment was scrubbed...
Name: PastedGraphic-5.pdf
Type: application/pdf
Size: 262379 bytes
Desc: not available
URL: <https://mailhost.cecs.pdx.edu/mailman/private/grace-core/attachments/20121123/0bac7e4c/attachment-0009.pdf>
-------------- next part --------------

==================================================
some random related stuff
==================================================



YET ANOTHER ASIDE: "Typed Imports"

Just as you can write "def A = b" to get local type inference, and
"def A : T = e" to declare with a manifest type, as well as "import
"foo" as A" to declare foo via local type inference, we should support
"import "foo" as A : T" to import foo bound to A but with static type
T.  If it turns out foo doesn't satisfy type T, we raise a compile
time or bind-time error.  This gets flexibility from structural typing
for modules -- that T could come from a different module:

import "stackSpec" as StackSpec
import "stackImpl" as StackImpl : StackSpec 

or even inline

type StackType = {
push(Object)
pop -> Object
}

import "stackImpl" as StackImpl : StackType   // almost! 

Should the stackImpl module change under the current program, that
change can be caught statically

YET ANOTHER ANOTHER ASIDE: "Type providers"

*modules are really things that the Grace module loader can resolve
into singleton objects. So modules should be able to the kind of
things that C# does with its "type providers" - a module like
"service:google.com/search" could look up the URL, decode the
metadata, and offer it as a Grace module, along with its types.
This doesn't belong here, does it!



YET ANOTHER ASIDE: "mixins"

method mixin (x)  {
return object {
  inherits x 
  //extra bits go here
}}

or this

class Trait.apply (x) {
inherits x 
 //extra bits go here
}


do we want to support this?  Can we do so (easily?)  with sufficient
"staticness" restrictions on x?  (The method/object constructor
version currently works in minigrace, the class version doesn't)

Mixins are one option to support multiple inheritance in practice
(e.g. for dialects) without adding (much) complexity / or other
features to Grace.





More information about the Grace-core mailing list