O Move 2024 beta agora inclui

macro

funções!

Funções macro no Move são semelhantes a funções regulares: você define os parâmetros, o tipo de retorno e a lógica que determina como suas funções macro operam, e então você pode chamar suas funções macro de qualquer lugar no seu código. Ao contrário das funções regulares, no entanto, o compilador expande essas funções no local para executar as operações definidas em linha.

As principais diferenças que separam as funções macro das funções normais são:

  • Extensões de sintaxe de função macro

  • Mover expansão do compilador e chamar semântica

  • Parâmetros lambda

Extensões de sintaxe: A sintaxe Move especifica como definir funções de macro, seus argumentos e sua invocação. Essas regras não apenas ajudam o compilador a diferenciar macros de funções normais, mas também fornecem uma diferenciação visual entre funções normais e macro durante o uso.

Expansão do compilador e semântica de chamada: Move avalia avidamente argumentos de função normais. Em outras palavras, o tempo de execução avalia completamente os argumentos que você passa para uma função normal, sejam eles necessários para a lógica da função ou não. Em contraste, as funções de macro substituem diretamente as expressões por seus argumentos durante a expansão, e o tempo de execução não verá a chamada de macro de forma alguma! Isso significa que os argumentos não são avaliados de forma alguma se o código do corpo da macro com o argumento nunca for alcançado. Por exemplo, se o argumento for substituído em uma expressão if-else e a ramificação com esse argumento nunca for tomada, o argumento não será avaliado.

Parâmetros lambda: Além de expressões normais, as funções Macro permitem que você forneça código parametrizado como argumentos de função de ordem superior. Isso é possível com a introdução do tipo lambda, permitindo que autores de macro criem macros poderosas e flexíveis.

Para mais detalhes sobre como definir funções macro, consulte a referência oficial no livro Move.

Anatomia das funções macro

O compilador Move expande funções macro durante a compilação, permitindo expressividade não disponível em funções normais. Para indicar esse comportamento de substituição especial, as funções macro têm seus parâmetros de tipo e parâmetros de expressão prefixados com um

$

personagem. Considere uma função Move intitulada `my_assert` que se comporta como o primitivo do compilador assert! (sem erros inteligentes).

macro fun my_assert($cond: bool, $code: u64) {     if (!$cond) abortar $code. }

O compilador Move substitui os argumentos para parâmetros de tipo e parâmetros de expressão durante a expansão. Esse comportamento cria o processo de avaliação lazy para argumentos mencionados na seção anterior. Como o código mostra, a macro `my_assert` verifica a condição passada a ela, abortando o programa se a condição `$cond` resolver para

falso

(ou não `true`) com o `$code` passado como segundo argumento.

Para invocar o

minha_afirmação

função macro, o programador usa um caractere ! após o nome da função para indicar visualmente que os argumentos não são avaliados avidamente:

my_assert!(vector[] == vector[], 0); my_assert!(true, 1 / 0); // não abortará! '1 / 0' não é avaliado.

Como você pode notar, o

$código

valor passado para a macro na segunda invocação inclui uma operação de divisão por zero. Embora um pouco artificial, seu propósito é mostrar que o compilador não avaliará a expressão porque o fluxo de controle nunca alcançará sua execução devido à condição verdadeira fornecida.

Macros com argumentos lambda

A adição de funções de macro inclui a introdução de um recurso totalmente novo: lambda. Usando parâmetros com tipos lambda, você pode passar código do chamador para o corpo da macro. Enquanto a substituição é feita em tempo de compilação, elas são usadas de forma semelhante a funções anônimas, lambdas ou closures em outras linguagens.

Por exemplo, considere um loop que executa um lambda em cada número de

0

para um número n que o código fornece.

macro pública fun do($n: u64, $f: |u64| -> ()) {     deixe mut i = 0;      deixe parar = $n;      enquanto (i < parar) {         $f(i);          i = i + 1;      } }

