From 5c124acf7ef1147d1c4d785692664f9174687f83 Mon Sep 17 00:00:00 2001 From: Dhruvan Date: Thu, 5 Dec 2024 16:56:05 -0500 Subject: [PATCH] feat: update documentation and links for Helix language features, including questionable types and panic handling --- .xmake/macosx/arm64/project.lock | 0 astro.config.mjs | 2 +- src/content/docs/docs/index.mdx | 6 +- src/content/docs/docs/language/panicking.mdx | 14 +- .../docs/docs/language/questionable.mdx | 21 +- src/content/docs/docs/language/requires.mdx | 386 +++++++++++++++++- src/content/docs/docs/language/structs.mdx | 30 +- 7 files changed, 442 insertions(+), 17 deletions(-) create mode 100644 .xmake/macosx/arm64/project.lock diff --git a/.xmake/macosx/arm64/project.lock b/.xmake/macosx/arm64/project.lock new file mode 100644 index 0000000..e69de29 diff --git a/astro.config.mjs b/astro.config.mjs index b17ec2b..e47fe9e 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -69,7 +69,7 @@ export default defineConfig({ baseUrl: 'https://github.com/helixlang/helix-site/edit/new-template/src/content/docs/docs/', }, social: { - github: 'https://github.com/helixlang/helix', + github: 'https://github.com/helixlang/helix-lang', }, customCss: ["/src/styles/docstyle.css"], expressiveCode: { diff --git a/src/content/docs/docs/index.mdx b/src/content/docs/docs/index.mdx index 004803d..50e81ec 100644 --- a/src/content/docs/docs/index.mdx +++ b/src/content/docs/docs/index.mdx @@ -8,7 +8,7 @@ Helix is a modern, compiled programming language designed to empower developers ## **Why Choose Helix?** #### 1. **Performance at Its Core** -Helix uses the LLVM toolchain** to compile to native machine code, leveraging the power of cutting-edge compiler optimizations to produce fast, predictable binaries. It offers: +Helix uses the LLVM toolchain¹ to compile to native machine code, leveraging the power of cutting-edge compiler optimizations to produce fast, predictable binaries. It offers: - Low-level access to system resources when needed. - Zero-cost abstractions that don't compromise performance or efficiency. - Fine-grained control over memory, concurrency, data layout, and more. @@ -63,4 +63,6 @@ Helix seamlessly interacts with other languages: Helix is the language that adapts to your needs—whether you're building performance-critical systems or expressive, maintainable applications. Explore the journey ahead and see why Helix is the next evolution in programming. -** Helix at the current stage uses a an intermediate C++ backend to get to the LLVM IR, but we are planing to move to the LLVM IR directly in the future. +--- + +*1. Helix at the current stage uses a an intermediate C++ backend to get to the LLVM IR, but we are planing to move to the LLVM IR directly in the future.* diff --git a/src/content/docs/docs/language/panicking.mdx b/src/content/docs/docs/language/panicking.mdx index ee84724..b1d0653 100644 --- a/src/content/docs/docs/language/panicking.mdx +++ b/src/content/docs/docs/language/panicking.mdx @@ -3,7 +3,7 @@ title: Panicking description: Comprehensive guide to the `panic` statement in Helix, its behavior, and best practices for error handling. --- -import { Tabs, TabItem, Aside } from '@astrojs/starlight/components'; +import { CardGrid, LinkCard, Tabs, TabItem, Aside } from '@astrojs/starlight/components'; ## Panic in Helix @@ -30,7 +30,7 @@ In Helix, **`panic`** is a special statement used to signal an error by returnin #### With Questionable Types (`?`) When a function's return type is a questionable type, `panic` sets the return value to an error. This value can then be validated using `...?` or `error in ...` syntax. -Refer to the [Questionable Types Guide](../questionable-types) for more details on questionable types. +Refer to the [Questionable Types Guide](../questionable) for more details on questionable types. ```helix import std::errors; @@ -290,3 +290,13 @@ fn main() { The **`panic` statement** in Helix is a powerful and integrated error-handling mechanism. By leveraging questionable types (`?`) or `std::Error::`, you can create predictable and robust error propagation paths. Ensure every `panic` is handled gracefully with checks (`...?`, `error in ..`, `.has_value()`) and explicitly extract values where needed. This design keeps Helix code clean, safe, and predictable. + +### References + + + + \ No newline at end of file diff --git a/src/content/docs/docs/language/questionable.mdx b/src/content/docs/docs/language/questionable.mdx index fed3415..af9b0d5 100644 --- a/src/content/docs/docs/language/questionable.mdx +++ b/src/content/docs/docs/language/questionable.mdx @@ -3,7 +3,7 @@ title: Questionable description: Comprehensive guide to using questionable types in Helix, their behavior, and best practices for error and null handling. --- -import { Tabs, TabItem, Aside } from '@astrojs/starlight/components'; +import { CardGrid, LinkCard, Tabs, TabItem, Aside } from '@astrojs/starlight/components'; ## Questionable Types (?) @@ -114,7 +114,7 @@ A questionable type can hold one of three states: - **errors** can not be assigned directly. Since they are the panicked state of a function. - They can be assigned as a result of a function call. but not directly. - - refer to [Panic](../panic) for more information. + - refer to [Panic](../panicking) for more information. ```helix let x: int? = divide(10, 0); // Holds an error ``` @@ -255,4 +255,19 @@ if x?: ### Conclusion -Questionable types (`?`) in Helix are a powerful tool for managing nullable and error-prone data. By embedding error and null handling into the type system, Helix enables clean, concise, and safe code. Use `...?`, `in`, and `error in ...` to validate questionable types and write robust programs. \ No newline at end of file +Questionable types (`?`) in Helix are a powerful tool for managing nullable and error-prone data. By embedding error and null handling into the type system, Helix enables clean, concise, and safe code. Use `...?`, `in`, and `error in ...` to validate questionable types and write robust programs. + +### References + + + + + diff --git a/src/content/docs/docs/language/requires.mdx b/src/content/docs/docs/language/requires.mdx index bc4a4d0..44596ce 100644 --- a/src/content/docs/docs/language/requires.mdx +++ b/src/content/docs/docs/language/requires.mdx @@ -1,3 +1,387 @@ --- title: Requires (Generics/Type Bounds) ---- \ No newline at end of file +description: A detailed guide to the `requires` keyword in Helix for generics, constraints, and conditional type logic. +--- + +import { CardGrid, LinkCard, Tabs, TabItem, Aside } from '@astrojs/starlight/components'; + +## Introduction to `requires` + +The `requires` keyword in Helix enables powerful generics and type constraints. By explicitly defining what types or constraints are allowed, it ensures robust and reusable constructs. The `requires` keyword can be used in the following constructs: + +- [**Structs**](../structs) +- [**Classes**](../classes) +- [**Operators**](../operators) +- [**Functions**](../functions) +- [**Type Definitions**](../type-system) + +--- + +### Syntax Overview + +The `requires` keyword introduces generic parameters, optionally constrained by types or conditional logic. + +#### Grammar Reference + +```ebnf +RequiresDecl ::= `requires` `<` ((`const`? Ident `:` Type) | Ident) (`,` ((`const` Ident `:` Type) | Ident))*? `>` TypeBoundDecl? +TypeBoundDecl ::= `if` BinaryExpr | PrimaryExpr +``` + +This grammar indicates: +1. A `requires` clause defines one or more generic parameters. +2. Parameters can optionally include: + - `const` for constant values. + - A constraint (`:` Type) specifying the expected type. +3. A `requires` clause may optionally be followed by a `TypeBoundDecl` for additional type contracts/bounds logic using `if`. + +--- + + + +--- + +### Using `requires` in Constructs + +#### Functions + +You can attach the `requires` clause to a function definition after its signature to define generic functions: + +```helix +fn max(a: T, b: T) -> T + requires if T has Comparable { + return a > b ? a : b; +} +``` + +- **Explanation**: + - `T` is a generic type. + - The function works only for types that implement the `Comparable` interface. + - For more info on `has`, `derives` refer to [Type System](../type-system). + +For a more generic function (without constraints), would only take the `requires` clause: + +```helix +fn print_value(value: T) + requires { + print(value); +} +``` + +- **Explanation**: + - The function accepts any type `T`. + +#### Structs + +The `requires` clause enables generics for structs: + +```helix +struct Container requires { + let value: T; +} +``` + +- **Explanation**: + - `Container` holds a value of type `T`. + +##### Adding Constraints to Structs + +```helix +struct Box requires { + let data: T; +} +``` + +- **Explanation**: + - `T` must be of type `Storable`. + +If we want a slightly less strict constraint, we can use `derives`: + +```helix +struct Box requires + if T derives Storable { + let data: T; +} +``` + +- **Explanation**: + - `T` must derive from `Storable`. (OOP inheritance hierarchy) + +#### Classes + +Similar to structs, classes can use the `requires` clause for generics: + +```helix +class Cache requires + if K has Hashable { + let storage: map::; + + fn set(key: K, value: V) { + self.storage.insert(key, value); + } +} +``` + +- **Explanation**: + - `K` must be implement `Hashable`. Refer to [Interfaces](../interfaces) for more on how to define and implement interfaces. + - `V` is a value type without additional constraints. + +#### Operators + +Operators can also use `requires` to generalize their behavior: + +```helix +class Number { + let value: int = 0; + + op - fn (self, b: T) -> T + requires if T has Subtractable { + return self.value - b; + } +} +``` + +- **Explanation**: + - This generic operator works for any type `T` that supports the subtraction operator. + +#### Type Definitions + +The `requires` clause can define generic type aliases: + +```helix +type Pair requires = (T, U); // tuple of two types +``` + +- **Explanation**: + - `Pair` is a generic type holding two related values. + - Usage: `let p: Pair:: = (10, "Helix");` + +--- + +### Conditional Constraints + +Helix allows logical expressions in `requires` clauses to define more complex constraints. + +#### Example: Logical Constraints +```helix +fn validate(value: T) -> bool + requires if T has Number || T has String { + return true; +} +``` + +- **Explanation**: + - The function accepts only numbers or strings. + +#### Example: Combining Multiple Parameters +```helix +struct Graph requires + if N derives Node && E derives Edge { + let nodes: list::; + let edges: list::; +} +``` + +- **Explanation**: + - `N` and `E` are constrained to types deriving `Node` and `Edge`, respectively. + +--- + +### Type Bounds (Advanced) + +The `if` keyword in the `requires` clause allows you to specify type bounds for generic parameters. +They can be any valid expression that evaluates to a boolean value. For example: + +```helix +struct CheckType requires { + static let is_const = true; +} + +struct CheckType requires { + static let is_const = false; +} + +fn only_const() + requires if CheckType::::is_const { + // Do something +} + +fn only_not_const() + requires if !CheckType::::is_const { + // Do something else +} + +fn main() { + only_const::(); // Compiles + only_not_const::(); // Compiles + + // The following would not compile + only_const::(); // error: no matching function + only_not_const::(); // error: no matching function +} +``` + +- **Explanation**: + - The `CheckType` struct has two overloads based on the `T` type being `const` or not. + - The `only_const` function only works with `const` types since if a type thats const is passed it would call the first overload of `CheckType` that sets `is_const` to `true`. + - The `only_not_const` function only works with non-`const` types since if a type thats not const is passed it would call the second overload of `CheckType` that sets `is_const` to `false` and negates it. + +--- + +### Checking the Type of a Generic outside the `requires` Clause + +You can check the type of a generic parameter outside the `requires` clause using the `has` or `derives` operator: + +```helix +fn to_string(value: T) -> string + requires { + + eval if T has String { + return value as string; + } else eval if T has Displayable { + return value.show(); + } else { + return "Unknown"; + } +} +``` + +- **Explanation**: + - The function converts the generic value to a string based on its type. + - The `eval if` construct allows conditional logic based on the type of `T`. + - At compile-time, the correct branch is chosen based on the type of `T`. + - This is a powerful zero-cost abstraction that ensures type safety. While being as dynamic as python with types. + +Refer to [Type System](../type-system) for more about helix's powerful type system. + +--- + +### Examples and Use Cases + + + +```helix +fn min(a: T, b: T) -> T + requires if T has Comparable { + return a if a < b else b; +} + +fn main() { + let result = min(10, 20); // the types of T are inferred + print(result); // Outputs: 10 +} +``` + + +```helix +struct Holder with Displayable + requires if T has Displayable { + let item: T; + + fn display(self) { + self.item.display(); + } +} + +fn main() { + let h = Holder { item = "Hello, Helix!" }; + h.display(); // Outputs: Hello, Helix +} +``` + + +```helix +op == fn eq(a: T, b: T) -> bool + requires if T has Equatable { + return a.equals(b); +} + +fn main() { + let is_equal = 10 == 20; + print(is_equal); // Outputs: false +} +``` + + + +--- + +### Common Errors and Pitfalls + +- **Ambiguous Constraints**: Avoid ambiguous constraints that can lead to compile-time errors. For example: + ```helix + fn print_value(value: T) + requires { + print(value); + } + + fn print_value(value: T) + requires if T has Displayable { + print(value.show()); + } + ``` + - This would result in an ambiguous call error. + - Since both functions take a generic, but the first function would be called for any type, and the second function would be called for any type that implements `Displayable`. + - This would result in a compile-time error since the compiler wouldn't know which function to call. As both functions are valid for a type that implements `Displayable`. + - To fix this, you can either remove the first function or add a constraint to the first function that would make it more specific than the second function. + +- **Overly Specific Constraints**: Avoid constraints that are too specific and limit the reusability of your code. For example: + ```helix + struct Box requires { + let data: T; + } + ``` + - This would limit the `Box` struct to only work with `int`'s and not any other type (even if it derives from `int`). + - To fix this, you can remove the constraint or make it more general. Or add a type bound that would allow any type that derives from an `int`. + +--- + + + +--- + +### Conclusion + +The `requires` keyword in Helix is a powerful tool for defining generic types and constraints. By specifying the expected types and conditions, you can ensure type safety and robustness in your code. Use `requires` in functions, structs, classes, operators, and type definitions to create reusable and flexible constructs. + +### References + + + + + \ No newline at end of file diff --git a/src/content/docs/docs/language/structs.mdx b/src/content/docs/docs/language/structs.mdx index 72c8f45..02cd474 100644 --- a/src/content/docs/docs/language/structs.mdx +++ b/src/content/docs/docs/language/structs.mdx @@ -3,12 +3,15 @@ title: Structures description: Comprehensive guide to using structs in Helix for storing aggregate types. --- -import { Tabs, TabItem, Aside } from '@astrojs/starlight/components'; +import { CardGrid, LinkCard, Tabs, TabItem, Aside } from '@astrojs/starlight/components'; ## Structs in Helix In Helix, **structs** are used exclusively for storing aggregate types. Unlike classes, structs cannot have methods, constructors, or derived relationships. However, they can: +- Contain other structs. - Contain variables (fields). +- Contain Type Aliases/Definitions. +- Contain anonymous or named enumerations. - Overload anonymous operators (`op` without aliases). - Use interfaces with the `with` keyword to implement variables and operators. @@ -66,7 +69,7 @@ Even though implicit initialization is valid, relying on it in these scenarios c Structs can define **generic parameters** using the `requires` keyword. This allows you to create structs that can store or manipulate data types generically. #### Syntax Overview -The `requires` declaration specifies type parameters and optionally enforces constraints on those parameters. Constraints are covered in more detail in [Requires](../requires.mdx). +The `requires` declaration specifies type parameters and optionally enforces constraints on those parameters. Constraints are covered in more detail in [Requires](../requires). ```helix struct Box requires { @@ -90,7 +93,7 @@ fn main() { ``` --- @@ -137,7 +140,7 @@ fn main() { ``` --- @@ -247,7 +250,7 @@ fn main() { ``` --- @@ -267,6 +270,17 @@ This example demonstrates how to extend a struct to implement additional functio Structs in Helix are lightweight and efficient for storing aggregate data. By restricting their functionality to fields and anonymous operators, Helix ensures that structs remain simple and focused on their purpose. -For more information: -- Refer to [Requires](../requires.mdx) for advanced details on generics and constraints. -- Explore [Operators](../operators.mdx) for a comprehensive guide to operator overloading in Helix. \ No newline at end of file +### References + + + + + \ No newline at end of file