0. 시작 전 정리

https://www.youtube.com/watch?v=KhYTFglbF2k
영상을 보고 내용을 정리합니다.
intro 부터 Usable Switches 까지 내용을 다룹니다. 나머지 내용 도 곧 정리해 발행 예정입니다.

  • Speaker: Bob Nystrom (Dart 언어팀 소프트웨어 엔지니어)
  • Recordpattern matching은 Dart 3.0에서 제공되는 가장 큰 두 가지 기능입니다. 그것들이 무엇인지, 그것들이 무엇에 유용한지, Dart 언어의 나머지 부분과 어떻게 조화를 이루는지에 대해 이야기할 것입니다.



1. intro

  • 몇 년동안 사용자들이 패턴 매칭을 Dart에 추가해달라고 요청이 있었습니다. 그래서 Dart3에 대해 우리가 작업하고 있는것을 보여주면 재밌을 것 같아 영상 준비 했습니다.
  • 패턴 매칭은 1970년대부터 프로그래밍 언어와 관련있었는데 대부분 ML 머신러닝 제품군의 함수형(functional) 언어에 국한되어 있었습니다.
  • 지난 10년 동안 JavaScript의 distructuring, Python의 tuple, C#의 pattern matching 등 다른 언어의 유사한 기능을 통해 주류(mainstream)로 이동하기 시작 했습니다.
  • 따라서 오늘 제가 "tuple" 또는 "tupple" 또는 "record" 또는 "distructuring(디스트럭처링)"이라고 말하면 제가 말하는 내용을 쉽게 이해하게 될 것 입니다.
  • 근데 몰라도 괜찮습니더 이 강연은 젠틀한 소개 영상이니깐요,
    패턴 매칭에 익숙하신 분들도 저희가 원래 다트를 중심으로 디자인되지 않았던 것을 어떻게 개조하고 있는지 흥미롭게 봐주셨으면 합니다.
  • 조정될 수도 있고 모든 내용을 다루지도 않을 거지만
    두 가지 기능이 어떻게 작동하는지 보여주기를 바랍니다.



2. Multiple Return

https://youtu.be/KhYTFglbF2k?t=118
: 함수가 여러값을 반환하고 싶을 때

하나의 예시를 보여드리겠습니다.

  • json 데이터 값을 가져와 두 요소를 추출해야되고, 각각을 병시적으로 다운캐스팅까지 해야합니다. (name이 문자열이고 age가 int라는거 추적 못하는 상황)
  • 그럴 땐 클래스 정의로 이 문제를 해결할 수 있습니다.

    그럼 안전하고 좋아요, 캐스트도 사라지고!
    근데 몇 비트의 데이터를 묶기 위해 일부 클래스를 정의하고 이름을 정의하는건 장황한 일입니다.

record

따라서 첫 번째 제안은 레코드(records)라는 새로운 기본 제공 컬렉션 유형을 추가하는 것입니다.

레코드 표현법은 list Literal처럼 보이지만 대괄호가 아닌 괄호로 표현합니다.

  (String, int) userInfo(Map json) { ... }
  • 레코드 타입 annotation은 괄호로 묶인 필드 리스트 이기도 합니다.
  • 여기서 중요한 점은 각 필드 마다 고유한 타입이 있다는 것 입니다.
    • 따라서 이전 list를 사용하는 것과 달리 레코드로 반환할 때 타입 시스템은 name이 문자열이고 age가 int임을 기억합니다.

그런 다음 호출자 쪽에서 레코드에 해당 필드를 가져올 땐 아래와 같이 표현합니다.

var name = info.$1;
var age = info.$2;
  • $로 시작하는 게터일 뿐
  • 각 필드가 다른 유형을 가질 수 있기에 인덱스를 사용하는 아래 첨자 연산자[] 대신 각 필드에 명명된 게터.$를 사용했습니다.

잠시 이론적인 내용으로 빠져보면,,

What are records?

  • records는 real values이기에 records와 변수, 리스트에 저장 할 수 있고 함수에 전달할 수 있습니다.
  • 그리고 Dart 모든 것과 마찬가지로 records도 object, 객체 입니다.
  • records는 value types 입니다.
    여러 필드에 equals, hashCode 메소드를 자동으로 정의합니다.
    • 따라서 동일한 필드 세트가 있고 세트 필드 값이 모두 동일한 경우 두 레코드는 동일하다고 바라봅니다.
  • object이지만 records엔 persistent oject identity가 없습니다.
    • 이것은 기본적으로 컴파일러가 할당을 자유롭게 최적화하고 매우 가볍게 만든다는 것을 의미합니다.

자,, 그럼,, 다시 코드로 돌아와서 이어서 설명하겠습니다.

