Dapatkah seseorang memberi tahu saya mengapa Haskell Prelude mendefinisikan dua fungsi terpisah untuk eksponen (yaitu ^
dan **
)? Saya pikir sistem tipe seharusnya menghilangkan duplikasi semacam ini.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Dapatkah seseorang memberi tahu saya mengapa Haskell Prelude mendefinisikan dua fungsi terpisah untuk eksponen (yaitu ^
dan **
)? Saya pikir sistem tipe seharusnya menghilangkan duplikasi semacam ini.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Jawaban:
Sebenarnya ada tiga operator eksponensial: (^)
, (^^)
dan (**)
. ^
adalah eksponen integral non-negatif, ^^
adalah eksponensial bilangan bulat, dan eksponen **
floating-point:
(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
Alasannya adalah keamanan tipe: hasil operasi numerik umumnya memiliki tipe yang sama dengan argumen masukan. Tapi Anda tidak bisa menaikkan kekuatan Int
floating-point dan mendapatkan hasil tipe Int
. Dan sistem tipe mencegah Anda melakukan ini: (1::Int) ** 0.5
menghasilkan kesalahan tipe. Hal yang sama berlaku untuk (1::Int) ^^ (-1)
.
Cara lain untuk meletakkan ini: Num
tipe ditutup di bawah ^
(mereka tidak diharuskan memiliki pembalikan perkalian), Fractional
tipe ditutup di bawah ^^
, Floating
tipe ditutup di bawah **
. Karena tidak ada Fractional
contoh Int
, Anda tidak dapat menaikkannya ke pangkat negatif.
Idealnya, argumen kedua dari ^
akan dibatasi secara statis menjadi non-negatif (saat ini, 1 ^ (-2)
memunculkan pengecualian waktu proses). Tetapi tidak ada jenis bilangan asli di Prelude
.
Sistem tipe Haskell tidak cukup kuat untuk mengekspresikan tiga operator eksponensial sebagai satu. Apa yang Anda inginkan sebenarnya adalah seperti ini:
class Exp a b where (^) :: a -> b -> a
instance (Num a, Integral b) => Exp a b where ... -- current ^
instance (Fractional a, Integral b) => Exp a b where ... -- current ^^
instance (Floating a, Floating b) => Exp a b where ... -- current **
Ini tidak benar-benar berfungsi meskipun Anda mengaktifkan ekstensi kelas jenis multi-parameter, karena pemilihan instance harus lebih pintar daripada yang saat ini diizinkan oleh Haskell.
Int
dan Integer
. Untuk dapat memiliki tiga deklarasi instance tersebut, resolusi instance harus menggunakan backtracking, dan tidak ada compiler Haskell yang mengimplementasikannya.
Itu tidak mendefinisikan dua operator - itu mendefinisikan tiga! Dari Laporan:
Ada tiga operasi eksponen dua argumen: (
^
) menaikkan bilangan apa pun menjadi pangkat integer nonnegatif, (^^
) menaikkan bilangan pecahan ke pangkat bilangan bulat apa pun, dan (**
) menggunakan dua argumen floating-point. Nilaix^0
ataux^^0
adalah 1 untuk setiapx
, termasuk nol;0**y
tidak ditentukan.
Artinya ada tiga algoritma yang berbeda, dua di antaranya memberikan hasil yang tepat ( ^
dan ^^
), sedangkan **
memberikan hasil perkiraan. Dengan memilih operator mana yang akan digunakan, Anda memilih algoritma mana yang akan dipanggil.
^
membutuhkan argumen kedua menjadi Integral
. Jika saya tidak salah, implementasi bisa lebih efisien jika Anda tahu Anda bekerja dengan eksponen integral. Juga, jika Anda menginginkan sesuatu seperti 2 ^ (1.234)
, meskipun basis Anda adalah integral, 2, hasil Anda jelas akan menjadi pecahan. Anda memiliki lebih banyak opsi sehingga Anda dapat memiliki kontrol yang lebih ketat atas jenis apa yang masuk dan keluar dari fungsi eksponensial Anda.
Sistem tipe Haskell tidak memiliki tujuan yang sama dengan sistem tipe lainnya, seperti C, Python, atau Lisp. Mengetik bebek (hampir) kebalikan dari pola pikir Haskell.
class Duck a where quack :: a -> Quack
menentukan apa yang kita harapkan dari seekor bebek, lalu setiap contoh menentukan sesuatu yang dapat berperilaku seperti bebek.
Duck
.