Selain as-pattern, apa lagi yang bisa @ maksud di Haskell?

15

Saya sedang mempelajari Haskell saat ini dan mencoba memahami proyek yang menggunakan Haskell untuk mengimplementasikan algoritma kriptografi. Setelah membaca Learn You a Haskell for Great Good online, saya mulai memahami kode dalam proyek itu. Kemudian saya menemukan saya terjebak pada kode berikut dengan simbol "@":

-- | 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

Di sini, randomMtx didefinisikan sebagai berikut:

-- | 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

Dan PRFKey didefinisikan di bawah ini:

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

Semua sumber informasi yang dapat saya temukan mengatakan bahwa @ adalah pola as, tetapi bagian kode ini tampaknya tidak demikian. Saya telah memeriksa tutorial online, blog, dan bahkan laporan bahasa Haskell 2010 di https://www.haskell.org/definition/haskell2010.pdf . Tidak ada jawaban untuk pertanyaan ini.

Lebih banyak cuplikan kode dapat ditemukan dalam proyek ini menggunakan @ dengan cara ini juga:

-- | 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))

Saya sangat menghargai bantuan apa pun dalam hal ini.

SigurdW
sumber
11
Ini adalah aplikasi tipe . Lihat juga T&J ini . Anda juga dapat melihat komit yang memperkenalkan mereka ke dalam kode.
MikaelF
Terima kasih banyak untuk tautannya! Ini persis apa yang saya cari. Anehnya, Anda bahkan mengidentifikasi komitmen kode! Banyak terima kasih untuk itu. Hanya ingin tahu tentang bagaimana Anda menemukannya? @MikaelF
SigurdW
2
Github memiliki antarmuka sendiri untuk menyalahkan git , yang akan memberi tahu Anda di mana komit setiap baris terakhir dimodifikasi.
MikaelF
Terima kasih banyak atas tip yang bermanfaat ini :)
SigurdW
1
@MichaelLitchard Sangat senang Anda bisa mendapat manfaat darinya. Saya berterima kasih kepada orang-orang baik yang menghabiskan waktu untuk membantu saya. Semoga jawabannya juga bisa membantu orang lain.
SigurdW

Jawaban:

17

Itu @nadalah fitur canggih dari Haskell modern, yang biasanya tidak dicakup oleh tutorial seperti LYAH, juga tidak dapat ditemukan dalam Laporan.

Ini disebut aplikasi tipe dan merupakan ekstensi bahasa GHC. Untuk memahaminya, pertimbangkan fungsi polimorfik sederhana ini

dup :: forall a . a -> (a, a)
dup x = (x, x)

Panggilan secara intuitif dupberfungsi sebagai berikut:

  • yang penelepon memilih sebuah tipe a
  • yang penelepon memilih sebuah nilai x dari jenis yang dipilih sebelumnyaa
  • dup lalu menjawab dengan nilai tipe (a,a)

Dalam arti tertentu, dupdibutuhkan dua argumen: jenis adan nilai x :: a. Namun, GHC biasanya dapat menyimpulkan jenis a(misalnya dari x, atau dari konteks tempat kami menggunakan dup), jadi kami biasanya hanya menyampaikan satu argumen dup, yaitu x. Misalnya, sudah

dup True    :: (Bool, Bool)
dup "hello" :: (String, String)
...

Sekarang, bagaimana jika kita ingin lulus asecara eksplisit? Nah, dalam hal ini kita dapat mengaktifkan TypeApplicationsekstensi, dan menulis

dup @Bool True      :: (Bool, Bool)
dup @String "hello" :: (String, String)
...

Perhatikan @...argumen yang mengandung tipe (bukan nilai). Itu adalah sesuatu yang ada pada waktu kompilasi, hanya - pada saat runtime argumen tidak ada.

Mengapa kita menginginkan itu? Yah, terkadang tidak ada di xsekitar, dan kami ingin mendorong kompiler untuk memilih yang benar a. Misalnya

dup @Bool   :: Bool -> (Bool, Bool)
dup @String :: String -> (String, String)
...

