On Mar 4, 2020, at 19:12, Richard Damon <rich...@damon-family.org> wrote:
> 
> Yes, because of the NaN issue, you sort of need an 'Almost Total Order' and 
> 'Really Truly a Total Order', the first allowing the small exception of a 
> very limited (maybe only one) special value that breaks the true definition 
> of Total Order so that we could call Floats to be an Almost Total Order.

The obvious answer is to have PartialOrder < NaNOrder < TotalOrder, where a 
type is NaN-ordered if it’s partially ordered, and its subset of all self-equal 
objects is totally ordered.

I can’t imagine when a generic AlmostTotalOrder that didn’t specify how it 
fails could be useful; a NaNOrder is useful all over the place. A median that 
ignores NaN values requires NaNOrdered input. Even a median that does something 
stupid with NaN—see below.

Of course there might be other kinds of almost total orders that might be 
useful. If anyone runs into one, they can write their own ABC with the proper 
relationships to the stdlib ones. Or even propose it for the stdlib. But they 
couldn’t do anything useful with a general AlmostTotalOrder if it had to handle 
both NaN and whatever their different case was.

> This wouldn't help the median issue as having median reject being given Float 
> values because they don't form a true Total Order would be much worse than 
> the issue of it getting confused with NaNs.

Well, if you want to handle floats and Decimals and do something stupid with 
NaN values so the user has to be careful, it seems to me the right thing to do 
is require NaNOrder but document that you do something stupid with NaN values 
so the user has to be careful.

> The presence of NaN in the float system does add a significant complexity to 
> dealing with floating point numbers. Sometimes I wonder if since Python 
> supports dynamic typing of results, might not do better by removing the NaN 
> value from Floats and Decimals, and make the operations that generate the NaN 
> generate an object of a special NaN type.

The thing is, in most cases you’d be duck typing and get no benefit, because 
float and floatnan both have the same methods, etc. Sure, when you’re 
type-checking with isinstance you could choose whether to check float|floatnan 
or just float, but how often do you write such type checks? And if you really 
want that, you could write a Float ABC that does the same thing, although IIRC 
ABCs only have a subclasshook rather than an instancehook so you need a new 
metaclass:

    class FloatMeta(ABCMeta):
        def __instancecheck__(self, instance):
            return isinstance(instance, float) and not math.isnan(instance)

    class Float(metaclass=FloatMeta):
        pass

People have experimented a lot with similar things and beyond in static type 
systems: you can define a new type which is just the non-NaN floats, or just 
the finite floats, or just the floats from -1 to 1, or just the three floats -1 
or 0 or 1, and the compiler can check that you’re always using it safely. (If 
you call a function that takes one of those non-NaN floats with a float, unless 
the compiler can see an if not isnan(x) or a pattern match that excludes NaN or 
something else that guarantees correctness, it’s a TypeError.) Fitting this 
into the type algebra (& and | and -) is pretty easy. I’m not aware of anyone 
translating that idea to dynamic type systems, but it could be interesting.

_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/XCPFUQCK7GUIPHKPYQETPWPPA4XNOVLI/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to