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

У меня есть протокол со статическим методом с параметром по умолчанию. Я хочу изменить значение по умолчанию в классе, реализующем протокол. По сути дела, то, что легко сделать с классами и super.
У меня есть решение только тогда, когда протокол не имеет связанного типа.

Следующий код работает, но как только вы раскомментируете объявление связанного типа, он не скомпилируется.

protocol Protocol {
//    associatedtype AssociatedType
}

extension Protocol {
    func sayHello(name: String = "World") {
        print("Hello, \(name)!")
    }
}

class Class<T>: Protocol {
    typealias AssociatedType = T

    func sayHello(name: String = "Stack Overflow") {
        // Uncommenting the Protocol.AssociatedType causes:
        // Protocol can only be used as a generic constraint because it has associated type requirements
        (self as Protocol).sayHello(name)
    }
}

Class<()>().sayHello()

Я понимаю, почему он не компилируется: Protocol не имеет конкретного типа для AssociatedType.
Так что, возможно, вопрос следует читать «Могу ли я явно специализировать протокол?», на что я полагаю, что ответ отрицательный.

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

protocol Parent {}

protocol Protocol: Parent {
    associatedtype AssociatedType
}

extension Parent {
    func sayHello(name: String = "World") {
        print("Hello, \(name)!")
    }
}

class Class<T>: Protocol {
    typealias AssociatedType = T

    func sayHello(name: String = "Stack Overflow") {
        (self as Parent).sayHello(name)
    }
}

Class<()>().sayHello()

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

Просто чтобы быть уверенным, что я ясно, вот что я хотел бы, только заменив класс на протокол:

class Protocol<T> {
    func sayHello(name: String = "World") {
        print("Hello, \(name)!")
    }
}

class Class<T>: Protocol<T> {
    override func sayHello(name: String = "Stack Overflow") {
        super.sayHello(name)
    }
}

Class<()>().sayHello()

person ThinkChaos    schedule 27.08.2016    source источник


Ответы (2)


Вы пытаетесь заново изобрести наследование в протоколах, а такого нет. Но понять то, о чем вы говорите, тривиально; просто скажи, что ты имеешь в виду. Вы не имеете в виду: «Я хочу делать то, что унаследовал». Вы имеете в виду: «Я хочу вести себя обычным образом». Просто укажите имя для этого общего поведения. Это устраняет всю двусмысленность в отношении того, какой из них вы имеете в виду.

protocol Protocol {
        associatedtype AssociatedType
}

extension Protocol {
    // Put the default behavior on the protocol, not on the instance
    // Of course you could also put it on the instance if that were convenient.
    static func defaultSayHello(_ name: String = "World") {
        print("Hello, \(name)!")
    }

    // If you want a default on the instance, too, provide one that we an override
    func sayHello(_ name: String = "World") {
        Self.defaultSayHello(name)
    }
}

class Class<T>: Protocol {
    typealias AssociatedType = T

    func sayHello(name: String = "Stack Overflow") {
        // Now the default behavior lives on my type
        Class.defaultSayHello(name)
    }
}

// But other types can get default behavior
class OtherClass<T>: Protocol {
    typealias AssociatedType = T
}

Class<()>().sayHello() // Hello, Stack Overflow!
OtherClass<()>().sayHello() // Hello, World!

Единственная разочаровывающая часть этого заключается в том, что Swift не предоставляет возможности ограничить defaultSayHello разработчиками Protocol. Так что технически это может назвать кто угодно. Иногда стоит поставить перед ним префикс _, чтобы указать, что посторонние не должны этого делать. Это основная проблема контроля доступа в протоколах, не имеющая ничего общего с этим конкретным вопросом; это возникает все время, когда вы хотите «вещи, которые мои разработчики могут использовать на себе, но не должны вызываться случайным образом». Сегодня у Swift нет решения для этого.

person Rob Napier    schedule 27.08.2016
comment
Я не хочу принимать этот ответ, потому что, как вы указали, он загрязняет общедоступный API, чего я действительно пытаюсь избежать. Однако это меня вдохновило, поэтому я опубликовал свой собственный ответ (который также не отвечает на вопрос). - person ThinkChaos; 28.08.2016

Вдохновленный ответом Роба Нэпьера, вот что я сделал; старая добрая перегрузка по умолчанию:

protocol Protocol {
    associatedtype AssociatedType
}

extension Protocol {
    func sayHello(name: String = "World") {
        print("Hello, \(name)!")
    }
}

class Class<T>: Protocol {
    typealias AssociatedType = T

    func sayHello() {
        self.sayHello("Stack Overflow")
    }
}

Class<()>().sayHello()      // Hello, Stack Overflow!
Class<()>().sayHello("you") // Hello, you!

Это соответствует моим потребностям, но не отвечает на вопрос. Так что я не на 100% удовлетворен.
Я считаю, что Rust делает это правильно, позволяя трейтам/протоколам быть универсальными как с использованием X<T>, так и связанных типов.

person ThinkChaos    schedule 27.08.2016