
Notes on Haskell: What's Wrong with the For Loop - eaxitect
http://notes-on-haskell.blogspot.com/2007/02/whats-wrong-with-for-loop.html
======
colanderman
That word, closure. You keep using it, but I do not think it means what you
think it means.

A closure is an runtime structure used to implement static scope of locally-
defined first-class functions. Closures allow locally-defined functions to
"remember" variable bindings in their enclosing scope.

Beside that you can implement this feature WITHOUT closures (e.g. source
rewriting), none of the examples presented actually require static scope!!

It boggles my mind that programmers can have such strong opinions on language
features _without actually knowing what they're called_.

~~~
ScottBurson
Yes, this has been one of my pet peeves for a while too. Everybody: a closure,
as the parent says, is an _implementation_ construct. It is not something you
can find in your source code. The syntactic construct -- the thing you write
in your code -- is called a _lambda expression_. Not a "lambda function", and
not a "closure"!

Lambda expressions are to closures as `new' expressions are to instances: a
lambda expression evaluates to a closure, and a `new' expression evaluates to
an instance. Since any expression can be evaluated multiple times, in general
the relationship is one-to-many: a lambda expression can be evaluated N times
to produce N closures, just as a `new' expression can be evaluated N times to
produce N instances.

(Instances and closures are closely related: an instance is a piece of state
with several operations that can be invoked on it, while a closure is a piece
of state with one operation that can be invoked on it.)

~~~
Confusion
Your insistence on a specific meaning for the words ignores how words are
actually used: overloaded based on context. If I call these things 'closures',
you understand I'm talking about the 'lambda expressions' and not the
implementation construct.

    
    
      class Foo
      end
    

There, a class object. Oh sorry, an object is an implementation construct;
what you have there is an _expression_ that produces an object... <\- that is
unhelpful semantic squabbling. It's highly inconvenient to use such indirect
language. This is the view of a class object that I have most of the time and
a 'lambda expression' is the view I have of a closure most of the time.

If I see the Eiffel tower, I say: look, the Eiffel tower. Not: look, an image
of the Eiffel tower, with some details obscured by clouds and smoke and
without considering any of the construction details and history that are an
essential part of the Eiffel tower. For all practical purposes of
communication, it's the Eiffel tower.

~~~
gsg
That's not a fair example. There are languages that have lambda expressions
but not the lexical scope that requires closures, whereas there are no
languages that have class definition expressions but not classes.

Given there is a meaningful distinction to be made, insistence on accurate
terminology becomes a lot more reasonable.

------
jerf
"But it does highlight the key failing of for loops: they conflate three
separate kinds of operations -- filtering, reduction and transformation."

There's actually four kinds of operations: filtering, reduction,
transformation, and _good Lord man what the hell are you doing with the loop
index? did it just go negative? It did! Why? And it still works‽_ , which is
actually quite hard to simulate with functional programming. Fortunately.

~~~
rtaycher
I can top that mine went up to 17 and then jumped to 200 for no obvious
reason(some sort of memory bug where memory in a different part of the program
overlapped with the loop variable).

~~~
dkersten
Did i still work? A bug isn't a fundamental kind of loop operation.

------
Stormbringer
Colour me stupid, but in the article it gives this code as having some
horrible hard to find bug:

    
    
      String s = "";
      for (int i = 0; i < args.length; i++) {  
        s += array[i];
      }
    

Now, as soon as I looked at that I immediately thought "well, they're not
putting anything in between the params when they concatenate them", e.g. I
would expect to see:

    
    
        s += array[i] + "\t";
    

(or a comma or newline instead of a tab)

My next thought was "what happens if there are no args?" E.g. args.length
returns 0, and they try to access the first element in the list (due to
heritage from c pointers we start counting at 0) which is the 'zeroth' one,
which doesn't exist... it should throw an array out of bounds exception.

\-----

The reason I ask, is that based on the comments, the people reading the blog
think the problem is in the operator overloading. Are they expecting this
iteration through the args list to _sum_ the args, and then assign the sum to
the string?

If so, then I don't see how the bug could remain undetected:

 _"it could take weeks to get a bug report, and weeks more to get a fix
ready"_

Whereas in the unlikey event that I'm right, I _still_ don't see how it would
be hard to fix?

What am I missing?

~~~
jawn
There is no check that array[i] actually exists. If array[] has 2 items but
args[] has 5, you will overflow the array.

~~~
feral
I've been trying to figure out what he was getting at, as well. The original
article, by Elliotte, seems to have updated the code snippet to:

' String s = ""; for (int i = 0; i < args.length; i++) { s += args[i]; } '

I deduce from this that the error was in the copy/paste. The line that was
supposed to be changed to 's+= args[i]' was accidentally left as 's+=
array[i]'

As such, its a simple failure to replace all instances of 'array' in the
pasted code, rather than anything to do with 'operator overloading', or even
to do with checking if 'array[i]' was out of bounds - there is simply no
variable 'array' supposed to be in that context.

I wish Turoff had been less obtuse about it, especially as the original
Elliotte post seems to have been silently updated.

~~~
Stormbringer
In the interest of being fair, it is (by internet standards) quite an old blog
post, which was probably a bit of a toss off effort in the first place.

If 'the bug' is just a copy/paste problem of a variable that doesn't exist in
that scope, then compilation will fail at that point, so again, I don't see
how that could be the one that Turoff said would survive for weeks in the
wild.

~~~
kscaldef
But, if the bug is a copy/paste of a variable that _does_ exist in that scope,
he's exactly correct that it could survive for weeks and be very difficult to
actually track down (because of the sort of blindness to details that happens
when you read "idioms" like this by visual pattern matching rather than
actually reading the code).

------
Dn_Ab
I feel this post is more an argument for first class functions than closures.
It is true that much of the usefulness of first class functions is lost
without closures, so having useful first class function almost necessitates
(though does not guarantee) closures. But in none of the examples is the key
attribute of closing over free variables emphasized, which is the powerful and
_dangerous_ part.

A simple example showing the usefulness of inner functions might have been
more pertinent to the term closure IMO.

~~~
Stormbringer
It is a pretty convincing argument for adding transformation (do this thing to
every item in the list), and filtering (give me a sub list of everything that
meets this condition)

It is a much weaker argument for adding summation, since you'd have to get
into the down and dirty details of whatever operation your summation
represents, e.g. is it associative? You can see how it get ugly fast in the
discussion about the differences between foldl and foldr (presumably fold-left
and fold-right respectively)

\----

Now, Java had filtering in one of the libraries, I remember using filtering
with folder operations many moons ago, so it's not like you can't roll your
own. Likewise with a "do this thing to everything in the list" operation.

For loops have a use, specifically when you want to do things in a particular
order. Reading lines from a file for instance you might want the lines you're
putting into your data structure to be in the same order as they are in the
file (as a specific counter-example, I once wrote a 'unique line' app where it
didn't care about the ordering, of course if I'd been on unix there would have
been some command line tool to do it for me, but it was fun and fast to write
and hand tuning the grep would have been a PITA)

BUT, there are a lot of tiems I've used for loops where the sequential nature
of the loop is irrelevant, and here I could see someone making an argument
that the for loop is clumsy and random (from the article they object because
it uses too many tokens! The horror! :D )

I could also see someone making the counter-argument that what that actually
means is that the for loop is _powerful_ (never mind that we have no objective
standard for what that means. Perhaps 'powerful' in the sense of 'easy to blow
your own foot off with' would fit here :D )

------
joe_the_user
OK,

Let's imagine there are two kinds of language/program bugs.

1) A given expression does not mean what you think it means. 2) A given series
of expression all mean what you think they mean but the combination of them
isn't what you think it should be.

I would submit that bug type #2 is the main sort of bug that's going to bite
you and bug type #1 is relatively benign (if occasionally infuriating).
Especially, even in language with obscure syntax and hairy ambiguity, you can
pepper your code with asserts and track down your problem reasonably quickly.

I have been assure that functional programming does help with bug #2. I would
love some detailed blog post akin to this which says how.

This article seems to deal entirely with bug type #1. Show me how functional
programming deals with bug type #2.

------
mapleoin
Heh, all of these points could easily be applied to Python as well. All the
concepts are there in addition to the for loop. It's nice how you can do a lot
of _functional_ programming in Python with these simple tools (map, filter and
reduce (Haskell's foldl).

I wonder why there aren't more people advocating the use of these functions in
Python programs instead of for loops.

~~~
Dn_Ab
Java's younger cousin C# also has this with Select, Where and Aggregate. But
sometimes the foreach and even the for loop actually makes more sense, in
particular when dealing with very imperative algorithms or are not acting over
a (full) sequence at all.

I'm usually only reading and not writing python but I think list
comprehensions are more idiomatic. Based on some comments I've seen here I
also kind of get the impression comprhensions are abused.

~~~
Fargren
Well, if you don't want to work with the whole sequence, you can filter it.
But yeah, in those cases and some others, the for loop may be more idiomatic
and better performing.

~~~
Dn_Ab
Yes I'm talking about being able to stop early for one example. Or if the
thing you are dealing with is not actually a sequence for another.

~~~
darklajid
The former can probably be solved for lots of cases by TakeWhile(), which
evaluates before consuming more elements and could be used to replace "abort
the loop" patterns.

The latter is a different thing..

1: <http://msdn.microsoft.com/en-us/library/bb534804.aspx>

------
scotty79
You can execute loop in your head just by reading it line by line. Token by
token. With other solutions you need to know other things to determine what
goes inside and believe it is actually what you want it to be. Loops ar flat,
explicit and versatile. I think that is the reason behind their popularity.

~~~
Symmetry
If you're familiar with foldr or its ilk you can execute them in your head
token by token too, to the same extent you can in a for loop. In the case of a
for loop you have to be familiar with the syntax and semantics of "for" in
your language so you know that "for(i = 0; i<n; i++)" is different from "for(i
= 0; i++; i < n)" and likewise you have to be familiar with the syntax and
semantics of any functional construct you use.

~~~
scotty79
To understand loop you just need to know in which order to execute its parts.
And you see how it reduces, maps an filters your data. You don't need to keep
stack in your head. To understand how functional constructs work you have to
know a lot more because they are specialised and it's not that easy to track
what they exactly do because of recursion. You have to know them and trust
them. With loop all internal logic is linear and in plain sight.

~~~
Symmetry
Its certainly true that to understand a fold at the abstraction level that C
uses you have to know more, but that isn't the same as you having to know it
in order to program effectively. The point of abstraction is that you don't
need to know the fine details of whats happening under the hood. You might
talk about how, in a C program you can watch what happens to your loop
variable as you execute, but really "variables" are just abstractions over
what the machine is really doing. If you were to look at the assembly spit out
by your compiler that loop variable might be in a register or on the stack or
in some combination thereof, and if your compiler does loop unrolling it might
have different values at the same time even in the assembly. And once you get
into the reorder buffer or a modern CPU I guarantee you that what actually
happens will bear very little resemblance to what you would naively think if
you just looked at the C code.

So you can execute a loop C-token by C-token and you can execute a fold
Haskel-token by Haskel-token, but both are abstractions high above whats
happening at the machine level and I don't see why we should prefer one over
the other.

------
mathgladiator
The real reason: it's much work to prove loop invariants that use structural
induction.

