scc@cl.cam.ac.uk (Stephen Crawley) (03/19/88)
In article <1139@PT.CS.CMU.EDU> edw@IUS1.CS.CMU.EDU (Eddie Wyatt) writes: In an earlier article, someone else writes: >> [... Clu iterators are coroutines] No they are not. Most of the things that you can do with coroutines you can't do with iterators. >But do you need coroutines to implimented generlized iteration. >I think not. Spot on. You CAN implement iterators with coroutines, but in practice they are implemented more cheaply by turning the loop body into a procedure like thing that is passed to the iterator as a hidden parameter. More details on the implementation of CLU iterators can be found in DGS Note 124 "VAX CLU Implementation Notes" by Sharon E. Perl 29/11/84 Laboratory for Computer Science, Massachusetts Institute of Technology > [...] >Example: > > void iterate_inorder(root,func) > TREE *root; > void (*func)(); > { > if (root != NULLPTR(TREE)) > { > iterate_inorder(root->left,func); > func(root->val); > iterate_inorder(root->right,func); > } > } > > I do see one problem though, which is dealing with variable > referenced inside the loop. They would have to become > global. Comments as to why this method might not work in general? Apart from being a disgusting hack ... making your "local" variables into globals will only work if the pseudo-iterator is NEVER called recursively. To get it to work recursively, there are two approaches. 1) Declare the relevant local variables as a structure, and pass a pointer to the struct as an extra parameter to the iter procedure and hence to the "loop body" procedure. This involves casting / loopholing of the pointer, or a generic type parameter in a high level programming language. 2) Declare the loop body procedure in the same (non-global) scope as the local variables for the loop. The loop locals are then available to the loop body procedure as non-locals. This assumes that your language allows this ... C, BCPL, Pascal, Modula-*, Ada etc don't, but Algol-68 and Mesa do, as do any languages which support proper procedure closures. Here's an example of 2) ... P: proc (...) = x, y: FooType loopbody: proc (elem: ElemType) = { ... x := list_elem.foo list_elem.foo := y ... } (* body of P *) ... iterate_over(list, loopbody) ... } -- Steve
jejones@mcrware.UUCP (James Jones) (03/20/88)
There's one thing that bugs me about CLU iterators, which on the whole are pretty neat: if I have two things I want to iterate through in lockstep, how do I do it? (That's a non-rhetorical question--it would be great to hear about a way to do it.) James Jones
dana@gmu90x.UUCP (J Dana Eckart) (03/21/88)
In article <616@mcrware.UUCP> jejones@mcrware.UUCP (James Jones) writes: >There's one thing that bugs me about CLU iterators, which on the whole are >pretty neat: if I have two things I want to iterate through in lockstep, >how do I do it? > James Jones I have been concerned about this same problem. I wrote a short paper about this that appeared in SIGPLAN Notices in April 1987. I would be curious to know what you (and the rest of the net) think of it. I have submitted a more recent and indepth paper on the subject to a conference that more clearly addresses some of the "problems" with the proposal that appears in the SIGPLAN Notices paper. If there is sufficient interest I might consider posting the "refined" proposal. J Dana Eckart UUCP: ...!(gatech | pyrdc)!gmu90x!dana ARPA: dana@gmu90x.gmu.edu SNAIL: P.O. Box 236/Fairfax, VA 22030-0236
peter@athena.mit.edu (Peter J Desnoyers) (03/24/88)
[]
Since we've spent a lot of time explaining how you can iterate through
multi-element data objects in C or similar languages by using a
'traversing function' that applies a function to each element, I might
as well point out that you can (even though I cringe at the thought)
write a 'real' iterator in C. Contrast the methods used in the
following code fragments:
Iterators:
/* calculate n, sum x, sum x^^2 */
for (leaf = iter( tree, &s); leaf != NULL; leaf = iter( tree, &s)) {
sum += leaf->size;
sum_squared += leaf->size * leaf->size;
n++; }
/* yield successive elements from a tree */
iter( tree_node * t, stack ** s) {
tree_node * tmp;
if (* s == NULL) {
* s = malloc( lots-o-space);
push( *s, t); }
if (* s is empty) {
free( *s);
* s = NULL;
return (NULL); }
for (tmp = pop( *s); !tmp->leaf; tmp = tmp->left)
push( *s, tmp->right);
return( tmp);
}
When done with 'traversing functions' [Is there a proper name for
these things? Please let me know if there is.] the code looks like
this:
/* define function to be applied */
f( tree_node * t, int * sum, int * sum_squared, int * n) {
* sum += t->size; ...}
/* apply it */
iter( tree, f, &sum, &sum_squared, &n);
/* traverser function: */
iter( tree, function, varargs stuff...) {
if (tree->leaf)
(*function)(leaf, varargs stuff...);
else {
iter( tree->right, function, ...);
iter( tree->left, function, ...); } }
I'm not sure if this proves anything - I think the first makes more
sense, especially if I don't have to write the iterator and it runs on
a fast machine :-).
Peter Desnoyers
sommar@enea.se (Erland Sommarskog) (03/25/88)
Stephen Crawley (scc@cl.cam.ac.uk) writes: >2) Declare the loop body procedure in the same (non-global) scope as > the local variables for the loop. The loop locals are then available > to the loop body procedure as non-locals. This assumes that your > language allows this ... C, BCPL, Pascal, Modula-*, Ada etc don't, > but Algol-68 and Mesa do, as do any languages which support proper > procedure closures. > <Example deleted for brevity> I'm not entirely sure what you mean, so my apologies if my objection isn't relevant. But looking at your example, it seems something that is straghtforward to do in Ada. I even did it not long ago. And since everyone is posting their favourite C iterators, you get some outlines of it in Ada too. First I have a generic package for binary trees. The package includes generic procedures for traversing the tree in different directions. This looks like: Generic Type Data_type is limited private; With procedure Assign(A : in out Data_type; B : in Data_type); With function "<"(A, B : Data_type) return boolean is <>; With function ">"(A, B : Data_type) return boolean is <>; -- We need the assignment and relations since we don't know anything -- about what the user want to strore in the tree. Package binary_trees is Type Tree_type is private; Type Node_type is private; Null_node : constant Node_type; .... Generic With Procedure Treat(Node : in Node_type; Data : in out Data_type); Procedure Traverse_forward(Tree : Tree_type); .... End Binary_trees; When I want to traverse the tree, I just instantiate the traversion procedure with a local one: Package Tree_handler is new Binary_trees(Data_ref); Use Tree_handler; ... Function Find_node(Criterion : Some_type) return Node_type is -- Traverses the (globally declared) tree and returns the first -- node that matches the criterion. Begin Declare Sought_node : Node_type; Found : exception; Procedure Check_this_one(Node : in Node_type; Data : in out Data_ref) is Begin If Data.Test_item = Criterion then Sought_node := Node; Raise Found; End if; End Check_this_one; Procedure Search is new Traverse_forward(Check_this_one); Begin Search(Global_tree); Exception when found => Return Sought_node; End; End Find_node; Now, isn't this very similar to the example Stephen posted? (There are probably some people objecting strongly against the use of the exception here. And I may well agree it's brute force. I included it just to show that there is a way out, ever the author of the iterator forgot to put in a stop condition.) -- Erland Sommarskog ENEA Data, Stockholm sommar@enea.UUCP "Si tu crois l'amour tabou... Regarde bien, les yeux d'un fou!!!" -- Ange
scc@cl.cam.ac.uk (Stephen Crawley) (03/26/88)
In article <616@mcrware.UUCP> jejones@mcrware.UUCP (James Jones) writes: >There's one thing that bugs me about CLU iterators, which on the whole are >pretty neat: if I have two things I want to iterate through in lockstep, >how do I do it? (That's a non-rhetorical question--it would be great to >hear about a way to do it.) > > James Jones If I understand your problem correctly, there's a simple answer. In CLU, an iterator may be declared to yield a tuple each time it iterates. So you can iterate over 2 lists (say) in lock-step as follows. lock_step = iter (list1, list2: my_l_type) yields (my_v_type, my_v_type) my_nil: my_l_type := my_l_type$get_nil () % erm ... while list1 ~= my_nil & list2 ~= my_nil & yield (list1.value, list2.value) list1 := list1.next list2 := list2.next end end lock_step [The mussing around with my_nil is for real. There is no universal nil value in Clu, so you have to somehow construct one for each recursive abstract data type. It is usually has to be done using a oneof type ... sigh] And just for the hell of it, here's a generic 2 list iterator!! lock_step2 = iter [lt, t: type] (list1, list2: lt) yields (t, t) where lt has is_empty: proctype (lt) returns (bool), next: proctype (lt) returns (lt), value: proctype (lt) returns (t) while ~lt$is_empty (list1) & ~lt$is_empty (list2) do yield (lt$value (list1), lt$value (list2)) list1 := lt$list1.next list2 := lt$list2.next end end lock_step2 It strikes me that the above solution is too obvious. Perhaps James had a more tricky case in mind? -- Steve Newsgroups: comp.lang.misc Subject: Re: iterators (was Re: From Modula to Oberon) References: <2827@enea.se> <1557@pasteur.Berkeley.Edu> <3764@bloom-beacon.MIT.EDU> <3864@bloom-beacon.MIT.EDU> <616@mcrware.UUCP> From: scc@cl.cam.ac.uk (Stephen Crawley) Reply-To: scc@cl.cam.ac.uk (Stephen Crawley) Organization: U of Cambridge Comp Lab, UK Keywords: lockstep iteration In article <616@mcrware.UUCP> jejones@mcrware.UUCP (James Jones) writes: >There's one thing that bugs me about CLU iterators, which on the whole are >pretty neat: if I have two things I want to iterate through in lockstep, >how do I do it? (That's a non-rhetorical question--it would be great to >hear about a way to do it.) > > James Jones If I understand your problem correctly, there's a simple answer. In CLU, an iterator may be declared to yield a tuple each time it iterates. So you can iterate over 2 lists (say) in lock-step as follows. lock_step = iter (list1, list2: my_l_type) yields (my_v_type, my_v_type) my_nil: my_l_type := my_l_type$get_nil () % erm ... while list1 ~= my_nil & list2 ~= my_nil & yield (list1.value, list2.value) list1 := list1.next list2 := list2.next end end lock_step [The mussing around with my_nil is for real. There is no universal nil value in Clu, so you have to somehow construct one for each recursive abstract data type. It is usually has to be done using a oneof type ... sigh] And just for the hell of it, here's a generic 2 list iterator!! lock_step2 = iter [lt, t: type] (list1, list2: lt) yields (t, t) where lt has is_empty: proctype (lt) returns (bool), next: proctype (lt) returns (lt), value: proctype (lt) returns (t) while ~lt$is_empty (list1) & ~lt$is_empty (list2) do yield (lt$value (list1), lt$value (list2)) list1 := lt$list1.next list2 := lt$list2.next end end lock_step2 It strikes me that the above solution is too obvious. Perhaps James had a more tricky case in mind? -- Steve Newsgroups: comp.lang.misc Subject: Re: iterators (was Re: From Modula to Oberon) References: <2827@enea.se> <1557@pasteur.Berkeley.Edu> <3764@bloom-beacon.MIT.EDU> <3864@bloom-beacon.MIT.EDU> <616@mcrware.UUCP> From: scc@cl.cam.ac.uk (Stephen Crawley) Reply-To: scc@cl.cam.ac.uk (Stephen Crawley) Organization: U of Cambridge Comp Lab, UK Keywords: lockstep iteration In article <616@mcrware.UUCP> jejones@mcrware.UUCP (James Jones) writes: >There's one thing that bugs me about CLU iterators, which on the whole are >pretty neat: if I have two things I want to iterate through in lockstep, >how do I do it? (That's a non-rhetorical question--it would be great to >hear about a way to do it.) > > James Jones If I understand your problem correctly, there's a simple answer. In CLU, an iterator may be declared to yield a tuple each time it iterates. So you can iterate over 2 lists (say) in lock-step as follows. lock_step = iter (list1, list2: my_l_type) yields (my_v_type, my_v_type) my_nil: my_l_type := my_l_type$get_nil () % erm ... while list1 ~= my_nil & list2 ~= my_nil & yield (list1.value, list2.value) list1 := list1.next list2 := list2.next end end lock_step [The mussing around with my_nil is for real. There is no universal nil value in Clu, so you have to somehow construct one for each recursive abstract data type. It is usually has to be done using a oneof type ... sigh] And just for the hell of it, here's a generic 2 list iterator!! lock_step2 = iter [lt, t: type] (list1, list2: lt) yields (t, t) where lt has is_empty: proctype (lt) returns (bool), next: proctype (lt) returns (lt), value: proctype (lt) returns (t) while ~lt$is_empty (list1) & ~lt$is_empty (list2) do yield (lt$value (list1), lt$value (list2)) list1 := lt$list1.next list2 := lt$list2.next end end lock_step2 It strikes me that the above solution is too obvious. Perhaps James had a more tricky case in mind? -- Steve
scc@jenny (Stephen Crawley) (03/28/88)
In article <2913@enea.se> sommar@enea.UUCP(Erland Sommarskog) writes: >Stephen Crawley (scc@cl.cam.ac.uk) writes: >>2) Declare the loop body procedure in the same (non-global) scope as >> the local variables for the loop. The loop locals are then available >> to the loop body procedure as non-locals. This assumes that your >> language allows this ... C, BCPL, Pascal, Modula-*, Ada etc don't, >> but Algol-68 and Mesa do, as do any languages which support proper >> procedure closures. >> <Example deleted for brevity> > >I'm not entirely sure what you mean, so my apologies if my objection >isn't relevant. But looking at your example, it seems something that >is straghtforward to do in Ada. Arland's objection is both relevant and correct. Since Ada does not allow you to treat procedures as values, the mechanism I proposed (passing procedure as parameters to other procedures) does not work. However, Ada does allow you to use procedures as parameters to a generic package. As Erland pointed out, this allows you to construct iterators. [It is sad that Ada forces you to go to the trouble of constructing a generic package for a data structure you want to iterate over.] Perhaps I shouldn't make statements about languages I've never used ... >(There are probably some people objecting strongly against the use >of the exception here. And I may well agree it's brute force. >I included it just to show that there is a way out, ever the author >of the iterator forgot to put in a stop condition.) I'll excuse you! :-) End conditions on pseudo-iterators are frequently messy. Yet another reason for wanting REAL iterators in a language. [Sorry about the crap on the end of my last article. People around here have made it unnecessarily hard to post news ... and that was the result.] -- Steve
sommar@enea.se (Erland Sommarskog) (03/30/88)
Stephen Crawley (scc@cl.cam.ac.uk) writes: >However, Ada does allow you to use procedures as parameters to a generic >package. As Erland pointed out, this allows you to construct iterators. >[It is sad that Ada forces you to go to the trouble of constructing a > generic package for a data structure you want to iterate over.] Minor correction: A package like a tree handler should always be generic. But if Ada had had procedure parameters, we wouldn't have been forced to declare the iterator as a generic procedure. I don't really want to go into the debate on generic vs. "real" procedure parameters. I can imagine that the Ada designers felt procedure parameters as redundant aside with procedures as generic parameters. But I'm not really fond of declarations like Generic With procedure A(P1 : T; P2 : T2); Procedure B; Together with the instantiation this becomes unnecessarily verbose. The compiler and library overhead is also greater for generics. -- Erland Sommarskog ENEA Data, Stockholm sommar@enea.UUCP