Ôi trời, tôi rất phấn khích khi cố gắng trả lời câu hỏi này tốt nhất có thể. Tôi hy vọng tôi có thể có được suy nghĩ của tôi đúng thứ tự.
Như @Doval đã đề cập và người hỏi đã chỉ ra (mặc dù thô lỗ), bạn không thực sự có một hệ thống loại. Bạn có một hệ thống kiểm tra động bằng cách sử dụng các thẻ, nói chung là yếu hơn nhiều, và cũng ít thú vị hơn nhiều.
Câu hỏi về "hệ thống kiểu gì" có thể khá triết lý và chúng ta có thể điền vào một cuốn sách với các quan điểm khác nhau về vấn đề này. Tuy nhiên, vì đây là trang dành cho lập trình viên, tôi sẽ cố gắng giữ câu trả lời của mình thực tế nhất có thể (và thực sự, các loại cực kỳ thực tế trong lập trình, mặc dù một số người có thể nghĩ).
Tổng quan
Hãy bắt đầu với một chiếc quần lót để hiểu hệ thống loại nào tốt cho việc này, trước khi đi sâu vào nền tảng chính thức hơn. Một hệ thống loại áp đặt cấu trúc trên các chương trình của chúng tôi . Chúng cho chúng ta biết làm thế nào chúng ta có thể cắm các chức năng và biểu thức khác nhau lại với nhau. Không có cấu trúc, các chương trình là không thể bảo vệ và cực kỳ phức tạp, sẵn sàng gây ra tác hại ở một lỗi nhỏ nhất của lập trình viên.
Viết chương trình với hệ thống kiểu giống như lái xe trong điều kiện đúc tiền - phanh hoạt động, cửa đóng an toàn, động cơ được bôi dầu, v.v. Viết chương trình không có hệ thống kiểu giống như đi xe máy không đội mũ bảo hiểm và làm bánh xe ra khỏi spaghetti. Bạn hoàn toàn không kiểm soát được bạn.
Để thảo luận, chúng ta hãy nói rằng chúng ta có một ngôn ngữ với biểu thức bằng chữ num[n]
và str[s]
đại diện cho chữ số n và chuỗi s, tương ứng, và các hàm nguyên thủy plus
và concat
, với ý nghĩa dự định. Rõ ràng, bạn không muốn có thể viết một cái gì đó như plus "hello" "world"
hoặc concat 2 4
. Nhưng làm thế nào chúng ta có thể ngăn chặn điều này? Một tiên nghiệm , không có phương pháp để phân biệt chữ số 2 với chuỗi "thế giới" theo nghĩa đen. Điều chúng tôi muốn nói là những biểu thức này nên được sử dụng trong các bối cảnh khác nhau; họ có nhiều loại khác nhau
Ngôn ngữ và các loại
Hãy lùi lại một chút: ngôn ngữ lập trình là gì? Nói chung, chúng ta có thể chia một ngôn ngữ lập trình thành hai lớp: cú pháp và ngữ nghĩa. Chúng cũng được gọi là statics và động lực tương ứng. Nó chỉ ra rằng hệ thống loại là cần thiết để trung gian tương tác giữa hai phần này.
Cú pháp
Một chương trình là một cái cây. Đừng để bị lừa bởi những dòng văn bản bạn viết trên máy tính; đây chỉ là những đại diện có thể đọc được của con người trong một chương trình. Chương trình này là một Cây Cú pháp Trừu tượng . Ví dụ: trong C chúng ta có thể viết:
int square(int x) {
return x * x;
}
Đó là cú pháp cụ thể cho chương trình (đoạn). Đại diện của cây là:
function square
/ | \
int int x return
|
times
/ \
x x
Một ngôn ngữ lập trình cung cấp một ngữ pháp xác định các cây hợp lệ của ngôn ngữ đó (có thể sử dụng cú pháp cụ thể hoặc trừu tượng). Điều này thường được thực hiện bằng cách sử dụng một cái gì đó như ký hiệu BNF. Tôi cho rằng bạn đã làm điều này cho ngôn ngữ bạn đã tạo.
Ngữ nghĩa
OK, chúng tôi biết chương trình là gì, nhưng nó chỉ là một cấu trúc cây tĩnh. Có lẽ, chúng tôi muốn chương trình của chúng tôi thực sự tính toán một cái gì đó. Chúng tôi cần ngữ nghĩa.
Ngữ nghĩa của ngôn ngữ lập trình là một lĩnh vực nghiên cứu phong phú. Nói rộng ra, có hai cách tiếp cận: ngữ nghĩa học và ngữ nghĩa hoạt động . Ngữ nghĩa học biểu thị mô tả một chương trình bằng cách ánh xạ nó vào một số cấu trúc toán học cơ bản (ví dụ: số tự nhiên, hàm liên tục, v.v.). cung cấp ý nghĩa cho chương trình của chúng tôi. Ngược lại, ngữ nghĩa hoạt động, định nghĩa một chương trình bằng cách chi tiết cách thức thực thi. Theo tôi, ngữ nghĩa hoạt động trực quan hơn đối với các lập trình viên (bao gồm cả bản thân tôi), vì vậy hãy gắn bó với điều đó.
Tôi sẽ không tìm hiểu cách xác định ngữ nghĩa hoạt động chính thức (các chi tiết có một chút liên quan), nhưng về cơ bản, chúng tôi muốn các quy tắc như sau:
num[n]
là một giá trị
str[s]
là một giá trị
- Nếu
num[n1]
và ước tính num[n2]
cho các số nguyên n_1$ and $n_2$, then
cộng (num [n1], num [n2]) `ước tính cho số nguyên $ n_1 + n_2 $.
- Nếu
str[s1]
và str[s2]
ước lượng cho chuỗi s1 và s2, sau đó concat(str[s1], str[s2])
ước tính cho chuỗi s1s2.
V.v. Các quy tắc trong thực tế chính thức hơn rất nhiều, nhưng bạn có được ý chính. Tuy nhiên, chúng tôi sớm gặp phải một vấn đề. Điều gì xảy ra khi chúng ta viết như sau:
concat(num[5], str[hello])
Hừm. Đây là một câu hỏi hóc búa. Chúng tôi chưa định nghĩa một quy tắc ở bất cứ đâu về cách nối một số với một chuỗi. Chúng tôi có thể cố gắng tạo ra một quy tắc như vậy, nhưng theo trực giác chúng tôi biết rằng hoạt động này là vô nghĩa. Chúng tôi không muốn chương trình này có hiệu lực. Và do đó, chúng tôi được dẫn đến các loại.
Các loại
Chương trình là một cái cây như được định nghĩa bởi ngữ pháp của ngôn ngữ. Các chương trình được đưa ra ý nghĩa bởi các quy tắc thực hiện. Nhưng một số chương trình không thể được thực thi; có nghĩa là, một số chương trình là vô nghĩa . Các chương trình này được đánh máy sai. Do đó, gõ đặc trưng cho các chương trình có ý nghĩa trong một ngôn ngữ. Nếu một chương trình được gõ tốt, chúng ta có thể thực hiện nó.
Hãy cho một số ví dụ. Một lần nữa, như với các quy tắc đánh giá, tôi sẽ trình bày các quy tắc đánh máy không chính thức, nhưng chúng có thể được thực hiện nghiêm ngặt. Dưới đây là một số quy tắc:
- Mã thông báo của biểu mẫu
num[n]
có loại nat
.
- Mã thông báo của biểu mẫu
str[s]
có loại str
.
- Nếu biểu thức
e1
có kiểu nat
và biểu thức e2
có kiểu nat
, thì biểu thức plus(e1, e2)
có kiểu nat
.
- Nếu biểu thức
e1
có kiểu str
và biểu thức e2
có kiểu str
, thì biểu thức concat(e1, e2)
có kiểu str
.
Vì vậy, theo các quy tắc này, có plus(num[5], num[2])
loại nat
, nhưng chúng ta không thể gán loại cho plus(num[5], str["hello"])
. Chúng tôi nói rằng một chương trình (hoặc biểu thức) được gõ tốt nếu chúng tôi có thể gán cho nó bất kỳ loại nào và nó được gõ sai. Một hệ thống loại là âm thanh nếu tất cả các chương trình gõ tốt có thể được thực thi. Haskell là âm thanh; C thì không.
Phần kết luận
Có quan điểm khác về các loại. Các loại trong một số ý nghĩa tương ứng với logic trực giác, và chúng cũng có thể được xem như là đối tượng trong lý thuyết thể loại. Hiểu các kết nối này là hấp dẫn, nhưng nó không cần thiết nếu người ta chỉ muốn viết hoặc thậm chí thiết kế một ngôn ngữ lập trình. Tuy nhiên, hiểu các loại như một công cụ để kiểm soát sự hình thành chương trình là điều cần thiết để thiết kế và phát triển ngôn ngữ lập trình. Tôi chỉ trầy xước bề mặt của những loại có thể thể hiện. Tôi hy vọng bạn nghĩ rằng chúng đủ giá trị để kết hợp với ngôn ngữ của bạn.