Conversation
|
I'm struggling a bit with trying to implement a sort function. I wanted to use Obviously, if a slice contains elements that are different types, it cannot be sorted in this way, so I would need to somehow check if every element in the slice is the same type, and that type implements I've tried a type switch, and I've tried reflection, but it always goes to the default case and just returns the existing list. |
|
We could create a |
| // isZeroValue returns whether a value is the zero value for its type. | ||
| // An empty array, map or slice is assumed to be a zero value. | ||
| func isZeroValue(v any) bool { | ||
| vval := reflect.ValueOf(v) | ||
| if !vval.IsValid() { | ||
| return true | ||
| } | ||
| switch vval.Kind() { //nolint:exhaustive | ||
| case reflect.Array, reflect.Map, reflect.Slice, reflect.String: | ||
| return vval.Len() == 0 | ||
| case reflect.Bool: | ||
| return !vval.Bool() | ||
| case reflect.Complex64, reflect.Complex128: | ||
| return vval.Complex() == 0 | ||
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
| return vval.Int() == 0 | ||
| case reflect.Float32, reflect.Float64: | ||
| return vval.Float() == 0 | ||
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | ||
| return vval.Uint() == 0 | ||
| case reflect.Struct: | ||
| return false | ||
| default: | ||
| return vval.IsNil() | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Could this be replaced with something like
truth, ok := template.IsTrue(v)
if !ok {
panic(fmt.Sprintf("unable to determine zero value for %v", v))
}
return !truth?
twpayne
left a comment
There was a problem hiding this comment.
Thanks for this, but there are several problems. See the individual review comments.
The underlying problem is that we're trying to build dynamically-typed template functions on top of strict, statically-typed Go functions, and an extra conversion layer is needed.
| If *list* cannot be sorted, it is simply returned. | ||
|
|
||
| ```text | ||
| {{ list list "c" "a" "b" | sort }} |
| func NewFuncMap() template.FuncMap { | ||
| return template.FuncMap{ | ||
| "compact": compactTemplateFunc, | ||
| "concat": slices.Concat[[]any], |
There was a problem hiding this comment.
This needs to be more generic so that it can accept []string and other slices. Note that Go cannot automatically convert []any to []string. See https://blog.merovius.de/posts/2018-06-03-why-doesnt-go-have-variance-in/ if you want the gory details.
| "contains": reverseArgs2(strings.Contains), | ||
| "eqFold": eqFoldTemplateFunc, | ||
| "fromJSON": eachByteSliceErr(fromJSONTemplateFunc), | ||
| "has": reverseArgs2(slices.Contains[[]any]), |
| case reflect.Uint: | ||
| return convertAndSortSlice[uint](list) | ||
| case reflect.Uint8: | ||
| return convertAndSortSlice[uint8](list) |
There was a problem hiding this comment.
This will not work as expected, as []byte is an alias for []uint8 and in other template functions we treat []bytes as strings.
| } | ||
| } | ||
|
|
||
| switch firstElemType.Kind() { //nolint:exhaustive |
There was a problem hiding this comment.
No need to be exhaustive here. It's sufficient to cover the common types (int, float64, string, and bool).
There was a problem hiding this comment.
If I don't disable exhaustive the linter complains because I'm not accounting for the other Kind cases:
missing cases in switch of type reflect.Kind: reflect.Invalid, reflect.Bool, reflect.Complex64, reflect.Complex128, reflect.Array, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer|reflect.Ptr, reflect.Slice, reflect.Struct, reflect.UnsafePointer (exhaustive)
There was a problem hiding this comment.
You can use a default: case which panics with an "unsupported type" message.
There was a problem hiding this comment.
Hmm, in that case feel free to disregard/disable the linter. Note that this exhaustive checking might be covered by multiple linters and you might need to disable all of them. Also, check to see if the warning is coming from golangci-lint with chezmoi's configuration, or if it's an extra linter being run by your IDE.
There was a problem hiding this comment.
https://pkg.go.dev/github.com/nishanths/exhaustive#hdr-Definition_of_exhaustiveness
We can add the -default-signifies-exhaustive flag to the linter if you'd prefer.
There was a problem hiding this comment.
Also, I'm using golangci-lint as the linter in VS Code with Error Lens, as well as Run on Save to format:
{
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"emeraldwalk.runonsave": {
"commands": [
{
"match": "\\.go$",
"cmd": "gci write ${file} --skip-generated -s standard -s default -s prefix(github.com/chezmoi/templatefuncs)"
},
{
"match": "\\.go$",
"cmd": "golines --base-formatter=\"gofumpt -extra\" --max-len=128 --write-output ${file}"
}
]
}
}| } | ||
|
|
||
| // sortTemplateFunc is the core implementation of the `sort` template function. | ||
| func sortTemplateFunc(list []any) any { |
There was a problem hiding this comment.
Trying to write a generic sort function is hard. I would instead have separate sortInts and sortFloat64s functions, with sort converting all arguments to strings and then sorting those.
There was a problem hiding this comment.
Is what I described as terrible sort roughly sufficient?
|
|
||
| // uniqTemplateFunc is the core implementation of the `uniq` template function. | ||
| func uniqTemplateFunc(list []any) []any { | ||
| seen := make(map[any]struct{}) |
There was a problem hiding this comment.
This will not work as expected. For example, see https://go.dev/play/p/qemCBLcOyPh. I think it can fail in other ways too.
|
I think this highlights that we need more robust test data. I didn't notice most of these because I was only testing with It would help to test with some predefined |
|
Okay, I'm going to essentially start over in some new commits. First up is (a hopefully better) |
|
I might squash and merge this tomorrow (unless there are more mistakes) as I can't really come up with any more list function implementations that I'm happy with at the moment. It could benefit from a quality pass for error message consistency etc. and also to revert the Go version change, as it's only necessary for the I can make I'm also happy to change if v.Kind() != reflect.Slice {
...
}to a |


WIP