Lambda the Ultimate

inactiveTopic Generators in Python, Icon and Scheme
started 10/4/2001; 5:05:44 AM - last post 10/12/2001; 4:14:25 PM
Ehud Lamm - Generators in Python, Icon and Scheme  blueArrow
10/4/2001; 5:05:44 AM (reads: 1933, responses: 10)
Generators in Python, Icon and Scheme
Oleg's comp.lang.scheme response to two recent discussions here about generators in Python, and programming language expressiveness.

This basically shows how easy it is to use continuations to create generators. This is, of course, not news: a classic example of continuations shows how to use them to solve the same-fringe problem, which is solved using recursive generators, of the sort shown here. Oleg's discussion and clear presentation are useful if you are not familiar with the concept of continuations, or are baffled about using them properly.

Notice, that the my point in the discussion was precisely that generators are less powerful than continuations, and thus can be easier to understand by people unfamiliar with "exotic" control structures.

Continuations are an extremely powerful notion, and should really be mastered by anyone keen on understanding programming languages (you may want to read EOPL2 or LiSP).


Posted to functional by Ehud Lamm on 10/4/01; 5:10:51 AM

Noel Welsh - Re: Generators in Python, Icon and Scheme  blueArrow
10/4/2001; 6:06:32 AM (reads: 1970, responses: 1)
In the same Usenet thread is the reference to the Henry Baker paper showing coroutines without continuations. The URL is http://linux.rice.edu/~rahul/hbaker/Iterator.html if you don't want to dig it out of Google.

Generators certainly can be easier to understand than continuations, but I do think the implementation in Python is taking the wrong direction. I think they're better off getting real closures and then implementing generators via closures (perhaps with some sugar) than implementing a separate generator type which requires special handling in the runtime. After all, speed is not an essential concern in Python.

This is also how I'd implement it in Lisp - a standard library built on closures or continuations with standard sugar to express the common patterns. Except in Lisp sugar is first class (kinda, but you know what I mean I hope!)

Ehud Lamm - Re: Generators in Python, Icon and Scheme  blueArrow
10/4/2001; 6:27:15 AM (reads: 2044, responses: 0)
I don't know enough Python to judge this or any other Python design decision. Was the rationale for this decison published anywhere?

Ehud Lamm - Re: Generators in Python, Icon and Scheme  blueArrow
10/4/2001; 6:31:58 AM (reads: 1969, responses: 0)
Henry Baker's paper is an old time friend. I quite agree with his conclusion about the control structures of most OOPL. As I repeatedly said, my point wasn't to say that generators are more powerful than other constructs.

Isn't the closure passing technique inefficient?

Noel Welsh - Re: Generators in Python, Icon and Scheme  blueArrow
10/4/2001; 7:46:57 AM (reads: 1977, responses: 1)
The design rationale is at http://python.sourceforge.net/peps/pep-0255.html. It doesn't appear that closures ala Baker were considered. They would be more inefficient then the stack frame technique the Python implementation uses as it requires a new closure be created every time you yield (off the top of my head). I don't think this would be a major problem though - Python isn't exactly a speed demon as it stands.

Ehud Lamm - Re: Generators in Python, Icon and Scheme  blueArrow
10/5/2001; 8:34:07 AM (reads: 2002, responses: 0)
Thanks. I added the PEPs to the design docs page.

Oleg - Re: Generators in Python, Icon and Scheme  blueArrow
10/8/2001; 5:12:55 PM (reads: 1902, responses: 0)
Henry Baker is rather unfair to C++ in his article. He writes,

"However, recent experience with C and C++ has finally revealed the source of my cognitive dissonance--these languages lack the ability to write and use simple mapping functions like Lisp's mapcar! In particular, while C and C++ offer the ability to pass a function as an argument to another function (i.e., a "funarg"), the functions being passed cannot contain references to variables of intermediate scope (i.e., the functions are not function closures), and therefore they are essentially useless for most mapping processes."

That is not the case. It is easy and straightforward to apply a function -- moreover, a closure -- to all or particular elements of a collection. It was easy in C++ version 2, it is especially easy now, with templated functions. Many STL algorithms (e.g., find, search, etc.) take a predicate, which is often a closure. A LinAlg package http://pobox.com/~oleg/ftp/LinAlg.README.txt has allowed you to write

 void foo(Matrix& m, Matrix& m1)
 {
  class ApplyFunction : public ElementWiseAction
  {
    double (*func)(const double x);
    void  operator () (REAL& element) { element = func(element); }
    public: ApplyFunction(double (*_func)(const double x)) : func(_func) {}
  };
  to_every(m).apply(ApplyFunction(sin));
  to_every(m1).apply(ApplyFunction(cos));
 }
