DEV Community

TinyNight(JP)
TinyNight(JP)

Posted on • Edited on

[Ruby] How to Truly Understand unless and until

I frequently see comments that 'unless' and 'until' are difficult to understand.
I've also heard that it's prohibited in tools like rubocop.
This is a fundamentally flawed way of thinking about unless and until.

As a basic principle, abandon the idea of ​​"if it becomes false".
The basic principle is to remember what happens when the condition evaluates to true, and you won't get confused.

unless

Think of unless as an expression that will not be executed if the condition is met.

result = 1 if a && b     # If a && b is satisfied, execute the left side.
result = 2 unless a && b # If a && b is satisfied, do not execute the left side.
Enter fullscreen mode Exit fullscreen mode

When combined with return (guard clause), it should be considered a condition for proceeding with the process.

return if a && b     # Terminate processing if (a && b) is satisfied.
return unless a && b # Proceed with processing if (a && b) is satisfied.
Enter fullscreen mode Exit fullscreen mode

When using them as guard clauses, remember the following: The two have distinctly different roles.

Keywords Role Action
return if expression blacklist Terminate processing if the expression is satisfied
return unless expression whitelist Proceed with processing if the expression is satisfied

To differentiate their use, any judgment formulas that involve both should be separated.
An example is shown below.

def gagazet_gate(yevonite) # case 1
  return if yevonite == Kimahri
  # Blocks only Kimahri. This allows unauthorized people to pass through.
end

def gagazet_gate(yevonite) # case 2
  return unless yevonite == Summoner || yevonite == Guardian
  # Summoners and Guardians are allowed through. This means Kimahri can also get through.
end

def gagazet_gate(yevonite) # Bad case
  return if yevonite == Kimahri || yevonite != Summoner && yevonite != Guardian
    # "No passing if you're Kimahri, and also if you're not a summoner, and also if..."
    # Not Biran!
end

def gagazet_gate(yevonite) # Best case
  return unless yevonite == Summoner || yevonite == Guardian
  return if yevonite == Kimahri
  # Summoner may pass. Guardians may pass. Kimahri not pass!
  # Since these two guard clauses serve diametrically opposite purposes, they should be used separately.
end
Enter fullscreen mode Exit fullscreen mode

until

The until statement can be thought of as a condition that exits the loop when it becomes true.

i = 0
p i while (i += 1) < 10 # Loops while i is less than 10
p i until (i -= 1) <= 0 # Exit the loop when i becomes 0 or less
Enter fullscreen mode Exit fullscreen mode

For example, the following example is easy to understand. The formula is also elegant.

a = (1..100).to_a
a.shift until a.empty? # Exit the loop when a becomes empty
Enter fullscreen mode Exit fullscreen mode

Summary

While it's often suggested that unless be replaced with if not, that suggestion is based on a flawed premise.
unless and until are not constructs that reverse the evaluation of if and while conditional expressions. Rather, they are constructs that "reverse the behavior" of a conditional expression.

Furthermore, until is actually more suitable for loop control than while because it clarifies the goal.
until is the true ace among loop constructs. It is definitely not just a variation of while.

Top comments (0)