Haskells Weak Head Form Normal

9

Saya telah menemukan beberapa hal yang menjengkelkan. Saya tahu bahwa haskell bekerja dengan bentuk normal kepala lemah (WHNF) dan saya tahu apa ini. Mengetik kode berikut ke dalam ghci (saya menggunakan perintah: sprint yang mengurangi ekspresi menjadi WHNF untuk pengetahuan saya.):

let intlist = [[1,2],[2,3]]
:sprint intlist

memberikan intlist = _ini sangat masuk akal bagi saya.

let stringlist = ["hi","there"]
:sprint stringlist 

memberi stringlist = [_,_] Ini sudah membingungkan saya. Tapi kemudian:

let charlist = [['h','i'], ['t','h','e','r','e']]
:sprint charlist

secara mengejutkan memberi charlist = ["hi","there"]

Sejauh yang saya mengerti Haskell, string tidak lain adalah daftar karakter, yang tampaknya dikonfirmasi dengan memeriksa jenis "hi" :: [Char]dan ['h','i'] :: [Char].

Saya bingung, karena dalam pemahaman saya ketiga contoh di atas kurang lebih sama (daftar daftar) dan karenanya harus dikurangi menjadi WHNF yang sama, yaitu _. Apa yang saya lewatkan?

Terima kasih

duepiert
sumber
Ini tampaknya terkait
Bergi
@Bergi pertanyaan-pertanyaan itu tentu terkait, tetapi tampaknya tidak ada yang membahas mengapa "bla"dan ['b','l','a']akan keluar secara berbeda.
leftaroundabout
@leftaroundabout Karena "bla"bisa kelebihan beban, tetapi ['b','l','a']dikenal sebagai String/ [Char]?
Bergi
1
@Bergi Saya memikirkannya juga, tapi itu tidak masuk akal karena ['b', 'l', 'a']bisa juga kelebihan beban , dan juga "bla"hanya kelebihan beban jika -XOverloadedStringsdihidupkan.
leftaroundabout
2
Tampaknya terkait dengan parser, mungkin khusus untuk GHCi? (Saya tidak tahu bagaimana Anda menguji WHNF dalam kode yang dikompilasi GHC.) Kutipan itu sendiri tampaknya menjadi pemicunya.
chepner

Jawaban:

5

Perhatikan bahwa :sprinttidak tidak mengurangi ekspresi untuk WHNF. Jika ya, maka yang berikut akan memberi 4daripada _:

Prelude> let four = 2 + 2 :: Int
Prelude> :sprint four
four = _

Sebaliknya, :sprintmengambil nama penjilidan, melintasi representasi internal dari nilai penjilidan, dan menunjukkan bagian yang sudah "dievaluasi" (yaitu, bagian yang merupakan konstruktor) sambil menggunakan _sebagai penampung untuk barang yang tidak dievaluasi (yaitu, fungsi malas yang ditangguhkan panggilan). Jika nilainya sama sekali tidak dievaluasi, evaluasi tidak akan dilakukan, bahkan untuk WHNF. (Dan jika nilainya sepenuhnya dievaluasi, Anda akan mendapatkannya, bukan hanya WHNF.)

Apa yang Anda amati dalam percobaan adalah kombinasi tipe numerik polimorfik versus monomorfik, representasi internal berbeda untuk string literal versus daftar karakter eksplisit, dll. Pada dasarnya, Anda mengamati perbedaan teknis dalam cara ekspresi literal yang berbeda dikompilasi ke kode byte. Jadi, menafsirkan detail implementasi ini sebagai sesuatu yang berkaitan dengan WHNF akan membingungkan Anda. Secara umum, Anda harus menggunakan:sprint sebagai alat debugging saja, bukan sebagai cara untuk belajar tentang WHNF dan semantik evaluasi Haskell.

Jika Anda benar-benar ingin memahami apa :sprintyang dilakukan, Anda dapat menyalakan beberapa flag di GHCi untuk melihat bagaimana ekspresi sebenarnya ditangani dan, jadi, akhirnya dikompilasi ke bytecode:

> :set -ddump-simpl -dsuppress-all -dsuppress-uniques

Setelah ini, kita dapat melihat alasan Anda intlistmemberi _:

> let intlist = [[1,2],[2,3]]
==================== Simplified expression ====================
returnIO
  (: ((\ @ a $dNum ->
         : (: (fromInteger $dNum 1) (: (fromInteger $dNum 2) []))
           (: (: (fromInteger $dNum 2) (: (fromInteger $dNum 3) [])) []))
      `cast` <Co:10>)
     [])

Anda dapat mengabaikan panggilan returnIOluar :dan, dan berkonsentrasi pada bagian yang dimulai dengan((\ @ a $dNum -> ...

Berikut $dNumadalah kamus untuk Numbatasannya. Ini berarti bahwa kode yang dihasilkan belum menyelesaikan tipe aktual adalam jenis tersebut Num a => [[a]], sehingga seluruh ekspresi masih direpresentasikan sebagai pemanggilan fungsi yang mengambil (kamus untuk) Numjenis yang sesuai . Dengan kata lain, ini adalah kesalahan yang tidak dievaluasi, dan kami mendapatkan:

> :sprint intlist
_

Di sisi lain, tentukan jenisnya sebagai Int, dan kodenya benar-benar berbeda:

> let intlist = [[1::Int,2],[2,3]]
==================== Simplified expression ====================
returnIO
  (: ((: (: (I# 1#) (: (I# 2#) []))
         (: (: (I# 2#) (: (I# 3#) [])) []))
      `cast` <Co:6>)
     [])

dan begitu pula :sprinthasilnya:

> :sprint intlist
intlist = [[1,2],[2,3]]

Demikian pula, string literal dan daftar karakter yang eksplisit memiliki representasi yang sangat berbeda:

> let stringlist = ["hi", "there"]
==================== Simplified expression ====================
returnIO
  (: ((: (unpackCString# "hi"#) (: (unpackCString# "there"#) []))
      `cast` <Co:6>)
     [])

> let charlist = [['h','i'], ['t','h','e','r','e']]
==================== Simplified expression ====================
returnIO
  (: ((: (: (C# 'h'#) (: (C# 'i'#) []))
         (: (: (C# 't'#)
               (: (C# 'h'#) (: (C# 'e'#) (: (C# 'r'#) (: (C# 'e'#) [])))))
            []))
      `cast` <Co:6>)
     [])

dan perbedaan dalam :sprintoutput mewakili artefak yang bagian dari ekspresi GHCi dianggap dievaluasi ( :konstruktor eksplisit ) versus tidak dievaluasi (thunks unpackCString#).

KA Buhr
sumber