函数
完整的函数语法清单在最后。
ReScript 函数用箭头声明,并返回一个表达式,就像 JS 函数一样。它们也可以编译成干净的 JS 函数。
这声明了一个函数并给它指定了 greet
这个名字,你可以这样调用它:
多参数函数的参数用逗号分隔:
对于较长的函数,你得用 {}
将函数体围起来:
如果你的函数没有参数,只需要写 let greetMore = () => {...}
。
标签参数
多参数函数,特别是那些参数类型相同的函数,在调用时可能会很混乱。
你可以为参数附加标签,在参数名前面加上 ~
符号:
你可以按任意顺序传入这些参数:
声明中的 ~x
部分意味着该函数接受一个标签为 x
的参数,并且可以在函数体中用相同的名称来指代它。你也可以在函数体中用不同的名字指代参数,这样更简洁:
事实上,(~radius)
只是 (~radius as radius)
的一个缩写。
下面是给参数标记类型的语法:
可选的标签参数
带标签的函数参数可以在声明时设置成可选的。你可以在调用函数时省略它们。
当使用这种语法的时候,radius
被包装在标准库的 option
类型中,默认值为 None
。如果函数调用提供了这个参数,那么它会被包装到 Some
中。所以这里 radius
的类型是 None| Some(int)
。
这里有更多关于 option
类型的信息。
注意:因为类型系统的限制,只要函数有一个可选参数,你就需要确保在它后面至少有一个位置参数(又称非标记的,非可选的参数)。如果没有,可以提供一个虚拟的 unit
(又表示为 ()
)参数。
类型签名和类型标注
当涉及到类型签名和类型标注时,带有可选标记参数的函数可能会令人困惑。事实上,一个可选标记参数的类型在不同地方看起来是不同的,这取决于你是正在调用函数,还是在函数体内部。在函数外部,一个原生值要么被传入(比如 int
),要么不传入。在函数内部,参数总是存在的,但其值是一个 option
值(option<int>
)。这意味着类型签名是不同的,这取决于你标注的是函数类型还是参数类型,前者是一个原生值,而后者是一个 option
。
如果我们回到之前的例子,同时给它的参数添加一个签名和类型标注,我们会得到这样的结果:
第一行是函数的签名,我们会在一个接口文件中这样定义它(见签名章节)。函数的签名描述了外部世界与函数交互的类型,因此 radius
的类型是 int
,因为它在调用时确实期望得到一个 int
。
在第二行中,我们对参数进行标注,以帮助我们在函数内部使用参数时记住参数的类型,这里 radius
在函数内部将是一个 option<int>
。
如果你在编写带有可选标记参数的函数的类型签名时遇到了困难,请记住参数类型内外有别!
显式传入的 option
值
有时,你想将值转发给函数,但不确定值是 None
还是 Some(a)
。你可能会这样做:
这会变得很啰嗦。我们提供了一个简写:
这样做的意思是“我知道 radius
是可选值,当我给它传值时,它需要是一个 int
,但我不确定传的值是 None
还是 Some(val)
,所以我传整个 option
包装给你”。
有默认值的可选参数
可选的标签参数也可以提供一个默认值,这种情况下,它们不会被包装在 option
类型中。
递归函数
ReScript 默认不允许函数在它内部调用自身。要创建一个递归函数,需要在 let
后面加上 rec
关键字:
一个简单的递归函数可能看起来像这样:
递归调用一个函数对性能和调用栈不利。不过 ReScript 会智能地将尾递归编译为快速的 JS 循环。试着检查一下上面代码的 JS 输出!
互递归函数
互递归函数像递归函数一样使用 rec
关键字开头,然后用 and
关键字串在一起:
去柯里化的函数
ReScript 的函数是默认柯里化的,这是少数的编译到 JS 之后付出的性能代价之一。编译器会尽最大可能去除这些柯里化。然而,在一些边缘情况下,你可能希望保证函数没有柯里化。这时可以在函数的参数列表前放一个 .
:
如果你为去柯里化函数标注了类型,你也要在那加一个 .
。
注意:声明和调用位置都要加上去柯里化的标记。
这个特性看起来似乎微不足道,但实际上是 ReScript 作为一门函数式语言最重要的特性之一。如果你想要在 JS 输出中移除所有运行时柯里化
,我们鼓励你使用它。
Async/Await(自 v10.1 起)
就像在 JS 中一样,可以通过在定义前添加 async
来声明一个异步函数,可以在这类函数中使用 await
。输出结果看上去和 JS 没多少区别:
getUser
的返回类型被推断为 promise<string>
。类似地,await getUserName(userID)
在函数返回 promise<string>
时返回一个 string
。在 async
函数之外使用 await
(包括在异步函数的非异步回调中)是一个错误。
人性化的错误处理
错误处理是通过简单地使用 try/catch
或者匹配 exception
的 switch 来完成的,就像在非异步的函数中一样。JS 异常和 ReScript 中定义的异常都可被捕获。编译器负责将 JS 异常打包成内置的 JsError
异常:
ignore() 函数
偶尔你可能想忽略一个函数的返回值,ReScript 提供了一个 ignore()
函数,它抛弃传入的值并返回 ()
:
技巧和诀窍
有关函数的语法清单:
函数声明
RES// anonymous function
(x, y) => 1
// bind to a name
let add = (x, y) => 1
// labeled
let add = (~first as x, ~second as y) => x + y
// with punning sugar
let add = (~first, ~second) => first + second
// labeled with default value
let add = (~first as x=1, ~second as y=2) => x + y
// with punning
let add = (~first=1, ~second=2) => first + second
// optional
let add = (~first as x=?, ~second as y=?) => switch x {...}
// with punning
let add = (~first=?, ~second=?) => switch first {...}
带类型标注的函数声明
RES// anonymous function
(x: int, y: int): int => 1
// bind to a name
let add = (x: int, y: int): int => 1
// labeled
let add = (~first as x: int, ~second as y: int) : int => x + y
// with punning sugar
let add = (~first: int, ~second: int) : int => first + second
// labeled with default value
let add = (~first as x: int=1, ~second as y: int=2) : int => x + y
// with punning sugar
let add = (~first: int=1, ~second: int=2) : int => first + second
// optional
let add = (~first as x: option<int>=?, ~second as y: option<int>=?) : int => switch x {...}
// with punning sugar
// note that the caller would pass an `int`, not `option<int>`
// Inside the function, `first` and `second` are `option<int>`.
let add = (~first: option<int>=?, ~second: option<int>=?) : int => switch first {...}
应用函数
RESadd(x, y)
// labeled
add(~first=1, ~second=2)
// with punning sugar
add(~first, ~second)
// application with default value. Same as normal application
add(~first=1, ~second=2)
// explicit optional application
add(~first=?Some(1), ~second=?Some(2))
// with punning
add(~first?, ~second?)
在应用时加上类型标注
RES// labeled
add(~first=1: int, ~second=2: int)
// with punning sugar
add(~first: int, ~second: int)
// application with default value. Same as normal application
add(~first=1: int, ~second=2: int)
// explicit optional application
add(~first=?Some(1): option<int>, ~second=?Some(2): option<int>)
// no punning sugar when you want to type annotate
独立的类型签名
RES// first arg type, second arg type, return type
type add = (int, int) => int
// labeled
type add = (~first: int, ~second: int) => int
// labeled
type add = (~first: int=?, ~second: int=?, unit) => int
在接口文件中加上类型签名
在你的接口文件(.resi
)中为一个来自实现文件(.res
)的函数加上类型注解:
let add: (int, int) => int
类型标注部分和上面的是一样的。
不要混淆 let add: myType
和 type add = myType
。在 .resi
接口文件中使用时,前者导出绑定 add
,并将其类型标注为 myType
;后者导出类型 add
,其值为 myType
类型。