타입(Type)

타입은 ReScript의 꽃이라 할 수 있습니다! ReScript에 타입은,

  • 강력합니다. 타입은 다른 타입으로 바뀌지 않습니다. JavaScript에서는 코드가 실행(런타임)될 때 변수의 타입이 바뀌기도 합니다. 예를 들어, number 타입의 변수가 때로 string 타입의 변수로 바뀌기도 합니다. 이는 하나의 반 기능(anti-feature)으로, 코드를 읽거나 디버깅할 때 코드를 더욱 이해하기 어렵습니다.

  • 정적입니다. ReScript의 타입들은 컴파일 후 지워지며, 타입이 필요 없는 런타임 단계에서는 그 정보들이 남아있지 않습니다. 사용된 타입들이 퍼포먼스 저하에 영향을 끼치지 않는다는 것입니다. 또한, 런타임 중에 타입 정보는 필요하지 않을 것이기에, ReScript는 타입 오류들을 포함한 모든 정보를 컴파일 중에 알려줍니다. 버그를 보다 더 빨리 잡아보세요!

  • 명확합니다. JavaScript로 컴파일되는 다른 여러 타입 언어들 비교했을 때 가장 큰 차별화 요소로, ReScript의 타입 시스템은 절대 틀리지 않을 것을 보장합니다. 대부분의 타입 시스템은, 값의 타입을 추측하여 때로는 편집기에서 잘못된 타입을 표시합니다. 그러나 ReScript는 다릅니다. ReScript에서는 그런 부류의 때때로 부정확한 타입 시스템이 예상 불일치에서 발생하는 위험이라 믿기 때문에 절대 틀리지 않습니다.

  • 빠릅니다. 많은 개발자가 자신들의 프로젝트의 빌드 시간 중 얼마나 많은 시간이 타입 검사에 투자되는지를 과소평가합니다. ReScript의 타입 검사기는 가장 빠른 검사기 중 하나입니다.

  • 유추됩니다. 타입을 일일이 쓸 필요가 없습니다! ReScript는 변수의 값에서 타입을 유추할 수 있습니다. ReScript가 여러분의 파일의 모든 타입을 오류 없이, 직접 주석을 달지 않고도 빠르게 유추할 수 있다는 게 마치 마법 같아 보일 수도 있습니다. ReScript의 세계에 오신 것을 환영합니다! =)

지금부터 ReScript 타입 시스템에 대해 하나하나 알아보겠습니다.

추론(Inference)

다음의 let 바인딩에는 어떠한 타입도 작성하지 않았습니다.

ReScriptJS Output
let score = 10
let add = (a, b) => a + b

ReScript는 score가 가지고 있는 값 10을 토대로, scoreint임을 알고 있습니다. 이를 추론이라 합니다. 마찬가지로, ReScript는 정수에서 작동하는 + 연산자를 토대로, add 함수가 두 개의 int를 받고 int를 반환하는 것으로 추론할 수 있습니다.

타입 어노테이션(Type Annotation)

위와 같이 타입들이 추론되지만, 여러분은 타입을 또한 유동적으로 손수 쓸 수 있습니다. 이걸 ReScript에서는 값에 어노테이션(Annotation)을 쓴다고 합니다.

ReScriptJS Output
let score: int = 10

만약 score의 타입 어노테이션이 ReScript가 유추한 타입과 일치하지 않는 경우, ReScript는 컴파일 시에 에러를 보여줄 것입니다. ReScript는 다른 많은 언어와는 다르게 여러분의 타입 어노테이션이 맞을 것이라고 가볍게 넘기지 않을 것입니다.

다음과 같이 어떤 표현이든 괄호 안에 넣고 어노테이션을 달 수도 있습니다.

ReScriptJS Output
let myInt = 5
let myInt: int = 5
let myInt = (5: int) + (4: int)
let add = (x: int, y: int) : int => x + y
let drawCircle = (~radius as r: int): circleType => /* code here */

