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=-4.1 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,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 4DA7D1F453 for ; Wed, 1 May 2019 04:25:59 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 52FA9120A3E; Wed, 1 May 2019 13:25:55 +0900 (JST) Received: from o1678916x28.outbound-mail.sendgrid.net (o1678916x28.outbound-mail.sendgrid.net [167.89.16.28]) by neon.ruby-lang.org (Postfix) with ESMTPS id 86C4F1209B1 for ; Wed, 1 May 2019 13:25:53 +0900 (JST) Received: by filter0031p3las1.sendgrid.net with SMTP id filter0031p3las1-30293-5CC91FD1-1E 2019-05-01 04:25:53.636785974 +0000 UTC m=+460398.851655184 Received: from herokuapp.com (unknown [54.159.201.37]) by ismtpd0003p1iad1.sendgrid.net (SG) with ESMTP id T5eBpJuWTpWcEP1j_tq0CQ for ; Wed, 01 May 2019 04:25:53.457 +0000 (UTC) Date: Wed, 01 May 2019 04:25:53 +0000 (UTC) From: unihedron@unihedro.com Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 67991 X-Redmine-Project: ruby-trunk X-Redmine-Issue-Id: 15814 X-Redmine-Issue-Author: unihedron X-Redmine-Sender: unihedron 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?8ovh=2FWvtRuw7KKmHKsyNyMbhmGOnICmaQReihvqOmGmP2Bpy=2F2+L6enHIRR6Eh?= =?us-ascii?Q?eCsisrBnqmoSdEczEQodF2xFeCUZXzBbK=2Fwdt9l?= =?us-ascii?Q?y2C=2Ft+aqxFmffG=2FnQ9lckEyTu5wGWVQeiSRz7h6?= =?us-ascii?Q?UUCDKVCViuO=2FyeGNIIJpmWLbO+6f1I3VTbSPVCU?= =?us-ascii?Q?PPcXPtnaCo8ftaVSLAuD1y3CC8tugToiLTw=3D=3D?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 92508 Subject: [ruby-core:92508] [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 unihedron (Unihedron 0). Description updated I messed up brackets. ---------------------------------------- Feature #15814: Capturing variable in case-when branches https://bugs.ruby-lang.org/issues/15814#change-77868 * 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/