[comp.lang.modula2] C v.s. Modula II

gillies@p.cs.uiuc.edu (08/12/88)

When I first came to grad school, and began writing C code again, I
was unexpectedly disappointed.  I had grown used to a Modula-II like
language (MESA).  Some of the things that really bothered me about C
were:

1.  It's a one-pass-compiler language.  So you must pollute your
code with multiple declarations if you make forward procedure calls.

2.  Local changes can require non-local code restructuring.  This is a
collosal pain.  For instance, moving a procedure in a file often
requires adding new procedure declarations.  Adding an else-clause can
require you to restructure the {}'s.  Often, for(;;) loops are written
with *no body*, and adding additional commands can require major
restructuring of the loop.

3.  Include files are hard to keep track of, nest, and structure in a
big project.  Modules & Interfaces seem to work much better.

4.  I really miss the way MESA treats records as first class types.
For instance, you could say:

Gillies: RECORD[first: INTEGER, last: CARDINAL, data: CHAR];
var: Gillies <- [-5, 23, 'c];			 -- (record assignment)
var1: Gillies <- [first: -5, data: 'c, last: 23];-- (using named fields)

5.  I miss MESA INLINE procedures.  They are simpler and (often) more
efficient than C's macro expansions.  

6.  C has no provision for callback procedures (you need nested
procedures and the right semantics for the 'display').  So data
abstraction is hindered in C.


Don Gillies, Dept. of Computer Science, University of Illinois
1304 W. Springfield, Urbana, Ill 61801      
ARPA: gillies@cs.uiuc.edu   UUCP: {uunet,ihnp4,harvard}!uiucdcs!gillies

chris@mimsy.UUCP (Chris Torek) (08/14/88)

In article <79500004@p.cs.uiuc.edu> gillies@p.cs.uiuc.edu writes:
>... had grown used to a Modula-II like language (MESA).

Mesa is considerably more useful for writing Real Programs than is
Modula-II, if only because its I/O really is standard (or at least
only has to deal with one O/S!).  So this is more of a Mesa-vs-C:

>Some of the things that really bothered me about C were:
>
>1.  It's a one-pass-compiler language.  So you must pollute your
>code with multiple declarations if you make forward procedure calls.

As a matter of taste, I happen to like a requirement for forward
declarations that must match the later definitions.  (I have an intense
*dis*like for Pascal's forward, which requires omitting parameters in
the actual definition.)  Forward declarations help me, as a reader,
know what types are attached to functions and arguments.

(Of course, this is less important with better programming
environments, where I can put the definition up in another window at
the touch of a few keys.  On the other hand, I am now at home, typing
away on my H19, and there is no other window....)

>2.  Local changes can require non-local code restructuring.

Not if you format well :-) .

>Adding an else-clause can require you to restructure the {}'s.

Nope:

	if (a)
		foo;
	/* add else here */
	if (b) ...


	if (a)
		foo;
	else {
		stuff;
		more stuff;
	}
	if (b) ...


>Often, for(;;) loops are written with *no body*, and adding additional
>commands can require major restructuring of the loop.

I do not understand this either.

>3.  Include files are hard to keep track of, nest, and structure in a
>big project.  Modules & Interfaces seem to work much better.

Any automated scheme (Mesa's is semi-automated) tends to work better
than any manual scheme.  Some people have developed automated schemes
for C, although since it is not handed down from on high, it can be
hard to get people to use your favourite.

>4.  I really miss the way MESA treats records as first class types.

This really is a serious problem in C.  It is, however, partly fixed
in the dpANS, where automatic aggregates may be initialised.  There are
still no generic aggregate constructors, nor is there any really decent
way to deal with unions (the first-member rule is not useless but does
not do anywhere near enough).

>5.  I miss MESA INLINE procedures.  They are simpler and (often) more
>efficient than C's macro expansions.  

Actually, they are not always simpler.  What are the semantics of the
static variable below?:

	inline int unique_integer() { static int next; return (next++); }

The intended semantics are obvious, but providing exactly those semantics
is not trivial, since all users of the inline function, regardless of
source file, must use the same `next', which must be different from the
`next' of any other inline function.

(GCC, incidentally, does this right, when it does it at all.  The gcc
we compiled a month or two ago on a Sun broke when asked to deal with
inline functions, although the code it tried to generate would have been
right---if that makes any sense to you.)

>6.  C has no provision for callback procedures (you need nested
>procedures and the right semantics for the 'display').  So data
>abstraction is hindered in C.

You need neither, and I personally believe that those sorts of nested
variables (if not the procedures themselves) are a misfeature---they
add a fair bit of expense to the calling sequence (gotta maintain them
displays or static links) and account for very few actual variable
accesses.  Better to use (semi-)hidden pointer parameters a la C++,
which also effectively gives you closures.

Someone else complained about a lack of `var' parameters.  Again, I
think this is a very *good* thing.  One of the worst problems with
reading Pascal code is that every procedure and function call might
potentially alter every variable argument.  If I see a section of C
code that reads

	munch(a, b, &c, d, e + 7);

I can assume that it cannot change a, b, and d (unless they are arrays,
arrh), while the same statement in Pascal, stripped of its warning
`&', tells me no such thing.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

gillies@p.cs.uiuc.edu (08/15/88)

I will elaborate, since I think parts of my posting were misunderstood:

>Adding an else-clause can require you to restructure the {}'s.

Here is an extreme example, and many others exist:

if (a)
	if (b)
		if (c) do_something_useful();

/* Add an else-clause for the second -if- produces */

if (a)	{
	if (b) {
		if (c) do_something_useful();
	}
	else	c_sucks_the_big_wazoo_sometimes();
}

Look at all the braces which had to be added to make the else clause
bind correctly!

>Often, for(;;) loops are written with *no body*, and adding additional
>commands can require major restructuring of the loop.

/* Here is a way to copy a string */
for(p=charbuf, q=copybuff; *p++=*q++;);

/* What if I wanted to debug this, and print characters as they were copied? */
for(p=charbuf, q=copybuff; *q;) {
	putchar(*p++=*q++);		/* <theoretical example only> */
}

Notice how I had to restructure the looping condition to make this
change.  Things can be much worse with a more complicated loop.

>6.  C has no provision for callback procedures (you need nested
>procedures and the right semantics for the 'display').  So data
>abstraction is hindered in C.

Let us say I have a data-abstract code package that exports a procedure
that traverses a dictionary data structure.  The format of the data
structure is proprietary.  Callback requires ONE level of procedural
nesting for each nested loop.  How can I express something like this
in C?

dict: Dictionary.Handle; 		-- global variable

PrintDictionary: PROCEDURE = {
  wh: Window.Handle := Somewhere.GetAWindowHandle[];     -- used in callback
  -- A CallbackProc is a PROC[a:LONG STRING] RETURNS[BOOLEAN];
  ShowName: Dictionary.CallbackProc = {  
    Put.String[wh, a];
    Put.CR[wh];
    RETURN[TRUE]
  }
  Dictionary.Enumerate[dict, ShowName];
}

I call the Dictionary.Enumerate[] procedure, and it repeatedly makes
call to the ShowName procedure. FURTHERMORE, from within the ShowName
procedure, I can access & change the variables global to
PrintDictionary (wh, the window handle).  This is crucial if you want
to get something done, in a type-safe manner.  I don't think C++'s
pointer parameters cut the mustard.....

Callback procedures are a nice way of supporting abstract enumeration
when the language lacks something like CLU's explicit enumerators.


Don Gillies, Dept. of Computer Science, University of Illinois
1304 W. Springfield, Urbana, Ill 61801      
ARPA: gillies@cs.uiuc.edu   UUCP: {uunet,ihnp4,harvard}!uiucdcs!gillies

nagler%olsen@unizh.UUCP (Robert Nagler) (08/16/88)

Question:  Do people like these conversations?  I seems to me
    that C vs. M2 on an M2 mailing list is a bit silly.  I realize
    the need for open debate, but anyone who has done serious work
    in M2 (Son of Mesa) would undoubtably agree that M2 is superior
    to C.  Please respond to me directly to keep down the temperature
    on the net.

Sorry for the length, but it seems there is a lot of misinformation
going around.

Chris Torek writes:
> Mesa is considerably more useful for writing Real Programs than is
> Modula-II, if only because its I/O really is standard (or at least
> only has to deal with one O/S!).  So this is more of a Mesa-vs-C:

There is more than enough supporting evidence to the contrary since
DEC-SRC (loaded with PARC people) have been using M2+ which merely
has built-in monitors, exceptions, and generic pointers.  They also
did an AGC, but I don't see as a must (as I've mentioned a while
ago in another note).   We do "Real Programs" here which deal with
Real Customers on a 24 hour a day basis all year long (except when
the A/C goes out).  Everyone who sees our work says: "You did this
ALL in M2?"  Funny, how empirical evidence kills even the most
die-hard blow-hards.

>>2.  Local changes can require non-local code restructuring.
>Not if you format well :-) .

