Ruby Documentation for Real Beginners

Angus Morrison
Sep 10, 2019 · 8 min read

When you first start learning to code, documentation sucks.

The first time I tried solving problems with Ruby was on Codewars. Like so many beginners, I flailed around overcomplicating things and ignoring every convention until I stumbled on solutions that worked, somehow. The great thing about Codewars is that once you’ve solved a problem, you can see the solutions of the other, better coders who have gone before. The horrible thing about being a beginner is that you don’t understand what you’re looking at.

map? reduce? Enumerator? Like any good wannabe developer, I turned to Google, which in turn lead me to the wealth of Ruby documentation available online. Unfortunately for the hopeful learner, documentation is almost as confusing as code.

It’s true of practically every language, from C++ (whose only official documentation is an international treaty directing the implementation of compilers and standard library implementations) to JavaScript. MDN’s JavaScript documentation is beautifully designed, but it’s written in something that only approximates English.

For software developers with even a little experience, this is fine. For software developers with a lot of experience, nothing could be less trouble. For beginners who know just enough syntax to suspect they need to filter, map, or reduce, but don’t know enough to interpret the phrase “Combines all elements of enum by applying a binary operation, specified by a block or a symbol that names a method or operator,” it’s a nightmare.

Standard documentation fails the people who rely on it most. True, it’s a learning curve that all programmers overcome with time, but it’s so much steeper than it needs to be.

Below, I’ll propose some beginner-friendly documentation to complement a beginner-friendly language — Ruby. As a test run, I’ve picked out three critical functions that I’ve used every day since starting to code. These are the foundations that make the rest of the language feel less daunting.

If, until now, you thought that an enum was a large, flightless bird, this one’s for you.


each

First up, the Array each method. A true bread-and-butter function.

Ruby-Doc.org documentation

Not the worst offender, as documentation goes, but there are still some features sure to trip up the newcomer. Let’s take a look.

each { | item | block } ary
  • The heading fails to communicate what each is being called on. Experienced programmers know that, because we’re in the documentation for the Array class, each must be called on an instance of an array object. To novices, this is not intuitive.
  • The abbreviated return value ary is less clear than a plain English approach.
Calls the given block once for each element in self, passing that element as a parameter. Returns the array itself.
  • It’s a safe bet that a beginner checking the documentation for a basic method like each hasn’t encountered the concept of self yet.
  • Even the standard English can be better worded.

I actually like the Ruby Docs code example. It illustrates the concept and explains itself well.

Morrison documentation

each
Syntax 1: your_array.each { |item| block } → your_arrayOne by one, each item in your_array is passed into the block of code, and the block runs.When the block has run for every item in your_array, each returns your_array.
Syntax 2: your-array.each → EnumeratorIf you don't provide a block of code, each returns an Enumerator object. Learn more
Why is each useful?
Use cases include:
* Printing out each item in an array.
* Modifying each item of your array in-place (i.e. when you want to change your_array instead of creating a new array).
* Creating a loop with as many cycles as your_array has items.
Examplesarray = ["a", "b", "c"]
array.each { | item | print x + "-- " }
# produces: a-- b-- c--array = ["A", "B", "C"]
array.each { | item | item.downcase! }
# array is now ["a", "b", "c"]

This structure makes a number of improvements:

  • Explanations are now grouped with the syntactic structure they correspond to. “Co-location” is a UX technique that dramatically reduces cognitive load in the reader.
  • Variable names are human-readable and provide greater context to the reader.
  • Non-specialist language walks the reader through the action of the method in simple, digestible steps.
  • Sample use cases are provided. Knowing why something is useful is critical to understanding how something is used.
  • Example 1 now uses more intuitive string concatenation syntax.

map

The Array class map method is each's teenage brother: more expressive, less understood.

Ruby-Doc.org documentation

This suffers from similar problems to each , with some nasty additions.

See also Enumerable#collect
  • Because Ruby’s Array class has access to the Enumerable mixin, both map and collect methods are available to arrays. They do the exact same thing. This is a source of endless confusion among rookie coders, yet “See also” makes collect seem like optional extra reading.
  • Example 3 muddies the waters by including a with_index method without introduction. If we separate concerns in our code, we should do it in documentation too.

Morrison documentation

