-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
409 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
::: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
``` |
Oops, something went wrong.