एक चर प्रकार के रूप में जेनेरिक प्रोटोकॉल का उपयोग कैसे करें


89

मान लीजिए कि मेरे पास एक प्रोटोकॉल है:

public protocol Printable {
    typealias T
    func Print(val:T)
}

और यहाँ कार्यान्वयन है

class Printer<T> : Printable {

    func Print(val: T) {
        println(val)
    }
}

मेरी अपेक्षा यह थी कि मैं Printableइस तरह मूल्यों को मुद्रित करने के लिए चर का उपयोग करने में सक्षम होना चाहिए :

let p:Printable = Printer<Int>()
p.Print(67)

कंपाइलर को इस त्रुटि की शिकायत है:

"प्रोटोकॉल 'प्रिंट करने योग्य' का उपयोग केवल एक सामान्य बाधा के रूप में किया जा सकता है क्योंकि इसमें स्व या संबद्ध प्रकार की आवश्यकताएं हैं"

क्या मुझसे कुछ ग़लत हो रहा है ? इसे ठीक करने का कोई उपाय ?

**EDIT :** Adding similar code that works in C#

public interface IPrintable<T> 
{
    void Print(T val);
}

public class Printer<T> : IPrintable<T>
{
   public void Print(T val)
   {
      Console.WriteLine(val);
   }
}


//.... inside Main
.....
IPrintable<int> p = new Printer<int>();
p.Print(67)

EDIT 2: वास्तविक दुनिया का उदाहरण जो मैं चाहता हूं। ध्यान दें कि यह संकलन नहीं करेगा, लेकिन जो मैं प्राप्त करना चाहता हूं उसे प्रस्तुत करता है।

protocol Printable 
{
   func Print()
}

protocol CollectionType<T where T:Printable> : SequenceType 
{
   .....
   /// here goes implementation
   ..... 
}

public class Collection<T where T:Printable> : CollectionType<T>
{
    ......
}

let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection()
for item in col {
   item.Print()
}

1
यहाँ 2014 से Apple डेवलपर फ़ोरम पर एक प्रासंगिक थ्रेड है जहाँ Apple पर एक स्विफ्ट डेवलपर द्वारा यह प्रश्न (एक डिग्री तक) संबोधित किया गया है: devforums.apple.com/thread/230611 (नोट: एक Apple डेवलपर अकाउंट को देखने के लिए यह आवश्यक है पृष्ठ।)
टाइटेनियमडेकॉय

जवाबों:


88

