เหตุใดการกำหนดขอบเขตของ defvar จึงแตกต่างกันโดยไม่มีค่าเริ่มต้น


10

สมมติว่าฉันมีชื่อไฟล์elisp-defvar-test.elที่มี:

;;; elisp-defvar-test.el ---  -*- lexical-binding: t -*- 

(defvar my-dynamic-var)

(defun f1 (x)
  "Should return X."
  (let ((my-dynamic-var x))
    (f2)))

(defun f2 ()
  "Returns the current value of `my-dynamic-var'."
  my-dynamic-var)

(provide 'elisp-dynamic-test)

;;; elisp-defvar-test.el ends here

ฉันโหลดไฟล์นี้แล้วไปที่บัฟเฟอร์รอยขีดข่วนและเรียกใช้:

(setq lexical-binding t)
(f1 5)
(let ((my-dynamic-var 5))
  (f2))

(f1 5)ส่งคืน 5 ตามที่คาดไว้ซึ่งแสดงว่าเนื้อความของf1ถือmy-dynamic-varเป็นตัวแปรที่กำหนดขอบเขตแบบไดนามิกตามที่คาดไว้ อย่างไรก็ตามรูปแบบสุดท้ายให้ข้อผิดพลาดเป็นโมฆะตัวแปรเพื่อmy-dynamic-varระบุว่ามันกำลังใช้การกำหนดขอบเขตศัพท์สำหรับตัวแปรนี้ ดูเหมือนว่าจะขัดแย้งกับเอกสารสำหรับdefvarซึ่งบอกว่า:

defvarรูปแบบนอกจากนี้ยังประกาศตัวแปรเป็น "พิเศษ" เพื่อที่จะอยู่เสมอผูกพันแบบไดนามิกแม้ว่าlexical-bindingเป็นเสื้อ

ถ้าฉันเปลี่ยนdefvarรูปแบบในไฟล์ทดสอบเพื่อระบุค่าเริ่มต้นตัวแปรนั้นจะถือว่าเป็นแบบไดนามิกเสมอเช่นเอกสารกล่าว ทุกคนสามารถอธิบายได้หรือไม่ว่าทำไมการกำหนดขอบเขตของตัวแปรถูกกำหนดโดยdefvarค่าเริ่มต้นเมื่อมีการประกาศตัวแปรนั้นหรือไม่

นี่คือข้อผิดพลาดย้อนหลังในกรณีที่สำคัญ:

Debugger entered--Lisp error: (void-variable my-dynamic-var)
  f2()
  (let ((my-dynamic-var 5)) (f2))
  (progn (let ((my-dynamic-var 5)) (f2)))
  eval((progn (let ((my-dynamic-var 5)) (f2))) t)
  elisp--eval-last-sexp(t)
  eval-last-sexp(t)
  eval-print-last-sexp(nil)
  funcall-interactively(eval-print-last-sexp nil)
  call-interactively(eval-print-last-sexp nil nil)
  command-execute(eval-print-last-sexp)

4
ฉันคิดว่าการอภิปรายในข้อผิดพลาด # 18059มีความเกี่ยวข้อง
โหระพา

เป็นคำถามที่ดีมากและใช่โปรดดูการอภิปรายข้อผิดพลาด # 18059
ดึง

ฉันเข้าใจแล้วดูเหมือนว่าเอกสารจะได้รับการอัปเดตเพื่อแก้ไขปัญหานี้ใน Emacs 26
Ryan C. Thompson

คำตอบ:


8

สาเหตุที่ทั้งสองได้รับการปฏิบัติแตกต่างกันเป็นส่วนใหญ่ "เพราะนั่นคือสิ่งที่เราต้องการ" โดยเฉพาะอย่างยิ่งรูปแบบการโต้แย้งเดี่ยวของdefvarปรากฏเมื่อนานมาแล้ว แต่ภายหลังกว่าอื่น ๆ และโดยทั่วไปก็คือ "แฮ็ค" เพื่อปิดเสียงคำเตือนของคอมไพเลอร์: ในเวลาดำเนินการมันไม่มีผลเลยดังนั้นในฐานะ "อุบัติเหตุ" พฤติกรรมการห้ามไม่ให้พูดของ(defvar FOO)เพียงนำไปใช้กับไฟล์ปัจจุบันเท่านั้น (เนื่องจากคอมไพเลอร์ไม่มีวิธีที่จะรู้ว่า defvar ดังกล่าวถูกเรียกใช้งานในไฟล์อื่น ๆ )

เมื่อlexical-bindingเปิดตัวใน Emacs-24 เราตัดสินใจที่จะใช้(defvar FOO)แบบฟอร์มนี้อีกครั้งแต่นั่นก็หมายความว่าตอนนี้มันจะมีผลกระทบ

ส่วนหนึ่งที่จะรักษาพฤติกรรม "ส่งผลกระทบต่อไฟล์ปัจจุบัน" ก่อนหน้านี้ แต่ที่สำคัญกว่านั้นคือการอนุญาตให้ไลบรารีใช้totoเป็น var ที่มีการกำหนดขอบเขตแบบไดนามิกโดยไม่ป้องกันไลบรารีอื่น ๆ จากการใช้งานtotoเป็น ความขัดแย้ง แต่ไม่ได้ใช้ทุกที่ที่น่าเศร้า) พฤติกรรมใหม่ของ(defvar FOO)ถูกกำหนดให้ใช้กับไฟล์ปัจจุบันเท่านั้นและได้รับการปรับปรุงเพื่อให้ใช้กับขอบเขตปัจจุบันเท่านั้น(เช่นหากปรากฏในฟังก์ชันจะมีผลต่อการใช้งานของ var นั้นภายในฟังก์ชันนั้น)

