Sự khác biệt giữa các loại lớp của Haskell và giao diện của Go là gì?


8

Tôi tự hỏi liệu có sự khác biệt giữa các loại loại của Haskell và giao diện của Go không. Cả hai đều xác định loại dựa trên các hàm, theo cách đó, một giá trị khớp với một loại, nếu một hàm được yêu cầu bởi loại được xác định cho giá trị.

Có sự khác biệt nào không hay đây chỉ là hai cái tên cho cùng một thứ?

Câu trả lời:


6

Hai khái niệm rất rất giống nhau. Trong các ngôn ngữ OOP bình thường, chúng tôi đính kèm một vtable (hoặc cho các giao diện: có thể) cho từng đối tượng:

| this
v
+---+---+---+
| V | a | b | the object with fields a, b
+---+---+---+
  |
  v
 +---+---+---+
 | o | p | q | the vtable with method slots o(), p(), q()
 +---+---+---+

Điều này cho phép chúng ta gọi các phương thức tương tự như this->vtable.p(this).

Trong Haskell, bảng phương thức giống như một đối số ẩn ẩn:

method :: Class a => a -> a -> Int

sẽ trông giống như chức năng C ++

template<typename A>
int method(Class<A>*, A*, A*)

trong đó Class<A>là một ví dụ của typeclass Classcho loại A. Một phương thức sẽ được gọi như

typeclass_instance->p(value_ptr);

Ví dụ là tách biệt với các giá trị. Các giá trị vẫn giữ loại thực tế của chúng. Mặc dù kiểu chữ cho phép một số đa hình, nhưng đây không phải là đa hình. Điều đó làm cho không thể lập danh sách các giá trị thỏa mãn a Class. Ví dụ, giả sử chúng ta có instance Class Int ...instance Class String ..., chúng ta không thể tạo một loại danh sách không đồng nhất như thế [Class]có giá trị như thế nào [42, "foo"]. (Điều này có thể xảy ra khi bạn sử dụng tiện ích mở rộng các loại hiện có của kiểu Cameron, giúp chuyển đổi hiệu quả sang cách tiếp cận Go).

Trong Go, một giá trị không triển khai một bộ giao diện cố định. Do đó, nó không thể có một con trỏ vtable. Thay vào đó, các con trỏ tới các loại giao diện được triển khai dưới dạng các con trỏ béo bao gồm một con trỏ tới dữ liệu, một con trỏ khác tới nó:

    `this` fat pointer
    +---+---+
    |   |   |
    +---+---+
 ____/    \_________
v                   v
+---+---+---+       +---+---+
| o | p | q |       | a | b | the data with
+---+---+---+       +---+---+ fields a, b
itable with method
slots o(), p(), q()

this.itable->p(this.data_ptr)

Nó có thể được kết hợp với dữ liệu thành một con trỏ béo khi bạn chuyển từ một giá trị bình thường sang một loại giao diện. Khi bạn có một loại giao diện, loại dữ liệu thực tế đã trở nên không liên quan. Thực tế, bạn không thể truy cập trực tiếp vào các trường mà không thông qua các phương thức hoặc hạ thấp giao diện (có thể thất bại).

Cách tiếp cận của Go để gửi giao diện đi kèm với chi phí: mỗi con trỏ đa hình lớn gấp đôi một con trỏ bình thường. Ngoài ra, truyền từ giao diện này sang giao diện khác liên quan đến việc sao chép các con trỏ phương thức sang một vtable mới. Nhưng một khi chúng ta đã xây dựng được nó, điều này cho phép chúng ta gửi các cuộc gọi phương thức đến nhiều giao diện, một thứ mà các ngôn ngữ OOP truyền thống phải chịu. Ở đây, m là số phương thức trong giao diện đích và b là số lớp cơ sở:

  • C ++ không cắt đối tượng hoặc cần đuổi theo con trỏ kế thừa ảo khi truyền, nhưng sau đó có quyền truy cập vtable đơn giản. O (1) hoặc O (b) chi phí phát sóng, nhưng gửi phương thức O (1).
  • Java Hotspot VM không phải làm bất cứ điều gì khi upcasting, nhưng khi tra cứu phương thức giao diện thực hiện tìm kiếm tuyến tính thông qua tất cả các khả năng được thực hiện bởi lớp đó. O (1) upcasting, nhưng phương thức O (b) gửi.
  • Python không phải làm bất cứ điều gì khi upcasting, nhưng sử dụng tìm kiếm tuyến tính thông qua danh sách lớp cơ sở được tuyến tính hóa C3. O (1) upcasting, nhưng phương thức O (b²) gửi? Tôi không chắc độ phức tạp thuật toán của C3 là gì.
  • .NET CLR sử dụng một cách tiếp cận tương tự như Hotspot nhưng thêm một mức độ gián tiếp khác trong nỗ lực tối ưu hóa cho việc sử dụng bộ nhớ. O (1) upcasting, nhưng phương thức O (b) gửi.

