← Back to context

Comment by LegionMammal978

1 day ago

> I don't understand why someone would want to hold on nostalgically to restrictions we no longer face.

On the other hand, I don't understand why someone would want to shoehorn all possible forms of control flow into "everything is a function object that can receive argument values that are substituted into parameter variables". I could just as easily say that all programs are a special case of cellular automata or string-rewriting systems or whatever other Turing tarpit.

You call imperative mutation "contorting your brain", but you always have to keep close track of which variables change and which stay the same, even if you shove them into function parameters. Local immutability comes at the cost of disguising which variables may have their values replaced over the course of the execution.

> What you'd want to do is define combinators that encapsulate the specific pattern of computation you want to implement.

Yes, I agree that control-flow combinators are a good model for many forms of programming. But they are mostly orthogonal to the broader imperative vs. functional style for sequential control flow, except for the historical factor of older imperative languages having poor support for capturing values into closures.

> I know of one major use case where you'd want to use tail recursion directly: state machines and interpreters. See https://en.wikisource.org/wiki/Lambda:_The_Ultimate_GOTO for an example of the former.

I can accept that direct threading makes contingent sense in that Python case, given current implementations. It seems to mostly be an unfortunate limitation that the compilers have poor handling of dispatch loops. (Indeed, the author still had to use special implementation-specific contortions to get the compiler to generate acceptable register handling.)

But otherwise, I'd generally prefer structured programming, whether it be with imperative statements or with control-flow combinators, over the totally unstructured control flow offered by bare tail calls.

> On the other hand, I don't understand why someone would want to shoehorn all possible forms of control flow into "everything is a function object that can receive argument values that are substituted into parameter variables".

Huh? Where did you get that impression from?

I was suggesting to remove some special case handling of certain tail recursive constructs (commonly known as 'loops') and just handle them via the general mechanism (via function calls, which are already in practically any language, and just need to be properly compiled) and perhaps at most offer some syntactic sugar to define loops in terms of the underlying tail recursive constructs.

I was not suggesting to 'shoehorn all possible forms of control flow' into function calls. (Sorry, I don't know what a 'function object' is. I just assume you mean a function?)

For example, many languages offer exception handling and there's also async operations and multi-threading and call-with-currenc-continuation and (early) return etc. I have expressed no opinion on those, and I'm not even sure they can all be handled as function calls.

> But otherwise, I'd generally prefer structured programming, whether it be with imperative statements or with control-flow combinators, over the totally unstructured control flow offered by bare tail calls.

I am suggesting to use combinators in most cases, and mostly use (tail) calls to define new combinators, rarely directly. Your compiler (and language) should ideally make this pattern of usage fast (which modern compilers can generally do, because they can eg often in-line the whole combinator thing).

I am observing that the commonly accepted loops are just some special cases of (something like) combinators and shouldn't be raised on a pedestal. Ideally they should be defined in the standard library, and not on the built-in level.