พื้นฐาน(defvar FOO VAL)และ(defvar FOO)เป็นเพียงสองสิ่งที่ "แตกต่างอย่างสิ้นเชิง" พวกเขาเพิ่งจะใช้คำหลักเดียวกันเพื่อเหตุผลทางประวัติศาสตร์


1
+1 สำหรับคำตอบ แต่วิธีการของ Common Lispนั้นชัดเจนและดีกว่า IMHO
ดึง

@Drew: ฉันเห็นด้วยเป็นส่วนใหญ่ แต่การนำ(defvar FOO)โหมดเก่ากลับมาใช้ใหม่ได้เข้ากันได้กับรหัสเก่ามากขึ้น นอกจากนี้ IIRC ปัญหาเกี่ยวกับวิธีแก้ปัญหาของ CommonLisp ก็คือมันมีค่าใช้จ่ายค่อนข้างสูงสำหรับล่ามบริสุทธิ์เช่น Elisp's (เช่นทุกครั้งที่คุณประเมินว่าletคุณต้องมองเข้าไปในร่างกายของมันในกรณีdeclareที่มีบางอย่างที่ส่งผลต่อ vars)
สเตฟาน

ตกลงทั้งสองข้อ
ดึง

4

จากการทดลองฉันเชื่อว่าปัญหาคือ(defvar VAR)ไม่มีค่าเริ่มต้นเท่านั้นที่มีผลกับไลบรารีที่ปรากฏ

เมื่อฉันเพิ่มลง(defvar my-dynamic-var)ใน*scratch*บัฟเฟอร์ข้อผิดพลาดจะไม่เกิดขึ้นอีก

ตอนแรกฉันคิดว่านี่เป็นเพราะการประเมินรูปแบบนั้น แต่จากนั้นฉันสังเกตเห็นอย่างแรกเลยว่าเพียงแค่ไปที่ไฟล์ที่มีรูปแบบนั้นอยู่ก็เพียงพอแล้ว และยิ่งไปกว่านั้นเพียงเพิ่ม (หรือลบ) รูปแบบในบัฟเฟอร์ที่ไม่มีการประเมินมันก็เพียงพอที่จะเปลี่ยนแปลงสิ่งที่เกิดขึ้นเมื่อมีการประเมินภายในบัฟเฟอร์เดียวกันกับที่มี(let ((my-dynamic-var 5)) (f2))eval-last-sexp

(ฉันไม่มีความเข้าใจที่แท้จริงว่าเกิดอะไรขึ้นที่นี่ฉันพบว่าพฤติกรรมน่าแปลกใจ แต่ฉันไม่คุ้นเคยกับรายละเอียดของวิธีการใช้งานฟังก์ชั่นนี้)

