Есть ли способ ограничить `Self` универсальным типом?

https://www.raywenderlich.com/148448/introduction-protocol-oriated-programming

protocol Bird {
    var name: String { get }
    var canFly: Bool { get }
    func doSomething()
}

protocol Flyable {
    var airspeedVelocity: Double { get }
}

extension Bird {
    // Flyable birds can fly!
    var canFly: Bool { return self is Flyable }
    func doSomething() {
        print("default Bird: \(name)")
    }
}

class FlappyBird: Bird, Flyable {
    let name: String
    let canFly = true
    var airspeedVelocity: Double = 5.0

    init(name: String) {
        self.name = name
    }
}

class Penguin: Bird {
    let name: String
    let canFly = false

    init(name: String) {
        self.name = name
    }
}

class Owl<T> : Bird {
    let name: String
    let power: T

    init(name: String, power: T) {
        self.name = name
        self.power = power
    }
}

extension Bird where Self: FlappyBird {
    func doSomething() {
        print("FlappyBird: \(name)")
    }
}

extension Bird where Self: Owl<String> {
    func doSomething() {
        print("Owl<String>: \(name)")
    }
}

    let fb = FlappyBird(name:"PAK")
    let penguin = Penguin(name:"Mr. Pickle")
    let nightOwl = Owl<String>(name:"Night Owl", power:"Who")
    let dayOwl = Owl<Int>(name:"Day Owl", power: 50)

    let birds: [Bird] = [fb, penguin, nightOwl, dayOwl]

    birdloop: for bird in birds {
        bird.doSomething()
    }

Результат, который я получаю:

FlappyBird: PAK
default Bird: Mr. Pickle
default Bird: Night Owl
default Bird: Day Owl

Первый результат работает, как ожидалось, так как

extension Bird where Self: FlappyBird {
    func doSomething() {
        print("FlappyBird: \(name)")
    }
}

Второй результат работает, как ожидалось, поскольку вызывает расширение протокола по умолчанию:

extension Bird {
        // Flyable birds can fly!
        var canFly: Bool { return self is Flyable }
        func doSomething() {
            print("default Bird: \(name)")
        }
    }

Третий результат, который я ожидал бы напечатать

Owl<String>: Night Owl

поскольку nightOwl относится к типу Owl<String>. Но вместо этого он вызывает расширение протокола по умолчанию:

default Bird: Night Owl

Есть какая-то причина, почему

extension Bird where Self: FlappyBird {
  func doSomething() {
    print("default Bird: \(name)")
  }
}

называется для типа FlappyBird, но

extension Bird where Self: Owl<String> {
  func doSomething() {
    print("Owl<String>: \(name)")
  }
}

нет вызывается для типа Owl<String>?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
374
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Для универсального типа Owl<T> вам разрешено иметь ограничение where Self: Owl<String>, но оно будет работать только в контекстах, где доступна информация о конкретном типе.

Чтобы было понятно, что происходит, примите во внимание следующее:

let nightOwl = Owl<String>(name: "Night Owl", power: "Who")
nightOwl.doSomething() // prints "Owl<String>: Night Owl"

В отличие от этого:

let nightOwl: Bird = Owl<String>(name: "Night Owl", power: "Who")
nightOwl.doSomething() // prints "default Bird: Night Owl"

Когда Swift создает таблицы свидетелей протокола для типов Owl<T> и FlappyBird, он должен действовать по-разному для каждого из них, поскольку Owl является универсальным. Если у него нет конкретной информации о типе, то есть Owl<String> на сайте вызова, он должен использовать реализацию по умолчанию для Owl<T>. Эта «потеря» информации о типе происходит, когда вы вставляете сов в массив типа [Bird].

В случае FlappyBird, поскольку существует только одна возможная реализация (поскольку она не является универсальной), компилятор создает таблицу свидетелей с «ожидаемой» ссылкой на метод, которой является print("FlappyBird: \(name)"). Поскольку FlappyBird не является универсальным, его таблица-свидетель не нуждается в какой-либо ссылке на неограниченную реализацию doSomething() по умолчанию и, следовательно, может правильно вызывать ограниченную реализацию, даже если конкретная информация о типе отсутствует.

Чтобы прояснить, что компилятору «требуется» откат для универсального типа, вы можете удалить соответствие Bird из Owl<T> и попытаться полагаться исключительно на ограниченную реализацию по умолчанию. Это приведет к ошибке компиляции с ошибкой, которая, как обычно в Swift, сильно вводит в заблуждение.

Value of type 'Owl' has no member 'doSomething'

По сути, кажется, что таблица свидетелей не может быть построена, потому что для этого требуется реализация, которая будет работать для всех типов T на Owl.

Рекомендации

  1. https://forums.swift.org/t/swifts-method-dispatch/7228
  2. https://developer.apple.com/videos/play/wwdc2016/416/

@AllenHumphreys ответьте здесь - достойное объяснение того, почему.

Что касается части исправления, реализуйте doSomething() для универсального Owl как:

class Owl<T> : Bird {
    //...
    func doSomething() {
        print("Owl<\(type(of: power))>: \(name)")
    }
}

и теперь вам больше не нужен doSomething() в extension Bird where Self: Owl<String>

Другие вопросы по теме