Limiting scope within a procedure

Posted by Admin on 21-Apr-2011 16:15

I come from a Java background, and  one of the areas I find to be most troublesome when working with  Progress is handling the scope of variables. Consider the following  snippet:

IF FALSE THEN DO:
  DEFINE VARIABLE i AS INTEGER INITIAL 4.
END.
DISP i.

This is fairly bothersome to me. The variable defined in a  block doesn't lose scope. What really gets me is that information  contained in a block that will never fire is able to get out.

It makes  sense to me that you want to limit the scope of variables to as little  as necessary to get the job done. I fix a lot of bugs that are caused by  using file-scoped variables as local variables, and by local variables  that are not used properly. The majority of these bugs wouldn't exist if  variables were declared where they are used and then disappear at the  end of the scope where they are declared.

My question is: what techniques do  you guys use to prevent "scope-bugs" from cropping up?   

Edit: and a semi-related question, where can I go to report this particular issue? I don't know if I would call it a bug report or a feature request (as it may be intended behavior for it to be the way it is right now, although I personally consider it a harmful mechanic.)

All Replies

Posted by Thomas Mercer-Hursh on 21-Apr-2011 18:31

Having a variable definition in an ID FALSE DO block is just plain nonsense and should never be done.  Having a variable definition in an internal procesure or method is perfectly sensible and limits scope exactly as you want.  Scope to the procedure or class when appropriate, but scope to the internal procedure or method when possible.

Posted by Admin on 21-Apr-2011 19:11

Obviously it being in a block that will always be false is nonsense. But every IF block is expected to not be entered sometimes, correct? If that wasn't the case, it wouldn't be an IF block. In other words, while one that is explicitly false is rather silly, bear in mind it's merely a placeholder for the fact that every IF block has potential to evaluate to false.

I completely agree with your statement "scope to the internal procedure or method when possible" when juxtaposed to your previous statement, "Scope to the procedure or class when appropriate." However, there are times when even being scoped to the internal procedure or method is too much exposure. I want the variable to have only as much exposure as it needs. So if I have a method or internal procedure with an IF or FOR or other DO block, and I have a need to use a variable inside that block and only in that block (not before, not after) I should be able to define that variable in the block, and have it go away at the end of the block. As it stands, that variable can be used for the rest of the procedure.

The implications of this are serious. Consider something like this

/* sum the tax paid on orders for the last year, sorted by customer */


DEFINE VARIABLE lastYear AS DATE.
lastYear = getOneYearAgoToday(). /* assume such function exists */
FOR EACH customer NO-LOCK WHERE customer.active:
  DEFINE VARIABLE taxsum AS DECIMAL INITIAL 0.0 .
  FOR EACH order NO-LOCK WHERE order.cust-no EQ customer.cust-no
                           AND order.order-date GT lastYear:
    FOR EACH order-line NO-LOCK WHERE order-line.order-no EQ order.order.no:
      taxsum = taxsum + ( order-line.price * customer.tax-rate ).
    END. /* order line */
  END. /* order */
  CREATE tt-cust-tax. /* our temp-table records */
  ASSIGN tt-cust-tax.cust-no = customer.cust-no
         tt-cust-tax.taxsum  = taxsum.
END. /* customer */
/* I should not be able to access the "taxsum" variable here. It should
   go away at the end of the "FOR EACH customer" block */

You may want to use "taxsum" in another loop later. You shouldn't have to pick a different variable name just because it's been used already: doing so is simply going to be awkward and clunky. A variable defined in a scope should be limited to that scope. In any event, the variable is accessible to everything after that for loop. What's worse is that it contains the value of the taxsum for the last customer in the loop! This is a frightening prospect, and a nightmare to debug when it happens.

Posted by Thomas Mercer-Hursh on 21-Apr-2011 19:20

No, the point is that blocks don't scope variables, so putting a definiton inside any block is meaningless ... the IF FALSE just makes it particularly nonsensical.  Divide functional units areound internal procedures or methods and they do scope variables and buffers.  Problem solved.

Posted by Peter Judge on 21-Apr-2011 19:20

This is fairly bothersome to me. The variable defined in a block doesn't

lose scope. What really gets me is that information contained in a block

that will never fire is able to get out.

