2017-12-06 17 views
11

Bài tập là viết hàm map() của riêng tôi qua Collection (mà không sử dụng bất kỳ hàm nguyên thủy chức năng nào, chẳng hạn như reduce()). Nó sẽ xử lý một trường hợp như thế này:Tại sao yêu cầu chung là khi tất cả các loại đã được xác định?

func square(_ input: Int) -> Int { 
      return input * input 
     } 
let result = input.accumulate(square) // [1,2,3] => [1,4,9] 

nỗ lực đầu tiên của tôi là:

extension Collection { 
    func accumulate(_ transform: (Element) -> Element) -> [Element] { 

     var array: [Element] = [] 
     for element in self { 
      array.append(transform(element)) 
     } 
     return array 
    } 
}  

này hoạt động tốt trong một sân chơi, nhưng thất bại trong việc xây dựng đối với các bài kiểm tra, đưa ra một lỗi:

Value of type '[Int]' has no member 'accumulate' 

giải pháp là để genericize các accumulate phương pháp:

extension Collection { 
    func accumulate<T>(_ transform: (Element) -> T) -> [T] { 

     var array: [T] = [] 
     for element in self { 
      array.append(transform(element)) 
     } 
     return array 
    } 
}  

Tôi nhận ra rằng phiên bản chung là ít hạn chế (không yêu cầu biến đổi để trả về cùng loại), nhưng cho rằng các phép thử không yêu cầu tính tổng quát này, tại sao trình biên dịch lại?

Out of tò mò tôi đã cố gắng:

extension Collection { 
    func accumulate<Element>(_ transform: (Element) -> Element) -> [Element] { 

     var array: [Element] = [] 
     for element in self { 
      array.append(transform(element)) 
     } 
     return array 
    } 
}  

mà ném build lỗi hấp dẫn: '(Self.Element) -> Element' is not convertible to '(Element) -> Element' tại báo cáo kết quả append().

Vậy trình biên dịch (dĩ nhiên) biết rằng Phần tử đầu tiên là Self.Element, nhưng không xử lý kiểu Element khác như nhau. Tại sao?


UPDATE:

Dựa trên các câu trả lời, dường như từ chối của phiên bản đầu tiên là một lỗi biên dịch, cố định trong XCode 9.2 (Tôi đang trên 9.1).

Nhưng tôi vẫn tự hỏi liệu trong

func accumulate(_ transform: (Element) -> Element) -> [Element] 

nó sẽ thấy hai loại (Self.ElementElement) hoặc nhận ra rằng họ đang như nhau.

Vì vậy, tôi đã làm bài kiểm tra này:

let arr = [1,2,3] 
arr.accumulate { 
    return String(describing: $0) 
} 

Chắc chắn, có những lỗi mong đợi: error: cannot convert value of type 'String' to closure result type 'Int'

Vì vậy, câu trả lời đúng là: trình biên dịch sẽ đối xử với tham chiếu đến các phần tử như nhau, như miễn là không có loại chung nào làm quá tải tên.

Nhưng kỳ lạ, mặc dù, điều này thành công:

[1,2,3].accumulate { 
    return String(describing: $0) 
} 

PS. Cảm ơn tất cả mọi người vì đầu vào của bạn! Tiền thưởng được tự động trao tặng.

+2

Cả nỗ lực đầu tiên của bạn và lần thử thứ hai biên dịch và chạy mà không gặp sự cố. –

Trả lời

8

Lỗi xây dựng ban đầu là lỗi trình biên dịch. Thực tế, trình biên dịch sẽ nhận ra rằng tất cả các phiên bản Element đều giống nhau, miễn là Element không bị quá tải làm loại chung trên hàm.

4
  • Giới thiệu câu hỏi đầu tiên, làm việc với Xcode 9.2 và Swift 4 tôi không nhận được bất kỳ lỗi xây dựng như:

    Value of type '[Int]' has no member 'accumulate'

    làm như vậy này:

    var mystuff:[Int] = [1,2,3] 
    let result = mystuff.accumulate(square) 
    

    nó chỉ đem lại cho tôi kết quả chính xác [1,4,9]

  • Đối với câu hỏi thứ hai, nguyên mẫu chức năng là sai, bạn nên cố gắng Self.Element:

    extension Collection { 
        func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element] { 
        var array: [Element] = [] 
        for element in self { 
         array.append(transform(element)) 
        } 
        return array 
        } 
    } 
    
+3

Có, nhưng phương pháp được đề xuất của bạn tương đương với lần thử thứ hai trong câu hỏi. –

