Dart: Funções
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