Jak utworzyć domyślną wartość dla argumentu funkcji w Clojure

127

Przychodzę z tym:

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

(ciąg-> liczba całkowita "10")
(ciąg-> liczba całkowita "FF" 16)

Ale to musi być lepszy sposób na zrobienie tego.

jcubic
źródło

Odpowiedzi:

170

Funkcja może mieć wiele podpisów, jeśli sygnatury różnią się pod względem liczby. Możesz użyć tego do podania wartości domyślnych.

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

Zauważ, że założenie falsei niloba są uważane za nie-wartości, (if (nil? base) 10 base)można skrócić do (if base base 10)lub dalej do (or base 10).

Brian Carper
źródło
3
Myślę, że byłoby lepiej, gdyby druga linia powiedziała (recur s 10), używając recurzamiast powtarzać nazwę funkcji string->integer. Ułatwiłoby to w przyszłości zmianę nazwy funkcji. Czy ktoś zna jakiś powód, aby nie używać recurw takich sytuacjach?
Rory O'Kane
10
Wygląda na to, że recurdziała tylko na tej samej mocy. jeśli próbowałeś powtórzyć powyżej, na przykład:java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:
djhaskin987
Wpadłem na ten sam problem. Czy po prostu miałoby sens wywołanie samej funkcji (tj. (string->integer s 10))?
Kurt Mueller
155

Możesz także zniszczyć restargumenty jako mapę od Clojure 1.2 [ ref ]. Pozwala to nazwać i podać wartości domyślne dla argumentów funkcji:

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

Teraz możesz zadzwonić

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

lub

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

Możesz to zobaczyć w akcji tutaj: https://github.com/Raynes/clavatar/blob/master/src/clavatar/core.clj (na przykład)

Matthew Gilliard
źródło
1
Tak łatwe do zrozumienia, jeśli pochodzisz z języka Python :)
Dan
1
Jest to o wiele łatwiejsze do zrozumienia niż przyjęta odpowiedź… czy to przyjęty sposób „Clojurian” ? Rozważ dodanie do tego dokumentu.
Droogans,
1
I dodaje problem z przewodnikiem redakcyjnym nieoficjalnych adres tej pomocy.
Droogans,
1
Ta odpowiedź skuteczniej oddaje „właściwy” sposób zrobienia tego niż zaakceptowana odpowiedź, chociaż obie będą działać dobrze. (oczywiście wielką mocą języków Lisp jest to, że zazwyczaj istnieje wiele różnych sposobów na zrobienie tej samej podstawowej rzeczy)
johnbakers
2
Wydawało mi się to trochę rozwlekłe i przez chwilę miałem problemy z zapamiętaniem go, więc utworzyłem nieco mniej rozwlekłe makro .
akbiggs
33

To rozwiązanie jest tym bliższe duchowi oryginalnego rozwiązania , ale jest minimalnie czystsze

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

Podobny wzór , które mogą być przydatne zastosowania orw połączeniu zlet

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

Chociaż w tym przypadku jest bardziej rozwlekły, może być przydatny, jeśli chcesz, aby wartości domyślne zależały od innych wartości wejściowych . Na przykład rozważmy następującą funkcję:

(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

To podejście można łatwo rozszerzyć tak, aby pracowało z nazwanymi argumentami (jak w rozwiązaniu M. Gilliara):

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

Lub używając jeszcze więcej fuzji:

(defn exemplar [a & {:keys [b c] :or {b 5}}]
  (let [c (or c (* 7 b))]
    (println a b c)))
metasoarous
źródło
Jeśli nie potrzebujesz swoich wartości domyślnych zależnych od innych wartości domyślnych (a może nawet jeśli to zrobisz), powyższe rozwiązanie Matthew pozwala również na wiele wartości domyślnych dla różnych zmiennych. Jest znacznie czystszy niż używanie zwykłegoor
johnbakers
Jestem noobem Clojure, więc może OpenLearner ma rację, ale jest to interesująca alternatywa dla powyższego rozwiązania Matthew. Cieszę się, że wiem o tym, czy ostatecznie zdecyduję się go użyć, czy nie.
GlenPeterson
orróżni się od, :orponieważ ornie zna różnicy nili false.
Xiangru Lian
@XiangruLian Czy mówisz, że kiedy używasz: lub, jeśli podasz false, będzie wiedział, że użyje false zamiast domyślnego? Podczas gdy z lub użyje wartości domyślnej, gdy zostanie przekazana wartość false, a sama nie jest fałszywa?
Didier A.,
8

Jest jeszcze jedno podejście, które warto rozważyć: funkcje częściowe . Są to prawdopodobnie bardziej „funkcjonalne” i bardziej elastyczne sposoby określania wartości domyślnych funkcji.

Zacznij od utworzenia (jeśli to konieczne) funkcji, której parametr (y) chcesz podać jako domyślne jako parametry wiodące:

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

Dzieje się tak, ponieważ wersja Clojure partialumożliwia podanie wartości „domyślnych” tylko w kolejności, w jakiej pojawiają się one w definicji funkcji. Po uporządkowaniu parametrów zgodnie z życzeniem można utworzyć „domyślną” wersję funkcji za pomocą partialfunkcji:

(partial string->integer 10)

Aby ta funkcja była wywoływana wiele razy, możesz umieścić ją w zmiennej za pomocą def:

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

Możesz także utworzyć „lokalną wartość domyślną” za pomocą let:

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

Podejście do funkcji częściowych ma jedną kluczową przewagę nad innymi: odbiorca funkcji nadal może zdecydować, jaka będzie wartość domyślna, a nie producent funkcji, bez konieczności modyfikowania definicji funkcji . Jest to zilustrowane na przykładzie, w hexktórym zdecydowałem, że domyślna funkcja decimalnie jest tym, czego chcę.

Kolejną zaletą tego podejścia jest możliwość przypisania funkcji domyślnej innej nazwy (dziesiętnej, szesnastkowej itp.), Która może być bardziej opisowa i / lub mieć inny zakres (zmienna, lokalna). W razie potrzeby funkcję częściową można również łączyć z niektórymi z powyższych podejść:

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

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

(Zauważ, że różni się to nieco od odpowiedzi Briana, ponieważ kolejność parametrów została odwrócona z powodów podanych na górze tej odpowiedzi)

optevo
źródło
1
Nie o to chodzi w pytaniu; to jest jednak interesujące.
Zaz