for years (since 1992, to be precise). The LinAlg package also offers something like generators. For example,
     to_every(MatrixDiag(m)) += 1;
increments the diagonal of m by 1.
     to_every(MatrixRow(m,1)) *= of_every(ConstMatrixColumn(m1,2));

multiplies every element of the first row of m by the corresponding element of the second column of m1. The latter operations are highly efficient. LinAlg also offers more conventional "iterators" (various matrix streams: row streams, column streams, block streams). LinAlg even supports lazy matrices.

Ehud Lamm - Re: Generators in Python, Icon and Scheme  blueArrow
10/9/2001; 12:00:52 PM (reads: 1889, responses: 0)
This kind of iterators are not realy problematic in any language that allows passing routines as parameters (I have similar Ada code, for example).

Problems begin when what you want is an active iterator (some way to control when a new element is returned). The conventional OOP solution is to have some kind of Iterator object, which essentialy stores the position inside the collection. This, though cumbersome, is doable. But using this approach on more complicated data structure (i.e., trees) can be quite difficult. Without solving this issue you are stuck, and can't implement recursive traversal algorithms.

A less important problem is, of course, the lack of anonymous functions, which forces you to declare countless functions (your example overlooked this, by using sin and cos). For example simply incerementing each element in a collection, will force you to write an inc function.

<aside>Consider: In Ada/C you must define new routines; in Scheme you have anonymous functions; in Haskell/J you can specialize existing routines (1+) "fixes" one of the arguments of +. </aside>

Ehud Lamm - Re: Generators in Python, Icon and Scheme  blueArrow
10/9/2001; 12:03:50 PM (reads: 1889, responses: 0)
I know you can mimic Closures using objects, and use this to solve some of the problems I mentioned above, but in most OOPL this is ugly, and may problematic in some cases.

Oleg - Re: Generators in Python, Icon and Scheme  blueArrow
10/9/2001; 5:38:36 PM (reads: 1888, responses: 0)
I admit a lousy example. This one is better:
Matrix m(-1,rsize-2,2,csize+1);
struct filler : public ElementWiseAction {
double counter; const double incr;
void operator () (REAL& element)
	        { element = counter; counter += incr; }
filler(const double _init_val,const double _incr) :
counter(_init_val), incr(_incr) {}
};
to_every(m).apply(filler(pattern,incr));
assert( of_every(m).max_abs() == 
pattern + incr*(m.q_no_elems()-1) );

 

A less important problem is, of course, the lack of anonymous functions, which forces you to declare countless functions (your example overlooked this, by using sin and cos). For example simply incerementing each element in a collection, will force you to write an inc function.

Not necessarily. Consider the following snippet:

       MakeTestFunction("x^3 - 2*x - 5, from the Forsythe book",
         Lambda((const double x),double,
     	   return (sqr(x)-2)*x - 5))().
         run(2.0,3.0,2.0945514815);
This and the previous example are taken from LinAlg, and from

http://pobox.com/~oleg/ftp/c++-digest/Functional-Cpp.html http://pobox.com/~oleg/ftp/c++-digest/Lambda-CPP-more.html

This is only way way of introducing lambda abstractions into C++.

In Haskell/J you can specialize existing routines (1+) "fixes" one of the arguments of +

You can do similar things in C++. STL has a whole bunch of adapters -- such as bind1st, bind2nd. So, for example, you can write

	find_first_of(begin,end,bind2nd(less,5));
to find the first element of any collection that is less than 5.

Brian McNamara and Yannis Smaragdakis have been developing FC++, http://www.cc.gatech.edu/~yannis/fc++/. They have managed to implement most of the Haskell prelude, in C++.

Neither this nor previous message attempted to make any value judgement.

Ehud Lamm - Re: Generators in Python, Icon and Scheme  blueArrow
10/12/2001; 4:14:25 PM (reads: 1867, responses: 0)
Neither this nor previous message attempted to make any value judgement


We mentioned FC++ and other similar libraries in the past. The technqiues are nice to know about, but I think the jury is still out on their practical usefulness.

The STL did do a great job by introducing function objects etc.

I might mention that you can do most of this in other OOP languages, but you may encounter interesting difficulties.

In Ada for example, you have to wrestle with type scoping rules that are meant to prevent memory leacks. This can be quite bothersome. A less important issue is that yuo can't overload the function calling operator ().

Still, one has to remember that all these things are awkward in languages that don't allow nesting of routines. At least Ada allows you to do that (as oppsoed to C).


Now what about some value judgements?