records 표현식 구문은 예쁘지만, 호출 부분에서 각 필드에 접근하는 표현식 구문은 여전히 별로입니다.

매번 로컬 변수로 저장하는 코드 귀찮아지는 일이 잖아요
그래서 다음 단계는 destructuring 입니다.

record pattern


우리는 이것을 패턴이라고 부르고
위와 같이 두개의 필드가 있는 경우엔 레코드 패턴이라고 칭합니다.

  • 패턴은 항상 어떤 값과 일치하고, 위 코드는 함수 호출의 결과입니다.
  • 레코드 패턴은 레코드의 두 필드에 엑세스 합니다.
    • 그런 다음 레코드 패턴은 이를 두개의 필드 서브패턴으로 전달합니다.
    • 여기서 서브패턴은 name과 age며, 둘다 변수 패턴(variable patterns)입니다.
    • 변수 패턴은 값을 받아 새 변수에 바인딩 합니다.
    • 그러면 name과 age라는 두개의 로컬 변수(지역변수)를 얻게 됩니다.
  • 때로는 언더바(_)를 이용하여 필드를 삭제 할 수도 있습니다.
    var (_, age) = userInfo(json);


기존 선언된 name과 age 변수값을 변경하고 싶을 때
위와 같이 패턴을 사용할 수 있습니다.



3. Control Flow in Argument Lists

https://youtu.be/KhYTFglbF2k?t=373

Swift나 Scala 와 같은 언어에 익숙하다면 왜 tuple이 아닌 records를 호출하는지 궁금할 것입니다.

이를 설명하기 위해 다른 사용 사례를 설명드리겠습니다.

몇년전 collection 리터럴 내에서 iffor 사용할 수 있도록 추가 지원했습니다.

  • 목표는 Flutter 코드를 더 쉽게 작성하는 것 이였고 이와 같이 전체를 명령어 코드를 작성하는 것 대신 위젯 트리 표현식의 list 리터럴에 직접 컨트롤 플로우를 인라인 할 수 있게 되었습니다.

  • child 위젯이 collection 리터럴 안에 있을때만 if문이 작동됩니다.

  • 그러나 이러한 종류의 조건부 동작이 필요한 많은 child 위젯 예제와 같이 named arguments에 직접 있습니다.

    • 그래서 여기서 할 수 있는 최선은 조건식을 많이 사용하는 것...
    • 꽤 형편없죠

그래서 argument 목록 내에서도 if를 지원하고 싶습니다.
그럴려면 이제 arguments 팩을 나타내는 확산할 수 있는 개체가 필요 합니다.

  • 각 파라미터 타입이 다를 수 있으므로 Lists는 작동하지 않습니다.
  • 튜플은 positional 파라미터에 가장 적합 하지만 다트에서는 파라미터 이름을 지정할 수 있죠.
  • 튜플이 있는 다른 언어에도 종종 레코드라는 항목이 있습니다.
    • 레코드는 위치 대신 각 필드의 이름이 지정되는 별도의 타입입니다.

그러나 Dart에서 함수는 positional 파라미터named 파라미터를 모두 가질 수 있습니다.

  • 그래서 임의의 arguments 모음을 나타낼 수 있는 single object를 원하는 경우 함께 혼합된 positional 파라미터named 파라미터를 모두 지원해야합니다.

그래서 디자인에서 tuples과 records를 positional과 named 모두 지원하는 단일 구조로 병합했습니다.

궁극적으로
아래와 같은 UI코드를 가져와

아래와 같이 바꿀 수 있다는 것입니다.



4. JSON Destructuring

https://youtu.be/KhYTFglbF2k?t=501

해당 코드를

desctructuring(구조 분해)을 이용해 아래 코드처럼 작성 가능합니다 🎉

  • record 패턴 외로도 collection을 분해 하기 위한 map, list 패턴도 있습니다.
  • 패턴의 구조와 구문이 데이터 생성 표현식과 미러링 하는 방법에 주목 하십쇼
    • 이것은 데이터를 시각적으로 표현하는데 도움이 되는 방식인데, 패턴 매칭에서 제가 제일 좋아하는 측면입니다.
    • 이제 이와 같은 구조 분해 변수(Destructuring Variable) 선언을 사용하여 데이터가 예상한 구조를 가지고 있다는 것을 알게 됩니다.



네트워크 또는 무언가를 통해 들어오는 경우 먼저 확인해야합니다.

위와 같이 json 데이터를 전송하는 네트워크 프로토콜이 있다고 상상해봅시다.

사용하기 전 예상한 대로 보이는지 확인해야 합니다 👀 그리고 이것은 예상한 대로 보이기 위해, 지금 작성해야되는 종류의 코드 입니다.