What if you change a parameter from by-value to by-reference (for
performance reasons).  In M2, this would simply be a change to
the def mod.  In C, you would have to change *every* single reference
to that procedure.  Ah yes, we always pass our parameters by reference
in C, therefore this should never occur? [See below]

>>Adding an else-clause can require you to restructure the {}'s.
>Nope:
Yup:
    if (a)
        if (b)
            do_this;
goes to:
    if (a)
        if (b)
            do_this();
    else
        do_that();
In M2, this couldn't happen (in Pascal, it could).

>>3.  Include files are hard to keep track of, nest, and structure in a...
>Any automated scheme (Mesa's is semi-automated) tends to work better...

Wrongo! The advantage of Mesa is *compiled* interfaces.  This is
the most significant advance in programming since type checking
(in my humble opininion).  If someone changes a structure in C,
you may never know about it (I've seen "make -t" abused too often).
Compiled interfaces eliminate this problem.

>>5.  I miss MESA INLINE procedures.  They are simpler and (often) more
>>efficient than C's macro expansions.
>Actually, they are not always simpler.  What are the semantics of the
>static variable below?:
>        inline int unique_integer() { static int next; return (next++); }

In M2 (Mesa), you would never write a procedure this way.
Module variables are used instead of static variables.  In fact,
you wouldn't write a macro this way in C?  What language is that?

>Someone else complained about a lack of `var' parameters....
>      munch(a, b, &c, d, e + 7);
>I can assume that it cannot change a, b, and d....

Almost every parameter (>int) in C is passed by-reference, so you
never know what's being modified.  [This convention seems to have been
adopted by an earlier inadequacy in C compilers.  Kind of like "stay away
from enumerated types."]   My biggest problem with Unix man pages is
that you never know what the function is expecting unless you read
the detailed descriptions of all the parameters).

One of my favorite features of C is:
    scanf( "%d", i );
I can't tell you how many times I have had to help novice C programs
with this.  If "i" is on the stack, you get pretty random results.
    scanf( "%s\n", "Some string to print" );
Is also interesting.  Sometimes people make mistakes, but far be it
for the compiler to tell them that they are stupid.

-- END Reply -- BEGIN A Different Perspective

The following is *my* comparison between C and M2.  I have taken a
typical procedure in Unix (which I was so fortunate enough to use
the other day) and declared it in M2 and C.  The C declaration is
first.  The function "setitimer" was taken from the "man page" and
the supporting declarations were taken from an include file "sys/time.h"
comments were included (were appropriate).

struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* and microseconds */
};
/*
 * Names of the interval timers, and structure
 * defining a timer setting.
 */
#define ITIMER_REAL     0
#define ITIMER_VIRTUAL  1
#define ITIMER_PROF     2

struct  itimerval {
    struct  timeval it_interval;    /* timer interval */
    struct  timeval it_value;       /* current value */
};

setitimer(which, value, ovalue)
int which;
struct itimerval *value, *ovalue;

Is "value" modified?  What is the return value of the function?  Ah
yes, -1 means false and 0 means true.  WAIT, 0 means false in C and
non-zero means true.   However, I needless to say had to read the man
page several times to figure out that "it_value" was relative to now
and that "it_interval" would be reloaded *after* "it_value" expired.
Believe it or not, I have looked at this man page on several different
occasions and still have trouble remembering which way is up.

In M2, I would define the procedure as follows:
TYPE
    Time = RECORD
        seconds      : CARDINAL;
        microseconds : CARDINAL;
    END;
    TimerSetting = RECORD
        interval : Time;  (* Value to reload after "first" expires *)
        first    : Time;  (* Relative amount to "now" for expiration *)
    END;
    TimerTypes = (  (* Three distinct timers per process which measure: *)
        real,   (*    real time *)
        virtual,    (*    time when process is in user code *)
        profile     (*    virtual and time when process runs in kernel.
                          Warning: SIGPROF interrupts system calls! *)
    );
PROCEDURE setitimer(
        which    : TimerTypes;
        value    : TimerSetting; (* If value.first = 0, turn off timer *)
    VAR oldValue : TimerSetting
    )        : BOOLEAN;          (* FALSE ==> failure; see errno (ugh!)*)

The first thing to note is that there are no descriptions of the names!
I don't have comments like "/* and microseconds */" to clutter up the
brain.   Comments contain *new* information which generally contains
some semantic content that could not otherwise be provided by the
M2 part of the declaration.  With this (C-like) M2 declaration,
I believe I could have guessed how setitimer works.

We use a lot of C-Unix interfaces in our Modula-2 via special def mods
supported by the Sun Compiler (thanx Sun).  In all cases, we immitate the
C declarations as they are defined in C.  In no cases, have I been unable
to translate the C declarations.   However, if I were given the reverse
to do in C, you would be hard-pressed to do some of the following:

PROCEDURE Insert(
    key   : ARRAY OF SYSTEM.BYTE;       (* BTW, can I do HIGH( key ) in C? *)
    );

TYPE
    IntPtr = POINTER TO INTEGER;
    SomeProc = PROCEDURE (
        Object,
    ) : IntPtr;
PROCEDURE Foo(
    bar : Object;
    val : CARDINAL
    )   : SomeProc;

TYPE
    Values = [ 0 .. 65535 ];    (* int? short? char? Your guess... *)
    Bits   = SET OF [ 0 .. 15 ];        (* ditto *)


I have spoken before about these trivialities as above as well
as the real features of M2.  I am interested in honest answers.
How many of the "C" people who read this list have programmed
in Modula-2 (or Mesa for that matter)?  Do you want to know what
real problems are?  Do you care?

I've programmed in C for five years and in Modula-2 for four years.
I liked C, because it freed me from Pascal.  I like M2, because it
allows me to design and build systems quickly and efficiently.  With
C I am still trying to figure out which makefiles I *must* include
before I include the one I want (among other things).

What's bad about M2?  There are two flavors of implementations:
one-pass and two-pass.  The two-pass systems are more mature.
The one-pass systems are probably better, but it is hard to use them
because we wrote 500 modules using the two-pass style (anyone written
an automatic converter yet?).  Most of the M2 implementations
don't do: automatic inlining (not so hard), register allocation (local
would be fine), any amount of code optimization, and/or code elimination.

The problem is that no one takes a half a minute to think what it
would take to make a good M2 compiler.  (I'll supply a empirically
proven portable library free of charge.)  Forget the past, one
company says, and implements a great one-pass compiler.  Another
company rewrites their compiler/linker (from scratch) and now it
doesn't link our system so we have to stick with the old version.
However, almost every PC M2 compiler comes with its own handy-dandy
text editor so that it will sell to the people who use it once and
then shelve it.  Short-sightedness (both forwards and backwards)
is killing M2.  The language itself isn't nearly as bad as the
implementation madness.

Even with all of these problems I can still write a program that uses
>100 light-weight processes, screen graphics, and user interrupts and
port it from the PC to the Sun in under an hour.  The LWP model was
ported in a couple of days from the PC to the Sun.  You just don't
know until you've tried it.  M2 ain't so bad.

Once again, sorry for the length.  I hope this helps someone.

Rob Nagler     mcvax!olsen!nagler@uunet.uu.net
Olsen & Associates/Research Institute for Applied Economics/Zuerich

chris@mimsy.UUCP (Chris Torek) (08/16/88)

>Chris Torek writes:
>>Mesa is considerably more useful for writing Real Programs than is
>>Modula-II, if only because its I/O really is standard (or at least
>>only has to deal with one O/S!).  So this is more of a Mesa-vs-C:

In article <8808151857.AA05996@klaus.olsen.uucp> nagler%olsen@unizh.UUCP
(Robert Nagler) writes:
>There is more than enough supporting evidence to the contrary since
>DEC-SRC (loaded with PARC people) have been using M2+ which merely
>has built-in monitors, exceptions, and generic pointers.

`Merely'?!

>>>2.  Local changes can require non-local code restructuring.

>>Not if you format well :-) .

>What if you change a parameter from by-value to by-reference (for
>performance reasons).

You have me there (but then that was why I put in the `:-)' ).  But
this is irrevocably tied in with the `var' parameter declaration
format, which I personally hate.

>>>Adding an else-clause can require you to restructure the {}'s.

>>Nope:

>Yup:
>    if (a)
>        if (b)
>            do_this;
>goes to:
>    if (a)
>        if (b)
>            do_this();
>    else
>        do_that();

But (says he) the original code is not `well-formatted':

    if (a) {
	if (b) {
	    do_this;
	}
    }

Adding the else is now trivial.

Basically, this just means using full bracketing even though the
language does not provide fully bracketed constructs (I happen to like
fully bracketed syntax a la Algol, even to the silly sdrawkcab
spellings).

>>>3.  Include files are hard to keep track of, nest, and structure in a...

>>Any automated scheme (Mesa's is semi-automated) tends to work better...

>Wrongo! The advantage of Mesa is *compiled* interfaces.

Funny, our Mesa people seem to spend about half their time working
around this `advantage', since Xerox keeps sending incompatible
revisions of things.  (Not a problem with the language!)

>This is the most significant advance in programming since type checking
>(in my humble opininion).

Hm?  It *is* type checking.  Mesa embeds it in the language by
providing syntax and semantics for it.  You still have to use it by
hand (hence `semi-automated').

>>>5.  I miss MESA INLINE procedures.  They are simpler and (often) more
>>>efficient than C's macro expansions.

>>   inline int unique_integer() { static int next; return (next++); }

>In M2 (Mesa), you would never write a procedure this way.
>Module variables are used instead of static variables.  In fact,
>you wouldn't write a macro this way in C?  What language is that?

It is one of the GNU C compiler's (numerous) extensions to C.

A Mesa module variable is simply a static variable that is local to a
module, just like C file-static (or if exported, just like C
file-global).  As such, it cannot be part of an inline function that is
also accessible-but-still-in-line outside that module.  GCC gets this
right, by allowing inline functions outside modules (and playing naming
games to get it to work at all).

>>Someone else complained about a lack of `var' parameters....
>>      munch(a, b, &c, d, e + 7);
>>I can assume that it cannot change a, b, and d....

>Almost every parameter (>int) in C is passed by-reference, so you
>never know what's being modified.

I disagree with this claim, but even granting it (temporarily! :-) ),
while I do not know what *is* being modified, I *do* know what *cannot*
*be* modified, which to me is at least as important.

>... My biggest problem with Unix man pages is
>that you never know what the function is expecting unless you read
>the detailed descriptions of all the parameters).

Funny, the same would be true if it were M2 and the parameters were
declared `var', without any other changes.  What is needed is access
rights (e.g., Ada---much as I think that language is overdone).

>One of my favorite features of C is:
>    scanf( "%d", i );

# this is one reason why `lint' should be run every time you run the
# compiler (not that there are no reasons not to run lint every time):
% cat t.c
main() { int i; scanf("%d", i); exit(0); }
% lint t.c
t.c:
t.c(1): warning: scanf argument is type (int) rather than pointer (arg 2)
t.c(1): warning: i may be used before set
scanf returns value which is always ignored
%

A deficient implementation is not a reason to complain about the
language!  And, similarly, a good but extended implementation, such as
your M2+, or any other M2 with the (remarkably few, but sometimes
remarkably large, as in I/O) holes filled in is not a reason to praise
the language.

[comparison of 4.2BSD setitimer declarations vs. newly created
equivalent M2 declarations]

>... I needless to say had to read the man page several times to figure
>out that "it_value" was relative to now and that "it_interval" would be
>reloaded *after* "it_value" expired.

In other words, the names and comments in the first part of the manual
entry---the part where the type of the function (including arguments)
is described---are insufficient.  You then provide a different version
written in M2 syntax, with *different names* and *different comments*:

>The first thing to note is that there are no descriptions of the names!
>I don't have comments like "/* and microseconds */" to clutter up the
>brain.   Comments contain *new* information which generally contains
>some semantic content that could not otherwise be provided by the
>M2 part of the declaration.  With this (C-like) M2 declaration,
>I believe I could have guessed how setitimer works.

And from changing the names and the comments, you conclude that the M2
declaration (which you wrote, so naturally you will understand it) is
inherently superiour to the C declaration?

C declarations do have their problems.  The major complaint seems to be
that they are overly terse; I also believe that the various pre- and
post-fix styles are part of the problem.  If `pointer' always came
after the name, or if `function returning' always came before, most of
the mysterious parentheses in C declarations like

	void (*signal(int sig, void (*sigfn)(int sig)))(int sig);
	/* to use the dpANS declaration syntax */

would go away.  But you seem to imply that M2 would somehow automatically
improve naming conventions and commenting, which I find unlikely.

>PROCEDURE Insert(
>    key   : ARRAY OF SYSTEM.BYTE;       (* BTW, can I do HIGH( key ) in C? *)
>    );

I have forgotten what HIGH(key) is supposed to do, but assuming that it
does the obvious---returns the size of the array, in whatever units---
the answer is `yes and no': you can get the size, but the caller must
provide the size; the called procedure can no longer obtain it.  This
is done so that the procedure call conventions need not provide array
descriptors, and because arrays are second-class data objects in C
(which, incidentally, *is* one of C's serious shortcomings).

	void Insert(void *key, size_t keylength);
	/* again, dpANS syntax. also legal is:
	void Insert(void *, size_t)
	   but I think not using parameter names is dumb */

>TYPE
>    IntPtr = POINTER TO INTEGER;
>    SomeProc = PROCEDURE (
>        Object,
>    ) : IntPtr;
>PROCEDURE Foo(
>    bar : Object;
>    val : CARDINAL
>    )   : SomeProc;


(I do not understand the syntax `PROCEDURE(Object,)'.  Since M2 does
not have first class functions, I assume that SomeProc is actually
a pointer to some sort of procedure.  Anyway:)

	typedef int *IntPtr;
	typedef IntPtr (*SomeProc)(XXX);/* where XXX depends on what
					   the above means */

	SomeProc foo(Object bar, unsigned val);

