Recently I said that checked exceptions or not really exceptional. Here is a good example about the tradeoffs.
Consider building a directed acyclic graph
(DAG), a
dependency graph for example. The Node class has a
method to add a child:
class Node {
public void addChild(Node child) { ... }
}
We must make sure that the graph stays acyclic. So the child node may not be an
ancestor of the current node. We add a check to addChild.
class Node {
public void addChild(Node child) {
if (child.hasDescendant(this)) {
/* NOT SUITABLE */
}
}
public boolean hasDescendant(Node other) { ... }
}
What shall we do at NOT SUITABLE? We have three options:
ISPARENT, to tell the caller that the
operation is not possible.The arguments:
An unchecked exception indicates a programming error, and there are two diferent situations:
If addChild() is an API method, used as part of a user
action, it is normal business to get a node which can not be made a
child.
We could require the caller to first call hasDescendant()
explicitly, which again would turn the NOT SUITABLE part into
a programming error, but addChild() has to call it again to
be sure. How silly. As is often the case, first verifying that an
operation is possible is the same effort as just running it, possibly
being told it cannot be done.
So assume we are in the API situation, not in the algorithm-guarantees-non-fuckup situation.
Throwing a checked exception would be a strong hint that the operation may not succeed on circumstances. Yet, as I argued in the article linked above: this is not exceptional. It is normal business?
Which leads us to the special-value return. I started to call those an Explainer. It encodes why the operation was not possible, with as much detail as needed. Examples:
Map.get(key) a null return is a good
explainer with as enought detail, telling us "no value for 'key'". More is
not needed.
OK, NOOP, ISPARENT for the
cases "child was added", "child was already added" and "given node is an
ancestor so not a suitable child".Did you note how I tried above to not say that an operation "failed". Not easy after being brain-washed for 40 years.😀
In languages with union types, like Python and TypeScript, it is slightly
easier to return either a result or an Explainer. In Java it
would be some Either<Stuff, Explainer> that needs to be
defined.
Is there a case for checked exceptions still? The longer I ponder it the thinner the case gets. It seems nice to ignore the explainer (exception) and let it bubble up. Lets compare:
public Either<Result, Explainer> doStuff() {
Either<String, Explainer> s = compute(...);
if (s.isRight()) {
return Either.ofRight(s.right());
}
...
}
with
public Result doStuff() throws Explainer {
String s = compute(...);
...
}
The latter is obviously more conscise in Java, though the main eye-strainer
for me is more the new Either() necessary to match the result
type. In a language with union types, like TypeScript, this is just:
public doStuff(): Result | Explainer {
const s = compute(...);
if (s instanceof Explainer) {
return s;
}
... move on with s
}
The advantage of exception forwarding amounts to the avoidance of a mere if/return combo. Yes, you say, but what if there are four for five of those in a row? Then auto-bubbling looks much better — hmm, until you have to debug at what line exactly in the 😠code the exception is raised.
What if we made explicit forwarding simpler. If we have union types, like in Python and TypeScript, imagine a syntax like:
const text: string = compute(...) or return;
The compiler would unpack this into
const text: string | Explainer = compute(...);
if (text instanceof Explainer) {
return text;
}
Easy forwarding, explainers need not come along as exceptions and it is obvious were the code did the short turn, eventually. I am dreaming.
What happens to a clock which is moved at some speed for some time. According to special relativity it runs slower on the move. Lets get a bit of an intuitive understanding of this.
I described the light clock earlier and showed how it runs slower when it is moving. Now, what if we have two identical light clocks, synchronized at some position. Then we move one by a distance $d$ and then stop again. Does it now
Consider the light speeding at $c$ along the light clock of length $l$ on the left of the diagram between $A$ and $B$.
A second, identical light clock is moved along the distance $d$. For simplicity, assume initially that we move the second clock with a speed $v$ such that it ticks exactly $n/2$ times during the move. A tick is one complete cycle of the clock where the light beam runs from $A$ to $B$ and back to $A$.
What is the speed component $v_l$ of the moving clock along $l$, the vertical axis? We have the speed $c$ along the diagonal $a$ and we have defined to move the clock with $v$ along $b$. Consider the time $T$ to be the time it takes along $a$, then $a/T = c$, $b/T = v$ and $l/T = v_l$. We also have $a^2 = b^2 + l^2$. Divide by $T$ to get $c^2 = v^2 + v_l^2$ or $v_l = \sqrt{c^2 - v^2}$.
How often does the moving clock tick until it reaches the end of the move? Since it moves with $v$ along $d$, the time is $T_d = d/v$. During this time, the total vertical movement of the light beam is $T_d v_l = v_l d/v$. Divide by $2l$ to get the count of the moving clock as $N_d = v_l d/2lv$.
Similarly the count for the stationary clock is $N_0 = cT_d/2l = cd/2lv$. The lag factor $L$ as the factor of how the moving clock ticks slower is therefore $$L=N_d/N_0 = v_l/c = \sqrt{c^2 - v^2}/c = \sqrt{1 - (v/c)^2}\,.$$ Should we not have found the Lorentz factor, not its inverse? No, it is just right, since the Lorentz factor relates the time between two ticks of the moving clock and the stationary. Since we count the ticks, we get its inverse.
What does this mean for clock synchronization: If I bring a well synchronized clock from here to there, I cannot avoid having the lag factor $L=\sqrt{1-(v/c)^2}$. But look, if the speed $v$ with which we move the clock is (nearly) zero, $L$ is (nearly) $1$, so there is no lag for very slow movement.
But how bad does it get? Can we measure the lag? Suppose we move a clock with an airplane and, for simplicity, assume it can do $\unit{1000}{km/h}$. We then have $$v/c = 1000\cdot1000/3600/299792458 \approx 9.27\cdot10^{-7}\,,$$ for a lag factor of $$L \approx 1-\sqrt{1-(9.27\cdot10^{-7})^2} = 4.3\cdot10^{-13}\,.$$
Now suppose we fly the clock $\unit{1000}{km}$ away, meaning the time of movement is one hour or $\unit{3600}{s}$. After the move, the moved clock lags by $\unit{3600}{s}\cdot 4.3\cdot10^{-13} \approx 1.5\cdot10^{-9}$ or $\unit{1.5}{ns}$.
Once we stop moving the clock, it runs as fast as before, but it is no longer synchronized: it runs with a lag which depends on the velocity of the move and the time it took — or the distance. Here you can experiment with different values.
The Twin Paradox describes how one twin, traveling with non-trivial speed for some time and returning back home, is then younger than his twin. This is explained in detail by Special Relativity physics. See the Wikipedia Twin Paradox page, for example.
On this page you find wording like "...clock is running slow...". How's that? The explanation I can grok most easily makes use of the Light Clock.
While in the stationary clock, light is bouncing at full speed $c$ between $A$ and $B$, in the moving clock, as viewed from the stationary frame, the light has to travel the path $A\to B' \to A'$. To travel the distance $d$ between $A$ and $B'$ it needs a time $t$ such that $d/t = c$. The distance $d$ can be partitioned into a forward move of the clock by $a$ and the clock size $b$ such that $d^2 = a^2 + b^2$. Divide by $t^2$ to get $$d^2/{t^2} = c^2 = (a/t)^2 + (b/t)^2\,.$$ The term $a/t$ is the speed of how fast the clock is moving, call it $v$. And $b/t$ is the speed left for the light clock to actually tick. Call it $c'$ to get $$ c' = \sqrt{c^2 - v^2}\,. $$
This $c' < c$ is how the light clock runs slower. And not only the clock. The clock is a viable measure of time, so time actually runs slower. How? Well! One way I intuit this is to imagine that my body, as a rough biological clock, ticks by means of metabolism, which comes down to chemical reactions, which again are based on the exchange of electrons in atoms and molecules, which again requires the exchange of virtual photons which $\dots$ are light.
A simple, striking way to say it is: when moving, the light clock gets busy with moving so it has less resources to tick.