ไม่สามารถเชี่ยวชาญฟังก์ชันทั่วไปได้อย่างชัดเจน


92

ฉันมีปัญหากับรหัสต่อไปนี้:

func generic1<T>(name : String){
}

func generic2<T>(name : String){
     generic1<T>(name)
}

generic1 (ชื่อ)ผลการรวบรวมข้อผิดพลาด "ไม่สามารถมีความเชี่ยวชาญอย่างชัดเจนฟังก์ชั่นทั่วไป"

มีวิธีใดบ้างที่จะหลีกเลี่ยงข้อผิดพลาดนี้ ฉันไม่สามารถเปลี่ยนลายเซ็นของฟังก์ชัน generic1 ได้ดังนั้นจึงควรเป็น (String) -> Void


2
อะไรคือจุดสำคัญของการใช้ประเภททั่วไปที่นี่เมื่อไม่สามารถอนุมานสำหรับบริบทได้? หากใช้ประเภททั่วไปภายในเท่านั้นคุณควรระบุประเภทภายในเนื้อความของฟังก์ชัน
Kirsteins

มีการTใช้ประเภทตัวยึดตำแหน่งgeneric1()หรือไม่ คุณจะเรียกใช้ฟังก์ชันนั้นอย่างไรเพื่อให้คอมไพเลอร์สามารถสรุปประเภทได้
Martin R

3
ฉันหวังว่าจะมีวิธีเรียกใช้ฟังก์ชันเช่น gene1 <blaClass> ("SomeString")
Greyisf

2
Generics ไม่ได้มีไว้สำหรับกรณีที่คอมไพเลอร์สามารถอนุมานบริบทได้เท่านั้น การอธิบายถึงฟังก์ชันที่เชี่ยวชาญอย่างชัดเจนจะทำให้สามารถอนุมานส่วนอื่น ๆ ของโค้ดได้ อดีต: func foo<T>() -> T { ... }ที่ไม่อะไรกับวัตถุtชนิดและผลตอบแทนT tความเชี่ยวชาญอย่างชัดเจนTจะช่วยให้t1สามารถแทรกซึมเข้าไปvar t1 = foo<T>()ได้ ฉันหวังว่าจะมีวิธีเรียกใช้ฟังก์ชันเหล่านี้ด้วย C # อนุญาต
nacho4d

1
ฉันเพิ่งเจอเรื่องนี้เหมือนกัน ไม่มีเหตุผลที่จะสร้างฟังก์ชันทั่วไปหากคุณถูกบังคับให้ส่งประเภทเป็นพารามิเตอร์ นี่มันต้องมีบั๊กใช่ไหม!
นิค

คำตอบ:


161

ฉันมีปัญหานี้เช่นกันและพบวิธีแก้ปัญหาสำหรับกรณีของฉัน

ในบทความนี้ผู้เขียนมีปัญหาเดียวกัน

https://www.iphonelife.com/blog/31369/swift-programming-101-generics-practical-guide

ดังนั้นปัญหาดูเหมือนว่าคอมไพเลอร์จำเป็นต้องสรุปประเภทของ T อย่างใด แต่ไม่อนุญาตให้ใช้ <type> ทั่วไป (params ... )

โดยปกติคอมไพลเลอร์สามารถค้นหาชนิดของ T ได้โดยการสแกนประเภทพารามิเตอร์เนื่องจากเป็นที่ที่ใช้ T ในหลายกรณี

ในกรณีของฉันมันแตกต่างกันเล็กน้อยเพราะประเภทการส่งคืนของฟังก์ชันของฉันคือ T. ในกรณีของคุณดูเหมือนว่าคุณไม่ได้ใช้ T เลยในฟังก์ชันของคุณ ฉันเดาว่าคุณเพิ่งทำให้โค้ดตัวอย่างง่ายขึ้น

ดังนั้นฉันจึงมีฟังก์ชันดังต่อไปนี้

func getProperty<T>( propertyID : String ) -> T

และในกรณีของตัวอย่างเช่น

getProperty<Int>("countProperty")

คอมไพเลอร์ให้ข้อผิดพลาด:

ไม่สามารถเชี่ยวชาญฟังก์ชันทั่วไปได้อย่างชัดเจน

ดังนั้นเพื่อให้แหล่งข้อมูลอื่นแก่คอมไพเลอร์ในการอนุมานประเภทของ T คุณต้องประกาศประเภทของตัวแปรอย่างชัดเจนว่าค่าที่ส่งคืนจะถูกบันทึกไว้

