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.

Reply via email to