0

This is working great for Doubles and Floats. But getting other numerals like Ints involved is proving really difficult.

public protocol TemperatureConvertable: FloatLiteralConvertible, CustomStringConvertible {
    func +(lhs: Self, rhs: Self) -> Self
    func -(lhs: Self, rhs: Self) -> Self
    func *(lhs: Self, rhs: Self) -> Self
    func /(lhs: Self, rhs: Self) -> Self
}

extension Double: TemperatureConvertable {}
extension Float: TemperatureConvertable {}

public func fahrenheitToCelsius<T: TemperatureConvertable>(fahrenheit: T) -> T {
    // (°F − 32) ÷ 1.8 =°C
    return (fahrenheit - 32.0) / 1.8
}

public func celsiusToFahrenheit<T: TemperatureConvertable>(celsius: T) -> T {
    // (°C × 1.8) + 32 =°F
    return (celsius * 1.8) + 32.0
}

From what I can find there isn't a NumberLiteralConvertible or similar. And just creating a Numeric protocol like others suggest makes the arithmetic fail because the types don't match. Ideally I'd like something like this thats more generic but this I get Binary operator '-' cannot be applied to operands of type 'T' and 'Double' on the line of actual math for fahrenheit - 32.0 because the lhs and rhs aren't both Self.

public protocol TemperatureConvertable: CustomStringConvertible {
    func +(lhs: Self, rhs: Self) -> Self
    func -(lhs: Self, rhs: Self) -> Self
    func *(lhs: Self, rhs: Self) -> Self
    func /(lhs: Self, rhs: Self) -> Self
}

extension Int: TemperatureConvertable {}
extension Double: TemperatureConvertable {}
extension Float: TemperatureConvertable {}

public func fahrenheitToCelsius<T: TemperatureConvertable>(fahrenheit: T) -> T {
    // (°F − 32) ÷ 1.8 =°C
    return (fahrenheit - 32.0) / 1.8
}

public func celsiusToFahrenheit<T: TemperatureConvertable>(celsius: T) -> T {
    // (°C × 1.8) + 32 =°F
    return (celsius * 1.8) + 32.0
}
Ryan Poolos
  • 18,421
  • 4
  • 65
  • 98

1 Answers1

2

You need a way to create a TemperatureConvertable from a Double and viceversa.

So your functions will be able to internally use Double(s) to make the operations. And finally convert the result to T for the output.

Step 1

public protocol TemperatureConvertible: CustomStringConvertible {
    // this part is not needed
    // func +(lhs: Self, rhs: Self) -> Self
    // func -(lhs: Self, rhs: Self) -> Self
    // func *(lhs: Self, rhs: Self) -> Self
    // func /(lhs: Self, rhs: Self) -> Self

    init(_ other: Double)
    var double: Double { get }
}

Now lets update your extensions to Double, Float and Int.

You don't need to add the initializer we declared above since these types already have it.

extension Int: TemperatureConvertible {
    public var double: Double { return Double(self) }
}
extension Double: TemperatureConvertible {
    public var double: Double { return self }
}
extension Float: TemperatureConvertible {
    public var double: Double { return Double(self) }
}

Step 2

Now you can rewrite your functions like this

public func fahrenheitToCelsius<T: TemperatureConvertible>(fahrenheit: T) -> T {
    // (°F − 32) ÷ 1.8 =°C
    let celsius = (fahrenheit.double - 32.0) / 1.8
    return T(celsius)
}

public func celsiusToFahrenheit<T: TemperatureConvertible>(celsius: T) -> T {
    // (°C × 1.8) + 32 =°F
    let fahrenheit = (celsius.double * 1.8) + 32.0
    return T(fahrenheit)
}

Test

let celsius: Int = 20
let fahrenheit = celsiusToFahrenheit(celsius)
//  ^ it's an Int

Tip

For readability and consistency with the Cocoa framework I suggest you to rename your functions like this

public func fahrenheitFromCelsius<T: TemperatureConvertible>(_ celsius: T) -> T

public func celsiusFromFahrenheit<T: TemperatureConvertible>(_ fahrenheit: T) -> T
Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • Needing to declare a variable for double and internally use it seems really messy. Though I suppose its still hidden from the users of the methods at least. Definitely hoping there is a cleaner way. – Ryan Poolos Feb 26 '16 at 14:05
  • @RyanPoolos: please note that you **do need** to use a `Double` internally. Infact using `Int` for the operations inside your 2 functions will produce a big rounding. Would you be ok with that? – Luca Angeletti Feb 26 '16 at 14:07
  • Its not using Doubles I have an issue with its needing to have have private double instance variable that is messy to me. – Ryan Poolos Feb 26 '16 at 16:09
  • But it's **not** a `Double` instance variable (stored property). It's a **computed property**, it's very similar to a function. I am not adding a `Double` property to `Int`, I'm just making an `Int` capable of converting itself to `Double`. – Luca Angeletti Feb 26 '16 at 16:28
  • If it converted automatically like the literals do then I'd be all over it likely. But since you have to call a property on it or cast it, its not seamless. – Ryan Poolos Feb 26 '16 at 21:36
  • By making it FloatLiteralConvertible its completely seamless until Ints are involve. By making in IntegerLiteralConvertible its completely seamless until floats are involve. Some how Apple is also making it ok to add 5 + 5.0 with some other protocol and I think thats the key I'm looking for. – Ryan Poolos Feb 26 '16 at 21:37