# Maybe Thinking

Josh Clayton

A coworker of mine recently submitted a pull request introducing some simple reporting into an application, where he was displaying an average time to complete a survey or `"not enough results"` otherwise.

Here’s the code:

``````def completion_time_average_in_words
if team_result_completion_times.empty?
"not enough results"
else
distance_of_time_in_words(average_team_completion_time)
end
end

def average_team_completion_time
team_result_completion_times.inject(0.0, :+)/team_result_completion_times.size
end
``````

In this case, `team_result_completion_times` (not shown) can return an array of zero or more integers representing the number of seconds it took to complete a survey. When we have results, we display the length of time (e.g. ```"20 minutes"``` or `"3 days"`).

What struck me as interesting was that checking emptiness of `team_result_completion_times` was effectively a guard clause against dividing by zero. This results in interacting with `team_result_completion_times` in two different methods, and any other places that use `team_result_completion_times` need to also check for the length of the array (or risk odd behavior).

## Division in Ruby

Let’s take a quick tangent on division in Ruby, and why it can exhibit odd behavior.

First, let’s assume a simple case, where we don’t look carefully at types and are attempting to calculate an average:

``````def average(collection)
collection.inject(:+)/collection.length
end
``````

Running this in a console leads to some interesting results:

``````>> average [1]      # 1   - looks good!
>> average [1, 3]   # 2   - looks good!
>> average [1, 6]   # 3   - wait, what?
>> average [1, 6.0] # 3.5 - looks good!
``````

The odd behavior occurs when dividing two `Fixnum`s; in the last example, adding a `Fixnum` and a `Float` results in a `Float`, so division returns a `Float` and we see the expected significant digits.

To fix this, we can update `average` to use an initial value of `0.0`, as it will cast addition to a `Float` for us:

``````def average(collection)
collection.inject(0.0, :+)/collection.length
end
``````

And the results:

``````>> average [1]      # 1.0 - looks good!
>> average [1, 3]   # 2.0 - looks good!
>> average [1, 6]   # 3.5 - looks good!
>> average [1, 6.0] # 3.5 - looks good!
``````

Finally, let’s look at one final case: dividing by zero.

``````>> average [] # NaN - looks... good?
``````

For other forms of division by zero, we see different results:

``````>> 1/0   # raises ZeroDivisionError
>> 0/0   # raises ZeroDivisionError
>> 1/0.0 # Infinity
>> 0/0.0 # NaN
``````

## Solving in Elm with Maybe

Let’s look at a similar solution in Elm.

``````completionTimeAverageInWords : List Float -> String
completionTimeAverageInWords teamResultCompletionTimes =
let
completionTime =
averageTeamCompletionTime teamResultCompletionTimes
in
Maybe.withDefault "not enough results" <| Maybe.map distanceOfTimeInWords completionTime

averageTeamCompletionTime : List Float -> Maybe Float
averageTeamCompletionTime teamResultCompletionTimes =
case List.length teamResultCompletionTimes of
0 ->
Nothing

completionTimesLength ->
Just <| (List.sum teamResultCompletionTimes)/(completionTimesLength |> toFloat)
``````

Here, we have two functions (named similarly so it’s easier to draw parallels between the two implementations).

The difference here is that `averageTeamCompletionTime` returns a ```Maybe Float``` (take a look at the docs on Elm’s implementation of `Maybe` if you’re unfamiliar), and we encapsulate both when the list is empty and when the list has values in only one spot. Instead of having both functions operate on the length of the list to determine which string to display, `completionTimeAverageInWords` relies on `averageTeamCompletionTime` to do all the work, just like we want.

## Modifying the Previous Ruby Code

Introducing `Maybe` in Ruby might be overkill (or it might not be?), but it did hone in on the problem of spreading length logic across potentially multiple methods. Instead, it might be safest to check for `nil` (since, if it’s forgotten, it’ll be easier to spot when something breaks):

``````def completion_time_average_in_words
if result = average team_result_completion_times
distance_of_time_in_words result
else
"not enough results"
end
end

def average(collection)
if collection.length.zero?
nil
else
collection.inject(0.0, :+)/collection.length
end
end
``````

I considered using a `case` statement in Ruby checking when the value was `Float::NAN` and handling the “nothing” case there; however, I realized that forgetting to check for both paths and performing an operation “infinity” times or transferring “infinity” dollars would be problematic.

While perhaps not as elegant as Elm’s handling of `Maybe`, this seems like a better direction to go in than introspecting on an external source of information in two methods. This then places the responsibility of caller of `average` to handle both when the average present and when it is `nil`.