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

feat: Add more slice methods to the stdlib #5424

Merged
merged 6 commits into from
Jul 8, 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
11 changes: 3 additions & 8 deletions compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1117,16 +1117,11 @@ where
}

fn quote() -> impl NoirParser<ExpressionKind> {
token_kind(TokenKind::Quote).validate(|token, span, emit| {
let tokens = match token {
token_kind(TokenKind::Quote).map(|token| {
ExpressionKind::Quote(match token {
Token::Quote(tokens) => tokens,
_ => unreachable!("token_kind(Quote) should guarantee parsing only a quote token"),
};
emit(ParserError::with_reason(
ParserErrorReason::ExperimentalFeature("quoted expressions"),
span,
));
ExpressionKind::Quote(tokens)
})
})
}

Expand Down
70 changes: 65 additions & 5 deletions docs/docs/noir/concepts/data_types/slices.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ fn main() {
Applies a function to each element of the slice, returning a new slice containing the mapped elements.

```rust
fn map<U>(self, f: fn(T) -> U) -> [U]
fn map<U, Env>(self, f: fn[Env](T) -> U) -> [U]
```

example
Expand All @@ -213,7 +213,7 @@ Applies a function to each element of the slice, returning the final accumulated
parameter is the initial value.

```rust
fn fold<U>(self, mut accumulator: U, f: fn(U, T) -> U) -> U
fn fold<U, Env>(self, mut accumulator: U, f: fn[Env](U, T) -> U) -> U
```

This is a left fold, so the given function will be applied to the accumulator and first element of
Expand Down Expand Up @@ -247,7 +247,7 @@ fn main() {
Same as fold, but uses the first element as the starting element.

```rust
fn reduce(self, f: fn(T, T) -> T) -> T
fn reduce<Env>(self, f: fn[Env](T, T) -> T) -> T
```

example:
Expand All @@ -260,12 +260,72 @@ fn main() {
}
```

### filter

Returns a new slice containing only elements for which the given predicate returns true.

```rust
fn filter<Env>(self, f: fn[Env](T) -> bool) -> Self
```

example:

```rust
fn main() {
let slice = &[1, 2, 3, 4, 5];
let odds = slice.filter(|x| x % 2 == 1);
assert_eq(odds, &[1, 3, 5]);
}
```

### join

Flatten each element in the slice into one value, separated by `separator`.

Note that although slices implement `Append`, `join` cannot be used on slice
elements since nested slices are prohibited.

```rust
fn join(self, separator: T) -> T where T: Append
```

example:

```rust
struct Accumulator {
total: Field,
}

// "Append" two accumulators by adding them
impl Append for Accumulator {
fn empty() -> Self {
Self { total: 0 }
}

fn append(self, other: Self) -> Self {
Self { total: self.total + other.total }
}
}

fn main() {
let slice = &[1, 2, 3, 4, 5].map(|total| Accumulator { total });

let result = slice.join(Accumulator::empty());
assert_eq(result, Accumulator { total: 15 });

// We can use a non-empty separator to insert additional elements to sum:
let separator = Accumulator { total: 10 };
let result = slice.join(separator);
assert_eq(result, Accumulator { total: 55 });
}
```

### all

Returns true if all the elements satisfy the given predicate

```rust
fn all(self, predicate: fn(T) -> bool) -> bool
fn all<Env>(self, predicate: fn[Env](T) -> bool) -> bool
```

example:
Expand All @@ -283,7 +343,7 @@ fn main() {
Returns true if any of the elements satisfy the given predicate

```rust
fn any(self, predicate: fn(T) -> bool) -> bool
fn any<Env>(self, predicate: fn[Env](T) -> bool) -> bool
```

example:
Expand Down
30 changes: 30 additions & 0 deletions docs/docs/noir/standard_library/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ For primitive integer types, the return value of `default` is `0`. Container
types such as arrays are filled with default values of their element type,
except slices whose length is unknown and thus defaulted to zero.

---

## `std::convert`

Expand Down Expand Up @@ -85,6 +86,7 @@ For this reason, implementing `From` on a type will automatically generate a mat

`Into` is most useful when passing function arguments where the types don't quite match up with what the function expects. In this case, the compiler has enough type information to perform the necessary conversion by just appending `.into()` onto the arguments in question.

---

## `std::cmp`

Expand Down Expand Up @@ -178,6 +180,8 @@ impl<A, B, C, D, E> Ord for (A, B, C, D, E)
where A: Ord, B: Ord, C: Ord, D: Ord, E: Ord { .. }
```

---

## `std::ops`

### `std::ops::Add`, `std::ops::Sub`, `std::ops::Mul`, and `std::ops::Div`
Expand Down Expand Up @@ -301,3 +305,29 @@ impl Shl for u16 { fn shl(self, other: u16) -> u16 { self << other } }
impl Shl for u32 { fn shl(self, other: u32) -> u32 { self << other } }
impl Shl for u64 { fn shl(self, other: u64) -> u64 { self << other } }
```

---

## `std::append`

### `std::append::Append`

`Append` can abstract over types that can be appended to - usually container types:

#include_code append-trait noir_stdlib/src/append.nr rust

`Append` requires two methods:

- `empty`: Constructs an empty value of `Self`.
- `append`: Append two values together, returning the result.

Additionally, it is expected that for any implementation:

- `T::empty().append(x) == x`
- `x.append(T::empty()) == x`

Implementations:
```rust
impl<T> Append for [T]
impl Append for Quoted
```
35 changes: 35 additions & 0 deletions noir_stdlib/src/append.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Appends two values together, returning the result.
//
// An alternate name for this trait is `Monoid` if that is familiar.
// If not, it can be ignored.
//
// It is expected that for any implementation:
// - `T::empty().append(x) == x`
// - `x.append(T::empty()) == x`
// docs:start:append-trait
trait Append {
fn empty() -> Self;
fn append(self, other: Self) -> Self;
}
// docs:end:append-trait

impl<T> Append for [T] {
fn empty() -> Self {
&[]
}

fn append(self, other: Self) -> Self {
// Slices have an existing append function which this will resolve to.
self.append(other)
}
}

impl Append for Quoted {
michaeljklein marked this conversation as resolved.
Show resolved Hide resolved
fn empty() -> Self {
quote {}
}

fn append(self, other: Self) -> Self {
quote { $self $other }
}
}
1 change: 1 addition & 0 deletions noir_stdlib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod uint128;
mod bigint;
mod runtime;
mod meta;
mod append;

// Oracle calls are required to be wrapped in an unconstrained function
// Thus, the only argument to the `println` oracle is expected to always be an ident
Expand Down
29 changes: 29 additions & 0 deletions noir_stdlib/src/slice.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::append::Append;

impl<T> [T] {
#[builtin(array_len)]
pub fn len(self) -> u32 {}
Expand Down Expand Up @@ -85,6 +87,33 @@ impl<T> [T] {
accumulator
}

// Returns a new slice containing only elements for which the given predicate
// returns true.
pub fn filter<Env>(self, predicate: fn[Env](T) -> bool) -> Self {
let mut ret = &[];
for elem in self {
if predicate(elem) {
ret = ret.push_back(elem);
}
}
ret
}

// Flatten each element in the slice into one value, separated by `separator`.
pub fn join(self, separator: T) -> T where T: Append {
let mut ret = T::empty();

if self.len() != 0 {
ret = self[0];

for i in 1..self.len() {
ret = ret.append(separator).append(self[i]);
}
}

ret
}

// Returns true if all elements in the slice satisfy the predicate
pub fn all<Env>(self, predicate: fn[Env](T) -> bool) -> bool {
let mut ret = true;
Expand Down
7 changes: 7 additions & 0 deletions test_programs/compile_success_empty/slice_join/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "slice_join"
type = "bin"
authors = [""]
compiler_version = ">=0.31.0"

[dependencies]
16 changes: 16 additions & 0 deletions test_programs/compile_success_empty/slice_join/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use std::append::Append;

fn main() {
let slice = &[1, 2, 3, 4, 5];

let odds = slice.filter(|x| x % 2 == 1);
assert_eq(odds, &[1, 3, 5]);

let odds_and_evens = append_three(odds, &[100], &[2, 4]);
assert_eq(odds_and_evens, &[1, 3, 5, 100, 2, 4]);
}

fn append_three<T>(one: T, two: T, three: T) -> T where T: Append {
// The `T::empty()`s here should do nothing
T::empty().append(one).append(two).append(three).append(T::empty())
}
Loading