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

use generic pointers #123

Closed
wants to merge 2 commits into from
Closed

Conversation

Stebalien
Copy link
Member

@Stebalien Stebalien commented Aug 2, 2024

It sucks, but, as far as I know, this is the only way to avoid reflection and other pointy nastiness.

After applying @Wondertan, it's less terrible.

It sucks, but, as far as I know, this is the only way to avoid
reflection and other pointy nastiness.
@Stebalien
Copy link
Member Author

Comment on lines -227 to -229
valueType := reflect.TypeOf(t.Value).Elem()
value := reflect.New(valueType).Interface().(T)

Copy link

@Wondertan Wondertan Aug 2, 2024

Choose a reason for hiding this comment

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

Another arguably cleaner option is to add New parametrized method on HamtValue. Then, everywhere you need to instantiate a new value of parametrized typed, you do smth like:

var v V
v = v.New()

and keep your type safety without any!

Copy link

@Wondertan Wondertan Aug 2, 2024

Choose a reason for hiding this comment

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

The next step towards making it even more ergonomic is a generic constructor func:

type HamtValue[V any] interface {
  New() V
}

func New[V HamtValue[V]() (v V){
  return v.New()
}

Then, anywhere you need generic instantiation you simply use the generic New func.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm. You're right. We'd also need an IsNil(), but that works much better.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, we don't even need that.

Choose a reason for hiding this comment

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

Or IsZero() to be a bit more idiomatic.

Copy link
Member Author

Choose a reason for hiding this comment

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

Pushed. Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

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

@Wondertan I just used Equal(zeroValue).

type HamtValue[T any] interface {
Equal(T) bool
type HamtValue[V any] interface {
New() V
Copy link
Member

Choose a reason for hiding this comment

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

I can't say I love this bit, although it's nicer than seeing **

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't love it either, but it beats my previous approach and, as you say, beats **.

Comment on lines +44 to +52
func zero[T any]() T {
var zero T
return zero
}

// Returns true if the hamt value equals the zero value.
func isHamtValueZero[T HamtValue[T]](v T) bool {
return v.Equal(zero[T]())
}
Copy link
Member

Choose a reason for hiding this comment

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

When using a pointer type, this ends up checking for nil, right? that's what we want for those cases; I'm slightly concerned about valid zero values being the desired case where we are using this to check for "should delete". If you want to insert the zero value in, then you have no way to achieve that now.

I think we might be able to get around some of this awkwardness by refactoring modifyValue because it's currently overloaded for the insert & delete case but the public API has a Delete method so we should strictly apply set and delete semantics to the API and then have the freedom internally to chase that intent down to the bottom.

Copy link
Member Author

Choose a reason for hiding this comment

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

When using a pointer type, this ends up checking for nil, right?

Yes.

valid zero values being the desired case where we are using this to check for "should delete".

Yeah..., but that only matters if you want 'nil' to be a valid value.

I think we might be able to get around some of this awkwardness by refactoring modifyValue because it's currently overloaded for the insert & delete case but the public API has a Delete method so we should strictly apply set and delete semantics to the API and then have the freedom internally to chase that intent down to the bottom.

I think this is the way to go, but I'd do this in a followup patch.

Copy link
Member

Choose a reason for hiding this comment

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

@Stebalien

Yeah..., but that only matters if you want 'nil' to be a valid value.

But with the current form here we're not limiting the generic type to be a pointer, it could be a Foo{ id int } in which case the empty value of {id:0} may be what you want to store.

Maybe this can just be clarified with docs though, recommend use of a pointer type.

@rvagg
Copy link
Member

rvagg commented Aug 5, 2024

I've pulled this in to rvagg/generics and made some additional modifications, including creating and holding the zero value on the Node and its children instead of making a new one every time we pass through modifyValue(). (Although, I can't say I measured a huge perf difference when doing this in filecoin-project/go-state-types#298).

@rvagg rvagg closed this Aug 5, 2024
@rvagg rvagg deleted the steb/generic-pointers branch August 5, 2024 10:51
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