Project

General

Profile

Feature #15814

Updated by nobu (Nobuyoshi Nakada) over 5 years ago

In ruby, when a case-when statement is written, the when branches accepts expressions which will be evaluated to objects, then === is called to check if any of them returns true: 

 ```ruby 
 case 'a' 
 when 'abc' 
   # not matched 
 when Regexp.new('[abc]') 
   puts :matched # => matched 
 end 
 ``` 

 To demonstrate what is being done here, this is a mock: 

 ```ruby 
 equal_all_mock = Object.new 
 class << equal_all_mock 
   def ===(anything) true end 
 end 
 # 1 
 case 'a' 
 when equal_all_mock 
   puts :matched # => matched 
 end 
 # 2 
 if equal_all_mock === 'a' 
   puts :matched # => matched 
 end 
 ``` 

 Often times when matching for conditional statements, they have values in addition to being truthy or falsey; for example, it is very tempting to write (bugged) code like this (context: parsing 2D robot path instructions): 

 ```ruby 
 case 
 when i = '^v<>'.index(code) 
   x += [0, 0, -1, 1][i] 
   y += [1, -1, 0, 0][i] 
 when code = '/\\'[code] 
   if code == '/' 
     dx, dy =    dy,    dx 
   else 
     dx, dy = -dy, -dx 
   end 
 when code == '#' 
   dx = -dx 
   dy = -dy 
 end 
 ``` 

 This pattern has problems: 

 1. Using assignment to capture expressions "leaks" the local variable into the current scope, which the case block doesn't lock into a block scope, as it's not a proc 

 2. Even if the match fails, the expression is still written; `code = '/\\'[code]` in this case may assign nil, of which then `code == '#'` will fail 

 3. The alternative would be using regex, such as `/\^v<>/` and then using `$&` to fetch match data... but the global variable pattern is said to be discouraged, and while it works in this specific case it doesn't work in others, like if I want to act upon the index of an array search (but not when the search result is nil) 

 Thus my proposal: 

 ```ruby 
 case 
 when '^v<>'.index(code) => i 
   x += [0, 0, -1, 1][i] 
   y += [1, -1, 0, 0][i] 
 when '/\\'[code] => code 
   if code == '/' 
     dx, dy =    dy,    dx 
   else 
     dx, dy = -dy, -dx 
   end 
 when code == '#' 
   dx = -dx 
   dy = -dy 
 end 
 ``` 

 This is based on the `rescue Exception => e` syntax. The `when expression => i` format could potentially even be extended to: 

 ```ruby ``` 
 case 'foobar' 
 when /fo./ => match 
   p match # => foo 
 end 
 ``` 

 or with a proc that accepts 0~1 parameters (if it expects one, ruby could feed in the truthy value): 
 ```ruby ``` 
 case 
 when '^v<>'.index[code] do |i| 
   x += [0, 0, -1, 1][i] 
   y += [1, -1, 0, 0][i] 
 end 
 when '/\\'[code] do |code| 
   if code == '/' 
     dx, dy =    dy,    dx 
   else 
     dx, dy = -dy, -dx 
   end 
 end 
 when code == '#' 
   dx = -dx 
   dy = -dy 
 end 
 ``` 

 While some cases like these could be replaced by if-else statements I feel like this would be much better as an enhancement on the pattern-matching side. Scala, for example, does have `case x if x % 15 == 0 => { statements }` in its pattern-matching; handy when writing fizzbuzz.

Back