ExpandCollapsePrev Next Index

+ 2.1 Type Constraints

Felix provides a simple system of type constraints. The need for type constraints arose when binding C:

  class Constraints {
  type Int = "int";
  type Long = "long";
  ctor Int : int = "$1";
  ctor Long: long= "$1";
  ctor int : Int = "$1";
  ctor long: Long= "$1";
  fun add: Int * Int -> Int = "$1+$2";
  fun add: Long * Long -> Long = "$1+$2";
  instance Str[Int] { fun str(x:Int) => str (int x); }
  instance Str[Long] { fun str(x:Long) => str (long x); }

C has a lot of integer types and writing out all the operators for all the types is boring, error prone, and slows down compilation. If you also wanted to allow mixed operators you'd need to add:

  fun add: Int * Long -> Long = "$1+$2";
  fun add: Long * Int -> Long = "$1+$2";

and you can see we would rapidly get into an untenable combinatorial explosion.

To solve the problem we use type constraints.

First we introduce a construction to make a set of types:

  typedef integers = typesetof(Int, Long);

Now we write polymorphic functions with type constraints:

  fun add[I in integers]: I * I -> I = "$1+$2";

Here the type variable I may only be int or long. If you wanted to support mixed addition:

  fun add[I in integers, J in integers]: I * J -> I = "(?1)($1+$2)";

Note carefully the difference between these two functions: in the first function I is set to either int or long and so only two ints or two longs can be added.

In the second case, I and J are set independently to either int or long which allows any combination to be added.

There is a shorthand for the second formulation:

  fun add: !integers * !integers -> int = "(int)($1+$2)";

but note the downside: lacking names for the type variables we must return a fixed type. This notation, however, if exceptionally useful when binding external C libraries:

  header myf = "int f(int, int);";
  fun f: !integers * !integers -> int = "f($1, $2)" requires myf;

The reason this is so useful is that if we just lifted the signature of f from C, we'd be ignoring the fact that the C programmer has automatic conversions and can call {f(42L, 23u)}, which would fail in Felix because it has no automatic conversions. The user would have to cast every argument to exactly the right type which is not practical. By using constrained polymorphism, we can make all the calls that work in C work in Felix, and by using the shorthand notation the overhead specifying the function is minimal.

When Felix performs overload resolution, initially candidates are found without considering type constraints; that is, as if the type variables were able to bind to any type.

Then, the type constraints are checked and candidates failing to meet the constraints are rejected. For example:

  fun f[T in integers]: T -> T = "$1+1";
  fun f[T in floats]: T -> T = "$1+2";
  println$ (f 1.2), f (Int 1);

Type sets also support the set union operator:

  typedef mynumbers = integers \(\cup\) floats;
  } // end class Constraints

Note that the typesets ints and floats, along with some other basic type sets, are defined in the standard library:

std/scalar/ctypedefs.flx
  #line 53 "/home/travis/build/felix-lang/felix/src/packages/core_scalar_types.fdoc"
  pod type char = "char";
  
  #line 70 "/home/travis/build/felix-lang/felix/src/packages/core_scalar_types.fdoc"
  pod type tiny = "signed char";
  pod type short = "short";
  pod type int = "int";
  pod type long = "long";
  pod type vlong = "long long";
  pod type utiny = "unsigned char";
  pod type ushort = "unsigned short";
  pod type uint = "unsigned int";
  pod type ulong = "unsigned long";
  pod type uvlong = "unsigned long long";
  
  pod type intmax = "intmax_t" requires C99_headers::stdint_h;
  pod type uintmax = "uintmax_t" requires C99_headers::stdint_h;
  pod type size = "size_t" requires C89_headers::stddef_h;
  pod type ssize = "ssize_t" requires C89_headers::stddef_h;
  
  
  #line 95 "/home/travis/build/felix-lang/felix/src/packages/core_scalar_types.fdoc"
  pod type int8 = "int8_t" requires C99_headers::stdint_h;
  pod type int16 = "int16_t" requires C99_headers::stdint_h;
  pod type int32 = "int32_t" requires C99_headers::stdint_h;
  pod type int64 = "int64_t" requires C99_headers::stdint_h;
  pod type uint8 = "uint8_t" requires C99_headers::stdint_h;
  pod type uint16 = "uint16_t" requires C99_headers::stdint_h;
  pod type uint32 = "uint32_t" requires C99_headers::stdint_h;
  pod type uint64 = "uint64_t" requires C99_headers::stdint_h;
  
  #line 117 "/home/travis/build/felix-lang/felix/src/packages/core_scalar_types.fdoc"
  pod type byte = "unsigned char";
  type caddress = "void *";
  _gc_pointer type address = "void *";
  
  pod type ptrdiff = "ptrdiff_t" requires C89_headers::stddef_h;
  
  pod type intptr = "intptr_t" requires C99_headers::stdint_h;
  pod type uintptr = "uintptr_t" requires C99_headers::stdint_h;
  #line 397 "/home/travis/build/felix-lang/felix/src/packages/core_scalar_types.fdoc"
  pod type float = "float";
  pod type double = "double";
  pod type ldouble = "long double";
  pod type fcomplex = "::std::complex<float>" requires Cxx_headers::complex;
  pod type dcomplex = "::std::complex<double>" requires Cxx_headers::complex;
  pod type lcomplex = "::std::complex<long double>" requires Cxx_headers::complex;
  
  
  #line 477 "/home/travis/build/felix-lang/felix/src/packages/core_scalar_types.fdoc"
  Types associated with raw address calculations.
  typedef addressing = typesetof (
    byte,
    address,
    caddress
  );
  
  Character types.
  typedef chars = typesetof (char);
  
  #line 489 "/home/travis/build/felix-lang/felix/src/packages/core_scalar_types.fdoc"
  "natural" sized signed integer types.
  These correspond to C/C++ core types.
  typedef fast_sints = typesetof (tiny, short, int, long, vlong);
  
  Exact sized signed integer types.
  In C these are typedefs.
  In Felix they're distinct types.
  typedef exact_sints = typesetof(int8,int16,int32,int64);
  
  "natural" sized unsigned integer types.
  These correspond to C/C++ core types.
  typedef fast_uints = typesetof (utiny, ushort, uint, ulong,uvlong);
  
  Exact sized unsigned integer types.
  In C these are typedefs.
  In Felix they're distinct types.
  typedef exact_uints = typesetof (uint8,uint16,uint32,uint64);
  
  Weirdo signed integers types corresponding to
  typedefs in C.
  typedef weird_sints = typesetof (ptrdiff, ssize, intmax, intptr);
  
  Weirdo unsigned integers types corresponding to
  typedefs in C.
  typedef weird_uints = typesetof (size, uintmax, uintptr);
  
  All the signed integers.
  typedef sints = fast_sints \(\cup\) exact_sints \(\cup\) weird_sints;
  
  All the usigned integers.
  typedef uints = fast_uints \(\cup\) exact_uints \(\cup\) weird_uints;
  
  All the fast integers.
  typedef fast_ints = fast_sints \(\cup\) fast_uints;
  
  All the exact integers.
  typedef exact_ints = exact_sints \(\cup\) exact_uints;
  
  All the integers.
  typedef ints = sints \(\cup\) uints;
  
  #line 532 "/home/travis/build/felix-lang/felix/src/packages/core_scalar_types.fdoc"
  All the core floating point types.
  typedef floats = typesetof (float, double, ldouble);
  
  All the core approximations to real types.
  typedef reals = ints \(\cup\) floats;
  
  All the core approximations to complex types.
  typedef complexes = typesetof (fcomplex,dcomplex,lcomplex);
  
  All the core approximations to numbers.
  typedef numbers = reals \(\cup\) complexes;
  #line 547 "/home/travis/build/felix-lang/felix/src/packages/core_scalar_types.fdoc"
  All the basic scalar types.
  typedef basic_types = bool \(\cup\) numbers \(\cup\) chars \(\cup\) addressing;
  
  // we define this now, we will open it later...
  instance [t in basic_types] Eq[t] {
    fun == : t * t -> bool = "$1==$2";
  }
  
  // we open this now even though we haven't developed
  // the instances yet....
  open[T in basic_types] Show[T];