steven@mcvax.uucp (Steven Pemberton) (07/28/86)
(A long time ago,) in article <149@opus.UUCP> rcd@opus.UUCP (Dick Dunn) wrote: > The worst of it is that <<nested procedures are seldom necessary>> IF a > language provides other hiding mechanisms. Simple as it is, C's rule for > limiting the scope of static objects to a compilation unit is entirely > adequate for this sort of hiding. Let me try and give a simplified description of a case where nested procedures are useful, i.e. functional, not just cosmetic. You are implementing an interpreter for a programming language. Among the simple types in this language are unbounded length integers. Among the compound types are sequences of any other type: so you can have sequences of integers, but also sequences of sequences of integers, and so on. Sequences are written e.g. {1; 2; 100000}, and there is a shorthand for ranges: {1..100000}. The implementation should be able to handle ranges where the lowerbound is very far from the upperbound (eg {1..10**1000}). Among the simple commands of the language is a write command that can write values of any type. Among the compound commands is a for-loop FOR name IN sequence : commands which executes 'commands' for each element of the sequence, e.g. FOR i IN {1..1000000}: WRITE i**2 Now you want to implement this language in a good modular fashion: you want to write a separate module to implement the values of the language, so that the interpreter proper can be written without reference to how the values are implemented. This will then allow you to try different representations for values, without constantly having to rewrite the interpreter. Now, how do you arrange it so that the interpreter can step through each element of a sequence without knowing how they are represented? In particular, suppose you decide to hold ranges internally as only the lower-bound and upper-bound? One possibility is to supply a routine in the values module that steps through each element of a supplied sequence, calling a supplied routine. In Pascal: procedure forEach(seq: value; procedure action(elem: value)); begin { For each element e of sequence seq, call action(e) } end; Now you can code the procedure to effect the write command as follows: procedure doWrite(v: value); var first: boolean; procedure writeElement(element: value); begin if not first then write('; '); first:=false; doWrite(element) end; begin if simple(v) then writeSimple(v) else begin first:=true; write('{'); forEach(v, writeElement); write('}') end end; Similarly, you can implement the for command as follows: procedure executeFor(name, expression, commands: parseTree); var loc: location; val: value; procedure loop(element: value); begin assign(element, {to:} loc); execute(commands) end; begin loc:=locate(name); val:=evaluate(expression); forEach(val, loop) end; Nested procedures give you the option to choose this method of implementation. Steven Pemberton, CWI, Amsterdam; steven@mcvax.uucp (Sorry I took so long to follow up. I was thinking about it.)
ludemann@ubc-cs.UUCP (Peter Ludemann) (07/30/86)
In article <7026@boring.mcvax.UUCP> steven@mcvax.uucp (Steven Pemberton) writes: > >Let me try and give a simplified description of a case where nested >procedures are useful, i.e. functional, not just cosmetic. > > ... > >One possibility is to supply a routine in the values module that steps >through each element of a supplied sequence, calling a supplied routine. >In Pascal: > > procedure forEach(seq: value; procedure action(elem: value)); > begin > { For each element e of sequence seq, call action(e) } > end; > >Now you can code the procedure to effect the write command as follows: > > procedure doWrite(v: value); > var first: boolean; > > procedure writeElement(element: value); > begin > if not first then write('; '); > first:=false; > doWrite(element) > end; > > begin > if simple(v) then writeSimple(v) > else > begin > first:=true; > write('{'); > forEach(v, writeElement); > write('}') > end > end; This is a good example of why nested procedures should NOT exist. The procedure writeElement has a side-effect: modifying the variable "first". Because this example is small, it is easily comprehended, but a larger example could be quite incomprehensible. I've written lots of these kinds of systems using a language with no nested procedures. There is a straightforward way of handling such cases: pass the control information explicitely to the "action" procedure. This requires an extra initialisation procedure: procedure forEach(seq: value; procedure actionInit(var control: bool); procedure action(var control: bool; elem: value)); begin var control: bool; actionInit(control); { For each element e of sequence seq, call action(control, e) } end; procedure writeInit(var first: bool); begin first := true; end; procedure writeElement(var first: bool; element: value); begin if not first then write('; '); first := false; doWrite(element) end procedure doWrite(v: value); begin if simple(v) then writeSimple(v) else begin write('{'); forEach(v, writeInit, writeElement); write('}') end end; Of course, this requires knowing the type of the "control" variable (it could be a fairly large record, containing perhaps a character count, an indentation level, or whatever). I have ideas on hiding this information, if anyone is interested.
steven@mcvax.uucp (Steven Pemberton) (08/04/86)
In article <337@ubc-cs.UUCP> ludemann@ubc-cs.UUCP (Peter Ludemann) writes: [ Example program from original posting ] > This is a good example of why nested procedures should NOT exist. > The procedure writeElement has a side-effect: modifying the variable > "first". Because this example is small, it is easily comprehended, > but a larger example could be quite incomprehensible. I'm unclear about this point. Procedures by definition have side-effects. Maybe you mean that it modifies a non-local variable, but that is another question altogether. Anyway, if the example is comprehensible, it stands. Any use of procedures could become incomprehensible, but that's no argument against procedures. > I've written lots of these kinds of systems using a language with no > nested procedures. There is a straightforward way of handling such > cases: pass the control information explicitely to the "action" > procedure. This requires an extra initialisation procedure: > > procedure forEach(seq: value; > procedure actionInit(var control: bool); > procedure action(var control: bool; elem: value)); > begin > var control: bool; > actionInit(control); > { For each element e of sequence seq, call action(control, e) } > end; It also requires a new version of forEach for every different type of action procedure you need to call. Even the example program requires two: one for writeElement which has a boolean parameter, and another for loop which has a 'location' parameter. Steven Pemberton, CWI, Amsterdam; steven@mcvax.uucp Lrf, vg'f ebg guvegrrarq!
cjl@iuvax.UUCP (08/13/86)
> It also requires a new version of forEach for every different type of action > procedure you need to call. Even the example program requires two: one for > writeElement which has a boolean parameter, and another for loop which has a > 'location' parameter. A procedure without procedure parameters can be asserted with pre- and post-conditions. The inclusion of procedure parameters makes it almost impossible to assert the pre- and post-conditions. In other words procedures with procedure parameters are just control macros, not solid abstraction. This may be the reason why Ada eliminates procedure parameters. In Ada, you can only have generic procedures with procedure parameters. You must instantiate generic procedures for each use of different procedure parameters. That enforces the abstraction and descent use of every procedure. If this represents a good software engineering principle, then it may be better to write two different versions of forEach for this example than to share codes with one forEach and non-local variable references. C.J.Lo Dept. of CIS, IUPUI UUCP : ...!iuvax!cjl ARPA : cjl@Indiana@CSNet-Relay
aglew@ccvaxa.UUCP (08/17/86)
> A procedure without procedure parameters can be asserted with >pre- and post-conditions. The inclusion of procedure parameters makes >it almost impossible to assert the pre- and post-conditions. In other words >procedures with procedure parameters are just control macros, not solid >abstraction. Bullshit. You can provide pre and post conditions for a procedure only because you are able to do so for each statement in the procedure. An invocation of a procedure parameter can have valid procedure parameters just as easily as any other parameter. These must be satisfied by all procedures that can be passed to that parameter. Unfortunately, satisfaction of these pre and post conditions is rarely checked automatically for procedure parameters, but then, neither is it done for complex data structures.