Skip to content

Commit

Permalink
Merge pull request #1 from Rigidity/type-system
Browse files Browse the repository at this point in the history
Add Type System docs
  • Loading branch information
Rigidity authored Aug 4, 2024
2 parents 888dc61 + 34a4c93 commit 376cffb
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 10 deletions.
12 changes: 6 additions & 6 deletions docs/definitions/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ A lambda function, also known as an [anonymous function](https://en.wikipedia.or
It's convenient for passing functions as parameters:

```rue
fun main() -> Int[] {
fun main() -> List<Int> {
map([1, 2, 3], fun(num) => num * 100)
}
fun map(list: Int[], mapper: fun(num: Int) -> Int) -> Int[] {
if list is (Int, Int[]) {
fun map(list: List<Int>, mapper: fun(num: Int) -> Int) -> List<Int> {
if list is (Int, List<Int>) {
return [mapper(list.first), ...map(list.rest, mapper)];
}
nil
Expand All @@ -106,12 +106,12 @@ 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[] {
fun main() -> List<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[]) {
fun map<T>(list: List<T>, mapper: fun(num: T) -> T) -> List<T> {
if list is (T, List<T>) {
return [mapper(list.first), ...map(list.rest, mapper)];
}
nil
Expand Down
33 changes: 33 additions & 0 deletions docs/types/pair-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
slug: /pair-types
---

# Pair Types

A pair has two values, a `first` and `rest`. You can create a pair like this:

```rue
let pair = (42, 34);
```

## Lists

Pairs can be nested arbitrarily:

```rue
let chain = (100, (200, (300, nil)));
```

In fact, the above structure is identical to a nil-terminated list with those items.

```rue
let chain = [100, 200, 300];
```

These both have the following type:

```rue
(Int, (Int, (Int, nil)))
```

Which can be assigned to the recursive type `List<Int>`.
121 changes: 121 additions & 0 deletions docs/types/type-checking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
slug: /type-checking
---

# Type Checking

Types can be assigned or cast to different types so long as they have the same structure. However, sometimes you need to distinguish between and narrow types at runtime. This is when type checking comes in.

Type checks have the following form:

```rue
value is Type
```

They emit the code required to check the type at runtime, and return a `Bool` value.

## Structure

If you have a value of type `Any`, you can determine whether it's an atom or a pair.

For example:

```rue
fun count_atoms(value: Any) -> Int {
if value is Bytes {
1
} else {
let first = count_atoms(value.first);
let rest = count_atoms(value.rest);
first + rest
}
}
```

## Length

You can check against a type with a fixed length:

```rue
fun calculate_coin_id(
parent_coin_info: Bytes,
puzzle_hash: Bytes,
amount: Int,
) -> Bytes32 {
assert parent_coin_info is Bytes32;
assert puzzle_hash is Bytes32;
sha256(parent_coin_info + puzzle_hash + amount as Bytes)
}
```

## Values

You can even check against a specific integer value:

```rue
let num: Any = 42;
assert num is 42;
```

Although if you have an `Int` already, you may as well just do a simple equality check:

```rue
let num = 42;
assert num == 42;
```

## Complex Checks

You can check against more complicated nested types as well:

```rue
struct Point {
x: Int,
y: Int,
}
let value: Any = 42;
assert !(value is Point);
```

## Union Narrowing

If you have a union of one or more values, you can check if it's one of the items in the union. This will narrow the type if it's not.

```rue
let value: Bytes32 | nil = nil;
if !(value is nil) {
// value must be Bytes32
}
```

## Recursive Checks

You can check against recursive types only if it would disambiguate a union:

```rue
let list: List<Int> = [1, 2, 3];
assert value is (Int, List<Int>);
```

However, if you were to try to do this, it would fail since it's a recursive type:

```rue
let value: Any = 42;
assert value is List<Int>; // Type error
```

To achieve this, you can write your own recursive function to check instead:

```rue
fun is_int_list(value: Any) -> Bool {
match value {
(Int, Any) => is_int_list(value.rest),
nil => true,
_ => false,
}
}
```
147 changes: 147 additions & 0 deletions docs/types/type-system.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
slug: /type-system
---

# Type System

## Builtin Types

Rue has a structural type system, which is similar to [duck typing](https://en.wikipedia.org/wiki/Duck_typing). One key difference is that types with different semantics must be cast, even if they have the same structure.

### Any

Makes no assumption about the structure of the value. Anything can be assigned to `Any`.

```rue
let value: Any = (14, 32);
```

### List

Represents a recursive structure of nested pairs.

```rue
let value: List<Int> = [1, 2, 3];
```

### Bytes

Any atomic CLVM value can be represented as `Bytes`. However, only strings and hex literals are treated as `Bytes` by default.

```rue
let hex: Bytes = 0xFACE;
let string: Bytes = "Hello";
```

When you add byte values together, they will be concatenated.

### Bytes32

When an atomic value is exactly 32 bytes in length, it can be represented as `Bytes32`. This enhances type safety of things such as sha256 hashes.

```rue
let hash: Bytes32 = 0x38b1cec180a0bc0f5ec91097cec51971df126e3b18af54ddba4a3e4a36f9c285;
```

### PublicKey

When an atomic value is exactly 48 bytes in length, it can be represented as `PublicKey`.

More specifically, `PublicKey` is a [BLS12-381](https://en.wikipedia.org/wiki/BLS_digital_signature#BLS12-381) G1Element.

```rue
let pk: PublicKey = 0xb3596acaa39f19956f77b84cef87a684ea0fec711e6ec9e55df3cffd4a6e05d3e2da842433dccb6042ee35c14d892206;
```

When you add public keys together, it will call the CLVM `g1_add` operator (formerly known as `point_add`).

### Int

Any atomic CLVM value can be represents as `Int`.

```rue
let num: Int = 42;
```

You can perform standard arithmetic on integers, as well as bitwise math:

```rue
let a: Int = ((42 * 34) / 8 + 9 - -10) % 16;
let b: Int = ((100 >> 3) << 2) & 420 | ~6;
```

### Bool

A boolean is either `true` or `false`.

```rue
let flag: Bool = true;
```

You can use logical operators on booleans:

```rue
let value: Bool = (true && false) || true;
```

### nil

The simplest type of them all, `nil` only has one value:

```rue
let value: nil = nil;
```

## Inference

In many cases, the type of variables can be inferred based on their value.

For example:

```rue
let value = 42;
```

Here, `value` is known to be `Int` even though it wasn't specified. It's generally cleaner to omit the type in places where it's obvious.

## Casting

### Structural Cast

You can cast the type of a value with the `as` keyword. This can only be done as long as both types are structurally related.

For example, casting an `Int` to `Bytes` is fine since both of them are atomic values:

```rue
let value = 42;
let bytes = 42 as Bytes;
```

You can cast more complicated types such as pairs:

```rue
let pair = ("Hello, ", "world!");
let casted = pair as (Int, Int);
```

However, this is **not** allowed, since the structure would differ (and this could cause runtime errors):

```rue
let pair = (42, 34);
let num = pair as Int; // Type error
```

### Reinterpret Cast

Sometimes, you need to change the type of a variable without the type system getting in the way.

:::warning
This is unsafe and could result in hard to debug issues at runtime if you are not careful. It should be scrutinized when auditing Rue code.
:::

There is a built in function called `cast` which will do this:

```rue
let pair = (42, 34);
let num = cast::<Int>(pair); // Ok
```
9 changes: 8 additions & 1 deletion sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ const sidebars: SidebarsConfig = {
{
type: "category",
label: "Types",
items: ["types/structs", "types/enums", "types/type-aliases"],
items: [
"types/type-system",
"types/type-checking",
"types/pair-types",
"types/structs",
"types/enums",
"types/type-aliases",
],
collapsed: false,
},
],
Expand Down
7 changes: 4 additions & 3 deletions src/theme/prism-rue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ Prism.languages.rue = {
multiline: true,
greedy: true,
},
number: /\b[0-9][0-9_]*\b/,
operator: /[+\-*?%!\^]|<[<=]?|>[>=]?|=[=>]?|!=?|\.(?:\.\.)?|::|->?|&&?|\|\|?/,
number: /\b(?:0[xX][0-9a-fA-F_]+|[0-9][0-9_]*)\b/,
operator:
/[+\-*?%!\^~]|<[<=]?|>[>=]?|=[=>]?|!=?|\.(?:\.\.)?|::|->?|&&?|\|\|?/,
punctuation: /[(){}[\],:]/,
"control-flow": {
pattern: /\b(?:if|else|return|raise|assert|assume)\b/,
Expand All @@ -49,7 +50,7 @@ Prism.languages.rue = {
pattern: /\bnil\b/,
alias: "constant",
},
builtin: /\b(?:Int|Any|Bytes32|Bytes|PublicKey|Nil|Bool)\b/,
builtin: /\b(?:Int|Any|Bytes32|Bytes|PublicKey|Bool)\b/,
"class-name": /\b[A-Z][a-z][a-zA-Z0-9_]*\b/,
constant: /\b[A-Z][A-Z0-9_]*\b/,
};

0 comments on commit 376cffb

Please sign in to comment.