Apa artinya Weak Head Normal Form (WHNF)? Apa arti Head Normal form (HNF) dan Normal Form (NF)?
Negara Haskell Dunia Nyata :
Fungsi seq yang familier mengevaluasi ekspresi pada apa yang kita sebut head normal form (disingkat HNF). Ia berhenti begitu mencapai konstruktor terluar ("kepala"). Ini berbeda dari bentuk normal (NF), di mana ekspresi sepenuhnya dievaluasi.
Anda juga akan mendengar programmer Haskell merujuk pada bentuk kepala normal yang lemah (WHNF). Untuk data normal, bentuk normal kepala lemah sama dengan bentuk normal kepala. Perbedaannya hanya muncul untuk fungsi, dan terlalu sulit untuk diperhatikan di sini.
Saya telah membaca beberapa sumber dan definisi ( Haskell Wiki dan Haskell Mail List dan Kamus Gratis ) tetapi saya tidak mengerti. Bisakah seseorang memberi contoh atau memberikan definisi awam?
Saya kira ini akan mirip dengan:
WHNF = thunk : thunk
HNF = 0 : thunk
NF = 0 : 1 : 2 : 3 : []
Bagaimana seq
dan ($!)
terkait dengan WHNF dan HNF?
Memperbarui
Saya masih bingung. Saya tahu beberapa jawaban mengatakan untuk mengabaikan HNF. Dari membaca berbagai definisi tampaknya tidak ada perbedaan antara data reguler di WHNF dan HNF. Namun, sepertinya ada perbedaan ketika datang ke suatu fungsi. Jika tidak ada perbedaan, mengapa seq
perlu foldl'
?
Titik kebingungan lainnya adalah dari Haskell Wiki, yang menyatakan bahwa seq
mengurangi menjadi WHNF, dan tidak akan melakukan apa pun untuk contoh berikut. Kemudian mereka mengatakan bahwa mereka harus menggunakan seq
untuk memaksa evaluasi. Apakah itu tidak memaksanya ke HNF?
Kode umum newbie stack meluap:
myAverage = uncurry (/) . foldl' (\(acc, len) x -> (acc+x, len+1)) (0,0)
Orang-orang yang memahami bentuk kepala normal dan lemah kepala normal (whnf) dapat segera memahami apa yang salah di sini. (acc + x, len + 1) sudah ada di whnf, jadi seq, yang mengurangi nilai menjadi whnf, tidak melakukan apa-apa untuk ini. Kode ini akan membangun thunks seperti contoh foldl yang asli, mereka hanya akan berada di dalam tuple. Solusinya adalah dengan memaksa komponen tuple, misalnya
myAverage = uncurry (/) . foldl' (\(acc, len) x -> acc `seq` len `seq` (acc+x, len+1)) (0,0)
sumber
Jawaban:
Saya akan mencoba memberikan penjelasan secara sederhana. Seperti yang telah ditunjukkan orang lain, bentuk normal kepala tidak berlaku untuk Haskell, jadi saya tidak akan mempertimbangkannya di sini.
Bentuk normal
Ekspresi dalam bentuk normal sepenuhnya dievaluasi, dan tidak ada sub-ekspresi yang dapat dievaluasi lebih lanjut (yaitu tidak mengandung thunks yang tidak dievaluasi).
Semua ungkapan ini dalam bentuk normal:
Ungkapan-ungkapan ini tidak dalam bentuk normal:
Bentuk normal kepala lemah
Ekspresi dalam bentuk normal kepala lemah telah dievaluasi ke konstruktor data terluar atau abstraksi lambda ( kepala ). Sub-ekspresi mungkin atau mungkin belum dievaluasi . Oleh karena itu, setiap ekspresi bentuk normal juga dalam bentuk normal kepala lemah, meskipun sebaliknya tidak berlaku secara umum.
Untuk menentukan apakah suatu ekspresi dalam bentuk normal kepala lemah, kita hanya perlu melihat bagian terluar dari ekspresi itu. Jika itu adalah konstruktor data atau lambda, itu dalam bentuk normal kepala lemah. Jika itu adalah aplikasi fungsi, itu bukan.
Ekspresi ini dalam bentuk normal kepala lemah:
Seperti disebutkan, semua ekspresi bentuk normal yang tercantum di atas juga dalam bentuk normal kepala lemah.
Ungkapan-ungkapan ini tidak dalam bentuk normal kepala lemah:
Stack overflow
Mengevaluasi ekspresi ke bentuk normal kepala lemah mungkin mengharuskan ekspresi lain dievaluasi ke WHNF terlebih dahulu. Misalnya, untuk mengevaluasi
1 + (2 + 3)
ke WHNF, pertama-tama kita harus mengevaluasi2 + 3
. Jika mengevaluasi satu ekspresi mengarah ke terlalu banyak evaluasi bersarang ini, hasilnya adalah stack overflow.Ini terjadi ketika Anda membangun ekspresi besar yang tidak menghasilkan konstruktor data atau lambdas sampai sebagian besar telah dievaluasi. Ini sering disebabkan oleh jenis penggunaan
foldl
:Perhatikan bagaimana ia harus cukup dalam sebelum bisa membuat ekspresi menjadi bentuk normal kepala lemah.
Anda mungkin bertanya-tanya, mengapa Haskell tidak mengurangi ekspresi batin sebelumnya? Itu karena kemalasan Haskell. Karena tidak dapat diasumsikan secara umum bahwa setiap subekspresi akan diperlukan, ekspresi dievaluasi dari luar pada.
(GHC memiliki penganalisis ketelitian yang akan mendeteksi beberapa situasi di mana subekspresi selalu diperlukan dan kemudian dapat mengevaluasinya lebih awal. Namun ini hanya pengoptimalan, dan Anda tidak boleh bergantung padanya untuk menyelamatkan Anda dari luapan).
Ekspresi semacam ini, di sisi lain, benar-benar aman:
Untuk menghindari pembangunan ekspresi besar ini ketika kita tahu semua subekspresi harus dievaluasi, kami ingin memaksa bagian dalam dievaluasi lebih dulu.
seq
seq
adalah fungsi khusus yang digunakan untuk memaksa ekspresi dievaluasi. Semantiknya adalah bahwaseq x y
setiap kaliy
dievaluasi ke bentuk normal kepala lemah,x
juga dievaluasi untuk bentuk normal kepala lemah.Ini adalah tempat lain yang digunakan dalam definisi
foldl'
, varian ketatfoldl
.Setiap iterasi
foldl'
memaksa akumulator ke WHNF. Karena itu ia menghindari membangun ekspresi yang besar, dan karena itu menghindari tumpah tumpukan.Tetapi seperti contoh pada HaskellWiki menyebutkan, ini tidak menyelamatkan Anda dalam semua kasus, karena akumulator hanya dievaluasi ke WHNF. Dalam contoh tersebut, akumulator adalah tupel, sehingga hanya akan memaksa evaluasi konstruktor tupel, dan bukan
acc
ataulen
.Untuk menghindari hal ini, kita harus membuatnya sehingga mengevaluasi kekuatan konstruktor tuple evaluasi
acc
danlen
. Kami melakukan ini dengan menggunakanseq
.sumber
\x -> 1 + 1
juga WHNF tetapi bukan HNF.seq
argumen mereka?:set +s
. Anda kemudian dapat melihat bahwafoldl' f
akhirnya mengalokasikan lebih banyak hal daripadafoldl' f'
.Bagian tentang Bentuk Normal Thunks dan Weak Head dalam deskripsi haskell Wikibooks tentang kemalasan memberikan deskripsi WHNF yang sangat baik bersama dengan penggambaran yang bermanfaat ini:
sumber
Program Haskell adalah ekspresi dan dijalankan dengan melakukan evaluasi .
Untuk mengevaluasi ekspresi, ganti semua aplikasi fungsi dengan definisinya. Urutan Anda melakukan ini tidak terlalu penting, tetapi tetap penting: mulai dengan aplikasi terluar dan lanjutkan dari kiri ke kanan; ini disebut evaluasi malas .
Contoh:
Evaluasi berhenti ketika tidak ada lagi aplikasi fungsi yang tersisa untuk diganti. Hasilnya dalam bentuk normal (atau bentuk normal tereduksi , RNF). Tidak masalah di mana urutan Anda mengevaluasi ekspresi, Anda akan selalu berakhir dengan bentuk normal yang sama (tetapi hanya jika evaluasi berakhir).
Ada deskripsi yang sedikit berbeda untuk evaluasi malas. Yaitu, ia mengatakan bahwa Anda harus mengevaluasi semuanya untuk bentuk normal kepala lemah saja. Ada tiga kasus untuk ekspresi di WHNF:
constructor expression_1 expression_2 ...
(+) 2
atausqrt
\x -> expression
Dengan kata lain, kepala ekspresi (yaitu aplikasi fungsi terluar) tidak dapat dievaluasi lebih lanjut, tetapi argumen fungsi dapat berisi ekspresi yang tidak dievaluasi.
Contoh WHNF:
Catatan
sumber
seq
infoldl'
force evaluasi dari WHNF ke HNF?seq expr1 expr2
akan mengevaluasi ekspresi pertamaexpr1
menjadi WHNF sebelum mengevaluasi ekspresi keduaexpr2
.Penjelasan yang baik dengan contoh diberikan di http://foldoc.org/Weak+Head+Normal+Form form normal menyederhanakan bahkan bit ekspresi di dalam abstraksi fungsi, sedangkan form normal head "lemah" berhenti pada abstraksi fungsi .
Dari sumbernya, jika Anda memiliki:
yang dalam bentuk normal kepala lemah, tetapi tidak dalam bentuk normal kepala ... karena aplikasi yang mungkin terjebak di dalam fungsi yang belum dapat dievaluasi.
Bentuk normal kepala aktual akan sulit diimplementasikan secara efisien. Itu akan membutuhkan menusuk di dalam fungsi. Jadi keuntungan dari bentuk normal kepala lemah adalah Anda masih dapat mengimplementasikan fungsi sebagai tipe buram, dan karenanya lebih kompatibel dengan bahasa yang dikompilasi dan optimisasi.
sumber
WHNF tidak ingin tubuh lambdas dievaluasi, jadi
seq
ingin argumen pertamanya berada di WHNF, jadimengevaluasi ke
alih-alih, apa yang akan menggunakan HNF
sumber
Pada dasarnya, anggaplah Anda memiliki semacam pukulan
t
,.Sekarang, jika kita ingin mengevaluasi
t
ke WHNF atau NHF, yang sama kecuali untuk fungsi, kita akan menemukan bahwa kita mendapatkan sesuatu sepertit1 : t2
dimanat1
dant2
adalah thunks. Dalam hal ini,t1
akan menjadi Anda0
(atau lebih tepatnya, seorang thunk untuk0
tidak diberikan tambahan unboxing)seq
dan$!
evaluasi WHNF. Catat itusumber