Mengapa (atau mengapa tidak) jenis eksistensial dianggap praktik buruk dalam pemrograman fungsional?

43

Apa sajakah teknik yang mungkin saya gunakan untuk secara konsisten refactor kode menghapus ketergantungan pada tipe eksistensial? Biasanya ini digunakan untuk mendiskualifikasi konstruksi yang tidak diinginkan dari jenis Anda serta untuk memungkinkan konsumsi dengan sedikit pengetahuan tentang jenis yang diberikan (atau begitulah pemahaman saya).

Adakah yang datang dengan cara konsisten sederhana untuk menghilangkan ketergantungan pada kode ini yang masih mempertahankan beberapa manfaat? Atau setidaknya ada cara tergelincir dalam abstraksi yang memungkinkan penghapusan tanpa memerlukan kode churn yang signifikan untuk mengatasi perubahan?

Anda dapat membaca lebih lanjut tentang tipe eksistensial di sini ("jika Anda berani ..").

Petr Pudlák
sumber
8
@RobertHarvey Saya menemukan posting blog yang membuat saya berpikir tentang pertanyaan ini pertama kali: Haskell Antipattern: Typistlass Eksistensial . Juga, makalah Joey Adams menyebutkan menggambarkan beberapa masalah dengan eksistensial di Bagian 3.1. Jika Anda memiliki argumen yang bertentangan, silakan bagikan.
Petr Pudlák
5
@ PetrPudlák: Perlu diingat bahwa antipattern tidak ada tipe eksistensial secara umum, tetapi penggunaan khusus mereka ketika sesuatu yang lebih sederhana dan lebih mudah (dan lebih baik didukung di Haskell) akan melakukan pekerjaan yang sama.
CA McCann
10
Jika Anda ingin tahu mengapa penulis posting blog tertentu menyatakan pendapat, maka orang yang mungkin harus Anda tanyakan adalah penulis.
Eric Lippert
6
@Ptolemy Itu masalah pendapat. Menggunakan Haskell selama bertahun-tahun, saya tidak bisa membayangkan menggunakan bahasa fungsional yang tidak memiliki sistem tipe yang kuat.
Petr Pudlák
4
@Ptolemy: Mantra Haskell adalah "jika berhasil dikompilasi", mantra Erlangs adalah "biarkan crash". Pengetikan dinamis baik sebagai lem, tetapi saya pribadi tidak akan membangun benda hanya dengan menggunakan lem.
Den

Jawaban:

8

Jenis eksistensial tidak benar-benar dianggap praktik buruk dalam pemrograman fungsional. Saya pikir apa yang membuat Anda tersandung adalah bahwa salah satu kegunaan yang paling sering dikutip untuk eksistensial adalah antipattern typeclass antrian , yang banyak orang percaya adalah praktik buruk.

Pola ini sering digunakan sebagai jawaban atas pertanyaan tentang bagaimana memiliki daftar elemen yang diketik secara heterogen yang semuanya menerapkan typeclass yang sama. Misalnya, Anda mungkin ingin memiliki daftar nilai yang memiliki Showinstance:

