ตัวเลือกของบรรณาธิการ:

การโฆษณา

บ้าน - การตั้งค่าอินเทอร์เน็ต
ชื่อฟังก์ชั่นในการเขียนโปรแกรมคืออะไร ฟังก์ชั่นการเขียนโปรแกรม

วัตถุประสงค์ของงาน: 1) ศึกษากฎเกณฑ์ในการอธิบายฟังก์ชัน 2) ฝึกฝนทักษะการใช้ฟังก์ชันเมื่อเขียนโปรแกรมด้วยภาษา C++

ข้อมูลทางทฤษฎี

โมดูลหลักของโปรแกรมในภาษา C++ คือฟังก์ชัน

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

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

ไวยากรณ์สำหรับฟังก์ชันต้นแบบพร้อมพารามิเตอร์คือ:

return_value_type function_name (พารามิเตอร์_list_with_type_indication);

ฟังก์ชันใน C++ เป็นแบบมาตรฐาน (ไลบรารี) และผู้ใช้สามารถตั้งโปรแกรมได้

คุณสมบัติมาตรฐาน

คำอธิบายของฟังก์ชันมาตรฐานมีอยู่ในไฟล์ที่รวมอยู่ในโปรแกรมโดยใช้คำสั่ง #include ไฟล์ดังกล่าวเรียกว่าไฟล์ส่วนหัว พวกเขามีนามสกุล h

การอ้างถึงชื่อฟังก์ชันในโปรแกรมหลักเรียกว่าการเรียกใช้ฟังก์ชัน

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

y = บาป(x); //ฟังก์ชันการคำนวณไซน์

นิยามฟังก์ชัน

โดยทั่วไป ฟังก์ชันจะถูกกำหนดดังนี้:

return_value_type function_name (พิมพ์ parameter_name,..., พิมพ์ parameter_name)

body_function

คุณสมบัติที่ตั้งโปรแกรมได้

ฟังก์ชั่นที่โปรแกรมเมอร์สร้างขึ้นเองทำให้กระบวนการเขียนโปรแกรมง่ายขึ้นเนื่องจาก:

    ช่วยหลีกเลี่ยงการตั้งโปรแกรมซ้ำเนื่องจากสามารถใช้ฟังก์ชันเดียวกันในโปรแกรมต่างๆได้

    เพิ่มระดับของโมดูลาร์ของโปรแกรมจึงทำให้อ่าน แก้ไข และแก้ไขข้อผิดพลาดได้ง่ายขึ้น

ตัวอย่าง9 .1. มาสร้างฟังก์ชันที่พิมพ์อักขระ 65 "*" ติดต่อกันกัน เพื่อให้ฟังก์ชันนี้ทำงานได้ในบางบริบท ฟังก์ชันนี้จะรวมอยู่ในโปรแกรมการพิมพ์หัวจดหมาย โปรแกรมประกอบด้วยฟังก์ชัน: main() และ stars()

//หัวจดหมาย

#รวม

const int ขีดจำกัด = 65;

ดาวเป็นโมฆะ(เป็นโมฆะ); // ฟังก์ชั่นต้นแบบ stars()

ศาล<<"Moscow Institute of Electronic Engineering"<

// คำจำกัดความของฟังก์ชัน stars()

สำหรับ (นับ = 1; นับ<=Limit; count++)

เราดูตัวอย่างฟังก์ชันธรรมดาที่ไม่มีอาร์กิวเมนต์และไม่ส่งคืนค่าใดๆ

พารามิเตอร์ฟังก์ชัน

ลองดูตัวอย่างการใช้พารามิเตอร์ฟังก์ชัน

ตัวอย่าง9. 2. มาเขียนฟังก์ชันสเปซ() กัน ซึ่งอาร์กิวเมนต์จะเป็นจำนวนช่องว่างที่ฟังก์ชันนี้ควรพิมพ์

#กำหนดที่อยู่ "เซเลโนกราด"

#กำหนดชื่อ "สถาบันมอสโกวิศวกรรมอิเล็กทรอนิกส์"

#กำหนดแผนก “สารสนเทศและการเขียนโปรแกรม”

const int LIMIT=65;

#รวม

พื้นที่ว่าง (หมายเลข int);

ศาล<

ช่องว่าง=(จำกัด - strlen(ชื่อ))/2; //คำนวณจำนวนเท่าไหร่

//ต้องการช่องว่าง

ศาล<

