Bằng cách đưa chữ ký kiểu tường minh, bạn ngăn GHC từ làm cho một số giả định về mã của bạn. Tôi sẽ chỉ cho một ví dụ (lấy từ question này):
foo (x:y:_) = x == y
foo [_] = foo []
foo [] = False
Theo GHCi, loại chức năng này là Eq a => [a] -> Bool
, như bạn mong muốn. Tuy nhiên, nếu bạn khai báo foo
bằng chữ ký này, bạn sẽ gặp lỗi "biến loại mơ hồ".
Lý do tại sao chức năng này chỉ hoạt động mà không có chữ ký loại là do cách đánh máy hoạt động trong GHC. Khi bạn bỏ qua một chữ ký loại, foo
được giả định là có một mẫu [a] -> Bool
đối với một số loại cố định a
. Khi bạn hoàn thành việc nhập nhóm liên kết, bạn tổng quát các loại. Đó là nơi bạn nhận được forall a. ...
. Mặt khác, khi bạn khai báo chữ ký kiểu đa hình, bạn tuyên bố rõ ràng rằng foo
là đa hình (và do đó loại []
không phải khớp với loại đối số đầu tiên) và bùng nổ, bạn sẽ nhận được loại không rõ ràng biến.
Bây giờ, biết điều này, chúng ta hãy so sánh cốt lõi:
fib = 0:1:zipWith (+) fib (tail fib)
-----
fib :: forall a. Num a => [a]
[GblId, Arity=1]
fib =
\ (@ a) ($dNum :: Num a) ->
letrec {
fib1 [Occ=LoopBreaker] :: [a]
[LclId]
fib1 =
break<3>()
: @ a
(fromInteger @ a $dNum (__integer 0))
(break<2>()
: @ a
(fromInteger @ a $dNum (__integer 1))
(break<1>()
zipWith
@ a @ a @ a (+ @ a $dNum) fib1 (break<0>() tail @ a fib1))); } in
fib1
Và đối với thứ hai:
fib :: Num a => [a]
fib = 0:1:zipWith (+) fib (tail fib)
-----
Rec {
fib [Occ=LoopBreaker] :: forall a. Num a => [a]
[GblId, Arity=1]
fib =
\ (@ a) ($dNum :: Num a) ->
break<3>()
: @ a
(fromInteger @ a $dNum (__integer 0))
(break<2>()
: @ a
(fromInteger @ a $dNum (__integer 1))
(break<1>()
zipWith
@ a
@ a
@ a
(+ @ a $dNum)
(fib @ a $dNum)
(break<0>() tail @ a (fib @ a $dNum))))
end Rec }
Với loại chữ ký rõ ràng, như với foo
trên, GHC có để điều trị fib
như giá trị đệ quy có khả năng đa hình. Chúng tôi có thể chuyển một số khác nhau Num
từ điển sang fib
trong zipWith (+) fib ...
và tại thời điểm này, chúng tôi sẽ phải ném hầu hết danh sách đi, vì khác nhau Num
có nghĩa là khác nhau (+)
. Tất nhiên, một khi bạn biên dịch với tối ưu hóa, GHC nhận thấy rằng từ điển Num
không bao giờ thay đổi trong "cuộc gọi đệ quy" và tối ưu hóa nó đi.
Trong lõi ở trên, bạn có thể thấy rằng GHC thực sự cung cấp cho fib
một từ điển Num
(có tên $dNum
) lặp đi lặp lại.
Vì fib
không có chữ ký loại được giả định là đơn nhất trước khi khái quát hóa toàn bộ nhóm ràng buộc được hoàn thành, các phụ đề fib
được cung cấp chính xác cùng loại với toàn bộ fib
. Nhờ vậy, fib
trông giống như:
{-# LANGUAGE ScopedTypeVariables #-}
fib :: forall a. Num a => [a]
fib = fib'
where
fib' :: [a]
fib' = 0:1:zipWith (+) fib' (tail fib')
Và bởi vì các loại vẫn được cố định, bạn có thể sử dụng chỉ là một từ điển được lúc khởi động.
Một bên: Lý do về hiệu năng trong ghci hơi lạ - a) chậm hơn 30 lần so với ghc và b) bất kỳ mã thế giới thực nào sẽ sử dụng tối ưu hóa, vì vậy các bài học được học trong ghci sẽ không hữu ích. –