Generic raw types (Int, Float, Double) generate strange error messages
Can someone here maybe take a look at the code and tell me what is wrong with it? I basically try to create a couple of common functions that work with certain types of type Int
, Float
, Double
etc.
Unfortunately, I cannot get it to work properly. This is the code that works (in part):
// 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 30
. Ok, so far so good. Things get weird when I try to pass an array Int
s:
extension Int : SummableMultipliable {}
var intVec : [Int] = [1,2,3,4]
vec_dot(intVec, intVec)
Bam! Exception thrown:
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 another casting:
let x = Double(a[i] * b[1])
Error: Could not find an overload for "init" that takes the supplied arguments.
let y = a[i] * b[1]
let x = Double(y)
Error: Unable to call "init" with an argument of type "T".
Next I tried:
let y = Double(a[i]) * Double(b[1])
let x = y
Error: Unable to call '*' using an argument list of type (Double, Double ').
I've tried a lot more. Once I try to pass Int
as a generic type, nothing else works.
Perhaps I'm just missing something fundamental here, or I'm just too stupid to understand general programming. In C ++, I would do in 2 seconds.
source to share
When called with an array, Int
a[i] * b[i]
is Int
and cannot be added Double
with as
.
To fix this problem, you can change your function vec_dot
to return T
an object instead Double
. To make initialization var s : T = 0
, you need to do SummableMultipliable
flows from IntegerLiteralConvertible
(to which already meet Int
and Double
):
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 create a Double
, you can add a method doubleValue()
to the protocol SummableMultipliable
:
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
}
Note: as @akashivskyy correctly said, the loop should be written faster than
for i in 0 ..< a.count { ... }
If you want to get creative and impress or confuse your coworkers, you can replace the entire loop with a single expression:
let s : T = reduce(Zip2(a, b), 0) { $0 + $1.0 * $1.1 }
source to share
The reason for the exclusion is, as you may have noticed, a lower one. In fairness, what you are trying to do is illegal and the compiler should not let you do that in the first place.
Since he vec_dot
only knows what T
is SummableMultipliable
, he cannot transform it to Double
exactly the same (how is it supposed that he is not String
?).
A simple way to get rid of this problem is to get rid of the general restrictions and use the overload function :
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, see Martin R's answer .
source to share