Apa yang buruk tentang Template Haskell?

252

Tampaknya Template Haskell sering dipandang oleh komunitas Haskell sebagai kenyamanan yang tidak menguntungkan. Sulit untuk mengatakan dengan tepat apa yang telah saya amati dalam hal ini, tetapi pertimbangkan beberapa contoh ini

Saya telah melihat berbagai posting blog di mana orang melakukan hal-hal yang cukup rapi dengan Template Haskell, memungkinkan sintaksis yang lebih cantik yang tidak mungkin dilakukan dalam Haskell biasa, serta pengurangan boilerplate yang luar biasa. Jadi mengapa Template Haskell dipandang rendah dengan cara ini? Apa yang membuatnya tidak diinginkan? Dalam keadaan apa Templat Haskell harus dihindari, dan mengapa?

Dan Burton
sumber
56
Saya tidak setuju dengan suara untuk pindah; Saya mengajukan pertanyaan ini dengan semangat yang sama dengan yang saya tanyakan. Apa yang buruk tentang Malas I / O? dan saya berharap untuk melihat jawaban dengan cara yang sama. Saya terbuka untuk menulis ulang pertanyaan jika itu akan membantu.
Dan Burton
51
@ErikPhilips Mengapa Anda tidak membiarkan orang-orang yang sering membuat tag ini memutuskan apakah itu milik sini? Sepertinya interaksi Anda satu-satunya dengan komunitas Haskell adalah mengajukan pertanyaannya.
Gabriel Gonzalez
5
@GabrielGonzalez sudah jelas dengan pertanyaan saat ini dan menjawab bahwa itu tidak mengikuti FAQ di bawah jenis pertanyaan apa yang tidak boleh saya tanyakan di sini: tidak ada masalah aktual yang harus diselesaikan . Pertanyaannya tidak memiliki masalah khusus kode dipecahkan dan hanya bersifat konseptual. Dan berdasarkan pertanyaan saya harus menghindari template haskell, juga jatuh ke Stack Overflow bukan Mesin Rekomendasi .
Erik Philips
27
@ErikPhilips Aspek Mesin Rekomendasi tidak relevan dengan pertanyaan ini, karena Anda akan melihat bahwa itu mengacu pada keputusan antara alat yang berbeda (misalnya "beri tahu saya bahasa mana yang harus saya gunakan"). Sebaliknya, saya hanya meminta penjelasan tentang Template Haskell secara khusus, dan FAQ menyatakan "jika motivasi Anda adalah" Saya ingin orang lain menjelaskan [kosong] kepada saya ", maka Anda mungkin baik-baik saja." Bandingkan dengan, misalnya, GOTO masih dianggap berbahaya?
Dan Burton
29
Voting untuk dibuka kembali. Hanya karena ini adalah pertanyaan tingkat yang lebih tinggi, tidak berarti itu bukan pertanyaan yang baik.
György Andrasek

Jawaban:

171

Salah satu alasan untuk menghindari Template Haskell adalah karena ia secara keseluruhan tidak aman untuk semua, sehingga bertentangan dengan "semangat Haskell." Berikut ini beberapa contohnya:

  • Anda tidak memiliki kendali atas jenis Haskell AST yang mana dari kode TH yang akan dihasilkan, di luar tempat kode itu muncul; Anda dapat memiliki nilai tipe Exp, tetapi Anda tidak tahu apakah itu ekspresi yang mewakili [Char]atau (a -> (forall b . b -> c))atau apa pun. TH akan lebih dapat diandalkan jika seseorang dapat menyatakan bahwa suatu fungsi hanya dapat menghasilkan ekspresi dari jenis tertentu, atau hanya deklarasi fungsi, atau hanya pola pencocokan konstruktor data, dll.
  • Anda dapat menghasilkan ekspresi yang tidak dikompilasi. Anda menghasilkan ekspresi yang mereferensikan variabel bebas fooyang tidak ada? Beruntung, Anda hanya akan melihat bahwa ketika benar-benar menggunakan generator kode Anda, dan hanya dalam keadaan yang memicu pembuatan kode tertentu. Sangat sulit untuk menguji unit juga.

TH juga berbahaya:

  • Kode yang berjalan pada waktu kompilasi dapat dilakukan secara sewenang-wenang IO, termasuk meluncurkan rudal atau mencuri kartu kredit Anda. Anda tidak ingin harus melihat melalui setiap paket komplotan rahasia yang pernah Anda unduh untuk mencari eksploitasi TH.
  • TH dapat mengakses fungsi dan definisi "modul-pribadi", dalam beberapa kasus benar-benar melanggar enkapsulasi.

