golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
121.35k stars 17.38k forks source link

text/template: range over func and int #66107

Open akclace opened 4 months ago

akclace commented 4 months ago

Proposal Details

The Rangefunc change is adding support for range on custom functions.

The text/html template std library has a range operator which is documented as

{{range pipeline}} T1 {{end}}
    The value of the pipeline must be an array, slice, map, or channel

The template library should support range over a function. The use case is wanting to return a Rangefunc on top of a sql rows iterator which can be passed to the template for text generation.

This was the discussion in the group https://groups.google.com/g/golang-nuts/c/_lykyoQHmGc

Merovius commented 4 months ago

I think we need to define some semantics and I think there are likely open questions (and in part without obvious answers).

First, obviously, there are one- and two-variable iterators. What should . be set to in {{ range $x }}, if $x is iter.Seq2[A, B]? For map[K]V we set it to the values, which would indicate it should be B, for consistency. On the other hand, there is no a priori guarantee that the second argument of an iter.Seq2 really is necessarily "value-like".

Then we have the range form {{ range $x, $y := $z }}, that is, a range action that initializes one or two variables. Presumably, using two variables with an iter.Seq will be an error. Should it also be an error to use one variable with an iter.Seq2 (see also #65236)?

Lastly: If calling a function or method in a template (either by using a function name as an action, or using the call function), the function can optionally return a second argument of type error: If that error is not nil, execution of the template is aborted and the error is returned. This brings up the question of how to handle iter.Seq2[T, error].

AIUI it is not entirely clear how error handling with iterators will work, really. For example, if you have an iterator that reads lines from a file, parsing each line into a struct, there are two ways it can fail: 1. a Read can fail, fatally aborting iteration or 2. the parsing of a single line can fail, allowing to handle the error of that line differently, but continue iteration. Some have suggested using an iter.Seq2[T, error] for both, in which case a template action ranging over it should probably abort if an error occurs. On the other hand, it might be reasonable to want to still handle that error from the template and continue iteration as well - and only abort on "fatal" errors. I'm not sure how much we want to make a policy choice about that at this point.

An alternative I could imagine is to say that you can range over an iter.Seq2 in three ways: 1. {{ range $it }}, which yields the T and aborts template execution if the second argument is an error, 2. {{ range $x := $it }} which behaves the same (but sets $x instead of .) and 3. {{ range $x, $err := $it }}, which does not abort on errors, allowing the template to handle it as it pleases. This would effectively put the control over what to do into the hands of the template author.

If we were to go with that suggestion, we would probably want to make {{ range $it }}/{{ range $x := $it }} yield the As when applying it to an iter.Seq2[A, B] - inconsistently with maps, but more consistently with iter.Seq2[T, error].

At least those are my thoughts. There are probably other ways to define these semantics.

earthboundkid commented 4 months ago

Presumably, using two variables with an iter.Seq2 will be an error.

I assume this is a typo for iter.Seq.

Merovius commented 4 months ago

@earthboundkid Correct, edited, thanks.

rsc commented 4 months ago

Regarding the semantics, it seems like we should require that if the iterator yields 2 values, then you have to use the {{range $x, $y := $f}} form.

rsc commented 4 months ago

This proposal has been added to the active column of the proposals project and will now be reviewed at the weekly proposal review meetings. — rsc for the proposal review group

leb-kuchen commented 4 months ago

It would be consistent to allow ranging over int as well.

rsc commented 3 months ago

This seems fine but we should wait for #66056 to help with the implementation.

rsc commented 2 months ago

Based on the discussion above, this proposal seems like a likely accept. — rsc for the proposal review group

The proposal is to allow range over func and int the same as in Go.

rsc commented 2 months ago

No change in consensus, so accepted. 🎉 This issue now tracks the work of implementing the proposal. — rsc for the proposal review group

The proposal is to allow range over func and int the same as in Go.

gopherbot commented 1 week ago

Change https://go.dev/cl/596956 mentions this issue: text/template: support range-over-func