I would argue that the vast majority of functions in real world software are maybe functions in that they can fail. You need to be able to deal with failure. Not only can the user not be logged in, there can be a network issue, etc that makes even downstream functions fail.
Also, you have to deal with developer mistakes and what happens when they call incorrectly. This can be something as simple as getting the first element of a collection. What happens when the collection is empty? You can adopt the C++ approach of “undefined behavior” but it turns out to be dangerous.
Monads provide a nice disciplined way to dealing with this and composing together functions that can potentially fail.
Thankfully, newer languages such as providing support for monads and older languages are evolving features/libraries for monadic error handling.
If enumerating every possible failure mode of a function is impossible, then that would underscore the importance of failing fast and dynamically restarting components in order to provide robustness in the face of unforeseeable errors.
Also, you have to deal with developer mistakes and what happens when they call incorrectly. This can be something as simple as getting the first element of a collection. What happens when the collection is empty? You can adopt the C++ approach of “undefined behavior” but it turns out to be dangerous.
Monads provide a nice disciplined way to dealing with this and composing together functions that can potentially fail.
Thankfully, newer languages such as providing support for monads and older languages are evolving features/libraries for monadic error handling.