Giao thức không phù hợp với chính nó?


125

Tại sao mã Swift này không được biên dịch?

protocol P { }
struct S: P { }

let arr:[P] = [ S() ]

extension Array where Element : P {
    func test<T>() -> [T] {
        return []
    }
}

let result : [S] = arr.test()

Trình biên dịch nói: "Loại Pkhông phù hợp với giao thứcP " (hoặc, trong các phiên bản sau của Swift, "Sử dụng 'P' làm loại cụ thể tuân thủ giao thức 'P' không được hỗ trợ.").

Tại sao không? Điều này cảm thấy như một lỗ hổng trong ngôn ngữ, bằng cách nào đó. Tôi nhận ra rằng vấn đề bắt nguồn từ việc khai báo mảng arrlà một mảng của loại giao thức , nhưng đó có phải là một điều không hợp lý để làm không? Tôi nghĩ các giao thức có chính xác để giúp cung cấp các cấu trúc với thứ gì đó giống như hệ thống phân cấp không?


1
Khi bạn loại bỏ chú thích kiểu trong let arrdòng, trình biên dịch sẽ nhập kiểu vào [S]và mã biên dịch. Có vẻ như một loại giao thức không thể được sử dụng giống như một mối quan hệ siêu lớp.
vadian

1
@vadian Đúng, đó là những gì tôi đã đề cập trong câu hỏi của mình khi tôi nói "Tôi nhận ra rằng vấn đề bắt nguồn từ việc khai báo mảng mảng là một mảng của loại giao thức". Nhưng, như tôi tiếp tục nói trong câu hỏi của mình, toàn bộ quan điểm của các giao thức thường là chúng có thể được sử dụng theo cùng một cách như một mối quan hệ siêu lớp! Chúng được dự định để cung cấp một loại cấu trúc phân cấp cho thế giới của các cấu trúc. Và họ thường làm. Câu hỏi là, tại sao không nên làm việc ở đây ?
matt

1
Vẫn không hoạt động trong Xcode 7.1, nhưng hiện tại thông báo lỗi "sử dụng 'P' làm loại cụ thể tuân thủ giao thức 'P' không được hỗ trợ" .
Martin R

1
@MartinR Đó là một thông báo lỗi tốt hơn. Nhưng nó vẫn cảm thấy với tôi như một lỗ hổng trong ngôn ngữ.
mờ

Chắc chắn rồi! Ngay cả vớiprotocol P : Q { } , P không phù hợp với Q.
Martin R

Câu trả lời:


66

EDIT: Mười tám tháng nữa làm việc với Swift, một bản phát hành lớn khác (cung cấp chẩn đoán mới) và nhận xét từ @AyBayBay khiến tôi muốn viết lại câu trả lời này. Chẩn đoán mới là:

"Sử dụng 'P' làm loại cụ thể tuân thủ giao thức 'P' không được hỗ trợ."

Điều đó thực sự làm cho toàn bộ điều này rõ ràng hơn rất nhiều. Phần mở rộng này:

