Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby의 패턴 매칭 소개

Ruby의 패턴 일치, 그 역할, 코드 가독성 향상에 도움이 되는 방법에 대한 간략한 설명으로 시작하겠습니다.

몇 년 전 나와 같은 사람이라면 Regex의 패턴 일치와 혼동할 수 있습니다. 다른 컨텍스트 없이 '패턴 일치'를 Google에서 빠르게 검색해도 해당 정의에 매우 가까운 콘텐츠를 제공합니다.

공식적으로 패턴 일치는 다른 데이터와 비교하여 모든 데이터(문자 시퀀스, 일련의 토큰, 튜플 또는 기타)를 확인하는 프로세스입니다.

프로그래밍 측면에서 언어의 기능에 따라 다음 중 하나를 의미할 수 있습니다.

  1. 예상 데이터 유형과 일치
  2. 예상되는 해시 구조에 대한 일치(예:특정 키의 존재)
  3. 예상 배열 길이와 일치
  4. 일부 변수에 일치 항목(또는 일부) 할당

패턴 매칭을 처음 접한 것은 Elixir를 통해서였습니다. Elixir는 패턴 일치를 위한 일급 지원을 제공하므로 = 연산자는 사실 match입니다. 단순 할당이 아닌 연산자입니다.

이것은 Elixir에서 다음이 실제로 유효한 코드임을 의미합니다:

iex> x = 1
iex> 1 = x

이를 염두에 두고 오늘부터 Ruby 2.7 이상에 대한 새로운 패턴 일치 지원과 이를 사용하여 코드 가독성을 높이는 방법을 살펴보겠습니다.

case를 사용한 루비 패턴 매칭 /in

Ruby는 특별한 case와 패턴 일치를 지원합니다. /in 표현. 구문은 다음과 같습니다.

case <expression>
in <pattern1>
  # ...
in <pattern2>
  # ...
else
  # ...
end

case와 혼동하지 마십시오. /when 표현. whenin 분기는 단일 case에 혼합될 수 없습니다. .

else를 제공하지 않는 경우 표현식에서 실패하면 NoMatchingPatternError가 발생합니다. .

Ruby의 패턴 일치 배열

패턴 일치는 데이터 유형, 길이 또는 값에 대해 사전에 필요한 구조에 배열을 일치시키는 데 사용할 수 있습니다.

예를 들어, 다음은 모두 일치합니다(첫 번째 in case로 평가됩니다. 첫 번째 경기를 돌보지 않음):

case [1, 2, "Three"]
in [Integer, Integer, String]
  "matches"
in [1, 2, "Three"]
  "matches"
in [Integer, *]
  "matches" # because * is a spread operator that matches anything
in [a, *]
  "matches" # and the value of the variable a is now 1
end

이 유형의 패턴 일치 절은 메서드 호출에서 여러 신호를 생성하려는 경우 매우 유용합니다.

Elixir 세계에서 이것은 :ok 둘 다 가질 수 있는 작업을 수행할 때 자주 사용됩니다. 결과 및 :error 예를 들어 데이터베이스에 삽입된 결과입니다.

더 나은 가독성을 위해 사용하는 방법은 다음과 같습니다.

def create
  case save(model_params)
  in [:ok, model]
    render :json => model
  in [:error, errors]
    render :json => errors
  end
end
 
# Somewhere in your code, e.g. inside a global helper or your model base class (with a different name).
def save(attrs)
  model = Model.new(attrs)
  model.save ? [:ok, model] : [:error, model.errors]
end

Ruby의 패턴 일치 개체

Ruby의 객체를 일치시켜 특정 구조를 적용할 수도 있습니다.

case {a: 1, b: 2}
in {a: Integer}
  "matches" # By default, all object matches are partial
in {a: Integer, **}
  "matches" # and is same as {a: Integer}
in {a: a}
  "matches" # and the value of variable a is now 1
in {a: Integer => a}
  "matches" # and the value of variable a is now 1
in {a: 1, b: b}
  "matches" # and the value of variable b is now 2
in {a: Integer, **nil}
  "does not match" # This will match only if the object has a and no other keys
end

이는 모든 매개변수에 대한 일치에 대한 강력한 규칙을 부과할 때 효과적입니다.

예를 들어, 멋진 인사를 작성하는 경우 다음과 같은(강력한 의견) 구조를 가질 수 있습니다.

def greet(hash = {})
  case hash
  in {greeting: greeting, first_name: first_name, last_name: last_name}
    greet(greeting: greeting, name: "#{first_name} #{last_name}")
  in {greeting: greeting, name: name}
    puts "#{greeting}, #{name}"
  in {name: name}
    greet(greeting: "Hello", name: name)
  in {greeting: greeting}
    greet(greeting: greeting, name: "Anonymous")
  else
    greet(greeting: "Hello", name: "Anonymous")
  end
end
 
greet # Hello, Anonymous
greet(name: "John") # Hello, John
greet(first_name: "John", last_name: "Doe") # Hello, John Doe
greet(greeting: "Bonjour", first_name: "John", last_name: "Doe") # Bonjour, John Doe
greet(greeting: "Bonjour") # Bonjour, Anonymous

Ruby의 변수 바인딩 및 고정

위의 몇 가지 예에서 보았듯이 패턴 일치는 패턴의 일부를 임의의 변수에 할당하는 데 정말 유용합니다. 이것을 변수 바인딩이라고 하며 변수에 바인딩할 수 있는 몇 가지 방법이 있습니다.

  1. 강력한 유형 일치 사용, 예:in [Integer => a] 또는 in {a: Integer => a}
  2. 유형 지정이 없는 경우, 예:in [a, 1, 2] 또는 in {a: a}에서 .
  3. 변수 이름이 없으면 기본적으로 키 이름을 사용합니다. 예:in {a:} a라는 변수를 정의합니다. a 키의 값으로 .
  4. 바인드 휴식, 예:in [Integer, *rest] 또는 in {a: Integer, **rest} .