Lalu ada beberapa masalah yang membuat fungsi TH kurang menyenangkan untuk digunakan sebagai pengembang perpustakaan:

  • Kode TH tidak selalu dapat digubah. Katakanlah seseorang membuat generator untuk lensa, dan lebih sering daripada tidak, generator itu akan disusun sedemikian rupa sehingga hanya dapat dipanggil langsung oleh "pengguna akhir," dan bukan oleh kode TH lainnya, dengan misalnya mengambil daftar konstruktor tipe untuk menghasilkan lensa sebagai parameter. Sulit untuk membuat daftar itu dalam kode, sementara pengguna hanya perlu menulis generateLenses [''Foo, ''Bar].
  • Pengembang bahkan tidak tahu bahwa kode TH dapat dibuat. Tahukah Anda bahwa Anda dapat menulis forM_ [''Foo, ''Bar] generateLens? Qhanya sebuah monad, sehingga Anda dapat menggunakan semua fungsi yang biasa di atasnya. Beberapa orang tidak mengetahui hal ini, dan karena itu, mereka membuat beberapa versi kelebihan beban dari fungsi yang sama dengan fungsi yang sama, dan fungsi-fungsi ini menyebabkan efek mengasapi tertentu. Juga, kebanyakan orang menulis generator mereka di Qmonad bahkan ketika mereka tidak perlu, yang seperti menulis bla :: IO Int; bla = return 3; Anda memberi fungsi lebih "lingkungan" daripada yang dibutuhkan, dan klien fungsi tersebut diminta untuk menyediakan lingkungan itu sebagai akibatnya.

Akhirnya, ada beberapa hal yang membuat fungsi TH kurang menyenangkan untuk digunakan sebagai pengguna akhir:

  • Kegelapan. Ketika fungsi TH memiliki tipe Q Dec, ia dapat menghasilkan apa pun di tingkat atas modul, dan Anda sama sekali tidak memiliki kendali atas apa yang akan dihasilkan.
  • Monolithisme. Anda tidak dapat mengontrol berapa banyak fungsi TH dihasilkan kecuali pengembang mengizinkannya; jika Anda menemukan fungsi yang menghasilkan antarmuka basis data dan antarmuka serialisasi JSON, Anda tidak bisa mengatakan "Tidak, saya hanya ingin antarmuka basis data, terima kasih; Saya akan memutar antarmuka JSON sendiri"
  • Jalankan waktu. Kode TH membutuhkan waktu yang relatif lama untuk dijalankan. Kode ditafsirkan lagi setiap kali file dikompilasi, dan seringkali, satu ton paket diperlukan oleh kode TH yang sedang berjalan, yang harus dimuat. Ini memperlambat waktu kompilasi.
