type Swift 2: ¿Hay alguna forma de usar 'default' en la instrucción switch de enum con los valores asociados?




use enum in switch swift (2)

No, no hay forma de que la coincidencia de patrones de Swift coincida con los diferentes valores enum que tienen valores asociados idénticos. Esa es la respuesta a tu pregunta inmediata.

Es posible, como sugiere sabiamente Rob, refactorizar el código repetido a partir de las declaraciones de casos, pero no es posible que un único enunciado de caso coincida con los valores enum y extraiga los valores asociados.

El hecho de que te encuentres queriendo hacer esto sugiere que quizás quieras reconsiderar tu diseño enum. Existe un comportamiento compartido y una estructura compartida entre muchos de los casos, pero se supone que los casos enum son mutuamente exclusivos e independientes.

¿Quizás área, ciudad, asentamiento, calle y casa son realmente el mismo tipo de cosas?

indirect enum Location {
    case Title(String?)
    case Region(Location)
    case BinaryLocation(BinaryKind, Location, Location)

    enum BinaryKind {
        case Area
        case City
        case Settlement
        case Street
        case House
    }
}

(No entiendo cuál es el significado de esas dos ubicaciones asociadas, pero como lo hacen, recomendaría encontrar más nombres explicativos que BinaryLocation y BinaryKind ).

Esta podría no ser siquiera una situación apropiada para las enumeraciones en absoluto; por ejemplo, algo así podría funcionar mejor:

protocol Location {
    var description: String { get }
}

struct Title: Location {
    var title: String?

    var description: String {
        return title
    }
}

// ... and one for Region, and then ...

protocol BinaryLocation {
    var child0: Location { get }
    var child1: Location { get }
}

extention BinaryLocation: Location {
    var description: String {
        return "\(child0), \(child1)"
    }
}

// ...and then either individual structs for House, Street, etc., or
// an enum like BinaryKind above.

No puedo decirlo, porque no conozco toda tu situación.

Lo que puedo decir es que su preocupación por el código repetido es válida, y esa preocupación específica es una pista para dar un paso atrás y observar el panorama general de su modelo. Tomar preguntas estrictas sobre el tipo exigente como señales para reconsiderar sus opciones de modelado es la "manera rápida":

  1. "¿Por qué estoy recibiendo este error / golpear esta pared?" Se convierte en ...
  2. "¿Por qué el compilador piensa eso?" Se convierte en ...
  3. (a) "¿Qué afirmaciones estoy haciendo en mi modelo sobre la estructura de mi dominio de problema?" (b) "¿Se sostienen esas afirmaciones?"

Tengo una enumeración recursiva, donde la mayoría de los casos tienen los mismos tipos de valores asociados:

indirect enum Location {
    case Title(String?)
    case Region(Location)
    case Area(Location, Location)
    case City(Location, Location)
    case Settlement(Location, Location)
    case Street(Location, Location)
    case House(Location, Location)
}

Lo que quiero hacer es formar una buena descripción de cadena, que incluirá todos los títulos que no sean nulos.

func getStringFromLocation(location: Location) -> String? {
    var parts: [String?] = []

    switch location {
    case .Title(let title): return title
    case .House(let title, let parent):
        parts.append(getStringFromLocation(parent))
        parts.append(getStringFromLocation(title))
    case .Street(let title, let parent):
        parts.append(getStringFromLocation(parent))
        parts.append(getStringFromLocation(title))
    case .Settlement(let title, let parent):
        parts.append(getStringFromLocation(parent))
        parts.append(getStringFromLocation(title))
    case .City(let title, let parent):
        parts.append(getStringFromLocation(parent))
        parts.append(getStringFromLocation(title))
    case .Area(let title, let parent):
        parts.append(getStringFromLocation(parent))
        parts.append(getStringFromLocation(title))
    case .Region(let title):
        parts.append(getStringFromLocation(title))
    }

    return parts
        .filter { $0 != nil }
        .map { $0! }
        .joinWithSeparator(", ")
}

El problema es que cinco de siete casos posibles son exactamente iguales y tengo un montón de código de copiado que, como supongo, no es bueno. ¿Qué pasa si tengo una enumeración de cien casos?

¿Hay alguna forma de escribir algo como esto?

switch location {
case .Title(let title): 
    parts.append(title)
case .Region(let title):
    parts.append(getStringFromLocation(title))
default (let title, let parent):
    parts.append(getStringFromLocation(parent))
    parts.append(getStringFromLocation(title))
}

... usando algún caso predeterminado para manejar todos los casos similares?


Si bien estoy de acuerdo con la preocupación de Paul de que es extraño anidar la Location precisamente de esta manera, el problema básico es solucionable. Personalmente, no lo resolvería por default , simplemente simplificaría el código y usaría las herramientas que nos proporciona Swift (como CustomStringConvertible ; también puse etiquetas en sus datos; era demasiado confuso con solo dos elementos de Location que tenían completamente diferentes significados):

indirect enum Location: CustomStringConvertible {
    case Title(String?)
    case Region(Location)
    case Area(title: Location, parent: Location)
    case City(title: Location, parent: Location)
    case Settlement(title: Location, parent: Location)
    case Street(title: Location, parent: Location)
    case House(title: Location, parent: Location)

    var description: String {

        func format(locs: (Location, Location)) -> String {
            return [locs.0, locs.1].map{$0.description}.filter{$0 != ""}.joinWithSeparator(", ")
        }

        switch self {
        case .Title(let title): return title ?? ""

        case .Region(let title): return "\(title)"

        case .House(let data):      return format(data)
        case .Street(let data):     return format(data)
        case .Settlement(let data): return format(data)
        case .City(let data):       return format(data)
        case .Area(let data):       return format(data)
        }
    }
}

Observe cómo descargo toda la tupla en los data . No es necesario separar la tupla en la coincidencia de patrones. Las enumeraciones nunca tienen múltiples datos asociados. Siempre tienen exactamente una: una tupla. (Lo mismo es cierto para las funciones. Todas las funciones toman un valor y devuelven un valor. Ese valor podría ser una tupla).

Pero si realmente deseaba deshacerse de ese return format(data) repetido de return format(data) , puede hacerlo a través de Mirror . (Puedes resolver un número bastante sorprendente de cosas a través de Mirror . Debes ser muy cuidadoso antes de hacerlo. Este caso es simplemente un tipeo duplicado, no una lógica duplicada. Una mecanografía duplicada no es algo que debas crear mucha complejidad para eliminar. )

Así es como lo harías:

var description: String {
    switch self {
    case .Title(let title): return title ?? ""

    case .Region(let title): return "\(title)"

    default:
        let m = Mirror(reflecting: self)
        guard let locs = (m.children.first?.value as? (Location, Location)) else {
            preconditionFailure("Unexpected data in enum. Probably missing a case somewhere.")
        }
        return [locs.0, locs.1].map{$0.description}.filter{$0 != ""}.joinWithSeparator(", ")
    }
}

La lección aquí es que el primer hijo de una enumeración es una tupla de todos sus datos.

Pero usar Mirror es mucho más frágil (note que abrí la posibilidad de colisionar). Y aunque una enumeración es posiblemente una gran herramienta aquí, aún puede querer reconsiderar esta estructura de datos.







enumeration