Devolución de genéricos restringidos desde funciones y métodos


Me gustaría crear una función que devuelva un objeto que se ajuste a un protocolo, pero el protocolo usa un typealias. Dado el siguiente ejemplo de juguete:

protocol HasAwesomeness {
    typealias ReturnType
    func hasAwesomeness() -> ReturnType
}

extension String: HasAwesomeness {
    func hasAwesomeness() -> String {
        return "Sure Does!"
    }
}

extension Int: HasAwesomeness {
    func hasAwesomeness() -> Bool {
        return false
    }
}

String y Int se han extendido para ajustarse a HasAwesomeness, y cada uno implementa el método hasAwesomeness() para devolver un tipo diferente.

Ahora me gustaría crear una clase que devuelva un objeto que se ajuste al protocolo HasAwesomeness. No me importa cuál sea la clase, solo que puedo enviar el mensaje hasAwesomenss(). Cuando intento el a continuación, genero un error de compilación:

class AmazingClass: NSObject {
    func returnsSomethingWithAwesomeness(key: String) -> HasAwesomeness {
        ...
    }
}

ERROR: El protocolo 'HasAwesomeness' solo se puede usar como una restricción genérica porque tiene requisitos de tipo propios o asociados

Como se puede imaginar, la intención de returnsSomethingWithAwesomeness es devolver un String o Int basado en aceptar el parámetro key. El error que arroja el compilador tiene sentido por qué no está permitido, pero da una idea de cómo arreglar la sintaxis.

func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
{
    ...
}

Bien, mi lectura is the method returnsSomethingWithAwesomeness es un método genérico que devuelve cualquier tipo T que tenga el subtipo HasAwesomness. Pero, la siguiente implementación arroja más errores de tipo en tiempo de compilación:

func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
{
    if key == "foo" {
        return "Amazing Foo"
    }
    else {
        return 42
    }
}

ERROR: El tipo ' T ' no se ajusta al protocolo 'StringLiteralConvertible'

ERROR: El tipo ' T ' no se ajusta al protocolo 'IntegerLiteralConvertible'

Bien, así que ahora estoy atascado. ¿Podría alguien por favor ayudar a llenar los vacíos en mi comprensión acerca de tipos y genéricos, posiblemente me señala a recursos útiles?

Author: zoul, 2014-12-10

1 answers

Creo que la clave para entender lo que está pasando aquí es distinguir entre las cosas que se determinan dinámicamente en tiempo de ejecución, y las cosas que se determinan estáticamente en tiempo de compilación. No ayuda que, en la mayoría de lenguajes como Java, los protocolos (o interfaces) tratan de obtener un comportamiento polimórfico en tiempo de ejecución, mientras que en Swift, los protocolos con tipos asociados también se utilizan para obtener un comportamiento polimórfico en tiempo de compilación.

Cada vez que ves un marcador de posición genérico, como T en su ejemplo, qué tipo se rellena para este T se determina en tiempo de compilación. Así, en su ejemplo:

func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T

Está diciendo: returnsSomethingWithAwesomeness es una función que puede operar en cualquier tipo T, siempre y cuando T se ajuste a HasAwesomeness.

Pero lo que se rellena para T se determina en el punto returnsSomethingWithAwesomeness se llama-Swift mirará toda la información en el sitio de la llamada y decidirá qué tipo es T, y reemplazará todos los marcadores de posición T con ese tipo.*

Así que supongamos que en el sitio de la llamada la opción es que T es un String, puede pensar en returnsSomethingWithAwesomeness como reescrito con todas las ocurrencias del marcador de posición T reemplazado por String:

// giving the type of s here fixes T as a String
let s: String = returnsSomethingWithAwesomeness("bar")

func returnsSomethingWithAwesomeness(key: String) -> String {
    if key == "foo" {
        return "Amazing Foo"
    }
    else {
        return 42
    }
}

Nota, T se sustituye por Stringy no por un tipo de HasAwesomeness. HasAwesomeness solo se usa como una restricción, es decir, restringiendo qué tipos posibles T pueden ser.

Cuando lo miras así, puedes ver que return 42 en el else no hace sense - ¿cómo podría devolver 42 de una función que devuelve una cadena?

Para asegurarse de que returnsSomethingWithAwesomeness puede funcionar con lo que sea que T termine siendo, Swift le restringe a usar solo aquellas funciones que están garantizadas para estar disponibles a partir de las restricciones dadas. En este caso, todo lo que sabemos acerca de T es que se ajusta a HasAwesomeness. Esto significa que puede llamar al método returnsSomethingWithAwesomeness en cualquier T, o usarlo con otra función que constriñe un tipo a HasAwesomeness, o asignar una variable de tipo T a otra uno (todos los tipos soportan asignación), y eso es.

No se puede comparar con otros Ts (no hay garantía de que sea compatible con ==). No puede construir otros nuevos (¿quién sabe si T tendrá un método de inicialización apropiado?). Y no se puede crear a partir de una cadena o entero literal (hacer eso requeriría T para ajustarse a StringLiteralConvertible o IntegerLiteralConvertible, que no necesariamente-de ahí esos dos errores cuando intenta crear el tipo utilizando uno de estos tipos de literal).

Es posible escribir funciones genéricas que devuelvan un tipo genérico que se ajuste a un protocolo. Pero lo que se devolvería sería un tipo específico, no el protocolo, por lo que el tipo no se determinaría dinámicamente. Por ejemplo:

func returnCollectionContainingOne<C: ExtensibleCollectionType where C.Generator.Element == Int>() -> C {

    // this is allowed because the ExtensibleCollectionType procol 
    // requires the type implement an init() that takes no parameters
    var result = C()

    // and it also defines an `append` function that allows you to do this:
    result.append(1)

    // note, the reason it was possible to give a "1" as the argument to
    // append was because of the "where C.Generator.Element == Int" part
    // of the generic placeholder constraint 

    return result
}

// now you can use returnCollectionContainingOne with arrays:
let a: [Int] = returnCollectionContainingOne()

// or with ContiguousArrays:
let b: ContiguousArray = returnCollectionContainingOne()

Piense en returnCollectionContainingOne en este código como si fueran realmente dos funciones, una implementada para ContiguousArray y otra para Array, escritas automáticamente por el compilador en el punto en que las llama (y por lo tanto donde puede arreglar C para ser un tipo específico). No una función que devuelve un protocolo, sino dos funciones que devuelven dos tipos diferentes. Así que de la misma manera returnsSomethingWithAwesomeness no puede devolver un String o un Int en tiempo de ejecución basado en algún argumento dinámico, no se puede escribir una versión de returnCollectionContainingOne que devuelva un array o un array contiguo. Todo lo que puede devolver es un T, y en tiempo de compilación cualquier T realmente es puede ser rellenado por el compilador.

* esto es una ligera simplificación de lo que el compilador en realidad lo hace, pero va a hacer para esta explicación.

 58
Author: Airspeed Velocity,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-10 19:38:01