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

content: Describe result of delimiting an empty collection #2906

Open
willfaught opened this issue Feb 14, 2025 · 18 comments
Open

content: Describe result of delimiting an empty collection #2906

willfaught opened this issue Feb 14, 2025 · 18 comments
Labels

Comments

@willfaught
Copy link
Contributor

https://gohugo.io/functions/collections/delimit/:

collections.Delimit
Loops through any array, slice, or map and returns a string of all the values separated by a delimiter.
Syntax
collections.Delimit COLLECTION DELIMITER [LAST]
Returns
string
Alias
delimit
Delimit a slice:

{{ $s := slice "b" "a" "c" }}
{{ delimit $s ", " }} → b, a, c
{{ delimit $s ", " " and "}} → b, a and c
Delimit a map:

The delimit function sorts maps by key, returning the values.

{{ $m := dict "b" 2 "a" 1 "c" 3 }}
{{ delimit $m ", " }} → 1, 2, 3
{{ delimit $m ", " " and "}} → 1, 2 and 3

Presumably it returns the empty string, but it should say that.

@jmooring
Copy link
Member

I really don't want to go down this path, because the next ask is, "We need to document what happens with collections.Last with an empty collection." Etc. Etc.

@jmooring jmooring transferred this issue from gohugoio/hugo Feb 14, 2025
@jmooring jmooring changed the title Delimit doc doesn't specify what happens for empty collections content: Describe result of delimiting an empty collection Feb 14, 2025
@jmooring jmooring added Proposal and removed Bug labels Feb 14, 2025
@irkode
Copy link
Contributor

irkode commented Feb 14, 2025

There's nothing wrong with clearly stating the result of a function call. The user of a function should know what he's going to get.

Imho the point is to identify the parts where it is necessary and how much details need to be given towards the audience - keep in mind that many users do not have a programming background.

Delimiting for me means I have something to delimit, so the it's quite obvious that if there's nothing an empty string is returned. so it's just one sentence like "Delimiting an empty slice or map will return an empty string". That won't hurt and make it all clear.

Just like that one "The delimit function sorts maps by key, returning the values". 👍

see append ;-)

@jmooring
Copy link
Member

Just like that one

Maybe I'm just being disagreeable, but the ability to delimit a map was surprising to me, as is its sorting when you do so, unless you think about it and understand that Go maps are unordered. To me, what happens with delimit is self-evident.

I've been trying, with feedback from the community, to eliminate inconsistencies in the documentation for a long time. If we're going to add this to the delimit documentation, we should add it in all the other applicable places, and it's formatting/location should be consistent.

If someone want to go through all the functions and methods and develop a list of where we should do this, I'm open to it as long as I don't have to do the work.

@willfaught
Copy link
Contributor Author

If we're going to add this to the delimit documentation, we should add it in all the other applicable places, and it's formatting/location should be consistent.

@jmooring Can you be specific?

@jmooring
Copy link
Member

@willfaught I cannot be specific because I don't know, which is why I wrote:

If someone want to go through all the functions and methods and develop a list of where we should do this

@irkode
Copy link
Contributor

irkode commented Feb 14, 2025

you already follow a rolling release with the docs, so one improvement on a one page is an improvement (ok maybe in 97% of the cases). It helps to identify similar conditions for readers and authors and maybe next time one reads another page he misses that and just adds...

places and all the other

But it does not always be the one big shot thing. For that one I would just pick the place that seems to fit best, add it and see what happens. I could elaborate on a proposal. Ofc only if you are willing to go that way cause all over it will put load on you.

TL;TR; and definitely not part for an issue discussion

Indeed, direct delimit is surprising usually you have to do hash.values | delimit. For me is surprising there's no map.keys or map.values available (unless I've overseen). The expectations vary depending on the knowledge. (random, insertion order, sorted)... depends. and that's the 👍 for mentioning a sorted order.

I've been trying, with feedback from the...

