Skip to content

Commit

Permalink
Implement builtin type Map, and its corresponding functions, as well …
Browse files Browse the repository at this point in the history
…as the Maybe type

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

Implement builtin type Map, and its corresponding functions, as well as the Maybe type

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

Changes made on  09-12-2024, includes fixing requests from #743

Fixes based on PR review

Partially updates doc and follows requests from PR review

Fixes documentation, makes tests return simpler values, deletes useless snapshot

Removes map_tests.bend.snap
  • Loading branch information
In-Veritas committed Dec 26, 2024
1 parent efc3b38 commit 7048390
Show file tree
Hide file tree
Showing 30 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 7048390

Please sign in to comment.