extension Array where Element : P {

không áp dụng khi Element == Pkể từ khi Pkhông được xem là một sự phù hợp cụ thể của P. (Giải pháp "đặt nó trong hộp" bên dưới vẫn là giải pháp chung nhất.)


Câu trả lời cũ:

Đó là một trường hợp khác của siêu dữ liệu. Swift thực sự muốn bạn có được một loại cụ thể cho hầu hết những thứ không tầm thường. [P]không phải là một loại cụ thể (bạn không thể phân bổ một khối bộ nhớ có kích thước đã biết choP ). (Tôi không nghĩ điều đó thực sự đúng; bạn hoàn toàn có thể tạo ra một cái gì đó có kích thước Pbởi vì nó được thực hiện thông qua sự gián tiếp .) Tôi không nghĩ có bất kỳ bằng chứng nào cho thấy đây là trường hợp "không nên" hoạt động. Điều này trông rất giống một trong những trường hợp "chưa làm việc" của họ. (Thật không may, gần như không thể để Apple xác nhận sự khác biệt giữa các trường hợp đó.) Thực tế đó Array<P>có thể là một loại biến (trong đóArraykhông thể) chỉ ra rằng họ đã thực hiện một số công việc theo hướng này, nhưng các siêu dữ liệu Swift có rất nhiều cạnh sắc nét và các trường hợp chưa được thực hiện. Tôi không nghĩ rằng bạn sẽ nhận được câu trả lời "tại sao" tốt hơn thế. "Bởi vì trình biên dịch không cho phép nó." (Không hài lòng, tôi biết. Toàn bộ cuộc sống Swift của tôi)

Giải pháp là hầu như luôn luôn để mọi thứ trong một hộp. Chúng tôi xây dựng một loại tẩy.

protocol P { }
struct S: P { }

struct AnyPArray {
    var array: [P]
    init(_ array:[P]) { self.array = array }
}

extension AnyPArray {
    func test<T>() -> [T] {
        return []
    }
}

let arr = AnyPArray([S()])
let result: [S] = arr.test()

Khi Swift cho phép bạn thực hiện điều này trực tiếp (điều mà tôi mong đợi cuối cùng), rất có thể nó sẽ chỉ bằng cách tạo hộp này cho bạn tự động. Enumive đệ quy đã có chính xác lịch sử này. Bạn phải đóng hộp chúng và điều đó cực kỳ khó chịu và hạn chế, và cuối cùng trình biên dịch được thêm vào indirectđể làm điều tương tự tự động hơn.


Rất nhiều thông tin hữu ích trong câu trả lời này, nhưng giải pháp thực tế trong câu trả lời của Tomohiro tốt hơn giải pháp đấm bốc được trình bày ở đây.
jsadler

@jsadler Câu hỏi không phải là làm thế nào để khắc phục giới hạn, nhưng tại sao giới hạn đó lại tồn tại. Thật vậy, theo như lời giải thích, cách giải quyết của Tomohiro đặt ra nhiều câu hỏi hơn là câu trả lời. Nếu chúng tôi sử dụng ==trong ví dụ Mảng của mình, chúng tôi sẽ gặp lỗi, Yêu cầu cùng loại làm cho tham số chung 'Phần tử' không chung chung. "Tại sao Tomohiro không sử dụng để ==tạo ra cùng một lỗi?
matt

@Rob Napier Tôi vẫn bối rối trước phản ứng của bạn. Làm thế nào để Swift thấy sự cụ thể hơn trong giải pháp của bạn so với bản gốc? Có vẻ như bạn vừa gói gọn mọi thứ trong một cấu trúc ... Idk có lẽ tôi đang vật lộn để hiểu hệ thống loại nhanh chóng nhưng tất cả điều này có vẻ giống như ma thuật
AyBayBay

@AyBayBay Câu trả lời cập nhật.
Rob Napier

Cảm ơn bạn rất nhiều @RobNapier Tôi luôn ngạc nhiên với tốc độ trả lời của bạn và khá thẳng thắn về cách bạn tìm thấy thời gian để giúp đỡ mọi người nhiều như bạn. Tuy nhiên, các chỉnh sửa mới của bạn chắc chắn đưa nó vào quan điểm. Một điều nữa tôi muốn chỉ ra, việc hiểu loại tẩy cũng giúp tôi. Bài viết này đặc biệt đã làm một công việc tuyệt vời: krakendev.io/blog/generic-prot Protocol-and-their-shortcomings TBH Idk tôi cảm thấy thế nào về một số thứ này. Có vẻ như chúng ta đang tính toán các lỗ hổng trong ngôn ngữ nhưng Idk làm thế nào apple sẽ xây dựng một số thứ này.
AyBayBay

109

Tại sao các giao thức không phù hợp với chính họ?

Cho phép các giao thức phù hợp với chính họ trong trường hợp chung là không có cơ sở. Vấn đề nằm ở yêu cầu giao thức tĩnh.

Bao gồm các:

  • static phương pháp và tính chất
  • Người khởi tạo
  • Các loại liên kết (mặc dù các loại này hiện ngăn chặn việc sử dụng giao thức như một loại thực tế)

Chúng tôi có thể truy cập các yêu cầu này trên một trình giữ chỗ chung Ttrong đó T : P- tuy nhiên chúng tôi không thể truy cập chúng trên chính loại giao thức, vì không có loại tuân thủ cụ thể nào để chuyển tiếp. Vì vậy, chúng tôi không thể cho phép Tđược P.

Xem xét những gì sẽ xảy ra trong ví dụ sau nếu chúng tôi cho phép Arraytiện ích mở rộng được áp dụng cho [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()

Chúng tôi không thể gọi appendNew()một [P], bởi vì P(the Element) không phải là một loại cụ thể và do đó không thể được khởi tạo. Nó phải được gọi trên một mảng với các phần tử được gõ cụ thể, trong đó kiểu đó phù hợp với P.

Đó là một câu chuyện tương tự với các yêu cầu thuộc tính và phương thức tĩnh:

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

Chúng tôi không thể nói chuyện về SomeGeneric<P>. Chúng ta cần triển khai cụ thể các yêu cầu giao thức tĩnh (chú ý cách không có triển khai foo()hoặc barđược định nghĩa trong ví dụ trên). Mặc dù chúng ta có thể định nghĩa việc triển khai các yêu cầu này trong một Pphần mở rộng, nhưng chúng chỉ được xác định cho các loại cụ thể phù hợp với P- bạn vẫn không thể tự gọi chúng P.

Bởi vì điều này, Swift hoàn toàn không cho phép chúng tôi sử dụng một giao thức như một loại phù hợp với chính nó - bởi vì khi giao thức đó có các yêu cầu tĩnh, thì không.

Các yêu cầu giao thức sơ thẩm không có vấn đề gì, vì bạn phải gọi chúng theo một thực thể thực tế phù hợp với giao thức (và do đó phải thực hiện các yêu cầu). Vì vậy, khi gọi một yêu cầu trên một thể hiện được gõ là P, chúng ta có thể chuyển tiếp cuộc gọi đó đến việc triển khai yêu cầu đó của loại cụ thể.

Tuy nhiên, việc đưa ra các ngoại lệ đặc biệt cho quy tắc trong trường hợp này có thể dẫn đến sự không nhất quán đáng ngạc nhiên về cách các giao thức được xử lý bằng mã chung. Mặc dù điều đó đang được nói, tình huống không quá giống với associatedtypecác yêu cầu - điều này (hiện tại) ngăn bạn sử dụng một giao thức như một loại. Có một hạn chế ngăn bạn sử dụng một giao thức là một loại phù hợp với chính nó khi nó có các yêu cầu tĩnh có thể là một tùy chọn cho phiên bản ngôn ngữ trong tương lai

Chỉnh sửa: Và như được khám phá dưới đây, nó trông giống như những gì nhóm Swift đang hướng tới.


@objc giao thức

Và trên thực tế, đó chính xác là cách ngôn ngữ xử lý các @objcgiao thức. Khi họ không có yêu cầu tĩnh, họ tuân thủ chính họ.

Các biên dịch sau đây tốt:

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đòi hỏi phải Tphù hợp với P; nhưng chúng ta có thể thay thế trong Pcho TPkhông có những yêu cầu tĩnh. Nếu chúng ta thêm một yêu cầu tĩnh vào P, ví dụ không còn biên dịch:

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

Vì vậy, một cách giải quyết cho vấn đề này là tạo giao thức của bạn @objc. Cấp, đây không phải là một cách giải quyết lý tưởng trong nhiều trường hợp, vì nó buộc các loại tuân thủ của bạn phải là các lớp, cũng như yêu cầu thời gian chạy Obj-C, do đó không thể thực hiện được trên các nền tảng không phải của Apple như Linux.

Nhưng tôi nghi ngờ rằng giới hạn này là (một trong) lý do chính tại sao ngôn ngữ đã thực hiện 'giao thức mà không có yêu cầu tĩnh phù hợp với chính nó' cho các @objcgiao thức. Mã chung được viết xung quanh chúng có thể được đơn giản hóa đáng kể bởi trình biên dịch.

Tại sao? Bởi vì @objccác giá trị gõ giao thức thực sự chỉ là các tham chiếu lớp có yêu cầu được gửi đi bằng cách sử dụng objc_msgSend. Mặt khác, các @objcgiá trị không được gõ giao thức phức tạp hơn, vì chúng mang theo cả hai bảng giá trị và chứng kiến ​​để vừa quản lý bộ nhớ của giá trị được gói (có khả năng được lưu trữ gián tiếp) của chúng và để xác định cách triển khai nào cần gọi khác yêu cầu, tương ứng.

Do cách biểu diễn đơn giản hóa này cho các @objcgiao thức, một giá trị của loại giao thức như vậy Pcó thể chia sẻ cùng biểu diễn bộ nhớ như một 'giá trị chung' của một số trình giữ chỗ chung T : P, có lẽ giúp nhóm Swift dễ dàng cho phép tự tuân thủ. Điều này cũng không đúng đối với các @objcgiao thức không phải là giao thức, tuy nhiên, vì các giá trị chung này hiện không mang theo các bảng chứng kiến ​​giá trị hoặc giao thức.

Tuy nhiên, tính năng này là có chủ ý và hy vọng sẽ được triển khai cho các @objcgiao thức không , như được xác nhận bởi thành viên nhóm Swift, Slava Pestov trong các nhận xét của SR-55 khi trả lời câu hỏi của bạn về câu hỏi này (được nhắc bởi câu hỏi này ):

Matt Neuburg đã thêm một bình luận - 7 tháng 9 năm 2017 1:33 PM

Điều này không biên dịch:

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

Thêm @objclàm cho nó biên dịch; loại bỏ nó làm cho nó không biên dịch lại. Một số người trong chúng tôi trên Stack Overflow thấy điều này đáng ngạc nhiên và muốn biết liệu đó có phải là lỗi cố ý hay trường hợp lỗi không.

Slava Pestov đã thêm một bình luận - 7 tháng 9 năm 2017 1:53 CH

Đó là cố ý - dỡ bỏ hạn chế này là những gì lỗi này là về. Như tôi đã nói nó khó khăn và chúng tôi chưa có kế hoạch cụ thể nào.

Vì vậy, hy vọng rằng một ngày nào đó ngôn ngữ cũng sẽ hỗ trợ cho các @objcgiao thức không phải là giao thức.

Nhưng những giải pháp hiện tại nào dành cho các @objcgiao thức không?


Triển khai các phần mở rộng với các ràng buộc giao thức

Trong Swift 3.1, nếu bạn muốn một tiện ích mở rộng có ràng buộc rằng một trình giữ chỗ chung chung hoặc loại được liên kết phải là một loại giao thức nhất định (không chỉ là một loại cụ thể phù hợp với giao thức đó) - bạn có thể chỉ cần định nghĩa điều này bằng một ==ràng buộc.

Ví dụ: chúng tôi có thể viết phần mở rộng mảng của bạn dưới dạng:

extension Array where Element == P {
  func test<T>() -> [T] {
    return []
  }
}

let arr: [P] = [S()]
let result: [S] = arr.test()

Tất nhiên, điều này bây giờ ngăn chúng ta gọi nó trên một mảng với các phần tử loại cụ thể phù hợp với P. Chúng tôi có thể giải quyết vấn đề này bằng cách chỉ xác định một tiện ích mở rộng bổ sung khi nào Element : Pvà chỉ cần chuyển tiếp vào == Ptiện ích mở rộng:

extension Array where Element : P {
  func test<T>() -> [T] {
    return (self as [P]).test()
  }
}

let arr = [S()]
let result: [S] = arr.test()

Tuy nhiên, điều đáng chú ý là điều này sẽ thực hiện chuyển đổi O (n) của mảng thành a [P], vì mỗi phần tử sẽ phải được đóng hộp trong một thùng chứa tồn tại. Nếu hiệu suất là một vấn đề, bạn chỉ cần giải quyết vấn đề này bằng cách triển khai lại phương thức mở rộng. Đây không phải là một giải pháp hoàn toàn thỏa đáng - hy vọng phiên bản ngôn ngữ trong tương lai sẽ bao gồm cách thể hiện 'loại giao thức hoặc tuân thủ ràng buộc của loại giao thức'.

Trước Swift 3.1, cách tổng quát nhất để đạt được điều này, như Rob thể hiện trong câu trả lời của mình , chỉ đơn giản là xây dựng một loại trình bao bọc cho a [P], sau đó bạn có thể xác định (các) phương thức tiện ích mở rộng của mình.


Truyền một thể hiện gõ giao thức cho một trình giữ chỗ chung bị ràng buộc

Hãy xem xét tình huống sau đây (có thể xảy ra, nhưng không phổ biến):

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)

Chúng ta không thể vượt qua pđể takesConcreteP(_:), vì chúng tôi không thể hiện thay thếP cho một trình giữ chỗ chung T : P. Chúng ta hãy xem một vài cách để chúng ta có thể giải quyết vấn đề này.

1. Mở hiện sinh

Hơn là cố gắng thay thế Pcho T : P, những gì nếu chúng ta có thể thâm nhập vào các kiểu dữ liệu cụ cơ bản mà các Pgiá trị đánh máy đã đóng gói và thay thế đó để thay thế? Thật không may, điều này đòi hỏi một tính năng ngôn ngữ gọi là mở hiện sinh , hiện không có sẵn cho người dùng.

Tuy nhiên, Swift thực hiện ngầm existentials mở (giá trị giao thức, đánh máy) khi truy cập vào các thành viên trên chúng (tức là nó đào ra kiểu thời gian chạy và làm cho nó dễ tiếp cận theo hình thức một placeholder generic). Chúng ta có thể khai thác thực tế này trong một phần mở rộng giao thức trên P:

extension P {
  func callTakesConcreteP/*<Self : P>*/(/*self: Self*/) {
    takesConcreteP(self)
  }
}

Lưu ý Selftrình giữ chỗ chung ẩn mà phương thức tiện ích mở rộng, được sử dụng để nhập selftham số ẩn - điều này xảy ra đằng sau hậu trường với tất cả các thành viên mở rộng giao thức. Khi gọi một phương thức như vậy trên một giá trị gõ giao thức P, Swift đào ra loại bê tông cơ bản và sử dụng phương thức này để đáp ứng Selftrình giữ chỗ chung. Đây là lý do tại sao chúng tôi có thể gọi takesConcreteP(_:)với self- chúng tôi hài lòng Tvới Self.

Điều này có nghĩa là bây giờ chúng ta có thể nói:

p.callTakesConcreteP()

takesConcreteP(_:)được gọi với trình giữ chỗ chung Tđược thỏa mãn bởi loại bê tông cơ bản (trong trường hợp này S). Lưu ý rằng đây không phải là "giao thức phù hợp với chính họ", vì chúng tôi thay thế một loại cụ thể thay vì P- thử thêm một yêu cầu tĩnh vào giao thức và xem điều gì xảy ra khi bạn gọi nó từ bên trong takesConcreteP(_:).

Nếu Swift tiếp tục không cho phép các giao thức tuân thủ chính mình, thì giải pháp thay thế tốt nhất tiếp theo sẽ hoàn toàn mở ra các tồn tại khi cố gắng chuyển chúng thành đối số cho các tham số của loại chung - thực hiện chính xác những gì trampoline mở rộng giao thức của chúng tôi đã làm, chỉ cần không có bản tóm tắt.

Tuy nhiên, lưu ý rằng việc mở các tồn tại không phải là một giải pháp chung cho vấn đề giao thức không tuân thủ chính chúng. Nó không xử lý các tập hợp không đồng nhất của các giá trị gõ giao thức, tất cả có thể có các loại cụ thể cơ bản khác nhau. Ví dụ: xem xét:

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) 

