Giao diện của Java và lớp kiểu của Haskell: sự khác biệt và điểm giống nhau?


112

Trong khi tôi đang học Haskell, tôi nhận thấy nó lớp kiểu , được cho là một phát minh tuyệt vời có nguồn gốc từ Haskell.

Tuy nhiên, trong trang Wikipedia về loại :

Lập trình viên định nghĩa một lớp kiểu bằng cách chỉ định một tập hợp các tên hàm hoặc hằng số, cùng với các kiểu tương ứng của chúng, phải tồn tại cho mọi kiểu thuộc lớp đó.

Điều này có vẻ khá gần với Giao diện của Java đối với tôi (trích dẫn trang Giao diện của Wikipedia (Java) ):

Giao diện trong ngôn ngữ lập trình Java là một kiểu trừu tượng được sử dụng để chỉ định một giao diện (theo nghĩa chung của thuật ngữ) mà các lớp phải thực thi.

Hai cái này trông khá giống nhau: lớp kiểu giới hạn hành vi của một kiểu, trong khi giao diện giới hạn hành vi của lớp.

Tôi tự hỏi sự khác biệt và giống nhau giữa lớp kiểu trong Haskell và giao diện trong Java, hoặc có thể chúng khác nhau về cơ bản?

CHỈNH SỬA: Tôi nhận thấy ngay cả haskell.org cũng thừa nhận rằng chúng tương tự nhau . Nếu chúng rất giống nhau (hoặc là chúng?), Thì tại sao type class lại bị cường điệu hóa như vậy?

CHỈNH SỬA THÊM: Chà, rất nhiều câu trả lời tuyệt vời! Tôi đoán tôi sẽ phải để cho cộng đồng quyết định cái nào là tốt nhất. Tuy nhiên, trong khi đọc câu trả lời, tất cả họ dường như chỉ nói rằng "có rất nhiều thứ mà typeclass có thể làm trong khi giao diện không thể hoặc phải đối phó với generic" . Tôi không thể không tự hỏi, có bất kỳ giao diện nào có thể làm trong khi kính xếp chữ không thể làm được không? Ngoài ra, tôi nhận thấy rằng Wikipedia tuyên bố rằng typeclass ban đầu được phát minh trong bài báo năm 1989 * "Làm thế nào để làm cho tính đa hình ad-hoc bớt đặc biệt hơn", trong khi Haskell vẫn còn trong cái nôi của nó, trong khi dự án Java được bắt đầu vào năm 1991 và phát hành lần đầu tiên vào năm 1995 Vì vậy, có thể thay vì typeclass tương tự như giao diện, ngược lại, các giao diện đó lại bị ảnh hưởng bởi typeclass? có tài liệu / giấy tờ nào ủng hộ hoặc bác bỏ điều này không? Cảm ơn vì tất cả các câu trả lời, tất cả đều rất khai sáng!

Cảm ơn tất cả các đầu vào!


3
Không, thực sự không có bất cứ thứ gì giao diện có thể làm được mà các lớp kiểu không thể làm được, với lưu ý chính là giao diện thường xuất hiện trong các ngôn ngữ có các tính năng tích hợp không có trong Haskell. Nếu các lớp kiểu được thêm vào Java, chúng cũng có thể sử dụng các tính năng đó.
CA McCann

8
Nếu có nhiều câu hỏi, bạn nên đặt nhiều câu hỏi, không nên cố gắng nhồi nhét tất cả vào một câu hỏi. Dù sao, để trả lời câu hỏi cuối cùng của bạn: Ảnh hưởng chính của Java là Objective-C ( chứ không phải C ++ như thường được báo cáo sai), có ảnh hưởng chính đến lượt mình là Smalltalk và C. Các giao diện của Java là sự thích nghi của các giao thức Objective-C , một lần nữa chính thức hóa ý tưởng về giao thức trong OO, do đó dựa trên ý tưởng về các giao thức trong mạng, cụ thể là ARPANet. Tất cả điều này đã xảy ra rất lâu trước khi bài báo bạn trích dẫn. ...
Jörg W Mittag,

