Skip to content

Commit 34a4c93

Browse files
committed
Add Type System docs
1 parent 888dc61 commit 34a4c93

File tree

6 files changed

+319
-10
lines changed

6 files changed

+319
-10
lines changed

docs/definitions/functions.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,12 @@ A lambda function, also known as an [anonymous function](https://en.wikipedia.or
8787
It's convenient for passing functions as parameters:
8888

8989
```rue
90-
fun main() -> Int[] {
90+
fun main() -> List<Int> {
9191
map([1, 2, 3], fun(num) => num * 100)
9292
}
9393
94-
fun map(list: Int[], mapper: fun(num: Int) -> Int) -> Int[] {
95-
if list is (Int, Int[]) {
94+
fun map(list: List<Int>, mapper: fun(num: Int) -> Int) -> List<Int> {
95+
if list is (Int, List<Int>) {
9696
return [mapper(list.first), ...map(list.rest, mapper)];
9797
}
9898
nil
@@ -106,12 +106,12 @@ You can define generic types on functions which will get replaced when called.
106106
Here's a simple example, building on the previous:
107107

108108
```rue
109-
fun main() -> Int[] {
109+
fun main() -> List<Int> {
110110
map([1, 2, 3], fun(num) => num * 100)
111111
}
112112
113-
fun map<T>(list: T[], mapper: fun(num: T) -> T) -> T[] {
114-
if list is (T, T[]) {
113+
fun map<T>(list: List<T>, mapper: fun(num: T) -> T) -> List<T> {
114+
if list is (T, List<T>) {
115115
return [mapper(list.first), ...map(list.rest, mapper)];
116116
}
117117
nil

docs/types/pair-types.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
slug: /pair-types
3+
---
4+
5+
# Pair Types
6+
7+
A pair has two values, a `first` and `rest`. You can create a pair like this:
8+
9+
```rue
10+
let pair = (42, 34);
11+
```
12+
13+
## Lists
14+
15+
Pairs can be nested arbitrarily:
16+
17+
```rue
18+
let chain = (100, (200, (300, nil)));
19+
```
20+
21+
In fact, the above structure is identical to a nil-terminated list with those items.
22+
23+
```rue
24+
let chain = [100, 200, 300];
25+
```
26+
27+
These both have the following type:
28+
29+
```rue
30+
(Int, (Int, (Int, nil)))
31+
```
32+
33+
Which can be assigned to the recursive type `List<Int>`.

docs/types/type-checking.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
---
2+
slug: /type-checking
3+
---
4+
5+
# Type Checking
6+
7+
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.
8+
9+
Type checks have the following form:
10+
11+
```rue
12+
value is Type
13+
```
14+
15+
They emit the code required to check the type at runtime, and return a `Bool` value.
16+
17+
## Structure
18+
19+
If you have a value of type `Any`, you can determine whether it's an atom or a pair.
20+
21+
For example:
22+
23+
```rue
24+
fun count_atoms(value: Any) -> Int {
25+
if value is Bytes {
26+
1
27+
} else {
28+
let first = count_atoms(value.first);
29+
let rest = count_atoms(value.rest);
30+
first + rest
31+
}
32+
}
33+
```
34+
35+
## Length
36+
37+
You can check against a type with a fixed length:
38+
39+
```rue
40+
fun calculate_coin_id(
41+
parent_coin_info: Bytes,
42+
puzzle_hash: Bytes,
43+
amount: Int,
44+
) -> Bytes32 {
45+
assert parent_coin_info is Bytes32;
46+
assert puzzle_hash is Bytes32;
47+
sha256(parent_coin_info + puzzle_hash + amount as Bytes)
48+
}
49+
```
50+
51+
## Values
52+
53+
You can even check against a specific integer value:
54+
55+
```rue
56+
let num: Any = 42;
57+
assert num is 42;
58+
```
59+
60+
Although if you have an `Int` already, you may as well just do a simple equality check:
61+
62+
```rue
63+
let num = 42;
64+
assert num == 42;
65+
```
66+
67+
## Complex Checks
68+
69+
You can check against more complicated nested types as well:
70+
71+
```rue
72+
struct Point {
73+
x: Int,
74+
y: Int,
75+
}
76+
77+
let value: Any = 42;
78+
assert !(value is Point);
79+
```
80+
81+
## Union Narrowing
82+
83+
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.
84+
85+
```rue
86+
let value: Bytes32 | nil = nil;
87+
88+
if !(value is nil) {
89+
// value must be Bytes32
90+
}
91+
```
92+
93+
## Recursive Checks
94+
95+
You can check against recursive types only if it would disambiguate a union:
96+
97+
```rue
98+
let list: List<Int> = [1, 2, 3];
99+
100+
assert value is (Int, List<Int>);
101+
```
102+
103+
However, if you were to try to do this, it would fail since it's a recursive type:
104+
105+
```rue
106+
let value: Any = 42;
107+
108+
assert value is List<Int>; // Type error
109+
```
110+
111+
To achieve this, you can write your own recursive function to check instead:
112+
113+
```rue
114+
fun is_int_list(value: Any) -> Bool {
115+
match value {
116+
(Int, Any) => is_int_list(value.rest),
117+
nil => true,
118+
_ => false,
119+
}
120+
}
121+
```

docs/types/type-system.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
---
2+
slug: /type-system
3+
---
4+
5+
# Type System
6+
7+
## Builtin Types
8+
9+
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.
10+
11+
### Any
12+
13+
Makes no assumption about the structure of the value. Anything can be assigned to `Any`.
14+
15+
```rue
16+
let value: Any = (14, 32);
17+
```
18+
19+
### List
20+
21+
Represents a recursive structure of nested pairs.
22+
23+
```rue
24+
let value: List<Int> = [1, 2, 3];
25+
```
26+
27+
### Bytes
28+
29+
Any atomic CLVM value can be represented as `Bytes`. However, only strings and hex literals are treated as `Bytes` by default.
30+
31+
```rue
32+
let hex: Bytes = 0xFACE;
33+
let string: Bytes = "Hello";
34+
```
35+
36+
When you add byte values together, they will be concatenated.
37+
38+
### Bytes32
39+
40+
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.
41+
42+
```rue
43+
let hash: Bytes32 = 0x38b1cec180a0bc0f5ec91097cec51971df126e3b18af54ddba4a3e4a36f9c285;
44+
```
45+
46+
### PublicKey
47+
48+
When an atomic value is exactly 48 bytes in length, it can be represented as `PublicKey`.
49+
50+
More specifically, `PublicKey` is a [BLS12-381](https://en.wikipedia.org/wiki/BLS_digital_signature#BLS12-381) G1Element.
51+
52+
```rue
53+
let pk: PublicKey = 0xb3596acaa39f19956f77b84cef87a684ea0fec711e6ec9e55df3cffd4a6e05d3e2da842433dccb6042ee35c14d892206;
54+
```
55+
56+
When you add public keys together, it will call the CLVM `g1_add` operator (formerly known as `point_add`).
57+
58+
### Int
59+
60+
Any atomic CLVM value can be represents as `Int`.
61+
62+
```rue
63+
let num: Int = 42;
64+
```
65+
66+
You can perform standard arithmetic on integers, as well as bitwise math:
67+
68+
```rue
69+
let a: Int = ((42 * 34) / 8 + 9 - -10) % 16;
70+
let b: Int = ((100 >> 3) << 2) & 420 | ~6;
71+
```
72+
73+
### Bool
74+
75+
A boolean is either `true` or `false`.
76+
77+
```rue
78+
let flag: Bool = true;
79+
```
80+
81+
You can use logical operators on booleans:
82+
83+
```rue
84+
let value: Bool = (true && false) || true;
85+
```
86+
87+
### nil
88+
89+
The simplest type of them all, `nil` only has one value:
90+
91+
```rue
92+
let value: nil = nil;
93+
```
94+
95+
## Inference
96+
97+
In many cases, the type of variables can be inferred based on their value.
98+
99+
For example:
100+
101+
```rue
102+
let value = 42;
103+
```
104+
105+
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.
106+
107+
## Casting
108+
109+
### Structural Cast
110+
111+
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.
112+
113+
For example, casting an `Int` to `Bytes` is fine since both of them are atomic values:
114+
115+
```rue
116+
let value = 42;
117+
let bytes = 42 as Bytes;
118+
```
119+
120+
You can cast more complicated types such as pairs:
121+
122+
```rue
123+
let pair = ("Hello, ", "world!");
124+
let casted = pair as (Int, Int);
125+
```
126+
127+
However, this is **not** allowed, since the structure would differ (and this could cause runtime errors):
128+
129+
```rue
130+
let pair = (42, 34);
131+
let num = pair as Int; // Type error
132+
```
133+
134+
### Reinterpret Cast
135+
136+
Sometimes, you need to change the type of a variable without the type system getting in the way.
137+
138+
:::warning
139+
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.
140+
:::
141+
142+
There is a built in function called `cast` which will do this:
143+
144+
```rue
145+
let pair = (42, 34);
146+
let num = cast::<Int>(pair); // Ok
147+
```

sidebars.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@ const sidebars: SidebarsConfig = {
2121
{
2222
type: "category",
2323
label: "Types",
24-
items: ["types/structs", "types/enums", "types/type-aliases"],
24+
items: [
25+
"types/type-system",
26+
"types/type-checking",
27+
"types/pair-types",
28+
"types/structs",
29+
"types/enums",
30+
"types/type-aliases",
31+
],
2532
collapsed: false,
2633
},
2734
],

src/theme/prism-rue.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ Prism.languages.rue = {
2424
multiline: true,
2525
greedy: true,
2626
},
27-
number: /\b[0-9][0-9_]*\b/,
28-
operator: /[+\-*?%!\^]|<[<=]?|>[>=]?|=[=>]?|!=?|\.(?:\.\.)?|::|->?|&&?|\|\|?/,
27+
number: /\b(?:0[xX][0-9a-fA-F_]+|[0-9][0-9_]*)\b/,
28+
operator:
29+
/[+\-*?%!\^~]|<[<=]?|>[>=]?|=[=>]?|!=?|\.(?:\.\.)?|::|->?|&&?|\|\|?/,
2930
punctuation: /[(){}[\],:]/,
3031
"control-flow": {
3132
pattern: /\b(?:if|else|return|raise|assert|assume)\b/,
@@ -49,7 +50,7 @@ Prism.languages.rue = {
4950
pattern: /\bnil\b/,
5051
alias: "constant",
5152
},
52-
builtin: /\b(?:Int|Any|Bytes32|Bytes|PublicKey|Nil|Bool)\b/,
53+
builtin: /\b(?:Int|Any|Bytes32|Bytes|PublicKey|Bool)\b/,
5354
"class-name": /\b[A-Z][a-z][a-zA-Z0-9_]*\b/,
5455
constant: /\b[A-Z][A-Z0-9_]*\b/,
5556
};

0 commit comments

Comments
 (0)