markus_p
markus_p

Reputation: 574

Generics of raw types (Int, Float, Double) create weird error messages

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 Ints:

 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

Answers (2)

akashivskyy
akashivskyy

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

Martin R
Martin R

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

Related Questions