เหตุใดชุมชน Lisp จึงต้องการสะสมวงเล็บทั้งหมดเมื่อสิ้นสุดฟังก์ชัน


26

เหตุใดชุมชน Lisp จึงต้องการสะสมวงเล็บทั้งหมดเมื่อสิ้นสุดฟังก์ชัน:

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)))

ทำไมไม่ใช้การประชุมเช่น C หรือ Java
โอเค Lisp มีอายุมากกว่าภาษาเหล่านั้นมาก แต่ฉันกำลังพูดถึง Lispers ร่วมสมัย

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)
  )
)

หมายเหตุ: ข้อมูลโค้ดมาจากหนังสือ "ความสุขของ Clojure"


13
ฉันบอกว่าในสมัยของ punch card มันเป็นเรื่องธรรมดาที่จะจบโปรแกรม Lisp ด้วยบัตรพิเศษที่มีวงเล็บขวา 80 อันเพื่อให้แน่ใจว่ามีเพียงพอที่จะจับคู่วงเล็บเปิดทั้งหมดในโปรแกรม .
Alger

2
ว้าวฉันชอบมัน. ฉันไม่ชอบวงเล็บเสมอ แต่ตัวอย่างที่สองของคุณดูดีสำหรับฉัน ไม่ดีเท่างูหลาม แต่เป็นการปรับปรุง
Eric Wilson

5
เพราะพวกเขาสามารถ?
Muad'Dib

มันจะดีถ้าภาษาของ Clojure เลื่อนการเยื้องเป็นโครงสร้างรหัส นั่นจะทำไปกับวงเล็บทั้งหมด (เช่น F # ฯลฯ ) แน่นอนว่าวงเล็บยังคงถูกกฎหมายหากต้องการ
intrepidis

คำตอบ:


28

เหตุผลหนึ่งที่ทำให้ภาษาที่ใช้อัลกอลสนับสนุนการจัดฟันด้วยตนเองนั้นก็คือการเพิ่มการเพิ่มจำนวนบรรทัดระหว่างการจัดฟันที่แยกจากกันโดยไม่ต้องย้ายเครื่องมือจัดฟัน นั่นคือถ้าเริ่มด้วย

if (pred)
{
  printf("yes");
}

เป็นเรื่องง่ายที่จะเข้ามาและเพิ่มคำสั่งอื่นภายในวงเล็บปีกกา:

if (pred)
{
  printf("yes");
  ++yes_votes;
}

มีรูปแบบเดิมแล้ว

if (pred)
{ printf("yes"); }

จากนั้นเราจะต้องมี "การย้าย" สองวงเล็บปีกกา แต่ตัวอย่างของฉันเกี่ยวข้องกับหลังมากขึ้น ที่นี่เครื่องมือจัดฟันกำลังแยกสิ่งที่ตั้งใจให้เป็นลำดับของข้อความซึ่งส่วนใหญ่จะเรียกใช้สำหรับผลข้างเคียง

ในทางกลับกันเสียงกระเพื่อมขาดงบ; ทุกรูปแบบคือการแสดงออกซึ่งให้คุณค่าบางอย่าง - แม้ว่าในบางกรณีที่หายาก (คิดว่า Common LISP) ค่านั้นจะถูกเลือกโดยเจตนาให้เป็น "ไม่มีค่า" ผ่านทาง(values)รูปแบบที่ว่างเปล่า มันน้อยร่วมกันเพื่อหาลำดับของการแสดงออกเมื่อเทียบกับการแสดงออกที่ซ้อนกัน ความปรารถนาที่จะ "เปิดลำดับขั้นตอนจนกว่าตัวคั่นการปิด" จะไม่เกิดขึ้นบ่อยนักเนื่องจากเมื่อคำสั่งหายไปและค่าที่ส่งคืนจะกลายเป็นสกุลเงินที่ใช้กันทั่วไปมันหายากกว่าที่จะเพิกเฉยต่อค่าส่งคืนของนิพจน์ หายากเพื่อประเมินลำดับของนิพจน์สำหรับผลข้างเคียงเพียงอย่างเดียว

ใน Common Lisp prognรูปแบบเป็นข้อยกเว้น (เช่นเดียวกับพี่น้อง):

(progn
  (exp-ignored-return-1)
  (exp-ignored-return-2)
  (exp-taken-return))

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

การทำให้เข้าใจง่ายขั้นต้นวงเล็บในส่วนต่างๆของโปรแกรม Lisp คือการกำหนดอาร์กิวเมนต์ที่ส่งผ่านไปยังฟังก์ชั่นเช่นเดียวกับในภาษา C-like และไม่คั่นบล็อกคำสั่ง ด้วยเหตุผลเดียวกันเรามักจะให้วงเล็บล้อมรอบการเรียกใช้ฟังก์ชันใน C ใกล้กับอาร์กิวเมนต์ดังนั้นเราก็ทำเช่นเดียวกันใน Lisp โดยมีแรงจูงใจน้อยกว่าที่จะเบี่ยงเบนจากการจัดกลุ่มใกล้เคียงนั้น

การปิดวงเล็บนั้นมีการนำเข้าน้อยกว่าการเยื้องของฟอร์มที่เปิด ในเวลาหนึ่งเรียนรู้ที่จะไม่สนใจวงเล็บและเขียนและอ่านตามรูปร่างเหมือนที่โปรแกรมเมอร์ Python ทำ อย่างไรก็ตามอย่าปล่อยให้การเปรียบเทียบนั้นนำคุณไปสู่การคิดว่าการลบวงเล็บทั้งหมดจะคุ้มค่า comp.lang.lispไม่มีที่ถกเถียงกันดีที่สุดสำหรับการบันทึกไว้


2
ฉันคิดว่าการเพิ่มในตอนท้ายไม่ใช่สิ่งที่หายากเช่น(let ((var1 expr1) more-bindings-to-be-added) ...)หรือ(list element1 more-elements-to-be-added)
Alexey

14

เพราะมันไม่ได้ช่วยอะไร เราใช้การเยื้องเพื่อแสดงโครงสร้างรหัส หากเราต้องการแยกบล็อคของรหัสเราใช้บรรทัดที่ว่างเปล่าจริงๆ

เนื่องจากไวยากรณ์ของ Lisp มีความสอดคล้องกันมากดังนั้นวงเล็บจึงเป็นแนวทางที่ชัดเจนสำหรับการเยื้องสำหรับโปรแกรมเมอร์และบรรณาธิการ

(สำหรับฉันคำถามค่อนข้างจะเหมือนกันว่าทำไมโปรแกรมเมอร์ C และ Java จึงชอบที่จะใช้เครื่องมือจัดฟัน)

เพียงแสดงให้เห็นว่าสมมติว่าผู้ให้บริการเหล่านี้มีให้บริการในภาษา C

Foo defer_expensive (Thunk cheap, Thunk expensive) {
    if (Foo good_enough = force (cheap)) {
        return good_enough; }
    else {
        return force (expensive); }}

วงเล็บปิดสองอันปิดสองระดับในการซ้อน เห็นได้ชัดว่าไวยากรณ์ถูกต้อง ในการเปรียบเทียบกับไวยากรณ์ Python วงเล็บปีกกาเป็นเพียงโทเค็น INDENT และ DEDENT ชัดเจน

แน่นอนว่านี่อาจไม่ใช่ "รูปแบบการรั้งที่แท้จริง" TMแต่ฉันเชื่อว่าเป็นเพียงอุบัติเหตุทางประวัติศาสตร์และนิสัย


2
นอกจากนี้เกือบทั้งหมดบรรณาธิการกระเพื่อมเครื่องหมายวงเล็บที่ตรงกันเมื่อปิด (บางส่วนยังถูกต้อง)ไป]หรือกลับกัน) เพื่อให้คุณรู้ว่าคุณปิดในปริมาณที่เหมาะสมแม้ว่าคุณจะไม่ได้ตรวจสอบระดับการเยื้อง
กำหนดค่า

6
WRT "คำถาม" เนื่องจาก "โยนไปรอบ ๆ " การปิดโทเค็นในรูปแบบของตัวอย่างที่สองช่วยให้คุณจัดแนวพวกเขาได้อย่างง่ายดายด้วยตาของคุณและดูสิ่งที่ปิดลงแม้ว่าคุณจะอยู่ในโปรแกรมแก้ไขข้อความที่ไม่มีการจับคู่อัตโนมัติ / คุณสมบัติเด่น
Mason Wheeler

1
@Mason Wheeler ใช่แน่นอน
Chiron

3
TBH สิ่งที่สิ่งนี้บอกกับฉันคือ parens ใน LISP ส่วนใหญ่จะซ้ำซ้อนโดยมีความเป็นไปได้ (เช่นเดียวกับ C ++) ที่การเยื้องอาจทำให้เข้าใจผิด - หากเยื้องบอกคุณว่าคุณต้องการรู้ว่ามันควรบอกคอมไพเลอร์ เช่นเดียวกับใน Haskell และ Python BTW - การมีเครื่องหมายปีกกาอยู่ในระดับเดียวกับ if / switch / while / อะไรก็ตามใน C ++ จะช่วยป้องกันกรณีที่การเยื้องนั้นทำให้เข้าใจผิด - การสแกนภาพลง LHS จะบอกคุณว่า open-brace นั้นถูกจับคู่กับ close-brace และการเยื้องนั้นสอดคล้องกับวงเล็บปีกกาเหล่านั้น
Steve314