>TYPE
>    Values = [ 0 .. 65535 ];    (* int? short? char? Your guess... *)
>    Bits   = SET OF [ 0 .. 15 ];        (* ditto *)

Ranges are another real shortcoming in C.  But I do wonder what
you get if you declare, e.g.,

TYPE small = [0..12]; VAR a, b : small; ...
     a := 9; b := 11; a := a + b;

How is this defined?  I hope it must be a (presumably run-time) error,
with a compile-time warning allowed if the compiler is good.  (If the
compiler is good enough and can prove that the code above must execute,
a compile-time error would be even better.)

For minimum ranges, the dpANS says that an `unsigned short' must
be able to hold the values in 0..65535, hence it would suffice for
both types above.  Speaking of sets, has M2 fixed the Pascal hole
about sets?  That is, must the compiler do the right thing for

    TYPE bigset = SET OF [0..41];

?  (Pascal allows compilers to abort if the set contains more bits than
fit in a `machine word'---whatever that may be :-) .  While some
implementations allow larger sets, it is NOT part of the language, and
this must be considered in any language discussion.  More below:)

>What's bad about M2?  There are two flavors of implementations:
>one-pass and two-pass.  The two-pass systems are more mature.
>The one-pass systems are probably better, but it is hard to use them
>because we wrote 500 modules using the two-pass style (anyone written
>an automatic converter yet?).

