Reputation: 654
I'm trying to write a generic function in Swift that takes any number, Int
, Float
, Double
, etc. by setting the generic type to <T: Numeric>
. So,
func doSomething<T: Numeric>(with foo: T, and bar: T) -> T {
// body
}
Most of the body will work for any Numeric
type, but along the way, I need to find the remainder... which means I need a different approach for FloatingPoint
types vs. Integer
types.
When T
is an Int
, this means using the modulo operator:
let remainder = foo % bar
However, when T
is a Double
or Float
, the modulo operator doesn't work and I need to use the truncatingRemainder(dividingBy:)
method:
let remainder = foo.truncatingRemainder(dividingBy: bar)
Where I'm struggling is to find a way to sift these out. In theory, I should just be able to do something like this:
var remainder: T
if T.self as FloatingPoint { // <- This doesn't work
remainder = foo.truncatingRemainder(dividingBy: bar)
} else {
remainder = foo % bar
}
This, of course, leads to this error, since FloatingPoint
has an associated type:
error: protocol 'FloatingPoint' can only be used as a generic constraint because it has Self or associated type requirements
I understand this error... essentially, FloatingPoint
is generic with a still-generic associated type for the adopting type to define.
However, I would like to know the best way to conditionally run select blocks of code that only apply to some more narrowly-defined Protocol than defined with the generic param (T
).
Specifically, is there a way to define a single generic function to handle all Numeric
types, then differentiate by FloatingPoint
vs. Integer
types within.
Upvotes: 0
Views: 567
Reputation: 1342
There are a couple issues at foot here.
Unless I am misreading the documentation for Numeric, a matrix type could reasonably conform to Numeric. If a matrix were passed into your function, you would have no real way to compute remainders because that's not a well-defined notion. Consequently, your function shouldn't be defined on all Numeric types. The solution is to define a new protocol which describes types with well-defined remainders. Unfortunately, as Alexander notes in the comments...
There are various technical reasons for this restriction, mostly centering around difficulties when a type would conform to a protocol in multiple ways.
I think you have two reasonable solutions.
A. Make two different overloads of doSomething
, one with T: FloatingPoint
and the other with T: BinaryInteger
. If there is too much shared code between these implementations, you could refactor the doSomething
into multiple functions, most of which would be defined on all of Numeric
.
B. Introduce a new protocol RemainderArithmetic: Numeric
which describes the operations you want. Then, write explicit conformances for all the particular types you want to use e.g. extension Double: RemainderArithmetic
and extension UInt: RemainderArithmetic
.
Neither of these solutions are particularly appealing, but I think they have one key advantage: Both of these solutions make clear the particular semantics of the types you are expecting. You are not really anticipating types other than BinaryInteger
's or FloatingPoint
's, so you shouldn't accept other types. The semantics of remainders can be extremely tricky as evidenced by the wide range of behaviors described on the wikipedia page for mod. Therefore, it probably isn't very natural to be defining a function the same way across integers and floating points. If you are certain that is what you want to do, both of these solutions make your assumptions about what types you are expecting explicit.
If you aren't satisfied by either of these solutions and can provide more details about what exactly you're trying to do, we might be able to find something else.
Upvotes: 2
Reputation: 534893
This doesn't sound like a good use case for a generic. You'll notice that operators like +
are not generic in Swift. They use overloading, and so should you.
Upvotes: 1