ABL variable scoping is only at the procedure level - so that includes classes, methods, user-defined functions, and internal and external procedures. Blocks have other scoping properties, but variable scope is not one of them. This is something that just is; I have no idea how necessary you find it - and there have been times when I've wanted this too - but if it's a pressing need, then I'd suggest getting in touch with Product Management and logging an enhancement request.

It makes sense to me that you want to limit the scope of variables to as

little as necessary to get the job done. I fix a lot of bugs that are caused

by using file-scoped variables as local variables, and by local variables

that are not used properly. The majority of these bugs wouldn't exist if

variables were declared where they are used and then disappear at the end of

the scope where they are declared.

I believe there are some 3rd party products like ProLint that can help you. Coding standards, naming conventions and code reviews will also help with this. But a lot of the time this is result of lazy or slipshod programming (and I've bitten myself in this way many times . The COMPILE .. PREPROCESS option shows buffer and transaction (I think) scope; not sure whether it also shows variable scope, and whether it could be enhanced to do so.

But the ABL is an old language, and is - unlike many others - almost completely backwards compatible, and so features implemented 20-some years ago are still in effect now; this affects how sweeping changes can be, to a degree.

-- peter

Posted by Admin on 22-Apr-2011 04:18

Progress just does not work that way. You can place DEFINE statements wherever you want, provided you place them before the first reference to it. Placing them inside conditional blocks is useless and misleading. Good practice is to place DEFINE statements at the top of the procedure / function / whatever.

I do have some issues though with the default scoping of record buffers. If you have an internal procedure that references a buffer, that buffer is then scoped to the whole procedure.

Example:

procedure findCustomer:

  define input parameter piCustNum as integer no-undo.


  find customer where customer.custnum = piCustNum no-lock no-error.

end procedure.

In this example the customer buffer is scoped to the whole .p. See listing file:
     File Name       Line Blk. Type   Tran            Blk. Label           
-------------------- ---- ----------- ---- --------------------------------
c:\temp\1.p             2 Procedure   No   Procedure findCustomer          
c:\temp\1.p             0 Procedure   No                                   
    Buffers: sports2000.Customer
If I explicitly set the buffer scope to the internal procedure things are as you would like to have:

procedure findCustomer:

  define input parameter piCustNum as integer no-undo.

  define buffer customer for customer.

  find customer where customer.custnum = piCustNum no-lock no-error.

end procedure.

New listing:
     File Name       Line Blk. Type   Tran            Blk. Label           
-------------------- ---- ----------- ---- --------------------------------
c:\temp\2.p             3 Procedure   No   Procedure findCustomer          
    Buffers: sports2000.customer

c:\temp\2.p             0 Procedure   No          
                        
You can see the customer buffer is now scoped to the internal procedure.
It is sad that scoping to the internal procedure is not standard behavior.

Posted by agent_008_nl on 22-Apr-2011 08:20

Great questions. Progress programmers tend to be quit lazy reducing variable- and bufferscopes.That makes maintaining their code often to a nightmare.

I always scope variables that are used only in a part of a routine by an 'extract method refactoring pattern'. Scopes of the buffers are also alll on the routines.

It is also a good habit to define variables with 'no-undo' by default (see docs for the reasons).

First step (extract calcCustTax routine):

FUNCTION calcCustTax RETURNS LOGICAL PRIVATE (BUFFER customer FOR customer):


  DEFINE VARIABLE taxsum  AS DECIMAL NO-UNDO INITIAL 0.0.
  DEFINE VARIABLE lastYear AS DATE      NO-UNDO .

  DEFINE BUFFER order       FOR order. /* not absolutely necessary because of the for - scope, but not bad too (imagine a leave is added in the loop */
  DEFINE BUFFER order-line  FOR order-line. /* not absolutely necessary because of the for - scope, but not bad too (imagine a leave is added in the loop */
  DEFINE BUFFER tt-cust-tax FOR tt-cust-tax.

  lastYear = getOneYearAgoToday(). /* assume such function exists */

  FOR EACH order NO-LOCK WHERE order.cust-no EQ customer.cust-no
                           AND order.order-date GT lastYear:
    FOR EACH order-line NO-LOCK WHERE order-line.order-no EQ order.order.no:
      taxsum = taxsum + ( order-line.price * customer.tax-rate ).
    END. /* order line */
  END. /* order */

  CREATE tt-cust-tax. /* our temp-table records */
  ASSIGN tt-cust-tax.cust-no = customer.cust-no
               tt-cust-tax.taxsum  = taxsum.
END FUNCTION.

/* code herebelow should also be placed in a routine with the customer buffer locally scoped */

FOR EACH customer NO-LOCK WHERE customer.active:
  calcCustTax(BUFFER customer).
END. /* customer */

Second step (remove temp taxsum):

FUNCTION calcCustTax RETURNS LOGICAL PRIVATE (BUFFER customer FOR customer):


  DEFINE VARIABLE taxsum  AS DECIMAL NO-UNDO INITIAL 0.0.
  DEFINE VARIABLE lastYear AS DATE      NO-UNDO .

  DEFINE BUFFER order       FOR order.
  DEFINE BUFFER order-line  FOR order-line.
  DEFINE BUFFER tt-cust-tax FOR tt-cust-tax.

  lastYear = getOneYearAgoToday(). /* assume such function exists */

  CREATE tt-cust-tax. /* our temp-table records */
  ASSIGN tt-cust-tax.cust-no = customer.cust-no.

  FOR EACH order NO-LOCK WHERE order.cust-no EQ customer.cust-no
                           AND order.order-date GT lastYear:
    FOR EACH order-line NO-LOCK WHERE order-line.order-no EQ order.order.no:
      ASSIGN tt-cust-tax.taxsum   = tt-cust-tax.taxsum   + ( order-line.price * customer.tax-rate ).
    END. /* order line */
  END. /* order */


END FUNCTION.

/* code herebelow should also be placed in a routine for scopingreasons */

FOR EACH customer NO-LOCK WHERE customer.active:
  calcCustTax(BUFFER customer).
END. /* customer */

Note also that removing temp lastYear (replacing the temp with the functioncall in the where-clause) is not desired for performancereasons.

--


Kind regards,

Stefan Houtzager

Houtzager ICT consultancy & development

www.linkedin.com/in/stefanhoutzager

Posted by GregHiggins on 22-Apr-2011 09:50

Variables are scoped to their containing procedure. The scoping mechanism is to create internal procedures and functions, and for classes, methods.

I can look at most ABL code and tell I didn't write it, merely by noting the number / size of internal procedures / functions.

Posted by Admin on 22-Apr-2011 12:54

I actually was hoping to submit a request for it, but hours of searching psdn and the primary site has yeilded nothing.

However, the point you bring up about the impact such a feature would have on legacy code is significant. I'm not a big fan of it when a new release comes out (in any language) and code breaks. That alone makes me realize that I actually don't want it implemented, even though I really do want the feature.

I like the idea of refactoring out into a different procedure, function or method, but there are times where it seems silly to do so. You know, when it's small enough that it doesn't really warrent it's own method, but it still requires the use of a variable. So it's unfortunate that there isn't a better way to get around this, but I suppose it would also have the benefit of lending itself to code reuse, which is a good thing.

Thanks for your time in this response!

Posted by Admin on 22-Apr-2011 13:03

The scoping of buffers is a whole 'nother ball of wax, although it is certainly a point along the same lines and is also a fairly significant source of headache in maintaining our (particularly large) legacy codebase.

You state that "Good practice is to place DEFINE statements at the top..." This is actually contrary to my experience in other languages. To quote Joshua Bloch, "If a variable is declared before it is used, it's just clitter - one more thing to distract the reader who is trying to figure out what the program does." However, in light the circumstances, I have to admit that defining at the top would be the most appropriate course of action. The key, it seems, is to keep methods short to eliminate the possibility of variable corruption. This is something that is considered a best practice across most languages anyway.

If I may ask, what command did you use to acquire that "listing file"?

Thanks for your insight on buffers!

Posted by Peter Judge on 22-Apr-2011 13:07

If I may ask, what command did you use to acquire that "listing file"?

Take a look at COMPILE ... LISTING ... in the Help.

-- peter

Posted by Admin on 22-Apr-2011 13:15

My variables in real code NO-UNDO due to local coding standards. The same is true with "no function calls in queries."

This is the route I've been taking with a lot of my code. It's usually not for the purposes of variable scoping (typically it's because there's a lot of copy/paste code that really should have been broken out into a separate routine to begin with) but it would serve the purpose.

There are times, however, where it would seem simply overkill to do this. In this example (which I admit is contrived) you essentially have a single line of code doing work. If you expand it out to the loops you still only have a handful. It seems to me it is a little... small to be broken out into a separate routine. But as I mentioned in other responses, it appears to be the best course of action here.

I was also unaware you could pass a buffer as a parameter; this is probably due to another local coding standard. I'll have to do some experimenting with that, as I could see it being quite useful. Right now our current method is to pass in ROWIDs and just do another find, which I must admit I'm not the biggest fan of this either, but I think it beats passing in the find criteria and doing another (potentially index-failing) search. I can only imagine that the ROWID searches are quite fast considering 1) They're already in the local cache being the record was just found recently and 2) The tight coupling with the database that ABL provides only further decreases this extra seek time.

