programing

프로토콜이 그 자체와 맞지 않나요?

testmans 2023. 4. 25. 22:01
반응형

프로토콜이 그 자체와 맞지 않나요?

왜 이 스위프트 코드가 컴파일되지 않는 거죠?

protocol P { }
struct S: P { }

let arr:[P] = [ S() ]

extension Array where Element : P {
    func test<T>() -> [T] {
        return []
    }
}

let result : [S] = arr.test()

컴파일러가 다음과 같이 말합니다. "입력하세요. '요즘은.P프로토콜에 않습니다.P"또는 Swift의 이후 버전에서는 "프로토콜 'P'에 부합하는 구체적인 유형으로 'P'를 사용하는 것은 지원되지 않습니다."

아, 그래요? 문제는 어레이를 선언하는 데서 비롯된다는 을 깨달았습니다.arr프로토콜 유형으로 배열되어 있는데, 그게 비합리적인 일인가요?프로토콜은 구조물에 계층 구조 같은 것을 공급하는 데 도움이 된다고 생각했는데요?

왜 프로토콜이 그 자체와 맞지 않는 거죠?

일반적인 경우 프로토콜이 자체와 일치하도록 허용하는 것은 타당하지 않습니다.이 문제는 정적 프로토콜 요구 사항에 있습니다.

여기에는 다음이 포함됩니다.

  • static방법 및 특성입니다.
  • 초기화자(Initializer)입니다.
  • 연결된 형식(현재 프로토콜을 실제 형식으로 사용할 수 없음)입니다.

일반 자리 표시자에서는 이러한 요구 에 액세스할 수 있습니다.T여기서, where where에 whereT : P– 그러나 전달할 구체적인 준수 유형이 없기 때문에 프로토콜 유형 자체로는 액세스할 수 없습니다.따라서 우리는 허락할 수 없습니다.T려려 to입니다P요.

다음 에서 '있다'를 허용하면 어떻게 생각해 보세요.Array이 는 to extension extension extension extension extension to to to to to to to to to to 。[P]다음을 참조하십시오

protocol P {
  init()
}

struct S  : P {}
struct S1 : P {}

extension Array where Element : P {
  mutating func appendNew() {
    // If Element is P, we cannot possibly construct a new instance of it, as you cannot
    // construct an instance of a protocol.
    append(Element())
  }
}

var arr: [P] = [S(), S1()]

// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
arr.appendNew()

도저히 부를 수 없습니다.appendNew()에 a로요.[P] 왜냐하면 ,이유는, 입니다.P잘 알고 있습니다.Element)는 구체적인 유형이 아니므로 인스턴스화할 수 없습니다.콘크리트 형식 요소가 있는 배열에서 호출되어야 하며, 여기서 해당 유형은 다음과 같습니다.P요.

정적 방법 및 속성 요구사항도 이와 유사합니다.

protocol P {
  static func foo()
  static var bar: Int { get }
}

struct SomeGeneric<T : P> {

  func baz() {
    // If T is P, what's the value of bar? There isn't one – because there's no
    // implementation of bar's getter defined on P itself.
    print(T.bar)

    T.foo() // If T is P, what method are we calling here?
  }
}

// error: Using 'P' as a concrete type conforming to protocol 'P' is not supported
SomeGeneric<P>().baz()

이렇게 말하면 안 돼요.SomeGeneric<P>정적 프로토콜 요구 사항의 구체적인 구현이 필요합니다(어떻게 구현이 없는지 주목하십시오).foo()아니면요?bar를 참조하십시오).이러한 요구 사항의 구현은 을 통해 정의할 수 있지만요.P확장 이것은 확, 음, 음, 음, 음, 음에 부합하는 콘크리트 유형에 대해서만 정의됩니다.P– 여전히 다음 날짜에 전화를 걸 수 없습니다.P네, 그렇습니다.

이러한 이유로 Swift는 프로토콜이 정적 요구 사항이 있는 경우 프로토콜을 자체와 일치하는 유형으로 사용하는 것을 완전히 금지합니다.

인스턴스 프로토콜 요구사항은 프로토콜에 부합하는 실제 인스턴스에서 호출해야 하므로 문제가 되지 않습니다.따라서 인스턴스에서 요구 사항을 호출할 때 다음과 같이 입력합니다.P그 통화 내용을 기본 콘크리트 유형의 요구 사항 이행으로 전달할 수 있습니다

