Using functions in Dart

Functions are another tool to change the program flow; a certain task is delegated to a function by calling it and providing some arguments. A function does the requested task and returns a value; the control flow returns where the function was called. In Java and C#, classes are indispensable and they are the most important structuring concept.

However, Dart is both functional and object-oriented. Functions are first-class objects themselves (they are of the function type) and can exist outside of a class as top-level functions (inside a class, they are called methods). In prorabbits_v2.dart of Chapter 1, Dart – A Modern Web Programming Language, calculateRabbits is an example of a top-level function; and deposit, withdraw, and toString, from banking_v2.dart of this chapter, are methods to be called on as an object of the class. Don't create a static class only as a container for helper functions!

Return types

A function can do either of the following:

  • Do something (have a so-called side effect): the return type, if indicated, is void, for example, the display function in return_types.dart. In fact, such a function does return an object, namely null (see the print in line (1) of the following code).
  • Return an exp expression, resulting in an object different from null, explicitly indicated by return exp like in displayStr (line (2)).

The { return exp; } syntax can be shortened to => exp;, as shown in display and displayStrShort; we'll use this function expression syntax wherever possible. The exp is an expression that returns a value, but it cannot be a statement like if. A function can be an argument to another function, like display in print line (1), or in line (4), where the isOdd function is passed to the where function:

main() {
  print(display('Hello')); // Message: Hello.   null   (1)
  print(displayStr('Hello')); // Message: Hello.       (2)
  print(displayStrShort('Hello')); // Message: Hello.
  print(display(display("What's up?")));               (3)
[1,2,3,4,5].where(isOdd).toList();     // [1, 3, 5] (4)  
}

display(message) => print('Message: $message.'),

displayStr(message) {
  return 'Message: $message.';
}

displayStrShort(message) => 'Message: $message.';
isOdd(n) => n % 2 == 1;

}

By omitting the parameter type, the display function becomes more general; its argument can be a String, number, Boolean, List, and so on.

Parameters

As all the parameter variables are objects, all the parameters are passed by reference; this means that the underlying object can be changed from within the function. Two types of parameters exist: the required (they come first in the parameter list) and the optional parameters. Optional parameters that depend on their position in the list are indicated between [] in the definition of the function. All the parameters we have seen so far in the examples were required, but the usage of only optional parameter(s) is also possible, as shown in the following code (refer to parameters.dart):

webLanguage([name]) =>  'The best web language is: $name';

When called, as shown in the following code, it produces the output shown as comments:

print(webLanguage());  // The best web language is: null
print(webLanguage('JavaScript')); // The best web language is:
 // JavaScript

An optional parameter can have a default value as shown in the following code:

webLanguage2([name='Dart']) =>  'The best web language is: $name';

If this function is called without an argument, the optional value will be substituted instead, but when called with an argument, this will take precedence:

print(webLanguage2());  // The best web language is: Dart
print(webLanguage2('JavaScript')); // The best web language is:
  // JavaScript

An example with required and optional parameters, with or without default values, (name=value) is as follows:

String hi(String msg, [String from, String to])
                    => '$msg from $from to $to';
String hi2(String msg, [String from='me', String to='you'])
                    => '$msg from $from to $to';

Here, msg always gets the first parameter value, from and to get a value when there are more parameters in that order (for this reason, they are called positional):

print(hi('hi'));                  // hi from null to null
print(hi('hi', 'me'));            // hi from me to null
print(hi('hi', 'me', 'you'));     // hi from me to you
print(hi2('hi'));                 // hi from me to you
print(hi2('hi', 'him'));          // hi from him to you
print(hi2('hi', 'him', 'her'));   // hi from him to her

While calling a function with optional parameters, it is often not clear what the code is doing. This can be improved by using named optional parameters. These are indicated by { } in the parameter list, such as in hi3:

String hi3(String msg, {String from, String to}) =>'$msg from $from to $to';

They are called with name:value and, because of the name, the position does not matter:

print(hi3('hi', to:'you', from:'me')); // hi from me to you

Named parameters can also have default values (name:value):

