From: tad.a.digger@... Date: 2019-05-01T04:31:30+00:00 Subject: [ruby-core:92509] [Ruby trunk Feature#15814] Capturing variable in case-when branches Issue #15814 has been updated by tad (Tadashi Saito). What is the relationship with #14912 that already committed? ---------------------------------------- Feature #15814: Capturing variable in case-when branches https://bugs.ruby-lang.org/issues/15814#change-77869 * Author: unihedron (Unihedron 0) * Status: Open * Priority: Normal * Assignee: * Target version: ---------------------------------------- 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: ``` 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): ``` 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. -- https://bugs.ruby-lang.org/ Unsubscribe: