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á


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.

DART
// 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',
);
TEXT
> 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


3. Declarações de Funções e Arrow Functions

(1) Sintaxe de Declaração de Função

▶ Exemplo

TEXT
> 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

DART
// 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);
}
TEXT
> 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

100%
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"]
TEXT
> 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

TEXT
> 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

DART
// 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
}
TEXT
> 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

TEXT
> 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

DART
// 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',
  );
}
TEXT
> 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

TEXT
> 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

DART
// 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
}
TEXT
> 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

TEXT
> 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

DART
// 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)
}
TEXT
> 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

TEXT
> 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

DART
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
}
TEXT
> 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

TEXT
> 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

DART
// 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
}
TEXT
> 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

TEXT
> 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

DART
void main() {
  final orders = ['ORD-001', 'ORD-002', 'ORD-003'];
  orders.forEach((order) => print('Processing: $order'));
}
TEXT
> 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

TEXT
> 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

DART
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]
}
TEXT
> 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

TEXT
> 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

DART
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
}
TEXT
> 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

TEXT
> 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

DART
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
}
TEXT
> 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

TEXT
> 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

DART
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
}
TEXT
> 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

DART
// ============================================
// 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);
}
TEXT
> 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:

TEXT
=== 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, enquanto void 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 reduce e fold? R: reduce não requer um valor inicial, mas a coleção não pode ser vazia, e seu tipo de retorno corresponde ao tipo do elemento. fold requer 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/where retornam uma List ou um Iterable? R: Elas retornam um Iterable (avaliação preguiçosa). Adicione .toList() se precisar de uma List. Avaliação preguiçosa significa que a cadeia map/where só é 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: typedef cria um alias para um tipo de função, tornando o código mais legível. Por exemplo, typedef Validator = bool Function(String); é mais claro que bool Function(String).


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Escreva uma função formatUSD que 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").
  2. Intermediário (Dificuldade ⭐⭐): Use uma closure para implementar uma função de fábrica makeDiscountCalculator que 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.
  3. Desafio (Dificuldade ⭐⭐⭐): Usando uma cadeia de chamadas map/where/fold em 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ço for.

← Aula Anterior | Próxima Aula →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%