O parâmetro lambda (

$f

) é o segundo argumento para a macro do. O tipo do lambda é definido como |u64| -> (), que recebe um u64 como entrada e retorna (). O tipo de retorno de () é implícito, então o argumento também pode ser escrito simplesmente como $f: |u64|.

A lógica do seu programa poderia então usar esta macro para somar os números de

0

para 10 usando o parâmetro lambda:

deixe mut soma = 0; faça!(10, |i| soma = soma + i);

Aqui, o trecho de código `|i| sum = sum + i` define o lambda para a invocação da macro. Quando ele é chamado no corpo da macro, seus argumentos são avaliados e vinculados no corpo do lambda. Observe que, diferentemente das chamadas de macro, os lambdas avaliarão completamente seus argumentos antes de executar o corpo do lambda.

As funções macro são higiênicas, então o i no lambda não é o mesmo que o i no do. Veja a referência Move para mais detalhes.)

Macros na biblioteca padrão do Move

Com a adição de macros, o

mover-stdlib

A biblioteca apresenta uma série de funções macro para capturar padrões comuns de programação.

Macros inteiras

Para

u64

e outros tipos inteiros, a biblioteca move-stdlib fornece um conjunto de funções macro que simplificam loops comuns. Este post examina o tipo u64, mas as mesmas funções macro estão disponíveis para u8, u16, u32, u128 e u256.

Você já viu um exemplo, pois o código da função do é uma função macro em std::u64.

A lista completa de funções de macro atualmente disponíveis para tipos inteiros é:

  • macro pública diversão do($stop: u64, $f: |u64|)

    Loops aplicando $f a cada número de 0 a $stop (exclusivo).

  • macro pública diversão do_eq($stop: u64, $f: |u64|)

    Loops aplicando $f a cada número de 0 a $stop (inclusive).

  • macro pública diversão range_do($start: u64, $stop: u64, $f: |u64|)

    Loops aplicando $f a cada número de $start a $stop (exclusivo).

  • macro pública diversão range_do_eq($start: u64, $stop: u64, $f: |u64|)

    Loops aplicando $f a cada número de $start a $stop (inclusive).

Para um exemplo simplificado de aplicação das funções macro definidas, considere o seguinte loop:

deixe mut i = 0; enquanto (i < 10) {     foo(i);      i = i + 1; }

Você pode reescrever esse loop implementando o

fazer

função macro para std::u64 como:

10u64.do!(|i| foo(i));

Da mesma forma, você pode tornar o seguinte loop mais conciso usando o

função macro u64::range_do_eq

:

deixe mut i = 1; // loop de 1 a 10^8 por 10s enquanto (i <= 100_000_000) {     foo(i);      i = i * 10; }

O resultado da reescrita:

1u64.range_do_eq!(8, |i| foo(10u64.pow(i)));

Embora isso seja potencialmente mais fácil de ler, será preciso mais esforço para executá-lo.

vetor

macros

Num espírito semelhante ao dos inteiros

fazer

macro, você pode iterar sobre os elementos de um vetor usando a função do.

vetor[1, 2, 3, 4, 5].faça!(|x| foo(x));

Isso é equivalente a:

deixe mut v = vetor[1, 2, 3, 4, 5]; v.reverse(); enquanto (!v.is_empty()) {     foo(v.pop_back()); }

O código expandido mostra que esse processo consome o vetor. Use o

do_ref

e macros do_mut para iterar por referência ou por referência mutável, respectivamente.

diversão check_coins(moedas: &vetor<Moeda<SUI>>) {     moedas.do_ref!(|moeda| assert!(moeda.valor() > 0));      /* expande para     deixe mut i = 0;      deixe n = moedas.len();      enquanto (i < n) {         deixe moeda = &moedas[i];          assert!(moeda.valor() > 0);          i = i + 1; }     */ } fun take_10(coins: &mut vector<Coin<SUI>>) {     coins.do_mut!(|coin| transfer::public_transfer(coin.take(10), @0x42));      /* expande para     deixe mut i = 0;      deixe n = coins.len();      enquanto (i < n) {         deixe coin = &mut coins[i];          transfer::public_transfer(coin.take(10), @0x42);          i = i + 1; }     */ }

Além da iteração, você pode modificar e criar vetores com macros.

vetor::tabular

cria um vetor de comprimento n aplicando um lambda a cada índice.

poderes divertidos_de_2(n: u64): vetor<u64> {     vetor::tabular(n, |i| 2u64.pow(i))     /* expande para     deixe mut v = vetor[];     deixe mut i = 0;     enquanto (i < n) {         v.push_back(2u64.pow(i));          i = i + 1;      };      v     */ }

vetor::mapa

cria um novo vetor a partir de um vetor existente aplicando um lambda a cada elemento. Enquanto vector::map opera em um vetor por valor, as macros map_ref e map_mut operam em um vetor por referência e referência mutável, respectivamente.

diversão into_balances(moedas: vetor<Coin<SUI>>): vetor<Balance<SUI>> {     moedas.mapa!(|moeda| moeda.into_balance())     /* expande para     deixe mut v = vetor[];      moedas.reverso();      enquanto (!moedas.está_vazio()) {         deixe moeda = moedas.pop_back();          v.push_back(moeda.into_balance());      };      v     */ }

Semelhante ao mapa,

vetor::filtro

cria um novo vetor a partir de um vetor existente, mantendo apenas elementos que satisfazem um predicado.

fun non_zero_numbers(numbers: vector<u64>): vector<u64> {     numbers.filter!(|n| n > 0)     /* expande para     let mut v = vector[];      numbers.reverse();      while (!numbers.is_empty()) {         let n = numbers.pop_back();          if (n > 0) {             v.push_back(n); }     };     v     */ }

Consulte a Tabela A no final deste artigo para obter uma lista de funções macro atualmente disponíveis para vetores.

opção

macros

Enquanto

Opção

não é uma coleção com mais de um elemento, o tipo tem macros do (e do_ref e do_mut) para acessar facilmente seu valor, se houver algum.

fun maybe_transfer(coin: Option<Coin<SUI>>, destinatário: endereço) {     coin.do!(|c| transfer::public_transfer(c, destinatário))     /* expande para     if (coin.is_some()) transfer::public_transfer(coin.destroy_some(), destinatário)     else coin.destroy_none()     */ }

destruir

executa a mesma ação que do, mas talvez uma macro mais útil seja destroy_or que fornece uma expressão padrão, que é avaliada somente se Option for none. O exemplo a seguir encadeia map, que aplica o lambda ao valor de Option se for some, e destroy_or para converter concisamente um Option<Coin<SUI>> para um Balance<SUI>.

divertido to_balance_opt(coin: Option<Coin<SUI>>): Balance<SUI> {     coin.map!(|c| c.into_balance()).destroy_or!(Balance::zero())     /* expande para     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()     */ }

Consulte a Tabela B no final deste artigo para obter uma lista de funções de macro atualmente disponíveis para Option.

Encerrar

Funções de macro podem simplificar seu código convertendo padrões comuns em macros reutilizáveis. A esperança é que funções de macro possam substituir muitos, se não todos, dos seus loops comuns (loop e while). Fique de olho em mais funções de macro na biblioteca move-stdlib e, em breve, no framework sui.

Para mais detalhes sobre como as funções macro funcionam, consulte o livro Move.

Tabela A: Funções macro disponíveis atualmente para vetores

Prepend

diversão macro pública

para cada linha abaixo:

tabular<$T>($n: u64, $f: |u64| -> $T): vetor<$T>

Crie um vetor de comprimento

não

chamando a função f em cada índice.

destruir<$T>($v: vetor<$T>, $f: |$T|)

Destrua o vetor

v

chamando f em cada elemento e então destruindo o vetor. Não preserva a ordem dos elementos no vetor (começa do fim do vetor).