map/collectmap and collect are functionally identical. Choose one and use it consistently to improve code readability. Learn more
Syntax 1: your_array.map { |item| block } → new_arrayOne by one, each item in your_array is passed into the block of code. The block runs and adds the output to a new array.When the block has run for every item in your_array, map returns new_array.
Syntax 2: your-array.each → EnumeratorIf you don't provide a block of code, each returns an Enumerator object. Learn more
Why is map useful?
Common use cases include:
* Applying a function to each item in an array without changing the original array.
* Extracting an array of object attributes from an array of objects (e.g. from an array of people who each have a name attribute, map could produce and array of names).
Examplesarray = [1, 2, 3]
array.map { | item | item * 2 }
# returns [2, 4, 6]
# array is unchanged

people = [person1, person2, person3]
people.map { | person | person.name }
#return ["Colin", "Jessica", "Jeff"]
#people is unchanged

This approach:

  • Clarifies the relationship between map and collect in beginner-friendly terms.
  • Includes a plain-English, real-world example that is easily visualised.
  • Avoids introducing additional methods like with_index.

reduce

Reduce has more moving parts than each or map and is less accessible as a result.

reduce(initial, sym) → objreduce(sym) → objreduce(initial) { | memo, obj | block } → objreduce { | memo, obj | block } → obj
  • Documenting reduce is complicated because it can be implemented with four different structures. Ruby Docs doesn’t co-locate these structures with their explanations, leaving the reader to piece together what’s happening.
  • The use of obj as the block local variable placeholder, differs from that used by each and map, which use item. Inconsistency breeds confusion.
Combines all elements of enum by applying a binary operation, specified by a block or a symbol that names a method or operator.
  • The essence of reduce’s function, the application of a “binary operation”, is also expressed in language that’s likely to trip the lay reader (or at least force them to Google).
The inject and reduce methods are aliases. There is no performance benefit to either.
  • It does, however, do a much better job than map of explaining the relationship between reduce and its alias, inject.

Morrison documentation

reduce / injectNote: reduce and inject are functionally identical. Choose one and use it consistently to improve code readability.
Syntax 1: your_array.reduce(optional_initial, operator) → valueStarting from 0 by default, or an optional_initial value you provide, the operator is applied to each item in your_array, reducing it to a single value.Operators must be given as a symbol (e.g. :+, :-, :*, :/).Examples[1, 1, 1].reduce(:+) → 3[1, 1, 1].reduce(3, :+) → 6
Syntax 2: your_array.reduce(optional_initial) { |memo, item| block }
→ value
The block is run for each item in your_array. Each time it runs, the result is stored in the temporary memo variable (known as an accumulator). The memo value is used to carry over the results of the previous operation to the next item in the array.When the block has run for every item in your_array, a single value is returned.memo starts from 0 by default, or the optional_initial value you provideExamples[1, 1, 1].reduce { | memo, item | memo + item } → 3
# 0 + 1 == 1
# 1 + 1 == 2
# 2 + 1 == 3
[1, 1, 1].reduce(3) { | memo, item | memo + item } → 6
# 3 + 1 = 4
# 4 + 1 = 5
# 5 + 1 = 6
Why is reduce useful?
You'll often want to produce a single value from an array of data, whether you're finding the total value of purchases or weighting RPG character statistics based on a series of variable parameters.
In fact, reduce is the basis of many quality-of-life functions included in Ruby's enumerable module, such as:* sum
* min
* max
* count

Let’s break down what’s happening here:

  • Instead of four, different expressions of the same function, this approach reduces (wahey!) the variants to two by clearly indicating optional parameters in novice-readable terms.
  • Because the symbol and block approaches have major differences, I found it instructive to provide a set of examples for each, allowing learners to see that both approaches gives the same results.
  • The explanations sacrifice some mathematical rigour in favour of legibility, which, for a beginner, is paramount.

In reality, all of these newbie descriptions omit some of technical detail that may be of value the the seasoned programmer. Do I advocate replacing our existing documentation with this more instructive approach? No. But any language that maintains accessible documentation alongside its gold standard will quickly become the drug of choice for learners everywhere.

Angus Morrison

Written by

Software Engineer @ Bamboo. Recovering product manager and UX Consultant.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade