Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates builtin type Map so that it stores Maybes in its nodes. #743

Merged
merged 2 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 103 additions & 53 deletions docs/builtins.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,19 +161,44 @@ tree = ![![!1, !2],![!3, !4]]
```

Technically your trees don't need to end with leaves, but if you don't, your program will be very hard to reason about.
## Maybe

```python
type Maybe(T):
Some{ value }
None
```
**`Maybe`** is a structure that may or not contain a value. It is meant to be used as a return type for functions that can fail. This way you don't need to resort to unreachable() in order to handle errors.

#### Syntax
Here's how you create a new `Maybe` containing the Nat value of 1:
```python
maybe = Maybe/Some(Nat/Succ(Nat/Zero))
```
## Maybe functions

### Maybe/unwrap
Maybe has a builtin function that returns the value inside the `Maybe` if it is `Some`, and returns `unreachable()` if it is `None`.
```python
def Maybe/unwrap(m: Maybe(T)) -> T:
match m:
case Maybe/Some:
return m.val
case Maybe/None:
return unreachable()
```
## Map

```python
type Map:
Node { value ~left ~right }
Leaf
type Map(T):
Node { value: Maybe(T), ~left: Map(T), ~right: Map(T) }
Leaf
```

**`Map`** represents a tree with values stored in the branches.
It is meant to be used as an efficient map data structure with integer keys and O(log n) read and write operations.

- **Node { value ~left ~right }**: Represents a map node with a `value` and `left` and `right` subtrees. Empty nodes have `*` stored in the `value` field.
- **Node { value: Maybe(T), ~left: Map(T), ~right: Map(T) }**: Represents a map node with a `Maybe` and `left` and `right` subtrees. Empty nodes have `Maybe/None` stored in the `value` field, whilst non-empty nodes have `Maybe/Some` stored in the `value` field.
- **Leaf**: Represents an unwritten, empty portion of the map.

#### Syntax
Expand Down Expand Up @@ -216,22 +241,19 @@ Retrieves a `value` from the `map` based on the `key`.
Returns a tuple with the value and the `map` unchanged.

```rust
Map/get map key =
match map {
Map/Leaf: (*, map)
Map/Node:
switch _ = (== 0 key) {
0: switch _ = (% key 2) {
0:
let (got, rest) = (Map/get map.left (/ key 2))
(got, (Map/Node map.value rest map.right))
_:
let (got, rest) = (Map/get map.right (/ key 2))
(got, (Map/Node map.value map.left rest))
}
_: (map.value, map)
}
}
def Map/get (map: Map(T), key: u24) -> (T, Map(T)):
match map:
case Map/Leaf:
return (unreachable(), map)
case Map/Node:
if (0 == key):
return (Maybe/unwrap(map.value), map)
elif (key % 2 == 0):
(got, rest) = Map/get(map.left, (key / 2))
return(got, Map/Node(map.value, rest, map.right))
else:
(got, rest) = Map/get(map.right, (key / 2))
return(got, Map/Node(map.value, map.left, rest))
```

#### Syntax
Expand All @@ -256,29 +278,23 @@ And the value resultant from the get function would be:

### Map/set

Sets a `value` in the `map` at the specified `key`.
Returns the map with the new value.

```rust
Map/set map key value =
match map {
Map/Node:
switch _ = (== 0 key) {
0: switch _ = (% key 2) {
0: (Map/Node map.value (Map/set map.left (/ key 2) value) map.right)
_: (Map/Node map.value map.left (Map/set map.right (/ key 2) value))
}
_: (Map/Node value map.left map.right)
}
Map/Leaf:
switch _ = (== 0 key) {
0: switch _ = (% key 2) {
0: (Map/Node * (Map/set Map/Leaf (/ key 2) value) Map/Leaf)
_: (Map/Node * Map/Leaf (Map/set Map/Leaf (/ key 2) value))
}
_: (Map/Node value Map/Leaf Map/Leaf)
}
}
def Map/set (map: Map(T), key: u24, value: T) -> Map(T):
match map:
case Map/Node:
if (0 == key):
return Map/Node(Maybe/Some(value), map.left, map.right)
elif ((key % 2) == 0):
return Map/Node(map.value, Map/set(map.left, (key / 2), value), map.right)
else:
return Map/Node(map.value, map.left, Map/set(map.right, (key / 2), value))
case Map/Leaf:
if (0 == key):
return Map/Node(Maybe/Some(value), Map/Leaf, Map/Leaf)
elif ((key % 2) == 0):
return Map/Node(Maybe/None, Map/set(Map/Leaf, (key / 2), value), Map/Leaf)
else:
return Map/Node(Maybe/None, Map/Leaf, Map/set(Map/Leaf, (key / 2),value))
```

#### Syntax
Expand Down Expand Up @@ -319,17 +335,17 @@ Applies a function to a value in the map.
Returns the map with the value mapped.

```rust
Map/map (Map/Leaf) key f = Map/Leaf
Map/map (Map/Node value left right) key f =
switch _ = (== 0 key) {
0: switch _ = (% key 2) {
0:
(Map/Node value (Map/map left (/ key 2) f) right)
_:
(Map/Node value left (Map/map right (/ key 2) f))
}
_: (Map/Node (f value) left right)
}
def Map/map (map: Map(T), key: u24, f: T -> T) -> Map(T):
match map:
case Map/Leaf:
return Map/Leaf
case Map/Node:
if (0 == key):
return Map/Node(Maybe/Some(f(Maybe/unwrap(map.value))), map.left, map.right)
elif ((key % 2) == 0):
return Map/Node(map.value, Map/map(map.left, (key / 2), f), map.right)
else:
return Map/Node(map.value, map.left, Map/map(map.right, (key / 2), f))
```

#### Syntax
Expand All @@ -341,6 +357,40 @@ x[0] @= lambda y: String/concat(y, " and mapped")
# x[0] now contains "swapped and mapped"
```


