Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 103 additions & 29 deletions src/companion-modules.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
# Companion Modules

In Flix every enum and trait declaration is associated with a _companion
module_.
Inside a module we can declare an enum, struct, effect, or trait with the
same name as the module. Such a declaration is called the _companion_ of the
module.

## Enum Companions

When we declare an enum, its type and cases are automatically available inside
its companion module. For example, we can write:
For example:

```flix
enum Color {
case Red,
case Green,
case Blue
mod Color {
pub enum Color {
case Red,
case Green,
case Blue
}
}
```

Here the `Color` enum is the companion of the `Color` module.

The companion's name is exported from the module. This means that `Color` can
refer to both the module and the enum. We can refer to a case as `Color.Red`
or as `Color.Color.Red`.

The companion must appear before any other declaration inside its module.
Otherwise the compiler raises an error.

## Enum Companions

When an enum is declared as the companion of its module, the type and its
cases are automatically available throughout the module:

```flix
mod Color {
pub enum Color {
case Red,
case Green,
case Blue
}

pub def isWarm(c: Color): Bool = match c {
case Red => true
case Green => false
Expand All @@ -24,40 +46,92 @@ mod Color {
}
```

Here the `Color` type and the `Red`, `Green`, and `Blue` cases are automatically
in scope within the companion `Color` module.
Here the `Color` type and the `Red`, `Green`, and `Blue` cases are in scope
within the companion `Color` module.

## Trait Companions
## Struct Companions

Every trait declaration also gives rise to a companion module.
A struct may also be declared as the companion of its module. The fields of a
struct are only visible from within its companion module, so any function that
reads or writes them must live there.

For example, we can define a trait `Addable` for types whose elements can be added:
For example:

```flix
trait Addable[t] {
pub def add(x: t, y: t): t
mod Point {
pub struct Point[r] {
x: Int32,
mut y: Int32
}

pub def area(p: Point[r]): Int32 \ r = p->x * p->y
}
```

The `Addable` trait implicitly introduces a companion module `Addable`. We
typically use the companion module to store functionality that is related to the
trait.
Here `area` can access the `x` and `y` fields because it lives inside the
companion module of `Point`. See [Structs](structs.md) for more on field
visibility.

## Effect Companions

For example, we could have:
An effect may be declared as the companion of its module. The default handler
for the effect, if any, lives in the same companion module:

```flix
mod Fs.Glob {
pub eff Glob {
def glob(base: String, pattern: String): Result[IoError, List[String]]
}

// Handlers and helpers for the effect go here.
}
```

## Trait Companions

A trait may also be declared as the companion of its module. We typically use
the companion module to store functionality that is related to the trait:

```flix
mod Addable {
pub trait Addable[t] {
pub def add(x: t, y: t): t
}

pub def add3(x: t, y: t, z: t): t with Addable[t] = add(add(x, y), z)
}
```

When accessing a member of `Addable`, Flix will automatically look in both the
trait declaration and its companion module. Consequently, `Addable.add`
refers to the trait member `add` whereas `Addable.add3` refers to the
function inside the `Addable` module. Note that the `add` signature is in the
scope of the `Addable` module.
When accessing a member of `Addable`, Flix automatically looks in both the
trait declaration and its companion module. Consequently, `Addable.add` refers
to the trait member `add` whereas `Addable.add3` refers to the function inside
the `Addable` module.

We should be aware that functions defined in the companion module of a trait
cannot be redefined by instances of the associated trait. Thus we
should only put members into the companion namespace when we do not intend
to redefine them later.
cannot be redefined by instances of the associated trait. Thus we should only
put members into the companion module when we do not intend to redefine them
later.

## Instances in Companion Modules

A trait instance may be declared in the companion module of its type. For
example, instances of `Add`, `Sub`, and `ToString` for the `Size` enum are
placed alongside the enum itself:

```flix
mod Fs.Size {
pub enum Size(Int64) with Eq, Order, Hash

instance Add[Size] {
pub def add(x: Size, y: Size): Size =
let Size(x1) = x;
let Size(y1) = y;
Size(x1 + y1)
}

pub def zero(): Size = Size(0i64)
}
```

