2

When utilizing the new case ... in pattern matching in Ruby, is there a nice way to prevent binding of nil to a variable?

case method()
in ... # Some cases here
  ...
in ...
  ...
in variable # If previous clauses did not match, then capture to variable
  puts variable
else # How to prevent that variable also captures nil?
  puts "Nil" # -> This is not called if method() returns nil
end

I have found the following two ways but they kind of seem ugly:

1.) Use if qualifier

case method()
in ... # Some cases here
  ...
in ...
  ...
in variable if variable
  puts variable
else 
  puts "Nil"
end

2.) Reverse match order

case method()
in ... # Some cases here
  ...
in ...
  ...
in nil
  puts "Nil"
in variable # Catch all
  puts variable
end

Is there something nicer?


How could this also be used? It would allow a nice way to do comparison and assignment against nil using the one-line pattern matching expression:

if method() => variable
  ... 
end

(This question was inspired by a question on assignments in if statements)

Christopher Oezbek
  • 23,994
  • 6
  • 61
  • 85
  • Does adding an explicit pattern match for `NilClass` work? – Tom Lord Mar 06 '21 at 22:32
  • 1
    `NilClass` and `nil` can both be used interchangeably here, but I would like something like `in variable: !NilClass` – Christopher Oezbek Mar 06 '21 at 22:35
  • 2
    Readers: I was perplexed by seeing `in` rather than `when` in the `case` statement. I soon discovered that this *pattern matching* was introduced in v2.7, whilst the documentation evidently first appeared for v3.0.0. If this is new to you as well you may find [this article](https://www.toptal.com/ruby/ruby-pattern-matching-tutorial) provides a helpful introduction. – Cary Swoveland Mar 06 '21 at 23:01
  • IMO your second option looks pretty decent – max pleaner Mar 07 '21 at 07:52

1 Answers1

1

Match NilClass Without Assignment

If you've decided that nil should be ignored, you can do it by simply matching against NilClass and then not assigning the result. Do this before calling your as-pattern (e.g. in variable) or an else clause. For example:

# arg can be assigned `nil` but can't be unassigned; this
# ensures that any nil values were actually passed in
def pattern_match arg
  case arg
  in /^a$/ => bar
  in NilClass
  in bar
  end

  # We print a message here to differentiate between arg and
  # bar being `nil`, because otherwise the method simply
  # returns `nil` since bar is auto-vivified by the
  # interpreter when it evaluates the case statement. This is
  # a potential source of confusion when looking at the
  # results.
  bar || 'matched NilClass'
end

# pass values through a scope gate for testing each
# assignment to bar in isolation
[?a, 'str', 1, nil].map { pattern_match _1 }
#=> ["a", "str", 1, "matched NilClass"]
Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • Since NilClass has only a single(ton) instance, why not use `nil` directly? – Christopher Oezbek Mar 07 '21 at 18:55
  • @ChristopherOezbek Because `case` uses the case equality operator, not standard equality, and I find that `NilClass === arg` expresses my intent more clearly than `nil == nil`. Note that you can also pattern match against String, Integer, and so forth, so using NilClass explicitly makes a useful example. Your semantic intent may vary, but in this particular case the net result will be the same either way. – Todd A. Jacobs Mar 07 '21 at 21:18