Giới Thiệu
Trong thế giới lập trình JavaScript và TypeScript, có rất nhiều khía cạnh thú vị mà các lập trình viên khám phá. Một trong những khía cạnh đó là toán tử bitwise và kiểu dữ liệu lồng ghép. Bài viết này sẽ giới thiệu về thư viện Pipetype, một thử nghiệm thú vị nhưng không thành công của tôi, nơi tôi cố gắng kết hợp các khái niệm này trong một cú pháp dễ hiểu.
1. Câu Chuyện Về Hai Ống |
JavaScript đầy những điều kỳ quặc, nhưng ít điều nào thú vị như dấu gạch đứng dọc |.
1.1 Toán Tử Hợp Nhất Trong TypeScript
Trong TypeScript, | là toán tử hợp nhất:
typescript
// Định nghĩa một kiểu ID có thể là string hoặc number
type ID = string | number;
→ “Hoặc là.” Một giá trị có thể là một chuỗi hoặc một số.
1.2 Trong Thế Giới Bitwise của JavaScript
Trong thế giới bitwise của JavaScript, | là toán tử để bật công tắc:
javascript
// Phép toán bitwise OR
console.log(1 | 2); // Kết quả: 3
// Mô tả chi tiết:
// 0001 | 0010 = 0011
→ “Cả hai cùng một lúc.” Một số mới với hai công tắc được bật.
1.3 Hai Thế Giới Khác Nhau
Hai thế giới. Cùng một ký hiệu. Nhưng lại mang những ý nghĩa khác nhau:
- Hợp nhất TypeScript = một giá trị có thể được xác thực bởi bất kỳ kiểu nào trong số này.
- Bitwise OR = bật bất kỳ tổ hợp nào trong số này.
Thí nghiệm mới của tôi bắt đầu từ đây. Tôi muốn biết chúng ta có thể tái tạo cú pháp của TypeScript trong thời gian thực đến mức nào.
2. BigInt Tham Gia Vào Bữa Tiệc
Các mẹo bitwise rất thú vị, nhưng số trong JavaScript chỉ cho bạn 53 bit an toàn. Điều này giống như có một bảng điều khiển chỉ với 53 nút bấm:
javascript
// Mô tả bảng điều khiển
const buttons = Array.from({ length: 53 }, (_, i) => `[ ${i + 1} ]`);
Điều này rất tuyệt cho một dự án nhỏ, nhưng thật tồi tệ khi bạn muốn có một số lượng hợp nhất tùy ý.
2.1 Giới Hạn Của Số Thông Thường
Nhập BigInt. Không có giới hạn về số bit:
javascript
// Bảng điều khiển không giới hạn
const infiniteButtons = Array.from({ length: 1000000 }, (_, i) => `[ ${i + 1} ]`);
BigInt trở thành nền tảng: mỗi trình xác thực nhận được một bit trong một bầu trời vô tận.
3. Bit Như Một Con Trỏ: Bản Đồ Xác Thực
Toàn bộ mẹo này hoạt động vì mỗi trình xác thực cần một bit duy nhất — giống như việc mỗi tủ trong một hành lang có công tắc đèn riêng của nó.
3.1 Tìm Hiểu Về Bit Shifting
Chúng ta bắt đầu với số 1n. Trong nhị phân, nó trông như thế này:
javascript
// Biểu diễn nhị phân
const one = 0b0001; // '0b' là tiền tố cho nhị phân
Áp dụng một bit shift trái (<<):
1n << 0n→ không di chuyển →0001(vẫn là 1n)1n << 1n→ di chuyển sang trái một lần →0010(2n)1n << 2n→ di chuyển sang trái hai lần →0100(4n)1n << 3n→ di chuyển sang trái ba lần →1000(8n)
Mỗi lần dịch chuyển là nhân với 2 — tạo ra chuỗi lũy thừa của 2:
javascript
// Chuỗi lũy thừa của 2
const powersOfTwo = [1n, 2n, 4n, 8n, 16n];
Đó là lý do tại sao dịch chuyển lại hữu ích ở đây: mỗi lần dịch chuyển đảm bảo chỉ có một bit được bật, do đó không có hai trình xác thực nào va chạm với nhau.
3.2 Sử Dụng Bit Shifts Cho Các Trình Xác Thực
Đây là cách chúng ta gán “các khe” duy nhất cho các trình xác thực của mình:
javascript
const validators = new Map<bigint, (x: unknown) => boolean>();
validators.set(1n << 0n, (x) => typeof x === "string"); // 0001
validators.set(1n << 1n, (x) => typeof x === "number"); // 0010
validators.set(1n << 2n, (x) => Array.isArray(x)); // 0100
Khi kết hợp chúng thật dễ dàng với |:
javascript
// Kết hợp các kiểu
const combined = (string | number); // 0001 | 0010 = 0011 (3n)
Và việc kiểm tra cũng dễ dàng với &:
javascript
// Kiểm tra sự tồn tại
console.log(0011 & 0010 === 0010); // true (bao gồm "number")
console.log(0011 & 0100 === 0000); // false (không bao gồm "array")
3.3 Giúp Việc Trong Việc Gán Trình Xác Thực
Việc viết 1n << 0n, 1n << 1n, 1n << 2n ở khắp mọi nơi khá nhàm chán. Vì vậy, chúng tôi thêm một trợ lý: getNextFlag() — để chúng ta có thể chỉ cần yêu cầu “bit tiếp theo có sẵn” thay vì phải dịch chuyển thủ công.
javascript
let lastFlag = 0n;
function getNextFlag(): bigint {
lastFlag = lastFlag === 0n ? 1n : lastFlag << 1n;
return lastFlag;
}
3.4 Thiết Lập Trình Xác Thực Sạch Hơn
Giờ việc gán các trình xác thực trông sẽ như thế này:
javascript
const validators = new Map<bigint, (x: unknown) => boolean>();
validators.set(getNextFlag(), (x) => typeof x === "string"); // 0001
validators.set(getNextFlag(), (x) => typeof x === "number"); // 0010
validators.set(getNextFlag(), (x) => Array.isArray(x)); // 0100
Dễ đọc hơn nhiều, nhưng bạn vẫn biết điều gì đang xảy ra bên dưới: mỗi cờ mới chỉ là một lũy thừa của 2.
4. Proxy: Biến Toán Học Thành Đường
Tất cả những điều này là thú vị, nhưng không ai muốn viết cờ cả ngày. Đó là nơi Proxy tỏa sáng: chúng ta chặn quyền truy cập thuộc tính và tạo cờ ngay lập tức.
javascript
const Type = new Proxy({}, {
get(_, key: string) {
const bit = getNextFlag();
validators.set(bit, (x) => typeof x === key);
return bit;
}
});
Bây giờ nó trông giống như TypeScript.
5. Giấc Mơ So Với Thực Tế
Tại thời điểm này, thí nghiệm cảm thấy kỳ diệu:
- Hợp nhất tiện lợi tại thời gian chạy.
- Mặt nạ BigInt cho trình xác thực vô hạn.
- Cú pháp trông giống như TypeScript.
Nhưng rồi thực tế ập đến.
Bộ biên dịch TypeScript không thể nhìn xuyên qua BigInt.
Từ góc độ hệ thống kiểu, những mặt nạ này chỉ là những con số. Mờ đục. Không thể suy diễn.
Ma thuật tan biến. Thiếu sự suy diễn, tính tiện lợi đã sụp đổ. TypeScript không thể thu hẹp. Không thể gợi ý. Không thể giúp đỡ.
Những gì bắt đầu như đường ngọt ngào kết thúc bằng toán học không bền vững tại thời gian chạy.
6. Thí Nghiệm Thất Bại Yêu Thích Của Tôi
Đến lúc này, tôi phải thừa nhận: Pipetype đã thất bại. Nó không phải là công cụ có thể thống nhất các hợp nhất tại thời gian chạy và biên dịch. Nó là một sự tò mò thông minh nhưng đã gục ngã dưới ánh mắt nghiêm khắc của bộ biên dịch TypeScript.
Nhưng tôi vẫn yêu nó.
- Nó đã dạy tôi cách mà
|có thể kéo dài. - Nó cho tôi thấy cách BigInt thay đổi sân chơi.
- Nó nhắc nhở tôi cách mà Proxies có thể giả dạng cú pháp.
Đôi khi những thí nghiệm tốt nhất là những thí nghiệm thất bại, vì chúng dạy bạn nơi mà giới hạn thực sự nằm.
Phụ Lục: Những Điều Sâu Sắc
- Bitwise Như Tủ Đồ: OR (
|) mở nhiều tủ. AND (&) kiểm tra xem một tủ có thuộc về không. - BigInt cho Trình Xác Thực Vô Hạn: Vượt qua 53 bit, số thông thường không ổn định. BigInt? Công tắc vô hạn.
- Proxy cho Đường:
Type.stringkhông phải là một chuỗi. Đó là một mặt nạ BigInt. - Tại Sao Nó Thất Bại: Không có sự suy diễn trong TypeScript. Không có khả năng thu hẹp, các hợp nhất tại thời gian chạy đã mất đi lợi thế của chúng.
👉 Đó là Pipetype: một sự kết hợp của các hợp nhất và mặt nạ bit, Proxies và đường ngọt, BigInts và giấc mơ — và bài học rằng đôi khi thất bại là điều thú vị nhất.