Antarmuka Java dan kelas tipe Haskell: perbedaan dan persamaan?

112

Saat saya mempelajari Haskell, saya melihat kelas tipenya , yang seharusnya merupakan penemuan hebat yang berasal dari Haskell.

Namun, di halaman Wikipedia tentang kelas tipe :

Pemrogram mendefinisikan kelas tipe dengan menetapkan sekumpulan fungsi atau nama konstanta, bersama dengan tipenya masing-masing, yang harus ada untuk setiap tipe yang termasuk dalam kelas tersebut.

Yang tampaknya agak dekat dengan Antarmuka Java bagi saya (mengutip halaman Antarmuka Wikipedia (Java) ):

Antarmuka dalam bahasa pemrograman Java adalah tipe abstrak yang digunakan untuk menentukan antarmuka (dalam pengertian umum istilah) yang harus diterapkan kelas.

Keduanya terlihat agak mirip: kelas tipe membatasi perilaku tipe, sementara antarmuka membatasi perilaku kelas.

Saya ingin tahu apa perbedaan dan persamaan antara kelas tipe di Haskell dan antarmuka di Java, atau mungkin mereka berbeda secara mendasar?

EDIT: Saya perhatikan bahkan haskell.org mengakui bahwa mereka serupa . Jika mereka sangat mirip (atau apakah mereka?), Lalu mengapa kelas tipe diperlakukan dengan hype seperti itu?

EDIT LEBIH BANYAK: Wow, begitu banyak jawaban bagus! Saya rasa saya harus membiarkan komunitas memutuskan mana yang terbaik. Namun, saat membaca jawaban, semuanya sepertinya hanya mengatakan bahwa "ada banyak hal yang dapat dilakukan kelas tipe sementara antarmuka tidak dapat atau harus mengatasi obat generik" . Saya bertanya-tanya, adakah antarmuka yang dapat dilakukan sementara kelas tipe tidak bisa? Selain itu, saya perhatikan bahwa Wikipedia mengklaim bahwa kelas tipe awalnya ditemukan pada makalah tahun 1989 * "Cara membuat polimorfisme ad-hoc kurang ad hoc", sementara Haskell masih dalam buaiannya, sementara proyek Java dimulai pada tahun 1991 dan pertama kali dirilis pada tahun 1995 Jadi mungkin bukan kelas tipe yang mirip dengan antarmuka, sebaliknya, antarmuka itu dipengaruhi oleh kelas tipe?Apakah ada dokumen / kertas yang mendukung atau menyangkal hal ini? Terima kasih atas semua jawaban, semuanya sangat mencerahkan!

Terima kasih atas semua masukannya!

zw324
sumber
3
Tidak, sebenarnya tidak ada antarmuka yang dapat melakukan yang tidak dapat dilakukan kelas jenis itu, dengan peringatan utama bahwa antarmuka umumnya muncul dalam bahasa yang memiliki fitur bawaan yang tidak ditemukan di Haskell. Jika kelas tipe ditambahkan ke Java, mereka juga dapat menggunakan fitur tersebut.
CA McCann
8
Jika Anda memiliki banyak pertanyaan, Anda harus mengajukan beberapa pertanyaan, bukan mencoba menjejalkan semuanya menjadi satu pertanyaan. Bagaimanapun, untuk menjawab pertanyaan terakhir Anda: pengaruh utama Java adalah Objective-C (dan bukan C ++ seperti yang sering dilaporkan secara salah), yang pengaruh utamanya pada gilirannya adalah Smalltalk dan C. Antarmuka Java adalah adaptasi dari protokol Objective-C yang lagi-lagi a formalisasi gagasan protokol dalam OO, yang pada gilirannya didasarkan pada gagasan protokol dalam jaringan, khususnya ARPANet. Ini semua terjadi jauh sebelum makalah yang Anda kutip. ...
Jörg W Mittag
1
... Pengaruh Haskell di Java datang jauh kemudian, dan terbatas pada Generik, yang, bagaimanapun, dirancang bersama oleh salah satu desainer Haskell, Phil Wadler.
Jörg W Mittag
5
Ini adalah artikel Usenet oleh Patrick Naughton, salah satu desainer asli Java: Java Sangat Dipengaruhi oleh Objective-C dan bukan C ++ . Sayangnya, itu sangat tua sehingga posting orignal bahkan tidak muncul di arsip Google.
Jörg W Mittag
8
Ada pertanyaan lain yang ditutup sebagai duplikat yang tepat dari pertanyaan ini, tetapi memiliki jawaban yang jauh lebih mendalam: stackoverflow.com/questions/8122109/…
Ben

