Bagaimana cara kerja pengecualian di Haskell?

87

Di GHCi:

Prelude> error (error "")
*** Exception: 
Prelude> (error . error) ""
*** Exception: *** Exception: 

Mengapa yang pertama bukan pengecualian bersarang?

Daniel Wagner
sumber
9
Ini adalah transformasi yang diizinkan GHC untuk dilakukan: "Saya adalah Compiler yang beroperasi sendiri, dan semua _ | _s serupa bagi saya". Apakah Anda menanyakan detail implementasi yang membuat kedua baris tersebut dikompilasi secara berbeda?
shachaf
3
errorkhusus dan bukan merupakan mekanisme pengecualian. Untuk pengecualian yang nyata dan dapat ditangkap, lihat Errormonad.
Cat Plus Plus
1
Perhatikan, misalnya, yang (\f g x -> f (g x)) error error ""berperilaku berbeda dari (.) error error "", meskipun fungsinya setara dengan (.). Mungkin ini ada hubungannya dengan flag pengoptimalan yang telah dikompilasi dengan Prelude.
shachaf
5
Juga iterate error "" !! ndan luar biasa fix error.
Vitus
9
Saya selalu berpura-pura error = errordan memprogram sesuai dengan itu.
Gabriel Gonzalez

Jawaban:

103

Jawabannya adalah bahwa ini adalah semantik pengecualian yang tidak tepat (agak mengejutkan)

Ketika kode murni dapat ditampilkan untuk mengevaluasi satu set nilai luar biasa (yaitu nilai erroratau undefined, dan secara eksplisit bukan jenis pengecualian yang dihasilkan di IO ), maka bahasa mengizinkan nilai apa pun dari set itu untuk dikembalikan. Nilai luar biasa di Haskell lebih mirip NaNkode floating point, daripada pengecualian berbasis aliran kontrol dalam bahasa imperatif.

Gotcha sesekali bahkan untuk Haskeller tingkat lanjut adalah kasus seperti:

 case x of
   1 -> error "One"
   _ -> error "Not one"

Karena kode mengevaluasi satu set pengecualian, GHC bebas memilih satu. Dengan pengoptimalan aktif, Anda mungkin mendapati ini selalu bernilai "Bukan satu".

Mengapa kita melakukan ini? Karena jika tidak, kami akan terlalu membatasi urutan evaluasi bahasa, misalnya kami harus memperbaiki hasil deterministik untuk:

 f (error "a") (error "b")

dengan misalnya, mengharuskan itu dievaluasi dari kiri ke kanan jika ada nilai kesalahan. Sangat tidak Haskelly!

Karena kami tidak ingin melumpuhkan pengoptimalan yang dapat dilakukan pada kode kami hanya untuk mendukung error, solusinya adalah menentukan bahwa hasilnya adalah pilihan non-deterministik dari kumpulan nilai luar biasa: pengecualian tidak tepat! Di satu sisi, semua pengecualian dikembalikan, dan satu dipilih.

Biasanya, Anda tidak peduli - pengecualian adalah pengecualian - kecuali Anda peduli dengan string di dalam pengecualian, dalam hal ini menggunakan erroruntuk men-debug sangat membingungkan.


Referensi: Semantik untuk pengecualian yang tidak tepat , Simon Peyton Jones, Alastair Reid, Tony Hoare, Simon Marlow, Fergus Henderson. Proc Desain dan Implementasi Bahasa Pemrograman (PLDI'99), Atlanta. ( PDF )

Don Stewart
sumber
2
Saya memahami GHC memilih salah satu pengecualian yang dapat ditemukan. Tetapi dalam contoh "kasus" Anda, pengecualian "Bukan satu" tidak dapat ditemukan untuk input 1, jadi saya masih akan mengklasifikasikannya sebagai bug.
Peaker
8
@Peaker dead code elim - pengoptimal tidak perlu melihat x untuk melihat bahwa kesalahan adalah hasilnya, semua cabang menghasilkan nilai "sama", sehingga dapat mengabaikan nilai input sepenuhnya. Bukan bug, dengan pengecualian yang tidak tepat!
Don Stewart
1
@ lpsmith: Saya pikir semua jenis pengecualian tidak tepat (ketika dilempar menggunakan throw) dan Anda dapat secara deterministik melempar pengecualian dengan throwIO.
FunctorSalad
4
@Peaker Saya pikir Anda benar. Saya tidak berpikir ghc harus mengoptimalkan ekspresi ini jika mereka ingin bermain sesuai aturan yang ditetapkan dalam makalah pengecualian yang tidak tepat.
Agustus
3
Apa kertas pengecualian tepat tidak tampaknya katakan adalah bahwa jika kasus scrutinee adalah nilai kesalahan, maka nilai kesalahan dari cabang-cabang dapat dikembalikan. Jadi case error "banana" of (x:xs) -> error "bonobo"bisa memberi Anda * Exception: bonobo.
Ben Millwood