Reputation: 574
Can someone here maybe take a look at the code and tell me what's wrong with it? I essentially try to build a couple of generic functions that operate on certain raw types like Int
, Float
, Double
etc.
Unfortunately I can't get it working properly. This is the code that works (partially):
// http://stackoverflow.com/a/24047239/2282430
protocol SummableMultipliable: Equatable {
func +(lhs: Self, rhs: Self) -> Self
func *(lhs: Self, rhs: Self) -> Self
}
extension Double: SummableMultipliable {}
func vec_dot<T where T: SummableMultipliable>(a : [T], b: [T]) -> Double {
assert(a.count == b.count, "vectors must be of same length")
var s : Double = 0.0
for var i = 0; i < a.count; ++i {
let x = (a[i] * b[i]) as Double
s = s + x
}
return s
}
Now when I write:
var doubleVec : [Double] = [1,2,3,4]
vec_dot(doubleVec, doubleVec)
It returns the correct result of 30
. Ok, so far so good. Things get weird when I try to pass an array of Int
s:
extension Int : SummableMultipliable {}
var intVec : [Int] = [1,2,3,4]
vec_dot(intVec, intVec)
Bam! Exception thrown at:
let x = (a[1] * b[1]) as Double
* thread #1: tid = 0x139dd0, 0x00000001018527ad libswiftCore.dylib`swift_dynamicCast + 1229, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
* frame #0: 0x00000001018527ad libswiftCore.dylib`swift_dynamicCast + 1229
frame #1: 0x000000010d6c3a09 $__lldb_expr248`__lldb_expr_248.vec_dot <A : __lldb_expr_248.SummableMultipliable>(a=Swift.Array<T> at 0x00007fff5e5a9648, b=Swift.Array<T> at 0x00007fff5e5a9640) -> Swift.Double + 921 at playground248.swift:54
frame #2: 0x000000010d6c15b0 $__lldb_expr248`top_level_code + 1456 at playground248.swift:64
frame #3: 0x000000010d6c4561 $__lldb_expr248`main + 49 at <EXPR>:0
frame #4: 0x000000010165b390 FirstTestPlayground`get_field_types__XCPAppDelegate + 160
frame #5: 0x000000010165bea1 FirstTestPlayground`reabstraction thunk helper from @callee_owned () -> (@unowned ()) to @callee_owned (@in ()) -> (@out ()) + 17
frame #6: 0x000000010165ab61 FirstTestPlayground`partial apply forwarder for reabstraction thunk helper from @callee_owned () -> (@unowned ()) to @callee_owned (@in ()) -> (@out ()) + 81
frame #7: 0x000000010165bed0 FirstTestPlayground`reabstraction thunk helper from @callee_owned (@in ()) -> (@out ()) to @callee_owned () -> (@unowned ()) + 32
frame #8: 0x000000010165bf07 FirstTestPlayground`reabstraction thunk helper from @callee_owned () -> (@unowned ()) to @callee_unowned @objc_block () -> (@unowned ()) + 39
frame #9: 0x0000000101fedaac CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
frame #10: 0x0000000101fe37f5 CoreFoundation`__CFRunLoopDoBlocks + 341
frame #11: 0x0000000101fe2fb3 CoreFoundation`__CFRunLoopRun + 851
frame #12: 0x0000000101fe29f6 CoreFoundation`CFRunLoopRunSpecific + 470
frame #13: 0x000000010208f2b1 CoreFoundation`CFRunLoopRun + 97
frame #14: 0x0000000101658be8 FirstTestPlayground`top_level_code + 3784
frame #15: 0x000000010165b3ba FirstTestPlayground`main + 42
frame #16: 0x0000000103cd9145 libdyld.dylib`start + 1
I tried to perform a different casting:
let x = Double(a[i] * b[1])
Error: Could not find an overload for 'init' that accepts the supplied arguments.
let y = a[i] * b[1]
let x = Double(y)
Error: Cannot invoke 'init' with an argument of type 'T'.
Next, I tried:
let y = Double(a[i]) * Double(b[1])
let x = y
Error: Cannot invoke '*' with an argument list of type '(Double, Double').
I tried out many more things. As soon as I try to pass Int
as a generic type, nothing works anymore.
Maybe I'm just missing something fundamental here or I am just too dumb to understand generic programming. In C++, I'd be done in 2 seconds.
Upvotes: 4
Views: 1918
Reputation: 45180
The reason of the exception is, as you may have noticed, the downcast. To be fair, what you're trying to do is illegal and the compiler shouldn't allow you to do that at the first place.
Since vec_dot
only knows that T
is SummableMultipliable
, it cannot convert it into Double
just like that (how it is supposed to know that it's not a String
?).
The simple way to get rid of this problem is, well, getting rid of the generic constraints and use function overloading instead:
func vec_dot(a: [Double], b: [Double]) -> Double {
assert(a.count == b.count, "vectors must be of same length")
var s: Double = 0.0
for i in 0 ..< a.count {
let x = (a[i] * b[i])
s += x
}
return s
}
func vec_dot(a: [Int], b: [Int]) -> Double {
return vec_dot(a.map({ Double($0) }), b.map({ Double($0) }))
}
var doubleVec: [Double] = [1, 2, 3, 4]
vec_dot(doubleVec, doubleVec) // 30.0
var intVec: [Int] = [1, 2, 3, 4]
vec_dot(intVec, intVec) // 30.0
If you still want to stick with protocols and generics, refer to Martin R's answer instead.
Upvotes: 3
Reputation: 539805
When called with an Int
array, a[i] * b[i]
is an Int
and cannot be cast
to Double
with as
.
To solve that problem, you can change your vec_dot
function to return a T
object instead of a Double
.
To make the initialization var s : T = 0
work, you have to make SummableMultipliable
derive from IntegerLiteralConvertible
(to which Int
and Double
already conform):
protocol SummableMultipliable: Equatable, IntegerLiteralConvertible {
func +(lhs: Self, rhs: Self) -> Self
func *(lhs: Self, rhs: Self) -> Self
}
func vec_dot<T where T: SummableMultipliable>(a : [T], b: [T]) -> T {
assert(a.count == b.count, "vectors must be of same length")
var s : T = 0
for var i = 0; i < a.count; ++i {
let x = (a[i] * b[i])
s = s + x
}
return s
}
Example:
var doubleVec : [Double] = [1,2,3,4]
let x = vec_dot(doubleVec, doubleVec)
println(x) // 30.0 (Double)
var intVec : [Int] = [1,2,3,4]
let y = vec_dot(intVec, intVec)
println(y) // 30 (Int)
Alternatively, if the vector product should always produce a Double
, you can
add a doubleValue()
method to the SummableMultipliable
protocol:
protocol SummableMultipliable: Equatable {
func +(lhs: Self, rhs: Self) -> Self
func *(lhs: Self, rhs: Self) -> Self
func doubleValue() -> Double
}
extension Double: SummableMultipliable {
func doubleValue() -> Double { return self }
}
extension Int : SummableMultipliable {
func doubleValue() -> Double { return Double(self) }
}
func vec_dot<T where T: SummableMultipliable>(a : [T], b: [T]) -> Double {
assert(a.count == b.count, "vectors must be of same length")
var s : Double = 0
for var i = 0; i < a.count; ++i {
let x = (a[i] * b[i]).doubleValue()
s = s + x
}
return s
}
Remark: As @akashivskyy correctly said, the loop should be written more swiftly as
for i in 0 ..< a.count { ... }
If you want to get fancy and impress or puzzle your co-workers then you can replace the entire loop with a single expression:
let s : T = reduce(Zip2(a, b), 0) { $0 + $1.0 * $1.1 }
Upvotes: 5