var value : Int = getProperty("countProperty")

วิธีนี้ทำให้คอมไพลเลอร์รู้ว่า T ต้องเป็นจำนวนเต็ม

ดังนั้นฉันคิดว่าโดยรวมก็หมายความว่าถ้าคุณระบุฟังก์ชันทั่วไปอย่างน้อยคุณต้องใช้ T ในประเภทพารามิเตอร์ของคุณหรือเป็นประเภทการส่งคืน


2
ช่วยฉันประหยัดเวลาได้มาก ขอบคุณ.
chrislarson

4
มีวิธีดำเนินการดังกล่าวหรือไม่หากไม่มีค่าตอบแทน เช่นfunc updateProperty<T>( propertyID : String )
Kyle Bashour

1
ฉันไม่เห็นว่าทำไมคุณถึงอยากทำแบบนั้น? เนื่องจากคุณไม่ได้ส่งคืนอะไรเลยจึงไม่มีประโยชน์ในการประกาศฟังก์ชันของคุณโดยทั่วไป
ThottChief

@ThottChief มีประโยชน์มากมายเช่นหากคุณใช้ / สร้างแป้นพิมพ์หรือรับชื่อประเภทเป็นสตริงเป็นต้น
zaitsman

ขอบคุณคำตอบที่ดี ฉันหวังว่าสิ่งนี้จะใช้งานได้let value = foo() as? Typeเพื่อให้สามารถใช้ใน a ifหรือguardผลลัพธ์จะเป็นทางเลือก แต่มันไม่ ...
agirault

54

สวิฟต์ 5

โดยทั่วไปมีหลายวิธีในการกำหนดฟังก์ชันทั่วไป แต่จะขึ้นอยู่กับเงื่อนไขที่Tต้องใช้เป็น a parameterหรือเป็นreturn type.

extension UIViewController {
    class func doSomething<T: UIView>() -> T {
        return T()
    }

    class func doSomethingElse<T: UIView>(value: T) {
        // Note: value is a instance of T
    }

    class func doLastThing<T: UIView>(value: T.Type) {
        // Note: value is a MetaType of T
    }
}

หลังจากนั้นเราต้องให้Tเมื่อโทร

let result = UIViewController.doSomething() as UIImageView // Define `T` by casting, as UIImageView
let result: UILabel = UIViewController.doSomething() // Define `T` with property type, as UILabel
UIViewController.doSomethingElse(value: UIButton()) // Define `T` with parameter type, as UIButton
UIViewController.doLastThing(value: UITextView.self) // Define `T` with parameter type, as UITextView

อ้างอิง:

  1. http://austinzheng.com/2015/01/02/swift-generics-pt-1/
  2. https://dispatchswift.com/type-constraints-for-generics-in-swift-d6bf2f0dbbb2

คำตอบที่ดีสำหรับปัญหาทั่วไป ... เนื่องจากมีหลายวิธีในการแก้ไข ฉันยังตระหนักว่าself.result = UIViewController.doSomething()งานของมันเองตราบเท่าที่คุณพิมพ์ทรัพย์สินเมื่อคุณประกาศ
teradyl

คำตอบที่ดีในการอธิบายสิ่งต่างๆมากมาย
Okhan Okbay

Java doLastThing<UITextView>()กลายเป็น Swift doLastThing(UITextView.self)ซึ่งไม่ใช่สิ่งที่แย่ที่สุดอย่างน้อยที่สุด ดีกว่าต้องพิมพ์ผลลัพธ์ที่ซับซ้อนอย่างโจ่งแจ้ง ขอบคุณสำหรับวิธีแก้ปัญหา
Erhannis

18

วิธีแก้ปัญหาคือการใช้ประเภทคลาสเป็นพารามิเตอร์ (เช่นใน Java)

เพื่อให้คอมไพเลอร์รู้ว่าเขากำลังจัดการกับประเภทใดให้ส่งคลาสเป็นอาร์กิวเมนต์

extension UIViewController {
    func navigate<ControllerType: UIViewController>(_ dump: ControllerType.Type, id: String, before: ((ControllerType) -> Void)?){
        let controller = self.storyboard?.instantiateViewController(withIdentifier: id) as! ControllerType
        before?(controller)
        self.navigationController?.pushViewController(controller, animated: true)
    }
}

