Reputation: 18940
Let's say I've defined such a protocol:
protocol EuclideanPoint {
func distance(other: Self) -> Double
func dimension() -> UInt
}
Now I'd like to extend [Float]
and [Double]
to adopt that protocol.
But the following code:
extension [Float]: EuclideanPoint {
func distance(other: [Float]) {
return Double(zip(self, other).map{a, b in pow(a-b,2)}.reduce(0, combine: +))
}
func dimension() {
return UInt(self.count)
}
}
is invalid because of the error
error: constrained extension must be declared on the unspecialized generic type 'Array' with constraints specified by a 'where' clause
I found similar questions (like this), but the suggested solution is to use extension CollectionType where Generator.Element == S { ... }
, but in this context it leads to the error:
error: protocol 'CollectionType' can only be used as a generic constraint because it has Self or associated type requirements
Is there any solution to this?
EDIT:
using the proposed solution:
protocol DoubleConvertibleType {
var doubleValue: Double { get }
}
extension Double : DoubleConvertibleType { var doubleValue: Double { return self } }
extension Float : DoubleConvertibleType { var doubleValue: Double { return Double(self) } }
extension CGFloat: DoubleConvertibleType { var doubleValue: Double { return Double(self) } }
extension Array where Element : DoubleConvertibleType {
func distance(other: Array) -> Double {
return Double(zip(self, other).map{ pow($0.0.doubleValue - $0.1.doubleValue, 2) }.reduce(0, combine: +))
}
func dimension() -> UInt {
return UInt(self.count)
}
}
gives [Double]
and [Float]
the .distance()
and .dimension()
methods. Yet [Double]
or [Float]
cannot be used in place of something that is required to conform to the EuclideanPoint protocol, producing the error:
error: type '[Double]' does not conform to protocol 'EuclideanPoint'
Upvotes: 4
Views: 2354
Reputation: 73186
EDITED
The following solution is somewhat generic, conforms to protocol EuclidianPoint
, and is based upon two assumptions:
That we're allowed to include a generic type constraint for your blueprint for method distance
in your EuclideanPoint
protocol, and that, instead of argument type being Self
, we'll use a generic ([T]
). We will, however, ascertain (at compile time) that [T]
is of the same type as Self
(and here, Self
of [Double]
, [Float]
or [Int]
type), and ascertain that [T] conforms to the protocol EuclidianPoint
.
That you're ok that we leave functional programming techniques such as .map
and .reduce
out of this specific application, and focus only on attaining a "generic array adopted to euclidian protocol". These .map
, .reduce
etc feats in Swift are indeed neat and useful, but are in many applications just wrappers for behind-the-hood for-loops, so you won't lose any performance over doing things in a manual imperative style. In fact, .reduce
is known to perform quite non-optional due to repeated array-copy-assignments while reducing the array (I won't go into this more here...). Anyway, perhaps you can make use of my example and tweak it back to something more functional-paradigmy.
We begin by a custom type protocol, MyTypes
, that will act as an interface for which types we want to include in our generic. We also add the slightly updated EuiclidianPoint
protocol, where we use protocol MyTypes
as a type restraint to the generic T
used in the distance (...)
function blue-print.
/* Used as type constraint for Generator.Element */
protocol MyTypes {
func -(lhs: Self, rhs: Self) -> Self
func +=(inout lhs: Self, rhs: Self)
}
extension Int : MyTypes { }
extension Double : MyTypes { }
extension Float : MyTypes { }
/* Extend with the types you wish to be covered by the generic ... */
/* Used as extension to Array : blueprints for extension method
to Array where Generator.Element are constrainted to MyTypes */
protocol EuclideanPoint {
func distance<T: MyTypes> (other: [T]) -> Double?
func dimension() -> UInt
}
Note that I've changed the Double
return of distance
to an optional; you may handle this as you will, but in case the lengths of self
and other
arrays differ, or types Self
and [T]
differ, there will be some need of showing non-conformance -- I'll use nil
for this here.
We can now implement implement our extension of Array
by the EuclidianPoint
protocol:
/* Array extension by EuclideanPoint protocol */
extension Array : EuclideanPoint {
func distance<T: MyTypes> (other: [T]) -> Double? {
/* [T] is Self? proceed, otherwise return nil */
if let a = self.first {
if a is T && self.count == other.count {
var mySum: Double = 0.0
for (i, sElement) in self.enumerate() {
mySum += pow(((sElement as! T) - other[i]) as! Double, 2)
}
return sqrt(mySum)
}
}
return nil
}
func dimension() -> UInt {
return UInt(self.count)
}
}
Note that in the inner if clause of the distance
function we use an explicit down cast to T
, but since we've asserted that elements of Self
are of type T
, this is ok.
Anyway, with this, we're done, and we can test our "generic" array extensions, which we note now also conforms to your protocol EuclidianPoint
.
/* Tests and Examples */
let arr1d : [Double] = [3.0, 4.0, 0.0]
let arr2d : [Double] = [-3.0, -4.0, 0.0]
let arr3d : [Double] = [-3.0, -4.0]
let arr1f : [Float] = [-3.0, -4.0, 0.0]
let arr1i = [1, 2, 3]
let _a = arr1d.dimension() // 3, OK
let _b = arr1d.distance(arr2d) // 10, OK (A->B dist)
let _c = arr1d.distance(arr1f) // nil (Incomp. types)
let _d = arr1d.distance(arr3d) // nil (Incomp. sizes)
let _e = arr1i.distance(arr1d) // nil (Incomp. types)
/* for use in function calls: generic array parameters constrained to
those that conform to protocol 'EuclidianPoint', as requested */
func bar<T: MyTypes, U: protocol<EuclideanPoint, _ArrayType> where U.Generator.Element == T> (arr1: U, _ arr2: U) -> Double? {
// ...
return arr1.distance(Array(arr2))
/* We'll need to explicitly tell the distance function
here that we're sending an array, by initializing an
array using the Array(..) initializer */
}
let myDist = bar(arr1d, arr2d) // 10, OK
Ok!
A note still remaining from my first answer:
Extension of generic type Array to protocol was actually just recently asked here:
The consensus is the you cannot do a generic extension of array to a protocol in the "neat swifty" way that you possible expect. There are however workarounds to mimic such a behaviour, one being the one I've used above. If you are interested in another method, I suggest you look into this thread.
Upvotes: 1
Reputation: 57114
Foreword: As @difri correctly mentions in the comment we can not yet create an extension conforming to a protocol when using generic constrains at the same time. There are already a couple of radars open - searching for "extension of type with constraints cannot have an inheritance clause" will yield a couple of them.
Actual Answer: Building on @LeoDabus awesome answer and my experimenting I came up with the following:
protocol DoubleConvertibleType {
var doubleValue: Double { get }
}
extension Double : DoubleConvertibleType { var doubleValue: Double { return self } }
extension Float : DoubleConvertibleType { var doubleValue: Double { return Double(self) } }
extension CGFloat: DoubleConvertibleType { var doubleValue: Double { return Double(self) } }
extension Array where Element : DoubleConvertibleType {
func distance(other: Array) -> Double {
return Double(zip(self, other).map{ pow($0.0.doubleValue - $0.1.doubleValue, 2) }.reduce(0, combine: +))
}
func dimension() -> UInt {
return UInt(self.count)
}
}
Testing it with
let arr1 = [1.5, 2, 3]
let arr2 = [5.5, 2, 3]
let arrD = arr1.distance(arr2)
Somewhat correctly prints
16
To get the correct answer (at least what I would suspect) you have to wrap the distance
into sqrt
:
return sqrt(Double(zip(self, other).map{ pow($0.0.doubleValue - $0.1.doubleValue,2) }.reduce(0, combine: +)))
Which then correctly prints
4
Upvotes: 1
Reputation: 26917
You can extend SequenceType
instead of Array
extension SequenceType where Generator.Element == Float {
//
}
Upvotes: 1