Wednesday, October 12, 2011

About Complex in Squeak

I noticed that Complex is present in Squeak trunk and not in Pharo, though not used. So I suggested to create a shared external package common to the two distributions in this thread with a few other technical questions. This can benefit to Pharo users, and since the base of interested people did not seem that large to me, it can't be bad to group forces.

I got quite a few answers, thanks to all participants.  Scattering my answers in many posts would lead to a lot of repetitions. Grouping the threads into a big string would be misbehaved. Though all subjects are tied together: this is a pretext for blogging. So here are my comments.

Why Complex was removed from Pharo?
I don't have a mailing list ref available, but I think it was on a path to a reduced image (not to say minimal). Complex is not used in a base image so it could be loaded on demand. It's the never ending discussions of whether constructing an image by assembling modules (packages?) or by stripping... Full vs Core...
Another argument was that Complex implementation was not that good (understand not universal - see below). So we should better let it leave the image and give a chance to alternate solutions (driven by different trade off/paradigms - see below).

Why Quaternion?
Quaternion are to SO3 what Complex are to SO2. They are often used in engineering, specially in robot arm control or spatial vehicle control because not suffering from Euler/Cardan angle singularities. They also replace trigonometric operations by algebraic ones (an advantage if there is no hard-wired trigonometry in FPU they just require sqrt). You can find Quaternion in squeaksource.
Octonions are generalization in spaces of dimension 4, so can be used in generalized relativity... I'm not aware of any Smalltalk implementation yet, maybe this niche is too narrow ;)

Why I've suggested to rename Complex as ComplexNumber?
Because more expressive. I think it was also to match isComplexNumber. But, sure, we can dissociate the two...
And also for stupid digression about an eventual distinction required by a symbolic Complex - apologize for the follow ups in the thread ;) .

Why I suggested to implement isComplexNumber rather than isComplex?
Because the collisions and are not just imaginary ;) but happening for real as Juan reported.
There is an issue opened in mantis.

Why -1 sqrt raise an Error instead of answering one of the complex roots?
This raises the question of the paradigm we want to use...
It's very much like Fraction and Integer.
In Smalltalk, every Fraction with a denominator = 1 is automatically reduced to an Integer. Thus we can consider that every Integer is a special kind of Fraction (with an implicit 1 denominator). It's thus logical to have Integer behave like a Fraction and respond to numerator, denominator, fractionPart, integerPart and even answer true to isFraction and to selfasFraction (if it quacks like a duck... I made the changes recently in trunk).
So we could consider the same with ComplexNumber and Number. Every Number is a Complex with a null imaginary part. Thus we could let Number behave like a Complex and extend its mathematical functions over complex domain (-1 sqrt, 2 arcCos, -1 argCosh etc...), and also let it answer to some Complex specific protocol like conjugated , imaginaryPart, realPart... In such paradigm, every Complex with a null imaginary part would be automatically reduced to a Number.

This discussion already took place before...
Above generalization is quite nice. But unlike the case of Integer and Fraction we are changing the behaviour of sqrt and potentially breaking compatibility.
Some applications do expect and require an Exception. If the Exception does not happen, then it will lead to delayed failure, or worse, incorrect results.
One possible incorrect result would be to fill your bank account we imaginary €.
Especially, knowing that 1 i isNumber now answers true, defensive "type" checks that worked once are now by-passed, which worsen the situation. See digression below.

The opposite point of view is to preserve compatibility and implement both behaviours.
The solution adopted in Squeak is to let the programmer explicitly force a Number to behave as a Complex with -1 asComplex sqrt. In such case, the Complex must not automatically be reduced to a Number when its imaginary part is null. this way we can also chain complex behaviors, and that's what we currently get with 2 asComplex sqrt arcSin we don't need to force a second time 2 asComplex sqrt asComplex arcSin.
Another solution would be to distinguish the selectors (like -1 sqrt versus -1 complexSqrt) and let Number and Complex respond to both protocols... But that's many mathematical functions to duplicate.

I don't think Squeak solution is that bad, especially if we consider that Complex are rarely used. It is somehow very much like statically typed languages FORTRAN/C++ so some of you might not like it ;)
The biggest grief is that we don't have static typing (not every receiver is literal), so we are forced to always send asComplex when in doubt. This can lead to scattering more such messages than manageable... That might happen in case of large usage of complex, but until now, nobody never complained.
So I would not change implementation without very careful thoughts, and probably not change it at all in trunk. Because such change is a complete shift of paradigm with potentially nasty side effects for Complex-unaware-apps.
If we want such change, I vote for removing Complex from image (the arguments already raised in Pharo).

Why 1 i isNumber answers true?
This was just to let this assertion work (0 i = 0) = (0 = 0 i), originated here... OK, they don't behave the same, but still can be equal (or shall they not?).
The second argument was that a Complex is a Number as the name doesn't tell - well ComplexNumber is more expressive, I warned you ;)

Why I want to revert this change?
1) This change did pierce now obsolete isNumber defence... And expectations: belongs to R suddenly became belongs to C.
If you analyze senders of isNumber, just tell me how many are equipped to handle a Complex? I tried it once.
It's the same compatibility problem as -1 sqrt...
2) Since both Number and Complex answer true to isNumber, then we logically shifted to the more general point of view that every Number is a ComplexNumber.
Unfortunately, this is not true, it does not match current paradigm. A Number does not behave like a Complex, so isNumber distinction is now quite useless.

The side effects are not imaginary ;), again this broke Quaternion implementation for example. I had to commit a quick workaround (x isNumber and: [x isComplex not]) but it's overkill, and just a smell that isNumber does not discriminate enough.
We now badly need isRealNumber - where real must be understood as not imaginary - see below.

Why reverting Complex>>isNumber rather than creating Number>>isRealNumber?
1) Please don't confuse isRealNumber and isFloat, that happens each time I open this can of worms. So I prefer to repeat myself, we have no representation of reals in Squeak, only of some special kinds of reals, Integer, Fraction and the degenerated inexact rounded fractions (I named Float).
We could represent many others (like AlgebraicNumber in MathMorph).
But real numbers are uncountable... See
And we can only represent a countable subset of the real numbers... See
That's my first reason: this selector is confusing.
2) With current implementation of Complex, a Complex currently means two things:
- if imaginary part is not null, then it is a real ComplexNumber, well I mean unreal...
- if imaginary part is null, then it is a real number that behaves like a Complex
Shall 0 i isRealNumber answer true? The question will arise inevitably...
That's my second reason: this selector is confusing.
What we really seek is behavesLikeARealNumber. Ouch! I wish I never see such specialization in trunk ;)
Ah, the good old time when
  • isComplex did mean isKindOf: Complex
  • isNumber did mean isKindOf: Number (what Complex are not).
The alternative is to shift the paradigm and really let Number behave like Complex as told above, and assume all implications or reject the problem out the image... I have no easy solution handy.