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 33C6C1F453 for ; Wed, 1 May 2019 08:03:32 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id C636C120AAF; Wed, 1 May 2019 17:03:26 +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 A91C0120AB4 for ; Wed, 1 May 2019 17:03:23 +0900 (JST) Received: by filter0109p3las1.sendgrid.net with SMTP id filter0109p3las1-6645-5CC952CB-3 2019-05-01 08:03:23.143606985 +0000 UTC m=+472363.382956770 Received: from herokuapp.com (unknown [54.159.201.37]) by ismtpd0001p1iad1.sendgrid.net (SG) with ESMTP id ZaHpwTqeR_GDwbXhoAE-fQ for ; Wed, 01 May 2019 08:03:23.068 +0000 (UTC) Date: Wed, 01 May 2019 08:03:23 +0000 (UTC) From: unihedron@unihedro.com Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 67994 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=2FWvtRuw7KKmHKsyNyMbhmGOnICmaQReihvqOmGlR5+X45gAsc2dLpNhYEU?= =?us-ascii?Q?d4npygjmp7ZtZhxQ5p+tc0vzTKc7xgAMCNgKesN?= =?us-ascii?Q?ugFoOdGBjAqphax5P+TJNjWYkcpOHyx4yMYDp0r?= =?us-ascii?Q?CsJuTOv6ly1Rcv0zIEmSgZRpW5Uip4pFRA6nr06?= =?us-ascii?Q?DqEMooIdw1JZZnVTvdPavMpbddU4HpvQnVQ=3D=3D?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 92511 Subject: [ruby-core:92511] [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). #14912 is more thought-out and seems to have made good progress, my regret is I didn't come upon it when trying to search for duplicate issues. #14709 seems to have a collection of case studies and really interesting discussions, but "if we were going to add pattern matching in Ruby, we should add it with better syntax" which #14912 seems to address. Unfortunately I can't seem to close this ticket, even after having written this. ---------------------------------------- Feature #15814: Capturing variable in case-when branches https://bugs.ruby-lang.org/issues/15814#change-77871 * 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/