티스토리 뷰

다트 공식 문서 번역

다트] 패턴 종류

철철박사 2024. 7. 27. 23:43
반응형

패턴 종류

패턴이 어떻게 작동하는지, 어디에서 사용할 수 있는지, 그리고 일반적인 사용 사례에 대해서 알아보자.

 

 

 

패턴 우선순위

연산자의 우선순위와 유사하게, 패턴 평가도 우선 순위 규칙을 따른다. 낮은 우선순위의 패턴을 먼저 계산하기 위해 괄호로 둘러싼 패턴을 사용할 수 있다.

 

다음은 우선순위를 오름차순으로 나열한 것이다.

  • 논리 or 패턴
  • 논리 and 패턴
  • 관계 패턴 
  • 후위 단항 패턴(case, null-check, null-assert)
  • 나머지 기본 패턴, 컬렉션 타입 패턴(레코드, 리스트, 맵) 및 객체 패턴은 다른 데이터를 포함하므로 가장 먼저 외부 패턴으로 평가된다.

 

 

 

논리 or 패턴 ( 서브패턴 1 || 서브패턴 2)

논리 or 패턴은 서브패턴 1 || 서브패턴 2와 같이 서브 패턴을 분리하고 어느 한 분기라도 일치하면 일치한다. 분기는 왼쪽에서 오른쪽으로 평가된다. 한 분기가 일치하면 나머지는 평가되지 않는다.

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

 

논리 or 패턴의 서브패턴은 변수를 바인딩할 수 있지만, 패턴이 일치할 때는 하나의 분기만 평가되기 때문에 분기는 동일한 변수 세트를 정의해야 한다.

 

 

 

논리 and 패턴 (서브패턴 1 && 서브패턴 2)

&&로 구분된 서브패턴이 모두 참일 때만 일치한다. 왼쪽 분기가 일치하지 않으면 오른쪽 분기는 평가되지 않는다.

 

논리 곱 패턴의 서브 패턴은 변수로 바인딩할 수 있지만, 각 서브패턴의 변수는 겹치지 않아야 한다. 패턴이 일치하면 두 변수가 모두 바인딩된다.

switch ((1, 2)) {
  // 오류, 두 서브패턴이 모두 b를 바인딩하려고 시도한다.
  case (var a, var b) && (var b, var c): // ...
}

 

 

 

관계 패턴 ( == 표현식, < 표현식 등...)

관계 패턴은 동등 또는 관계 연산자(==, !=, <, <=, >, >=)를 사용하여 일치하는 값을 주어진 상수와 비교한다. 패턴은 주어진 상수를 인수로 사용하여 일치하는 경우 true가 된다.

관계 패턴은 수치 범위에 대한 매칭에 유용하며, 특히 논리 and 패턴과 결합할 때 유용하다.

String asciiCharType(int char) {
  const space = 32;
  const zero = 48;
  const nine = 57;
  
  return switch (char) {
    < space => 'control',
    == space => 'space',
    > space && < zero => 'punctuation',
    >= zero && <= nine => 'digit',
    _ => '',
  };
}

 

 

 

캐스트 패턴 (as)

캐스트 패턴(타입 변환 패턴)은 패턴 분해 중간에 타입 변환을 삽입하여 값을 다른 서브 패턴에 전달하는 데 사용된다.

(num, Object) record = (1, 's');
var (i as int, s as String) = record;

 

캐스트 패턴은 값이 명시된 타입을 가지고 있지 않을 경우 예외를 발생한다.

 

 

 

널-체크 패턴 (서브패턴?)

널-체크(null-check) 패턴은 null이 아닌 경우를 확인하고, 내부 패턴을 확인한다. null이면 매치는 실패한다. 이를 통해서 null이 아닌 값을 바인딩할 수 있게 된다.

 

null 값을 매치 실패로 취급하고 예외를 발생하지 않게 하려면 널-체크 패턴을 사용하자.

String? maybeString = 'string';

switch (maybeString) {
  case var s?:
    // case 본문에서 s는 String이 된다.
    print(s.length);
  case null:
    print(0);
}

 

값이 null일 때 일치하게 하려면 상수 null을 사용하자.

 

 

 

널-단언 패턴 (서브패턴!)

널-단언(null-assert) 패턴은 객체가 null이 아닌 경우에 먼저 일치하고, 그다음에 값을 대상으로 일치한다. null인 경우에는 예외를 발생한다. 

 

