Power Query M: Quando devemos usar uma função explícita em vez de each _?

Tempo de leitura:

5-8 minutos

Neste tutorial vamos perceber a diferença entre utilizar each _ e declarar explicitamente uma função na linguagem M do Power Query.

O objetivo não é deixar de utilizar each. Pelo contrário: each é extremamente útil para escrever funções simples de forma rápida e compacta.

No entanto, à medida que as nossas expressões se tornam mais avançadas, declarar explicitamente os parâmetros de uma função pode tornar o código mais claro e, em algumas situações, torna-se mesmo necessário.

Vamos começar pelo exemplo mais simples e aumentar gradualmente a complexidade.

1. each _ vs. função explícita

Vamos começar por criar uma lista com os números de 1 a 10:

{1 .. 10}

Pretendemos agora multiplicar cada elemento da lista por 2.

Podemos utilizar List.Transform:

List.Transform(
Source,
each _ * 2
)

A função List.Transform percorre cada elemento da lista e executa a função que fornecemos no segundo argumento.

Neste caso:

each _ * 2

O underscore _ representa o elemento atual da lista.

Assim, durante a execução, podemos imaginar:

_ = 1 → 1 * 2
_ = 2 → 2 * 2
_ = 3 → 3 * 2
...

Mas podemos escrever exatamente a mesma transformação utilizando uma função explícita:

List.Transform(
Source,
(x) => x * 2
)

Neste caso, demos ao parâmetro da função o nome x.

Portanto:

each _ * 2

é equivalente a:

(x) => x * 2

O resultado é exatamente o mesmo.

Neste exemplo concreto, a função explícita não apresenta uma vantagem significativa. Na realidade, each _ é mais curto e perfeitamente legível.

O objetivo deste primeiro exemplo é apenas perceber uma ideia fundamental:

each é uma forma abreviada de declarar uma função com um parâmetro implícito representado por _.


2. Cenário 1 – Quando existem vários parâmetros

Vamos agora analisar uma situação em que a função explícita deixa de ser apenas uma alternativa.

Considere a seguinte lista:

{10, 20, 30, 40}

Pretendemos somar todos os valores.

Podemos utilizar List.Accumulate:

List.Accumulate(
{10, 20, 30, 40},
0,
(acumulado, valor) => acumulado + valor
)

Neste caso, a função fornecida ao List.Accumulate recebe dois parâmetros:

(acumulado, valor) =>

O primeiro, acumulado, contém o resultado acumulado até ao momento.

O segundo, valor, contém o elemento atual da lista.

A execução ocorre conceptualmente desta forma:

acumulado valor resultado
0 10 10
10 20 30
30 30 60
60 40 100

O resultado final é:

100

Aqui já encontramos uma diferença importante.

each representa essencialmente uma função com apenas um parâmetro implícito:

(_) => ...

Mas neste caso necessitamos de dois:

(acumulado, valor) =>

Por isso, precisamos de declarar explicitamente a função.

Criar uma lista de valores acumulados

Podemos utilizar o mesmo princípio para criar uma lista com a evolução do acumulado:

List.Accumulate(
Source,
{0},
(acumulado, valor) =>
acumulado & {List.Last(acumulado) + valor}
)

Aqui o estado inicial já não é simplesmente:

0

mas sim:

{0}

Ou seja, uma lista.

Em cada iteração:

List.Last(acumulado)

obtém o último valor calculado, soma-lhe o valor atual e acrescenta o resultado à lista.

Para:

{10, 20, 30, 40}

obtemos:

{0, 10, 30, 60, 100}

Este é um excelente exemplo de uma situação em que uma função explícita é necessária porque a função recebe vários parâmetros.


3. Cenário 2 – Declarar tipos de dados

Outra vantagem das funções explícitas é podermos definir claramente os tipos de dados esperados pela função.

Vamos criar uma função para calcular:

Quantidade × Preço

Podemos escrever:

let
fxMultiplicar =
(quantidade as number, preco as number) as number =>
quantidade * preco,
Resultado =
fxMultiplicar(50, 15)
in
Resultado

Vamos analisar a declaração:

(quantidade as number, preco as number) as number =>

Estamos a indicar que a função recebe dois parâmetros:

quantidade
preco

e que ambos têm de ser do tipo:

number

Depois dos parâmetros temos ainda:

as number

que define o tipo de dados devolvido pela função.

Podemos interpretar a assinatura desta função como:

number + number → number

Neste caso:

fxMultiplicar(50, 15)

devolve:

750

A grande vantagem é que estamos a tornar explícito o contrato da função.

Sabemos exatamente:

o que entra;
que tipos são aceites;
e que tipo será devolvido.

Se tentarmos fornecer um valor incompatível com os tipos declarados, o Power Query pode identificar esse problema.

Por exemplo:

fxMultiplicar("50", 15)

Aqui "50" é texto, enquanto a função espera um number.

Assim, quando os tipos dos parâmetros são relevantes, a função explícita permite escrever código mais rigoroso e previsível.


4. Cenário 3 – Quando existem vários âmbitos

Vamos agora entrar numa das situações mais importantes: funções aninhadas.

Vamos criar uma tabela:

let
Source =
#table(
{"Produto", "Valores", "Factor"},
{
{"A", {10, 20, 40}, 2},
{"B", {2, 4, 6}, 3}
}
)
in
Source

Temos:

Produto Valores Factor
A {10,20,40} 2
B {2,4,6} 3

