So erstellen Sie einen Standardwert für das Funktionsargument in Clojure

126

Ich komme mit:

(defn string-> integer [str & [base]]
  (Integer / parseInt str (if (nil? Base) 10 base)))

(string-> integer "10")
(string-> integer "FF" 16)

Aber es muss ein besserer Weg sein, dies zu tun.

jcubic
quelle

Antworten:

170

Eine Funktion kann mehrere Signaturen haben, wenn sich die Signaturen in ihrer Arität unterscheiden. Damit können Sie Standardwerte angeben.

(defn string->integer 
  ([s] (string->integer s 10))
  ([s base] (Integer/parseInt s base)))

Beachten Sie, dass unter der Annahme , falseund nilbeide nicht-Werte betrachtet, (if (nil? base) 10 base)könnte verkürzt werden (if base base 10), oder weiter zu (or base 10).

Brian Carper
quelle
3
Ich denke, es wäre besser, in der zweiten Zeile zu sagen (recur s 10), recuranstatt den Funktionsnamen zu wiederholen string->integer. Das würde es in Zukunft einfacher machen, die Funktion umzubenennen. Kennt jemand einen Grund, recurin diesen Situationen nicht zu verwenden ?
Rory O'Kane
10
Es sieht so aus, als würde recurnur mit derselben Arität gearbeitet. Wenn Sie oben versucht haben, zum Beispiel:java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:
djhaskin987
Bin auf dasselbe Problem gestoßen. Wäre es nur sinnvoll, den Funktionsaufruf selbst zu haben (dh (string->integer s 10))?
Kurt Mueller
154

Sie können restArgumente seit Clojure 1.2 [ ref ] auch als Map zerstören . Auf diese Weise können Sie Funktionsargumente benennen und Standardeinstellungen angeben:

(defn string->integer [s & {:keys [base] :or {base 10}}]
    (Integer/parseInt s base))

Jetzt können Sie anrufen

(string->integer "11")
=> 11

oder

(string->integer "11" :base 8)
=> 9

Sie können dies hier in Aktion sehen: https://github.com/Raynes/clavatar/blob/master/src/clavatar/core.clj (zum Beispiel)

Matthew Gilliard
quelle
1
So einfach zu verstehen, wenn Sie aus einem Python-Hintergrund kommen :)
Dan
1
Dies ist viel einfacher zu verstehen als die akzeptierte Antwort ... ist dies der akzeptierte "Clojurian" Weg? Bitte erwägen Sie, dieses Dokument zu ergänzen.
Droogans
1
Ich habe dem inoffiziellen Styleguide ein Problem hinzugefügt , um dieses Problem zu beheben.
Droogans
1
Diese Antwort erfasst den "richtigen" Weg effektiver als die akzeptierte Antwort, obwohl beide gut funktionieren. (Natürlich ist eine große Macht der Lisp-Sprachen, dass es normalerweise viele verschiedene Möglichkeiten gibt, dasselbe Grundlegende zu tun)
Johnbakers
2
Das sah für mich etwas wortreich aus und ich hatte eine Weile Probleme, mich daran zu erinnern, also habe ich ein etwas weniger ausführliches Makro erstellt .
Akbiggs
33

Diese Lösung ist dem Geist der ursprünglichen Lösung näher , aber geringfügig sauberer

(defn string->integer [str & [base]]
  (Integer/parseInt str (or base 10)))

Ein ähnliches Muster, das nützlich sein kann, orkombiniert mitlet

(defn string->integer [str & [base]]
  (let [base (or base 10)]
    (Integer/parseInt str base)))

In diesem Fall ist dies zwar ausführlicher, es kann jedoch hilfreich sein, wenn die Standardeinstellungen von anderen Eingabewerten abhängen sollen . Betrachten Sie beispielsweise die folgende Funktion:

(defn exemplar [a & [b c]]
  (let [b (or b 5)
        c (or c (* 7 b))]
    ;; or whatever yer actual code might be...
    (println a b c)))

(exemplar 3) => 3 5 35

Dieser Ansatz kann leicht erweitert werden, um auch mit benannten Argumenten zu arbeiten (wie in der Lösung von M. Gilliar):

(defn exemplar [a & {:keys [b c]}]
  (let [b (or b 5)
        c (or c (* 7 b))]
    (println a b c)))

Oder noch mehr Fusion:

(defn exemplar [a & {:keys [b c] :or {b 5}}]
  (let [c (or c (* 7 b))]
    (println a b c)))
metasoarous
quelle
Wenn Sie Ihre Standardeinstellungen nicht abhängig von anderen Standardeinstellungen benötigen (oder vielleicht sogar, wenn Sie dies tun), ermöglicht die Lösung von Matthew oben auch mehrere Standardwerte für verschiedene Variablen. Es ist viel sauberer als mit einem normalenor
johnbakers
Ich bin ein Clojure-Neuling, also hat OpenLearner vielleicht Recht, aber dies ist eine interessante Alternative zu Matthews obiger Lösung. Ich bin froh darüber zu wissen, ob ich mich letztendlich dafür entscheide oder nicht.
GlenPeterson
orist anders als :orda orkennt den Unterschied von nilund nicht false.
Xiangru Lian
@XiangruLian Wollen Sie damit sagen, dass bei der Verwendung von: oder, wenn Sie false übergeben, false anstelle der Standardeinstellung verwendet wird? Während mit oder würde es die Standardeinstellung verwenden, wenn sie falsch übergeben wird und nicht selbst falsch?
Didier A.
8

Es gibt noch einen anderen Ansatz, den Sie in Betracht ziehen sollten: Teilfunktionen . Dies ist wohl eine "funktionalere" und flexiblere Möglichkeit, Standardwerte für Funktionen anzugeben.

Erstellen Sie zunächst (falls erforderlich) eine Funktion mit den Parametern, die Sie als Standardparameter als Hauptparameter bereitstellen möchten:

(defn string->integer [base str]
  (Integer/parseInt str base))

Dies geschieht, weil Sie mit Clojures Version von partialdie "Standard" -Werte nur in der Reihenfolge angeben können, in der sie in der Funktionsdefinition angezeigt werden. Nachdem die Parameter wie gewünscht bestellt wurden, können Sie mit der folgenden partialFunktion eine "Standard" -Version der Funktion erstellen :

(partial string->integer 10)

Um diese Funktion mehrmals aufrufbar zu machen, können Sie sie in eine Variable einfügen, indem Sie Folgendes verwenden def:

(def decimal (partial string->integer 10))
(decimal "10")
;10

Sie können auch einen "lokalen Standard" erstellen, indem Sie let:

(let [hex (partial string->integer 16)]
  (* (hex "FF") (hex "AA")))
;43350

Der Teilfunktionsansatz hat einen entscheidenden Vorteil gegenüber den anderen: Der Benutzer der Funktion kann weiterhin entscheiden, wie hoch der Standardwert sein soll, und nicht der Hersteller der Funktion, ohne die Funktionsdefinition ändern zu müssen . Dies wird in dem Beispiel veranschaulicht, in dem hexich entschieden habe, dass die Standardfunktion decimalnicht das ist, was ich will.

Ein weiterer Vorteil dieses Ansatzes besteht darin, dass Sie der Standardfunktion einen anderen Namen (dezimal, hexadezimal usw.) zuweisen können, der möglicherweise aussagekräftiger ist und / oder einen anderen Bereich (var, local). Falls gewünscht, kann die Teilfunktion auch mit einigen der oben genannten Ansätze gemischt werden:

(defn string->integer 
  ([s] (string->integer s 10))
  ([base s] (Integer/parseInt s base)))

(def hex (partial string->integer 16))

(Beachten Sie, dass dies geringfügig von Brians Antwort abweicht, da die Reihenfolge der Parameter aus den oben in dieser Antwort angegebenen Gründen umgekehrt wurde.)

optevo
quelle
1
Dies ist nicht das, wonach die Frage fragt; es ist jedoch interessant.
Zaz