do<$T>($v: vetor<$T>, $f: |$T|)

Destrua o vetor

v

chamando f em cada elemento e então destruindo o vetor. Preserva a ordem dos elementos no vetor.

do_ref<$T>($v: &vetor<$T>, $f: |&$T|)

Executar uma ação

e

em cada elemento do vetor v. O vetor não é modificado.

do_mut<$T>($v: &mut vetor<$T>, $f: |&mut $T|)

Executar uma ação

e

em cada elemento do vetor v. A função f/ recebe uma referência mutável ao elemento.

mapa<$T, $U>($v: vetor<$T>, $f: |$T| -> $U): vetor<$U>

Mapear o vetor

v

para um novo vetor aplicando a função f a cada elemento. Preserva a ordem dos elementos no vetor, o primeiro é chamado de primeiro.

map_ref<$T, $U>($v: &vetor<$T>, $f: |&$T| -> $U): vetor<$U>

Mapear o vetor

v

para um novo vetor aplicando a função f a cada elemento. Preserva a ordem dos elementos no vetor, o primeiro é chamado de primeiro.

filtro<$T: soltar>($v: vetor<$T>, $f: |&$T| -> bool): vetor<$T>

Filtrar o vetor

v

aplicando a função f a cada elemento. Retorna um novo vetor contendo apenas os elementos para os quais f retorna true.

partição<$T>($v: vetor<$T>, $f: |&$T| -> bool): (vetor<$T>, vetor<$T>)

Dividir o vetor

v

em dois vetores aplicando a função f a cada elemento. Retorna uma tupla contendo dois vetores: o primeiro contendo os elementos para os quais f retorna true, e o segundo contendo os elementos para os quais f retorna false.

find_index<$T>($v: &vector<$T>, $f: |&$T| -> bool): Opção<u64>

Encontra o índice do primeiro elemento no vetor

v

que satisfaz o predicado f. Retorna some(index) se tal elemento for encontrado, caso contrário none().

contagem<$T>($v: &vetor<$T>, $f: |&$T| -> bool): u64

Conte quantos elementos no vetor

v

satisfazer o predicado f.

fold<$T, $Acc>($v: vetor<$T>, $init: $Acc, $f: |$Acc, $T| -> $Acc): $Acc

Reduza o vetor

v

para um único valor aplicando a função f a cada elemento. Semelhante a fold_left em Rust e reduce em Python e JavaScript.

qualquer<$T>($v: &vetor<$T>, $f: |&$T| -> bool): bool

Se algum elemento no vetor

v

satisfaz o predicado f. Se o vetor estiver vazio, retorna falso.

all<$T>($v: &vetor<$T>, $f: |&$T| -> bool): bool

Se todos os elementos do vetor

v

satisfaz o predicado f. Se o vetor estiver vazio, retorna verdadeiro.

zip_do<$T1, $T2>($v1: vetor<$T1>, $v2: vetor<$T2>, $f: |$T1, $T2|)

Destrói dois vetores

v1

e

v2

chamando f para cada par de elementos. Aborta se os vetores não forem do mesmo comprimento. A ordem dos elementos nos vetores é preservada.

zip_do_reverse<$T1, $T2>($v1: vetor<$T1>, $v2: vetor<$T2>, $f: |$T1, $T2|)

Destrói dois vetores

v1

e

v2

chamando f para cada par de elementos. Aborta se os vetores não tiverem o mesmo comprimento. Começa do fim dos vetores.

zip_do_ref<$T1, $T2>($v1: &vetor<$T1>, $v2: &vetor<$T2>, $f: |&$T1, &$T2|)

Iterar através de

v1

e

v2

e aplicar a função f às referências de cada par de elementos. Os vetores não são modificados. Aborta se os vetores não tiverem o mesmo comprimento. A ordem dos elementos nos vetores é preservada.

zip_do_mut<$T1, $T2>($v1: &mut vetor<$T1>, $v2: &mut vetor<$T2>, $f: |&mut $T1, &mut $T2|)

