Một hệ thống loại chung tốt


29

Người ta thường chấp nhận rằng các tổng quát Java đã thất bại theo một số cách quan trọng. Sự kết hợp của các ký tự đại diện và giới hạn dẫn đến một số mã nghiêm trọng không thể đọc được.

Tuy nhiên, khi tôi nhìn vào các ngôn ngữ khác, tôi thực sự dường như không thể tìm thấy một hệ thống loại chung mà các lập trình viên hài lòng.

Nếu chúng ta lấy những điều sau đây làm mục tiêu thiết kế của một hệ thống kiểu như vậy:

  • Luôn tạo ra các khai báo kiểu dễ đọc
  • Dễ học (không cần phải đánh vào hiệp phương sai, chống chỉ định, v.v.)
  • tối đa hóa số lượng lỗi thời gian biên dịch

Có ngôn ngữ nào hiểu đúng không? Nếu tôi google, điều duy nhất tôi thấy là những lời phàn nàn về cách hệ thống hút ngôn ngữ X. Đây có phải là loại phức tạp vốn có trong cách gõ chung không? Chúng ta có nên từ bỏ việc cố gắng xác minh loại an toàn 100% tại thời điểm biên dịch không?

Câu hỏi chính của tôi là ngôn ngữ "làm cho đúng" là tốt nhất đối với ba mục tiêu này. Tôi nhận ra rằng đó là chủ quan, nhưng cho đến nay tôi thậm chí không thể tìm thấy một ngôn ngữ mà không phải tất cả các lập trình viên đều đồng ý rằng hệ thống loại chung là một mớ hỗn độn.

Phụ lục: như đã lưu ý, sự kết hợp giữa phân nhóm / kế thừa và tổng quát là những gì tạo ra sự phức tạp, vì vậy tôi thực sự tìm kiếm một ngôn ngữ kết hợp cả hai và tránh sự bùng nổ của sự phức tạp.


2
Bạn có ý nghĩa easy-to-read type declarationsgì? Tiêu chí thứ ba cũng không rõ ràng: ví dụ: tôi có thể biến chỉ mục mảng ra khỏi giới hạn ngoại lệ thành lỗi thời gian biên dịch bằng cách không cho phép bạn lập chỉ mục mảng trừ khi tôi có thể tính toán chỉ mục tại thời gian biên dịch. Ngoài ra, các tiêu chí thứ hai quy định ra phân nhóm. Đó không hẳn là một điều xấu nhưng bạn nên biết những gì bạn đang hỏi.
Doval


9
@gnat, đây chắc chắn không phải là một cơn thịnh nộ chống lại Java. Tôi lập trình gần như độc quyền trong Java. Quan điểm của tôi là nó thường được chấp nhận trong cộng đồng Java rằng Generics là thiếu sót (không phải là một thất bại hoàn toàn, nhưng có lẽ là một phần), vì vậy đây là một câu hỏi hợp lý để hỏi cách chúng nên được thực hiện. Tại sao họ sai và những người khác đã làm cho họ đúng? Hoặc là thực sự không thể có được thuốc generic hoàn toàn đúng?
Peter

1
Có phải tất cả mọi người chỉ ăn cắp từ C # sẽ có ít khiếu nại hơn. Đặc biệt Java đang ở một vị trí để bắt kịp bằng cách sao chép. Thay vào đó họ quyết định các giải pháp kém hơn. Nhiều câu hỏi mà các ủy ban thiết kế Java vẫn thảo luận đã được quyết định và triển khai trong C #. Họ thậm chí không nhìn.
usr

2
@emodendroket: Tôi nghĩ rằng hai khiếu nại lớn nhất của tôi về thuốc generic C # là không có cách nào để áp dụng ràng buộc "siêu kiểu" (ví dụ Foo<T> where SiameseCat:T) và không có khả năng có loại chung chung không thể chuyển đổi được Object. IMHO, .NET sẽ được hưởng lợi từ các loại tổng hợp tương tự như các cấu trúc, nhưng thậm chí còn trần hơn. Nếu KeyValuePair<TKey,TValue>là một loại như vậy, thì một loại IEnumerable<KeyValuePair<SiameseCat,FordFocus>>có thể được sử dụng IEnumerable<KeyValuePair<Animal,Vehicle>>, nhưng chỉ khi loại đó không thể được đóng hộp.
supercat

Câu trả lời:


