Functions in Dart

Functions are very import part of any programming language. In Dart, Functions are first-class objects. You can assign function to variable, pass as arguments and returns from function.

Let’s see following points in functions:

  1. Higher order functions
  2. Anonymous functions
  3. Lexical scoping & Lexical closures
  4. Fat arrow functions
  5. typedef keyword

Higher order functions

Functions which accept function as argument and/or return function are Higher order functions. Let’s see an example!

                                
    int performOperation(int i, int j, Function(int, int) operation) {
        return operation(i, j);
    }
        
    void main() {
        int result1 = performOperation(2, 3, (i, j) => i + j);
        print(result1); // 5
        
        int result2 = performOperation(4, 3, (i, j) {
            return i - j;
        });
        print(result2); // 1
    }                                      
                                
                            

Here, performOperation is a higher-order function which accepts function named operation as an argument. Function operation accepts two integers as an arguments and perform an operation and return an integer.

Let’s take another example!

                            
    int Function(int, int) performOperation(String operationType) {
        switch (operationType) {
            case 'SUM':
                return (i, j) => i + j;
            case 'SUB':
                return (i, j) => i - j;
            case 'MUL':
                return (i, j) => i * j;
            case 'DIV':
                return (i, j) => i ~/ j;
            default:
                throw "Unidentified operation";
        }
    }
        
    void main() {
        Function sumOperation = performOperation('SUM');
        print(sumOperation(2, 3));
        
        Function subOperation = performOperation('SUB');
        print(subOperation(8, 5));
    }
                                                                                                     
                            
                        

Here, performOperation returns function based on operationType.

Anonymous Functions

Nameless functions or functions without name are called as Anonymous functions. They are also called lambda functions or closures. One widely used use-case is pass lambda to high-order functions which accept function as an argument. Let’s take an example.

                            
    void main() {
        var filterEvens = (int i) {
            return i % 2 == 0;
        };
        
        List elements = [1, 2, 3, 4, 5, 6];
        List evenElements = elements.where(filterEvens).toList();
        for (int e in evenElements) {
            print(e);
        }
    }
                            
                        

Here, filterEvens is a anonymous function. It takes an integer and return boolean. Another way to create anonymous function:

                            
    void main() {
        List elements = [1, 2, 3, 4, 5, 6];
        List evenElements = elements.where((i) => i % 2 == 0).toList();
        for (int e in evenElements) {
            print(e);
        }
    }
                            
                        

Here, we are directly passing lambda function to argument of where function.

Lexical Scope

Lexical scope is also know as Static scope. When you have function inside function then your outer functions’ variables are resolved in nested function. In other way, when you define variable in outer function then it will be accessible to all its inner functions.

                            
    void main() {
        parseResponse();
    }
        
    void parseResponse() {
        String response = "SUCCESS";
        int responseCode = 200;
        
        bool responseValid() {
            return responseCode == 200 && response != null && response.isNotEmpty;
        }
        
        if (responseValid()) {
            print("Response is valid");
        } else {
            print("Response is not valid");
        }
    }                                
                            
                        

Here, response and responseCode are accessible to responseValid method.

Lexical Closures

Any function can access variables in its lexical scope even if it is invoked outside of its original scope. Such, function is called as lexical closures. In simple words, inner function can still use variables of outer function even if outer function has returned.

                            
ListView.builder(
  itemBuilder: (context, index) {
    return GestureDetector(
      onTap: () => print("Tapped: $index"),
      child: Text("Index: $index"),
    );
  },
  itemCount: 10,
);
                            
                        

This is standard example of ListView.builder in flutter. Here, onTap function can access to index variable so we can know on which item user has tapped. onTap might be called after 20 seconds or 2 minutes, however we will get a value of index. That’s the lexical closure.

Fat arrow functions

Fat arrow function is a short version of anonymous function. When function has only one expression then it qualifies as fat arrow function. Let’s first see an example without fat arrow syntax:

                            
    void main() {
        List elements = [1, 2, 3, 4, 5, 6];
        List evenElements = elements.where((i) {
            return i % 2 == 0; // Function without fat arrow syntax
        }).toList();
        for (int e in evenElements) {
            print(e);
        }
    }                                  
                            
                        

Here, we passed lambda to where function. Now, let’s convert it to fat arrow function:

                            
    void main() {
        List elements = [1, 2, 3, 4, 5, 6];
        List evenElements = elements.where((i) => i % 2 == 0) // Function with fat arrow syntax
                                            .toList();
        for (int e in evenElements) {
            print(e);
        }
    }
                            
                        

It is more concise and readable! Cool, isn’t it?

typedef keyword

As we discussed functions are first-class objects. We can define functions same as any-other type like int or string.

                            
    typedef FilterFunction = bool Function(int a);

    void main() {
        print("Printing evens:");
        filter((i) => i % 2 == 0);
        print("\nPrinting odds:");
        filter((i) => i % 2 != 0);
    }
    
    void filter(FilterFunction filterNumbers) {
        List elements = [1, 2, 3, 4, 5, 6];
        List evenElements = elements.where(filterNumbers).toList();
        for (int e in evenElements) {
        print(e);
        }
    }                                                                  
                            
                        

Here, we passed lambda to where function. Now, let’s convert it to fat arrow function:

                            
    void main() {
        List elements = [1, 2, 3, 4, 5, 6];
        List evenElements = elements.where((i) => i % 2 == 0) // Function with fat arrow syntax
                                            .toList();
        for (int e in evenElements) {
            print(e);
        }
    }
                            
                        

Here, we created FilterFunction using typedef, which takes one integer and returns a boolean. So, whenever we use FilterFunction, we know that what does it mean! It makes code more readable.

That’s it! Thanks for reading!

Thanks for reading! Please share this article with others to spread knowledge.

Leave a Reply

Your email address will not be published. Required fields are marked *