Dart: Funções

Carlos Costa

No Dart funções são objetos, isso que significa que elas podem ser assinadas por variáveis e passadas como argumentos de outras funções

Um simples exemplo de função:

fn(Function callback){
  callback('hello, world!');
}

fn(print); // hello, world!

Ao logo desse post vamos entender melhor a estrutura e comportamento de funções no Dart.

Arrow function


Arrow function é uma forma curta de escrever funções usando o padrão () => return. Exemplo:

String fn(){
  return 'hello, world!';
}

//usando arrow functions
String fn() => 'hello world';

Arrow functions permitem apenas expressões no seu corpo, statements(if/else, for loops, while/do-while, etc...) não são aceitos. Exemplo;

var x = 10;
var fn = () => x > 5 ? 'hello' : 'world!';

print(fn()) //world;

Parâmetros


Tipos de parâmetros em funções.

  • parâmetros posicionais obrigatórios;
  • parâmetros posicionais opcionais;
  • parâmetros nomeados;
  • parâmetros default.

Vejamos exemplos de cada um.

Parâmetros posicionais obrigatórios: Os argumentos da função devem ser inseridos na ordem da declaração. Exemplo:

fn(String a, bool b, int c){
  print(a);
  print(b);
  print(c);
}

    //a    //b   //c
fn('hello', true, 2022);

Parâmetros posicionais opcionais: remove a obrigatoriedade de determinado parâmetros. Exemplo:

fn(String a, int c, [bool? b]){
  print(a); //hello
  print(b); //null
  print(c); //2022
}

    //a     //c
fn('hello', 2022);

Parâmetros nomeados: O argumento é passado especificando o nome da parâmetro. Exemplo:

fn({ required String a, required bool b, required int c }){
  print(a); //hello
  print(b); //false
  print(c); //10
}

fn(c: 10, b: false, a: 'hello');

Usando parâmetros nomeados a ordem dos argumentos não importa.

Parâmetros default: é possível definir um valor default caso o parâmetro não receba nenhum argumento:

fn({String a = 'hello', bool b = true, int? c}) {
  print(a); //hello
  print(b); //true
  print(c); //10
}

fn(c: 10);
fn(String a, int c, [bool? b = true]){
  print(a); //hello
  print(b); //true
  print(c); //2022
}

  //a     //c
fn('hello', 2022);

Funções são objetos


No Dart é possível passar uma função como argumentos para outra função.

fn(callback){
  callback('hello world');
}

fn((arg) => {
  print(arg)
});

Funções também podem assinadas por variáveis.

var fn1 = (){
  print('hello world!');
};

var fn2 = () => print('hello world!');

fn1();
fn2();

Escopo


Variáveis declaradas no escopo pai podem ser acessadas no escopo filho, o contrário não é possível.

Exemplo: escopo filho acessando variável do escopo pai.

void fn1(){
  // scope pai
  var v1 = true;

  void fn2(){
    // scope filho
    print(v1)
  }
}

Quando o escopo pai tenta acessar uma variável no escopo filho o seguinte erro acontece.

void fn1(){
  // scope pai
  print(v2);

  void fn2(){
    // scope filho
    var v2 = true;
  }
}

👇 Executando.

💀 app.dart:8:9: Error: Undefined name 'v2'.
   print(v2);
         ^^

Ou seja, a variável não está definida para aquele escopo.

Closures


Closures são funções internas que conseguem manipular variáveis do escopo pai.

Vejamos um exemplo.

add(int i){
  // função interna
  return (arg) => arg + i;
}

var add10 = add(10);
var add2 = add(2);

print(add10(1)); //11
print(add2(1)); //3

No exemplo anterior a variável i é acessada e armazenada pela função interna.

var add10 = add(10)

// 👇 equivalente

var add10 = (arg) => 10 + arg

Mais um exemplo: função que acumula strings internamente.

cumulate() {
  String store = '';

  return (String arg) {
    store += arg;
    return store;
  };
}

var c = cumulate();
print(c('hello '));
print(c('world '));
print(c('and universe'));

☝️ Executando esse código teremos a seguinte saída. 👇

hello
hello world
hello world and universe

No exemplo anterior criamos uma função cumulare que define internamente uma variável storage, essa variável será incrementada a cada chamada da função c.

typedef


A keyword typedef serve para definir tipos de funções, ou seja, podemos definir um tipo de função e usá-la como um tipo de variável.

Exemplo: definir o tipo Calc que retorna int e tem dois parâmetros int.

typedef Calc = int Function(int a, int b);

Calc add = (int a, int b) => a + b;
Calc sub = (int a, int b) => a - b;
Calc mul = (int a, int b) => a * b;
Calc div = (int a, int b) => a ~/ b;

Usando generics: Agora vamos fazer o mesmo exemplo anterior usando generics, dessa forma será possível definir o tipo de retorno e dos parâmetros quando a função for criada.

typedef Calc<T> = T Function(T a, T b);

Calc<int> add = (a, b) => a + b;
Calc<int> sub = (a, b) => a - b;
Calc<num> mul = (a, b) => a * b;
Calc<double> div = (a, b) => a / b;

Recursividade


Uma função recursiva é uma função que chama a si mesma na sua definição.

Exemplo: Criar uma função chamada loop que vai receber dois parâmetros, o primeiro parâmetro será um número inteiro i e o segundo parâmetro será uma função que vai ser chamada i vezes.

loop(int i, Function f) {
  //condição de parada
  if (i > 0) {

    // chamada do callback
    f(i);

    // chamada recursiva
    loop(i - 1, f);
  }
}

loop(5, (i) => print('$i • hello world'));

👆 Executando esse código teremos a seguinte saída. 👇

5 • hello world
4 • hello world
3 • hello world
2 • hello world
1 • hello world

Referências