1
@ Steve314: ไม่การเยื้องนั้นซ้ำซ้อน การเยื้องอาจทำให้เข้าใจผิดใน Python และ Haskell ด้วย (คิดเกี่ยวกับการเยื้องครั้งเดียวหรือตาราง) มันก็เป็นเพียงการแยกวิเคราะห์ที่ทำให้เข้าใจผิดเช่นกัน ฉันคิดว่าวงเล็บเป็นเครื่องมือสำหรับนักเขียนและตัวแยกวิเคราะห์ในขณะที่การเยื้องเป็นเครื่องมือสำหรับนักเขียนและผู้อ่าน (มนุษย์) กล่าวอีกนัยหนึ่งเยื้องคือความคิดเห็นวงเล็บคือไวยากรณ์
Svante

11

รหัสมีขนาดกะทัดรัดมากขึ้นแล้ว การเคลื่อนไหวในเครื่องมือแก้ไขคือโดยการแสดงออก s ดังนั้นคุณไม่ต้องการพื้นที่สำหรับการแก้ไข รหัสส่วนใหญ่จะอ่านตามโครงสร้างและประโยคไม่ใช่ตามตัวคั่น


ช่างเป็นเกียรติที่ได้รับคำตอบจากคุณ :) ขอบคุณ
Chiron

ตัวแก้ไขใดที่เคลื่อนไหวด้วยการแสดงออก s? โดยเฉพาะอย่างยิ่งฉันvimจะทำเช่นนั้นได้อย่างไร
hasen

2
@HasenJ คุณอาจจำเป็นต้องใช้ปลั๊กอินvimacs.vim : P
Mark C

5

Lispers คุณรู้ hatefully bletcherous เป็นมันอาจจะมีเป็นบางje sais quoi ภาคตะวันออกเฉียงเหนือกับตัวอย่างที่สอง:

(defn defer-expensive [cheap expensive]
  (if-let [good-enough (force cheap)]
    good-enough
    (force expensive)
  )
)

ในตอนแรกฉันไม่สามารถวางนิ้วลงบนต้นกำเนิดของเสน่ห์ได้ดังนั้นพูดแล้วฉันก็รู้ว่ามันเป็นเพียงการขาดไก:

(defn defer-expensive [cheap expensive]      
  (if-let [good-enough (force cheap)]
    good-enough   ;   )
    (force expensive) 
  )
)

Voila!

ฉันจะฝึกจับนักเลง Lisp ของฉันตอนนี้!


2
นี่เป็นหนึ่งในคำตอบที่สนุกที่สุดและมีการประเมินน้อยที่สุดที่ฉันเคยเห็นในเว็บไซต์นี้ ปลายหมวกของฉัน
byxor

0

ในกรณีของฉันฉันพบบรรทัดที่อุทิศให้กับตัวคั่นพื้นที่ของหน้าจอเสียและเมื่อคุณเขียนรหัสใน C ยังมีรูปแบบของ

if (pred) {
   printf("yes");
   ++yes_votes;
}

ทำไมคนใส่วงเล็บปีกกาเปิดในบรรทัดเดียวกันของ "ถ้า" เพื่อประหยัดพื้นที่และเพราะมันดูเหมือนซ้ำซ้อนที่จะมีสายของตัวเองเมื่อ "ถ้า" มีสายของตัวเองแล้ว

คุณถึงผลลัพธ์เดียวกันโดยใส่วงเล็บไว้ท้ายสุด ใน C จะดูแปลกเพราะคำแถลงสิ้นสุดในเซมิโคลอนและวงเล็บไม่เปิดเช่นนี้

{if (pred)
    printf("yes");
}

มันก็เหมือนวงเล็บปีกกาปิดท้ายที่สุดมองออกไปนอกสถานที่ มันเปิดแบบนี้

if (pred) {
    printf("yes");
}

ให้มุมมองที่ชัดเจนของบล็อกและขีด จำกัด โดย '{' & '}'

และด้วยความช่วยเหลือของเครื่องมือแก้ไขที่จับคู่วงเล็บโดยการเน้นพวกมันเหมือนกับ vim คุณสามารถไปยังจุดสิ้นสุดที่วงเล็บทั้งหมดถูกบรรจุและเลื่อนผ่านพวกเขาได้อย่างง่ายดายและจับคู่ parens ที่เปิดทั้งหมดและดูฟอร์ม lisp ซ้อนกัน

(defn defer-expensive [cheap expensive]
    _(if-let [good-enough (force cheap)]
    good-enough
    (force expensive)_))

คุณสามารถวางเคอร์เซอร์ของคุณใน paren ปิดตรงกลางและมี paren เปิดของ if- ให้เน้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.