Move 2024 Beta enthält jetzt
Makro
Funktionen!
Makrofunktionen in Move ähneln normalen Funktionen: Sie definieren die Parameter, den Rückgabetyp und die Logik, die bestimmen, wie Ihre Makrofunktionen funktionieren, und können dann Ihre Makrofunktionen von überall in Ihrem Code aus aufrufen. Im Gegensatz zu normalen Funktionen erweitert der Compiler diese Funktionen jedoch an Ort und Stelle, um die definierten Operationen inline auszuführen.
Die Hauptunterschiede zwischen Makrofunktionen und normalen Funktionen sind:
Erweiterungen der Makrofunktionssyntax
Verschieben der Compilererweiterung und Aufrufsemantik
Lambda-Parameter
Syntaxerweiterungen: Die Move-Syntax gibt an, wie Makrofunktionen, ihre Argumente und ihr Aufruf definiert werden. Diese Regeln helfen dem Compiler nicht nur, Makros von normalen Funktionen zu unterscheiden, sondern sorgen auch bei der Verwendung für eine visuelle Unterscheidung zwischen normalen und Makrofunktionen.
Compilererweiterung und Aufrufsemantik: Move wertet normale Funktionsargumente eifrig aus. Mit anderen Worten, die Laufzeit wertet die Argumente, die Sie an eine normale Funktion übergeben, vollständig aus, unabhängig davon, ob sie für die Funktionslogik erforderlich sind oder nicht. Im Gegensatz dazu ersetzen Makrofunktionen die Ausdrücke während der Erweiterung direkt durch ihre Argumente, und die Laufzeit sieht den Makroaufruf überhaupt nicht! Dies bedeutet, dass Argumente überhaupt nicht ausgewertet werden, wenn der Makrokörpercode mit dem Argument nie erreicht wird. Wenn beispielsweise das Argument in einem if-else-Ausdruck ersetzt wird und der Zweig mit diesem Argument nie genommen wird, wird das Argument nicht ausgewertet.
Lambda-Parameter: Zusätzlich zu normalen Ausdrücken können Sie mit Makrofunktionen parametrisierten Code als Funktionsargumente höherer Ordnung angeben. Dies wird durch die Einführung des Lambda-Typs möglich, sodass Makroautoren leistungsstarke und flexible Makros erstellen können.
Weitere Einzelheiten zum Definieren von Makrofunktionen finden Sie in der offiziellen Referenz im Move-Buch.
Anatomie von Makrofunktionen
Der Move-Compiler erweitert Makrofunktionen während der Kompilierung und ermöglicht so Ausdrucksstärke, die in normalen Funktionen nicht verfügbar ist. Um dieses spezielle Substitutionsverhalten anzuzeigen, werden den Typparametern und Ausdrucksparametern von Makrofunktionen ein
$
Zeichen. Betrachten Sie eine Move-Funktion mit dem Titel „my_assert“, die sich wie das Compiler-Grundelement „assert!“ verhält (ohne clevere Fehler).
Makro-Spaß my_assert($cond: bool, $code: u64) { wenn (!$cond) $code abbrechen. }
Der Move-Compiler ersetzt die Argumente während der Erweiterung durch Typparameter und Ausdrucksparameter. Dieses Verhalten erzeugt den im vorherigen Abschnitt erwähnten verzögerten Auswertungsprozess für Argumente. Wie der Code zeigt, überprüft das Makro `my_assert` die ihm übergebene Bedingung und bricht das Programm ab, wenn die Bedingung `$cond` zu
FALSCH
(oder nicht „true“) mit dem als zweites Argument übergebenen „$code“.
Zum Aufrufen der
meine_assert
Makrofunktion verwendet der Programmierer ein !-Zeichen nach dem Funktionsnamen, um visuell anzuzeigen, dass die Argumente nicht sofort ausgewertet werden:
my_assert!(vector[] == vector[], 0); my_assert!(true, 1 / 0); // wird nicht abgebrochen! „1 / 0“ wird nicht ausgewertet.
Wie Sie vielleicht bemerken,
$Code
Der beim zweiten Aufruf an das Makro übergebene Wert enthält eine Division durch Null. Obwohl dies etwas konstruiert ist, soll es zeigen, dass der Compiler den Ausdruck nicht auswertet, da der Kontrollfluss aufgrund der bereitgestellten wahren Bedingung niemals seine Ausführung erreichen wird.
Makros mit Lambda-Argumenten
Mit der Erweiterung der Makrofunktionen wird eine völlig neue Funktion eingeführt: Lambda. Mithilfe von Parametern mit Lambda-Typen können Sie Code vom Aufrufer an den Hauptteil des Makros übergeben. Während die Ersetzung zur Kompilierzeit erfolgt, werden sie ähnlich wie anonyme Funktionen, Lambdas oder Closures in anderen Sprachen verwendet.
Betrachten Sie beispielsweise eine Schleife, die ein Lambda für jede Zahl ausführt von
0
zu einer Zahl n, die der Code bereitstellt.
öffentliches Makro fun do($n: u64, $f: |u64| -> ()) { let mut i = 0; let stop = $n; während (i < stop) { $f(i); i = i + 1; } }
Der Lambda-Parameter (
$f
) ist das zweite Argument des do-Makros. Der Typ des Lambdas ist definiert als |u64| -> (), das ein u64 als Eingabe nimmt und () zurückgibt. Der Rückgabetyp von () ist implizit, daher könnte das Argument auch einfach als $f: |u64| geschrieben werden.
Ihre Programmlogik könnte dann dieses Makro verwenden, um die Zahlen aus
0
auf 10 mit dem Lambda-Parameter:
sei mut Summe = 0; mache!(10, |i| Summe = Summe + i);
Hier definiert der Codeausschnitt `|i| sum = sum + i` das Lambda für den Makroaufruf. Wenn es im Hauptteil des Makros aufgerufen wird, werden seine Argumente ausgewertet und im Hauptteil des Lambdas gebunden. Beachten Sie, dass Lambdas im Gegensatz zu Makroaufrufen ihre Argumente vollständig auswerten, bevor der Lambda-Hauptteil ausgeführt wird.
Makrofunktionen sind hygienisch, daher ist das i im Lambda nicht dasselbe wie das i im do. Weitere Einzelheiten finden Sie in der Move-Referenz.)
Makros in der Move-Standardbibliothek
Durch das Hinzufügen von Makros wird die
Verschieben-Stdlib
Die Bibliothek führt eine Reihe von Makrofunktionen ein, um gängige Programmiermuster zu erfassen.
Integer-Makros
Für
u64
und andere Integer-Typen. Die Bibliothek move-stdlib bietet eine Reihe von Makrofunktionen, die gängige Schleifen vereinfachen. Dieser Beitrag untersucht den Typ u64, aber dieselben Makrofunktionen sind für u8, u16, u32, u128 und u256 verfügbar.
Sie haben bereits ein Beispiel gesehen, da der Funktionscode „do“ eine Makrofunktion in std::u64 ist.
Die vollständige Liste der derzeit verfügbaren Makrofunktionen für Integer-Typen lautet:
öffentliches Makro Spaß do($stop: u64, $f: |u64|)
Schleifen, die $f auf jede Zahl von 0 bis $stop anwenden (exklusiv).
öffentliches Makro Spaß do_eq($stop: u64, $f: |u64|)
Schleifen, in denen $f auf jede Zahl von 0 bis $stop (einschließlich) angewendet wird.
öffentliches Makro Spaß range_do($start: u64, $stop: u64, $f: |u64|)
Schleifen, die $f auf jede Zahl von $start bis $stop anwenden (exklusiv).
öffentliches Makro Spaß range_do_eq($start: u64, $stop: u64, $f: |u64|)
Schleifen, in denen $f auf jede Zahl von $start bis $stop (einschließlich) angewendet wird.
Ein vereinfachtes Beispiel für die Anwendung der definierten Makrofunktionen ist die folgende Schleife:
lass mut i = 0; während (i < 10) { foo(i); i = i + 1; }
Sie können diese Schleife umschreiben, indem Sie Folgendes implementieren:
Tun
Makrofunktion für std::u64 als:
10u64.do!(|i| foo(i));
Ebenso könnten Sie die folgende Schleife prägnanter gestalten, indem Sie
u64::range_do_eq Makrofunktion
:
let mut i = 1; // Schleife von 1 bis 10^8 in 10er-Schritten while (i <= 100_000_000) { foo(i); i = i * 10; }
Das Ergebnis der Umschreibung:
1u64.range_do_eq!(8, |i| foo(10u64.pow(i)));
Dies ist zwar möglicherweise leichter zu lesen, die Ausführung erfordert jedoch mehr Gas.
Vektor
Makros
In einem ähnlichen Geiste wie bei ganzen Zahlen
Tun
Makro können Sie mit der Funktion do über die Elemente eines Vektors iterieren.
Vektor[1, 2, 3, 4, 5].mach!(|x| foo(x));
Dies entspricht:
let mut v = Vektor[1, 2, 3, 4, 5]; v.reverse(); während (!v.is_empty()) { foo(v.pop_back()); }
Der erweiterte Code zeigt, dass dieser Prozess den Vektor verbraucht. Verwenden Sie die
do_ref
und do_mut-Makros zum Iterieren per Referenz bzw. per veränderbarer Referenz.
Spaß check_coins(coins: &vector<Coin<SUI>>) { coins.do_ref!(|coin| assert!(coin.value() > 0)); /* wird erweitert zu let mut i = 0; let n = coins.len(); während (i < n) { let coin = &coins[i]; assert!(coin.value() > 0); i = i + 1; } */ } Spaß take_10(Münzen: &mut Vektor<Coin<SUI>>) { coins.do_mut!(|coin| transfer::public_transfer(coin.take(10), @0x42)); /* wird erweitert zu let mut i = 0; let n = coins.len(); während (i < n) { let coin = &mut coins[i]; transfer::public_transfer(coin.take(10), @0x42); i = i + 1; } */ }
Zusätzlich zur Iteration können Sie Vektoren mit Makros ändern und erstellen.
Vektor::tabellieren
erstellt einen Vektor der Länge n, indem auf jeden Index ein Lambda angewendet wird.
fun powers_of_2(n: u64): vector<u64> { vector::tabulate(n, |i| 2u64.pow(i)) /* wird erweitert zu let mut v = vector[]; let mut i = 0; while (i < n) { v.push_back(2u64.pow(i)); i = i + 1; }; v */ }
Vektor::Karte
erstellt einen neuen Vektor aus einem vorhandenen Vektor, indem auf jedes Element ein Lambda angewendet wird. Während vector::map einen Vektor nach Wert bearbeitet, bearbeiten die Makros map_ref und map_mut einen Vektor nach Referenz bzw. veränderlicher Referenz.
Spaß into_balances(Münzen: Vektor<Münze<SUI>>): Vektor<Balance<SUI>> { coins.map!(|Münze| Münze.into_balance()) /* wird erweitert zu let mut v = Vektor[]; Münzen.reverse(); während (!Münzen.is_empty()) { let Münze = Münzen.pop_back(); v.push_back(Münze.into_balance()); }; v */ }
Ähnlich wie Karte,
Vektor::Filter
Erstellt einen neuen Vektor aus einem vorhandenen Vektor, indem nur die Elemente beibehalten werden, die ein Prädikat erfüllen.
Spaß non_zero_numbers(numbers: vector<u64>): vector<u64> { numbers.filter!(|n| n > 0) /* wird erweitert zu let mut v = vector[]; numbers.reverse(); während (!numbers.is_empty()) { let n = numbers.pop_back(); wenn (n > 0) { v.push_back(n); } }; v */ }
Eine Liste der derzeit verfügbaren Makrofunktionen für Vektoren finden Sie in Tabelle A am Ende dieses Artikels.
Option
Makros
Während
Option
ist keine Sammlung mit mehr als einem Element. Der Typ verfügt über do-Makros (und do_ref- und do_mut-Makros), um einfach auf seinen Wert zuzugreifen, falls es sich um einen solchen handelt.
Spaß, vielleicht_Übertragung (Münze: Option<Münze<SUI>>, Empfänger: Adresse) { coin.do!(|c| Übertragung::öffentliche_Übertragung(c, Empfänger)) /* wird erweitert zu wenn (Münze.ist_irgendein()) Übertragung::öffentliche_Übertragung(Münze.zerstören_irgendein(), Empfänger) sonst Münze.zerstören_keine() */ }
zerstören
führt dieselbe Aktion aus wie do, aber ein vielleicht nützlicheres Makro ist destroy_or, das einen Standardausdruck bereitstellt, der nur ausgewertet wird, wenn die Option keine ist. Das folgende Beispiel verkettet map, das das Lambda auf den Wert der Option anwendet, wenn es ein Wert ist, und destroy_or, um eine Option<Coin<SUI>> präzise in eine Balance<SUI> umzuwandeln.
Spaß to_balance_opt(coin: Option<Coin<SUI>>): Balance<SUI> { coin.map!(|c| c.into_balance()).destroy_or!(Balance::zero()) /* wird erweitert zu let opt = if (coin.is_some()) Option::some(coins.destroy_some().into_balance()) else Option::none(); if (opt.is_some()) opt.destroy_some() else Balance::zero() */ }
Eine Liste der derzeit verfügbaren Makrofunktionen für Option finden Sie in Tabelle B am Ende dieses Artikels.
Einpacken
Makrofunktionen können Ihren Code vereinfachen, indem sie gängige Muster in wiederverwendbare Makros umwandeln. Die Hoffnung ist, dass Makrofunktionen viele, wenn nicht alle Ihrer üblichen Schleifen (Loop und While) ersetzen können. Halten Sie Ausschau nach weiteren Makrofunktionen in der Move-Stdlib-Bibliothek und bald auch im Sui-Framework.
Weitere Einzelheiten zur Funktionsweise von Makrofunktionen finden Sie im Buch „Move“.
Tabelle A: Derzeit verfügbare Makrofunktionen für Vektoren
Voranstellen
öffentlicher Makrospaß
zu jeder Zeile unten:
tabellieren<$T>($n: u64, $f: |u64| -> $T): Vektor<$T>
Erstellen Sie einen Vektor der Länge
N
indem die Funktion f für jeden Index aufgerufen wird.
zerstören<$T>($v: Vektor<$T>, $f: |$T|)
Zerstöre den Vektor
v
indem f für jedes Element aufgerufen und dann der Vektor zerstört wird. Die Reihenfolge der Elemente im Vektor wird nicht beibehalten (beginnt am Ende des Vektors).
mache<$T>($v: Vektor<$T>, $f: |$T|)
Zerstöre den Vektor
v
indem f für jedes Element aufgerufen und dann der Vektor zerstört wird. Behält die Reihenfolge der Elemente im Vektor bei.
do_ref<$T>($v: &Vektor<$T>, $f: |&$T|)
Ausführen einer Aktion
F
auf jedem Element des Vektors v. Der Vektor wird nicht verändert.
do_mut<$T>($v: &mut-Vektor<$T>, $f: |&mut $T|)
Ausführen einer Aktion
F
auf jedem Element des Vektors v. Die Funktion f/ nimmt eine veränderbare Referenz auf das Element an.
Karte<$T, $U>($v: Vektor<$T>, $f: |$T| -> $U): Vektor<$U>
Den Vektor abbilden
v
in einen neuen Vektor, indem die Funktion f auf jedes Element angewendet wird. Behält die Reihenfolge der Elemente im Vektor bei, „first“ wird „first“ aufgerufen.
map_ref<$T, $U>($v: &Vektor<$T>, $f: |&$T| -> $U): Vektor<$U>
Den Vektor abbilden
v
in einen neuen Vektor, indem die Funktion f auf jedes Element angewendet wird. Behält die Reihenfolge der Elemente im Vektor bei, „first“ wird „first“ aufgerufen.
Filter<$T: Drop>($v: Vektor<$T>, $f: |&$T| -> bool): Vektor<$T>
Filtern Sie den Vektor
v
indem die Funktion f auf jedes Element angewendet wird. Gibt einen neuen Vektor zurück, der nur die Elemente enthält, für die f true zurückgibt.
Partition<$T>($v: Vektor<$T>, $f: |&$T| -> bool): (Vektor<$T>, Vektor<$T>)
Teilen Sie den Vektor
v
in zwei Vektoren, indem die Funktion f auf jedes Element angewendet wird. Gibt ein Tupel mit zwei Vektoren zurück: Der erste enthält die Elemente, für die f „true“ zurückgibt, und der zweite enthält die Elemente, für die f „false“ zurückgibt.
find_index<$T>($v: &vector<$T>, $f: |&$T| -> bool): Option<u64>
Findet den Index des ersten Elements im Vektor
v
das das Prädikat f erfüllt. Gibt some(index) zurück, wenn ein solches Element gefunden wird, andernfalls none().
Anzahl<$T>($v: &Vektor<$T>, $f: |&$T| -> bool): u64
Zählen Sie, wie viele Elemente im Vektor
v
das Prädikat f erfüllen.
Falte<$T, $Acc>($v: Vektor<$T>, $init: $Acc, $f: |$Acc, $T| -> $Acc): $Acc
Reduzieren Sie den Vektor
v
auf einen einzelnen Wert, indem die Funktion f auf jedes Element angewendet wird. Ähnlich wie fold_left in Rust und reduce in Python und JavaScript.
beliebig<$T>($v: &vector<$T>, $f: |&$T| -> bool): bool
Ob ein beliebiges Element im Vektor
v
erfüllt das Prädikat f. Wenn der Vektor leer ist, wird false zurückgegeben.
alle<$T>($v: &Vektor<$T>, $f: |&$T| -> bool): bool
Ob alle Elemente im Vektor
v
erfüllt das Prädikat f. Wenn der Vektor leer ist, wird „true“ zurückgegeben.
zip_do<$T1, $T2>($v1: Vektor<$T1>, $v2: Vektor<$T2>, $f: |$T1, $T2|)
Zerstört zwei Vektoren
v1
Und
v2
durch Aufruf von f für jedes Elementpaar. Bricht ab, wenn die Vektoren nicht gleich lang sind. Die Reihenfolge der Elemente in den Vektoren bleibt erhalten.
zip_do_reverse<$T1, $T2>($v1: Vektor<$T1>, $v2: Vektor<$T2>, $f: |$T1, $T2|)
Zerstört zwei Vektoren
v1
Und
v2
durch Aufrufen von f für jedes Elementpaar. Bricht ab, wenn die Vektoren nicht die gleiche Länge haben. Beginnt am Ende der Vektoren.
zip_do_ref<$T1, $T2>($v1: &Vektor<$T1>, $v2: &Vektor<$T2>, $f: |&$T1, &$T2|)
Iterieren Sie durch
v1
Und
v2
und wende die Funktion f auf die Referenzen jedes Elementpaars an. Die Vektoren werden nicht verändert. Abbruch, wenn die Vektoren nicht gleich lang sind. Die Reihenfolge der Elemente in den Vektoren bleibt erhalten.
zip_do_mut<$T1, $T2>($v1: &mut Vektor<$T1>, $v2: &mut Vektor<$T2>, $f: |&mut $T1, &mut $T2|)
Iterieren Sie durch
v1
und v2 und wende die Funktion f auf veränderbare Referenzen jedes Elementpaars an. Die Vektoren können verändert werden. Abbruch, wenn die Vektoren nicht gleich lang sind. Die Reihenfolge der Elemente in den Vektoren bleibt erhalten.
zip_map<$T1, $T2, $U>($v1: Vektor<$T1>, $v2: Vektor<$T2>, $f: |$T1, $T2| -> $U): Vektor<$U>
Zerstört zwei Vektoren
v1
Und
v2
indem auf jedes Elementpaar die Funktion f angewendet wird. Die zurückgegebenen Werte werden in einem neuen Vektor zusammengefasst. Abbruch, wenn die Vektoren nicht gleich lang sind. Die Reihenfolge der Elemente in den Vektoren bleibt erhalten.
zip_map_ref<$T1, $T2, $U>($v1: &vector<$T1>, $v2: &vector<$T2>, $f: |&$T1, &$T2| -> $U): Vektor<$U>
Iterieren Sie durch
v1
und v2 und wenden Sie die Funktion f auf die Referenzen jedes Elementpaars an. Die zurückgegebenen Werte werden in einem neuen Vektor gesammelt. Abbruch, wenn die Vektoren nicht gleich lang sind. Die Reihenfolge der Elemente in den Vektoren bleibt erhalten.
Tabelle B: Derzeit verfügbare Makrofunktionen für Option
Voranstellen
öffentlicher Makrospaß
zu jeder Zeile unten:
zerstören<$T>($o: Option<$T>, $f: |$T|)
Zerstören
Option<T>
und rufen Sie den Abschluss f für den darin enthaltenen Wert auf, wenn dieser einen enthält.
do<$T>($o: Option<$T>, $f: |$T|)
Zerstören
Option<T>
und rufen Sie den Abschluss f für den darin enthaltenen Wert auf, wenn dieser einen enthält.
do_ref<$T>($o: &Option<$T>, $f: |&$T|)
Führen Sie eine Schließung für den Wert im Inneren aus.
T
wenn es eines enthält.
do_mut<$T>($o: &mut Option<$T>, $f: |&mut $T|)
Führen Sie einen Abschluss für die veränderliche Referenz auf den Wert im Inneren aus.
T
wenn es eines enthält.
oder<$T>($o: Option<$T>, $default: Option<$T>): Option<$T>
Wählen Sie die erste
Manche
Wert aus den beiden Optionen oder None, wenn beide None sind. Entspricht Rusts a.or(b).
und<$T, $U>($o: Option<$T>, $f: |$T| -> Option<$U>): Option<$U>
Wenn der Wert
Manche
, rufe den Abschluss f darauf auf. Andernfalls gib None zurück. Entspricht Rusts t.and_then(f).
und_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> Option<$U>): Option<$U>
Wenn der Wert
Manche
, rufe den Abschluss f darauf auf. Andernfalls gib None zurück. Entspricht Rusts t.and_then(f).
map<$T, $U>($o: Option<$T>, $f: |$T| -> $U): Option<$U>
Karte ein
Option<T>
zu Option<U>, indem eine Funktion auf einen enthaltenen Wert angewendet wird. Entspricht Rusts t.map(f).
map_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> $U): Option<$U>
Karte ein
Option<T>
Wert zu Option<U>, indem eine Funktion auf einen enthaltenen Wert per Referenz angewendet wird. Originaloption<T> bleibt erhalten. Entspricht Rusts t.map(f).
filter<$T: drop>($o: Option<$T>, $f: |&$T| -> bool): Option<$T>
Zurückkehren
Keiner
wenn der Wert „None“ ist, andernfalls geben Sie „Option<T>“ zurück, wenn das Prädikat „f“ „true“ zurückgibt.
ist_ein_und<$T>($o: &Option<$T>, $f: |&$T| -> bool): bool
Zurückkehren
FALSCH
Wenn der Wert „None“ ist, geben Sie andernfalls das Ergebnis des Prädikats f zurück.
zerstören_oder<$T>($o: Option<$T>, $default: $T): $T
Zerstören
Option<T>
und gibt den darin enthaltenen Wert zurück, wenn dieser einen enthält, andernfalls den Standardwert. Entspricht Rusts t.unwrap_or(default).
Hinweis: Diese Funktion ist eine effizientere Version von
mit_Standard_zerstören
, da der Standardwert nur dann ausgewertet wird, wenn dies erforderlich ist. Die Funktion destroy_with_default sollte zugunsten dieser Funktion verworfen werden.