dflemstr
sumber
4
Tambahkan ke fakta ini bahwa templat-haskell secara historis sangat buruk didokumentasikan. (Meskipun saya baru saja melihat lagi dan tampaknya hal-hal setidaknya setidaknya sedikit lebih baik sekarang.) Juga, untuk memahami templat-haskell, Anda pada dasarnya harus memahami tata bahasa bahasa Haskell, yang memaksakan sejumlah kompleksitas (ini bukan Skema). Dua hal ini berkontribusi pada saya dengan sengaja tidak berusaha untuk memahami TH ketika saya masih pemula di Haskell.
mightybyte
14
Jangan lupa bahwa penggunaan Template Haskell dengan tiba-tiba berarti urutan deklarasi penting! TH hanya tidak terintegrasi sekencang yang mungkin diharapkan mengingat semir halus Haskell (baik itu 1,4, '98, 2010 atau bahkan Glasgow).
Thomas M. DuBuisson
13
Anda dapat alasan tentang Haskell tanpa terlalu banyak kesulitan, tidak ada jaminan seperti itu tentang Template Haskell.
Agustus
15
Dan apa yang terjadi dengan janji Oleg tentang alternatif yang aman untuk TH? Saya merujuk pada karyanya berdasarkan kertas "Akhirnya Tagless, Dievaluasi Sebagian" dan lebih banyak catatannya di sini . Itu tampak sangat menjanjikan ketika mereka mengumumkannya dan kemudian saya tidak pernah mendengar sepatah kata pun tentang itu.
Gabriel Gonzalez
53

Ini semata-mata pendapat saya sendiri.

  • Ini jelek untuk digunakan. $(fooBar ''Asdf)tidak terlihat bagus. Dangkal, tentu, tetapi berkontribusi.

  • Bahkan lebih buruk untuk menulis. Mengutip kadang-kadang berhasil, tetapi sering kali Anda harus melakukan pencangkokan dan pemipaan AST manual. The API besar dan berat, selalu ada banyak kasus Anda tidak peduli tentang tapi masih perlu pengiriman, dan kasus Anda peduli tentang cenderung untuk hadir dalam berbagai bentuk serupa tapi tak sama (data vs newtype, merekam -style vs konstruktor normal, dan sebagainya). Membosankan dan berulang-ulang untuk menulis dan cukup rumit untuk tidak mekanis. The reformasi usulan alamat beberapa ini (membuat tanda kutip lebih luas berlaku).

  • Pembatasan panggung adalah neraka. Tidak dapat memisahkan fungsi-fungsi yang didefinisikan dalam modul yang sama adalah bagian yang lebih kecil darinya: konsekuensi lainnya adalah jika Anda memiliki sambungan tingkat atas, semua yang ada di dalam modul akan berada di luar ruang lingkup apa pun sebelumnya. Bahasa lain dengan properti ini (C, C ++) membuatnya bisa diterapkan dengan memungkinkan Anda untuk meneruskan mendeklarasikan sesuatu, tetapi Haskell tidak. Jika Anda memerlukan referensi siklik antara deklarasi yang disambungkan atau dependensi dan tanggungannya, Anda biasanya hanya kacau.

  • Itu tidak disiplin. Yang saya maksud dengan ini adalah bahwa sebagian besar waktu ketika Anda mengekspresikan abstraksi, ada semacam prinsip atau konsep di balik abstraksi itu. Untuk banyak abstraksi, prinsip di belakangnya dapat dinyatakan dalam tipenya. Untuk kelas tipe, Anda sering dapat merumuskan undang-undang yang harus dipatuhi oleh mesin virtual dan klien dapat memikul Jika Anda menggunakan fitur generik baru GHC untuk mengabstraksi bentuk deklarasi instance atas tipe data apa pun (dalam batas), Anda bisa mengatakan "untuk tipe penjumlahan, kerjanya seperti ini, untuk tipe produk, berfungsi seperti itu". Templat Haskell, di sisi lain, hanyalah makro. Ini bukan abstraksi pada level ide, tetapi abstraksi pada level AST, yang lebih baik, tetapi hanya sedikit, daripada abstraksi pada level teks biasa. *

  • Itu mengikat Anda ke GHC. Secara teori kompiler lain dapat mengimplementasikannya, tetapi dalam praktiknya saya ragu ini akan pernah terjadi. (Ini berbeda dengan berbagai jenis ekstensi sistem yang, meskipun mungkin hanya diimplementasikan oleh GHC saat ini, saya dapat dengan mudah membayangkan diadopsi oleh kompiler lain di ujung jalan dan akhirnya distandarisasi.)

  • API tidak stabil. Ketika fitur bahasa baru ditambahkan ke GHC dan paket template-haskell diperbarui untuk mendukungnya, ini sering melibatkan perubahan yang tidak kompatibel ke belakang untuk tipe data TH. Jika Anda ingin kode TH Anda kompatibel dengan lebih dari satu versi GHC, Anda harus sangat berhati-hati dan mungkin menggunakannya CPP.

  • Ada prinsip umum bahwa Anda harus menggunakan alat yang tepat untuk pekerjaan dan yang terkecil yang akan mencukupi, dan dalam analogi itu Template Haskell adalah sesuatu seperti ini . Jika ada cara untuk melakukannya bukan Template Haskell, itu umumnya lebih disukai.

Keuntungan dari Template Haskell adalah Anda dapat melakukan hal-hal yang tidak dapat Anda lakukan dengan cara lain, dan ini adalah hal yang besar. Sebagian besar hal-hal yang digunakan untuk TH hanya dapat dilakukan jika mereka diterapkan secara langsung sebagai fitur kompiler. TH sangat bermanfaat untuk memiliki keduanya karena memungkinkan Anda melakukan hal-hal ini, dan karena memungkinkan Anda membuat prototipe ekstensi kompiler potensial dengan cara yang jauh lebih ringan dan dapat digunakan kembali (lihat berbagai paket lensa, misalnya).

Untuk meringkas mengapa saya pikir ada perasaan negatif terhadap Template Haskell: Ini memecahkan banyak masalah, tetapi untuk masalah apa pun yang diselesaikan, rasanya harus ada solusi yang lebih baik, lebih elegan, dan berdisiplin yang lebih cocok untuk menyelesaikan masalah itu, salah satu yang tidak menyelesaikan masalah dengan secara otomatis menghasilkan boilerplate, tetapi dengan menghilangkan kebutuhan untuk memiliki boilerplate.

* Meskipun saya sering merasa CPPmemiliki rasio power-to-weight yang lebih baik untuk masalah-masalah yang dapat diselesaikan.

EDIT 23-04-14: Apa yang sering saya coba sampaikan di atas, dan baru-baru ini, adalah bahwa ada perbedaan penting antara abstraksi dan deduplikasi. Abstraksi yang tepat sering menghasilkan deduplikasi sebagai efek samping, dan duplikasi sering kali merupakan pertanda abstraksi yang tidak memadai, tetapi bukan karena itu bernilai. Abstraksi yang tepat adalah apa yang membuat kode menjadi benar, dapat dipahami, dan dapat dipelihara. Deduplikasi hanya membuatnya lebih pendek. Template Haskell, seperti makro pada umumnya, adalah alat untuk deduplikasi.

glaebhoerl
sumber
"CPP memiliki rasio power-to-weight yang lebih baik untuk masalah-masalah yang dapat diselesaikan". Memang. Dan di C99 dapat memecahkan apa pun yang Anda inginkan. Pertimbangkan ini: COS , Chaos . Saya juga tidak mengerti mengapa orang berpikir generasi AST lebih baik. Itu hanya lebih ambiguitas dan kurang ortogonal dengan fitur bahasa lainnya
Britton Kerin
31

Saya ingin membahas beberapa poin yang diangkat dflemstr.

Saya tidak menemukan fakta bahwa Anda tidak dapat mengetik TH untuk mengkhawatirkan itu. Mengapa? Karena walaupun ada kesalahan, itu masih akan mengkompilasi waktu. Saya tidak yakin apakah ini memperkuat argumen saya, tetapi semangat ini serupa dengan kesalahan yang Anda terima saat menggunakan templat di C ++. Saya pikir kesalahan ini lebih dimengerti daripada kesalahan C ++, karena Anda akan mendapatkan versi cetak yang cukup dari kode yang dihasilkan.

Jika ungkapan TH / quasi-quoter melakukan sesuatu yang sangat canggih sehingga sudut-sudut rumit bisa bersembunyi, maka mungkin itu keliru?

Saya sedikit melanggar aturan ini dengan quasi-quoters yang saya kerjakan belakangan ini (menggunakan haskell-src-exts / meta) - https://github.com/mgsloan/quasi-extras/tree/master/examples . Saya tahu ini memperkenalkan beberapa bug seperti tidak dapat menyatukan dalam pemahaman daftar umum. Namun, saya berpikir bahwa ada peluang bagus bahwa beberapa ide di http://hackage.haskell.org/trac/ghc/blog/Template%20Haskell%20Proposal akan berakhir di kompiler. Sampai saat itu, perpustakaan untuk mengurai Haskell ke pohon TH adalah pendekatan yang hampir sempurna.

Mengenai kecepatan kompilasi / dependensi, kita dapat menggunakan paket "nol" untuk memasukkan kode yang dihasilkan. Ini setidaknya bagus untuk pengguna perpustakaan yang diberikan, tetapi kami tidak dapat melakukan jauh lebih baik untuk kasus mengedit perpustakaan. Bisakah dependensi TH mengasup binari yang dihasilkan? Saya pikir itu meninggalkan semua yang tidak direferensikan oleh kode yang dikompilasi.

Pembatasan pementasan / pemisahan langkah-langkah kompilasi dari modul Haskell tidak menyedot.

RE Opacity: Ini sama untuk setiap fungsi perpustakaan yang Anda panggil. Anda tidak memiliki kendali atas apa yang akan dilakukan Data.List.groupBy. Anda hanya memiliki "jaminan" / konvensi yang masuk akal bahwa nomor versi memberi tahu Anda sesuatu tentang kompatibilitas. Ini agak berbeda dari masalah kapan.

Di sinilah penggunaan zeroth terbayar - Anda sudah membuat versi file yang dihasilkan - sehingga Anda akan selalu tahu kapan bentuk kode yang dihasilkan telah berubah. Melihat diffs mungkin sedikit sulit, untuk sejumlah besar kode yang dihasilkan, jadi itu satu tempat di mana antarmuka pengembang yang lebih baik akan berguna.

RE Monolithism: Anda tentu saja dapat memposting-proses hasil dari ekspresi TH, menggunakan kode waktu kompilasi Anda sendiri. Tidak akan terlalu banyak kode untuk difilter pada tipe / nama deklarasi tingkat atas. Heck, Anda bisa membayangkan menulis fungsi yang melakukan ini secara umum. Untuk memodifikasi / menghapus kuasiquoter kuasi, Anda dapat mencocokkan pola pada "QuasiQuoter" dan mengekstrak transformasi yang digunakan, atau membuat yang baru dalam hal yang lama.

mgsloan
sumber
1
Mengenai Opacity / Monolithism: Anda tentu saja dapat berjalan melalui [Dec]dan menghapus hal-hal yang tidak Anda inginkan, tetapi katakanlah fungsi membaca file definisi eksternal ketika membuat antarmuka JSON. Hanya karena Anda tidak menggunakan itu Dectidak membuat generator berhenti mencari file definisi, membuat kompilasi gagal. Karena alasan itu, alangkah baiknya memiliki versi Qmonad yang lebih ketat yang memungkinkan Anda menghasilkan nama baru (dan hal-hal semacam itu) tetapi tidak mengizinkan IO, sehingga, seperti yang Anda katakan, orang dapat memfilter hasilnya / melakukan komposisi lain .
dflemstr
1
Saya setuju, harus ada versi Q / kutipan non-IO! Ini juga akan membantu menyelesaikan - stackoverflow.com/questions/7107308/… . Setelah Anda memastikan bahwa kode waktu kompilasi tidak melakukan apa pun yang tidak aman, sepertinya Anda bisa menjalankan pemeriksa keamanan pada kode yang dihasilkan, dan pastikan itu tidak merujuk pada barang pribadi.
mgsloan
1
Apakah Anda pernah menulis argumen balasan Anda? Masih menunggu tautan yang Anda janjikan. Bagi mereka yang mencari konten lama dari jawaban ini: stackoverflow.com/revisions/10859441/1 , dan bagi mereka yang dapat melihat konten yang dihapus: stackoverflow.com/revisions/10913718/6
Dan Burton
1
apakah ada versi yang lebih terkini dari nol dari yang ada di peretasan? Yang itu (dan repo darcs) terakhir diperbarui pada tahun 2009. Kedua salinan tidak membangun dengan ghc saat ini (7,6).
aavogt
1
@ aavogt tgeeky sedang berusaha memperbaikinya, tapi saya tidak berpikir dia selesai: github.com/technogeeky/zeroth Jika tidak ada yang melakukannya / membuat sesuatu untuk ini, saya akan segera menyelesaikannya.
mgsloan
16

Jawaban ini adalah jawaban atas masalah yang dikemukakan oleh illissius, poin demi poin:

  • Ini jelek untuk digunakan. $ (fooBar '' Asdf) tidak terlihat bagus. Dangkal, tentu, tetapi berkontribusi.

Saya setuju. Saya merasa $ () dipilih agar terlihat seperti bagian dari bahasa - menggunakan palet simbol Haskell yang sudah dikenal. Namun, itulah yang Anda / tidak / inginkan dalam simbol yang digunakan untuk penyambungan makro Anda. Mereka pasti berbaur terlalu banyak, dan aspek kosmetik ini sangat penting. Saya suka tampilan {{}} untuk splices, karena keduanya sangat berbeda secara visual.

  • Bahkan lebih buruk untuk menulis. Mengutip kadang-kadang berhasil, tetapi sering kali Anda harus melakukan pencangkokan dan pemipaan AST manual. [API] [1] besar dan berat, selalu ada banyak kasus yang tidak Anda pedulikan tetapi masih perlu dikirim, dan kasus yang Anda pedulikan cenderung hadir dalam berbagai bentuk yang serupa tetapi tidak identik (data) vs tipe baru, gaya rekaman vs konstruktor normal, dan sebagainya). Membosankan dan berulang-ulang untuk menulis dan cukup rumit untuk tidak mekanis. [Proposal reformasi] [2] membahas beberapa hal ini (membuat penawaran lebih luas berlaku).

