[M3devel] language evolution?

dirk muysers dmuysers at hotmail.com
Sun May 9 09:57:50 CEST 2021

Then program in golang. It gives you just that

Sent from my iPhone

On 9 May 2021, at 09:51, Jay K <jayk123 at hotmail.com> wrote:

I am not suggesting remove anything.
Interfaces/modules must retain their existing features. Global variables, etc.
But I'd like types to gain features. Specifically as I said: I should not have to heap allocate state in order to say state.function() nor should state.function() require a function pointer (or vtable pointer). Nor would I like to say state.function(state).

Is that too much to ask?

 - Jay

From: Dirk Muysers <dmuysers at hotmail.com>
Sent: Sunday, May 9, 2021 7:45 AM
To: Jay K <jayk123 at hotmail.com>; Mika Nystrom <mika at alum.mit.edu>; m3devel at elegosoft.com <m3devel at elegosoft.com>
Subject: Re: [M3devel] language evolution?

Do you want to destroy the language?

On 09/05/2021 09:42:25, Jay K <jayk123 at hotmail.com> wrote:

Also I must point out something, in my way of thinking.

I think more programming should be type-based, not module- or subsystem- based.

Interfaces should be primarily containers of types, and types containers of state and associated functions.
(at least syntactically; types need not contain function pointers, just
that static function dispatch should be driven by the types of parameters
esp. the first parameter).

Interfaces should not directly contain many functions.
Just like interfaces should not directly contain many variables -- global variables.

So types need more powers and less restriction.

I want to more often say:


and not


(and esp. not state.functions.function(state))

Even if not all programming can/should be type-based, there should at least be plenty of linguistic power for such programming.

- Jay

From: M3devel <m3devel-bounces at elegosoft.com> on behalf of Jay K <jayk123 at hotmail.com>
Sent: Sunday, May 9, 2021 3:28 AM
To: Mika Nystrom <mika at alum.mit.edu>; m3devel <m3devel at elegosoft.com>
Subject: Re: [M3devel] language evolution?

I admit I do not understand the brand feature.

I have never seen it elsewhere and I do not know how it translates to C or C++.
The language definition is generally too high level for me to understand.
I always think in terms of how things translate to C, which is then
easily translated to assembly. C++ trivially translates to C. Modula-3 almost
does too.

In C and C++, names determine type identity, so:

struct Age
 int value;

struct Distance
 int value;

are different types. No brand is needed. No structural hashing occurs.

Granted, typedefs are transparent, so:
typedef int Distance;
typedef int Age;

is not so "strong".

I know the Modula-3 designers debated this and I should reread that chapter.
I often wonder if the solution should not be somehow both.
We could use name + hash.
If you ignore pickles, or at least pickles surviving type renames while retaining structure (Foo => Foov1),
and you can afford to store and compare variably lengthed strings, I think this works and wins.

Regarding indirection of objects, the problem is the heap allocation and garbage collector pressure.
Not the indirection.

In C++ we can do something like:

Type xstorage; // static or automatic
Type* x = &xstorage; // unify with all pointer-wanting code

and then just use x.

I kinda think objects should be "var", not "ref".
To resolve the part about taking address of locals -- because we *can* actually take the address of locals.

The problem then becomes, maybe, where can you store them?
But records have this problem too.

It is true, I guess, I haven't done it, that records + procedures come close, but there
are a few critical differences you indicate.
 - Lack of inheritance.
 - Convenient vtable initialization.
 - Calling:
    jay.fn^.age(jay);  yuck!
 - Size:
   Given only non-virtual functions, don't store anything.

Inheritance is strongly debated either way.
It is clearly useful and sensible sometimes (look at m3front!), and maybe it is overused sometimes (look at m3front? 🙂 ).

Basically, yes, you really do want:

 jay.age()  no function pointer or vtable pointer

instead of

I mean, you know, achieving state.function() (or "object.method()") in "systems programming" is
the stuff of countless person years (C++, Java, COM, etc.) and Modula-3 does support it,
but it comes with unnecessary heap allocation.

As well, you want state.function() to be dynamically dispatched or not.
Depending on which function.

You know, I mean, C++ strikes imho a very healthy balance here.

I know ultimately C++ is complicated, but it is death by a thousand cuts, and we should be able to afford a few?

