เหตุใดภาษาการเขียนโปรแกรมส่วนใหญ่จึงมีคำหลักหรือไวยากรณ์พิเศษสำหรับการประกาศฟังก์ชั่น [ปิด]


39

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

ตัวอย่างเช่นใน Python:

x = 2
y = addOne(x)
def addOne(number): 
  return number + 1

ทำไมจะไม่ล่ะ:

x = 2
y = addOne(x)
addOne = (number) => 
  return number + 1

ในทำนองเดียวกันในภาษาเช่น Java:

int x = 2;
int y = addOne(x);

int addOne(int x) {
  return x + 1;
}

ทำไมจะไม่ล่ะ:

int x = 2;
int y = addOne(x);
(int => int) addOne = (x) => {
  return x + 1;
}

ไวยากรณ์นี้ดูเหมือนเป็นธรรมชาติมากขึ้นในการประกาศบางสิ่งบางอย่าง (ไม่ว่าจะเป็นฟังก์ชั่นหรือตัวแปร) และคำหลักที่น้อยกว่าเช่นdefหรือfunctionในบางภาษา และ IMO ก็มีความสอดคล้องกันมากขึ้น (ฉันมองในที่เดียวกันเพื่อทำความเข้าใจกับชนิดของตัวแปรหรือฟังก์ชั่น) และอาจทำให้ parser / ไวยากรณ์ง่ายขึ้นในการเขียน

