[Cuis-dev] New updates posted on Github

Juan Vuletich juan at jvuletich.org
Wed Jun 26 12:04:26 PDT 2019


I had a chance to work on a few things with Andres Valloud, here is his 
summary of what we just posted on Github.

First, we turned Complex into a loadable package once more.  Usually the 
disadvantage is that loading such packages results in code overrides.  
For instance, since the message sqrt will have to change behavior (so 
that -1 sqrt returns a complex number, rather than fail outright), one 
would expect that loading Complex would have to modify the base system 
to introduce the new feature.  This time, however, we engineered how 
Complex interacts with the rest of the system so that loading the 
Complex package does not result in any base system code modifications.  
Of course, loading Complex causes -1 sqrt to evaluate to 0 + 1i, as before.

The key to accomplishing this was to introduce a new exception, 
NegativePowerError, which by default comes in without a defaultAction 
method.  When Complex loads, it simply provides defaultAction so that 
said exceptions are handled by invoking complex number arithmetic.  We 
hope this mechanism illustrates one way of making different code 
packages coexist more harmoniously.  Please include Complex as a 
requirement to your packages as appropriate.

Second, and along the same lines, we also found that the arithmetic 
error exceptions looked very similar and yet their implementation was 
very different.  Further, there were some discrepancies in how they were 
used and handled (some would do things like ^ZeroDivide signal..., and 
some others simply ZeroDivide signal...).  In addition, for floating 
point numbers, some of the results were not following the IEEE spec.

As an aside, it seems trivial, but even the following four operations 
require *substantial* spec lawyering to get right:

0.0 - 0.0
0.0 - -0.0
-0.0 - 0.0
-0.0 - -0.0

Looks trivial --- it isn't.  And then you consider +, /, and *.  So, we 
reorganized these exceptions under a general class called 
ArithmeticMessageError.  This new abstract exception has three instance 
variables that will look familiar immediately: receiver, message, and 
arguments.  These are provided by the location where the exception 
occurs, which in turn allows e.g. defaultAction to resume the 
computation as required without having to either a) guess what went 
wrong, or b) needing a multitude of different exceptions depending on 
what actually happened.  Currently, ArithmeticMessageError has two 
subclasses for situations that are special enough to deserve their own 
class: division by zero, and raising a negative number to powers.

In the "rationals" (i.e. Integer and Fraction), things like 1/0 are 
undefined. But in floating point land, dividing aFloat by zero can be 
one of three things (!): +INF, -INF, and NaN.  No, this was not our 
idea, but we care that things work according to the manual so that our 
system enables as many folks as possible.  Since floating point requires 
special handling, there is a ZeroDivide exception to do so.  Something 
similar happens with NegativePowerError.  In the "rationals", things 
like -8 raisedTo: 1/3 make sense.  That's because, generally,

a^(p/q) = (a^p)^1/q

Note the sign of the result, when it is defined, depends on the sign of 
a and (essentially) the "parity" of p/q.  Again, this was not our idea, 
but we gather the world mostly works this way :).

With floating point numbers, however, it's not obvious what a^b is when 
a and b are floating point numbers.  Suppose a is negative, and b is 1.0 
/ 3.0.  Looking at that floating point 1/3, does that mean that if the 
last bit of the mantissa is zero, that the result does not exist?  And 
that if the last bit of the mantissa is 1, then it's ok to take 
something *like* the cubic root of a?  What does that even mean when b 
is the result of who knows how many operations, each of which is 
presumably on the order of one ulp off?  Thus, said powers fail, and the 
result is NaN for floating point numbers.

If you are curious, consider the fantastic behavior of the following two 
examples.  Both fail gracefully, as they should.

-32 raisedTo: (1/5.0) asTrueFraction
-32 raisedTo: (1/5.0) successor asTrueFraction

Note that if Complex is loaded, however, a^b will happily work for 
floating point numbers and produce complex numbers, as expected.  For 
instance, take G_6.  Since -8 is 8 * w_3, then one of the possible cubic 
roots is 2 * w_2 (and so is 2 * w_5).

If this behavior does not work for you, look for the relevant 
defaultAction method, and also take a look at floatErrorValue.  We tried 
to make these moldable and easy to change so that people can configure 
the system to their liking, while requiring minimal modifications.

Third, all these improvements to exceptions brought up the problem that 
SUnit was catching errors by handling Error.  This is not ideal because, 
as just exemplified above, any exception can provide a defaultAction 
method that recovers its occurrence.  But handling Error like this:

[3 / 0] on: Error do: [:ex | ex return: 'everybody knows this is bad']

prevents Error from running its defaultAction, and so a recoverable 
error always becomes an unrecoverable error.  Instead, the above code 
should say this:

[3 / 0] on: UnhandledError do: [:ex | ex return: 'evidently this error 
was unexpected']

Consequently, TestResult exError should answer UnhandledError, rather 
than Error.  We applied this change.  Please consider how you handle 
exceptions with these observations in mind to maximize flexibility and 

Andres thinking the above should be at least mostly correct, Juan 
approving without being too worried.

More information about the Cuis-dev mailing list