การออกแบบไวยากรณ์ - ทำไมต้องใช้วงเล็บเมื่อไม่มีการส่งอาร์กิวเมนต์?


66

ในหลายภาษาไวยากรณ์function_name(arg1, arg2, ...)ถูกใช้เพื่อเรียกใช้ฟังก์ชัน เมื่อเราต้องการที่จะเรียกฟังก์ชั่นโดยไม่ขัดแย้งใด ๆ function_name()ที่เราต้องทำ

ฉันคิดว่ามันแปลกที่คอมไพเลอร์หรือสคริปต์ล่ามจะต้อง()ประสบความสำเร็จในการตรวจสอบว่าเป็นการเรียกใช้ฟังก์ชัน หากตัวแปรรู้จักกันว่า callable ทำไมถึงไม่function_name;พอ?

ในบางภาษาเราสามารถทำได้: function_name 'test';หรือแม้แต่function_name 'first' 'second';เรียกฟังก์ชั่นหรือคำสั่ง

ฉันคิดว่าวงเล็บจะดีกว่านี้หากพวกเขาต้องการเพียงแค่ประกาศลำดับความสำคัญและในสถานที่อื่นเป็นทางเลือก ตัวอย่างเช่นการทำif expression == true function_name;ควรถูกต้องตามที่if (expression == true) function_name();ควร

สิ่งที่น่ารำคาญที่สุดในความคิดของฉันคือทำ'SOME_STRING'.toLowerCase()เมื่อไม่มีข้อโต้แย้งที่ชัดเจนโดยฟังก์ชันต้นแบบ ทำไมนักออกแบบถึงเลือกคนที่เรียบง่าย'SOME_STRING'.lower?

คำเตือน:อย่าเข้าใจฉันผิดฉันชอบไวยากรณ์ C-like! ;) ฉันแค่ถามว่ามันจะดีกว่า ต้อง()มีข้อได้เปรียบด้านประสิทธิภาพหรือไม่หรือทำให้เข้าใจรหัสได้ง่ายขึ้น? ฉันอยากรู้จริงๆว่าเหตุผลคืออะไร


103
คุณจะทำอย่างไรถ้าคุณต้องการส่งผ่านฟังก์ชันเป็นอาร์กิวเมนต์ไปยังฟังก์ชันอื่น
Vincent Savard

30
ตราบใดที่มนุษย์ยังต้องการรหัสอ่านความสามารถในการอ่านก็คือราชา
Tulains Córdova

75
นั่นคือสิ่งที่มีความสามารถในการอ่านที่คุณถามเกี่ยวกับ()แต่สิ่งที่โดดเด่นในโพสต์ของคุณคือif (expression == true)คำสั่ง คุณกังวลเกี่ยวกับการฟุ่มเฟือย()'s แต่แล้วใช้ฟุ่มเฟือย== true:)
เดวิดอาร์โน

15
Visual Basic อนุญาตสิ่งนี้
edc65

14
มันเป็นข้อบังคับใน Pascal คุณใช้ parens สำหรับฟังก์ชั่นและโพรซีเดอร์ที่รับอาร์กิวเมนต์เท่านั้น
RemcoGerlich

คำตอบ:


251

สำหรับภาษาที่ใช้ฟังก์ชั่นชั้นหนึ่งมันเป็นเรื่องธรรมดามากที่ไวยากรณ์ของการอ้างถึงฟังก์ชั่นคือ:

a = object.functionName

ในขณะที่การเรียกฟังก์ชันนั้นคือ:

b = object.functionName()

aในตัวอย่างข้างต้นจะอ้างอิงถึงฟังก์ชั่นดังกล่าวข้างต้น (และคุณสามารถเรียกมันได้โดยการทำa()) ในขณะที่bจะมีค่าตอบแทนของฟังก์ชั่น

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


9
คุณอาจต้องการพูดถึงว่าความแตกต่างนี้น่าสนใจเฉพาะเมื่อมีผลข้างเคียง - สำหรับฟังก์ชั่นที่บริสุทธิ์ไม่สำคัญ
Bergi

73
@Bergi: มันสำคัญมากสำหรับฟังก์ชั่นที่บริสุทธิ์! ถ้าฉันมีฟังก์ชั่นmake_listที่แพ็คอาร์กิวเมนต์ของมันลงในรายการมันมีความแตกต่างอย่างมากระหว่างการf(make_list)ส่งผ่านฟังก์ชันไปยังfหรือผ่านรายการที่ว่างเปล่า
user2357112

12
@Bergi: ถึงอย่างนั้นฉันก็ยังต้องการที่จะผ่านSATInstance.solveหรือฟังก์ชั่นบริสุทธิ์ที่มีราคาแพงอย่างไม่น่าเชื่อไปยังฟังก์ชั่นอื่นโดยไม่ต้องพยายามเรียกใช้ทันที นอกจากนี้ยังทำให้สิ่งต่าง ๆ ที่น่าอึดอัดใจจริงๆถ้าsomeFunctionทำสิ่งที่แตกต่างกันขึ้นอยู่กับว่าsomeFunctionมีการประกาศว่าจะบริสุทธิ์ ตัวอย่างเช่นถ้าผมมี (pseudocode) func f() {return g}, func g() {doSomethingImpure}และfunc apply(x) {x()}แล้วโทรออกapply(f) fถ้าฉันประกาศว่าfบริสุทธิ์แล้วก็apply(f)ผ่านgไปapplyและapplyโทรgและทำสิ่งที่ไม่บริสุทธิ์
user2357112

6
@ user2357112 กลยุทธ์การประเมินเป็นอิสระจากความบริสุทธิ์ การใช้ไวยากรณ์ของการโทรเป็นคำอธิบายประกอบเมื่อประเมินสิ่งที่เป็นไปได้ (แต่อาจจะน่าอึดอัดใจ) อย่างไรก็ตามมันจะไม่เปลี่ยนผลลัพธ์หรือความถูกต้องของมัน เกี่ยวกับตัวอย่างของคุณapply(f)ถ้าgไม่บริสุทธิ์ (และapplyเช่นกัน) จากนั้นสิ่งทั้งปวงนั้นไม่บริสุทธิ์และพังทลายลงแน่นอน
Bergi

14
ตัวอย่างของสิ่งนี้คือ Python และ ECMAScript ซึ่งทั้งสองอย่างไม่มีแนวคิดหลักของ "เมธอด" แต่คุณมีคุณสมบัติที่มีชื่อซึ่งจะส่งคืนออบเจ็กต์ฟังก์ชั่นชั้นหนึ่งที่ไม่ระบุชื่อ โดยเฉพาะอย่างยิ่งในทั้ง Python และ ECMAScript การพูดfoo.bar()ไม่ใช่การดำเนินการหนึ่งเดียว "การเรียกวิธีการbarของวัตถุfoo" แต่เป็นการดำเนินการสองอย่าง "การตรวจสอบเขตข้อมูลbarของวัตถุfoo จากนั้นเรียกวัตถุที่ส่งคืนจากเขตข้อมูลนั้น" ดังนั้น.เป็นผู้ประกอบการเขตเลือกและ()เป็นผู้ประกอบการโทรและพวกเขามีความเป็นอิสระ
Jörg W Mittag

63

ที่จริงสกาล่าอนุญาตให้ทำเช่นนี้แม้ว่าจะมีการประชุมที่ปฏิบัติตาม: หากวิธีการมีผลข้างเคียง, วงเล็บควรจะใช้ต่อไป

ในฐานะนักเขียนคอมไพเลอร์ฉันจะพบว่าการมีวงเล็บอยู่ในวงเล็บค่อนข้างสะดวก ฉันมักจะรู้ว่านั่นคือการเรียกใช้เมธอดและฉันไม่จำเป็นต้องสร้างในแฉกสำหรับกรณีแปลก ๆ

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

การส่งผ่านพารามิเตอร์ไม่ได้เป็นลักษณะการกำหนดเพียงอย่างเดียวของการเรียกเมธอด ทำไมฉันถึงจัดการกับวิธีที่ไม่มีพารามิเตอร์ต่างจากวิธีที่มีพารามิเตอร์


ขอขอบคุณและคุณให้เหตุผลที่ถูกต้องว่าทำไมสิ่งนี้จึงสำคัญ คุณสามารถยกตัวอย่างว่าทำไมนักออกแบบภาษาควรใช้ฟังก์ชั่นในต้นแบบ?
David Refoua

การปรากฏตัวของวงเล็บทำให้ไม่ต้องสงสัยเลยว่ามันเป็นวิธีการที่เรียกว่าฉันเห็นด้วยอย่างสมบูรณ์ ฉันต้องการภาษาการเขียนโปรแกรมของฉันชัดเจนกว่าโดยนัย
Corey Ogburn

