Generic "math functions"

That sounds like a rationale for including basic trig functions in the standard library itself.

1 Like

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.

1 Like

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?

4 Likes

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. :-)

1 Like

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 Int8s 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.

1 Like

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.

1 Like

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,

  1. Is this a necessary abstraction layer? I mean, don't we then have to redefine cos yet again to simply take any MathType and return another MathType? (By MathType I mean some algebriac type).

  2. 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.