참고: 마지막 줄의 (~radius as r: int)는 이름이 지정된 인자입니다. 이에 대한 자세한 내용은 함수 페이지에서 확인하세요.

타입의 별칭(Type alias)

타입에 다른 이름을 부여할 수 있습니다. 다음의 예시들은 서로 같은 뜻입니다.

ReScriptJS Output
type scoreType = int
let x: scoreType = 10

타입 매개변수 (Type Parameter - Aka Generic)

타입들은 매개변수를 받을 수 있습니다(다른 언어에서는 Generics라고 합니다.). 매개변수 이름은 '로 시작해야 합니다.

다음은 중복을 제거하기 위해 매개 변수화한 타입을 사용한 예제입니다. 사용 전:

ReScriptJS Output
// this is a tuple of 3 items, explained next
type intCoordinates = (int, int, int)
type floatCoordinates = (float, float, float)

let a: intCoordinates = (10, 20, 20)
let b: floatCoordinates = (10.5, 20.5, 20.5)

사용 후:

ReScriptJS Output
type coordinates<'a> = ('a, 'a, 'a)

let a: coordinates<int> = (10, 20, 20)
let b: coordinates<float> = (10.5, 20.5, 20.5)

위의 코드는 설명을 위한 인위적인 예일뿐입니다. 타입이 유추되었으므로 다음과 같이 작성할 수 있습니다.

ReScriptJS Output
let buddy = (10, 20, 20)

타입 시스템은 이것이 (int, int, int)라고 유추합니다. 그 외에는 다른 어떤 것도 쓰여질 필요가 없습니다.

타입 인자는 여러 위치에 나타납니다. ReScript의 array<'a> 타입은 타입 매개변수가 필요한 유형입니다.

ReScriptJS Output
// inferred as `array<string>`
let greetings = ["hello", "world", "how are you"]

타입 인수는 여러 위치에 나타납니다. ReScript의 array<'a> 타입은 타입 매개변수가 필요한 유형입니다.

타입은 많은 인자를 받을 수 있고 구성할 수 있습니다.

ReScriptJS Output
type result<'a, 'b> =
  | Ok('a)
  | Error('b)

type myPayload = {data: string}

type myPayloadResults<'errorType> = array<result<myPayload, 'errorType>>

let payloadResults: myPayloadResults<string> = [
  Ok({data: "hi"}),
  Ok({data: "bye"}),
  Error("Something wrong happened!")
]

재귀 타입(Recursive Types)

함수와 마찬가지로 타입은 rec을 사용하여 자기 자신을 참조할 수 있습니다.

ReScriptJS Output
type rec person = {
  name: string,
  friends: array<person>
}

상호적 재귀 타입(Mutually Recursive Types)

타입은 and을 통해 상호적 재귀가 될 수 있습니다.

ReScriptJS Output
type rec student = {taughtBy: teacher}
and teacher = {students: array<student>}

타입 비상구(Type Escape Hatch)

ReScript의 타입 시스템은 강력하며, 암시적 타입 변환과 값의 타입을 임의로 추측하는 등의 위험을 허락하지 않습니다. 하지만, 실용적인 측면에서 ReScript는 타입 시스템을 "속일 수" 있는 하나의 비상구를 제공합니다.

ReScriptJS Output
external myShadyConversion: myType1 => myType2 = "%identity"

이 선언은 myType1의 선택을 myType2로 바꿔줍니다. 이런 비상구는 아래와 같이 사용할 수 있습니다.

ReScriptJS Output
external convertToFloat : int => float = "%identity"
let age = 10
let gpa = 2.1 +. convertToFloat(age)

절대 이 기능을 남용하지 마십시오. 예를 들어, 기존의 지나치게 동적인 JS 코드의 작업이 필요할 때, 세련되게 사용하기를 바랍니다.

external에 대한 자세한 내용은 여기서 확인하세요.

참고: 이 external은 앞에 @ 어트리뷰트가 붙지 않은 유일한 external입니다.