Haskell: Typeclass vs passing sebuah fungsi

16

Bagi saya tampaknya Anda selalu dapat melewati argumen fungsi daripada menggunakan typeclass. Misalnya daripada mendefinisikan typeclass kesetaraan:

class Eq a where 
  (==)                  :: a -> a -> Bool

Dan menggunakannya dalam fungsi lain untuk menunjukkan argumen tipe harus merupakan instance dari Eq:

elem                    :: (Eq a) => a -> [a] -> Bool

Tidak bisakah kita mendefinisikan elemfungsi kita tanpa menggunakan typeclass dan alih-alih memberikan argumen fungsi yang berfungsi?

mahdix
sumber
2
itu disebut lewat kamus. Anda dapat menganggap batasan typeclass sebagai argumen implisit.
Poscat
2
Anda bisa melakukan itu, tetapi jelas jauh lebih mudah untuk tidak harus melewati suatu fungsi dan hanya menggunakan fungsi "standar" tergantung pada jenisnya.
Robin Zigmond
2
Anda bisa mengatakannya seperti itu, ya. Tapi saya berpendapat bahwa setidaknya ada satu keuntungan penting lainnya: kemampuan untuk menulis fungsi polimorfik yang bekerja pada semua jenis yang mengimplementasikan "antarmuka" atau serangkaian fitur tertentu. Saya pikir batasan typeclass menyatakan bahwa sangat jelas dengan cara yang tidak melewati argumen fungsi tambahan. Terutama karena (sayangnya implisit) "hukum" yang harus dipenuhi oleh banyak jenis kacamata. Sebuah Monad mkendala mengatakan lebih bagi saya daripada lewat argumen fungsi tambahan jenis a -> m adan m a -> (a -> m b) -> m b.
Robin Zigmond
1
The TypeApplicationsekstensi memungkinkan Anda membuat argumen implisit eksplisit. (==) @Int 3 5membandingkan 3dan 5secara khusus sebagai Intnilai. Anda dapat menganggap @Intsebagai kunci dalam kamus fungsi kesetaraan tipe-spesifik, bukan Intfungsi perbandingan-spesifik itu sendiri.
chepner

Jawaban:

19

Iya. Ini disebut "gaya lewat kamus". Kadang-kadang ketika saya melakukan beberapa hal yang sangat rumit, saya perlu membuang typeclass dan mengubahnya menjadi kamus, karena kamus yang lewat lebih kuat 1 , namun seringkali cukup rumit, membuat kode sederhana secara konseptual terlihat cukup rumit. Saya menggunakan gaya passing kamus kadang-kadang dalam bahasa yang bukan Haskell untuk mensimulasikan kacamata ketik (tetapi telah belajar bahwa itu biasanya bukan ide yang bagus seperti kedengarannya).

Tentu saja, setiap kali ada perbedaan dalam kekuatan ekspresif, ada pertukaran. Meskipun Anda dapat menggunakan API yang diberikan dengan lebih banyak cara jika ditulis menggunakan DPS, API mendapatkan lebih banyak informasi jika Anda tidak bisa. Salah satu cara ini muncul dalam praktik adalah dalam Data.Set, yang bergantung pada kenyataan bahwa hanya ada satu Ordkamus per jenis. The Settoko elemen diurutkan sesuai Ord, dan jika Anda membangun satu set dengan satu kamus, dan kemudian dimasukkan unsur menggunakan satu yang berbeda, seperti akan mungkin dengan DPS, Anda bisa memecahkan Set's invarian dan menyebabkan kecelakaan. Masalah keunikan ini dapat dikurangi dengan menggunakan hantu eksistensialketik untuk menandai kamus, tetapi, sekali lagi, dengan biaya kompleksitas yang cukup mengganggu di API. Ini juga muncul dengan cara yang hampir sama di TypeableAPI.

Bit keunikan tidak sering muncul. Apa jenis kacamata yang hebat adalah menulis kode untuk Anda. Sebagai contoh,

catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g

yang mengambil dua "prosesor" yang mengambil input dan mungkin memberikan output, dan menggabungkannya, meratakannya Nothing, harus ditulis dalam DPS kira-kira seperti ini:

catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g

Kami pada dasarnya harus mengeja jenis yang kami gunakan lagi, meskipun kami sudah mengeja dalam tanda tangan jenis, dan bahkan itu berlebihan karena kompiler sudah tahu semua jenis. Karena hanya ada satu cara untuk membangun diberikan Semigrouppada suatu jenis, kompiler dapat melakukannya untuk Anda. Ini memiliki efek tipe "bunga majemuk" ketika Anda mulai mendefinisikan banyak instance parametrik dan menggunakan struktur tipe Anda untuk menghitung untuk Anda, seperti pada Data.Functor.*kombinator, dan ini digunakan untuk efek yang besar di deriving viamana Anda pada dasarnya dapat memperoleh semua Struktur aljabar "standar" dari tipe Anda ditulis untuk Anda.

Dan bahkan tidak membuat saya mulai menggunakan MPTC dan fundeps, yang memberi informasi kembali ke pemeriksaan ketik dan inferensi. Saya tidak pernah mencoba mengubah hal seperti itu ke DPS - saya menduga itu akan melibatkan banyak bukti kesetaraan - tetapi dalam hal apa pun saya yakin itu akan menjadi lebih banyak pekerjaan untuk otak saya daripada saya akan merasa nyaman dengan.

-

1 U nless Anda gunakan reflectiondalam hal ini mereka menjadi setara dalam kekuasaan - tetapi reflectionjuga dapat menjadi rumit untuk digunakan.

luqui
sumber
Saya sangat tertarik dengan fundeps yang diekspresikan melalui DPS. Apakah Anda tahu beberapa sumber yang direkomendasikan tentang hal ini? Pokoknya, penjelasannya sangat bisa dipahami.
bob
@ Bob, tidak begitu saja, tapi itu akan menjadi eksplorasi yang menarik. Mungkin bertanya pertanyaan baru tentang itu?
luqui
5

Iya. Itu (disebut kamus lewat) pada dasarnya adalah apa yang dilakukan kompiler untuk mengetikkan kacamata. Untuk fungsi itu, dilakukan secara harfiah, akan terlihat sedikit seperti ini:

elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool
elemBy _ _ [] = False
elemBy eq x (y:ys) = eq x y || elemBy eq x ys

Memanggil elemBy (==) x xssekarang setara dengan elem x xs. Dan dalam kasus khusus ini, Anda dapat melangkah lebih jauh: eqmemiliki argumen pertama yang sama setiap kali, sehingga Anda dapat menjadikannya sebagai tanggung jawab penelepon untuk menerapkannya, dan berakhir dengan ini:

elemBy2 :: (a -> Bool) -> [a] -> Bool
elemBy2 _ [] = False
elemBy2 eqx (y:ys) = eqx y || elemBy2 eqx ys

Memanggil elemBy2 (x ==) xssekarang setara dengan elem x xs.

...Oh tunggu. Itu baru saja any. (Dan pada kenyataannya, di perpustakaan standarelem = any . (==) ,.)

Joseph Sible-Reinstate Monica
sumber
Lewat kamus AFAIU adalah pendekatan Scala untuk mengkodekan kelas tipe. Argumen ekstra tersebut dapat dinyatakan sebagai implicitdan kompiler akan menyuntikkannya untuk Anda dari ruang lingkup.
michid