Saya juga setuju dengan ini, namun, seperti yang diperhatikan oleh beberapa komentar di "Petunjuk Arah Baru untuk TH", kurangnya kutipan AST yang bagus di luar kotak bukanlah cacat kritis. Dalam paket WIP ini, saya berupaya mengatasi masalah ini dalam bentuk pustaka: https://github.com/mgsloan/quasi-extras . Sejauh ini saya mengizinkan splicing di beberapa tempat lebih banyak dari biasanya dan dapat mencocokkan pola pada AST.

  • Pembatasan panggung adalah neraka. Tidak dapat memisahkan fungsi-fungsi yang didefinisikan dalam modul yang sama adalah bagian yang lebih kecil darinya: konsekuensi lainnya adalah jika Anda memiliki sambungan tingkat atas, semua yang ada di dalam modul akan berada di luar ruang lingkup apa pun sebelumnya. Bahasa lain dengan properti ini (C, C ++) membuatnya bisa diterapkan dengan memungkinkan Anda untuk meneruskan mendeklarasikan sesuatu, tetapi Haskell tidak. Jika Anda memerlukan referensi siklik antara deklarasi yang disambungkan atau dependensi dan tanggungannya, Anda biasanya hanya kacau.

Saya pernah mengalami masalah definisi TH siklik menjadi tidak mungkin sebelumnya ... Ini sangat mengganggu. Ada solusi, tapi jelek - bungkus hal-hal yang terlibat dalam ketergantungan siklik dalam ekspresi TH yang menggabungkan semua deklarasi yang dihasilkan. Salah satu dari deklarasi generator ini bisa saja berupa quasi-quoter yang menerima kode Haskell.

  • Itu tidak berprinsip. Yang saya maksud dengan ini adalah bahwa sebagian besar waktu ketika Anda mengekspresikan abstraksi, ada semacam prinsip atau konsep di balik abstraksi itu. Untuk banyak abstraksi, prinsip di belakangnya dapat dinyatakan dalam tipenya. Ketika Anda mendefinisikan kelas tipe, Anda sering dapat merumuskan undang-undang mana yang harus dipatuhi oleh instance dan klien dapat berasumsi. Jika Anda menggunakan GHC [fitur generik baru] [3] untuk mengabstraksi bentuk deklarasi instance atas tipe data apa pun (dalam batas), Anda bisa mengatakan "untuk tipe penjumlahan, kerjanya seperti ini, untuk tipe produk, berfungsi seperti itu ". Tapi Template Haskell hanyalah makro bodoh. Ini bukan abstraksi pada level ide, tetapi abstraksi pada level AST, yang lebih baik, tetapi hanya secara sederhana, daripada abstraksi pada level teks biasa.

Itu hanya tidak berprinsip jika Anda melakukan hal-hal yang tidak berprinsip dengannya. Satu-satunya perbedaan adalah bahwa dengan compiler menerapkan mekanisme untuk abstraksi, Anda lebih percaya bahwa abstraksi tidak bocor. Mungkin demokratisasi desain bahasa memang terdengar agak menakutkan! Pencipta perpustakaan TH perlu mendokumentasikan dengan baik dan jelas mendefinisikan arti dan hasil dari alat yang mereka berikan. Sebuah contoh yang baik dari berprinsip TH adalah paket turunan : http://hackage.haskell.org/package/derive - ia menggunakan DSL sehingga contoh dari banyak derivasi / menentukan / derivasi yang sebenarnya.

  • Itu mengikat Anda ke GHC. Secara teori kompiler lain dapat mengimplementasikannya, tetapi dalam praktiknya saya ragu ini akan pernah terjadi. (Ini berbeda dengan berbagai jenis ekstensi sistem yang, meskipun mungkin hanya diimplementasikan oleh GHC saat ini, saya dapat dengan mudah membayangkan diadopsi oleh kompiler lain di ujung jalan dan akhirnya distandarisasi.)

Itu poin yang cukup bagus - API TH cukup besar dan kikuk. Mengimplementasikan ulang sepertinya bisa jadi sulit. Namun, hanya ada beberapa cara untuk mengiris masalah mewakili ASK Haskell. Saya membayangkan bahwa menyalin ADT TH, dan menulis konverter ke representasi AST internal akan membuat Anda mendapatkan banyak jalan di sana. Ini akan setara dengan upaya (tidak signifikan) untuk menciptakan haskell-src-meta. Ini juga bisa diimplementasikan kembali dengan mencetak THT AST dan menggunakan parser internal kompiler.

