Как использовать универсальный протокол в качестве типа переменной
допустим, у меня есть протокол:
public protocol Printable {
typealias T
func Print(val:T)
}
а вот и реализация
class Printer<T> : Printable {
func Print(val: T) {
println(val)
}
}
Я ожидал, что я должен быть в состоянии использовать Printable переменная для печати таких значений:
let p:Printable = Printer<Int>()
p.Print(67)
компилятор жалуется на эту ошибку :
"протокол ' Printable' может использоваться только в качестве общего ограничения, потому что
он имеет собственные или связанные требования к типу"
Я делаю что-то неправильно ? В любом случае, чтобы исправить это ?
**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: реальный пример того, что я хочу. Обратите внимание, что это не будет компилироваться, но представляет то, что я хочу достичь.
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()
}
3 ответов:
как указывает Томас, вы можете объявить свою переменную, не давая тип вообще (или вы могли бы явно дать его как тип
Printer<Int>. Но вот объяснение, почему вы не можете иметь типPrintableпротокол.вы не можете обрабатывать протоколы с соответствующими типами, такими как обычные протоколы, и объявлять их как автономные типы переменных. Чтобы подумать о том, почему, рассмотрим этот сценарий. Предположим, вы объявили протокол для хранения некоторого произвольного типа, а затем извлекли его назад:
// 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"хорошо, пока все хорошо.
теперь основная причина, по которой у вас будет тип переменной, - это протокол, который реализует тип, а не фактический тип, так что вы можете назначить различные типы объектов, которые все соответствуют этому протоколу, одной и той же переменной и получить полиморфное поведение во время выполнения в зависимости от того, что на самом деле является объектом.
но вы не можете сделать это, если протокол имеет связанный тип. Как будет работать следующий код на практике?
// 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()в приведенном выше коде, что бы типа
xбыть? АнInt? ИлиString? В Swift все типы должны быть исправлены во время компиляции. Функция не может динамически переключаться с одного типа на другой на основе факторов, определенных во время выполнения.вместо этого, вы можете использовать
StoredTypeкак общее ограничение. Предположим,вы хотите распечатать любой сохраненный тип. Вы можете написать такую функцию, как это:func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer)это нормально, потому что во время компиляции, если компилятор записывает две версии
printStoredValue: дляIntС иStringы. В этих двух версиях,xкак известно, имеет определенный тип.
есть еще одно решение, которое не было упомянуто по этому вопросу, которое использует метод под названием стирания типа. Чтобы создать абстрактный интерфейс для универсального протокола, создайте класс или структуру, которая обертывает объект или структуру, соответствующие протоколу. Класс-оболочка, обычно называемый " Any{protocol name}", сам соответствует протоколу и реализует свои функции путем перенаправления всех вызовов на внутренний объект. Попробуйте пример ниже на детской площадке:
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тип
printerизвестно, чтоAnyPrinter<Int>и может использоваться для абстрагирования любой возможной реализации протокола принтера. Хотя AnyPrinter не является технически абстрактным, его реализация-это просто переход к реальному типу реализации и может использоваться для отделения типов реализации от типов, использующих их.следует отметить, что
AnyPrinterне обязательно явно сохранять базовый экземпляр. На самом деле, мы не можем, так как мы не можем объявитьAnyPrinterиметьPrinter<T>собственность. Вместо этого мы получаем указатель на функцию_printна базу . Звонюbase.printбез вызова она возвращает функцию, где база карри как переменная self, и таким образом сохранены для последующих вызовов.еще одна вещь, которую следует иметь в виду, заключается в том, что это решение по существу является еще одним слоем динамической отправки, что означает небольшое снижение производительности. Кроме того, экземпляр стирания типа требует дополнительной памяти поверх базового пример. По этим причинам стирание типа не является бесплатной абстракцией.
очевидно, что есть некоторая работа по настройке стирания типа, но это может быть очень полезно, если требуется абстракция общего протокола. Этот шаблон находится в стандартной библиотеке swift с такими типами, как
AnySequence. Дальнейшее чтение:http://robnapier.net/erasureбонус:
если вы решите, что хотите ввести ту же реализацию
Printerвезде, вы можете обеспечьте инициализатор удобства дляAnyPrinterкоторый вводит этот тип.extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLogэто может быть простой и сухой способ выразить инъекции зависимости для протоколов, которые вы используете в своем приложении.
адресация вашего обновленного варианта использования:
(кстати,
Printableуже является стандартным протоколом Swift, поэтому вы, вероятно, захотите выбрать другое имя, чтобы избежать путаницы)чтобы применить определенные ограничения на исполнителей протокола, вы можете ограничить typealias протокола. Итак, чтобы создать свою коллекцию протоколов, которая требует, чтобы элементы были доступны для печати:
// 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 }теперь, если вы хотите реализовать коллекцию, которая может содержать только печатаемые элементы:
struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... }однако, это, вероятно, мало фактической полезности, так как вы не можете ограничить существующие структуры Swift collection, как это, только те, которые вы реализуете.
вместо этого следует создать универсальные функции, которые ограничивают их ввод в коллекции, содержащие печатаемые элементы.
func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }
Comments