4

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)

Travis Griggs
  • 21,522
  • 19
  • 91
  • 167

3 Answers3

5

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.

Kametrixom
  • 14,673
  • 7
  • 45
  • 62
  • Thanks, this helped a lot. It steered me in the right direction. I'll put what I ended up doing in a separate "answer" below. – Travis Griggs Sep 30 '15 at 18:36
2

In newer versions of Swift, this is now possible if the values are of the same type:

enum Foo {
    case bar(Int),  bas(Int)
}

var this = .random() ? Foo.bar(5) : Foo.bas(5)
switch this {
    case .bar(let foo), .bas(let foo): print(foo) // Always prints: 5
}

You can even do this:

enum Foo {
    case bar(Int, Int),  bas(Int, Int)
}

var this = .random() ? Foo.bar(5) : Foo.bas(5)
switch this {
    case .bar(let foo, let bat), .bas(let foo, let bat): print(foo, bat) // Always prints: 5 5
}
0-1
  • 702
  • 1
  • 10
  • 30
0

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)
}
Travis Griggs
  • 21,522
  • 19
  • 91
  • 167