I am mostly brainstorming. Regardless of the final name choice, there will inevitably be lots of people aliasing it according to their own preference, since there is so much precedence in so many different directions. As long as the implementation is available generically in some form, we can all alias it easily.
In favor of the direction of the pitch. In particular, the introduction of a new, separate Math module is an important precedent to set. It would be great to see more domain-specific modules embedded with Swift moving forward.
One more consideration against the Math namespace: it forgoes the ability to use the static member functions directly with type context. For example, when passing a parameter to a function (without importing the Math module), the difference can be as significant as
I'm not sure why we'd have member functions and free-floating aliases for everything. That seems like a crutch to comfort developers from certain other languages - in which case, is it unreasonable to ask them to bring it themselves?
As for namespacing the math functions: that's only useful if we expect alternative implementations to be a thing (e.g. reduced precision/ffast-math-style assumptions, perhaps?). That might be better expressed as a kind of FastFloat<T> wrapper though, because it could cover more operations (like equality checking, assuming NaN and -0 don't exist).
Even in the prior case where we want these alternative implementations on Float/Double directly, I think it's still valuable to provide "default" conformances without a namespace. This mirrors the way String provides multiple implementations of Collection, but we removed the explicit String.CharacterView to make it an un-namespaced conformance.
Not a big fan of the Mathable name. Mathematical sounds nicer.
The names of these functions are strongly conserved across languages, but they are not universal; we could consider more verbose names inline with Swift usual patterns. sine , cosine , inverseTangent , etc. This holds some appeal especially for the more niche functions (like expm1 ), but the weight of common practice is pretty strong here; almost all languages use essentially the same names for these operations.
I fully agree, with the caveat that in some cases, argument labels can dramatically improve readability without taking away from standard terminology (even if they are formally part of a declaration's name).
The free functions are the natural spelling of these operations. No one expects to see x.sin() any more than we want to see x.adding(1) instead of x + 1.â The static functions are implementation hooks that are necessary to implement the free functions in a generic fashion, with the added benefit that they can be used in situations where importing the free functions may be undesirable.
[â ] to some extent, this is a matter of taste, and you can make an argument that you really do want to use x.sin( ), I guess, but sin(x) is explicitly given in the API Design Guidelines as an example of following precedent, so I think the guidance is pretty clear here.
I would push back on verbose naming for mathematical operations. There is a benefit in keeping mathematical notation compact. It is more helpful to be able to visually parse all of an equation at once and see the overall pattern to it, than it is to spell out each constituent operation in a beginner friendly way. For example, in my opinion the following code would become less readable if the mathematical operations were more verbose leading me to have to break up the equations into more pieces spread over more lines.
func getCircleIntersections(p1: CGPoint, p2: CGPoint, r1: CGFloat, r2: CGFloat) -> (CGPoint, CGPoint)? {
let L = (p2 - p1).length
let A = (pow(r1,2) - pow(r2,2))/(2*pow(L,2))
let disc = 2*(pow(r1,2) + pow(r2,2))/pow(L,2) - pow((pow(r1,2)-pow(r2,2)),2)/(pow(L,4)) - 1 //discriminant
if disc > 0 {
let B = 0.5*sqrt(disc)
let v0 = CGVector(dx: p1.x + p2.x, dy: p1.y + p2.y)
let v1 = CGVector(dx: p2.x - p1.x, dy: p2.y - p1.y)
let v2 = CGVector(dx: p2.y - p1.y, dy: p1.x - p2.x)
let v3 = 0.5*v0 + A*v1 + B*v2
let v4 = 0.5*v0 + A*v1 - B*v2
return (CGPoint(x: v3.dx, y: v3.dy), CGPoint(x: v4.dx, y: v4.dy))
} else {
return nil
}
}
When reading dense mathematical code the clarity of an individual exponent operation is the least of my concerns. The closer my code can become to compact handwritten mathematical equations the better.
I'd expect to see the whole proposed API surface, either in the proposal or in a linked implementation, so that it's clear what the additions will be. For example, on my first read I missed that the list of functions in the section that follows what I quoted (Functions not defined on Mathable) will all be included in the Math module. Are you planning to add one of those for a future version of the proposal?
For exponentiation specifically, I stuck with the pow name in NumericAnnex for a few iterations and realized that ** really is just much nicer. I think it would be worthwhile to consider adding that operator and its associated precedence to this proposal. It has strong precedent in many languages.
I donât think sine(x) and cosine(x) is an improvement over Float.sin(x) and Float.cos(x). In all of high school and college i never once wrote the word âsineâ on a piece of paper so itâd be weird to start writing it in code. (By the same argument, never have i ever written âsqrtâ on a piece of paper either so I have no problem with .squareRoot().) sin and cos also have the benefit of being 3 characters long each, so code with a lot of trig functions lines up nicely.
The same reasoning goes for log(), i really donât know what percentage of people even know that itâs short for âlogarithmâ. For obvious reasons this function should not be available as a top-level function, which is also a pretty strong argument against Swift.sin(_:) and Swift.cos(_:).
I donât use the other math functions enough to care how theyâre spelled.
Iâm in favor of ** over pow(a, b). might lose a potential operator candidate for vector dot products though.
I remember when writing this function I thought that even the pow() function was too verbose and wished I could use a Python style ** operator. Here is how it compares.
//Swift, C, Java etc.
let A = (pow(r1,2) - pow(r2,2))/2*pow(L,2)
let disc = 2*(pow(r1,2) + pow(r2,2))/pow(L,2) - pow((pow(r1,2)-pow(r2,2)),2)/pow(L,4) - 1
//Python, Ruby
let A = (r1**2 - r2**2)/2*L**2
let disc = 2*(r1**2 + r2**2)/L**2 - (r1**2-r2**2)**2/L**4 - 1
Finally, in a perfect world where ^ wasn't already used up on bitwise XOR
//Julia, R, MATLAB
let A = (r1^2 - r2^2)/2*L^2
let disc = 2*(r1^2 + r2^2)/L^2 - (r1^2-r2^2)^2/L^4 - 1
Some random thoughts, most of these are loosely held, and I am not a numerics expert :-)
I am in love with this proposal.
The name needs to be bikeshed for sure, thanks for including a list of candidates in alternates considered, and please include more options that come up in this thread. Of the options I've seen, I'd +1 Mathematical
I agree with your general sense that there are strong terms of art here, and inverseHyperbolicTangent is ridiculous - it doesn't lead to clarity.
I think it would be really nice to pull sqrt into the common framework somehow, even if it leads to a duplicate squareRoot member. The later can be deprecated and (e.g.) hidden from code completion. The numeric programing community will just laugh at Swift if they have to use squareRoot but can use exp and atan2 like they expect.
Fixing pow w.r.t. pown is great. Thing to consider: should we also eventually support pow on integer/integer arguments? Does this cause a problem?
While I think that we should keep the basic terms of art w.r.t. these functions, I personally think that this is an opportunity to fix points of confusion - concretely, for the atan2 example you mention, we can and probably should add argument labels to fix the bug. This won't affect code completion, but will lead to more clear code.
It would be very nice to see a full list of the free functions that won't be provided with import Math after the proposal. You point out that some are out of scope because they are already there, but it isn't clear what the state of things is after you're done. copysign and some of the others listed are pretty important.
Thank you so much for driving this forward. I'm thrilled that you're aiming to get this into 5.1!!
to add to this, usually when iâm using math functions, iâm copying down a formula from wikipedia or some academic paper. i have no idea how the formula actually works or what it means mathematically,, my goal is just to accurately reproduce it in code. If the wikipedia article says âatanhâ and i see Float.atanh in code completion, i can be pretty sure thatâs the function i want. If the function is called Float.inverseHyperbolicTangent, and the article says âatanhâ, i am not going to find the right function.
While I think cos, sqrt, and other common operations should probably stay as they are, I wonder if there's opportunity to improve on the C names in a few other areas. For example, why is lgamma not called lnGamma? Similarly, expm1 is less clear to me than expMinusOne or even expSub1, and it has always bothered me that log is not ln.
There's definitely an argument for prior art, but I think there are also a few functions where the C name is non-obvious from a mathematical context, and in those cases I think Swift has room to improve.
Might be a silly question, but canât we make these functions more readable for non-mathematics of us? I never liked the short forms of all these operator functions. When I encounter something like erf my first impression would be, what the heck?!
Swift already names things like random instead of rand, which is more readable and better understandable.
Thatâs an excellent point, but the good news is that the pitch is just slightly out of sync with where the implementation has gone, so this isnât a problem.
Specifically, the âMathableâ protocol (or whatever name we use) now only provides the math functions, and we introduce a new âRealâ protocol that is Mathable & FloatingPoint (much like @xwuâs approach). Most people would write code directly against this protocol most of the time, but in SIMD contexts you can use âSIMD where Scalar: Realâ, and with e.g. a Complex type, you would have the Mathable conformanceâand you can also write against Mathable & Numeric. This works pretty nicely, and makes the naming of Mathable less important because most users wonât use it as a constraint.
I think we could clarify some of these (lgamma might be logGamma, for instance) but we donât want to improve readability for the people who donât use the functions at the cost of making it worse for people who use them all the time. âerfâ is the name of the functionâyou can call it âerrorFunctionâ but I donât think thatâs much better for non-statisticians (the naming guidelines might lead us to just âerror(x)â, but thatâs even worseâit sounds like itâs reporting an error), and it is much worse for statisticians.