Walaupun saya bisa saja salah, saya tidak melihat TH sebagai ekstensi kompiler yang rumit, dari perspektif implementasi. Ini sebenarnya salah satu manfaat dari "menjaganya tetap sederhana" dan tidak memiliki lapisan dasar menjadi beberapa sistem templating yang secara teoritis menarik dan dapat diverifikasi secara statis.

  • API tidak stabil. Ketika fitur bahasa baru ditambahkan ke GHC dan paket template-haskell diperbarui untuk mendukungnya, ini sering melibatkan perubahan yang tidak kompatibel ke belakang untuk tipe data TH. Jika Anda ingin kode TH Anda kompatibel dengan lebih dari satu versi GHC, Anda harus sangat berhati-hati dan mungkin menggunakannya CPP.

Ini juga poin yang bagus, tetapi agak dramatis. Meskipun ada penambahan API akhir-akhir ini, mereka belum menginduksi kerusakan secara luas. Juga, saya berpikir bahwa dengan kutipan AST superior yang saya sebutkan sebelumnya, API yang benar-benar perlu digunakan dapat dikurangi secara substansial. Jika tidak ada konstruksi / pencocokan membutuhkan fungsi yang berbeda, dan sebaliknya dinyatakan sebagai literal, maka sebagian besar API menghilang. Selain itu, kode yang Anda tulis akan port lebih mudah ke representasi AST untuk bahasa yang mirip dengan Haskell.


Singkatnya, saya berpikir bahwa TH adalah alat yang kuat, semi-diabaikan. Lebih sedikit kebencian dapat mengarah pada eko-sistem perpustakaan yang lebih hidup, mendorong penerapan prototipe fitur bahasa yang lebih banyak. Telah diamati bahwa TH adalah alat yang dikuasai, yang dapat membuat Anda / melakukan / hampir apa saja. Anarki! Ya, menurut saya kekuatan ini memungkinkan Anda untuk mengatasi sebagian besar keterbatasannya, dan membangun sistem yang mampu melakukan pendekatan meta-pemrograman yang cukup berprinsip. Sebaiknya gunakan hacks jelek untuk mensimulasikan implementasi yang "tepat", karena dengan cara ini desain implementasi yang "tepat" akan secara bertahap menjadi jelas.

Dalam versi nirwana ideal pribadi saya, banyak bahasa yang benar-benar akan keluar dari kompiler, ke perpustakaan-perpustakaan dari varietas ini. Fakta bahwa fitur diimplementasikan sebagai perpustakaan tidak banyak mempengaruhi kemampuan mereka untuk setia abstrak.