Why should one need a converter?  Either forward declarations are
unnecessary and the compiler had better do the right thing no matter
how many passes it uses internally, or they are required and a two
(or N) pass compiler had better complain if they are not there.
Anything else is not M2 (unless the language definition really says
that compilers can complain depending on how they are implemented!).

[more woes about existing implementations deleted]

The implementation is not the language.  No doubt there are some
terrible implementations of M2, as well as some good ones.  The same is
true of C.  The biggest hole in the language is that it does not
provide any I/O.  Neither does C-the-language-syntax-and-semantics, but
stdio is considered a part of `the language'.  If there were a standard
I/O package for M2, the hole would vanish.

As a `for instance', consider VAX/VMS FORTRAN.  By all accounts this
is a wonderful implementation, and is a language in which it is easy
to write reasonable code that runs quickly and does whatever task is
at hand.  On the other hand, the 4.2BSD f77 FORTRAN compiler is slow,
buggy, produces lousy code, and has a weak library.  Neither one says
anything about the language *per se*.

Basically, my point can be stated thus:  If you have to extend the
language---either in internal syntax or in support libraries---to
accomplish what you must do, the language is insufficient for your
needs.  Since literally everyone has to extend Modula II for anything
that reads input or prints a result, that language is insufficient for
those needs.  Clearly, if you finish your task, the (new) language
*is* sufficient for those needs.

