Haskells schwacher Kopf Normalform

9

Ich bin über einige irritierende Dinge gestolpert. Ich weiß, dass Haskell mit schwacher Kopfnormalform (WHNF) funktioniert und ich weiß, was das ist. Geben Sie den folgenden Code in ghci ein (ich verwende den Befehl: sprint, der den Ausdruck meines Wissens auf WHNF reduziert.):

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

gibt intlist = _das macht mir total Sinn.

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

gibt stringlist = [_,_] diese bereits verwirrt mich. Aber dann:

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

überraschend gibt charlist = ["hi","there"]

Soweit ich Haskell verstanden habe, sind Zeichenfolgen nichts anderes als Listen von Zeichen, was durch Überprüfen der Typen "hi" :: [Char]und bestätigt zu werden scheint ['h','i'] :: [Char].

Ich bin verwirrt, weil nach meinem Verständnis alle drei obigen Beispiele mehr oder weniger gleich sind (eine Liste von Listen) und daher auf den gleichen WHNF reduziert werden sollten, nämlich _. Was vermisse ich?

Vielen Dank

duepiert
quelle
Dies scheint verwandt zu sein
Bergi
@Bergi diese Fragen sind sicherlich verwandt, aber keiner scheint sich mit dem Warum zu befassen "bla"und ['b','l','a']würde anders herauskommen.
Links um den
@leftaroundabout Da "bla"könnte überladen sein, ist aber ['b','l','a']bekanntermaßen ein String/ [Char]?
Bergi
1
@Bergi Ich habe auch darüber nachgedacht, aber es ist nicht wirklich plausibel, weil ['b', 'l', 'a']es auch überladen sein könnte und ebenfalls "bla"nur überladen ist, wenn -XOverloadedStringses eingeschaltet ist.
Links um den
2
Scheint parserbezogen zu sein, möglicherweise spezifisch für GHCi? (Ich weiß nicht, wie Sie in GHC-kompiliertem Code auf WHNF testen.) Die Anführungszeichen selbst scheinen der Auslöser zu sein.
Chepper

Antworten:

5

Beachten Sie, dass :sprintsich nicht um einen Ausdruck zu WHNF reduzieren. Wenn dies der Fall wäre, würde Folgendes 4eher geben als _:

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

Nimmt vielmehr :sprintden Namen einer Bindung, durchläuft die interne Darstellung des Bindungswerts und zeigt die bereits "bewerteten Teile" (dh die Teile, die Konstruktoren sind) an, während sie _als Platzhalter für nicht bewertete Thunks (dh die angehaltene Lazy-Funktion) verwendet werden Anrufe). Wenn der Wert vollständig nicht bewertet wird, wird keine Bewertung durchgeführt, auch nicht für WHNF. (Und wenn der Wert vollständig ausgewertet wird, erhalten Sie das, nicht nur WHNF.)

Was Sie in Ihren Experimenten beobachten, ist eine Kombination aus polymorphen und monomorphen numerischen Typen, verschiedenen internen Darstellungen für Zeichenfolgenliterale im Vergleich zu expliziten Listen von Zeichen usw. Grundsätzlich beobachten Sie technische Unterschiede bei der Kompilierung verschiedener Literalausdrücke zu Bytecode. Die Interpretation dieser Implementierungsdetails als etwas, das mit WHNF zu tun hat, wird Sie also hoffnungslos verwirren. Im Allgemeinen sollten Sie :sprintnur als Debugging-Tool verwenden, nicht als Möglichkeit, sich mit WHNF und der Semantik der Haskell-Evaluierung vertraut zu machen.

Wenn Sie wirklich verstehen möchten, was :sprintpassiert, können Sie einige Flags in GHCi aktivieren, um zu sehen, wie Ausdrücke tatsächlich behandelt werden, und so schließlich zu Bytecode kompiliert werden:

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

Danach können wir den Grund sehen, den Sie intlistangeben _:

> 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>)
     [])

Sie können den returnIOund den äußeren :Aufruf ignorieren und sich auf den Teil konzentrieren, der mit beginnt((\ @ a $dNum -> ...

Hier $dNumist das Wörterbuch für die NumEinschränkung. Dies bedeutet, dass der generierte Code den tatsächlichen Typ aim Typ noch nicht aufgelöst hat Num a => [[a]], sodass der gesamte Ausdruck weiterhin als Funktionsaufruf dargestellt wird, der ein (Wörterbuch für) einen geeigneten NumTyp verwendet. Mit anderen Worten, es ist ein unbewerteter Thunk, und wir bekommen:

> :sprint intlist
_

Geben Sie andererseits den Typ als an Int, und der Code ist völlig anders:

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

und so ist die :sprintAusgabe:

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

Ebenso haben Literalzeichenfolgen und explizite Zeichenlisten völlig unterschiedliche Darstellungen:

> 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>)
     [])

und die Unterschiede in der :sprintAusgabe stellen Artefakte dar, von denen Teile des Ausdrucks, die GHCi als bewertet (explizite :Konstruktoren) betrachtet, als nicht bewertet (die unpackCString#Thunks) betrachten.

KA Buhr
quelle