Consider your accelerator pedal in a car. Somewhat simple method to increase fuel to the engine. If something is wrong with the engine, it doesn't message that back to you through the pedal. Even though the pedal won't work. This can be as trivial as the car not being on, the pedal will not work.
So, if you are picking the straw man where every function has side effects and communicates back through a side channel, I agree. But if you are building a system where some things would require way more effort and code to get what you are aiming for, then we are in the realm of this article, where a panic that sends it back to the user makes far more sense.
I don't see how this is a problem? The accelerator pedal API, so to speak, is purely mechanical -- its "return value" is whether or not you were able to put it to the position you wanted, it doesn't promise to return any information about the downstream effects of that pressure/setting.
The accelerator is tightly coupled to the valve which controls how much fuel is given to the engine, but anything after that is a downstream (event-driven?) effect that I as a driver have to observe through other means. So if the valve is stuck I expect to learn about that failure after I call this fn, sure. But the impact on the intake, or the engine RPM, or the transmission, or my wheel speed, or my actual velocity, these are all things I discover through other means.
So it's not
fn depress_accelerator(f64 amount) -> CarState result
but rather something like fn depress_accelerator(f64 amount) -> bool worked
fn depress_accelerator(f64 amount) -> f64 throttle
It's not complicated. When I call a function, I expect it will do what its signature says it can do, and I expect it to return control flow to me. If you can't rely on these assumptions, it's effectively impossible to build reliable software.That said, this is precisely my point. Downstream from where my direct interaction is with the system, something can go wrong. And it doesn't make sense to think of it only in terms of a single "main." Instead, there are various parts of the system that all have different responsibilities. In particular, side channels of information are setup to route some errors/information back to a user that are not necessarily part of any individual function. And some systems should panic/fail instead of trying to continue, given the state of the system.
So, if something is being punted up from inside a software system, it makes sense to have an exception be there, to me. As it does, as well, to the article in this post. I can think this, all the while also agreeing that most of the time using return based responses makes a lot of sense.
Edit: Incidentally, I think your first function is more correct there. The state should also reflect where the accelerator pedal is currently sensed at. Any actual acceleration will be in response to the rest of the systems reading that state. There is literally no way for the pedal to know if it succeeded or not.
Success for the pedal is defined in terms of what it can know, same as any other component in a system. The pedal is typically a mechanical device, so what it can know is restricted to its mechanical outcomes. Was the floor mat stuck behind it? If so, pressing the thing to 100% only results in an outcome of 50% depression -- maybe that's failure. And so on.
It's incoherent for depress_accelerator to return information about the car in which the engine it is connected to is installed. Does `ls /mnt/volume/file` return information about the chassis of the server in which the hard drive hosting `/mnt/volume` is installed?
> it doesn't make sense to think of [programs/systems] only in terms of a single "main."
Why not? You can absolutely model any program, composed of arbitrarily many parts/components, as a graph of components with a single root. I acknowledge that's not the only model, but it's definitely universal and effective.
> In particular, side channels of information are setup to route some errors/information back to a user that are not necessarily part of any individual function. And some systems should panic/fail instead of trying to continue, given the state of the system.
What do you mean by "errors/information ... not part of any individual function"? Isn't it the case that anything which could produce an error is necessarily part of a function i.e. a sub-tree of the execution call stack? If I write a fn doSomething(input x), why should the result of my implementation do anything, in general, other than return execution to my caller with an appropriate result value?
> So, if something is being punted up from inside a software system, it makes sense to have an exception be there, to me. As it does, as well, to the article in this post. I can think this, all the while also agreeing that most of the time using return based responses makes a lot of sense.
Functions define layers of abstraction. A given function knows only its own implementation, narrow properties of its caller (input parameters and output expectations), and the characteristics of the functions it calls as defined by the signatures of those called functions.
There should never be the concept of something "being punted up from inside a software system". A function is defined in terms of its inputs and outputs. Its success or failure is defined in terms of those things only.