Functions

IPL provides a mechanism for declaring functions which help to organise code. In this section we describe how to declare and exploit functions in IPL.

In order to define a function in IPL we write for example

function max(a:int, b:int):int {
  return if (a>b) then a else b
}

Here we have explicitly given types to the arguments of the function max we have defined, as well as an explicit return type. Every function must declare a return statement for every branch. IPL will produce a validation error if this is not present. For example, defining the following function:

function max(a:int, b:int):int {
  if a>b then 
    {
      let x = 0
    } 
    else return b
}

will result in the validation error:

This function does not specify a return value for all branches.

The types that can be defined on functions can also be general map, set or list types. For example the following is valid:

function map_mult(a:int,b:int,c:<int,int>map):<int,int> map  {
  let res = get(c,a)
  let new_res = b * a 
  insert(c,a=new_res)
  return c
}

It is possible to define simple for loops in ipl, either over a range of integer type or over the values of an enumeration type. To enumerate over an enumeration type it is possible to write for example:

enum test {
  a "a"
  b "b"
  c "c"
}

receive (act:bounds){
  for x in test {
    if (x in {|a,b|}) 
      then state.sum = state.sum + 1 
      else state.sum = state.sum + 2
  }
}

To write a for loop for a range, one can declare a loop for example in a function:

function sum(count:int, limit:int):int {
  let z = 0
  for x in (0,count,2){
    z = z+x
    if z>limit then break
  }
  return max(z,limit)
}

where in for(0,count,2) the 0 is the start counter, the expression count is the end counter and the 2 is the step increment.

A function can be called from any expression in the language. Specifically they can be called on validation statements, receive or reject code or other functions. As an example let us consider writing functions we can use in an action validator:

internal state {
  upperBound: int
}

action bounds {
  bound1:int
  bound2:int
  validate{max(bound1,bound2)< state.upperBound}
}

The validator calls the function we defined called max.

We can also call functions from receive statements or other functions:

internal state {
  upperBound: int
  sum: int
}

action bounds {
  bound1:int
  bound2:int
  validate{max(bound1,bound2)< state.upperBound}
}

function sum(count:int, limit:int):int {
  let z = 0
  for x in (0,count){
    z = z+x
    if z>limit then break
  }
  return max(z,limit)
}

receive (act:bounds){
  let x = max(act.bound1,act.bound2)
  state.sum = sum(x+state.sum,state.upperBound)
}

Note that it is not possible to define recursive functions since termination cannot be guaranteed in the generated code. For example defining the functions

function g(x:int):int {
  return f(x)
}

function f(x:int):int {
  return g(x)
}

will result in the error.

This function has a mutually recursive dependency which is not permissible in ipl. g->f->g

It is possible to reference an internal state declaration in a function, but it is not possible to modify its fields. For example writing the following function for our example above:

function getStateBound():int {
  return state.upperBound
}

is admissible, but writing the function

function setStateBound(a:int):int {
  state.upperBound = a
  return state.upperBound
}

will result in the error

Reassignment of internal state type fields is not allowed in functions.