Để tôn trọng người đọc nhanh, trước tiên tôi bắt đầu với định nghĩa chính xác, tiếp tục với phần giải thích "tiếng Anh đơn giản" nhanh hơn và sau đó chuyển sang các ví dụ.
Dưới đây là một định nghĩa ngắn gọn và chính xác được điều chỉnh lại một chút:
Một đơn nguyên (trong khoa học máy tính) chính thức là một bản đồ:
gửi mọi loại X
ngôn ngữ lập trình đã cho sang một loại mới T(X)
(được gọi là "loại tính T
toán có giá trị trong X
");
được trang bị một quy tắc để soạn hai chức năng của biểu mẫu
f:X->T(Y)
và g:Y->T(Z)
cho một chức năng g∘f:X->T(Z)
;
theo cách liên kết theo nghĩa hiển nhiên và unital đối với một hàm đơn vị nhất định được gọi pure_X:X->T(X)
, được coi là lấy một giá trị cho phép tính thuần túy trả về giá trị đó.
Vì vậy, nói một cách đơn giản, một đơn nguyên là một quy tắc để chuyển từ bất kỳ loại nào X
sang loại khácT(X)
và một quy tắc để chuyển từ hai chức năng f:X->T(Y)
và g:Y->T(Z)
(mà bạn muốn soạn nhưng không thể) cho một chức năng mớih:X->T(Z)
. Mà, tuy nhiên, không phải là thành phần trong ý nghĩa toán học nghiêm ngặt. Về cơ bản chúng ta là "uốn" thành phần của hàm hoặc xác định lại cách các hàm được tạo.
Thêm vào đó, chúng tôi yêu cầu quy tắc sáng tác của đơn nguyên để đáp ứng các tiên đề toán học "hiển nhiên":
- Tính kết hợp : Soạn
f
với g
và sau đó với h
(từ bên ngoài) phải giống như sáng tác g
với h
và sau đó với f
(từ bên trong).
- Tài sản Unital : Sáng tác
f
với chức năng nhận dạng ở hai bên sẽ mang lại f
.
Một lần nữa, nói một cách đơn giản, chúng ta không thể điên cuồng định nghĩa lại thành phần chức năng của mình như chúng ta muốn:
- Trước tiên chúng ta cần tính kết hợp để có thể soạn một số hàm liên tiếp
f(g(h(k(x)))
, ví dụ , và không phải lo lắng về việc chỉ định các cặp hàm tổng hợp thứ tự. Vì quy tắc đơn nguyên chỉ quy định cách tạo một cặp hàm , không có tiên đề đó, chúng ta sẽ cần biết cặp nào được tạo trước, v.v. (Lưu ý rằng khác với thuộc tính giao hoán được f
cấu thành g
giống như được g
cấu thành với f
, không bắt buộc).
- Và thứ hai, chúng ta cần tài sản unital, đơn giản chỉ cần nói rằng danh tính sáng tác tầm thường theo cách chúng ta mong đợi. Vì vậy, chúng ta có thể tái cấu trúc một cách an toàn bất cứ khi nào những danh tính đó có thể được trích xuất.
Vì vậy, một lần nữa tóm tắt: Một đơn nguyên là quy tắc mở rộng loại và các hàm tổng hợp thỏa mãn hai tiên đề - tính kết hợp và thuộc tính unital.
Trong điều kiện thực tế, bạn muốn đơn nguyên được triển khai cho bạn bằng ngôn ngữ, trình biên dịch hoặc khung sẽ đảm nhiệm việc soạn thảo các hàm cho bạn. Vì vậy, bạn có thể tập trung vào việc viết logic của hàm thay vì lo lắng về cách thực thi của chúng.
Đó là bản chất của nó, một cách ngắn gọn.
Là nhà toán học chuyên nghiệp, tôi thích tránh gọi h
"thành phần" của f
và g
. Bởi vì về mặt toán học, nó không phải là. Gọi nó là "thành phần" giả định không chính xác đó h
là thành phần toán học thực sự, mà nó không phải là. Nó thậm chí không được xác định duy nhất bởi f
và g
. Thay vào đó, nó là kết quả của "quy tắc sáng tác" mới của đơn nguyên của chúng tôi. Mà có thể hoàn toàn khác với thành phần toán học thực tế ngay cả khi cái sau tồn tại!
Để làm cho nó bớt khô hơn, hãy để tôi thử minh họa bằng ví dụ rằng tôi đang chú thích với các phần nhỏ, vì vậy bạn có thể bỏ qua ngay đến điểm.
Ngoại lệ ném như ví dụ Monad
Giả sử chúng ta muốn soạn hai hàm:
f: x -> 1 / x
g: y -> 2 * y
Nhưng f(0)
không được xác định, vì vậy một ngoại lệ e
được ném. Sau đó, làm thế nào bạn có thể xác định giá trị thành phần g(f(0))
? Ném một ngoại lệ một lần nữa, tất nhiên! Có lẽ giống nhau e
. Có thể một ngoại lệ mới được cập nhật e1
.
Chính xác thì chuyện gì xảy ra ở đây? Đầu tiên, chúng ta cần (các) giá trị ngoại lệ mới (khác nhau hoặc giống nhau). Bạn có thể gọi chúng nothing
hoặc null
bất cứ điều gì nhưng bản chất vẫn giống nhau - chúng phải là các giá trị mới, ví dụ: nó không nên là một number
ví dụ của chúng tôi ở đây. Tôi không muốn gọi cho họ null
để tránh nhầm lẫn với cách null
thực hiện bằng bất kỳ ngôn ngữ cụ thể nào. Tương tự, tôi thích tránh nothing
vì nó thường được liên kết với null
, về nguyên tắc, đó là điều null
nên làm, tuy nhiên, nguyên tắc đó thường bị bẻ cong vì bất kỳ lý do thực tế nào.
Chính xác ngoại lệ là gì?
Đây là một vấn đề không quan trọng đối với bất kỳ lập trình viên có kinh nghiệm nào nhưng tôi muốn bỏ vài từ chỉ để dập tắt bất kỳ con sâu nào của sự nhầm lẫn:
Ngoại lệ là một đối tượng đóng gói thông tin về cách kết quả thực hiện không hợp lệ xảy ra.
Điều này có thể bao gồm từ bỏ đi bất kỳ chi tiết nào và trả về một giá trị toàn cầu duy nhất (như NaN
hoặc null
) hoặc tạo danh sách nhật ký dài hoặc chính xác những gì đã xảy ra, gửi nó đến cơ sở dữ liệu và sao chép tất cả trên lớp lưu trữ dữ liệu phân tán;)
Sự khác biệt quan trọng giữa hai ví dụ cực đoan về ngoại lệ này là trong trường hợp đầu tiên không có tác dụng phụ . Trong cái thứ hai có. Điều này đưa chúng ta đến câu hỏi (ngàn đô la):
Là ngoại lệ được phép trong các chức năng thuần túy?
Câu trả lời ngắn hơn : Có, nhưng chỉ khi chúng không dẫn đến tác dụng phụ.
Câu trả lời dài hơn. Để được thuần túy, đầu ra của hàm của bạn phải được xác định duy nhất bởi đầu vào của nó. Vì vậy, chúng tôi sửa đổi chức năng f
của mình bằng cách gửi 0
đến giá trị trừu tượng mới e
mà chúng tôi gọi là ngoại lệ. Chúng tôi đảm bảo rằng giá trị e
không chứa thông tin bên ngoài không được xác định duy nhất bởi đầu vào của chúng tôi x
. Vì vậy, đây là một ví dụ về ngoại lệ mà không có tác dụng phụ:
e = {
type: error,
message: 'I got error trying to divide 1 by 0'
}
Và đây là một tác dụng phụ:
e = {
type: error,
message: 'Our committee to decide what is 1/0 is currently away'
}
Trên thực tế, nó chỉ có tác dụng phụ nếu thông điệp đó có thể thay đổi trong tương lai. Nhưng nếu nó được đảm bảo không bao giờ thay đổi, giá trị đó sẽ trở thành dự đoán duy nhất và do đó không có tác dụng phụ.
Để làm cho nó thậm chí sillier. Một chức năng trở lại 42
bao giờ rõ ràng là tinh khiết. Nhưng nếu ai đó điên rồ quyết định tạo 42
một biến có giá trị có thể thay đổi, thì chính hàm đó sẽ ngừng hoàn toàn trong các điều kiện mới.
Lưu ý rằng tôi đang sử dụng ký hiệu nghĩa đen cho đơn giản để thể hiện bản chất. Thật không may, mọi thứ bị rối tung trong các ngôn ngữ như JavaScript, error
không phải là loại hành xử theo cách chúng ta muốn ở đây đối với thành phần chức năng, trong khi các loại thực tế thích null
hoặc NaN
không hành xử theo cách này mà chỉ đi qua một số giả tạo và không phải lúc nào cũng trực quan chuyển đổi loại.
Kiểu mở rộng
Vì chúng tôi muốn thay đổi thông điệp bên trong ngoại lệ của mình, chúng tôi thực sự đang khai báo một loại mới E
cho toàn bộ đối tượng ngoại lệ và đó là những gì maybe number
nó làm, ngoài tên khó hiểu của nó, là loại number
hoặc loại ngoại lệ mới E
, vì vậy nó thực sự là sự kết hợp number | E
của number
và E
. Cụ thể, nó phụ thuộc vào cách chúng tôi muốn xây dựng E
, không được đề xuất cũng như không được phản ánh trong tên maybe number
.
Thành phần chức năng là gì?
Đây là toán học chức năng hoạt động lấy
f: X -> Y
và g: Y -> Z
và xây dựng thành phần của họ như là chức năng h: X -> Z
thỏa mãn h(x) = g(f(x))
. Vấn đề với định nghĩa này xảy ra khi kết quả f(x)
không được phép làm đối số của g
.
Trong toán học, các hàm này không thể được tạo mà không cần làm thêm. Giải pháp toán học nghiêm ngặt cho ví dụ trên của chúng tôi về f
và g
là loại bỏ 0
khỏi tập hợp định nghĩa của f
. Với bộ định nghĩa mới (loại hạn chế mới hơn x
), f
trở nên có thể kết hợp với g
.
Tuy nhiên, nó không thực tế trong lập trình để hạn chế tập hợp định nghĩa f
như thế. Thay vào đó, ngoại lệ có thể được sử dụng.
Hoặc như cách tiếp cận khác, các giá trị nhân tạo được tạo ra như NaN
, undefined
, null
, Infinity
vv Vì vậy, bạn đánh giá 1/0
để Infinity
và 1/-0
để -Infinity
. Và sau đó buộc giá trị mới trở lại vào biểu thức của bạn thay vì ném ngoại lệ. Dẫn đến kết quả bạn có thể hoặc không thể dự đoán được:
1/0 // => Infinity
parseInt(Infinity) // => NaN
NaN < 0 // => false
false + 1 // => 1
Và chúng tôi trở lại những con số thông thường sẵn sàng để tiếp tục;)
JavaScript cho phép chúng tôi tiếp tục thực hiện các biểu thức số bằng mọi giá mà không đưa ra các lỗi như trong ví dụ trên. Điều đó có nghĩa là, nó cũng cho phép soạn thảo các chức năng. Đó chính xác là những gì đơn nguyên nói về - đó là một quy tắc để soạn các hàm thỏa mãn các tiên đề như được định nghĩa ở đầu câu trả lời này.
Nhưng liệu quy tắc soạn thảo hàm, phát sinh từ việc triển khai JavaScript để xử lý các lỗi số, có phải là một đơn vị không?
Để trả lời câu hỏi này, tất cả những gì bạn cần là kiểm tra các tiên đề (còn lại là bài tập không phải là một phần của câu hỏi ở đây;).
Có thể ném ngoại lệ để xây dựng một đơn nguyên?
Thật vậy, một đơn nguyên hữu ích hơn thay vào đó sẽ là quy tắc quy định rằng nếu f
ném ngoại lệ cho một số người x
, thì thành phần của nó với bất kỳ g
. Cộng với việc tạo ra ngoại lệ E
duy nhất trên toàn cầu chỉ với một giá trị có thể có ( đối tượng đầu cuối trong lý thuyết danh mục). Bây giờ hai tiên đề có thể kiểm tra được ngay lập tức và chúng ta có được một đơn nguyên rất hữu ích. Và kết quả là những gì được biết đến như là đơn nguyên .