Skip to content

Commit

Permalink
Add initial docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Rigidity committed Jul 6, 2024
1 parent ec6f4c6 commit a338b6a
Show file tree
Hide file tree
Showing 9 changed files with 409 additions and 3 deletions.
48 changes: 48 additions & 0 deletions docs/definitions/constants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
slug: /constants
---

# Constants

A constant is similar to a let binding. The difference is that it can be defined outside of a function, as a standalone item. Therefore, the order doesn't matter, but it can only reference other items within the same scope, rather than statements like let bindings.

Here is an example:

```rue
const TWO: Int = 2;
fun main() -> Int {
42 * TWO
}
```

## Inline Constants

When you mark a constant as inline, it will be inserted wherever it's referenced rather than being defined within the compiled output only a single time. This is typically not a good idea to do unless the constant's value is really short and it's more efficient to inline it. Benchmarking would be required to determine this.

An inline constant can be defined and used in the same way:

```rue
inline const TWO: Int = 2;
fun main() -> Int {
42 * TWO
}
```

## Circular References

Constants must not reference themselves, either directly or indirectly. If you find yourself needing to do this for some reason, consider writing a function instead.

For example, this is clearly impossible:

```rue
const NUM: Int = NUM;
```

But this is also circular:

```rue
const FIRST: Int = SECOND;
const SECOND: Int = FIRST;
```
155 changes: 155 additions & 0 deletions docs/definitions/functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
slug: /functions
---

# Functions

A function allows you to separate code, reuse it in multiple places, and use recursion to repeat things multiple times.

Here is a simple function example:

```rue
fun main() -> Int {
square(42)
}
fun square(num: Int) -> Int {
num * num
}
```

This will square the value `42` and return the result.

## Inline Functions

If a function is concise and you don't want the additional overhead of the function call, you can define an inline function instead. This will replace function calls with the body directly, instead of defining the function and referencing it.

For example, in this example the function body is simple enough that it makes sense to inline it:

```rue
fun main() -> Int {
double(100)
}
inline fun double(num: Int) -> Int {
num * 2
}
```

This is the exact same as writing the following:

```rue
fun main() -> Int {
42 * 2
}
```

As you can see, `num` just got replaced with the argument value and inserted in place of the function call.

:::note
Inline functions are treated as constants, so recursion is not allowed. Use normal functions if you need recursion.
:::

## Recursion

