What's Wrong with the For Loop
notes-on-haskell.blogspot.com
notes-on-haskell.blogspot.com
Lambdas by them self are incredibly useful but a closure has a lot more benefits. Your API can return functions that manipulate values through references without exposing exposing the value and giving a lot of flexibility.
Personally, I consider higher-order functions and lambdas convincing enough to have them added to a language, but it takes more than that to convince a broad majority that does not have much exposure to functional programming. While showing how cool you can sum things up with Haskell is certainly nice, the degree of flexibility and usability that is added to APIs is much more convincing.
Edit: Had to write this in a hurry and now came back to make it more substantial.
String[] mp3s = new File("/home/drostie/Music").list(new FilenameFilter() {
public boolean accept(File dir, String name) {
return (name.length > 4) && (
name.substring(name.length - 4).toLowerCase() === ".mp3"
);
}
});
In Node.js this becomes: var mp3s = fs.readdirSync("/home/drostie/Music").filter(function (name) {
return name.slice(-4).toLowerCase() === ".mp3";
});
But as you said, this doesn't use the environment. The question is, when do you really need the environment?One answer is that it's usually important when that environment is changing and you're modifying that environment; then you have to worry, "Am I modifying the right value? Do I have the right value?" And that's a much more difficult case. MIT's Abelson-Sussman lectures are very clear when talking with Scheme about assignment. "Why do you care about assignment? Because sometimes you want new Date(), or Math.random(), or any number of other constructs which don't return the same thing when you call them twice in a row."
There are some simple use cases for this, like tracking how much work you're doing. In client-side JavaScript you might write something like this:
var el_text_area = document.getElementById("wordbox"),
el_charcount = document.getElementById("charcount"),
clock;
el_text_area.onblur = function () {
// stop updating when the user isn't typing.
if (clock !== undefined) {
clearInterval(clock);
}
};
el_text_area.onfocus = function () {
el_text_area.onblur(); //clear the clock if it somehow already exists.
clock = setInterval(function () {
el_charcount.value = el_text_area.value.length;
}, 50);
};
All of those external variables are being dynamically modified.The last case where I really used closures was as a debugging tool; I wrote a function which would take as input a function and 'wrap' it up for tracing its execution. Here it is in its full gory detail:
function log_call(fn) {
var count = 0;
return function () {
var out, name = fn.name + "#" + (count += 1);
function log(verb, value) {
console.log(name + " " + verb + " ", value);
}
log("<--", [].slice.call(arguments, 0));
try {
out = fn.apply(this, arguments);
log("-->", out);
return out;
} catch (e) {
log("--X", e);
throw e;
}
};
}
I use it by writing code like this: function fibs(n) {
return n <= 1 ? n : fibs(n - 2) + fibs(n - 1);
}
// fibs seems slow, let's see what's going on here
fibs = log_call(fibs);
//later in the code
fibs(22);
The above code is in fact horribly inefficient, and gets to fibs#57313 before it starts to construct the Fibonacci numbers. The debug trace shows all of this, if you run it.We can solve that by now defining a caching decorator:
function cached_onearg(fn) {
var cache = {};
return function (x) {
var out;
if (cache.hasOwnProperty(x)) {
return cache[x];
} else {
return (cache[x] = fn.call(this, x));
}
};
}
fibs = log_call(cached_onearg(function (n) {
return n <= 1 ? n : fibs(n - 2) + fibs(n - 1);
));
The logger now shows a much more reasonable 43 calls.> Listing 1. The simplest possible closure
> 3.times {puts "Inside the times method."}
that's not the simplest possible anything. it's magical syntactic sugar gone wild.
And I never, ever, use them to replace a for loop. Approaching closures from the "because they can replace loops in great ways!" is IMO pretty boneheaded.
Let's take something from iOS land, where blocks (Obj-C closures) make my life easier than it ever has been before. If you want to hide an object, then destroy it once its hidden:
[UIView animateWithDuration:0.2 animations:^{ myObj.position.x += 10.0; } completion:^(BOOL finished) { [myObj destroy]; }];
How would you implement this without closures? How much of a clusterfuck will it be? Will you have to install something that will poll for the animation to be finished? Will you write a big "animation manager" class that will throw around a bunch of function pointers and callbacks and marshall all of your animations for you? Will you have to declare a load of protocols and support classes just to implement said callbacks?
The biggest boon to closures, for "regular" programmers, is the dramatic simplification of async code.
> The biggest boon to closures, for "regular" programmers, is the dramatic simplification of async code.
Consider the way it's currently done in Java:
listener.addCallback(new AsyncCallback<ReturnType>(Parameters ..) {
public void onSuccess(ReturnType returned) {
System.out.println("Successfully returned!");
}
public void onFailure(Throwable thrown) {
System.out.println("Unsuccessfully returned!");
}
});
This is unbelievably ugly, and yet this is about as easy as it gets in Java. In order to make asynchronous callbacks "easier" you have to instantiate an anonymous class with very precisely named methods.It's not only wasteful, but the potential for error is huge. Closures could make this much, much simpler.
class __AnimationHandler(superclass):
def __init__(self, obj):
self.obj = obj
def animations(self):
self.obj.position.x += 10.0
def onCompletion(self, finished):
self.obj.destroy()
ui_view.animateWithDuration(0.2, handler=__AnimationHandler)
Although I definitely prefer closures, this is hardly a clusterfuck. But it would be fuglier in C/C++.[1] Yes, python has closures, which obey confusing rules and often work. But the pythonic way is an object handler.
x = 0
def foo():
x = x + 1
print x
UnboundLocalError: local variable 'x' referenced before assignment
The javascript version works fine. var x = 0;
var foo = function() { x = x + 1; console.log(x); }
I realize there are ways around this, but they are considered unpythonic. For example: x = [0]
def foo():
x[0] = x[0] + 1
print x[0]
This works as you'd expect.Python3's nonlocal allows explicit enabling of scheme-like behaviour.
You don't have to look too far to find out: blocks haven't always been supported, and the older animation APIs still exist on UIView. You need to set a completion callback selector instead of passing a block. It's certainly uglier, on both sides of the API, but particularly on the implementation side. Blocks wrap up the state they need by default (well, for the majority of cases) which saves a lot of tedious work.
-beginAnimations:context: -commitAnimations
etc.
The "completion" feature can be implemented via delegating. One thing to note here is: If you don't need the completion block you can also just write:
myObj.position.x += 10.0
without blocks at all. At least on OS X every animatable object as an animator proxy that works like this:
[myObj animator].position.x += 10.0;
Then an implicit animation kicks in that can be configured. The default implicit animation duration is 0.25s but this can all the configured.
The original Core Animation (without blocks) made working with animations so much easier and cooler. You could do everything you can do with the blocks API - although the blocks API is much nicer of course.
What makes closures is that it opens up a whole new way of coding. Two things specifically make this possible: that they can be treated as data[1], and that they can capture local variables.
Closures is, for example, what makes the entire premise of OS X's Grand Central Dispatch framework possible. You can create a closure, and seamlessly execute it in a background thread, on a different core. You can build complex queues and dependencies of operations and use all cores in parallel, while keeping a very simple interface to the whole system.
Here's an example pulled from my toying around in Ruby, trying to implement a machine learning algorithm.
thetas = gradient_descent 0.1, 10 do |theta0, theta1|
J theta0, theta1, training_set
end
This code uses a gradient_descent function, and passes it a closure to find the values of theta0 and theta1 which minimize the function. It will automatically figure out how many arguments to minimize, based on the closure's `arity`. The closure captures the `training_set` variable and uses the arguments to call cost function `J`. This would not have been possible, or nearly as easy, without closures. Peruse the entire <60lines code at [2].Closures open up a whole new world of possibilities.
[1]: Most languages cannot serialize a closure, though. Lisp is pretty unique there. [2]: https://gist.github.com/48a7006e138460b65173
Perl can also serialise closures using the Data::Dump::Streamer module - https://metacpan.org/module/Data::Dump::Streamer
If you want to process a sequence of values, and filter, aggregate etc., a for loop is probably too low-level; with judicious use of some libraries, you can work to a higher level abstraction. This is no different in C than it is in Haskell; it's just harder in C, because C is low level to start with, and doesn't have very powerful abstraction features.
The real risk here, I think, is using the wrong language to solve a set of problems, rather than using the wrong feature.
The real use comes when you want to perform more complex control-flow logic with different transformations. When you are working with planar undirected graphs instead of arrays, for example a HoMM3's hexagonal grid, repeatedly writing out your breadth-first-search code to do simple graph modifications/transformations gets old really fast.
With a map you know it's just transforming the list with a function; with a reduce you know its combining the elements of a list with a function.
With a for loop, it could be any of those, or other things besides. And it takes more code--more to read, more to think about--to encode any of them.
The real gain is when you are working with data structures other than arrays, then closures are 1 line rather than writing a 25 line breadth-first-traversal! If you think writing lots of for loops is tedious and confusing, try writing lots and lots of BFSs, Queue and all.
The reason Python is cutting out reduce is that Van Rossum has an irrational antipathy towards functional programming, and that Python is really an imperative language throughout. (I know all too well as a functional programming aficionado forced to use Python.)
My closure AHA! moment was writing asynchronous networking code for the iPhone. Instead of dealing with delegates and writing protocols, I could just pass it a block of code to run when the request completed. It turned a multiple file and many line design, and brought it down to just a couple of lines. It was great.
Well yes and no, a `for` loop is a (pretty shitty, when it's C-style) general-purpose iteration methods, closures let API authors and developers craft more precise iterations with clearer meanings, or nice shortcuts.
It's indeed not where closures shine though. But closures tend to shine in developers building their own new control structures, which is very hard to demonstrate to people far from "enlightenment".
Take Smalltalk's conditional. I find it one of the most beautiful pieces of code I know of, in usage and implementation alike. But for 9 java developers out of 10 the reaction will most likely be "java's got `if`, why would you care?"
I think your reaction - seeing it as beautiful - is more likely if you're relatively new to closures and code as data. The fact that treating code as data lets you write your control flow as libraries, doesn't mean that it is good to write your control flow via libraries. The power of an abstraction is not necessarily well correlated with the desirability of its use; I rather suspect the reverse may be the case.
https://github.com/lorenzo-stoakes/weak/blob/master/board/he...
(helper functions for unit tests within a chess engine.)
Having closures allows you to be very flexible in the way you interact with a block of code, and there are some things you just cannot de-duplicate without them.
However they are just a tool and as the cliché goes - 'after a while everything starts to look like a nail' - so as with all programming it's a matter of using taste and good judgement to use them where they reduce duplication/coupling + increase maintainability and [the endlessly subjective but equally vastly important] readability.
"Oh, hey! We can replace a for loop doing addition with a fold, with only the added conceptual complexity of ensuring that (+) is an associative operator, and making sure that we don't accidentally blow our stack by unexpectedly recursing too far because we assumed the wrong evaluation order." (Which by the way his example does.)
f1 list = foldr (&&) True list -- && is logical and
f2 list = foldr (||) False list -- || is logical or
f3 list = foldl max (head list) (tail list)
f4 list = foldl min (head list) (tail list)
Do you consider them trivial, or too hard to understand without rewriting as a loop? Honest question.I think if you use a non-associative function as an argument, then fold is hard to understand. But then an explicit loop won't help much.
Check out http://lambda-the-ultimate.org/node/587 for some non trivial examples.
But why? A SQL query or a Java/Python/whatever program might get translated to zillions of CMP and JMP instructions, but I see no reason why you should have to do that translation yourself in order to read the code. In fact that would practically preclude the use of any minimally high-level programming language.
Also, everybody's focusing on closures, which makes sense given the article, but I think the main point is about higher-order functions which require first-class functions (what the author calls closures).
Using higher-order functions in place of loops is much like using structured programming instead of goto.
A for loop is just a specialized form of goto that makes what its doing more explicit, lowers spaghetti code and makes making mistakes harder.
A fold or a map is just a specialized form of a for loop that makes what its doing more explicit, lowers spaghetti code and makes making mistakes harder.
Everybody decrying the article would probably never dream of using gotos for control flow--structured programming is patently superior! And yet you are also reluctant to use higher-order functions.
I think this is a perfect realization of PG's ideas in his Blub essay.
Coincidentally, I remember when I was first learning Perl, then C++ and then Java, a lot of the stuff I read was far more evangelical about OOP.
This followed by disappointment, as I was unable to experience a similar enlightenment for cases that did not have completely embarrassing data parallelism. For instance, given arrays A, B, and C, we want to set A[i] = B[i-1] + C[i+1] (with special cases at boundaries). How would you do this with the map/filter/fold primitive recursion patterns?
Edit: Upon thinking of it more, would slices do the trick? So, in Python:
A = [0,0,0,0,0]
B = [1,2,3,4,5]
C = [6,7,8,9,10]
A[1:-1] = map(sum, zip(B[:-2], C[2:]))
Is there a better way to do this? I would say it is much less easy to read than A[i] = B[i-1] + C[i+1]. I would probably have to add a comment to the effect of "this is what this line is doing", which goes against the self-documenting argument. a = zipWith (+) (0 : b) (tail c ++ [0])
and that would make a[i] = b[i-1] (where b[-1] is 0) + c[i+1] (EDIT: where the last element of c is 0 (thanks tkahn6!)).Also, interestingly, if you have lazy evaluation, you can use a zip to produce the fibonacci numbers: just zip the list up with itself.
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
so you cons 0 to 1 to the zip, and the zip is combining 0 with 1, and then 1 with 1, and then 1 with 2. fibs[0] = 0
fibs[1] = 1
fibs[i, i>=2] = fibs[i-2] + fibs[i-1]
and this looks like the math behind it./1 million random floats between 0 and 10
a:1000000?10.0; b:1000000?10.0; c:1000000?10.0;
sum each flip (a;prev b;next c)
/To do it in parallel
sum peach flip (a;prev b;next c)
Sometimes it is like living in a world where people refuse to use sheet music/clefs/bars and instead they read and write music by listing the notes, their frequencies and how long to play them for. And you can't tell them otherwise. People who recoil at APL because there are a few unusual symbols involved will gladly play a guitar and deal with all sorts of arcane symbols. If we really wanted to do a good job we'd go beyond ASCII.
I think from your python, you want the first element of A to be 0? (Python is not my best language.) Thus in Haskell,
a = 0 : zipWith (+) b cThe lining up is a matter of prepending the lists with some border values; then, use map to do the add. Finally, discard the edge cases.
A[2] = B[2-1] + C[2+1]
A[2] = B[1] + C[3]
A[2] = 2 + 9
A[2] = 11
Your solution gives 12 for A[2].
f xs ys = zipWith (+) (0:xs) $ tail $ ys ++ [0]
f [1..5] [6..10] == [7,9,11,13,4]
I assume you want B[-1] and C[5] to yield 0 in this case.Unsurprising for a Haskeller, but he forgot the fourth and likely most common use of for loops: iterative code that has side effects.
I think the author already covered this case. It's basically a classic usage of fold:
fold f a 0 = f(a[i], f(a[i-1], f(..., f(a[0], 0)...)))
He even points out that it's better for the compiler if code is written this way. I.e.: a = map g inputData
b = fold f a 0
The compiler now knows the first line can be parallelized, whereas the second cannot. If the map were mixed up with the fold code (which it often is in big for loops), the compiler would have a harder time of it.In haskell, this is done as follows:
sequence_ (map (\x -> putStrLn (show x)) [1..10])
This is equivalent to: for i in range(1,11):
print str(x)
However, in my experience, this is a highly uncommon use of for loops. I've almost never done it, either in haskell or in python. Consider the side effectful code: for line in open("urls.txt").readlines():
scrape_url(line)
Do I really need to do this in order?Just because you have side-effects does not mean you can't express your computation more neatly using a higher-order function. And some of the imperative uses of a loop are easy to abstract into a higher-order function as well: while (true) {...} becomes forever, for example.
Conceptually, a for...in loop is basically like a map, except that it doesn't return a list. Of course, this is yet another fault of Python with an arbitrary separation of statements and expressions. If the for...in loop was just an expression--and that's basically what a list comprehension is, if you squint--it would probably be exactly like map.
My real point was that the only way to get C-style for-loop behavior in Python is to do for i in range(a,b), which is basically equivalent to map fn [1..10].
Edit: I consider loop based programming my most essential coding pattern.
I mean I use what I think are closures in Javascript, but I thought they were to 'prevent the pollution of the global namespace' (a noble endeavor). I don't get how it relates to map, reduce(fold?) & filter.
A closure is a lambda that is closed over its free variables meaning that the variables which are not defined in the body of the function (variables that are not local or the formal arguments) are bound to what their values are in that lexical scope at the time they are constructed and do not change with the calling environment.
In javascript if I do:
var x = 2;
var f = function() {
return x;
}
console.log(f())
(function() {
var x = 3;
console.log(f());
})();
It will print '2' and '2'. f is a closure. It is closed over its free variable 'x', 'x' in f is bound to the nearest 'x' that was in scope when it was written.A lambda is just a function, it does not necessarily imply there are free variables which it is closed over.
function() { var y = 23; return y * 40; }
is a lambda with no free variables.Technically the following implementation of evens is a closure:
isEven x = (x `mod` 2) == 0
evens xs = filter isEven xs
Can you see why? `isEven` is a free variable in evens which is bound to the isEven defined above it. evens is a closure, but generally we wouldn't refer to it as such.I'm confused a little with regards to the closure closing over the "value" of free variables, or the reference.
For example, in python:
i = 1
def f():
return i
i = 2
def g():
return i
Both f() and g() return 2, even though when f() is defined, shouldn't i=1 be closed over in this case? That's what I find confusing here, in the sense that the state of the environment changes outside the closure affect things inside the closure.This behavior is the motivation behind the do keyword in CoffeeScript.
Here's more on this:
http://stackoverflow.com/questions/233673/lexical-closures-i...
Closures are way more expensive if you include the call overhead.
There is something wrong with a for loop, just like there is something wrong with goto--it's at too low a level of abstraction. A goto could signify any sort of jump; a for signifies any sort of repeated operation. A fold or a map or a filter or a zip signifies a very specific type of repeated operation that is hard to mess up.
I get the feeling a lot of people on HN just fell out of university or work in a niche area. The rest of us don't care for such isolation and prefer to work with deterministic and well known tooling.
<sarcasm>
If that is the case, then the summation operator in mathematics should be replaced with Lambda Calculus or the world will fall apart...
Linux obviously doesn't work because it uses for loops in the Kernel.
Windows needs to be rewritten in Haskell or LISP to make it pure.
OMG I just realised, I'm not allowed to walk a list in C without implementing car and struct pair. Damn my stack just blew.
</sarcasm>
I've played with OCaml, Haskell, Erlang, Scheme and Common Lisp, and I always end up going back to Python, C, and C++ for all of my "real" development.
IMO the abstractions available in the more mainstream languages are easier to understand and use in almost every case. They also perform better on existing hardware.
This particular article is really bad because most of its arguments are straw men.
For example:
"Instead, higher level operations like sum, prod and concat are defined in terms of folds. And your code is then written in terms of these higher-level reducing operations, making your code more concise, easier to read, easier to write, and easier to understand."
Building higher level abstractions from the language's lower level abstractions is basically the entire point of programming in any language. It's convenient that Haskell's library includes functions like sum, prod and concat, but it's hardly the only language including them, and it's trivial to write them correctly and reuse them in the languages without them.
And then he says stuff like "If you're still not worried about bugs, think about the possible typos. Then think about how often you write loops like this." Because nobody ever makes typos while using folds and closures, right?
for (int i = 0; i < array.length; i++) { newarray[i] = f(array[i]) }
by contrast, the functional-programming method allows you to define the new array immediately in terms of the old one, just mentioning the latter once and without an index variable:
newlist = map f oldlist
To my eye, this gives me far fewer chances to botch it, and my reader can instantly tell that (1) newlist corresponds to oldlist in length and order
(2) the only difference between the two is that the elements are related by the function f.
Every time I come across a for loop that builds up an array, I worry whether the bounds checking is wrong, whether the right index variable is used (esp. if there are nested loops), and whether an elements were added to the array before or after the loop. Lots to worry about.There's no free lunch.
Admittedly, you do have to worry about the stack in Haskell, but that is entirely the fault of laziness and not recursion or higher-order functions. You're not really worried about a call stack, you're worried about having a really big thunk (deferred expression).
There may be no free lunch, but some are much better value for money than others.
If you type flod instead of fold, the compiler will complain. If you have an off-by-one error in your for loop, not so much.
foldl :: (a -> b -> a) -> a -> [b] -> a
foldr :: (a -> b -> b) -> b -> [a] -> b
So the only time it will be the same is if a and b are the same type.Which they will be in most of the example code he used. They're not the best examples.