ExpandCollapseNext Index

+ 1.1 Functions and Procedures

+ 1.1.1 Felix functions

When you see code like this:

  println$ "Square of " + str 1 + " is " + str (1 * 1);
  println$ "Square of " + str 2 + " is " + str (2 * 2);
  println$ "Sguare of " + str 3 + " is " + str (3 * 1);

Square of 1 is 1
Square of 2 is 4
Sguare of 3 is 3

You know you need a function (notice the typos on the last line?) Basic Felix functions are easy:

  fun square(x:int)=>x * x;  // define a function
  println$ "Square of " + str 1 + " is " + str (square 1);
  println$ "Square of " + str 2 + " is " + str 2.square;
  println$ "Sguare of " + str 3 + " is " + (str$ square 3);

Square of 1 is 1
Square of 2 is 4
Sguare of 3 is 9

We fixed the bug in the calculation of the square of 3, and we're also showing the three ways to apply a function: high precedence application using operator whitespace which needs surrounding parentheses for correct grouping, reverse application using operator dot, which is even higher precedence than whitespace, and so does not, and finally we're applying the str function to a whitespace application using operator dollar, which has lower precedence than whitespace.

There's a longer way to write a function:

  fun long-winded-square(x:int):int = {
    val s = x * x;
    return x;
  }

This way allows complicated expressions to be broken up into arbitrary code.

Functions are not allowed to have side effects.

+ 1.1.2 Felix Procedures

Still, there must be a way to repeat the print: there is, by using a procedure.

  proc ps(x:int) {
    println$ "Square of " + str x + " is " + str x.square;
  }
  ps 1; ps 2; ps 3;

Square of 1 is 1
Square of 2 is 4
Square of 3 is 9

A procedure is like a function, except it returns no value, and it may and indeed should have side effects.

+ 1.1.3 Multiple arguments

A function (or procedure) always has exactly one argument. However you can simulate multiple arguments with a tuple:

  fun addup (x:int, y:int) => x + y;
  println$ addup (1,2); // 3
  val pair = 1,2;
  println$ addup pair; // 3

3
3

The last line shows clearly that addup takes a single argument which is a pair (tuple with two components).

+ 1.1.4 Function types

A function value has a type:

    int -> int       // type of square
    int -> void      // type of ps
    int * int -> int // type of addup

+ 1.1.5 Generators

There is a special kind of procedure that looks like a function and may have side-effects, it is called a generator:

  var x =0;
  gen incx():int = { val tmp = x; ++x; return tmp; }
  println$ incx(), incx();

(0, 1)

When an expression contains one or more generator application, the applications are lifted out and evaluated in pre-order: this is basically left to right order of writing for terms at the same level, except that if a generator has an argument that is, or contains a generator application, that will have to be evaluated first. The serialisation of generator application is deterministic and based on the structure of the expression being evaluated after syntactic sugar is removed. For example

  gen g1 (y:int) = { ++x; return y + x; } 
  gen g2 (y:int) = { ++x; return y + 2 * x; } 
  x = 0;
  var r1 = (g1 1).g2;
  println$ r1;

6

r will be evaluated as:

  x = 0;
  var a1 = g1 1;  // 2 (x=1) 
  var r2 = g2 a1; // 6 (x=2)
  println$ r2;

6

noting reverse application is just sugar for a swapped forward application.

Generators have the same type as a function. This is a design compromise in Felix.

+ 1.1.6 C functions

The function and procedure:

  cfun f(x:int) => x;
  cproc p(x:int) { println x; }

have types

     int --> int
     int --> void

respectively, which are the types of classic C functions:

  int (*)(int)
  void (*)(int)

You can use this kind of function to force generation of a classic C style function for performance, so you that know the type exactly, and so you can pass it as an argument to a C library. In particular such a function or procedure should not reference Felix globals (because this requires passing the thread frame pointer), nor do anything directly or indirectly requiring garbage collection (which also requires thread frame pointer since that in turn contains a pointer to the garbage collector).

C functions can be used wherever a Felix function is required, a wrapper is generated automatically.

C functions of this kind are still defined with Felix code: they're Felix functions with an enforced C interface. In fact many ordinary Felix functions will be reduced to C functions by the optimiser, but this form enforces the use of C interface.

It seems unfortunate that whilst a cfun or cproc has a C interface it is defined in Felix, whereas a cstruct is a Felix interface to a struct defined in C, and cenum and cflag provide Felix models of enumerations or sets of integer constants defined in C. The role reversal here seems confusing, especially as Felix functions and types can be defined in C using fun, proc and type keywords and C code given as a string, and ctypes provides a shorthand form for multiple instances of type eliding the strings. In turn cenum may be considered a shorthand for a type binding and a list of const definitions. Also, a shorthand for lifting C functions from libraries using a fun or proc without a defining string can be used. The situation is further complicated by the callback feature.

+ 1.2 Higher Order Functions

In Felix, functions can be nested:

  fun f(x:int) = {
    fun g(y:long)=> x.long + y;
     return g;
  }

The function f here has type int -> (long -> long). This type is the same as int -> long -> long since the arrow operator is right associative. There's a simpler way to write the function f:

  fun f(x:int) (y:long) => x.long + y;

Here one says f has arity 2. Note it still has only one argument, namely x of type int. A function with arity 2 or greater is called a higher order function or Hof for short.

Note that in:

  proc h(x:int) (y:long) { println$ x.long + y; }

h is a function, not a procedure: it has type

  int -> (long -> void)

so that rather, h is a function returning a procedure.

It is possible to partially apply a higher order function.

  fun f2(x:int) (y:long) => x.long + y;
  val g = f2 1;
  println$ g 2l;

3

Here g is just the value returned by f when applied to 1, which is a function accepting a long and returning a long.

+ 1.2.1 A style issue

Many functions of "two" arguments can be written either accepting a tuple of two components or as a higher order function of arity 2. Without optimisation, higher order functions are extremely inefficient: tuples are faster, but they cannot be partially applied. However, the higher order form does not permit the "arguments" to be "compressed" into a single value which the tuple form does.

However, Felix is pretty good at optimisation and both version of f given above will likely reduce to a simple addition after inlining.

Therefore the question really is: are you most likely to aggregate the arguments, or the function with the first argument? Use a tuple in the first case and a higher order function in the second.

Of course, you should chose the form of a function which itself has the type required for it to be an argument of a higher order function such as an iteration, map or fold of some data structure.

The library module categ defines a way to convert between these styles for 2 or 3 "arguments". The function curry takes a function accepting a tuple of 2 or 3 components and makes it into a higher order function with arity 2 or 3. The functions uncurry2 and uncurry3 reverse this transformation and convert a higher order function of arity 2 or 3, into a function accepting a tuple of 2 or 3 components, respectively.

+ 1.2.2 Anonymous function values

You can write a function without a name in an expression:

  val three = (fun (x:int)=> x + 1) 2;

There is a special short hand for procedures of unit argument:

  val hello1 = { println "Hello"; };

This is an expression equivalent to

  val hello2 = proc () { println "Hello"; };

There's a similar shorthand for function or unit argument:

  val y = 1;
  val add1 = { val x = y + 1; return x; };

distinguished from the procedure case by ending with a return statement.