null 값은 매치 실패가 아닌 에러가 되게 하려면 널-단언 패턴을 사용하자.

List<String?> row = ['user', null];

switch (row) {
  case ['user', var name!]:  // 에러 발생
    // case 본문에 name는 널 가능이 아닌 String 타입이 된다.
    print(name);
}

 

변수 선언 패턴에서 널 값을 제거하기 위해 널-단언 패턴을 사용하면 된다. 하지만 null이면 예외를 발생한다.

(int?, int?) position = (2, 3);

var (x!, y!) = position;

 

 

 

상수 패턴 (123, null, 'abc', ....)

상수 패턴은 값이 상수와 동일할 때 일치한다. 

switch (number) {
  // 1 == number인 경우 일치한다.
  case 1:
    // ...
}

 

간단한 리터럴과 명명된 상수 참조를 상수 패턴으로 직접 사용할 수 있다.

  • 숫자 리터럴 - 123, 3.14
  • 불리언 리터럴 - true, false
  • 문자열 리터럴 - 'abc'
  • 명명된 상수 - someConstant, math.pi, double.infinity
  • 상수 생성자 - const Point(0, 0)
  • 상수 컬렉션 리터럴 - const [], const {1, 2}

더 복잡한 상수 표현식은 괄호를 둘러싸고 const로 접두사를 붙여야 한다 : const (1 + 2)

// 리스트
case [a, b]:

// 상수 리스트
case const [1, 2]:

 

 

 

변수 패턴 (var, final, String, int, _)

변수 패턴은 일치하거나 해체된 값에 새 변수를 바인딩한다. 패턴이 일치한 경우에만 도달할 수 있는 코드 영역에서 변수가 유효한다. 그 외의 코드 영역에서는 변수를 사용할 수 없다.

switch ((1, 2)) {
  // 2개의 값을 가지는 레코드이면 a, b 변수에 바인딩하는 패턴이다.
  case (var a, var b):
    // case 본문에서만 a, b가 유효한 변수이다.
}

 

타입이 지정된 변수 패턴은 값의 타입도 일치해야 한다. 

switch ((1, 2)) {
  // 일치하지 않는다.
  case (int a, String b):
    // ...
}

 

와일드카드를 변수 패턴에 사용하면 모든 값을 허용하게 된다.

case (int a, _): // ...

 

 

 

식별자 패턴 (foo, _)

식별자 패턴은 콘텍스트에 따라 상수 패턴이나 변수 패턴으로 동작할 수 있다. 

 

선언 컨텍스트 - 식별자 이름과 함께 새 변수를 선언한다.

var (a, b) = (1, 2);

 

할당 컨텍스트 - 기존 변수에 식별자 이름을 할당한다.

int a, b;
(a, b) = (1, 2);

 

매칭 컨텍스트 - 이름이 와일드카드(_)가 아닌 경우에는 명명된 상수 패턴으로 처리된다.

const c = 1;

switch (2) {
  case c:
    print('match $c');
  default:
    print('no match');
}

 

어떤 콘텍스트에서든 와일드카드 식별자는 모든 값을 일치시키고 해당 값을 버린다. 