그러나 이 경우 규칙에 대해 특별한 예외를 두면 일반 코드로 프로토콜이 처리되는 방식에 놀라운 불일치가 발생할 수 있습니다.그렇다고는 하지만 상황이 그렇게 다르지 .associatedtype요구 사항 – (현재) 프로토콜을 유형으로 사용할 수 없습니다.프로토콜이 정적 요구 사항을 가지고 있을 때 프로토콜 자체를 준수하는 유형으로 하지 못하도록 제한하는 이 향후 버전의 언어에 대한 옵션이 될 수 있습니다.

편집: 아래에서 살펴본 것처럼 Swift 팀이 목표로 하는 것과 비슷합니다.


@objc입니다.

그리고 사실, 그게 바로 언어에서 다루는 방식이에요@objc프로토콜입니다.정적인 요구사항이 없는 경우, 그들은 그들 자신에게 부합합니다.

다음 컴파일은 정상입니다.

import Foundation

@objc protocol P {
  func foo()
}

class C : P {
  func foo() {
    print("C's foo called!")
  }
}

func baz<T : P>(_ t: T) {
  t.foo()
}

let c: P = C()
baz(c)

baz이렇게 해야 돼요.T일치합니다.P;는 대신 ;는 대신 쓸 수 있습니다P해위에 대해서요T왜냐면P이겁니다.여기에 정적인 조건을 요.P이 예제는 더 이상 컴파일하지 않습니다.

import Foundation

@objc protocol P {
  static func bar()
  func foo()
}

class C : P {

  static func bar() {
    print("C's bar called")
  }

  func foo() {
    print("C's foo called!")
  }
}

func baz<T : P>(_ t: T) {
  t.foo()
}

let c: P = C()
baz(c) // error: Cannot invoke 'baz' with an argument list of type '(P)'

그래서 이 문제에 대한 은 프로토콜을 만드는 것입니다.@objc. 물론, 이 방법은 많은 경우 Obj-C 런타임을 필요로 할 뿐만 아니라 Linux와 같은 Apple 이외의 플랫폼에서는 실행할 수 없기 때문에 이상적인 해결 방법이 아닙니다 물론, 이 방법은 많은 경우 Obj-C 실행 시간이 필요할 뿐만 아니라 적합한 유형을 클래스로 만들어야 하므로 Linux와 같은 Apple이 아닌 플랫폼에서는 실행 가능하지 않습니다.

하지만 이 제한은 '정적인 요구사항 없이 프로토콜이 그 자체로 적합하다'라는 표현을 이미 구현하고 있는 이유 중 하나라고 생각합니다.@objc프로토콜입니다.컴파일러는 이러한 코드 주위에 작성된 일반 코드를 상당히 단순화할 수 있습니다.

왜요 왜? 때문에요? 네, 그렇습니다.@objcprotocol-module 은 사실상 protocol-module을 사용하여 요구 사항이 디스패치되는 클래스 참조일 뿐입니다.objc_msgSend반대로 논하다 보면요.@objcprotocol-module 값은 값(간접적으로 저장된) 래핑된 값의 메모리를 관리하고 서로 다른 요구사항에 대해 호출할 구현을 결정하기 위해 값과 감시 테이블을 모두 가지고 다니기 때문에 더 복잡합니다.

그렇기 때문에 이 표현은 간결하게 표현하다.@objc프로토콜 이러한 프로토콜 유형의 값입니다.P는 일반 자리 표시자 유형의 '메모리 값과 한 메모리 표현을 공유할 수 있습니다.T : P아마도 Swift 팀이 자기 적합성을 쉽게 허용할 수 있을 것입니다.그렇지 않은 경우에도 마찬가지입니다.@objc그러나 현재 프로토콜에는 값이나 프로토콜 감시 테이블이 없습니다.

그러나 이 기능은 의도적인 것이므로 다음 사용자에게 롤아웃되기를 희망합니다.@objcSwift 팀원 Slava Pestov가 SR-55의 코멘트에서 확인한 바와 같이 프로토콜은 다음과 같습니다( 질문에 의해 권장됨).

Matt Neuburg가 의견을 추가했습니다 - 2017년 9월 7일 오후 1:33

컴파일은 다음과 같습니다.

@objc protocol P {}
class C: P {}

func process<T: P>(item: T) -> T { return item }
func f(image: P) { let processed: P = process(item:image) }

