Restricting associated values


#1

Is there a way to restrict the associated values of an enum? For example,
suppose I have this type:

enum Angle {
    case radians(Double)
    case degrees(Double)
}

I want to ensure that the radians values is always in [0, 2π) and the
degrees values is always in [0, 360). Ideally I would like to write an
initializer which is called when the user writes eg. “let x: Angle =
.degrees(-45)” and contains the logic to wrap the provided value into the
allowed range (in this case by adding a multiple of 360).

I don’t see a way to do it. Is this possible?

The closest I’ve found is to create auxiliary types such as

struct Degree { … }
struct Radian { … }

and give them appropriate initializers, then use them for the associated
values. However that is undesirable because it adds an extra level of depth
to get at the actual numeric values.

Is there a better way?

Nevin


(David Sweeris) #2

Not off the top of my head, at least not without changing the syntax.

You could add two inits, with different argument labels, and not directly set the case: “let x = Angle(degrees: -45)”

You could add "public var radians: Double { get {...} set {...} }" (and again for "degrees") to `Angle`. The getter would need to switch on self to figure out if you need to convert between the two (like if someone said ".radians(2).degrees"): “var x = Angle.radians(0); x.degrees = -45"

Alternately, you could add "subscript() -> Double" and switch on self in both the setter and the getter to figure out if you should be wrapping at 2π or 360 and to do any conversions: “var x: Angle = .radians(0); x[] = -45"

Hope that helps
- Dave Sweeris

···

On Jun 18, 2017, at 19:30, Nevin Brackett-Rozinsky via swift-users <swift-users@swift.org> wrote:

Is there a way to restrict the associated values of an enum? For example, suppose I have this type:

enum Angle {
    case radians(Double)
    case degrees(Double)
}

I want to ensure that the radians values is always in [0, 2π) and the degrees values is always in [0, 360). Ideally I would like to write an initializer which is called when the user writes eg. “let x: Angle = .degrees(-45)” and contains the logic to wrap the provided value into the allowed range (in this case by adding a multiple of 360).

I don’t see a way to do it. Is this possible?

The closest I’ve found is to create auxiliary types such as

struct Degree { … }
struct Radian { … }

and give them appropriate initializers, then use them for the associated values. However that is undesirable because it adds an extra level of depth to get at the actual numeric values.

Is there a better way?


(Howard Lovatt) #3

To me Angle is a unit with two common representations: radians and degrees.
It's not an enum because it doesn't have two values, it has one value that
you can view in two ways.

Therefore I would make an Angle struct, something like:

//: Angle struct instead of angle enum

import Foundation

struct Angle {

    static let d2R = Double.pi / 180

    static let r2D = 180 / Double.pi

    private var degs: Double

    var degrees: Double {

        return degs

    }

    var radians: Double {

        return degs * Angle.d2R

    }

    init(degrees: Double = 0) {

        degs = degrees.truncatingRemainder(dividingBy: 180)

    }

    init(radians: Double) {

        self.init(degrees: radians * Angle.r2D)

    }

}

extension Angle: Hashable {

    var hashValue: Int {

        return degs.hashValue

    }

    static func ==(lhs: Angle, rhs: Angle) -> Bool {

        return lhs.degs == rhs.degs

    }

}

extension Angle/*: FloatingPoint*/ {

    static func +(lhs: Angle, rhs: Angle) -> Angle {

        return Angle(degrees: lhs.degs + rhs.degs)

    }

    static func +=(lhs: inout Angle, rhs: Angle) {

        lhs.degs += rhs.degs

        lhs.degs = lhs.degs.truncatingRemainder(dividingBy: 180)

    }

    // Rest of FloatingPoint ...

}

// Trig

extension Angle {

    var sin: Double {

        return Foundation.sin(radians) // Need to qualify name to stop name
clash

    }

    // Other trig

}

let a = Angle(degrees: 90) // 90

a.radians // pi / 2

a.sin // 1

let b = Angle(radians: 3) // Almost pi

b.degrees // 171.9

let c = a + b // Wraps over 180 degrees

c.degrees // 81.9

c == b // false

c.hashValue // 463...

let d = Angle(degrees: -90) // -90

d.radians // -pi / 2

var e = Angle(radians: -3) // Almost -pi

e.degrees // -171.9

e += d // Wraps over -180 degrees

e.degrees // -81.9

  -- Howard.

···

On 19 June 2017 at 13:32, David Sweeris via swift-users < swift-users@swift.org> wrote:

On Jun 18, 2017, at 19:30, Nevin Brackett-Rozinsky via swift-users < > swift-users@swift.org> wrote:

Is there a way to restrict the associated values of an enum? For example,
suppose I have this type:

enum Angle {
    case radians(Double)
    case degrees(Double)
}

I want to ensure that the radians values is always in [0, 2π) and the
degrees values is always in [0, 360). Ideally I would like to write an
initializer which is called when the user writes eg. “let x: Angle =
.degrees(-45)” and contains the logic to wrap the provided value into the
allowed range (in this case by adding a multiple of 360).

I don’t see a way to do it. Is this possible?

The closest I’ve found is to create auxiliary types such as

struct Degree { … }
struct Radian { … }

and give them appropriate initializers, then use them for the associated
values. However that is undesirable because it adds an extra level of depth
to get at the actual numeric values.

Is there a better way?

Not off the top of my head, at least not without changing the syntax.

You could add two inits, with different argument labels, and not directly
set the case: “let x = Angle(degrees: -45)”

You could add "public var radians: Double { get {...} set {...} }" (and
again for "degrees") to `Angle`. The getter would need to switch on self
to figure out if you need to convert between the two (like if someone said
".radians(2).degrees"): “var x = Angle.radians(0); x.degrees = -45"

