`sqrt(0)` v `sin(0)` inconsistency

Found a small inconsistency in math functions:

import Foundation
sqrt(0) // βœ…
sin(0)  // πŸ›‘ error: ambiguous use of 'sin'
// note: found these candidates:
//	public func sin(_ x: CGFloat) -> CGFloat
//	public func sin(_ x: Float) -> Float
//	public func sin(_ x: Float80) -> Float80
//	public func sin(_ __x: Double) -> Double

IMHO both should be :white_check_mark: or both should be :stop_sign:

1 Like

The behavior is different because sqrt is a generic function implemented in terms of the squareRoot protocol requirement on FloatingPoint, while sin is just a set of function overloads (there is not protocol in the stdlib that sin could be constrained to).

Note that none of these come from Foundation, they're transitive imports from the system C library overlay (the Darwin/Glibc/etc module). Consistent generic bindings are provided the RealModule in Numerics, FWIW.

5 Likes

You provided a good technical explanation why we have this inconsistency. Yet that's an inconsistency.. It does look to me it's possible to fix the inconsistency one way or another, with enough motivation. E.g. another overload could be added that takes Int parameter. Or some "TrigFunctions" protocol could be introduced, etc. Don't shoot the messenger..


Points of differences highlighted with orange diamonds:

// MARK: sqrt
sqrt(0) // πŸ”Ά
    // command clicking brings here: Darwin/C/math:
    // public func sqrt(_: Double) -> Double
sqrt(0.0)
    // command clicking brings here: Darwin/C/math:
    // public func sqrt(_: Double) -> Double
sqrt(int) // πŸ›‘ Cannot convert value of type 'Int' to expected argument type 'Double'

sqrt(double)
    // command clicking brings here: Darwin:
    // public func sqrt(_: Double) -> Double
sqrt(float)
    // command clicking brings here: Darwin:
    // public func sqrt<T>(_ x: T) -> T where T : FloatingPoint
sqrt(cgFloat)
    // command clicking brings here: Darwin:
    // public func sqrt<T>(_ x: T) -> T where T : FloatingPoint
sqrt(anyFloatingPoint) // πŸ”Ά
    // command clicking brings here: Darwin:
    // public func sqrt<T>(_ x: T) -> T where T : FloatingPoint
// MARK: sin
sin(0) // πŸ”Ά πŸ›‘ Ambiguous use of 'sin'

sin(0.0)
    // command clicking brings here: Darwin:
    // public func sin(_ x: Double) -> Double
sin(int) // πŸ›‘ No exact matches in call to global function 'sin'

sin(double)
    // command clicking brings here: Darwin:
    // public func sin(_ x: Double) -> Double
sin(float)
    // command clicking brings here: Darwin:
    // public func sin(_ x: Float) -> Float
sin(cgFloat)
    // command clicking brings here: Darwin:
    // public func sin(_ x: Double) -> Double
sin(anyFloatingPoint) // πŸ”Ά πŸ›‘ No exact matches in call to global function 'sin'

That sin(anyFloatingPoint) doesn't work is probably a more harming inconsistency.

func foo<T: FloatingPoint>(generic: T, existential: any FloatingPoint) {
    sqrt(generic)       // πŸ”Ά
    sqrt(existential)   // πŸ”Ά
    
    sin(generic)        // πŸ”Ά πŸ›‘ No exact matches in call to global function 'sin'
    sin(existential)    // πŸ”Ά πŸ›‘ No exact matches in call to global function 'sin'
}

BTW, I tried import Darwin and it worked in Xcode but not on godbolt, and as import Foundation worked in both β€” I took the path of least resistance and imported Foundation without further research on what minimal import statement would make it compilable everywhere. Once Swift starts encouraging the policy of "thou shalt not import unnecessarily" (by way of issuing the relevant warnings, I mean) β€” I would obey.


Edit: Adding inconsistencies related to generic/existential usage above, for completeness

2 Likes

A sin overload with an integer argument is at best mostly useless, and more likely harmful. This inconsistency only really matters for literals,ΒΉ and zero is the only integer literal that's of any real interest as an argument to sine, but sin(0) is just 0, so adding another candidate to the overload set for sin just to resolve this inconsistency doesn't have much value.

This is the ElementaryFunctions protocol in Numerics. I recommend using it. Ideally it would be part of the standard library, but SE-0246 had to be rolled back due to type checker issues. We hope to revisit it in the future, but for now these API (and more besides) are readily available in the numerics package.

Zero messengers have been shot.


ΒΉ being able to use sin in generic code does have value, but adding an Int overload doesn't get you that. The ElementaryFunctions protocol does, which is another reason to use it.

5 Likes

Might be a compiler bug that we don't favor the DefaultFloatLiteral type (Double) when an "integer" literal is used in floating point context.

3 Likes

We do so when the context is constrained to FloatingPoint, but not when there are just multiple concrete overloads. I think one can argue that this is a bug (or at least non-optimal behavior), but AFAIK the type checker has always handled it so.

2 Likes

So is sqrt(0) :sweat_smile:. I could have used pow or log instead of sin – kind strange that sqrt(4) works and pow(2, 2) or log(10000) don't . More important is the inconsistency between these two API's when using them via generics (as you mentioned) and existentials. I updated the post above to include those examples.

BTW, what would be the proper "minimal" import clause so all those mentioned guys (sin, sqrt, pow, log) compile on both Xcode and godbolt? Would that involve #if?

1 Like

Seriously, use numerics and all of these issues are solved (except for godbolt support, we'll have to work on that). One import for every platform. Generic bindings for all of these operations.

You've identified a definite problem with the existing bindings for these platform C operations as imported to Swift, but I'm not sure why you don't want to use the solution that already exists.

3 Likes

I hear you... Re external packages (even when they are Apple's) in some projects I use them (especially when I use many other packages included non-apple's), in some projects I prefer not using them (especially when that project doesn't need to use any other packages, apple's or non-apple's) and in some instances this is impossible (all those self contained environments like Coderpad, Codility, etc).

I'd appreciate the answer to the above "minimal import" question. Strictly speaking it's unrelated to sin(0)... what do I import to compile sin(0.0)?

1 Like

Is numerics going to be part of this new standard library?

… what new standard library?

1 Like

If you don’t mind all the other stuff that comes in with Foundation, that’s your answer, but that’s a much heavier-weight dependency than Numerics.

A more minimal portable import would be if/when we finally get a name for the C standard library module.

New foundation?

Foundation isn't the standard library, and Foundation has to continue exposing the bindings for these operations that it always has (i.e. the concrete ones from the C library) for source- and binary-stability reasons.

4 Likes

I guess this would make a "minimal" import clause, to use in cases when I can't use external packages:

#if canImport(Darwin)
    import Darwin
#elseif canImport(Glibc)
    import Glibc
#elseif canImport(Foundation)
    import Foundation
#else
    #error("WTH")
#endif

sin(0.0)

A bit mouthful but seems to be as "lightweight" (IRT code size) as possible.

I read this question as: "is the discussed functionality which is currently available in numerics going to be integrated in the standard library"? This link has some further info on the matter:

2 Likes

Thanks @tera @scanon

Maybe a better question is, would ElementaryFunctions ever be 'promoted' to the Standard Library? If I'm understanding the documentation correctly, it sounds like it would fit right in with the other Numeric Protocols.

1 Like

Yes, I mentioned this above:

Ok, thanks for walking me through that, I think I understand this better now. Much appreciated.