Saya menyadari bahwa ini bisa berpotensi dianggap subjektif atau mungkin off-topik pertanyaan, jadi saya berharap bahwa daripada harus ditutup itu akan mendapatkan bermigrasi, mungkin untuk Programmer.
I'm mulai belajar Haskell, sebagian besar untuk membangunnya sendiri, dan aku suka banyak dari ide-ide dan prinsip-prinsip dukungan bahasa. Saya menjadi terpesona dengan bahasa-bahasa fungsional setelah mengambil teori bahasa kelas di mana kita bermain-main dengan Cadel, dan aku telah mendengar banyak hal baik tentang bagaimana produktif Haskell bisa, jadi saya pikir saya'd menyelidiki sendiri. Sejauh ini, saya suka bahasa, kecuali untuk satu hal yang saya dapat't hanya mendapatkan jauh dari: ibu-ibu sialan fungsi tanda tangan.
Saya profesional latar belakang sebagian besar melakukan OO, terutama di pulau Jawa. Kebanyakan dari tempat-tempat yang saya've bekerja untuk memiliki dipalu di banyak standar modern dogma, Lincah, Kode yang Bersih, TDD, dll. Setelah beberapa tahun bekerja dengan cara ini, Itu pasti telah menjadi zona nyaman saya, terutama gagasan bahwa "baik" kode harus menjadi diri mendokumentasikan. I've menjadi digunakan untuk bekerja di sebuah IDE, dimana panjang dan verbose nama metode dengan sangat deskriptif tanda tangan non-isu dengan intelligent auto penyelesaian dan berbagai macam alat-alat analisis untuk menavigasi paket dan simbol-simbol; jika saya dapat menekan Ctrl+Space di Eclipse, kemudian menyimpulkan apa metode ini melakukan dari melihat namanya dan lokal cakupan variabel-variabel yang terkait dengan argumen bukan menarik sampai JavaDocs, I'm bahagia dalam kotoran.
Ini, jelas, bukan bagian dari komunitas praktek-praktek terbaik dalam Haskell. I'telah membaca banyak pendapat yang berbeda tentang masalah ini, dan saya mengerti bahwa Haskell masyarakat menganggap kekompakan untuk menjadi "pro". I've pergi melalui Cara Membaca Haskell, dan saya memahami alasan di balik banyak keputusan, tapi itu doesn't berarti bahwa saya seperti mereka; satu huruf nama-nama variabel, dll. aren't menyenangkan bagi saya. Saya mengakui bahwa saya'll harus mendapatkan digunakan untuk itu jika aku ingin tetap hacking dengan bahasa.
Tapi aku bisa't mendapatkan alih fungsi tanda tangan. Ambil contoh ini, seperti yang ditarik dari Belajar Haskell[...]'s bagian pada fungsi sintaks:
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"
| weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
Saya menyadari bahwa ini adalah contoh yang bodoh yang hanya dibuat untuk tujuan menjelaskan penjaga dan kelas kendala, tetapi jika anda adalah untuk memeriksa hanya tanda tangan dari fungsi itu, anda akan memiliki tidak tahu yang mana dari argumen ini dimaksudkan untuk menjadi berat atau tinggi. Bahkan jika anda menggunakan Float
atau Double
dan bukan dari jenis apa pun, ia tetap tidak dapat segera dilihat.
Pada awalnya, saya pikir saya akan menjadi lucu dan pintar dan brilian dan mencoba untuk menipunya lagi menggunakan jenis variabel nama dengan beberapa kendala kelas:
bmiTell :: (RealFloat weight, RealFloat height) => weight -> height -> String
Ini meludahkan kesalahan (sebagai samping, jika ada yang bisa menjelaskan kesalahan saya, saya'd bersyukur):
Could not deduce (height ~ weight)
from the context (RealFloat weight, RealFloat height)
bound by the type signature for
bmiTell :: (RealFloat weight, RealFloat height) =>
weight -> height -> String
at example.hs:(25,1)-(27,27)
`height' is a rigid type variable bound by
the type signature for
bmiTell :: (RealFloat weight, RealFloat height) =>
weight -> height -> String
at example.hs:25:1
`weight' is a rigid type variable bound by
the type signature for
bmiTell :: (RealFloat weight, RealFloat height) =>
weight -> height -> String
at example.hs:25:1
In the first argument of `(^)', namely `height'
In the second argument of `(/)', namely `height ^ 2'
In the first argument of `(<=)', namely `weight / height ^ 2'
Tidak memahami sepenuhnya mengapa tidak't bekerja, saya mulai Googling, dan saya bahkan menemukan ini sedikit posting yang menunjukkan parameter bernama, khusus, spoofing bernama parameter melalui type
, tapi itu tampaknya sedikit banyak.
Apakah tidak ada cara yang dapat diterima untuk kerajinan fungsi informatif tanda tangan? Adalah "Haskell Cara" hanya untuk Haddock omong kosong dari segala sesuatu?
Jenis tanda tangan yang bukan Jawa-gaya signature. Java-gaya tanda tangan anda akan memberitahu anda yang parameter berat badan dan yang lebih tinggi hanya karena bercampur dengan nama parameter dengan parameter jenis. Haskell dapat't melakukan hal ini sebagai aturan umum, karena fungsi yang didefinisikan menggunakan pencocokan pola dan beberapa persamaan, seperti dalam:
map :: (a -> b) -> [a] -> [b]
map f (x:xs) = f x : map f xs
map _ [] = []
Berikut parameter pertama bernama f
dalam persamaan pertama dan _
(yang cukup banyak berarti "tidak disebutkan namanya") yang kedua. Parameter kedua doesn't memiliki nama dalam salah satu persamaan; di bagian pertama ini memiliki nama-nama (dan programmer mungkin akan berpikir itu sebagai "xs daftar"), sementara di kedua itu's benar-benar ekspresi literal.
Dan kemudian ada's point-definisi gratis seperti:
concat :: [[a]] -> [a]
concat = foldr (++) []
Jenis signature memberitahu kita dibutuhkan suatu parameter yang tipe [[a]]
, tapi tidak ada nama untuk parameter ini muncul saja dalam sistem.
Di luar individu persamaan untuk fungsi, nama-nama ini digunakan untuk merujuk pada argumen yang tidak relevan pula kecuali sebagai dokumentasi. Sejak ide "nama kanonik" untuk fungsi's parameter isn't didefinisikan dengan baik di Haskell, tempat untuk informasi "parameter pertama dari bmiTell
mewakili berat badan sementara yang kedua merupakan ketinggian" di dokumentasi, tidak dalam jenis tanda tangan.
Saya benar-benar setuju bahwa apa fungsi tidak harus jernih dari "semua" informasi yang tersedia tentang hal itu. Di Jawa, yang merupakan fungsi's nama, dan parameter jenis dan nama. Jika (seperti biasa) pengguna akan membutuhkan informasi lebih dari itu, anda menambahkannya dalam dokumentasi. Di Haskell informasi publik tentang fungsi adalah fungsi's nama dan jenis parameter. Jika pengguna akan membutuhkan informasi lebih dari itu, anda menambahkannya dalam dokumentasi. Catatan IDEs untuk Haskell seperti Leksah akan dengan mudah menunjukkan Haddock komentar.
Perhatikan bahwa pilihan hal yang harus dilakukan dalam bahasa yang kuat dan ekspresif jenis sistem seperti Haskell's lebih sering untuk mencoba membuat banyak kesalahan mungkin terdeteksi sebagai jenis kesalahan. Dengan demikian, fungsi seperti bmiTell
segera set off tanda-tanda peringatan untuk saya, untuk alasan berikut:
[a]
argumen ++
do)Satu hal yang sering dilakukan untuk meningkatkan safety tipe ini memang untuk membuat newtypes, seperti dalam link yang anda temukan. Saya don't benar-benar berpikir ini memiliki banyak yang harus dilakukan dengan parameter bernama lewat, lebih dari itu adalah tentang membuat datatype yang secara eksplisit merupakan tinggi, daripada setiap jumlah lain yang mungkin anda ingin mengukur dengan nomor. Jadi saya tidak't memiliki newtype nilai-nilai yang muncul hanya pada panggilan; aku akan menggunakan newtype nilai di mana pun saya punya data ketinggian dari juga, dan mengedarkannya sebagai data ketinggian daripada sebagai angka, sehingga saya mendapatkan jenis-keselamatan (dan dokumentasi) manfaat di mana-mana. Saya hanya akan membuka nilai ke nomor baku ketika saya harus lulus itu untuk sesuatu yang beroperasi pada angka-angka dan bukan pada ketinggian (seperti operasi aritmatika dalam bmiTell
).
Catatan bahwa ini tidak memiliki runtime overhead; newtypes diwakili identik dengan data "dalam" type wrapper, jadi bungkus/membuka operasi ada-ops yang mendasari representasi dan hanya dihapus selama kompilasi. Itu hanya menambahkan karakter tambahan dalam kode sumber, namun karakter-karakter tersebut persis dokumentasi anda're apapun, dengan manfaat tambahan yang diberlakukan oleh compiler; Jawa-gaya tanda tangan memberitahu anda yang parameter berat badan dan yang lebih tinggi, tetapi compiler masih won't dapat memberitahu jika anda secara tidak sengaja melewati mereka yang salah jalan di sekitar!
Ada pilihan lain, tergantung pada bagaimana konyol dan/atau bertele-tele anda ingin mendapatkan dengan anda jenis.
Misalnya, anda bisa melakukan ini...
type Meaning a b = a
bmiTell :: (RealFloat a) => a `Meaning` weight -> a `Meaning` height -> String
bmiTell weight height = -- etc.
...tapi yang's sangat konyol, berpotensi membingungkan, dan doesn't membantu dalam kebanyakan kasus. Hal yang sama berlaku untuk ini, yang juga memerlukan menggunakan ekstensi bahasa:
bmiTell :: (RealFloat weight, RealFloat height, weight ~ height)
=> weight -> height -> String
bmiTell weight height = -- etc.
Sedikit lebih masuk akal akan hal ini:
type Weight a = a
type Height a = a
bmiTell :: (RealFloat a) => Weight a -> Height a -> String
bmiTell weight height = -- etc.
...tapi yang's masih agak konyol dan cenderung untuk tersesat ketika GHC memperluas jenis sinonim.
Masalah sebenarnya di sini adalah bahwa anda're melampirkan tambahan konten semantik untuk nilai yang berbeda dari yang sama polymorphic, yang akan melawan arus dari bahasa itu sendiri dan, seperti itu, biasanya tidak idiomatik.
Salah satu pilihan, tentu saja, adalah untuk hanya berurusan dengan tidak informatif jenis variabel. Tapi yang's sangat tidak memuaskan jika ada's yang signifikan perbedaan antara dua hal dari jenis yang sama yang's tidak jelas dari urutan mereka're diberikan dalam.
Apa yang saya'd sarankan anda mencoba, sebaliknya, menggunakan type
pembungkus untuk menentukan semantik:
newtype Weight a = Weight { getWeight :: a }
newtype Height a = Height { getHeight :: a }
bmiTell :: (RealFloat a) => Weight a -> Height a -> String
bmiTell (Weight weight) (Height height)
Lakukan ini adalah tempat di dekat sebagai umum sebagai layak untuk menjadi, saya pikir. It's sedikit ekstra mengetik (ha ha) tapi tidak hanya membuat anda jenis tanda tangan yang lebih informatif bahkan dengan jenis sinonim diperluas, memungkinkan jenis checker menangkap jika anda salah menggunakan berat badan sebagai tinggi, atau seperti. Dengan GeneralizedNewtypeDeriving
ekstensi anda bahkan bisa mendapatkan otomatis kasus bahkan untuk jenis kelas yang dapat't biasanya dapat diperoleh.
Haddock dan/atau juga melihat fungsi persamaan (nama anda terikat untuk hal-hal) ini adalah cara yang saya katakan apa yang's terjadi. Anda dapat Haddock parameter individu, seperti,
bmiTell :: (RealFloat a) => a -- ^ your weight
-> a -- ^ your height
-> String -- ^ what I'd think about that
jadi itu's tidak hanya gumpalan teks yang menjelaskan semua hal.
Alasan lucu jenis variabel didn't bekerja adalah bahwa anda fungsi:
(RealFloat a) => a -> a -> String
Tapi anda berusaha mengubah:
(RealFloat weight, RealFloat height) => weight -> height -> String
ini setara dengan ini:
(RealFloat a, RealFloat b) => a -> b -> String
Jadi, dalam hal ini jenis tanda tangan yang anda telah mengatakan bahwa dua yang pertama memiliki argumen yang berbeda * jenis, tetapi GHC telah ditentukan bahwa (berdasarkan penggunaan anda) mereka harus sama ** jenis. Jadi itu mengeluh bahwa ia tidak dapat menentukan yang berat
dan tinggi
adalah tipe yang sama, meskipun mereka harus (yaitu, diusulkan jenis tanda tangan tidak cukup ketat dan akan memungkinkan tidak valid menggunakan function).
berat
harus jenis yang sama sebagai tinggi
karena anda're membagi mereka (tidak ada implisit gips). berat ~ tinggi
berarti mereka're jenis yang sama. ghc telah pergi sedikit menjelaskan bagaimana ia sampai pada kesimpulan bahwa berat ~ tinggi
diperlukan, maaf. Anda diperbolehkan untuk memberitahu apa itu/anda ingin menggunakan sintaks dari jenis keluarga ekstensi:
{-# LANGUAGE TypeFamilies #-}
bmiTell :: (RealFloat weight, RealFloat height,weight~height) => weight -> height -> String
bmiTell weight height
| weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"
| weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
Namun, ini isn't ideal baik. Anda harus diingat bahwa Haskell menggunakan very berbeda paradigma memang, dan anda harus berhati-hati untuk tidak mengubah asumsi bahwa apa yang penting dalam bahasa lain adalah penting di sini. Anda belajar saat anda're luar zona kenyamanan anda. It's seperti seseorang dari London berputar di Toronto dan mengeluh kota ini membingungkan karena semua jalan-jalan yang sama, sementara orang dari Toronto mungkin mengklaim London ini membingungkan karena tidak ada keteraturan di jalan-jalan. Apa yang anda're memanggil kebingungan disebut kejelasan oleh Haskellers.
Jika anda ingin kembali untuk lebih berorientasi obyek kejelasan tujuan, kemudian membuat bmiTell bekerja hanya pada orang-orang, sehingga
data Person = Person {name :: String, weight :: Float, height :: Float}
bmiOffence :: Person -> String
bmiOffence p
| weight p / height p ^ 2 <= 18.5 = "You're underweight, you emo, you!"
| weight p / height p ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| weight p / height p ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
Ini, saya percaya, adalah semacam cara anda akan membuat ini jelas dalam OOP. Aku benar-benar don't percaya anda're menggunakan jenis metode OOP argumen untuk memperoleh informasi ini, anda harus diam-diam menggunakan parameter nama untuk kejelasan daripada jenis, dan itu's hampir tidak adil untuk mengharapkan haskell untuk memberitahu anda nama parameter ketika anda mengesampingkan membaca nama parameter dalam pertanyaan anda.[lihat * di bawah ini] jenis sistem di Haskell adalah sangat fleksibel dan sangat kuat, silakan don't menyerah hanya karena itu's awalnya mengasingkan untuk anda.
Jika anda benar-benar ingin untuk memberitahu anda, kami dapat melakukannya untuk anda:
type Weight = Float -- a type synonym - Float and Weight are exactly the same type, but human-readably different
type Height = Float
bmiClear :: Weight -> Height -> String
....
Yang's pendekatan yang digunakan dengan String yang mewakili nama file, jadi kita mendefinisikan
type FilePath = String
writeFile :: FilePath -> String -> IO () -- take the path, the contents, and make an IO operation
yang memberikan kejelasan yang anda incar. Namun itu's merasa bahwa
type FilePath = String
kekurangan jenis keselamatan, dan bahwa
newtype FilePath = FilePath String
atau sesuatu yang bahkan lebih pintar akan menjadi ide yang jauh lebih baik. Melihat Ben's jawaban untuk poin yang sangat penting tentang jenis keamanan.
[*] OKE, yang dapat anda lakukan :t di sini dan mendapatkan jenis tanda tangan tanpa nama parameter, tapi di sini adalah untuk pengembangan interaktif dari kode sumber. Perpustakaan anda atau modul seharusnya't tetap tercatat dan hacky, anda harus menggunakan sangat ringan sintaks haddock dokumentasi sistem dan menginstal haddock lokal. Yang lebih sah versi keluhan anda akan bahwa ada isn't :v perintah yang mencetak kode sumber untuk fungsi bmiTell. Metrik yang menunjukkan bahwa anda Haskell kode untuk masalah yang sama akan lebih pendek dengan faktor (saya menemukan sekitar 10 dalam kasus saya dibandingkan dengan setara OO atau non-oo penting kode), sehingga menampilkan definisi dalam gchi sering masuk akal. Kita harus mengirimkan permintaan fitur.
Mungkin tidak relevan untuk fungsi dengan piffling dua argumen, however... Jika anda memiliki fungsi yang membutuhkan banyak argumen, dari jenis yang sama atau hanya tidak jelas memesan, mungkin layak mendefinisikan sebuah struktur data yang mewakili mereka. Misalnya,
data Body a = Body {weight, height :: a}
bmiTell :: (RealFloat a) => Body a -> String
Sekarang anda dapat menulis baik
bmiTell (Body {weight = 5, height = 2})
atau
bmiTell (Body {height = 2, weight = 5})
dan itu akan bernilai benar kedua-duanya, dan juga menjadi damed jelas untuk siapa pun mencoba untuk membaca kode anda.
It's mungkin lebih layak untuk fungsi-fungsi dengan jumlah yang lebih besar dari argumen, meskipun. Untuk hanya dua, aku akan pergi dengan orang lain dan hanya type
itu jadi jenis tanda tangan dokumen yang benar parameter order dan anda mendapatkan compile-time error jika anda mencampur mereka.