Apa jawaban khas Haskell untuk kode boilerplate? Abstraksi. Apa abstraksi favorit kita? Fungsi dan typeclasses!

Typeclasses mari kita mendefinisikan satu set metode, yang kemudian dapat digunakan dalam segala macam fungsi generik pada kelas itu. Namun, selain ini, satu-satunya cara kelas membantu menghindari boilerplate adalah dengan menawarkan "definisi default". Sekarang ini adalah contoh fitur yang tidak berprinsip!

  • Set pengikat minimal tidak dapat dideklarasikan / dikompilasi. Ini bisa mengarah pada definisi yang tidak sengaja yang menghasilkan bottom karena rekursi timbal balik.

  • Terlepas dari kemudahan dan kekuatan yang akan dihasilkan, Anda tidak dapat menentukan standar superclass, karena instance yatim http://lukepalmer.wordpress.com/2009/01/25/a-world-without-orphans/ Ini akan memungkinkan kami memperbaiki hierarki numerik dengan anggun!

  • Mengejar kemampuan seperti-TH untuk standar metode mengarah ke http://www.haskell.org/haskellwiki/GHC.Generics . Meskipun ini adalah hal yang keren, kode debugging satu-satunya pengalaman saya yang menggunakan generik ini hampir tidak mungkin, karena ukuran tipe yang diinduksi untuk dan ADT serumit AST. https://github.com/mgsloan/th-extra/commit/d7784d95d396eb3abdb409a24360beb03731c88c

    Dengan kata lain, ini mengikuti fitur yang disediakan oleh TH, tetapi harus mengangkat seluruh domain bahasa, bahasa konstruksi, ke dalam representasi sistem tipe. Walaupun saya dapat melihatnya bekerja dengan baik untuk masalah umum Anda, untuk masalah yang kompleks, tampaknya cenderung menghasilkan tumpukan simbol yang jauh lebih mengerikan daripada peretasan TH.

    TH memberi Anda perhitungan waktu-kompilasi tingkat-nilai dari kode output, sedangkan generik memaksa Anda untuk mengangkat bagian pencocokan pola / rekursi dari kode ke dalam sistem tipe. Meskipun ini membatasi pengguna dalam beberapa cara yang cukup berguna, saya tidak berpikir kompleksitasnya sepadan.

Saya berpikir bahwa penolakan TH dan metaprogramming seperti lisp mengarah ke preferensi terhadap hal-hal seperti metode-default daripada lebih fleksibel, ekspansi makro seperti deklarasi instance. Disiplin menghindari hal-hal yang dapat menyebabkan hasil yang tidak terduga adalah bijaksana, namun, kita tidak boleh mengabaikan bahwa sistem tipe Haskell yang memungkinkan memungkinkan metaprogramming yang lebih andal daripada di banyak lingkungan lain (dengan memeriksa kode yang dihasilkan).

mgsloan
sumber
4
Jawaban ini tidak berdiri sendiri dengan sangat baik: Anda membuat banyak referensi ke jawaban lain yang harus saya cari dan temukan sebelum saya dapat membaca jawaban Anda dengan benar.
Ben Millwood
Itu benar. Saya mencoba menjelaskan apa yang sedang dibicarakan. Mungkin saya akan mengedit ke poin illisuis inline.
mgsloan
Saya harus mengatakan bahwa mungkin "tidak berprinsip" adalah kata yang lebih kuat daripada yang seharusnya saya gunakan, dengan beberapa konotasi yang tidak saya maksudkan - itu tentu saja tidak bermoral! Titik peluru itu adalah yang paling sulit saya hadapi, karena saya punya perasaan atau gagasan yang belum terbentuk di kepala saya dan kesulitan memasukkannya ke dalam kata-kata, dan "tidak berprinsip" adalah salah satu kata yang saya pegang yang ada di suatu tempat di sekitarnya. "Disiplin" mungkin lebih dekat. Karena tidak memiliki formulasi yang jelas dan sederhana saya memilih beberapa contoh saja. Jika seseorang dapat menjelaskan kepada saya dengan lebih jelas apa yang saya maksud, saya akan sangat menghargainya!
glaebhoerl
(Juga saya pikir Anda mungkin telah mengaktifkan pembatasan pementasan dan kutipan yang buruk untuk ditulis.)
glaebhoerl
1
Doh! Terima kasih telah menunjukkan itu! Tetap. Ya, tidak disiplin mungkin akan menjadi cara yang lebih baik untuk menggambarkannya. Saya pikir perbedaan di sini adalah benar-benar antara "built-in", dan karenanya mekanisme abstraksi "dipahami dengan baik", versus "ad-hoc", atau mekanisme abstraksi yang ditentukan pengguna. Saya kira apa yang saya maksud adalah bahwa Anda mungkin dapat mendefinisikan perpustakaan TH yang mengimplementasikan sesuatu yang sangat mirip dengan pengiriman typeclass (walaupun pada waktu kompilasi tidak runtime)
mgsloan
8

