Skip to content

Commit

Permalink
Updates builtin type Map so that it stores Maybes in its nodes. (#743)
Browse files Browse the repository at this point in the history
  • Loading branch information
In-Veritas authored Dec 26, 2024
1 parent efc3b38 commit a52cce7
Show file tree
Hide file tree
Showing 29 changed files with 460 additions and 199 deletions.
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):
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))


# 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

0 comments on commit a52cce7

Please sign in to comment.