Mục đích của đa hình là gì?
Tính đa hình làm cho hệ thống kiểu tĩnh linh hoạt hơn mà không làm mất (đáng kể) sự an toàn kiểu tĩnh bằng cách nới lỏng các điều kiện cho sự tương đương kiểu. Bằng chứng vẫn là một chương trình sẽ chỉ chạy nếu nó không chứa bất kỳ lỗi loại nào.
Kiểu dữ liệu hoặc hàm đa hình thường tổng quát hơn kiểu dữ liệu đơn hình vì nó có thể được sử dụng trong nhiều tình huống hơn. Theo nghĩa này, tính đa hình thể hiện ý tưởng tổng quát hóa trong các ngôn ngữ được đánh máy nghiêm ngặt.
Điều này áp dụng cho Javascript như thế nào?
Javascript có một hệ thống kiểu động yếu. Một hệ thống loại như vậy tương đương với một hệ thống loại nghiêm ngặt chỉ chứa một loại. Chúng ta có thể coi kiểu như vậy là kiểu liên hợp lớn (cú pháp giả):
type T =
| Undefined
| Null
| Number
| String
| Boolean
| Symbol
| Object
| Array
| Map
| ...
Mọi giá trị sẽ được liên kết với một trong những lựa chọn thay thế loại này tại thời điểm chạy. Và vì Javascript được gõ yếu, mọi giá trị có thể thay đổi kiểu của nó bất kỳ số lần nào.
Nếu chúng ta xem xét quan điểm lý thuyết về kiểu và xem xét rằng chỉ có một kiểu, chúng ta có thể nói chắc chắn rằng hệ thống kiểu của Javascript không có khái niệm về tính đa hình. Thay vào đó chúng ta có kiểu gõ vịt và kiểu ép buộc ngầm.
Nhưng điều này không khiến chúng ta phải suy nghĩ về các loại trong chương trình của mình. Do thiếu các kiểu trong Javascript, chúng ta cần suy ra chúng trong quá trình viết mã. Tâm trí của chúng ta phải chờ đợi trình biên dịch bị thiếu, tức là ngay khi chúng ta nhìn vào một chương trình, chúng ta phải nhận ra không chỉ các thuật toán, mà còn cả các kiểu cơ bản (có thể là đa hình). Những loại này sẽ giúp chúng tôi xây dựng các chương trình đáng tin cậy hơn và mạnh mẽ hơn.
Để làm điều này đúng cách, tôi sẽ cung cấp cho bạn một cái nhìn tổng quan về các biểu hiện phổ biến nhất của đa hình.
Đa hình tham số (hay còn gọi là generic)
Tính đa hình tham số nói rằng các kiểu khác nhau có thể hoán đổi cho nhau vì các kiểu không quan trọng chút nào. Một hàm xác định một hoặc nhiều tham số của kiểu đa hình tham số không được biết gì về các đối số tương ứng nhưng đối xử với chúng như nhau, vì chúng có thể áp dụng cho bất kỳ kiểu nào. Điều này khá hạn chế, bởi vì một hàm như vậy chỉ có thể hoạt động với những thuộc tính của các đối số của nó mà không phải là một phần dữ liệu của chúng:
// parametric polymorphic functions
const id = x => x;
id(1); // 1
id("foo"); // "foo"
const k = x => y => x;
const k_ = x => y => y;
k(1) ("foo"); // 1
k_(1) ("foo"); // "foo"
const append = x => xs => xs.concat([x]);
append(3) ([1, 2]); // [1, 2, 3]
append("c") (["a", "b"]); // ["a", "b", "c"]
Đa hình Ad-hoc (còn gọi là quá tải)
Tính đa hình Ad-hoc nói rằng các kiểu khác nhau chỉ tương đương cho một mục đích cụ thể. Để tương đương theo nghĩa này, một kiểu phải triển khai một tập hợp các chức năng cụ thể cho mục đích đó. Khi đó, một hàm xác định một hoặc nhiều tham số của kiểu đa hình ad-hoc cần phải biết bộ hàm nào được liên kết với mỗi đối số của nó.
Tính đa hình đặc biệt làm cho một hàm tương thích với một miền lớn hơn của các kiểu. Ví dụ sau minh họa mục đích "map-over" và cách các kiểu có thể triển khai ràng buộc này. Thay vì một tập hợp hàm, ràng buộc "có thể lập bản đồ" chỉ bao gồm một map
hàm duy nhất :
// Option type
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();
}
};
// ad-hoc polymorphic function
const map = f => t => t.map(f, t);
// helper/data
const sqr = x => x * x;
const xs = [1, 2, 3];
const x = new Some(5);
const y = new None();
// application
console.log(
map(sqr) (xs) // [1, 4, 9]
);
console.log(
map(sqr) (x) // Some {x: 25}
);
console.log(
map(sqr) (y) // None {}
);
Đa hình kiểu phụ
Vì các câu trả lời khác đã bao gồm đa hình kiểu phụ nên tôi bỏ qua.
Tính đa hình cấu trúc (hay còn gọi là kiểu phụ nghiêm ngặt)
Đa hình cấu trúc nói rằng các kiểu khác nhau là tương đương, nếu chúng chứa cùng một cấu trúc theo cách đó, kiểu này có tất cả các thuộc tính của kiểu kia nhưng có thể bao gồm các thuộc tính bổ sung. Điều đó đang được nói, đa hình cấu trúc là gõ vịt tại thời điểm biên dịch và chắc chắn cung cấp một số an toàn kiểu bổ sung. Nhưng bằng cách tuyên bố rằng hai giá trị thuộc cùng một kiểu chỉ vì chúng chia sẻ một số thuộc tính, nó hoàn toàn bỏ qua cấp độ ngữ nghĩa của các giá trị:
const weight = {value: 90, foo: true};
const speed = {value: 90, foo: false, bar: [1, 2, 3]};
Thật không may, speed
nó được coi là một loại phụ weight
và ngay sau khi chúng tôi so sánh các value
đặc tính, chúng tôi gần như so sánh táo với cam.