Functions
Learning Objectives
- You know how to define functions in Dart.
- You know of pass-by-value and pass-by-reference.
- You know of functions as objects and you know of higher-order functions.
Almost all programming languages provide a mechanism to divide the code into smaller, reusable parts — typically functions. Functions are blocks of code that perform a specific task and can be called from other parts of the program. Functions help in organizing code, making it more readable, and reducing redundancy.
Defining functions
In Dart, functions consist of a return type, function name, optional parameters, and a function body, like in many other programming languages. For example, the following program shows the function main
, which is the entrypoint to all Dart applications.
Above, the return type is void
(i.e., nothing), the name is main
, there are no parameters, and the function body is empty.
Dart uses
lowerCamelCase
for naming functions and variables. This means that the first letter of the function name is lowercase, and the first letter of each subsequent word is capitalized. For example,displaySum
is a function name that follows thelowerCamelCase
naming convention.
Function parameters
Function parameters are defined inside the parentheses of the function declaration. If there are multiple parameters, the parameters are separated by commas. Below, the function displaySum
takes two integers a
and b
as parameters, and prints their sum.
Functions can also have optional parameters. Optional parameters are defined by enclosing the parameter name in square brackets. In the following example, the function displaySum
has an optional parameter c
that defaults to 0
.
Optional parameters can also be named parameters. Named parameters are defined by enclosing the parameter name in curly braces. Named parameters can be passed in any order, and they can be omitted if they have default values.
In the following example, the function displaySum
has named parameters b
and c
that both default to 0
. When the function is called, the values of b
and c
are passed by name.
Pass-by-value and object references
Pass-by-value means that the function receives a copy of the value of the variable, not the original variable itself. An alternative option would be pass-by-reference, where the function receives a reference to the original variable. The key difference between these two are that pass-by-value does not allow the function to modify the original variable, while pass-by-reference does.
Dart uses pass-by-value for all variable passing, but the behavior to some extent depends on the type of the variable being passed. When passing primitive types (e.g. int, double, String, bool), the behavior is the same as pass-by-value in other languages. When passing objects, the value being passed is the reference to the object, i.e. the whole object is not copied.
For primitive types, the function receives a copy of the value, and any modifications to the parameter inside the function do not affect the original variable outside the function.
When passing objects, the value being passed is the reference to the object. This means that calling methods or making modifications to the object’s properties within the function can affect the original object, as the copy of the reference points to the same object.
At the same time, reassigning the variable inside the function does not affect the original variable outside the function, as the reference is changed to point to a new object.
This distinction is important to understand the behavior of functions when working with both primitive types and complex objects.
Primitive variables like
String
andint
are immutable in Dart, meaning that their values cannot be changed once they are assigned. When you modify an immutable variable, you are actually creating a new variable with a new value that replaces the old one. This is why the original variable remains unchanged when passed to a function.
Returning values
Functions can return values using the return
keyword, which is followed by the value to be returned. The return type is declared before the function name.
Note that although in some languages like Scala where the last expression in a function is automatically returned, in Dart, you need to explicitly use the return
keyword to return a value from a function.
That is, the following function would not return the sum of a
and b
. When you try to run the program, you’ll notice an error from the compiler.
Dart also supports the arrow syntax for single expression functions. The arrow =>
replaces the curly braces and the (optional) return
keyword.
While Dart could infer the types of parameters and return values, they should always be explicitly declared. This makes the code more readable and helps catch errors early in the development process.
Functions as objects and higher-order functions
In Dart, functions are first-class objects. This means that functions can be assigned to variables, passed as parameters to other functions, and even returned from other functions.
The following example shows assigning a function to a variable and calling the function using the variable.
Functions can be passed as parameters to other functions, enabling the creation of higher-order functions. A higher-order function is a function that accepts another function as a parameter or returns a function.
Higher-order functions can also return other functions. This allows you to create closures or dynamically generate new functions based on input.
Above, the getMultiplier
function generates and returns a closure, which is a function that remembers its surrounding context (the factor
variable in this case).