Pretendemos multiplicar cada número existente na coluna Valores pelo Factor da respetiva linha.

Aqui temos duas operações diferentes.

Primeiro, precisamos de percorrer as linhas da tabela.

Depois, para cada linha, temos de percorrer os elementos da lista existente em [Valores].

Podemos escrever:

let
Source =
#table(
{"Produto", "Valores", "Factor"},
{
{"A", {10, 20, 40}, 2},
{"B", {2, 4, 6}, 3}
}
),
Calculo =
Table.AddColumn(
Source,
"Multiplicar pelo Fator",
(LinhaTabela) =>
List.Transform(
LinhaTabela[Valores],
(ValorLista) =>
ValorLista * LinhaTabela[Factor]
)
)
in
Calculo

Aqui existem dois âmbitos diferentes.

A função:

(LinhaTabela) =>

recebe a linha atual da tabela.

Por exemplo:

Produto = A
Valores = {10,20,40}
Factor = 2

Dentro dessa função chamamos List.Transform.

Esta função recebe outro parâmetro:

(ValorLista) =>

que representa cada elemento da lista.

Assim, temos:

LinhaTabela → linha atual da tabela
ValorLista → elemento atual da lista

Para a primeira linha:

LinhaTabela[Factor] = 2

enquanto ValorLista passa sucessivamente por:

10
20
40

Portanto:

ValorLista * LinhaTabela[Factor]

executa:

10 × 2 = 20
20 × 2 = 40
40 × 2 = 80

A grande vantagem das funções explícitas começa agora a tornar-se evidente.

Ao utilizarmos nomes como:

LinhaTabela
ValorLista

sabemos imediatamente qual é o âmbito de cada valor.

Em expressões aninhadas, isto pode tornar o código bastante mais fácil de compreender do que utilizar vários each e vários _.


5. Cenário 4 – Referenciar o contexto exterior

Vamos agora avançar um passo.

No exemplo anterior existiam dois âmbitos.

Neste exemplo, a função interior vai precisar de consultar diretamente informação que pertence ao âmbito exterior.

Imaginemos duas tabelas.

A primeira contém clientes:

ID_Cliente Cliente
1 Contoso
2 Fabrikam
3 Adventure Works

E temos uma segunda tabela chamada Vendas:

ID_Cliente Valor
1 100
1 250
2 300
3 150
3 400

Pretendemos adicionar à tabela de clientes uma nova coluna chamada:

ValoresVenda

Essa coluna deverá conter as linhas da tabela Vendas correspondentes a cada cliente.

Podemos utilizar:

NovaColuna =
Table.AddColumn(
Tabela,
"ValoresVenda",
(LinhaCliente) =>
Table.SelectRows(
Vendas,
(LinhaVendas) =>
LinhaVendas[ID_Cliente] =
LinhaCliente[ID_Cliente]
)
)

Vamos analisar cuidadosamente o âmbito.

A função exterior é:

(LinhaCliente) =>

LinhaCliente representa a linha atual da tabela onde estamos a adicionar a nova coluna.

Depois executamos:

Table.SelectRows(
Vendas,
...
)

Esta função percorre as linhas da tabela Vendas.

A função interior é:

(LinhaVendas) =>

Por isso temos agora:

LinhaCliente → linha da tabela exterior
LinhaVendas → linha da tabela Vendas

E chegamos à parte mais importante:

LinhaVendas[ID_Cliente] =
LinhaCliente[ID_Cliente]

Do lado esquerdo estamos a consultar o ID_Cliente da linha atual da tabela Vendas.

Do lado direito estamos a consultar o ID_Cliente da linha pertencente ao âmbito exterior.

Por exemplo, se estivermos no cliente 1:

LinhaCliente[ID_Cliente] = 1

o Table.SelectRows vai testar as linhas de Vendas:

1 = 1 → verdadeiro
1 = 1 → verdadeiro
2 = 1 → falso
3 = 1 → falso
3 = 1 → falso

Como resultado, a nova coluna contém apenas as vendas pertencentes ao cliente atual.

Este exemplo demonstra uma característica muito importante das funções em M:

uma função interior pode continuar a aceder a identificadores definidos no âmbito da função exterior.

E aqui a vantagem da função explícita torna-se particularmente evidente.

Em vez de termos vários _ cujo significado depende do each onde nos encontramos, podemos escrever:

LinhaCliente
LinhaVendas

e perceber imediatamente de que contexto vem cada valor.


Conclusão

each continua a ser extremamente útil em Power Query.

Para uma transformação simples como:

each _ * 2

não existe normalmente qualquer necessidade de escrever:

(x) => x * 2

A primeira solução é mais curta e perfeitamente clara.

Mas à medida que o código evolui, as funções explícitas começam a oferecer vantagens importantes.

Com elas podemos:

  1. trabalhar com vários parâmetros;
  2. declarar os tipos de dados dos parâmetros e do resultado;
  3. distinguir diferentes âmbitos em funções aninhadas;
  4. referenciar claramente valores pertencentes ao contexto exterior.

A regra prática pode ser muito simples:

Se a expressão é curta e existe apenas um contexto óbvio, each _ é normalmente a melhor escolha.

Quando precisamos de identificar parâmetros, distinguir âmbitos ou trabalhar com lógica mais avançada, uma função explícita torna o código muito mais claro e controlável.

Próximo artigo:

Artigo Anterior:


Comentários

Leave a Reply

Discover more from Exceldriven

Subscribe now to keep reading and get access to the full archive.

Continue reading