Gán cùng một giá trị cho nhiều biến?


12

Đôi khi tôi cần đặt cùng một giá trị cho nhiều biến.

Trong Python, tôi có thể làm

f_loc1 = f_loc2 = "/foo/bar"

Nhưng trong elisp, tôi đang viết

(setq f_loc1 "/foo/bar" f_loc2 "/foo/bar")

Tôi tự hỏi nếu có một cách để đạt được điều này "/foo/bar"chỉ bằng một lần?


1
Xin chào, Chillar, bạn có thể vui lòng chọn câu trả lời của @tarsius làm câu trả lời được chấp nhận không? Câu trả lời của tôi cho thấy giải pháp mang lại cho bạn những gì bạn muốn, nhưng như tôi đã nói, đó là "thực hiện một loại" giải quyết thách thức nhân tạo được cho là này nhưng không hữu ích trong thực tế. tarsuis đã nỗ lực rất nhiều trong việc thể hiện sự cân nhắc rõ ràng này trong câu trả lời của anh ấy, vì vậy tôi nghĩ rằng câu trả lời của anh ấy xứng đáng là "câu trả lời đúng", vì vậy mọi người đều vui vẻ trở lại.
Đánh dấu Karpov

1
Tôi cho rằng nhu cầu gán cùng một giá trị cho nhiều biến chỉ ra một lỗi thiết kế cần được sửa chữa thay vì tìm đường cú pháp để che đậy :)
lunaryorn

1
@lunaryorn nếu muốn đặt source& targetvào cùng một đường dẫn lúc đầu và tôi có thể thay đổi targetsau đó, làm thế nào để đặt chúng sau đó?
ChillarAnand

Hiển thị thêm mã, để chúng tôi có thể cho biết ý của bạn về "biến" cho trường hợp sử dụng của bạn; tức là loại biến nào bạn đang cố gắng đặt. Xem bình luận của tôi cho câu trả lời của @ JordonBiondo.
Drew

Câu trả lời:


21

setq trả về giá trị, vì vậy bạn chỉ có thể:

(setq f-loc1 (setq f-loc2 "/foo/bar"))

Nếu bạn không muốn dựa vào điều đó, thì hãy sử dụng:

(setq f-loc1 "/foo/bar" f-loc2 f-loc1)

Cá nhân tôi sẽ tránh cái sau và thay vào đó viết:

(setq f-loc1 "/foo/bar"
      f-loc2 f-loc1)

hoặc thậm chí

(setq f-loc1 "/foo/bar")
(setq f-loc2 f-loc1)

Và cách tiếp cận đầu tiên tôi sẽ chỉ sử dụng trong những trường hợp khá đặc biệt khi nó thực sự nhấn mạnh ý định, hoặc khi phương án thay thế sẽ là sử dụng cách tiếp cận thứ hai hoặc cách khác progn.


Bạn có thể dừng đọc ở đây nếu những điều trên làm bạn hạnh phúc. Nhưng tôi nghĩ rằng đây là một cơ hội tốt để cảnh báo bạn và những người khác không lạm dụng macro.


Cuối cùng, trong khi thật hấp dẫn khi viết một macro như setq-every"bởi vì điều này là không thể và chúng ta có thể", tôi thực sự khuyên bạn không nên làm như vậy. Bạn đạt được rất ít bằng cách làm điều đó:

(setq-every value a b)
   vs
(setq a (setq b value))

Nhưng đồng thời bạn làm cho những người đọc khác khó đọc mã hơn và chắc chắn điều gì sẽ xảy ra. setq-everycó thể được triển khai theo nhiều cách khác nhau và những người dùng khác (bao gồm cả bạn trong tương lai) không thể biết tác giả nào chọn, chỉ bằng cách xem mã sử dụng nó. [Ban đầu tôi đã đưa ra một số ví dụ ở đây nhưng những điều đó bị ai đó coi là mỉa mai và phẫn nộ.]