Anyway, while it is possible to compare specific features of one
language with those of another, I think blanket comparisons will always
fail in some way.  I will not claim that C is `better' than M2, nor
vice versa.  I *will* say that I like this feature, or dislike that
one.  I will also go so far as to say that discarding some features in
favour of `better' ones from another language might not improve the
first language after all.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

chris@mimsy.UUCP (Chris Torek) (08/16/88)

In article <79500010@p.cs.uiuc.edu> gillies@p.cs.uiuc.edu writes:
>I will elaborate, since I think parts of my posting were
>misunderstood:

(Some were; some I answered just a few minutes ago)

>Often, for(;;) loops are written with *no body*, and adding additional
>commands can require major restructuring of the loop.
>/* Here is a way to copy a string */
>for(p=charbuf, q=copybuff; *p++=*q++;);
>
>/* What if I wanted to debug this, and print characters as they were copied? */
>for(p=charbuf, q=copybuff; *q;) {
>	putchar(*p++=*q++);		/* <theoretical example only> */
>}

Well, you *could* write

	for (p = charbuf, q = copybuf; putchar(*p++ = *q++););

but all of these examples are grotesqueries.

>Notice how I had to restructure the looping condition to make this
>change.  Things can be much worse with a more complicated loop.

I will agree that if you are going to revise a loop you must look
at the `for' part as well as the body.  So what?  If I am going to
revise a

	# unspecified generic odd-looking fully-bracketed language:
	for i from 31 to 97 do a[i] <- f(i); rof;

into a

	for i in a[] do a[i] <- f(i); rof;

I will have to look at the `for' part as well as think about what
is going on.

