That sounds like a rationale for including basic trig functions in the standard library itself.
i also wouldnât want vectors and matrices in the same library as DSP stuff. the difference in utility just doesnât justify relegating them to a highly domain-specific math library.
Counterpoint: Why not?
If youâre doing math things, then you should be able to import Math
and have access to powerful mathematical structures and algorithms.
I need vectors and matrices for graphics programming. I donât need linear algebra or DSP frameworks.
Iâm pretty sure that we do not, and should not, tailor the Swift core libraries to the particular use-cases of specific individual programmers.
Separating them into distinct modules allows people to pull in the parts they need. Thatâs a basic design pattern of the language. Why have modules at all if weâre not going to use them for organization?
iâm sure this is true of most people using vectors and matrices. Doing a dot product on a Vector3
is a lot more common than doing a Fourier transform. Commonly used stuff should go in the standard library. Uncommonly used stuff should go in a separate module. As far as i know this is exactly what we do.
âŚright. Iâm saying that uncommonly used math stuff, like Fourier transforms, should go in a separate module, named Math
, and not in the standard library.
I also think linear algebra types should go there, although it sounds like you would prefer them in the standard library itself.
i think trig functions, vectors, fixed-size matrices, and quaternions should go in the standard library. I think n-by-n matrices, linear algebra, and DSP stuff should go in a Math
module. I thought you wanted all of those things to go in the Math
module.
Great, glad we got the confusion cleared up. :-)
Several of the operation types included in ElementaryFunctions
aren't actually algebraically closed as promised by the API. For example the log
of a Real
type does not always map to a Real
type. Same thing with sqrt
. So that's an API promise that can't be mathematically delivered.
As you know, we talked about this issue in several places on these forums before. One solution is to define a complete set of algebriac types and how the elementary functions map between those. This can have huge performance advantages for numerical computations.
My attempts at doing this were a bit kludgy with Swift, although reading the opaque result types this morning makes me think that opaque types solve some of those issues.
log
and sqrt
, as commonly defined on FloatingPoint
, are both closed. They return NaN
for inputs for which there is no meaningful result. This behavior is defined by IEEE 754 in the case of sqrt
and recommended in the case of log
.
If you want to get a complex result instead, the normal way to do that would be to explicitly convert the real input to a complex value first. I don't think that Swift should silently do this promotion--Swift doesn't generally do silent promotion. If I add two Int8
s and the result would overflow, I don't get an Int16
back. Instead I have to convert them to Int16
and then perform the addition.
I think the model you want to have is an interesting one, but it's not the current Swift arithmetic model, and would imply much broader changes to the language.
I'm drawing a very strong distinction here between the storage type (e.g., Double
, Int16
) and the algebriac type (e.g., Real
, Imaginary
). Promoting between storage types is a very different problem than moving between algebriac types.
I guess I'm less inclined to 'do things the way they've always been done', and would rather use this opportunity to do things in a way that would advance programming practices.
Right, but that's a higher-level of abstraction than this proposal targets. This proposal is about getting the necessary building blocks on what you term "storage types" to enable you to build those higher-level generic abstractions.
Okay, that makes sense. Two questions then,
-
Is this a necessary abstraction layer? I mean, don't we then have to redefine
cos
yet again to simply take anyMathType
and return anotherMathType
? (ByMathType
I mean some algebriac type). -
If this really is a necessary abstraction layer, then hesitate to use
Real
in this context because it suggests some sort of compliance with the algebriac type, whereas in reality it's reflecting compliance with the IEEE 754 type. Right?
Yes, it is a necessary abstraction layer. Real
is an apt name for the protocol. The concrete types that will conform to Real
will have IEEE 754 semantics not because they conform to Real
but because they conform to FloatingPoint
. Other types are free to conform to Real
and model the reals in other ways that arenât constrained by IEEE 754.
But like I said above... this definition of Real promises that the Log of a real number maps to a real number. How does this make it an appropriate name? That is a promise that canât be kept. Youâre going to have to elaborate on your answer.
Here is a protocol of math types that stays closed under those math types.
Ah, I see: you object to that promise. Any model of the reals necessarily has compromises, and the utility of the model derives in part from what is omitted as much as it does from what is included.
We return only the principal value for sqrt
and other multivalued functions, for example. You could argue that we should return all roots where there are a finite number of them (or maybe even design something to return all countably infinite roots), but I would say that it is less computationally useful.
It is already the case for addition, subtraction, and multiplication that these are guaranteed to return a value of type Self
in the Numeric
protocol (i.e., for all numeric types). This is a useful guarantee. We make this guarantee here for elementary functions defined on Real
. It is imperfect but useful; otherwise, adding an angle to the sine of a value would always require casting in generic algorithms.
I am not exercised about such a design potentially placing limits on future models that allow for more complex relationships among numeric types. As @scanon has said, that would be a much larger undertaking, and if it is to be shipped with Swift itself then Iâm comfortable with saying that the design must accommodate existing use cases. That is, we would stipulate that the way complex, real, and imaginary types interact must be such that an imaginary result can be transparently propagated as NaN in a real-valued expression.
This is my point. Either 1) call the protocol Real
, but then make it follow the correct algebraic structure of real numbers, or 2) own the fact that your staying within the contrived IEEE 754 definition of reals by naming it something more appropriate (like Numeric
does).
As I wrote, all models are necessarily imperfect. Real
is a very good name as that is what we are approximating. I donât see why it needs to have caveats any more than UnsignedInteger
.