space((LIMIT - strlen(แผนก))/2); // อาร์กิวเมนต์ - การแสดงออก

ศาล<

//คำจำกัดความของฟังก์ชัน stars()

สำหรับ (นับ = 1; นับ<=LIMIT; count++)

//คำจำกัดความของฟังก์ชันช่องว่าง()

พื้นที่ว่าง (หมายเลข int)

สำหรับ (นับ = 1; นับ<=number; count++)

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

หากฟังก์ชันต้องการมากกว่าหนึ่งอาร์กิวเมนต์ในการสื่อสารด้วย คุณสามารถระบุรายการอาร์กิวเมนต์ที่คั่นด้วยเครื่องหมายจุลภาคพร้อมกับชื่อฟังก์ชันได้:

เป็นโมฆะ printnum (int i, int j)

( โต้แย้ง<<"Координаты точек”<< i << j <

ค่าอินพุตของฟังก์ชันสามารถประมวลผลได้เนื่องจากมีอยู่ การโต้แย้ง- ค่าเอาต์พุตจะถูกส่งกลับโดยใช้คีย์เวิร์ด return

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

คำว่าฟังก์ชันและขั้นตอนหมายถึงอะไร?

  • การทำงานในการเขียนโปรแกรม รูทีนย่อยที่ถูกเรียกจากรูทีนย่อยอื่นตามจำนวนครั้งที่ต้องการ
  • ขั้นตอน- ส่วนที่ตั้งชื่อของโปรแกรม (รูทีนย่อย) เรียกซ้ำ ๆ จากส่วนที่ตามมาของโปรแกรมตามจำนวนครั้งที่ต้องการ

การเปรียบเทียบฟังก์ชันและขั้นตอน

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

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

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

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

ความแตกต่างระหว่างฟังก์ชันและขั้นตอนในการเขียนโปรแกรมมีดังนี้:

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

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

ฟังก์ชั่นคืออะไร

มีการใช้ชื่อฟังก์ชัน: 1) เพื่อสร้างเอกสาร; 2) สำหรับ API นั่นคืออินเทอร์เฟซสำหรับเชื่อมต่อกับโปรแกรมหรือระบบปฏิบัติการทั้งหมดของแอปพลิเคชันใด ๆ ดังนั้นจึงสมเหตุสมผลที่จะเตือนอีกครั้งว่าควรตั้งชื่อเหล่านี้ให้เข้าใจง่ายและหากเป็นไปได้ควรให้เหมาะสมกับการดำเนินการที่กำลังดำเนินการอยู่

มาสรุปกัน

ดังนั้นฟังก์ชันจึงเป็นคอนเทนเนอร์ชนิดหนึ่งสำหรับการจัดกลุ่มอัลกอริธึม พวกเขา:

  1. รับผิดชอบงานเฉพาะ
  2. โต้ตอบกับวัตถุอื่น
  3. เป็นพื้นฐานแนวคิดของการเขียนโปรแกรมสมัยใหม่ ไม่ว่ามันจะฟังดูน่าสมเพชแค่ไหนก็ตาม

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

สิ่งพิมพ์ก่อนหน้า:

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

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

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

จะเรียกใช้ฟังก์ชันได้อย่างไร?

ในการเรียกใช้ฟังก์ชัน คุณต้องสร้างมันขึ้นมา แม้ว่าจะมีฟังก์ชั่นในตัวด้วย ตัวอย่างเช่น: cos, sin, md5, นับ, หน้าท้องและอื่น ๆ หากต้องการเรียกมัน คุณเพียงแค่ต้องกำหนดค่าที่ต้องการให้กับตัวแปร

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

ฟังก์ชั่น hello() ( echo "Hello, world!"; )

แล้วเราก็โทรหาเธอ ยิ่งกว่านั้น ถ้ามันไม่มีพารามิเตอร์ เราก็เพียงแค่ใส่วงเล็บ ในการเรียกใช้ฟังก์ชันนี้ เราใช้เพียงบรรทัด: สวัสดี();- ฟังก์ชันใดๆ ก็สามารถคืนค่าโดยใช้คำสงวนได้ กลับ- คำสั่งนี้จะหยุดการดำเนินการฟังก์ชันและส่งค่าส่งคืนไปยังโปรแกรมที่เรียก ฟังก์ชั่น sum($first, $second) ($r=$first + $second; return $r;) echo sum(2,5); ผลลัพธ์ของการทำงานของโปรแกรมจะเท่ากับ 7 ตัวแปรท้องถิ่นและระดับโลก

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

$per="ดิมา"; ฟังก์ชั่นไพรเมอร์() // ดำเนินการ: แสดงตัวแปรท้องถิ่น ( echo "My name is ".$per; ) echo primer();

ในกรณีนี้ วลี “ฉันชื่อ” จะปรากฏบนหน้าจอ ซึ่งหมายความว่าตัวแปร $per ถูกสร้างขึ้นภายในฟังก์ชันไพรเมอร์ และกำหนดค่าเป็นศูนย์ตามค่าเริ่มต้น เพื่อหลีกเลี่ยงวงกบดังกล่าว คุณต้องใช้ตัวดำเนินการ ทั่วโลก- มาแก้ไขโค้ดด้านบนให้ถูกต้อง:

$per="ดิมา"; ฟังก์ชั่นไพรเมอร์() // ดำเนินการ: แสดงตัวแปรโกลบอล ( global $per; echo "ฉันชื่อ ".$per; ) echo primer();

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

ฟังก์ชันของอาร์กิวเมนต์ตั้งแต่สองตัวขึ้นไป

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

… function font($text, $size=5) // Do: ขนาดฟอนต์เอาท์พุต ( echo " ".$ข้อความ"; ) แบบอักษร("สวัสดี
",1); แบบอักษร("สวัสดี
",2); แบบอักษร("สวัสดี
",3); แบบอักษร("สวัสดี
",4); แบบอักษร("สวัสดี
",5); แบบอักษร("สวัสดี
",6); แบบอักษร("สวัสดี
");

ขนาดตัวอักษรคือ 5 หากเราละเว้นพารามิเตอร์ตัวที่สองของฟังก์ชัน มันจะเท่ากับค่านี้

บทสรุป

ก่อนที่ฉันจะกล่าวคำอำลา ฉันอยากจะดึงความสนใจของคุณไปที่คำแนะนำสักข้อหนึ่ง ประกอบด้วยการรวมฟังก์ชันทั้งหมดที่คุณเขียนไว้ในไฟล์เดียว (เช่น function.php) จากนั้นในไฟล์ที่คุณต้องการเรียกใช้ฟังก์ชัน คุณเพียงแค่ต้องรวม function.php แล้วทุกอย่างก็จะพร้อมใช้งาน ซึ่งจะทำให้เข้าใจตรรกะในโปรแกรมของคุณได้ง่ายขึ้นมาก หากต้องการเชื่อมต่อ ให้ใช้:

include_once("function.php");

need_once("function.php");

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

นี่เป็นบทความที่สามในซีรีส์ “ทฤษฎีหมวดหมู่สำหรับโปรแกรมเมอร์”

ใครต้องการประเภท?

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

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

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

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

ประเภทจำเป็นสำหรับการจัดองค์ประกอบ

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

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

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

ใน Haskell ยกเว้นในบางกรณีที่เกิดขึ้นไม่บ่อยนัก ประเภทคำอธิบายประกอบเป็นทางเลือก โปรแกรมเมอร์มักจะใช้พวกมันเพราะว่า Types สามารถบอกคุณได้มากมายเกี่ยวกับความหมายของโค้ดของคุณ และการประกาศประเภทจะช่วยให้คุณเข้าใจข้อผิดพลาดในการคอมไพล์ แนวทางปฏิบัติทั่วไปใน Haskell คือการเริ่มต้นโครงการโดยการพัฒนาประเภทต่างๆ ต่อมา พิมพ์คำอธิบายประกอบเป็นพื้นฐานของการใช้งานและกลายเป็นความคิดเห็นที่รับประกันโดยคอมไพเลอร์

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

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

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

ประเภทอะไรบ้าง?

คำอธิบายประเภทที่ง่ายที่สุดคือเป็นชุดของค่า ประเภท Bool (โปรดจำไว้ว่า ประเภทที่เป็นรูปธรรมจะขึ้นต้นด้วยตัวพิมพ์ใหญ่ในภาษา Haskell) สอดคล้องกับชุดขององค์ประกอบสองประการ: จริงและเท็จ ประเภท Char คือชุดของอักขระ Unicode ทั้งหมด เช่น "a" หรือ "ę"

เซตสามารถมีขอบเขตหรือไม่มีที่สิ้นสุด ประเภทสตริง ซึ่งโดยพื้นฐานแล้วเป็นคำพ้องสำหรับรายการถ่าน เป็นตัวอย่างของเซตที่ไม่มีที่สิ้นสุด

เมื่อเราประกาศ x เป็นจำนวนเต็ม:
x::จำนวนเต็ม
เราบอกว่ามันเป็นองค์ประกอบของเซตจำนวนเต็ม จำนวนเต็มใน Haskell เป็นเซตอนันต์ และสามารถใช้สำหรับเลขคณิตที่มีความแม่นยำทุกรูปแบบ นอกจากนี้ยังมีชุด Int แบบจำกัดซึ่งสอดคล้องกับประเภทเครื่อง เช่น int ในภาษา C++

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

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

ในโลกอุดมคติ เราสามารถพูดได้ว่าประเภทใน Haskell เป็นเซต และฟังก์ชันใน Haskell เป็นฟังก์ชันทางคณิตศาสตร์ที่อยู่ระหว่างนั้น มีปัญหาเล็กๆ น้อยๆ ประการหนึ่ง นั่นคือ ฟังก์ชันทางคณิตศาสตร์ไม่ได้ใช้โค้ดใดๆ แต่รู้เพียงคำตอบเท่านั้น ฟังก์ชันใน Haskell จะต้องคำนวณคำตอบ นี่ไม่ใช่ปัญหาหากสามารถหาคำตอบได้ในจำนวนขั้นตอนที่จำกัด ไม่ว่าจะมากเพียงใด แต่มีการคำนวณบางอย่างที่เกี่ยวข้องกับการเรียกซ้ำ และการคำนวณเหล่านั้นอาจไม่มีวันเสร็จสิ้น เราไม่สามารถยกเลิกการอนุญาตฟังก์ชันที่ไม่ยุติใน Haskell ได้ เนื่องจากการแยกแยะว่าฟังก์ชันยุติหรือไม่ (ปัญหาการหยุดอันโด่งดัง) นั้นไม่สามารถแก้ไขได้ นั่นเป็นสาเหตุที่นักวิทยาศาสตร์คอมพิวเตอร์เกิดแนวคิดที่ยอดเยี่ยม หรือการแฮ็กสกปรก ขึ้นอยู่กับมุมมองของคุณ เพื่อขยายแต่ละประเภทด้วยค่าพิเศษที่เรียกว่า ก้น (หมายเหตุผู้แปล: คำนี้ (ล่าง) ฟังดูงี่เง่าในภาษารัสเซีย ถ้าใครรู้ตัวเลือกที่ดีช่วยแนะนำด้วย)ซึ่งเขียนแทนด้วย _|_ หรือใน Unicode ⊥ "ค่า" นี้สอดคล้องกับการคำนวณที่ไม่สมบูรณ์ ดังนั้นฟังก์ชันจึงประกาศเป็น:
f::บูล -> บูล
อาจส่งคืน True, False หรือ _|_; อย่างหลังหมายความว่าฟังก์ชันไม่เคยเสร็จสมบูรณ์

สิ่งที่น่าสนใจคือ เมื่อคุณยอมรับ Bottom ลงในระบบประเภทแล้ว จะสะดวกที่จะถือว่าข้อผิดพลาดรันไทม์ทั้งหมดเป็นด้านล่าง และแม้แต่อนุญาตให้ฟังก์ชันส่งคืนด้านล่างอย่างชัดเจน อย่างหลังมักจะทำโดยใช้นิพจน์ที่ไม่ได้กำหนด:
f::Bool -> Bool f x = ไม่ได้กำหนด
คำจำกัดความนี้ผ่านการตรวจสอบประเภทเนื่องจากไม่ได้กำหนดการประเมินไปที่ด้านล่าง ซึ่งรวมอยู่ในทุกประเภท รวมถึง Bool คุณยังสามารถเขียน:
f::Bool -> Bool f = ไม่ได้กำหนด
(ไม่มี x) เพราะด้านล่างเป็นสมาชิกประเภท Bool -> Bool ด้วย

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

เนื่องจากด้านล่าง หมวดหมู่ของประเภทและฟังก์ชันของ Haskell จึงเรียกว่า Hask ไม่ใช่ Set จากมุมมองทางทฤษฎี นี่เป็นสาเหตุของภาวะแทรกซ้อนที่ไม่มีที่สิ้นสุด ดังนั้น ณ จุดนี้ ฉันจะใช้มีดแล่เนื้อและเรียกมันว่าวันละครั้ง จากมุมมองเชิงปฏิบัติ คุณสามารถละเว้นฟังก์ชันที่ไม่สิ้นสุดและด้านล่าง และถือว่า Hask เป็นชุดที่เต็มเปี่ยม

ทำไมเราต้องมีแบบจำลองทางคณิตศาสตร์?

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

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

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

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

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

พิจารณาคำจำกัดความของฟังก์ชันแฟกทอเรียลใน Haskell ซึ่งเป็นภาษาที่ยืมตัวไปสู่ความหมายเชิง denotational ได้อย่างง่ายดาย:
ข้อเท็จจริง n = ผลิตภัณฑ์
นิพจน์คือรายการจำนวนเต็มตั้งแต่ 1 ถึง n ฟังก์ชันผลิตภัณฑ์จะคูณองค์ประกอบทั้งหมดของรายการ เหมือนกับคำจำกัดความของแฟกทอเรียลที่นำมาจากตำราเรียนทุกประการ เปรียบเทียบสิ่งนี้กับ C:
int fact(int n) ( int i; int result = 1; สำหรับ (i = 2; i<= n; ++i) result *= i; return result; }
ฉันควรดำเนินการต่อหรือไม่? (หมายเหตุผู้แปล: ผู้เขียนโกงเล็กน้อยโดยใช้ฟังก์ชั่นห้องสมุดใน Haskell อันที่จริงไม่จำเป็นต้องโกง คำอธิบายที่ตรงไปตรงมาตามคำจำกัดความนั้นไม่ยากอีกต่อไป):
ข้อเท็จจริง 0 = 1 ข้อเท็จจริง n = n * ข้อเท็จจริง (n - 1)
โอเค ยอมรับทันทีว่าช็อตถูก! แฟกทอเรียลมีคำจำกัดความทางคณิตศาสตร์ที่ชัดเจน ผู้อ่านที่ชาญฉลาดอาจถามว่า: แบบจำลองทางคณิตศาสตร์สำหรับการอ่านอักขระจากแป้นพิมพ์หรือการส่งแพ็กเก็ตผ่านเครือข่ายคืออะไร? เป็นเวลานานแล้วที่คำถามนี้อาจเป็นคำถามที่น่าอึดอัดใจ นำไปสู่การอธิบายที่ค่อนข้างสับสน ความหมายเชิง Denotational ดูเหมือนจะไม่เหมาะสมสำหรับปัญหาสำคัญจำนวนมากที่จำเป็นสำหรับการเขียนโปรแกรมที่มีประโยชน์ และสามารถแก้ไขได้ง่ายด้วยความหมายเชิงปฏิบัติการ ความก้าวหน้ามาจากทฤษฎีหมวดหมู่ Eugenio Moggi ค้นพบว่าเอฟเฟกต์ทางคอมพิวเตอร์สามารถแปลงเป็น Monad ได้ สิ่งนี้กลายเป็นข้อสังเกตที่สำคัญที่ไม่เพียงแต่ทำให้ความหมายเชิง denotational ชีวิตใหม่ และทำให้โปรแกรมที่ใช้งานได้ล้วนสะดวกยิ่งขึ้น แต่ยังให้ข้อมูลใหม่เกี่ยวกับการเขียนโปรแกรมแบบดั้งเดิมอีกด้วย ฉันจะพูดถึงพระสงฆ์ในภายหลังเมื่อเราพัฒนาเครื่องมือที่มีหมวดหมู่มากขึ้น

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

ฟังก์ชั่นที่สะอาดและสกปรก

สิ่งที่เราเรียกว่าฟังก์ชันในภาษา C++ หรือภาษาที่จำเป็นอื่นๆ นั้นไม่เหมือนกับสิ่งที่นักคณิตศาสตร์เรียกว่าฟังก์ชัน ฟังก์ชันทางคณิตศาสตร์เป็นเพียงการแมปจากค่าหนึ่งไปยังอีกค่าหนึ่ง

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

นอกจากนี้ การคำนวณกำลังสองของตัวเลขไม่ควรมีผลข้างเคียงในการให้ขนมสุนัขของคุณอร่อยๆ "ฟังก์ชัน" ที่ทำสิ่งนี้ไม่สามารถสร้างแบบจำลองด้วยฟังก์ชันทางคณิตศาสตร์ได้อย่างง่ายดาย

ในภาษาโปรแกรม ฟังก์ชันที่ให้ผลลัพธ์เดียวกันบนอาร์กิวเมนต์เดียวกันเสมอและไม่มีผลข้างเคียงเรียกว่าบริสุทธิ์ ในภาษาเชิงฟังก์ชันล้วนๆ เช่น Haskell ฟังก์ชันทั้งหมดล้วนล้วนแต่ ทำให้ง่ายต่อการกำหนดความหมายเชิง denotational ของภาษาเหล่านี้และสร้างแบบจำลองโดยใช้ทฤษฎีหมวดหมู่ สำหรับภาษาอื่นๆ คุณสามารถจำกัดตัวเองให้อยู่ในกลุ่มย่อยล้วนๆ หรือคิดถึงผลข้างเคียงแยกกันได้ตลอดเวลา ต่อไปเราจะมาดูกันว่า monads อนุญาตให้เราสร้างโมเดลเอฟเฟกต์ทุกประเภทโดยใช้ฟังก์ชันล้วนๆ ได้อย่างไร ผลก็คือ เราไม่สูญเสียอะไรเลยจากการจำกัดตัวเองให้อยู่กับฟังก์ชันทางคณิตศาสตร์เท่านั้น

ตัวอย่างประเภท

เมื่อคุณตัดสินใจว่าประเภทเป็นชุด คุณสามารถสร้างตัวอย่างที่แปลกใหม่ได้ เช่น ประเภทใดที่สอดคล้องกับเซตว่าง? ไม่ มันไม่ถือเป็นโมฆะในภาษา C++ แม้ว่าประเภทนี้จะเรียกว่า Void ใน Haskell ก็ตาม นี่คือประเภทที่ไม่เติมค่าใดๆ คุณสามารถกำหนดฟังก์ชันที่รับ Void ได้ แต่คุณไม่สามารถเรียกฟังก์ชันนั้นได้ หากต้องการเรียกมัน คุณต้องระบุค่าประเภท Void และไม่มีอยู่จริง ส่วนสิ่งที่ฟังก์ชันนี้สามารถส่งคืนได้นั้นไม่มีข้อจำกัด มันสามารถส่งคืนประเภทใดก็ได้ (แม้ว่าสิ่งนี้จะไม่เกิดขึ้นเพราะไม่สามารถเรียกได้) กล่าวอีกนัยหนึ่ง มันเป็นฟังก์ชันที่มีความหลากหลายในชนิดส่งคืน พวกแฮสเคลเลอร์เรียกมันว่า:
ไร้สาระ::โมฆะ -> ก
(หมายเหตุผู้แปล: เป็นไปไม่ได้ที่จะกำหนดฟังก์ชันดังกล่าวใน C++: ใน C++ แต่ละประเภทจะมีค่าอย่างน้อยหนึ่งค่า)

(โปรดจำไว้ว่า a เป็นตัวแปรประเภท ซึ่งสามารถเป็นประเภทใดก็ได้) ชื่อนี้ไม่ใช่เรื่องบังเอิญ มีการตีความประเภทและฟังก์ชันที่ลึกซึ้งยิ่งขึ้นในแง่ของตรรกะที่เรียกว่า Curry-Howard isomorphism ประเภท Void แสดงถึงความไม่จริง และฟังก์ชันไร้สาระแสดงถึงการยืนยันว่าบางสิ่งตามมาจากความเท็จ ดังในภาษาละตินวลี "ex falso sequitur quodlibet" (หมายเหตุผู้แปล: สิ่งใดก็ตามที่ตามมาจากความเท็จ)