Iterar através de

v1

e v2 e aplicar a função f a referências mutáveis ​​de cada par de elementos. Os vetores podem ser modificados. Aborta se os vetores não tiverem o mesmo comprimento. A ordem dos elementos nos vetores é preservada.

zip_map<$T1, $T2, $U>($v1: vetor<$T1>, $v2: vetor<$T2>, $f: |$T1, $T2| -> $U): vetor<$U>

Destrói dois vetores

v1

e

v2

aplicando a função f a cada par de elementos. Os valores retornados são coletados em um novo vetor. Aborta se os vetores não tiverem o mesmo comprimento. A ordem dos elementos nos vetores é preservada.

zip_map_ref<$T1, $T2, $U>($v1: &vetor<$T1>, $v2: &vetor<$T2>, $f: |&$T1, &$T2| -> $U): vetor<$U>

 

Iterar através de

v1

e v2 e aplicar a função f a referências de cada par de elementos. Os valores retornados são coletados em um novo vetor. Aborta se os vetores não tiverem o mesmo comprimento. A ordem dos elementos nos vetores é preservada.

Tabela B: Funções macro disponíveis atualmente para a opção

Prepend

diversão macro pública

para cada linha abaixo:

destruir<$T>($o: Opção<$T>, $f: |$T|)

 

Destruir

Opção<T>

e chame o fechamento f no valor interno se ele contiver um.

do<$T>($o: Opção<$T>, $f: |$T|)

 

Destruir

Opção<T>

e chame o fechamento f no valor interno se ele contiver um.

do_ref<$T>($o: &Opção<$T>, $f: |&$T|)

 

Execute um fechamento no valor interno

para

se tiver um.

do_mut<$T>($o: &mut Opção<$T>, $f: |&mut $T|)

Execute um fechamento na referência mutável para o valor dentro

para

se tiver um.

ou<$T>($o: Opção<$T>, $default: Opção<$T>): Opção<$T>

 

Selecione o primeiro

Alguns

valor das duas opções, ou Nenhum se ambas forem Nenhum. Equivalente ao a.or(b) de Rust.

e<$T, $U>($o: Opção<$T>, $f: |$T| -> Opção<$U>): Opção<$U>

 

Se o valor for

Alguns

, chame o closure f nele. Caso contrário, retorne None. Equivalente ao t.and_then(f) de Rust.

and_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> Option<$U>): Option<$U>

Se o valor for

Alguns

, chame o closure f nele. Caso contrário, retorne None. Equivalente ao t.and_then(f) de Rust.

mapa<$T, $U>($o: Opção<$T>, $f: |$T| -> $U): Opção<$U>

 

Mapear um

Opção<T>

para Option<U> aplicando uma função a um valor contido. Equivalente ao t.map(f) de Rust.

map_ref<$T, $U>($o: &Opção<$T>, $f: |&$T| -> $U): Opção<$U>

Mapear um

Opção<T>

valor para Option<U> aplicando uma função a um valor contido por referência. Option<T> original é preservado. Equivalente ao t.map(f) de Rust.

filtro<$T: drop>($o: Opção<$T>, $f: |&$T| -> bool): Opção<$T>

Retornar

Nenhum

se o valor for Nenhum, caso contrário, retorne Option<T> se o predicado f retornar verdadeiro.

é_algum_e<$T>($o: &Opção<$T>, $f: |&$T| -> bool): bool

Retornar

falso

se o valor for Nenhum, caso contrário, retorne o resultado do predicado f.

destroy_or<$T>($o: Opção<$T>, $default: $T): $T

Destruir

Opção<T>

e retornar o valor interno se ele contiver um, ou default caso contrário. Equivalente ao t.unwrap_or(default) do Rust.

Nota: esta função é uma versão mais eficiente de

destruir_com_padrão

, pois não avalia o valor padrão a menos que seja necessário. A função destroy_with_default deve ser descontinuada em favor desta função.