Aplikasi tipe sering berguna dalam kombinasi dengan beberapa ekstensi lain yang membuat inferensi tipe tidak layak untuk GHC, seperti tipe ambigu atau tipe keluarga. Saya tidak akan membahasnya, tetapi Anda hanya dapat memahami bahwa kadang-kadang Anda benar-benar perlu membantu kompiler, terutama ketika menggunakan fitur tipe-level yang kuat.

Sekarang, tentang kasus spesifik Anda. Saya tidak memiliki semua detail, saya tidak tahu perpustakaan, tetapi sangat mungkin bahwa Anda nmewakili semacam nilai bilangan alami pada tingkat tipe . Di sini kita menyelam dalam ekstensi yang agak canggih, seperti yang disebutkan di atas plus DataKinds, mungkin GADTs, dan beberapa mesin ketik kelas. Meskipun saya tidak bisa menjelaskan semuanya, mudah-mudahan saya bisa memberikan wawasan dasar. Secara intuitif,

foo :: forall n . some type using n

mengambil sebagai argumen @n, semacam waktu kompilasi alami, yang tidak diteruskan saat runtime. Sebagai gantinya,

foo :: forall n . C n => some type using n

dibutuhkan @n( waktu kompilasi), bersama dengan bukti yang nmemenuhi kendala C n. Yang terakhir adalah argumen run-time, yang mungkin mengekspos nilai aktual n. Memang, dalam kasus Anda, saya kira Anda memiliki sesuatu yang agak mirip

value :: forall n . Reflects n Int => Int

yang pada dasarnya memungkinkan kode untuk membawa alami tingkat-jenis ke tingkat-jangka, pada dasarnya mengakses "tipe" sebagai "nilai". (Ngomong-ngomong, tipe di atas dianggap sebagai yang "ambigu" - Anda benar-benar perlu @nmembuat ambigu.)

Akhirnya: mengapa kita ingin lulus npada level tipe jika kita kemudian mengkonversinya ke level term? Tidak akan lebih mudah untuk menuliskan fungsi seperti

foo :: Int -> ...
foo n ... = ... use n

bukannya lebih rumit

foo :: forall n . Reflects n Int => ...
foo ... = ... use (value @n)

Jawaban jujurnya adalah: ya, itu akan lebih mudah. Namun, memiliki npada tingkat tipe memungkinkan kompiler untuk melakukan pemeriksaan yang lebih statis. Misalnya, Anda mungkin ingin tipe mewakili "integer modulo n", dan memungkinkan menambahkannya. Memiliki

data Mod = Mod Int  -- Int modulo some n

foo :: Int -> Mod -> Mod -> Mod
foo n (Mod x) (Mod y) = Mod ((x+y) `mod` n)

berfungsi, tetapi tidak ada pemeriksaan itu xdan ydari modulus yang sama. Kita mungkin menambahkan apel dan jeruk, jika kita tidak hati-hati. Kita malah bisa menulis

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)

mana yang lebih baik, tetapi masih memungkinkan untuk memanggil foo 5 x ybahkan ketika ntidak 5. Tidak baik. Sebagai gantinya,

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))

mencegah hal-hal yang salah. Compiler secara statis memeriksa semuanya. Kode lebih sulit digunakan, ya, tetapi dalam arti membuatnya lebih sulit untuk digunakan adalah intinya: kami ingin membuatnya mustahil bagi pengguna untuk mencoba menambahkan sesuatu dari modulus yang salah.

Kesimpulan: ini adalah ekstensi yang sangat canggih. Jika Anda seorang pemula, Anda harus perlahan-lahan maju menuju teknik-teknik ini. Jangan berkecil hati jika Anda tidak dapat memahami mereka hanya setelah belajar singkat, itu butuh waktu. Buat langkah kecil pada satu waktu, selesaikan beberapa latihan untuk setiap fitur untuk memahami intinya. Dan Anda akan selalu memiliki StackOverflow ketika Anda terjebak :-)

chi
sumber
Terima kasih banyak atas penjelasan terperinci Anda! Ini benar-benar menyelesaikan masalah saya, dan saya kira saya akan membutuhkan lebih banyak waktu untuk menemukan jawabannya sendiri. Juga terima kasih atas saran Anda!
SigurdW