[comp.lang.modula3] Questions from a new advocate of Modula-3

new@ee.udel.edu (Darren New) (02/08/91)

Maybe somebody out there can answer a couple of questions for me about
Mod-3, especially if you have a running version (since all I have is
the report):

(Note: I can FTP to gatekeeper, so a pointer to the appropriate month were
this was discussed would be enough. Thanks!)

1) The type of an expression is the set of all values the expression can
take on.  By definition, a constant expression can only take on one value.
So if I declare something as

PROCEDURE x(y := 6);

does this not imply that the type of y is [6..6]?  What is the type really,
and why is it INTEGER instead of CARDINAL (if it is)?  Should the
report not say something about "base type" instead in this case?

2) The NEW expression's behaviour is not defined in the case that not
enough memory is left.  Is this a checked runtime error, or does it
return NIL?  If it returns NIL, then it isn't returning a "new" value.
If it is a checked runtime error, how do you allocate all available
memory without crashing?

3) A new variable is initialized to one of the members of the type.
Does this mean that (say) a UNTRACED REF REAL could be initialized to point
to any REAL anywhere in memory, or only to a REAL that has been
actually allocated via NEW?  I.e., if REALs must be on 4-byte boundries,
could a newly-declared but otherwise uninitialized UNTRACED REF REAL point
to *any* 4-byte-aligned quantity, or to any in the untraced heap, or
to any previously-initialized real, or what? Does an initialized nonNIL
REF necessarily point to initialized data?

4) BIT n OF t <: t    -- I can accept that
   t <: BIT n OF t    -- What about BIT 2 OF [0 .. 255]? Is this a static
      error, a checked runtime error, or implementation dependant?

5) Why try for generic packages given that objects can support generic
   types (like the Queue and IntQueue example)?

6) I would think that REAL <: LONGREAL, but that doesn't appear to be the case.

7) Has an "assert" capability been considered?  Maybe preconditions and
   postconditions could be included.  It seems like this would make
   for a much "safer" situation where an unsafe module exports a safe
   interface.  Asserts in interfaces could help document the semantics
   of the interface.

------------

Then some comments:

REFs should be defined to be initialized to NIL (unless explicitly
initialized). This would catch more errors because a pointer that
missed being initialized could not point to valid data possibly being
used by some other module/thread/whatever. This would also prevent
code which assumed that pointer are initialized to NIL (probably the
vast majority of cases) from failing when it isn't (Holy VAXisms, batman!).

An interface should be defined which specifies the ranges of integers,
floats, etc.  (Like LIMITS.H in ANSI C)


--- Darren New --- Grad Student --- CIS --- Univ. of Delaware ---
----- Network Protocols, Graphics, Programming Languages, 
      Formal Description Techniques (esp. Estelle), Coffee, Amigas -----
              =+=+=+ Let GROPE be an N-tuple where ... +=+=+=
-- 
--- Darren New --- Grad Student --- CIS --- Univ. of Delaware ---
----- Network Protocols, Graphics, Programming Languages, 
      Formal Description Techniques (esp. Estelle), Coffee, Amigas -----
              =+=+=+ Let GROPE be an N-tuple where ... +=+=+=

gnelson (Greg Nelson) (02/08/91)

Here are answers to some questions from Darren New:

    1) The type of an expression is the set of all values the expression
    can take on.  By definition, a constant expression can only take
    on one value.  So if I declare something as
    
    PROCEDURE x(y := 6);
    
    does this not imply that the type of y is [6..6]?  What is the type
    really, and why is it INTEGER instead of CARDINAL (if it is)?  Should
    the report not say something about "base type" instead in this case?

The type of an expression CONTAINS all values the expression can take
on, but it may contain other values as well.  For example, if the type
of n is INTEGER, the type of n - n is also INTEGER, not [0..0].

In general, to determine the type of an expression, you look in the
expressions chapter.  In particular, the type of an integer literal
is defined to be INTEGER, even if the literal is positive.

    2) The NEW expression's behaviour is not defined in the case that
    not enough memory is left.  Is this a checked runtime error, or
    does it return NIL?  If it returns NIL, then it isn't returning
    a "new" value.  If it is a checked runtime error, how do you
    allocate all available memory without crashing?

The report should have defined this to be a checked runtime error.
If you want to allocate all available memory without crashing, you
have to use a lower-level, implementation-dependent interface.

    3) A new variable is initialized to one of the members of the type.
    Does this mean that (say) a UNTRACED REF REAL could be initialized
    to point to any REAL anywhere in memory, or only to a REAL that
    has been actually allocated via NEW?  I.e., if REALs must be on
    4-byte boundries, could a newly-declared but otherwise uninitialized
    UNTRACED REF REAL point to *any* 4-byte-aligned quantity, or to
    any in the untraced heap, or to any previously-initialized real,
    or what?  Does an initialized nonNIL REF necessarily point to
    initialized data?

In principle an UNTRACED REF REAL could be initialized to point to
a REAL anywhere in memory.  If the implementation requires real pointers
to be byte-aligned, then the initial value would have to be
byte-aligned.  An initialized non-nil REF must point to valid data;
but the data can be arbitrary; for example:

    VAR r := NEW(REF INTEGER)

