Generic "math functions"

It is in Rust and Kotlin at least.

We would still need to use the name log for the two-argument variant though. Albeit probably with an argument label (probably log(_:base:)) which would set it apart from logging.

4 Likes

Rust and Kotlin use ln, but I'm actually having a hard time thinking of any other language that uses this convention. Off the top of my head the C-family, Python, Ruby, Julia, Lisp, Fortran, Mathematica, Java all use log.

Part of this is, I'm sure, bias of mathematicians writing math libraries--in most of the US mathematics community, log is the natural log, whereas physicists and engineers are much more likely to use ln.

I'm pretty meh on this either way, but I'll add it to the alternatives considered section.

3 Likes

ISO 80000-2 (and previously ISO 31-11) recommends ln for the natural logarithm. (As well as lb and lg for base 2 and base 10 respectively, although I don't think those are necessarily important enough to need their own names.)

I would certainly consider the ISO recommendations when deciding on names for mathematical functions.

4 Likes

What about Computable as in computable numbers/functions? It may be inappropriate… but just a thought I haven’t seen proposed.

Yeah, that's been my experience as well — in math books "log" by itself means the natural log, and in other subjects there isn't really a huge consensus. With all the emphasis on using computers in essentially all disciplines of science these days, I'd expect that since most languages seem to have followed mathematicians' lead on this, that other areas will as well.

I don't think there's much danger of anyone getting confused about what log does... (_ :Double) -> Double is not the signature of a function that logs anything. In fact, I think the only thing that'd prevent having an alias to print called log is print's ability to take a single argument of any type, and even then, the correct function could be inferred by whether the code expects a return value (except for let _ = log(2), which is probably for the best because this seems like not the greatest idea). Anyway, the point is I'm not worried about that.

WRT the name, Mathable, is there a reason not to just call it Scalar? Rounding errors aside, would a mathematically-correct Scalar protocol need anything that isn't in Mathable?

@scanon Something that's still unclear to me: what is the reason for namespacing these functions (e.g. Double.Math.sin)? It's not really explained in the proposal.

2 Likes

? In all of high school and college, log was the base 10 log, and ln was the natural log. the way you remember it is log starts with lo which looks like a 10, for base 10, and ln is nl backwards, for ā€œnatural logā€.

if that’s not enough, i got out my old TI-84 calculator, and it has a LOG button and a LN button. Guess what each one does.

But anyway, I think there’s a bigger inconsistency and that’s that the ā€œcurrentā€ mathematical things in Float and Double, like Double.pi, Double.e, Double.squareRoot(_:), are all in the top level namespace. Putting the rest in Double.Math would be inconsistent, especially if we wanted to add more math constants later. It would be really weird if φ lived in Double.Math.phi, but Ļ€ lived in Double.pi.

Plus, I just don’t find the namespacing argument for

let y:Double = Double.Math.sin(x)

instead of

let y:Double = Double.sin(x)

that compelling.

The SIMD types would conform to it, and it would be v v weird if Vector4 conformed to Scalar too.

11 Likes

Mainly to group them in autocomplete so if you start typing Float.Math... you can find all of them easily to aid discovery, and move them out of the way if you're not looking for them. The expectation is that most people who are using these functions heavily will use the free functions--the static methods are implementation hooks that are also available if you need to use them in a context where you don't want to export the free functions. It's not a huge deal, but I think makes it tidier.

pi and squareRoot are where they are partially for historical reasons, but also because they're much more commonly used than anything else. sqrt is also available on T.Math in the newer proposal, and I wouldn't be opposed to adding pi and e there as well. I added a note about this under alternatives considered.

Apparently I'm no one now :stuck_out_tongue:

1 Like

i didn’t see your post, sorry

I think this highlights a more significant problem in Swift. If modules outside the standard library cannot directly and efficiently access intrinsic functionality, and also the standard library does not expose all available intrinsics in a zero-overhead manner, then architecturally Swift is not suited for low-level systems programming.

• • •

I also think we should have at least a general plan for what else will eventually be added to the Math module. For instance, do we want it to include linear algebra / matrices / signal-processing / a Swifty interface to the Accelerate framework / linear programming / convex optimization / numerical differential-equation solvers / etc.

• • •

Regarding the nested namespace (eg. Float.Math), I’d lean toward omitting it. I don’t think we need it, and its presence interferes with using the dot-prefixed shorthand for static Self-returning functions. Besides, pretty much everything a numeric type is capable of can be categorized as ā€œmathā€, so what would we really gain?

1 Like

Builtins are an implementation technique to let us express the mapping from LLVM IR to Swift's public standard library API within Swift itself (to some degree). You don't gain any efficiency or other benefit from, e.g., accessing Builtin.Int32 over the public Int32. The purpose of proposals like this is to expose underlying OS or hardware functionality at the standard library level in a way it can be compiled efficiently, and you don't need direct access to Builtin to get that (nor do you really want it, LLVM's semantics are constantly changing and not always designed all that well with direct user interaction in mind—it's our job as implementers to provide the stable, usable semantics in an efficient way).

15 Likes

Those are mostly things I would expect to have in packages, rather than built into the stdlib-adjacent Math module (a core currency type for shaped arrays and maybe vectors and matrices excepted). ā€œMathā€ as an import in most programming language is pretty well scoped to the elementary functions, and these other operations tend to go in other modules. Swift doesn’t have to follow that model, of course, but I also don’t see a great reason to do something different.

I disagree that this "aids discoverability". I think it does the opposite by stashing these operations under a namespace that people will not know/remember to look for. If I was looking for "sin", I would expect to find either global "sin" or "Float.sin", not to look for "Float.Math" first. Especially if I have seen other mathematical operations directly on Float.

I can see the potential benefit that once you have found Float.Math, you see a list of the math.h functions, but there aren't so many other methods on Float that I would have trouble finding them anyway.

It also makes this code look more complicated than it is - when I see the Math associatedtype, I wonder what kind of magic it is there to provide, since it is not obvious that it's just a namespace.

and move them out of the way if you're not looking for them

I guess, but I don't think this should drive the decision.

The expectation is that most people who are using these functions heavily will use the free functions

Agreed.

It's not a big deal to me either way, but I do think we're better off without the extra level of namespace.

8 Likes

My thought is that having found ā€˜sin’, this makes it easier to say ā€œI wonder what else is available?ā€ and pull up exactly that list. This is especially nice w.r.t. functions like ā€˜root’ and ā€˜exp10’ that don’t exist in most C math libraries, or other operations we might add in the future—you may not even know that you’re looking for them, but this will help you find them.

4 Likes

i love Target!

2 Likes

Ah, I’d missed that detail, and it certainly is a good reason to not call the protocol Scalar!

2 Likes

Rather off topic, but talking of naming, when I first looked into Swift, I found it rather surprising that a modern language used ā€˜print’ as the name of the function that spits out caveman debugging info. It reminded me of when I first learned to program in the early 80s. There’s no ink or printing involved. Personally I preferred the Obj-C ā€˜Log’ vernacular.

1 Like

Well, let’s put it this way:

If someone writes import Math and the only thing they get is a handful of top-level functions that were already available as static methods in the standard library, that’s going to feel underwhelming and incomplete.

If it also gives them vectors and matrices, then surely it will provide BLAS-equivalent operations to use. And at that point, it probably ought to include Fourier transforms as well. So perhaps not the entirety of Accelerate, but at least a Swifty interface to vDSP would seem reasonable for a core Math module.

Obviously I’m not suggesting to start with all that, but I think it’s reasonable to have such a destination in mind.

disagree. sin and cos are basic operations with a lot of use. i would not want to import an entire DSP framework just to use cos(x)