Blog

  • Libraries

    Elixir provides excellent interoperability with Erlang libraries. Let us discuss a few libraries in brief.

    The Binary Module

    The built-in Elixir String module handles binaries that are UTF-8 encoded. The binary module is useful when you are dealing with binary data that is not necessarily UTF-8 encoded. Let us consider an example to further understand the Binary module −

    # UTF-8
    IO.puts(String.to_char_list("Ø"))
    
    # binary
    IO.puts(:binary.bin_to_list "Ø")

    When the above program is run, it produces the following result −

    [216]
    [195, 152]
    

    The above example shows the difference; the String module returns UTF-8 codepoints, while :binary deals with raw data bytes.

    The Crypto Module

    The crypto module contains hashing functions, digital signatures, encryption and more. This module is not part of the Erlang standard library, but is included with the Erlang distribution. This means you must list :crypto in your project’s applications list whenever you use it. Let us see an example using the crypto module −

    IO.puts(Base.encode16(:crypto.hash(:sha256, "Elixir")))

    When the above program is run, it produces the following result −

    3315715A7A3AD57428298676C5AE465DADA38D951BDFAC9348A8A31E9C7401CB
    

    The Digraph Module

    The digraph module contains functions for dealing with directed graphs built of vertices and edges. After constructing the graph, the algorithms in there will help finding, for instance, the shortest path between two vertices, or loops in the graph. Note that the functions in :digraph alter the graph structure indirectly as a side effect, while returning the added vertices or edges.

    digraph = :digraph.new()
    coords = [{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}]
    [v0, v1, v2] = (for c <- coords, do: :digraph.add_vertex(digraph, c))
    :digraph.add_edge(digraph, v0, v1)
    :digraph.add_edge(digraph, v1, v2)
    for point <- :digraph.get_short_path(digraph, v0, v2) do 
       {x, y} = point
       IO.puts("#{x}, #{y}")
    end

    When the above program is run, it produces the following result −

    0.0, 0.0
    1.0, 0.0
    1.0, 1.0
    

    The Math Module

    The math module contains common mathematical operations covering trigonometry, exponential and logarithmic functions. Let us consider the following example to understand how the Math module works −

    # Value of pi
    IO.puts(:math.pi())
    
    # Logarithm
    IO.puts(:math.log(7.694785265142018e23))
    
    # Exponentiation
    IO.puts(:math.exp(55.0))
    
    #...

    When the above program is run, it produces the following result −

    3.141592653589793
    55.0
    7.694785265142018e23
    

    The Queue Module

    The queue is a data structure that implements (double-ended) FIFO (first-in first-out) queues efficiently. The following example shows how a Queue module works −

    q = :queue.new
    q = :queue.in("A", q)
    q = :queue.in("B", q)
    {{:value, val}, q} = :queue.out(q)
    IO.puts(val)
    {{:value, val}, q} = :queue.out(q)
    IO.puts(val)

    When the above program is run, it produces the following result −

    A
    B
    
  • Concurrency

    Concurrency is the execution of several instruction sequences at the same time. It involves performing more than one task simultaneously.

    Dart uses Isolates as a tool for doing works in parallel. The dart:isolate package is Dart’s solution to taking single-threaded Dart code and allowing the application to make greater use of the hard-ware available.

    Isolates, as the name suggests, are isolated units of running code. The only way to send data between them is by passing messages, like the way you pass messages between the client and the server. An isolate helps the program to take advantage of multicore microprocessors out of the box.

    Example

    Let’s take an example to understand this concept better.

    import 'dart:isolate';  
    void foo(var message){ 
       print('execution from foo ... the message is :${message}'); 
    }  
    void main(){ 
       Isolate.spawn(foo,'Hello!!'); 
       Isolate.spawn(foo,'Greetings!!'); 
       Isolate.spawn(foo,'Welcome!!'); 
       
       print('execution from main1'); 
       print('execution from main2'); 
       print('execution from main3'); 
    }

    Here, the spawn method of the Isolate class facilitates running a function, foo, in parallel with the rest of our code. The spawn function takes two parameters −

    • the function to be spawned, and
    • an object that will be passed to the spawned function.

    In case there is no object to pass to the spawned function, it can be passed a NULL value.

    The two functions (foo and main) might not necessarily run in the same order each time. There is no guarantee as to when foo will be executing and when main() will be executing. The output will be different each time you run.

    Output 1

    execution from main1 
    execution from main2 
    execution from main3 
    execution from foo ... the message is :Hello!! 
    

    Output 2

    execution from main1 
    execution from main2 
    execution from main3 
    execution from foo ... the message is :Welcome!! 
    execution from foo ... the message is :Hello!! 
    execution from foo ... the message is :Greetings!! 
    

    From the outputs, we can conclude that the Dart code can spawn a new isolate from running code like the way Java or C# code can start a new thread.

    Isolates differ from threads in that an isolate has its own memory. There’s no way to share a variable between isolates—the only way to communicate between isolates is via message passing.

    Note − The above output will be different for different hardware and operating system configurations.

    Isolate v/s Future

    Doing complex computational work asynchronously is important to ensure responsiveness of applications. Dart Future is a mechanism for retrieving the value of an asynchronous task after it has completed, while Dart Isolates are a tool for abstracting parallelism and implementing it on a practical high-level basis.

  • Async

    An asynchronous operation executes in a thread, separate from the main application thread. When an application calls a method to perform an operation asynchronously, the application can continue executing while the asynchronous method performs its task.

    Example

    Let’s take an example to understand this concept. Here, the program accepts user input using the IO library.

    import 'dart:io'; 
    void main() { 
       print("Enter your name :");            
       
       // prompt for user input 
       String name = stdin.readLineSync();  
       
       // this is a synchronous method that reads user input 
       print("Hello Mr. ${name}"); 
       print("End of main"); 
    } 

    The readLineSync() is a synchronous method. This means that the execution of all instructions that follow the readLineSync() function call will be blocked till the readLineSync() method finishes execution.

    The stdin.readLineSync waits for input. It stops in its tracks and does not execute any further until it receives the user’s input.

    The above example will result in the following output −

    Enter your name :     
    Tom                   
    
    // reads user input  
    Hello Mr. Tom 
    End of main
    

    In computing, we say something is synchronous when it waits for an event to happen before continuing. A disadvantage in this approach is that if a part of the code takes too long to execute, the subsequent blocks, though unrelated, will be blocked from executing. Consider a webserver that must respond to multiple requests for a resource.

    A synchronous execution model will block every other user’s request till it finishes processing the current request. In such a case, like that of a web server, every request must be independent of the others. This means, the webserver should not wait for the current request to finish executing before it responds to request from other users.

    Simply put, it should accept requests from new users before necessarily completing the requests of previous users. This is termed as asynchronous. Asynchronous programming basically means no waiting or non-blocking programming model. The dart:async package facilitates implementing asynchronous programming blocks in a Dart script.

    Example

    The following example better illustrates the functioning of an asynchronous block.

    Step 1 − Create a contact.txt file as given below and save it in the data folder in the current project.

    1, Tom 
    2, John 
    3, Tim 
    4, Jane 
    

    Step 2 − Write a program which will read the file without blocking other parts of the application.

    import "dart:async"; 
    import "dart:io";  
    
    void main(){ 
       File file = new File( Directory.current.path+"\\data\\contact.txt"); 
       Future<String> f = file.readAsString();  
      
       // returns a futrue, this is Async method 
       f.then((data)=>print(data));  
       
       // once file is read , call back method is invoked  
       print("End of main");  
       // this get printed first, showing fileReading is non blocking or async 
    }

    The output of this program will be as follows −

    End of main 
    1, Tom 
    2, John 
    3, Tim 
    4, Jan
    

    The “end of main” executes first while the script continues reading the file. The Future class, part of dart:async, is used for getting the result of a computation after an asynchronous task has completed. This Future value is then used to do something after the computation finishes.

    Once the read operation is completed, the execution control is transferred within “then()”. This is because the reading operation can take more time and so it doesn’t want to block other part of program.

    Dart Future

    The Dart community defines a Future as “a means for getting a value sometime in the future.” Simply put, Future objects are a mechanism to represent values returned by an expression whose execution will complete at a later point in time. Several of Dart’s built-in classes return a Future when an asynchronous method is called.

    Dart is a single-threaded programming language. If any code blocks the thread of execution (for example, by waiting for a time-consuming operation or blocking on I/O), the program effectively freezes.

    Asynchronous operations let your program run without getting blocked. Dart uses Future objects to represent asynchronous operations.

  • Errors Handling

    Elixir has three error mechanisms: errors, throws and exits. Let us explore each mechanism in detail.

    Error

    Errors (or exceptions) are used when exceptional things happen in the code. A sample error can be retrieved by trying to add a number into a string −

    IO.puts(1 + "Hello")

    When the above program is run, it produces the following error −

    ** (ArithmeticError) bad argument in arithmetic expression
       :erlang.+(1, "Hello")
    

    This was a sample inbuilt error.

    Raising Errors

    We can raise errors using the raise functions. Let us consider an example to understand the same −

    #Runtime Error with just a message
    raise "oops"  # ** (RuntimeError) oops

    Other errors can be raised with raise/2 passing the error name and a list of keyword arguments

    #Other error type with a message
    raise ArgumentError, message: "invalid argument foo"

    You can also define your own errors and raise those. Consider the following example −

    defmodule MyError do
       defexception message: "default message"
    end
    
    raise MyError  # Raises error with default message
    raise MyError, message: "custom message"  # Raises error with custom message

    Rescuing Errors

    We do not want our programs to abruptly quit but rather the errors need to be handled carefully. For this we use error handling. We rescue errors using the try/rescue construct. Let us consider the following example to understand the same −

    err = try do
       raise "oops"
    rescue
       e in RuntimeError -> e
    end
    
    IO.puts(err.message)

    When the above program is run, it produces the following result −

    oops
    

    We have handled errors in the rescue statement using pattern matching. If we do not have any use of the error, and just want to use it for identification purposes, we can also use the form −

    err = try do
       1 + "Hello"
    rescue
       RuntimeError -> "You've got a runtime error!"
       ArithmeticError -> "You've got a Argument error!"
    end
    
    IO.puts(err)

    When running above program, it produces the following result −

    You've got a Argument error!
    

    NOTE − Most functions in the Elixir standard library are implemented twice, once returning tuples and the other time raising errors. For example, the File.read and the File.read! functions. The first one returned a tuple if the file was read successfully and if an error was encountered, this tuple was used to give the reason for the error. The second one raised an error if an error was encountered.

    If we use the first function approach, then we need to use case for pattern matching the error and take action according to that. In the second case, we use the try rescue approach for error prone code and handle errors accordingly.

    Throws

    In Elixir, a value can be thrown and later be caught. Throw and Catch are reserved for situations where it is not possible to retrieve a value unless by using throw and catch.

    The instances are quite uncommon in practice except when interfacing with libraries. For example, let us now assume that the Enum module did not provide any API for finding a value and that we needed to find the first multiple of 13 in a list of numbers −

    val = try do
       Enum.each 20..100, fn(x) ->
    
      if rem(x, 13) == 0, do: throw(x)
    end "Got nothing" catch x -> "Got #{x}" end IO.puts(val)

    When the above program is run, it produces the following result −

    Got 26
    

    Exit

    When a process dies of “natural causes” (for example, unhandled exceptions), it sends an exit signal. A process can also die by explicitly sending an exit signal. Let us consider the following example −

    spawn_link fn -> exit(1) end

    In the example above, the linked process died by sending an exit signal with value of 1. Note that exit can also be “caught” using try/catch. For example −

    val = try do
       exit "I am exiting"
    catch
       :exit, _ -> "not really"
    end
    
    IO.puts(val)

    When the above program is run, it produces the following result −

    not really
    

    After

    Sometimes it is necessary to ensure that a resource is cleaned up after some action that can potentially raise an error. The try/after construct allows you to do that. For example, we can open a file and use an after clause to close it–even if something goes wrong.

    {:ok, file} = File.open "sample", [:utf8, :write]
    try do
       IO.write file, "olá"
       raise "oops, something went wrong"
    after
       File.close(file)
    end

    When we run this program, it will give us an error. But the after statement will ensure that the file descriptor is closed upon any such event.

  • Libraries

    A library in a programming language represents a collection of routines (set of programming instructions). Dart has a set of built-in libraries that are useful to store routines that are frequently used. A Dart library comprises of a set of classes, constants, functions, typedefs, properties, and exceptions.

    Importing a library

    Importing makes the components in a library available to the caller code. The import keyword is used to achieve the same. A dart file can have multiple import statements.

    Built in Dart library URIs use the dart: scheme to refer to a library. Other libraries can use a file system path or the package: scheme to specify its URI. Libraries provided by a package manager such as the pub tool uses the package: scheme.

    The syntax for importing a library in Dart is given below −

    import 'URI'
    

    Consider the following code snippet −

    import 'dart:io' 
    import 'package:lib1/libfile.dart' 
    

    If you want to use only part of a library, you can selectively import the library. The syntax for the same is given below −

    import 'package: lib1/lib1.dart' show foo, bar;  
    // Import only foo and bar. 
    
    import 'package: mylib/mylib.dart' hide foo;  
    // Import all names except foo
    

    Some commonly used libraries are given below −

    Sr.NoLibrary & Description
    1dart:ioFile, socket, HTTP, and other I/O support for server applications. This library does not work in browser-based applications. This library is imported by default.
    2dart:coreBuilt-in types, collections, and other core functionality for every Dart program. This library is automatically imported.
    3dart: mathMathematical constants and functions, plus a random number generator.
    4dart: convertEncoders and decoders for converting between different data representations, including JSON and UTF-8.
    5dart: typed_dataLists that efficiently handle fixed sized data (for example, unsigned 8 byte integers).

    Example : Importing and using a Library

    The following example imports the built-in library dart: math. The snippet calls the sqrt() function from the math library. This function returns the square root of a number passed to it.

    import 'dart:math'; 
    void main() { 
       print("Square root of 36 is: ${sqrt(36)}"); 
    }

    Output

    Square root of 36 is: 6.0
    

    Encapsulation in Libraries

    Dart scripts can prefix identifiers with an underscore ( _ ) to mark its components private. Simply put, Dart libraries can restrict access to its content by external scripts. This is termed as encapsulation. The syntax for the same is given below −

    Syntax

    _identifier
    

    Example

    At first, define a library with a private function.

    library loggerlib;                            
    void _log(msg) {
       print("Log method called in loggerlib msg:$msg");      
    } 

    Next, import the library

    import 'test.dart' as web; 
    void main() { 
       web._log("hello from webloggerlib"); 
    } 

    The above code will result in an error.

    Unhandled exception: 
    No top-level method 'web._log' declared.  
    NoSuchMethodError: method not found: 'web._log' 
    Receiver: top-level 
    Arguments: [...] 
    #0 NoSuchMethodError._throwNew (dart:core-patch/errors_patch.dart:184) 
    #1 main (file:///C:/Users/Administrator/WebstormProjects/untitled/Assertion.dart:6:3) 
    #2 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:261) 
    #3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:148)
    

    Creating Custom Libraries

    Dart also allows you to use your own code as a library. Creating a custom library involves the following steps −

    Step 1: Declaring a Library

    To explicitly declare a library, use the library statement. The syntax for declaring a library is as given below −

    library library_name  
    // library contents go here 
    

    Step 2: Associating a Library

    You can associate a library in two ways −

    • Within the same directory
    import 'library_name'
    
    • From a different directory
    import 'dir/library_name'
    

    Example: Custom Library

    First, let us define a custom library, calculator.dart.

    library calculator_lib;  
    import 'dart:math'; 
    
    //import statement after the libaray statement  
    int add(int firstNumber,int secondNumber){ 
       print("inside add method of Calculator Library ") ; 
       return firstNumber+secondNumber; 
    }  
    int modulus(int firstNumber,int secondNumber){ 
       print("inside modulus method of Calculator Library ") ; 
       return firstNumber%secondNumber; 
    }  
    int random(int no){ 
       return new Random().nextInt(no); 
    }

    Next, we will import the library −

    import 'calculator.dart';  
    void main() {
       var num1 = 10; 
       var num2 = 20; 
       var sum = add(num1,num2); 
       var mod = modulus(num1,num2); 
       var r = random(10);  
       
       print("$num1 + $num2 = $sum"); 
       print("$num1 % $num2= $mod"); 
       print("random no $r"); 
    } 

    The program should produce the following output −

    inside add method of Calculator Library  
    inside modulus method of Calculator Library  
    10 + 20 = 30 
    10 % 20= 10 
    random no 0 
    

    Library Prefix

    If you import two libraries with conflicting identifiers, then you can specify a prefix for one or both libraries. Use the ‘as’ keyword for specifying the prefix. The syntax for the same is given below −

    Syntax

    import 'library_uri' as prefix
    

    Example

    First, let us define a library: loggerlib.dart.

    library loggerlib;  
    void log(msg){ 
       print("Log method called in loggerlib msg:$msg");
    }   

    Next, we will define another library: webloggerlib.dart.

    library webloggerlib; 
    void log(msg){ 
       print("Log method called in webloggerlib msg:$msg"); 
    } 

    Finally, we will import the library with a prefix.

    import 'loggerlib.dart'; 
    import 'webloggerlib.dart' as web;  
    
    // prefix avoids function name clashes 
    void main(){ 
       log("hello from loggerlib"); 
       web.log("hello from webloggerlib"); 
    } 

    It will produce the following output −

    Log method called in loggerlib msg:hello from loggerlib 
    Log method called in webloggerlib msg:hello from webloggerlib 
    
  • Comprehensions

    List comprehensions are syntactic sugar for looping through enumerables in Elixir. In this chapter we will use comprehensions for iteration and generation.

    Basics

    When we looked at the Enum module in the enumerables chapter, we came across the map function.

    Enum.map(1..3, &(&1 * 2))

    In this example, we will pass a function as the second argument. Each item in the range will be passed into the function, and then a new list will be returned containing the new values.

    Mapping, filtering, and transforming are very common actions in Elixir and so there is a slightly different way of achieving the same result as the previous example −

    for n <- 1..3, do: n * 2

    When we run the above code, it produces the following result −

    [2, 4, 6]
    

    The second example is a comprehension, and as you can probably see, it is simply syntactic sugar for what you can also achieve if you use the Enum.map function. However, there are no real benefits to using a comprehension over a function from the Enum module in terms of performance.

    Comprehensions are not limited to lists but can be used with all enumerables.

    Filter

    You can think of filters as a sort of guard for comprehensions. When a filtered value returns false or nil it is excluded from the final list. Let us loop over a range and only worry about even numbers. We will use the is_even function from the Integer module to check if a value is even or not.

    import Integer
    IO.puts(for x <- 1..10, is_even(x), do: x)

    When the above code is run, it produces the following result −

    [2, 4, 6, 8, 10]
    

    We can also use multiple filters in the same comprehension. Add another filter that you want after the is_even filter separated by a comma.

    :into Option

    In the examples above, all the comprehensions returned lists as their result. However, the result of a comprehension can be inserted into different data structures by passing the :into option to the comprehension.

    For example, a bitstring generator can be used with the :into option in order to easily remove all spaces in a string −

    IO.puts(for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>)

    When the above code is run, it produces the following result −

    helloworld
    

    The above code removes all spaces from the string using c != ?\s filter and then using the :into option, it puts all the returned characters in a string.

  • Typedef

    typedef, or a function-type alias, helps to define pointers to executable code within memory. Simply put, a typedef can be used as a pointer that references a function.

    Given below are the steps to implement typedefs in a Dart program.

    Step 1: Defining a typedef

    typedef can be used to specify a function signature that we want specific functions to match. A function signature is defined by a function’s parameters (including their types). The return type is not a part of the function signature. Its syntax is as follows.

    typedef function_name(parameters)
    

    Step 2: Assigning a Function to a typedef Variable

    A variable of typedef can point to any function having the same signature as typedef. You can use the following signature to assign a function to a typedef variable.

    type_def  var_name = function_name
    

    Step 3: Invoking a Function

    The typedef variable can be used to invoke functions. Here is how you can invoke a function −

    var_name(parameters) 
    

    Example

    Let’s now take an example to understand more on typedef in Dart.

    At first, let us define a typedef. Here we are defining a function signature. The function will take two input parameters of the type integer. Return type is not a part of the function signature.

    typedef ManyOperation(int firstNo , int secondNo); //function signature
    

    Next, let us define the functions. Define some functions with the same function signature as that of the ManyOperation typedef.

    Add(int firstNo,int second){ 
       print("Add result is ${firstNo+second}"); 
    }  
    Subtract(int firstNo,int second){ 
       print("Subtract result is ${firstNo-second}"); 
    }  
    Divide(int firstNo,int second){ 
       print("Add result is ${firstNo/second}"); 
    }

    Finally, we will invoke the function via typedef. Declare a variable of the ManyOperations type. Assign the function name to the declared variable.

    ManyOperation oper ;  
    
    //can point to any method of same signature 
    oper = Add; 
    oper(10,20); 
    oper = Subtract; 
    oper(30,20); 
    oper = Divide; 
    oper(50,5); 

    The oper variable can point to any method which takes two integer parameters. The Add function’s reference is assigned to the variable. Typedefs can switch function references at runtime

    Let us now put all the parts together and see the complete program.

    typedef ManyOperation(int firstNo , int secondNo); 
    //function signature  
    
    Add(int firstNo,int second){ 
       print("Add result is ${firstNo+second}"); 
    } 
    Subtract(int firstNo,int second){ 
       print("Subtract result is ${firstNo-second}"); 
    }
    Divide(int firstNo,int second){ 
       print("Divide result is ${firstNo/second}"); 
    }  
    Calculator(int a, int b, ManyOperation oper){ 
       print("Inside calculator"); 
       oper(a,b); 
    }  
    void main(){ 
       ManyOperation oper = Add; 
       oper(10,20); 
       oper = Subtract; 
       oper(30,20); 
       oper = Divide; 
       oper(50,5); 
    } 

    The program should produce the following output −

    Add result is 30 
    Subtract result is 10 
    Divide result is 10.0 
    

    Note − The above code will result in an error if the typedef variable tries to point to a function with a different function signature.

    Example

    Typedefs can also be passed as a parameter to a function. Consider the following example −

    typedef ManyOperation(int firstNo , int secondNo);   //function signature 
    Add(int firstNo,int second){ 
       print("Add result is ${firstNo+second}"); 
    }  
    Subtract(int firstNo,int second){
       print("Subtract result is ${firstNo-second}"); 
    }  
    Divide(int firstNo,int second){ 
       print("Divide result is ${firstNo/second}"); 
    }  
    Calculator(int a,int b ,ManyOperation oper){ 
       print("Inside calculator"); 
       oper(a,b); 
    }  
    main(){ 
       Calculator(5,5,Add); 
       Calculator(5,5,Subtract); 
       Calculator(5,5,Divide); 
    } 

    It will produce the following output −

    Inside calculator 
    Add result is 10 
    Inside calculator 
    Subtract result is 0 
    Inside calculator 
    Divide result is 1.0
    
  • Sigils

    In this chapter, we are going to explore sigils, the mechanisms provided by the language for working with textual representations. Sigils start with the tilde (~) character which is followed by a letter (which identifies the sigil) and then a delimiter; optionally, modifiers can be added after the final delimiter.

    Regex

    Regexes in Elixir are sigils. We have seen their use in the String chapter. Let us again take an example to see how we can use regex in Elixir.

    # A regular expression that matches strings which contain "foo" or
    # "bar":
    regex = ~r/foo|bar/
    IO.puts("foo" =~ regex)
    IO.puts("baz" =~ regex)

    When the above program is run, it produces the following result −

    true
    false
    

    Sigils support 8 different delimiters −

    ~r/hello/
    ~r|hello|
    ~r"hello"
    ~r'hello'
    ~r(hello)
    ~r[hello]
    ~r{hello}
    ~r<hello>
    

    The reason behind supporting different delimiters is that different delimiters can be more suited for different sigils. For example, using parentheses for regular expressions may be a confusing choice as they can get mixed with the parentheses inside the regex. However, parentheses can be handy for other sigils, as we will see in the next section.

    Elixir supports Perl compatible regexes and also support modifiers. You can read up more about the use of regexes here.

    Strings, Char lists and Word lists

    Other than regexes, Elixir has 3 more inbuilt sigils. Let us have a look at the sigils.

    Strings

    The ~s sigil is used to generate strings, like double quotes are. The ~s sigil is useful, for example, when a string contains both double and single quotes −

    new_string = ~s(this is a string with "double" quotes, not 'single' ones)
    IO.puts(new_string)

    This sigil generates strings. When the above program is run, it produces the following result −

    "this is a string with \"double\" quotes, not 'single' ones"
    

    Char Lists

    The ~c sigil is used to generate char lists −

    new_char_list = ~c(this is a char list containing 'single quotes')
    IO.puts(new_char_list)

    When the above program is run, it produces the following result −

    this is a char list containing 'single quotes'
    

    Word Lists

    The ~w sigil is used to generate lists of words (words are just regular strings). Inside the ~w sigil, words are separated by whitespace.

    new_word_list = ~w(foo bar bat)
    IO.puts(new_word_list)

    When the above program is run, it produces the following result −

    foobarbat
    

    The ~w sigil also accepts the c, s and a modifiers (for char lists, strings and atoms, respectively), which specify the data type of the elements of the resulting list −

    new_atom_list = ~w(foo bar bat)a
    IO.puts(new_atom_list)

    When the above program is run, it produces the following result −

    [:foo, :bar, :bat]
    

    Interpolation and Escaping in Sigils

    Besides lowercase sigils, Elixir supports uppercase sigils to deal with escaping characters and interpolation. While both ~s and ~S will return strings, the former allows escape codes and interpolation while the latter does not. Let us consider an example to understand this −

    ~s(String with escape codes \x26 #{"inter" <> "polation"})
    # "String with escape codes & interpolation"
    ~S(String without escape codes \x26 without #{interpolation})
    # "String without escape codes \\x26 without \#{interpolation}"
    

    Custom Sigils

    We can easily create our own custom sigils. In this example, we will create a sigil to convert a string to uppercase.

    defmodule CustomSigil do
       def sigil_u(string, []), do: String.upcase(string)
    end
    
    import CustomSigil
    
    IO.puts(~u/tutorials point/)

    When we run the above code, it produces the following result −

    TUTORIALS POINT
    

    First we define a module called CustomSigil and within that module, we created a function called sigil_u. As there is no existing ~u sigil in the existing sigil space, we will use it. The _u indicates that we wish use u as the character after the tilde. The function definition must take two arguments, an input and a list.

  • Debugging

    Every now and then, developers commit mistakes while coding. A mistake in a program is referred to as a bug. The process of finding and fixing bugs is called debugging and is a normal part of the development process. This section covers tools and techniques that can help you with debugging tasks.

    The WebStorm editor enables breakpoints and step-by-step debugging. The program will break at the point where the breakpoint is attached. This functionality is like what you might expect from Java or C# application development. You can watch variables, browse the stack, step over and step into method and function calls, all from the WebStorm Editor.

    Adding a Breakpoint

    Consider the following code snippet. (TestString.dart)

    void main() { 
       int a = 10, b = 20, c = 5; 
       c = c * c * c; 
       
       print("$a + $b = ${a+b}"); 
       print("$a%$b = ${a%b}");  // Add a break point here 
       print("$a*$b = ${a*b}"); 
       print("$a/$b = ${a/b}"); 
       print(c); 
    }

    To add a breakpoint, click on the left margin to. In the figure given below, line number 7 has a break point.

    Add a Breakpoint

    Run the program in debug mode. In the project explorer right click on the dart program in our case TestString.dart.

    Debug TestString

    Once the program runs in debug mode, you will get the Debugger window as shown in the following screenshot. The variables tab shows the values of variables in the current context. You can add watchers for specific variables and listen to that values changes using watches window.

    Add Watchers

    Step Into (F7) arrow icon on debug menu helps to Executes code one statement at a time. If main methods call a subroutine, then this will go into the subroutine code also.

    Step over (F8): It is similar to Step Into. The difference in use occurs when the current statement contains a call to a subroutine. If the main method calls a subroutine, step over will not drill into the subroutine. it will skip the subroutine.

    Step Out (Shift+F8): Executes the remaining lines of a function in which the current execution point lies. The next statement displayed is the statement following the subroutine call.

    After running in debug mode, the program gives the following output −

    10 + 20 = 30 
    10 % 20 = 10 
    10 * 20 = 200 
    10 / 20 = 0.5 
    125
    
  • Exceptions

    An exception (or exceptional event) is a problem that arises during the execution of a program. When an Exception occurs the normal flow of the program is disrupted and the program/Application terminates abnormally.

    Built-in Dart exceptions include −

    Sr.NoExceptions & Description
    1DeferredLoadExceptionThrown when a deferred library fails to load.
    2FormatExceptionException thrown when a string or some other data does not have an expected format and cannot be parsed or processed.
    3IntegerDivisionByZeroExceptionThrown when a number is divided by zero.
    4IOExceptionBase class for all Inupt-Output related exceptions.
    5IsolateSpawnExceptionThrown when an isolate cannot be created.
    6TimeoutThrown when a scheduled timeout happens while waiting for an async result.

    Every exception in Dart is a subtype of the pre-defined class Exception. Exceptions must be handled to prevent the application from terminating abruptly.

    The try / on / catch Blocks

    The try block embeds code that might possibly result in an exception. The on block is used when the exception type needs to be specified. The catch block is used when the handler needs the exception object.

    The try block must be followed by either exactly one on / catch block or one finally block (or one of both). When an exception occurs in the try block, the control is transferred to the catch.

    The syntax for handling an exception is as given below −

    try { 
       // code that might throw an exception 
    }  
    on Exception1 { 
       // code for handling exception 
    }  
    catch Exception2 { 
       // code for handling exception 
    } 
    

    Following are some points to remember −

    • A code snippet can have more than one on / catch blocks to handle multiple exceptions.
    • The on block and the catch block are mutually inclusive, i.e. a try block can be associated with both- the on block and the catch block.

    The following code illustrates exception handling in Dart −

    Example: Using the ON Block

    The following program divides two numbers represented by the variables x and y respectively. The code throws an exception since it attempts division by zero. The on block contains the code to handle this exception.

    main() { 
       int x = 12; 
       int y = 0; 
       int res;  
       
       try {
    
      res = x ~/ y; 
    } on IntegerDivisionByZeroException {
      print('Cannot divide by zero'); 
    } }

    It should produce the following output −

    Cannot divide by zero
    

    Example: Using the catch Block

    In the following example, we have used the same code as above. The only difference is that the catch block (instead of the ON block) here contains the code to handle the exception. The parameter of catch contains the exception object thrown at runtime.

    main() { 
       int x = 12; 
       int y = 0; 
       int res;  
       
       try {  
    
      res = x ~/ y; 
    } catch(e) {
      print(e); 
    } }

    It should produce the following output −

    IntegerDivisionByZeroException
    

    Example: on…catch

    The following example shows how to use the on…catch block.

    main() { 
       int x = 12; 
       int y = 0; 
       int res;  
       
       try { 
    
      res = x ~/ y; 
    } on IntegerDivisionByZeroException catch(e) {
      print(e); 
    } }

    It should produce the following output −

    IntegerDivisionByZeroException
    

    The Finally Block

    The finally block includes code that should be executed irrespective of an exception’s occurrence. The optional finally block executes unconditionally after try/on/catch.

    The syntax for using the finally block is as follows −

    try { 
       // code that might throw an exception 
    }  
    on Exception1 { 
       // exception handling code 
    }  
    catch Exception2 { 
       //  exception handling 
    }  
    finally { 
       // code that should always execute; irrespective of the exception 
    }
    

    The following example illustrates the use of finally block.

    main() { 
       int x = 12; 
       int y = 0; 
       int res;  
       
       try { 
    
      res = x ~/ y; 
    } on IntegerDivisionByZeroException {
      print('Cannot divide by zero'); 
    } finally {
      print('Finally block executed'); 
    } }

    It should produce the following output −

    Cannot divide by zero 
    Finally block executed
    

    Throwing an Exception

    The throw keyword is used to explicitly raise an exception. A raised exception should be handled to prevent the program from exiting abruptly.

    The syntax for raising an exception explicitly is −

    throw new Exception_name()
    

    Example

    The following example shows how to use the throw keyword to throw an exception −

    main() { 
       try { 
    
      test_age(-2); 
    } catch(e) {
      print('Age cannot be negative'); 
    } } void test_age(int age) { if(age<0) {
      throw new FormatException(); 
    } }

    It should produce the following output −

    Age cannot be negative
    

    Custom Exceptions

    As specified above, every exception type in Dart is a subtype of the built-in class Exception. Dart enables creating custom exceptions by extending the existing ones. The syntax for defining a custom exception is as given below −

    Syntax: Defining the Exception

    class Custom_exception_Name implements Exception { 
       // can contain constructors, variables and methods 
    } 
    

    Custom Exceptions should be raised explicitly and the same should be handled in the code.

    Example

    The following example shows how to define and handle a custom exception.

    class AmtException implements Exception { 
       String errMsg() => 'Amount should be greater than zero'; 
    }  
    void main() { 
       try { 
    
      withdraw_amt(-1); 
    } catch(e) {
      print(e.errMsg()); 
    } finally {
      print('Ending requested operation.....'); 
    } } void withdraw_amt(int amt) { if (amt <= 0) {
      throw new AmtException(); 
    } }

    In the above code, we are defining a custom exception, AmtException. The code raises the exception if the amount passed is not within the excepted range. The main function encloses the function invocation in the try…catch block.

    The code should produce the following output −

    Amount should be greater than zero 
    Ending requested operation....