On Mon, Apr 24, 2017 at 3:09 PM Rob Pike <r...@golang.org> wrote: > Your point 3 misses an important practical detail. Packages that use > recover internally call panic with an identifiable type and the recover > code checks whether the type is the expected one and, if not, panics again, > thus behaving like any other unexpected problem. > > To extend on this:
JSON/Gob decoding is an abstraction over which you operate. Because it is an abstraction, the internal implementation can be substituted by any other implementation as long as the semantics are kept the same. Liskov and Wing formulated this property in the presence of a subtyping system, but there are variants of this formulation and this is one of them. In particular, we can replace a slow implementation with a faster one. Even better, we can exploit the abstraction and cheat in the implementation, violating our normally defined rules in the programming language or coding style, as long as we preserve the abstraction and our cheat doesn't leak out (i.e., we get caught cheating). Using panic()/recover() internally constitutes what is called a "benign effect" in the sense that no-one apart from the implementation itself knows about the effect. Rob's other example with isolation of, say, HTTP requests and recover()'ing on errors in one request holds an important subtlety. A program may not first mess up the heap in a way which is observable by some other goroutine, then panic() and then recover() while keeping the heap in a messed up state. For the abstraction to hold, you must assume a (global) store/heap and make sure your panic()'ing doesn't alter said heap in an observable way that could affect other goroutines. The notion is related to program proof as well. Say, we are trying to prove properties about unsigned integers. An unsigned integer of, say, 64bit can be seen as an array of 64 bool values and we can define a 1-bit full-adder, combine those into a carry-lookahead adder and then define an ALU. However, such structure is hard to prove properties about. We could also define natural numbers as peano numbers: data UInt : Type where Zero : UInt Succ : UInt -> UInt which is to say that Zero is a representation of zero (0) and Succ is a representation of a successor to some other UInt. So 1 = (Succ Zero) and 3 = (Succ (Succ (Succ Zero))). The kicker is that a lot of proofs are easier on the peano numbers than on a system where we have defined what an ALU is based on a bool array. So we once and for all prove that the two systems are equivalent, except perhaps for runtime efficiency[0]. And now, we can pick either representation for our proofs, whatever turns out to be easiest. Proving that something like X + Y = Y + X (commutativity) tends to be easier on peano numbers, but a proof on properties about XOR or other bitwise operations tend to be easier on the bool array. Due to the abstraction, we can freely replace our peano variant or our bool array with a native 64bit uint. This obtains the full speed of the CPU while still allowing us the necessary structure to prove properties about programs. In essence, Rob is alluding to the same strategy w.r.t. the use over panic()/recover(): Once we've proven that the variant is semantically equivalent, we can replace one based on errors deeply nested, with a variant based on recover(). In short: you are allowed use of panic()/recover() in a program as long as you can guarantee a local benign effect which cannot be observed from the outside. The difference compared to a "traditional" exception flow is that you allow the exception to cross an API boundary in a language such as Java. In turn, the exception is part of the expected return type and signals an error. The problem with that solution is that exception flow doesn't follow the usual scope rules of a programs control flow, and this can lead to subtle errors. Especially if concurrency and state are involved[1]. [0] The astute reader may note that in reality, you want to prove the equivalence on natural numbers mod 2^64 for this to work out. In particular, one needs a proper formalization of overflow and underflow. And some times once also needs to mention the changes to flag registers in order to capture how a real CPU operates. [1] Erlang prefers the "go way" as well. The rule is that exceptions should not cross module boundaries but be converted into normal values which are passed around. -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.