Jawaban:

50

Saya akan mengatakan bahwa antarmuka adalah semacam kelas tipe di SomeInterface tmana semua nilai memiliki tipe t -> whatever(di mana whatevertidak mengandung t). Ini karena dengan jenis hubungan pewarisan di Java dan bahasa serupa, metode yang dipanggil bergantung pada jenis objek tempat mereka dipanggil, dan tidak ada yang lain.

Itu berarti sangat sulit untuk membuat hal-hal seperti add :: t -> t -> tdengan antarmuka, di mana polimorfik pada lebih dari satu parameter, karena tidak ada cara bagi antarmuka untuk menentukan bahwa tipe argumen dan tipe kembalian metode adalah tipe yang sama dengan tipe dari objek yang dipanggil (yaitu tipe "diri"). Dengan Generics, ada semacam cara untuk memalsukan ini dengan membuat antarmuka dengan parameter generik yang diharapkan memiliki tipe yang sama dengan objek itu sendiri, seperti bagaimana Comparable<T>melakukannya, di mana Anda diharapkan menggunakan Foo implements Comparable<Foo>sehingga compareTo(T otherobject)jenis memiliki tipe t -> t -> Ordering. Tapi itu tetap mengharuskan programmer untuk mengikuti aturan ini, dan juga menyebabkan sakit kepala ketika orang ingin membuat fungsi yang menggunakan antarmuka ini, mereka harus memiliki parameter tipe generik rekursif.

Selain itu, Anda tidak akan memiliki hal-hal seperti empty :: tkarena Anda tidak memanggil fungsi di sini, jadi ini bukan metode.

newacct
sumber
1
Scala traits (pada dasarnya interface) memungkinkan this.type sehingga Anda dapat mengembalikan atau menerima parameter dari "self type". Scala memiliki fitur yang benar-benar terpisah yang mereka sebut "tipe diri" btw yang tidak ada hubungannya dengan ini. Tak satu pun dari ini adalah perbedaan konseptual, hanya perbedaan implementasi.
tanah liat
45

Apa yang mirip antara antarmuka dan kelas tipe adalah bahwa mereka menamai dan mendeskripsikan satu set operasi terkait. Operasi itu sendiri dijelaskan melalui nama, masukan, dan keluarannya. Demikian juga mungkin ada banyak implementasi dari operasi ini yang mungkin akan berbeda dalam implementasinya.

Berikut adalah beberapa perbedaan penting:

  • Metode antarmuka selalu dikaitkan dengan instance objek. Dengan kata lain, selalu ada parameter 'ini' yang tersirat yang merupakan objek tempat metode dipanggil. Semua input ke fungsi kelas tipe bersifat eksplisit.
  • Implementasi antarmuka harus didefinisikan sebagai bagian dari kelas yang mengimplementasikan antarmuka. Sebaliknya, 'instance' kelas jenis dapat didefinisikan sepenuhnya terpisah dari jenis yang terkait ... bahkan di modul lain.