Not only trying you did a very good job. I'm here for almost a year (fresher ;-) but I've seen a lot of improvements on the docs. and I blame that mostly up on you. Thx for spending the effort here.

For me you have a quite cool mixture of technical part and examples. The problem with documentation is always the target group. Especially becoming more and more popular, more and more users lack the knowledge about internals. So on the mid/long run your target group will (and maybe already is) less technical.

For the Interface documentation input -> output I always try to follow the 100% philosophy. A large amount of bugs in software are related to that...

I agree with the effort and time... documentation managing is a mess.

that all said. I appreciate our work so much 💯

@willfaught
Copy link
Contributor Author

willfaught commented Feb 14, 2025

@jmooring I was referring to:

we should add it in all the other applicable places, and it's formatting/location should be consistent.

What do you mean by other places? Other functions that return strings? Other functions that operate on slices?

What do you mean by consistent formatting? Do you mean consistent wording? Do you mean ensuring that the wording appears in its own paragraph, as opposed to adding it as a sentence of an existing paragraph?

Why isn't adding "For an empty slice, the empty string is returned." as a new paragraph to the delimit doc sufficient concretely? Give a single example, please.

I have no idea what you mean, so you've erected a barrier to closing this issue that isn't actionable. It seems to me that if someone wants to make other places in the doc consistent with the delimit doc after this is resolved, they are more than welcome to, and can do it in a separate issue/PR, especially if all they have is a vague sense that "something" has to be "done".

@jmooring
Copy link
Member

jmooring commented Feb 17, 2025

@willfaught @irkode

Is there anything in this list that you find surprising?

{{ collections.After 1 slice }} → []any{}
{{ collections.Append (slice 42) slice }} → []int{42}
{{ collections.Append slice (slice 42) }} → []any{42}
{{ collections.Apply slice "add" 42 "." }} → []any{}
{{ collections.Complement (slice 42) slice }} → []any{}
{{ collections.Complement slice (slice 42) }} → []int{42}
{{ collections.Delimit slice ", " }} → ""
{{ collections.First 1 slice }} → []any{}
{{ collections.Index slice 0 }} → nil
{{ collections.Intersect (slice 42) slice }} → []int{}
{{ collections.Intersect slice (slice 42) }} → []any{}
{{ collections.KeyVals "foo" slice }} → types.KeyValues{Key:"foo", Values:[]any{[]any{}}}
{{ collections.Merge dict dict }} → map[string]any{}
{{ collections.Querify slice }} → ""
{{ collections.Reverse slice }} → []any{}
{{ collections.Seq slice }} → error
{{ collections.Shuffle slice }} → []any{}
{{ collections.Sort slice }} → []any{}
{{ collections.SymDiff (slice 42) slice }} → []any{42}
{{ collections.SymDiff slice (slice 42) }} → []int{42}
{{ collections.Union (slice 42) slice }} → []int{42}
{{ collections.Union slice (slice 42) }} → []any{42}
{{ collections.Uniq slice }} → []any{}
{{ collections.Where slice "foo" "bar" }} → []any{}

{{ math.Add 42 slice }} → error
{{ math.Div 42 slice }} → error
{{ math.Max slice }} → 0
{{ math.Min slice }} → 0
{{ math.Mul 42 slice }} → error
{{ math.Product slice }} → 0
{{ math.Sub 42 slice }} → error 
{{ math.Sum slice }} → 0

{{ strings.Split "" "," }} → []string{""}

@willfaught
Copy link
Contributor Author

{{ collections.KeyVals "foo" slice }} → types.KeyValues{Key:"foo", Values:[]any{[]any{}}}

Should it be []any{[]any(nil)}?

{{ collections.Reverse slice }} → []any{}

Can funcs like this return []any(nil) instead?

{{ math.Max slice }} → 0
{{ math.Min slice }} → 0

I would expect an error, since both (max (slice)) and (max (slice 0 -1 -2 -3)) are 0 and you need to be able to distinguish between the cases. There has to be at least one arg. If there's one arg, the result is the arg. Similar issue with math.Product/Sum.

