SE-0246: Generic Math(s) Functions

The review of SE-0246: Generic Math(s) Functions begins now and runs through March 20, 2019.

The proposal is written by @scanon.

The review manager is @John_McCall; I am kicking off the review on his behalf.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to John as the review manager via email or direct message on the forums. If you send John email, please put "SE-0246" somewhere in the subject line.

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?

  • Is the problem being addressed significant enough to warrant a change to Swift?

  • Does this proposal fit well with the feel and direction of Swift?

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thank you for contributing to Swift!

14 Likes

I mainly agree with the proposal with some remarks:

  • I would strongly prefer ln as the natural logarithm function name. It has a lot of advantages (preferred nomenclature in a lot of fields, avoids conflicts with logging frameworks...) over log.
  • I think this proposal review is the perfect moment to debate the inclusion of ** as the power operator in the Swift standard library.

Yes.

Yes, using this compact syntax foreign to Swift seems reasonable in this mathematical context.

Yes, as a heavy Python user I feel having well-designed math functions and operators is an important feature for a language to be usable in a lot of contexts.

Read the pitch thread and the proposal.

2 Likes

I tried searching in the pitch thread, so apologies if I overlooked something: Is there a particular argument for working with angles in degrees for the trig functions?

(Also, swift needs namespaces :wink:).

The trig functions all work in radians. That's the standard mathematics definition of these functions. We might add support for degrees at some future point, but my first choice would be to do that by defining an Angle type.

(Ah, I see that the proposal has a typo here. I'll put up a fix for that).

Edit: fixed.

5 Likes

In degrees?!? Clearly I was not reading closely enough the first time, but that is definitely what the proposal says. Is that serious or a typo?

Edit: Posted too slow. Already answered. Thanks @scanon.

Finally! I've read the whole thing. I believe it is laying a good foundation for future Swift math libs, std or 3rd party.

Foundation has the Measurement API which I personally believe should provide sin, cos, tan, etc as

func sin(_ x : Measurement<UnitAngle>) -> Double
5 Likes

This is something we can certainly propose and provide in the future :slight_smile: If you haven’t already filed one, we’d appreciate a Radar!

3 Likes

I think the ConformingType.Math indirection feels unusual and confusing but I understand and appreciate the need for it and think it is a good trade off. The Math module also makes the need to write out the above code line by using free functions. Part of me wonders if the Math module should just be included without the requisite import. The point of a standard library is usually to give expected methods to a beginning user and the complexity of sin, cos, log, etc not being there on the getgo I think is unintuitive.

Yes I strongly believe Swift should redefine this functionality to be extensible and more generic. This is imperative for a modern programming language and the easier it is to support things like SIMD and complex functions the better Swift's prospects.

Yes I strongly believe that this fits well into Swift's current style especially with how numerics are defined. Swift must have great support for low-lying mathematical operations of any precision or chosen field if it is to be a go to language for large swaths of people.

Otherwise I do think that log should potentially ln because log–though correct–may be confused with some sort of logging feature. I would most prefer that all the functions become log(base: Value = SomeWayToGet.e) and exp(base: Value = SomeWayToGet.e) as this is the most swifty in my opinion. I understand that from the current SIMD implementation these names were chosen and in a mathematical context e, 2, and 10 are commonly the only values ever used; however, I think the language should try and shed this historical baggage to give the best ergonomics for mathematicians, data scientists, app developers, financial analysts, or a student. Plus this would help add arbitrary types where log and exp do not make sense for particular values–say a field with no concept of e.

I believe this is better then most other languages in terms of how the implementation is derived. Many languages make it very difficult for a user to add their own integral or complex types. This use case may be rare but should be possible if a user is to trust the language to be reliable for their uses cases. Rust's type system also allows a similar api and there are libraries I have used that use traits in the place of these protocols. These are more generic and allow a particular new type to only inherit the exact feature set it needs (for example imagine a Exponentiable type) but I think these are overly verbose for Swift and beyond the scope.

I did an in-depth read and went and looked at my own Utility libraries for doing these functions generically in my frameworks as well as how my other favorite languages do it. I'm very excited for this to possibly be added.

1 Like

I feel like "elementary" is too generic a term for ElementaryFunctions for people to assume the math meaning (I hadn't heard of it before this proposal). Because of that (and with exceptions like gamma and erf), I like TranscendentalFunctions better, even though the exponential stuff is technically not. If we're just tossing names into the ring, Wikipedia also suggests "AnalyticFunctions" is a valid term, but that's edging back into overly generic territory.

I don't like the name "Real". For one, it's not obvious that it means "real number", but more importantly I would expect non-floating-point types to be able to conform to something called "Real" (integers? rationals? fixed-point? Foundation.Decimal?). It makes contrasting between floating-point numbers and real numbers ("real real numbers"?) a little harder. Some alternatives, none of which I like: MathematicalFloatingPoint, MathematicalScalar, FloatingPointMath, FPMath, FloatingPointTranscendentalFunctions (is it even worth it at this point?).

I'm also a little surprised Real is a protocol itself instead of just a typealias, since it has no requirements and no additional semantic meaning and is unlikely to grow either.

Oh, and I like ln better than log.

8 Likes

The real numbers are closed under multiplicative inverses and square roots*. The integers are not. I would be extremely surprised by integers conforming to a "Real" protocol.

It doesn't have requirements, but represents a useful abstraction against which to write generic code. For example, it's the protocol that I would make a Complex type generic over:

struct Complex<T> where T: Real {
  var real: T
  var imag: T
  ...
}

In addition, there are some operations that are defined on it--they simply aren't customization points--like signGamma. It would also probably be the right place to define constants like e if we decide to add them.

It's rather like SignedInteger; it doesn't add a lot of operations itself, but it's still an extremely useful point in the Protocol graph. If we don't have a name for it, then we end up making it harder for users to write generic code because they have to conform to FloatingPoint & ElementaryFunctions to get the operations they want.

(*) yeah, yeah, the positive reals.

7 Likes

[citation needed]

:-)

4 Likes

Why is that? It doesn't seem like complex numbers implicitly need the ElementaryFunctions. Maybe I'm just being naive, though, assuming no one will want the polar/exponential form right away.

I think there are plenty of use cases that don't need them, but there's also enough of a critical mass that do need them that in practice the alternative will be most generic code being written against Complex<T> where T: ElementaryFunctions instead of Complex. That's not terrible, but each constraint that we require for user code to be useful does impose a small burden on people, and they pile up.

1 Like

+1 for ln.

log2, log10 and ln are all unambiguous, while log could be interpreted as any one of them. The fact that modern languages such as Rust and Kotlin has gone in this direction, makes it feel less of a divergence too, and more like joining them in moving forward.

17 Likes

Is import Math even possible to implement? I thought submodule infrastructure was yet to be designed. Moreover, why are these mathy operations modularized but the SIMD types were put into top-level stdlib?

2 Likes

It's not a submodule. It's a module that sits next to the stdlib. There's a working implementation linked in the proposal that you can experiment with.

This was addressed in the SIMD proposal thread: SE-0229 — SIMD Vectors - #112 by scanon. This would be the first library that's "distributed next to the standard library" (the third point there), but the first two points still apply:

  • We need the SIMD types in the stdlib because we want to use them to implement features in the stdlib. That's not a concern for the Math module because we have the escape valve of the implementation hooks T.Math.exp( ) that we can use in the standard library.
  • The functions defined by the Math module don't change how (or if) other modules are imported, whereas a module containing the SIMD types would have.
3 Likes

Apologies, before asking I checked the SIMD proposal and the approval review comments but didn't go back to every message in the thread. Indeed, what you said there makes sense :)

1 Like

IMO this is something long overdue, so I'm beyond thrilled we're finally getting this.

I would like to echo the idea that we go with ln over log for the natural logarithm. The ambiguity brought on by log has always felt unwarranted, and naming this ln would avoid any potential ambiguity. I'd also suggest we keep log10 in addition to ln, just so we don't have any confusion around ln being the natural log, while log would be the base 10 log.

+1 from me! Slight preference for ln rather than log and indeed I like @nuclearace's idea about keeping both... but would be happy either way.