Ai đó có thể cho tôi biết tại sao Haskell Prelude định nghĩa hai hàm riêng biệt cho lũy thừa (tức là ^
và **
) không? Tôi nghĩ rằng hệ thống loại phải loại bỏ loại trùng lặp này.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Ai đó có thể cho tôi biết tại sao Haskell Prelude định nghĩa hai hàm riêng biệt cho lũy thừa (tức là ^
và **
) không? Tôi nghĩ rằng hệ thống loại phải loại bỏ loại trùng lặp này.
Prelude> 2^2
4
Prelude> 4**0.5
2.0
Câu trả lời:
Thực tế, có ba nhà khai thác lũy thừa: (^)
, (^^)
và (**)
. ^
là lũy thừa tích phân không âm, ^^
là lũy thừa số nguyên và **
là lũy thừa dấu phẩy động:
(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
Lý do là kiểu an toàn: kết quả của các phép toán số thường có cùng kiểu với (các) đối số đầu vào. Nhưng bạn không thể tăng Int
một lũy thừa dấu phẩy động và nhận được một kết quả kiểu Int
. Và do đó, hệ thống kiểu ngăn bạn làm điều này: (1::Int) ** 0.5
tạo ra lỗi kiểu. Cũng vậy (1::Int) ^^ (-1)
.
Một cách khác để đặt điều này: Num
các loại được đóng dưới ^
(chúng không bắt buộc phải có một phép nghịch đảo nhân), Fractional
các loại được đóng dưới ^^
, Floating
các loại được đóng dưới **
. Vì không có Fractional
ví dụ nào Int
nên bạn không thể nâng nó lên mức phủ định.
Lý tưởng nhất, đối số thứ hai của ^
sẽ bị hạn chế tĩnh là không âm (hiện tại, 1 ^ (-2)
ném một ngoại lệ thời gian chạy). Nhưng không có loại cho số tự nhiên trong Prelude
.
Hệ thống kiểu của Haskell không đủ mạnh để thể hiện ba toán tử lũy thừa làm một. Những gì bạn thực sự muốn là một cái gì đó như thế này:
class Exp a b where (^) :: a -> b -> a
instance (Num a, Integral b) => Exp a b where ... -- current ^
instance (Fractional a, Integral b) => Exp a b where ... -- current ^^
instance (Floating a, Floating b) => Exp a b where ... -- current **
Điều này không thực sự hoạt động ngay cả khi bạn bật tiện ích mở rộng lớp loại đa tham số, vì lựa chọn cá thể cần phải thông minh hơn Haskell hiện cho phép.
Int
và Integer
. Để có thể có ba khai báo cá thể đó, giải pháp đối tượng phải sử dụng tính năng quay lui và không có trình biên dịch Haskell nào thực hiện điều đó.
Nó không định nghĩa hai toán tử - nó định nghĩa ba! Từ báo cáo:
Có ba phép toán lũy thừa hai đối số: (
^
) nâng một số bất kỳ lên lũy thừa số nguyên không âm, (^^
) nâng một số phân số lên lũy thừa số nguyên bất kỳ và (**
) nhận hai đối số dấu phẩy động. Giá trị củax^0
hoặcx^^0
là 1 cho bất kỳx
, bao gồm cả 0;0**y
không định nghĩa được.
Điều này có nghĩa là có ba thuật toán khác nhau, hai trong số đó cho kết quả chính xác ( ^
và ^^
), trong khi **
cho kết quả gần đúng. Bằng cách chọn toán tử sẽ sử dụng, bạn chọn thuật toán sẽ gọi.
^
yêu cầu đối số thứ hai của nó là một Integral
. Nếu tôi không nhầm, việc triển khai có thể hiệu quả hơn nếu bạn biết bạn đang làm việc với số mũ tích phân. Ngoài ra, nếu bạn muốn một cái gì đó như 2 ^ (1.234)
, mặc dù cơ số của bạn là một tích phân, 2, kết quả của bạn rõ ràng sẽ là phân số. Bạn có nhiều tùy chọn hơn để có thể kiểm soát chặt chẽ hơn những loại nào đang vào và ra khỏi hàm lũy thừa của bạn.
Hệ thống kiểu của Haskell không có cùng mục tiêu với các hệ thống kiểu khác, chẳng hạn như C, Python hoặc Lisp. Kiểu gõ vịt (gần như) trái ngược với tư duy Haskell.
class Duck a where quack :: a -> Quack
xác định những gì chúng ta mong đợi ở một con vịt, và sau đó mỗi trường hợp chỉ định một cái gì đó có thể hoạt động như một con vịt.
Duck
.