String hi4(String msg, {String from:'me', String to:'you'}) =>'$msg from $from to $to';

It is called as follows:

print(hi4('hi', from:'you')); // hi from you to you

To summarized it:

  • Optional positional parameters: [param]
  • Optional positional parameters with default values: [param=value]
  • Optional named parameters: {param}
  • Optional named parameters with default values: {param:value}

First class functions

A function can contain other functions, such as calcRabbits contains calc(years) in prorabbits_v4.dart:

String calculateRabbits(int years) {
  calc(years) => (2 * pow(E, log(GROWTH_FACTOR) * years)).round().toInt();
  var out = "After $years years:	 ${calc(years)} animals";
  return out;
}

This can be useful if the inner function needs to be called several times within the outer function, but it cannot be called from outside of this outer function. A slight variation would be to store the function in a calc variable that has the Function type, like in prorabbits_v5.dart:

String calculateRabbits(int years) {
  var calc = (years) => (2 * pow(E, log(GROWTH_FACTOR) * years)).round().toInt();                          (1)
  assert(calc is Function);
  var out = "After $years years:	 ${calc(years)} animals";
  return out;
}

The right-hand side of line (1) is an anonymous function or lambda that takes the years parameter and returns the expression after => (the lambda operator). It could also have been written as follows:

var calc2 = (years) {
  return (2 * pow(E, log(GROWTH_FACTOR) * years)).round().toInt();	
};

In prorabbits_v6.dart, the calc function is made top-level and is passed in the lineOut function as a parameter named fun:

void main() {
  print("The number of rabbits increases as:
");
  for (int years = 0; years <= NO_YEARS; years++) {
    lineOut(years, calc(years));
  }
}

calc(years) => // code omitted, same as line(1)
 //in the preceding code

lineOut(yrs, fun) {
  print("After $yrs years:	 ${fun} animals");
}

In the variation to the previous code, prorabbits_v7.dart has the calc inner function that has no parameter, yet it can use the years variable that exists in the surrounding scope. For this reason, calc is called a closure; it closes over the surrounding variables, retaining their values:

String calculateRabbits(int years) {
  calc() => (2 * pow(E, log(GROWTH_FACTOR) * years)).round().toInt();
  
  var out = "After $years years:	 ${calc()} animals";
  return out;
}

Closures can also be defined as top-level functions, as shown by closure.dart. The multiply function returns a function (that itself takes an i parameter). So, mult2 in the following code is a function that needs to be called with a parameter, for example, mult2(3):

// short version:  multiply(num n) => (num i) => n * i;
// long version:
Function multiply(num n) {
  return (num i) => n * i;
}

main() {
  var two = 2;
  var mult2 = multiply(two); // this is called partial application
  assert(mult2 is Function);
  print('${mult2(3)}'), // 6
}

This closure behavior (true lexical scoping) is most clearly seen in closure2.dart, where three anonymous functions (each of which retains the value of i) are added to a lstFun list. While calling them (the call is made with the () operator after the lstFun[i] list element), they know their value of i; this is a great improvement over JavaScript:

main() {
  var lstFun = [];
  for(var i in [10, 20, 30]) {
    lstFun.add( () => print(i) );
  }
  
  print(lstFun[0]()); //  10  null
  print(lstFun[1]()); //  20  null
  print(lstFun[2]()); //  30  null
}

While all these code variations might now perhaps seem as just esthetical, they can make your code clearer in more complex examples and we'll make good use of them in the forthcoming apps. The definition of a function comprises of its name, parameters, and return type, which is also called its signature. If you find this signature occurring often in your code, you can define it as a function type with typedef, as shown in the following code:

typedef int addInts(int a, b);

Then, you can use addInts as the type of a function that takes two values of int and returns an int value.

Both, in functional and OO programming, it is essential to break a large problem into smaller ones. In functional programming, the decomposition in functions is used to support a divide-and-conquer approach to problem solving. As a last remark, Dart does not have overloading of functions (or methods or constructors), because typing the arguments is not a requirement, Dart can't make the distinction. Every function must have a unique name and there can be only one constructor named after the class, but a class can have other constructors as well.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.97.75