Can I bind different associated values in a Swift enum to the same var?

只谈情不闲聊 提交于 2019-12-29 07:18:09

问题


I made a little "Angle" enum so that I could program with different interchangeable angular formats:

enum Angle {
    case Radians(CGFloat)
    case Degrees(CGFloat)
    case Rotations(CGFloat)
}

I find that there's some redundant boilerplate code with this approach though. For example, I wanted to add a computed var that just returned the raw underlying associated float:

var raw:CGFloat {
    switch self {
    case let .Radians(value):
        return value
    case let .Degrees(value):
        return value
    case let .Rotations(value):
        return value
    }
}

I tried to change that to read:

case .Radians(let value), .Degrees(let value):
    return value

I hoped it would be clever enough to realize it was only ever going to resolve one match, so the conflict was ignorable. But no such device. Compiler says it can't resolve the conflict between the two let value statements.

So is there a bit of idiomatic cleverness I haven't discovered with Swift enum's yet that would make it so I don't have to repeat myself so much there?

Another similar example, was when I implemented the * function:

func * (lhs:Angle, rhs:CGFloat) -> Angle {
    switch lhs {
    case .Radians:
        return .Radians(lhs.raw * rhs)
    case .Degrees:
        return .Degrees(lhs.raw * rhs)
    case .Rotations:
        return .Rotations(lhs.raw * rhs)
    }
}

It seems like there should be a simpler way to express that: "Make the same enum type with my associated value times the scalar argument".

(I'm not sure I've got a good title on this question, feel free to improve/suggest better)


回答1:


That's an interesting case: An enum somehow isn't the right data type, because the value isn't either in radians or in degrees, both are just angles and not really different things. Also typealias Radians = Double wouldn't work because there's no unit safety.

Maybe you can use something like this though:

import Darwin

struct Angle {
    enum Unit {
        case Radians
        case Degrees
        case Rotations

        var radiansFactor : Double {
            switch self {
            case Radians: return 1
            case Degrees: return 180.0 / M_PI
            case Rotations: return 1 / 2 / M_PI
            }
        }
    }

    var unit : Unit {
        didSet {
            value /= oldValue.radiansFactor
            value *= unit.radiansFactor
        }
    }
    var value : Double
}

func * (var lhs: Angle, rhs: Double) -> Angle {
    lhs.value *= rhs
    return lhs
}

var angle = Angle(unit: .Degrees, value: 180)

angle.value             // 180.0
angle.unit = .Radians
angle.value             // 3.141592...
angle.unit = .Rotations
angle.value             // 0.5

Oh and as for the answer to your original question: No you cannot.




回答2:


Based on @Kametrixom 's response, what I ended up doing for this particular "Angle" modeling problem was as follows:

import UIKit
import CoreGraphics

let Tau = CGFloat(2 * M_PI)

struct Angle {
    enum Unit:CGFloat {
        case Radians = 1.0
        case Degrees = 57.29577951309314 // 360.0 / Tau
        case Rotations = 6.28318530717959 //Tau
    }
    var raw:CGFloat = 0.0
    var unit:Unit = .Radians

    func convert(anotherUnit:Unit) -> Angle {
        return self.unit == anotherUnit ? self : Angle(raw: self.raw * self.unit.rawValue / anotherUnit.rawValue, unit: anotherUnit)
    }

    var radians:Angle {
        return self.convert(.Radians)
    }

    var degrees:Angle {
        return self.convert(.Degrees)
    }

    var rotations:Angle {
        return self.convert(.Rotations)
    }

    var cos:CGFloat {
        return CoreGraphics.cos(self.radians.raw)
    }

    var sin:CGFloat {
        return CoreGraphics.sin(self.radians.raw)
    }

    var half:Angle {
        return self / 2
    }
}

extension Angle: Comparable {}

func < (lhs:Angle, rhs:Angle) -> Bool {
    return lhs.radians.raw < rhs.radians.raw
}

func == (lhs:Angle, rhs:Angle) -> Bool {
    return lhs.radians.raw == rhs.radians.raw
}

func + (lhs:Angle, rhs:Angle) -> Angle {
    let other = rhs.convert(lhs.unit)
    return Angle(raw: lhs.raw + other.raw, unit: lhs.unit)
}

func - (lhs:Angle, rhs:Angle) -> Angle {
    let other = rhs.convert(lhs.unit)
    return Angle(raw: lhs.raw - other.raw, unit: lhs.unit)
}

func * (lhs:CGFloat, rhs:Angle) -> Angle {
    return rhs * lhs
}

func * (lhs:Angle, rhs:CGFloat) -> Angle {
    return Angle(raw: lhs.raw * rhs, unit: lhs.unit)
}

func / (lhs:Angle, rhs:CGFloat) -> Angle {
    return Angle(raw: lhs.raw / rhs, unit: lhs.unit)
}


来源:https://stackoverflow.com/questions/32827507/can-i-bind-different-associated-values-in-a-swift-enum-to-the-same-var

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!