case [_, var y, _]:
  print('The middle element is $y);

 

 

 

괄호

수식에 괄호를 사용하는 것과 마찬가지로 패턴에서 괄호를 사용하면 패턴 우선순위를 조절할 수 있다.

예를 들어, 불리언 상수 x, y, z가 각각 true, true, false로 설정된 경우를 가정해 보겠다.

x || y && z => ...
(x || y) && z => ...

 

첫 번째 경우에서는 논리 and 패턴인 y && z가 먼저 평가된다. 논리 and 패턴이 논리 or 패턴보다 우선순위가 높기 때문이다. 두 번째 경우에는 논리 or 패턴이 괄호로 묶어서 먼저 평가되므로 다른 일치 일치 결과가 나오게 된다.

 

 

 

리스트 패턴 ([서브패턴 1, 서브패턴 2])

리스트 패턴은 List를 구현하는 값과 일치하는지 확인한다.

case [a, b]:  // ...

 

리스트 패턴은 요소 수가 전체 리스트와 일치해야 한다. 그러나 리스트 요소 수에 관계없이 나머지 요소를 대신할 수 있는 나머지 요소(rest element)를 사용할 수 있다.

 

 

 

나머지 요소 패턴(...)

리스트 패턴에는 임의 길이의 리스트와 일치하는 나머지 요소(rest element)를 하나 포함시킬 수 있다.

var [a, b, ..., c, d] = [1, 2, 3, 4, 5, 6, 7];

print('$a $b $c $d');  // 1 2 6 7

 

나머지 요소는 다른 서브 패턴과 일치하지 않는 요소를 새 리스트로 바인딩하는 서브패턴을 가질 수도 있다.

var [a, b, ...rest, c, d] = [1, 2, 3, 4, 5, 6, 7];

print('$a $b $rest $c $d');  // 1 2 [3, 4, 5] 6 7

 

 

 

Map 패턴 ({"key": 서브패턴 1, someConst: 서브패턴 2}

Map 패턴은 Map을 구현한 값과 일치하는지 확인하거나 맵의 키에 대해 서브패턴을 재귀적으로 일치하는지 확인한다.

Map 패턴은 패턴이 전체 맵과 일치할 필요는 없다. 맵 패턴은 패턴과 일치하지 않는 키는 무시한다.

 

 

 

Record 패턴 ((서브패턴 1, 서브패턴2), (x: 서브패턴1, y: 서브패턴 2))

레코드 패턴은 레코드 객체와 일치하는지 확인하거나 레코드의 필드를 구조분해한다. 값이 패턴과 같은 모양의 레코드가 아니면 일치하지 않게 된다. 

 

레코드 패턴은 패턴이 전체 레코드와 일치해야 한다. 패턴을 사용하여 명명된 필드가 있는 레코드를 구조 분해하려면 패턴에 필드 이름을 포함시키면 된다.

var (myString: foo, myNumber: bar) = (myString: 'string', myNumber: 1);

 

필드 서브패턴의 변수 패턴과 식별자 패턴에 대한 게터 이름은 생략할 수 있으며, 변수 패턴이나 식별자 패턴에서 추론될 수 있다. 

 

다음 패턴 쌍은 모두 동일

// 변수 서브 패턴을 사용한 레코드 패턴
var (untyped: untyped, typed: int typed) = record;
var (:untyped, :int typed) = record;

switch (record) {
  case (untyped: var untyped, typed: int typed):  //...
  case (:var untyped, :int typed):  // ...
}


// 널-체크와 널-단언 서브패턴을 사용한 레코드 패턴
switch(record) {
  case (checked: var checked?, asserted: var asserted!):  // ...
  case (:var checked?, :var asserted!):  // ...
}


// 캐스트 서브패턴을 사용한 레코드 패턴
var (untyped: untyped as int, typed: typed as String) = record;
var (:untyped as int, :typed as String) = record;

 

 

 

객체 패턴 (SomeClass(x: 서브패턴 1, y: 서브패턴 2))

객체 패턴은 객체의 속성에 대한 게터를 사용하여 데이터를 구조분해 하기 위해 일치하는 값과 지정된 이름이 있는 타입을 확인한다. 값이 같은 타입이 아니면 일치하지 않는다.

switch (shape) {
  // shape가 Rect 타입이고, Rect의 속성이 일치하는 경우에 일치한다.
  case Rect(width: var w, height: var h):  // ...
}

 

필드 서브패턴에 변수 패턴이나 식별자 패턴에서 변수 패턴에 대한 게터 이름은 생략할 수 있으며, 변수 패턴이나 식별자 패턴에서 추론될 수 있다.

// Point의 x와 y 속성의 값을 변수 x와 y에 바인딩한다.
var Point(:x, :y) = Point(1, 2);

 

객체 패턴은 패턴이 전체 객체와 일치할 필요가 없다. 일부 필드만 구조분해할 수 있다.

 

 

 

와일드카드 패턴 (_)

와일드카드(_) 패턴은 어떤 변수 패턴이나 식별자 패턴이든 변수에 바인딩하지 않는 와일드카드이다.

나중에 위치 값 구조 분해를 위해 서브패턴이 필요한 위치에 플레이스홀더로 유용하다. 

var list = [1, 2, 3];
var [_, two, _] = list;

 

타입 주석을 가진 와일드카드 이름은 값의 타입을 확인하지만 값은 바인딩하지 않을 때 유용하다.

switch (record) {
  case (int _, String _):
    print('첫 번째 필드는 int이고, 두 번째 필드는 String이다');
}
반응형

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

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