> A lot of production code uses urlparse() and it raises ValueError. https://docs.python.org/3/library/urllib.parse.html#urllib.p...

Yes, that was precisely my point: I don't understand why people are okay with this. URLs being invalid isn't an error; it's expected. If you're constructing a URL from a string, you haven't yet validated the URL. So why would it be exceptional for the validation to fail? I would expect explicitly calling a parser function, to give me the sum-typed result that the parser produces: either the successfully-parsed data, or the relevant shift/reduce failure.

Consider: when I call urlparse(), I'm not demanding that the runtime construct a URL from the string. The function is not called `URL()`, like it is in Java (where IMHO it becomes perfectly justified to throw an exception — you expect `URL()` to give you a URL.) Calling a parse function like `urlparse()` does not imply that the thing I get back is going to be a URL. It implies that it's going to be the result of parsing, whatever that is. Just like, when I call `requests.get()`, I expect that the thing I get back is going to be the result of the request, whatever that is.

> I ask because 1/0 is perfectly recoverable in most cases.

It is recoverable, but only locally. No caller has any idea what to do with the fact that the callee divided by zero. It's a case where doing something and then not immediately handling it in the same local scope, is a programming error on the part of the library author.

> What should the language do? raise an exception? return NaN? Panic? Core dump?

Certainly, in dynamic languages like Python, programming errors in libraries have to be dealt with somehow. But it doesn't really matter what particular mechanism they use, because they shouldn't be recoverable by the caller. The callee failed the runtime's preconditions — like doing a use-after-free in C. The whole runtime is now poisoned, because the library, failing to handle this failure, very likely left its internal state in some indeterminate half-mutated form.

This is just a too-late equivalent to a static language's "this fails to compile." So aborting the entire runtime — or doing anything else that means "this is unshippable as-is" — is probably the right choice. (Maybe delete any generated .pyc file for the module the code was defined in, while you're at it.)

Maybe use a mechanism that can be caught by the runtime itself — if the language abstract-machine includes some sort of mobile-code-execution-sandbox abstraction like V8's isolates. Because programming errors in sandboxed user code shouldn't kill the host-runner. But that's the only case I can see where it'd be relevant.

Ideally, in general, a dynamic language should do enough static analysis at load time to detect programming errors, such that it can have `import` itself raise, refusing to poison the environment with a module with axiom-invalidating semantics.

(Though, in the case of integer division specifically, we've really just screwed ourselves over in most languages by making it a binary operator with a single integer result upon untrusted input. IMHO languages could do a lot better, by 1. having an explicit "integer divisor" linear-type that cannot be zero, and only allowing integer division by that type, such that you have to construct that type first; and/or 2. making integer division by regular can-be-zero integers a branching operation, where you must always define the code path for when the divisor is zero.)

> Golang has two error channels, panics and return values, and I'd argue in this respect it's worse than Java.

I disagree. Golang has one "error channel" — return values. You should never see/catch a panic from a callee. You should be able to just forget panics exist — similar to Errors in Java, or `error`s in Haskell.

Golang panics have two uses:

1. the same thing I described above — unrecoverable operational states and runtime-detected programming errors in libraries. Abstract-machine axiom violations. Callers aren't supposed to handle/`recover` these. Nobody's supposed to touch these; they're supposed to just let them bubble, until the program halts. It doesn't matter that these are implemented via `panic` — they could just as well not bubble at all, and terminate the process on the very instruction they're raised on. They just-so-happen to use `panic` so that deferred functions get called for cleanup, so that sockets aren't left hanging open, files get flushed to disk, etc.

2. structured goto — like Ruby's `throw`+`catch`, (which have nothing to do with its exception system, which uses `raise`+`rescue`), or Common Lisp's `call/cc`. Golang JSON parsing, for example, uses `panic`+`recover` as a way to break out of parser stack frames until it hits the toplevel frame of the parse routing; so it can then immediately return a parse error to the caller. These are never supposed to leak outside the design of a library; so you're not supposed to be handling these, either. Also, this isn't error handling, per se; this is an optimization over returning a condition, then checking for that condition in the caller and breaking out of a loop and returning the same condition further, etc. It could just as well be used to signal a condition like "I'm done, I found what I'm looking for" as "I failed to parse." It's infrastructure, mechanism, that says nothing about the semantics of what it's trying to do or what type of information it's carrying. (Personally, I would argue that this should have been given a separate name from `panic`; and that `recover` should have only been able to catch these, not the axiom-violation panics of #1. But I understand that Golang's designers are big fans of avoiding special-purpose semantic primitives in favor of general-purpose "mechanism" primitives — as can be seen in their previous design of e.g. Plan9's everything-is-a-filesystem "mechanism" primitive.)