추가하고 있습니다.@objc컴파일합니다. 삭제하면 다시 컴파일되지 않습니다.Stack Overflow에 있는 우리들 중 일부는 이것이 의도적인 것인지 아니면 버그가 많은 경우인지 알고 싶어합니다.

Slava Pestov가 의견을 추가했습니다 - 2017년 9월 7일 오후 1:53

의도적인 것입니다. 이 제한을 해제하는 것이 이 버그의 목적입니다.말씀드렸듯이 까다롭고 아직 구체적인 계획은 없습니다.

그래서 언젠가는 언어가 비언어적 지원을 해줄 수 를 바랍니다.@objc프로토콜도 있습니다.

하지만 현재 어떤 해결책이 있을까요?@objc로토?? ????


프로토콜 제약 조건이 있는 확장을 구현하고 있습니다.

Swift 3.1에서 일반 자리 표시자 또는 관련 유형이 지정된 프로토콜 유형이어야 한다는 제약 조건을 가진 확장을 원하는 경우(이 프로토콜에 부합하는 구체적인 유형이 아니라) Swift 3.1을 사용하여 간단히 정의할 수 있습니다.==이겁니다.

예를 들어 어레이 확장을 다음과 같이 쓸 수 있습니다.

extension Array where Element == P {
  func test<T>() -> [T] {
    return []
  }
}

let arr: [P] = [S()]
let result: [S] = arr.test()

물론 이제는 물, 이, 음, 음, 음, 음, 음, 음, 음, type, type, type, type, type, type, type, type, type, type, type, type, type, type, type, type, type, type, type, type, type, type, type, type, that, type, that, that, that, that, that, that, that, that, that, that, that에 맞는P 이 문제를 해결하려면 , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,Element : P, 그리고 앞으로만 이동해 주세요== P장확확자를선선선선다다.

extension Array where Element : P {
  func test<T>() -> [T] {
    return (self as [P]).test()
  }
}

let arr = [S()]
let result: [S] = arr.test()

이렇게 하면 배열이 O(n)로 변환됩니다. O(n) O(n)로 변환됩니다.[P]각 요소는 기존 컨테이너에 포장되어야 합니다.성능에 문제가 있는 경우 확장 방법을 다시 구현하여 간단히 해결할 수 있습니다.이는 완전히 만족스러운 해결책은 아닙니다. 향후 버전의 언어에는 '프로토콜 유형 또는 프로토콜 유형에 부합하는' 제약 조건을 표현하는 방법이 포함되기를 바랍니다.

Swift 3.1 이전에, Rob이 답변에서 보듯이, 가장 일반적인 방법은 단순히 랩퍼 유형을 만드는 것입니다.[P]이겁니다.


프로토콜 형식 인스턴스를 제한된 일반 자리 표시자에게 전달합니다.

다음과 같은 상황을 고려합니다(조종되지만 드물지는 않은 경우).

protocol P {
  var bar: Int { get set }
  func foo(str: String)
}

struct S : P {
  var bar: Int
  func foo(str: String) {/* ... */}
}

func takesConcreteP<T : P>(_ t: T) {/* ... */}

let p: P = S(bar: 5)

// error: Cannot invoke 'takesConcreteP' with an argument list of type '(P)'
takesConcreteP(p)

통과할 수 없습니다.p로로 이동합니다.takesConcreteP(_:)지금은 대신할 수 없기 때문에요.P일반 자리 표시자일 경우 합니다.T : P이 문제를 해결할 수 있는 몇 가지 방법을 살펴보겠습니다.

1. 기존 정보를 엽니다.

대신해서 쓰려고 하지 말고요.P해위에 대해서요T : P자, 그럼 그 밑바탕에 있는 콘크리트 유형을 어떨까요?P타이핑된 값이 랩핑되어 있고 대신 그것을 대체하고 있습니까?그러나 여기에는 현재 사용자가 직접 사용할 수 없는 기존 정보 열기라는 언어 기능이 필요합니다.

그러나 Swift는 멤버에 액세스할 때 기존 구성 요소(프로토콜 형식 값)를 암시적으로 엽니다(즉, 런타임 유형을 추출하여 일반 자리 표시자 형태로 액세스할 수 있도록 함).이 사실을 악용할 수 있습니다P다음을 참조하십시오

extension P {
  func callTakesConcreteP/*<Self : P>*/(/*self: Self*/) {
    takesConcreteP(self)
  }
}

