ExpandCollapsePrev Next Index

+ 16.1 Classes

In many languages we can organise groups of definitions into some kind of namespace. In C++ we can use namespaces or classes.

+ 16.1.1 Basic class and reference

In Felix, our namespace construction is called class. Here is an example:

  class A
  {
    fun f: int->int;
    fun g: int->int;
  }

To use a symbol from a class we can use qualification with a similar syntax to C++:

  var x1 = A::f (1);
  var x2 = A::g (1);

+ 16.1.2 Nested Classes

Classes can be nested:

  class B
  {
    fun h: int -> int;
    class C
    {
      fun k: int -> int;
    }
  }
  fun glo: int -> int;

and of course the obvious syntax is used for qualified names:

  var x3 = B::C::k (1);
  var x3a = root::glo (1);

Note the outermost scope is designated by the special name root. The usual C++ syntax of an initial {::} is not permitted.

+ 16.1.3 Derived classes

Given a class you can make another which inherits the symbols from the first. The class from which we inherit is termed a base for the one doing the inheriting which is said to be derived:

  class Base
  {
    fun f: int -> int;
  }
  class Derived
  {
    inherit Base;
    fun g: int-> int;
  }
  var x4 = Base::f (1);
  var x5 = Derived::g (1);
  var x6 = Derived:: f(1);

It is important to understand that the injection of the symbol f defined in Base into Derived allows lookup to find f in Derived with qualified lookup syntax {Derived::f} but it does not make the function entity a member of Derived.

Instead you should think of inheritance creating a pointer to the original symbol. I will show why you must take this view shortly.

+ 16.1.4 Opening classes

You can avoid qualified access by opening a class. This can be done with the open directive:

  open Base;
  open Derived;
  var x7 = f(1);
  var x8 = g(1);

Now, you may wonder whether that f is the one from class Base of the one from class Derived. If you are wondering you didn't read the previous comment!

There are two access paths to the function f, namely {Base::f} and {Derived::f}, but there's only one function. Opening the two classes makes both paths implicit so there are two ways to find f. But there is still only one f so there is no ambiguity applying it.

If you open a class inside another, the additional search paths are only available inside the class and any nested scopes. Such symbols are private to the class. For example:

  class Another 
  {
    open Base;
    fun k(x:int)=>f(x);
  }
  // Another::f does not work!

+ 16.1.5 Hiding exposed symbols

A symbol exposed through an open directive will be hidden by a definition or injection from an inherit directive of the same name (for non-functions) or same name and signature (for functions).

Intuitively, opened scopes hide just behind the current scope, so in a nested context we have a list of search scopes: the inner most scope, then symbols exposed by opens of that scope, then the next innermost scope, then its opens, up to the top level scope.

  class X
  {
    open Base;
    fun f: int -> int;
    fun g(x:int) => f x; // X::f
  }