{-# LANGUAGE ExistentialTypes #-}

class Shape s where
   area :: s -> Double

newtype Circle = Circle { radius :: Double }
instance Shape Circle where
   area (Circle r) = pi * r^2

newtype Square = Square { side :: Double }
    area (Square s) = s^2

data AnyShape = forall x. Shape x => AnyShape x
instance Shape AnyShape where
    area (AnyShape x) = area x

example :: [AnyShape]
example = [AnyShape (Circle 1.0), AnyShape (Square 1.0)]

Masalah dengan kode seperti ini adalah ini:

  1. Satu-satunya operasi yang berguna yang dapat Anda lakukan pada AnyShapeadalah untuk mendapatkan wilayahnya.
  2. Anda masih perlu menggunakan AnyShapekonstruktor untuk memasukkan salah satu tipe bentuk ke dalam AnyShapetipe tersebut.

Jadi ternyata, potongan kode itu tidak benar-benar memberi Anda apa pun yang tidak lebih pendek:

class Shape s where
   area :: s -> Double

newtype Circle = Circle { radius :: Double }
instance Shape Circle where
   area (Circle r) = pi * r^2

newtype Square = Square { side :: Double }
    area (Square s) = s^2

example :: [Double]
example = [area (Circle 1.0), area (Square 1.0)]

Dalam kasus kelas multi-metode, efek yang sama dapat secara umum dicapai lebih sederhana dengan menggunakan pengkodean "catatan metode" —daripada menggunakan sejenis kelas Shape, Anda menentukan jenis rekaman yang bidangnya adalah "metode" dari Shapejenis tersebut. , dan Anda menulis fungsi untuk mengonversi lingkaran dan kotak Anda menjadi Shapes.


Tetapi itu tidak berarti bahwa tipe eksistensial adalah masalah! Sebagai contoh, di Rust mereka memiliki fitur yang disebut objek sifat yang sering digambarkan orang sebagai tipe eksistensial atas suatu sifat (versi kelas tipe Rust). Jika typeclasses eksistensial adalah antipattern di Haskell, apakah itu berarti Rust mengambil solusi yang buruk? Tidak! Motivasi di dunia Haskell adalah tentang sintaks dan kenyamanan, bukan tentang prinsip.

Cara yang lebih matematis menempatkan ini menunjukkan bahwa AnyShapejenis dari atas dan Doubleyang isomorfik -ada adalah "lossless konversi" di antara mereka (baik, menyimpan untuk floating point presisi):

forward :: AnyShape -> Double
forward = area

backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))

Jadi sebenarnya, Anda tidak mendapatkan atau kehilangan kekuatan dengan memilih satu vs yang lain. Yang berarti pilihan harus didasarkan pada faktor-faktor lain seperti kemudahan penggunaan atau kinerja.


Dan perlu diingat bahwa tipe eksistensial memiliki kegunaan lain di luar contoh daftar heterogen ini, jadi ada baiknya memilikinya. Misalnya, STtipe Haskell , yang memungkinkan kita untuk menulis fungsi yang secara eksternal murni tetapi menggunakan operasi mutasi memori secara internal, menggunakan teknik berdasarkan tipe eksistensial untuk menjamin keamanan pada waktu kompilasi.

Jadi jawaban umumnya adalah tidak ada jawaban umum. Penggunaan tipe eksistensial hanya dapat dinilai dalam konteks — dan jawabannya mungkin berbeda tergantung pada fitur dan sintaksis apa yang disediakan oleh berbagai bahasa.

sakundim
sumber
Itu bukan isomorfisme.
Ryan Reich
2

Saya tidak terlalu terbiasa dengan Haskell, jadi saya akan mencoba menjawab bagian umum dari pertanyaan sebagai pengembang C # fungsional non-akademik.

Setelah melakukan beberapa bacaan, ternyata:

  1. Wildcard Java mirip dengan tipe eksistensial:

    Perbedaan antara tipe eksistensial Scala dan wildcard Java sebagai contoh

  2. Wildcard tidak diimplementasikan dalam C # sepenuhnya: varians umum didukung, tetapi varians situs panggilan tidak:

    C # Generics: Wildcard

  3. Anda mungkin tidak memerlukan fitur ini setiap hari, tetapi ketika Anda melakukannya, Anda akan merasakannya (mis. Harus memperkenalkan jenis tambahan untuk membuat semuanya berfungsi):

    Wildcard dalam batasan generik C #

Berdasarkan informasi ini, tipe / wildcard berguna jika diterapkan dengan benar dan tidak ada yang salah dengan itu, tetapi mungkin dapat disalahgunakan seperti fitur bahasa lainnya.

Sarang
sumber