암묵적인 일반 표현에 주목하십시오.Self확장 메서드가 하는 자리 표시자로, 암시적인 확 type place 장메 、 암암 、 암암 、 암암 、 암암 the the the 。self매개 변수 – 이 작업은 모든 프로토콜 확장 멤버와 함께 백그라운드에서 수행됩니다. 입력된 값에서 메서드를 호출할 때 사용합니다.P, 는 기초 콘크리트 유형을 파내고, 이를 사용하여 , Swift, Swift를 만족시킵니다.Self이겁니다.그래서 이렇게 부를 수 있는 거예요.takesConcreteP(_:)와와 with와 함께요.self- 만족합니다 - 만족합니다T와와 with와 함께요.Self요.

이제 다음과 같이 말할 수 있습니다.

p.callTakesConcreteP()

그리고 ㅇㅇㅇㅇㅇ요.takesConcreteP(_:)일반 자리 표시자로 호출됩니다.T기초 콘크리트 유형으로 만족해야 합니다(이 경우).S의전'이 아니라 '자신에게 맞는 의정서'가 '자신에게 맞는 의정서'가 아니라'자신에게 맞는 의정서'라는 점에 유의하시기 바랍니다.P– 프로토콜에 정적 요구 사항을 추가하고 내부에서 호출하면 어떻게 되는지 확인해 보십시오takesConcreteP(_:)요.

Swift가 프로토콜을 계속 준수할 수 없는 경우, 차선책은 범용 유형의 매개변수에 대한 인수로 전달하려고 할 때 암묵적으로 기존 정보를 여는 것입니다. 즉, 상용판 없이 프로토콜 확장 trampoline이 했던 것과 정확히 동일한 작업을 수행할 수 있습니다.

그러나 기존 정보를 여는 것은 프로토콜이 자신을 준수하지 않는 문제에 대한 일반적인 해결책이 아닙니다.서로 다른 기본 콘크리트 유형을 가질 수 있는 프로토콜 유형 값의 이기종 집합은 다루지 않습니다.예를 들어 다음과 같습니다.

struct Q : P {
  var bar: Int
  func foo(str: String) {}
}

// The placeholder `T` must be satisfied by a single type
func takesConcreteArrayOfP<T : P>(_ t: [T]) {}

// ...but an array of `P` could have elements of different underlying concrete types.
let array: [P] = [S(bar: 1), Q(bar: 2)]

// So there's no sensible concrete type we can substitute for `T`.
takesConcreteArrayOfP(array) 

같은 이유로, 여러 개의 ㄹ 수 있는 함수를 사용할 수 있습니다.T 변수가 동일한 유형의 인수를 사용해야 하지만 두 개의 인수가 있는 경우에는 매개 변수도 문제가 됩니다. 그러나 두 개일 경우P컴파일 시 두 값이 동일한 기본 콘크리트 유형을 가지고 있다고 보장할 수 없습니다.

이 문제를 해결하기 위해, 우리는 활자 지우개를 사용할 수 있습니다.

2. 활자 지우개를 만듭니다.

Rob이 말하듯, 유형 지우개(type easker)는 프로토콜이 호환되지 않는 문제에 대한 가장 일반적인 해결책입니다.이를 통해 인스턴스 요구 사항을 기본 인스턴스로 전달하여 프로토콜 유형 인스턴스를 해당 프로토콜을 준수하는 구체적인 유형으로 래핑할 수 있습니다.

자 그럼 이제 글씨를 지우는 상자를 만들어볼까요?P다음을 준수하는 기본 임의 인스턴스에 대한 의 인스턴스 요구 사항입니다.P다음을 참조하십시오

struct AnyP : P {

  private var base: P

  init(_ base: P) {
    self.base = base
  }

  var bar: Int {
    get { return base.bar }
    set { base.bar = newValue }
  }

  func foo(str: String) { base.foo(str: str) }
}

자, 그럼 이번에는 그냥 이렇게 얘기하면 되겠네요.AnyP대신해서요.P다음을 참조하십시오

let p = AnyP(S(bar: 5))
takesConcreteP(p)

// example from #1...
let array = [AnyP(S(bar: 1)), AnyP(Q(bar: 2))]
takesConcreteArrayOfP(array)

