티스토리 뷰

다트 공식 문서 번역

다트] 패턴

철철박사 2024. 7. 27. 15:10
반응형

패턴

패턴은 다트 언어 구문의 한 종류로 실제 값과 일치시킬 수 있는 값 집합의 형태를 나타낸다.

 

 

 

패턴이 하는 일

패턴은 값이 일치하는지 확인하는 매치(match)과 값의 구조를 분해(destructure)할 수 있다. 따로 사용하거나 둘 다 사용할 수 있다.

 

먼저 패턴 매칭은 주어진 값이 다음과 같은 조건을 만족하는지 확인할 수 있다.

  • 특정한 형태를 가지는지?
  • 특정한 상수인지?
  • 다른 값과 동일하지?
  • 특정한 타입을 가지는지?

그런 다음, 패턴 구조 분해를 사용하여 값을 구성 요소로 쉽게 분해할 수 있는 선언적 구문을 제공한다. 이 과정에서 해당 구성 요소 중 일부 또는 전부를 변수에 바인딩한다.

 

 

 

매칭

패턴은 항상 값이 패턴과 일치하는지 확인하는 것이다. 매치의 조건은 사용하는 패턴의 종류에 따라 다르다.

 

예를 들어, 상수 패턴은 값이 패턴의 상수와 동일한 경우에 일치한다.

switch (number) {
  // 상수 패턴은 1 == number인 경우 일치한다.
  case 1:
    print('one');
}

 

많은 패턴은 부분 패턴(subpatterns)을 사용한다. 부분 패턴은 외부 패턴(outer patterns)과 내부 패턴(inner patterns)을 사용한다. 외부 패턴이 만족할 때, 내부 패턴을 확인해 나가면서 재귀적으로 작동한다.

 

예를 들어, 모든 컬렉션 타입 패턴의 개별 필드는 변수 패턴이나 상수 패턴이 될 수 있다. 이 경우, 리스트가 외부 패턴이 되고, 개별 필드는 내부 패턴이 된다. 즉, 먼저 리스트인지 확인하고, 만족하면 개별 필드를 확인하게 된다.

const a = 'a';
const b = 'b';

switch(obj) {
  // 리스트 패턴[a, b]는 obj가 두 개의 요소를 가진 리스트인 경우를 먼저 확인하고,
  // 일치하면 서브패턴 'a'와 'b'가 일치하는지 확인한다.
  case [a, b]:
    print('$a, %b');
}

 

매칭된 값을 무시하려면 와일드카드 패턴(_)을 사용할 수 있다.

switch(obj) {
  // 리스트 패턴['a', _]는 obj가 두 개의 요소를 가진 리스트인 경우를 먼저 확인하고,
  // 일치하면 서브패턴 'a'가 일치하는지 확인한다.
  // 두 번째 요소는 와일드카드 패턴을 사용해서 어떤 값이 되든지 상관하지 않는다.
  case ['a', _]:
    print('$a, %b');
}

 

리스트 패턴의 경우 나머지 요소를 나타내는 rest element를 사용할 수 있다.

switch(obj) {
  // 리스트 패턴['a', ...]는 obj가 1개 이상의 요소를 가지는 리스트인지 먼저 확인한다.
  // 일치하면 서브패턴 'a'가 첫 번째 요소와 일치하는지 확인한다. 
  // 나머지 요소들은 상관하지 않는다.
  case ['a', ...]:
    print('$a, %b');
}

 

 

 

구조 분해

객체와 패턴이 일치하면 패턴은 객체의 데이터에 접근하고 구성 요소를 추출할 수 있다.

var numList = [1, 2, 3];

// 리스트 패턴 [a, b, c]는 numList의 세 요소를 분해하고,
// a, b, c 세 변수에 할당한다. 
var [a, b, c] = numList;

print(a + b + c);

 

패턴 내부에는 모든 종류의 패턴을 중첩시킬 수 있다. 예를 들어, 다음 패턴은 첫 번째 요소가 'a' 또는 'b'이고, 두 개의 요소를 가지는 리스트인지 확인한다. 일치하면 구조 분해를 해서 c 변수에 바인딩한다.

