প্রোটোকল কেন তাদের সাথে খাপ খায় না?
প্রোটোকলগুলিকে সাধারণ ক্ষেত্রে নিজেকে খাপ খাইয়ে দেওয়া মজাদার নয়। স্থিতিশীল প্রোটোকল প্রয়োজনীয়তাগুলির সাথে সমস্যাটি রয়েছে।
এর মধ্যে রয়েছে:
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
) একটি কংক্রিট প্রকার নয় এবং সেইজন্য instantiated করা যাবে না। এটি অবশ্যই কংক্রিট-টাইপযুক্ত উপাদানগুলির সাথে একটি অ্যারেতে কল করা উচিত , যেখানে সেই ধরণের অনুসারে 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
নিজে কল করতে পারবেন না ।
এ কারণে, সুইফট নিজেকে প্রোটোকলকে এমন এক প্রকারের মতো ব্যবহার করতে আমাদের সম্পূর্ণরূপে অস্বীকার করে যা নিজের মতো করে - কারণ যখন প্রোটোকলের স্থির প্রয়োজনীয়তা থাকে তখন তা হয় না।
আপনার অবশ্যই যেমন ইনস্ট্যান্স প্রোটোকল প্রয়োজনীয়তাগুলি সমস্যাযুক্ত নয় প্রোটোকলের সাথে সঙ্গতিপূর্ণ এমন একটি প্রকৃত উদাহরণে তাদের কল (এবং অবশ্যই প্রয়োজনীয়তা প্রয়োগ করা উচিত)। সুতরাং যেমন টাইপ করা কোনও প্রয়োজনে কল করার সময় 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
-প্রোটোকল-টাইপ করা মানগুলি আরও জটিল, কারণ এগুলি উভয়ই তাদের (সম্ভাব্য পরোক্ষভাবে সঞ্চিত) মোড়ানো মানের মেমরি পরিচালনা করতে এবং পৃথকগুলির জন্য কোন প্রয়োগগুলি কল করতে হবে তা নির্ধারণ করার জন্য মান এবং সাক্ষী সারণী উভয়কেই বহন করে carry প্রয়োজনীয়তা, যথাক্রমে।
@objc
প্রোটোকলের জন্য এই সরলীকৃত উপস্থাপনার কারণে, এই জাতীয় প্রোটোকল ধরণের P
একটি মান কিছু জেনেরিক স্থানধারকের 'জেনেরিক মান' হিসাবে একই মেমরির উপস্থাপনা ভাগ করে নিতে পারে T : P
, সম্ভবতঃ সুইফট টিমের পক্ষে স্ব-সংশ্লেষণের অনুমতি দেওয়া সহজ হয়। অ- @objc
প্রোটোকলগুলির ক্ষেত্রেও এটি একই সত্য নয় তবে যেমন জেনেরিক মানগুলি বর্তমানে মান বা প্রোটোকল সাক্ষীর সারণী বহন করে না।
তবে এই বৈশিষ্ট্যটি হয় ইচ্ছাকৃত ও বেসরকারি আউট ঘূর্ণিত করা আশা হয় @objc
, প্রোটোকল যেমন সুইফট দলের সদস্য স্লাভা 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 পিএম
এটি ইচ্ছাকৃত - এই নিষেধাজ্ঞাকে উত্তোলনই এই বাগটি সম্পর্কে। যেমন আমি বলেছিলাম এটি জটিল এবং আমাদের কাছে এখনও কোনও দৃ concrete় পরিকল্পনা নেই।
সুতরাং আশা করি এটি এমন কিছু যা ভাষা একদিন অ- @objc
প্রোটোকলগুলির পক্ষেও সমর্থন করবে ।
তবে নন- @objc
প্রোটোকলগুলির জন্য কী কী বর্তমান সমাধান রয়েছে ?
প্রোটোকল সীমাবদ্ধতার সাথে এক্সটেনশনগুলি কার্যকর করা
সুইফট ৩.১-এ, আপনি যদি কোনও সীমাবদ্ধতার সাথে কোনও এক্সটেনশন চান তবে প্রদত্ত জেনেরিক স্থানধারক বা সম্পর্কিত প্রকারটি অবশ্যই প্রদত্ত প্রোটোকল প্রকার (কেবলমাত্র একটি কংক্রিট নয় যা সেই প্রোটোকলের সাথে সঙ্গতিপূর্ণ) হতে পারে - আপনি কেবল কোনও ==
সীমাবদ্ধতার সাথে এটি সংজ্ঞায়িত করতে পারেন ।
উদাহরণস্বরূপ, আমরা আপনার অ্যারে এক্সটেনশনটি এইভাবে লিখতে পারি:
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()
তবে এটি লক্ষণীয় যে এটি একটি অ্যারে একটি ও (এন) রূপান্তর সম্পাদন করবে [P]
, কারণ প্রতিটি উপাদান একটি অস্তিত্বের পাত্রে বাক্সে রাখতে হবে। পারফরম্যান্স যদি কোনও সমস্যা হয় তবে আপনি কেবল এক্সটেনশন পদ্ধতিটি পুনরায় প্রয়োগ করে সমাধান করতে পারেন। এটি পুরোপুরি সন্তোষজনক সমাধান নয় - আশা করি ভাষার ভবিষ্যতের সংস্করণটিতে একটি 'প্রোটোকল টাইপ বা প্রোটোকল টাইপের অনুসারে' সীমাবদ্ধতা প্রকাশ করার উপায় অন্তর্ভুক্ত থাকবে ।
সুইফট ৩.১ এর আগে রব তার উত্তরে যেমনটি অর্জন করার সর্বাধিক সাধারণ উপায়টি হ'ল এটির জন্য একটি মোড়কের ধরণটি তৈরি করা [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(_:)
পারি না । আসুন কয়েকটি দৃষ্টিতে যাচাই করে আমরা এই সমস্যাটি সমাধান করতে পারি atP
T : P
1. অস্তিত্ব খোলার
বরং প্রতিস্থাপন করার প্রচেষ্টার চেয়ে P
জন্য T : P
, কি যদি আমরা অন্তর্নিহিত কংক্রিট টাইপ মধ্যে খনন করতে পারে যে P
টাইপ মান মোড়ানো এবং বিকল্প পরিবর্তে ছিল? দুর্ভাগ্যক্রমে, এর জন্য একটি ভাষা বৈশিষ্ট্য প্রয়োজন যা খোলার অস্তিত্ব বলা হয় , যা বর্তমানে ব্যবহারকারীদের জন্য সরাসরি উপলব্ধ নয়।
যাইহোক, সুইফট করে পরোক্ষভাবে খোলা existentials (প্রোটোকল টাইপ মান) যখন তাদের উপর সদস্যদের অ্যাক্সেস (অর্থাত এটা রানটাইম টাইপ আউট digs এবং এটি একটি জেনেরিক স্থানধারক আকারে অ্যাক্সেসযোগ্য করুন)। প্রোটোকল এক্সটেনশনে আমরা এই সত্যটি কাজে লাগাতে পারি 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
মান থাকে তবে সংকলনের সময় আমরা গ্যারান্টি দেওয়ার কোনও উপায় নেই যে উভয়েরই একই অন্তর্নিহিত কংক্রিট রয়েছে have টাইপ করুন।
এই সমস্যাটি সমাধান করার জন্য, আমরা একটি টাইপ ইরেজার ব্যবহার করতে পারি।
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]
কোড এবং সংকলিত করে। দেখে মনে হচ্ছে যে কোনও প্রোটোকল টাইপ ক্লাস - সুপার ক্লাস সম্পর্ক হিসাবে একইভাবে ব্যবহার করা যায় না।