Ngoài mô hình, @ có thể có ý nghĩa gì khác trong Haskell?


15

Tôi hiện đang nghiên cứu Haskell và cố gắng hiểu một dự án sử dụng Haskell để thực hiện các thuật toán mã hóa. Sau khi đọc Learn You a Haskell for Great Good online, tôi bắt đầu hiểu mã trong dự án đó. Sau đó, tôi thấy tôi bị kẹt ở đoạn mã sau với ký hiệu "@":

-- | Generate an @n@-dimensional secret key over @rq@.
genKey :: forall rq rnd n . (MonadRandom rnd, Random rq, Reflects n Int)
       => rnd (PRFKey n rq)
genKey = fmap Key $ randomMtx 1 $ value @n

Ở đây RandomMtx được định nghĩa như sau:

-- | A random matrix having a given number of rows and columns.
randomMtx :: (MonadRandom rnd, Random a) => Int -> Int -> rnd (Matrix a)
randomMtx r c = M.fromList r c <$> replicateM (r*c) getRandom

Và PRFKey được định nghĩa dưới đây:

-- | A PRF secret key of dimension @n@ over ring @a@.
newtype PRFKey n a = Key { key :: Matrix a }

Tất cả các nguồn thông tin tôi có thể tìm thấy nói rằng @ là mẫu, nhưng đoạn mã này rõ ràng không phải là trường hợp đó. Tôi đã kiểm tra hướng dẫn trực tuyến, blog và thậm chí báo cáo ngôn ngữ Haskell 2010 tại https://www.haskell.org/def định / haskell2010.pdf . Đơn giản là không có câu trả lời cho câu hỏi này.

Nhiều đoạn mã có thể được tìm thấy trong dự án này bằng cách sử dụng @ theo cách này:

-- | Generate public parameters (\( \mathbf{A}_0 \) and \(
-- \mathbf{A}_1 \)) for @n@-dimensional secret keys over a ring @rq@
-- for gadget indicated by @gad@.
genParams :: forall gad rq rnd n .
            (MonadRandom rnd, Random rq, Reflects n Int, Gadget gad rq)
          => rnd (PRFParams n gad rq)
genParams = let len = length $ gadget @gad @rq
                n   = value @n
            in Params <$> (randomMtx n (n*len)) <*> (randomMtx n (n*len))

Tôi đánh giá cao bất kỳ sự giúp đỡ về điều này.


11
Đây là những ứng dụng loại . Xem thêm Q & A này . Bạn cũng có thể nhìn vào các cam kết đã đưa chúng vào mã.
MikaelF

Cảm ơn rất nhiều cho các liên kết! Đây là chính xác những gì tôi đang tìm kiếm. Đáng ngạc nhiên, bạn thậm chí xác định cam kết của mã! Rất cám ơn vì điều đó. Chỉ tò mò về làm thế nào để bạn tìm thấy nó? @MikaelF
SigurdW

2
Github có giao diện riêng để git đổ lỗi , nó sẽ cho bạn biết cam kết nào từng dòng được sửa đổi lần cuối.
MikaelF

Cảm ơn rất nhiều về lời khuyên hữu ích này :)
SigurdW

1
@MichaelLitchard Rất vui vì bạn có thể hưởng lợi từ nó. Tôi biết ơn những người tử tế đã dành thời gian để giúp đỡ tôi. Hy vọng câu trả lời cũng có thể giúp đỡ người khác.
SigurdW

Câu trả lời:


16

Đó @nlà 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 duphoạ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 đó, dupcó hai đối số: loại avà giá trị x :: a. Tuy nhiên, GHC thường có thể suy ra loại a(ví dụ từ xhoặ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 amột cách rõ ràng thì sao? Chà, trong trường hợp đó chúng ta có thể bật TypeApplicationstiệ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ó xxung 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 @nphả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 đó xycó 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 yngay cả khi nkhô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 :-)


Cảm ơn bạn rất nhiều vì lời giải thích chi tiết của bạn! Nó thực sự giải quyết vấn đề của tôi, và tôi đoán tôi sẽ cần nhiều thời gian hơn để tự mình tìm ra câu trả lời. Cũng cảm ơn bạn đã gợi ý của bạn!
SigurdW
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.