Không thể chuyên môn hóa một cách rõ ràng một hàm chung chung


91

Tôi gặp sự cố với mã sau:

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

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

các generic1 (tên) kết quả lỗi biên dịch "Không thể chuyên một cách rõ ràng chức năng chung chung"

Có cách nào để tránh lỗi này không? Tôi không thể thay đổi chữ ký của hàm generic1, do đó nó phải là (Chuỗi) -> Void


2
Lợi ích của việc sử dụng kiểu chung chung ở đây là gì khi nó không thể được suy ra cho ngữ cảnh? Nếu kiểu chung chỉ được sử dụng trong nội bộ, bạn nên chỉ định kiểu trong nội dung của hàm.
Kirsteins

Loại trình giữ chỗ có Tđược sử dụng ở tất cả generic1()không? Bạn sẽ gọi hàm đó như thế nào để trình biên dịch có thể suy ra kiểu?
Martin R

3
Tôi hy vọng rằng có một số cách để gọi chức năng như genetic1 <blaClass> ( "SomeString")
Greyisf

1
Generics không chỉ dành cho các trường hợp khi trình biên dịch có thể suy ra ngữ cảnh. Việc chuyên môn hóa chức năng một cách rõ ràng sẽ cho phép các phần khác của mã được đưa vào. ví dụ: func foo<T>() -> T { ... }điều đó thực hiện một cái gì đó với một đối tượng tkiểu Tvà trả về t. Chuyên môn rõ ràng Tsẽ cho phép t1được thâm nhập var t1 = foo<T>(). Tôi ước có một cách để gọi các hàm theo cách này. C # cho phép nó
nacho4d

1
Tôi cũng vừa gặp phải vấn đề này. Sẽ không có ý nghĩa gì khi tạo một hàm chung chung nếu bạn buộc phải chuyển kiểu làm tham số. Đây phải là một lỗi phải không ?!
Nick

Câu trả lời:


160

Tôi cũng gặp sự cố này và tôi đã tìm thấy một giải pháp cho trường hợp của mình.

Trong bài viết này tác giả có cùng một vấn đề

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

Vì vậy, vấn đề dường như là, trình biên dịch cần phải suy ra kiểu của T bằng cách nào đó. Nhưng nó không được phép chỉ sử dụng chung chung <type> (params ...).

Thông thường, trình biên dịch có thể tìm kiểu của T, bằng cách quét các kiểu tham số vì đây là nơi T được sử dụng trong nhiều trường hợp.

Trong trường hợp của tôi thì hơi khác một chút, vì kiểu trả về của hàm của tôi là T. Trong trường hợp của bạn, có vẻ như bạn đã không sử dụng T trong hàm của mình. Tôi đoán bạn chỉ đơn giản hóa mã ví dụ.

Vì vậy, tôi có chức năng sau

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

Và trong trường hợp, chẳng hạn

getProperty<Int>("countProperty")

trình biên dịch cho tôi lỗi:

Không thể chuyên môn hóa một cách rõ ràng một hàm chung chung

Vì vậy, để cung cấp cho trình biên dịch một nguồn thông tin khác để suy ra kiểu của T từ đó, bạn phải khai báo rõ ràng kiểu của biến mà giá trị trả về được lưu trong đó.

var value : Int = getProperty("countProperty")

Bằng cách này, trình biên dịch biết rằng T phải là một số nguyên.

Vì vậy, tôi nghĩ về tổng thể nó chỉ đơn giản có nghĩa là nếu bạn chỉ định một hàm chung, ít nhất bạn phải sử dụng T trong các kiểu tham số của bạn hoặc như một kiểu trả về.


2
Đã tiết kiệm cho tôi rất nhiều thời gian. Cảm ơn bạn.
chrislarson

3
Có cách nào để làm điều đó nếu không có giá trị trả về không? tức là,func updateProperty<T>( propertyID : String )
Kyle Bashour,