The rest make sense to me!


Noticed some things while researching this:

https://gohugo.io/functions/collections/querify/:

collections.Querify [VALUE...]

This might be clearer at a glance: collections.Querify [KEY VALUE]...

https://gohugo.io/functions/collections/after/:

collections.After INDEX COLLECTION

I think INDEX should be COUNT or LENGTH?

https://gohugo.io/functions/collections/symdiff/:

COLLECTION | collections.SymDiff COLLECTION

Seems odd to show pipe syntax. collections.SymDiff COLLECTION COLLECTION seems more normalized.

When searching for "math.max", math.Max is the 5th result:

https://gohugo.io/functions/math/max/:

Returns the greater of all numbers. Accepts scalars, slices, or both.

greater of all numbers -> greatest number?


Nice job with the doc redesign, by the way!

@irkode
Copy link
Contributor

irkode commented Feb 17, 2025

{{ collections.Apply slice "add" 42 "." }} → []any{}

would have expected that to fail -> so the "." is unset which makes add failing - looks like the implemention check for empty array
-> could be "collections.Apply for an empty collection will always return an the empty collection"

{{ collections.First 1 slice }} → []any{}
{{ collections.Index slice 0 }} → nil

by heart I would have expected that to fail (or return nil). These two are equivalents, both try to return the first element of the array, a typical out of bounds in other languages. - but I like that behavior which is like natural speech.

There's a subtle difference in treating the number in index and first/after/last.
-> maybe a general point for the "Types" doc page. Maybe that's because of my 100years of ARRAY in classic languages ;-)

WARN  after 10 slice -> []
WARN  after 1 slice -> []
WARN  last 0 slice -> []
WARN  last 10 slice -> []
WARN  first 0 slice -> []

{{ collections.KeyVals "foo" slice }} → types.KeyValues{Key:"foo", Values:[]any{[]any{}}}

just the type why isn't that return type just a MAP (for the users) - is there any difference in methods? (didn't scan docs)

{{ collections.Seq slice }} → error }}

arguments FIRST INCREMENT LAST are implicit INTs so wrong type

you may also use seq 2 -1 -2 -> [2 1 0 -1 -2] which might not be obvious and is not covered by example

{{ math.Max slice }} → 0
{{ math.Min slice }} → 0

I would keep the behavior: If I never had any money, the max and min I had would be 0 and leave the empty slice test to the user.
if that would throw an error you would have to try catch all max function calls. with won't work
-> but that should be documented
if I had a simple NIL test i would do return nil. maybe I want a isNil function to distinguish between nil false 0 ;-)

{{ collections.Delimit slice ", " }} → ""
{{ strings.Split "" "," }} → []string{""}

I think it's common understanding that these do not fail but return the expected type of value. at least in a requirement spec that would be one point to specify - split could maybe return an empty list ...
-> imho these could be expressed with the examples

** while writing this** .... if we treat Examples as part of the SPEC/Documentation this all could be handled with examples for the edge cases.


@jmooring
Copy link
Member

So, between the two of you, can you come up with a document (to be split into pieces later) that provides an example for each edge case for each of the functions that require it? No text—just examples. I'm thinking we add something like this to the bottom of the relevant function pages. For example, for the delimit page:

## Edge cases

```go-html-template
{{ collections.Delimit slice ", " }} → ""
{{ another edge case }} → something
```

This isn't about making changes to how the functions behave, but instead to document existing behavior, regardless of whether you think it's wrong.

@willfaught
Copy link
Contributor Author

I really don't want to go down this path, because the next ask is, "We need to document what happens with collections.Last with an empty collection." Etc. Etc.

I think the doc should explicitly specify this in words. To illustrate part or all of the specification with examples is error-prone and verbose. Examples should supplement specifications, especially for things that are complicated to explain in text, or things that have complex implications. Go doc successfully specifies built-in template funcs with just text.

