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

Массивы дженериков в Swift

Я играл с массивами общих классов с разными типами. Проще всего объяснить мою проблему с помощью некоторого примера кода:

// Obviously a very pointless protocol...
protocol MyProtocol {
    var value: Self { get }
}

extension Int   : MyProtocol {  var value: Int    { return self } }
extension Double: MyProtocol {  var value: Double { return self } }

class Container<T: MyProtocol> {
    var values: [T]

    init(_ values: T...) {
        self.values = values
    }

    func myMethod() -> [T] {
        return values
    }
}

Теперь, если я попытаюсь создать массив контейнеров, например:

var containers: [Container<MyProtocol>] = []

Я получаю сообщение об ошибке:

Протокол "MyProtocol" может использоваться только в качестве общего ограничения, поскольку он имеет требования к себе или связанному типу.

Чтобы исправить это, я могу использовать [AnyObject]:

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]
// Explicitly stating the types just for clarity.

Но теперь возникает другая "проблема" при перечислении через containers:

for container in containers {
    if let c = container as? Container<Int> {
        println(c.myMethod())

    } else if let c = container as? Container<Double> {
        println(c.myMethod())
    }
}

Как вы можете видеть в приведенном выше коде, после определения типа container в обоих случаях вызывается тот же метод. Мой вопрос:

Есть ли лучший способ получить container с правильным типом, чем приведение к любому возможному типу container? Или есть что-то еще, что я забыл?

4b9b3361

Ответ 1

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

protocol MyProtocol {
    func getValue() -> Self 
}

extension Int: MyProtocol {
    func getValue() -> Int {
        return self
    }
}

extension Double: MyProtocol {
    func getValue() -> Double {
        return self
    }
}

Обратите внимание, что свойство value, которое вы первоначально вложили в объявление протокола, было изменено на метод, возвращающий объект.

Это не очень интересно.

Но теперь, поскольку вы избавились от свойства value в протоколе, MyProtocol может использоваться как тип, а не только как ограничение типа. Ваш класс Container больше не нужен вообще. Вы можете объявить это следующим образом:

class Container {
    var values: [MyProtocol]

    init(_ values: MyProtocol...) {
        self.values = values
    }

    func myMethod() -> [MyProtocol] {
        return values
    }
}

И поскольку Container больше не является общим, вы можете создать Array из Container и выполнить итерацию по ним, распечатав результаты метода myMethod():

var containers = [Container]()

containers.append(Container(1, 4, 6, 2, 6))
containers.append(Container(1.2, 3.5))

for container in containers {
    println(container.myMethod())
}

//  Output: [1, 4, 6, 2, 6]
//          [1.2, 3.5]

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

И в качестве бонуса (если вы хотите это назвать), ваш массив значений MyProtocol может даже смешивать разные типы, соответствующие MyProtocol. Поэтому, если вы дадите расширение String a MyProtocol следующим образом:

extension String: MyProtocol {
    func getValue() -> String {
        return self
    }
}

Фактически вы можете инициализировать Container со смешанными типами:

let container = Container(1, 4.2, "no kidding, this works")

[Предупреждение. Я тестирую это на одной из игровых площадок онлайн. Я еще не смог проверить его в Xcode...]

Edit:

Если вы все еще хотите, чтобы Container был общим и содержал только один тип объекта, вы можете добиться этого, выполнив его собственный протокол:

protocol ContainerProtocol {
    func myMethod() -> [MyProtocol]
}

class Container<T: MyProtocol>: ContainerProtocol {
    var values: [T] = []

    init(_ values: T...) {
        self.values = values
    } 

    func myMethod() -> [MyProtocol] {
        return values.map { $0 as MyProtocol }
    }
}

Теперь у вас все еще может быть массив объектов [ContainerProtocol] и итерации через них, вызывающие myMethod():

let containers: [ContainerProtocol] = [Container(5, 3, 7), Container(1.2, 4,5)]

for container in containers {
    println(container.myMethod())
}

Возможно, это все еще не работает для вас, но теперь Container ограничивается одним типом, и все же вы все равно можете перебирать массив из объектов ContainterProtocol.

Ответ 2

Это хороший пример того, "что вы хотели?" И на самом деле демонстрирует сложность, которая взрывается, если у Swift действительно были первоклассные типы.

protocol MyProtocol {
    var value: Self { get }
}

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

var containers: [Container<MyProtocol>] = []

Итак, определенный во время компиляции, какой тип это? Забудьте компилятор, просто сделайте это на бумаге. Да, не знаю, какого типа это будет. Я имею в виду конкретный тип. Нет метатипов.

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]

Вы знаете, что спуститесь по неправильной дороге, когда AnyObject пробрался в ваши подписи. Ничто об этом никогда не будет работать. После AnyObject будет только вретище.

Или есть что-то еще, что я забыл?

Да. Вам нужен тип, и вы его не предоставили. Вы предоставляете правило для ограничения типа, но не фактического типа. Вернитесь к своей реальной проблеме и подумайте об этом глубже. (Анализ метатипов почти никогда не является вашей "реальной" проблемой, если вы не работаете над CS PhD, и в этом случае вы будете делать это в Идрисе, а не в Swift.) Какую актуальную проблему вы решаете?

Ответ 3

Это может быть лучше объяснено с помощью таких протоколов, как Equatable. Вы не можете объявить массив [Equatable], потому что в то время как два экземпляра Int можно сравнивать друг с другом, а два экземпляра Double можно сравнивать друг с другом, вы не можете сравнить Int с Double, хотя они оба реализовать Equatable.

MyProtocol - это протокол, который означает, что он предоставляет общий интерфейс. К сожалению, вы также использовали Self в определении. Это означает, что каждый тип, соответствующий MyProtocol, будет реализовывать его по-разному.

Вы сами написали - Int будет иметь value как var value: Int, а MyObject будет иметь value как var value: MyObject.

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

Если вы замените этот Self на конкретный тип, например. AnyObject, он будет работать. Однако в настоящее время (Xcode 6.3.1) он вызывает ошибку сегментации при компиляции).

Ответ 4

Если вы попробуете этот измененный пример на игровой площадке, он будет систематически аварийно:

// Obviously a very pointless protocol...
protocol MyProtocol {
    var value: Int { get }
}

extension Int   : MyProtocol {  var value: Int    { return self } }
//extension Double: MyProtocol {  var value: Double { return self } }

class Container<T: MyProtocol> {
    var values: [T]

    init(_ values: T...) {
        self.values = values
    }
}


var containers: [Container<MyProtocol>] = []

Возможно, они все еще работают над этим, и в будущем все может измениться. В любом случае, теперь я объясняю это тем, что протокол не является конкретным типом. Таким образом, вы уже не видите, сколько места в барах будет занимать что-то, соответствующее протоколу (например, Int может не занимать одинаковое количество бара как Double). Таким образом, может быть довольно сложной проблемой выделение массива в ram. Используя NSArray, вы выделяете массив указателей (указатели на NSObjects), и все они занимают одинаковое количество бара. Вы можете придумать NSArray как массив конкретного типа "указатель на NSObject". Таким образом, проблема не вычисляется при распределении ram.

Считайте, что Array, а также Dictionary в Swift являются Generic Struct, а не объектами, содержащими указатели на объекты, как в Obj-C.

Надеюсь, что это поможет.

Ответ 5

Я изменил объявление массива как массив AnyObject, чтобы можно было использовать фильтр, карту и уменьшить (а также добавить еще несколько объектов для проверки).

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0), "Hello", "World", 42]

Это позволит вам проверить тип в массиве и фильтр перед тем, как вы пройдете цикл через массив

let strings = containers.filter({ return ($0 is String) })

println(strings) // [Hello, World]

for ints in containers.filter({ return ($0 is Int) }) {
    println("Int is \(foo)") // Int is 42
}

let ints = containers.filter({ return ($0 is Container<Int>) })
// as this array is known to only contain Container<Int> instances, downcast and unwrap directly
for i in ints as [Container<Int>] {
    // do stuff
    println(i.values) // [1, 2, 3]
}