Restituzione di generici vincolati da funzioni e metodi

Vorrei creare una funzione che restituisca un object conforms a un protocollo, ma il protocollo utilizza typealias . Dato il seguente esempio di giocattolo:

 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 e Int sono stati estesi per conformarsi a HasAwesomeness e ciascuno implementa il metodo hasAwesomeness() per restituire un tipo diverso.

Ora vorrei creare una class che restituisca un object conforms al protocollo HasAwesomeness . Non mi interessa quale sia la class, solo che posso inviare il messaggio hasAwesomenss() . Quando provo quanto segue, genero un errore di compilazione:

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

ERRORE: il protocollo ‘HasAwesomeness’ può essere usato solo come un vincolo generico perché ha dei requisiti di tipo Self o associati

Come puoi immaginare, l’intenzione per returnsSomethingWithAwesomeness è di restituire un parametro key a String o Int basato su ok. L’errore che il compilatore genera in qualche modo ha senso perché non è consentito, ma fornisce una panoramica sulla correzione della syntax.

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

Bene, la mia lettura è il metodo returnsSomethingWithAwesomeness è un metodo generico che restituisce qualsiasi tipo T che ha il sottotipo HasAwesomness . Ma la seguente implementazione genera più errori di tipo compile-time:

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

ERRORE: il tipo “T” non è conforms al protocollo “StringLiteralConvertible”

ERRORE: il tipo ‘T’ non è conforms al protocollo ‘IntegerLiteralConvertible’

Bene, quindi ora sono bloccato. Qualcuno, per favore, può aiutare a colmare le lacune nella mia comprensione dei tipi e dei farmaci generici, eventualmente indicandomi risorse utili?

Penso che la chiave per capire cosa sta succedendo qui è la distinzione tra le cose che sono determinate dynamicmente in fase di runtime, e le cose che sono determinate staticamente al momento della compilazione. Non aiuta che, nella maggior parte dei linguaggi come Java, i protocolli (o interfacce) riguardino il comportamento polimorfico in fase di esecuzione , mentre in Swift vengono utilizzati anche protocolli con tipi associati per ottenere un comportamento polimorfico in fase di compilazione .

Ogni volta che vedi un segnaposto generico, come T nel tuo esempio, quale tipo è compilato per questo T è determinato al momento della compilazione. Quindi, nel tuo esempio:

func returnsSomethingWithAwesomeness(key: String) -> T

sta dicendo: returnsSomethingWithAwesomeness è una funzione che può operare su qualsiasi tipo T , purché T conforms a HasAwesomeness .

Ma ciò che è stato riempito per T è determinato al punto in returnsSomethingWithAwesomeness viene returnsSomethingWithAwesomeness con la returnsSomethingWithAwesomeness : Swift guarderà tutte le informazioni nel sito di chiamata e deciderà quale tipo T è, e sostituirà tutti T segnaposto di tipo T con quel tipo. *

Quindi supponiamo che nel sito di chiamata la scelta sia che T è una String , puoi pensare a returnsSomethingWithAwesomeness come riscritta con tutte le occorrenze del segnaposto T sostituito con 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 è sostituito da String e non con un tipo di HasAwesomeness . HasAwesomeness è usato solo come un vincolo, cioè, limitando ciò che i possibili tipi T possono essere.

Quando lo guardi in questo modo, puoi vedere che il return 42 else non ha senso – come potresti restituire 42 da una funzione che restituisce una stringa?

Per assicurarsi che returnsSomethingWithAwesomeness possa funzionare con qualunque cosa T finito, Swift ti limita a usare solo quelle funzioni che sono garantite per essere disponibili dai vincoli dati. In questo caso, tutto ciò che sappiamo di T è che è conforms a HasAwesomeness . Ciò significa che è ansible chiamare il metodo returnsSomethingWithAwesomeness su qualsiasi T , oppure utilizzarlo con un’altra funzione che vincola un tipo a HasAwesomeness , oppure assegnare una variabile di tipo T a un altro (tutti i tipi supportano l’assegnazione), e questo è quanto .

Non è ansible confrontarlo con altri Ts (nessuna garanzia supporta == ). Non puoi costruirne di nuovi (chissà se T avrà un metodo di inizializzazione appropriato?). E non puoi crearlo da una stringa o da un intero letterale (farlo richiede T per conformarsi a StringLiteralConvertible o IntegerLiteralConvertible , che non necessariamente – quindi questi due errori quando provi e crei il tipo usando uno di questi tipi di letterali).

È ansible scrivere funzioni generiche che restituiscono un tipo generico conforms a un protocollo. Ma quello che verrebbe restituito sarebbe un tipo specifico, non il protocollo, quindi quale tipo non sarebbe determinato dynamicmente. Per esempio:

 func returnCollectionContainingOne() -> 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() 

Pensa a returnCollectionContainingOne in questo codice come se fossero due funzioni, una implementata per ContiguousArray e una per Array , scritta automaticamente dal compilatore nel punto in cui le chiami (e quindi dove può correggere C per essere un tipo specifico). Non una funzione che restituisce un protocollo, ma due funzioni che restituiscono due tipi diversi. returnsSomethingWithAwesomeness stesso modo, returnsSomethingWithAwesomeness non può restituire una String o un Int in fase di esecuzione in base ad alcuni argomenti dinamici, non è ansible scrivere una versione di returnCollectionContainingOne che ha restituito un array o un array contiguo. Tutto ciò che può restituire è una T , e al momento della compilazione qualsiasi cosa T realtà può essere compilata dal compilatore.

* Questa è una leggera semplificazione di ciò che il compilatore effettivamente fa, ma lo farà per questa spiegazione.