Reputation: 1084
I have a simple generic struct defined - its only requirements is that its stored properties be Comparable
:
struct Bounds<A: Comparable> {
let lower: A
let upper: A
}
However, I'd like to provide a couple of specialized initializers for the struct, which would use some math operations to set up the properties.
init(value: Double, tolerance: Percentage) {
self.lower = value * ( 1 - tolerance )
self.upper = value * ( 1 + tolerance )
}
init(value: Measurement, tolerance: Percentage) {
self.lower = value.value * ( 1 - tolerance )
self.lower = value.value * ( 1 - tolerance )
}
The result should obviously be two different structs, where A is a Double
or a Measurement
.
But how do I do this?
I can't provide specialized init methods in the definition as the compiler will complain that Double is not convertible to A
. OK...
I can't provide the init methods in individual extensions constrained by specific types (where A == Double
) as the compiler complains:
Same-type requirement makes generic parameter 'A' non-generic
Maybe I should be using a protocol to which both Double
and Measurement
conform in initialization, but that seems odd, since the Bounds
struct should just care that they both conform to Comparable
.
I feel like I'm either missing something really simple, or trying to do something really misguided with generics. Which is it, SO?
Upvotes: 5
Views: 898
Reputation: 329
The correct answer to this is Martin R.'s "workaround." The issue here is that Comparable
does not have any of these math operations defined on it, or even any guarantee that it is a numeric type at all. It could just as easily be a string, or an array, or any other type that implements Comparable
.
So, yes, you must write extensions constrained to either a common protocol that implements these operators or to the concrete types in question. For example:
extension Bounds where A: FloatingPoint {
init(value: A, tolerance: Percentage) {
self.lower = value * ( 1 - tolerance )
self.upper = value * ( 1 + tolerance )
}
}
Or, if Measurement
does not conform to FloatingPoint
:
extension Bounds where A == Measurement {
init(value: A, tolerance: Percentage) {
self.lower = value.value * ( 1 - tolerance )
self.upper = value.value * ( 1 + tolerance )
}
}
Also, since you probably don't want to use any non-numeric types for your bounds then I would define it as something along these lines:
struct Bounds<A: Numeric & Comparable> {
let upper: A
let lower: A
}
Upvotes: 0
Reputation: 59506
The main problem is that you are performing operations on types where that operations have not been defined.
E.g.
value * ( 1 - tolerance )
The operation -
between Int
and Tolerance
where is defined?
This is how you can fix it
protocol BoundsType: Comparable {
func *(lhs: Self, rhs: Self) -> Self
var prev: Self { get }
var next: Self { get }
init(double: Double)
init<M:Measurement>(measurement:M)
}
protocol Percentage {
associatedtype BoundsType
var toBoundsType: BoundsType { get }
}
protocol Measurement {
associatedtype BoundsType
var toBoundsType: BoundsType { get }
}
struct Bounds<A: BoundsType, P:Percentage, M:Measurement
where P.BoundsType == A, M.BoundsType == A> {
let lower: A
let upper: A
init(value: Double, tolerance: P) {
self.lower = A(double:value) * (tolerance.toBoundsType.prev)
self.upper = A(double:value) * (tolerance.toBoundsType.next)
}
init(value: M, tolerance: P) {
self.lower = A(measurement:value) * tolerance.toBoundsType.prev
self.upper = A(measurement:value) * tolerance.toBoundsType.next
}
}
Upvotes: 0
Reputation: 539765
Not exactly what you are asking for, but a possible workaround (Swift 3):
extension Bounds where A: FloatingPoint {
init(value: A, tolerance: A) {
self.lower = value * ( A(1) - tolerance )
self.upper = value * ( A(1) + tolerance )
}
}
let b = Bounds(value: 4.0, tolerance: 0.1)
print(b.dynamicType) // Bounds<Double>
Upvotes: 1
Reputation: 73186
You could restrict your generic more strongly, to access in-protocol blueprinted methods for transforming e.g. a Double
value to the type of the generic.
protocol FromDoubleTransformable {
static func doubleToSelf(from: Double) -> Self
}
/* drawback: only types conforming to 'FromDoubleTransformable'
will be able to be used as generic in 'Bounds' below */
extension Int: FromDoubleTransformable {
static func doubleToSelf(from: Double) -> Int {
// simple example without any out-of-bounds checking
return Int(from)
}
}
struct Bounds<A: protocol<Comparable, FromDoubleTransformable>> {
let lower: A
let upper: A
init(value: Double, tolerance: Double) {
self.lower = A.doubleToSelf(value * ( 1 - tolerance ))
self.upper = A.doubleToSelf(value * ( 1 + tolerance ))
}
}
let foo = Bounds<Int>(value: 120, tolerance: 0.1)
print(foo.dynamicType) // Bounds<Int>
print(foo.lower, foo.upper) // 108 132
Upvotes: 0