4 ms·
I find the go pattern absurd. Which of these is easier to read: foldr k z = go where go [] = z go (y:ys) = y `k` go ys or fold
by tome 4y ago
I find the go pattern absurd. Which of these is easier to read:
foldr k z = go
where
go [] = z
go (y:ys) = y `k` go ys
or
foldr k z = foldr_k_z
where
foldr_k_z [] = z
foldr_k_z (y:ys) = y `k` foldr_k_z ys
- ghusbands 4y agoI think I prefer this: foldr _ z [] = z foldr k z (x:xs) = k x $ foldr k z xs
- tome 4y agoI suspect we all prefer that, but the point of abstracting out a closure that captures k and z is for performance.
- chowells 4y agoEh, the performance isn't from abstracting out a closure. It's from making the definition non-recursive so that it can be inlined. Then the compiler can see and inline the k and z parameters into the "go" block to eliminate indirect references. It's really all about inlining.
- tome 4y agoIf it was just about making it non recursive so it could be inlined then the following would be sufficient: foldr k z = foldr' k z where foldr' k z [] = z foldr' k z (y:ys) = y `k` foldr' k z ys That's obviously not sufficient, so it must have something to do with the nature of the closure. In this case I presume that it's because the closure captures k and z, although if you have any evidence to the contrary that would be interesting to see.
- chowells 4y agoThat's a reasonable question. It comes down to being transparent with the compiler. Not redefining k and z at every step is what allows their values to be inlined. You could make an argument about a sufficiently advanced compiler and partial evaluation, but the fact is that partial evaluation is far too slow to rely on for things you could just make explicit in the code instead. When the definition closes over the names, they trivially refer back to the same thing every time. So when the definition of go is in the same scope as what k and z refer to (which is usually the case after inlining foldr), k and z can be inlined into go. When this happens, note that it's actually no longer constructing a closure at runtime. It has essentially closed over the values at compile time, using some very trivial transformations. If you use a definition that is too complex for those trivial transformations, you're getting in the way of the compiler doing its job. I always prefer to write my code with sympathy for the compiler. The less magic it needs to do, the better it does its job.
- gpderetta 4y agoThe first.
- tome 4y agoDo you have insight you can share into why you find it that way?
- chowells 4y agoI do. The name foldr_k_z doesn't say what the function is doing. It's just syntactic punning on a function call with two additional arguments. That's actually negative for comprehensibility. Names should be semantic, not syntactic. And that name doesn't say a thing about its meaning. The most it tells you is that it's related to foldr and its k and z parameters. But the details? Well you have to look for those. When you look at the definition, you discover that the it's the foldr worker that closes over k and z. You could name it foldrWorkerThatClosesOverKandZ, I suppose. But does that name contain any information that isn't present in the context? Does it help you actually understand anything? I'd argue "of course not". You already know that it's the foldr worker because it's a local recursive definition inside foldr. And you already know it closes over k and z because it uses them without defining them locally. Nothing in that name provides additional semantic value. You could still use it anyway, on the argument that a little redundancy can help aid reading. But the more Haskell code you read and write, the less that redundancy helps you with anything. On the other hand, the proliferation of names that contain almost no semantic content starts to drag on you. And so an idiom was developed for naming recursive workers that do the core job of what the parent's name promises: just name it "go". Nothing to think about. It's reduced down to a level that communicates exactly that it's not clever. It's just doing the thing it has to do. And it's standardized. If you see it, you know exactly what it's doing. There's no need to waste time mapping a new name into your existing set of well-known patterns. So... As to the original argument's point? I think it probably is awkward for pedagogy. But it's absolutely better for actively using the language.
- abecedarius 4y agoHow about 'folding'? I've settled on that kind of name for looping/recursing helper functions. Scheme has a bit of syntactic sugar called "named let" which makes this internal-helper pattern more concise/direct.