[swift-users] Restricting associated values

Karl Wagner razielim at gmail.com
Mon Jun 19 13:12:45 CDT 2017


> On 19. Jun 2017, at 20:03, Karl Wagner <razielim at gmail.com> wrote:
> 
> 
>> On 19. Jun 2017, at 04:30, Nevin Brackett-Rozinsky via swift-users <swift-users at swift.org <mailto:swift-users at 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 at swift.org <mailto:swift-users at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-users <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

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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20170619/ea24c728/attachment.html>


More information about the swift-users mailing list