>> the IDEA of, "foo takes a function and returns a function," is a fairly widespread idea in the JS community: The same is true of ruby, and a few other mainstream languages.

I would say that it's getting mainstream in general. I'm working on an ASP.NET Core application as I'm writing this comment, and you'd be surprised as to how many functions get passed around and returned routinely. I was surprised myself.

>> Scoping is semantics, not syntax.

It is, but semantics is supposed to correlate with syntax in a sane way. In JS, it does not - you can place "var" inside a block, but it's not actually scoped to that block. If they required all vars to be declared at the beginning of the function, it would have made more sense. And I'm not aware of any other language that made the same blunder, except for old (pre-.NET) Visual Basic.

Python scoping is comparatively sane in comparison in a sense that the rules follow the syntax. You still can't scope to a block, but you're not misled to believe that you could.

>> As for the 'magic semicolons,' JS doesn't do ASI when the syntax is ambiguous

return

42

>> all a closure is is a lexically closed environment, so semantically, EVERY function is a closure.

True, but in common use, we usually mean "closures that can close over something other than the global environment". The other kind is just too trivial to be worth discussing on its own.

Regarding lambda semantics, I'll grant you that Python doesn't have full-fledged lambdas in that sense. But then again, they argue that if you need a multiline closure, it should have a name for readability purposes - I don't necessarily agree, but it's not an obviously invalid argument. And as far as expressiveness goes, named local functions provide the same degree of it, even if the code is more verbose.

On Ruby, I think the distinction between blocks, procs and lambdas is necessary if you want to provide various gradations of what "capture the lexical scope" means - and in particular, how you want to treat the meaning of "return", "break" and similar statements that, effectively, have an implicit jump target. In some circumstances, you may want to capture the target from the outer scope; in others, you do not. Still, they could have made do with two different types of closures, rather than three, one of which isn't even first-class.

FWIW, in C#, lambdas behave quite a bit like Java ones, except 1) they can mutate locals (which are automatically hoisted to the heap in that case), and 2) instead of implicitly implementing interfaces, the language provides function types (delegates), and a lambda automatically creates an instance of a delegate when used in the appropriate context. So you can directly pass functions to other functions etc.

(I oversimplify somewhat, because C# delegates are actually multicast, so they're not quite a bare-bones function type - but 99% of the time all those extras are unused.)

Then we have C++, where lambdas are also just objects with operator() defined for them, and no specific type otherwise (you have std::function when you need such a type, but idiomatic C++ is to use templates, so that lambdas can be inlined as much as possible).