Metaprogramming & language design [Was Re: [stack] Digest Number 28]

Massimo Dentico m.dentico@galactica.it
Thu, 08 Jun 2000 06:13:39 +0200


Mark van Gulik on Concatenative mailing list wrote:
> 
> From: Massimo Dentico <m.dentico@galactica.it>
> > Sorry for the delay Mark, but I'm busy in these days and I need much time
> > to write in English. Some subjects are already addressed by Bill, I agree
> > with him thus I do not intend to be redundant.
> >
> > Mark van Gulik wrote:
> > >
> > > Massimo Dentico <m.dentico@teseo.it> wrote:
> > > > Mark van Gulik wrote:
> > > > > I'm positive that Forth is applicable over an enormous range of domains,
> > > > > mostly due to its core simplicity and extreme extensibility.  I'm mostly
> > > > > concerned, however, with the ability to build *restrictions* in the
> > > > > language.
> > > >
> > > > I completely agree with this. In particular, the key concept is
> > > > ".. the ability to build *restrictions* in the language". This means
> > > > that *you*, the programmer, build the restrictions when you really
> > > > need it and it's not the language designer that in arbitrary way
> > > > restricts you.
> > >
> > > I almost agree.  The language designer also has the responsibility of
> > > building the right restrictions *into* the language, such that it will have
> > > clean semantics and high reliability (the two tend to go together).  For
> > > example, in another thread I argued that conservative garbage collection
> > > (CGC) in Forth is really CGC in some proper subset of Forth.  That's because
> > > Forth's semantics are too strongly defined in terms of machine
> > > representation to allow CGC (in the full language).  A redesign of Forth is
> > > possible to accomodate CGC for the full language, but it wouldn't look much
> > > like Forth afterwards.  Basically issues like GC, type systems,
> > > optimization, and design-by-contract must be *designed into* a language, not
> > > added later (typically by subtractive synthesis as with CGC and retrofitted
> > > type systems).
> >
> > I disagree here. The advantage of a meta-programming (constructing
> > programs that inspects/manipulates other programs) tool with reflective
> > capabilities (ability to inspect/modify itself even if, in Forth, is fairly
> > ad-hoc) like Forth is exactly this: the tool have the ability to absorb and
> > arrange disparate syntaxes/semantics.
> 
> Suppose you want to find all calls to some routine.  An integrated
> development environment like Smalltalk allows you to do this search via
> metaprogramming.  I agree that this is a good thing.  On the other hand, the
> compiler in Smalltalk can be modified (each class can use a different
> Compiler class to compile the class's methods).  That is probably a bad
> thing, because it means the code that searches through the library for all
> calls to some routine might not work any more.  Or maybe the browsers stop
> working, or exception handling breaks because of assumptions about the
> compiler, etc.  If you define a clean, generic protocol for the Compiler
> class you can avoid some of these problems, but this is just language
> design!
> 
> I'm in favor of lots and lots of metaprogramming.  I'm *not* in favor of
> leaving the system so open to fundamental changes that you can no longer
> trust a single line of code to do what you expect it to.  Some kinds of
> metaprogramming are this severe.

Recapitulation: we agree about metaprogramming as a good thing;
we agree about the necessity to express restrictions in metaprograms,
better if into the language itself; we disagree about built-in
restrictions.
 
> Consider optimization.  If a wide-open metaprogramming system allows the
> compiler to be augmented with new code to perform optimizations, this is
> probably a Bad Thing.

There is a system that do this, Pliant. The author differentiates
optimizations done only at the intermediate level (machine indipendent)
and machine specific optimization:

Pliant - release XX (current is 39)
- http://pliant.cx/

in particular "The Pliant language specifications"

[..] "Meta programming: the great step":
- http://pliant.cx/pliant/language/

> Without strict rules or strongly-worded guidelines about what invariants
> an optimization must maintain, someone might add an optimization that
> breaks the ability to do symbolic debugging, or breaks the ability
> to search for all calls to a routine. It would be much better if the
> rules were made very explicit.

Exactly, *very explicit*, not implicit in the language.

> In that sense, the optimization framework would deal with the metaprogramming
> stuff, and present a safe, simplified interface for user-supplied optimization
> rules to use.
>
> Treating most of the metaprogramming facilities as part of a language rather
> than merely a library, I believe the language designer must be *very*
> careful about what's available in the metaprogramming interface.

What I propose is to put metaprogramming facilities *and* restrictions
into libraries, reconciling flexibility, extensibility with safety.
This is not a problem if you have the ability to build restrictions in
the language itself, you don't need another notation.

What about if you build into the language some restrictions that you
consider absolute necessary to ensure a certain safety level and in the
future someone discovers a better way to ensure the same safety level but
with less restriction to expressiveness? (This could be related to the
Turing-equivalence of such powerful type system, thus undecidability
creeps in ... but I'm not qualified to explore this subject: volunteers
are more than welcomes).

> Water is very, very flexible, but it's only when you remove most of this
> flexibility by freezing it that you can build useful things from it.

In your metaphor: no, it's sufficient (and often more useful) to build
conduits (restrictions). You can even transform water in vapor, removing
some restrictions and enforcing other restrictions.
 
> > For example, redefining the semantics of some words (creating new contexts
> > for old programs) probably it's possible to accommodate GC better than in
> > traditional languages (without GC); it's even possible to imagine a metaprogram
> > that takes as input a Forth program with direct memory management and gives as
> > output a Forth program integrated with GC. Some research on static (compile-
> > time) automatic memory management exists also.
> 
> The safest way to support GC (by far) is to *design* it into the language at
> the beginning.  It's almost impossible to add it safely to a language, but
> it's almost trivial to include it in a new language.

The safest way to support GC, in my opinion, is to *demonstrate* that
the algorithm is safe. With metaprogramming you could combine a specific
program with a specific GC algorithm (better for this program, perhaps
obtained from a more general GC via some specialization technique).
Given the context (the program), it could be more simple to demonstrate
GC algorithm safety against this program rather than an unknown context
(every possible programs). The idea of using different GC algorithms at
the same time for different program is certainly not new.

However, I'm not an expert about GC and formal treatment of programs,
thus I'm unable to establish the possible difficulties in this procedure.
I admit that, with your strategy, metaprogramming is more approachable by
the "average" programmer, but this doesn't mean that the situation will
never change (after all, freedom implies responsibility).
 
> > However, I consider Forth more a conceptual model than a language in itself:
> > it's defined more by "concepts" like words, staks, dictionaries, text input
> > buffer than by a particular set of primitives.
> 
> Ok, but then I'll just change what I say should have metaprogramming
> restrictions:  "Forth + its primitive words would benefit from having
> restrictions of the form...".

I could speak about the CMoF (Conteptual Model of Forth) .... :)

About Forth primitives, they are not necessary if you give a meta-circular
definition of the language, choose carefully a subset of words necessary
to define it, the language kernel (usually those words with a mapping 1:1,
or near, to machine instructions) and then define the rest of the language
in term of this subset (it's not so difficult if you write, as usual in
Forth, mostly functional words, without side effects). In this way the
kernel can varies and you can adapt it to different hardware (not a fix set
of primitives) but the language (or the system) its always the same.
Probably you know all that: if I remember well, this is common practice
in the Forth community.

This "equational" treatment is even more evident in Joy, where side
effects are absent and definitions uses the symbol "==".


-- 
Massimo Dentico