20
จริง ๆ แล้ว Scala นั้นมีความลึกซึ้งมากกว่านั้นเล็กน้อย: ในขณะที่ภาษาอื่นอนุญาตให้รายการพารามิเตอร์หนึ่งรายการที่มีพารามิเตอร์เป็นศูนย์หรือมากกว่าเท่านั้น Scala อนุญาตให้รายการพารามิเตอร์เป็นศูนย์หรือมากกว่านั้นมีพารามิเตอร์เป็นศูนย์หรือมากกว่า ดังนั้นdef foo()เป็นวิธีที่มีรายการพารามิเตอร์ว่างหนึ่งรายการและdef fooเป็นวิธีที่ไม่มีรายการพารามิเตอร์และทั้งสองเป็นสิ่งที่แตกต่างกัน ! โดยทั่วไปจะต้องเรียกใช้เมธอดที่ไม่มีรายการพารามิเตอร์โดยไม่มีรายการอาร์กิวเมนต์และวิธีที่มีรายการพารามิเตอร์ว่างจะต้องถูกเรียกด้วยรายการอาร์กิวเมนต์ว่าง การเรียกใช้เมธอดที่ไม่มีรายการพารามิเตอร์ที่มีอาร์กิวเมนต์ว่างเป็นจริง…
Jörg W Mittag

19
…ตีความว่าเป็นการเรียกapplyเมธอดของออบเจ็กต์ที่ส่งคืนโดยเมธอด call ด้วยรายการอาร์กิวเมนต์ว่างขณะที่เรียกเมธอดที่มีรายการพารามิเตอร์ว่างโดยไม่มีรายการอาร์กิวเมนต์อาจขึ้นอยู่กับบริบท expansion ขยายเป็นวิธีที่นำไปใช้บางส่วน (เช่น "การอ้างอิงวิธีการ") หรือมันอาจจะไม่รวบรวมเลย นอกจากนี้ Scala ยังปฏิบัติตามหลักการเข้าถึงเครื่องแบบโดยไม่แยกแยะระหว่างการเรียกใช้เมธอดโดยไม่มีรายการอาร์กิวเมนต์และการอ้างอิงฟิลด์
Jörg W Mittag

3
แม้ในขณะที่ฉันมาชื่นชมทับทิมสำหรับกรณีที่ไม่มีการต้อง "พิธี" - การ parens วงเล็บประเภทอย่างชัดเจน - ฉันสามารถบอกได้ว่าวิธีการ-วงเล็บอ่านได้เร็วขึ้น ; แต่เมื่อคุ้นเคยสำนวน Ruby ก็สามารถอ่านและเขียนได้เร็วขึ้นแน่นอน
Radarbob

19

อันที่จริงแล้วนี่เป็นความบังเอิญที่ค่อนข้างละเอียดของตัวเลือกไวยากรณ์ ฉันจะพูดกับภาษาที่ใช้งานได้ซึ่งขึ้นอยู่กับแคลคูลัสแลมบ์ดาที่พิมพ์

ในภาษาดังกล่าวทุกฟังก์ชั่นมีอาร์กิวเมนต์เดียว สิ่งที่เรามักคิดว่าเป็น "ข้อโต้แย้งหลาย ๆ " เป็นจริงพารามิเตอร์เดียวของประเภทผลิตภัณฑ์ ตัวอย่างเช่นฟังก์ชันที่เปรียบเทียบจำนวนเต็มสองจำนวน:

leq : int * int -> bool
leq (a, b) = a <= b

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

leq some_pair = some_pair.1 <= some_pair.2

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

ฟังก์ชั่นที่ฟังดูเหมือนจะไม่มีข้อโต้แย้งล่ะ? Unitเช่นฟังก์ชั่นจริงมีประสิทธิภาพ สมาชิกรายเดียวของUnitมักจะเขียนเป็น()ดังนั้นจึงเป็นเหตุผลที่วงเล็บปรากฏ

say_hi : Unit -> string
say_hi a = "Hi buddy!"

ที่จะเรียกฟังก์ชั่นนี้เราจะต้องนำไปใช้กับค่าของประเภทUnitซึ่งจะต้องเป็นดังนั้นเราจึงจบลงด้วยการเขียน()say_hi ()

ดังนั้นจึงไม่มีสิ่งเช่นรายการข้อโต้แย้ง!


3
และนั่นคือเหตุผลที่วงเล็บว่างเปล่า()ดูเหมือนกับช้อนลบที่จับ
Mindwin

3
การกำหนดleqฟังก์ชั่นที่ใช้<แทนที่จะ<=ทำให้สับสน!
KRyan

5
คำตอบนี้อาจเข้าใจง่ายขึ้นหากใช้คำว่า "tuple" เพิ่มเติมจากวลีเช่น "ประเภทผลิตภัณฑ์"
Kevin