You can use [recursion](https://en.wikipedia.org/wiki/Recursion) to run code multiple times.

For example, the classic [factorial](https://en.wikipedia.org/wiki/Factorial):

```rue
fun main() -> Int {
factorial(5)
}
fun factorial(num: Int) -> Int {
if num > 1 {
num * factorial(num - 1)
} else {
1
}
}
```

In this case, we recursively call factorial on the previous number and multiply with the current number until we reach the base case of `1`.

So it will call:

- `5 * factorial(4)`
- `4 * factorial(3)`
- `3 * factorial(2)`

And so on, until it reaches the final result of `120`.

## Lambda Functions

A lambda function, also known as an [anonymous function](https://en.wikipedia.org/wiki/Anonymous_function), lets you define a function as an expression rather than a named item.

It's convenient for passing functions as parameters:

```rue
fun main() -> Int {
map([1, 2, 3], fun(num) => num * 100)
}
fun map(list: Int[], mapper: fun(num: Int) -> Int) -> Int[] {
if list is (Int, Int[]) {
return [mapper(list.first), ...map(list.rest, mapper)];
}
nil
}
```

## Generic Types

You can define generic types on functions which will get replaced when called.

Here's a simple example, building on the previous:

```rue
fun main() -> Int {
map([1, 2, 3], fun(num) => num * 100)
}
fun map<T>(list: T[], mapper: fun(num: T) -> T) -> T[] {
if list is (T, T[]) {
return [mapper(list.first), ...map(list.rest, mapper)];
}
nil
}
```

:::info
The only difference is that we made `map` generic over any type `T` rather than being specific to `Int`, since the type doesn't matter.
:::

## Captures

Functions can capture values from scopes they are defined in, even if they aren't an explicit parameter. This is seen in the previous example, where `factorial` captures itself so it can call it recursively.

The `main` function itself captures `factorial` so it can call it.

## Closures

If a function leaves the scope it's defined in, it must first [partially apply](https://en.wikipedia.org/wiki/Partial_application) its captures. This creates a [closure](<https://en.wikipedia.org/wiki/Closure_(computer_programming)>).

Because of this, functions can be passed to other functions or variables, otherwise known as [higher-order functions](https://en.wikipedia.org/wiki/Higher-order_function). This is one of the most powerful functional programming features.

Here is an example of this:

```rue
fun main() -> Int {
let doubler = fn(2);
doubler(1000)
}
fun multiplier(factor: Int) -> fun(num: Int) -> Int {
fun fn(num: Int) -> Int {
num * factor
}
fn
}
```

In this example, `multiplier` returns a function which captures `factor`. This creates a closure which is assigned to `doubler`, then called.

The output is `2000`.
47 changes: 47 additions & 0 deletions docs/definitions/let-bindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
slug: /let-bindings
---

# Let Bindings

In Rue, everything is immutable. This means there isn't the traditional concept of a mutable variable whose value can be reassigned. Once a value is given to a variable, it's final unless you make a new variable.

Here's an example:

```rue
fun main() -> Int {
let num = 42;
num + 10
}
```

This will bind `num` to the value `42`, then add it to `10` and return the result.

:::note

This is not possible, since it's immutable:

```rue
fun main() -> Int {
let num = 42;
num = 34; // You can't assign to bindings.
num + 10
}
```

:::

## What's the point?

Bindings let you prevent repetition and optimize code when a value is used more than once. It can also be used to make code clearer.

For example, we use `num` twice in this example, but it only needs to be written once, both in the code and in the compiled output:

```rue
fun main() -> Int {
let num = 42;
num * num
}
```

If a binding is only used once, it will be inlined as though you wrote the value directly.
6 changes: 6 additions & 0 deletions docs/getting-started/hello-world.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ fun main() -> Bytes {
}
```

:::info
You don't need to use `return` at the end of a function. Everything in Rue must return a value, including functions, so this is implied.
:::

Now, run the following command to run the file:

```bash
rue build hello.rue --run
```

You should see `"Hello, world!"` printed in the console.
85 changes: 85 additions & 0 deletions docs/types/enums.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
slug: /enums
---

# Enums

An enum allows you to define a type which can contain multiple variants, each with their own discriminator value, and optionally fields.

This is an example of an enum without fields:

```rue
enum Mode {
Locked,
Unlocked,
}
fun main() -> Int {
// By default, the type is `Mode::Locked`
let locked = Mode::Locked;
// Let's generalize that to `Mode`
let mode: Mode = locked;
// Now we can check the type
if !(mode is Mode::Locked) {
raise "This isn't possible.";
}
// Lastly, we can cast to an `Int`
mode as Int
}
```

## Discriminants

By default, the first discriminant is `0`, and each one after that increments by `1`.

However, you can also explicitly set the discriminant:

```rue
enum Mode {
Locked = 5,
Unlocked = 10,
}
```

## Fields

If any of the enum variants have fields, all variants of the enum will be represented as a list, with the discriminant being the first item.

Here is an example:

```rue
enum Value {
None,
Scalar {
num: Int,
},
Point {
x: Int,
y: Int,
},
}
fun main() -> Int {
number(Value::Point { x: 42, y: 34 })
}
fun number(value: Value) -> Int {
if value is Value::None {
return 0;
}
if value is Value::Scalar {
return value.num;
}
assume value is Value::Point;
value.x + value.y
}
```

:::note
There is currently no way to do pattern matching, chain if statements, or narrow the enum variants down incrementally. However, these are planned features.
:::
31 changes: 31 additions & 0 deletions docs/types/structs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
slug: /structs
---

# Structs

A struct allows you to define a type which can contain multiple fields.

Here is a simple example:

```rue
struct Point {
x: Int,
y: Int,
}
fun main() -> Int {
let point = Point {
x: 42,
y: 34,
};
assert !is_origin(point);
point.x * point.y
}
fun is_origin(point: Point) -> Bool {
point.x == 0 && point.y == 0
}
```
Loading

0 comments on commit a338b6a

Please sign in to comment.