Alternately, you could add "subscript() -> Double" and switch on self in
both the setter and the getter to figure out if you should be wrapping
at 2π or 360 and to do any conversions: “var x: Angle = .radians(0); x[] =
-45"

Hope that helps
- Dave Sweeris

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Karl) #4

I suggested a type like this when Xiaodi announced his maths library, but a more efficient implementation would look like this:

public struct Angle<T: FloatingPoint> {
    public let radians: T
    public var degrees: T {
        return (radians / .pi) * 180
    }
    
    public static func radians(_ rads: T) -> Angle {
        return Angle(radians: rads)
    }
    public static func degrees(_ degs: T) -> Angle {
        return Angle(radians: (degs / 180) * .pi)
    }
}

Floating-points don’t have extra inhabitants, so the enum representation would occupy { float size + 1 byte } of storage, with the extra byte marking which enum case you have.

A better approach is to store a single, normalised value (in this case, the ‘radians' value), and to provide initialisers which validate and normalise those input values. In your case, you wrap them to an allowed range.

I think the best-practice advice in this situation would be to consider switching: will anybody need to switch over the cases of your enum? In this case, no - Angle<T> is just a wrapper which statically verifies that the angle is in the expected “notation”; You want to put an angle of either notation in, and grab the same angle out in another notation. The underlying stored notation is an implementation detail, so a struct is better.

- Karl

···

On 19. Jun 2017, at 04:30, Nevin Brackett-Rozinsky via swift-users <swift-users@swift.org> wrote:

Is there a way to restrict the associated values of an enum? For example, suppose I have this type:

enum Angle {
    case radians(Double)
    case degrees(Double)
}

I want to ensure that the radians values is always in [0, 2π) and the degrees values is always in [0, 360). Ideally I would like to write an initializer which is called when the user writes eg. “let x: Angle = .degrees(-45)” and contains the logic to wrap the provided value into the allowed range (in this case by adding a multiple of 360).

I don’t see a way to do it. Is this possible?

The closest I’ve found is to create auxiliary types such as

struct Degree { … }
struct Radian { … }

and give them appropriate initializers, then use them for the associated values. However that is undesirable because it adds an extra level of depth to get at the actual numeric values.

Is there a better way?

Nevin
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Travis Griggs) #5

Lots of different ways I think. I chose not between struct and enum, but used both:

struct Angle {
  enum Unit:CGFloat {
    case radians = 1.0
    case degrees = 57.29577951309314 // 360.0 / Tau
    case rotations = 0.1591549430918953 // 1.0 / Tau
  }
  
  // MARK: - Stored Properties
  var raw:CGFloat = 0.0
  var unit:Unit = .radians
        ….
        // MARK: - Left for the Student

I do a bit of UI programming with angles and have found rotations (I’ve drunk too much of the Tau manifesto koolaid probably) to be the most natural fit for a lot of things. Having an angle object allows me to layer over (via extensions) the various cocoa/uikit apis that take angles with type safe angle objects, and yet given me the ability to express said angles in whatever intermediate domain best fits.

···

On Jun 18, 2017, at 10:33 PM, Howard Lovatt via swift-users <swift-users@swift.org> wrote:

To me Angle is a unit with two common representations: radians and degrees. It's not an enum because it doesn't have two values, it has one value that you can view in two ways.

Therefore I would make an Angle struct, something like:
<snip>


(Karl) #6

Oh, and one thing to note is that by making static initialiser functions, you can still pass angles in to functions by calling ".degrees(90)” or “.radians(.pi/4)”, so you kind-of emulate the convenience of using enums. IIRC, RawOptionSet does a similar trick.

The above struct is source-compatible with an equivalent enum representation; it all comes down to implementation details, and for this, the struct is more efficient.

- Karl

···

On 19. Jun 2017, at 20:03, Karl Wagner <razielim@gmail.com> wrote:

On 19. Jun 2017, at 04:30, Nevin Brackett-Rozinsky via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Is there a way to restrict the associated values of an enum? For example, suppose I have this type:

enum Angle {
    case radians(Double)
    case degrees(Double)
}

I want to ensure that the radians values is always in [0, 2π) and the degrees values is always in [0, 360). Ideally I would like to write an initializer which is called when the user writes eg. “let x: Angle = .degrees(-45)” and contains the logic to wrap the provided value into the allowed range (in this case by adding a multiple of 360).

I don’t see a way to do it. Is this possible?

The closest I’ve found is to create auxiliary types such as

struct Degree { … }
struct Radian { … }

and give them appropriate initializers, then use them for the associated values. However that is undesirable because it adds an extra level of depth to get at the actual numeric values.

Is there a better way?

Nevin
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

I suggested a type like this when Xiaodi announced his maths library, but a more efficient implementation would look like this:

public struct Angle<T: FloatingPoint> {
    public let radians: T
    public var degrees: T {
        return (radians / .pi) * 180
    }
    
    public static func radians(_ rads: T) -> Angle {
        return Angle(radians: rads)
    }
    public static func degrees(_ degs: T) -> Angle {
        return Angle(radians: (degs / 180) * .pi)
    }
}

Floating-points don’t have extra inhabitants, so the enum representation would occupy { float size + 1 byte } of storage, with the extra byte marking which enum case you have.

A better approach is to store a single, normalised value (in this case, the ‘radians' value), and to provide initialisers which validate and normalise those input values. In your case, you wrap them to an allowed range.

I think the best-practice advice in this situation would be to consider switching: will anybody need to switch over the cases of your enum? In this case, no - Angle<T> is just a wrapper which statically verifies that the angle is in the expected “notation”; You want to put an angle of either notation in, and grab the same angle out in another notation. The underlying stored notation is an implementation detail, so a struct is better.

- Karl