swiftmacosgenericsdecimal

Return generic to its discrete type


I run a bunch of calculations using Decimal and Double. Without generics there'd be a lot of repetitive code. There are places I eventually need the Decimal in a more native format but I can't seem to figure out how to do that in Swift.

A hypothetical example: there is no log2() in Decimal, but because of acceptable margin of error I want to convert it to Double and use it that way.

Originally I thought I'd just create a generic function that would work for both Double and Decimal

// Dtype is either Decimal or Double
typealias Dtype =  SignedNumeric

func mylog2<T: Dtype>(_ x: T) -> Double
{
    let x_double: Double = Double(truncating: x as NSNumber) //'T' is not convertible to 'NSNumber'
    return log2(x_double)
}

But I get an error, so then I thought I'd just separate them out into discrete functions. After thinking about it I figure T can't expose its actual Type. Then I thought I'd split the function into their discrete Types and try to use it that way:

func mylog2(_ x: Decimal) -> Double
{
    let x_double: Double = Double(truncating: x as NSNumber)
    return log2(x_double)
}

func mylog2(_ x: Double) -> Double
{
    return log2(x)
}

But then I don't know how to get T to use the mylog2() function by discrete types:

func myExample<T: Dtype>(_ a: T, _ b: T) -> Double
{
    let c: T = (a + b) * (b - a)
    //bunch of other maths funcs
    return mylog2(c) //No exact matches in call to global function 'mylog2'
}

I am pretty sure in other languages there are ways to handle situations like this so there must be a way. I may have approached the problem with an incorrect Swift mindset and there's a completely different way I'm not aware of how to do this. Anyway if someone can help me with the Swift way of tackling this I'd appreciate it.


Solution

  • Just declare your own protocol that requires a log2 method to be implemented. Conform both Double and Decimal to this protocol.

    protocol Log2 {
        func log2() -> Double
    }
    
    extension Decimal: Log2 {
        func log2() -> Double {
            Darwin.log2(Double(truncating: self as NSNumber))
        }
    }
    
    extension Double: Log2 {
        func log2() -> Double {
            Darwin.log2(self)
        }
    }
    

    Then you can constrain the generic type parameter to be Log2 & SignedNumeric.

    typealias Dtype = SignedNumeric & Log2
    
    func myExample<T: Dtype>(_ a: T, _ b: T) -> Double
    {
        let c: T = (a + b) * (b - a)
        //bunch of other maths funcs
        return c.log2()
    }
    

    Alternatively, make Dtype its own protocol, replacing Log2.

    protocol Dtype: SignedNumeric {
        func log2() -> Double
    
        // other functions you need on both Double and Decimal goes here...
    }