module Functions { 
 
  // LAMBDAS 
 
  // use fn to define lambda functions 
  fn test1() => { 
    // define a variable used by lambda function 
    let y = 0; 
 
    // define lambda function 
    let f = fn(x : int) : int => { 
      y += 1;                       // lambda functions can access variables in scope 
      return x + y; 
    } 
 
    // invoke lambda function 
    let x = f(1); 
  } 
 
  // FUNCTION TYPES 
 
  // use typedef to define a function type 
 
  // define a callback function type with a single integer parameter. 
  // omitting tje return type means the function's return type is void 
  typedef IntCallback = fn(value : int); 
 
  fn test2() => { 
    // define lambda function 
    let f = fn(x : int) { 
      print(x); 
    } 
 
    // cast function to callback type 
    let f2 = f::IntCallback;    // cast works because function signature matches 
  } 
 
  // USING FUNCTION TYPES WHEN DEFINING FUNCTIONS 
 
  // a function type can be assigned to a function implementation using fn::type 
  // when the type is assigned, the types of the parameters and return value 
  // do not need to be specified. 
 
  fn test3() => { 
    // define lambda function using callback function type 
    let f = fn::IntCallback(x) { // note parameter does not specify type 
      print(x); 
    } 
  } 
 
  // NAMED FUNCTIONS 
 
  // use fn to defined named functions, using same syntax as with lambdas 
  // the following function returns an incremented integer   
  fn inc(x: int) : int => x + 1; 
 
  fn test4() => { 
    // call function  
    let x = inc(1); 
  } 
 
  // VARIABLE NUMBER OF ARGUMENTS 
 
  // use ... to capture arguments as an array 
  // the following function returns the sum of it's arguments 
  fn sum(...numbers : int[]) : int => { 
    let value : int = 0; 
    for (number in numbers) { 
      value += number; 
    } 
    return value; 
  } 
 
  fn test5() => { 
    // call with regular args 
    let x1 = sum(1, 2, 3); 
 
    // use ... to flatten iterables into arguments 
    let x2 = sum(...[1, 2, 3]); 
 
    // can use ... along with regular arguments 
    let x3 = sum(1, ...[2, 3]); 
  } 
 
  // DEFAULT PARAMETER VALUES 
 
  // use = to specify default values for parameters 
  fn inc_with_default(x : int, inc_by : int = 1) => { 
    return x + inc_by; 
  } 
 
  fn test6() { 
    let x = 1; 
    x = inc_with_default(x);            // increments by 1 
    x = inc_with_default(x, 2);         // increments by 2 
  } 
 
  // OPTIONAL PARAMETER VALUES 
 
  // use an optional type and a none default value for optional parameters 
  fn inc_with_optional(x : int, inc_by : int? = none) { 
    if (inc_by) { 
      return x + inc_by!;   // increment by value if specified 
    } 
    return x; // don't increment if parameter not specified 
  } 
 
  fn test7() { 
    let x = 1; 
    x = inc_with_optional(x);     // increments by 1 
    x = inc_with_optional(x, 2);  // increments by 2 
  } 
 
  // MULTIPLE RETURN VALUES 
 
  // use a tuple to return multiple return values.  see tuples for more info. 
   
  fn return_2_ints(x : int, y : int) : (int, int) => { 
    return (x + 1, y + 1);  // return tuple 
  } 
 
  fn test8() { 
    let x = return_2_ints(1, 2);  // x is (2, 3) 
  } 
 
  // GENERIC FUNCTIONS 
 
  // use {T} after fn to define a generic function 
  fn generic_inc{T}(x : T) : T => x + 1::T; 
 
  fn test9() => { 
    // call generic function using explicit type parameter 
    let x1 = generic_inc{int}(1); 
 
    // call generic function using type parameter inference 
    // the type parameter (int) is inferred based on the type of the parameters (1) 
    let x2 = generic_inc(1); 
  } 
}