Digamos que eu tenha um protocolo:
public protocol Printable {
typealias T
func Print(val:T)
}
E aqui está a implementação
class Printer<T> : Printable {
func Print(val: T) {
println(val)
}
}
Minha expectativa era que eu deveria ser capaz de usar Printable
variáveis para imprimir valores como este:
let p:Printable = Printer<Int>()
p.Print(67)
O compilador está reclamando deste erro:
"protocolo 'Imprimível' só pode ser usado como uma restrição genérica porque tem requisitos próprios ou de tipo associado"
Estou fazendo algo errado ? Algum jeito de arrumar isso ?
**EDIT :** Adding similar code that works in C#
public interface IPrintable<T>
{
void Print(T val);
}
public class Printer<T> : IPrintable<T>
{
public void Print(T val)
{
Console.WriteLine(val);
}
}
//.... inside Main
.....
IPrintable<int> p = new Printer<int>();
p.Print(67)
EDIT 2: exemplo do mundo real do que eu quero. Observe que isso não irá compilar, mas apresenta o que desejo alcançar.
protocol Printable
{
func Print()
}
protocol CollectionType<T where T:Printable> : SequenceType
{
.....
/// here goes implementation
.....
}
public class Collection<T where T:Printable> : CollectionType<T>
{
......
}
let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection()
for item in col {
item.Print()
}
Respostas:
Como Thomas aponta, você pode declarar sua variável não fornecendo um tipo (ou você poderia explicitamente fornecê-la como tipo
Printer<Int>
. Mas aqui está uma explicação de por que você não pode ter um tipo dePrintable
protocolo.Você não pode tratar protocolos com tipos associados como protocolos regulares e declará-los como tipos de variáveis independentes. Para pensar sobre o motivo, considere este cenário. Suponha que você tenha declarado um protocolo para armazenar algum tipo arbitrário e, em seguida, buscá-lo de volta:
// a general protocol that allows for storing and retrieving // a specific type (as defined by a Stored typealias protocol StoringType { typealias Stored init(_ value: Stored) func getStored() -> Stored } // An implementation that stores Ints struct IntStorer: StoringType { typealias Stored = Int private let _stored: Int init(_ value: Int) { _stored = value } func getStored() -> Int { return _stored } } // An implementation that stores Strings struct StringStorer: StoringType { typealias Stored = String private let _stored: String init(_ value: String) { _stored = value } func getStored() -> String { return _stored } } let intStorer = IntStorer(5) intStorer.getStored() // returns 5 let stringStorer = StringStorer("five") stringStorer.getStored() // returns "five"
OK, até agora tudo bem.
Agora, a principal razão de você ter um tipo de variável sendo um protocolo implementado por um tipo, em vez do tipo real, é para que você possa atribuir diferentes tipos de objetos que estejam em conformidade com aquele protocolo para a mesma variável e obter polimórfico comportamento em tempo de execução dependendo do que o objeto realmente é.
Mas você não pode fazer isso se o protocolo tiver um tipo associado. Como o código a seguir funcionaria na prática?
// as you've seen this won't compile because // StoringType has an associated type. // randomly assign either a string or int storer to someStorer: var someStorer: StoringType = arc4random()%2 == 0 ? intStorer : stringStorer let x = someStorer.getStored()
No código acima, qual seria o tipo de
x
? UmInt
? Ou umString
? Em Swift, todos os tipos devem ser corrigidos em tempo de compilação. Uma função não pode mudar dinamicamente de retornar um tipo para outro com base em fatores determinados em tempo de execução.Em vez disso, você só pode usar
StoredType
como uma restrição genérica. Suponha que você queira imprimir qualquer tipo de tipo armazenado. Você poderia escrever uma função como esta:func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer)
Isso está OK, porque em tempo de compilação, é como se o compilador escrevesse duas versões de
printStoredValue
: uma paraInt
s e outra paraString
s. Dentro dessas duas versões,x
é conhecido por ser de um tipo específico.fonte
p
variável e passasse tipos inválidos para elaprint
? Exceção de tempo de execução?var someStorer: StoringType<Int>
ouvar someStorer: StoringType<String>
e resolver o problema delineado.Há mais uma solução que não foi mencionada nesta questão, que é usar uma técnica chamada apagamento de tipo . Para obter uma interface abstrata para um protocolo genérico, crie uma classe ou estrutura que envolva um objeto ou estrutura em conformidade com o protocolo. A classe wrapper, geralmente chamada de 'Any {protocol name}', está em conformidade com o protocolo e implementa suas funções encaminhando todas as chamadas para o objeto interno. Experimente o exemplo abaixo em um playground:
import Foundation public protocol Printer { typealias T func print(val:T) } struct AnyPrinter<U>: Printer { typealias T = U private let _print: U -> () init<Base: Printer where Base.T == U>(base : Base) { _print = base.print } func print(val: T) { _print(val) } } struct NSLogger<U>: Printer { typealias T = U func print(val: T) { NSLog("\(val)") } } let nsLogger = NSLogger<Int>() let printer = AnyPrinter(base: nsLogger) printer.print(5) // prints 5
O tipo de
printer
é conhecidoAnyPrinter<Int>
e pode ser usado para abstrair qualquer implementação possível do protocolo da impressora. Embora AnyPrinter não seja tecnicamente abstrato, sua implementação é apenas uma queda para um tipo de implementação real e pode ser usada para separar os tipos de implementação dos tipos que os utilizam.Uma coisa a observar é que
AnyPrinter
não é necessário reter explicitamente a instância base. Na verdade, não podemos, pois não podemos declararAnyPrinter
que temos umaPrinter<T>
propriedade. Em vez disso, obtemos um ponteiro de função_print
para aprint
função de base . Chamarbase.print
sem invocar retorna uma função em que base é curried como a variável própria e, portanto, é retida para invocações futuras.Outra coisa a ter em mente é que essa solução é essencialmente outra camada de despacho dinâmico, o que significa um pequeno impacto no desempenho. Além disso, a instância de apagamento de tipo requer memória extra sobre a instância subjacente. Por essas razões, o apagamento de tipo não é uma abstração gratuita.
Obviamente, há algum trabalho para configurar a eliminação do tipo, mas pode ser muito útil se a abstração de protocolo genérico for necessária. Este padrão é encontrado na biblioteca padrão do Swift com tipos semelhantes
AnySequence
. Leitura adicional: http://robnapier.net/erasureBÔNUS:
Se você decidir que deseja injetar a mesma implementação em
Printer
todos os lugares, poderá fornecer um inicializador de conveniência para oAnyPrinter
qual injeta esse tipo.extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLog
Essa pode ser uma maneira fácil e SECA de expressar injeções de dependência para protocolos que você usa em seu aplicativo.
fonte
fatalError()
) que é descrito em outros tutoriais de apagamento de tipo.Abordando seu caso de uso atualizado:
(btw
Printable
já é um protocolo Swift padrão, então você provavelmente vai querer escolher um nome diferente para evitar confusão)Para impor restrições específicas aos implementadores de protocolo, você pode restringir as alias de tipo do protocolo. Portanto, para criar sua coleção de protocolo que requer que os elementos possam ser impressos:
// because of how how collections are structured in the Swift std lib, // you’d first need to create a PrintableGeneratorType, which would be // a constrained version of GeneratorType protocol PrintableGeneratorType: GeneratorType { // require elements to be printable: typealias Element: Printable } // then have the collection require a printable generator protocol PrintableCollectionType: CollectionType { typealias Generator: PrintableGenerator }
Agora, se você quiser implementar uma coleção que possa conter apenas elementos imprimíveis:
struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... }
No entanto, isso provavelmente é de pouca utilidade real, uma vez que você não pode restringir estruturas de coleção Swift existentes dessa forma, apenas aquelas que você implementa.
Em vez disso, você deve criar funções genéricas que restringem sua entrada a coleções que contêm elementos imprimíveis.
func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }
fonte