Generic , Numeric addition with Double

(Carlhung) #1

I want to do something like:

func foo<T: Numeric>(double: Double, val: T) -> Double {
    return double + val
}

T can be Int or Double
how can I do it?

#2

You could do something like the following:

func foo<T: Numeric>(double: Double, val: T) -> Double {
    switch val {
    case is Int: return double + Double(val as! Int)
    case is Double: return double + (val as! Double)
    default: fatalError()
    }
}

But I think it would be better to just write two overloads.

(Carlhung) #3

sad. i thought there is a easier way to do other than casting type or overload.

#4

Swift (with very few exceptions) does not perform implicit type conversion or coercion. Even if you constrain the generic parameter to a small number of types, any expressions which require concrete types will require a cast.

(Quinn “The Eskimo!”) #5

While I agree that this approach is less than ideal, just FYI you can avoid the force casts by exploiting an as-pattern:

func foo<T: Numeric>(double: Double, val: T) -> Double {
    switch val {
    case let i as Int: return double + Double(i)
    case let d as Double: return double + d
    default: fatalError()
    }
}

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes
#6

Thank! I thought there was a way to do that, but I wasn't able to check the syntax, and I didn't want to get it wrong.

(Quinn “The Eskimo!”) #7

i thought there is a easier way to do other than casting type or
overload.

Can you explain more about what you’re looking for here? As Avi explained, Swift tries to avoid implicit conversions, and for good reason. Even in your small example, an implicit conversion from Int to Double could lose information. And it’s hard to offer better suggestions without more context.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes
(Steve Canon) #8

The reason why this is hard is that it's underspecified by your generic constraint. You say "T can be Int or Double", but you're not communicating that to the compiler. Your constraint only says "T is Numeric", which could be Int or Double or Int8 or Float80 or a complex number or matrix or bignum type that someone has defined themselves.

There are multiple ways to accomplish what you want, but all of them assume giving more information to the compiler, so that it can know what you know, and all have slightly different semantics. Quinn already sketched one option out, here are two others of the many options:

Simply overload the function

func foo(double: Double, val: Double) -> Double {
  return double + val
}

func foo(double: Double, val: Int) -> Double {
  return double + Double(val)
}

This is good in some ways (you always get optimal code for each supported type, and it's very clear what types are and are not supported) and bad in others (you have to write two almost-identical functions). If there are really only two types and no existing protocol that unites them, this is often the best option.

Define a protocol with what you need

protocol Fooable {
  var doubleValue: Double { get }
}

extension Double: Fooable {
  var doubleValue: Double { return self }
}

extension Int: Fooable {
  var doubleValue: Double { return Double(self) }
}

func foo<T>(double: Double, val: T) -> Double where T: Fooable {
  return double + val.doubleValue
}

This has some drawbacks in this case (a lot of machinery for a very simple function), but it makes good sense in cases where a small, coherent set of protocol requirements enables multiple useful generic algorithms.

4 Likes
(Lucian Boboc) #9

Why not use SignedNumeric & BinaryInteger as the constraint and use Double to always add the same type. Double being the largest it's init will not raise an exception at runtime.

func foo<T: SignedNumeric & BinaryInteger>(double: Double, val: T) -> Double {
    return double + Double(val)
}

Edit: This will not work because Double doesn't conform to BinaryInteger, i've tested only the Int case.
If you want to have Int or Double why not use an enum that adopts ExpressibleByIntegerLiteral and ExpressibleByFloatLiteral?

enum Either {
    typealias IntegerLiteralType = Int
    typealias FloatLiteralType = Double
    
    case int(Int)
    case double(Double)
    
    var value: Double {
        switch self {
        case .int(let value):
            return Double(value)
        case .double(let value):
            return value
        }
    }
}

extension Either: ExpressibleByIntegerLiteral {
    init(integerLiteral value: IntegerLiteralType) {
        self = .int(value)
    }
}

extension Either: ExpressibleByFloatLiteral {
    init(floatLiteral value: FloatLiteralType) {
        self = .double(value)
    }
}

func foo(double: Double, val: Either) -> Double {
    return double + val.value
}
1 Like