+0

@MartinR Vâng, dường như nó không tương đương với trình biên dịch. Lần thử thứ hai là: '(Element) -> Element -> Element'. Vì kiểu đó không được suy ra một cách chính xác vào thời gian biên dịch, nên tôi đề nghị sử dụng rõ ràng '(Self.Element) -> Element'. –

1

Tôi sẽ tập trung vào câu hỏi thứ hai của bạn.

func accumulate<Element>(_ transform: (Element) -> Element) -> [Element] 

Sự cố khi viết chữ ký này là bạn có hai loại khác nhau có cùng tên.

  • Loại đầu tiên là Element là loại chung của bạn (bit giữa các dấu ngoặc nhọn).
  • Loại thứ hai là Self.Element, tức là loại phần tử trong bộ sưu tập của bạn, được tuyên bố bởi chính giao thức. Thông thường, bạn không phải viết rõ ràng phần Self., nhưng vì loại chung của bạn có cùng tên với tên này, Swift không thể phân biệt chúng bằng cách khác.

Sự khác biệt là rõ ràng hơn nếu bạn thay đổi tên của các loại chung:

func accumulate<E>(_ transform: (E) -> E) -> [E] 

này tương đương với phiên bản accumulate<Element> - thay đổi tên đơn giản là nhấn mạnh những gì đang thực sự xảy ra.

Nói chung hơn, Swift sẽ cho phép bạn đặt tên cho các loại của mình bất cứ điều gì bạn muốn. Nhưng nếu tên của một loại xung đột với một loại khác từ một phạm vi khác, thì bạn phải phân biệt nó. Nếu bạn không phân biệt, Swift sẽ chọn trận đấu địa phương nhất. Trong hàm của bạn, kiểu generic là "hầu hết địa phương".

Hãy tưởng tượng bạn đã định nghĩa của riêng bạn kiểu String:

struct String { 
    // ... 
} 

này là hoàn toàn hợp lệ, nhưng nếu bạn muốn sau đó sử dụng các kiểu String được cung cấp bởi các tiêu chuẩn lib Swift, bạn phải disambiguate như thế này:

let my_string: String = String() 
let swift_string: Swift.String = "" 

Và đây là lý do tại sao Andrea thay đổi chữ ký của hàm. Bạn cần phải thông báo cho trình biên dịch loại "Yếu tố" mà bạn đang đề cập đến.

func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element] 

Nói chung, tôi khuyên bạn không nên chọn loại chung phù hợp với tên của loại khác bạn đang sử dụng. Nó chỉ gây nhầm lẫn cho mọi người.

1

Tôi không chắc chắn về câu hỏi đầu tiên của bạn. Nếu nó hoạt động trong sân chơi, nhưng không phải trong các bài kiểm tra của bạn, dự đoán đầu tiên của tôi sẽ là làm cho hàm đó được công khai. Các xét nghiệm thường được định nghĩa trong các mô-đun riêng biệt và để hiển thị nội dung nào đó trong một mô-đun khác, nó phải được khai báo công khai.

extension Collection { 
    public func accumulate //... 
} 
+0

Đây không phải là vấn đề truy cập. Các thử nghiệm có thể truy cập 'nội bộ' miễn là chúng được thiết lập chính xác. – vortek

1

Khi triển khai tiện ích mở rộng cho Collection, loại được kết hợp của bộ sưu tập sẽ theo mặc định Element; Nó sẽ là khó hiểu tên generic của bạn là "Element" (func accumulate<Element>), tuy nhiên, đối với trường hợp của bạn, thậm chí còn có không cần phải khai báo các phương pháp chữ ký của bạn như sau:

func accumulate<Element>(_ transform: (Self.Element) -> Element) -> [Element] 

Thay vào đó, nó sẽ là:

func accumulate(_ transform: (Element) -> Element) -> [Element] 

Cũng, nếu bạn đang hướng tới để cho phương pháp của bạn để có chức năng chỉ dành cho số nguyên, bạn nên hạn chế mở rộng của bạn sẽ được áp dụng duy nhất cho bộ sưu tập của BinaryInteger, như sau:

extension Collection where Element: BinaryInteger { 
    func accumulate(_ transform: (Element) -> Element) -> [Element] { 

     var array: [Element] = [] 
     for element in self { 
      array.append(transform(element)) 
     } 
     return array 
    } 
} 

Hoặc để mở rộng phạm vi, nó có thể là Numeric để thay thế.

Các vấn đề liên quan