switch(list) {
  case ['a' || 'b', var c]:
    print(c);
}

 

 

 

패턴이 나타날 수 있는 장소

패턴은 다트 언어에서 여러 곳에서 사용할 수 있다. 

  • 지역 변수 선언 및 할당
  • for 및 for-in 루프
  • if-case 및 switch-case
  • 컬렉션 리터럴의 제어 흐름

 

지역 변수 선언

지역 변수 선언 패턴은 다트에서 지역 변수 선언이 허용되는 곳이면 어디서나 사용할 수 있다. 패턴은 선언 오른쪽의 값과 구조가 일치해야 한다. 일치하면 해당 값이 분해되고 새로운 지역 변수에 바인딩된다.

var (a, [b, c]) = ['str', [1, 2]];

 

 

변수 할당

변수 할당 패턴은 먼저 일치하는 객체를 분해한 후에 값을 기존 변수에 할당한다. 예를 들어 두 개의 변수 값을 서로 교환하기 위해 변수 할당 패턴을 사용할 수 있다.

(a, b) = (b, a);

 

 

if-case 및 switch-case

모든 case 절에는 패턴이 있다. 이는 switch 문과 switch 표현식뿐만 아니라 if-case 문에도 적용된다. case에서는 어떤 종류의 패턴이든 사용할 수 있다.

 

case 패턴은 거부 가능하다. 즉, 패턴이 일치하지 않아도 에러가 발생하지 않는다. case 패턴이 일치하지 않는 경우, 다음 case로 실행이 계속된다.  case 패턴으로 분해한 값을 해당 case의 본문 내에서만 지역 변수로 사용된다.

switch(obj) {
  // obj가 1이면 일치한다.
  case 1:
    print('one');
  
  // obj가 first와 last 상수 값 사이에 있는 경우 일치한다.
  case >= first & <= last:
    print('in range');
  
  // obj가 두 개의 요소로 구성된 레코드이면, 해당 필드를 a와 b에 할당한다.
  case (var a, var b):
    print('a = $a, b = $b');
  
  default:
}

 

논리 OR 패턴은 switch 표현식이나 구문에서 여러 case가 동일한 바디 본문을 공유해야 할 때 유용하다.

var isPrimary = switch (color) {
  Color.red || Color.yellow || Color.blue => true,
  _ => false
};

 

논리 OR 패턴은 여러 case가 동일한 조건을 공유할 수 있도록 할 수도 있다. 조건은 when으로 나타낸다.

switch (shape) {
  case Square(size: var s) || Circle(size: var s) when s > 0:
    print('Non-empty symmetric shape');
}

 

 

for 및 for-in 루프

for 및 for-in 루프에서 패턴을 사용하여 컬렉션의 값에 대해 반복하고 분해할 수 있다.

 

다음 예제는 for-in 루프 객체 분해를 사용한다. for-in에 맵의 entries를 전달하고 MapEntry 객체를 분해한다.

Map<String, int> hist = {
  'a': 23,
  'b': 100,
}

for (var MapEntry(key: key, value: count) in hist.entries) {
  print('$key occured $count times');
}

 

객체 패턴은 hist.entries가 MapEntry 타입인지 확인한 다음, key와 value 이름으로 서브패턴을 진행한다. 결과적으로 각 반복에서 MapEntry의 key와 value의 게터가 호출되고, 각각의 지역변수 key와 count에 바인딩된다.

 

같은 이름의 게터 호출 결과를 같은 이름의 변수에 바인딩하는 것은 일반적인 사용법이다. 따라서 key:key와 같으느 중복된 구문을 :key와 같이 간소화할 수 있다.

for (var MapEntry(:key, value: count) in hist.entries) {
  print('$key occured $count times');
}

 

 

 

패턴의 사용 사례

이 섹션에서는 더 많은 사용 사례를 설명하며 다음과 같은 질문에 답한다.

  • 언제, 왜 패턴을 사용해야 하는가?
  • 어떤 종류의 문제를 해결할 수 있는가?
  • 어떤 관용구에 가장 적합한다?

 