ถัดมาเป็นประเภทที่สอดคล้องกับชุดซิงเกิลตัน นี่คือประเภทที่มีค่าที่เป็นไปได้เพียงค่าเดียว ความหมายนี้เป็นเพียง "มี" คุณอาจจำมันไม่ได้ในทันที แต่เป็นโมฆะในภาษา C ++ ลองนึกถึงฟังก์ชันจากและไปยังประเภทนี้ สามารถเรียกฟังก์ชัน void ได้เสมอ หากเป็นฟังก์ชันล้วนๆ ก็จะส่งกลับผลลัพธ์เดียวกันเสมอ นี่คือตัวอย่างของฟังก์ชันดังกล่าว:
int f44() ( กลับ 44; )
คุณอาจคิดว่าฟังก์ชันนี้ยอมรับ "nothing" แต่อย่างที่เราเพิ่งเห็น ไม่สามารถเรียกใช้ฟังก์ชันที่ยอมรับ "nothing" ได้เนื่องจากไม่มีค่าที่แสดงถึงประเภท "nothing" แล้วฟังก์ชันนี้ยอมรับอะไร? ตามหลักการแล้ว ต้องใช้ค่าจำลองที่มีเพียงอินสแตนซ์เดียว ดังนั้นเราจึงไม่จำเป็นต้องระบุอย่างชัดเจนในโค้ด อย่างไรก็ตาม Haskell มีสัญลักษณ์สำหรับความหมายนี้: วงเล็บคู่ว่าง () ดังนั้น เนื่องจากความบังเอิญที่น่าตลก (หรือไม่ใช่เรื่องบังเอิญ?) การเรียกใช้ฟังก์ชันจาก void จึงดูเหมือนกันทั้งใน C++ และ Haskell นอกจากนี้ เนื่องจาก Haskell ชอบความกะทัดรัด จึงมีการใช้สัญลักษณ์เดียวกัน () สำหรับประเภท ตัวสร้าง และค่าเดี่ยวที่สอดคล้องกับชุดซิงเกิลตัน นี่คือฟังก์ชั่นใน Haskell:
f44::() -> จำนวนเต็ม f44() = 44
บรรทัดแรกประกาศว่า f44 จะแปลงประเภท () ที่เรียกว่า "one" เป็นประเภท Integer บรรทัดที่สองระบุว่า f44 ใช้การจับคู่รูปแบบเพื่อแปลงคอนสตรัคเตอร์เพียงตัวเดียวสำหรับหนึ่งรายการ นั่นคือ () ให้เป็นตัวเลข 44 คุณเรียกใช้ฟังก์ชันนี้โดยระบุค่า ():
f44()
โปรดทราบว่าแต่ละฟังก์ชันของฟังก์ชันหนึ่งเทียบเท่ากับการเลือกองค์ประกอบหนึ่งรายการจากประเภทเป้าหมาย (ในที่นี้ เลือกจำนวนเต็ม 44) ที่จริงแล้ว คุณสามารถนึกถึง f44 ว่าเป็นอีกตัวแทนหนึ่งของตัวเลข 44 นี่คือตัวอย่างวิธีที่เราสามารถแทนที่การอ้างอิงโดยตรงไปยังองค์ประกอบของเซตด้วยฟังก์ชัน (ลูกศร) ฟังก์ชันจากหนึ่งถึงบางประเภท A อยู่ในการติดต่อแบบหนึ่งต่อหนึ่งกับองค์ประกอบของเซต A

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

ในทางคณิตศาสตร์ ฟังก์ชันจากเซต A ถึงเซตเดี่ยวจะจับคู่แต่ละองค์ประกอบกับองค์ประกอบเดียวของเซตนั้น สำหรับ A แต่ละตัวจะมีฟังก์ชันดังกล่าวเพียงฟังก์ชันเดียวเท่านั้น นี่คือสำหรับจำนวนเต็ม:
fInt::จำนวนเต็ม -> () fInt x = ()
คุณให้จำนวนเต็มใดๆ แก่มัน และมันจะคืนค่าหนึ่ง ด้วยจิตวิญญาณแห่งความกะทัดรัด Haskell อนุญาตให้ใช้ขีดล่างเป็นข้อโต้แย้งซึ่งจะถูกละทิ้งไป ด้วยวิธีนี้ไม่จำเป็นต้องตั้งชื่อให้ โค้ดด้านบนสามารถเขียนใหม่ได้เป็น:
fInt::จำนวนเต็ม -> () fInt_ = ()
โปรดทราบว่าการดำเนินการของฟังก์ชันนี้ไม่เพียงแต่เป็นอิสระจากค่าที่ส่งผ่านไปเท่านั้น แต่ยังเป็นอิสระจากประเภทของอาร์กิวเมนต์ด้วย