Certainly C's for statement can be abused, but I think the flexibility
it provides is worth it---look at the number of syntaxes for the Mesa
FOR statement to see what I mean.

>... callback procedures ... How can I express something like this
>in C?

>dict: Dictionary.Handle; 		-- global variable
>
>PrintDictionary: PROCEDURE = {
>  wh: Window.Handle := Somewhere.GetAWindowHandle[];     -- used in callback
>  -- A CallbackProc is a PROC[a:LONG STRING] RETURNS[BOOLEAN];
>  ShowName: Dictionary.CallbackProc = {  
>    Put.String[wh, a];
>    Put.CR[wh];
>    RETURN[TRUE]
>  }
>  Dictionary.Enumerate[dict, ShowName];
>}
>
>I call the Dictionary.Enumerate[] procedure, and it repeatedly makes
>call to the ShowName procedure. FURTHERMORE, from within the ShowName
>procedure, I can access & change the variables global to
>PrintDictionary (wh, the window handle).  This is crucial if you want
>to get something done, in a type-safe manner.  I don't think C++'s
>pointer parameters cut the mustard.....

Yes they do, but I will write this in C, rather than in C++.  (C++ can
make short work of it by using classes, so that is no fun at all.  It
is interesting, though, to note that either Dictionary or Window must
be a subclass of the other to do it.  That problem vanishes with
multiple inheritance, coming soon to a C++ compiler near you :-) .)

	/* typedef int boolean; */ /* in some header; or just use int */

	Dictionary_Handle dict;		/* global, as before */

	struct communique {
		Window_Handle wh;
		/*
		 * any other variables local to PrintDictionary
		 * that might be needed/modified in ShowName
		 * go here too.
		 */
	};

	static boolean ShowName(void *private_data, char *a) {
		struct communique *vars = private_data;

		Put_String(vars->wh, a);
		Put_CR(vars->wh);
		return TRUE;
	}

	void PrintDictionary() {
		struct communique vars;

		vars.wh = Somewhere_GetAWindowHandle();
		Dicationary_Enumerate((void *)&vars, dict, ShowName);
	}

