Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic half func in Swift

Tags:

swift

I'm trying to write generic half function for all numeric types:

func half<T where T: FloatingPointType>(value: T) -> T
{
    return value * 0.5
}

And I see this error:

No '*' candidates produce the expected contextual result type 'T'

Is it possible to write generic function in this case?

like image 376
ChikabuZ Avatar asked Aug 10 '16 13:08

ChikabuZ


2 Answers

Not in Swift 2.2. FloatingPointType is not a very powerful type. In Swift 3 this is possible if you limit yourself to BinaryFloatingPoint (which covers most of the floating point types you're probably thinking of, particularly Float and Double).

func half<T where T: BinaryFloatingPoint>(_ value: T) -> T {
    return value * 0.5
}

In the most general case of a custom FloatingPointType in Swift 2.2, the system may not have any way to convert this into a Double multiplication and then convert that back into your arbitrary FloatingPointType. The BinaryFloatingPoint protocol adds ExpressibleByFloatLiteral which means that 0.5 can be converted to your arbitrary type.

It's important to keep in mind that the 0.5 in this above code is of type T. It is not a Double. The following code still would not work:

func half<T where T: BinaryFloatingPoint>(_ value: T) -> T {
    let oneHalf = 0.5
    return value * oneHalf 
   // error: binary operator '*' cannot be applied to operands of type 'T' and 'Double'
}

You cannot multiply two arbitrary floating point numbers. Swift intentionally avoids a lot of automatic type coercion that was a source of subtle bugs in C. In the general case of a custom FloatingPoint, it is not possible to say that one type is "more accurate" than the other. They may have different accuracy over different ranges. And of course you could create a FloatingPoint that is much larger than a double. So there's no obvious "just promote everything to double" rule that you could use.

like image 106
Rob Napier Avatar answered Feb 10 '23 02:02

Rob Napier


You write "all numeric types", but based on the 0.5 multiplication this answer is based on the assumption that you refer to the commonly use floating point types (Double, Float, CGFloat).

You could create a custom protocol that blueprints the * function (for multiplying to values of Self) as well as an initializer by FloatLiteralType, and conform Double, Float and CGFloat to this protocol. The * function as well as the initializer are already implemented for these three types, so the conformance is simply explicitly telling the compiler that these types indeed conform to your custom protocol.

E.g.

protocol MyFloatingPointTypes {
    func *(lhs: Self, rhs: Self) -> Self
    init(_: FloatLiteralType)
}

extension Double : MyFloatingPointTypes {}
extension Float : MyFloatingPointTypes {}
extension CGFloat : MyFloatingPointTypes {}

func half<T: MyFloatingPointTypes>(value: T) -> T {
    return value * T(0.5)
}

/* example usage */
var dVal: Double = 10.5
var fVal: Float = 10.5
var cfVal: CGFloat = 10.5

dVal = half(dVal)
fVal = half(fVal)
cfVal = half(cfVal)

print(dVal, fVal, cfVal) // 5.25 5.25 5.25

Alternative: compound protocol constraint, using FloatLiteralConvertible

As @Hamish writes in his comments below, rather than blueprinting the initializer by Double (init(_: FloatLiteralType) in MyFloatingPointTypes, a better approach is to instead add an additional type constraint to the generic half(...) function, constraining the generic (in addition to MyFloatingPointTypes) to FloatLiteralConvertible:

// lets rename this protocol here, since it no longer has any direct association with floats
protocol Multiplicable {
    func *(lhs: Self, rhs: Self) -> Self
}

extension Double : Multiplicable {}
extension Float : Multiplicable {}
extension CGFloat : Multiplicable {}

func half<T: protocol<Multiplicable, FloatLiteralConvertible>>(value: T) -> T {
    return value * 0.5
}

/* example usage */
// ... same as above

Another (more general) alternative: compound protocol constraint, using IntegerLiteralConvertible

Or, if you want you half function to extend its generic "coverage" also to integer types (as also pointed out by @Hamish, thanks!) you could use the same compound protocol constraint method as above, but against IntegerLiteralConvertible:

protocol Divisable {
    func /(lhs: Self, rhs: Self) -> Self
}

extension Double : Divisable {}
extension Float : Divisable {}
extension CGFloat : Divisable {}
extension Int: Divisable {}

func half<T: protocol<Divisable, IntegerLiteralConvertible>>(value: T) -> T {
    return value / 2
}

/* example usage */
var dVal: Double = 10.5
var fVal: Float = 10.5
var cfVal: CGFloat = 10.5
var iVal: Int = 11

dVal = half(dVal)
fVal = half(fVal)
cfVal = half(cfVal)
iVal = half(iVal) // truncates decimal part

print(dVal, fVal, cfVal, iVal) // 5.25 5.25 5.25 5
like image 25
dfrib Avatar answered Feb 10 '23 01:02

dfrib