이제 우리가왜 그 상자를 만들어야 했는지 잠시 생각해 보세요.앞서 논의한 바와 같이, Swift는 프로토콜에 정적 요구사항이 있는 경우 구체적인 유형이 필요합니다.생각하다, 생각하다를 생각해 보세요.P정적인 요구사항이 있었습니다. 즉, 우리는 이러한 요구사항을 다음과 같이 구현해야 했을 것입니다.AnyP하지만 그것은 무엇으로 구현되었어야 했나요 하지만 무엇으로 구현되었어야 했을까요?우리는 지금 제멋대로의 경우를 다루고 있습니다.P기본 콘크리트 유형이 정적 요구 사항을 어떻게 구현하는지 알 수 없기 때문에 이를 의미 있게 표현할 수 없습니다.AnyP요.

따라서 이 경우 솔루션은 인스턴스 프로토콜 요구 사항의 경우에만 유용합니다.일반적인 경우, 여전히 치료할 수 없습니다P에 부합하는 구체적인 유형으로요.P요.

편집: Swift와 함께 18개월을 더 일했는데, 새로운 진단 프로그램을 제공하는 또 다른 메이저 릴리스입니다. 그리고 @AyBayBay의 코멘트는 이 답변을 다시 쓰고 싶게 만듭니다.새로운 진단은 다음과 같습니다.

"프로토콜 'P'에 부합하는 구체적인 형식으로 'P'를 사용하는 것은 지원되지 않습니다."

그래서 모든 게 훨씬 더 명확해졌네요이 확장자는 다음과 같습니다.

extension Array where Element : P {

이 경우는 해당되지 않습니다.Element == P부부가래로요.P의 구체적인 준수로 간주되지 않습니다.P (아래의 "포함" 솔루션이 여전히 가장 일반적인 솔루션입니다.) (아래의 "포함" 솔루션은 여전히 가장 일반적인 솔루션입니다.)


오래된 답변입니다.

또 다른 변형 유형이에요 빨라요 대부분의 비구체적인 것에 대해 구체적인 유형으로 접근하길 원해요 [P] 구체적인 유형이 아닙니다(알려진 크기의 메모리 블록을 다음에 할당할 수 없습니다). P를 참조하십시오. (실제로 그렇지 않은 것 같습니다. 크기가 큰 것을 만들 수 있습니다.P간접적인 방법으로 이루어지기 때문입니다.)저는 이것이 "해서는 안 되는" 경우라는 증거가 없다고 생각합니다.이것은 그들의 "아직 효과가 없다" 사례 중 하나와 매우 흡사합니다. (불행하게도 애플이 두 사례 사이의 차이를 확인하도록 하는 것은 거의 불가능합니다.)사실은요.Array<P> 유형(여기서 where변변수(((((((((((((((((((((()일 수 있습니다.Arraycannot)은 이미 이 방향으로 작업을 완료했음을 나타냅니다. Swift 메타타입은 날카로운 모서리와 구현되지 않은 케이스가 많습니다. cannot)컴파일러가 허락하지 않으니까불만족스러운 건 알아요)"컴파일러가 허락하지 않으니까." (불만족스럽다는 거 알아요.제 모든 스위프트 인생이요...)

해결책은 거의 항상 물건들을 상자에 넣는 것입니다.우리는 타자기를 만듭니다.

protocol P { }
struct S: P { }

struct AnyPArray {
    var array: [P]
    init(_ array:[P]) { self.array = array }
}

extension AnyPArray {
    func test<T>() -> [T] {
        return []
    }
}

let arr = AnyPArray([S()])
let result: [S] = arr.test()

Swift에서 직접 이 작업을 수행할 수 있게 되면(결국에는 예상되지만) 자동으로 이 상자를 만들 수 있습니다.재귀적 열거형에는 정확히 이 기록이 있습니다.당신은 그것들을 포장해야 했고 그것은 믿을 수 없을 정도로 성가시고 제한적이었습니다. 그리고 마침내 컴파일러는 덧붙였습니다.indirect동일한 작업을 더 자동으로 수행할 수 있습니다.

★★★★★★★★★★★★★★★★★★★★★★CollectionType의전 대신 의전입니다.Array구체적인 유형으로 프로토콜별 구속조건은 다음과 같이 이전 코드를 다시 작성할 수 있습니다.

protocol P { }
struct S: P { }

let arr:[P] = [ S() ]

extension CollectionType where Generator.Element == P {
    func test<T>() -> [T] {
        return []
    }
}

let result : [S] = arr.test()

언급URL : https://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself 입니다.

반응형