1
Tôi không hiểu tại sao bạn lại muốn làm điều đó? Vì bạn không trả về bất cứ thứ gì, nên không có lợi gì khi khai báo hàm của bạn một cách chung chung.
ThottChief

@ThottChief có rất nhiều lợi ích ví dụ như nếu bạn đang sử dụng / xây dựng keypaths, hoặc nhận được loại tên như là một chuỗi, vv
zaitsman

Câu trả lời tuyệt vời, cảm ơn. Tôi ước điều này sẽ hoạt động với let value = foo() as? Typeđể nó có thể được sử dụng trong một ifhoặc guardkết quả là tùy chọn, nhưng nó không ...
agirault

53

Swift 5

Thông thường, có nhiều cách để xác định các hàm chung. Nhưng chúng dựa trên điều kiện Tphải được sử dụng như a parameter, hoặc a 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
    }
}

Sau đó, chúng tôi phải cung cấp Tkhi gọi điện.

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

Tham khảo:

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

Câu trả lời tuyệt vời cho vấn đề chung ... vì có rất nhiều cách để khắc phục nó. Tôi cũng nhận ra rằng self.result = UIViewController.doSomething()nó tự hoạt động miễn là bạn đã nhập thuộc tính khi bạn khai báo.
teradyl

câu trả lời tuyệt vời giải thích rất nhiều điều.
Okhan Okbay

Java doLastThing<UITextView>()trở thành Swift doLastThing(UITextView.self), ít nhất ... không phải là tệ nhất. Tốt hơn là phải gõ rõ ràng các kết quả phức tạp. Cảm ơn về giải pháp.
Erhannis

18

Giải pháp là lấy kiểu lớp làm tham số (giống như trong Java)

Để cho trình biên dịch biết kiểu mà anh ta đang xử lý, hãy chuyển lớp làm đối số

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

Gọi là:

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

1
Điều này là tuyệt vời và tốt hơn nhiều so với câu trả lời được chấp nhận. Vui lòng xem xét chỉnh sửa để loại bỏ phần lịch sử, hoặc ít nhất là đặt nó bên dưới giải pháp. Cảm ơn!
Dan Rosenstark

1
@DanRosenstark Cảm ơn thông tin phản hồi :)
Orkhan Alikhanov

Mặc dù điều này hiệu quả, tôi không nghĩ đó là phương pháp hay nhất. Lý do là bây giờ định nghĩa hàm đang đưa ra quyết định phải làm gì nếu có vấn đề với dàn diễn viên. Vì vậy, ở đây bạn chỉ gặp sự cố nếu quá trình cast không thành công. Tốt hơn nên để khách hàng quyết định phải làm gì vì điều này có thể khác nhau tùy theo khách hàng. Vì vậy, tôi sẽ bỏ phiếu cho câu trả lời này vì lý do này.
smileBot

@smileBot Ý của bạn là ví dụ tồi hay cách tiếp cận?
Orkhan Alikhanov

Tiếp cận. Tôi nghĩ rằng đó là một quy tắc chung của lập trình để trì hoãn chuyên môn hóa khi thực tế. Đây là một phần của ý tưởng về sự hiểu biết về trách nhiệm. Chức năng không có trách nhiệm chuyên môn hóa cái chung chung. Đây là khả năng đáp ứng của người gọi, vì hàm không thể biết tất cả người gọi sẽ cần gì. Nếu hàm có vai trò này thì điều này sẽ hạn chế việc sử dụng hàm mà không được lợi. Bạn có thể khái quát điều này thành nhiều vấn đề mã hóa. Khái niệm duy nhất này đã giúp tôi rất nhiều.
smileBot

4

Bạn không cần một hàm chung ở đây vì bạn có các kiểu tĩnh (Chuỗi làm tham số), nhưng nếu bạn muốn có một hàm chung gọi một hàm khác, bạn có thể làm như sau.

