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

Add support for iterators #169

Merged
merged 13 commits into from
Sep 2, 2024
Merged

Add support for iterators #169

merged 13 commits into from
Sep 2, 2024

Conversation

gio54321
Copy link
Contributor

@gio54321 gio54321 commented Aug 27, 2024

This PR aims at addressing issue #132 by introducing the possibility to iterate over arrays.
This makes it possible to write a circuit like this

fn zip(a1: [Field; 3], a2: [Field; 3]) -> [[Field; 2]; 3] {
    let mut result = [[a1[0], a1[0]], [a1[0], a1[0]], [a1[0], a1[0]]];
    for index in 0..3 {
        result[index] = [a1[index], a2[index]];
    }
    return result;
}

fn main(pub arr: [Field; 3]) -> Field {
    let expected = [1, 2, 3];
    for pair in zip(arr, expected) {
        assert_eq(pair[0], pair[1]);
    }

    return arr[0];
}

This also improves the iterate.no test as mentioned in the issue

fn House.windows(house: House) -> Field {
    let mut windows_count = 0;
    for room in house.rooms {
        windows_count = windows_count + room.windows();
    }

    return windows_count;
}

Copy link
Collaborator

@katat katat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

Since this is also highly related to the const generic feature , so I think it'd better to be based on the generic PR branch #147.

// for i in 0..5 { ... }
// ^
let (end, end_span) = match tokens.bump(ctx) {
// here we decide if this for loop is over a range or is an iterator,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not always the case once the generic feature is introduced. For example:

fn range(const START: Field, const END: Field) {
    for ii in START..END { // START / END are const generic variables, which are not in numeric form in the syntax.
        ...
    }
}

So I think the safer way to detect whether it is range or iterator can be based on the presence of .., which indicates an range.

// for now this means that the expression should have type `Array`
let iterator_typ = self.compute_type(iterator, typed_fn_env)?;
let iterator_typ =
iterator_typ.expect("Could not compute type of iterator (TODO: better error)");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be an error thrown with a span, so it can better inform the users where the error is originated in the code.

// exit the scope
typed_fn_env.pop();
} else {
panic!("Iterator should be an array (TODO: better error)");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same ^

@@ -5,6 +5,7 @@ use kimchi::circuits::wires::Wire;
use num_bigint::BigUint;
use num_traits::Num as _;
use serde::{Deserialize, Serialize};
use toml::value::Array;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems unused

@gio54321 gio54321 changed the base branch from main to rfc-0-impl August 28, 2024 08:20
@gio54321
Copy link
Contributor Author

Should have addressed most of the things, now merging into rfc-0-impl

One thing to note is that for now the iterator variable is immutable, maybe we could consider adding also mutable iterator variables? For example this code would add one to every element of arr

for mut elem in array {
    elem = elem + 1
}

though I don't necessarily think that this is a good pattern and I tend to think this should not be allowed, since to me it is not clear if this is mutating the underlying structure or just the local variable elem.

Copy link
Collaborator

@katat katat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. I think that is a good spot. We may come back to this mutable feature if the practical projects really demands it.

There are just a couple of minor issues to fix. The main one is to add the new iterator example to tests. Maybe rename the iterators.no to generic_iterator.no as it involves generic and helps differentiate from the existing iterator.no.

@@ -0,0 +1,16 @@
fn zip(a1: [Field; LEN], a2: [Field; LEN]) -> [[Field; 2]; LEN] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this new example should be added as a new test in the tests/examples.rs

iterator,
body,
} => {
let iterator_expr = self
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it is better to call it as iterator_var, since it is of VarOrRef after the expression has been computed.

src/mast/mod.rs Outdated

mono_fn_env.store_type(
&var.value,
&MTypeInfo::new(&array_element_type, var.span, None),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary & prefix

TyKind::Array(element_type, _len) => *element_type,
TyKind::GenericSizedArray(element_type, _size) => *element_type,
_ => {
return Err(self.error(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a negative test (negative_tests.rs) to cover this scenario, this would help remind this type check should be covered in the tast phase if it happens to refactor at later stage :D

Copy link
Collaborator

@katat katat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work!

This feature is a great fit with the generic arrays. The generic feature enable working with varying length of arrays, while this iterator feature simplifies the use of these generic array and make it practical.

@katat katat merged commit fd1d827 into zksecurity:rfc-0-impl Sep 2, 2024
1 check passed
var: Ident,
iterator: Box<Expr>,
body: Vec<Stmt>,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

late to the party, but wouldn't it be cleaner (and perhaps safer since forloops have annoying logic for generics) to have a common structure for them?

ForLoop {
  var: Ident,
  range: RangeOrIterator,
  body: Vec<Stmt>,
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cf #177

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants