Ich habe Haskell gelernt, und mir ist aufgefallen, dass viele der integrierten Funktionen Parameter in einer Reihenfolge akzeptieren, die intuitiv zu dem ist, was ich erwarten würde. Zum Beispiel:
%Vor% Wenn ich zweimal 7 replizieren möchte, würde ich replicate 2 7
schreiben. Aber wenn man auf Englisch laut vorliest, fühlt sich der Funktionsaufruf so an, als würde er sagen: "Repliziere 2, 7 Mal". Wenn ich die Funktion selbst geschrieben hätte, hätte ich das erste und das zweite Argument so getauscht, dass replicate 7 2
"replicate 7, 2 mal" lesen würde.
Einige andere Beispiele erschienen, als ich 99 Haskell-Probleme durchging. Ich musste eine Funktion schreiben:
%Vor% Es nimmt eine Liste als erstes Argument und ein Int
als zweites Argument. Intuitiv hätte ich den Header als dropEvery :: Int -> [a] -> [a]
geschrieben, sodass dropEvery 3 [1..100]
wie folgt lesen würde: "lasst jedes dritte Element in der Liste [1..100]
fallen. Aber im Beispiel der Frage würde es so aussehen: dropEvery [1..100] 3
.
Ich habe das auch mit anderen Funktionen gesehen, die ich momentan nicht finden kann. Ist es üblich, Funktionen aus einem praktischen Grund so zu schreiben, oder ist das alles nur in meinem Kopf?
Einer der Gründe, warum Funktionen so geschrieben werden, liegt darin, dass ihre Curry-Formen sich als nützlich erweisen.
Betrachten Sie zum Beispiel die Funktionen map
und filter
:
Wenn ich die geraden Zahlen in einer Liste behalten und sie dann durch 2 teilen wollte, könnte ich schreiben:
%Vor%was auch so geschrieben werden kann:
%Vor%Stellen Sie sich dies als eine Pipeline vor, die von rechts nach links geht:
Der .
-Operator bei der Verbindung von Pipeline-Segmenten - ähnlich wie der Operator |
in der Unix-Shell funktioniert.
Das ist alles möglich, weil das Listenargument für map
und filter
die letzten Parameter für diese Funktionen sind.
Wenn Sie Ihre dropEvery
mit dieser Signatur schreiben:
Dann können wir es in eine dieser Pipelines aufnehmen, z. B .:
%Vor%In Haskell ist es üblich, Funktionsparameter so zu ordnen, dass Parameter, die eine Operation "konfigurieren", zuerst kommen und die "Hauptsache, an der gearbeitet wird" als letzte kommt. Dies ist oft kontraproduktiv, wenn Sie aus anderen Sprachen kommen, da dies dazu führt, dass Sie am Ende die "am wenigsten wichtigen" Informationen zuerst weitergeben. Es ist besonders unangenehm von OO kommend, wo das "Haupt" -Argument normalerweise das Objekt ist, auf dem die Methode aufgerufen wird und so früh im Aufruf auftritt, dass es vollständig außerhalb der Parameterliste ist!
Es gibt jedoch eine Methode für unseren Wahnsinn. Der Grund dafür ist, dass die teilweise Anwendung (durch Curry) so einfach und in Haskell so weit verbreitet ist. Angenommen, ich habe Funktionen wie foo :: Some -> Config -> Parameters -> DataStrucutre -> DataStructure
und bar :: Differnt -> Config -> DataStructure -> DataStructure
. Wenn Sie nicht an das Denken höherer Ordnung gewöhnt sind, sehen Sie diese als Dinge, die Sie aufrufen, um eine Datenstruktur zu transformieren. Aber Sie können auch als Factory für "DataStructure transformers" verwenden: Funktionen vom Typ DataStructure -> DataStructure
.
Es ist sehr wahrscheinlich, dass es andere Operationen gibt, die von konfiguriert sind, wie zB DataStructure -> DataStructure
functions; Zumindest gibt es fmap
zum Umwandeln von Transformers von DataStructures in Transformer von Funktoren von DataStructures (Listen, Maybes, IOs usw.).
Wir können das manchmal auch ein bisschen weiter bringen. Betrachte foo :: Some -> Config -> Parameters -> DataStructure -> DataStructure
erneut. Wenn ich erwarte, dass Aufrufer von foo
es oft mehrmals mit dem gleichen Some
und Config
aufrufen, aber mit Parameters
, dann werden sogar noch mehr partielle Anwendungen nützlich.
Natürlich, selbst wenn die Parameter in der "falschen" Reihenfolge für meine partielle Anwendung sind, kann ich es trotzdem tun, indem ich Kombinatoren wie flip
und / oder Wrapper-Funktionen / Lambdas erzeuge. Aber das führt zu viel "Rauschen" in meinem Code, was bedeutet, dass ein Leser in der Lage sein muss, herauszufinden, was "wichtig" ist und was gerade Schnittstellen anpasst.
Die grundlegende Theorie ist also, dass ein Funktionsschreiber versucht, die Verwendungsmuster der Funktion zu antizipieren und seine Argumente in der Reihenfolge von "am stabilsten" bis "am wenigsten stabil" aufzulisten. Dies ist natürlich nicht nur die Betrachtung, und oft gibt es widersprüchliche Muster und keine klare "beste" Reihenfolge.
Aber "die Reihenfolge, in der die Parameter in einem englischen Satz aufgeführt würden, der den Funktionsaufruf beschreibt" wäre nicht etwas, dem ich beim Entwerfen einer Funktion viel Gewicht beimessen würde (und auch nicht in anderen Sprachen). Haskell-Code liest einfach nicht wie Englisch (auch nicht in den meisten anderen Programmiersprachen), und es ist nicht wirklich hilfreich, wenn man versucht, es in einigen Fällen näher zu bringen.
Für Ihre spezifischen Beispiele:
Für replicate
scheint mir, dass der a
-Parameter das "main" -Argument ist, also würde ich es als letztes setzen, wie es die Standardbibliothek tut. Es ist nicht viel drin; Es scheint nicht viel sinnvoller zu sein, zuerst die Anzahl der Replikationen zu wählen und eine Funktion a -> [a]
zu verwenden, als das replizierte Element zuerst auszuwählen und eine Funktion Int -> [a]
zu haben.
dropEvery
scheint tatsächlich seine Argumente in einer wackeligen Reihenfolge zu nehmen, aber nicht, weil wir auf Englisch "jedes N-te Element in einer Liste fallen lassen". Funktionen, die eine Datenstruktur annehmen und eine "modifizierte Version derselben Struktur" zurückgeben, sollten fast immer die Datenstruktur als ihr letztes Argument verwenden, wobei die Parameter, die die "Modifikation" konfigurieren, zuerst kommen.
Um den anderen Antworten hinzuzufügen, gibt es auch oft einen Anreiz, das letzte Argument zu demjenigen zu machen, dessen Konstruktion wahrscheinlich am kompliziertesten ist und / oder eine Lambda-Abstraktion ist. So kann man schreiben
%Vor%anstatt die große Rechnung mit Klammern und ein paar kleinen Argumenten umgeben zu haben, die am Ende enden.
Tags und Links haskell