Sử dụng phương pháp Chung

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
} 

Sử dụng lớp chung chung (Ít linh hoạt hơn vì lớp chung có thể được xác định cho 1 loại chỉ cho mỗi trường hợp)

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ôi nghĩ rằng khi bạn chỉ định hàm chung, bạn nên chỉ định một số tham số của kiểu T, như sau:

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

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

và nếu bạn muốn gọi phương thức handle (), thì bạn có thể thực hiện việc này bằng cách viết giao thức và chỉ định ràng buộc kiểu cho 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)
}

vì vậy bạn có thể gọi hàm chung này với String:

generic2("Some")

và nó sẽ biên dịch


1

Cho đến nay, phương pháp hay nhất của cá nhân tôi là sử dụng câu trả lời của @ orkhan-alikhanov. Hôm nay, khi nhìn vào SwiftUI và làm thế nào .modifier()ViewModifierđược thực hiện, tôi thấy một cách khác (hoặc là nó nhiều hơn một cách giải quyết?)

Đơn giản chỉ cần quấn hàm thứ hai thành a struct.

Thí dụ:

Nếu điều này cung cấp cho bạn thông báo "Không thể chuyên môn hóa rõ ràng một hàm chung chung"

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

Điều này có thể giúp ích. Wrap khai báo generic1thành một struct:

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

và gọi nó bằng:

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

Nhận xét:

  • Tôi không biết liệu nó có hữu ích trong bất kỳ trường hợp có thể nào không, khi thông báo lỗi này xảy ra. Tôi chỉ biết, rằng tôi đã bị mắc kẹt nhiều lần khi điều này xảy ra. Tôi biết, hôm nay giải pháp này đã hữu ích khi thông báo lỗi xuất hiện.
  • Cách Swift xử lý Generics đối với tôi vẫn còn khó hiểu.
  • Ví dụ này và cách giải quyết với structlà một ví dụ điển hình. Cách giải quyết ở đây không có thêm một chút thông tin nào - mà là vượt qua trình biên dịch. Cùng một thông tin, nhưng kết quả khác nhau? Vậy thì có gì đó không ổn. Nếu đó là một lỗi biên dịch, nó có thể được sửa.

0

Tôi đã gặp sự cố tương tự với hàm lớp chung của mình class func retrieveByKey<T: GrandLite>(key: String) -> T?.

Tôi không thể gọi nó là let a = retrieveByKey<Categories>(key: "abc")khi Danh mục là một lớp con của GrandLite.

let a = Categories.retrieveByKey(key:"abc")đã trả lại GrandLite, không phải Danh mục. Các hàm chung không suy ra kiểu dựa trên lớp gọi chúng.

class func retrieveByKey<T: GrandLite>(aType: T, key: String>) -> T?đã cho tôi một lỗi khi tôi cố gắng let a = Categories.retrieveByKey(aType: Categories, key: "abc")đã cho tôi một lỗi rằng nó không thể chuyển đổi Danh mục.Type thành GrandLite, mặc dù Danh mục là một lớp con của GrandLite. TUY NHIÊN...

class func retrieveByKey<T: GrandLite>(aType: [T], key: String) -> T? đã hoạt động nếu tôi đã thử let a = Categories.retrieveByKey(aType: [Categories](), key: "abc")rõ ràng là một phép gán rõ ràng cho một lớp con không hoạt động, nhưng một phép gán ngầm sử dụng kiểu chung khác (mảng) lại hoạt động trong Swift 3.


1
Để sửa lỗi, bạn phải cung cấp aTypedưới dạng một ví dụ của Tthay vì đặt Categorieschính nó. Ví dụ: let a = Categories.retrieveByKey(aType: Categories(), key: "abc"). Một giải pháp khác là xác định aType: T.Type. Sau đó gọi phương thức nhưlet a = Categories.retrieveByKey(aType: Categories.self, key: "abc")
nahung89
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.