Vì những lý do tương tự, một hàm có nhiều Ttham số cũng sẽ có vấn đề, vì các tham số phải lấy các đối số cùng loại - tuy nhiên nếu chúng ta có hai Pgiá trị, không có cách nào chúng ta có thể đảm bảo tại thời điểm biên dịch rằng cả hai đều có cùng một bê tông cơ bản kiểu.

Để giải quyết vấn đề này, chúng ta có thể sử dụng một loại tẩy.

2. Xây dựng một loại tẩy

Như Rob nói , một loại tẩy , là giải pháp chung nhất cho vấn đề giao thức không tuân thủ chính họ. Chúng cho phép chúng ta bọc một thể hiện gõ giao thức theo một kiểu cụ thể phù hợp với giao thức đó, bằng cách chuyển tiếp các yêu cầu thể hiện đến thể hiện bên dưới.

Vì vậy, hãy xây dựng một hộp xóa loại chuyển tiếp Pcác yêu cầu cá thể lên một thể hiện tùy ý cơ bản phù hợp với 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) }
}

Bây giờ chúng ta chỉ có thể nói về mặt AnyPthay vì P:

let p = AnyP(S(bar: 5))
takesConcreteP(p)

// example from #1...
let array = [AnyP(S(bar: 1)), AnyP(Q(bar: 2))]
takesConcreteArrayOfP(array)

