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

Proposal for generics-based Setter #201

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft

Conversation

medge-mailgun
Copy link
Contributor

@medge-mailgun medge-mailgun commented Sep 18, 2024

Using generics, it should now be possible to achieve setter functionality without reflection. If accepted, original methods will be marked as Deprecated but remain for existing usage.

A major change here is that slices and maps do not work with the setter.Default() method. setter.Slice() has been added thus far. setter.Map() has proven interesting to do effectively and usage within Mailgun has not yet been found.

Edit: while this was not done for performance reasons, rather as an attempt to simplify implementation code, here are the comparisons between approaches:

Running on an M3 Macbook yields the following benchmark results:

go test -bench="BenchmarkSetter*" -benchmem -count=10 -tags holster_test_mode -run="^$" ./...

goos: darwin
goarch: arm64
pkg: github.com/mailgun/holster/v4/setter
BenchmarkSetterNew-12    	1000000000	         0.9060 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12    	1000000000	         0.9524 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12    	1000000000	         0.9062 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12    	1000000000	         0.9053 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12    	1000000000	         0.9059 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12    	1000000000	         0.9024 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12    	1000000000	         0.9057 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12    	1000000000	         0.9052 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12    	1000000000	         0.9054 ns/op.       0 B/op	       0 allocs/op
BenchmarkSetterNew-12    	1000000000	         0.9056 ns/op	       0 B/op	       0 allocs/op

BenchmarkSetter-12       	210037180	         5.647 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12       	213309236	         5.626 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12       	210853106	         5.648 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12       	211399341	         5.635 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12       	213350558	         5.690 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12       	213267204	         5.671 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12       	210416016	         5.672 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12       	210588644	         5.678 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12       	210472189	         5.790 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12       	212123646	         5.683 ns/op	       0 B/op	       0 allocs/op

Updated benchmarks with a Slice test added:

goos: darwin
goarch: arm64
pkg: github.com/mailgun/holster/v4/setter
BenchmarkSetterNew-12          	1000000000	         0.9063 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12          	1000000000	         0.9057 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12          	1000000000	         0.9063 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12          	1000000000	         0.9061 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12          	1000000000	         0.9064 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12          	1000000000	         0.9054 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12          	1000000000	         0.9047 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12          	1000000000	         0.9048 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12          	1000000000	         0.9064 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew-12          	1000000000	         0.9060 ns/op	       0 B/op	       0 allocs/op

BenchmarkSetter-12             	213260366	         5.625 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12             	212896548	         5.636 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12             	211280826	         5.614 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12             	212247787	         5.660 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12             	211494753	         5.642 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12             	211180790	         5.643 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12             	211073300	         5.649 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12             	211085336	         5.660 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12             	205125676	         5.782 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetter-12             	205552485	         5.777 ns/op	       0 B/op	       0 allocs/op

BenchmarkSetterNew_Slice-12    	415313892	         2.892 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew_Slice-12    	414445138	         2.890 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew_Slice-12    	414308188	         2.894 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew_Slice-12    	413750835	         2.897 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew_Slice-12    	414357903	         2.888 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew_Slice-12    	414068553	         2.893 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew_Slice-12    	414783637	         2.894 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew_Slice-12    	413121838	         2.910 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew_Slice-12    	414325414	         3.040 ns/op	       0 B/op	       0 allocs/op
BenchmarkSetterNew_Slice-12    	414430587	         2.892 ns/op	       0 B/op	       0 allocs/op

BenchmarkSetter_Slice-12       	18901965	        62.86 ns/op	     112 B/op	       4 allocs/op
BenchmarkSetter_Slice-12       	18860769	        62.71 ns/op	     112 B/op	       4 allocs/op
BenchmarkSetter_Slice-12       	19149100	        62.84 ns/op	     112 B/op	       4 allocs/op
BenchmarkSetter_Slice-12       	19035922	        62.91 ns/op	     112 B/op	       4 allocs/op
BenchmarkSetter_Slice-12       	18961514	        62.92 ns/op	     112 B/op	       4 allocs/op
BenchmarkSetter_Slice-12       	18966596	        62.92 ns/op	     112 B/op	       4 allocs/op
BenchmarkSetter_Slice-12       	18814023	        63.76 ns/op	     112 B/op	       4 allocs/op
BenchmarkSetter_Slice-12       	18702063	        63.67 ns/op	     112 B/op	       4 allocs/op
BenchmarkSetter_Slice-12       	18443803	        63.64 ns/op	     112 B/op	       4 allocs/op
BenchmarkSetter_Slice-12       	18936728	        63.31 ns/op	     112 B/op	       4 allocs/op

@thrawn01
Copy link
Contributor

thrawn01 commented Sep 18, 2024 via email

@medge-mailgun
Copy link
Contributor Author

medge-mailgun commented Sep 18, 2024

@thrawn01 good to know! The change was mostly intended to be a code reduction change vs. a performance improvement, for sure. The benchmark was added purely for curiosity's sake 🙏
Caveat, of course, is that this doesn't seem to work for Slices and Maps, so may be less of a code reduction than originally hoped 😓


// IsZeroNew compares the given value to its Golang-specified zero value.
// It works for any type T that satisfies comparable.
func IsZeroNew[T comparable](value T) bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

should it be exportable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question. The original method is both exported and used in a few libraries so that was the motivation, though I'm still pondering if it's needed 🤔

Copy link
Contributor

@vtopc vtopc Sep 20, 2024

Choose a reason for hiding this comment

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

It is better to make it non-exportable and who needs could copy it as it's pretty small:

A little copying is better than a little dependency.

https://go-proverbs.github.io/

Well, I just don't like this name 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Bahaha I don't like it either :D I'll leave the existing method (IsZero) exported and un-export this one, given the restricted use 👍

assert.Equal(t, false, setter.IsZeroNew(thing))
}

// Not possible now given compiler warnings
Copy link
Contributor

Choose a reason for hiding this comment

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

In what Go version?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a good point - this change would not work pre-Go generics, as far as I am aware. So Go 1.18+?

Copy link
Contributor

Choose a reason for hiding this comment

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

We are not running it in Go 1.18 - https://github.com/mailgun/holster/blob/master/.github/workflows/test.yaml#L19-L20

Are you seeing these warnings on your local env?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's correct. And that's why these tests are commented out. It's not possible to do what these tests did previously with the new methods. That's what its demonstrating 👀

Mind you, the name of the methods changed. I can fix that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And to be explicit: this test was previously trying to assign an int to a string, which you technically could do in the original method. The generics form doesn't allow that. It's a compilation error

Copy link
Contributor

Choose a reason for hiding this comment

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

And to be explicit: this test was previously trying to assign an int to a string, which you technically could do in the original method. The generics form doesn't allow that. It's a compilation error

I got it now.

Yeah, we don't need them at all, please remove the commented code. https://kentcdodds.com/blog/please-dont-commit-commented-out-code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, all that will be removed once we settle on the PR final form 👍 I just left them there as a note to reviewers!

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