โทรเป็น:

self.navigate(UserDetailsViewController.self, id: "UserDetailsViewController", before: {
        controller in
        controller.user = self.notification.sender
    })

1
นี่เป็นคำตอบที่ดีและดีกว่าคำตอบที่ยอมรับ โปรดพิจารณาแก้ไขเพื่อลบส่วนที่ผ่านมาหรืออย่างน้อยก็วางไว้ด้านล่างโซลูชัน ขอบคุณ!
Dan Rosenstark

1
@DanRosenstark ขอบคุณสำหรับคำติชม :)
Orkhan Alikhanov

แม้ว่าจะได้ผล แต่ฉันไม่คิดว่ามันเป็นแนวทางปฏิบัติที่ดีที่สุด เหตุผลก็คือตอนนี้คำจำกัดความของฟังก์ชันกำลังตัดสินใจว่าจะทำอย่างไรหากมีปัญหากับนักแสดง ดังนั้นที่นี่คุณจะพังหากการแคสต์ล้มเหลว เป็นการดีกว่าที่จะให้ลูกค้าตัดสินใจว่าจะทำอย่างไรเนื่องจากสิ่งนี้อาจแตกต่างกันไปตามลูกค้า ดังนั้นฉันจะลงคะแนนคำตอบนี้ด้วยเหตุผลนี้
smileBot

@smileBot คุณหมายถึงตัวอย่างไม่ดีหรือแนวทาง?
Orkhan Alikhanov

วิธีการ. ฉันคิดว่ามันเป็นกฎทั่วไปของการเขียนโปรแกรมที่จะเลื่อนความเชี่ยวชาญออกไปเมื่อใช้งานได้จริง นี่เป็นส่วนหนึ่งของแนวคิดในการเข้าใจความรับผิดชอบ ไม่ใช่หน้าที่ของฟังก์ชันที่จะเชี่ยวชาญเรื่องทั่วไป นี่คือความรับผิดชอบของผู้โทรเนื่องจากฟังก์ชันไม่สามารถทราบได้ว่าผู้โทรทั้งหมดต้องการอะไร หากฟังก์ชั่นเข้ามาในบทบาทนี้จะ จำกัด การใช้ฟังก์ชันนี้เพื่อไม่ให้ได้กำไร คุณสามารถสรุปสิ่งนี้ให้กับปัญหาการเข้ารหัสได้มากมาย แนวคิดเดียวนี้ช่วยฉันได้มหาศาล
smileBot

4

คุณไม่จำเป็นต้องใช้ทั่วไปที่นี่เนื่องจากคุณมีประเภทคงที่ (สตริงเป็นพารามิเตอร์) แต่ถ้าคุณต้องการให้ฟังก์ชันทั่วไปเรียกฟังก์ชันอื่นคุณสามารถทำสิ่งต่อไปนี้ได้

ใช้วิธีการทั่วไป

func fetchObjectOrCreate<T: NSManagedObject>(type: T.Type) -> T {
    if let existing = fetchExisting(type) {
       return existing
    }
    else {
        return createNew(type)
    }
}

func fetchExisting<T: NSManagedObject>(type: T.Type) -> T {
    let entityName = NSStringFromClass(type)
     // Run query for entiry
} 

func createNew<T: NSManagedObject>(type: T.Type) -> T {
     let entityName = NSStringFromClass(type)
     // create entity with name
} 

การใช้คลาสทั่วไป (มีความยืดหยุ่นน้อยกว่าเนื่องจากสามารถกำหนดประเภททั่วไปได้ 1 ประเภทต่ออินสแตนซ์เท่านั้น)

class Foo<T> {

   func doStuff(text: String) -> T {
      return doOtherStuff(text)
   }

   func doOtherStuff(text: String) -> T {

   }  

}

let foo = Foo<Int>()
foo.doStuff("text")

3

ฉันคิดว่าเมื่อคุณระบุฟังก์ชันทั่วไปคุณควรระบุพารามิเตอร์บางอย่างของประเภท T เช่นตาม:

func generic1<T>(parameter: T) {
    println("OK")
}

func generic2<T>(parameter: T) {
    generic1(parameter)
}

และถ้าคุณต้องการเรียกวิธี handle () คุณสามารถทำได้โดยการเขียนโปรโตคอลและระบุข้อ จำกัด ประเภทสำหรับ T:

