diff --git a/src/companion-modules.md b/src/companion-modules.md index f197600..a39e55a 100644 --- a/src/companion-modules.md +++ b/src/companion-modules.md @@ -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 @@ -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. diff --git a/src/for-llms.md b/src/for-llms.md index a92fbbc..291d601 100644 --- a/src/for-llms.md +++ b/src/for-llms.md @@ -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 diff --git a/src/structs.md b/src/structs.md index 0b2eebf..2f1d96d 100644 --- a/src/structs.md +++ b/src/structs.md @@ -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. @@ -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 = @@ -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 } @@ -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 + } } ``` @@ -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] + } } ``` @@ -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]] + } } ```