I don't mind doing that work if I have assurance that it will be merged.

@irkode
Copy link
Contributor

irkode commented Feb 20, 2025

I prepared a document to show how it could like for the discussed functions considering some findings while preparing:

My suggestion would be to have the following after the Aliaswhich is last defined in front matter:

starting a Usage section which tells how the function is to be used. No complex stuff here like combining the function with otheres (except where neccessary - one must have a slice to explain delimit.

  • one sentence to describe a us case
  • enough short code examples to show it

after that zero an Examples section which describes some extended or complex use cases. each with a heading or some natural text and then the code

some with notes at the right places to highlight additional important stuff or info.

That way we have a clear separation between

  • the usage which contains input -> output examples
  • additional examples that bring out deeper information

If you put that in /content/en/functions and call hugoit will show mostly like the docs pages (except the stuff from foint matter.

EdgeCases.md

some remarks for stuff found in these function pages

  • some start with a sentence after the Alias some not
  • types fe Map slice are sometimes capitalized sometimes not - dunno if that's by intention/context
  • some used methods in text are links some not - dunno if that's by intention/context
  • sometimes ANY is used as return type, but it's always a slice (eg collections.After)

I also here and there found small typos, missing blanks etc also when normally using docs - would it be ok, to just file a PR for such things for master or do you want a branch, an issue or both?

@jmooring
Copy link
Member

jmooring commented Feb 20, 2025

@irkode

Having consistent level 2 headings ("Usage" and "Examples") on each function and method page would be great, and your explanation makes sense to me. I presume you would include the edge/unexpected cases at the end of the input -> output examples within the Usage section.

In some cases we may require additional level 2 headings, but I think those two are a good baseline.


Image

Notes

  1. The function/method description at the top (see screen capture above) comes from the description field in front matter, and I would appreciate leaving these as-is, including lack of special formatting, unless a change is absolutely necessary (i.e., it's wrong or unclear).

  2. The text in the description field is intentionally terse.

  3. The lack of formatting (e.g., not using backticks) in the description field is intentional: this field is used in places where we don't want raw Markdown or rendered HTML.

  4. Sometimes we partially duplicate the text in the description field at the top of the body to provide a formatted version of it, or to expand upon the description a bit, but in general we don't want to repeat ourselves.

Important

I have a branch in progress with a huge change set (affects > 500 files). It would be best to hold off on any PR's until this is merged, which should happen within the next week.

Implementation

I suggest we start with a handful of pages in one section to work out the kinks, if any.

Summary

Many of these pages were written years ago, and while I have attempted to add some consistency, getting them factually correct has been the priority:

  • Correcting explanations and examples that were just plain wrong: arguments in the wrong order, showing the wrong output (e.g., 2+2=5), etc.
  • Removing irrelevant examples
  • Removing unexpected terminology (e.g., "magic variables", "shadow members")
  • Developing a common language (i.e., the glossary, which was a non-trivial exercise)

Any help to provide consistency will be much appreciated.

@jmooring
Copy link
Member

jmooring commented Feb 20, 2025

With the previous theme the "On This Page" sidebar was configured for level 2 headings only, so sometimes the information was structured accordingly, using level 3 heading sparingly. That's no longer the case. The "On This Page" sidebar presents h2, h3, and h4 headings (indented by level), providing an opportunity to improve the structure and presentation, particularly in the "Examples" section that you propose.

In some cases we may require additional level 2 headings

One example of this is the resources.GetRemote page. In my view this page requires more than 2 level 2 headings:

  • Usage
  • Options (the options map)
  • Examples
  • Error handling
  • Caching
  • Security

Another example is transform.Highlight.

Yeah, you could place Options as a level 3 heading under Usage, but 1 subheading is goofy looking and doesn't make organizational sense to me. We might also consider 3 baseline level 2 headings:

  • Arguments (which would include the options map if present)
  • Usage
  • Examples

And some pages require a top level "Methods" section as well (e.g, hugo.Store).

Something to think about.

@irkode
Copy link
Contributor

irkode commented Feb 22, 2025

I presume you would include the edge/unexpected cases at the end of the input -> output examples within the Usage section.

I would tend to not call that edgs/unexpected. It's a clear definition what it does and what it returns. Using examples and text here makes it easier for non-technical users to understand. For technical users that will help to get the points that might be different with their experience (other languages...)

and ofc there's an order from common to special usages/returns with the common cases first.

Red numbers:

  1. Name and description (front matter)
  2. Syntax, Arguments, return values, Alias (front matter)
  3. Usage:
    • how to use the function
    • what you can expect when calling
  4. Examples: (maybe "extended Usage" )
    • extended usage involving other functionalities
    • showing possible applications of the function
  5. (Advanced) Examples: fully elaborate real use cases including full output

yellow

Section 3 fully describes what you can expect and how it behaves. No complex scenarios here. Each entry either tells

  • what you can do with or wehat the function returns (1) it , incl code (3)
  • simple code (no additional html) and result (unless needed)

=> now all is described to use the function

Section 4: visualizes some common paradigms the same way (5+6) little more complex

=> provides hints or common patterns for utilizing

Section 5: would give a complete elaborated example with

  • a longer description (7)
  • real code example with surrounding html, markdown (8)
  • result document (9)

=> for deeper understanding

Image

tbc need a break

@irkode
Copy link
Contributor

irkode commented Feb 22, 2025

In some cases we may require additional level 2 headings, but I think those two are a good baseline.

Maybe just take these as a startup and see how far we get.

Notes

1-3
yeah, didn't want to change anything there. It provides a good startup and consistent view.

  1. Sometimes we partially duplicate

sometimes the text at the top of the article is even more brief that the one from the description. Just stumbled over simetimes it starts with an example directly and sometimes not. With the new setup,

  • all articles start with the same layout
  • the text above the example summarizes the code shown inside. In most cases that won't be a duplicate cause it's one specific case only whereas the description summarizes the feature.

Important and Implementation
no PR yet
section based to give it a tryout

definitely agreed

Summary

getting them factually correct has been the priority:
...

right approach for me
and I believe that has been quite an effort usually is not taken. For me as a user very much appreciated. Thx

Any help to provide consistency will be much appreciated.

For sure I will.

@irkode
Copy link
Contributor

irkode commented Feb 22, 2025

With the previous theme the "On This Page" sidebar was configured for level 2 headings only, so sometimes the information was structured accordingly, using level 3 heading sparingly. That's no longer the case. The "On This Page" sidebar presents h2, h3, and h4 headings (indented by level), providing an opportunity to improve the structure and presentation, particularly in the "Examples" section that you propose.

sounds good to me.

In some cases we may require additional level 2 headings

  • Usage
  • Options (the options map)
  • Examples
  • Error handling
  • Caching
  • Security

or

  • Arguments (which would include the options map if present)
  • Usage
  • Examples

haven't had a look at a function having examples yet but right off the bat I would not move it below Usage.

quick check currently the functions section has > 275 pages, but just two have Arguments headings (Where and transform.Highlight) mmh think more have some arguments/options....

I'll take these two and elaborate

And some pages require a top level "Methods" section as well

short: yes, as of now it is the case

(e.g, hugo.Store).
Something to think about.

long: I would keep them but right off the bat

this seems to be related to the docs and Object Model organization. We have Methods but no explicit Objects or Types;-). And some kind of namespace for functions which effectively is hidden for a lot using aliases. Sometimes the underlaying Go stuff shines through (maps.Scratch, interfaces)

The outer view: hugo.Store actually is a function in the Hugo namespace which returns the Store/Scratch Object of a page/site/core(hugo) . Hidden behind chaining. This has some methods that are currently described at all places and there's newScratch ... ...

Having a method section below a function does not seem right to me...

nothing I would like to touch - looks like complete new topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants