Itu @n
adalah 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 dup
berfungsi 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, dup
dibutuhkan dua argumen: jenis a
dan 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 a
secara eksplisit? Nah, dalam hal ini kita dapat mengaktifkan TypeApplications
ekstensi, 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 x
sekitar, 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 n
mewakili 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 n
memenuhi 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 @n
membuat ambigu.)
Akhirnya: mengapa kita ingin lulus n
pada 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 n
pada 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 x
dan y
dari 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 y
bahkan ketika n
tidak 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 :-)