Project

General

Profile

Feature #15865

`<expr> in <pattern>` expression

Added by mame (Yusuke Endoh) 29 days ago. Updated 14 days ago.

Status:
Open
Priority:
Normal
Target version:
-
[ruby-core:92733]

Description

How about adding a syntax for one-line pattern matching: <expr> in <pattern> ?

[1, 2, 3] in x, y, z #=> true (with assigning 1 to x, 2 to y, and 3 to z)
[1, 2, 3] in 1, 2, 4 #=> false

More realistic example:

json = {
  name: "ko1",
  age: 39,
  address: { postal: 123, city: "Taito-ku" }
}

if json in { name:, age: (20..), address: { city: "Taito-ku" } }
  p name #=> "ko1"
else
  raise "wrong format"
end

It is simpler and more composable than "case...in" when only one "in" clause is needed. I think that in Ruby a pattern matching would be often used for "format-checking", to check a structure of data, and this use case would usually require only one clause. This is the main rationale for the syntax I propose.

Additional two small rationales:

  • It may be used as a kind of "right assignment": 1 + 1 in x behaves like x = 1 + 1. It returns true instead of 2, though.
  • There are some arguments about the syntax "case...in". But if we have <expr> in <pattern>, "case...in" can be considered as a syntactic sugar that is useful for multiple-clause cases, and looks more natural to me.

There are two points I should note:

  • <expr> in <pattern> is an expression like <expr> and <expr>, so we cannot write it as an argument: foo(1 in 1) causes SyntaxError. You need to write foo((1 in 1)) as like foo((1 and 1)). I think it is impossible to implement.
  • Incomplete pattern matching also rewrites variables: [1, 2, 3] in x, 42, z will write 1 to the variable "x". This behavior is the same as the current "case...in".

Nobu wrote a patch: https://github.com/nobu/ruby/pull/new/feature/expr-in-pattern


Related issues

Related to Ruby trunk - Feature #14912: Introduce pattern matching syntaxAssignedActions

History

Updated by shevegen (Robert A. Heiler) 29 days ago

I don't have a big pro/contra opinion per se on the whole topic of pattern matching,
but I would wait a bit before finalizing more and more additions on top of it. Now pattern
matching is probably there to say, I understand that, but it may still be better to not add
lots of further building blocks to it. Another smaller reason is that at the least I
find pattern matching to be somewhat more complex to understand; I understand that the
suggestion here is simpler than long case/in statements, but I am still not sure if it would
be a good idea to add too much to ideas/changes in a short period of time, without seeing
how it may be used in "real" production code.

Updated by mame (Yusuke Endoh) 28 days ago

  • Description updated (diff)

Oops, the first example was wrong. Fixed.

-[1, 2, 3] in 1, 2, 3 #=> false
+[1, 2, 3] in 1, 2, 4 #=> false

Updated by Eregon (Benoit Daloze) 28 days ago

RHS assignment looks a bit weird to me.

I guess most languages have <pattern> SIGIL/KEYWORD <expr>, which seems more natural like:

x, y, z = [1, 2, 3]
{ name:, age: } = json

if { name:, age: (20..), address: { city: "Taito-ku" } } = json
  p name
end

But I guess = is not available for this or causes too many syntax conflicts?
Maybe another sigil or keyword could be used?

The current order is also opposite of for's order, which seems confusing:

for k, v in h
  p [k, v]
end

h.first in k, v
p [k, v]

However, one can argue it's consistent with the case ... in order I suppose (but it's on different lines, so there is not really a RHS assignment):

case h.first
in k, v
  p [k, v]
end

FWIW I feel this feature is somewhat similar to the Oz programming language's local [A B] = Xs in {Show A} end construct (which keeps assignments on the LHS by convention).

Updated by ko1 (Koichi Sasada) 28 days ago

Trivial comment.

We can't introduce guard pattern (pat if expr) because it will conflict with expr if cond.

Updated by nobu (Nobuyoshi Nakada) 28 days ago

ko1 (Koichi Sasada) wrote:

We can't introduce guard pattern (pat if expr) because it will conflict with expr if cond.

You have to write as val in expr and cond.

Updated by matz (Yukihiro Matsumoto) 28 days ago

I am pretty positive about adding single line pattern matching in Ruby. I am still wondering whether if is a right keyword for it. Let me think about it for a while.

Matz.

Updated by Eregon (Benoit Daloze) 28 days ago

matz (Yukihiro Matsumoto) Do you mean in rather than if for the keyword?

Updated by Eregon (Benoit Daloze) 14 days ago

mame (Yusuke Endoh) wrote:

  • Incomplete pattern matching also rewrites variables: [1, 2, 3] in x, 42, z will write 1 to the variable "x". This behavior is the same as the current "case...in".

This sounds concerning to me.
With case/in, it's clear where the variables should be defined, and it's a matter of fixing it so the variables are only defined in that in pattern branch, and nil in other branches.
(IMHO we should fix that for 2.7, otherwise it will be a compatibility issue).

But here it's unclear how long variables in inline patterns should live.
Probably for the whole method? Or just for the if?

E.g.:

json = { name: "Me", age: 28, hobby: :ruby }
if json in { name:, age: (...20) }
  ...
end

if json in { name:, hobby: }
  # BUG: age should not be set here
  puts "Hello #{name}, you enjoy #{hobby} and are #{age || "unknown"} years old"
end

When used as if expr in pattern, I think it is natural to expect no significant side effects, but changing local variables for a partial match seems kind of problematic.

#9

Updated by ktsj (Kazuki Tsujimoto) 4 days ago

Also available in: Atom PDF