Funções no Dart — Cidadãos de Primeira Classe e Closures
Funções são os blocos de construção do código — pequenas funções se combinam para construir grandes sistemas.
1. O que Você Aprenderá
- Declarações de funções e arrow functions (sintaxe
=>) - Parâmetros opcionais: Parâmetros nomeados
{}e parâmetros posicionais[] - O marcador de parâmetro
required - Closures e o mecanismo de captura de variáveis
- Funções de alta ordem: forEach / map / where / reduce / fold
2. A História Real de um Desenvolvedor
(1) A Dor: Erros Frequentes de Invocação Devidos à Confusão de Parâmetros
Bob definiu uma função processOrder no DataPipeline com 7 parâmetros posicionais. Membros da equipe frequentemente trocavam a ordem dos parâmetros ao chamar — alguém passou taxRate como discountRate, fazendo com que todos os 50.000 pedidos de um lote tivessem cálculos de desconto incorretos. As reclamações de clientes dispararam e as correções de emergência levaram 2 dias.
(2) A Solução: Parâmetros Nomeados
Os parâmetros nomeados {} do Dart dão a cada parâmetro um rótulo claro, eliminando a dependência de ordem durante a invocação. O compilador também pode verificar se os parâmetros required foram fornecidos.
// Antes: parâmetros posicionais - fácil de trocar
// processOrder('ORD-001', 1500.0, 0.08, 0.1, true, 'USD', 'json');
// Depois: parâmetros nomeados - auto-documentável
processOrder(
id: 'ORD-001',
amount: 1500.0,
taxRate: 0.08,
discountRate: 0.1,
currency: 'USD',
);
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
(3) Os Benefícios
- Parâmetros nomeados tornam as chamadas de função auto-documentáveis, eliminando a necessidade de verificar a definição para entender o significado dos parâmetros.
- O marcador
requiredpermite que o compilador ajude você a verificar parâmetros obrigatórios. - Funções de alta ordem tornam as operações com coleções mais concisas, reduzindo o uso de laços for em 70%.
3. Declarações de Funções e Arrow Functions
(1) Sintaxe de Declaração de Função
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Métodos de declaração de função
// Declaração de função padrão
double calculateTax(double amount, double taxRate) {
return amount * taxRate;
}
// Arrow function (corpo de expressão)
double calculateTaxShort(double amount, double taxRate) =>
amount * taxRate;
// Função void
void printOrderSummary(String id, double amount) {
print('Order $id: \$${amount.toStringAsFixed(2)} USD');
}
// Ponto de entrada main
void main() {
print(calculateTax(1500.0, 0.08)); // 120.0
print(calculateTaxShort(1500.0, 0.08)); // 120.0
printOrderSummary('ORD-001', 1500.0);
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
| Forma | Sintaxe | Caso de Uso |
|---|---|---|
| Função Padrão | { return expr; } |
Múltiplas declarações |
| Arrow Function | => expr |
Retorno de expressão única |
| Função Void | void name() {} |
Sem valor de retorno |
4. Tipos de Parâmetros em Detalhe
(1) Árvore de Decisão de Parâmetros
graph TD A[Parâmetros de Função] --> B[Posicionais<br/>obrigatórios por padrão] A --> C[Nomeados<br/>opcionais por padrão] B --> B1[Posicional obrigatório] B --> B2["Posicional opcional []"] C --> C1["Nomeado obrigatório required"] C --> C2["Nomeado opcional com padrão"]
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Parâmetros posicionais
// Parâmetros posicionais obrigatórios
double calculateTotal(double amount, double taxRate) {
return amount * (1 + taxRate);
}
// Parâmetros posicionais opcionais
String formatCurrency(double amount, [String currency = 'USD']) {
return '\$${amount.toStringAsFixed(2)} $currency';
}
void main() {
print(calculateTotal(1500.0, 0.08)); // 1620.0
print(formatCurrency(1500.0)); // $1500.00 USD
print(formatCurrency(1500.0, 'EUR')); // $1500.00 EUR
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Parâmetros nomeados
// Parâmetros nomeados - opcionais por padrão
double processOrder({
required String id,
required double amount,
double taxRate = 0.08,
double discountRate = 0,
String currency = 'USD',
}) {
final discounted = amount * (1 - discountRate);
final taxed = discounted * (1 + taxRate);
print('Order $id: \$${taxed.toStringAsFixed(2)} $currency');
return taxed;
}
void main() {
processOrder(id: 'ORD-001', amount: 1500.0);
processOrder(
id: 'ORD-002',
amount: 3200.0,
taxRate: 0.10,
discountRate: 0.15,
currency: 'EUR',
);
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: O marcador required
// required força o chamador a fornecer o parâmetro
class OrderValidator {
bool validate({
required String orderId,
required double amount,
String? customerName, // Opcional - pode ser nulo
}) {
if (orderId.isEmpty) return false;
if (amount <= 0) return false;
return true;
}
}
void main() {
final validator = OrderValidator();
// validator.validate(orderId: 'ORD-001'); // Erro! amount é obrigatório
validator.validate(orderId: 'ORD-001', amount: 1500.0); // OK
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
| Tipo de Parâmetro | Sintaxe | Obrigatório por Padrão | Valor Padrão |
|---|---|---|---|
| Posicional | Type name |
Sim | Nenhum |
| Posicional Opcional | [Type name = default] |
Não | Pode ser especificado |
| Nomeado | {Type name} |
Não | null |
| Nomeado Obrigatório | {required Type name} |
Sim | Nenhum |
| Nomeado com Padrão | {Type name = default} |
Não | Valor especificado |
5. Closures e Captura de Variáveis
(1) Princípios de Closure
Uma closure é um objeto de função que pode acessar variáveis de seu escopo léxico, mesmo quando chamada fora desse escopo.
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Noções básicas de closure
// Função que retorna uma função (closure)
Function makeTaxCalculator(double taxRate) {
// taxRate é capturada pela função retornada
return (double amount) => amount * taxRate;
}
void main() {
final usTax = makeTaxCalculator(0.08);
final euTax = makeTaxCalculator(0.20);
print(usTax(1500.0)); // 120.0 (8% imposto)
print(euTax(1500.0)); // 300.0 (20% imposto)
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Closure capturando variáveis
void main() {
// Closure de contador
int Function() makeCounter() {
int count = 0; // Variável capturada
return () => ++count;
}
final counterA = makeCounter();
final counterB = makeCounter();
print(counterA()); // 1
print(counterA()); // 2
print(counterB()); // 1 (variável capturada separada)
print(counterA()); // 3
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: Aplicação prática de closures
// Fábrica de filtros do DataPipeline
typedef OrderFilter = bool Function(Map<String, dynamic> order);
OrderFilter makeAmountFilter(double minAmount, {double? maxAmount}) {
return (order) {
final amount = order['amount'] as double;
if (amount < minAmount) return false;
if (maxAmount != null && amount > maxAmount) return false;
return true;
};
}
void main() {
final orders = [
{'id': 'ORD-001', 'amount': 1500.0},
{'id': 'ORD-002', 'amount': 50.0},
{'id': 'ORD-003', 'amount': 3200.0},
];
final premiumFilter = makeAmountFilter(1000);
final midRangeFilter = makeAmountFilter(100, maxAmount: 1000);
print(orders.where(premiumFilter).length); // 2
print(orders.where(midRangeFilter).length); // 1
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
6. Funções de Alta Ordem
(1) Funções de Alta Ordem para Operações com Coleções
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: forEach
void main() {
final orders = ['ORD-001', 'ORD-002', 'ORD-003'];
orders.forEach((order) => print('Processing: $order'));
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: map — Transformação
void main() {
final amounts = [1500.0, 3200.0, 890.0];
// Transformar valores em strings formatadas
final formatted = amounts.map((a) => '\$${a.toStringAsFixed(2)} USD').toList();
print(formatted); // [$1500.00 USD, $3200.00 USD, $890.00 USD]
// Aplicar cálculo de imposto
final withTax = amounts.map((a) => a * 1.08).toList();
print(withTax); // [1620.0, 3456.0, 961.2]
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: where — Filtragem
void main() {
final orders = [
{'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed'},
{'id': 'ORD-002', 'amount': 50.0, 'status': 'completed'},
{'id': 'ORD-003', 'amount': 3200.0, 'status': 'pending'},
];
// Filtrar pedidos completados de alto valor
final premiumCompleted = orders
.where((o) => o['status'] == 'completed')
.where((o) => (o['amount'] as double) >= 1000)
.toList();
print('Premium completed: ${premiumCompleted.length}'); // 1
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: reduce — Agregação
void main() {
final amounts = [1500.0, 3200.0, 890.0];
// Somar todos os valores
final total = amounts.reduce((sum, amount) => sum + amount);
print('Total: \$${total.toStringAsFixed(2)} USD'); // $5590.00 USD
// Encontrar máximo
final maxAmount = amounts.reduce((max, amount) => amount > max ? amount : max);
print('Max: \$${maxAmount.toStringAsFixed(2)} USD'); // $3200.00 USD
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
▶ Exemplo
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
: fold — Agregação com valor inicial
void main() {
final orders = [
{'amount': 1500.0, 'status': 'completed'},
{'amount': 3200.0, 'status': 'pending'},
{'amount': 890.0, 'status': 'completed'},
];
// fold com valor inicial e acumulador
final completedRevenue = orders.fold<double>(0.0, (sum, order) {
if (order['status'] == 'completed') {
return sum + (order['amount'] as double);
}
return sum;
});
print('Completed revenue: \$${completedRevenue.toStringAsFixed(2)} USD');
// Completed revenue: $2390.00 USD
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
| Função | Propósito | Tipo de Retorno | Precisa de Valor Inicial |
|---|---|---|---|
forEach |
Iterar e executar | void | Não |
map |
Transformar cada elemento | Iterable |
Não |
where |
Filtrar elementos | Iterable |
Não |
reduce |
Agregar em valor único | T | Não (mas a coleção não pode ser vazia) |
fold |
Agregar com valor inicial | Qualquer | Sim |
7. Exemplo Completo: Biblioteca de Funções de Análise de Pedidos do DataPipeline
// ============================================
// Biblioteca de Funções de Análise de Pedidos do DataPipeline
// Demonstra funções, closures e funções de alta ordem
// ============================================
typedef Order = Map<String, dynamic>;
// Fábrica de filtros usando closures
bool Function(Order) makeFilter({
double? minAmount,
String? requiredStatus,
}) {
return (Order order) {
if (minAmount != null && (order['amount'] as double) < minAmount) {
return false;
}
if (requiredStatus != null && order['status'] != requiredStatus) {
return false;
}
return true;
};
}
// Função de agregação com fold
Map<String, double> aggregateByCategory(List<Order> orders) {
return orders.fold<Map<String, double>>({}, (acc, order) {
final category = (order['category'] ?? 'Uncategorized') as String;
final amount = order['amount'] as double;
acc[category] = (acc[category] ?? 0) + amount;
return acc;
});
}
// Formatar entrada de relatório
String formatEntry(String category, double amount) =>
' $category: \$${amount.toStringAsFixed(2)} USD';
// Função principal do pipeline
void runPipeline(List<Order> orders, {double minAmount = 0}) {
final filter = makeFilter(minAmount: minAmount, requiredStatus: 'completed');
final filtered = orders.where(filter).toList();
final total = filtered.fold<double>(
0, (sum, o) => sum + (o['amount'] as double));
final byCategory = aggregateByCategory(filtered);
print('=== DataPipeline Report ===');
print('Total orders: ${orders.length}');
print('Filtered: ${filtered.length} (min: \$${minAmount} USD)');
print('Revenue: \$${total.toStringAsFixed(2)} USD');
print('\nBy Category:');
byCategory.forEach((cat, amount) => print(formatEntry(cat, amount)));
}
void main() {
final orders = <Order>[
{'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed', 'category': 'Electronics'},
{'id': 'ORD-002', 'amount': 50.0, 'status': 'completed', 'category': 'Books'},
{'id': 'ORD-003', 'amount': 3200.0, 'status': 'pending', 'category': 'Electronics'},
{'id': 'ORD-004', 'amount': 890.0, 'status': 'completed', 'category': 'Clothing'},
{'id': 'ORD-005', 'amount': 2100.0, 'status': 'completed', 'category': 'Electronics'},
];
runPipeline(orders, minAmount: 100);
}
> Saída: Execute localmente em DartPad ou usando `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x, e os resultados podem variar ligeiramente dependendo da versão do SDK.
Saída:
=== DataPipeline Report ===
Total orders: 5
Filtered: 3 (min: $100 USD)
Revenue: $4490.00 USD
By Category:
Electronics: $3600.00 USD
Clothing: $890.00 USD
❓ Perguntas Frequentes
P: Parâmetros nomeados e posicionais podem ser misturados? R: Sim, mas parâmetros nomeados devem vir após os posicionais. Por exemplo,
void f(int a, {int? b})é válido, enquantovoid f({int? b}, int a)é inválido.
P: Há diferença de performance entre arrow functions e funções regulares? R: Não. Arrow functions são apenas açúcar sintático; compilam para exatamente o mesmo código que funções regulares. A escolha depende da legibilidade do código.
P: Qual a diferença entre
reduceefold? R:reducenão requer um valor inicial, mas a coleção não pode ser vazia, e seu tipo de retorno corresponde ao tipo do elemento.foldrequer um valor inicial, pode trabalhar com uma coleção vazia, e seu tipo de retorno pode ser diferente.foldé geralmente recomendado.
P: Uma closure captura variáveis por valor ou por referência? R: Closures do Dart capturam variáveis por referência (não uma cópia do valor). Portanto, modificar a variável dentro da closure afeta a variável externa, e modificações externas afetam a closure.
P:
map/whereretornam umaListou umIterable? R: Elas retornam umIterable(avaliação preguiçosa). Adicione.toList()se precisar de umaList. Avaliação preguiçosa significa que a cadeiamap/wheresó é computada quando iterada, sem criar coleções intermediárias.
P: Funções podem ser passadas como argumentos para qualquer função? R: Sim, funções Dart são cidadãos de primeira classe. Podem ser atribuídas a variáveis, passadas como argumentos e retornadas como valores.
P: Para que serve
typedef? R:typedefcria um alias para um tipo de função, tornando o código mais legível. Por exemplo,typedef Validator = bool Function(String);é mais claro quebool Function(String).
📖 Resumo
- Funções Dart são cidadãos de primeira classe: podem ser atribuídas, passadas como argumentos e retornadas.
- Parâmetros nomeados +
requiredtornam as chamadas de função auto-documentáveis e previnem confusão de parâmetros. - Closures capturam referências a variáveis externas, permitindo funções de fábrica e filtros configuráveis.
- Funções de alta ordem
map/where/reduce/foldsão ferramentas centrais para operações com coleções. - Decisão de parâmetros de função: use parâmetros nomeados
requiredpara os obrigatórios, e parâmetros nomeados com padrão para os opcionais.
📝 Exercícios
- Básico (Dificuldade ⭐): Escreva uma função
formatUSDque use parâmetros nomeados para aceitar um valor e símbolo de moeda, retornando uma string formatada (ex.:formatUSD(amount: 1500.5, symbol: '€')retorna"€1,500.50"). - Intermediário (Dificuldade ⭐⭐): Use uma closure para implementar uma função de fábrica
makeDiscountCalculatorque aceite um parâmetro de taxa de desconto e retorne uma função para calcular o preço com desconto. Crie duas calculadoras com taxas de desconto diferentes e compare os resultados. - Desafio (Dificuldade ⭐⭐⭐): Usando uma cadeia de chamadas
map/where/foldem um conjunto de dados de pedidos: filtre pedidos completados → agrupe por categoria → calcule a receita total por categoria → encontre a categoria com maior receita. Faça todo este processo sem usar nenhum laçofor.