1

How can I compactly write an extension of an Array in Swift 3.0 which works for both Float and Double Element types?

The following doesn't work:

extension Array where Element: FloatingPoint
{
    public func multiply(mult: Double) -> [Double] {
        return self.map{Double($0) * mult}
    }
}

With the error being

Unable to infer closure type in the current context

Why can't the closure type be inferred? Is this a current limitation of the compiler, or is there a good reason why the closure type can't be inferred?

A more explicit version doesn't work either:

extension Array where Element: FloatingPoint
{
    public func multiply(mult: Double) -> [Double] {
        return self.map{(x: Element) -> Double in Double(v: x) * mult}
    }
}

With the error this time being

Ambiguous reference to member '*'

Where again I'm not sure of the reason for this error.

Danra
  • 9,546
  • 5
  • 59
  • 117
  • @Hamish Doing what you suggest works, thanks! That might be good enough. However, what if I indeed want to return an array of `Double`? (For example, consider a case where instead of controlling the type of `mult` parameter, as in the example above, I instead have no parameters, but instead multiply by a function which I didn't write which returns `Double`. In that case to multiply the `Array` elements by that function they need to be converted to `Double` and a `[Double]` should be returned. – Danra Jan 25 '17 at 16:44
  • I went ahead and converted my comments to an answer :) – Hamish Jan 25 '17 at 18:58
  • @Hamish you might be interested at DoubleConvertible implementation with Initializer as suggested by Martin R here http://stackoverflow.com/questions/26794282/how-do-i-make-my-operator-work-with-double-and-int which I used here http://stackoverflow.com/a/29179878/2303865 – Leo Dabus Jan 25 '17 at 19:50
  • @LeoDabus Yeah, it's quite a common solution to the problem of "a type which you can pass into a given overload of a function" (in this case `Double`'s `init(_:)`). You can actually generalise it too in order to work with conversions between different types, see for example [my answer here](http://stackoverflow.com/a/37566026/2976878). – Hamish Jan 25 '17 at 19:57
  • Yes and Swift 3 even allows Protocol Composition so you can specify more than one protocol (check Swift 3 book page 576/577) and allow you to pass an object of any type if it has the required properties. – Leo Dabus Jan 25 '17 at 20:08
  • 1
    @LeoDabus Swift 2 also had protocol composition ;) It just had different syntax (`protocol`). – Hamish Jan 25 '17 at 20:09

1 Answers1

3

Logically, your extension should work by multiplying an array of homogenous floating-point types with a value of the same type, and returning an array of that type. You can simply express this with an argument of type Element, and a return of [Element]:

// this could also just be an extension of Sequence
extension Array where Element : FloatingPoint {

    public func multiply(by factor: Element) -> [Element] {
        return self.map { $0 * factor }
    }
}

Therefore for a [Float], this would accept an argument of type Float and return a [Float].

However, what if I indeed want to return an array of Double?

I do not believe it's possible to construct a Double from an arbitrary FloatingPoint (or even BinaryFloatingPoint) conforming instance, as neither protocol (although they do require implementation of various aspects of the IEEE 754 specification) actually defines the precise encoding of the conforming type.

However, if you really want this, you could just write two overloads – one for Float elements and one for Double elements:

extension Sequence where Iterator.Element == Float {

    public func multiply(by factor: Double) -> [Double] {
        return self.map { Double($0) * factor }
    }
}

extension Sequence where Iterator.Element == Double {

    public func multiply(by factor: Double) -> [Double] {
        return self.map { $0 * factor }
    }
}

Alternatively, if you plan on making this work with a broader range of types, you can use a protocol in order to define a requirement that allows conforming types to express their value as a Double:

protocol ConvertibleToDouble {
    func _asDouble() -> Double
}

extension Float : ConvertibleToDouble {
    func _asDouble() -> Double { return Double(self) }
}

extension Double : ConvertibleToDouble {
    func _asDouble() -> Double { return self }
}

extension Sequence where Iterator.Element : ConvertibleToDouble {

    func multiply(by factor: Double) -> [Double] {
        return self.map { $0._asDouble() * factor }
    }
}
Hamish
  • 78,605
  • 19
  • 187
  • 280