1
... Ảnh hưởng của Haskell đối với Java đến muộn hơn nhiều và chỉ giới hạn trong Generics, xét cho cùng, được đồng thiết kế bởi một trong những nhà thiết kế của Haskell, Phil Wadler.
Jörg W Mittag

5
Đây là một bài viết trên Usenet của Patrick Naughton, một trong những nhà thiết kế ban đầu của Java: Java bị ảnh hưởng mạnh mẽ bởi Objective-C chứ không phải C ++ . Thật không may, nó quá cũ nên bài đăng gốc thậm chí không xuất hiện trong kho lưu trữ của Google.
Jörg W Mittag

8
Có một câu hỏi khác đã được đóng lại như một bản sao chính xác của câu này, nhưng nó có một câu trả lời chuyên sâu hơn nhiều: stackoverflow.com/questions/8122109/…
Ben,

Câu trả lời:


50

Tôi muốn nói rằng một giao diện giống như một lớp kiểu SomeInterface ttrong đó tất cả các giá trị đều có kiểu t -> whatever(nơi whateverkhông chứat ). Điều này là do với kiểu quan hệ kế thừa trong Java và các ngôn ngữ tương tự, phương thức được gọi phụ thuộc vào loại đối tượng mà chúng được gọi, và không có gì khác.

Điều đó có nghĩa là rất khó để tạo ra những thứ như add :: t -> t -> tvới một giao diện, nơi nó đa hình trên nhiều tham số, bởi vì không có cách nào để giao diện xác định rằng kiểu đối số và kiểu trả về của phương thức là cùng kiểu với kiểu của đối tượng mà nó được gọi (tức là kiểu "tự"). Với Generics, có một số cách để giả mạo điều này bằng cách tạo một giao diện với tham số chung được mong đợi là cùng loại với chính đối tượng, như cách Comparable<T>nó hoạt động, nơi bạn dự kiến ​​sẽ sử dụng Foo implements Comparable<Foo>để compareTo(T otherobject)loại có loại t -> t -> Ordering. Nhưng điều đó vẫn đòi hỏi người lập trình phải tuân theo quy tắc này, và cũng gây đau đầu khi mọi người muốn tạo một hàm sử dụng giao diện này thì phải có tham số kiểu chung đệ quy.

Ngoài ra, bạn sẽ không có những thứ như empty :: tvì bạn không gọi một hàm ở đây, vì vậy nó không phải là một phương thức.


1
Đặc điểm Scala (về cơ bản là các giao diện) cho phép this.type để bạn có thể trả về hoặc chấp nhận các tham số của "self type". Scala có một tính năng hoàn toàn riêng biệt mà họ gọi là btw "tự loại" không liên quan gì đến điều này. Không có gì trong số này là sự khác biệt về khái niệm, chỉ là sự khác biệt về triển khai.
đất sét

45

Điều tương tự giữa các giao diện và các lớp kiểu là chúng đặt tên và mô tả một tập hợp các thao tác liên quan. Bản thân các hoạt động được mô tả thông qua tên, đầu vào và đầu ra của chúng. Tương tự như vậy, có thể có nhiều cách triển khai các hoạt động này có thể sẽ khác nhau trong việc triển khai chúng.

Với điều đó, đây là một số khác biệt đáng chú ý:

  • Các phương thức Interfaces luôn được liên kết với một thể hiện đối tượng. Nói cách khác, luôn có một tham số ngụ ý 'this' là đối tượng mà phương thức được gọi. Tất cả các đầu vào cho một hàm lớp kiểu là rõ ràng.
  • Việc triển khai giao diện phải được định nghĩa là một phần của lớp thực hiện giao diện. Ngược lại, một 'thể hiện' của lớp kiểu có thể được định nghĩa hoàn toàn tách biệt với kiểu liên kết của nó ... ngay cả trong một mô-đun khác.

