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


10

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

例を挙げれば

a = 1
a = 2

Ruby(または好みに応じてblub)では、

(def a 1)
(def a 2)

Clojureで?

回答:


9

すでにお気づきのとおり、Clojureで可変性が推奨されていないという事実は、それが禁止されていること、およびそれをサポートする構成がないことを意味するものではありません。そのdefため、他の言語での割り当てと同様に、環境でバインディングを変更/変更できることは正しいです(varsに関する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も変更されていることに注意してください。

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

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

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

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


1
ミューテーションを回避することは、はるかに望ましいプログラミングスタイルです。この発言は最近すべての言語に当てはまることをお勧めします。Clojureだけではありません;)
David Arno

2

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

Clojureのは、についてです管理する(すなわち、変異ポイントを制御することにより、可変状態をRefよ、Atomよ、Agentよ、とVar秒)。もちろん、相互運用機能を介して使用するJavaコードは自由に実行できます。

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

Var(たとえば、ローカル変数ではなく)を別の値にバインドすることを意味している場合は、そうです。実際、Varsと地球環境で述べたように、VarsはClojureの4つの「参照タイプ」の1つとして具体的に含まれています(ただし、主に動的 Var s を参照していると言います)。

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


1

Clojureから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!"

Rubyで同じことを簡単に行うことはできませんか?与えられた提案は、単に値を返す関数を定義することです。Rubyにも機能があります!
エヴァンZamir

はい、知っています。しかし、提案された問題(バインディングの変更など)を解決するための必須の方法を奨励する代わりに、Clojureは機能的なパラダイムを採用しています。
Tiago Dall'Oca 16
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.