ฉันรู้ว่าภาษาน้อยมากที่ใช้ความคิดนี้ (CoffeeScript, Haskell) แต่ภาษาส่วนใหญ่มีไวยากรณ์พิเศษสำหรับฟังก์ชั่น (Java, C ++, Python, JavaScript, C #, PHP, Ruby)

แม้ใน Scala ซึ่งรองรับทั้งสองวิธี (และมีการอนุมานประเภท) มันเป็นเรื่องธรรมดามากที่จะเขียน:

def addOne(x: Int) = x + 1

ค่อนข้างมากกว่า:

val addOne = (x: Int) => x + 1

IMO, atleast ใน Scala นี่อาจเป็นเวอร์ชั่นที่เข้าใจง่ายที่สุด แต่สำนวนนี้ไม่ค่อยจะตามมา:

val x: Int = 1
val y: Int = addOne(x)
val addOne: (Int => Int) = x => x + 1

ฉันทำงานเกี่ยวกับภาษาของเล่นของตัวเองและฉันสงสัยว่ามีข้อผิดพลาดใด ๆ หรือไม่หากฉันออกแบบภาษาของฉันด้วยวิธีการดังกล่าวและหากมีเหตุผลทางประวัติศาสตร์หรือทางเทคนิค


9
เหตุผลน่าจะเป็นประวัติศาสตร์ ใครก็ตามที่ทำก่อนก็ทำอย่างนั้นและทุกคนอื่นคัดลอก แต่ฉันสงสัยว่าเราสามารถรู้ได้อย่างแน่นอน

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

15
ฉันไม่เห็นด้วยว่าข้อเสนอของคุณเป็นวิธีที่เป็นธรรมชาติกว่าในการประกาศฟังก์ชั่น ฉันไม่ชอบไวยากรณ์ของ Coffeescript จริง ๆ และส่วนหนึ่งนั้นเกี่ยวข้องกับไวยากรณ์การประกาศฟังก์ชันของมัน ความจริงก็คือถ้ามันไม่พังไม่ได้แก้ไข การเขียน 'def' หรือ 'ฟังก์ชั่น' ไม่ใช่เรื่องใหญ่และเป็นวิธีที่ชัดเจนกว่าเมื่อเปรียบเทียบกับสัญลักษณ์ที่เหมือน Perl อันแฟนซีซึ่งมีตาที่ไม่คิดหรือเหนื่อยล้าอาจถูกเข้าใจผิดว่าเป็นเรื่องอื่น ในบันทึกส่วนตัวที่สมบูรณ์ฉันยังคิดว่าไวยากรณ์ที่คุณแนะนำในตัวอย่างดูน่าเกลียดกว่าไวยากรณ์ปัจจุบัน
รอย

22
'=>' ไม่ใช่ "คำหลักหรือไวยากรณ์พิเศษสำหรับการประกาศฟังก์ชัน" อย่างไร
TML

11
ฉันไม่ทราบเกี่ยวกับคุณ แต่สำหรับฉัน(int => int) addOne = (x) => {คือ "พิเศษ" และ "ซับซ้อน" มากกว่าint addOne(int) {...
Bogdan Alexandru

คำตอบ:


48

ฉันคิดว่าเหตุผลคือภาษายอดนิยมส่วนใหญ่มาจากหรือได้รับอิทธิพลจากตระกูลภาษา C เมื่อเทียบกับภาษาที่ใช้งานได้และรากของพวกเขาแลมบ์ดาแคลคูลัส

และในภาษาเหล่านี้ฟังก์ชั่นไม่ได้เป็นเพียงแค่ค่าอื่น:

  • ใน C ++, C # และ Java คุณสามารถโอเวอร์โหลดฟังก์ชั่น: คุณสามารถมีสองฟังก์ชั่นที่มีชื่อเดียวกัน แต่มีลายเซ็นที่แตกต่างกัน
  • ใน C, C ++, C # และ Java คุณสามารถมีค่าที่แสดงถึงฟังก์ชั่นได้ แต่พอยน์เตอร์ของฟังก์ชัน ส่วนหนึ่งของเหตุผลคือส่วนใหญ่ไม่ได้เป็นเพียงแค่ฟังก์ชั่น แต่เป็นฟังก์ชั่นร่วมกับบางสถานะ (ไม่แน่นอน)
  • ตัวแปรที่มีความไม่แน่นอนโดยค่าเริ่มต้น (คุณต้องใช้const, readonlyหรือfinalจะห้ามการกลายพันธุ์) แต่ฟังก์ชั่นไม่สามารถมีพระราชเสาวนีย์
  • จากมุมมองทางเทคนิคเพิ่มเติมรหัส (ซึ่งประกอบด้วยฟังก์ชั่น) และข้อมูลจะแยกจากกัน โดยทั่วไปแล้วพวกเขาครอบครองส่วนต่าง ๆ ของหน่วยความจำและพวกเขาเข้าถึงแตกต่างกัน: รหัสโหลดครั้งเดียวเท่านั้นแล้วดำเนินการ (แต่ไม่ได้อ่านหรือเขียน) ในขณะที่ข้อมูลมักจะถูกจัดสรรและ deallocated อย่างต่อเนื่องและถูกเขียนและอ่าน

    และเนื่องจาก C หมายถึง "ใกล้เคียงกับโลหะ" มันสมเหตุสมผลที่จะสะท้อนความแตกต่างนี้ในรูปแบบของภาษาด้วย

  • "ฟังก์ชั่นเป็นเพียงค่า" ซึ่งเป็นพื้นฐานของการตั้งโปรแกรมการทำงานได้รับแรงฉุดในภาษาทั่วไปค่อนข้างเร็วเมื่อเร็ว ๆ นี้เห็นได้จากการแนะนำของ lambdas ใน C ++, C # และ Java (2011, 2007, 2014)


12
C # มีฟังก์ชั่นที่ไม่ระบุชื่อ - ซึ่งเป็นเพียง lambdas โดยไม่มีการอนุมานประเภทและไวยากรณ์ clunky - ตั้งแต่ปี 2005
Eric Lippert

2
"จากมุมมองทางเทคนิครหัส (ซึ่งประกอบด้วยฟังก์ชั่น) และข้อมูลจะแยกจากกัน" - บางภาษา LISP ที่รู้กันดีที่สุดส่วนใหญ่จะไม่ทำการแยกนั้นและไม่ปฏิบัติกับรหัสและข้อมูลที่แตกต่างกัน (LISPs เป็นตัวอย่างที่รู้จักกันดีที่สุด แต่มีภาษาอื่น ๆ มากมายเช่น REBOL ที่ทำสิ่งนี้)
Benjamin Gruenbaum

1
@BenjaminGruenbaum ฉันกำลังพูดถึงคอมไพล์โค้ดในหน่วยความจำไม่ใช่ระดับภาษา
svick

C มีพอยน์เตอร์ของฟังก์ชันซึ่งอย่างน้อยก็ถือได้ว่าเป็นค่าอื่น ค่าอันตรายที่ต้องยุ่งกับเพื่อให้แน่ใจ แต่สิ่งต่าง ๆ เกิดขึ้น
Patrick Hughes

@ แพทริกฮิวจ์ใช่ฉันพูดถึงพวกเขาในจุดที่สองของฉัน แต่พอยน์เตอร์ของฟังก์ชั่นนั้นค่อนข้างแตกต่างจากฟังก์ชั่น
svick

59

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

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

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

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


12
ผมไม่เห็นว่าฟังก์ชั่นการรักษาเป็นหน่วยงานที่ชื่อจำเป็นต้องทำให้รหัสยากที่จะเข้าใจ ซึ่งเกือบเป็นจริงสำหรับรหัสใด ๆ ที่ตามกระบวนทัศน์กระบวนงาน แต่อาจไม่เป็นจริงสำหรับรหัสที่ติดตามกระบวนทัศน์การทำงาน
Kyle Strand

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

12
+1 รุ่นที่สั้นกว่าอาจเป็น "ทำไมภาษาธรรมชาติทุกภาษาจึงแยกคำนามออกจากคำกริยา"
msw

2
"หยดของตัวละครที่เข้าใจยากนั้นใช้ได้สำหรับเครื่องที่จะตีความ แต่มันคงเป็นไปไม่ได้สำหรับมนุษย์ที่จะเข้าใจและดูแลรักษา" สิ่งที่มีคุณสมบัติสำหรับผื่นสำหรับการเปลี่ยนแปลงทางไวยากรณ์อย่างเล็กน้อยตามที่เสนอในคำถาม หนึ่งปรับเข้ากับไวยากรณ์ได้อย่างรวดเร็ว ข้อเสนอนี้เป็นการออกจากการประชุมที่รุนแรงน้อยกว่าการเรียกใช้เมธอดไวยากรณ์OO (args)ในเวลานั้นOO แต่ข้อหลังไม่ได้พิสูจน์แล้วว่าเป็นไปไม่ได้ที่มนุษย์จะเข้าใจและบำรุงรักษาไม่ได้ แม้ว่าความจริงที่ว่าในวิธีการภาษา OO ส่วนใหญ่ไม่ควรคิดว่าเก็บไว้ในอินสแตนซ์ของชั้นเรียน
Marc van Leeuwen

4
@MarcvanLeeuwen ความคิดเห็นของคุณเป็นความจริงและสมเหตุสมผล แต่ความสับสนบางอย่างเกิดขึ้นจากวิธีการโพสต์ดั้งเดิม จริงๆแล้วมีคำถามอยู่ 2 ข้อ: (i) ทำไมจึงเป็นเช่นนี้ และ (ii) ทำไมไม่ใช้วิธีนี้แทน . คำตอบwhatsisnameนั้นเป็นประเด็นแรกมากขึ้น (และแจ้งเตือนเกี่ยวกับอันตรายของการลบสิ่งที่ไม่ปลอดภัยเหล่านี้) ในขณะที่ความคิดเห็นของคุณเกี่ยวข้องกับส่วนที่สองของคำถามมากขึ้น เป็นไปได้แน่นอนที่จะเปลี่ยนไวยากรณ์นี้ (และตามที่คุณอธิบายไว้มันทำมาหลายครั้งแล้ว ... ) แต่มันจะไม่เหมาะกับทุกคน (เนื่องจากoopไม่เหมาะกับทุกคนเช่นกัน)
Hoki

10

คุณอาจสนใจที่จะเรียนรู้ว่าในยุคก่อนประวัติศาสตร์ภาษา ALGOL 68 ใช้ไวยากรณ์ใกล้กับสิ่งที่คุณเสนอ การรับรู้ว่าตัวระบุฟังก์ชั่นถูกผูกไว้กับค่าเช่นเดียวกับตัวระบุอื่น ๆ คุณสามารถในภาษานั้นประกาศฟังก์ชั่น (คงที่) โดยใช้ไวยากรณ์

ฟังก์ชั่นชนิด ชื่อ = ( พารามิเตอร์รายการ ) ผลประเภท : ร่างกาย ;

ตัวอย่างของคุณจะอ่านอย่างชัดเจน

PROC (INT)INT add one = (INT n) INT: n+1;

การรับรู้ถึงความซ้ำซ้อนในประเภทเริ่มต้นสามารถอ่านได้จาก RHS ของการประกาศและการเป็นประเภทฟังก์ชั่นเริ่มต้นด้วยPROCสิ่งนี้สามารถทำสัญญา (และมักจะ)

PROC add one = (INT n) INT: n+1;

แต่โปรดทราบว่า=ยังคงมาก่อนรายการพารามิเตอร์ โปรดทราบด้วยว่าหากคุณต้องการตัวแปรฟังก์ชัน(ซึ่งสามารถกำหนดค่าฟังก์ชันประเภทเดียวกันในภายหลังได้) =ควรเปลี่ยนค่าโดย:=ให้หนึ่งใน

PROC (INT)INT func var := (INT n) INT: n+1;
PROC func var := (INT n) INT: n+1;

อย่างไรก็ตามในกรณีนี้ทั้งสองรูปแบบเป็นตัวย่อจริง เนื่องจากตัวระบุfunc varมอบหมายการอ้างอิงไปยังฟังก์ชั่นที่สร้างขึ้นในท้องถิ่นรูปแบบที่ขยายอย่างเต็มที่จะเป็น

REF PROC (INT)INT func var = LOC PROC (INT)INT := (INT n) INT: n+1;

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

โดยวิธีการสนทนาข้างต้นจะเน้นความแตกต่างที่สำคัญอย่างหนึ่งระหว่างตัวแปรและฟังก์ชั่น: นิยามฟังก์ชั่นมักจะผูกชื่อกับค่าฟังก์ชั่นเฉพาะที่ไม่สามารถเปลี่ยนแปลงได้ในภายหลังในขณะที่นิยามตัวแปรมักจะแนะนำตัวบ่งชี้ที่มีค่าเริ่มต้นสามารถเปลี่ยนได้ในภายหลัง (ไม่ใช่กฎสัมบูรณ์; ฟังก์ชั่นตัวแปรและค่าคงที่ที่ไม่ใช่ฟังก์ชั่นเกิดขึ้นในภาษาส่วนใหญ่) นอกจากนี้ในภาษาที่คอมไพล์ค่าที่ถูกผูกไว้ในนิยามของฟังก์ชั่นมักจะเป็นค่าคงที่เวลาคอมไพล์แล้ว รวบรวมโดยใช้ที่อยู่คงที่ในรหัส ใน C / C ++ นี่เป็นสิ่งที่จำเป็น เทียบเท่ากับ ALGOL 68

PROC (REAL) REAL f = IF mood=sunny THEN sin ELSE cos FI;

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


1
"นักคณิตศาสตร์ไม่ชอบf = nn + 1 มากกว่าf ( n ) = n + 1" ... ไม่ต้องพูดถึงนักฟิสิกส์ที่ชอบเขียนเฉพาะf = n + 1 ...
leftaroundabout

1
@leftaroundabout นั่นเป็นเพราะ⟼เป็นความเจ็บปวดที่จะเขียน อย่างสุจริต
Pierre Arlaud

8

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

ใน Scala ซึ่งมีทั้งฟังก์ชั่นและวิธีการมีความแตกต่างดังต่อไปนี้ระหว่างวิธีการและฟังก์ชั่น:

  • วิธีการสามารถทั่วไปฟังก์ชั่นไม่สามารถ
  • เมธอดสามารถไม่มีรายการพารามิเตอร์หนึ่งหรือหลายรายการฟังก์ชันมีรายการพารามิเตอร์หนึ่งรายการเสมอ
  • เมธอดสามารถมีรายการพารามิเตอร์โดยนัยฟังก์ชันไม่สามารถทำได้
  • เมธอดสามารถมีพารามิเตอร์ทางเลือกพร้อมอาร์กิวเมนต์ดีฟอลต์ฟังก์ชันไม่สามารถทำได้
  • เมธอดสามารถมีพารามิเตอร์ซ้ำได้ฟังก์ชันไม่สามารถทำได้
  • วิธีการสามารถมีพารามิเตอร์โดยชื่อฟังก์ชั่นไม่สามารถ
  • สามารถเรียกเมธอดด้วยอาร์กิวเมนต์ที่มีชื่อฟังก์ชันไม่สามารถทำได้
  • ฟังก์ชั่นเป็นวัตถุวิธีการไม่ได้

ดังนั้นการแทนที่ที่คุณเสนอก็ไม่ได้ผลอย่างน้อยในกรณีเหล่านั้น


2
"ฟังก์ชั่นคือวัตถุวิธีต่างๆเป็นของวัตถุ" ควรใช้กับทุกภาษา (เช่น C ++) หรือไม่
svick

9
"เมธอดสามารถมีพารามิเตอร์แบบชื่อฟังก์ชั่นไม่สามารถทำได้" - นี่ไม่ใช่ความแตกต่างพื้นฐานระหว่างเมธอดและฟังก์ชั่นมันเป็นเพียงเรื่องแปลกของสกาลา เหมือนกับคนอื่น ๆ ส่วนใหญ่ (ทั้งหมด?)
253751

1
วิธีการเป็นพื้นฐานเพียงฟังก์ชั่นชนิดพิเศษ -1 โปรดทราบว่า Java (และนามสกุล Scala) ไม่มีฟังก์ชั่นประเภทอื่น มัน "ฟังก์ชั่น" เป็นวัตถุที่มีวิธีการหนึ่ง (โดยทั่วไปฟังก์ชั่นอาจไม่ได้เป็นวัตถุหรือแม้กระทั่งค่าชั้นหนึ่ง; ไม่ว่าจะขึ้นอยู่กับภาษา)
Jan Hudec

@JanHudec: Semantically, Java มีทั้งฟังก์ชั่นและวิธีการ; มันใช้คำศัพท์ "วิธีการแบบคงที่" เมื่ออ้างถึงฟังก์ชั่น แต่คำศัพท์ดังกล่าวไม่ได้ลบความแตกต่างทางความหมาย
supercat

3

เหตุผลที่ฉันคิดได้คือ:

  • คอมไพเลอร์รู้ว่าเราประกาศอะไรง่ายกว่า
  • มันสำคัญสำหรับเราที่จะรู้ (ในทางที่ไม่สำคัญ) ไม่ว่าจะเป็นฟังก์ชันหรือตัวแปร ฟังก์ชั่นมักจะเป็นกล่องดำและเราไม่สนใจการใช้งานภายใน ฉันไม่ชอบการอนุมานประเภทกับประเภทส่งคืนใน Scala เพราะฉันเชื่อว่าการใช้ฟังก์ชันที่มีประเภทส่งคืนง่ายกว่า: มักเป็นเอกสารที่ให้ไว้เท่านั้น
  • และที่สำคัญที่สุดคือกลยุทธ์ฝูงชนที่ใช้ในการออกแบบภาษาโปรแกรม C ++ ถูกสร้างขึ้นเพื่อขโมยโปรแกรมเมอร์ C และ Java ได้รับการออกแบบในลักษณะที่ไม่ทำให้โปรแกรมเมอร์ C ++ กลัวและ C # เพื่อดึงดูดโปรแกรมเมอร์ Java แม้ C # ซึ่งฉันคิดว่าเป็นภาษาที่ทันสมัยมากกับทีมที่น่าทึ่งอยู่ข้างหลังคัดลอกข้อผิดพลาดบางอย่างจาก Java หรือแม้กระทั่งจาก C.

2
"... และ C # เพื่อดึงดูดโปรแกรมเมอร์ Java" - นี่เป็นที่น่าสงสัย C # คล้ายกับ C ++ มากกว่า Java และบางครั้งดูเหมือนว่ามันถูกทำให้เข้ากันไม่ได้กับ Java (ไม่มี wildcard, ไม่มีคลาสภายใน, ไม่มีความแปรปรวนแบบย้อนกลับ…)
Sarge Borsch

1
@SargeBorsch ฉันไม่คิดว่ามันเข้ากันไม่ได้กับ Java ฉันค่อนข้างแน่ใจว่าทีม C # เพียงพยายามทำถูกต้องหรือทำได้ดีกว่า แต่คุณต้องการดู Microsoft ได้เขียน Java & JVM ของตนเองแล้วและถูกฟ้องร้อง ดังนั้นทีม C # จึงมีแนวโน้มที่จะทำให้เกิด eclipse Java มันเข้ากันไม่ได้แน่นอนและดีกว่าสำหรับมัน Java เริ่มต้นด้วยข้อ จำกัด พื้นฐานบางอย่างและฉันดีใจที่ทีม C # เลือกเช่นเพื่อทำ generics แตกต่างกัน (มองเห็นได้ในระบบประเภทและไม่ใช่แค่น้ำตาล)
codenheim

2

หากมีคำถามใดที่ไม่สนใจที่จะพยายามแก้ไขซอร์สโค้ดบนเครื่องที่มีข้อ จำกัด เรื่อง RAM มากหรือลดเวลาในการอ่านออกจากฟลอปปี้ดิสก์จะเกิดอะไรขึ้นกับการใช้คำหลัก?

แน่นอนว่ามันอ่านได้x=y+zดีกว่าstore the value of y plus z into xแต่นั่นไม่ได้หมายความว่าอักขระเครื่องหมายวรรคตอนนั้น "ดีกว่า" มากกว่าคำหลัก หากตัวแปรi, jและkมีIntegerและxเป็นRealพิจารณาบรรทัดต่อไปนี้ในปาสคาล:

k := i div j;
x := i/j;

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

ในขณะที่มีบริบทเพียงไม่กี่อย่างที่สามารถช่วยให้คำจำกัดความของฟังก์ชั่นกระชับ (เช่นแลมบ์ดาซึ่งใช้เป็นส่วนหนึ่งของการแสดงออกอื่น) ฟังก์ชั่นโดยทั่วไปควรจะโดดเด่นและจดจำได้ง่าย ในขณะที่มันอาจเป็นไปได้ที่จะทำให้ความแตกต่างที่ลึกซึ้งยิ่งขึ้นและใช้อะไรเลยนอกจากตัวอักษรวรรคตอนจุดจะเป็นอย่างไร การบอกFunction Foo(A,B: Integer; C: Real): Stringให้ชัดเจนว่าชื่อของฟังก์ชันคืออะไรพารามิเตอร์ที่ต้องการและสิ่งที่ส่งคืน อาจจะสั้นลงหกหรือเจ็ดตัวอักษรโดยแทนที่Functionด้วยเครื่องหมายวรรคตอนบางตัว แต่จะได้อะไรบ้าง

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


3
ฉันไม่คิดว่าคำถามนี้เกี่ยวกับความกระชับ โดยเฉพาะอย่างยิ่งเนื่องจากตัวอย่างvoid f() {}นั้นสั้นกว่า lambda ที่เทียบเท่าใน C ++ ( auto f = [](){};), C # ( Action f = () => {};) และ Java ( Runnable f = () -> {};) ความกระชับของแลมบ์ดามาจากการอนุมานแบบและละเว้นreturnแต่ฉันไม่คิดว่ามันเกี่ยวข้องกับสิ่งที่คำถามนี้ถาม
svick

2

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

ในภาษาของ ML หรือ Miranda Heritage OTOH คุณกำหนดฟังก์ชั่นส่วนใหญ่ ดูตัวอย่างรหัส Haskell แท้จริงแล้วมันเป็นลำดับของคำจำกัดความของฟังก์ชั่นซึ่งส่วนใหญ่มีฟังก์ชั่นในท้องถิ่นและฟังก์ชั่นในท้องถิ่นของฟังก์ชั่นในท้องถิ่นนั้น ดังนั้นความสนุกสนานคำหลักใน Haskell จะผิดพลาดเป็นใหญ่เป็นต้องมีคำสั่ง assingment ในภาษาที่จำเป็นที่จะเริ่มต้นด้วยการกำหนด สาเหตุที่ได้รับมอบหมายอาจเป็นเพียงคำสั่งที่พบบ่อยที่สุด


1

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

ไวยากรณ์ที่คุณเสนอดูเหมือนจะแตกต่างจากรูปแบบสัญกรณ์บางครั้งที่ใช้ในการแสดงฟังก์ชั่นหรือประเภทของฟังก์ชั่นในคณิตศาสตร์ ซึ่งหมายความว่าเช่นเดียวกับไวยากรณ์ทั้งหมดมันอาจจะดึงดูดโปรแกรมเมอร์บางคนมากกว่าคนอื่น ๆ (ในฐานะนักคณิตศาสตร์ฉันชอบมันมาก)

อย่างไรก็ตามคุณควรทราบว่าในภาษาส่วนใหญ่defไวยากรณ์ -style (เช่นไวยากรณ์ดั้งเดิม) จะทำงานแตกต่างจากการกำหนดตัวแปรมาตรฐาน

  • ในCและC++ตระกูลฟังก์ชันโดยทั่วไปจะไม่ถือว่าเป็น "วัตถุ" เช่นชิ้นข้อมูลที่พิมพ์เพื่อคัดลอกและวางลงในสแต็กและอะไรก็ตาม (ใช่คุณสามารถมีตัวชี้ฟังก์ชั่น แต่ยังคงชี้ไปที่รหัสที่ปฏิบัติการได้ไม่ใช่ที่ "data" ในความหมายทั่วไป)
  • ในภาษา OO ส่วนใหญ่มีวิธีการจัดการพิเศษ (เช่นฟังก์ชั่นสมาชิก); นั่นคือมันไม่ได้เป็นเพียงแค่ฟังก์ชั่นที่ประกาศภายในขอบเขตของคำจำกัดความของคลาส ความแตกต่างที่สำคัญที่สุดคือวัตถุที่วิธีการที่ถูกเรียกมักจะส่งผ่านเป็นพารามิเตอร์แรกโดยนัยกับวิธีการ Python ทำให้สิ่งนี้ชัดเจนด้วยself(ซึ่งจริงๆแล้วไม่ใช่คำหลักคุณสามารถทำให้ตัวระบุที่ถูกต้องเป็นอาร์กิวเมนต์แรกของวิธีการ)

คุณต้องพิจารณาว่าไวยากรณ์ใหม่ของคุณอย่างถูกต้อง (และหวังอย่างสังหรณ์ใจ) แสดงถึงสิ่งที่คอมไพเลอร์หรือล่ามกำลังทำจริง มันอาจช่วยในการอ่านพูดความแตกต่างระหว่าง lambdas กับวิธีการใน Ruby สิ่งนี้จะทำให้คุณทราบว่ากระบวนทัศน์ของฟังก์ชั่นเป็นเพียงแค่ข้อมูลแตกต่างจากกระบวนทัศน์ทั่วไปของ OO / กระบวนงานอย่างไร


1

สำหรับบางฟังก์ชั่นภาษาไม่ใช่ค่า ในภาษาดังกล่าวต้องบอกว่า

val f = << i : int  ... >> ;

เป็นคำนิยามฟังก์ชั่นในขณะที่

val a = 1 ;

ประกาศค่าคงที่ทำให้เกิดความสับสนเพราะคุณใช้ไวยากรณ์เดียวเพื่อหมายถึงสองสิ่ง

ภาษาอื่น ๆ เช่น ML, Haskell และ Scheme จะทำหน้าที่เป็นค่าคลาส 1 แต่ให้ผู้ใช้มีไวยากรณ์พิเศษสำหรับการประกาศค่าคงที่ของฟังก์ชัน * พวกเขาใช้กฎที่ว่า "การใช้แบบฟอร์มที่สั้นลง" คือถ้าโครงสร้างเป็นทั้งสามัญและ verbose คุณควรให้ผู้ใช้ชวเลข มันไม่เหมาะสมที่จะให้ผู้ใช้สองไวยากรณ์ที่แตกต่างกันซึ่งหมายถึงสิ่งเดียวกัน; บางครั้งความสง่างามควรเสียสละเพื่อประโยชน์

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

- แก้ไข -

ปัญหาอื่นที่ยังไม่มีใครหยิบยกขึ้นมา (ยัง) หากคุณอนุญาต

{ 
    val f = << i : int ... g(i-1) ... >> ;
    val g = << j : int ... f(i-1) ... >> ;
    f(x)
}

และคุณอนุญาต

{
    val a = 42 ;
    val b = a + 1 ;
    a
} ,

มันเป็นไปตามที่คุณควรอนุญาต

{
    val a = b + 1 ; 
    val b = a - 1 ;
    a
} ?

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

- สิ้นสุดการแก้ไข -

* อาจเป็นที่ถกเถียงกันอยู่ว่า Haskell ไม่ได้อยู่ในรายการนี้ มันมีสองวิธีในการประกาศฟังก์ชั่น แต่ในความหมายทั่วไปของไวยากรณ์สำหรับการประกาศค่าคงที่ของประเภทอื่น ๆ


0

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

ในกรณีของคุณฟังก์ชั่นที่มี 4 ตัวแปรจะเป็น:

(int, long, double, String => int) addOne = (x, y, z, s) => {
  return x + 1;
}

เมื่อฉันดูที่ส่วนหัวของฟังก์ชั่นและดู (x, y, z, s) แต่ฉันไม่รู้ประเภทของตัวแปรเหล่านี้ หากฉันต้องการทราบประเภทของzพารามิเตอร์ตัวที่สามฉันจะต้องดูที่จุดเริ่มต้นของฟังก์ชั่นและเริ่มนับ 1, 2, 3 แล้วจึงดูว่าเป็นประเภทdoubleใด double zในอดีตวิธีที่ผมมองตรงและดู


3
แน่นอนว่ามีสิ่งที่ยอดเยี่ยมที่เรียกว่าการอนุมานแบบซึ่งช่วยให้คุณสามารถเขียนบางสิ่งบางอย่างvar addOne = (int x, long y, double z, String s) => { x + 1 }ในภาษาที่คุณเลือกเอง (ตัวอย่าง: C #, C ++, Scala) แม้แต่การอนุมานประเภทโลคัลที่ จำกัด อย่างอื่นที่ใช้โดย C # ก็เพียงพอแล้วสำหรับสิ่งนี้ ดังนั้นคำตอบนี้เป็นเพียงการวิพากษ์วิจารณ์ไวยากรณ์เฉพาะที่น่าสงสัยในตอนแรกและไม่ได้ใช้จริงทุกที่ (แม้ว่าไวยากรณ์ของ Haskell มีปัญหาที่คล้ายกันมาก)
amon

4
@ มอนคำตอบของเขาอาจจะวิพากษ์วิจารณ์ไวยากรณ์ แต่ก็ชี้ให้เห็นว่าไวยากรณ์ที่มีวัตถุประสงค์ในคำถามอาจไม่ใช่ "ธรรมชาติ" สำหรับทุกคน

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

1
การวิจารณ์ดูเหมือนว่าถูกต้องสำหรับฉัน สำหรับ Java OP ดูเหมือนว่า [input, output, name, input] เป็นฟังก์ชั่นที่ง่ายกว่า / ชัดเจนกว่า def [แค่ชื่อเอาท์พุทอินพุต]? เมื่อ @ m3th0dman ระบุด้วยลายเซ็นที่ยาวกว่าวิธีการ 'สะอาด' ของ OP จะกลายเป็นสิ่งที่ยืดยาวมาก
Michael

0

มีเหตุผลที่ง่ายมากที่จะมีความแตกต่างดังกล่าวในภาษามากที่สุดคือมีความจำเป็นที่จะแยกแยะการประเมินผลและการประกาศ ตัวอย่างของคุณดี: ทำไมไม่ชอบตัวแปร? นิพจน์ตัวแปรจะถูกประเมินทันที

Haskell มีรูปแบบพิเศษที่ไม่มีความแตกต่างระหว่างการประเมินและการประกาศซึ่งเป็นสาเหตุที่ไม่จำเป็นต้องใช้คำหลักพิเศษ


2
แต่ไวยากรณ์บางอย่างสำหรับฟังก์ชั่นแลมบ์ดาก็แก้ปัญหานี้ด้วยเหตุนี้จึงไม่ใช้มัน
svick

0

ฟังก์ชั่นถูกประกาศแตกต่างจากตัวอักษรวัตถุ ฯลฯ ในภาษาส่วนใหญ่เนื่องจากมีการใช้งานแตกต่างกันดีบั๊กแตกต่างกันและก่อให้เกิดแหล่งที่มาของข้อผิดพลาดที่แตกต่างกัน

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

พิจารณาการดีบักโมดูลเคอร์เนลบางประเภทใน Java โดยที่ทุกอ็อบเจ็กต์มีการดำเนินการ toString () ในขณะที่อาจคาดว่าเมธอด toString () ควรคืนค่าวัตถุ แต่อาจจำเป็นต้องแยกชิ้นส่วนและประกอบชิ้นส่วนของวัตถุใหม่เพื่อแปลค่าของมันเป็นวัตถุสตริง หากคุณพยายามที่จะดีบั๊กวิธีที่ toString () จะโทรหา (ในสถานการณ์แบบ hook-and-template) เพื่อทำงานและเน้นวัตถุในหน้าต่างตัวแปรของ IDE ส่วนใหญ่โดยไม่ได้ตั้งใจมันสามารถดีบักเกอร์ได้ นี่เป็นเพราะ IDE จะพยายาม toString () วัตถุที่เรียกรหัสมากคุณกำลังอยู่ในกระบวนการของการแก้จุดบกพร่อง ไม่มีค่าดั้งเดิมใด ๆ ที่ทำให้อึเป็นเช่นนี้เพราะความหมายเชิงความหมายของค่าดั้งเดิมถูกกำหนดโดยภาษาไม่ใช่โปรแกรมเมอร์

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