जैसा कि थॉमस बताते हैं, आप अपने वेरिएबल को एक प्रकार न देकर घोषित कर सकते हैं (या आप इसे स्पष्ट रूप से टाइप के रूप में दे सकते हैं Printer<Int>। लेकिन यहां इस बात का स्पष्टीकरण है कि आपके पास एक प्रकार का Printableप्रोटोकॉल क्यों नहीं हो सकता है।

आप नियमित प्रोटोकॉल जैसे संबद्ध प्रकारों के साथ प्रोटोकॉल का इलाज नहीं कर सकते हैं और उन्हें स्टैंडअलोन चर प्रकार के रूप में घोषित कर सकते हैं। ऐसा क्यों है, इस परिदृश्य पर विचार करें। मान लीजिए कि आपने कुछ मनमाने प्रकार के भंडारण के लिए एक प्रोटोकॉल घोषित किया और फिर उसे वापस लाया:

// a general protocol that allows for storing and retrieving
// a specific type (as defined by a Stored typealias
protocol StoringType {
    typealias Stored

    init(_ value: Stored)
    func getStored() -> Stored
}

// An implementation that stores Ints
struct IntStorer: StoringType {
    typealias Stored = Int
    private let _stored: Int
    init(_ value: Int) { _stored = value }
    func getStored() -> Int { return _stored }
}

// An implementation that stores Strings
struct StringStorer: StoringType {
    typealias Stored = String
    private let _stored: String
    init(_ value: String) { _stored = value }
    func getStored() -> String { return _stored }
}

let intStorer = IntStorer(5)
intStorer.getStored() // returns 5

let stringStorer = StringStorer("five")
stringStorer.getStored() // returns "five"

ठीक है, अब तक बहुत अच्छा।

अब, मुख्य कारण आपके पास एक प्रकार का वैरिएबल एक प्रोटोकॉल होगा जो वास्तविक प्रकार के बजाय एक प्रकार का उपकरण है, ऐसा इसलिए है ताकि आप विभिन्न प्रकार के ऑब्जेक्ट को असाइन कर सकें जो सभी उस प्रोटोकॉल के समान चर के अनुरूप हों, और पॉलीमॉर्फिक प्राप्त करें वास्तव में वस्तु क्या है, इसके आधार पर रनटाइम पर व्यवहार।

लेकिन आप ऐसा नहीं कर सकते हैं यदि प्रोटोकॉल में एक संबद्ध प्रकार है। निम्नलिखित कोड व्यवहार में कैसे काम करेगा?

// as you've seen this won't compile because
// StoringType has an associated type.

// randomly assign either a string or int storer to someStorer:
var someStorer: StoringType = 
      arc4random()%2 == 0 ? intStorer : stringStorer

let x = someStorer.getStored()

उपरोक्त कोड में, क्या प्रकार xहोगा? एक Int? या एक String? स्विफ्ट में, सभी प्रकार के संकलन समय पर तय होने चाहिए। एक फ़ंक्शन गतिशील रूप से रनटाइम पर निर्धारित कारकों के आधार पर एक प्रकार से दूसरे में लौटने से बदलाव नहीं कर सकता है।

इसके बजाय, आप केवल StoredTypeएक सामान्य बाधा के रूप में उपयोग कर सकते हैं । मान लीजिए कि आप किसी भी प्रकार के संग्रहित प्रकार का प्रिंट आउट लेना चाहते हैं। आप इस तरह एक समारोह लिख सकते हैं:

func printStoredValue<S: StoringType>(storer: S) {
    let x = storer.getStored()
    println(x)
}

printStoredValue(intStorer)
printStoredValue(stringStorer)

यह ठीक है, क्योंकि संकलन के समय, यह ऐसा है जैसे कंपाइलर दो संस्करणों को लिखता है printStoredValue: Intएस के लिए एक , और Stringएस के लिए एक । उन दो संस्करणों के भीतर, xएक विशिष्ट प्रकार के होने के लिए जाना जाता है।


20
दूसरे शब्दों में, पैरामीटर के रूप में जेनेरिक प्रोटोकॉल होने का कोई तरीका नहीं है और इसका कारण यह है कि स्विफ्ट, जेनेरिक के .NET शैली रनटाइम समर्थन का समर्थन नहीं करता है? यह काफी असुविधाजनक है।
ताम्रलेन

मेरा .NET ज्ञान थोड़ा आसान है ... क्या आपके पास .NET में कुछ इसी तरह का उदाहरण है जो इस उदाहरण में काम करेगा? इसके अलावा, यह देखना थोड़ा कठिन है कि आपके उदाहरण में प्रोटोकॉल आपको क्या खरीद रहा है। रनटाइम के दौरान, यदि आप अपने pवेरिएबल में विभिन्न प्रकार के प्रिंटर असाइन करते हैं और फिर अमान्य प्रकार पास करते हैं, तो आप क्या व्यवहार की अपेक्षा करेंगे print? क्रम अपवाद?
एयरस्पीड वेग

@AirspeedVelocity मैंने C # उदाहरण को शामिल करने के लिए सवाल अपडेट किया है। अच्छी तरह से मूल्य के लिए मुझे इसकी आवश्यकता क्यों है कि यह मुझे एक इंटरफ़ेस को लागू करने के लिए विकसित करने की अनुमति देगा। अगर मुझे किसी फ़ंक्शन के लिए मुद्रण योग्य पास करने की आवश्यकता है, तो मैं घोषणा में इंटरफ़ेस का उपयोग कर सकता हूं और अपने फ़ंक्शन को छूने के बिना कई अंतर कार्यान्वयनों को पास कर सकता हूं। इसके अलावा संग्रह पुस्तकालय को लागू करने के बारे में सोचें आपको इस प्रकार के कोड प्लस टी पर अतिरिक्त बाधाओं की आवश्यकता होगी।
Tamerlane

4
सैद्धांतिक रूप से, यदि सी # की तरह कोण कोष्ठक का उपयोग करके जेनेरिक प्रोटोकॉल बनाना संभव था, तो क्या प्रोटोकॉल प्रकार के चर के निर्माण की अनुमति होगी? (स्टोरिंग टाइप <Int>, StoringType <String>)
GeRyCh

1
जावा में आप समतुल्यता कर सकते हैं var someStorer: StoringType<Int>या var someStorer: StoringType<String>उस समस्या को हल कर सकते हैं जिसे आप रेखांकित करते हैं।
जेरेमीप

42

इस प्रश्न पर एक और समाधान नहीं किया गया है, जो कि टाइप इरेज़र नामक तकनीक का उपयोग कर रहा है । जेनेरिक प्रोटोकॉल के लिए एक सार इंटरफ़ेस प्राप्त करने के लिए, एक वर्ग या संरचना बनाएं जो किसी ऑब्जेक्ट या संरचना को लपेटता है जो प्रोटोकॉल के अनुरूप होता है। आवरण वर्ग, जिसे आमतौर पर 'कोई {प्रोटोकॉल नाम}' नाम दिया जाता है, स्वयं प्रोटोकॉल के अनुरूप होता है और आंतरिक कॉल पर सभी कॉल को अग्रेषित करके अपने कार्यों को कार्यान्वित करता है। एक खेल के मैदान में नीचे दिए गए उदाहरण का प्रयास करें:

import Foundation

public protocol Printer {
    typealias T
    func print(val:T)
}

struct AnyPrinter<U>: Printer {

    typealias T = U

    private let _print: U -> ()

    init<Base: Printer where Base.T == U>(base : Base) {
        _print = base.print
    }

    func print(val: T) {
        _print(val)
    }
}

struct NSLogger<U>: Printer {

    typealias T = U

    func print(val: T) {
        NSLog("\(val)")
    }
}

let nsLogger = NSLogger<Int>()

let printer = AnyPrinter(base: nsLogger)

printer.print(5) // prints 5

के प्रकार के printerलिए जाना जाता है AnyPrinter<Int>और प्रिंटर प्रोटोकॉल के किसी भी संभावित कार्यान्वयन को सार करने के लिए इस्तेमाल किया जा सकता है। जबकि AnyPrinter तकनीकी रूप से अमूर्त नहीं है, यह कार्यान्वयन एक वास्तविक कार्यान्वयन प्रकार के माध्यम से एक गिरावट है, और उनका उपयोग करके प्रकारों से कार्यान्वयन प्रकारों को डिकूप करने के लिए उपयोग किया जा सकता है।

ध्यान देने वाली एक बात यह है कि AnyPrinterआधार के उदाहरण को स्पष्ट रूप से बनाए रखने की आवश्यकता नहीं है। वास्तव में, हम नहीं कर सकते क्योंकि हम AnyPrinterएक Printer<T>संपत्ति की घोषणा नहीं कर सकते । इसके बजाय, हमें _printआधार फ़ंक्शन के लिए एक फ़ंक्शन पॉइंटर मिलता है printbase.printबिना आह्वान के यह कहते हैं कि यह एक फ़ंक्शन देता है जहां आधार को स्व-चर के रूप में क्यूरेट किया जाता है, और इस प्रकार भविष्य के चालान के लिए इसे बरकरार रखा जाता है।

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

जाहिर है कि टाइप एस्ट्रस को स्थापित करने के लिए कुछ काम है, लेकिन यह बहुत उपयोगी हो सकता है यदि जेनेरिक प्रोटोकॉल एब्स्ट्रेक्शन की जरूरत है। यह पैटर्न स्विफ्ट मानक लाइब्रेरी में पाया जाता है जैसे प्रकार AnySequence। आगे पढ़े: http://robnapier.net/erasure

बक्शीश:

यदि आप तय करते हैं कि आप Printerहर जगह एक ही कार्यान्वयन को इंजेक्ट करना चाहते हैं , तो आप इसके लिए एक सुविधा आरंभीक प्रदान कर सकते हैंAnyPrinter जिसके वह प्रकार इंजेक्ट करता है।

extension AnyPrinter {

    convenience init() {

        let nsLogger = NSLogger<T>()

        self.init(base: nsLogger)
    }
}

let printer = AnyPrinter<Int>()

printer.print(10) //prints 10 with NSLog

यह उन प्रोटोकॉल के लिए निर्भरता इंजेक्शन व्यक्त करने का एक आसान और DRY तरीका हो सकता है जो आप अपने ऐप में उपयोग करते हैं।


इसके लिए धन्यवाद। मुझे एब्सट्रैक्ट क्लास (जो निश्चित रूप से मौजूद नहीं है और जिसका उपयोग करके फेक किया जाना चाहिए fatalError()) का उपयोग करने से बेहतर है टाइप एरेस्योर (फंक्शन पॉइंटर्स का उपयोग करना) का यह पैटर्न अन्य प्रकार के इरेज़र ट्यूटोरियल में वर्णित है।
पीछा करें

4

आपके अद्यतन उपयोग मामले को संबोधित करना:

(Btw Printable पहले से ही एक मानक स्विफ्ट प्रोटोकॉल है ताकि आप भ्रम से बचने के लिए एक अलग नाम चुनना चाहें)

प्रोटोकॉल कार्यान्वयनकर्ताओं पर विशिष्ट प्रतिबंध लागू करने के लिए, आप प्रोटोकॉल के टाइपेलियास को बाधित कर सकते हैं। तो अपने प्रोटोकॉल संग्रह को बनाने के लिए तत्वों को प्रिंट करने योग्य होना चाहिए:

// because of how how collections are structured in the Swift std lib,
// you’d first need to create a PrintableGeneratorType, which would be
// a constrained version of GeneratorType
protocol PrintableGeneratorType: GeneratorType {
    // require elements to be printable:
    typealias Element: Printable
}

// then have the collection require a printable generator
protocol PrintableCollectionType: CollectionType {
    typealias Generator: PrintableGenerator
}

अब यदि आप एक संग्रह लागू करना चाहते हैं जिसमें केवल मुद्रण योग्य तत्व शामिल हो सकते हैं:

struct MyPrintableCollection<T: Printable>: PrintableCollectionType {
    typealias Generator = IndexingGenerator<T>
    // etc...
}

हालाँकि, यह शायद थोड़ी वास्तविक उपयोगिता है, क्योंकि आप मौजूदा स्विफ्ट संग्रह की संरचना को नहीं रोक सकते हैं, केवल वही जिसे आप लागू करते हैं।

इसके बजाय, आपको जेनेरिक फ़ंक्शंस बनाने चाहिए जो प्रिंट करने योग्य तत्वों वाले संग्रह में उनके इनपुट को बाधित करते हैं।

func printCollection
    <C: CollectionType where C.Generator.Element: Printable>
    (source: C) {
        for x in source {
            x.print()
        }
}

अरे यार ये तो बीमार लग रहा है। मुझे जो चाहिए वो है जेनेरिक सपोर्ट वाला प्रोटोकॉल। मैं कुछ इस तरह की उम्मीद कर रहा था: प्रोटोकॉल संग्रह <टी>: अनुक्रम। और बस। कोड के नमूने के लिए धन्यवाद मुझे लगता है कि इसे पचाने में थोड़ा समय लगेगा :)
तामेरलेन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.