ฟังก์ชันที่สามารถกำหนดได้ด้วยสูตรเดียวกันสำหรับประเภทใดๆ เรียกว่า โพลิมอร์ฟิกแบบพาราเมตริก คุณสามารถใช้ฟังก์ชันดังกล่าวทั้งกลุ่มได้ด้วยสมการเดียว โดยใช้พารามิเตอร์แทนประเภทเฉพาะ จะเรียกใช้ฟังก์ชัน polymorphic จากชนิดใดชนิดหนึ่งไปได้อย่างไร? แน่นอน เราจะเรียกมันว่าหน่วย:
หน่วย::a -> () หน่วย _ = ()
ใน C ++ คุณจะใช้มันในลักษณะนี้:
แม่แบบ หน่วยเป็นโมฆะ(T) ()
(หมายเหตุผู้แปล: เพื่อช่วยให้คอมไพเลอร์ปรับให้เหมาะสมใน noop จะดีกว่า):
แม่แบบ หน่วยเป็นโมฆะ(T&&) ()
ถัดไปใน "ประเภทของประเภท" คือชุดของสององค์ประกอบ ในภาษา C++ เรียกว่า bool และใน Haskell ก็ไม่น่าแปลกใจที่เรียกว่า Bool ข้อแตกต่างก็คือใน C++ bool นั้นเป็นประเภทในตัว ในขณะที่ใน Haskell สามารถกำหนดได้ดังนี้:
ข้อมูลบูล = จริง | เท็จ
(คำจำกัดความนี้ควรอ่านได้ดังนี้: Bool อาจเป็น True หรือ Falseก็ได้) โดยหลักการแล้ว เป็นไปได้ที่จะอธิบายประเภทนี้ในภาษา C++:
บูล enum (จริง, เท็จ);
แต่การแจงนับ C++ นั้นเป็นจำนวนเต็มจริงๆ เราสามารถใช้ "class enum" ของ C ++ 11 ได้ แต่จากนั้นจะต้องมีคุณสมบัติค่าด้วยชื่อคลาส: bool::true หรือ bool::false ไม่ต้องพูดถึงความจำเป็นในการรวมส่วนหัวที่เกี่ยวข้องในทุกไฟล์ ที่ใช้มัน

ฟังก์ชัน Pure Bool เพียงเลือกค่าสองค่าจากประเภทเป้าหมาย หนึ่งค่าที่สอดคล้องกับ True และอีกค่าหนึ่งสอดคล้องกับค่าเท็จ

ฟังก์ชันใน Bool เรียกว่าเพรดิเคต ตัวอย่างเช่น ไลบรารี Data.Char ใน Haskell มีเพรดิเคตจำนวนมาก เช่น IsAlpha หรือ isDigit มีไลบรารีที่คล้ายกันใน C ++ ซึ่งประกาศฟังก์ชัน isalpha และ isdigit เหนือสิ่งอื่นใด แต่จะส่งคืน int แทนที่จะเป็นค่าบูลีน ภาคแสดงปัจจุบันถูกกำหนดไว้ใน และถูกเรียกว่า ctype::is(alpha, c) และ ctype::is(digit, c)



 


อ่าน:


ใหม่

วิธีฟื้นฟูรอบประจำเดือนหลังคลอดบุตร:

ไดเรกทอรีไดโอด ไดโอดเรียงกระแสกำลังสูง 220V

ไดเรกทอรีไดโอด ไดโอดเรียงกระแสกำลังสูง 220V

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

วิธีรีเซ็ตรหัสผ่านผู้ดูแลระบบบน Mac OS X โดยไม่ต้องใช้แผ่นดิสก์การติดตั้ง

วิธีรีเซ็ตรหัสผ่านผู้ดูแลระบบบน Mac OS X โดยไม่ต้องใช้แผ่นดิสก์การติดตั้ง

แม้จะมีชื่อที่ไม่ชัดเจน แต่บทความนี้จะไม่เกี่ยวกับการแฮ็กบัญชีใน Mac OS X (คุณสามารถอ่านเกี่ยวกับเรื่องนี้ได้หากต้องการ...

การตั้งค่า Shadow Defender

การตั้งค่า Shadow Defender

และอื่นๆ อีกมากมาย โดยเฉพาะอย่างยิ่ง เราได้กล่าวถึงสิ่งต่างๆ เช่น (ซึ่งสามารถทำหน้าที่ป้องกันการติดเชื้อได้ หรืออย่างน้อยก็เป็นวิธีหนึ่งในการกลับมา...

ทำไมโปรเซสเซอร์ในคอมพิวเตอร์ของฉันถึงร้อนจัด?

ทำไมโปรเซสเซอร์ในคอมพิวเตอร์ของฉันถึงร้อนจัด?

ฉันไม่ได้วางแผนที่จะเขียนบทความนี้ มีคำถามมากมายเกี่ยวกับแล็ปท็อปที่ร้อนเกินไป การทำความสะอาด และการเปลี่ยนแผ่นระบายความร้อน บน...

ฟีดรูปภาพ อาร์เอสเอส