พิธีการส่วนใหญ่จะปฏิบัติกับ int f (int x, int y) เป็นฟังก์ชั่นจาก I ^ 2 ถึง I แลมบ์ดาแลมบ์ดานั้นแตกต่างกันมันไม่ได้ใช้วิธีนี้เพื่อจับคู่สิ่งที่อยู่ในรูปแบบอื่น ๆ แคลคูลัสแลมบ์ดาใช้วิธีการแกง การเปรียบเทียบจะเป็น x -> (y -> (x <= y)) (x -> (y -> (x <= y)) 2 จะประเมินเป็น (y-> 2 <= y) และ (x -> (y -> (x <= y)) 2 3 จะประเมินเป็น ( y-> 2 <= y) 3 ซึ่งจะประเมินเป็น 2 <= 3
Taemyr

1
@PeriataBreatta ฉันไม่คุ้นเคยกับประวัติการใช้()เพื่อเป็นตัวแทนของหน่วย ฉันจะไม่แปลกใจถ้าอย่างน้อยส่วนหนึ่งของเหตุผลก็คล้ายกับ "ฟังก์ชั่นไร้พารามิเตอร์" อย่างไรก็ตามมีคำอธิบายอื่นที่เป็นไปได้สำหรับไวยากรณ์ ในพีชคณิตของชนิดUnitในความเป็นจริงเป็นตัวตนของประเภทผลิตภัณฑ์ ผลิตภัณฑ์ไบนารี่เขียนเป็น(a,b)เราสามารถเขียนผลิตภัณฑ์เอกนารี(a)ได้ดังนั้นส่วนขยายแบบลอจิคัลจะเป็นการเขียนผลิตภัณฑ์โมฆะอย่างง่าย()
Gardenhead

14

ใน Javascript เช่นการใช้ชื่อเมธอด without () จะส่งคืนฟังก์ชันโดยไม่ต้องดำเนินการ วิธีนี้คุณสามารถส่งผ่านฟังก์ชันเป็นอาร์กิวเมนต์ไปยังวิธีอื่นได้

ใน Java มีการเลือกตัวระบุที่ตามด้วย () หรือ (... ) หมายถึงการเรียกเมธอดขณะที่ตัวระบุที่ไม่มี () หมายถึงตัวแปรสมาชิก สิ่งนี้อาจปรับปรุงความสามารถในการอ่านได้เนื่องจากคุณไม่ต้องสงสัยเลยว่าคุณกำลังเผชิญกับวิธีการหรือตัวแปรสมาชิก ในความเป็นจริงชื่อเดียวกันสามารถใช้ทั้งสำหรับวิธีการและตัวแปรสมาชิกทั้งสองจะสามารถเข้าถึงได้ด้วยไวยากรณ์ที่เกี่ยวข้อง


4
+1 ตราบใดที่มนุษย์จำเป็นต้องใช้รหัสในการอ่านการอ่านก็เป็นสิ่งที่สำคัญ
Tulains Córdova

1
@ TulainsCórdovaฉันไม่แน่ใจว่า perl เห็นด้วยกับคุณ
Racheet

@Racheet: Perl อาจไม่ได้ แต่Perl Best Practicesไม่
slebetman

1
@Reeteet Perl สามารถอ่านได้มากถ้ามันเขียนเพื่อให้สามารถอ่านได้ นั่นเป็นสิ่งเดียวกันสำหรับทุก ๆ ภาษาที่ไม่มีความลับ สั้นกระชับ Perl สามารถอ่านได้มากโปรแกรมเมอร์ Perl เช่นเดียวกับรหัส Scala หรือ Haskell สามารถอ่านได้กับคนที่มีประสบการณ์ในภาษาเหล่านั้น ฉันยังสามารถพูดภาษาเยอรมันกับคุณในลักษณะที่ซับซ้อนมากซึ่งถูกต้องตามหลักไวยากรณ์อย่างสมบูรณ์ แต่คุณก็ยังไม่เข้าใจคำศัพท์แม้ว่าคุณจะเชี่ยวชาญภาษาเยอรมันก็ตาม นั่นไม่ใช่ความผิดของเยอรมันเช่นกัน ;)
simbabque

ในจาวามันไม่ได้ "ระบุ" วิธีการ มันเรียกมันว่า Object::toStringระบุวิธีการ
njzk2

10

ไวยากรณ์ตามความหมายดังนั้นเรามาเริ่มจากความหมาย:

วิธีการใช้ฟังก์ชั่นหรือวิธีการคืออะไร?

มีหลายวิธีจริง:

  • ฟังก์ชั่นสามารถเรียกใช้โดยมีหรือไม่มีข้อโต้แย้ง
  • ฟังก์ชั่นสามารถถือเป็นค่า
  • ฟังก์ชั่นสามารถนำไปใช้บางส่วน (การสร้างการปิดโดยใช้อาร์กิวเมนต์อย่างน้อยหนึ่งข้อและปิดมากกว่าอาร์กิวเมนต์ที่ผ่าน)

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

ในภาษา C และ C-like:

  • การเรียกใช้ฟังก์ชันทำได้โดยใช้วงเล็บเพื่อใส่อาร์กิวเมนต์ (อาจไม่มี) ในรูปแบบ func()
  • การรักษาฟังก์ชั่นเป็นค่าสามารถทำได้โดยเพียงแค่ใช้ชื่อเป็นใน&func(C การสร้างตัวชี้ฟังก์ชั่น)
  • ในบางภาษาคุณมีซินแทกซ์สั้น ๆ สำหรับการใช้งานบางส่วน Java อนุญาตให้someVariable::someMethodใช้ตัวอย่าง (จำกัด ตัวรับเมธอด แต่ยังมีประโยชน์)

สังเกตว่าการใช้งานแต่ละรูปแบบมีรูปแบบที่แตกต่างกันอย่างไรเพื่อให้คุณสามารถแยกได้อย่างง่ายดาย


2
ดี "ง่าย" เป็นหนึ่งในนั้น "ชัดเจนว่าถ้าเข้าใจแล้ว" สถานการณ์ ผู้เริ่มต้นจะได้รับการพิสูจน์ในการสังเกตว่ามันไม่มีความชัดเจนโดยไม่มีคำอธิบายว่าทำไมเครื่องหมายวรรคตอนบางอย่างจึงมีความหมาย
Eric Lippert

2
@EricLippert: อันที่จริงแล้วความง่ายดายไม่ได้แปลว่าเป็นเรื่องง่าย แม้ว่าในกรณีนี้ฉันจะชี้ให้เห็นว่าวงเล็บยังใช้สำหรับฟังก์ชั่น "การขอร้อง" ในวิชาคณิตศาสตร์ ที่ถูกกล่าวว่าฉันได้พบผู้เริ่มต้นในหลายภาษาที่ดิ้นรนกับแนวคิด / ความหมายมากกว่าไวยากรณ์โดยทั่วไป
Matthieu M.

คำตอบนี้สับสนความแตกต่างระหว่าง "currying" และ "แอพลิเคชันบางส่วน" ตัว::ดำเนินการใน Java เป็นตัวดำเนินการแอปพลิเคชันบางส่วนไม่ใช่ตัวดำเนินการ currying แอปพลิเคชันบางส่วนเป็นการดำเนินการที่รับฟังก์ชั่นหนึ่งค่าหรือมากกว่านั้นและส่งคืนฟังก์ชันที่ยอมรับค่าที่น้อยลง การ Currying ทำงานเฉพาะกับฟังก์ชั่นเท่านั้นไม่ใช่ค่า
Periata Breatta

@PeriataBreatta: จุดที่ดีได้รับการแก้ไข
Matthieu M.

8

ในภาษาที่มีผลข้างเคียง IMO มีประโยชน์มากในการแยกแยะความแตกต่างระหว่างการอ่าน (ในทฤษฎีข้างเคียงฟรี) ของตัวแปร

variable

และการเรียกฟังก์ชันซึ่งอาจทำให้เกิดผลข้างเคียง

launch_nukes()

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


1
โปรดทราบว่า OP แสดงกรณีที่วัตถุเป็นอาร์กิวเมนต์เท่านั้นและจะนำเสนอก่อนชื่อฟังก์ชั่น (ซึ่งยังมีขอบเขตสำหรับชื่อฟังก์ชั่น) noun.verbไวยากรณ์อาจมีความชัดเจนในความหมายเป็นnoun.verb()และน้อยรกเล็กน้อย ในทำนองเดียวกันฟังก์ชั่นที่member_ส่งคืนการอ้างอิงด้านซ้ายอาจมีไวยากรณ์ที่เป็นมิตรมากกว่าฟังก์ชั่น setter ทั่วไป: obj.member_ = new_valuevs. obj.set_member(new_value)( _postfix เป็นคำใบ้ที่บอกว่า "เปิดเผย" เตือนความทรงจำของ_ คำนำหน้าสำหรับฟังก์ชั่น "ซ่อน")
Paul A. Clayton

1
@ PaulA.Clayton ฉันไม่เห็นว่าคำตอบของฉันไม่ครอบคลุม: ถ้าnoun.verbหมายถึงการเรียกใช้ฟังก์ชันคุณจะแยกความแตกต่างจากการเข้าถึงสมาชิกได้อย่างไร
Daniel Jour

1
ในเรื่องที่เกี่ยวกับการอ่านตัวแปรที่ไม่มีผลข้างเคียงในทางทฤษฎีผมแค่อยากจะบอกว่า: ระวังเพื่อนที่เป็นเท็จเสมอ
Justin เวลา

8

ไม่มีคำตอบอื่นใดที่พยายามจัดการกับคำถาม: ควรมีความซ้ำซ้อนในการออกแบบภาษามากน้อยเพียงใด เพราะแม้ว่าคุณจะสามารถออกแบบภาษาเพื่อที่ให้x = sqrt yx เป็นรากที่สองของ y นั่นไม่ได้หมายความว่าคุณควรจะทำ

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

ความซ้ำซ้อนยังช่วยให้อ่านง่ายเพราะมีเงื่อนงำมากขึ้น ภาษาอังกฤษที่ไม่มีความซ้ำซ้อนจะอ่านยากมากและเช่นเดียวกันกับภาษาโปรแกรม


ในแง่หนึ่งฉันเห็นด้วย: ภาษาการเขียนโปรแกรมควรมีกลไก "ตาข่ายนิรภัย" แต่ฉันไม่เห็นด้วยว่า "ความซ้ำซ้อนเล็กน้อย" ในไวยากรณ์เป็นวิธีที่ดีในการทำเช่นนี้ - นั่นคือต้นแบบและสิ่งที่ส่วนใหญ่ทำคือแนะนำเสียงที่ทำให้เกิดข้อผิดพลาดที่ยากต่อการมองเห็นและเปิดโอกาสสำหรับข้อผิดพลาดทางไวยากรณ์ ข้อบกพร่องจริง ชนิดของฟุ่มเฟื่อยที่ช่วยการอ่านมีน้อยจะทำอย่างไรกับไวยากรณ์มากขึ้นด้วยการตั้งชื่อ และเครือข่ายความปลอดภัยนั้นถูกนำไปใช้อย่างมีประสิทธิภาพมากที่สุดในฐานะระบบพิมพ์ที่แข็งแกร่งซึ่งการเปลี่ยนแปลงแบบสุ่มส่วนใหญ่จะทำให้โปรแกรมพิมพ์ผิด
leftaroundabout

@leftaroundabout: ฉันชอบคิดเกี่ยวกับการตัดสินใจออกแบบภาษาในแง่ของระยะทาง Hamming หากสิ่งปลูกสร้างทั้งสองมีความหมายต่างกันก็มักจะช่วยให้พวกเขาแตกต่างกันอย่างน้อยสองวิธีและมีรูปแบบที่แตกต่างในทางเดียวเท่านั้นที่จะสร้างคอมไพล์เลอร์ได้ ออกแบบภาษาโดยทั่วไปไม่สามารถรับผิดชอบต่อการสร้างความมั่นใจว่าตัวบ่งชี้มีความแตกต่างได้อย่างง่ายดาย แต่ถ้าเช่นได้รับมอบหมายต้อง:=และการเปรียบเทียบ==กับ=เพียงอย่างเดียวใช้งานได้สำหรับการเริ่มต้นรวบรวมเวลาแล้วน่าจะเป็นของความผิดพลาดการเปลี่ยนรถในการกำหนดจะได้รับการลดลงอย่างมาก
supercat

@leftaroundabout: ในทำนองเดียวกันถ้าผมกำลังออกแบบภาษาที่ผมอาจจะต้องใช้()สำหรับการภาวนาของฟังก์ชั่นเป็นศูนย์การโต้แย้ง แต่ยังจำเป็นต้องมีสัญญาณที่จะ "เอาอยู่ของ" ฟังก์ชั่น การกระทำก่อนหน้านี้เป็นเรื่องธรรมดามากขึ้นดังนั้นการบันทึกโทเค็นในกรณีที่พบน้อยกว่านั้นไม่ได้มีประโยชน์ทั้งหมด
supercat

@supercat: ดีอีกครั้งฉันคิดว่านี่เป็นการแก้ปัญหาในระดับที่ไม่ถูกต้อง การมอบหมายและการเปรียบเทียบเป็นการดำเนินงานที่แตกต่างกันตามแนวคิดดังนั้นการประเมินฟังก์ชั่นและการใช้ฟังก์ชั่นตามลำดับ ดังนั้นพวกเขาควรมีประเภทที่แตกต่างกันอย่างชัดเจนจากนั้นก็ไม่สำคัญอีกต่อไปหากผู้ประกอบการมีระยะทางที่ต่ำ Hamming เพราะการพิมพ์ผิดใด ๆ จะเป็นข้อผิดพลาดประเภทการรวบรวมเวลา
leftaroundabout

@leftaroundabout: หากมีการกำหนดตัวแปรแบบบูลหรือถ้าประเภทการคืนค่าของฟังก์ชันเป็นตัวชี้ไปยังฟังก์ชัน (หรือ - ในบางภาษา - ฟังก์ชันจริง) แทนที่การเปรียบเทียบกับการมอบหมาย หรือการเรียกใช้ฟังก์ชั่นที่มีการอ้างอิงฟังก์ชั่นอาจทำให้โปรแกรมที่ถูกต้อง syntactically แต่มีความหมายที่แตกต่างกันโดยสิ้นเชิงจากรุ่นเดิม การตรวจสอบชนิดจะพบข้อผิดพลาดบางอย่างโดยทั่วไป แต่การทำให้รูปแบบไวยากรณ์แตกต่างกันดูเหมือนว่าเป็นวิธีที่ดีกว่า
supercat

5

ในกรณีส่วนใหญ่สิ่งเหล่านี้เป็นทางเลือกทางไวยากรณ์ของไวยากรณ์ของภาษา มันจะมีประโยชน์สำหรับไวยากรณ์สำหรับการสร้างของแต่ละบุคคลที่จะ (ค่อนข้าง) โปร่งใสเมื่อนำมารวมกัน (หากมีความคลุมเครือเช่นในการประกาศ C ++ บางรายการจะต้องมีกฎเฉพาะสำหรับการแก้ปัญหา) คอมไพเลอร์ไม่มีละติจูดสำหรับการเดา จำเป็นต้องปฏิบัติตามข้อกำหนดทางภาษา


Visual Basic ในรูปแบบต่าง ๆ แยกความแตกต่างระหว่างโพรซีเดอร์ที่ไม่ส่งคืนค่าและฟังก์ชั่น

ขั้นตอนจะต้องเรียกว่าเป็นคำสั่งและไม่จำเป็นต้องมี parens เพียงแค่คั่นด้วยเครื่องหมายจุลภาคคั่นหากมี ฟังก์ชั่นจะต้องเรียกว่าเป็นส่วนหนึ่งของการแสดงออกและต้องมีการล้อเลียน

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

(ในทางตรงกันข้าม Visual Basic ใช้ parens เดียวกัน()สำหรับการอ้างอิงในอาร์เรย์เป็นสำหรับการโทรฟังก์ชั่นเพื่อการอ้างอิงอาร์เรย์มีลักษณะเช่นฟังก์ชั่นการโทร. และนี่ก้อ refactoring คู่มือของอาร์เรย์เป็นโทรหน้าที่ดังนั้นเราสามารถไตร่ตรองภาษาอื่น ๆ ที่ใช้[]'s สำหรับการอ้างอิงอาร์เรย์ แต่ฉันเชือนแช ... )


ในภาษา C และ C ++ เนื้อหาของตัวแปรจะถูกเข้าถึงโดยอัตโนมัติโดยใช้ชื่อของพวกเขาและหากคุณต้องการอ้างถึงตัวแปรเองแทนที่จะเป็นเนื้อหาของมันคุณจะต้องใช้&โอเปอเรเตอร์unary

กลไกประเภทนี้สามารถใช้กับชื่อฟังก์ชันได้ ชื่อฟังก์ชั่นดิบอาจบ่งบอกถึงการเรียกใช้ฟังก์ชั่นในขณะที่&ผู้ประกอบการยูนารีจะใช้ในการอ้างถึงฟังก์ชั่น (ตัวเอง) เป็นข้อมูล โดยส่วนตัวแล้วฉันชอบความคิดในการเข้าถึงฟังก์ชั่นไม่มีข้อโต้แย้งที่ไม่มีผลข้างเคียงพร้อมกับไวยากรณ์เดียวกับตัวแปร

นี่เป็นไปได้อย่างสมบูรณ์แบบ (เช่นเดียวกับตัวเลือกประโยคอื่น ๆ สำหรับเรื่องนี้)


2
แน่นอนในขณะที่ยังขาดภาพขั้นพื้นฐานของวงเล็บในการโทรขั้นตอนเป็นความแตกต่างที่ไม่จำเป็นในการเปรียบเทียบกับสายฟังก์ชั่นที่เพิ่มวงเล็บที่จำเป็นให้กับพวกเขาจะเป็นความแตกต่างที่ไม่จำเป็นระหว่างงบหลายแห่งซึ่งเป็นภาษาพื้นฐานที่ได้มามีผลกระทบที่มีมาก เตือนความทรงจำของขั้นตอน เป็นเวลานานแล้วที่ฉันทำงานใน MS BASIC เวอร์ชันใด แต่มันยังไม่อนุญาตให้ใช้ไวยากรณ์call <procedure name> (<arguments>)หรือไม่ พริตตี้ให้แน่ใจว่าเป็นวิธีที่ถูกต้องของการใช้วิธีการกลับมาเมื่อฉันได้เขียนโปรแกรม QuickBASIC ในชีวิตอื่น ...
Periata Breatta

1
@PerataBreatta: VB ยังคงอนุญาตให้ใช้ "การเรียก" ไวยากรณ์และบางครั้งก็จำเป็นต้องใช้ในกรณีที่สิ่งที่จะเรียกว่าเป็นนิพจน์ [เช่นหนึ่งสามารถพูดว่า "Call If (someCondition, Delegate1, Delegate2) (อาร์กิวเมนต์)" โดยตรง การเรียกใช้ผลลัพธ์ของโอเปอเรเตอร์ "If" จะไม่ถูกต้องตามคำสั่งแบบสแตนด์อโลน]
supercat

@PerataBreatta ฉันชอบ VB6 ไม่มีการเรียกโพรเซสวงเล็บปีกกาเพราะมันทำรหัสเหมือน DSL ดูดี
Mark Hurd

@supercat ใน LinqPad มันวิเศษมากที่ฉันต้องใช้Callสำหรับวิธีการที่ฉันแทบไม่จำเป็นต้องใช้ในรหัสการผลิต "ปกติ"
Mark Hurd

5

ความสอดคล้องและการอ่าน

ถ้าฉันได้เรียนรู้ว่าคุณเรียกใช้ฟังก์ชันXเช่นนี้แล้วผมคาดว่ามันจะทำงานในลักษณะเดียวกันสำหรับการขัดแย้งใด:X(arg1, arg2, ...)X()

ในเวลาเดียวกันฉันเรียนรู้ว่าคุณสามารถกำหนดตัวแปรเป็นสัญลักษณ์บางอย่างและใช้มันดังนี้:

a = 5
b = a
...

ตอนนี้ฉันจะคิดอย่างไรเมื่อพบสิ่งนี้

c = X

การเดาของคุณดีเท่ากับของฉัน ในสถานการณ์ปกติXตัวแปรคือ แต่ถ้าเราต้องใช้เส้นทางของคุณมันอาจเป็นฟังก์ชั่น! ฉันต้องจดจำหรือไม่ว่าสัญลักษณ์ใดแมปกับกลุ่มใด (ตัวแปร / ฟังก์ชั่น)?

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

หมายเหตุด้านข้าง 1: คำตอบอื่น ๆ ไม่สนใจอีกสิ่งหนึ่งอย่างสมบูรณ์: ภาษาอาจอนุญาตให้คุณใช้ชื่อเดียวกันสำหรับทั้งฟังก์ชั่นและตัวแปรและแยกความแตกต่างตามบริบท ดูCommon Lispเป็นตัวอย่าง ฟังก์ชั่นxและการxอยู่ร่วมกันของตัวแปรปรับได้อย่างสมบูรณ์แบบ

ด้านข้างหมายเหตุ 2: object.functionnameคำตอบที่ได้รับการยอมรับแสดงให้เราเห็นไวยากรณ์: ประการแรกมันไม่สากลสำหรับภาษาที่มีฟังก์ชั่นชั้นหนึ่ง ประการที่สองเป็นโปรแกรมเมอร์ฉันจะรักษานี้เป็นข้อมูลเพิ่มเติม: เป็นของfunctionname objectไม่ว่าจะobjectเป็นวัตถุชั้นเรียนหรือ namespace ที่ไม่สำคัญมาก แต่มันก็บอกว่ามันเป็นที่ไหนสักแห่ง ซึ่งหมายความว่าคุณต้องเพิ่มซินแท็กซ์object.สำหรับแต่ละฟังก์ชั่นระดับโลกหรือสร้างบางส่วนobjectเพื่อให้ฟังก์ชั่นทั่วโลกทั้งหมด

และไม่ว่าด้วยวิธีใดคุณจะสูญเสียความสามารถในการแยกเนมสเปซสำหรับฟังก์ชั่นและตัวแปร


ได้. ความสอดคล้องเป็นเหตุผลที่ดีพอหยุดเต็ม (ไม่ใช่ว่าจุดอื่น ๆ ของคุณไม่ถูกต้อง - เป็น)
Matthew อ่าน

4

หากต้องการเพิ่มคำตอบอื่น ๆ ให้ใช้ตัวอย่าง C นี้:

void *func_factory(void)
{
        return 0;
}

void *(*ff)(void);

void example()
{
        ff = func_factory;
        ff = func_factory();
}

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

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


3
จาวาสคริปต์ไม่ได้ขาดระบบประเภท มันขาดประเภทประกาศ แต่มันก็ตระหนักถึงความแตกต่างระหว่างประเภทของค่าที่แตกต่างกัน
Periata Breatta

1
Periata Breatta: ประเด็นก็คือตัวแปรและคุณสมบัติของ Java อาจมีประเภทของค่าใด ๆ ดังนั้นคุณจึงไม่สามารถรู้ได้เสมอว่ามันเป็นฟังก์ชั่นในการอ่านโค้ดหรือไม่
reinierpost

ตัวอย่างเช่นฟังก์ชั่นนี้ทำอะไร? function cross(a, b) { return a + b; }
Mark K Cowan

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