Độ phức tạp điển hình cho việc gửi phương thức tốt hơn nhiều vì việc tra cứu phương thức thường có thể được lưu trong bộ nhớ cache, nhưng trường hợp phức tạp nhất là khá kinh khủng.

Để so sánh, Go có phát sóng phương thức O (1) hoặc O (m) và phương thức O (1). Haskell không có upcasting (ràng buộc một kiểu với một lớp loại là hiệu ứng thời gian biên dịch) và gửi phương thức O (1).


Cảm ơn vì [42, "foo"]. Đó là một ví dụ sinh động.
ceving

2
Mặc dù câu trả lời này được viết tốt và chứa thông tin hữu ích, tôi nghĩ rằng bằng cách tập trung vào việc thực hiện trong mã được biên dịch, nó vượt quá đáng kể sự tương đồng giữa các giao diện và các lớp loại. Với các lớp loại (và hệ thống loại Haskell nói chung), hầu hết các nội dung thú vị xảy ra trong quá trình biên dịch và không được phản ánh trong mã máy cuối cùng.
KA Buhr

7

Có một số khác biệt

  1. Các kiểu chữ Haskell được gõ theo danh nghĩa - bạn phải khai báo đó Maybelà a Monad. Các giao diện đi được gõ theo cấu trúc: nếu circlekhai báo area() float64squarecả hai đều nằm dưới giao diện shapetự động.
  2. Haskell (với các phần mở rộng GHC) có các loại Loại tham số nhiều loại và (như Maybe aví dụ của tôi ) cho các loại loại cao hơn. Go không có tương đương cho những điều này.
  3. Trong Haskell, các lớp loại được tiêu thụ với tính đa hình ràng buộc mang lại cho bạn các ràng buộc không thể diễn tả được với Go. Ví dụ: + :: Num a => a -> a -> ađảm bảo rằng bạn sẽ không cố gắng thêm dấu phẩy động và bậc bốn, không thể diễn tả được trong Go.

Là 1. thực sự là một sự khác biệt hay chỉ là thiếu đường?
ceving

1
Giao diện đi xác định một giao thức cho các giá trị, các lớp loại Haskell định nghĩa một giao thức cho các loại, đó cũng là một sự khác biệt khá lớn, tôi nói. (Đó là lý do tại sao chúng được gọi là "lớp loại", sau tất cả. Chúng phân loại các loại, không giống như các lớp OO (hoặc giao diện của Go), phân loại các giá trị.)
Jörg W Mittag

1
@ceving, nó chắc chắn không phải là đường theo nghĩa thông thường: nếu Haskell nhảy sang thứ gì đó giống như Scala liên quan đến các lớp loại, nó sẽ phá vỡ rất nhiều mã hiện có.
walpen

@ JörgWMittag Tôi sẽ đồng ý ở đó và tôi thích câu trả lời của bạn: Tôi đã cố gắng nhận được nhiều hơn sự khác biệt từ quan điểm của người dùng.
walpen

@walpen Tại sao điều này phá vỡ mã? Tôi tự hỏi làm thế nào mã như vậy có thể tồn tại khi xem xét tính nghiêm ngặt của hệ thống loại Haskell.
ceving

4

Chúng hoàn toàn khác nhau. Giao diện đi xác định một giao thức cho các giá trị, các lớp loại Haskell định nghĩa một giao thức cho các loại. (Đó là lý do tại sao chúng được gọi là "lớp loại", sau tất cả. Chúng phân loại các loại, không giống như các lớp OO (hoặc giao diện của Go), phân loại các giá trị.)

Giao diện đi chỉ là gõ cấu trúc cũ nhàm chán, không có gì hơn.


1
Bạn có thể giải thích nó được không? Thậm chí có thể không có giai điệu hạ thấp. Những gì tôi đã đọc nói rằng các lớp loại là đa hình ad-hoc có thể so sánh với quá tải toán tử, giống như các giao diện trong Go.
ceving

Từ hướng dẫn của Haskell : "Giống như khai báo giao diện, khai báo lớp Haskell xác định giao thức sử dụng một đối tượng"
ceving

2
Từ đó cùng hướng dẫn ( đậm tôi nhấn mạnh): "Loại lớp [...] cho phép chúng tôi tuyên bố mà loạitrường hợp trong đó lớp" Các trường hợp của một giao diện Go là các giá trị , các trường hợp của một lớp Haskell loại là loại . Các giá trị và loại sống trong hai thế giới hoàn toàn riêng biệt (ít nhất là trong các ngôn ngữ như Haskell và Go, các ngôn ngữ được gõ phụ thuộc như Agda, Guru, Epigram, Idris, Isabelle, Coq, v.v. là một vấn đề khác).
Jörg W Mittag

Bỏ phiếu vì câu trả lời là sâu sắc, nhưng tôi nghĩ chi tiết hơn có thể giúp đỡ. Và những gì nhàm chán về gõ cấu trúc?! Theo tôi thì nó khá hiếm và đáng để ăn mừng.
Max Heiber
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.