Ich habe mir kürzlich selbst Haskell beigebracht, und eine meiner Übungen bestand darin, die Funktion filter
erneut zu implementieren. Von all den Übungen, die ich durchgeführt habe, scheint mir meine Antwort am hässlichsten und längsten zu sein. Wie könnte ich es verbessern? Gibt es Haskell-Tricks, die ich noch nicht kenne?
Danke
Der einfachste Weg, Ihre Implementierung zu verbessern, ist Wächter . Anstelle von pattern = value
können Sie schreiben schreiben pattern | boolean = value
; Dies trifft nur zu, wenn boolean
wahr ist. So können wir
( otherwise
ist nur ein Synonym für True
.) Jetzt haben wir filter p xs
an zwei Stellen, sodass wir sie in eine where
-Klausel verschieben können; Diese werden von allen geteilt, die ein gemeinsames Muster haben, auch wenn es einen anderen Schutz hat:
(Diese Implementierung ist die eine verwendet von GHCs Prelude .)
Nun sind keine davon rekursiv. Dies kann nachteilig sein, aber es macht die Funktion faul. Wenn wir eine tail-rekursive Version wollen, könnten wir
schreiben %Vor% Beachten Sie jedoch, dass dies auf unendlichen Listen fehlschlagen würde (obwohl alle anderen Implementierungen funktionieren), dank der reverse
, also machen wir es mit $!
strikt. (Ich denke, ich habe das richtig gemacht - ich hätte die falsche Variable erzwingen können. Ich denke, ich habe diese richtig verstanden.)
Diese Implementierungen sehen alle wie Ihre aus. Es gibt natürlich andere. Einer basiert auf foldr
:
Wir nutzen den Punkt-freien Stil hier; Da xs
das letzte Argument sowohl für filter4
als auch für foldr check []
ist, können wir es genauso vermeiden wie beim letzten Argument von check
.
Sie könnten auch die Listenmonade nutzen:
%Vor% Die Listenmonade repräsentiert Nichtdeterminismus. Sie wählen ein Element x
von xs
, stellen sicher, dass es p
erfüllt, und geben es dann zurück, wenn dies der Fall ist. Alle diese Ergebnisse werden dann gesammelt. Aber beachte, dass dies jetzt allgemeiner ist; das funktioniert für jede MonadPlus
(eine Monade, die auch ein Monoid ist, das heißt, eine assoziative binäre Operation mplus
- ++
für Listen - und ein Identitätselement mzero
- []
für Listen), wie []
oder Maybe
. Zum Beispiel filter5 even $ Just 1 == Nothing
und filter5 even $ Just 2 == Just 2
.
Wir können auch die foldr
-basierte Version anpassen, um eine andere generische Typ-Signatur zu erhalten:
Das Data.Foldable-Modul bietet die Klasse Foldable
type, die eine beliebige Struktur darstellt, die wie eine Liste fold
ed sein kann (statt das Ergebnis in eine generische Monoid
zu legen). Unsere filter
erfordert eine MonadPlus
Einschränkung für das Ergebnis damit wir return x
schreiben können. Die Funktion foldMap
benötigt eine Funktion, die alles in Elemente eines Monoid
konvertiert und dann alle zusammen verkettet. Der Unterschied zwischen dem f a
auf der linken Seite und dem m a
auf der rechten Seite bedeutet, dass Sie beispielsweise filter6
a Maybe
erhalten und eine Liste zurück erhalten könnten.
Ich bin mir sicher, dass es (viele!) andere Implementierungen von filter
gibt, aber das sind die 6, an die ich relativ schnell denken könnte. Nun, welche mag ich eigentlich am liebsten? Es ist ein Tossup zwischen dem direkten filter2
und dem foldr
-basierten filter4
. Und filter5
ist nett für seine generische Typ-Signatur. (Ich glaube nicht, dass ich jemals eine Typensignatur wie filter6
benötigt habe.) Die Tatsache, dass filter2
von GHC verwendet wird, ist ein Plus, aber GHC verwendet auch einige funky rewrite-Regeln, also ist es nicht offensichtlich mir, dass es ohne diese überlegen ist. Persönlich würde ich wahrscheinlich mit filter4
(oder filter5
, wenn ich die genericity benötigte), aber filter2
ist definitiv in Ordnung.
Du könntest es zumindest etwas DRYEN, indem du den üblichen myfilter f xs
code herausholst:
In Haskell können (und sollten) Sie Wachen anstelle von if- then-else:
%Vor%Dies ist im Grunde die gleiche Definition wie verwendet in der Standardbibliothek .