Se você pode usar def para redefinir variáveis, como isso é considerado imutável?

10

Tentando aprender Clojure e você não pode deixar de saber continuamente como Clojure tem tudo a ver com dados imutáveis. Mas você pode redefinir facilmente uma variável usando defcerto? Entendo que os desenvolvedores do Clojure evitam isso, mas você pode evitar alterar variáveis ​​em qualquer idioma da mesma forma. Alguém pode me explicar como isso é diferente, porque acho que estou perdendo isso nos tutoriais e livros que estou lendo.

Para dar um exemplo, como é

a = 1
a = 2

em Ruby (ou blub, se você preferir) diferente de

(def a 1)
(def a 2)

em Clojure?

Evan Zamir
fonte

Respostas:

9

Como você já notou, o fato de a mutabilidade ser desencorajada no Clojure não significa que ela é proibida e que não há construções que a suportem. Portanto, você está certo que o uso defpode alterar / alterar uma ligação no ambiente de maneira semelhante à atribuição feita em outros idiomas (consulte a documentação do Clojure em vars ). Ao alterar as ligações no ambiente global, você também altera os objetos de dados que usam essas ligações. Por exemplo:

user=> (def x 1)
#'user/x
user=> (defn f [y] (+ x y))
#'user/f
user=> (f 1)
2
user=> (def x 100)
#'user/x
user=> (f 1)
101

Observe que, após redefinir a ligação para x, a função ftambém mudou, porque seu corpo usa essa ligação.

Compare isso com os idiomas nos quais a redefinição de uma variável não exclui a ligação antiga, mas apenas a obscurece , ou seja, torna-a invisível no escopo que vem após a nova definição. Veja o que acontece se você escrever o mesmo código no SML REPL:

- val x = 1;
val x = 1 : int
- fun f y = x + y;
val f = fn : int -> int
- f 1;
val it = 2 : int
- val x = 100;
val x = 100 : int
- f 1;
val it = 2 : int

Observe que após a segunda definição de x, a função fainda usa a ligação x = 1que estava no escopo quando foi definida, ou seja, a ligação val x = 100não substitui a ligação anterior val x = 1.

Bottomline: Clojure permite alterar o ambiente global e redefinir as ligações nele. Seria possível evitar isso, como outros idiomas, como o SML, mas a defconstrução no Clojure tem o objetivo de acessar e alterar um ambiente global. Na prática, isso é muito semelhante ao que a atribuição pode fazer em linguagens imperativas como Java, C ++, Python.

Ainda assim, o Clojure fornece muitas construções e bibliotecas que evitam a mutação, e você pode percorrer um longo caminho sem usá-lo. Evitar a mutação é de longe o estilo de programação preferido no Clojure.

Giorgio
fonte
1
Evitar a mutação é de longe o estilo de programação preferido. Eu sugiro que esta declaração se aplique a todos os idiomas atualmente; não apenas Clojure;)
David Arno
2

Clojure é tudo sobre dados imutáveis

Clojure é cerca de gerir o estado mutável, controlando os pontos de mutação (isto é, Refs, Atoms, Agents, e Vars). Embora, é claro, qualquer código Java usado por meio de interoperabilidade possa fazer o que bem entender.

Mas você pode redefinir facilmente uma variável usando def certo?

Se você quer dizer vincular a Var(em oposição a, por exemplo, uma variável local) a um valor diferente, então sim. De fato, como observado em Vars e no ambiente global , Vars são especificamente incluídos como um dos quatro "tipos de referência" de Clojure (embora eu diria que eles se referem principalmente a s dinâmicos Var ).

Com o Lisps, há uma longa história de execução de atividades de programação interativa e exploratória via REPL. Isso geralmente envolve definir novas variáveis ​​e funções, além de redefinir as antigas. No entanto, fora do REPL, defa a Varé considerada má forma.

Nathan Davis
fonte
1

De Clojure para os bravos e verdadeiros

Por exemplo, no Ruby, você pode executar várias atribuições a uma variável para aumentar seu valor:

severity = :mild
  error_message = "OH GOD! IT'S A DISASTER! WE'RE "
  if severity == :mild
    error_message = error_message + "MILDLY INCONVENIENCED!"
  else
    error_message = error_message + "DOOOOOOOMED!"
  end

Você pode ficar tentado a fazer algo semelhante no Clojure:

(def severity :mild)
  (def error-message "OH GOD! IT'S A DISASTER! WE'RE ")
  (if (= severity :mild)
      (def error-message (str error-message "MILDLY INCONVENIENCED!"))
  (def error-message (str error-message "DOOOOOOOMED!")))

No entanto, alterar o valor associado a um nome como esse pode dificultar a compreensão do comportamento do seu programa, porque é mais difícil saber qual valor está associado a um nome ou por que esse valor pode ter sido alterado. O Clojure possui um conjunto de ferramentas para lidar com mudanças, que você aprenderá no Capítulo 10. À medida que aprender o Clojure, descobrirá que raramente precisará alterar uma associação de nome / valor. Aqui está uma maneira de escrever o código anterior:

(defn error-message [severity]
   (str "OH GOD! IT'S A DISASTER! WE'RE "
   (if (= severity :mild)
     "MILDLY INCONVENIENCED!"
     "DOOOOOOOMED!")))

(error-message :mild)
  ; => "OH GOD! IT'S A DISASTER! WE'RE MILDLY INCONVENIENCED!"
Tiago Dall'Oca
fonte
Não é possível fazer facilmente a mesma coisa em Ruby? A sugestão dada é simplesmente definir uma função que retorna um valor. Ruby também tem funções!
precisa
Sim eu conheço. Mas, em vez de incentivar uma maneira imperativa de resolver o problema proposto (como alterar as ligações), Clojure adota o paradigma funcional.
Tiago Dall'Oca