Closed d-torrance closed 6 days ago
Can we also have InfiniteNumber
be a subclass? Then in functions that accept infinity we need to check that the only allowed Number
is +/-infinity
Done! Now things like atan infinity
work.
I'm all in favour of such a change. I do feel like this is a bit hacky though.
Until now, one of the basic principles of the class system of M2 is that only BasicList
and HashTable
have meaningful inheritance trees, in the sense that user-created classes necessarily have the same data type as its ancestor. If we're going to break this principle, maybe we should do it in a more systematic way?
BTW this also reminds me of the RingElement
vs Number
debate (don't have a comp atm so can't look up the relevant link).
I guess I'm proposing that we add Number
to BasicList
and HashTable
as possible ancestors of user-defined classes. :) (That list isn't complete either -- users can also subclass FunctionClosure
and PythonObject
. )
Right now, the subclasses of Number
are a hodge-podge of different wrappers around various C structs from GMP, MPFR, and MPFI. The actual implementations don't matter, but what's important is that we can perform arithmetic operations on them, plug them into functions, etc. In this sense, Constant
and InfiniteNumber
are the same. Sure, under the hood, they're lists, but we'll never actually want to do anything list-like with them other than access their elements so we can get the information we need to do number-like things with them.
Here's the Number
/RingElement
discussion: #1519
I went ahead and made IndeterminateNumber
a subclass of Number
for good measure.
There's only InexactNumber
left!
It was already good to go (it's defined in classes.dd
):
i1 : ancestors InexactNumber
o1 = {InexactNumber, Number, Thing}
o1 : List
Ah I see! In response to Paul's point, I'd like to repeat the suggestion of having UnionType
s from https://github.com/Macaulay2/M2/issues/1979. It would make a lot of method declarations easier.
Wait, I thought the idea was for Number
to be the type family for all types of numbers, some implemented as lists and some not. Why add an intermediary?
I realized I was needing to define peek
and toExternalString
for each of these top-level Number
types since they were losing their inheritance on BasicList
. I figured adding an intermediate type would simplify this. Also, this lets us use all the Constant
arithmetic methods that use numeric
for any other top-level number types. InfiniteNumber
already had its own, but now things like the following work:
i1 : 2 + indeterminate
o1 = NotANumber
o1 : RR (of precision 53)
Sorry, I don't follow. What's an example of this problem? I think the name ListNumber
is misleading.
The main issue is that there were a few methods that no longer worked for these types since they no longer inherited from BasicList
that we might want to keep around. toExternalString
was the big one. It was calling simpleToString
and returning <<a list>>
, which resulted in a syntax error building the documentation. I figured peek
was also nice to have. There might be others.
I figured a parent class for all Number
types that are implemented using lists would be nice to simplify installing these sorts of methods. So rather than:
toExternalString Constant := toExternalString InfiniteNumber := toExternalString IndeterminateNumber := lookup(toExternalString, BasicList)
we can do:
toExternalString ListNumber := lookup(toExternalString, BasicList)
I'm definitely open to changing the name.
toExternalString
methods were correct to begin with, and your change highlighted that problem. For instance, this is so unnecessary and bizarre:
i7 : toExternalString(-infinity)
o7 = new InfiniteNumber from {-1}
2. Further proving the point, I think this is a regression:
```m2
i12 : toExternalString pi
o12 = new Constant from {symbol pi,pi0,piRRi0}
ListNumber
as well, but very different from the others.Number
as a TypeFamily
or UnionType
? If so, I think we should do it properly, defining those types and making the necessary changes in the interpreter.
Dan's old suggestion was that installing a method on something like Number = new TypeFamily from {Constant, InfiniteNumber, ...}
would go through and install the method on each member one by one. I think this is fine for now, but in the long term my hope is something like
Number = new UnionType
Constant = new Type of Number from BasicList
Then Number
would be an interface-only type, and Constant
would be a type of BasicList
. Then lookup(func, Constant)
would first check (func, Number)
and its ancestors, then (func, BasicList)
and its ancestors.
(sidenote: I think new Module of Vector from {...}
should really be new Vector of Module from {...}
).
Fair enough -- I'll revert the last commit.
Currently, we can create subclasses of
Number
:But we can't actually create any instances of these types:
We modify things so that we can create subclasses of
Number
whose instances are lists or hash tables:Such objects will forget that they are lists or hash tables (except for low-level things like accessing values with
#
), but provided that we install anumeric
method, then mostNumber
methods should work out of the box:The reason to do all of this is to simplify how we define methods for the
Constant
type, which is currently a subclass ofBasicList
for things likepi
andii
. By makingConstant
a subclass ofNumber
, we can use inheritance and not have to worry about installing a ton of methods to make sure that things likesin pi
will work.Before
After