Project

General

Profile

Actions

Feature #20349

open

Pattern Matching - Expose local variable captures

Added by baweaver (Brandon Weaver) about 1 month ago. Updated about 1 month ago.

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

Description

In Regular Expressions we have the ability to utilize Regexp.last_match (link) to access the most recent match data from a regular expression match. I would like to propose that we have similar functionality for pattern matching:

require "rubocop"

def ast_from(string)
  case string
  when String then processed_source_from(string).ast
  when RuboCop::ProcessedSource then string.ast
  when RuboCop::AST::Node then string
  else nil
  end
end

def longform_block?(node)
  node in [:block,                        # s(:block,
    [:send, target, target_method],    #   s(:send, s(:array), :select),
    [[:arg, v]],                       #   s(:args, s(:arg, :v)),
    [:send, [:lvar, ^v], block_method] #   s(:send, s(:lvar, :v), :even?))
  ]
end

longform_block?(ast_from("[1, 2, 3].select { |v| v.even? }"))
# => true

# Name is not important, idea is, name can be debated. `source` is used to not
# show AST nodes
PatternMatch.last_match.transform_values(&:source)
# => {:node=>"[1, 2, 3].select { |v| v.even? }",
# :result=>"[1, 2, 3].select { |v| v.even? }",
# :target=>"[1, 2, 3]",
# :target_method=>:select,
# :v=>:v,
# :block_method=>:even?}

# Hacky version to show how / where behavior could be captured
def longform_block?(node)
  result = node in [:block,                        # s(:block,
    [:send, target, target_method],    #   s(:send, s(:array), :select),
    [[:arg, v]],                       #   s(:args, s(:arg, :v)),
    [:send, [:lvar, ^v], block_method] #   s(:send, s(:lvar, :v), :even?))
  ]

  pp(binding.local_variables.to_h { [_1, binding.local_variable_get(_1).then { |s| s.source rescue s }] })

  result
end

Updated by Eregon (Benoit Daloze) about 1 month ago

This would make every pattern matching a lot slower which seems a big no-no performance wise.
I think that's enough to reject this proposal.
Because it would force to create that binding/Hash eagerly in case PatternMatch.last_match is used later.

Actually, $~/Regexp.last_match and $_ are already causing significant performance problems for Ruby implementations, so I am strongly against any more of these "magically create variables in the caller".

BTW the example doesn't show how this is useful, is it to print the current state for debugging?
If so a helper method taking a binding seems appropriate (called inside the longform_block? method, not outside).

Updated by nobu (Nobuyoshi Nakada) about 1 month ago

baweaver (Brandon Weaver) wrote:

PatternMatch.last_match.transform_values(&:source)

Your proposal is PatternMatch.last_match, right?

# => {:node=>"[1, 2, 3].select { |v| v.even? }",
# :result=>"[1, 2, 3].select { |v| v.even? }",
# :target=>"[1, 2, 3]",
# :target_method=>:select,
# :v=>:v,
# :block_method=>:even?}

Why :node and :result which are not involved in the pattern matching are contained?

  pp(binding.local_variables.to_h { [_1, binding.local_variable_get(_1).then { |s| s.source rescue s }] })

You look like just want a method to make this local variable hash, to me.

Updated by Eregon (Benoit Daloze) about 1 month ago

nobu (Nobuyoshi Nakada) wrote in #note-2:

You look like just want a method to make this local variable hash, to me.

Binding#to_h or similar would be convenient.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0