Functions
Function definitions and usage in IPL
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.
Defining Functions
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
}For Loops
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.
Calling Functions
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
Referencing State
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.