ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
From: unihedron@unihedro.com
To: ruby-core@ruby-lang.org
Subject: [ruby-core:92507] [Ruby trunk Feature#15814] Capturing variable in case-when branches
Date: Wed, 01 May 2019 04:24:32 +0000 (UTC)	[thread overview]
Message-ID: <redmine.issue-15814.20190501042430.1b2bd9b8e9a6c03a@ruby-lang.org> (raw)
In-Reply-To: redmine.issue-15814.20190501042430@ruby-lang.org

Issue #15814 has been reported by unihedron (Unihedron 0).

----------------------------------------
Feature #15814: Capturing variable in case-when branches
https://bugs.ruby-lang.org/issues/15814

* 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/

       reply	other threads:[~2019-05-01  4:24 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <redmine.issue-15814.20190501042430@ruby-lang.org>
2019-05-01  4:24 ` unihedron [this message]
2019-05-01  4:25 ` [ruby-core:92508] [Ruby trunk Feature#15814] Capturing variable in case-when branches unihedron
2019-05-01  4:31 ` [ruby-core:92509] " tad.a.digger
2019-05-01  4:34 ` [ruby-core:92510] " nobu
2019-05-01  8:03 ` [ruby-core:92511] " unihedron

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.ruby-lang.org/en/community/mailing-lists/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=redmine.issue-15814.20190501042430.1b2bd9b8e9a6c03a@ruby-lang.org \
    --to=ruby-core@ruby-lang.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).