Satu masalah yang agak pragmatis dengan Template Haskell adalah bahwa ia hanya berfungsi ketika interteter bytecode GHC tersedia, yang tidak berlaku pada semua arsitektur. Jadi jika program Anda menggunakan Haskell Templat atau bergantung pada perpustakaan yang menggunakannya, itu tidak akan berjalan pada mesin dengan ARM, MIPS, S390 atau PowerPC CPU.

Ini relevan dalam praktiknya: git-annex adalah alat yang ditulis dalam Haskell yang masuk akal untuk dijalankan pada mesin yang mengkhawatirkan penyimpanan, mesin tersebut sering memiliki non-i386-CPU. Secara pribadi, saya menjalankan git-annex pada NSLU 2 (32 MB RAM, 266MHz CPU; apakah Anda tahu Haskell berfungsi dengan baik pada perangkat keras seperti itu?) Jika itu akan menggunakan Template Haskell, ini tidak mungkin.

(Situasi tentang GHC pada ARM meningkat banyak hari ini dan saya pikir 7.4.2 bahkan bekerja, tetapi intinya masih berlaku).

Joachim Breitner
sumber
1
“Template Haskell mengandalkan kompiler bytecode dan interpreter bawaan GHC untuk menjalankan ekspresi sambungan.” - haskell.org/ghc/docs/7.6.2/html/users_guide/…
Joachim Breitner
2
ah, saya itu - tidak, TH tidak akan bekerja tanpa penerjemah bytecode, tapi itu berbeda dari (meskipun relevan dengan) ghci. Saya tidak akan terkejut jika ada hubungan yang sempurna antara ketersediaan ghci dan ketersediaan penerjemah bytecode, mengingat bahwa ghci memang tergantung pada penerjemah bytecode, tetapi masalahnya ada kurangnya penerjemah bytecode, bukan kurangnya ghci secara khusus.
muhmuhten
1
(kebetulan, ghci memiliki dukungan yang sangat buruk untuk penggunaan interaktif TH. coba ghci -XTemplateHaskell <<< '$(do Language.Haskell.TH.runIO $ (System.Random.randomIO :: IO Int) >>= print; [| 1 |] )')
muhmuhten
Ok, dengan ghci saya mengacu pada kemampuan GHC untuk menginterpretasikan (alih-alih mengkompilasi) kode, terlepas dari apakah kode tersebut datang secara interaktif dalam binary ghci, atau dari sambatan TH.
Joachim Breitner
6

Mengapa TH buruk? Bagi saya, begini:

Jika Anda perlu menghasilkan begitu banyak kode berulang sehingga Anda mendapati diri Anda mencoba menggunakan TH untuk menghasilkan secara otomatis, Anda salah melakukannya!

Pikirkan tentang itu. Setengah daya tarik dari Haskell adalah bahwa desain tingkat tinggi memungkinkan Anda untuk menghindari sejumlah besar kode boilerplate yang tidak berguna yang harus Anda tulis dalam bahasa lain. Jika Anda memerlukan pembuatan kode waktu kompilasi, pada dasarnya Anda mengatakan bahwa bahasa atau desain aplikasi Anda gagal. Dan kami programmer tidak suka gagal.

Terkadang, tentu saja, itu perlu. Tetapi kadang-kadang Anda dapat menghindari kebutuhan TH dengan menjadi sedikit lebih pintar dengan desain Anda.

(Yang lain adalah bahwa TH adalah level yang cukup rendah. Tidak ada desain tingkat tinggi yang agung; banyak detail implementasi internal GHC terekspos. Dan itu membuat API mudah berubah ...)

Matematika Matematika
sumber
Saya tidak berpikir itu berarti bahwa bahasa atau aplikasi Anda telah mengecewakan Anda, terutama jika kami mempertimbangkan QuasiQuotes. Selalu ada trade-off ketika datang ke sintaksis. Beberapa sintaks lebih baik menggambarkan domain tertentu, sehingga Anda ingin dapat beralih ke sintaks lain kadang-kadang. QuasiQuotes memungkinkan Anda untuk beralih di antara sintaksis secara elegan. Ini sangat kuat dan digunakan oleh Yesod dan aplikasi lain. Mampu menulis kode pembuatan HTML menggunakan sintaksis yang terasa seperti HTML adalah fitur yang luar biasa.
CoolCodeBro
@CoolCodeBro Ya, kuasi-mengutip cukup bagus, dan saya kira sedikit ortogonal ke TH. (Jelas itu diterapkan di atas TH.) Saya sedang berpikir lebih banyak tentang menggunakan TH untuk menghasilkan instance kelas, atau membangun fungsi beberapa arities, atau sesuatu seperti itu.
MathematicalOrchid