delete
ist eine modale Suche. Es gibt zwei verschiedene Betriebsmodi - ob das Ergebnis bereits gefunden wurde oder nicht. Sie können foldr
verwenden, um eine Funktion zu erstellen, die den Status bei jedem Überprü- fen des Elements nach unten übergibt. Im Fall von delete
kann der Zustand ein einfacher Bool
sein. Es ist nicht gerade der beste Typ, aber es wird reichen. Nachdem Sie den Zustandstyp identifiziert haben, können Sie mit der Arbeit an der foldr
Konstruktion beginnen. Ich werde es durchgehen und herausfinden, wie ich es gemacht habe. Ich werde ScopedTypeVariables
aktivieren, nur damit ich die Art der Teilausdrücke besser kommentieren kann. Wenn Sie den Zustandstyp kennen, wissen Sie, dass Sie foldr
möchten, um eine Funktion zu generieren, die einen Wert dieses Typs annimmt und einen Wert des gewünschten endgültigen Typs zurückgibt. Das ist genug, um Dinge zu skizzieren.
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f undefined xs undefined
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g = undefined
Es ist ein Anfang. Die genaue Bedeutung von g
ist hier ein wenig knifflig. Es ist eigentlich die Funktion zur Bearbeitung des Rests der Liste. Es ist richtig, es als eine Fortsetzung zu betrachten, tatsächlich. Es stellt absolut dar, den Rest der Falte, mit Ihrem beliebigen Staat durchzuführen, den Sie wählen, um fortzufahren. Angesichts dessen ist es an der Zeit herauszufinden, was man in diese undefined
Orte stecken kann.
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f undefined xs undefined
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g found | x == a && not found = g True
| otherwise = x : g found
Das scheint relativ einfach. Wenn das aktuelle Element das Objekt ist, nach dem gesucht wird und das noch nicht gefunden wurde, geben Sie es nicht aus und setzen Sie den Status fort, der auf True
gesetzt wurde, um anzuzeigen, dass es gefunden wurde. otherwise
, geben Sie den aktuellen Wert aus und fahren Sie mit dem aktuellen Status fort.Dies lässt den Rest der Argumente nur foldr
. Der letzte ist der Ausgangszustand. Der andere ist die Statusfunktion für eine leere Liste. Ok, die sind auch nicht schlecht.
{-# LANGUAGE ScopedTypeVariables #-}
delete :: forall a. Eq a => a -> [a] -> [a]
delete a xs = foldr f (const []) xs False
where
f :: a -> (Bool -> [a]) -> (Bool -> [a])
f x g found | x == a && not found = g True
| otherwise = x : g found
Unabhängig vom Status eine leere Liste erstellen, wenn eine leere Liste gefunden wird. Und der Ausgangszustand ist, dass das gesuchte Element noch nicht gefunden wurde.
Diese Technik ist auch in anderen Fällen anwendbar. Zum Beispiel kann foldl
als foldr
auf diese Weise geschrieben werden. Wenn Sie foldl
als eine Funktion betrachten, die wiederholt einen anfänglichen Akkumulator transformiert, können Sie erraten, dass die Funktion erzeugt wird - wie der Anfangswert transformiert wird.
{-# LANGUAGE ScopedTypeVariables #-}
foldl :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = undefined
Die Basis Fälle sind nicht zu schwierig zu finden, wenn das Problem als die Manipulation der Anfangsspeicher definiert ist, z
dort genannt. Die leere Liste ist die Identitätstransformation id
, und der an die erstellte Funktion übergebene Wert ist z
.
Die Implementierung von g
ist komplizierter. Es kann nicht blind auf Typen durchgeführt werden, da es zwei verschiedene Implementierungen gibt, die alle erwarteten Werte und Typprüfungen verwenden. Dies ist ein Fall, in dem Typen nicht ausreichen und Sie die Bedeutung der verfügbaren Funktionen berücksichtigen müssen.
Beginnen wir mit einer Bestandsaufnahme der Werte, die scheinbar verwendet werden sollten, und ihrer Typen. Die Dinge, die so scheinen, als müssten sie im Körper von g
verwendet werden, sind f :: a -> b -> a
, x :: b
, cont :: (a -> a)
und acc :: a
. f
wird offensichtlich als zweites Argument x
nehmen, aber es gibt eine Frage der geeigneten Stelle zu verwenden cont
. Um herauszufinden, wo es hingehört, denken Sie daran, dass es die Transformationsfunktion darstellt, die durch die Verarbeitung des Rests der Liste zurückgegeben wird, und dass foldl
das aktuelle Element verarbeitet und das Ergebnis dieser Verarbeitung an den Rest der Liste übergibt.
{-# LANGUAGE ScopedTypeVariables #-}
foldl :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = cont $ f acc x
auch Dies deutet darauf hin, dass foldl'
mit nur einer winzigen Veränderung auf diese Weise geschrieben werden kann:
{-# LANGUAGE ScopedTypeVariables #-}
foldl' :: forall a b. (a -> b -> a) -> a -> [b] -> a
foldl' f z xs = foldr g id xs z
where
g :: b -> (a -> a) -> (a -> a)
g x cont acc = cont $! f acc x
Der Unterschied besteht darin, dass ($!)
verwendet wird Bewertung der f acc x
vorzuschlagen, bevor es zu cont
übergeben wird. (Ich sage „vorschlagen“, weil es einige Grenzfälle, wo ($!)
nicht Auswertung zwingen sogar soweit WHNF.)
Ich schätze Ihre Antwort und finde es nützlich. Ich würde aufrüsten, wenn ich genug Ansehen hätte, aber ich akzeptiere es noch nicht, weil deine Antwort nicht ganz das ist, wonach ich suche. Aber das ist meine Schuld, weil ich die Frage nicht präzisiert habe. Ich werde sehen, ob ich sie bearbeiten kann. Ich interessiere mich mehr für einen Ansatz, der mit foldr eine Funktion generiert. Du hast recht, dass deine erste Implementierung genau das ist, was ich ausprobiert habe. – user168064
@ user168064 Ok. Nachdem ich ungefähr 3 Stunden damit verbracht habe, das Thema zu lernen, kann ich auch deine beabsichtigte Frage beantworten. Ich denke, es verdient, eine zweite Antwort zu sein mehr als eine Bearbeitung zu diesem, so dass eine andere Antwort eingeht. – Carl