Đó @n
là một tính năng nâng cao của Haskell hiện đại, thường không được đề cập trong các hướng dẫn như LYAH, và cũng không thể tìm thấy Báo cáo.
Nó được gọi là một ứng dụng loại và là một phần mở rộng ngôn ngữ GHC. Để hiểu nó, hãy xem xét hàm đa hình đơn giản này
dup :: forall a . a -> (a, a)
dup x = (x, x)
Gọi trực giác dup
hoạt động như sau:
- những người gọi chọn một loại
a
- những người gọi chọn một giá trị
x
của các loại được chọn trước đóa
dup
sau đó trả lời với giá trị loại (a,a)
Theo một nghĩa nào đó, dup
có hai đối số: loại a
và giá trị x :: a
. Tuy nhiên, GHC thường có thể suy ra loại a
(ví dụ từ x
hoặc từ bối cảnh chúng ta đang sử dụng dup
), vì vậy chúng ta thường chỉ chuyển một đối số cho dup
, cụ thể là x
. Chẳng hạn, chúng ta có
dup True :: (Bool, Bool)
dup "hello" :: (String, String)
...
Bây giờ, nếu chúng ta muốn vượt qua a
một cách rõ ràng thì sao? Chà, trong trường hợp đó chúng ta có thể bật TypeApplications
tiện ích mở rộng và viết
dup @Bool True :: (Bool, Bool)
dup @String "hello" :: (String, String)
...
Lưu ý các @...
đối số mang kiểu (không phải giá trị). Đó là những thứ tồn tại vào thời gian biên dịch, chỉ - trong thời gian chạy, đối số không tồn tại.
Tại sao chúng ta muốn điều đó? Vâng, đôi khi không có x
xung quanh, và chúng tôi muốn prod trình biên dịch để chọn đúng a
. Ví dụ
dup @Bool :: Bool -> (Bool, Bool)
dup @String :: String -> (String, String)
...
Các ứng dụng loại thường hữu ích khi kết hợp với một số tiện ích mở rộng khác khiến cho suy luận kiểu không khả thi đối với GHC, như kiểu mơ hồ hoặc kiểu họ. Tôi sẽ không thảo luận về những điều đó, nhưng bạn có thể hiểu đơn giản rằng đôi khi bạn thực sự cần phải giúp trình biên dịch, đặc biệt là khi sử dụng các tính năng cấp độ mạnh mẽ.
Bây giờ, về trường hợp cụ thể của bạn. Tôi không có tất cả các chi tiết, tôi không biết thư viện, nhưng rất có thể bạn n
đại diện cho một loại giá trị số tự nhiên ở cấp độ loại . Ở đây chúng ta đang đi sâu vào các phần mở rộng khá tiên tiến, như các phần mở rộng đã nói ở trên DataKinds
, có thể GADTs
, và một số máy móc thiết bị đánh máy. Trong khi tôi không thể giải thích mọi thứ, hy vọng tôi có thể cung cấp một số hiểu biết cơ bản. Trực giác,
foo :: forall n . some type using n
lấy làm đối số @n
, một kiểu tự nhiên thời gian biên dịch, không được truyền vào thời gian chạy. Thay thế,
foo :: forall n . C n => some type using n
mất @n
(compile-time), cùng với một bằng chứng rằng n
đáp ứng hạn chế C n
. Cái sau là một đối số thời gian chạy, có thể phơi bày giá trị thực của n
. Thật vậy, trong trường hợp của bạn, tôi đoán bạn có một cái gì đó mơ hồ giống
value :: forall n . Reflects n Int => Int
về cơ bản cho phép mã mang mức tự nhiên cấp độ đến cấp độ hạn, về cơ bản truy cập "loại" dưới dạng "giá trị". (Nhân tiện, loại trên được coi là "mơ hồ", nhân tiện - bạn thực sự cần @n
phải định hướng.)
Cuối cùng: tại sao người ta muốn vượt qua n
ở cấp độ loại nếu sau đó chúng ta sẽ chuyển đổi nó thành cấp độ hạn? Sẽ không dễ dàng hơn để viết ra các chức năng như
foo :: Int -> ...
foo n ... = ... use n
thay vì cồng kềnh hơn
foo :: forall n . Reflects n Int => ...
foo ... = ... use (value @n)
Câu trả lời trung thực là: có, nó sẽ dễ dàng hơn. Tuy nhiên, có n
ở mức loại cho phép trình biên dịch thực hiện kiểm tra tĩnh hơn. Chẳng hạn, bạn có thể muốn một kiểu đại diện cho "số nguyên modulo n
" và cho phép thêm các kiểu đó. Đang có
data Mod = Mod Int -- Int modulo some n
foo :: Int -> Mod -> Mod -> Mod
foo n (Mod x) (Mod y) = Mod ((x+y) `mod` n)
hoạt động, nhưng không có kiểm tra đó x
và y
có cùng mô-đun. Chúng tôi có thể thêm táo và cam, nếu chúng tôi không cẩn thận. Thay vào đó chúng ta có thể viết
data Mod n = Mod Int -- Int modulo n
foo :: Int -> Mod n -> Mod n -> Mod n
foo n (Mod x) (Mod y) = Mod ((x+y) `mod` n)
cái nào tốt hơn, nhưng vẫn cho phép gọi foo 5 x y
ngay cả khi n
không 5
. Không tốt. Thay thế,
data Mod n = Mod Int -- Int modulo n
-- a lot of type machinery omitted here
foo :: forall n . SomeConstraint n => Mod n -> Mod n -> Mod n
foo (Mod x) (Mod y) = Mod ((x+y) `mod` (value @n))
ngăn chặn những điều đi sai Trình biên dịch tĩnh kiểm tra mọi thứ. Mã khó sử dụng hơn, vâng, nhưng trong một ý nghĩa làm cho nó khó sử dụng hơn là toàn bộ vấn đề: chúng tôi muốn làm cho người dùng không thể thử thêm một cái gì đó của mô-đun sai.
Kết luận: đây là những phần mở rộng rất tiên tiến. Nếu bạn là người mới bắt đầu, bạn sẽ cần phải từ từ tiến tới những kỹ thuật này. Đừng nản lòng nếu bạn không thể nắm bắt chúng chỉ sau một nghiên cứu ngắn, sẽ mất một thời gian. Thực hiện một bước nhỏ tại một thời điểm, giải quyết một số bài tập cho từng tính năng để hiểu điểm của nó. Và bạn sẽ luôn có StackOverflow khi bạn bị mắc kẹt :-)