그렇다면 기존 변수를 하위 패턴으로 사용하려는 경우 어떻게 일치시킬 수 있습니까? 변수 고정을 사용할 수 있는 경우입니다. ^ (핀) 연산자:

a = 1
case {a: 1, b: 2}
in {a: ^a}
  "matches"
end

변수가 패턴 자체에 정의되어 있는 경우에도 이것을 사용할 수 있으므로 다음과 같은 강력한 패턴을 작성할 수 있습니다.

case order
in {billing_address: {city:}, shipping_address: {city: ^city}}
  puts "both billing and shipping are to the same city"
else
  raise "both billing and shipping must be to the same city"
end

변수 바인딩과 관련하여 언급해야 할 중요한 단점 중 하나는 패턴이 완전히 일치하지 않더라도 변수는 여전히 바인딩되어 있다는 것입니다. 이는 때때로 유용할 수 있습니다.

그러나 대부분의 경우 이는 미묘한 버그의 원인이 될 수도 있습니다. 따라서 경기 내에서 사용된 그림자 변수 값에 의존하지 않도록 하십시오. 예를 들어 다음과 같이 도시가 "Amsterdam"이지만 대신 "Berlin"이 됩니다.

city = "Amsterdam"
order = {billing_address: {city: "Berlin"}, shipping_address: {city: "Zurich"}}
case order
in {billing_address: {city:}, shipping_address: {city: ^city}}
  puts "both billing and shipping are to the same city"
else
  puts "both billing and shipping must be to the same city"
end
puts city # Berlin instead of Amsterdam

루비의 커스텀 클래스 매칭

Ruby에서 사용자 정의 클래스 패턴 일치를 인식하도록 몇 가지 특별한 방법을 구현할 수 있습니다.

예를 들어 사용자의 first_name에 대해 패턴을 일치시키려면 및 last_name , 우리는 deconstruct_keys를 정의할 수 있습니다. 수업:

class User
  def deconstruct_keys(keys)
    {first_name: first_name, last_name: last_name}
  end
end
 
case user
in {first_name: "John"}
  puts "Hey, John"
end

keys deconstruct_keys에 대한 인수 패턴에서 요청된 키를 포함합니다. 이것은 모든 키를 계산하는 데 비용이 많이 드는 경우 수신자가 필요한 키만 제공하는 방법입니다.

deconstruct_keys와 같은 방식으로 , 우리는 deconstruct의 구현을 제공할 수 있습니다. 객체가 배열로 패턴 일치되도록 허용합니다. 예를 들어 Location이 있다고 가정해 보겠습니다. 위도와 경도가 있는 클래스. deconstruct_keys 사용 외에도 위도 및 경도 키를 제공하기 위해 [latitude, longitude] 형식의 배열을 노출할 수 있습니다. 뿐만 아니라:

class Location
  def deconstruct
    [latitude, longitude]
  end
end
 
case location
in [Float => latitude, Float => longitude]
  puts "#{latitude}, #{longitude}"
end

복잡한 패턴에 가드 사용

일반 패턴 일치 연산자로 표현할 수 없는 복잡한 패턴이 있는 경우 if를 사용할 수도 있습니다. (또는 unless ) 경기에 대한 가드를 제공하는 문:

case [1, 2]
in [a, b] if b == a * 2
  "matches"
else
  "no match"
end

=>를 사용한 패턴 일치 /in case 없이

Ruby 3+를 사용 중인 경우 더 많은 패턴 일치 마법에 액세스할 수 있습니다. Ruby 3부터는 case 문 없이 한 줄로 패턴 일치를 수행할 수 있습니다.

[1, 2, "Three"] => [Integer => one, two, String => three]
puts one # 1
puts two # 2
puts three # Three
 
# Same as above
[1, 2, "Three"] in [Integer => one, two, String => three]

위의 구문에 else가 없는 경우 절, 데이터 구조를 미리 알고 있을 때 가장 유용합니다.

예를 들어 이 패턴은 관리자만 허용하는 기본 컨트롤러 내부에 잘 맞을 수 있습니다.

class AdminController < AuthenticatedController
  before_action :verify_admin
 
  private
 
  def verify_admin
    Current.user => {role: :admin}
  rescue NoMatchingPatternError
    raise NotAllowedError
  end
end

Ruby의 패턴 매칭:Watch This Space

처음에는 패턴 매칭이 이해하기 조금 이상하게 느껴질 수 있습니다. 누군가에게는 영광스러운 객체/배열 해체처럼 느껴질 수도 있습니다.

하지만 Elixir의 인기를 짐작할 수 있는 척도가 된다면 패턴 매칭은 무기고에 있는 훌륭한 도구입니다. Elixir에서 직접 사용해 본 결과 익숙해지면 없이는 살기 힘들다는 것을 확인할 수 있습니다.

Ruby 2.7을 사용하는 경우 패턴 일치(case /in ) 아직 실험적입니다. Ruby 3에서는 case /in 새로 도입된 단일 라인 패턴 일치 표현식이 실험적인 동안 안정 상태로 이동했습니다. Warning[:experimental] = false로 경고를 끌 수 있습니다. 코드 또는 -W:no-experimental 명령줄 키.

Ruby의 패턴 매칭은 아직 초기 단계이지만 이 소개가 유용하고 앞으로의 개발에 대해 저만큼 기대가 컸으면 합니다!

추신 Ruby Magic 게시물이 언론에 공개되는 즉시 읽고 싶다면 Ruby Magic 뉴스레터를 구독하고 게시물을 놓치지 마세요!