Re: [swift-users] Restricting associated values

2017-06-19 Thread Karl Wagner via swift-users

> On 19. Jun 2017, at 04:30, Nevin Brackett-Rozinsky via swift-users 
>  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 
> 

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

public struct Angle {
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 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___
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


Re: [swift-users] Restricting associated values

2017-06-19 Thread Karl Wagner via swift-users

> On 19. Jun 2017, at 20:03, Karl Wagner  wrote:
> 
> 
>> On 19. Jun 2017, at 04:30, Nevin Brackett-Rozinsky via swift-users 
>> > 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 
>> 
> 
> I suggested a type like this when Xiaodi announced his maths library, but a 
> more efficient implementation would look like this:
> 
> public struct Angle {
> 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 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

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___
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


Re: [swift-users] Restricting associated values

2017-06-19 Thread Travis Griggs via swift-users

> On Jun 18, 2017, at 10:33 PM, Howard Lovatt via swift-users 
>  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:
> 

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.
___
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


Re: [swift-users] Restricting associated values

2017-06-18 Thread Howard Lovatt via swift-users
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
>
>
___
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users