类型

类型是 ReScript 的亮点!它们是:

  • 强类型。一个类型无法变为另一个类型。在 JavaScript 中,当代码执行时(也称运行时),变量的类型可能会改变。例如,一个类型为 number 的变量可能变为 string 类型。这是不好的特性,它使代码在阅读或调试时更难理解

  • 静态类型。ReScript 类型在编译后被擦除,在运行时不存在。不必担心类型会拖累性能。你在运行时不需要类型信息;我们在编译时报告所有信息(尤其是所有类型错误)。更早地捕获错误!

  • 健全(sound)。这是我们与其他编译到 JavaScript 的带类型语言的最大区别。我们的类型系统保证永远不会出错。大多数类型系统对一个值的类型进行猜测,有时在编辑器中不能显示正确的类型。我们不这样做。我们相信,由于与期望不匹配,一个会出错的类型系统最终会变得危险

  • 快速。许多开发者低估了项目构建时类型检查的耗时。我们的类型检查器是最快的之一

  • 类型推导。你不必写出类型!ReScript 可以从值中推导出类型。是的,我们可以推断出你的程序的所有类型,没有不正确的地方,不需要你手动标注,而且速度很快,看起来就像魔法一样。欢迎来到 ReScript =)

下面的章节将探讨类型系统的更多内容。

类型推导

这个 let 绑定不包含任何手写的类型:

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

ReScript 知道 socre 是一个 int,通过 10 来判断。这就是所谓的推导。同样,它也知道 add 函数接受两个 int 并返回了一个 int,这是由对 int 做加法的 + 运算符判断的。

类型标注

但你也可以选择性地写下类型,也就是标注你的值:

ReScriptJS Output
let score: int = 10

如果 score 的类型标注与我们推断的类型不一致,我们会在编译时向你显示一个错误。我们不会默默地假定你的类型标注是对的,这与许多其他语言不同。

你也可以用括号把任何表达式包起来,并对其进行标注:

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) 是一个标签参数。查看 函数 章节以了解更多。

类型别名

你可以用不同的名字指代一个类型。它们是等价的:

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

类型参数(泛型)

类型可以接受参数,类似于其他语言中的泛型。参数名必须' 开头。

参数化类型的应用是为了消除重复代码。使用前:

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),什么都不需要写。

类型参数出现在很多地方。 array<'a> 类型就是这样一种类型,它需要一个类型参数:

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

如果类型不能接受参数,标准库将需要定义 arrayOfStringarrayOfIntarrayOfTuplesOfInt 等类型。这将是很啰嗦的。

类型可以接收许多参数,并且是可组合的。

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!")
]

递归类型

就像函数一样,一个类型也可以使用 rec 在其内部引用自己:

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

互递归类型

类型也可以通过 and 来实现 相互 递归:

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

类型逃生通道

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 是唯一一个前面没有 @ 属性的。