Ich bin neu in Haskell und funktionaler Programmierung und ich habe ein Programm, das funktioniert, aber den Stack nach einigen Sekunden überläuft. Meine Frage ist, was soll ich von hier aus machen? Wie kann ich zumindest einen Hinweis darauf bekommen, wo es auftritt, den Stapel ausdrucken oder irgendetwas?
Das Programm ist sehr langsam, wenn es in ghci mit: trace ausgeführt wird, so dass der Stapelüberlauf nicht auftritt. Es tritt auch nicht mit runhaskell auf, die nur mehr und mehr Gedächtnis essen werden. Ich bekomme den Fehler nur beim Übersetzen mit ghc und Ausführen.
In Ihrem Fall ist es ein Striktheitsproblem, das den Stapelüberlauf verursacht. Eine wirklich einfache Möglichkeit, solche Probleme zu finden, ist die Verwendung der deepseq-Bibliothek . Dies fügt ein paar Funktionen hinzu, mit denen Sie einen Wert vollständig auswerten können (was besser ist als seq
, was nur eine Ebene nach unten geht). Die Schlüsselfunktion ist force :: NFData a => a -> a
. Dies nimmt einen Wert, bewertet es vollständig und gibt es zurück.
Es funktioniert jedoch nur für Typen, die die Klasse NFData
type implementieren. Glücklicherweise gibt es in der deepseq-ten Bibliothek ein Template-Haskell-Makro: deriveNFData
. Dies wird mit Ihren eigenen Datentypen verwendet, zB deriveNFData ''BfMachine
.
Um zu verwenden, setzen Sie force $
vor Ihre Funktionen, die Striktheitsprobleme haben können (oder liftM force $
für monadische Funktionen). ZB mit deinem Code, stelle ich es vor step
, da das die Schlüsselfunktion in der Datei war:
Dies löst das Problem tatsächlich - selbst nach ein paar Minuten Laufzeit ist es nicht abgestürzt und die Speicherauslastung beträgt nur 3,2 MB.
Sie können bei dieser Lösung bleiben oder versuchen zu finden, wo das wahre Striktheitsproblem ist (wie das alles streng macht). Dies tun Sie, indem Sie die Erzwingung aus der Funktion step
entfernen und sie auf die Hilfsfunktionen anwenden, die sie verwendet (zB setElem
, findPrevBacket
usw.). Es stellt sich heraus, dass setElem
der Täter ist, indem force
vor diese Funktion gestellt wird, löst auch das Striktheitsproblem. Ich nehme an, es ist, weil die if
in der Karte Lambda bedeutet, dass die meisten Werte nie in der Liste ausgewertet werden müssen, und möglicherweise riesige Thunks aufbauen, während das Programm fortfährt.
Die einfachste Strategie ist die Verwendung der Trace-Funktion. Betrachten Sie diese Funktion zB:
%Vor% Wenn Sie beispielsweise ./program 13
ausführen, erhalten Sie 42
. Wenn Sie ./program 29
ausführen, erhalten Sie jedoch einen Stapelüberlauf.
Um dies zu debuggen, plazieren Sie trace
-Anweisungen für jeden Fall (von Debug.Trace
):
trace
hat den Typ String -> a -> a
und gibt die angegebene Zeichenfolge aus und gibt dann den Wert des zweiten Arguments zurück. Es ist eine spezielle Funktion, da es IO in einer reinen Funktion ausführt. Es ist jedoch großartig für das Debuggen.
Wenn Sie in diesem Fall das Programm jetzt mit ./program 19
ausführen, erhalten Sie die Ausgabe:
Zeigt genau an, was aufgerufen wurde.
Wenn Sie es jetzt mit ./program 29
ausführen, erhalten Sie:
Diese hübsche Darstellung zeigt deutlich, wie die Schleife abläuft. Während in diesem Beispiel ziemlich offensichtlich war, wo das Problem lag, ist es für komplexere Funktionen nützlich (besonders wenn der Stapelüberlauf mehrere Funktionen umfasst - tun Sie dies nur mit allen Funktionen, die Sie vermuten, könnte das Problem sein).
Tags und Links haskell debugging stack-overflow