[M3devel] declaring/initializing variables "anywhere"

Rodney M. Bates rodney_bates at lcwb.coop
Tue Aug 4 18:42:23 CEST 2015


You're poking at a really big peeve of mine.  I do a lot of maintenance work.  It usually
involves finding one's way around in hundreds of thousands of lines of unfamiliar code.
If you're exceptionally lucky, an eventual one-line change can be figured out after vetting
a mere several tens of lines.  Very often, it is more like a few hundreds, and not
infrequently, even more.

The average line of actively maintained code is written once and read something like
seven times.  (I don't remember where that number came from.)  Speaking for myself,
even most brand new code gets read at least seven times before it gets past my own testing,
before it gets passed to anybody else to either use or further test.  Moreover, the initial
writer already has lots of context in his head that a maintainer has to ferret out.

What I am leading to is that things should be *locally explicit*, far more than we usually
see.  Saving keystrokes once at initial writing time, at the cost of forcing the poor wretches
who have to come along and figure it out later to constantly locate, open, scan another
source file, every fourth token they read, is penny wise and pound foolish.  It's classic
case of instant-gratification immaturity.  Ensuring a ton of aggravation later, to
save an ounce of effort now.  It's just plain laziness.

(more below:)

On 08/04/2015 05:02 AM, Jay K wrote:
> I had:
>
>
> PROCEDURE CompileProcAllocateJmpbufs (p: Proc) =
> VAR module := Module.Current ();
>      alloca := Module.GetAlloca (module);
>      size := Module.GetJmpbufSize (module);

So, coded like this, the poor schmuck who has to come along and figure this
out will have to find Module.i3 and search it for declarations of Current,
GetAlloca, and GetJmpbufSize, just to know what types we have.  And possibly
more steps, as with alloca, whose type, as we will find out, requires
going to yet another source file CG.i3.  It can be even worse, e.g.:

   VAR V := Ptr^.Field[I].method(X);

You have to track down the type of Ptr, to find Field, to find the type of
its elements, to find method, to find its result type.

Sometimes it's obvious from identifier names, but more often, it is not, or at
least only too vaguely for serious software work.  Is a module denoted by an
abstract type?  An integer numbering of modules?  something else?  Very often,
there are two or three different types that denote a module or whatever.

(An aside:  In C++, things often get dramatically worse when coders, overly
enamored with cleverness for its own sake, define implicit type conversions
among these, so the poor maintainer has even less idea what is going on.
Moreover, the conversions could be declared anywhere in the tens of transitively
#included files, and there is no path leading to them, only exhaustive search
through all the files.  And this possibility has to be considered for every
single assignment and actual parameter.)

Instead, I always code cases like this by putting both the type and the initializer
in the declaration:
   VAR module : Module.T := Module.Current ();

I even often do it this way when the initializer is a literal.  For one thing,
I may want a subrange for the type of the variable.

The exception is when the initializer itself also names the type.  It's shorter,
but at no loss of explicitness.

   VAR rec := SomeInterface.SomeType { 0 , NIL , FALSE };

>      try_count := p.try_count;
> BEGIN
>
>
> which is fairly nice and elegant.
> I like the static type inference.
>
>
> but p could be NIL so I need like:
>
>
> PROCEDURE CompileProcAllocateJmpbufs (p: Proc) =
> VAR module: Module.T;
>      alloca: CG.Proc;
>      size: CG.Var;
>      try_count: INTEGER;
> BEGIN
>      IF p = NIL THEN RETURN END;
>      module := Module.Current ();
>      alloca := Module.GetAlloca (module);
>      size := Module.GetJmpbufSize (module);
>      try_count := p.try_count;
>
>
> double the lines, and a need to state types that I didn't have to state before,
>

This will save wasted computation calling the functions, if it turns out p=NIL.
Depending on the purpose of the code.  In some code, I care about efficiency
at this level.

>
> OR I can use WITH to get the "best" of both, but implied extra indentation.
>

If I were to propose any language change at all, it would be to allow a WITH
binding to optionally specify the type too, for exactly the same reasons.

   WITH w : T = Var.method(x) DO ...

Many a WITH statement has been far too hard to read, requiring the side trips of
checking other source files to see what this value really is.



>
>
> Pretty much all other mainstream languages in use today,
> except C89, are "better" than this, they all let you
> both delay local variable declaration until arbitrarily
> into a function, but with implying that you should indent further,
> or close an extra block.
>

To be clear, I definitely do not advocate allowing declarations and statements
to be arbitrarily mixed in a block, BUT...  if we did it, at least the decls
would be syntactically explicit with a VAR, and we would not have to "define"
our language with words like "if it looks like a declaration...".

>
> Most mainstream languages also share Modula-3's type inference
> and let you omit the type, except C.
>
>
> Can we update Modula-3 here somehow?
>
>
> Or just use "with"?
>
>
> Proposals for a syntax that works well with the existing language and compiler?
>
>
> Thank you,
>   - Jay
>
>
>
>
> _______________________________________________
> M3devel mailing list
> M3devel at elegosoft.com
> https://mail.elegosoft.com/cgi-bin/mailman/listinfo/m3devel
>

-- 
Rodney Bates
rodney.m.bates at acm.org



More information about the M3devel mailing list