ฉันจะเพิ่มว่ารูปแบบของdefvar(โดยไม่มีค่าเริ่มต้น) นี้ป้องกันไม่ให้คอมไพเลอร์ไบต์บ่นเกี่ยวกับการใช้ตัวแปรแบบไดนามิกที่กำหนดจากภายนอกในไฟล์ elisp ที่กำลังรวบรวม แต่ด้วยตัวของมันเองมันไม่ได้ทำให้ตัวแปรboundpนั้นเป็น; ดังนั้นจึงไม่ได้กำหนดตัวแปรอย่างเคร่งครัด (โปรดทราบว่าหากตัวแปรนั้นเป็น boundpปัญหานี้จะไม่เกิดขึ้นเลย)

ในทางปฏิบัติผมคิดว่านี้เป็นไปได้ผลงานออกมา ok โดยที่คุณไม่รวม(defvar my-dynamic-var)อยู่ในห้องสมุดคำศัพท์ที่มีผลผูกพันใด ๆ ที่ใช้ของคุณmy-dynamic-varตัวแปร (ซึ่งคงจะมีความหมายที่แท้จริงที่อื่น ๆ )


แก้ไข:

ขอบคุณที่ตัวชี้จาก @npostavs ในความคิดเห็น:

ทั้งสองeval-last-sexpและeval-defunใช้eval-sexp-add-defvarsเพื่อ:

เสริม EXP กับdefvars ทั้งหมดที่อยู่ข้างหน้าในบัฟเฟอร์

โดยเฉพาะที่ตั้งอยู่ทั้งหมดdefvar, defconstและdefcustomอินสแตนซ์ (แม้เมื่อความเห็นฉันยังสังเกตเห็น)

เนื่องจากนี่เป็นการค้นหาบัฟเฟอร์ในเวลาเรียกใช้มันจะอธิบายว่าแบบฟอร์มเหล่านี้มีผลต่อบัฟเฟอร์ได้อย่างไรโดยไม่ได้รับการประเมินและยืนยันว่าแบบฟอร์มต้องปรากฏในไฟล์ elisp เดียวกัน (และยังเร็วกว่ารหัสที่ประเมิน) .


2
IIUC ข้อผิดพลาด # 18059ยืนยันความพยายามของคุณ
เพรา

2
ดูเหมือนว่าeval-sexp-add-defvarsจะตรวจสอบ defvars ในข้อความบัฟเฟอร์
npostavs

1
+1 เห็นได้ชัดว่าคุณสมบัตินี้ไม่ชัดเจนหรือไม่ปรากฏแก่ผู้ใช้อย่างชัดเจน การแก้ไขเอกสารสำหรับข้อผิดพลาด # 18059 ช่วย แต่นี่ก็ยังเป็นสิ่งที่ลึกลับหากไม่ได้บอบบางสำหรับผู้ใช้
ดึง

0

ฉันไม่สามารถทำซ้ำสิ่งนี้ได้เลยการประเมินตัวอย่างหลังทำงานได้ดีที่นี่และส่งคืน 5 ตามที่คาดไว้ คุณแน่ใจหรือว่าคุณไม่ได้ทำการประเมินmy-dynamic-varด้วยตัวเอง นั่นจะทำให้เกิดข้อผิดพลาดเนื่องจากตัวแปรนั้นเป็นโมฆะมันไม่ได้ถูกกำหนดเป็นค่าและมันจะมีเพียงถ้าคุณผูกมันแบบไดนามิก


1
คุณได้ตั้งค่าlexical-bindingแบบไม่มีศูนย์ก่อนประเมินแบบฟอร์มหรือไม่? ฉันได้รับพฤติกรรมที่คุณอธิบายด้วยlexical-bindingศูนย์ แต่เมื่อฉันตั้งค่าเป็นไม่ใช่ศูนย์ฉันจะได้รับข้อผิดพลาดเป็นโมฆะตัวแปร
Ryan C. Thompson

ใช่ฉันบันทึกสิ่งนี้ลงในไฟล์แยกต่างหากย้อนกลับตรวจสอบที่lexical-bindingตั้งค่าและประเมินฟอร์มตามลำดับ
wasamasa

@wasamasa สร้างใหม่ให้ฉันบางทีคุณอาจได้รับmy-dynamic-varค่าไดนามิกสูงสุดในเซสชั่นปัจจุบันของคุณโดยบังเอิญ? ฉันคิดว่ามันสามารถทำเครื่องหมายว่ามันพิเศษอย่างถาวร
npostavs
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.