Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project does not currently adhere to a particular versioning scheme.

## [Unreleased]

### Added

- Add core `List` combinators to the standard library: `map`, `fold`, `sum`, `all`, `any`, `find`, `contains`, `take`, `drop`, `at`, `zip`, and `range`.

## [0.2.38] - 2025-02-23

### Added
Expand Down
133 changes: 133 additions & 0 deletions docs/builtins.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,139 @@ Example:
# Result: Result/Ok/tag ([1, 3], [5, 6])
```

#### List/map

```python
#{
Applies a function to each element of a list, returning a new list.
#}
def List/map(xs: List(T), f: T -> U) -> List(U):
```
Example:
```python
List/map([1, 2, 3], λx: x + 1)
# [2, 3, 4]
```

#### List/fold

```python
#{
Folds a list from the left, combining each element with an accumulator.
#}
def List/fold(xs: List(T), init: A, f: A -> T -> A) -> A:
```
Example:
```python
List/fold([1, 2, 3], 0, λacc, x: acc + x)
# 6
```

#### List/sum

```python
#{
Sums a list of unsigned numbers.
#}
def List/sum(xs: List(u24)) -> u24:
```

#### List/all

```python
#{
Returns 1 if all elements satisfy the predicate, 0 otherwise. Short-circuits.
#}
def List/all(xs: List(T), pred: T -> u24) -> u24:
```

#### List/any

```python
#{
Returns 1 if any element satisfies the predicate, 0 otherwise. Short-circuits.
#}
def List/any(xs: List(T), pred: T -> u24) -> u24:
```

#### List/find

```python
#{
Returns the first element satisfying the predicate as `Maybe/Some`, or `Maybe/None`.
#}
def List/find(xs: List(T), pred: T -> u24) -> Maybe(T):
```

#### List/contains

```python
#{
Returns 1 if the value occurs in the list, 0 otherwise.
#}
def List/contains(xs: List(u24), val: u24) -> u24:
```

#### List/take

```python
#{
Returns the first `n` elements of a list (or the whole list if shorter).
#}
def List/take(xs: List(T), n: u24) -> List(T):
```
Example:
```python
List/take([1, 2, 3, 4], 2)
# [1, 2]
```

#### List/drop

```python
#{
Removes the first `n` elements of a list (empty if `n` exceeds length).
#}
def List/drop(xs: List(T), n: u24) -> List(T):
```

#### List/at

```python
#{
Returns the element at index `i` as `Maybe/Some`, or `Maybe/None` if out of bounds.
#}
def List/at(xs: List(T), i: u24) -> Maybe(T):
```

#### List/zip

```python
#{
Pairs elements of two lists, truncating to the shorter length.
#}
def List/zip(xs: List(A), ys: List(B)) -> List((A, B)):
```
Example:
```python
List/zip([1, 2, 3], [4, 5])
# [(1, 4), (2, 5)]
```

#### List/range

```python
#{
Returns the list of numbers in the half-open interval [start, end).
#}
def List/range(start: u24, end: u24) -> List(u24):
```
Example:
```python
List/range(0, 5)
# [0, 1, 2, 3, 4]
```

## Result

```python
Expand Down
123 changes: 123 additions & 0 deletions src/fun/builtins.bend
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,129 @@ List/filter (List/Cons x xs) pred =
(List/filter xs pred)
}

#{ Applies a function to each element of a list, returning a new list. #}
def List/map(xs: List(T), f: T -> U) -> List(U):
fold xs:
case List/Nil:
return List/Nil
case List/Cons:
return List/Cons(f(xs.head), xs.tail)

#{ Folds a list from the left, combining each element with an accumulator. #}
def List/fold(xs: List(T), init: A, f: A -> T -> A) -> A:
fold xs with acc=init:
case List/Nil:
return acc
case List/Cons:
return xs.tail(f(acc, xs.head))

#{ Sums a list of unsigned numbers. #}
def List/sum(xs: List(u24)) -> u24:
fold xs:
case List/Nil:
return 0
case List/Cons:
return xs.head + xs.tail

#{ Returns 1 if all elements satisfy the predicate, 0 otherwise. Short-circuits. #}
def List/all(xs: List(T), pred: T -> u24) -> u24:
match xs:
case List/Nil:
return 1
case List/Cons:
if pred(xs.head):
return List/all(xs.tail, pred)
else:
return 0

