Eksponensial di Haskell

91

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
skytreebird
sumber

Jawaban:

130

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 Intfloating-point dan mendapatkan hasil tipe Int. Dan sistem tipe mencegah Anda melakukan ini: (1::Int) ** 0.5menghasilkan kesalahan tipe. Hal yang sama berlaku untuk (1::Int) ^^ (-1).

Cara lain untuk meletakkan ini: Numtipe ditutup di bawah ^(mereka tidak diharuskan memiliki pembalikan perkalian), Fractionaltipe ditutup di bawah ^^, Floatingtipe ditutup di bawah **. Karena tidak ada Fractionalcontoh 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.

Mikhail Glushenkov
sumber
31

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.

agustus
sumber
4
Apakah pernyataan tentang ini tidak dapat diterapkan masih benar? IIRC, haskell memiliki opsi untuk parameter kedua dari kelas tipe multi-parameter yang akan ditentukan secara ketat oleh parameter pertama. Apakah ada masalah lain di luar ini yang tidak bisa diselesaikan?
RussellStewart
2
@singular Itu masih benar. Argumen pertama tidak menentukan yang kedua, misalnya, Anda ingin eksponennya menjadi Intdan Integer. Untuk dapat memiliki tiga deklarasi instance tersebut, resolusi instance harus menggunakan backtracking, dan tidak ada compiler Haskell yang mengimplementasikannya.
Agustus
7
Apakah argumen "tipe sistem tidak cukup kuat" masih berlaku pada Maret 2015?
Erik Kaplun
3
Anda tentu tidak dapat menulisnya seperti yang saya sarankan, tetapi mungkin ada cara untuk menyandikannya.
Agustus
2
@ErikAllik mungkin melakukannya untuk Haskell standar, karena tidak ada Laporan Haskell baru yang keluar sejak 2010.
Martin Capodici
10

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. Nilai x^0atau x^^0adalah 1 untuk setiap x, termasuk nol; 0**ytidak 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.

Gabe
sumber
4

^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.

Dan Burton
sumber
4
Saya tidak sepenuhnya setuju bahwa pola pikir tipe Haskell adalah kebalikan dari mengetik bebek. Kelas tipe Haskell cukup banyak seperti mengetik bebek. class Duck a where quack :: a -> Quackmenentukan apa yang kita harapkan dari seekor bebek, lalu setiap contoh menentukan sesuatu yang dapat berperilaku seperti bebek.
Agustus
9
@augustss Saya melihat dari mana Anda berasal. Tapi moto informal di balik mengetik bebek adalah "jika terlihat seperti bebek, bertindak seperti bebek, dan dukun seperti bebek, maka itu bebek." Di Haskell itu bukan bebek kecuali dinyatakan sebagai turunan dari Duck.
Dan Burton
1
Itu benar, tapi itulah yang saya harapkan dari Haskell. Anda dapat membuat apa pun yang Anda inginkan, tetapi Anda harus eksplisit. Kami tidak ingin salah mengira sesuatu yang tidak kami minta menjadi bebek.
Agustus
Ada perbedaan yang lebih spesifik antara cara Haskell melakukan sesuatu dan bebek mengetik. Ya, Anda dapat memberikan semua jenis kelas Bebek tetapi itu bukan Bebek. Ia mampu berkuak, pasti tapi tetap, secara konkret, apapun jenisnya. Anda masih tidak bisa memiliki daftar Bebek. Fungsi yang menerima daftar Bebek dan mencampur dan mencocokkan berbagai jenis kelas Bebek tidak akan berfungsi. Dalam hal ini Haskell tidak mengizinkan Anda untuk hanya mengatakan "Jika dukun seperti bebek, maka itu bebek." Di Haskell, semua Ducks Anda haruslah Quackers dengan tipe yang sama. Ini sangat berbeda dengan mengetik bebek.
mmachenry
Anda dapat memiliki daftar bebek campuran, tetapi Anda memerlukan ekstensi Kuantifikasi Eksistensial.
Bolpat