Obecnie studiuję Haskell i staram się zrozumieć projekt, w którym Haskell wdraża algorytmy kryptograficzne. Po przeczytaniu „ Naucz się Haskella dla wielkiego dobra” w Internecie, zaczynam rozumieć kod z tego projektu. Potem okazało się, że utknąłem na następującym kodzie z symbolem „@”:
-- | 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
Tutaj randomMtx jest zdefiniowany w następujący sposób:
-- | 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
A PRFKey jest zdefiniowany poniżej:
-- | A PRF secret key of dimension @n@ over ring @a@.
newtype PRFKey n a = Key { key :: Matrix a }
Wszystkie źródła informacji, które mogę znaleźć, mówią, że @ jest wzorem, ale ten fragment kodu najwyraźniej nie jest taki przypadek. Sprawdziłem samouczek online, blogi, a nawet raport językowy Haskell 2010 na https://www.haskell.org/definition/haskell2010.pdf . Po prostu nie ma odpowiedzi na to pytanie.
Więcej fragmentów kodu można znaleźć w tym projekcie również przy użyciu @ w ten sposób:
-- | 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))
Bardzo doceniam wszelką pomoc w tym zakresie.
źródło
Odpowiedzi:
Że
@n
to zaawansowana funkcja współczesnego Haskell, która zwykle nie jest objęta samouczkami takimi jak LYAH, ani nie można znaleźć w Raporcie.Nazywa się to aplikacją typu i jest rozszerzeniem języka GHC. Aby to zrozumieć, rozważ tę prostą funkcję polimorficzną
Intuicyjne wywoływanie
dup
działa w następujący sposób:a
x
uprzednio wybranym typema
dup
następnie odpowiada wartością typu(a,a)
W pewnym sensie
dup
przyjmuje dwa argumenty: typa
i wartośćx :: a
. Jednak GHC jest zwykle w stanie wywnioskować typa
(np. Zx
lub z kontekstu, w którym używamydup
), więc zwykle przekazujemy tylko jeden argumentdup
, mianowiciex
. Na przykład mamyCo teraz, jeśli chcemy przekazać
a
jawnie? W takim przypadku możemy włączyćTypeApplications
rozszerzenie i napisaćZwróć uwagę na
@...
argumenty niosące typy (nie wartości). Są to coś, co istnieje tylko w czasie kompilacji - w czasie wykonywania argument nie istnieje.Dlaczego tego chcemy? Czasami nie ma go w
x
pobliżu i chcemy zmusić kompilator do wybrania właściwegoa
. Na przykładAplikacje typu są często przydatne w połączeniu z niektórymi innymi rozszerzeniami, które sprawiają, że wnioskowanie typu jest niemożliwe dla GHC, takie jak typy niejednoznaczne lub rodziny typów. Nie będę ich omawiać, ale możesz po prostu zrozumieć, że czasami naprawdę potrzebujesz pomocy kompilatorowi, szczególnie gdy używasz zaawansowanych funkcji na poziomie tekstu.
Teraz o twoim konkretnym przypadku. Nie znam wszystkich szczegółów, nie znam biblioteki, ale jest bardzo prawdopodobne, że
n
reprezentujesz rodzaj wartości liczb naturalnych na poziomie typu . Tutaj nurkujemy w raczej zaawansowanych rozszerzeniach, takich jak wyżej wymienione, aDataKinds
możeGADTs
i w niektórych maszynach typowych. Chociaż nie potrafię wyjaśnić wszystkiego, mam nadzieję, że mogę zapewnić podstawowy wgląd. Intuicyjnie,przyjmuje za argument
@n
rodzaj naturalnego czasu kompilacji, który nie jest przekazywany w czasie wykonywania. Zamiast,zajmuje
@n
(czas kompilacji), wraz z dowodem na ton
spełnia WiązanieC n
. Ten ostatni argument jest argumentem wykonawczym, który może ujawnić rzeczywistą wartośćn
. Rzeczywiście, w twoim przypadku wydaje mi się, że masz coś niejasnegoco zasadniczo umożliwia kodowi przeniesienie poziomu typu naturalnego na poziom terminu, zasadniczo uzyskując dostęp do „typu” jako „wartości”. (Nawiasem mówiąc, powyższy typ jest uważany za „dwuznaczny” - naprawdę potrzebujesz
@n
go ujednoznacznić).Wreszcie: dlaczego warto przejść
n
na poziomie typu, jeśli później przekonwertujemy to na poziom terminu? Nie byłoby łatwiej po prostu napisać takie funkcje jakzamiast bardziej uciążliwego
Szczera odpowiedź brzmi: tak, byłoby łatwiej. Jednak posiadanie
n
na poziomie typu pozwala kompilatorowi przeprowadzać więcej kontroli statycznych. Na przykład możesz chcieć, aby typ reprezentował „liczby całkowite modulon
” i pozwolił na ich dodawanie. Mającydziała, ale nie ma wyboru, że
x
iy
mają ten sam moduł. Jeśli nie będziemy ostrożni, możemy dodać jabłka i pomarańcze. Zamiast tego moglibyśmy pisaćco jest lepsze, ale nadal pozwala dzwonić,
foo 5 x y
nawet jeślin
nie jest5
. Niedobrze. Zamiast,zapobiega błędom. Kompilator sprawdza wszystko statycznie. Kod jest trudniejszy w użyciu, tak, ale w pewnym sensie utrudnienie w użyciu jest sednem: chcemy, aby użytkownik nie mógł spróbować dodać czegoś o złym module.
Podsumowując: są to bardzo zaawansowane rozszerzenia. Jeśli jesteś początkujący, musisz powoli robić postępy w kierunku tych technik. Nie zniechęcaj się, jeśli nie możesz ich pojąć po krótkim studiu, zajmuje to trochę czasu. Zrób mały krok po kroku, rozwiąż kilka ćwiczeń dla każdej funkcji, aby zrozumieć jej sens. I zawsze będziesz mieć StackOverflow, gdy utkniesz :-)
źródło