Bây giờ, hãy xem xét tại sao chúng ta phải xây dựng cái hộp đó. Như chúng ta đã thảo luận sớm, Swift cần một loại cụ thể cho các trường hợp giao thức có yêu cầu tĩnh. Xem xét nếu Pcó một yêu cầu tĩnh - chúng tôi sẽ cần phải thực hiện điều đó trong AnyP. Nhưng nó nên được thực hiện như thế nào? Chúng tôi đang xử lý các trường hợp tùy ý tuân thủ Pở đây - chúng tôi không biết về cách các loại bê tông cơ bản của chúng thực hiện các yêu cầu tĩnh, do đó chúng tôi không thể diễn đạt điều này một cách có ý nghĩa trongAnyP .

Do đó, giải pháp trong trường hợp này chỉ thực sự hữu ích trong trường hợp yêu cầu giao thức thể hiện . Trong trường hợp chung, chúng ta vẫn không thể coi Plà một loại bê tông phù hợp với P.


2
Có lẽ tôi chỉ dày đặc, nhưng tôi không hiểu tại sao trường hợp tĩnh lại đặc biệt. Chúng tôi (trình biên dịch) biết nhiều hoặc ít về thuộc tính tĩnh của prototol tại thời điểm biên dịch như chúng tôi biết về thuộc tính cá thể của giao thức, cụ thể là người áp dụng sẽ triển khai nó. Vậy sự khác biệt là gì?
matt

