Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Because an if statement is very constrained in what it can do. Every if statement has two branches (maybe one is a no-op) and one of them executes. If someone sees an if statement, it is implementing a fork in the code path.

A function can do literally anything. Seeing a function call, it hasn't narrowed down the realm of what might be happening even a little bit.

"lambda f:" gives away very little information about what is about to happen. "if f:" gives away some information about what is about to happen.

Not a perfect rule of thumb, convoluted if statements do exist. But that is why functions are more in need of hints and comments than other control structures.



> "lambda f:" gives away very little information about what is about to happen. "if f:" gives away some information about what is about to happen.

I think that's an unfair comparison:

If 'the thing that happens' for "if f:" is 'branch on the truthyness of f', then I agree that's pretty clear. However, if we're going to ignore the content of the branches then we should also ignore the body of the function, so "lambda f:" is also pretty clear: 'parameterise with f'.

If we're worried that "lambda f:" could do pretty much anything in the body, then we should also worry that "if f:" can do pretty much anything in the branches.


> If we're worried that "lambda f:" could do pretty much anything in the body, then we should also worry that "if f:" can do pretty much anything in the branches.

That is the part common to both. If you take that common part out, you will be left with more information in the if statement than the function call.

In the function, you know what arguments it (might) be interested in. You know they are evaluated. But in the if statement, you know what values it is interested in, usually get the same amoutn of information about whether they are evaluated, much clearer guarentees about how they are used and that the aspect of them that matters is their truthiness. Also the code might be about to branch.

Put it this way - if(...) is a specific - and common - named function. It is a very easy argument to make that it is carrying more information about what is going on than a general anonymous function.


If you take that common part out, you will be left with more information in the if statement than the function call.

I don’t think this argument generalises well. Once you start working with structured data and more complicated control logic, a generic loop with an arbitrary body often provides less immediate information to a developer reading the code than a function that explicitly names a specific pattern of computation like map or filter.

There are many recurring patterns of computation that are much more specific than a generic map or filter, too. While imperative languages tend to have only a small number of built-in control structures, if we’re using higher-order functions then we can name as many of these patterns as we like.

For example, suppose we want to generate a list of the first few triangle numbers, which is a sequence of the form:

    [0, 0+1, 0+1+2, 0+1+2+3, ...]
Writing this in imperative Python code with a typical loop would give us something like this:

    triangle_numbers = []
    total = 0
    for n in range(6):
        total = total + n
        triangle_numbers.append(total)
Here’s an idiomatic functional version, which is real Haskell code using a standard library function that captures this pattern of sequence generation in just five characters:

    triangle_numbers = scanl (+) 0 [1..5]
Assuming you’re equally familiar with both programming styles, I think it’s fair to say that “scanl” tells you much more about what this code is doing than “for”.

The inner function here is just addition in both cases. However, the code within the Python loop is allowed to do whatever it wants, so the reader has to recognise the pattern surrounding the addition. In the Haskell version, the function you pass to scanl must take an accumulated value so far and a next value as parameters and it must return the next accumulated value to add to the sequence being generated, and that is all it is allowed to do. So you just need to write (+), which is Haskell’s notation for what Python calls operator.add or an equivalent lambda, and that tells the reader exactly how the next accumulated value is being derived at each step to build up the sequence.


Well, ok. But you've named all your functions here so I'm not sure which part of this you're arguing about.

    triangle_numbers = scanl(f, 0, [1,2,3,4,5])
gives me scant little information about what the value of triangle numbers is at the end of the operation.

    triangle_numbers = if(f, 0, [1,2,3,4,5])
or

    triangle_numbers = if(true, 0, f)
tells me that whatever f is something weird is going on. Commenting if statements to point out that they are branching code is foolish.


But you've named all your functions here so I'm not sure which part of this you're arguing about.

My intended point was that comparing lambda: to if: is apples-to-oranges.

Both styles in my example have an outer structure that builds a sequence, wrapped around an inner calculation that says how you build the next value in the sequence. The inner calculation here was simple addition, a one-liner in the for loop and (+) in the Haskell version. However, it could equally have been something more complicated.

If it were, a reader would still have to figure out what the body of the for loop was really doing. In contrast, the (possibly anonymous) function passed into scanl would still have a more constrained purpose, which is immediately known because scanl represents one specific pattern of computation. The implicit algorithm it represents has a hole that is a certain shape, and you can only pass it a function with the right shape to fit into that hole.

Put another way, if you’re programming in this functional style, it’s not writing the word lambda that tells you what the next piece of code is for, it’s passing that lambda expression into a higher-order function like scanl. Passing an anonymous function is like writing the nested block for a branch of an if statement or the body of a for loop. It’s the call to scanl that is roughly analogous to writing the if or for statement itself.


Lambdas are constrained by their type. If you see a lambda being passed to, let's say `map`, then (assuming you know the type of `map`) you know the type of the passed lambda.

Of course this assumes the lambda is pure, but in the kind of code we're talking about (pseudofunctional code with lots of higher-order functions) they should be pure for the most part.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: