web-dev-qa-db-ja.com

defを使用して変数を再定義できる場合、それはどのように不変と見なされますか?

Clojureを学ぼうとしても、Clojureが不変のデータについてどのように扱われているのかを継続的に言わざるを得ません。しかし、defを使用すると、変数を簡単に再定義できますか? Clojureの開発者はこれを避けていると思いますが、どの言語でも同じように変数を変更しないようにすることができます。私が読んでいるチュートリアルや本にそれが欠けていると思うので、誰かがこれがどのように違うのか説明してくれますか?.

例を挙げれば

a = 1
a = 2

Ruby(または、必要に応じてblub)とは異なる

(def a 1)
(def a 2)

clojureで?

10
Evan Zamir

すでにお気づきのとおり、Clojureで可変性が推奨されていないという事実は、それが禁止されていること、およびそれをサポートする構成がないことを意味するものではありません。したがって、defを使用すると、他の言語での割り当てと同様の方法で、環境内のバインディングを変更または変更できます( varに関するClojureのドキュメント を参照)。グローバル環境でバインディングを変更すると、これらのバインディングを使用するデータオブジェクトも変更されます。例えば:

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

xのバインディングを再定義した後、本体がそのバインディングを使用するため、関数fも変更されていることに注意してください。

これを、変数を再定義しても古いバインディングは削除されず、shadowsだけが削除される言語と比較してください。つまり、次のスコープで変数が非表示になります。新しい定義。 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

xの2番目の定義の後、関数fは、定義時にスコープ内にあったバインディングx = 1を引き続き使用することに注意してください。つまり、バインディングval x = 100は、以前のバインディングval x = 1を上書きします。

結論:Clojureは、グローバル環境を変更し、その中でバインディングを再定義することを可能にします。 SMLのような他の言語と同様に、これを回避することは可能ですが、Clojureのdef構造は、グローバル環境にアクセスして変更することを目的としています。実際には、これは、割り当てがJava、C++、Pythonなどの命令型言語で実行できるものと非常に似ています。

それでも、Clojureは変異を回避する多くの構造とライブラリを提供しており、まったく使用しなくても長い道のりを歩むことができます。変異を回避することは、Clojureのプログラミングスタイルで圧倒的に推奨されています。

9
Giorgio

Clojureはすべて不変データに関するものです

Clojureは、変異ポイント(つまり、Refs、Atoms、Agents、およびVars)。もちろん、Java=相互運用機能を介して使用するコードはすべて自由に実行できます。

しかし、def rightを使用すると、変数を簡単に再定義できますか?

Varを(たとえば、ローカル変数ではなく)別の値にバインドする場合は、yesを使用します。実際、 変数と地球環境 に記載されているように、Varsは、Clojureの4つの「参照タイプ」の1つとして具体的に含まれています(ただし、主にdynamicVars there)。

Lispを使用すると、REPLを介してインタラクティブで探索的なプログラミングアクティビティを実行する長い歴史があります。これには、新しい変数と関数の定義、および古い変数と関数の再定義が含まれることがよくあります。ただし、REPL以外では、defをre -Varingすることは不適切な形式と見なされます。

2
Nathan Davis

From Clojure for the Brave and True から

たとえば、Rubyでは、変数に複数の代入を実行して、その値を構築することができます。

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

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!")))

ただし、このような名前に関連付けられている値を変更すると、名前に関連付けられている値やその値が変更された理由を知ることが難しくなるため、プログラムの動作を理解するのが難しくなる可能性があります。 Clojureには、変更に対処するための一連のツールが用意されています。これについては、第10章で説明します。Clojureを学習すると、名前と値の関連付けを変更する必要がほとんどなくなります。上記のコードを作成する方法の1つを次に示します。

(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!"
1
Tiago Dall'Oca