Hacker News new | ask | show | jobs
by radmuzom 4367 days ago
Very true. I remember reading in StackOverflow where a guy commented that while programming in Haskell he was thinking more and programming less - he seemed to indicate that this was the most important thing. While design and structure is important, I think the reason he was "thinking so much" is that Haskell forces you to think non-trivially even for simple problems in many cases. One common argument made is that a loop is much more natural in many real-life scenarios rather than recursion or folds - so the mental model of the problem matches closely with the machine model - which is not the case when you are forced to use recursion.
1 comments

I completely disagree. And I'd go so far as to say that a lot of that criticism comes from a lack of familiarity in functional programming/Haskell compared to imperative programming. The argument of a loop being more realistic is horrible imo, as both refer to performing processes, which are abstract concepts: my mind probably views these differently to yours, and most likely very differently to a lay person, who will never have had to consider the idea.

I gave a talk recently, and I briefly explained how to decode a protocol buffer varint: if the last bit of a byte is true, shift the first 7 bits and repeat the process on the next byte. Does that map better to

    getVarInt :: G.Get Int
    getVarInt = G.getWord8 >>= getVarInt'
      where getVarInt' n
              | testBit n 7 = do
                m <- G.getWord8 >>= getVarInt'
                return $ shiftL m 7 .|. clearBit (fromIntegral n) 7
              | otherwise = return $ fromIntegral n
or

    def read_varint(self):
      run, value = 0, 0

      while True:
        bits = self.read(8)
        value |= (bits & 0x7f) << run
        run += 7

        if not (bits >> 7) or run == 35:
          break

      return value
I'm perfectly happy saying it is equally well represented on both of them. I know I much prefer the first, but that's probably just because I prefer recursion to an explicit loop. From the sounds of it, you'd prefer the second, and I believe that's just because you prefer loops to recursion.
In my experience the vast majority of people find explicit loops easier to understand. Thinking in terms of a function that calls itself is a struggle for most people. FP advocates that really care about language adoption should be more willing to recognize that this is a hurdle and not wave it away as a personal preference, IMO.
In my experience, the vast majority of people do not learn a functional language to the point where they are productive in it. My hypothesis is that the correlation between people who finding explicit loops easier to understand and people who do not learn functional languages to the point where they are productive is evidence of causation.
The problem is a possibly little more fundamental, though. Explaining a looping function to someone can be as easy as "think of how you do the dishes, you take one... repeat until there are no dishes/soap/water/room left."

This is very close to how every repeating process is taught and understood by folks. You take something, and repeat until there is a specific condition. Compared with a recursive solution. Which is of a few forms. One being to take your problem, break it into a series of identical problems and constantly restart with the results of having run a previous problem.

I grant that a recursive solution is not that much more difficult to phrase this way. "Take a dish, clean... start over unless there are no dishes/soap/whatever left." I just don't know if I have ever heard something stated this way.

In my experience, people who are ideological about FP often claim that the vast majority of people do not just get FP, and their preferences are misguided and not natural.

Yet, sequential and direct control flow are common in human language, we know how to tell or be told to do something N times. Recursive formulations are harder to explain, and most people don't learn math as easily as they do language.

It's all about precision. To tell someone how to do the dishes seems easy. But give the same instructions to someone who has never done dishes and you quickly see the problem. It goes the other way too. I dare you to try and explain a simple repeating algorithm in prose. No lists or numberings allowed.

Simple recursions work exactly the same as the equivalent iterations. They branch on a condition, execute a step and repeat. How state is handled is the key difference. As Haskell has no concept of a variable, the only way to rebind a name is to recall a function.

I was going to say that a professional in our field who has problems with recursion and the basics of discrete math should take a look in the mirror. But maybe we have managed to raise the abstractions high enough so that one can be productive without knowing the fundamentals of computing. I probably need to broaden my concept of a professional in our field.

Even if someone hasn't done the dishes before, contorting the explanation through recursion is not helpful. Recipes and instruction manuals are not recursive, and are rich in direct control flow and avoid indirect (higher order) control flow like the plague. Recursion for human discourse is just not used often, it is a relatively recent concept to us.

There is plenty of program writing to do that don't involve deep knowledge of maths, in fact I would say a vast overwhelming majority of work companies need to get done are not helped by, and possibly even hindered by, an intricate understanding and application of recursion (keep in mind, recursion is actually dangerous in strict languages). Most of us are more like police dectectives using well worn tooling and intuitive problem solving skills to get the job done. Sometimes we might even need the help of a mathemegician, but not most of the time.

I don't understand where you have "run += 7" and break if run = 35 in the python code. Does the Python code have more error checking than the Haskell code?
The Haskell code checks this implicitly with the use of the Get monad, I believe. In Python, you have to manually tell the code to not reach for bits that aren't there. In Haskell, the concept of a failure state being implicitly dealt with is used all over.

Also, the reason the Python code is a `while True` with an explicit counter and check at the bottom is because Python lacks a do-while loop

Huh, yeah, didn't notice that implementation difference. In the Python code, the longest valid varint is 5 bytes (want the result to fit into int32 I guess?). In this case the bug is not strictly a problem as the Haskell code is used to read varints from trusted inputs only, whereas the Python is designed to read untrusted input. But good catch