[M3devel] declaring/initializing variables "anywhere"

Rodney M. Bates rodney_bates at lcwb.coop
Tue Aug 4 21:02:53 CEST 2015



On 08/04/2015 01:07 PM, Jay wrote:
> But...Modula-3 already has this in VAR and WITH.

Huh?

WithSt   = WITH Binding {"," Binding} DO S END.
Binding  = Id "=" Expr.
               ^  no explicit type allowed here.

>
> Every language has this for temporaries:
>
> F1(F2() + F3())
>
> Are these bad?
>
> Too much explicit detail can actually get in the way of reading the code.

Yes, it is a balance.  But much of the world is waaaay to far on the implicit side.
Explicitly typing proper subexpressions would be far too tedious.  But that is exactly
why the rules for inferring should not be highly complex, multi-pronged, or depend on
a huge amount of context.
>
>
> People also claim "just hover over the variable in IDE or debugger" which I don't buy. The tools always lag the language -- even if some don't, old tools remain in use.
>

Yes, if such tools were ubiquitous and dependable, it would go a long way to help.

>
> "instant gratification" is also spun as "productivity" and "automatic maintenance" -- don't need to adjust redundant type declarations when things change, assuming compiler gets it right. Some people trust the deduction more than human, some don't.
>

Yes, there is a tension in making things self-adapt to one-place changes.  But if
it's as complicated as is often the case, the time saved editing will be overwhelmed
by the time figuring out what to change.  And trusting a language/compiler to make
the deduction that works right while the human can't understand it, or just doesn't
have the time and gumption, is insane.

>
>   - Jay
>
> On Aug 4, 2015, at 9:42 AM, "Rodney M. Bates" <rodney_bates at lcwb.coop> wrote:
>
>> 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
> _______________________________________________
> 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