การพิสูจน์นั้นยากกว่าในโลกของ OOP เนื่องจากผลข้างเคียงการสืบทอดที่ไม่ จำกัด และnull
การเป็นสมาชิกของทุกประเภท หลักฐานส่วนใหญ่ใช้หลักการอุปนัยเพื่อแสดงให้เห็นว่าคุณได้ครอบคลุมทุกความเป็นไปได้และสิ่งทั้งสามนั้นทำให้ยากต่อการพิสูจน์
สมมติว่าเรากำลังใช้ต้นไม้ไบนารีที่มีค่าจำนวนเต็ม (เพื่อรักษาไวยากรณ์ให้ง่ายขึ้นฉันจะไม่นำการเขียนโปรแกรมทั่วไปมาใช้ในสิ่งนี้แม้ว่ามันจะไม่เปลี่ยนแปลงอะไรเลย) ใน ML มาตรฐานฉันจะกำหนดเช่นนั้น นี้:
datatype tree = Empty | Node of (tree * int * tree)
สิ่งนี้นำเสนอชนิดใหม่ที่เรียกว่าtree
ค่าที่สามารถมีได้สองพันธุ์ (หรือคลาสเพื่อไม่ให้สับสนกับแนวคิด OOP ของคลาส) - Empty
ค่าที่ไม่มีข้อมูลและNode
ค่าที่มี 3-tuple ซึ่งเป็นอันดับแรกและสุดท้าย องค์ประกอบและองค์ประกอบกลางซึ่งเป็นtree
int
การประมาณการประกาศนี้ใน OOP ที่ใกล้เคียงที่สุดจะเป็นดังนี้:
public class Tree {
private Tree() {} // Prevent external subclassing
public static final class Empty extends Tree {}
public static final class Node extends Tree {
public final Tree leftChild;
public final int value;
public final Tree rightChild;
public Node(Tree leftChild, int value, Tree rightChild) {
this.leftChild = leftChild;
this.value = value;
this.rightChild = rightChild;
}
}
}
ด้วยข้อแม้ที่ตัวแปรประเภทต้นไม้ไม่สามารถเป็นnull
ได้
ทีนี้ลองเขียนฟังก์ชั่นเพื่อคำนวณความสูง (หรือความลึก) ของทรีและสมมติว่าเราเข้าถึงmax
ฟังก์ชั่นที่คืนค่าตัวเลขสองตัวที่ใหญ่กว่า:
fun height(Empty) =
0
| height(Node (leftChild, value, rightChild)) =
1 + max( height(leftChild), height(rightChild) )
เราได้นิยามheight
ฟังก์ชันตามกรณี - มีหนึ่งคำนิยามสำหรับEmpty
ต้นไม้และอีกหนึ่งคำนิยามสำหรับNode
ต้นไม้ คอมไพเลอร์รู้จำนวนต้นไม้ที่มีอยู่และจะส่งคำเตือนหากคุณไม่ได้กำหนดทั้งสองกรณี การแสดงออกNode (leftChild, value, rightChild)
ในลายเซ็นของฟังก์ชั่นผูกค่าของ 3 tuple เพื่อตัวแปรleftChild
, value
และrightChild
ตามลำดับเพื่อให้เราสามารถหมายถึงพวกเขาในความหมายฟังก์ชั่น มันคล้ายกับการประกาศตัวแปรท้องถิ่นเช่นนี้ในภาษา OOP:
Tree leftChild = tuple.getFirst();
int value = tuple.getSecond();
Tree rightChild = tuple.getThird();
เราจะพิสูจน์ได้height
อย่างไรว่าเราได้ปฏิบัติอย่างถูกต้อง? เราสามารถใช้การเหนี่ยวนำโครงสร้างซึ่งประกอบด้วย: 1. พิสูจน์ว่าheight
ถูกต้องในกรณีพื้นฐาน (s) tree
ประเภทของเรา( Empty
) 2. สมมติว่าการเรียกซ้ำheight
จะถูกต้องพิสูจน์ว่าheight
ถูกต้องสำหรับกรณีที่ไม่ใช่ฐาน ) (เมื่อต้นไม้เป็นจริงNode
)
สำหรับขั้นตอนที่ 1 เราจะเห็นว่าฟังก์ชันส่งกลับค่า 0 เสมอเมื่ออาร์กิวเมนต์เป็นEmpty
แผนผัง สิ่งนี้ถูกต้องตามคำจำกัดความของความสูงของต้นไม้
สำหรับขั้นตอนที่ 2 1 + max( height(leftChild), height(rightChild) )
ผลตอบแทนที่ฟังก์ชั่น สมมติว่าการเรียกซ้ำแบบเรียกคืนความสูงของเด็กอย่างแท้จริงเราจะเห็นว่าสิ่งนี้ถูกต้องเช่นกัน
และนั่นก็เป็นการพิสูจน์ที่สมบูรณ์ ขั้นตอนที่ 1 และ 2 รวมกันหมดความเป็นไปได้ทั้งหมด อย่างไรก็ตามโปรดทราบว่าเราไม่มีการกลายพันธุ์ไม่มีโมฆะและมีต้นไม้สองสายพันธุ์ นำเงื่อนไขทั้งสามนี้ไปใช้และการพิสูจน์อย่างรวดเร็วจะซับซ้อนมากขึ้นหากไม่สามารถปฏิบัติได้
แก้ไข:เนื่องจากคำตอบนี้ได้เพิ่มขึ้นไปด้านบนฉันต้องการเพิ่มตัวอย่างเล็กน้อยของการพิสูจน์และครอบคลุมการเหนี่ยวนำโครงสร้างครอบคลุมอีกเล็กน้อย ข้างต้นเราพิสูจน์แล้วว่าถ้าheight
ผลตอบแทนค่าส่งคืนนั้นถูกต้อง เราไม่ได้พิสูจน์ว่ามันจะส่งคืนค่าเสมอ เราสามารถใช้การเหนี่ยวนำเชิงโครงสร้างเพื่อพิสูจน์สิ่งนี้ได้เช่นกัน (หรือคุณสมบัติอื่น ๆ ) อีกครั้งในระหว่างขั้นตอนที่ 2 เราได้รับอนุญาตให้สมมติว่าการพักสายของการเรียกซ้ำเกิดขึ้นตราบใดที่การเรียกซ้ำเกิดซ้ำทั้งหมด ต้นไม้.
ฟังก์ชั่นสามารถล้มเหลวในการคืนค่าในสองสถานการณ์: ถ้ามันโยนข้อยกเว้นและถ้ามันวนซ้ำตลอดไป ก่อนอื่นเรามาพิสูจน์ว่าหากไม่มีข้อยกเว้นเกิดขึ้นฟังก์ชันจะยุติลง:
พิสูจน์ว่า (หากไม่มีข้อยกเว้นถูกโยนทิ้ง) ฟังก์ชั่นจะยุติลงสำหรับเคสพื้นฐาน ( Empty
) เนื่องจากเราคืนค่า 0 โดยไม่มีเงื่อนไขจึงสิ้นสุดลง
พิสูจน์ว่าฟังก์ชั่นนี้สิ้นสุดในกรณีที่ไม่ใช่ฐาน ( Node
) มีสามสายฟังก์ชั่นที่นี่: +
, และmax
height
เรารู้+
และmax
ยุติเพราะพวกเขาเป็นส่วนหนึ่งของห้องสมุดมาตรฐานของภาษาและพวกเขาได้กำหนดไว้อย่างนั้น ดังที่ได้กล่าวไว้ก่อนหน้านี้เราได้รับอนุญาตให้สมมติคุณสมบัติที่เรากำลังพยายามพิสูจน์ว่าเป็นจริงในการโทรแบบเรียกซ้ำตราบใดที่พวกเขาทำงานบนทรีย่อยทันทีดังนั้นการโทรก็จะheight
ยุติลงเช่นกัน
สรุปได้ว่าหลักฐาน โปรดทราบว่าคุณจะไม่สามารถพิสูจน์การสิ้นสุดด้วยการทดสอบหน่วย ตอนนี้สิ่งที่เหลืออยู่ก็คือการแสดงที่height
ไม่ส่งข้อยกเว้น
- พิสูจน์ว่า
height
ไม่มีข้อยกเว้นในกรณีพื้นฐาน ( Empty
) การส่งคืน 0 ไม่สามารถโยนข้อยกเว้นได้เราจึงเสร็จสิ้น
- พิสูจน์ว่า
height
ไม่มีข้อยกเว้นในกรณีที่ไม่ใช่ฐาน ( Node
) สมมติอีกครั้งว่าเรารู้+
และmax
ไม่โยนข้อยกเว้น และการเหนี่ยวนำเชิงโครงสร้างช่วยให้เราสามารถรับสายเรียกซ้ำไม่ได้เช่นกัน (เพราะทำงานกับลูก ๆ ของต้นไม้) แต่เดี๋ยวก่อน! ฟังก์ชั่นนี้เป็น recursive แต่ไม่recursive หาง เราสามารถระเบิดกอง! ข้อพิสูจน์ที่เราพยายามทำพบข้อผิดพลาด เราสามารถแก้ไขได้ด้วยการเปลี่ยนheight
ไปเป็นหาง recursive
ฉันหวังว่าสิ่งนี้แสดงให้เห็นว่าการพิสูจน์ไม่จำเป็นต้องน่ากลัวหรือซับซ้อน ในความเป็นจริงเมื่อใดก็ตามที่คุณเขียนโค้ดคุณได้สร้างหลักฐานไว้ในหัวอย่างไม่เป็นทางการ (ไม่เช่นนั้นคุณจะไม่เชื่อเลยว่าคุณเพิ่งใช้ฟังก์ชัน) โดยการหลีกเลี่ยงค่า Null การกลายพันธุ์ที่ไม่จำเป็นและการสืบทอดแบบไม่ จำกัด ถูกต้องค่อนข้างง่าย ข้อ จำกัด เหล่านี้ไม่รุนแรงเท่าที่คุณคิด:
null
เป็นข้อบกพร่องทางภาษาและการทำไปด้วยดีไม่มีเงื่อนไข
- บางครั้งการกลายพันธุ์นั้นเป็นสิ่งที่หลีกเลี่ยงไม่ได้และจำเป็น แต่ก็จำเป็นน้อยกว่าที่คุณคิดโดยเฉพาะเมื่อคุณมีโครงสร้างข้อมูลถาวร
- ขณะที่มีจำนวน จำกัด ของการเรียน (ในความรู้สึกที่ทำงาน) / subclasses (ในความหมาย OOP) ที่เทียบได้ไม่ จำกัด จำนวนของพวกเขาว่าเป็นเรื่องใหญ่เกินไปสำหรับคำตอบเดียว พอจะพูดได้ว่ามีการแลกเปลี่ยนการออกแบบ - ความสามารถในการพิสูจน์ความถูกต้องและความยืดหยุ่นในการขยาย