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.