Was kann @ neben dem Muster in Haskell noch bedeuten?

15

Ich studiere derzeit Haskell und versuche, ein Projekt zu verstehen, das Haskell zur Implementierung kryptografischer Algorithmen verwendet. Nachdem ich Learn You a Haskell for Great Good online gelesen habe , beginne ich, den Code in diesem Projekt zu verstehen. Dann stellte ich fest, dass ich beim folgenden Code mit dem Symbol "@" festsitze:

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

Hier ist randomMtx wie folgt definiert:

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

Und PRFKey ist unten definiert:

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

Alle Informationsquellen, die ich finden kann, sagen, dass @ das As-Muster ist, aber dieser Code ist anscheinend nicht der Fall. Ich habe das Online-Tutorial, die Blogs und sogar den Sprachbericht für Haskell 2010 unter https://www.haskell.org/definition/haskell2010.pdf überprüft . Auf diese Frage gibt es einfach keine Antwort.

Weitere Codefragmente finden Sie in diesem Projekt auch mit @ auf folgende Weise:

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

Ich freue mich sehr über jede Hilfe.

SigurdW
quelle
11
Dies sind Typanwendungen . Siehe auch diese Fragen und Antworten . Sie können sich auch das Commit ansehen, mit dem sie in den Code eingeführt wurden.
MikaelF
Vielen Dank für die Links! Genau das suche ich. Überraschenderweise identifizieren Sie sogar das Commit des Codes! Vielen Dank dafür. Nur neugierig, wie Sie es finden? @MikaelF
SigurdW
2
Github hat eine eigene Schnittstelle für Git-Schuld , die Ihnen sagt, in welchem ​​Commit jede Zeile zuletzt geändert wurde.
MikaelF
Vielen Dank für diesen nützlichen Tipp :)
SigurdW
1
@ MichaelLitchard Sehr froh, dass Sie davon profitieren können. Ich bin dankbar, dass freundliche Leute Zeit verbringen, um mir zu helfen. Hoffe, die Antwort kann auch anderen helfen.
SigurdW

Antworten:

16

Das @nist eine erweiterte Funktion des modernen Haskell, die in der Regel nicht durch Übungen wie Lyah abgedeckt, noch kann die den Bericht zu finden.

Es ist ein sogenannter Typ - Anwendung und ist eine GHC Spracherweiterung. Um es zu verstehen, betrachten Sie diese einfache polymorphe Funktion

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

Das intuitive Aufrufen dupfunktioniert wie folgt:

  • Der Anrufer wählt einen Typ a
  • Der Anrufer wählt einen Wert x des zuvor gewählten Typsa
  • dup antwortet dann mit einem Wert vom Typ (a,a)

In gewissem Sinne werden dupzwei Argumente verwendet: der Typ aund der Wert x :: a. GHC ist jedoch normalerweise in der Lage, den Typ abzuleiten a(z. B. aus xoder aus dem Kontext, in dem wir ihn verwenden dup), sodass wir normalerweise nur ein Argument an übergeben dup, nämlich x. Zum Beispiel haben wir

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

Was ist nun, wenn wir aexplizit weitergeben wollen? In diesem Fall können wir die TypeApplicationsErweiterung einschalten und schreiben

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

Beachten Sie die @...Argumente mit Typen (keine Werte). Dies ist etwas, das nur zur Kompilierungszeit existiert - zur Laufzeit existiert das Argument nicht.

Warum wollen wir das? Nun, manchmal gibt es keine x, und wir möchten den Compiler dazu bringen, das richtige auszuwählen a. Z.B

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

Typanwendungen sind häufig in Kombination mit einigen anderen Erweiterungen nützlich, die eine Typinferenz für GHC unmöglich machen, z. B. mehrdeutige Typen oder Typfamilien. Ich werde diese nicht diskutieren, aber Sie können einfach verstehen, dass Sie dem Compiler manchmal wirklich helfen müssen, insbesondere wenn Sie leistungsstarke Funktionen auf Typebene verwenden.

Nun zu Ihrem speziellen Fall. Ich habe nicht alle Details, ich kenne die Bibliothek nicht, aber es ist sehr wahrscheinlich, dass Ihr nWert eine Art natürlicher Zahl auf Typebene darstellt . Hier tauchen wir in ziemlich fortgeschrittenen Erweiterungen, wie den oben genannten plus DataKinds, und vielleicht GADTseinigen Maschinen der Typklasse. Obwohl ich nicht alles erklären kann, kann ich hoffentlich einige grundlegende Einblicke geben. Intuitiv,

foo :: forall n . some type using n

nimmt als Argument @neine Art Kompilierungszeit natürlich, die zur Laufzeit nicht übergeben wird. Stattdessen,

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

dauert @n(Kompilierungszeit), zusammen mit einem Beweis , der ndie Bedingung erfüllt C n. Letzteres ist ein Laufzeitargument, das den tatsächlichen Wert von verfügbar machen kann n. In Ihrem Fall haben Sie in der Tat etwas, das vage ähnelt

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

Dies ermöglicht es dem Code im Wesentlichen, die natürliche Textebene auf die Begriffebene zu bringen und im Wesentlichen auf den "Typ" als "Wert" zuzugreifen. (Der obige Typ wird übrigens als "mehrdeutig" angesehen - Sie müssen wirklich @nklarstellen.)

Schließlich: Warum sollte man auf Typebene bestehen wollen, nwenn wir das später in die Termstufe umwandeln? Wäre nicht einfacher, einfach Funktionen wie zu schreiben

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

statt der umständlicheren

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

Die ehrliche Antwort lautet: Ja, es wäre einfacher. Auf nTypebene kann der Compiler jedoch mehr statische Überprüfungen durchführen. Beispielsweise möchten Sie möglicherweise, dass ein Typ "Ganzzahlen modulo n" darstellt und diese hinzufügt. Haben

data Mod = Mod Int  -- Int modulo some n

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

funktioniert, aber es gibt keine Überprüfung, dass xund yhaben den gleichen Modul. Wir könnten Äpfel und Orangen hinzufügen, wenn wir nicht vorsichtig sind. Wir könnten stattdessen schreiben

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)

Das ist besser, erlaubt aber trotzdem anzurufen, foo 5 x yauch wenn nes nicht so ist 5. Nicht gut. Stattdessen,

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

verhindert, dass etwas schief geht. Der Compiler überprüft statisch alles. Der Code ist schwieriger zu verwenden, ja, aber in gewissem Sinne ist es der springende Punkt, die Verwendung zu erschweren: Wir möchten es dem Benutzer unmöglich machen, etwas mit dem falschen Modul hinzuzufügen.

Fazit: Dies sind sehr fortgeschrittene Erweiterungen. Wenn Sie ein Anfänger sind, müssen Sie sich langsam diesen Techniken nähern. Lassen Sie sich nicht entmutigen, wenn Sie sie nach nur einem kurzen Studium nicht erfassen können. Es dauert einige Zeit. Machen Sie jeweils einen kleinen Schritt und lösen Sie einige Übungen für jede Funktion, um den Sinn zu verstehen. Und du wirst immer StackOverflow haben, wenn du feststeckst :-)

Chi
quelle
Vielen Dank für Ihre ausführliche Erklärung! Es löst mein Problem wirklich und ich denke, ich würde viel mehr Zeit brauchen, um die Antwort selbst zu finden. Vielen Dank auch für Ihren Vorschlag!
SigurdW