1
@matt Một thể hiện được gõ giao thức (tức là thể hiện được gõ cụ thể được bọc trong tồn tại P) là tốt vì chúng ta chỉ có thể chuyển tiếp các cuộc gọi đến các yêu cầu cá thể đến thể hiện bên dưới. Tuy nhiên, đối với chính loại giao thức (nghĩa là a P.Protocol, chỉ là loại mô tả giao thức) - không có người áp dụng, do đó không có gì để gọi các yêu cầu tĩnh trên, đó là lý do tại sao trong ví dụ trên chúng ta không thể có SomeGeneric<P>(Đó là khác với một P.Type(siêu dữ liệu hiện sinh), mô tả một siêu dữ liệu cụ thể của một cái gì đó phù hợp với P- nhưng đó là một câu chuyện khác)
Hamish

Câu hỏi tôi đặt ra ở đầu trang này là tại sao một người áp dụng kiểu giao thức vẫn ổn và bản thân loại giao thức thì không. Tôi hiểu rằng đối với một loại giao thức, không có người áp dụng. - Điều tôi không hiểu là tại sao việc chuyển tiếp các cuộc gọi tĩnh đến loại chấp nhận lại khó hơn so với chuyển tiếp các cuộc gọi cá thể sang loại chấp nhận. Bạn đang đưa ra một lập luận rằng lý do có một khó khăn ở đây là do bản chất của các yêu cầu tĩnh nói riêng, nhưng tôi không thấy các yêu cầu tĩnh khó khăn hơn các yêu cầu cá thể như thế nào.
mờ

@matt Không phải là các yêu cầu tĩnh "khó" hơn yêu cầu cá thể - trình biên dịch có thể xử lý cả tiền phạt thông qua các tồn tại cho các thể hiện (ví dụ như nhập vào P) và một siêu dữ liệu tồn tại (ví dụ như P.Typesiêu dữ liệu). Vấn đề là đối với thuốc generic - chúng tôi không thực sự so sánh like cho thích. Khi TP, không có underyling bê tông (meta) loại yêu cầu về phía trước tĩnh để ( Tlà một P.Protocol, không phải là một P.Type) ....
Hamish

1
Tôi thực sự không quan tâm đến âm thanh, v.v., tôi chỉ muốn viết ứng dụng và nếu cảm thấy nó nên hoạt động thì nên làm. Ngôn ngữ chỉ nên là một công cụ, không phải là một sản phẩm. Nếu có một số trường hợp nó thực sự không hoạt động thì sẽ không cho phép trong những trường hợp đó nhưng hãy để mọi người khác sử dụng các trường hợp mà nó hoạt động và để họ tiếp tục với việc viết ứng dụng.
Jonathan.

17

Nếu bạn mở rộng CollectionTypegiao thức thay vì Arrayvà ràng buộc bởi giao thức là một loại cụ thể, bạn có thể viết lại mã trước đó như sau.

protocol P { }
struct S: P { }

let arr:[P] = [ S() ]

extension CollectionType where Generator.Element == P {
    func test<T>() -> [T] {
        return []
    }
}

let result : [S] = arr.test()

Tôi không nghĩ rằng Collection vs mảng là liên quan ở đây, sự thay đổi quan trọng được sử dụng == Pvs : P. Với == ví dụ ban đầu cũng hoạt động. Và một vấn đề tiềm năng (tùy thuộc vào ngữ cảnh) với == là nó không bao gồm phụ giao thức: nếu tôi tạo ra một protocol SubP: P, và sau đó xác định arrnhư [SubP]sau đó arr.test()sẽ không có tác dụng nữa (lỗi: SubP và P phải tương đương).
imre
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.