Redefinindo a variável let'd em malha Clojure
Pergunta
OK. Fui mexer com Clojure e eu sempre correr para o mesmo problema. Vamos dar este pequeno fragmento de código:
(let [x 128]
(while (> x 1)
(do
(println x)
(def x (/ x 2)))))
Agora eu esperava isso para imprimir uma seqüência começando com 128 assim:
128
64
32
16
8
4
2
Ao contrário, é um loop infinito, imprimindo 128 mais e mais. Claramente meu efeito colateral destina-se não está funcionando.
Então, como é que eu vou para redefinir o valor de x em um loop como este? Sei que isso pode não ser Lisp como (eu poderia usar uma função anônima que recurses nela é auto, talvez), mas se eu não descobrir como variável set como este, eu vou enlouquecer.
Meu outro palpite seria a criação uso !, mas que dá "destino da atribuição inválida", já que eu não estou em uma forma de ligação.
Por favor, me esclareça sobre como isto é suposto trabalho.
Solução
def
define um nível superior var, mesmo se você usá-lo em uma função ou circuito interno de algum código. O que você recebe em let
não são Vars. Por a documentação para let
:
Locals criados com let não são variáveis. Uma vez criados os seus valores nunca mudança!
(não Ênfase minha.) Você não precisa estado mutável para o seu exemplo aqui; você poderia usar loop
e recur
.
(loop [x 128]
(when (> x 1)
(println x)
(recur (/ x 2))))
Se você queria ser extravagante você poderia evitar o loop
explícita inteiramente.
(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))]
(doseq [x xs] (println x)))
Se você realmente queria usar estado mutável, um átomo trabalho poder .
(let [x (atom 128)]
(while (> @x 1)
(println @x)
(swap! x #(/ %1 2))))
(Você não precisa de um do
; while
envolve seu corpo em uma explícita para você.) Se você realmente queria fazer isso com vars você teria que fazer algo horrível assim.
(with-local-vars [x 128]
(while (> (var-get x) 1)
(println (var-get x))
(var-set x (/ (var-get x) 2))))
Mas isso é muito feio e não é Clojure idiomática em tudo. Para usar Clojure efetivamente você deve tentar parar de pensar em termos de estado mutável. Ele vai certamente deixá-lo louco tentando escrever código Clojure em um estilo não-funcional. Depois de um tempo você pode encontrá-lo para ser uma agradável surpresa como raramente você realmente precisa variáveis ??mutáveis.
Outras dicas
Vars (que é o que você começa quando você "def" algo) não são destinadas a ser transferido (mas pode ser):
user=> (def k 1)
#'user/k
user=> k
1
Não há nada que você parar de fazer:
user=> (def k 2)
#'user/k
user=> k
2
Se você quer um "lugar" de segmento local configurável você pode usar "ligação" e "set!":
user=> (def j) ; this var is still unbound (no value)
#'user/j
user=> j
java.lang.IllegalStateException: Var user/j is unbound. (NO_SOURCE_FILE:0)
user=> (binding [j 0] j)
0
Então você pode escrever um loop como este:
user=> (binding [j 0]
(while (< j 10)
(println j)
(set! j (inc j))))
0
1
2
3
4
5
6
7
8
9
nil
Mas eu acho que isso é muito unidiomatic.
Se você acha que ter variáveis ??locais mutáveis ??em funções puras seria um recurso conveniente agradável que não faz mal, porque a função ainda permanece pura, você poderia estar interessado nesta discussão lista de discussão onde rico Hickey explica suas razões para removê-los a partir da linguagem. Por que não locals mutáveis?
parte relevante:
Se moradores foram variáveis, ou seja, mutável, então encerramentos poderão fechar sobre estado mutável, e, dado que os fechamentos podem escapar (sem algum extra proibição de mesmo), o resultado seria thread-inseguro. E pessoas certamente fazê-lo, por exemplo, pseudo-objectos à base de fecho. O resultado seria um enorme buraco na abordagem de Clojure.
Sem locals mutáveis, as pessoas são forçadas a usar recorrência, um funcional looping construo. Enquanto isto pode parecer estranho à primeira vista, é tão sucinta como loops com mutação, e os padrões resultantes podem ser reutilizada em outras posições do Clojure, isto é RECUR, reduzir, alterar, comutar etc. são todos (logicamente) muito semelhante. Mesmo que eu poderia detectar e Prevenir mutação fechamentos de escapar, eu decidi mantê-lo dessa maneira para consistência. Mesmo no menor contexto, lacetes não mutação são fácil de entender e de depuração de mutação queridos. Em qualquer caso, Vars estão disponíveis para uso, quando apropriado.
A maioria dos postos preocupações posteriores execução uma macro with-local-vars
;)
Você poderia mais idiomaticamente usar iterate
e take-while
em vez disso,
user> (->> 128
(iterate #(/ % 2))
(take-while (partial < 1)))
(128 64 32 16 8 4 2)
user>