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

Определите протокол Swift, который требует определенного типа последовательности

Предположим, например, что мы говорим об элементах типа Int (но вопрос по-прежнему применяется к любому типу)

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

Стандартная библиотека Swift определяет протокол SequenceType как "Тип, который можно повторить с помощью цикла for... in loop". Поэтому мой инстинкт заключается в том, чтобы определить такой протокол:

protocol HasSequenceOfInts {
    var seq : SequenceType<Int> { get }
}

Но это не работает. SequenceType не является общим типом, который может быть специализированным, это протокол. Любой конкретный SequenceType имеет определенный тип элемента, но доступен только как связанный тип: SequenceType.Generator.Element

Итак, вопрос:

Как мы можем определить протокол, который требует определенного типа последовательности?

Вот некоторые другие вещи, которые я пробовал и почему они не правы:

Ошибка 1

protocol HasSequenceOfInts {
    var seq : SequenceType { get }
}

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

Ошибка 2

protocol HasSequenceOfInts {
    var seq : AnySequence<Int> { get }
}
class ArrayOfInts : HasSequenceOfInts {
    var seq : [Int] = [0,1,2]
}

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

Тип 'ArrayOfInts' не соответствует протоколу 'HasSequenceOfInts'

Это потому, что Array не является AnySequence (к моему удивлению... я ожидал, что AnySequence будет соответствовать любой последовательности Ints)

Ошибка 3

protocol HasSequenceOfInts {
    typealias S : SequenceType
    var seq : S { get }
}

Компилирует, но нет никаких обязательств, чтобы элементы последовательности seq имели тип Int

Ошибка 4

protocol HasSequenceOfInts {
    var seq : SequenceType where S.Generator.Element == Int
}

Невозможно использовать предложение where

Итак, теперь я полностью из идей. Я просто могу просто заставить мой протокол требовать Массив Int, но тогда я ограничиваю реализацию без уважительной причины, и это очень неуправляемо.

Успех обновления

См. ответ от @rob-napier, который очень хорошо объясняет ситуацию. Моя неудача 2 была довольно близка. Использование AnySequence может работать, но в вашем соответствующем классе вам нужно убедиться, что вы конвертируете из любой последовательности, которую вы используете в AnySequence. Например:

protocol HasSequenceOfInts {
    var seq : AnySequence<Int> { get }
}
class ArrayOfInts : HasSequenceOfInts {
    var _seq : [Int] = [0,1,2]
    var seq : AnySequence<Int> {
        get {
            return AnySequence(self._seq)
        }
    }
}
4b9b3361

Ответ 1

Есть две стороны этой проблемы:

  • Принятие произвольной последовательности ints

  • Возврат или сохранение произвольной последовательности int

В первом случае ответ заключается в использовании дженериков. Например:

func iterateOverInts<SeqInt: SequenceType where SeqInt.Generator.Element == Int>(xs: SeqInt) {
    for x in xs {
        print(x)
    }
}

Во втором случае вам нужен стиратель типа. Тип-ластик - это оболочка, которая скрывает фактический тип некоторой базовой реализации и представляет только интерфейс. У Swift есть несколько из них в stdlib, в основном с префиксом слова Any. В этом случае вы хотите AnySequence.

func doubles(xs: [Int]) -> AnySequence<Int> {
    return AnySequence( xs.lazy.map { $0 * 2 } )
}

Подробнее о AnySequence и типах-стирателях в целом см. Маленький респект для AnySequence.

Если вам это нужно в форме протокола (обычно вы этого не делаете, вам просто нужно использовать родовую версию, как в iterateOverInts), то также можно использовать ластик типа:

protocol HasSequenceOfInts {
    var seq : AnySequence<Int> { get }
}

Но seq должен возвращать AnySequence<Int>. Он не может вернуть [Int].

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

protocol HasSequenceOfInts {
    typealias SeqInt : IntegerType
    var seq: SeqInt { get }
}

