Namespaces

Flix supports hierarchical namespaces as known from many other programming languages.

Declaring a Namespace

We can declare a namespace to nest definitions and types within it. For example:

namespace Math {
    def sum(x: Int32, y: Int32): Int32 = x + y
}

Namespaces are hierarchical, so we can declare a deeper namespace:

namespace Core/Math {
    def sum(x: Int32, y32: Int): Int32 = x + y
}

Note that the fragments of a namespace are separated by /.

We can freely nest namespaces. For example:

namespace Core {
    namespace Math {

        def sum(x: Int32, y: Int32): Int32 = x + y

        namespace Stats {
            def median(xs: List[Int32]): Int32 = ???
        }
    }
}

Using Definitions from a Namespace

We can refer to definitions from a namespace by their fully-qualified name. For example:

namespace Core/Math {
    pub def sum(x: Int32, y: Int32): Int32 = x + y
}

def main(): Unit \ IO =
    Core/Math.sum(21, 42) |> println

Note that we must declare sum as public (pub) to allow access to it from outside its own namespace.

It can quickly get tedious to refer to definitions by their fully-qualified name.

The use construct allows us to "import" definitions from another namespace:

namespace Core/Math {
    pub def sum(x: Int32, y: Int32): Int32 = x + y
}

def main(): Unit \ IO =
    use Core/Math.sum;
    sum(21, 42) |> println

Here the use is local to the main function. A use can also appear at the top of a file:

use Core/Math.sum;

def main(): Unit \ IO =
    sum(21, 42) |> println

namespace Core/Math {
    pub def sum(x: Int32, y: Int32): Int32 = x + y
}

Using Multiple Definitions from a Namespaces

We can also use multiple definitions from a namespace:

use Core/Math.sum;
use Core/Math.mul;

def main(): Unit \ IO =
    mul(42, 84) |> sum(21) |> println

namespace Core/Math {
    pub def sum(x: Int32, y: Int32): Int32 = x + y
    pub def mul(x: Int32, y: Int32): Int32 = x * y
}

Multiple such uses can be grouped together:

use Core/Math.{sum, mul};

def main(): Unit \ IO =
    mul(42, 84) |> sum(21) |> println

namespace Core/Math {
    pub def sum(x: Int32, y: Int32): Int32 = x + y
    pub def mul(x: Int32, y: Int32): Int32 = x * y
}

Design Note

Flix does not support wildcard uses because they are inherently ambiguous and may lead to subtle errors during refactoring.

Avoiding Name Clashes with Renaming

We can use renaming to avoid name clashes between identically named definitions. For example:

use A.{concat => stringConcat};
use B.{concat => listConcat};

def main(): Unit \ IO =
    stringConcat("Hello", " World!") |> println

namespace A {
    pub def concat(x: String, y: String): String = x + y
}

namespace B {
    pub def concat(xs: List[Int32], ys: List[Int32]): List[Int32] = xs ::: ys
}

In many cases a better approach is to use a local use to avoid the problem in the first place.

Using Types from a Namespace

We can use types from a namespace in the same way as definitions. For example:

use A/B.Color;

def redColor(): Color = Color.Red

namespace A/B {
    pub enum Color {
        case Red, Blue
    }
}

We can also use type aliases in the same way:

use A/B.Color;
use A/B.Hue;

def blueColor(): Hue = Color.Blue

namespace A/B {
    pub enum Color {
        case Red, Blue
    }
    pub type alias Hue = Color
}

Using Enums from a Namespace

We can use enumerated types from a namespace. For example:

def blueIsRed(): Bool =
    use A/B.Color.{Blue, Red};
    Blue != Red

namespace A/B {
    pub enum Color with Eq {
        case Red, Blue
    }
}

Note that A/B.Color is the fully-qualified name of a type whereas A/B.Color.Red is the fully-qualified name of a tag inside an enumerated type. That is, a fully-qualified definition is of the form A/B/C.d, a fully-qualified type is of the form A/B/C.D, and finally a fully-qualified tag is of the form A/B/C.D.T.