>Callback procedures are a nice way of supporting abstract enumeration
>when the language lacks something like CLU's explicit enumerators.

Yes, they are.  While the above lacks the elegance of Mesa's syntax, it
works; it is portable; it can do everything the Mesa version can do.
Moreover, it does not require displays.  (One can simplify the above
example, eliminating the structure entirely, since there is only a
single pointer variable communicated.  In the general case, though, the
structure is required.)

Personally, I would rather use the elegant syntax.

Note that uplevel variable accesses can always be transformed into
references via an opaque pointer, as I did it above, at no cost to
those routines that do not participate.  The compiler must be able to
tell which procedures have such references, which which procedures call
such procedures, which procedures call procedures that . . ., etc.  A
better method, which does not require recursive inference, is to
generate new functions on the fly that automatically provide the
appropriate hidden argument.  This can be done with some cooperation
from the hardware and the O/S.  David Chase recently stirred up a fuss
in comp.lang.c by mentioning this technique.  (Search for the subject
`partial application', but be forewarned: it quickly evolved into a
debate about self-modifying code.)
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

rion@wdl1.UUCP (Rion Cassidy) (08/16/88)

 gillies@p.cs.uiuc.edu writes:

>When I first came to grad school, and began writing C code again, I
>was unexpectedly disappointed.  I had grown used to a Modula-II like
>language (MESA).  Some of the things that really bothered me about C
>were:

[complaints deleted]

You didn't say where you learned Mesa, but since Xerox is the only
place that uses it, I'd assume you came from there.

Your comments are interesting because when I interveiwed with Xerox
they said that Mesa was more like C than Pascal or M-2.

Rion Cassidy
rion@wdl1.arpa

marti@ethz.UUCP (Robert Marti) (08/18/88)

In article <8808151857.AA05996@klaus.olsen.uucp>, nagler%olsen@unizh.UUCP
(Robert Nagler) writes:

> We use a lot of C-Unix interfaces in our Modula-2 via special definition
> modules supported by the Sun Compiler (thanx Sun).
                                         ^^^^^^^^^
The thanks go to Oce Wissenschaftlisches Forschungsinstitut, Zurich,
and in particular to Leo Geissmann.


> What's bad about M2?  There are two flavors of implementations:
> one-pass and two-pass.

Make that one-pass and multi-pass.


> Rob Nagler     mcvax!olsen!nagler@uunet.uu.net
> Olsen & Associates/Research Institute for Applied Economics/Zuerich

-- 
Robert Marti                    Phone:       +41 1 256 52 36
Institut fur Informatik
ETH Zentrum                     CSNET/ARPA:  marti%ifi.ethz.ch@relay.cs.net
CH-8092 Zurich, Switzerland     UUCP:        ...uunet!mcvax!ethz!marti

gillies@p.cs.uiuc.edu (08/18/88)

In article <79500010@p.cs.uiuc.edu> gillies@p.cs.uiuc.edu writes:
>>I call the Dictionary.Enumerate[] procedure, and it repeatedly makes
>>call to the ShowName procedure. FURTHERMORE, from within the ShowName
>>procedure, I can access & change the variables global to
>>PrintDictionary (wh, the window handle).  This is crucial if you want
>>to get something done, in a type-safe manner.  I don't think C++'s
			      ^^^^^^^^^
>>pointer parameters cut the mustard.....

Written  2:32 am  Aug 16, 1988 by chris@mimsy.UUCP in comp.lang.modula2
>Yes they do, but I will write this in C, rather than in C++.  (C++ can
>make short work of it by using classes, so that is no fun at all.  It
>is interesting, though, to note that either Dictionary or Window must
>be a subclass of the other to do it.  That problem vanishes with
>multiple inheritance, coming soon to a C++ compiler near you :-) .)

(C programming example omitted)

Your example isn't type-safe, although it's same solution I use.  I
only asked the question because I knew it couldn't be answered in C.
Your coding trick starts crumbling with nested callback loops
(something you've probably never considered because it's so tedious in
C!)

Maybe it can be done type-safely in C++.  I hope it doesn't take one
class per callback proc or one per nest!  I am leery of C++, since it
resembles MESA, in that it's a cooking pot with everything but the
kitchen sink thrown in!  The result is an even bigger language that's
hard(er) to master (than C).

I think MESA descended from Algol and BCPL.  It happens to be enough
of a superset of PASCAL that a PASCAL->MESA translator exists.  If a
Xerox interviewer said that MESA was closer to 'C', he was massaging
your fears and/or stretching the truth.  The joke at Xerox is that
Modula-2 "is all the MESA Nicklaus Wirth could remember when he
returned to Switzerland" (he was a visiting scientist at Xerox).

Mesa lacks the pointer arithmetic of C, but has all the arithmetic
operators like :?, and also & !  >> << through the InLine procedure
library, with no loss in performance.


Don Gillies, Dept. of Computer Science, University of Illinois
1304 W. Springfield, Urbana, Ill 61801      
ARPA: gillies@cs.uiuc.edu   UUCP: {uunet,ihnp4,harvard}!uiucdcs!gillies

wyle@solaris.UUCP (Mitchell Wyle) (08/18/88)

>because we wrote 500 modules using the two-pass style (anyone written
>an automatic converter yet?).  Most of the M2 implementations
>don't do: automatic inlining (not so hard), register allocation (local
>would be fine), any amount of code optimization, and/or code elimination.

The Sun M2 compiler version 2.1 uses the Sun global optimizer for
peep-hole and shared library optimization.  I admit it's only in the
code generation, and not earlier, but it's better than nothing.

>The problem is that no one takes a half a minute to think what it
>would take to make a good M2 compiler.  (I'll supply a empirically
>proven portable library free of charge.)  Forget the past, one

Which library are you talking about?

>Rob Nagler     mcvax!olsen!nagler@uunet.uu.net
>Olsen & Associates/Research Institute for Applied Economics/Zuerich

-- 
-Mitchell F. Wyle            wyle@ethz.uucp
Institut fuer Informatik     wyle%ifi.ethz.ch@relay.cs.net
ETH Zentrum                  
8092 Zuerich, Switzerland    +41 1 256-5237