#{ Returns 1 if any element satisfies the predicate, 0 otherwise. Short-circuits. #}
def List/any(xs: List(T), pred: T -> u24) -> u24:
match xs:
case List/Nil:
return 0
case List/Cons:
if pred(xs.head):
return 1
else:
return List/any(xs.tail, pred)

#{ Returns the first element satisfying the predicate as `Maybe/Some`, else `Maybe/None`. #}
def List/find(xs: List(T), pred: T -> u24) -> Maybe(T):
match xs:
case List/Nil:
return Maybe/None
case List/Cons:
if pred(xs.head):
return Maybe/Some(xs.head)
else:
return List/find(xs.tail, pred)

#{ Returns 1 if `val` occurs in the list, 0 otherwise. #}
def List/contains(xs: List(u24), val: u24) -> u24:
match xs:
case List/Nil:
return 0
case List/Cons:
if xs.head == val:
return 1
else:
return List/contains(xs.tail, val)

#{ Returns the first `n` elements of a list (or the whole list if shorter). #}
def List/take(xs: List(T), n: u24) -> List(T):
match xs:
case List/Nil:
return List/Nil
case List/Cons:
switch n:
case 0:
return List/Nil
case _:
return List/Cons(xs.head, List/take(xs.tail, n-1))

#{ Removes the first `n` elements of a list (empty if `n` exceeds length). #}
def List/drop(xs: List(T), n: u24) -> List(T):
match xs:
case List/Nil:
return List/Nil
case List/Cons:
switch n:
case 0:
return List/Cons(xs.head, xs.tail)
case _:
return List/drop(xs.tail, n-1)

#{ Returns the element at index `i` as `Maybe/Some`, or `Maybe/None` if out of bounds. #}
def List/at(xs: List(T), i: u24) -> Maybe(T):
match xs:
case List/Nil:
return Maybe/None
case List/Cons:
switch i:
case 0:
return Maybe/Some(xs.head)
case _:
return List/at(xs.tail, i-1)

#{ Pairs elements of two lists, truncating to the shorter length. #}
def List/zip(xs: List(A), ys: List(B)) -> List((A, B)):
match xs:
case List/Nil:
return List/Nil
case List/Cons:
match ys:
case List/Nil:
return List/Nil
case List/Cons:
return List/Cons((xs.head, ys.head), List/zip(xs.tail, ys.tail))

#{ Returns the list of numbers in the half-open interval [start, end). #}
def List/range(start: u24, end: u24) -> List(u24):
if start < end:
return List/Cons(start, List/range(start + 1, end))
else:
return List/Nil

#{ Checks if two strings are equal. #}
String/equals (s1: String) (s2: String) : u24
String/equals (String/Nil) (String/Nil) = 1
Expand Down
32 changes: 32 additions & 0 deletions tests/golden_tests/run_file/list_combinators.bend
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
def double(x: u24) -> u24:
return x * 2

def add(a: u24, b: u24) -> u24:
return a + b

def is_even(x: u24) -> u24:
return (x % 2) == 0

def gt3(x: u24) -> u24:
return x > 3

def sum_pair(p: (u24, u24)) -> u24:
(x, y) = p
return x + y

def main() -> List(u24):
xs = List/range(1, 6)
a = List/sum(List/map(xs, double))
b = List/fold(xs, 0, add)
c = List/sum(xs)
d = List/all(xs, is_even)
e = List/any(xs, is_even)
f = List/contains(xs, 3)
g = List/contains(xs, 99)
h = List/sum(List/take(xs, 2))
i = List/sum(List/drop(xs, 2))
j = Maybe/unwrap(List/find(xs, gt3))
k = Maybe/unwrap(List/at(xs, 1))
l = List/sum(List/take(xs, 99))
m = List/sum(List/map(List/zip(xs, xs), sum_pair))
return [a, b, c, d, e, f, g, h, i, j, k, l, m]
9 changes: 9 additions & 0 deletions tests/snapshots/run_file__list_combinators.bend.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: tests/golden_tests.rs
input_file: tests/golden_tests/run_file/list_combinators.bend
---
NumScott:
[30, 15, 15, 0, 1, 1, 0, 3, 12, 4, 2, 15, 30]

Scott:
[30, 15, 15, 0, 1, 1, 0, 3, 12, 4, 2, 15, 30]