[swift-evolution] [Proposal] Enums with stored properties

Charles Srstka cocoadev at charlessoft.com
Wed Oct 12 14:21:30 CDT 2016


> On Oct 11, 2016, at 8:51 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
> 
> On Tue, Oct 11, 2016 at 8:21 PM, Charles Srstka via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> On Oct 11, 2016, at 4:42 PM, Braeden Profile via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> enum RectSize
>> {
>>    let height:Int
>>    let width:Int
>>    case small(width: 30, height: 30)
>>    case medium(width: 60, height: 60)
>>    case large(width: 120, height: 120)
>> }
> 
> I like the concept, but this doesn’t seem as flexible as it could be, and could get ugly when mixing these defaults with enum associated values. How about dynamic properties instead? Something like:
> 
> enum RectSize {
> 	var height: Int { get }
> 	var width: Int { get }
> 
> 	case small {
> 		height { return 30 }
> 		width { return 30 }
> 	}
> 
> 	case medium {
> 		height { return 60 }
> 		width { return 60 }
> 	}
> 
> 	case large {
> 		height { return 120 }
> 		width { return 120 }
> 	}
> 
> 	case custom(width: Int, height: Int) {
> 		height { return height }
> 		width { return width }
> 	}
> }
> 
> I'd be interested in expanding raw values to accommodate other types, but computed properties are already possible:
> 
> ```
> enum RectSize {
>     case small, medium, large
> 
>     var height: Int {
>         switch self {
>         case .small:
>             return 30
>         case .medium:
>             return 60
>         case .large:
>             return 120
>         }
>     }
>     
>     var width: Int {
>         return height
>     }
> } 
> ```
> 
> There have been off-and-on proposals to change the syntax from what it is currently, but none have been deemed a significant enough advantage to merit a change--even before source-breaking changes in Swift 3 were over. Keeping in mind that sugar is the lowest priority for Swift 4 (and not in scope for the current phase), what's the advantage of your proposed syntax for computed properties over the existing one?

It’s *possible*, but the syntax is incredibly verbose, unwieldy, and ugly. Imagine an enum with lots of cases and lots of properties—and before you say this is contrived, this already currently happens quite a lot with error enums (pardon the badly written error messages; this is only for example):

enum FileError: Error, LocalizedError {
    case notFound(url: URL)
    case accessDenied(url: URL)
    case incorrectFormat(url: URL)
    case ioError(url: URL)
    // ... imagine there are another 20 or so of these ...
    
    // Now, implement LocalizedError:
    
    var errorDescription: String? {
        switch self {
        case let .notFound(url: url):
            return "Could not access the file \(url.lastPathComponent) because it could not be found."
        case let .accessDenied(url: url):
            return "Could not access the file \(url.lastPathComponent) because access was denied."
        case let .incorrectFormat(url: url):
            return "Could not access the file \(url.lastPathComponent) because it was not in the expected format."
        case let .ioError(url: url):
            return "Could not access the file \(url.lastPathComponent) because an I/O error occurred."
        // ... etc ...
        }
    }
    
    var failureReason: String? {
        switch self {
        case let .notFound(url: url):
            return "The file \(url.lastPathComponent) could not be found."
        case let .accessDenied(url: url):
            return "We do not have permission to view the file \(url.lastPathComponent)"
        case let .incorrectFormat(url: url):
            return "The file \(url.lastPathComponent) was not in the expected format."
        case let .ioError(url: url):
            return "An I/O error occurred while accessing the file \(url.lastPathComponent)."
        // ... etc ...
        }
    }
    
    var recoverySuggestion: String? {
        switch self {
        case .notFound:
            return "Please locate the correct file and try again."
        case .accessDenied:
            return "You can change the file's permissions using the Finder's Get Info window."
        case .incorrectFormat:
            return "The file may have become corrupt."
        case .ioError:
            return "Dear Lord, the hard drive may be failing."
        // ... etc ...
        }
    }
    
    var helpAnchor: String? {
        switch self {
        case .notFound:
            return "notFound"
        case .accessDenied:
            return "accessDenied"
        case .incorrectFormat:
            return "incorrectFormat"
        case .ioError:
            return "ioError"
        // ... etc ...
        }
    }
    
    // Each of these errors references a file URL, so I may want a property for that too
    
    var url: URL {
        switch self {
        case let .notFound(url: url):
            return url
        case let .accessDenied(url: url):
            return url
        case let .incorrectFormat(url: url):
            return url
        case let .ioError(url: url):
            return url
        // ... etc ...
        }
    }
}

Look how ugly this is. Switch statements everywhere, related values separated by really large amounts of space (and even more so if this error had more cases, or if I’d implemented other error protocols like RecoverableError, CustomNSError, etc.).

With the improved syntax, this could look something like this instead:

enum FileError: Error, LocalizedError {
    var url: URL { get }
    
    case notFound(url: URL) {
        errorDescription = "Could not access the file \(url.lastPathComponent) because it could not be found."
        failureReason = "The file \(url.lastPathComponent) could not be found."
        recoverySuggestion = "Please locate the correct file and try again."
        helpAnchor = "notFound"
        url = url
    }
    
    case accessDenied(url: URL) {
        errorDescription = "Could not access the file \(url.lastPathComponent) because access was denied."
        failureReason = "We do not have permission to view the file \(url.lastPathComponent)"
        recoverySuggestion = "You can change the file's permissions using the Finder's Get Info window."
        helpAnchor = "accessDenied"
        url = url
    }
    
    case incorrectFormat(url: URL) {
        errorDescription = "Could not access the file \(url.lastPathComponent) because it was not in the expected format."
        failureReason = "The file \(url.lastPathComponent) was not in the expected format."
        recoverySuggestion = "The file may have become corrupt."
        helpAnchor = "incorrectFormat"
        url = url
    }
    
    case ioError(url: URL) {
        errorDescription = "Could not access the file \(url.lastPathComponent) because an I/O error occurred."
        failureReason = "An I/O error occurred while accessing the file \(url.lastPathComponent)."
        recoverySuggestion = "Dear Lord, the hard drive may be failing."
        helpAnchor = "ioError"
        url = url
    }
    
    // ... etc ...
}

I don’t think it can be denied that the second is orders of magnitude easier to read and comprehend.

Charles

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161012/218d7775/attachment.html>


More information about the swift-evolution mailing list