위와 같은 코드를 개선하기 위해 switch case에 패턴이 표시되도록 dart를 확장하고 있습니다.

case 안에 패턴.. case {'user': [String name, int age]}

  • 구조 분해 전에 패턴이 들어오는 값을 수락할지 여부를 결정 할 수 있습니다.
  • 따라서 사용자 문자열과 같은 constant(상수) 패턴은 값이 해당 문자열과 동일할 때 일치합니다.
  • map pattern과 list pattern은 값이 올바른 종류의 컬렉션인 경우 일치합니다.
    • 그런 다음 subpatterns와 재귀적으로 일치합니다.
  • 변수 패턴은 변수 타입이 있는 경우 모든 값과 일치합니다.
  • 따라서 이 패턴은 이전 코드와 정확히 동일한 모든 유효성 검사를 수행 한것과 동일한데 분명히 훨씬 더 명확하고 선언적이며 짧습니다.



5. Usable Switches

https://youtu.be/KhYTFglbF2k

또 다른 예시를 가져와봅니다.

아래와 같이 개선할 수 있습니다.

  1. 무의미한 break 문을 제거
    dart에선 아무 의미 없습니다. 이전 다른 언어에서 배우고 온 사람들에게 친숙하게 보이기 위해 있었습니다.
  1. 가드 조항을 추가했습니다.

    가드를 사용하면 임의의 표현식을 평가하여 문자가 일치 하는지 확인 할 수 있습니다.
    • 표현식이 거짓이면 케이스가 실패합니다.
    • 가드가 false일 때 실행이 전체 switch문 에서 건너뛰는 대신 다음 케이스로 계속 진행되기 때문에 case 내부에 if문을 포함하는 것과는 다릅니다.
  1. 패턴에서 논리 연산자를 허용합니다.

    패턴 중 하나가 일치하면 OR 패턴이 매치됩니다.
  • 그런 다음 여러개의 작은 패턴을 단일 케이스로 묶기 위해 이들을 함께 연결 할 수 있습니다.
  • and 연산자 비교문도 가능합니다.
  1. 스위치 표현식도 소개합니다.
  • 이전 2,3번 사례에서 예시 들었던 코드를 깨끗하게 가볍게 6줄로 압축했습니다.
  • 여러반향의 분기문이 단반향 분기 if문 처럼 자연스럽고 유용하게 느껴지도록 만들었습니다.
  1. 단일 패턴의 switch문이 무겁게 느껴질 때

    그래서 아래와 같이 단일 패턴을 포함할 수 있는 if-case 문도 추가했습니다.



🌸요약🌸

Dart3에 출시될 recordsPattern Macting 설명

  • records
    • 값을 할당할 때:
    (String, int) userInfo(Map json) => (json['name'] as String, json['age'] as int);
    • 값을 가져와 사용할 때:
    var info = userInfo(json);
    var name = info.$1;
    var age = info.$2;
  • record pattern (destructuring Pattern)
    var (name, age) = userInfo(json);
  • 나중에 ui Widget 안에서 arguments 조건문 쓸 수 있도록 할 거임
ListTile(
  title: Text('Welcome'),
  if(hasNextStep)
  	subtitle: Text('Tap to advance.'),
  onTap: (){}
);
  • json Destructuring
// before
final Map<String, List<dynamic>> json = {
	'user' : ['Bora', 26],
    };

// after
final {'user', [name, age]} json = {
	'user' : ['Bora', 26],
    };
  • switch
    • case에서 패턴 구문 사용 가능
    final json = {'user' : ['Bora', 26]};
    switch (json){
      case {'user': [String name, int age]}:
    }
    1. break문 생략가능
    2. 가드 조건 추가 when
    3. 논리연산자 사용가능
      : or(||), and (&&) 가능
    4. => 표현식 사용가능
    5. 단일 패턴 switch 일 때 대신할 if-case문 사용 가능
profile
𝙸 𝚊𝚖 𝚊 𝚌𝚞𝚛𝚒𝚘𝚞𝚜 𝚍𝚎𝚟𝚎𝚕𝚘𝚙𝚎𝚛 𝚠𝚑𝚘 𝚎𝚗𝚓𝚘𝚢𝚜 𝚍𝚎𝚏𝚒𝚗𝚒𝚗𝚐 𝚊 𝚙𝚛𝚘𝚋𝚕𝚎𝚖. 🇰🇷👩🏻‍💻

2개의 댓글

comment-user-thumbnail
2023년 8월 8일

이거 도움 너무 많이 됩니다 ㅋㅋㅋㅋ 감사해요 :)

1개의 답글