Nói chung, tôi nghĩ công bằng khi nói rằng các lớp kiểu mạnh mẽ và linh hoạt hơn các giao diện. Bạn sẽ xác định giao diện như thế nào để chuyển đổi một chuỗi thành một số giá trị hoặc phiên bản của kiểu triển khai? Nó chắc chắn không phải là không thể, nhưng kết quả sẽ không trực quan hoặc trang nhã. Bạn đã bao giờ ước có thể triển khai giao diện cho một kiểu trong thư viện đã biên dịch nào đó chưa? Cả hai đều dễ dàng thực hiện với các lớp kiểu.


1
Làm thế nào bạn sẽ mở rộng kính đánh máy? Kính xếp chữ có thể mở rộng các kính xếp chữ khác giống như cách các giao diện có thể mở rộng giao diện không?
CMCDragonkai

10
Có thể đáng để cập nhật câu trả lời này dựa trên các triển khai mặc định của Java 8 trong các giao diện.
Phục hồi Monica

1
@CMCDragonkai Có, ví dụ bạn có thể nói "class (Foo a) => Bar a where ..." để chỉ định rằng lớp kiểu Bar mở rộng lớp kiểu Foo. Giống như Java, Haskell có đa kế thừa ở đây.
Phục hồi Monica

Trong trường hợp này, không phải lớp kiểu giống như các giao thức trong Clojure nhưng với kiểu an toàn?
nawfal

24

Các lớp kiểu được tạo ra như một cách có cấu trúc để thể hiện "tính đa hình đặc biệt", về cơ bản là thuật ngữ kỹ thuật cho các hàm quá tải . Định nghĩa lớp kiểu trông giống như sau:

class Foobar a where
    foo :: a -> a -> Bool
    bar :: String -> a

Điều này có nghĩa là, khi bạn sử dụng hàm áp dụng foocho một số đối số của một kiểu thuộc về lớp Foobar, nó sẽ tìm kiếm một triển khai củafoo cụ thể cho kiểu đó và sử dụng nó. Điều này rất giống với tình huống nạp chồng toán tử trong các ngôn ngữ như C ++ / C #, ngoại trừ linh hoạt và tổng quát hơn.

Các giao diện phục vụ một mục đích tương tự trong các ngôn ngữ OO, nhưng khái niệm cơ bản hơi khác; Các ngôn ngữ OO đi kèm với khái niệm phân cấp kiểu tích hợp mà Haskell đơn giản là không có, điều này làm phức tạp vấn đề theo một số cách vì các giao diện có thể liên quan đến cả việc nạp chồng bằng kiểu con (nghĩa là gọi phương thức trên các trường hợp thích hợp, kiểu con triển khai giao diện mà siêu kiểu của chúng thực hiện) và bằng cách điều khiển dựa trên kiểu phẳng (vì hai lớp triển khai một giao diện có thể không có lớp cha chung cũng thực hiện nó). Với sự phức tạp bổ sung rất lớn được giới thiệu bởi kiểu con, tôi khuyên bạn nên nghĩ về các lớp kiểu như một phiên bản cải tiến của các hàm được nạp chồng trong một ngôn ngữ không phải OO.

Cũng cần lưu ý rằng các lớp kiểu có phương tiện điều phối linh hoạt hơn rất nhiều - các giao diện thường chỉ áp dụng cho lớp duy nhất thực hiện nó, trong khi các lớp kiểu được định nghĩa cho một kiểu , có thể xuất hiện ở bất kỳ đâu trong chữ ký của các hàm của lớp. Điều tương đương với điều này trong các giao diện OO sẽ cho phép giao diện xác định các cách để truyền một đối tượng của lớp đó đến các lớp khác, xác định các phương thức tĩnh và các hàm tạo sẽ chọn một triển khai dựa trên kiểu trả về được yêu cầu trong ngữ cảnh gọi, xác định các phương thức lấy các đối số cùng loại với lớp triển khai giao diện và nhiều thứ khác không thực sự dịch.

Tóm lại: Chúng phục vụ các mục đích tương tự, nhưng cách chúng hoạt động hơi khác nhau và các lớp kiểu đều biểu đạt hơn đáng kể và trong một số trường hợp, dễ sử dụng hơn vì làm việc trên các kiểu cố định chứ không phải là các phần của hệ thống phân cấp kế thừa.


Tôi đang đấu tranh với việc tìm hiểu hệ thống phân cấp loại trong Haskell, bạn nghĩ gì về loại hệ thống loại như Omega? Họ có thể mô phỏng cấu trúc phân cấp kiểu không?
CMCDragonkai

@CMCDragonkai: Tôi không đủ quen thuộc với Omega để thực sự nói, xin lỗi.
CA McCann

16

Tôi đã đọc các câu trả lời ở trên. Tôi cảm thấy mình có thể trả lời rõ ràng hơn một chút:

Một "loại lớp" Haskell và một "giao diện" Java / C # hoặc một "đặc điểm" Scala về cơ bản là tương tự nhau. Không có sự khác biệt về khái niệm giữa chúng nhưng có sự khác biệt về triển khai:

  • Các lớp kiểu Haskell được triển khai với các "thể hiện" tách biệt với định nghĩa kiểu dữ liệu. Trong C # / Java / Scala, các giao diện / đặc điểm phải được triển khai trong định nghĩa lớp.
  • Các lớp kiểu Haskell cho phép bạn trả về kiểu này hoặc kiểu tự. Đặc điểm Scala cũng vậy (loại này). Lưu ý rằng "tự loại" trong Scala là một tính năng hoàn toàn không liên quan. Java / C # yêu cầu một cách giải quyết lộn xộn với các số liệu chung để ước tính hành vi này.
  • Các lớp kiểu Haskell cho phép bạn xác định các hàm (bao gồm các hằng số) mà không cần đầu vào tham số kiểu "this". Các giao diện Java / C # và các đặc điểm Scala yêu cầu tham số đầu vào "this" trên tất cả các hàm.
  • Các lớp kiểu Haskell cho phép bạn xác định các triển khai mặc định cho các hàm. Các đặc điểm Scala và giao diện Java 8+ cũng vậy. C # có thể ước lượng một cái gì đó như thế này với các phương thức mở rộng.

2
Chỉ để thêm một số tài liệu cho các điểm câu trả lời này, tôi đã đọc On (Haskell) Type Classes và (C #) ngày hôm nay, cũng so sánh với các giao diện C # thay vì Javas, nhưng cũng nên hiểu về khía cạnh khái niệm mổ xẻ khái niệm về một giao diện xuyên biên giới ngôn ngữ.
daniel.kahlenberg

Tôi nghĩ rằng một số xấp xỉ cho những thứ như triển khai mặc định được thực hiện khá hiệu quả hơn trong C # với các lớp trừu tượng có lẽ?
Arwin

12

Trong chương trình Thạc sĩ Lập trình , có một cuộc phỏng vấn về Haskell với Phil Wadler, người phát minh ra các lớp kiểu, người giải thích sự tương đồng giữa các giao diện trong Java và các lớp kiểu trong Haskell:

Một phương thức Java như:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

rất giống với phương pháp Haskell:

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

Vì vậy, các lớp kiểu có liên quan đến các giao diện, nhưng tương ứng thực sự sẽ là một phương thức tĩnh được tham số hóa với một kiểu như trên.


Đây hẳn là câu trả lời, vì theo trang chủ của Phil Wadler , ông là nhà thiết kế nguyên tắc của Haskell, đồng thời ông cũng thiết kế phần mở rộng Generics cho Java, sau này được đưa vào chính ngôn ngữ này.
Wong Jia Hau


8

Đọc phần mở rộng và tích hợp phần mềm với các lớp loại trong đó các ví dụ được đưa ra về cách các lớp kiểu có thể giải quyết một số vấn đề mà giao diện không làm được.

Các ví dụ được liệt kê trong bài báo là:

  • vấn đề diễn đạt,
  • vấn đề tích hợp khung,
  • vấn đề về khả năng mở rộng độc lập,
  • sự chuyên chế của sự phân hủy thống trị, phân tán và rối ren.

3
Link đã chết ở trên. Hãy thử cái này thay thế .
Justin Leitgeb

1
Giờ thì người đó cũng đã chết. Hãy thử cái này
RichardW

7

Tôi không thể nói chuyện với cấp độ "cường điệu", nếu nó có vẻ tốt. Nhưng có các lớp kiểu tương tự nhau theo nhiều cách. Một điểm khác biệt mà tôi có thể nghĩ đến là nó Haskell bạn có thể cung cấp hành vi cho một số hoạt động của lớp kiểu :

class  Eq a  where
  (==), (/=) :: a -> a -> Bool
  x /= y     = not (x == y)
  x == y     = not (x /= y)

cho thấy rằng có hai hoạt động, bằng nhau (==) và không bằng nhau (/=), đối với những thứ là bản sao củaEq lớp kiểu. Nhưng phép toán không bằng được định nghĩa theo nghĩa bằng (do đó bạn chỉ phải cung cấp một) và ngược lại.

Vì vậy, trong Java có lẽ sẽ là một cái gì đó giống như:

interface Equal<T> {
    bool isEqual(T other) {
        return !isNotEqual(other); 
    }

    bool isNotEqual(T other) {
        return !isEqual(other); 
    }
}

và cách thức hoạt động là bạn chỉ cần cung cấp một trong những phương pháp đó để triển khai giao diện. Vì vậy, tôi muốn nói rằng khả năng cung cấp một loại triển khai một phần hành vi bạn muốn ở cấp giao diện là một sự khác biệt.


6

Chúng tương tự nhau (đọc là: có cách sử dụng tương tự), và có thể được triển khai tương tự: các hàm đa hình trong Haskell ẩn chứa một 'vtable' liệt kê các hàm được liên kết với typeclass.

Bảng này thường có thể được suy ra tại thời điểm biên dịch. Điều này có lẽ ít đúng hơn trong Java.

Nhưng đây là một bảng các hàm , không phải các phương thức . Các phương thức được liên kết với một đối tượng, Haskell typeclasses thì không.

Xem chúng giống như các generic của Java.


3

Như Daniel nói, việc triển khai giao diện được định nghĩa riêng biệt với các khai báo dữ liệu. Và như những người khác đã chỉ ra, có một cách đơn giản để xác định các hoạt động sử dụng cùng một loại miễn phí ở nhiều nơi. Vì vậy, nó dễ dàng xác địnhNum như một typeclass. Vì vậy, trong Haskell, chúng ta nhận được những lợi ích cú pháp của việc nạp chồng toán tử mà không thực sự có bất kỳ toán tử quá tải ma thuật nào - chỉ là kính chữ chuẩn.

Một điểm khác biệt nữa là bạn có thể sử dụng các phương pháp dựa trên một loại, ngay cả khi bạn chưa có giá trị cụ thể của loại đó!

Ví dụ read :: Read a => String -> a,. Vì vậy, nếu bạn có đủ thông tin loại khác về cách bạn sẽ sử dụng kết quả của một "đã đọc", bạn có thể để trình biên dịch tìm ra từ điển nào sẽ sử dụng cho bạn.

Bạn cũng có thể làm những việc như instance (Read a) => Read [a] where...vậy cho phép bạn xác định một phiên bản đọc cho bất kỳ danh sách những thứ có thể đọc được. Tôi không nghĩ điều đó hoàn toàn khả thi trong Java.

Và tất cả những điều này chỉ là kính đánh máy thông số đơn tiêu chuẩn mà không có thủ thuật nào xảy ra. Khi chúng tôi giới thiệu kính đánh máy đa tham số, thì một thế giới khả năng hoàn toàn mới sẽ mở ra, và thậm chí còn hơn thế nữa với các phụ thuộc hàm và họ kiểu, cho phép bạn nhúng nhiều thông tin và tính toán hơn vào hệ thống kiểu.


1
Các lớp kiểu bình thường là các giao diện vì các lớp kiểu đa tham số là để gửi nhiều lần trong OOP; bạn đạt được sự gia tăng tương ứng về sức mạnh của cả ngôn ngữ lập trình và sự đau đầu của lập trình viên.
CA McCann
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.