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

Michal J Gajda mgajda at mimuw.edu.pl
Thu Aug 5 03:51:10 UTC 2021


Yue,

Yes, it seems it would work
This useful workaround works uses exception effect.
So it may have two disadvantages:

1. You may only catch the effect in a monad that can catch exceptions, like
IO.
2. It does not give a clear path for handling collections of computations.

Note that DSLs also need to redefine Eq as effectful class. Then `(==) ::
Applicative expr => expr a -> expr a -> expr bool`.

This solution may be preferred if You have:

* a DSL already
* additional metadata attached to values, like provenance, security access
level, versioning, Merkle hash, memo hash, equivalence class etc.
* want to handle error values without exposing Yourself to imprecise
exceptions etc.

It has disadvantage of redefining several standard classes, and we do not
yet seem to have a library that would properly provide these type classes
parameterized over any Applicative.

But again, the need for more principled handling of error values will
likely come as software scales.
—
  Best regards
    Michał



On Wed, 4 Aug 2021 at 16:56 YueCompl <compl.yue at icloud.com> wrote:

> `Eq` relies on the established `Bool` type, then can we solve it, if given
> algebraic effects & handlers, by Church-encoding an effectful Boolean type?
> e.g.
>
> true === \a b -> a
> false === \a b -> b
> cmpWithNaN === \a b -> perform ComparingWithNaN
>
> Then (==) and (/=) in `Eq`, together with friends in `Ord` like (>) (<)
> all return `cmpWithNaN` when at least one NaN is involved.
>
> This mechanism is open so all kinds of anomalies can be handled similarly,
> otherwise even we have all NaN, Inf, Underflow, Overflow etc. handled well,
> there must be more situations we haven't thought of.
>
>
> On 2021-08-04, at 22:24, Michal J Gajda <mgajda at mimuw.edu.pl> wrote:
>
> 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
> analytics in Haskell. (Similar presentation was also well received on
> 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ł
>
>
> --
  Pozdrawiam
    Michał
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20210805/54bcc47c/attachment-0001.html>


More information about the Haskell-Cafe mailing list