Loại lớp và giao diện đối tượng


33

Tôi không nghĩ rằng tôi hiểu các loại lớp. Tôi đã đọc ở đâu đó nghĩ rằng các lớp loại là "giao diện" (từ OO) rằng một kiểu thực hiện là sai và gây hiểu nhầm. Vấn đề là, tôi đang gặp vấn đề khi xem chúng là một cái gì đó khác biệt và làm thế nào là sai.

Ví dụ: nếu tôi có một lớp loại (theo cú pháp Haskell)

class Functor f where
  fmap :: (a -> b) -> f a -> f b

Nó khác với giao diện [1] như thế nào (theo cú pháp Java)

interface Functor<A> {
  <B> Functor<B> fmap(Function<B, A> fn)
}

interface Function<Return, Argument> {
  Return apply(Argument arg);
}

Một sự khác biệt có thể tôi có thể nghĩ đến là việc triển khai lớp loại được sử dụng trong một lệnh gọi nhất định không được chỉ định mà được xác định từ môi trường - giả sử, kiểm tra các mô-đun có sẵn để triển khai loại này. Đó dường như là một tạo tác triển khai có thể được xử lý bằng ngôn ngữ OO; như trình biên dịch (hoặc thời gian chạy) có thể quét một trình bao bọc / bộ mở rộng / trình vá khỉ hiển thị giao diện cần thiết trên loại.

Tôi đang thiếu gì?

[1] Lưu ý rằng f ađối số đã bị xóa từ fmapkhi có ngôn ngữ OO, bạn sẽ gọi phương thức này trên một đối tượng. Giao diện này giả định f ađối số đã được sửa.

Câu trả lời:


46

Ở dạng cơ bản, các lớp kiểu có phần giống với giao diện đối tượng. Tuy nhiên, ở nhiều khía cạnh, chúng chung chung hơn nhiều.

  1. Công văn là về các loại, không phải giá trị. Không có giá trị được yêu cầu để thực hiện nó. Ví dụ, có thể thực hiện công văn về loại kết quả của hàm, như với Readlớp của Haskell :

    class Read a where
      readsPrec :: Int -> String -> [(a, String)]
      ...
    

    Công văn như vậy rõ ràng là không thể trong OO thông thường.

  2. Các lớp loại tự nhiên mở rộng thành nhiều công văn, chỉ đơn giản bằng cách cung cấp nhiều tham số:

    class Mul a b c where
      (*) :: a -> b -> c
    
    instance Mul Int Int Int where ...
    instance Mul Int Vec Vec where ...
    instance Mul Vec Vec Int where ...
    
  3. Các định nghĩa sơ thẩm là độc lập với cả định nghĩa lớp và kiểu, làm cho chúng có tính mô đun hơn. Một loại T từ mô-đun A có thể được thêm vào một lớp C từ mô-đun M2 mà không sửa đổi định nghĩa của một trong hai, chỉ bằng cách cung cấp một thể hiện trong mô-đun M3. Trong OO, điều này đòi hỏi nhiều tính năng ngôn ngữ bí truyền (và ít OO-ish) hơn như các phương thức mở rộng.

  4. Các lớp loại dựa trên đa hình tham số, không phân nhóm. Điều đó cho phép gõ chính xác hơn. Hãy xem xét ví dụ

    pick :: Enum a => a -> a -> a
    pick x y = if fromEnum x == 0 then y else x
    

    so với

    pick(x : Enum, y : Enum) : Enum = if x.fromEnum() == 0 then y else x
    

    Trong trường hợp trước, áp dụng pick '\0' 'x'có loại Char, trong khi trong trường hợp sau, tất cả những gì bạn biết về kết quả sẽ là Enum. (Đây cũng là lý do tại sao hầu hết các ngôn ngữ OO ngày nay đều tích hợp đa hình tham số.)

  5. Liên quan chặt chẽ là vấn đề của phương pháp nhị phân. Chúng hoàn toàn tự nhiên với các lớp loại:

    class Ord a where
      (<) :: a -> a -> Bool
      ...
    
    min :: Ord a => a -> a -> a
    min x y = if x < y then x else y
    

    Với phân nhóm một mình, Ordgiao diện là không thể thể hiện. Bạn cần một hình thức đệ quy phức tạp hơn hoặc đa hình tham số gọi là "định lượng giới hạn F" để thực hiện chính xác. So sánh Java Comparablevà cách sử dụng của nó:

    interface Comparable<T> {
      int compareTo(T y);
    };
    
    <T extends Comparable<T>> T min(T x, T y) {
      if (x.compareTo(y) < 0)
        return x;
      else
        return y;
    }
    

Mặt khác, các giao diện dựa trên phân nhóm tự nhiên cho phép hình thành các bộ sưu tập không đồng nhất, ví dụ, một danh sách các loại List<C>có thể chứa các thành viên có nhiều kiểu con khác nhau C(mặc dù không thể khôi phục loại chính xác của chúng, ngoại trừ bằng cách sử dụng các chương trình truyền phát). Để làm tương tự dựa trên các lớp loại, bạn cần các kiểu tồn tại như một tính năng bổ sung.


Ah, điều đó rất có ý nghĩa. Loại công văn dựa trên giá trị có lẽ là điều lớn lao mà tôi đã không nghĩ đến một cách đúng đắn. Vấn đề đa hình tham số và gõ cụ thể hơn có ý nghĩa. Tôi vừa mới kết nối các giao diện dựa trên đó và phân nhóm lại với nhau (dường như tôi nghĩ trong Java: - /).
oconnor0

Các kiểu tồn tại có gì đó giống với việc tạo ra các kiểu con Cmà không có sự hiện diện của truyền phát không?
oconnor0

Loại. Chúng là một phương tiện để tạo ra một loại trừu tượng, tức là ẩn đại diện của nó. Trong Haskell, nếu bạn cũng đính kèm các ràng buộc lớp với nó, bạn vẫn có thể sử dụng các phương thức của các lớp đó trên nó, nhưng không có gì khác. - Downcasts thực sự là một tính năng tách biệt với cả phân nhóm và định lượng hiện sinh, và về nguyên tắc, có thể được thêm vào trong sự hiện diện của cái sau. Cũng như có những ngôn ngữ OO không cung cấp nó.
Andreas Rossberg

PS: FWIW, các loại ký tự đại diện trong Java là các loại tồn tại, mặc dù khá hạn chế và đặc biệt (có thể là một phần lý do tại sao chúng hơi khó hiểu).
Andreas Rossberg

1
@didierc, điều đó sẽ bị hạn chế trong các trường hợp có thể được giải quyết hoàn toàn tĩnh. Ngoài ra, để phù hợp với các lớp loại, nó sẽ yêu cầu một hình thức phân giải quá tải có thể phân biệt chỉ dựa trên loại trả về (xem mục 1).
Andreas Rossberg

6

Ngoài câu trả lời xuất sắc của Andreas, xin lưu ý rằng các lớp loại có nghĩa là hợp lý hóa quá tải , ảnh hưởng đến không gian tên toàn cầu. Không có tình trạng quá tải trong Haskell ngoài những gì bạn có thể có được thông qua các lớp loại. Ngược lại, khi bạn sử dụng giao diện đối tượng, chỉ những hàm được khai báo để lấy đối số của giao diện đó mới cần phải lo lắng về tên hàm trong giao diện đó. Vì vậy, giao diện cung cấp không gian tên địa phương.

Ví dụ, bạn đã có fmaptrong một giao diện đối tượng được gọi là "Functor". Sẽ hoàn toàn ổn khi có fmapmột giao diện khác trong giao diện khác, nói "Structor". Mỗi đối tượng (hoặc lớp) có thể chọn và chọn giao diện mà nó muốn thực hiện. Ngược lại, trong Haskell, bạn chỉ có thể có một fmaptrong một bối cảnh cụ thể. Bạn không thể nhập cả hai loại lớp Functor và Structor vào cùng một ngữ cảnh.

Các giao diện đối tượng tương tự như chữ ký ML tiêu chuẩn hơn là các lớp loại.


và dường như có một mối quan hệ chặt chẽ giữa các mô-đun ML và các lớp loại Haskell. cse.unsw.edu.au/~chak/ con / DHC07.html
Steven Shaw

1

Trong ví dụ cụ thể của bạn (với lớp loại Functor), các triển khai Haskell và Java hoạt động khác nhau. Hãy tưởng tượng bạn có kiểu dữ liệu Có thể và muốn nó là Functor (đây là kiểu dữ liệu thực sự phổ biến trong Haskell, bạn cũng có thể dễ dàng thực hiện trong Java). Trong ví dụ Java của bạn, bạn sẽ làm cho lớp Có thể thực hiện giao diện Functor của bạn. Vì vậy, bạn có thể viết như sau (chỉ là mã giả vì tôi chỉ có nền c #):

Maybe<Int> val = new Maybe<Int>(5);
Functor<Int> res = val.fmap(someFunctionHere);

Lưu ý rằng rescó loại Functor, không có thể. Vì vậy, điều này làm cho việc triển khai Java gần như không thể sử dụng được do bạn mất thông tin loại cụ thể và cần phải thực hiện phôi. (ít nhất là tôi đã thất bại khi viết một triển khai như vậy trong đó các loại vẫn còn hiện diện). Với các lớp loại Haskell, bạn sẽ nhận được Có thể là Kết quả.


Tôi nghĩ vấn đề này là do Java không hỗ trợ các loại được phân loại cao hơn và không liên quan đến các cuộc thảo luận về giao diện Vs typeclass. Nếu Java có các loại cao hơn, thì fmap rất có thể trả về a Maybe<Int>.
dcastro
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.