leaves r pointing to an arbitrary integer.

    4) BIT n OF t <: t    -- I can accept that
       t <: BIT n OF t    -- What about BIT 2 OF [0 .. 255]? Is this a static
          error, a checked runtime error, or implementation dependant?

The language definition gives the implementer complete freedom over
which values of n are allowed in BITS n FOR T.  Unless the implementer
knows of a technique by which eight bits of information can be
represented with two bits of storage, he would do best to prohibit
BITS 2 FOR [0..255].  (If he knows such a technique, he should patent
it.)

    5) Why try for generic packages given that objects can support
       generic types (like the Queue and IntQueue example)?

Generics can be more efficient: for example, a generic matrix package
could be instantiated for either REAL or LONG REAL.  In principle you
could do this with objects: you just define an object type "number"
with methods add, multiply, etc., and then define two subtypes, in
one subtype the number object's data field is a REAL, in the other
subtype the data field is a LONGREAL.  But the overhead in this approach
is horrendous.

    6) I would think that REAL <: LONGREAL, but that doesn't appear
    to be the case.

That would have been a possible design.  It leads to a small thicket
of design questions.  For example, with VAR r: REAL and VAR lr:
LONGREAL, the subtype rule that you definitly allows the assignment
lr := r, which probably establishes the post-condition lr = r.  Whether
the reverse assignment is allowed is more questionable; and for the
reverse assignment to establish the equality is getting very dubious.
In the end we decided to avoid these questions by not including the
rule.

    7) Has an "assert" capability been considered?  Maybe preconditions
       and postconditions could be included.  It seems like this would
       make for a much "safer" situation where an unsafe module exports
       a safe interface.  Asserts in interfaces could help document
       the semantics of the interface.

SRC Modula-3 has an ASSERT pragma.

    REFs should be defined to be initialized to NIL (unless explicitly
    initialized).  This would catch more errors because a pointer that
    missed being initialized could not point to valid data possibly
    being used by some other module/thread/whatever.  This would also
    prevent code which assumed that pointer are initialized to NIL
    (probably the vast majority of cases) from failing when it isn't
    (Holy VAXisms, batman!).

This is a point on which reasonable people can differ.  I am skeptical
that more errors would be caught if the language requires that REFs
be initialized to NIL, since in fact, both existing implementations
of M3 do initialize REFs to NIL.

I find it most readable to supply the initial value if and only if
it matters to the algorithm.  Thus:

    VAR t: TEXT;
    BEGIN
      FOR i := 0 TO 9 DO
        IF P(i) OR i = 9 THEN t := Fmt.Int(i) END
      END;
      ...
    END

But

    VAR a: IntList := NIL;
    BEGIN
      FOR i := 0 TO 9 DO a := Cons(i, a) END;
      ...
    END
  
It is hard to recommend that programmers write the ":= NIL" if the
language defines it to be a no-op.  

    An interface should be defined which specifies the ranges of
    integers, floats, etc. (Like LIMITS.H in ANSI C)

I hope the new required floating point interfaces answer your needs
(see the "twelve changes to Modula-3" message last December on
comp.lang.modula3).

Greg Nelson

bbc@rice.edu (Benjamin Chase) (02/08/91)

gnelson (Greg Nelson) writes:

>Here are answers to some questions from Darren New:

>    2) The NEW expression's behaviour is not defined in the case that
>    not enough memory is left.  Is this a checked runtime error, or
>    does it return NIL?  If it returns NIL, then it isn't returning
>    a "new" value.  If it is a checked runtime error, how do you
>    allocate all available memory without crashing?

I think this is the wrong decision.  Darren and the previous people
asking this same question have a very good point.  There should be a
standard (ie.  not implementation-dependent) way to safely allocate
memory.  It shouldn't have to blow your program out of the water to
let you know that there was no available memory.  There should be a
way to get NEW to raise an exception.

>The report should have defined this to be a checked runtime error.
>If you want to allocate all available memory without crashing, you
>have to use a lower-level, implementation-dependent interface.

>    7) Has an "assert" capability been considered?  Maybe preconditions
>       and postconditions could be included.  It seems like this would
>       make for a much "safer" situation where an unsafe module exports
>       a safe interface.  Asserts in interfaces could help document
>       the semantics of the interface.

>SRC Modula-3 has an ASSERT pragma.

Perhaps another required module instead of, or in addition to, an
ASSERT pragma?  Yeah, I think I know why the pragma; you want the
compiler to evaluate the predicate if it can, and generate a
compile-time "assertion-botched" message.  This does have its selling
points.  People are more likely to use assertions if they feel that
there is no run-time penalty.  Sigh.

ps: Greg, my mailer couldn't easily reply to your posting...
--
	Ben Chase <bbc@rice.edu>, Rice University, Houston, Texas

muller@src.dec.com (Eric Muller) (02/09/91)

In article <BBC.91Feb8074227@sicilia.rice.edu>, Benjamin Chase writes:

> >SRC Modula-3 has an ASSERT pragma.
> 
> People are more likely to use assertions if they feel that
> there is no run-time penalty. 

The ASSERT pragma works like this:

	<* ASSERT boolean-expression *>

Normally, this is the same as:

    IF NOT boolean-expression THEN RuntimeError ("Assert Failed"); END;

If you compile your program with '-a', the pragma is ignored.

-- 
Eric.