प्रोटोकॉल स्वयं के अनुरूप क्यों नहीं हैं?
सामान्य मामले में अपने आप को अनुरूपित करने के लिए प्रोटोकॉल की अनुमति देना निराधार है। समस्या स्थैतिक प्रोटोकॉल आवश्यकताओं के साथ है।
इसमें शामिल है:
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]
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
परिभाषित नहीं हैं )। यद्यपि हम इन आवश्यकताओं के कार्यान्वयन को परिभाषित कर सकते हैं 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
।
let arr
लाइन में टाइप एनोटेशन को हटाते हैं , तो कंपाइलर टाइप को[S]
और कोड को कंपाइल करता है। ऐसा लगता है कि एक प्रोटोकॉल प्रकार को क्लास - सुपर क्लास रिलेशनशिप के समान उपयोग नहीं किया जा सकता है।