[New Old Idea] Enum Case Blocks

Sure. A simple one is a DownloadState that is also Loggable. The enum represents the various states a download may have. Every state provides a logLevel and a logMessage generated from a combination of its specific case and the unique data it has. This is the "inside voice". But we'll also see a DownloadView which treats the state value differently based on which case it renders, and a Downloader which manipulates the case.

The proposed enum syntax for DownloadState will immediately follow the original syntax.

As a reminder: the syntax proposal is only about optional syntax for writing enum cases, removing many switch statements.

Please do read the proposal from years ago to review other examples, edge cases, and more.

protocol Loggable {
    var logLevel: String { get }
    var logMessage: String { get }
}

And the enum, in current syntax. This should seem simple and familiar.

enum DownloadState {
    case initial
    case loading(url: URL, startedAt: Date)
    case image(UIImage)
    case text(String)
    case failed(Error)
    
    var logLevel: String {
        switch self {
        case .failed:
            return "error"
        case .image, .text:
            return "info"
        case .loading, .initial:
            return "verbose"
        }
    }
    
    var logMessage: String {
        switch self {
        case .initial:
            return "Download not yet identified."
        case let .loading(url: url, startedAt: startedAt):
            return "Started downloading \(url) at \(startedAt.formatted(date: .abbreviated, time: .shortened))."
        case let .image(image):
            return "Downloaded \(image.size) sized image."
        case let .text(text):
            return "Downloaded \(text.count) characters."
        case let .failed(error):
            return "Download error: \(error)."
        }
    }
}

And the proposed syntax. I find the grouping of data in this syntax to be easy to read and understand quickly. The associated data appears only once, but can still be used by string interpolation. It uses fewer self and return keywords, but more logLevel and logMessage, in the way a subclasses would as well.

enum DownloadState {
    case initial {
      var logLevel: String { "verbose" }
      var logMessage: String { "Download not yet identified." }
    }

    case loading(url: URL, startedAt: Date) {
      var logLevel: String { "verbose" }
      var logMessage: String {
        "Started downloading \(url) at \(startedAt.formatted(date: .abbreviated, time: .shortened))."
      }
    }

    case image(_ image: UIImage) {
      var logLevel: String { "info" }
      var logMessage: String { "Downloaded \(image.size) sized image." }
    }

    case text(_ text: String) {
      var logLevel: String { "info" }
      var logMessage: String { "Downloaded \(text.count) characters." }
    }

    case failed(_ error: Error) {
      var logLevel: String { "verbose" }
      var logMessage: String { "Download error: \(error)." }
    }
}

OK, so let's look at the Downloader that manipulates the state. It is the source of all that custom data that is used in each state. It publishes the changes to the state, but is unaware of how that state is visualized.

enum DownloadError: String, Error, CustomStringConvertible {
    case badMimeType = "Bad MIME type"
    case notUTF8Text = "Text is not valid UTF-8"
    case notImageData = "Data is not a valid image"
    var description: String { rawValue }
}

actor Downloader: ObservableObject {
    @MainActor @Published var downloadState = DownloadState.initial
    
    @MainActor
    func download(url: URL) async {
        do {
            downloadState = .loading(url: url, startedAt: Date.now)
            let (data, response) = try await URLSession.shared.data(from: url)
            guard let mimeType = response.mimeType,
                  mimeType.contains("image") || mimeType.contains("text") || mimeType.contains("json")
            else {
                downloadState = .failed(DownloadError.badMimeType)
                return
            }
            if mimeType.contains("image") {
                guard let image = UIImage(data: data) else {
                    downloadState = .failed(DownloadError.notImageData)
                    return
                }
                downloadState = .image(image)
            } else if mimeType.contains("text") || mimeType.contains("json") {
                guard let text = String(data: data, encoding: .utf8) else {
                    downloadState = .failed(DownloadError.notUTF8Text)
                    return
                }
                downloadState = .text(text)
            } else {
                downloadState = .failed(DownloadError.badMimeType)
            }
        } catch {
            downloadState = .failed(error)
        }
    }
}

Finally, the DownloadView that shows different views depending upon the case, and it uses the associated data while rendering that state. It also has a constant description of the current state showing from the Loggable interface. Notice the descriptions don't use switch because that logic is inherent to the data, and thus "inside" the enum, but the view being rendered does contain a switch because it's extracting data from the enum cases for various display methods.

struct DownloadView: View {
    @StateObject var downloader = Downloader()
    
    @State var now = Date()
    
    let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        VStack {
            Form {
                switch downloader.downloadState {
                case .initial:
                    Section {}
                case .loading(url: _, startedAt: let startedAt):
                    Section {
                        LazyVStack {
                            ProgressView {
                                Text("\(now.timeIntervalSince(startedAt))s")
                            }
                        }
                    }
                case .image(let image):
                    Section {
                        Image(uiImage: image)
                    }
                case .text(let text):
                    Section {
                        Text(text)
                    }.font(.body.monospaced())
                case .failed(let error):
                    Section {
                        VStack {
                            Text("Error")
                                .font(.headline)
                            Text(String(describing: error))
                        }
                        .padding()
                        .background(.red.opacity(0.25))
                    }
                }

                Text(downloader.downloadState.logLevel.uppercased())
                    .font(.headline)
                Text(downloader.downloadState.logMessage)
                
                Button("Download Image") {
                    Task {
                        let width = Int.random(in: 500..<800)
                        let height = Int.random(in: 380..<450)
                        await downloader.download(url: URL(string: "https://placeimg.com/\(width)/\(height)/nature")!)
                    }
                }
                Button("Download Text") {
                    Task {
                        let userId = Int.random(in: 7..<13)
                        await downloader.download(url: URL(string: "https://reqres.in/api/users/\(userId)")!)
                    }
                }
                Button("Download Apple Root Cert", role: .destructive) {
                    Task {
                        await downloader.download(url: URL(string: "https://www.apple.com/appleca/AppleIncRootCertificate.cer")!)
                    }
                }
            }
        }.onReceive(timer) { input in
            now = input
        }
    }
}

@patrickgoley I hope that helps provide an additional example. And finally, here's a few screenshots of this simple app running.