Functions: Structure, Evaluation, and Calling Scheme

The only object in the FISH language that can be executed is the function. The name of a function follows the c define statement, and its scope terminates with the c end statement. The c end statement also serves to return control to the caller when the function is executed. (Note that the c exit statement returns control prior to an c end statement.) Consider this example showing function construction and use:


model new
fish define xxx
  aa  = 2 * 3
  xxx = aa + bb
end

The value of xxx is changed when the function is executed. The variable aa is computed locally, but the existing value of bb is used in the computation of xxx. If values are not explicitly given to variables, they default to zero (integer). It is not necessary for a function to assign a value to the variable corresponding to its name. The function xxx may be invoked in one of the following ways:

  1. as the single word xxx inside a FISH function;

  2. as the variable xxx in a FISH formula, e.g.,:

    new_var = (math.sqrt(xxx) / 5.6)^4;
    
  3. as a single word @xxx in a PFC input line;

  4. as a single word [xxx] enclosed in brackets as outlined here;

  5. as a symbolic replacement for a number in a PFC input line; or

  6. as a parameter to the fish set, program list, or c history commands.

FISH functions may also take an arbitrary number of arguments, either using the argument statement or via defining the arguments in the function definition. For instance:


fish define fred
    fred = 3.0
end
fish define george
    argument one
    argument two
    george = one * two
end
list @george(@fred,2.0) @fred
fish define fun_1
    fun_1 = 1.0
    ii = io.out('fun_1')
end
fish define fun_2(arg1)
    fun_2 = 2.0
    ii = io.out('fun_2')
    ii = io.out(string(arg1))
end
fish define fun_3(arg1,arg2)
    fun_3 = 3.0
    ii = io.out('fun_3')
    ii = io.out(string(arg1))
    ii = io.out(string(arg2))
end
fish define execute
    fun_1 fun_2(1.0) fun_3(1.0,2.0)
end
@execute

A function may be referred to in another function before it is defined; the FISH compiler simply creates a symbol at the time of first mention and then links all references to the function when it is defined by a c define statement. A function cannot be deleted, but it can be redefined.

Function calls may be nested to any level (i.e., functions may refer to other functions, which may refer to others, ad infinitum). However, recursive function calls are not allowed (i.e., execution of a function must not invoke that same function). A recursive function call shows a recursive function call, which is not allowed, because the name of the defining function is used in such a way that the function will try to call itself. The example will produce an error on execution:

def force_sum
   force_sum = 0.0
   loop foreach local cp contact.list('ball-ball')
      force_sum = force_sum + contact.force.normal(cp)
   end_loop
end

The same function should be coded as shown:


fish define force_sum
   sum = 0.0
   loop foreach local cp contact.list('ball-ball')
      sum = sum + contact.force.global(cp)
   end_loop
   force_sum = sum
end

The difference between variables and functions is that a function is always executed whenever its name is mentioned, while a variable simply conveys its current value. However, the execution of a function may cause other variables (as opposed to functions) to be evaluated. This effect is useful, for example, when several histories of FISH variables are required. Only one function is necessary in order to evaluate several quantities, as below:


model new
fish define h_var_1
  ;
  ; bp22 = pointer to ball with ID = 22, set during creation...
  ; bp45 = pointer to ball with ID = 45, set during creation...
  ;
  xx = ball.pos.x(bp45)
  h_var_1 = ball.pos.x(bp45)
  h_var_2 = ball.pos.x(bp22)
  h_var_3 = math.abs(h_var_2 - xx)  ; use of xx here avoids recursion
  h_var_4 = ball.vel.x(bp45)
  h_var_5 = ball.vel.x(bp22)
  h_var_6 = math.abs(h_var_5 - h_var_4)
end

The PFC commands to request histories might be:

history @h_var_1
history @h_var_2
history @h_var_3
history @h_var_4
history @h_var_5
history @h_var_6

The function h_var_1 would be executed by PFC’s history logic at a specified interval. But as a side effect, the values of h_var_2 through h_var_6 would also be updated.