Thinking more on syntax..


  birth_year: INTEGER;


jay := Person.T{1900}; (* not really :) *)


The idea is a value's static type implies its declaring interface as "its" scope.
 C++ has a similar thing where parameter types imply namespace. Koenig lookup.

Though honestly I want to group arbitrarily many types in one interface/module,
so that breaks down. (e.g. m3c.m3) I understand the Interface.T idiom but I don't think
we should be bound by it. I don't think Modula-3 generally is. It is just
a convention.

And x.fn means x is first parameter to fn?

  birth_year: INTEGER;
  CONST Age = DogAge;

  birth_year: INTEGER;
  CONST Age = PersonAge;

 RETURN (Time.CurrentYear() - self.birth_year) * 7;
END DogAge;

PROCEDURE PersonAge(VAR self: Person):INTEGER =
 RETURN (Time.CurrentYear() - self.birth_year);
END PersonAge;


That is much more explicit, as to what kind of lookup occurs, and seems convenient enough,
and more flexible.

I am using "const" here to indicate "not virtual".
I think virtual could reuse the object/methods syntax.
But I grant maybe these syntaxes should be more similar. Again look at C++,
where the syntaxes are the same except for the "virtual".
So my syntax is probably too clever. We just need a way in the existing
"methods" syntax to indicate non-virtual.

And then there is the matter of static member functions.

I understand the point, that language changes ripple through the larger language ecosystem.
m3front is the primary but not only definition of the language, but in my opinion
should not hold language evolution at a stand still.

C++ has the same problem -- expression evaluation in debuggers has been a mess.
But, then again, it hasn't bothered me much either. You do not really need the
expression evaluator in the debugger to handle much of the language.

Language aware editors can suffer too, but again, no big deal, language unaware
editors suffice. :)

We might consider better librarizing m3front??
I understand, having multiple implementations can be seen as good or bad,
so converging them to fewer also good and bad.

To repeat, sorry, I think despite all the criticism of C++, it really should be considered
to borrow some features from. There really are plenty of non-controversial features.

