From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS4713 221.184.0.0/13 X-Spam-Status: No, score=-3.7 required=3.0 tests=AWL,BAYES_00, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED,SPF_PASS shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from neon.ruby-lang.org (neon.ruby-lang.org [221.186.184.75]) by dcvr.yhbt.net (Postfix) with ESMTP id 0743C1F453 for ; Wed, 1 May 2019 04:35:00 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id EA8A9120B29; Wed, 1 May 2019 13:34:54 +0900 (JST) Received: from o1678948x4.outbound-mail.sendgrid.net (o1678948x4.outbound-mail.sendgrid.net [167.89.48.4]) by neon.ruby-lang.org (Postfix) with ESMTPS id E11ED120A83 for ; Wed, 1 May 2019 13:34:51 +0900 (JST) Received: by filter0144p3las1.sendgrid.net with SMTP id filter0144p3las1-15196-5CC921EC-11 2019-05-01 04:34:52.42860841 +0000 UTC m=+461746.802965158 Received: from herokuapp.com (unknown [54.159.201.37]) by ismtpd0015p1iad1.sendgrid.net (SG) with ESMTP id 9TyYCE1BRB2w1W7X6GotWQ for ; Wed, 01 May 2019 04:34:52.253 +0000 (UTC) Date: Wed, 01 May 2019 04:34:52 +0000 (UTC) From: nobu@ruby-lang.org Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 67993 X-Redmine-Project: ruby-trunk X-Redmine-Issue-Id: 15814 X-Redmine-Issue-Author: unihedron X-Redmine-Sender: nobu X-Mailer: Redmine X-Redmine-Host: bugs.ruby-lang.org X-Redmine-Site: Ruby Issue Tracking System X-Auto-Response-Suppress: All Auto-Submitted: auto-generated X-SG-EID: =?us-ascii?Q?q8Dly+pU2+3ektTtZVXgZtbJPXwqo7p86jCsvYTW4BzZ7+yVkJRp9ofRbE3Fna?= =?us-ascii?Q?s8yWe4Qh75Zqfykeg8JPBLNfPAZCdGVYgLGLTVW?= =?us-ascii?Q?s+d8vARCPXAz9sKfOGKMlowy0roPD7PG2XPsm9F?= =?us-ascii?Q?I33l2p1FU8VJIp9+ha=2FJTgDx9qM6WcIEnLc=2Fuk4?= =?us-ascii?Q?lugEcCyszc=2F5RMlBkKCQyK6erQewSzsoCfQ=3D=3D?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 92510 Subject: [ruby-core:92510] [Ruby trunk Feature#15814] Capturing variable in case-when branches X-BeenThere: ruby-core@ruby-lang.org X-Mailman-Version: 2.1.15 Precedence: list Reply-To: Ruby developers List-Id: Ruby developers List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ruby-core-bounces@ruby-lang.org Sender: "ruby-core" Issue #15814 has been updated by nobu (Nobuyoshi Nakada). Description updated That is the exactly same feature which I proposed yeas ago and was rejected. ---------------------------------------- Feature #15814: Capturing variable in case-when branches https://bugs.ruby-lang.org/issues/15814#change-77870 * 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: ```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. -- https://bugs.ruby-lang.org/