ExpandCollapseNext Index

+ 1.1 Polymorphism

Almost everything (except variables) in Felix can be polymorphic, that is, parametrised by a list of types. Here is a simple example:

  fun diag[T,U] (x:T, y:U)=> y,x;
  a := 1,"Hi";
  println$ a, diag a, diag (diag a);

C bindings can be polymorphic too:

  type vector[T] = "::std::vector<?1>";
  proc push_back[T]: vector[T] * T = "$1.push_back($2)";

In the C code {?1}, {?2} represent the first and second type variables.

+ 1.1.1 Overloading

Polymorphic functions and non-polymorphic function can be overloaded. Overload resolution proceeds by a similar algorithm to C++.

Felix first finds all the candidates. It then proceeds to compare each signature with each other signature. If one is a proper specialisation of another, the more general case is thrown out. An ambiguity exists if this process leaves more than one candidate.

  fun f[T,V] (x:T, y:V)=>y,x;               //1
  fun f[T] (x:T, y:T)=>y,x;                 //2
  fun f[A,B,C] (x: A * B, y : B *C) => y,x; //3

Here, 2 and 3 are more specialised than 1, however neither is more specialised than the other.

If the call is to a higher order function and the call applies to several arguments in turn, all the available arguments are used in an attempt to remove an ambiguity.

+ 1.2 Advanced Type Calculus

Felix type system has several advanced features.

+ 1.2.1 Type functions

In Felix you can specify a function which accepts and returns types:

  typedef fun diag1 (x:TYPE) : TYPE => x * x;
  var b: diag1 int = 1,2; // OK

Here the name TYPE is a meta-type or kind representing all types.

A type function is similar to a type index, however functions cannot be overloaded based on calculated types, unless the applications are resolved before overloading is performed.

  typedef diag2[T] = T * T;

is similar to the type function diag1, but diag2 is a polymorphic type, which is not quite the same. Polymorphic type allow overloading, and more generally unification, to deduce the index type. This cannot be done for type functions. But type functions are more general because they can be named "unapplied" and later applied. And, they can be anonymous too: type lambdas.

The syntax for type functions is the same as for ordinary functions. Type functions can also be overloaded against each other.

+ 1.2.2 Type matches

Similar to ordinary matches, Felix has type matches:

  typedef T = int;
  val x : 
    typematch T with 
    | int => int * int  
    | _ => long 
    endmatch 
    = 1,2
  ;

A contrived example, but see the next section for a real use!

+ 1.2.3 Type Calculus: examples

Consider the C integral promotion rules. How would we represent this in Felix?

With a type function and match of course! This is from library file int.flx:

  typedef fun integral_promotion: TYPE -> TYPE =
    | tiny => int
    | utiny => int
    | short => int
    | ushort => int
    | int => int
    | uint => uint
    | long => long
    | ulong => ulong
    | vlong => vlong
    | uvlong => uvlong
  ;

Note here the same short hand as for function is used, the functional form is equivalent to a function containing a match on its only argument.

The type function integral_promotion defines how each C type is promoted according to ISO C rules.

Now why would we want that? Well consider, we would like to write this code:

  fun add[T in ints, U in ints]: T * U -> arithmax (T,U) = "$1+$2";

which can add up any Felix integers the same way as C does, arithmetic promotions and all .. but what is the return type? It is the largest of the two argument types. But how can we calculate that?

Here's the answer: arithmax.flx

This file is generated by the configure script for Felix because the rule is actually platform dependent!

You can see here, we have used the previously discussed integral_promotion function to reduce the number of cases in the typematch by removing the smaller types {tiny, utiny, short, ushort} .. although the main reason is that this is how ISO C specifies the calculation be done.