-
Notifications
You must be signed in to change notification settings - Fork 270
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
refactor: unify Zero / Empty to Default + refactor away from std::unsafe::zeroed() #5496
Conversation
0586874
to
95d2e36
Compare
eef1ae4
to
95d2e36
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't compile
Changes to circuit sizes
🧾 Summary (100% most significant diffs)
Full diff report 👇
|
noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/common.nr
Outdated
Show resolved
Hide resolved
noir-projects/noir-protocol-circuits/crates/public-kernel-lib/src/public_kernel_app_logic.nr
Outdated
Show resolved
Hide resolved
isEmpty() { | ||
return this.selector.isEmpty(); | ||
isDefault(_private: boolean) { | ||
return this.selector.isDefault() && _private == this.isPrivate; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the _private
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seemed like before we were only checking if the selector was empty, and not taking into consideration this.isPrivate. All we do here is now we are checking both cases explicitly, if isPrivate is true, and we pass in private, this means that we are checking for a private default case and now our isDefault check now correctly accounts for this
makeTuple(MAX_NEW_NOTE_HASHES_PER_TX, SideEffect.empty), | ||
makeTuple(MAX_NEW_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.empty), | ||
makeTuple(MAX_NEW_L2_TO_L1_MSGS_PER_TX, Fr.zero), | ||
Fr.zero(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inconsistency here hurts me =/ I get the Fr.default()
and Fr.ZERO
for comparisons, but mixing them up on the same struct...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, Fr.default is the func, but Fr.ZERO is the explicit zero...
makeTuple(MAX_NEW_NOTE_HASHES_PER_TX, SideEffect.default), | ||
makeTuple(MAX_NEW_NULLIFIERS_PER_TX, SideEffectLinkedToNoteHash.default), | ||
makeTuple(MAX_NEW_L2_TO_L1_MSGS_PER_TX, Fr.default), | ||
Fr.ZERO, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
static empty() { | ||
return new NullifierKeyValidationRequest(Point.ZERO, GrumpkinScalar.ZERO); | ||
static default() { | ||
return new NullifierKeyValidationRequest(Point.default(), GrumpkinScalar.ZERO); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, mixing default and zero is kinda confusing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GrumpkinScalar is a Fq, and I didn't really want to mess with it :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we still need to think about these changes a little. Somehow I feel we have ended up with a less expressive API that is a lot more difficult to read. Maybe it's just me, but when we extend the default
semantics too much we end up with changes like renaming nonEmptyNullifiers
to nonDefaultNullifiers
or calling empty arrays defaultWhatever
, which seems very weird to me.
I see things like this:
default
is for creating, and asigning a well defined default value (not necessarily zero) to something when instantiating it. Specially useful when building complex structures that have to be initialized in a very specific way.zero
is used for numeric and otherwise simple values (addresses, for example), in which the number 0 has mathematical significance. Calling itdefault
just makes it unnecessarily difficult to readempty
is for empty vecs (orBoundedVecs
in Noir, I know they're technically not empty)
With this approach, implementing something like is_default
becomes a lot more nuanced, since we want the ability to just use trait bounds (very convenient) to do things, but we (I?) don't want to end up with things like if myVeryCoolArray.is_default() { ...do stuff }
because as a dev, when I read that my brain goes:
"Why the hell is this guy calling a function to check if a simple array is empty? Maybe there's more to a it than I thought? Is my understanding of the world a lie?" - Grego's head, probably
If the rest of the team is in agreement over these changes, feel free to ignore my ramblings 🤣
I think you make a good, fair, point, and I generally like your split of default / zero / empty. I just think empty as a keyword has its issues as well. Given that we really never have any empty arrays / vecs at least when we're using anything that is trying to determine whether the array is "empty". They are technically not empty, and they are technically arrays full of some sort of value (even if just 0), whether the terminology for said value is "default" or something else, I think this should be clear and we should not just use "empty" for this case, or at least think about this a bit more as well and try to find some way to convey the above. For me, the ::new() constructor serves the purpose that your proposed ::default(), so I don't see that as an issue. The point of the highest convergence in thinking is in the case we have numerical significance, and which is why I think it's fair to keep a .ZERO static and maybe even helper fns isZero() etc, and use .ZERO when the zero value has mathematical significance, and default otherwise, even though they may be the same value under the hood. Of course also not married strongly to anything here, but do think that there are benefits in not hard-coding the zero / "default" values. It's similar to #3595, if a default value changes, needing to manually propagate it could be a huge footgun. |
Docs PreviewHey there! 👋 You can check your preview at https://66165e52a472580f1d5d6b1e--aztec-docs-dev.netlify.app |
@sklppy88 Grego has a point. AztecAddress::default() is just much less clear than AztecAddress::zero(). Sorry that I originally proposed using default for everything. It was a mistake. Regarding this:
When you get a length of these @sklppy88 Could you refactor this and follow Grego's approach? If you are unsure about some other scenario please just ask in our team channel. |
I've added #5618 to address in another PR |
yarn formatting fix
Closed in favor of #5685 |
resolves #4638.
Removes Empty trait, is_empty, is_zero, ...::zero(), and ...::empty() in favor of using std::Default trait
Also did some cleanup after seeing both is_empty(T) and T.is_empty() used. Cleaned to all use is_empty(T).
Removed usage of std::unsafe::zeroed()
Also removed assert_is_zero because it was not used and I assume a remnant from the past
Aligned ts to new changes