[Haskell-cafe] Why not some subclass of Floating to model NaNs as some handleable bottom?

Michal J Gajda mgajda at mimuw.edu.pl
Wed Aug 4 14:24:31 UTC 2021

```The infamous `NaN /= NaN` makes only sense for `NaN` originating as a
result, since we cannot compare `NaN`s originating from different
computations.
But it breaks `Eq` instance laws as needed for property tests.
That is why comparison on `NaN` is better handled by `isFinite`,
`isANumber` predicates.
Note that beside `NaN` we also have other anomalous values, like
Underflow, Overflow, +inf and -inf.
These are all error values, and can hardly be treated in any other way.
And they all need to be handled for floating points.

Yes, comparing `NaN` with anything should give a rise to another error value.
That means that the only way out is making Either Error Float, and
then `(>=) :: Either Error Float -> Either Error Float -> Either Error
Bool`
So basically we need to lift all `Num` operations to the `Either Error` Monad.

That is probably best way to fix the problem: once error value
appears, we need to treat it consistently throughout entire
computation.
At the same time, we do not want a single error value to dominate
entire computation, so that is why we treat collections of
computations as computations that give a collection of good results
and a collection of errors separately.
If we take this into consideration, we notice that most interesting
computations occur on collections of values, and thus yield a
collection of results, not just a single output.

That is one takeaway from the referenced presentation on data
Data Science Europe. It should be on YouTube by now.)

Example of a 3D rotation is instructive: if NaN appears for any single
coordinate, we can get a useful results for all other coordinates, and
thus narrow impact of an error.
If the next step is projection on X-Y coordinates, then NaN or
Over/Under-flow within Z does not affect the result.

To my understanding, that is also the reason why IEEE mandated special
treatment of error values: most of the computations happen on large
matrices, vectors etc, and crashing for each single NaN would be a
true disaster.
It can be even ignored, when the NaN is computed for an energy
component within a single frame of long-running simulation, and the
error disappears within a single time step.
--
Cheers
Michał

On Wed, Aug 4, 2021 at 4:00 PM YueCompl <compl.yue at icloud.com> wrote:
>
> Thanks Michał,
>
> I feel less confused as I realized the non-halting possibility per bottoms, from your hint.
>
> I too think the signaling NaN is dreadful enough, so fortunately it's rarely seen nowadays.
>
> Actually what's on my mind was roughly something like "Maybe on steroids", I'm aware that NaN semantics breaks `Num` (or descendants) laws, as seen at https://gitlab.haskell.org/ghc/ghc/-/blob/master/libraries/base/GHC/Float.hs
>
> > Note that due to the presence of @NaN@, not all elements of 'Float' have an additive inverse.
>
> > Also note that due to the presence of -0, Float's 'Num' instance doesn't have an additive identity
>
> > Note that due to the presence of @NaN@, not all elements of 'Float' have an multiplicative inverse.
>
> So it should have been another family of `Num` classes, within which, various NaN related semantics can be legal, amongst which I'd think:
>
> * Silent propagation of NaN in arithmetics, like `Maybe` monad does, seems quite acceptable
> * Identity test, namely `NaN` /= `NaN` - this lacks theoretical ground or not?
> * Comparison, neither `NaN` > 1 nor `NaN` <= 1 - whether or not there's a theoretical framework for this to hold? Maybe `Boolean` type needs enhancement too to do it?
>
> No such family of `Num` classes exists to my aware by now, I just can't help wondering why.
>
> Cheers,
> Compl
>
> On 2021-08-04, at 02:38, Michał J Gajda <mjgajda at gmail.com> wrote:
>
> Dear Yue,
>
> Bottom has much weaker semantics than an exception: it means You may never get a result and thus will never handle it!
>
> Another reason is convenience: it is frequently the case that giving NaN in a row of numbers is much more informative than crashing a program with an exception and never printing the result anyway.
>
> Finally IEEE special values have clear propagation semantics: they are basically Maybe on steroids.
>
> The problem with this approach is indeed a silent handling.
>
> But in order to fix this, it is better to add preconditions to specific algorithms that do not allow IEEE special value on input (`isFinite` or `isNotNaN`) and then track the origin of the special value with the methods like those described here: https://skillsmatter.com/skillscasts/14905-agile-functional-data-pipeline-in-haskell-a-case-study-of-multicloud-api-binding
>
> Never throw an error without telling exactly why it happened and exactly where to fix it :-). Using bottom is last resort; exceptions likewise.
> --
>   Cheers
>     Michał
>
>

--
Pozdrawiam
Michał
```