DEV Community

TinyNight(JP)
TinyNight(JP)

Posted 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.

As a basic principle, abandon the idea of ​​"if it becomes false".
The basic principle is to remember what happens when it becomes 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 the (a && b) is satisfied.
return unless a && b # Proceed with processing if the (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 guards 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!
  # These two gate clauses are meaningful precisely because they are 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 loops than while because it clarifies the goal.
The true ace of loop statements is until. It is definitely not a variation of while.

Top comments (0)