Thanks for your step-by-step. I do have a follow-up question though: what is the purpose of defining a buffer with the same name as the default buffer? Are there potential pitfalls with this methodology?

Posted by Admin on 22-Apr-2011 13:16

Thanks! I'll look into that. Although there are plenty of things I can say I don't care for with ABL, one of the things I am fond of is the insight the compiler provides with the XREF utility. This human-readable Listing file also looks to be useful. Thanks again.

Posted by Peter Judge on 22-Apr-2011 13:25

what is the purpose of defining a buffer with the same name as the default buffer?

Scoping to the (internal)procedure and not having to change code. This is a double-edged sword ...

Are there potential pitfalls with this methodology?

Mainly confusion about whether you're using a named buffer or not. And there's the real risk of breaking code that depends on bleeding scope, and because the name in the internal procedure is the same as the default, it's not immediately obvious what the scope is.

-- peter

Posted by Thomas Mercer-Hursh on 22-Apr-2011 13:25

A function can be only one line of code.  An IP too, for that matter. Likewise a method.  It is just a question of slicing up the problem into meaningful units.  If there is a variable or buffer scoping issue, then it is big enough to be an IP or method.

Some people like defining buffers with the same name as the table since the code then reads like it is working on the table but one actually has a scoped buffer.  I dislike doing this since it makes it less obvious that one has a local buffer so I always use a different name.  In fact, if I had multiple buffer definitions in multiple IPs, I would be inclined to name them differently so that the scope was obvious.  Helps avoid surprises too.

