Project

General

Profile

Actions

Feature #18384

open

Pattern Match Object

Added by baweaver (Brandon Weaver) over 2 years ago. Updated almost 2 years ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:106444]

Description

Related to discussion in #18369 it might be nice to have a literal syntax for constructing a single pattern match case outside of a one-liner.

Years ago in Qo I had done this via === to enable syntax like this:

list_of_people.select(&Qo[first_name: /^F/, last_name: /r$/, age: 20..40])

This is valid Ruby, but the pattern match syntax itself cannot be used outside of a literal match, making this impossible without syntax changes.

Proposal

My proposal would be a case which would be very useful in predicate methods (any?, all?, etc) and triple-equals responding methods (select, reject, grep, etc):

list_of_people.select(&pattern(
  first_name: /^F/,
  last_name: /r$/,
  age: 20..40 
))

...in which pattern would be substituted with a more appropriate name which I cannot think of at the moment.

Portability

Now the reason I think this could be very interesting is the portability of patterns. Consider the potential of making a PatternMatch object much like a Regular Expression:

TARGET_PERSON = PatternMatch.new(first_name: 'something')
list_of_people.select(&TARGET_PERSON)

As they can serve similar purposes of giving an expressive language to query against known structures I can see this making sense. The challenge is that the initialization of such an object would need to be special to accommodate the pattern matching syntax, adding more complicated parsing rules.

This behavior might be consistent with Proc, RegExp, and Range-like behavior.

ActiveRecord-like

This gets very close to the classic ActiveRecord where pattern:

People.where(age: 20..30)

Potential Issues

Now this is not without potential issue, as must be highlighted. The first, as just mentioned, is the ActiveRecord syntax and potentially overloading that and keyword arguments:

People.where(age: 20..30)

Without a clear signifier this could make parsing much more difficult.

Current Viable Workarounds

It also must be mentioned that this is currently possible:

list_of_people.select { _1 in { first_name: 'something' } }

...though the requirement of explicit braces feels a tinge verbose, I understand why they're present.

I think this is an acceptable compromise at the moment, but feel we're very close to an interesting syntactic breakthrough.

Updated by zverok (Victor Shepelev) over 2 years ago

I remember we briefly discussed the problem with @palkan (Vladimir Dementyev) and @matz (Yukihiro Matsumoto) in person at RubyConf'19, and, if the memory serves me, Matz said there was just no good syntax/semantics invented to put PM in a value.

If it could be achieved (and I really hope so!) there would be a lot of work considering how this value would be inspectable (e.g. #inspect and #to_s), and introspectable (e.g. something like #patter_type, #pattern_keys etc etc)โ€”all of it seem to have no current precedents in other features. Then, there would be a nasty question of dynamic creation, like, if I can do pattern(key_literal: value) then I probably should be able to pattern(key_variable_from_external_source => value_pattern_variable), which brings, like, a LOT of follow-up questions.

Maybe that's why this direction wasn't investigated: it leads to a proverbial rabbit hole. For now, PM seems both quite nice (haven't used it in production yet, we are still on 2.7, where it was "experimental") and quite "separate" from the rest of the language.

Updated by hmdne (hmdne -) over 2 years ago

There was an idea in the past, while various attempts to facilitate functional programming were considered, to deprecate a syntax like:

%w[hello world].map(&:length)

With a new syntax of:

%w[hello world].map{.length}

While this is unrelated, if this proposal gets revisited, then we may extend this proposal so we may also be able to do:

my_arrays.select { in [*, 3, *] }

From what I've understood, @matz (Yukihiro Matsumoto) decided to delay deployment of the aforementioned proposal, along with the .: operator because he wanted to get a bigger picture first.

But then, in this case maybe we could go even further and get rid of the {}s with a wordy operator like the following one, that also has a potential of being used also for other purposes:

my_arrays.select having in [*, 3, *]

Updated by cvss (Kirill Vechera) over 2 years ago

it leads to a proverbial rabbit hole

Probably, instead of a separate Patter Match class it would be enough to make a cosy shorthand creating a Proc that encloses a pattern matching code. That way, we avoid right now the need of the inspection and introspection of Pattern Match objects, and other extensions that I'm sure will follow like concatenation and intersection of patterns or more complex topologies. We even do not need know whether such a Proc object includes pattern matching or not.

Updated by palkan (Vladimir Dementyev) over 2 years ago

baweaver (Brandon Weaver) wrote:

Current Viable Workarounds

It also must be mentioned that this is currently possible:

list_of_people.select { _1 in { first_name: 'something' } }

In Ruby 3.1, we can omit parens:

list_of_people.select { _1 in first_name: 'something' | /another/ }

Much readable, WDYT?

Can we go further and remove _1? (As was mentioned above)

list_of_people.select { in first_name: 'something' | /another/ }

Not sure. And it's not clear how to handle multiline (do...end) scenario.
The following looks better to me (should we discuss adding _ as an alias for _1 again ๐Ÿ™‚?):

list_of_people.select { _ in first_name: 'something' | /another/ }

Omit in?

list_of_people.select { first_name: 'something' | /another/ }

Could be confused with a Hash. And multiline is hardly possible.

Another thing popped into my mind (the blast from the past): %p{...}

list_of_people.select(&%p{ first_name: 'something' | /another/ })

I like its explicitness; and simplicity in terms of implementation. But... &%p{...}.

To sum up, I think, the current syntax is pretty close to ideal:

list_of_people.select { _1 in first_name: 'something' | /another/ }
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0