Но теперь HasSequenceOfInts имеет a typealias со всеми вытекающими ограничениями. SeqInt может быть любым типом IntegerType (а не только Int), поэтому выглядит так же, как ограниченный SequenceType, и обычно нужен собственный ластик. Поэтому иногда эта методика полезна, но в вашем конкретном случае она просто возвращает вас в основном туда, где вы начали. Вы не можете ограничить SeqInt до Int здесь. Это должен быть протокол (конечно, вы могли бы придумать протокол и сделать Int единственным подходящим типом, но это не сильно меняется).

Кстати, о типах-ластиках, поскольку вы, вероятно, видите, что они очень механические. Это всего лишь коробка, которая отправляет что-то еще. Это говорит о том, что в будущем компилятор сможет автоматически генерировать эти типы-стиратели для нас. Компилятор исправил другие проблемы бокса для нас с течением времени. Например, вам приходилось создавать класс Box для хранения перечислений, которые имели общие связанные значения. Теперь это делается полуавтоматически с помощью indirect. Мы могли бы представить, что подобный механизм добавляется для автоматического создания AnySequence, когда это требуется компилятору. Поэтому я не думаю, что это глубокий "Swift дизайн не позволяет". Я думаю, что это просто "компилятор Swift не справляется с этим".

Ответ 2

Я считаю, что вам нужно отказаться от требования, чтобы он был только Int и работал вокруг него с помощью дженериков:

protocol HasSequence {
    typealias S : SequenceType
    var seq : S { get }
}

struct A : HasSequence {
    var seq = [1, 2, 3]
}

struct B : HasSequence {
    var seq : Set<String> = ["a", "b", "c"]
}

func printSum<T : HasSequence where T.S.Generator.Element == Int>(t : T) {
    print(t.seq.reduce(0, combine: +))
}

printSum(A())
printSum(B())    // Error: B.S.Generator.Element != Int

В текущем состоянии Swift вы не можете делать то, что хотите, возможно, в будущем.

Ответ 3

это очень конкретный пример по просьбе Даниэля Говарда

1) тип, соответствующий протоколу SequenceType, может быть почти любой последовательностью, даже если Array или Set соответствуют протоколу SequenceType, большая часть их функций поступает из наследования в CollectionType (который соответствует SequenceType)

Даниэль, попробуйте этот простой пример на игровой площадке

import Foundation

public struct RandomIntGenerator: GeneratorType, SequenceType {

    public func next() -> Int? {
        return random()
    }

    public func nextValue() -> Int {
        return next()!
    }
    public func generate() -> RandomIntGenerator {
        return self
    }
}

let rs = RandomIntGenerator()

for r in rs {
    print(r)
}

Как вы можете видеть, он соответствует протоколу SequenceType и создает бесконечный поток номеров Int. Прежде чем вы попытаетесь что-то реализовать, вы должны ответить на несколько вопросов.

  • Можно ли повторно использовать некоторые функции, которые доступны "бесплатно" в стандартной библиотеке Swift?
  • Я пытаюсь подражать некоторым функциям, которые не поддерживаются Swift? Swift - это не С++, Swift - это не ObjectiveC... и много конструкций, которые мы использовали, прежде чем Swift не имеет эквивалента в Swift.
  • Определите свой вопрос таким образом, чтобы мы могли понять ваши требования.

Вы ищете что-то вроде этого?

protocol P {
    typealias Type: SequenceType
    var value: Type { get set }
}
extension P {
    func foo() {
        for v in value {
            dump(v)
        }
    }
}

struct S<T: CollectionType>: P {
    typealias Type = T
    var value: Type
}

var s = S(value: [Int]())
s.value.append(1)
s.value.append(2)

s.foo()
/*
- 1
- 2
*/

let set: Set<String> = ["alfa", "beta", "gama"]
let s2 = S(value: set)
s2.foo()
/*
- beta
- alfa
- gama
*/

// !!!! WARNING !!!
// this is NOT possible
s = s2
// error: cannot assign value of type 'S<Set<String>>' to type 'S<[Int]>' (aka 'S<Array<Int>>')