Recent

Author Topic: Does a procedure in a procedure have any advantage?  (Read 2679 times)

coradi

  • Full Member
  • ***
  • Posts: 167
Does a procedure in a procedure have any advantage?
« on: October 29, 2024, 10:30:09 pm »
Code: [Select]
Procedure  Hallo(n:Byte);

Procedure test;
Var p : Byte;
Begin
p:= 5;
end;

Begin
  Writeln('Hallo, World ',n,'');
end;     
[\code]
Amstrad Schneider CPC 6128
AVR8/ARM(STM32)

Bart

  • Hero Member
  • *****
  • Posts: 5497
    • Bart en Mariska's Webstek
Re: Does a procedure in a procedure have any advantage?
« Reply #1 on: October 29, 2024, 10:49:08 pm »
Well, in your example it doesn't do anything....

Having a nested procedure (as it's called) makes the nestes procedure only visible to the procedure in question.
This could make for cleaner code.

Also, your nested procedure may make use of local variables of the procedure it's nested in, which otherwise would only be possible when passed as a parameter.

Code: Pascal  [Select][+][-]
  1. procedure Log(Lines: TStrings);
  2. var
  3.   i: Integer;
  4.  
  5.   function PrependDate(S: String): String;
  6.   begin
  7.     Result := DateToStr(Now) + ': [' + IntToStr(i) + ']  ' + S;
  8.   end;
  9.  
  10. begin
  11.   for i := 0 to Lines.Count - 1 do
  12.   begin
  13.     writeln(PrependDate(Lines[i]));
  14.   end;
  15. end;

The function PrependDate is only ever needed in the procedure Log, so there is no need to "expose" it.
The nested function also uses the variable i.
(Notice that it would probably be better to just pass i as a parameter to PrependDate in this particular case, but just showing you that in can be done. And it certainly has it's use cases.)

Bart

Warfley

  • Hero Member
  • *****
  • Posts: 1856
Re: Does a procedure in a procedure have any advantage?
« Reply #2 on: October 29, 2024, 10:49:53 pm »
Two things:
1. Scoping. If you only ever use a function within another function, there is no reason to define it globally. Always try to keep the scope as small as possible.
2. Context. You can access the context of the parent function:
Code: Pascal  [Select][+][-]
  1. procedure Foo(X: Integer);
  2. var
  3.   Y: Integer;
  4.  
  5. procedure PrintXY;
  6. begin
  7.   WriteLn('X: ', X, ' Y: ', Y);
  8. end;
  9.  
  10. begin
  11.   Y:=42;
  12.   PrintXY;
  13. end;

Curt Carpenter

  • Hero Member
  • *****
  • Posts: 578
Re: Does a procedure in a procedure have any advantage?
« Reply #3 on: October 29, 2024, 11:33:45 pm »
I think readability is the main advantage since it reminds us of the nested procedure's limited scope.  They can also let us break a long, complex procedure into smaller parts each with a clearly defined function of its own.  Other ways to accomplish those same things of course.

Thaddy

  • Hero Member
  • *****
  • Posts: 16419
  • Censorship about opinions does not belong here.
Re: Does a procedure in a procedure have any advantage?
« Reply #4 on: October 30, 2024, 10:05:27 am »
It is also a way to solve (and break off) recursion. But readability is the most important. It has always been part of Pascal.
It predates classes and objects, while performing similar tasks (except inheritance of course): it ties code to a specific scope. Frankly, that is the - and still is - advantage, by design.
« Last Edit: October 30, 2024, 10:15:40 am by Thaddy »
There is nothing wrong with being blunt. At a minimum it is also honest.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8136
Re: Does a procedure in a procedure have any advantage?
« Reply #5 on: October 30, 2024, 12:56:11 pm »
I'd suggest that it's particularly important if we consider a dialect of Pascal that doesn't allow Turbo-style "writeable constants" (AKA ALGOL "own variables"). Hence something like

Code: Pascal  [Select][+][-]
  1.   procedure initMapView;
  2.  
  3.   var
  4.     scratchLayer: TMapLayer;
  5.  
  6.  
  7.     procedure addArea(const area: TRegionDegs; colour: TColor; opacity: single);
  8.  
  9.     var
  10.       scratchArea: TMapArea;
  11.  
  12.     begin
  13.  
  14.       (* Add an areas object to the layer which will act as a container for     *)
  15.       (* coloured static blocks, returning a reference which may be used        *)
  16.       (* locally.                                                               *)
  17.  
  18.       scratchArea := scratchLayer.Areas.Add as TMapArea; // <------------------------
  19.  
  20.       (* Using that reference which is retained in-scope, set up drawing style  *)
  21.       (* and add features.                                                      *)
  22.  
  23.       scratchArea.FillColor := colour;
  24. ...
  25.     end { addArea } ;
  26.  
  27.  
  28.     procedure addAreas(const areas: TRegionsDegs; colour: TColor; opacity: single);
  29.  
  30.     var
  31.       i: integer;
  32.  
  33.     begin
  34.       for i := 0 to High(areas) do
  35.         addArea(areas[i], colour, opacity)
  36.     end { addAreas } ;
  37.  
  38.  
  39.   begin
  40.  
  41.   (* Add a layer object to the MapView which will act as a container for areas  *)
  42.   (* and tracks, returning a reference which may be used locally.               *)
  43.  
  44.     scratchLayer := MapView1.Layers.Add as TMapLayer;
  45.  
  46.   (* Using that reference which is retained in-scope, add coloured static areas *)
  47.   (* to represent properties or villages which should not be overflown at low-  *)
  48.   (* levels, and an "in-circuit" area within which aircraft should exercise     *)
  49.   (* caution.                                                                   *)
  50.  
  51.     addArea(InCircuit, clYellow, 1/3);
  52.     addAreas(Avoids, clRed, 1/6);
  53. ...
  54.   end { initMapView } ;
  55.  

Once we've got the local reference to the layer, we know that anything else we do is in that context. There's really no need to pass it as a parameter to subsequent procedures (addArea, addAreas and so on), or to start messing around declaring container classes which will never be reused.

Similarly, while initMapView will be called at a fairly high level, there's no need to make addArea, addAreas and so on publicly visible: they're strictly for use locally.

All of this is "Pascal as Wirth intended it": i.e. building on unambiguous scope and visibility rules.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Чебурашка

  • Hero Member
  • *****
  • Posts: 586
  • СЛАВА УКРАЇНІ! / Slava Ukraïni!
Re: Does a procedure in a procedure have any advantage?
« Reply #6 on: October 30, 2024, 01:10:21 pm »
Yes, it improves possibility of code organization significantly.

Often in languages without it, I ended up creating methods with names like this to indicate that subs where intended to be called only by procedure1 (and this is not enforced at all by compiler):

procedure procedure1_sub1();
procedure procedure1_sub2();

procedure procedure1();
begin
  procedure1_sub1();
  procedure1_sub2();
end
« Last Edit: October 30, 2024, 01:24:07 pm by Чебурашка »
FPC 3.2.0/Lazarus 2.0.10+dfsg-4+b2 on Debian 11.5
FPC 3.2.2/Lazarus 2.2.0 on Windows 10 Pro 21H2

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12005
  • FPC developer.
Re: Does a procedure in a procedure have any advantage?
« Reply #7 on: October 30, 2024, 01:20:34 pm »
Moreover if the procedures are relatively short, they can be inlined, making it a substitute for things that other languages use macros for.

Joanna from IRC

  • Hero Member
  • *****
  • Posts: 1296
Re: Does a procedure in a procedure have any advantage?
« Reply #8 on: October 31, 2024, 03:02:08 pm »
Nested procedures are helpful for executing code that will be used more than once only within the procedure. Their advantage is that they don’t clutter the rest of the program.
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  #pascal Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

wp

  • Hero Member
  • *****
  • Posts: 12530
Re: Does a procedure in a procedure have any advantage?
« Reply #9 on: October 31, 2024, 03:22:22 pm »
On the other hand, nested procedures sometimes can be found inside very long procedures, and they seem like an excuse that no time was taken to find better code. Unit ipHtmlTableLayout, for example, contains the procedure TIpNodeTableLayouter.Calcsize with almost 20.000 lines, among them 6 nested procedures, and one of them having 10.000 lines by itself. Such poorly written code is extremely hard to maintain, in particular when the original author (TurboPower in this examples) has disappeared.

(But I must confess that I wrote some of these monsters myself...)
« Last Edit: October 31, 2024, 03:27:22 pm by wp »

Warfley

  • Hero Member
  • *****
  • Posts: 1856
Re: Does a procedure in a procedure have any advantage?
« Reply #10 on: October 31, 2024, 03:54:51 pm »
There is also one thing I must admit wrt. to tooling, that is navigating nested procedures is a pain.

The first thing I do when I setup my Lazarus is pop the project inspector and code explorer into the side bar of the (docked) IDE, so I have all my files and all the functions in the file I'm currently working on visible.

One of the problems with the code explorer (aside from the fact that it's really slow on updating it's view, as well as not updating the currently open function unless you switch windows) is that it does not show you nested procedures.
So you have this really nice window which shows you all the stuff in your file, but only on a global level.

Also I feel that the positioning of nested functions is a bit annoying with being in between the function header and body, which makes it easy to lose track of where you are exactly in the main function or the nested function. A better alternative I think is the way Haskell does it, where the nested functions are below the function:
Code: Pascal  [Select][+][-]
  1. quicksort :: (Ord a) => [a] -> [a]
  2. quicksort [] = []
  3. quicksort (p:xs) = (quicksort lesser) ++ [p] ++ (quicksort greater)
  4.     where
  5.         lesser = filter (< p) xs
  6.         greater = filter (>= p) xs
Where the nested functions (lesser and greater in this case) are located below the code of the function, so you can read the function from top to bottom without helper code being interleaved

Joanna from IRC

  • Hero Member
  • *****
  • Posts: 1296
Re: Does a procedure in a procedure have any advantage?
« Reply #11 on: October 31, 2024, 11:39:54 pm »
I always indent and comment the end of the nested procedures and it sounds like very bad design to have thousands of lines of code in one.
Maybe code written without oop?
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  #pascal Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

440bx

  • Hero Member
  • *****
  • Posts: 4907
Re: Does a procedure in a procedure have any advantage?
« Reply #12 on: November 01, 2024, 01:23:42 am »
I always indent and comment the end of the nested procedures and it sounds like very bad design to have thousands of lines of code in one.
Maybe code written without oop?
There are two reasons to group instructions, they are:

1. A group of instructions accomplishes a specific, well defined logical task.
2. A group of instructions is executed multiple times.

Those are the two reasons instructions are grouped.

Let's start with case 2 above.  If that group is executed in one and only one function/procedure then that group should be nested into that function/procedure to reveal the fact that it is not executed anywhere else.  That gives locality which is important because the programmer doesn't have to wonder what other code in the program depends on that sequence of code.

Case 1 is the really interesting one and the one that most programming languages have not addressed satisfactorily (including Pascal.)  For the sake of a simple explanation and without loss of generality, presume that there are no sequences of code that are executed multiple times.  Also, presume what is the most common case, that the function/procedure is composed of a fairly large number of logical steps.  What follows is what usually happens in most languages, including Pascal. 

The top level function is broken down into a number of functions/procedures and those functions/procedures are then executed in a mostly linear fashion, I say mostly because some may be part of a loop or if statement or some other code that controls how or when the code is executed.   When the programmer reaches that code and sees "SomeFunction(SomeParameter1, SomeParameter2...SomeParameterN), it is quite often necessary for the programmer to "jump" to where "SomeFunction" is implemented to refresh his/her memory to see what the code actually does.  If there are 10,000 lines broken into small "acceptable" pieces to some programmers, that means the 10,000 lines are going to be broken into about 200 individual blocks of code.  That also means that when the programmer is reading the top level function, he/she will have their attention diverted away from the mainline 200 times.  Welcome to spaghetti code and jigsaw-puzzle programming.

The way it should be done is to have a way (a construct of some kind) to group code _inline_ so the programmer can see (and be told) that a sequence of instructions fulfills a specific logical purpose.  This is also a way to have the convenience of inline variables without inline variables and local types and constants that is only applicable to a very short sequence of code.

Inline/anonymous functions/procedures _almost_ fulfill the characteristics of the construct I alluded to above but, very unfortunately they require a "()" to cause them to be executed (because they weren't really designed to be a grouping construct.)

10,000 line functions/procedures are a whole lot better, easier to understand and maintain than functions/procedures broken into hundreds of scattered pieces that have to be mentally re-assembled to see what the function does and how it does it.

IOW, today's commonly accepted and supported programming philosophy is: let's make sure to make the big picture as hard to mentally create as possible.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8136
Re: Does a procedure in a procedure have any advantage?
« Reply #13 on: November 01, 2024, 09:04:53 am »
I always ... comment the end of the nested procedures

I'd add that that became mandatory with Ada and Modula-2, and was optional ** in ALGOL-60. There's obviously also lots of languages which use if...endif etc., and there's even a risk that some of these were BASIC-like and precede Ada (I /really/ hate the idea that something later accepted as good practice originated in K&K BASIC).

** ALGOL-60 apparently discarded everything after an END to the EOL as a comment. It's difficult to design a successor which mandates that /either/ it's a syntax-checked token/identifier /or/ it's an arbitrary comment: in practice I think the common adoption of // as a comment marker makes it sufficiently easy to append a comment that the ALGOL convention is obsolete.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

MathMan

  • Sr. Member
  • ****
  • Posts: 405
Re: Does a procedure in a procedure have any advantage?
« Reply #14 on: November 01, 2024, 11:57:33 am »
There are two reasons to group instructions, they are:

1. A group of instructions accomplishes a specific, well defined logical task.
2. A group of instructions is executed multiple times.

...

The top level function is broken down into a number of functions/procedures and those functions/procedures are then executed in a mostly linear fashion, I say mostly because some may be part of a loop or if statement or some other code that controls how or when the code is executed.   When the programmer reaches that code and sees "SomeFunction(SomeParameter1, SomeParameter2...SomeParameterN), it is quite often necessary for the programmer to "jump" to where "SomeFunction" is implemented to refresh his/her memory to see what the code actually does.  If there are 10,000 lines broken into small "acceptable" pieces to some programmers, that means the 10,000 lines are going to be broken into about 200 individual blocks of code.  That also means that when the programmer is reading the top level function, he/she will have their attention diverted away from the mainline 200 times.  Welcome to spaghetti code and jigsaw-puzzle programming.

I can follow your reasoning, but I beg to differ on your final sentence. The human brain unfortunately is not an unlimited stack machine - context will be lost whether you have a large 10k LOC function or a break down as described above. And if one follows (like me) a top down approach to understanding the second approach is easier to grasp (at least for me). But maybe I misunderstood.

The way it should be done is to have a way (a construct of some kind) to group code _inline_ so the programmer can see (and be told) that a sequence of instructions fulfills a specific logical purpose.  This is also a way to have the convenience of inline variables without inline variables and local types and constants that is only applicable to a very short sequence of code.

Inline/anonymous functions/procedures _almost_ fulfill the characteristics of the construct I alluded to above but, very unfortunately they require a "()" to cause them to be executed (because they weren't really designed to be a grouping construct.)

10,000 line functions/procedures are a whole lot better, easier to understand and maintain than functions/procedures broken into hundreds of scattered pieces that have to be mentally re-assembled to see what the function does and how it does it.

IOW, today's commonly accepted and supported programming philosophy is: let's make sure to make the big picture as hard to mentally create as possible.

Why not address the issue at hand - the need to leave the current function context to look up a specific sub-function - to the editor. What I would like to have is a means to let the editor blend in the sub-function source without actually changing the function source - kind of an extended folding. If something like that would be available it would help me tremendously.

Cheers,
MathMan

 

TinyPortal © 2005-2018