จุดประสงค์ของความหลากหลายคืออะไร?
ความหลากหลายทำให้ระบบประเภทคงที่มีความยืดหยุ่นมากขึ้นโดยไม่สูญเสียความปลอดภัยประเภทคงที่ (อย่างมีนัยสำคัญ) โดยการคลายเงื่อนไขสำหรับการเทียบเท่าประเภท หลักฐานยังคงอยู่ว่าโปรแกรมจะทำงานก็ต่อเมื่อไม่มีข้อผิดพลาดประเภทใด ๆ
ฟังก์ชัน polymorphic หรือชนิดข้อมูลมีความกว้างมากกว่า monomorphic เนื่องจากสามารถใช้ในสถานการณ์ที่กว้างขึ้นได้ ในแง่นี้ความหลากหลายแสดงถึงแนวคิดของการวางนัยทั่วไปในภาษาที่พิมพ์อย่างเคร่งครัด
สิ่งนี้ใช้กับ Javascript ได้อย่างไร?
Javascript มีระบบประเภทไดนามิกที่อ่อนแอ ระบบประเภทดังกล่าวเทียบเท่ากับระบบประเภทที่เข้มงวดซึ่งมีเพียงประเภทเดียว เราสามารถคิดว่าประเภทนี้เป็นประเภทยูเนี่ยนขนาดใหญ่ (ไวยากรณ์หลอก):
type T =
| Undefined
| Null
| Number
| String
| Boolean
| Symbol
| Object
| Array
| Map
| ...
ทุกค่าจะเชื่อมโยงกับทางเลือกประเภทใดประเภทหนึ่งเหล่านี้ในขณะรันไทม์ และเนื่องจาก Javascript ถูกพิมพ์อย่างอ่อนค่าทุกค่าสามารถเปลี่ยนประเภทกี่ครั้งก็ได้
หากเราใช้มุมมองเชิงทฤษฎีและพิจารณาว่ามีเพียงประเภทเดียวเราสามารถพูดได้อย่างมั่นใจว่าระบบประเภทของ Javascript ไม่มีแนวคิดเกี่ยวกับความหลากหลาย แต่เรามีการพิมพ์แบบเป็ดและการบังคับขู่เข็ญโดยนัย
แต่สิ่งนี้ไม่ควรทำให้เราคิดถึงประเภทต่างๆในโปรแกรมของเรา เนื่องจาก Javascript ไม่มีประเภทเราจึงจำเป็นต้องอนุมานระหว่างขั้นตอนการเข้ารหัส จิตใจของเราต้องยืนหยัดเพื่อคอมไพเลอร์ที่หายไปกล่าวคือทันทีที่เราดูโปรแกรมเราต้องจดจำไม่เพียง แต่อัลกอริทึมเท่านั้น แต่ยังรวมถึงประเภทพื้นฐาน (อาจจะเป็นหลายรูปแบบ) ด้วย ประเภทเหล่านี้จะช่วยให้เราสร้างโปรแกรมที่เชื่อถือได้และมีประสิทธิภาพมากขึ้น
เพื่อให้ทำสิ่งนี้ได้อย่างถูกต้องฉันจะให้ภาพรวมของอาการที่พบบ่อยที่สุดของความหลากหลาย
ความหลากหลายของพาราเมตริก (aka generics)
ความหลากหลายเชิงพาราเมตริกกล่าวว่าประเภทต่างๆสามารถใช้แทนกันได้เนื่องจากประเภทไม่สำคัญเลย ฟังก์ชันที่กำหนดพารามิเตอร์อย่างน้อยหนึ่งพารามิเตอร์ของประเภทพหุนามพาราเมตริกจะต้องไม่รู้อะไรเกี่ยวกับอาร์กิวเมนต์ที่เกี่ยวข้อง แต่ปฏิบัติเหมือนกันทั้งหมดเนื่องจากสามารถนำไปใช้กับประเภทใดก็ได้ สิ่งนี้ค่อนข้าง จำกัด เนื่องจากฟังก์ชันดังกล่าวสามารถใช้ได้เฉพาะกับคุณสมบัติของอาร์กิวเมนต์ที่ไม่ได้เป็นส่วนหนึ่งของข้อมูลเท่านั้น:
const id = x => x;
id(1);
id("foo");
const k = x => y => x;
const k_ = x => y => y;
k(1) ("foo");
k_(1) ("foo");
const append = x => xs => xs.concat([x]);
append(3) ([1, 2]);
append("c") (["a", "b"]);
ความหลากหลายของ Ad-hoc (aka overloading)
Ad-hoc polymorphism กล่าวว่าประเภทต่างๆมีความเท่าเทียมกันสำหรับวัตถุประสงค์เฉพาะเท่านั้น เพื่อให้เทียบเท่าในแง่นี้ประเภทต้องใช้ชุดของฟังก์ชันเฉพาะสำหรับวัตถุประสงค์นั้น ฟังก์ชันที่กำหนดพารามิเตอร์อย่างน้อยหนึ่งพารามิเตอร์ของชนิดโพลีมอร์ฟิค ad-hoc จำเป็นต้องทราบว่าชุดของฟังก์ชันใดที่เชื่อมโยงกับอาร์กิวเมนต์แต่ละตัว
ความหลากหลายของ Ad-hoc ทำให้ฟังก์ชันเข้ากันได้กับโดเมนประเภทใหญ่ ๆ ตัวอย่างต่อไปนี้แสดงให้เห็นถึงจุดประสงค์ "map-over" และวิธีใช้ข้อ จำกัด นี้ แทนที่จะเป็นชุดของฟังก์ชันข้อ จำกัด "mappable" จะรวมเฉพาะmap
ฟังก์ชันเดียว:
class Option {
cata(pattern, option) {
return pattern[option.constructor.name](option.x);
}
map(f, opt) {
return this.cata({Some: x => new Some(f(x)), None: () => this}, opt);
}
};
class Some extends Option {
constructor(x) {
super(x);
this.x = x;
}
};
class None extends Option {
constructor() {
super();
}
};
const map = f => t => t.map(f, t);
const sqr = x => x * x;
const xs = [1, 2, 3];
const x = new Some(5);
const y = new None();
console.log(
map(sqr) (xs)
);
console.log(
map(sqr) (x)
);
console.log(
map(sqr) (y)
);
ความแตกต่างของชนิดย่อย
เนื่องจากคำตอบอื่น ๆ ครอบคลุมความหลากหลายประเภทย่อยแล้วฉันจึงข้ามไป
ความหลากหลายของโครงสร้าง (aka strutrual subtyping)
ความหลากหลายเชิงโครงสร้างกล่าวว่าประเภทต่างๆมีความเท่าเทียมกันหากมีโครงสร้างเดียวกันในลักษณะนั้นประเภทหนึ่งมีคุณสมบัติทั้งหมดของอีกประเภทหนึ่ง แต่อาจรวมถึงคุณสมบัติเพิ่มเติม ดังที่กล่าวไว้ความแตกต่างของโครงสร้างคือการพิมพ์แบบเป็ดในเวลาคอมไพล์และแน่นอนว่ามีความปลอดภัยเพิ่มเติมบางประเภท แต่การอ้างว่าค่าสองค่าเป็นประเภทเดียวกันเพียงเพราะใช้คุณสมบัติบางอย่างร่วมกันมันจะละเว้นระดับความหมายของค่าโดยสิ้นเชิง:
const weight = {value: 90, foo: true};
const speed = {value: 90, foo: false, bar: [1, 2, 3]};
น่าเสียดายที่speed
ถือเป็นประเภทย่อยweight
และทันทีที่เราเปรียบเทียบvalue
คุณสมบัติเราแทบจะเปรียบเทียบแอปเปิ้ลกับส้ม