Jacob Parker
Jacob Parker

Reputation: 307

Swift operators for protocols

Say I have two structs that conform to a protocol,

protocol NumberType {
    var doubleValue: Double { get }
}

struct Rational: NumberType {
    let numerator: Int
    let denominator: Int
    var doubleValue: Double { get { return Double(numerator) / Double(denominator) } }
}

struct FixedPoint: NumberType {
    let value: Double
    var doubleValue: Double { get { return value } }
}

And I want to define arithmetic operators for NumberType that will in turn return NumberTypes.

func *(lhs: NumberType, rhs: NumberType) -> NumberType { return FixedPoint(lhs.doubleValue * rhs.doubleValue) }

But I also want to add more specific operators so when both are Rationals, I return a Rational.

func *(lhs: Rational, rhs: Rational) -> NumberType { return Rational(...) }

Is there a more elegant way to do this than to have one super-function per operator that does all type checking?

Edits

You should be able to multiply FixedPoint by Rational, since they both conform to NumberType.

The overloading strategy won't work, because if I do a * b * c where all values are Rational, the result of a * b is NumberType, and so (a * b) * c will fall back to using the generic NumberType multiplication operator. I'll lose my precision here.

I'm not bothered about being able to multiply by Doubles, Ints etc.

Further Edits

It's not correct to assume that the most generic case always returns a FixedPoint. I'm showing basic usage here, but if you have a FixedPoint equal to 0, and another Rational, and you wanted to add them, it's feasible that you could get a Rational in your most generic case.

Upvotes: 1

Views: 733

Answers (2)

Fogmeister
Fogmeister

Reputation: 77661

You could use generics here...

If you define the protocol like...

protocol NumberType {    
    var doubleValue: Double { get }

    init(doubleValue double: Double)
}

Then you could define a function like...

func *<T: NumberType>(lhs: T, rhs: T) -> T {
    return T(doubleValue: lhs.doubleValue * rhs.doubleValue)
}

This will allow you to use this function with any type of object as long as it conforms to NumberType. The only caveat is that both lhs and rhs must be the same type.

Then in the implementation of each NumberType you can define how the init works...

struct FixedPoint: NumberType {
    let value: Double
    var doubleValue: Double { get { return value } }

    init(doubleValue double: Double) {
        // this implementation may not be practical for things like Rationals etc...
        self.value = double
    }
}

Without having some common way of creating NumberTypes then you'd have to have multiple implementations of how the function works and therefore have your method of creating multiple instance depending on what the inputs are.

Upvotes: 2

JeremyP
JeremyP

Reputation: 86691

I would have the protocol declare a function that does what you want.

protocol NumberType {
    var doubleValue: Double { get }
    func multipliedBy(other: NumberType) -> NumberType
}

And then define the operator

func *(l: NumberType, r: NumberType) -> NumberType
{
    return l.multipliedBy(other: r)
}

Each type that implements the protocol, implements multipliedBy(other:) in a way that is suitable for its type.

Upvotes: 0

Related Questions