protocol Example {
    func handle() -> String
}

extension String: Example {
    func handle() -> String {
        return "OK"
    }
}

func generic1<T: Example>(parameter: T) {
    println(parameter.handle())
}

func generic2<T: Example>(parameter: T) {
    generic1(parameter)
}

ดังนั้นคุณสามารถเรียกฟังก์ชันทั่วไปนี้ด้วย String:

generic2("Some")

และจะรวบรวม


1

จนถึงตอนนี้แนวทางปฏิบัติที่ดีที่สุดส่วนบุคคลของฉันใช้คำตอบของ @ orkhan-alikhanov วันนี้เมื่อดู SwiftUI และวิธีการ.modifier()และViewModifierการนำไปใช้ฉันพบวิธีอื่น (หรือเป็นวิธีแก้ปัญหามากกว่านั้น?)

เพียงแค่รวมฟังก์ชันที่สองลงในไฟล์struct.

ตัวอย่าง:

หากสิ่งนี้ให้คำสั่ง "ไม่สามารถเชี่ยวชาญฟังก์ชันทั่วไปอย่างชัดเจน"

func generic2<T>(name: String){
     generic1<T>(name)
}

อันนี้อาจช่วยได้ รวมคำประกาศgeneric1เป็นstruct:

struct Generic1Struct<T> {
    func generic1(name: String) {## do, whatever it needs with T ##}
}

และเรียกมันด้วย:

func generic2<T>(name : String){
     Generic1Struct<T>().generic1(name: name)
}

หมายเหตุ:

  • ฉันไม่รู้ว่ามันจะช่วยได้หรือไม่เมื่อเกิดข้อความแสดงข้อผิดพลาดนี้ ฉันเพิ่งรู้ว่าฉันติดอยู่หลายครั้งเมื่อสิ่งนี้เกิดขึ้น ฉันรู้ว่าโซลูชันนี้ช่วยได้ในวันนี้เมื่อมีข้อความแสดงข้อผิดพลาดปรากฏขึ้น
  • วิธีที่ Swift จัดการกับ Generics สำหรับฉันยังคงสับสน
  • ตัวอย่างนี้และวิธีแก้ปัญหาด้วยstructเป็นตัวอย่างที่ดี วิธีแก้ปัญหาที่นี่ไม่มีข้อมูลเพิ่มเติมเล็กน้อย แต่ส่งผ่านคอมไพเลอร์ ข้อมูลเดียวกัน แต่ผลลัพธ์ต่างกัน? แล้วมีบางอย่างผิดปกติ หากเป็นคอมไพเลอร์บั๊กก็สามารถแก้ไขได้

0

class func retrieveByKey<T: GrandLite>(key: String) -> T?ฉันมีปัญหาคล้ายกับการทำงานของชั้นเรียนของฉันทั่วไป

ฉันไม่สามารถเรียกมันlet a = retrieveByKey<Categories>(key: "abc")ว่าหมวดหมู่เป็นคลาสย่อยของ GrandLite ได้

let a = Categories.retrieveByKey(key:"abc")ส่งคืน GrandLite ไม่ใช่หมวดหมู่ ฟังก์ชันทั่วไปจะไม่อนุมานประเภทตามคลาสที่เรียกใช้

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?ทำให้ฉันมีข้อผิดพลาดเมื่อฉันพยายามlet a = Categories.retrieveByKey(aType: Categories, key: "abc")ทำให้ฉันมีข้อผิดพลาดที่ไม่สามารถแปลงหมวดหมู่ได้พิมพ์เป็น GrandLite แม้ว่าหมวดหมู่จะเป็นคลาสย่อยของ GrandLite อย่างไรก็ตาม ...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? ใช้งานได้ถ้าฉันพยายามlet a = Categories.retrieveByKey(aType: [Categories](), key: "abc")เห็นได้ชัดว่าการกำหนดคลาสย่อยอย่างชัดเจนไม่ได้ผล แต่การกำหนดโดยนัยโดยใช้ประเภททั่วไป (อาร์เรย์) อื่นทำงานใน Swift 3


1
ในการแก้ไขข้อผิดพลาดคุณต้องระบุaTypeเป็นตัวอย่างTแทนที่จะใส่Categoriesเอง เช่น: let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). aType: T.Typeทางออกก็คือกำหนด แล้วเรียกเมธอดว่าlet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.