Posted by Thomas Mercer-Hursh on 22-Apr-2011 13:28

Only with an explicit define can you make intent clear and maintain full control.  If it were automatic, how would you access a buffer scoped to the procedure?

Posted by abevoelker on 22-Apr-2011 17:03

This is just a boneheaded design decision on the part of Progress to scope variables to the procedure level.  This type of thing leads to scope creep and breeds dangerous non-reentrant functions.  Tsk tsk.  It will never be fixed, so don't bother with the bug report, because there is way too much ancient Progress code that needs backwards compatibility.  I'll increment the Progress fail counter, though, if that makes you feel better.

progressFail++;

Posted by Thomas Mercer-Hursh on 22-Apr-2011 17:15

Someone got up on the wrong side of the bed this morning.  What, exactly, is wrong with scoping to the IP or method?

Posted by stevenseagal008 on 23-Apr-2011 14:37

It's harmful due to the fact that it is unexpected behavior, IMO.  I've never coded in a language that didn't scope variable declarations to the looping block, so it violates the principle of least surprise, in my (subjective) view.  But, then again, I haven't coded in very many older languages so maybe ABL is not a zebra in this regard (although C/C++ doesn't do this...).  Of course, it's not a "show stopper" or anything like that.  A compiler warning or something wouldn't hurt, I guess, to let you know that the compiler is moving the DEFINE statement.  Or even a footnote in the "DEFINE VARIABLE statement" portion of the help manual.

It's annoying because it imposes additional restrictions on the programmer.  If I wanted my variable definitions scoped to the procedure block, then I would move the DEFINE statement into the procedure block scope myself.  I think there are plenty of times when you have multiple loops in the same procedure block that would benefit from tightly scoped variables.  For instance, C and Java both have shorthand for defining your iterator variable within the loop:

//Loop 1
for (int i=0; i
}
//Loop 2 - in the same procedure block
for (int i=0; i
}

My iterator variable, i, only really needs to live inside these loops because that's the only place that its lifetime makes sense.  I want the compiler to complain loudly if I try to access i outside of those blocks!

Posted by Thomas Mercer-Hursh on 23-Apr-2011 14:53

I can see some point to auto-defining variables like that in the looping construct ... but the issue there seems to be more one of auto-definition than the scoping.  If one assumes explicit defines ... which has a certain virtue too, then one has a different situation.  E.g., in ABL, one might wish to say

do i = 1 to 10:

and have i autodefine and scope, but the price that one pays for that is that there is no explicit definition of i.  One certainly couldn't expect to say:

do i = 1 to 10:

  define variable i as integer no-undo.

Since the variable definition is after the use.  Moreover, it is in an iterating block and one does want to execute the define in every iteration.  So, one needs:

define variable i as integer no-undo.

do i = 1 to 10:

But, now the definition isn't in the block so how could it be scoped to the block.  Whereas:

procedure SumSomething:

  define variable i as integer no-undo.

  do i = 1 to 10:

Makes the scope really clear and also opens up the possibility that the scope is one loop or several depending on the need.

The key, really, is factoring code into small procedures or methods instead of having many lines of run on code.

FWIW, I stopped counting when I got to 50 languages in which I had written and that was probably 20 years ago.  One thing that has taught me is that any claims of "everybody else does it this way" only apply if one has not experienced many variations.  Clearly, there are some convenience short cuts in other languages that are not in ABL, but while convenient, I am not entirely sure they are good things because they make code less clear.

Posted by Admin on 23-Apr-2011 15:00

Thomas, Java and C# don't have auto define of variables (like Visual Basic has or had, not 100% about VB.NET because it's a toy and not a language).

for (int i=0; i

is an explicit variable definition, because it's prefixed with the type name. It's the same as

int i = 0;

for (i=0; i

with the difference, that in the first case i is scoped to the loop only, in the second case it's scoped to the method.

Posted by Thomas Mercer-Hursh on 23-Apr-2011 15:08

In the context of ABL, I would still count this as a form of autodefine since one does not have the full range of options available that one has with a DEFINE statement.

Posted by Admin on 23-Apr-2011 15:15

In the context of ABL, I would still count this as a form of autodefine since one does not have the full range of options available that one has with a DEFINE statement.

In the context of C# and Java I count that as a full variable definition: type and initial value. You can't add more (arrays are part of the type).

In the ABL, what else would be missing? Probably just NO-UNDO (kind of a sub-type to me), and the no longer that much relevant formatting options.

Posted by abevoelker on 23-Apr-2011 16:11

Something like this would be nice:

DO INT i=1 TO 10:

END.

As a special variable declaration shortcut, kind of like how you can get a LOGICAL short-cut-defined off of the MESSAGE ... UPDATE statement to get user input.

It's better than doing something gross like

DO (DEF VAR INT i NO-UNDO INIT 0) TO 10:

END.

which if anything this just highlights the annoyance of Progress' verbosity compared to other languages.

tamhas wrote:
The key, really, is factoring code into small procedures or methods instead of having many lines of run on code.

I  don't think that works when it makes more sense to have multiple  looping constructs in the same procedure/method block.  If you have to  wrap every looping construct in a procedure block to get variable  scoping working then you'll end up with spaghetti code.

tamhas wrote:
FWIW, I stopped counting when I got to 50 languages in which I had written and that was probably 20 years ago.  One thing that has taught me is that any claims of "everybody else does it this way" only apply if one has not experienced many variations.  Clearly, there are some convenience short cuts in other languages that are not in ABL, but while convenient, I am not entirely sure they are good things because they make code less clear.

Totally agree.  Variation between languages is what sets them apart, for better or worse, and makes life interesting.  In my opinion, though, ABL made a lot of decisions that fall into the 'worse' category.  But I'm sure many on here would disagree.  To each his own.  My last day coding in ABL is 04/26 though, so I won't be coming on here griping about it anymore. 

I'm not sure I agree about the conciseness thing.  I think

int i, j, k, l = 5;

is more concise and clear than

DEFINE VARIABLE i AS INTEGER NO-UNDO INITIAL 5.

DEFINE VARIABLE j AS INTEGER NO-UNDO INITIAL 5.

DEFINE VARIABLE k AS INTEGER NO-UNDO INITIAL 5.

DEFINE VARIABLE l AS INTEGER NO-UNDO INITIAL 5.

(My brain has to filter out too much garbage).  Also,

i++;

is more concise IMO than

ASSIGN i = i + 1.

But again, to each his own.

P.S. Why is there no ABL syntax highlighting option?  Pfft.

Posted by Thomas Mercer-Hursh on 23-Apr-2011 16:16

Whatever ... yes, I would consider NO-UNDO pretty important and the context is ABL, not the shorthand of C# and Java.

But, more to the point, the original and reinforced complain is not about DO i = 1 to 10, but about DO: DEFINE which has migrated to iterating blocks.  I submit that REPEAT: DEFINE, FOR EACH: DEFINE, and interating forms of DO... : DEFINE are highly questionable constructs since one doesn't want a variable defined within an interaction unless it is a part of a smaller scope.  And a plain DO:  DEFINE sets up ambiguities about whether the variable is or is not defined based only on whether the block is executed.  I see no good reason for this and it certainly runs counter to the whole structure of defining variables as a part of compilation.

SO, it seems like the only actual valid use case is for the loop variable in DO I = 1 to 10:  I have trouble seeing that as a major oversight, especially since there are constructs where one wants to know the value of I outside the loop, e.g., to determine when the loop finished.

Posted by Admin on 23-Apr-2011 16:20

SO, it seems like the only actual valid use case is for the loop variable in DO I = 1 to 10:  I have trouble seeing that as a major oversight, especially since there are constructs where one wants to know the value of I outside the loop, e.g., to determine when the loop finished.

I don't get that! In that case you could still define the variable outside the block and get exactly what you want. The ability to scope variables to blocks would not mean that you could no longer scoped them to internal procedures/methods or the whole compile unit.

Posted by Thomas Mercer-Hursh on 23-Apr-2011 16:22

How would you distinguish the case where you want the value of i outside the loop, e.g., to return which element in a set provided the match?

I strongly disagree about the spaghetti code.  Properly written methods in an OO language are often only a few lines long.  Encapsulating a logical unit in its own method or IP makes code more readable, not less, as well as making it more maintainable.

And what do you mean no syntax highlighting option?  Been one for years and years.

Posted by Admin on 23-Apr-2011 16:25

How would you distinguish the case where you want the value of i outside the loop, e.g., to return which element in a set provided the match?

 

It's the same way how you'd distinguish between a method scoped variable and a class scoped variable. Aren't you one of the biggest fan of naming prefixes on earth? Just one sample.

But very limited scoped variables do not necessarily be marked, typically their definition is just very few lines above where they are used.

Posted by abevoelker on 23-Apr-2011 16:27

tamhas wrote:

...since there are constructs where one wants to know the value of I outside the loop, e.g., to determine when the loop finished.

I would say that is stretching it.  Most of the time you don't care.  In the rare circumstance where you do care, you just define it outside the loop - e.g.

int i;

for (i=0; i

}

//check i here

More importantly is that this isn't a big issue to get worked up about, since nothing will be done about it due to backwards compatibility, or if something is done it will be kludgy.  Even if syntax like

DO INT i=0 TO 10:

END.

Were supported for scoping i to the loop, other cases are not handled, which is the main complaint.  The only backwards-supporting capability would be to add yet another keyword, like

DO DEF-SCOPED-LOOP:

END.

Which would just be yet another keyword to remember to tack onto 99% of your DO blocks, like remembering to put NO-UNDO onto 99% of your variable declarations or NO-LOCK onto 99% of your queries.  So, in my opinion, boneheadedDesignDecision == TRUE.

Message was edited by: Abe Voelker to fix the mixing up of iterating/DO block stuff

Posted by abevoelker on 23-Apr-2011 16:32

I should add that if you're checking the iterator's value outside of the loop, you probably aren't using a FOR loop, but an indeterminate construct like WHILE or UNTIL, since you don't know when the loop will exit.  So Progress, when/if you fix this, don't forget to tack on the new keyword to those types of loops as well.

Posted by Thomas Mercer-Hursh on 23-Apr-2011 16:36

My issue isn't about naming.  My issue is clear syntax about whether one is doing an on the fly definition versus using a previously defined variable.

But, regardless, my main point is that even if one came up with a reasonable syntax for defining the loop variable in this way, it is the only use case that I see, but the objections are phrased in terms of a blanket condemnation that blocks don't scope variables ... starting with the execrable IF FALSE THEN DO: DEFINE.  I have seen not one other use case proposed which makes sense and is not nicely handled by the use of IPs or methods.

Posted by Thomas Mercer-Hursh on 23-Apr-2011 16:40

The ability to scope variables to blocks would not mean that you could  no longer scoped them to internal procedures/methods or the whole  compile unit.

No, but give me a use case other than the loop variable which is not well addressed by IP or method encapsulation.

Posted by abevoelker on 23-Apr-2011 16:40

How would you distinguish the case where you want the value of i outside the loop, e.g., to return which element in a set provided the match?

Not sure I understand the question... but I take it to mean that mixing and matching procedure-scoped and iterator-block-scoped variables will get ugly.  Which, I agree with.  It's one of the symptoms of making boneheaded design decisions then having to stick with them forever.

I strongly disagree about the spaghetti code.  Properly written methods in an OO language are often only a few lines long.  Encapsulating a logical unit in its own method or IP makes code more readable, not less, as well as making it more maintainable.

Agree to disagree then I guess

And what do you mean no syntax highlighting option?  Been one for years and years.

Hmm I must be missing that one... I'm looking here: http://i.imgur.com/VebVm.png  Am I missing something?

Posted by Admin on 23-Apr-2011 16:40

My issue is clear syntax about whether one is doing an on the fly definition versus using a previously defined variable.

 

I think we have a very different understanding about "on the fly" definitions.

C#/Java and the here proposed ABL variants don't define variables on the fly. They just set the scoped (during compile time) smaller.

Posted by Thomas Mercer-Hursh on 23-Apr-2011 16:41

Same challenge as I gave to Mike.  Give me a use case other than the loop variable.

Posted by Thomas Mercer-Hursh on 23-Apr-2011 16:44

I should say you aren't using FOR.  Are you likely to want to write:

FOR ....

   DEFINE ...

I.e., the DEFINE executes on every iteration????

And, there are lots of reasons why one might want the loop variable outside the loop, even without WHILE or UNTIL ... not the least of which is error reporting.

Posted by Admin on 23-Apr-2011 16:50

Same challenge as I gave to Mike.  Give me a use case other than the loop variable.

Thomas, sorry won't take your Challenge. It's not worth it - and it's getting too late here. I need to seek real easter eggs with the boy tomorrow morning.

First, you're not ABL product management - so for what should I try to convince you? Second, knowing you, I doubt you would accept it anyhow. Third, I'm not requesting this feature in the ABL - I doubt it's a change that would ever make it into the language, source code compatibility is a major point here - I'm not questioning this point because it's a major strength of the language.

But I'm glad this scoping option is there when I'm writing C# code.

Languages are different. This is one of the differences that I have accepted. It's o.k. for me.

Posted by Thomas Mercer-Hursh on 23-Apr-2011 16:58

So, you are going to condem ABL for a shortcoming, but can't be bothered to tell us when it would be used even if it did exist?  Hard to take that seriously.

Posted by Thomas Mercer-Hursh on 23-Apr-2011 17:03

So, "in line" might be a better phrase than "on the fly", but the real point is that I keep asking for is whether there is any use case than the loop variable.  This thread did not start with nor has most of the negative criticism of ABL limited to the loop variable use case.  I don't dispute that the loop variable is a possibly valid use case, albeit one which is likely to be ugly given the existnig language structures.   I am questioning whether there is any other use case, e.g., anything related to the reaction to IF FALSE THEN DO:  DEFINE structure which started the thread.

Posted by Admin on 23-Apr-2011 17:15

So, you are going to condem ABL for a shortcoming, but can't be bothered to tell us when it would be used even if it did exist?  Hard to take that seriously.

It's not about the case. It's about the why and that this discussion will never lead anywhere.

One and only one try:

Did you ever write code in the ABL that had more than a single CATCH block (in nested blocks or not) for different types?

CATCH Consultingwerk.Exceptions.InvalidValueException

CATCH System.Exception

CATCH Progress.Lang.Error

Did you ever find out that in this case the variable names for the error objects need to be unique? Can't be a default like e or something that would be well explaining enough in the scoped of the CATCH block?

Do you think it's a good use of developers brain cells, to guess an available name for a variable in a CATCH block? e1, e2, e3, ... cei, sysex, ple, ...? And when debugging code (I know, you never have to) you insert a new CATCH block for a different error type somewhere in the top of a method, suddenly the CATCH block somewhere closed to the end fails to compile because it got invalid because of the new CATCH. Not very supportive.

Well, I know your answer - mine is a different one. It's just one sample, a new one for you to question.

Posted by abevoelker on 23-Apr-2011 17:47

I just meant that FOR loops are typically used when you know exactly how many times the loop will iterate.  If a FOR loop terminates in error before reaching the end of its iteration, it's something that would be caught by a stack trace and not by checking the iterator's value explicitly after the loop.  Of course there are cases when you may want to access the iterator value outside of a FOR loop... that's why the code I gave above works just fine.

Posted by abevoelker on 23-Apr-2011 17:51

Nice example.  I concur that this thread is going nowhere so I'll let this be my last post.  Sorry for cluttering up your inboxes!

Posted by Thomas Mercer-Hursh on 23-Apr-2011 17:57

The discussion can have several parts.  Having decided what one wants, one can despair of it being implemented, to be sure.  I have my own bucket of those.  Some have to do with breaking existing code; some have to do with the apparent mindset and value scheme of the people responsible.

But, what I am trying to do is to set aside the case of the loop variable.  Suppose that I grant that it would be nice to have a loop variable scoped to an iterating DO loop where there was a counter ... I'm not sure that I really agree, but I grant at least that this is a valid use case.

However, this very specific use case only got teased out after a lot of discussion about how ABL was demented in general because variable definitions were not scoped to loops generally.  I contend that it is nonsense to put definitions inside an iterating loop, so the only use case other than the loop variable is non-iterating blocks.  I see no use case for scoping variable definitions in that case which is not admirably served by the existing functionality of scoping to method and IP.

Without such a use case, I contend that it is inappropriate to castigate ABL for not having this functionality.

As for naming error objects, I always use meaningful names, so it is not a problem.  I would never write DO i = 1 to 10 in production code.

Posted by Thomas Mercer-Hursh on 23-Apr-2011 18:00

FOR loops are for iterating over unknown quantities and have no iterator.

As an example of a DO loop with iterator where one wants the value outside consider searching an array from to the current top to find a particular value.  One is going to exit when the value is found.

Posted by stevenseagal008 on 23-Apr-2011 19:05

My bad with the capitalization... I'm thinking in terms of non-ABL programming languages.  I meant 'for', 'while', and 'until', lowercase.  i.e. a DO i=1 TO 20 is equivalent to the for (int i=0; i

Posted by abevoelker on 23-Apr-2011 19:26

But, what I am trying to do is to set aside the case of the loop variable.  Suppose that I grant that it would be nice to have a loop variable scoped to an iterating DO loop where there was a counter ... I'm not sure that I really agree, but I grant at least that this is a valid use case.

However, this very specific use case only got teased out after a lot of discussion about how ABL was demented in general because variable definitions were not scoped to loops generally.  I contend that it is nonsense to put definitions inside an iterating loop, so the only use case other than the loop variable is non-iterating blocks.  I see no use case for scoping variable definitions in that case which is not admirably served by the existing functionality of scoping to method and IP.

That's my bad with the definitions inside the loop thing... I agree and fixed that.  My brain got tangled up between the 'DO block' and 'iterating loop' terminology, since they are tightly coupled in ABL (most languages have separate keywords for conditional and iterating blocks).  The initial question refers to conditional blocks, not necessarily iterating blocks, which I think should also have tight variable scoping.

I would never write DO i = 1 to 10 in production code.

Why?

(I know I said it would be my last post before but I had to correct the error of my ways)

Posted by Thomas Mercer-Hursh on 23-Apr-2011 23:22

I wouldn't use a variable like i in production code because I use meaningful variable names which make it clear what they are doing.

OK, there has been some confusion about iterating and non-iterating blocks, but I am still waiting for a use case other than the loop variable itself where block scoping provides a plus that IP or method scoping doesn't.

This thread is closed