공식 문서 / 언어 매뉴얼 / 레코드(Record)
Edit

레코드(Record)

ReScript에서의 레코드는 JavaScript의 객체(Object)와 유사합니다. 다만,

  • 기본적으로 불변입니다.

  • 고정된 필드를 가지고 있습니다. (확장 불가)

타입 선언

레코드는 반드시 타입 선언을 해야합니다.

ReScriptJS Output
type person = {
  age: int,
  name: string,
}

생성

위에서 선언한 person 레코드는 다음과 같이 생성합니다.

ReScriptJS Output
let me = {
  age: 5,
  name: "Big ReScript"
}

새로운 레코드 값을 생성하면 ReScrip는 값의 형태에 맞는 레코드 타입 선언을 찾으려고 합니다. 그리하여 위와 같이 meperson 타입으로 추론됩니다.

참고: 타입 선언이 다른 파일 또는 모듈에 있는 경우, 해당 유형이 어떤 파일 또는 모듈인지 다음과 같이 명시적으로 기재해야 합니다.

ReScriptJS Output
// School.res
type person = {age: int, name: string}
ReScriptJS Output
// Example.res

let me: School.person = {age: 20, name: "Big ReScript"}
/* or */
let me2 = {School.age: 20, name: "Big ReScript"}

meme2 모두에서 레코드 타입 정의 School를 찾을 수 있습니다. 첫번째의 보통의 타입 어노테이션을 가진 me가 선호됩니다.

접근

익숙한 .(dot) 어노테이션을 사용합니다.

ReScriptJS Output
let name = me.name

불변 갱신(Immutable Update)

... 스프레드(spread) 연산자를 사용하여 이전 레코드에서 새 레코드를 만들 수 있습니다. 원본 레코드는 변경되지 않습니다.

ReScriptJS Output
let meNextYear = {...me, age: me.age + 1}

참고: 레코드의 형태는 레코드 타입에 따라 고정되므로, 스프레드(spread)는 레코드 값에 새 필드를 추가할 수 없습니다.

가변 갱신(Mutable Update)

레코드 필드는 선택적으로 변경할 수 있습니다(이를 mutable이라 합니다). 이를 통해 = 연산자와 함께 해당 필드를 효율적으로 업데이트할 수 있습니다.

ReScriptJS Output
type person = {
  name: string,
  mutable age: int
}

let baby = {name: "Baby ReScript", age: 5}
baby.age = baby.age + 1 // `baby.age`은 이제 6 입니다. Happy birthday!

타입 선언에서 mutable(가변)으로 표시되지 않은 필드는 변경할 수 없습니다.

JavaScript 결과물

ReScript 레코드는 간단한 JavaScript 객체로 컴파일됩니다. 위의 예시들에서 JS Output 탭을 클릭해 다양한 JavaScript 결과물을 참조하세요.

선택적 레코드 필드

ReScript v10부터 선택적 레코드 필드를 도입하여, 다음과 같이 레코드를 생성할 때 생략할 수 있는 필드를 정의할 수 있습니다.

ReScriptJS Output
type person = {
  age: int,
  name?: string
}

name? 접미사가 붙어 있는 것에 주의하세요. 이것은 필드 자체가 선택사항(optional)이라는 의미입니다.

생성

레코드를 만들 때, 선택적 필드를 생략할 수 있습니다. 설정되지 않은 선택적 필드 값은 기본적으로 None입니다.

ReScriptJS Output
type person = {
  age: int,
  name?: string
}

let me = {
  age: 5,
  name: "Big ReScript"
}

let friend = {
  age: 7
}

이것은 패턴 매칭에 영향을 미치므로, 이것에 대해 곧 논의할 예정입니다.

불변 갱신(Immutable Update)

선택적 필드를 앞서 말한 불변 갱신으로 업데이트하면 선택 가능 여부에 상관없이 필드의 값을 설정할 수 있습니다.

ReScriptJS Output
type person = {
  age: int,
  name?: string
}

let me = {
  age: 123,
  name: "Hello"
}

let withoutName = {
  ...me,
  name: "New Name"
}

그러나 필드를 option 값으로 설정하려면 값 앞에 ?을 추가해야 합니다.

ReScriptJS Output
type person = {
  age: int,
  name?: string
}

let me = {
  age: 123,
  name: "Hello"
}

let maybeName = Some("My Name")

let withoutName = {
  ...me,
  name: ?maybeName
}

동일한 메커니즘을 사용하여 선택적 필드의 값을 ?None로 설정하면, 선택적 필드를 설정 취소할 수 있습니다.

선택적 필드에서의 패턴 매칭

ReScript의 가장 중요한 기능 중 하나인 패턴 매칭은 선택적 필드를 처리할 때 두 가지 주의 사항이 있습니다.

값을 직접 매칭할 때는 option입니다. 다음은 그 예시입니다.

ReScriptJS Output
type person = {
  age: int,
  name?: string,
}

let me = {
  age: 123,
  name: "Hello",
}

let isRescript = switch me.name {
| Some("ReScript") => true
| Some(_) | None => false
}

그러나 필드를 일반 레코드 구조의 일부로 매칭시키면, 근본적이고 선택적이지 않은 값으로 간주됩니다.

ReScriptJS Output
type person = {
  age: int,
  name?: string,
}

let me = {
  age: 123,
  name: "Hello",
}

let isRescript = switch me {
| {name: "ReScript"} => true
| _ => false
}

때때로 이 필드가 확실히 설정되었는지 알고 싶을 경우가 있을 것입니다. 그럴 때는 다음과 같이 매칭 옵션 앞에 ?를 추가하여 패턴 매칭 엔진에 전해주세요.

ReScriptJS Output
type person = {
  age: int,
  name?: string,
}

let me = {
  age: 123,
  name: "Hello",
}

let nameWasSet = switch me {
| {name: ?None} => false
| {name: ?Some(_)} => true
}

사용 팁과 트릭

필드 이름으로 레코드 타입 찾기

레코드에서, "나는 이 함수가 age라는 필드가 있는 한 어떠한 레코드 타입도 받아들이도록 하고 싶다."라고 말할 수 없습니다. 레코드 타입은 필드 이름으로 찾기 때문입니다. 다음 방법은 의도한 대로 작동하지 않습니다:

ReScriptJS Output
type person = {age: int, name: string}
type monster = {age: int, hasTentacles: bool}

let getAge = (entity) => entity.age

반면, getAge는 매개변수 entitymonster 타입이어야 하며, 이는 필드 age에 가장 가까운 레코드 타입이라고 추론될 것입니다. 그래서 다음 코드의 마지막 줄은 오류를 보고할 것입니다.

RES
let kraken = {age: 9999, hasTentacles: true} let me = {age: 5, name: "Baby ReScript"} getAge(kraken) getAge(me) // type error!

타입 시스템은 meperson이고, getAgemonster 타입으로만 작동한다고 할 것입니다. 이러한 기능이 필요하다면 ReScript 객체(object)를 사용해보세요.

레코드의 선택적 필드가 바인딩에 유용할 수 있습니다

많은 JavaScript API는 방대한 구성 객체를 가지고 있는 경우가 많은데, 레코드로 모델링하면 항상 레코드를 만들 때 모든 레코드 필드를 지정해야 하므로 약간 성가실 수 있습니다.

v10에 도입된 선택적 레코드 필드는 이 문제를 해결하기 위한 것입니다. 선택적 필드를 사용하면 모든 필드를 지정할 필요가 없으며 원하는 필드만 지정할 수 있습니다. 이것은 바인딩과 대규모 구성 개체를 가진 다른 API에게는 의미있는 인체 공학적 개선입니다.

디자인 결정

동적 언어를 주로 다루셨던 분들이라면, 바로 객체를 쓰지 않고, 위에서 언급된 제약을 왜 신경 써야 하는지 의아할 수도 있습니다. 레코드를 객체보다 먼저 고려하라고 해놓고, 레코드는 명시적으로 타입도 써줘야 하고 모든 필드가 정확히 일치해야 함수에 전달할 수 있는 등 다루기가 더 불편하니까요.

  1. 사실은 대부분의 앱에서 데이터의 형태는 실제로 고정되어 있고, 그렇지 않은 경우 잠재적으로 배리언트(다음 장 소개) + 레코드의 조합으로 더 잘 표현될 수 있습니다.

  2. 레코드 타입은 단일 명시적 타입 선언(우리는 이것을 "명목적 타이핑(nominal typing)"이라 부릅니다)을 통해 해결되기 때문에, 타입 오류 메시지는 상대편(튜플과 같은 "구조 타이핑(structural typing)")의 오류메시지보다 더 낫게 됩니다. 이것은 리팩토링을 유용하게 하는데, 레코드 타입의 필드를 변경하면 컴파일러가 일부분의 동일한 레코드가 오용되어 있음을 자연스럽게 알 수 있습니다. 반면, 구조적 타이핑에서는 어떤 필드를 바꿨을 때, 선언이 잘못된 것인지 아님 사용처가 잘못된 것인지 컴파일러가 추론할 방법이 없습니다.