[Haskell-cafe] Why not some subclass of Floating to model NaNs as some handleable bottom?
compl.yue at icloud.com
Thu Aug 5 08:22:56 UTC 2021
But I'd think algebraic effects is orthogonal to Monad/Applicative/Functor, it's capable to separate effects from pure code on its own.
Current standard classes in Haskell were designed without algebraic effect in mind. Especially the `Num` and its descendants, allied with `Eq` `Ord` and friends, I do see they can never allow `NaN` to be a legal existence, so there sure need another hierarchy of classes works similarly but embraces IEEE 754 at the same time. The hardware de facto has established intrinsic implementation of NaN/Inf semantics, including propagation during arithmetics and special rules during comparisons, we just need ratification in our type system, maybe retrospectively.
Or `NaN` is actually another [billion-dollar mistake](https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions <https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions>) we'd rather to avoid?
Aside from that, currently `ArithException` can only be caught within `IO` monad, but it can occur in evaluation of "pure" code. As I understand it, monad is a sublanguage to express "order" in execution (along with its bigger purpose of effect tracking), while evaluation of pure code has no "order" semantics thus in no need of monads. But pure code has nesting structures nevertheless, so to express the awareness/handling of NaN, "divide by zero" and similar situations, in a specific subsection of the pure code block, monad (i.e. IO here) based exception handling is not ideal here, as it sorta works by delimiting related pure computations into monadic steps, but that's unnatural or abuse of the execution order expressing device.
Algebraic effects & handlers is the perfect device for such support, as far as I see it now, but strangely I don't feel it's absolute necessary in doing this job. The situation is still messy in my head...
> On 2021-08-05, at 11:51, Michal J Gajda <mgajda at mimuw.edu.pl> wrote:
> 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, Merkel 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
> On Wed, 4 Aug 2021 at 16:56 YueCompl <compl.yue at icloud.com <mailto: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 <mailto: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
>> 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
>> 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
>> 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.
>> On Wed, Aug 4, 2021 at 4:00 PM YueCompl <compl.yue at icloud.com <mailto: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 <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.
>>> On 2021-08-04, at 02:38, Michał J Gajda <mjgajda at gmail.com <mailto: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 <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.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the Haskell-Cafe