Which reminds me, even C99 can introduce variables later in a function, without implying
opening a block/scope/indent. I miss that. :(

 - Jay

From: Mika Nystrom <mika at alum.mit.edu>
Sent: Saturday, May 8, 2021 9:30 PM
To: Jay K <jayk123 at hotmail.com>; m3devel <m3devel at elegosoft.com>
Subject: Re: [M3devel] language evolution?

Hi Jay,

Writing here as someone who is not really a language implementer or a compiler person, but someone who regularly writes a bit of Modula-3 code.

Let me take your ideas backwards...

2. what do you gain here?  I don't think enough to make the complication worthwhile.  Is it such a big deal to write "Person.Age(johnDoe)"?

So this used to annoy me as well.  I learned to live with it, because Modula-3 has some very effective ways of doing shortcuts for Person.Age:

-- if you find yourself typing it a lot in a single procedure, module, or block, you can just write CONST Age = Person.Age.  If in a module, you can just as well write FROM Person IMPORT Age;  Also consider using generics.

-- or ask yourself why you care about the indirection.  I have never been able to measure a performance difference, and I have tried a few times.

I think the design intent is that for performance critical stuff you use records and procedures and OOP gets the flexibility.  Do you need a middle ground, really?


So you ask OK, what we're proposing is optional.  But no, not really.  One of the super powers of Modula-3 is that it is a language in the same class as C++ but with a comprehensible type system.  There are at least two programs in CM3 that depend on understanding (at least the safe subset) of the type system.  If you start adding in non-virtual methods, ..... you are really making life difficult for these programs.  I don't know how many others may exist.  The ones in CM3 are Network Objects, and the Scheme interpreter I wrote ("Modula-3-Scheme"), which both use m3tk to understand basically arbitrary interfaces, and they really do need to understand the way to call methods, and they use the OOP features to add "surrogate objects" (I think that is a design pattern from Gamma et al.?  Or is it called a "facade"?)  If you add static/final methods you are suddenly making these systems keep track of different types of methods, on top of everything else they need to do.  Maybe not a major complication but still it is a complication that I think can be avoided.

1. This is trickier.  The javaism of having to NEW all OBJECTs in M3 is indeed a performance issue in many programs.

So let's make VAL = REF^-1 (i.e., the inverse of REF).


means T = U. for all U.

The only problem with it is... what the heck do you DO with a stack-allocated VAL OBJECT?  You can't pass it to anything that expects  an OBJECT, taking the address of something is verboten in non-UNSAFE Modula-3.  So you will have to declare your target procedures as taking VAR/READONLY x : VAL OBJECT ... parameters.

But what of the methods themselves?  The initial argument is the object itself.  They now take VAR/READONLY self : VAL OBJECT parameters too, I guess?  But then you probably break polymorphism?  (And if you are OK with breaking polymorphism, see my objections to your suggestion 2. above.  If all you want is a RECORD with PROCEDUREs, just do that!)

I would like VAL REF for another purpose.  To be able to do:


VAR aMegOfObjects := NEW(REF ARRAY OF T, 1024 * 1024);

I think that is at least as useful as stack-allocated objects.

What would be the meaning of


          T = VAL UNTRACED REF ...

At least the former is important because many existing OBJECT types are BRANDED.

Yeah this won't work because the methods are messed up.  See:

TYPE T = OBJECT METHODS hello() := HelloT; END;

PROCEDURE HelloT(self : T) = BEGIN IO.Put("hi!\n") END HelloT;



    u : U;
    u.hello()   (* what happens here? *)

the problem being that HelloT has the wrong type of initial argument.  It needs to be of type VAL T, not T.

What if you let it be of type T?

Then consider...

TYPE T = OBJECT METHODS goodbye() := GoodbyeT END;

VAR trap : T;

PROCEDURE GoodbyeT(me : T) = BEGIN trap := me END GoodbyeT;


      u : U;
  END; (* u is out of scope here *)
  trap.goodbye() (* ????? *)

The declaration of U parenthesizes inconveniently.

TYPE U = (VAL OBJECT METHODS goodbye() END) OVERRIDES goodbye := GoodbyeU END;

If you don't override a method when you make a VAL object it must be overridden with NIL, to preclude making references to the stack.

I guess you get a separate type hierarchy for VAL OBJECT types from OBJECT types, you can't inherit the methods.

Now there is a school of thought in OOP, I know, that holds that inheriting methods is a Bad Idea and dangerous.  That school would probably be OK with your idea.  It says that it's OK, and a Good Thing, to inherit the method signature, since it is part of the specification.  But implementations (i.e. method text) should not be inherited.  Are you part of this school?

Is there a problem with REF VAL OBJECT?  Yeah it becomes ambiguous.  Drat... is it (REF VAL) OBJECT = OBJECT or is it (REF (VAL OBJECT)), a REF to a given VAL OBJECT type?  These are obviously different concepts.

Are you sure you want this?  It seems tricky and not that convenient after all.


On Fri, May 7, 2021 at 11:53 PM Jay K <jayk123 at hotmail.com<mailto:jayk123 at hotmail.com>> wrote:
Here are some language features I would like.

1. OBJECTs that are not heap allocated.

Given that records can be on the stack or global, and can contain function pointers,
why must OBJECTS be heap allocated?

The syntax is almost obvious, except that I think OBJECT implies REF.
Perhaps we could say "VALUE" to negate that?
Or the existing keyword VAL could be repurposed?

2. non-virtual object methods; or merely non-virtual member functions in records.

   birth_year: INTEGER;
   CONST PROCEDURE age():INTEGER = Person_Age; (* not a function pointer *)

PROCEDURE Person_Age(VAR self: Person): INTEGER =
  RETURN Date.CurrentYear() - self.birth_year;
END Person_Age;

VAR johnDoe := Person{1980};


 => equivalent to Person_Age(johnDoe), but using the type
    of the prefix parameter to disambiguate the short name "age",
    so I do not have to state the type in the function name over and over.


This could kinda be considered the same request..if you give records the ability
to inherit..you know, how in C++, class and struct are really the same thing.

I get that Modula-3 is optionally safe, has no preprocessor, has easier
to implement generics -- it is not C++ -- but are these bad features?

 - Jay
M3devel mailing list
M3devel at elegosoft.com<mailto:M3devel at elegosoft.com>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://m3lists.elegosoft.com/pipermail/m3devel/attachments/20210509/df832463/attachment-0001.html>

More information about the M3devel mailing list