24

Trong khi Generics là xu hướng chủ đạo trong cộng đồng lập trình chức năng trong nhiều thập kỷ, việc thêm khái quát vào ngôn ngữ lập trình hướng đối tượng mang đến một số thách thức độc đáo, đặc biệt là sự tương tác của phân nhóm và khái quát.

Tuy nhiên, ngay cả khi chúng ta tập trung vào các ngôn ngữ lập trình hướng đối tượng và đặc biệt là Java, một hệ thống tổng quát tốt hơn nhiều có thể đã được thiết kế:

  1. Các loại chung nên được chấp nhận ở bất cứ nơi nào khác. Cụ thể, nếu Tlà một tham số loại, các biểu thức sau sẽ biên dịch mà không có cảnh báo:

    object instanceof T; 
    T t = (T) object;
    T[] array = new T[1];
    

    Vâng, điều này đòi hỏi thuốc generic phải được thống nhất, giống như mọi loại khác trong ngôn ngữ.

  2. Hiệp phương sai và chống chỉ định của một loại chung nên được chỉ định trong (hoặc suy ra từ) khai báo của nó, thay vì mỗi lần sử dụng loại chung, vì vậy chúng ta có thể viết

    Future<Provider<Integer>> s;
    Future<Provider<Number>> o = s; 
    

    thay vì

    Future<? extends Provider<Integer>> s;
    Future<? extends Provider<? extends Number>> o = s;
    
  3. Vì các loại chung có thể khá dài, chúng ta không cần chỉ định chúng một cách dư thừa. Đó là, chúng ta sẽ có thể viết

    Map<String, Map<String, List<LanguageDesigner>>> map;
    for (var e : map.values()) {
        for (var list : e.values()) {
            for (var person : list) {
                greet(person);
            }
        }
    }
    

    thay vì

    Map<String, Map<String, List<LanguageDesigner>>> map;
    for (Map<String, List<LanguageDesigner>> e : map.values()) {
        for (List<LanguageDesigner> list : e.values()) {
            for (LanguageDesigner person : list) {
                greet(person);
            }
        }
    }
    
  4. Bất kỳ loại nào cũng phải được chấp nhận như một tham số loại, không chỉ các loại tham chiếu. (Nếu chúng ta có thể có một int[], tại sao chúng ta không thể có một List<int>)?

Tất cả điều này có thể có trong C #.


1
Điều này cũng sẽ thoát khỏi khái quát tự giới thiệu? Điều gì sẽ xảy ra nếu tôi muốn nói rằng một đối tượng có thể so sánh có thể tự so sánh với bất kỳ thứ gì cùng loại hoặc một lớp con? Điều đó có thể được thực hiện? Hoặc nếu tôi viết một phương thức sắp xếp chấp nhận danh sách với các đối tượng có thể so sánh, thì tất cả cần phải so sánh với nhau. Enum là một ví dụ điển hình khác: Enum <E kéo dài Enum <E >>. Tôi không nói rằng một hệ thống loại sẽ có thể thực hiện những điều này, tôi chỉ tò mò về cách C # xử lý các tình huống này.
Peter

1
Suy luận kiểu chung của Java 7 và trợ giúp tự động của C ++ với một số trong những lo ngại này, nhưng là đường cú pháp và không thay đổi các cơ chế cơ bản.

Mặc dù suy luận kiểu của Java có một số trường hợp góc thực sự đáng ghét, giống như không làm việc với các lớp ẩn danh và không tìm thấy giới hạn phù hợp cho các ký tự đại diện khi bạn đánh giá một phương thức chung làm đối số cho một phương thức chung khác.
Doval

@Doval đó là lý do tại sao tôi nói nó giúp với một số mối quan tâm: nó không khắc phục được gì, và không giải quyết mọi thứ. Các thế hệ Java có rất nhiều vấn đề: trong khi tốt hơn các kiểu thô, chúng chắc chắn gây ra nhiều vấn đề đau đầu.

34

Việc sử dụng các kiểu con tạo ra rất nhiều phức tạp khi thực hiện lập trình chung. Nếu bạn khăng khăng sử dụng một ngôn ngữ với các kiểu con, bạn phải chấp nhận có một sự phức tạp vốn có trong lập trình chung đi kèm với nó. Một số ngôn ngữ làm điều đó tốt hơn những ngôn ngữ khác, nhưng bạn chỉ có thể mang nó đến nay.

Tương phản với ví dụ của Haskell chẳng hạn. Chúng đủ đơn giản để nếu bạn sử dụng kiểu suy luận, bạn có thể viết một hàm chung chính xác một cách tình cờ . Trong thực tế, nếu bạn chỉ định một loại duy nhất, trình biên dịch thường nói với bản thân, "Vâng, tôi đã đi để làm điều này chung chung, nhưng bạn hỏi tôi để làm cho nó chỉ cho ints, vì vậy bất cứ."

Phải thừa nhận rằng, mọi người sử dụng hệ thống loại của Haskell theo một số cách phức tạp đáng kinh ngạc, làm cho nó trở thành nguyên nhân của mọi người mới, nhưng bản thân hệ thống loại cơ bản là thanh lịch và được nhiều người ngưỡng mộ.


1
Cảm ơn câu trả lời này. Bài viết này bắt đầu với một số ví dụ của Joshua Bloch về việc thuốc generic quá phức tạp: artima.com/weblogs/viewpost.jsp?thread=222021 . Đây có phải là một sự khác biệt về văn hóa giữa Java và Haskell, trong đó các cấu trúc như vậy sẽ được coi là tốt trong Haskell, hoặc có sự khác biệt thực sự với hệ thống loại của Haskell tránh các tình huống như vậy không?
Peter

10
@Peter Haskell không có phân nhóm, và như Karl cho biết trình biên dịch có thể tự động suy ra các loại bao gồm các ràng buộc như "loại aphải là một số nguyên".
Doval

Nói cách khác hiệp phương sai , trong các ngôn ngữ như Scala.
Paul Draper

14

Đã có khá nhiều nghiên cứu về việc kết hợp thuốc generic với phân nhóm diễn ra khoảng 20 năm trước. Ngôn ngữ lập trình Thor được phát triển bởi nhóm nghiên cứu của Barbara Liskov tại MIT có một khái niệm về các mệnh đề "ở đâu" cho phép bạn chỉ định các yêu cầu của loại bạn đang tham số hóa. (Điều này tương tự với những gì C ++ đang cố gắng thực hiện với các khái niệm .)

Bài viết mô tả các khái quát của Thor và cách chúng tương tác với các kiểu con của Thor là: Day, M; Gruber, R; Liskov, B; Myers, AC: Các kiểu con so với các mệnh đề: ràng buộc đa hình tham số , ACM Conf trên Prog định hướng Obj, Sys, Lang và Ứng dụng , (OOPSLA-10): 156-158, 1995.

Tôi tin rằng, đến lượt họ, được xây dựng dựa trên công việc đã được thực hiện trên Emerald vào cuối những năm 1980. (Tôi chưa đọc tác phẩm đó, nhưng tài liệu tham khảo là: Đen, A; Hutchinson, N; Jul, E; Levy, H; Carter, L: Phân phối và các loại trừu tượng trong Emerald , _IEEE T. Software Eng., 13 ( 1): 65-76, 1987.

Cả Thor và Emerald đều là "ngôn ngữ hàn lâm" nên có lẽ họ không có đủ cách sử dụng để mọi người thực sự hiểu liệu mệnh đề (khái niệm) có thực sự giải quyết được bất kỳ vấn đề thực sự nào không. Thật thú vị khi đọc bài viết của Bjarne Stroustrup về lý do tại sao lần thử đầu tiên tại các khái niệm trong C ++ không thành công: Stroustrup, B: The C ++ 0x "Loại bỏ các khái niệm" Quyết định , Tiến sĩ Dobbs , ngày 22 tháng 7 năm 2009. (Thông tin thêm trên trang chủ của Stroustrup . )

Một hướng khác mà mọi người dường như đang cố gắng là một cái gì đó được gọi là đặc điểm . Ví dụ, ngôn ngữ lập trình Rust của Mozilla sử dụng các đặc điểm. Theo tôi hiểu (có thể sai hoàn toàn), việc tuyên bố rằng một lớp thỏa mãn một đặc điểm rất giống như nói rằng một lớp thực hiện một giao diện, nhưng bạn đang nói "hành xử như một" chứ không phải là "là". Có vẻ như các ngôn ngữ lập trình Swift mới của Apple đang sử dụng một khái niệm tương tự về giao thức để chỉ định các ràng buộc về các tham số cho tổng quát .

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.