प्रोटोकॉल खुद के अनुरूप नहीं है?


125

यह स्विफ्ट कोड संकलन क्यों नहीं करता है?

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" (या, स्विफ्ट के बाद के संस्करणों में, "पी 'का उपयोग प्रोटोकॉल के अनुरूप ठोस प्रकार का उपयोग करना है।' 'समर्थित नहीं है।")।

क्यों नहीं? यह भाषा में छेद की तरह महसूस होता है। मुझे एहसास है कि समस्या एक प्रोटोकॉल प्रकार की एक सरणी के arrरूप में सरणी घोषित करने से उपजी है , लेकिन क्या यह अनुचित बात है? मैंने सोचा कि प्रोटोकॉल वास्तव में एक प्रकार के पदानुक्रम की तरह कुछ के साथ संरचना की आपूर्ति में मदद करने के लिए थे?


1
जब आप let arrलाइन में टाइप एनोटेशन को हटाते हैं , तो कंपाइलर टाइप को [S]और कोड को कंपाइल करता है। ऐसा लगता है कि एक प्रोटोकॉल प्रकार को क्लास - सुपर क्लास रिलेशनशिप के समान उपयोग नहीं किया जा सकता है।
vadian

1
@vadian सही, मैं अपने प्रश्न का जिक्र कर रहा था जब मैंने कहा कि "मुझे एहसास है कि समस्या को गिरफ्तार करने वाले को प्रोटोकॉल प्रकार की एक सरणी के रूप में घोषित करने से उपजा है"। लेकिन, जैसा कि मैं अपने प्रश्न में कहना चाहता हूं, प्रोटोकॉल का पूरा बिंदु आमतौर पर यह है कि उनका उपयोग एक वर्ग के रूप में उसी तरह किया जा सकता है - सुपरक्लास संबंध! इनका उद्देश्य संसार की संरचना को एक प्रकार की श्रेणीबद्ध संरचना प्रदान करना है। और वे आमतौर पर करते हैं। सवाल यह है कि यहाँ काम क्यों नहीं करना चाहिए ?
मैट

1
अभी भी Xcode 7.1 में काम नहीं करता है, लेकिन त्रुटि संदेश अब "P 'का उपयोग करके एक ठोस प्रकार है जो प्रोटोकॉल' P 'के अनुरूप है, समर्थित नहीं है"
मार्टिन आर

1
@MartinR यह एक बेहतर त्रुटि संदेश है। लेकिन यह अभी भी मुझे भाषा के छेद की तरह लगता है।
मैट

ज़रूर! यहां तक ​​कि protocol P : Q { }, P, Q. के अनुरूप नहीं है
मार्टिन आर

जवाबों:


66

EDIT: अठारह महीने काम करने वाले w / Swift, एक और प्रमुख रिलीज़ (जो एक नया निदान प्रदान करता है), और @AyBayBay की एक टिप्पणी मुझे इस उत्तर को फिर से लिखना चाहती है। नया निदान है:

"प्रोटोकॉल 'पी' के अनुरूप एक ठोस प्रकार के रूप में 'पी' का उपयोग करना समर्थित नहीं है।"

यह वास्तव में इस पूरी बात को बहुत स्पष्ट करता है। यह एक्सटेंशन:

extension Array where Element : P {

तब Element == Pसे लागू नहीं होता है जब से Pएक ठोस अनुरूपता नहीं माना जाता है P। ("इसे एक बॉक्स में रखें" समाधान अभी भी सबसे सामान्य समाधान है।)


पुराना उत्तर:

यह अभी तक मेटाटाइप्स का एक और मामला है। स्विफ्ट वास्तव में चाहती है कि आप अधिकांश गैर-तुच्छ चीजों के लिए एक ठोस प्रकार प्राप्त करें। [P]एक ठोस प्रकार नहीं है (आप ज्ञात आकार की मेमोरी का एक ब्लॉक आवंटित नहीं कर सकते हैं P)। (मुझे नहीं लगता कि यह वास्तव में सच है; आप बिल्कुल कुछ आकार बना सकते हैं Pक्योंकि यह अप्रत्यक्ष रूप से किया जाता है ।) मुझे नहीं लगता कि इसका कोई सबूत है कि यह "काम नहीं" करना चाहिए। यह उनके "अभी तक काम नहीं करता है" मामलों में से एक की तरह बहुत दिखता है। (दुर्भाग्य से उन मामलों के बीच अंतर की पुष्टि करने के लिए Apple प्राप्त करना लगभग असंभव है।) तथ्य यह है कि Array<P>एक चर प्रकार (जहां हो सकता है )Arrayयह नहीं बता सकता है कि उन्होंने पहले से ही इस दिशा में कुछ काम किया है, लेकिन स्विफ्ट मेटाटाइप्स में बहुत तेज किनारों और अनइम्प्लीमेंट किए गए मामले हैं। मुझे नहीं लगता कि आप इससे बेहतर "क्यों" जवाब पाने जा रहे हैं। "क्योंकि संकलक इसे अनुमति नहीं देता है।" (असंतुष्ट, मुझे पता है। मेरी पूरी स्विफ्ट लाइफ ...)

समाधान लगभग हमेशा चीजों को एक बॉक्स में रखने के लिए होता है। हम एक प्रकार-इरेज़र का निर्माण करते हैं।

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()

जब स्विफ्ट आपको सीधे ऐसा करने की अनुमति देता है (जो मैं अंततः उम्मीद करता हूं), तो यह आपके लिए स्वचालित रूप से इस बॉक्स को बनाने की संभावना होगी। रिकर्सिव एनमों का वास्तव में यही इतिहास था। आपको उन्हें बॉक्स करना था और यह अविश्वसनीय रूप से कष्टप्रद और प्रतिबंधित था, और फिर अंत में कंपाइलर ने indirectएक ही चीज़ को अधिक स्वचालित रूप से करने के लिए जोड़ा ।


इस उत्तर में उपयोगी जानकारी के बहुत सारे, लेकिन टॉमहिरो के जवाब में वास्तविक समाधान यहां प्रस्तुत बॉक्सिंग समाधान से बेहतर है।
जेस्लर

@jsadler यह सवाल था कि सीमा के आसपास कैसे काम किया जाए, लेकिन सीमा क्यों मौजूद है। दरअसल, जहाँ तक स्पष्टीकरण जाता है, टॉमहिरो का वर्कअराउंड उत्तर देने की तुलना में अधिक प्रश्न उठाता है। यदि हम ==अपने ऐरे उदाहरण में उपयोग करते हैं , तो हमें एक त्रुटि मिलती है, समान-प्रकार की आवश्यकता सामान्य पैरामीटर को 'तत्व' गैर-सामान्य बनाता है। "टॉमहिरो ==एक ही त्रुटि उत्पन्न करने का उपयोग क्यों नहीं करता है ?
मैट

@ रोब नेपियर मैं अभी भी आपकी प्रतिक्रिया से हैरान हूं। स्विफ्ट अपने मूल बनाम समाधान में किसी भी अधिक संक्षिप्तता को कैसे देखती है? आपको लग रहा था कि किसी चीज़ को सिर्फ एक संरचना में लपेट दिया गया है ... Idk शायद मैं स्विफ्ट टाइप सिस्टम को समझने के लिए संघर्ष कर रहा हूँ लेकिन यह सब जादू की तरह लगता है
AyBayBay

@AyBayBay अपडेट किया गया उत्तर।
रोब नेपियर

बहुत बहुत धन्यवाद @RobNapier मैं हमेशा आपके उत्तरों की गति से चकित हूं और काफी स्पष्ट रूप से कि आप लोगों की मदद करने का समय कैसे पाते हैं जितना आप करते हैं। फिर भी आपके नए संपादन निश्चित रूप से इसे परिप्रेक्ष्य में रखते हैं। एक और बात जो मैं बताना चाहूंगा, उसे समझने में भी मुझे मदद मिली। इस लेख ने विशेष रूप से एक शानदार काम किया: krakendev.io/blog/generic-protocols-and-their-shortcomings TBH Idk, मैं इस सामान के बारे में कैसा महसूस करता हूं। ऐसा लगता है जैसे हम भाषा में छेद के लिए लेखांकन कर रहे हैं, लेकिन Idk कैसे सेब इस में से कुछ का निर्माण होगा।
AyBayBay

109

प्रोटोकॉल स्वयं के अनुरूप क्यों नहीं हैं?

सामान्य मामले में अपने आप को अनुरूपित करने के लिए प्रोटोकॉल की अनुमति देना निराधार है। समस्या स्थैतिक प्रोटोकॉल आवश्यकताओं के साथ है।

इसमें शामिल है:

  • static विधियाँ और गुण
  • Initialisers
  • संबद्ध प्रकार (हालांकि ये वर्तमान में एक वास्तविक प्रकार के रूप में एक प्रोटोकॉल के उपयोग को रोकते हैं)

हम एक जेनेरिक प्लेसहॉल्डर पर इन आवश्यकताओं को एक्सेस कर सकते हैं Tजहां T : P- लेकिन हम नहीं कर सकते , उन्हें प्रोटोकॉल प्रकार पर ही पहुँचने के कोई ठोस अनुरूप प्रकार पर अग्रेषित करने के लिए है के रूप में। इसलिए हम Tहोने की अनुमति नहीं दे सकतेP

विचार करें कि यदि हम Arrayविस्तार को लागू करने की अनुमति देते हैं तो निम्नलिखित उदाहरण में क्या होगा[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()[P]PElement ) एक ठोस प्रकार नहीं है और इसलिए इसे तत्काल नहीं किया जा सकता है। इसे कंक्रीट-टाइप किए गए तत्वों के साथ एक सरणी पर बुलाया जाना चाहिए, जहां उस प्रकार के अनुरूप है 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परिभाषित नहीं हैं )। यद्यपि हम इन आवश्यकताओं के कार्यान्वयन को परिभाषित कर सकते हैं aP विस्तार , ये केवल उन ठोस प्रकारों के लिए परिभाषित किए गए हैं जिनके अनुरूप P- आप अभी भी उन्हें Pस्वयं नहीं बुला सकते हैं ।

इस वजह से, स्विफ्ट हमें पूरी तरह से एक प्रोटोकॉल का उपयोग करने से रोकता है, जो खुद के अनुरूप है - क्योंकि जब उस प्रोटोकॉल में स्थिर आवश्यकताएं होती हैं, तो यह नहीं होता है।

इंस्टेंस प्रोटोकॉल की आवश्यकताएं समस्याग्रस्त नहीं हैं, क्योंकि आपको उन्हें एक वास्तविक उदाहरण पर कॉल करना होगा जो प्रोटोकॉल के अनुरूप है (और इसलिए आवश्यकताओं को लागू करना चाहिए)। इसलिए जब टाइप की आवश्यकता पर कॉल किया जाता है P, तो हम बस उस आवश्यकता के अंतर्निहित ठोस प्रकार के कार्यान्वयन पर कॉल कर सकते हैं।

हालांकि इस मामले में नियम के लिए विशेष अपवाद बनाने से प्रोटोकॉल में सामान्य कोड द्वारा कैसे व्यवहार किया जाता है, इसमें आश्चर्यजनक विसंगतियां हो सकती हैं। यद्यपि यह कहा जा रहा है कि स्थिति associatedtypeआवश्यकताओं से बहुत अधिक भिन्न नहीं है - जो (वर्तमान में) आपको एक प्रकार के रूप में एक प्रोटोकॉल का उपयोग करने से रोकती है। एक प्रतिबंध होने से जो आपको एक प्रोटोकॉल का उपयोग करने से रोकता है जो स्वयं के अनुरूप होता है जब उसके पास स्थिर आवश्यकताएं होती हैं जो भाषा के भविष्य के संस्करण के लिए एक विकल्प हो सकता है।

संपादित करें: और जैसा कि नीचे पता चला है, यह ऐसा दिखता है जैसे स्विफ्ट टीम का लक्ष्य क्या है।


@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 । दी, यह कई मामलों में एक आदर्श समाधान नहीं है, क्योंकि यह आपके अनुरूप प्रकारों को कक्षाओं के साथ-साथ ओब्ज-सी रनटाइम की आवश्यकता के लिए मजबूर करता है, इसलिए यह लिनक्स जैसे गैर-ऐप्पल प्लेटफार्मों पर व्यवहार्य नहीं बनाता है।

लेकिन मुझे संदेह है कि यह सीमा (प्राथमिक कारणों में से एक) है कि भाषा पहले से ही स्थैतिक आवश्यकताओं के बिना प्रोटोकॉल को लागू क्यों करती है? @objc है। उनके आसपास लिखे गए सामान्य कोड को कंपाइलर द्वारा काफी सरल बनाया जा सकता है।

क्यों? क्योंकि @objcप्रोटोकॉल-टाइप किए गए मान प्रभावी रूप से केवल कक्षा संदर्भ होते हैं जिनकी आवश्यकताओं का उपयोग करके भेजा जाता है objc_msgSend। दूसरी तरफ, गैर-@objc -प्रोटोकॉल-टाइप किए गए मान अधिक जटिल होते हैं, क्योंकि वे अपने मूल्य की मेमोरी और गवाह तालिका दोनों को साथ लेकर चलते हैं ताकि दोनों उनकी (संभवतः अप्रत्यक्ष रूप से संग्रहित) लिपटे मूल्य की स्मृति को प्रबंधित कर सकें और यह निर्धारित कर सकें कि अलग-अलग के लिए कॉल करने के लिए क्या कार्यान्वयन है। आवश्यकताओं, क्रमशः।

@objcप्रोटोकॉल के लिए इस सरलीकृत प्रतिनिधित्व के कारण, इस तरह के एक प्रोटोकॉल प्रकार का Pमूल्य कुछ जेनेरिक प्लेसहोल्डर के 'जेनेरिक मूल्य' के रूप में एक ही मेमोरी प्रतिनिधित्व साझा कर सकता है T : P, संभवतः स्विफ्ट टीम के लिए स्व-अनुरूपता की अनुमति देना आसान है। @objcहालांकि गैर- प्रोटोकॉल के लिए भी यह सच नहीं है, क्योंकि इस तरह के सामान्य मूल्य वर्तमान में मूल्य या प्रोटोकॉल गवाह तालिका नहीं रखते हैं।

हालांकि इस सुविधा है जानबूझकर और गैर करने के लिए उपलब्ध हो जाएगा करने के लिए उम्मीद है कि है @objc, प्रोटोकॉल के रूप में स्विफ्ट टीम के सदस्य Slava Pestov से इसकी पुष्टि एसआर -55 की टिप्पणियों में यह (से प्रेरित के बारे में अपने प्रश्न के उत्तर में यह सवाल ):

मैट न्यूबर्ग ने एक टिप्पणी जोड़ी - 7 सितंबर 2017 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इसे संकलित करता है; इसे हटाने से यह फिर से संकलित नहीं होता है। स्टैक ओवरफ्लो पर हम में से कुछ को यह आश्चर्यजनक लगता है और यह जानना चाहते हैं कि क्या यह जानबूझकर है या एक छोटी गाड़ी का मामला है।

स्लावा पेस्तोव ने एक टिप्पणी जोड़ी - 7 सितंबर 2017 1:53 अपराह्न

यह जानबूझकर है - इस प्रतिबंध को उठाना यह बग क्या है। जैसा मैंने कहा कि यह मुश्किल है और हमारे पास अभी तक कोई ठोस योजना नहीं है।

तो उम्मीद है कि यह कुछ ऐसा है कि भाषा एक दिन गैर- @objcप्रोटोकॉल के लिए भी समर्थन करेगी ।

लेकिन गैर- @objcप्रोटोकॉल के लिए वर्तमान समाधान क्या हैं ?


प्रोटोकॉल बाधाओं के साथ एक्सटेंशन को लागू करना

स्विफ्ट 3.1 में, यदि आप एक बाधा के साथ एक विस्तार चाहते हैं कि किसी दिए गए जेनेरिक प्लेसहोल्डर या संबद्ध प्रकार को एक दिया गया प्रोटोकॉल प्रकार होना चाहिए (न केवल एक ठोस प्रकार जो उस प्रोटोकॉल के अनुरूप हो) - तो आप बस इसे एक ==बाधा के साथ परिभाषित कर सकते हैं ।

उदाहरण के लिए, हम आपके सरणी विस्तार को इस प्रकार लिख सकते हैं:

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

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

बेशक, यह अब हमें इसे ठोस प्रकार के तत्वों के साथ एक सरणी पर कॉल करने से रोकता है जो अनुरूप हैं 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) रूपांतरण [P]करेगा, क्योंकि प्रत्येक तत्व को एक अस्तित्वगत कंटेनर में बॉक्स करना होगा। यदि प्रदर्शन एक समस्या है, तो आप विस्तार विधि को फिर से लागू करके इसे हल कर सकते हैं। यह पूरी तरह से संतोषजनक समाधान नहीं है - उम्मीद है कि भाषा के भविष्य के संस्करण में एक 'प्रोटोकॉल प्रकार को व्यक्त करने या प्रोटोकॉल प्रकार के अनुरूप ' को व्यक्त करने का एक तरीका शामिल होगा ।

3.1 को स्विफ्ट करने से पहले, इसे प्राप्त करने का सबसे सामान्य तरीका, जैसा कि रॉब अपने जवाब में दिखाता है , बस एक के लिए एक आवरण प्रकार का निर्माण करना है [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टाइप किए गए मूल्य को लपेटकर और बदले में प्रतिस्थापित किया गया था? दुर्भाग्य से, इसके लिए एक भाषा सुविधा की आवश्यकता होती है जिसे ओपनिंग प्रफेशनल कहा जाता है , जो वर्तमान में उपयोगकर्ताओं के लिए सीधे उपलब्ध नहीं है।

हालांकि, स्विफ्ट करता परोक्ष खुला existentials (प्रोटोकॉल टाइप मान) जब उन पर सदस्यों तक पहुँचने (यानी यह क्रम प्रकार बाहर खोदता है और यह एक जेनेरिक प्लेसहॉल्डर के रूप में सुलभ बना देता है)। हम इस तथ्य पर एक प्रोटोकॉल एक्सटेंशन में शोषण कर सकते हैं P:

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

निहित जेनेरिक Selfप्लेसहोल्डर पर ध्यान दें जो एक्सटेंशन विधि लेता है, जिसका उपयोग निहित selfपैरामीटर टाइप करने के लिए किया जाता है - यह सभी प्रोटोकॉल एक्सटेंशन सदस्यों के साथ पर्दे के पीछे होता है। प्रोटोकॉल टाइप किए गए मूल्य पर इस तरह की विधि को कॉल करते समय P, स्विफ्ट अंतर्निहित ठोस प्रकार को खोदता है, और Selfजेनेरिक प्लेसहोल्डर को संतुष्ट करने के लिए इसका उपयोग करता है । यही कारण है कि हम फोन करने में सक्षम हो takesConcreteP(_:)के साथ self- हम संतोषजनक रहे हैं Tके साथ Self

इसका मतलब है कि अब हम कह सकते हैं:

p.callTakesConcreteP()

और takesConcreteP(_:)इसके सामान्य प्लेसहोल्डर Tको अंतर्निहित ठोस प्रकार (इस मामले में S) से संतुष्ट होने के साथ बुलाया जाता है । ध्यान दें कि यह "स्वयं के अनुरूप प्रोटोकॉल" नहीं है, क्योंकि हम इसके बजाय एक ठोस प्रकार का प्रतिस्थापन कर रहे हैं P- प्रोटोकॉल में स्थिर आवश्यकता को जोड़ने का प्रयास करें और देखें कि जब आप इसे भीतर से कहते हैं तो क्या होता है takesConcreteP(_:)

यदि स्विफ्ट प्रोटोकॉल को स्वयं के अनुरूप होने से रोकना जारी रखता है, तो अगला सबसे अच्छा विकल्प स्पष्ट रूप से विद्यमानता को खोलना होगा जब उन्हें सामान्य प्रकार के मापदंडों के तर्क के रूप में पारित करने का प्रयास किया जाता है - प्रभावी रूप से हमारे प्रोटोकॉल एक्सटेंशन ट्रैम्पोलिन ने ठीक वही किया, जो बॉयलरप्लेट के बिना किया गया था।

हालाँकि, ध्यान दें कि अस्तित्ववादी खोलना प्रोटोकॉल की समस्या का सामान्य समाधान नहीं है, जो स्वयं के अनुरूप नहीं है। यह प्रोटोकॉल-टाइप किए गए मानों के विषम संग्रहों के साथ सौदा नहीं करता है, जिसमें सभी अलग-अलग अंतर्निहित ठोस प्रकार हो सकते हैं। उदाहरण के लिए, विचार करें:

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. एक प्रकार इरेज़र बनाएँ

जैसा कि रोब कहते हैं , एक प्रकार का इरेज़र , प्रोटोकॉल की समस्या का सबसे सामान्य समाधान है जो स्वयं के अनुरूप नहीं है। वे हमें एक प्रोटोकॉल में टाइप किए गए उदाहरण को एक ठोस प्रकार में लपेटने की अनुमति देते हैं जो उस प्रोटोकॉल के अनुरूप होता है, उदाहरण की आवश्यकताओं को अंतर्निहित उदाहरण के लिए अग्रेषित करके।

तो, चलिए एक प्रकार के मिटाने वाले बॉक्स का निर्माण करते हैं जो 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)

अब, एक क्षण के लिए विचार करें कि हमें उस बॉक्स का निर्माण क्यों करना था। जैसा कि हमने शुरुआती चर्चा की, स्विफ्ट को उन मामलों के लिए एक ठोस प्रकार की आवश्यकता होती है जहां प्रोटोकॉल की स्थिर आवश्यकताएं हैं। विचार करें कि Pक्या कोई स्थैतिक आवश्यकता है - हमें इसे लागू करने की आवश्यकता होगी AnyP। लेकिन इसे किस रूप में लागू किया जाना चाहिए था? हम मनमाने उदाहरणों के साथ काम कर रहे हैं जो Pयहाँ के अनुरूप हैं - हम इस बारे में नहीं जानते हैं कि उनके अंतर्निहित ठोस प्रकार स्थिर आवश्यकताओं को कैसे लागू करते हैं, इसलिए हम इसे सार्थक रूप से व्यक्त नहीं कर सकते AnyP

इसलिए, इस मामले में समाधान केवल उदाहरण के प्रोटोकॉल आवश्यकताओं के मामले में वास्तव में उपयोगी है । सामान्य स्थिति में, हम अभी भी Pएक ठोस प्रकार के रूप में व्यवहार नहीं कर सकते हैं जो इसके अनुरूप है P


2
हो सकता है कि मैं अभी सघन हो रहा हूं, लेकिन मुझे समझ नहीं आ रहा है कि स्टैटिक केस क्यों खास है। हम (संकलक) किसी प्रोटोटॉल की स्थैतिक संपत्ति के बारे में कम से कम संकलन समय पर जानते हैं, क्योंकि हम एक प्रोटोकॉल उदाहरण की संपत्ति के बारे में जानते हैं, जिसे अपनाने वाला इसे लागू करेगा। तो क्या अंतर है?
मैट

1
@matt एक प्रोटोकॉल-टाइप की गई आवृत्ति (अर्थात कंक्रीट में लिपटे हुए ठोस-प्रकार का उदाहरण P) ठीक है क्योंकि हम अंतर्निहित आवश्यकताओं के लिए केवल इंस्टेंस आवश्यकताओं को कॉल अग्रेषित कर सकते हैं। हालांकि, एक प्रोटोकॉल प्रकार के लिए (यानी P.Protocol, सचमुच, एक प्रोटोकॉल का वर्णन करने वाला केवल प्रकार) - कोई गोद लेने वाला नहीं है, इसलिए स्थैतिक आवश्यकताओं को कॉल करने के लिए कुछ भी नहीं है, यही कारण है कि उपरोक्त उदाहरण में हमारे पास नहीं हो सकता है SomeGeneric<P>(यह है एक अलग P.Type(अस्तित्वगत मेटाटाइप) के लिए अलग , जो किसी चीज़ के ठोस मेटाटाइप का वर्णन करता है P- जिसके अनुरूप - लेकिन यह एक और कहानी है)
हैमिश

इस पृष्ठ के शीर्ष पर मैं जो सवाल पूछता हूं, वह यह है कि प्रोटोकॉल-प्रकार अपनाने वाला ठीक है और प्रोटोकॉल प्रकार ही नहीं है। मैं समझता हूं कि एक प्रोटोकॉल प्रकार के लिए खुद को गोद लेने वाला नहीं है। - मुझे समझ में नहीं आता है कि स्थैतिक कॉल को गोद लेने के लिए आगे किसी भी प्रकार से कठिन क्यों नहीं है, उदाहरण के लिए कॉल को अपनाने के प्रकार को अग्रेषित करना है। आप एक तर्क दे रहे हैं कि विशेष रूप से स्थैतिक आवश्यकताओं की प्रकृति के कारण यहां एक कठिनाई है, लेकिन मैं यह नहीं देखता कि स्थैतिक आवश्यकताएं उदाहरण की आवश्यकताओं की तुलना में अधिक कठिन हैं।
मैट

@matt यह नहीं है कि स्थिर आवश्यकताएं उदाहरण की आवश्यकताओं की तुलना में "कठिन" हैं - संकलक इंस्टेंसेस के माध्यम से दोनों उदाहरणों के माध्यम से ठीक कर सकता है (यानी उदाहरण के रूप में टाइप किया गया है P) और एक अस्तित्वगत मेटाटिप्स (यानी P.Typeमेटाटाइप्स)। समस्या यह है कि जेनेरिक के लिए - हम वास्तव में जैसे के लिए तुलना नहीं कर रहे हैं। जब , कोई ठोस कंक्रीट (मेटा) प्रकार नहीं Tहै P, तो स्थैतिक आवश्यकताओं को आगे बढ़ाने के लिए ( Ta P.Protocol, not a P.Type) है ....
Hamish

1
मैं वास्तव में साउंडनेस आदि के बारे में परवाह नहीं करता हूं, मैं सिर्फ ऐप लिखना चाहता हूं, और अगर ऐसा लगता है कि इसे काम करना चाहिए तो बस इसे करना चाहिए। भाषा केवल एक उपकरण होनी चाहिए, उत्पाद नहीं। अगर कुछ ऐसे मामले हैं जो वास्तव में इसके लिए काम नहीं करेंगे, तो उन मामलों में इसे ठीक कर दें लेकिन बाकी सभी उन मामलों का उपयोग करें जिनके लिए यह काम कर रहा है और उन्हें लेखन ऐप्स के साथ आने दें।
जोनाथन।

17

यदि आप एक ठोस प्रकार के रूप में प्रोटोकॉल द्वारा और बाधा के 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()

मुझे नहीं लगता कि संग्रह बनाम एरे यहां प्रासंगिक है, महत्वपूर्ण परिवर्तन == Pबनाम का उपयोग कर रहा है : P। == मूल उदाहरण के साथ भी काम करता है। अगर मैं एक बनाने: और एक संभावित समस्या (संदर्भ के आधार पर) == साथ है कि यह शामिल नहीं उप प्रोटोकॉल है protocol SubP: P, और फिर परिभाषित arrके रूप में [SubP]तो arr.test()अब काम नहीं करेगा (त्रुटि: SubP और पी बराबर होना चाहिए)।
इमरे
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.