Bạn có thể đối phó với chi phí tinh thần đó mỗi khi bạn nhìn vào setq-everyhoặc bạn chỉ có thể sống với một nhân vật phụ duy nhất. (Ít nhất là trong trường hợp bạn chỉ phải đặt hai biến, nhưng tôi không thể nhớ rằng mình đã từng phải đặt nhiều hơn hai biến cho cùng một giá trị. Nếu bạn phải làm điều đó, thì có lẽ có điều gì đó sai. với mã của bạn.)

Mặt khác, nếu bạn thực sự sẽ sử dụng macro này nhiều hơn có thể hơn nửa tá lần, thì việc bổ sung có thể đáng giá. Ví dụ tôi sử dụng macro như --when-lettừ dash.elthư viện. Điều đó gây khó khăn hơn cho những người lần đầu tiên bắt gặp nó trong mã của tôi, nhưng vượt qua khó khăn trong việc hiểu là đáng giá vì nó được sử dụng nhiều hơn chỉ một vài lần và, theo tôi, nó làm cho mã dễ đọc hơn .

Người ta có thể lập luận rằng điều tương tự cũng áp dụng cho setq-every, nhưng tôi nghĩ rằng rất khó có khả năng macro đặc biệt này sẽ được sử dụng nhiều hơn một vài lần. Một nguyên tắc nhỏ là khi định nghĩa của macro (bao gồm chuỗi doc) thêm nhiều mã hơn so với việc sử dụng macro loại bỏ, thì macro rất có thể là quá mức cần thiết (mặc dù điều ngược lại không nhất thiết đúng).


1
sau khi bạn đặt một var trong setqbạn có thể tham chiếu giá trị mới của nó, vì vậy bạn cũng có thể sử dụng tính quái dị này: (setq a 10 b a c a d a e a)nó đặt abcd và e thành 10.
Jordon Biondo

1
@JordonBiondo, Có, nhưng tôi nghĩ mục đích của OP là giảm sự lặp lại trong mã của anh ấy :-)
Mark Karpov

3
Tôi muốn cho bạn 10 điểm để cảnh báo chống lạm dụng vĩ mô!
lunaryorn

Chỉ cần tạo một nhiệm vụ về thực hành tốt nhất vĩ mô. emacs.stackexchange.com/questions/21015 . Hy vọng @tarsius và những người khác đưa ra một câu trả lời tuyệt vời.
Yasushi Shoji

13

Một macro để làm những gì bạn muốn

Như một bài tập của một loại:

