2016-05-21 23 views
6

Ho un protocollo, Address, che eredita da un altro protocollo, Validator, e Address soddisfa il requisito Validator nell'estensione.Impossibile utilizzare il protocollo come associatedtype in un altro protocollo a Swift

V'è un altro protocollo, FromRepresentable, che ha un (ValueWrapper) requisito associatedType che dovrebbe essere Validator.

Ora se provo a utilizzare Address come associatedType, quindi non viene compilato. Si dice,

dedotto tipo 'indirizzo' (facendo corrispondere requisito 'valueForDetail') è valida: non è conforme a 'Validator'.

L'utilizzo è illegale? Non dovremmo essere in grado di utilizzare Address al posto di Validator, poiché tutti gli Addresses sono Validator.

Di seguito è il pezzo di codice che sto cercando.

enum ValidationResult { 
    case Success 
    case Failure(String) 
} 

protocol Validator { 
    func validate() -> ValidationResult 
} 

//Address inherits Validator 
protocol Address: Validator { 
    var addressLine1: String {get set} 
    var city: String {get set} 
    var country: String {get set} 
} 

////Fulfill Validator protocol requirements in extension 
extension Address { 
    func validate() -> ValidationResult { 
     if addressLine1.isEmpty { 
      return .Failure("Address can not be empty") 
     } 
     return .Success 
    } 
} 

protocol FormRepresentable { 
    associatedtype ValueWrapper: Validator 
    func valueForDetail(valueWrapper: ValueWrapper) -> String 
} 


// Shipping Address conforming to Address protocol. 
// It should also implicitly conform to Validator since 
// Address inherits from Validator? 
struct ShippingAddress: Address { 
    var addressLine1 = "CA" 
    var city = "HYD" 
    var country = "India" 
} 


// While compiling, it says: 
// Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform 
// to 'Validator'. 
// But Address confroms to Validator. 
enum AddressFrom: Int, FormRepresentable { 
    case Address1 
    case City 
    case Country 

    func valueForDetail(valueWrapper: Address) -> String { 
     switch self { 
     case .Address1: 
      return valueWrapper.addressLine1 
     case .City: 
      return valueWrapper.city 
     case .Country: 
      return valueWrapper.country 
     } 
    } 
} 

Aggiornamento: ha presentato una bug.

risposta

4

Il problema, che David has already eluded to, è che una volta vincolare il protocollo associatedtype a un protocollo specifico, è necessario utilizzare un tipo concreto per soddisfare il requisito
.

Questo perché protocols don't conform to themselves - il che significa, pertanto, che non è possibile utilizzare Address per soddisfare il requisito tipo associato del protocollo di un tipo che è conforme alla Validator, come Address è non un tipo che è conforme a Validator.

Come dimostro in my answer here, considerare il contro-esempio di:

protocol Validator { 
    init() 
} 
protocol Address : Validator {} 

protocol FormRepresentable { 
    associatedtype ValueWrapper: Validator 
} 

extension FormRepresentable { 
    static func foo() { 
     // if ValueWrapper were allowed to be an Address or Validator, 
     // what instance should we be constructing here? 
     // we cannot create an instance of a protocol. 
     print(ValueWrapper.init()) 
    } 
} 

// therefore, we cannot say: 
enum AddressFrom : FormRepresentable { 
    typealias ValueWrapper = Address 
} 

La soluzione più semplice sarebbe quella di abbandonare il vincolo Validator protocollo sul ValueWrapper tipo associato, che consente di utilizzare un tipo astratto nel metodo discussione.

protocol FormRepresentable { 
    associatedtype ValueWrapper 
    func valueForDetail(valueWrapper: ValueWrapper) -> String 
} 

enum AddressFrom : Int, FormRepresentable { 

    // ... 

    func valueForDetail(valueWrapper: Address) -> String { 
     // ... 
    } 
} 

Se è necessario il vincolo di tipo associato e ogni AddressFrom istanza si aspetta solo una singola implementazione concreta di Address come ingresso - si potrebbe usare farmaci generici in modo che il vostro AddressFrom a essere inizializzato con un determinato tipo concreto di indirizzo da utilizzare nel tuo metodo.

protocol FormRepresentable { 
    associatedtype ValueWrapper : Validator 
    func valueForDetail(valueWrapper: ValueWrapper) -> String 
} 

enum AddressFrom<T : Address> : Int, FormRepresentable { 

    // ... 

    func valueForDetail(valueWrapper: T) -> String { 
     // ... 
    } 
} 

// replace ShippingAddress with whatever concrete type you want AddressFrom to use 
let addressFrom = AddressFrom<ShippingAddress>.Address1 

Tuttavia, se si richiede sia il tipo di vincolo associato e ogni AddressFrom istanza deve essere in grado di gestire un ingresso di qualsiasi tipo di Address - si utilizzerà una cancellazione di tipo per avvolgere un arbitrario Address in un tipo concreto.

protocol FormRepresentable { 
    associatedtype ValueWrapper : Validator 
    func valueForDetail(valueWrapper: ValueWrapper) -> String 
} 

struct AnyAddress : Address { 

    private var _base: Address 

    var addressLine1: String { 
     get {return _base.addressLine1} 
     set {_base.addressLine1 = newValue} 
    } 
    var country: String { 
     get {return _base.country} 
     set {_base.country = newValue} 
    } 
    var city: String { 
     get {return _base.city} 
     set {_base.city = newValue} 
    } 

    init(_ base: Address) { 
     _base = base 
    } 
} 

enum AddressFrom : Int, FormRepresentable { 

    // ... 

    func valueForDetail(valueWrapper: AnyAddress) -> String { 
     // ... 
    } 
} 

let addressFrom = AddressFrom.Address1 

let address = ShippingAddress(addressLine1: "", city: "", country: "") 

addressFrom.valueForDetail(AnyAddress(address)) 
+0

Va bene, ho capito, ma c'è qualche ragione per cui dobbiamo usare concreteType per un Tipo associato con un vincolo di protocollo? Il compilatore non genera alcun errore se usiamo Address come argomento per una funzione che si aspetta il validatore. –

+0

Non capisco perché dobbiamo usare il tipo concreto se associatoTipo ha un vincolo di protocollo. Ha qualcosa a che fare con il comportamento di invarianza o l'allocazione di memoria? Avete qualche riferimento dove posso avere maggiori informazioni al riguardo. –

+0

@VishalSingh Ho paura di non poter offrire una ragione migliore di "perché è così com'è" - non posso certamente pensare a una ragione per cui non dovrebbe essere possibile, visto che la versione non vincolata funziona bene . Anche io non riesco a trovare nulla sulla mailing list o sul bug tracker di questa evoluzione. Potrebbe valere la pena archiviare un [bug report] (https://bugs.swift.org/secure/Dashboard.jspa) e vedere cosa dicono di questo. – Hamish

0

hai un diversi problemi:

Prima di tutto, in realtà non dichiarano che implementa Indirizzo Validator

//Address inherits Validator 
protocol Address : Validator { 
    var addressLine1: String {get set} 
    var city: String {get set} 
    var country: String {get set} 
} 

E non si dichiara il tipo associato per ValueWrapper:

typealias ValueWrapper = ShippingAddress 

E ti sembra di realtà verrà voglia di prendere un AddressFrom.valueForDetail ShippingAddress:

func valueForDetail(valueWrapper: ShippingAddress) -> String { 
    switch self { 
    case .Address1: 
     return valueWrapper.addressLine1 
    case .City: 
     return valueWrapper.city 
    case .Country: 
     return valueWrapper.country 
    } 
} 

Complessivamente, sembra che:

enum ValidationResult { 
    case Success 
    case Failure(String) 
} 

protocol Validator { 
    func validate() -> ValidationResult 
} 

//Address inherits Validator 
protocol Address : Validator { 
    var addressLine1: String {get set} 
    var city: String {get set} 
    var country: String {get set} 
} 

////Fulfill Validator protocol requirements in extension 
extension Address { 
    func validate() -> ValidationResult { 
     if addressLine1.isEmpty { 
      return .Failure("Address can not be empty") 
     } 
     return .Success 
    } 
} 

protocol FormRepresentable { 
    associatedtype ValueWrapper: Validator 
    func valueForDetail(valueWrapper: ValueWrapper) -> String 
} 


// Shipping Address conforming to Address protocol. 
// It should also implicity conform to Validator since 
// Address inherits from Validator? 
struct ShippingAddress: Address { 
    var addressLine1 = "CA" 
    var city = "HYD" 
    var country = "India" 
} 


// While compiling, it says: 
// Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform 
// to 'Validator'. 
// But Address confroms to Validator. 
enum AddressFrom: Int, FormRepresentable { 
    case Address1 
    case City 
    case Country 

    // define associated type for FormRepresentable 
    typealias ValueWrapper = ShippingAddress 
    func valueForDetail(valueWrapper: ShippingAddress) -> String { 
     switch self { 
     case .Address1: 
      return valueWrapper.addressLine1 
     case .City: 
      return valueWrapper.city 
     case .Country: 
      return valueWrapper.country 
     } 
    } 
} 
+0

Hey s orry Ho postato questa domanda mentre stavo ancora cercando di farlo funzionare. Ho modificato lo snippet di codice. L'indirizzo in realtà eredita dal Validator. Per quanto riguarda le tipealie, penso che dovrebbe essere in grado di inferire ValueWrapper dalla funzione valueForDetail (_: Address). Non voglio che si concretizzi. –

+0

Nota che in realtà non devi definire esplicitamente 'associatedtype' del protocollo (tramite' typealias') nella tua implementazione, Swift può dedurlo dal tipo di argomento del metodo. – Hamish