Verbose or shorter stdlib crystal docs preferred

I’ve been working on polishing up some stdlib crystal docs for a new functionality.

Are “fully fleshed, rich, with examples” stdlib docs preferred or “shorter, less verbose” explanation style docs?

Thanks!

I would say that examples are ok. But I would not expect the API docs to do a whole description of the module. I think that is more relevant to the crystal-book in some sections.

Do you want to share a sneak peek here of what you mean by “fully fleshed, rich, with examples”?

I agree with @bcardiff. When I’m first learning a language I like to have something like the crystal-book that holds your hand through the language and explains the big picture of some of the modules. After that I want lean API docs with well documented inputs and outputs, but keep examples to a minimal. I think it makes it easier to find things. However, I do occasionally wish there were examples, but I think they should be limited to one.

On a side note I do wish more time was given to macros in the crystal-book.

Example of “rich, full, with examples”

  # Returns a new array with all elements sorted based on the return value of
  # their comparison method `#<=>`
  #
  # If *stable* is `true`, performs a stable sort, i.e. equal elements' relative order is preserved
  # (slower, uses more memory).
  # If *stable* is `false`, performs an unstable sort, i.e. equal elements' relative order may change
  # (faster, uses less memory).
  # For elements where being equal means interchangeable (`Primitive` and `String`), unstable sort is the default
  # (identity isn't distinguishable, so relative order doesn't matter, so it defaults to faster method).
  # For everything else, stable sort is the default.
  # 
  # a = [3, 1, 2]
  # a.sort # => [1, 2, 3]
  # a      # => [3, 1, 2]
  #
  # class MyClass
  #   property val : Int32
  #
  #   def initialize(@val)
  #   end
  #
  #   def <=>(other)
  #     self.val <=> other.val
  #   end
  # end
  #
  # b = MyClass.new(1)
  # c = MyClass.new(1)
  # d = MyClass.new(0)
  # e = MyClass.new(2)
  # [b, c, d, e].sort                 # => [d, b, c, e] relative order is preserved by default
  # [b, c, d, e].sort(stable = false) # => [d, b, c, e] or [d, c, b, e] absolute order is respected, but relative order for equals may change
  # 
  def sort(stable = elements_have_identity?) : Array(T)
    ....

Terse might be something like

  # Returns a new array with all elements sorted based on the return value of
  # their comparison method `#<=>`
  #
  # If *stable* is `true`, performs a stable sort, see https://en.wikipedia.org/wiki/Sorting_algorithm#Stability
  # default is true for objects, false for Primitive and String.
  # ```
  # a = [3, 1, 2]
  # a.sort # => [1, 2, 3]
  # a      # => [3, 1, 2]
  # b = [my_instance1, my_instance2, my_instance3].sort(stable = false)
  # b # => if any instance's compare the same their relative order may be rearranged.
  def sort(stable = elements_have_identity?) : Array(T)
    ....

Thanks!

In this context, terse would be better IMO. The other example in the verbose example would be better suited to Comparable.

Best documentation consists of explanation of what it does, what the arguments do/are, and a short example of using it. For anything more link out to related types.

The rich example seems too complicated. It should focus on the bare minimum. I’m not sure what’s the best way to demonstrate stable sort. Maybe sort arrays to avoid introducing an extra type?
But the accompanying rich text is at the level of detail that we should be aiming for.

For what functionality?

I think discussing terse vs. rich is okay, but if we don’t know what is it for then it’s not clear why we are discussion it.

Just as a note, the current doc generator will take the first line of a doc comment as a summary, and show the full doc when you click on the definition. There’s already a terse vs. full in the current way.

The “new” stable parameter, in the example case.

I assume you mean in the example?
Yeah it’s tricky in this case since to actually do an example where “stable = false” matters it has to be an object, not a primitive…hmm…

Just a comment: I think stable short should be a separate function. It’s not like you want to configure sorting by passing an argument. When you sort stuff, you either want them stable or you don’t care. If you want it stable, you would call stable_sort.

That way there’s no need to clutter existing docs.

1 Like

Hmm

Yeah there has been a small amount of earlier discussion:

Here’s the situation:

Imagine I get an array of…instances coming out of a database query in a certain initial order given by the query. I want to have the initial order be my secondary ordering, so I sort_by to introduce a new primary ordering, while retaining the secondary ordering that the array started with.
If sort_by isn’t stable, any chained sort_by jumbles all previous ordering.
So for sort_by at least, it feels like it should be default stable or it can lead to…unanticipated behavior. This is how Rust does it (though Go doesn’t).
It seems to me that people that “don’t care, give me something sorted” might prefer a stable sort (fewer surprises, easier for beginners), unless they are really going for speed, in which case they might prefer an unstable sort.

For the other sort methods, it might not be intuitive that unstable by default means that comparisons that report “equal” are possible to result in a scrambled order, as well. https://github.com/crystal-lang/crystal/issues/6057#issuecomment-605584525

See also Poll: should default sort behavior be "fast" or "stable"?

So overall it seems to me like unstable_sort should be kind of an opt-in “you know what you’re getting in to” type of thing?

So maybe I can propose the addition of unstable_sort methods, for the reasons above, thoughts?

Thanks,

-Roger-

In terms of “big vs. small docs” I noticed that go’s docs have “big examples” but they’re just collapsed at the top of the documentation (so basically, at the Class or Module level), ex:

https://golang.org/pkg/sort
But maybe that’s because they don’t have a book?

If so it might be good to link from the stdlib docs to the book?
Just an idea.

I remembered one thing in favor of parameter with default. The default can be set based on the element type of the Array.

You can have an “sensible default,” for instance with an Array of Primitives, since there is no “identity” among equal elements, relative order doesn’t matter, so unstable sort works “as well as stable” so it can default to unstable without any penalty. It can set up an “intelligent default”.
The benefit being if somebody new’s up an array of Int32’s and runs sort on it they get the “fast sort” by default, without having to fully understand why. It might be worth having, so that people can call sort and it “just does the right thing” so nobody has to worry about it.

That’s how Java does it. If it’s a Collection of Primitives, then it uses “fast unstable sort” if it’s objects, it uses “stable sort”.

Or maybe that’s too confusing and better to just go with explicit (method names or parameter with a universal default)? See points above. Hmm…

Yeah, I don’t think such extensive examples should be in the API docs. Linking to advanced guides would be a good solution.