Python's Iterators are a Bad Implementation of Laziness
sandersn.com
sandersn.com
generator = (x * x for x in xrange(10)) # A generator
my_list = list(generator) # Build a list, reusable
That's the Python way of doing things: trust the programmer, & let him make mistakes.Also how does C# implement this kind of "smart" laziness? It seems to me there will be other more obscure problems: what happen if the Enumerable have side effect? Is the side effect repeated or is the result cached? Python's generator are implementation is simple: there's no tricks, magic, & layers of abstraction.
Like in your Python example, one is expected to put the results of an enumerable into a real data structure if you need it more than once and you suspect that the enumeration might be expensive or involve side effects.
Both ways "let the programmer make mistakes" -- in C#, you might accidentally enumerate something more than once without realizing it, which may be inefficient or cause subtle bugs. In Python, you might accidentally screw something up the way the fellow writing the post above did. One might think that the C# behavior is overly clever, and it's best to fail fast, but it's convenient in practice when you know that cycling again won't cause problems (the majority of the time.) I'm not sure which I prefer. One thing to note is that having static types a la C# does make it clear at all points whether you are dealing with an enumerable or a real list.
It seems that allowing lists and generators to be used in identical ways generally makes things easier to create and easier to use, but the differences in the corner cases suddenly mean you have to know about the detail. Then it becomes hard.
The problem isn't that it's easy to fix. We all know how to fix these sorts of things. The problem is that details you need to know are being hidden, because you shouldn't need to know about them.
When I write a routine should I always "assert" that its parameter is a list and not a generator? Do I have to write the code to use a slower technique just in case the parameter is a generator? Do I "ungenerate" the generator out into a list?
That can't be right ...
I agree. I would paraphrase this as: Leaky abstractions cause bugs, and unfortunately all abstractions turn out to leak at some point (I think someone else said it this way before, but I can't remember who right now). EDIT: Found it - http://www.joelonsoftware.com/articles/LeakyAbstractions.htm...
This doesn't mean we shouldn't use abstractions of course - it just means each abstraction comes with a cost.
That the caller can be trusted to use the proper type in arguments to a function a basic assumption in Python. The decision about how to handle type mismatch errors is up to you. You can handle generators any way you'd like, depending on your needs. There are other modern languages that don't make the same assumptions Python does.
So, overall, I don't really see the point of this article, besides someone who should go and use some defensive language.
And if you need to repeat the generator, use itertools.cycle.
You're using "laziness" in a language with mutation. What do you expect?
This is just silly. You can't use a generator twice, just like you can't keep reading from a file. When you've reached EOF, you're done. What does this guy expect, code that magically determines that when you ask for 'next()', you mean 'restart_at_the_beginning()'?
I also fail to see what this has to do with laziness: the generator lazily generates it's elements. However, at some points it's still done generating elements. That's not a failure of laziness or whatever, it's a failure of using the generator.
Doesn't seem quite so silly to me.
map(fn, g)
should have a side-effect on g.I'm not saying I agree with the guy, but to call it magic is misleading.
Yes you can't use a generator twice, that's the problem. If you use an imperative next() you have this problem. It can be solved by using lazy lists instead of generators.