(defmacro setq-every (value &rest vars)
  "Set every variable from VARS to value VALUE."
  `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars)))

Bây giờ hãy thử nó:

(setq-every "/foo/bar" f-loc1 f-loc2)

Làm thế nào nó hoạt động

Vì mọi người tò mò về cách thức hoạt động của nó (theo ý kiến), đây là một lời giải thích. Để thực sự học cách viết macro, hãy chọn một cuốn sách Common Lisp tốt (vâng, Common Lisp, bạn sẽ có thể làm những thứ tương tự trong Emacs Lisp, nhưng Common Lisp mạnh hơn một chút và có sách hay hơn, IMHO).

Macro hoạt động trên mã thô. Các macro không đánh giá các đối số của chúng (không giống như các hàm). Vì vậy, chúng tôi đã đánh giá valuevà thu thập ở đây vars, mà đối với vĩ mô của chúng tôi chỉ là biểu tượng.

prognnhóm một số setqhình thức thành một. Thứ này:

(mapcar (lambda (x) (list 'setq x value)) vars)

Chỉ cần tạo một danh sách các setqbiểu mẫu, sử dụng ví dụ của OP sẽ là:

((setq f-loc1 "/foo/bar") (setq f-loc2 "/foo/bar"))

Bạn thấy đấy, biểu mẫu nằm trong biểu mẫu backquote và được thêm tiền tố bằng dấu phẩy ,. Bên trong biểu mẫu được trích dẫn lại, mọi thứ đều được trích dẫn như bình thường, nhưng , tạm thời đánh giá lượt bật, vì vậy toàn bộ mapcarđược đánh giá tại thời điểm mở rộng vĩ mô.

Cuối cùng @loại bỏ dấu ngoặc đơn bên ngoài khỏi danh sách với setqs, vì vậy chúng tôi nhận được:

(progn
  (setq f-loc1 "/foo/bar")
  (setq f-loc2 "/foo/bar"))

Macro có thể tùy ý chuyển đổi mã nguồn của bạn, thật tuyệt phải không?

Một cảnh báo

Đây là một cảnh báo nhỏ, đối số đầu tiên sẽ được đánh giá nhiều lần, bởi vì macro này về cơ bản mở rộng ra như sau:

(progn
  (setq f-loc1 "/foo/bar")
  (setq f-loc2 "/foo/bar"))

Bạn thấy đấy, nếu bạn có một biến hoặc chuỗi ở đây thì không sao, nhưng nếu bạn viết một cái gì đó như thế này:

(setq-every (my-function-with-side-effects) f-loc1 f-loc2)

Sau đó, chức năng của bạn sẽ được gọi nhiều hơn một lần. Điều này có thể là không mong muốn. Dưới đây là cách khắc phục với sự trợ giúp của once-only(có sẵn trong gói MMT ):

(defmacro setq-every (value &rest vars)
  "Set every variable from VARS to value VALUE.

VALUE is only evaluated once."
  (mmt-once-only (value)
    `(progn ,@(mapcar (lambda (x) (list 'setq x value)) vars))))

Và vấn đề đã biến mất.


1
Sẽ rất tuyệt nếu bạn có thể thêm một số giải thích về cách thức hoạt động của mã này và mã này đang làm chi tiết hơn, vì vậy lần sau mọi người có thể tự viết một cái gì đó như thế này :)
clemera

Bạn không muốn đánh giá valuetại thời điểm mở rộng vĩ mô.
tarsius

@tarsius, tôi không: (macroexpand '(setq-every user-emacs-directory f-loc1 f-loc2))-> (progn (setq f-loc1 user-emacs-directory) (setq f-loc2 user-emacs-directory)). Nếu valueđược đánh giá tại thời điểm mở rộng vĩ mô, nó sẽ được thay thế bằng chuỗi thực tế. Bạn có thể thực hiện cuộc gọi chức năng với các tác dụng phụ, thay vào đó value, nó sẽ không được đánh giá cho đến khi mã mở rộng được chạy, hãy thử nó. valuenhư đối số của macro không được tự động đánh giá. Vấn đề thực sự là valuesẽ được đánh giá nhiều lần, có thể là không mong muốn, once-onlycó thể giúp đỡ ở đây.
Đánh dấu Karpov

1
Thay vì mmt-once-only(yêu cầu một dep bên ngoài), bạn có thể sử dụng macroexp-let2.
YoungFrog

2
Ừ Câu hỏi kêu lên để sử dụng phép lặp với set, không phải để bọc setqtrong một macro. Hoặc chỉ sử dụng setqvới nhiều đối số, bao gồm cả việc lặp lại các đối số.
vẽ

7

Vì bạn có thể sử dụng và thao tác các biểu tượng trong lisp, bạn chỉ cần lặp qua danh sách các biểu tượng và sử dụng set .

(dolist (var '(foo bar baz)) (set var 10))

(mapc (lambda (var) (set var 10)) '(foo bar baz))

(loop for var in '(foo bar baz) do (set var 11))

(--each '(foo bar baz) (set it 10))

2
Tuy nhiên, điều này không hoạt động đối với các biến bị ràng buộc từ
vựng

@npostavs: Đúng, nhưng đó có thể là những gì OP muốn / cần. Nếu anh ta không sử dụng các biến từ vựng thì đó chắc chắn là con đường để đi. Tuy nhiên, bạn đúng, "biến" đó không rõ ràng, vì vậy nói chung câu hỏi có thể có nghĩa là bất kỳ loại biến nào. Trường hợp sử dụng thực tế không được trình bày tốt, IMO.
Drew
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.