Como criar valor padrão para argumento de função no Clojure

127

Eu venho com isso:

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

(sequência-> inteiro "10")
(string-> inteiro "FF" 16)

Mas deve ser uma maneira melhor de fazer isso.

jcubic
fonte

Respostas:

170

Uma função pode ter várias assinaturas se as assinaturas diferirem em termos de área. Você pode usar isso para fornecer valores padrão.

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

Observe que supor falsee nilsão considerados não-valores (if (nil? base) 10 base)pode ser reduzido para (if base base 10)ou além de (or base 10).

Brian Carper
fonte
3
Eu acho que seria melhor para a segunda linha dizer (recur s 10), usando em recurvez de repetir o nome da função string->integer. Isso tornaria mais fácil renomear a função no futuro. Alguém sabe alguma razão para não usar recurnessas situações?
Rory O'Kane
10
Parece que recursó funciona na mesma aridade. se você tentou repetir acima, por exemplo:java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:
djhaskin987 20/08
Encontrei esse mesmo problema. Seria apenas bom ter a função chamada em si (ie (string->integer s 10))?
Kurt Mueller
155

Você também pode desestruturar restargumentos como um mapa desde o Clojure 1.2 [ ref ]. Isso permite nomear e fornecer padrões para argumentos de função:

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

Agora você pode ligar

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

ou

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

Você pode ver isso em ação aqui: https://github.com/Raynes/clavatar/blob/master/src/clavatar/core.clj (por exemplo)

Matthew Gilliard
fonte
1
Tão fácil de entender se vindo de um fundo Python :)
Dan
1
Isso é muito mais fácil de entender do que a resposta aceita ... essa é a maneira "clojuriana" aceita ? Por favor, considere adicionar a este documento.
Droogans
1
Eu adicionei um problema ao guia de estilo não oficial para ajudar a resolver isso.
Droogans
1
Essa resposta captura de maneira mais eficaz a maneira "adequada" de fazê-la do que a resposta aceita, embora ambas funcionem bem. (claro, um grande poder de linguagens Lisp é que geralmente há muitas maneiras diferentes de fazer a mesma coisa fundamental)
johnbakers
2
Isso me pareceu um pouco prolixo e eu tive problemas para lembrar por um tempo, então criei uma macro um pouco menos detalhada .
akbiggs
33

Esta solução está mais próxima do espírito da solução original , mas marginalmente mais limpa

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

Um padrão semelhante que pode ser útil usa orcombinado comlet

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

Embora neste caso seja mais detalhado, pode ser útil se você deseja ter padrões dependentes de outros valores de entrada . Por exemplo, considere a seguinte função:

(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

Essa abordagem pode ser facilmente estendida para trabalhar com argumentos nomeados (como na solução de M. Gilliar) também:

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

Ou usando ainda mais uma fusão:

(defn exemplar [a & {:keys [b c] :or {b 5}}]
  (let [c (or c (* 7 b))]
    (println a b c)))
metasoarous
fonte
Se você não precisa de seus padrões dependentes de outros padrões (ou mesmo se precisar), a solução de Matthew acima também permite vários valores padrão para variáveis ​​diferentes. É muito mais limpo do que usar um regularesor
johnbakers
Eu sou um noob do Clojure, então talvez o OpenLearner esteja certo, mas esta é uma alternativa interessante à solução de Matthew acima. Fico feliz em saber sobre isso, se eu finalmente decidir usá-lo ou não.
GlenPeterson
oré diferente de :oruma vez orque não sabe a diferença de nile false.
Xiangru Lian
@XiangruLian Você está dizendo que ao usar: ou, se passar falso, ele saberá usar falso em vez do padrão? Enquanto com ou usaria o padrão quando passado falso e não falso em si?
Didier A.
8

Há outra abordagem que você pode considerar: funções parciais . Essas são sem dúvida uma maneira mais "funcional" e mais flexível de especificar valores padrão para funções.

Comece criando (se necessário) uma função que tenha o (s) parâmetro (s) que você deseja fornecer como padrão (s) como o (s) parâmetro (s) principal (s):

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

Isso é feito porque a versão do Clojure partialpermite fornecer os valores "padrão" somente na ordem em que aparecem na definição da função. Uma vez que os parâmetros foram ordenados conforme desejado, você pode criar uma versão "padrão" da função usando a partialfunção:

(partial string->integer 10)

Para fazer com que essa função seja chamada várias vezes, você pode colocá-la em uma var usando def:

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

Você também pode criar um "padrão local" usando let:

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

A abordagem da função parcial tem uma vantagem importante sobre as outras: o consumidor da função ainda pode decidir qual será o valor padrão em vez do produtor da função, sem precisar modificar a definição da função . Isso é ilustrado no exemplo hexem que decidi que a função padrão decimalnão é o que eu quero.

Outra vantagem dessa abordagem é que você pode atribuir à função padrão um nome diferente (decimal, hexadecimal etc.), que pode ser mais descritivo e / ou de escopo diferente (var, local). A função parcial também pode ser combinada com algumas das abordagens acima, se desejado:

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

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

(Observe que isso é um pouco diferente da resposta de Brian, pois a ordem dos parâmetros foi revertida pelas razões indicadas na parte superior desta resposta)

optevo
fonte
1
Não é isso que a pergunta está pedindo; é interessante embora.
Zaz