This is the recommended location for instances when the trait is defined
elsewhere.
41 changes: 41 additions & 0 deletions src/for-llms.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,47 @@ def testAdd01(): Unit \ Assert =
Note: Use `@Test`, not `@test`. Other annotations are similarly uppercase, e.g.
`@Parallel`, `@Lazy`, `@MustUse`.

## Companions Go Inside the Module

A companion of a module is an enum, struct, effect, or trait whose name
matches the module's name. The current convention is to declare the companion
_inside_ the module, as the first declaration. The older sibling style
(declaring the enum, struct, effect, or trait next to the module) is no
longer idiomatic.

❌ **Old (no longer idiomatic):**

```
enum Color { // Wrong -- Outdated
case Red,
case Green,
case Blue
}

mod Color {
pub def isWarm(c: Color): Bool = ...
}
```

✅ **Current (correct, as of Flix 0.68.0):**

```flix
mod Color {
pub enum Color {
case Red,
case Green,
case Blue
}

pub def isWarm(c: Color): Bool = ...
}
```

Note: The companion must be the **first** declaration inside its module,
otherwise the compiler emits a `CompanionMustBeFirst` error. The same rule
applies to struct, effect, and trait companions. See
[Companion Modules](./companion-modules.md) for details.

## Datalog `inject` Requires Arity

Older versions of Flix allowed `inject` without specifying the arity of the
Expand Down
67 changes: 39 additions & 28 deletions src/structs.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,24 @@ Each operation has an effect in the region of the struct.

## Declaring a Struct

We can declare a struct as follows:
A struct is declared inside a module of the same name — its
[companion](companion-modules.md). For example:

```flix
struct Person[r] {
name: String,
mut age: Int32,
mut height: Int32
mod Person {
pub struct Person[r] {
name: String,
mut age: Int32,
mut height: Int32
}
}
```

Here we declare a struct with three fields: `name`, `age`, and `height`. The
`name` field is immutable, i.e. cannot be changed once the struct instance has
been created. The `age` and `heights` are mutable and hence can be changed after
creation. The `Person` struct has one type parameter: `r` which specifies the
region that the struct belongs to.
been created. The `age` and `height` fields are mutable and hence can be
changed after creation. The `Person` struct has one type parameter: `r` which
specifies the region that the struct belongs to.

Every struct must have a region type parameter and it must be the last in the
type parameter list.
Expand Down Expand Up @@ -108,9 +111,11 @@ module. We can think of this as a form of compiler-enforced encapsulation.
For example, if we write:

```flix
struct Point[r] {
x: Int32,
y: Int32
mod Point {
pub struct Point[r] {
x: Int32,
y: Int32
}
}

def area(p: Point[r]): Int32 \ r =
Expand Down Expand Up @@ -140,12 +145,12 @@ The Flix compiler emits two errors:
Instead, we should define the `area` function _inside_ the companion module:

```flix
struct Point[r] {
x: Int32,
y: Int32
}
mod Point {
pub struct Point[r] {
x: Int32,
y: Int32
}

mod Point { // Companion module for Point
pub def area(p: Point[r]): Int32 \ r =
p->x * p->y
}
Expand Down Expand Up @@ -173,10 +178,12 @@ been created.
For example, we can define a struct to represent a user:

```flix
struct User[r] {
id: Int32,
mut name: String,
mut email: String
mod User {
pub struct User[r] {
id: Int32,
mut name: String,
mut email: String
}
}
```

Expand Down Expand Up @@ -211,9 +218,11 @@ We remark that field immutability is _not_ transitive.
For example, we can define a struct:

```flix
struct Book[r] {
title: String,
authors: MutList[String, r]
mod Book {
pub struct Book[r] {
title: String,
authors: MutList[String, r]
}
}
```

Expand All @@ -236,11 +245,13 @@ mutable list.
We can define a struct for a binary search tree that is recursive and polymorphic:

```flix
struct Tree[k, v, r] {
key: k,
mut value: v,
mut left: Option[Tree[k, v, r]],
mut right: Option[Tree[k, v, r]]
mod Tree {
pub struct Tree[k, v, r] {
key: k,
mut value: v,
mut left: Option[Tree[k, v, r]],
mut right: Option[Tree[k, v, r]]
}
}
```

Expand Down
Loading