Functions are reusable fragments of code. Usually a function has a name, accept input and produce output. You can use functions to resolve a small problem. Maybe you need to resolve same problem multiple times with some variations, handled by input parameters.
Function Concepts
Most common a function declaration start with data type, then function name followed by a list of parameters enclosed in brackets and separated by comma. A regular function have also a block of code delimited by curly brackets {} immediately after the parameters closing bracket.
//function declaration
type name(type param,...) {
//statements
...
return expression;
}
When function do not return a result, you can use keyword "void". But if function do not have parameters you must use an empty list like this: ().
//void function
void name() {
//statements
...
}
Any Dart program must have a function main(). This is the entry point of application. If this function is not found, the program will not run.
//simple function
void main() {
print('Hello, World!');
}
Simple functions based on expressions do not need a return type and you can use symbols: "=>" that look like an arrow to point out the result as an expression. This notation is called "arrow function" and is also well known in JavaScript.
int sum(int a, int b) => a + b;
Like most other languages Dart functions can have a list of parameters that are enumerated after function name. Parameters are declared like any other variable with type and name. If more than one they are separated by comma. Parameters can be "positional" or "named" parameters.
Positional parameters require arguments of same type in the respective position as the parameters. Positional parameters can not have initial values unless they are also optional.
Now you can see the previous array function again, used into a real program:
// function with mandatory parameters
int sum(int a, int b) => a + b;
void main() {
var s = sum(4,4);
print('s= $s');
}
To define optional parameters is not enough to assign them the initial values. It is required to enclose them also in square brackets. This is Dart specific convention: One single square parenthesis contains all optional parameters, separated by comma.
// function with optional parameters
int sum(int a, int b, [int c=0, int d=0]) => a + b + c + d;
void main() {
//provide only 2 parameters
var s1 = sum(4,4);
print('s= $s1'); // 8
//provide 3 parameters
var s2 = sum(4,4,5);
print('s= $s2'); // 13
//provide 4 parameters
var s3 = sum(4,4,5,6);
print('s= $s3'); // 19
}
Homework: Time for a little exercise lazy boy!. Open this example on-line and add another optional parameter so you can have 5 numbers added in the sum function. Link: dart params
Dart has a special convention to define named parameters. These parameters must be enclosed in curly brackets like a data set. A little bit difficult o get used to it, pay attention to the example:
// function with named optional parameters
int sum(int a, int b, {int c=0, int d=0}) => a + b + c + d;
void main() {
var s1 = sum(4,4,d:5);
print('s1 = $s1'); // 13
var s2 = sum(4,4,c:5);
print('s2 = $s2'); // 13
var s3= sum(4,4,c:2,d:3);
print('s3 = $s3'); // 13
}
Homework: Exercise again, I know you love it!. Open this example on-line and fix it, WITHOUT COPY PASTE. It is full of errors: dart nparams
In a regular declaration you should have at least one "return" statement that is usually at the end of the function block. You may have several return statements, each can produce one result in different conditions.
//function with null result
double div(int a, int b) {
if (b != 0) {
return a/b;
}
return null;
}
void main() {
print(div(1,2)); //0.5
print(div(1,0)); //null
}
All functions return a value. If no return value is specified, the statement "return null;" is implicitly appended to the function body. This is a simple function that return null:
//simple function
foo() {}
void main() {
assert(foo() == null);
}
Dart have First-class functions. That is functions are treated like any other variable. For example, in Dart, a function can be passed as an argument to other functions, can be returned by another function and can be assigned as a value to a variable.
//simple arrow functions
int sum(int p1, int p2) => p1 + p2;
int dif(int p1, int p2) => p1 - p2;
//function with function as parameter
int test(Function f, int a, int b) {
return f(a,b);
}
void main() {
int x = test(sum, 12, 10) ;
print(x); //22
int y = test(dif, 12, 10) ;
print(y); //2
}
Note: A function that handle first-class functions are called hig order functions. In previous example, function test, accept parameter f that is a function. The functions sum/dif are used as arguments.
In general, functions have names, but sometimes not. Anonymous functions can be used as arguments or definitions that can be stored as collection elements. The syntax is similar to normal function, except there is no name and often no result type.
Next syntax pattern demonstrate anonymous function declaration.
//anonymous function
(type param, ...) {
//statements
...
return expression;
};
Of course also arrow pattern will enable creation of anonymous functions:
(type param, ...) => expression;
We can rebuild the example above to use anonymous functions as parameters:
//function "f" as parameter
int test(Function f, int a, int b) {
return f(a,b);
}
void main() {
int x = test((int p1, int p2) => (p1 + p2), 12, 10) ;
print(x); //22
int y = test((int p1, int p2) => (p1 - p2), 12, 10) ;
print(y); //2
}
A function has a local scope. This is a region of code where function parameters and function variables are visible. This scope is limited to function block. You can define anonymous inner blocks using nested brackets: {...{...}}.
In the next example variable x is modified inside the inner block and take value 7 that is propagated to outer block with no problem.
//nested lexical scope
void main() {
var x = 2;
{
print("x = $x"); // x = 2
x = 7;
}
print("x = $x"); // x = 7
}
In the next example variable x is redefined inside the inner block and then modified. The new value do not propagate to outside of the block. Variable x was shadowed by the local new variable x.
//nested lexical scope
void main() {
var x = 2;
{
var x = 7;
print("x = $x"); // x = 7
}
print("x = $x"); // x = 5 (not 7)
}
Note:Dart compiler is very thorough. It will not let you redefine a variable that is already used prior to declaration inside the nested block. Only unused variables can be redefined and shadowed!
In Dart you can also define a function nested inside other function. That means function scope is nested. The outer function variables are visible in the inner function but the inner variables are isolated from the outer function.
//parameter can shadow outer variables
void main() {
var x = 2;
var y = 4;
int test(int x) {
print("x = $x"); // x = 2
print("y = $y"); // y = 4
x = 7; // ignored
y = 7; // accepted
}
test(x); // send x = 2
print("x = $x"); // x = 2 (not 7)
print("y = $y"); // y = 7 (not 4)
}
Don't panic. Is not so complicated as it sound. The trick is to look at the example below. The outer function "foo" create a closure "bar". What is intreating is that we can create two version of "bar". Each version remember different states of "foo". That is, the bar instances remember the parent lexical scope.
//high order function
Function foo(int delta) {
return (int x) => delta + x;
}
void main() {
var test = 0;
//create first closure
var bar1 = foo(10);
test = bar1(2);
print(test); //expect 12
//create second closure
var bar2 = foo(20);
test = bar2(2);
print(test); //expect 22
}
Comment: In example above, "foo" is like a factory of "bar" functions. Then function "foo" is very similar to a class and "bar" is very similar to an object. That is in fact the technique used by functional programming languages to cover the lack of classes.
Read next: Classes