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.0 required=3.0 tests=BAYES_00,DKIM_ADSP_CUSTOM_MED, FORGED_GMAIL_RCVD,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, 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 E550F1F453 for ; Wed, 1 May 2019 04:31:36 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 50C43120B2B; Wed, 1 May 2019 13:31:32 +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 8D0E2120B2C for ; Wed, 1 May 2019 13:31:30 +0900 (JST) Received: by filter0080p3las1.sendgrid.net with SMTP id filter0080p3las1-15579-5CC92122-25 2019-05-01 04:31:30.90538831 +0000 UTC m=+460784.546696667 Received: from herokuapp.com (unknown [54.159.201.37]) by ismtpd0044p1mdw1.sendgrid.net (SG) with ESMTP id kIKbAkWsRTGC6YANhwBRVQ for ; Wed, 01 May 2019 04:31:30.696 +0000 (UTC) Date: Wed, 01 May 2019 04:31:30 +0000 (UTC) From: tad.a.digger@gmail.com Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 67992 X-Redmine-Project: ruby-trunk X-Redmine-Issue-Id: 15814 X-Redmine-Issue-Author: unihedron X-Redmine-Sender: tad 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?tmvSckiZRwSpVoBdBDpso+loBXxuHq+0GuAVoc0KoeZBNwOPolZJWQG2=2FC8zAJ?= =?us-ascii?Q?AC0cxcr1rHLIMvRTOBe92cxjCSs5o=2FF7LCmdF7k?= =?us-ascii?Q?rO5I5BEBVfOE9MBBFVjsoKiTmGkhWQHX8wtK6nI?= =?us-ascii?Q?ktSkQ1Sn+G47KvwKq4msON=2Fk+0o4njV7jeHnz6V?= =?us-ascii?Q?t9Ta0fagHEr6M9hffvTi2uDEnV8GNMhY8fg=3D=3D?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 92509 Subject: [ruby-core:92509] [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 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/