Secara umum, menurut saya adil untuk mengatakan bahwa kelas tipe lebih kuat dan fleksibel daripada antarmuka. Bagaimana Anda mendefinisikan antarmuka untuk mengonversi string ke beberapa nilai atau contoh dari tipe penerapan? Ini tentu bukan tidak mungkin, tetapi hasilnya tidak akan intuitif atau elegan. Pernahkah Anda berharap itu mungkin untuk mengimplementasikan antarmuka untuk suatu tipe di beberapa perpustakaan yang dikompilasi? Keduanya mudah dicapai dengan kelas tipe.

Daniel Pratt
sumber
1
Bagaimana Anda memperluas kelas tipe? Dapatkah kelas tipe memperluas kelas tipe lain seperti bagaimana antarmuka dapat memperluas antarmuka?
CMCDragonkai
10
Mungkin ada gunanya memperbarui jawaban ini mengingat implementasi default Java 8 di antarmuka.
Kembalikan Monica
1
@CMCDragonkai Ya, Anda dapat misalnya mengatakan "class (Foo a) => Bar a where ..." untuk menentukan bahwa kelas tipe Bar memperluas kelas tipe Foo. Seperti Java, Haskell memiliki banyak warisan di sini.
Kembalikan Monica
Dalam hal ini, bukankah kelas tipe sama dengan protokol di Clojure tetapi dengan keamanan tipe?
nawfal
24

Kelas jenis dibuat sebagai cara terstruktur untuk mengekspresikan "polimorfisme ad-hoc", yang pada dasarnya adalah istilah teknis untuk fungsi yang kelebihan beban . Definisi kelas tipe terlihat seperti ini:

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

Artinya, ketika Anda menggunakan fungsi apply foo ke beberapa argumen dari tipe yang termasuk dalam kelas Foobar, ia mencari implementasi fookhusus untuk tipe itu, dan menggunakannya. Ini sangat mirip dengan situasi dengan operator overloading dalam bahasa seperti C ++ / C #, kecuali lebih fleksibel dan umum.

Antarmuka memiliki tujuan yang sama dalam bahasa OO, tetapi konsep dasarnya agak berbeda; Bahasa OO hadir dengan gagasan built-in dari tipe hierarki yang tidak dimiliki Haskell, yang memperumit masalah dalam beberapa cara karena antarmuka dapat melibatkan overloading dengan subtipe (yaitu, memanggil metode pada contoh yang sesuai, subtipe yang mengimplementasikan antarmuka supertypes mereka lakukan) dan dengan pengiriman berbasis tipe datar (karena dua kelas yang mengimplementasikan antarmuka mungkin tidak memiliki superclass umum yang juga mengimplementasikannya). Mengingat kerumitan tambahan yang sangat besar yang diperkenalkan oleh subtipe, saya sarankan lebih membantu untuk memikirkan kelas tipe sebagai versi perbaikan dari fungsi yang kelebihan beban dalam bahasa non-OO.

Yang juga perlu diperhatikan adalah bahwa kelas tipe memiliki cara pengiriman yang jauh lebih fleksibel - antarmuka umumnya hanya berlaku untuk kelas tunggal yang mengimplementasikannya, sedangkan kelas tipe didefinisikan untuk suatu tipe , yang dapat muncul di mana saja dalam tanda tangan fungsi kelas. Setara dengan ini di antarmuka OO akan memungkinkan antarmuka untuk menentukan cara meneruskan objek kelas itu ke kelas lain, menentukan metode statis dan konstruktor yang akan memilih implementasi berdasarkan apa jenis kembalian yang diperlukan dalam konteks panggilan, menentukan metode yang mengambil argumen dengan tipe yang sama dengan kelas yang mengimplementasikan antarmuka, dan berbagai hal lain yang tidak benar-benar diterjemahkan sama sekali.

Singkatnya: Mereka melayani tujuan yang sama, tetapi cara kerjanya agak berbeda, dan kelas tipe keduanya jauh lebih ekspresif dan, dalam beberapa kasus, lebih mudah digunakan karena bekerja pada tipe tetap daripada potongan dari hierarki pewarisan.

CA McCann
sumber
Saya kesulitan memahami hierarki tipe di Haskell, apa pendapat Anda tentang tipe sistem tipe seperti Omega? Bisakah mereka mensimulasikan hierarki tipe?
CMCDragonkai
@CMCDragonkai: Saya tidak begitu paham dengan Omega untuk mengatakan, maaf.
CA McCann
16

Saya sudah membaca jawaban di atas. Saya merasa saya bisa menjawab dengan lebih jelas:

Sebuah "kelas tipe" Haskell dan "antarmuka" Java / C # atau "sifat" Scala pada dasarnya analog. Tidak ada perbedaan konseptual di antara keduanya tetapi terdapat perbedaan implementasi:

  • Kelas tipe Haskell diimplementasikan dengan "instance" yang terpisah dari definisi tipe data. Di C # / Java / Scala, antarmuka / sifat harus diimplementasikan dalam definisi kelas.
  • Kelas tipe Haskell memungkinkan Anda mengembalikan tipe atau tipe diri ini. Ciri-ciri scala juga berfungsi (tipe ini). Perhatikan bahwa "tipe diri" di Scala adalah fitur yang sama sekali tidak berhubungan. Java / C # memerlukan solusi yang berantakan dengan obat generik untuk memperkirakan perilaku ini.
  • Kelas tipe Haskell memungkinkan Anda mendefinisikan fungsi (termasuk konstanta) tanpa input parameter tipe "ini". Antarmuka Java / C # dan sifat Scala memerlukan parameter input "ini" pada semua fungsi.
  • Kelas tipe Haskell memungkinkan Anda menentukan implementasi default untuk fungsi. Begitu juga ciri-ciri Scala dan antarmuka Java 8+. C # dapat memperkirakan sesuatu seperti ini dengan metode ekstensi.
tanah liat
sumber
2
Hanya untuk menambahkan beberapa literatur ke poin jawaban ini, saya membaca On (Haskell) Type Classes dan (C #) Interfaces hari ini, yang juga membandingkan dengan antarmuka C # daripada Javas, tetapi harus memberikan pemahaman tentang sisi konseptual yang membedah gagasan tentang sebuah antarmuka melintasi batas bahasa.
daniel.kahlenberg
Saya pikir beberapa perkiraan untuk hal-hal seperti implementasi default dilakukan lebih efisien dalam C # dengan kelas abstrak mungkin?
Arwin
12

Dalam pikiran Master Pemrograman , ada sebuah wawancara tentang Haskell dengan Phil Wadler, penemu kelas tipe, yang menjelaskan kesamaan antara antarmuka di Java dan kelas tipe di Haskell:

Metode Java seperti:

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

sangat mirip dengan metode Haskell:

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

Jadi, kelas tipe terkait dengan antarmuka, tetapi korespondensi sebenarnya akan menjadi metode statis parametrized dengan tipe seperti di atas.

ewernli
sumber
Ini harus menjadi jawabannya, karena menurut homepage Phil Wadler , dia adalah desainer utama Haskell, pada saat yang sama dia juga merancang ekstensi Generik untuk Java, yang kemudian dimasukkan ke dalam bahasanya sendiri.
Wong Jia Hau
10

Tonton ceramah Phillip Wadler Faith, Evolution, and Programming Languages . Wadler bekerja di Haskell dan merupakan kontributor utama Java Generics.

Mcandre
sumber
1
Hal yang relevan berada di sekitar 25m (meskipun awalnya cukup lucu).
Fred Foo
8

Baca Ekstensi dan Integrasi Perangkat Lunak dengan Kelas Jenis di mana contoh diberikan tentang bagaimana kelas jenis dapat memecahkan sejumlah masalah yang antarmuka tidak bisa.

Contoh yang tertera di koran adalah:

  • masalah ekspresi,
  • masalah integrasi kerangka kerja,
  • masalah ekstensibilitas independen,
  • tirani dekomposisi dominan, hamburan dan kekusutan.
Channing Walton
sumber
3
Tautan sudah mati di atas. Coba yang ini saja .
Justin Leitgeb
1
Nah sekarang yang itu juga sudah mati. Coba yang ini
RichardW
7

Saya tidak bisa berbicara dengan level "hype", jika kelihatannya baik-baik saja. Tapi kelas tipe ya mirip dalam banyak hal. Satu perbedaan yang dapat saya pikirkan adalah bahwa Haskell Anda dapat memberikan perilaku untuk beberapa operasi kelas tipe :

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

yang menunjukkan bahwa ada dua operasi, sama (==), dan tidak sama (/=), untuk hal-hal yang merupakan instance dariEq kelas tipe. Tetapi operasi yang tidak sama didefinisikan dalam istilah yang sama (sehingga Anda hanya perlu memberikan satu), dan sebaliknya.

Jadi di Java yang mungkin tidak legal itu akan menjadi seperti ini:

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

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

dan cara kerjanya adalah Anda hanya perlu menyediakan salah satu metode tersebut untuk mengimplementasikan antarmuka. Jadi menurut saya kemampuan untuk menyediakan semacam implementasi parsial dari perilaku yang Anda inginkan di tingkat antarmuka adalah suatu perbedaan.

Chris
sumber
6

Mereka serupa (baca: memiliki kegunaan yang sama), dan mungkin diimplementasikan dengan cara yang sama: fungsi polimorfik di Haskell menggunakan sebuah 'vtable' yang mencantumkan fungsi-fungsi yang terkait dengan kelas tipe.

Tabel ini seringkali dapat disimpulkan pada waktu kompilasi. Ini mungkin kurang benar di Jawa.

Tapi ini adalah tabel fungsi , bukan metode . Metode terikat ke sebuah objek, kelas tipe Haskell tidak.

Lihat mereka seperti generik Java.

Alexandre C.
sumber
3

Seperti yang dikatakan Daniel, implementasi antarmuka didefinisikan terpisah dari deklarasi data. Dan seperti yang ditunjukkan orang lain, ada cara langsung untuk menentukan operasi yang menggunakan tipe bebas yang sama di lebih dari satu tempat. Jadi mudah untuk didefinisikan Numsebagai kelas tipe. Jadi di Haskell kita mendapatkan manfaat sintaksis dari operator yang kelebihan beban tanpa benar-benar memiliki operator ajaib yang kelebihan beban - hanya kelas tipe standar.

Perbedaan lainnya adalah Anda dapat menggunakan metode berdasarkan suatu tipe, bahkan ketika Anda belum memiliki nilai konkret dari tipe tersebut yang berkeliaran!

Sebagai contoh, read :: Read a => String -> a ,. Jadi, jika Anda memiliki cukup informasi jenis lain yang berkeliaran tentang bagaimana Anda akan menggunakan hasil "baca", Anda dapat membiarkan kompilator mencari tahu kamus mana yang akan digunakan untuk Anda.

Anda juga dapat melakukan hal-hal seperti instance (Read a) => Read [a] where...yang memungkinkan Anda menentukan instance baca apa pun daftar dapat dibaca. Saya rasa itu tidak mungkin di Jawa.

Dan semua ini hanyalah kelas tipe parameter tunggal standar tanpa ada tipuan yang terjadi. Setelah kami memperkenalkan kelas tipe multi-parameter, maka dunia kemungkinan baru akan terbuka, dan terlebih lagi dengan dependensi fungsional dan kelompok tipe, yang memungkinkan Anda menanam lebih banyak informasi dan komputasi dalam sistem tipe.

sclv
sumber
1
Kelas tipe normal adalah untuk antarmuka sebagai kelas tipe multi-parameter untuk multi-dispatch dalam OOP; Anda mendapatkan peningkatan yang sesuai dalam kekuatan bahasa pemrograman dan sakit kepala programmer.
CA McCann