Подтвердить что ты не робот

Как использовать общий протокол как тип переменной

Скажем, у меня есть протокол:

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()
}
4b9b3361

Ответ 1

Как указывает Томас, вы можете объявить свою переменную, не указывая тип вообще (или вы можете явно указать его как тип 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? A Int? Или a String? В Swift все типы должны быть зафиксированы во время компиляции. Функция не может динамически переходить от возврата одного типа к другому на основе факторов, определенных во время выполнения.

Вместо этого вы можете использовать только StoredType в качестве общего ограничения. Предположим, вы хотели распечатать какой-либо сохраненный тип. Вы можете написать такую ​​функцию:

func printStoredValue<S: StoringType>(storer: S) {
    let x = storer.getStored()
    println(x)
}

printStoredValue(intStorer)
printStoredValue(stringStorer)

Это нормально, потому что во время компиляции он как бы компилятор выписывает две версии printStoredValue: один для Int s и один для String s. В этих двух версиях x, как известно, имеет определенный тип.

Ответ 2

Есть еще одно решение, которое не упоминалось в этом вопросе, в котором используется метод type erasure. Чтобы получить абстрактный интерфейс для общего протокола, создайте класс или структуру, которые обертывают объект или структуру, которые соответствуют протоколу. Класс оболочки, обычно называемый "Any {имя протокола]", сам соответствует протоколу и реализует его функции, пересылая все вызовы внутреннему объекту. Попробуйте пример ниже на детской площадке:

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 на базовую функцию print. Вызов base.print, не вызывая его, возвращает функцию, в которой база указана как собственная переменная, и поэтому сохраняется для будущих вызовов.

Еще одна вещь, которую следует иметь в виду, заключается в том, что это решение является, по сути, еще одним слоем динамической отправки, что означает небольшое снижение производительности. Кроме того, экземпляр стирания типа требует дополнительной памяти поверх основного экземпляра. По этим причинам стирание типа не является бесплатной абстракцией.

Очевидно, что есть некоторая работа по настройке стирания типа, но это может быть очень полезно, если требуется абстракция общего протокола. Этот шаблон найден в быстрой стандартной библиотеке с такими типами, как 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

Это может быть простой и суровый способ выражения зависимостей для протоколов, которые вы используете в своем приложении.

Ответ 3

Адресация вашего обновленного варианта использования:

(btw Printable уже является стандартным протоколом Swift, поэтому, вероятно, вы захотите выбрать другое имя, чтобы избежать путаницы)

Чтобы обеспечить конкретные ограничения для разработчиков протоколов, вы можете ограничить типы типов протоколов. Поэтому для создания вашей коллекции протоколов, требующей, чтобы элементы были доступны для печати:

// 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, подобные этим, только те, которые вы реализуете.

Вместо этого вы должны создать общие функции, которые ограничивают их ввод в коллекции, содержащие доступные для печати элементы.

func printCollection
    <C: CollectionType where C.Generator.Element: Printable>
    (source: C) {
        for x in source {
            x.print()
        }
}