Ruby의 패턴 일치, 그 역할, 코드 가독성 향상에 도움이 되는 방법에 대한 간략한 설명으로 시작하겠습니다.
몇 년 전 나와 같은 사람이라면 Regex의 패턴 일치와 혼동할 수 있습니다. 다른 컨텍스트 없이 '패턴 일치'를 Google에서 빠르게 검색해도 해당 정의에 매우 가까운 콘텐츠를 제공합니다.
공식적으로 패턴 일치는 다른 데이터와 비교하여 모든 데이터(문자 시퀀스, 일련의 토큰, 튜플 또는 기타)를 확인하는 프로세스입니다.
프로그래밍 측면에서 언어의 기능에 따라 다음 중 하나를 의미할 수 있습니다.
- 예상 데이터 유형과 일치
- 예상되는 해시 구조에 대한 일치(예:특정 키의 존재)
- 예상 배열 길이와 일치
- 일부 변수에 일치 항목(또는 일부) 할당
패턴 매칭을 처음 접한 것은 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
표현. when
및 in
분기는 단일 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의 변수 바인딩 및 고정
위의 몇 가지 예에서 보았듯이 패턴 일치는 패턴의 일부를 임의의 변수에 할당하는 데 정말 유용합니다. 이것을 변수 바인딩이라고 하며 변수에 바인딩할 수 있는 몇 가지 방법이 있습니다.
- 강력한 유형 일치 사용, 예:
in [Integer => a]
또는in {a: Integer => a}
- 유형 지정이 없는 경우, 예:
in [a, 1, 2]
또는in {a: a}
에서 . - 변수 이름이 없으면 기본적으로 키 이름을 사용합니다. 예:
in {a:}
a
라는 변수를 정의합니다.a
키의 값으로 . - 바인드 휴식, 예:
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 뉴스레터를 구독하고 게시물을 놓치지 마세요!