### Map/contains
Checks if a `map` contains a given `key` and returns 0 or 1 as a `u24` number and the `map` unchanged.
```python
def Map/contains (map: Map(T), key: u24) -> (u24, Map(T)):
match map:
case Map/Leaf:
return (0, map)
case Map/Node:
if (0 == key):
match map.value:
case Maybe/Some:
return (1, map)
case Maybe/None:
return (0, map)
elif ((key % 2) == 0):
(new_value, new_map) = Map/contains(map.left, (key / 2))
return (new_value, Map/Node(map.value, new_map, map.right))
else:
(new_value, new_map) = Map/contains(map.right, (key / 2))
return (new_value, Map/Node(map.value, map.left, new_map))
```

#### Syntax

With the same map that we `set` in the previous section, we can call the function `Map/contains` explicitly:

```python
(num, map) = Map/contains(m, key)
return num
```
Whilst the `num` variable will contain 0 or 1 depending on if the key is in the map or not.


## Nat

```python
Expand Down
157 changes: 103 additions & 54 deletions src/fun/builtins.bend
Original file line number Diff line number Diff line change
Expand Up @@ -170,62 +170,111 @@ def Tree/reverse(tree: Tree(T)) -> Tree(T):
case Tree/Node:
return ![tree.right, tree.left]

# MAP Impl

type Map T = (Node (value: T) ~(left: (Map T)) ~(right: (Map T))) | (Leaf)

Map/empty : (Map T) = Map/Leaf

Map/get (map: (Map T)) (key: u24) : (T, (Map T)) =
match map {
Map/Leaf: (unreachable, map)
Map/Node:
switch _ = (== 0 key) {
0: switch _ = (% key 2) {
0:
let (got, rest) = (Map/get map.left (/ key 2))
(got, (Map/Node map.value rest map.right))
_:
let (got, rest) = (Map/get map.right (/ key 2))
(got, (Map/Node map.value map.left rest))
}
_: (map.value, map)
}
}

Map/set (map: (Map T)) (key: u24) (value: T) : (Map T) =
match map {
Map/Node:
switch _ = (== 0 key) {
0: switch _ = (% key 2) {
0: (Map/Node map.value (Map/set map.left (/ key 2) value) map.right)
_: (Map/Node map.value map.left (Map/set map.right (/ key 2) value))
}
_: (Map/Node value map.left map.right)
}
Map/Leaf:
switch _ = (== 0 key) {
0: switch _ = (% key 2) {
0: (Map/Node unreachable (Map/set Map/Leaf (/ key 2) value) Map/Leaf)
_: (Map/Node unreachable Map/Leaf (Map/set Map/Leaf (/ key 2) value))
}
_: (Map/Node value Map/Leaf Map/Leaf)
}
}
# MAYBE Impl

type Maybe(T):
Some { value: T }
None

# Removes the value on a Maybe
def Maybe/unwrap(m: Maybe(T)) -> T:
match m:
case Maybe/Some:
return m.value
case Maybe/None:
return unreachable()

Map/map (map: (Map T)) (key: u24) (f: T -> T) : (Map T)
Map/map (Map/Leaf) key f = Map/Leaf
Map/map (Map/Node value left right) key f =
switch _ = (== 0 key) {
0: switch _ = (% key 2) {
0:
(Map/Node value (Map/map left (/ key 2) f) right)
_:
(Map/Node value left (Map/map right (/ key 2) f))
}
_: (Map/Node (f value) left right)
}
# MAP Impl

type Map(T):
Node { value: Maybe(T), ~left: Map(T), ~right: Map(T) }
Leaf

# Creates an empty Map
def Map/empty() -> Map(T):
return Map/Leaf

# Gets a value on a Map
def Map/get (map: Map(T), key: u24) -> (T, Map(T)):
match map:
case Map/Leaf:
return (unreachable(), map)
case Map/Node:
if (0 == key):
In-Veritas marked this conversation as resolved.
Show resolved Hide resolved
return (Maybe/unwrap(map.value), map)
elif (key % 2 == 0):
(got, rest) = Map/get(map.left, (key / 2))
return(got, Map/Node(map.value, rest, map.right))
In-Veritas marked this conversation as resolved.
Show resolved Hide resolved
else:
(got, rest) = Map/get(map.right, (key / 2))
return(got, Map/Node(map.value, map.left, rest))


# Checks if a node has a value on a given key, returning Maybe/Some if it does, Maybe/None otherwise
def Map/get_check (map: Map(T), key: u24) -> (Maybe(T), Map(T)):
match map:
case Map/Leaf:
return (Maybe/None, map)
case Map/Node:
if (0 == key):
return (map.value, map)
elif (key % 2 == 0):
(new_value, new_map) = Map/get_check(map.left, (key / 2))
return (new_value, Map/Node(map.value, new_map, map.right))
else:
(new_value, new_map) = Map/get_check(map.right, (key / 2))
return (new_value, Map/Node(map.value, map.left, new_map))

# Sets a value on a Map
def Map/set (map: Map(T), key: u24, value: T) -> Map(T):
match map:
case Map/Node:
if (0 == key):
return Map/Node(Maybe/Some(value), map.left, map.right)
elif ((key % 2) == 0):
return Map/Node(map.value, Map/set(map.left, (key / 2), value), map.right)
else:
return Map/Node(map.value, map.left, Map/set(map.right, (key / 2), value))
case Map/Leaf:
if (0 == key):
return Map/Node(Maybe/Some(value), Map/Leaf, Map/Leaf)
elif ((key % 2) == 0):
return Map/Node(Maybe/None, Map/set(Map/Leaf, (key / 2), value), Map/Leaf)
else:
return Map/Node(Maybe/None, Map/Leaf, Map/set(Map/Leaf, (key / 2),value))


# Checks if a Map contains a given key
def Map/contains (map: Map(T), key: u24) -> (u24, Map(T)):
match map:
case Map/Leaf:
return (0, map)
case Map/Node:
if (0 == key):
match map.value:
case Maybe/Some:
return (1, map)
case Maybe/None:
return (0, map)
elif ((key % 2) == 0):
(new_value, new_map) = Map/contains(map.left, (key / 2))
return (new_value, Map/Node(map.value, new_map, map.right))
else:
(new_value, new_map) = Map/contains(map.right, (key / 2))
return (new_value, Map/Node(map.value, map.left, new_map))

# Applies a funtion to a value on a Map
def Map/map (map: Map(T), key: u24, f: T -> T) -> Map(T):
match map:
case Map/Leaf:
return Map/Leaf
case Map/Node:
if (0 == key):
return Map/Node(Maybe/Some(f(Maybe/unwrap(map.value))), map.left, map.right)
elif ((key % 2) == 0):
return Map/Node(map.value, Map/map(map.left, (key / 2), f), map.right)
else:
return Map/Node(map.value, map.left, Map/map(map.right, (key / 2), f))

# IO Impl

Expand Down
15 changes: 15 additions & 0 deletions tests/golden_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,21 @@ fn io() {
})
}

/// Runs a file that uses the prelude.
#[test]
fn prelude() {
run_golden_test_dir(function_name!(), &|code, path| {
let _guard = RUN_MUTEX.lock().unwrap();
let book = parse_book_single_file(code, path)?;
let compile_opts = CompileOpts::default();
let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true);
let (term, _, diags) =
run_book(book, RunOpts::default(), compile_opts, diagnostics_cfg, None, "run-c")?.unwrap();
let res = format!("{diags}{term}");
Ok(format!("Strict mode:\n{res}"))
})
}

/// Runs all examples in the examples folder.
#[test]
fn examples() -> Result<(), Diagnostics> {
Expand Down
Loading
Loading