여러 반환 값을 분해하기

레코드는 하나의 함수 호출에서 여러 값을 묶어서 반환할 수 있다. 패턴은 레코드의 필드를 함수 호출과 함께 직접 지역 변수로 분해할 수 있다.

 

다음은 패턴을 이용해서 레코드의 필드를 분해하는 예제이다.

// 패턴을 사용하지 않는 경우
var info = userInfo(json);
var name = info.$1;
var age = info.$2;
// 패턴을 사용하는 경우
var (name, age) = userInfo(json);

 

 

클래스 인스턴스 분해하기

객체 패턴은 지정된 객체 타입과 일치하면 해당 클래스의 게터를 사용하여 데이터를 분해할 수 있다. 클래스의 인스턴스를 분해하려면 지정된 타입 다음에 괄호 안에 분해할 속성을 지정한다. 

final Foo myFoo = Foo(one: 'one', two: 2);

var Foo(:one :two) = myFoo;
print('one $one, two $two');

 

 

 

복합 데이터 타입

객체 분해와 switch case는 복합 데이터 타입 스타일로 코드를 작성하는 데 적합하다. 

 

다음과 같은 경우에 이 방법을 사용하자.

  • 관련된 여러 타입들이 있다.
  • 특정한 동작이 각 타입마다 다르게 해야 한다.
  • 특정한 동작을 모든 타입 정의에 분산시키는 대신, 하나의 공통된 함수에 모아두고 싶다.

각 타입마다 인스턴스 메서드로 동작을 구현하는 대신, 단일 함수에서 switch를 사용해서 동작을 관리한다.

sealed class Shape {}

class Square implements Shape {
  final double length;
  Square(this.lenth);
}

class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

double calculateArea(Shape shape) => switch (shape) {
  Square(length: var l) => l * l,
  Circle(radius: var r) => 3.14 * r * r
};

 

 

 

JSON 검증

Map과 List 패턴은 JSON 데이터의 키-값 쌍을 분해하는 데 효과적이다.

var json = {
  'user': ['Lily', 13]
};

var {'user': [name, age]} = json

 

일반적으로 외부에서 입력되는 JSON 데이터는 예상한 구조를 가진다는 보장은 없다. 그러므로 먼저 데이터 구조를 확인하고 유효성을 검사해야 한다. 

 

패턴을 사용하지 않는 경우, 유효성 검사가 상당히 길고 장황해진다.

if (json is Map<String, Object?> &&
    json.length == 1 &&
    json.containsKey('user')) {
  var user = json['user'];
  
  if (user is List<Object> &&
      user.length == 2 &&
      user[0] is String &&
      user[1] is int) {
    var name = user[0] as String;
    var age = user[1] as int;
    print('User $name is $age years old.');
  }
}

 

단일 case 패턴으로 동일한 검증을 수행할 수 있다. 단일 case는 switch와 if-case로 구현할 수 있다. 보통 단일 case는 if-case문이 가장 적합하다.

 

패턴을 사용하면 코드는 훨씬 간결해 진다.

if (json case {'user': [String name, int age]}) {
  print('User $name is $age years old.');
}

 

이 경우 패턴은 다음을 동시에 검증한다.

  • json이 Map인지 확인
  • Map이면 json이 null이 아님을 확인
  • json이 'user'라는 키를 포함하고 있는지 확인
  • 'user' 키가 두 개의 값을 구성된 리스트인지 확인
  • 리스트의 값의 타입이 String과 int인지 확인
  • 값들을 담을 새로운 지역 변수의 타입이 String과 int인지 확인
반응형

'다트 공식 문서 번역' 카테고리의 다른 글

다트] 반복  (0) 2024.07.28
다트] 패턴 종류  (0) 2024.07.27
다트] 다트 타입 시스템  (0) 2024.07.25
다트] 타입 별칭, 인라인 함수 타입  (0) 2024.07.24
다트] 제네릭  (0) 2024.07.24
댓글
공지사항