Jika Anda dapat menggunakan def untuk mendefinisikan kembali variabel bagaimana itu dianggap tidak berubah?

10

Mencoba mempelajari Clojure dan Anda tidak dapat membantu tetapi terus menerus diberitahu bagaimana Clojure adalah tentang data yang tidak dapat diubah. Tetapi Anda dapat dengan mudah mendefinisikan kembali variabel dengan menggunakan defhak? Saya mendapatkan bahwa pengembang Clojure menghindari ini tetapi Anda bisa menghindari mengubah variabel dalam bahasa apa pun yang sama. Dapatkah seseorang menjelaskan kepada saya bagaimana ini berbeda, karena saya pikir saya kehilangan itu dari tutorial dan buku yang saya baca.

Untuk memberi contoh bagaimana

a = 1
a = 2

di Ruby (atau blub, jika Anda suka) berbeda dari

(def a 1)
(def a 2)

di Clojure?

Evan Zamir
sumber

Jawaban:

9

Seperti yang sudah Anda perhatikan, fakta bahwa sifat tidak stabil tidak dianjurkan di Clojure tidak berarti bahwa itu dilarang dan tidak ada konstruksi yang mendukungnya. Jadi Anda benar bahwa menggunakan defAnda dapat mengubah / memutasi pengikatan di lingkungan dengan cara yang mirip dengan tugas yang dilakukan dalam bahasa lain (lihat dokumentasi Clojure pada vars ). Dengan mengubah binding di lingkungan global Anda juga mengubah objek data yang menggunakan binding ini. Sebagai contoh:

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

Perhatikan bahwa setelah mendefinisikan kembali pengikatan x, fungsinya fjuga berubah, karena tubuhnya menggunakan pengikatan itu.

Bandingkan ini dengan bahasa yang mendefinisikan ulang suatu variabel tidak menghapus ikatan lama tetapi hanya membayangi saja, yaitu membuatnya tidak terlihat dalam lingkup yang muncul setelah definisi baru. Lihat apa yang terjadi jika Anda menulis kode yang sama di 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

Perhatikan bahwa setelah definisi kedua dari x, fungsi fmasih menggunakan pengikatan x = 1yang ada di ruang lingkup ketika itu didefinisikan, yaitu pengikatan val x = 100tidak menimpa pengikatan sebelumnya val x = 1.

Intinya: Clojure memungkinkan untuk mengubah lingkungan global dan mendefinisikan kembali ikatan di dalamnya. Mungkin untuk menghindari ini, seperti bahasa lain seperti SML, tetapi defkonstruk di Clojure dimaksudkan untuk mengakses dan mengubah lingkungan global. Dalam praktiknya, ini sangat mirip dengan tugas yang dapat dilakukan dalam bahasa imperatif seperti Java, C ++, Python.

Meski begitu, Clojure menyediakan banyak konstruksi dan perpustakaan yang menghindari mutasi, dan Anda bisa datang jauh tanpa menggunakannya sama sekali. Menghindari mutasi sejauh ini merupakan gaya pemrograman yang disukai di Clojure.

Giorgio
sumber
1
Menghindari mutasi sejauh ini merupakan gaya pemrograman yang disukai. Saya menyarankan agar pernyataan ini berlaku untuk setiap bahasa dewasa ini; bukan hanya Clojure;)
David Arno
2

Clojure adalah tentang data yang tidak dapat diubah

Clojure adalah tentang mengelola keadaan yang dapat berubah dengan mengendalikan titik-titik mutasi (yaitu, Refs, Atoms, Agents, dan Vars). Meskipun, tentu saja, kode Java apa pun yang Anda gunakan via interop dapat melakukan apa saja yang diinginkan.

Tetapi Anda dapat dengan mudah mendefinisikan kembali variabel dengan menggunakan def kan?

Jika maksud Anda ikatkan Var(sebagai lawan dari, misalnya, variabel lokal) ke nilai yang berbeda, maka ya. Bahkan, seperti yang disebutkan dalam Vars dan Lingkungan Global , Varsecara khusus dimasukkan sebagai salah satu dari empat "tipe referensi" Clojure (meskipun saya akan mengatakan mereka terutama merujuk pada dinamis Var di sana).

Dengan Lisps, ada sejarah panjang dalam melakukan kegiatan pemrograman interaktif dan eksplorasi melalui REPL. Ini sering melibatkan mendefinisikan variabel dan fungsi baru, serta mendefinisikan kembali yang lama. Namun, di luar repl itu, ulang defing Varbentuk miskin dianggap.

Nathan Davis
sumber
1

Dari Clojure for the Brave and True

Misalnya, di Ruby Anda dapat melakukan banyak penugasan ke sebuah variabel untuk membangun nilainya:

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

Anda mungkin tergoda untuk melakukan hal serupa di 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!")))

Namun, mengubah nilai yang dikaitkan dengan nama seperti ini dapat mempersulit pemahaman perilaku program Anda karena lebih sulit untuk mengetahui nilai mana yang dikaitkan dengan nama atau mengapa nilai itu mungkin berubah. Clojure memiliki seperangkat alat untuk menghadapi perubahan, yang akan Anda pelajari di Bab 10. Saat Anda mempelajari Clojure, Anda akan menemukan bahwa Anda jarang perlu mengubah asosiasi nama / nilai. Inilah salah satu cara Anda dapat menulis kode sebelumnya:

(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
sumber
Tidak bisakah seseorang dengan mudah melakukan hal yang sama di Ruby? Saran yang diberikan hanya untuk mendefinisikan fungsi yang mengembalikan nilai. Ruby juga memiliki fungsi!
Evan Zamir
Ya saya tahu. Tetapi alih-alih mendorong cara imperatif untuk menyelesaikan masalah yang diusulkan (seperti mengubah ikatan), Clojure mengadopsi paradigma fungsional.
Tiago Dall'Oca