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.