title | description | canonical |
---|---|---|
Function | Function syntax in ReScript | /docs/manual/latest/function |
Cheat sheet for the full function syntax at the end.
ReScript functions are declared with an arrow and return an expression, just like JS functions. They compile to clean JS functions too.
<CodeTab labels={["ReScript", "JS Output"]}>
letgreet= (name) =>"Hello "++name
functiongreet(name){return"Hello "+name;}
This declares a function and assigns to it the name greet
, which you can call like so:
<CodeTab labels={["ReScript", "JS Output"]}>
greet("world!") // "Hello world!"
greet("world!");
Multi-arguments functions have arguments separated by comma:
<CodeTab labels={["ReScript", "JS Output"]}>
letadd= (x, y, z) =>x+y+zadd(1, 2, 3) // 6
functionadd(x,y,z){return(x+y|0)+z|0;}
For longer functions, you'd surround the body with a block:
<CodeTab labels={["ReScript", "JS Output"]}>
letgreetMore= (name) => { letpart1="Hello"part1++" "++name }
functiongreetMore(name){return"Hello "+name;}
If your function has no argument, just write let greetMore = () => {...}
.
Multi-arguments functions, especially those whose arguments are of the same type, can be confusing to call.
<CodeTab labels={["ReScript", "JS Output"]}>
letaddCoordinates= (x, y) => { // use x and y here } // ...addCoordinates(5, 6) // which is x, which is y?
functionaddCoordinates(x,y){// use x and y here}addCoordinates(5,6);
You can attach labels to an argument by prefixing the name with the ~
symbol:
<CodeTab labels={["ReScript", "JS Output"]}>
letaddCoordinates= (~x, ~y) => { // use x and y here } // ...addCoordinates(~x=5, ~y=6)
functionaddCoordinates(x,y){// use x and y here}addCoordinates(5,6);
You can provide the arguments in any order:
<CodeTab labels={["ReScript", "JS Output"]}>
addCoordinates(~y=6, ~x=5)
addCoordinates(5,6);
The ~x
part in the declaration means the function accepts an argument labeled x
and can refer to it in the function body by the same name. You can also refer to the arguments inside the function body by a different name for conciseness:
<CodeTab labels={["ReScript", "JS Output"]}>
letdrawCircle= (~radiusasr, ~colorasc) => { setColor(c) startAt(r, r) // ... } drawCircle(~radius=10, ~color="red")
functiondrawCircle(r,c){setColor(c);returnstartAt(r,r);}drawCircle(10,"red");
As a matter of fact, (~radius)
is just a shorthand for (~radius as radius)
.
Here's the syntax for typing the arguments:
<CodeTab labels={["ReScript", "JS Output"]}>
letdrawCircle= (~radiusasr: int, ~colorasc: string) => { // code here }
functiondrawCircle(r,c){// code here}
Labeled function arguments can be made optional during declaration. You can then omit them when calling the function.
<CodeTab labels={["ReScript", "JS Output"]}>
// radius can be omittedletdrawCircle= (~color, ~radius=?, ()) => { setColor(color) switchradius { | None=>startAt(1, 1) | Some(r_) =>startAt(r_, r_) } }
varCaml_option=require("./stdlib/caml_option.js");functiondrawCircle(color,radius,param){setColor(color);if(radius===undefined){returnstartAt(1,1);}varr_=Caml_option.valFromOption(radius);returnstartAt(r_,r_);}
When given in this syntax, radius
is wrapped in the standard library's option
type, defaulting to None
. If provided, it'll be wrapped with a Some
. So radius
's type value is None | Some(int)
here.
More on option
type here.
Note for the sake of the type system, whenever you have an optional argument, you need to ensure that there's also at least one positional argument (aka non-labeled, non-optional argument) after it. If there's none, provide a dummy unit
(aka ()
) argument.
Functions with optional labeled arguments can be confusing when it comes to signature and type annotations. Indeed, the type of an optional labeled argument looks different depending on whether you're calling the function, or working inside the function body. Outside the function, a raw value is either passed in (int
, for example), or left off entirely. Inside the function, the parameter is always there, but its value is an option (option<int>
). This means that the type signature is different, depending on whether you're writing out the function type, or the parameter type annotation. The first being a raw value, and the second being an option.
If we get back to our previous example and both add a signature and type annotations to its argument, we get this:
<CodeTab labels={["ReScript", "JS Output"]}>
letdrawCircle: (~color: color, ~radius: int=?, unit) =>unit= (~color: color, ~radius: option<int>=?, ()) => { setColor(color) switchradius { | None=>startAt(1, 1) | Some(r_) =>startAt(r_, r_) } }
functiondrawCircle(color,radius,param){setColor(color);if(radius!==undefined){returnstartAt(radius,radius);}else{returnstartAt(1,1);}}
The first line is the function's signature, we would define it like that in an interface file (see Signatures). The function's signature describes the types that the outside world interacts with, hence the type int
for radius
because it indeed expects an int
when called.
In the second line, we annotate the arguments to help us remember the types of the arguments when we use them inside the function's body, here indeed radius
will be an option<int>
inside the function.
So if you happen to struggle when writing the signature of a function with optional labeled arguments, try to remember this!
Sometimes, you might want to forward a value to a function without knowing whether the value is None
or Some(a)
. Naively, you'd do:
<CodeTab labels={["ReScript", "JS Output"]}>
letresult=switchpayloadRadius { | None=>drawCircle(~color, ()) | Some(r) =>drawCircle(~color, ~radius=r, ()) }
varr=payloadRadius;varresult=r!==undefined ? drawCircle(color,Caml_option.valFromOption(r),undefined) : drawCircle(color,undefined);
This quickly gets tedious. We provide a shortcut:
<CodeTab labels={["ReScript", "JS Output"]}>
letresult=drawCircle(~color, ~radius=?payloadRadius, ())
varresult=drawCircle(1,undefined,undefined);
This means "I understand radius
is optional, and that when I pass it a value it needs to be an int
, but I don't know whether the value I'm passing is None
or Some(val)
, so I'll pass you the whole option
wrapper".
Optional labeled arguments can also be provided a default value. In this case, they aren't wrapped in an option
type.
<CodeTab labels={["ReScript", "JS Output"]}>
letdrawCircle= (~radius=1, ~color, ()) => { setColor(color) startAt(radius, radius) }
functiondrawCircle(radiusOpt,color,param){varradius=radiusOpt!==undefined ? radiusOpt : 1;setColor(color);returnstartAt(radius,radius);}
ReScript chooses the sane default of preventing a function to be called recursively within itself. To make a function recursive, add the rec
keyword after the let
:
<CodeTab labels={["ReScript", "JS Output"]}>
letrecneverTerminate= () =>neverTerminate()
functionneverTerminate(_param){while(true){_param=undefined;continue;};}
A simple recursive function may look like this:
<CodeTab labels={["ReScript", "JS Output"]}>
// Recursively check every item on the list until one equals the `item`// argument. If a match is found, return `true`, otherwise return `false`letreclistHas= (list, item) =>switchlist { | list{} =>false | list{a, ...rest} =>a===item||listHas(rest, item) }
functionlistHas(_list,item){while(true){varlist=_list;if(!list){returnfalse;}if(list.hd===item){returntrue;}_list=list.tl;continue;};}
Recursively calling a function is bad for performance and the call stack. However, ReScript intelligently compiles tail recursion into a fast JavaScript loop. Try checking the JS output of the above code!
Mutually recursive functions start like a single recursive function using the rec
keyword, and then are chained together with and
:
<CodeTab labels={["ReScript", "JS Output"]}>
letreccallSecond= () =>callFirst() andcallFirst= () =>callSecond()
functioncallSecond(_param){while(true){_param=undefined;continue;};}functioncallFirst(_param){while(true){_param=undefined;continue;};}
ReScript's functions are curried by default, which is one of the few performance penalties we pay in the compiled JS output. The compiler does a best-effort job at removing those currying whenever possible. However, in certain edge cases, you might want guaranteed uncurrying. In those cases, put a dot in the function's parameter list:
<CodeTab labels={["ReScript", "JS Output"]}>
letadd= (. x, y) =>x+yadd(. 1, 2)
functionadd(x,y){returnx+y|0;}add(1,2);
If you need to call a curried function with a single unit ()
argument, you can use the ignore()
function:
<CodeTab labels={["ReScript", "JS Output"]}>
letecho= (. a) =>aecho(. ignore())
functionecho(a){returna;}echo(undefined);
If you write down the uncurried function's type, you'll add a dot there as well.
Note: both the declaration site and the call site need to have the uncurry annotation. That's part of the guarantee/requirement.
This feature seems trivial, but is actually one of our most important features, as a primarily functional language. We encourage you to use it if you'd like to remove any mention of Curry
runtime in the JS output.
Cheat sheet for the function syntaxes:
// anonymous function (x, y) =>1// bind to a nameletadd= (x, y) =>1// labeledletadd= (~firstasx, ~secondasy) =>x+y// with punning sugarletadd= (~first, ~second) =>first+second// labeled with default valueletadd= (~firstasx=1, ~secondasy=2) =>x+y// with punningletadd= (~first=1, ~second=2) =>first+second// optionalletadd= (~firstasx=?, ~secondasy=?) =>switchx {...} // with punningletadd= (~first=?, ~second=?) =>switchfirst {...}
// anonymous function (x: int, y: int): int=>1// bind to a nameletadd= (x: int, y: int): int=>1// labeledletadd= (~firstasx: int, ~secondasy: int) : int=>x+y// with punning sugarletadd= (~first: int, ~second: int) : int=>first+second// labeled with default valueletadd= (~firstasx: int=1, ~secondasy: int=2) : int=>x+y// with punning sugarletadd= (~first: int=1, ~second: int=2) : int=>first+second// optionalletadd= (~firstasx: option<int>=?, ~secondasy: option<int>=?) : int=>switchx {...} // with punning sugar// note that the caller would pass an `int`, not `option<int>`// Inside the function, `first` and `second` are `option<int>`.letadd= (~first: option<int>=?, ~second: option<int>=?) : int=>switchfirst {...}
add(x, y) // labeledadd(~first=1, ~second=2) // with punning sugaradd(~first, ~second) // application with default value. Same as normal applicationadd(~first=1, ~second=2) // explicit optional applicationadd(~first=?Some(1), ~second=?Some(2)) // with punningadd(~first?, ~second?)
// labeledadd(~first=1: int, ~second=2: int) // with punning sugaradd(~first: int, ~second: int) // application with default value. Same as normal applicationadd(~first=1: int, ~second=2: int) // explicit optional applicationadd(~first=?Some(1): option<int>, ~second=?Some(2): option<int>) // no punning sugar when you want to type annotate
// first arg type, second arg type, return typetypeadd= (int, int) =>int// labeledtypeadd= (~first: int, ~second: int) =>int// labeledtypeadd= (~first: int=?, ~second: int=?, unit) =>int
To annotate a function from the implementation file (.res
) in your interface file (.resi
):
letadd: (int, int) =>int
The type annotation part is the same as the previous section on With Type Annotation.
Don't confuse let add: myType
with type add = myType
. When used in .resi
interface files, the former exports the binding add
while annotating it as type myType
. The latter exports the type add
, whose value is the type myType
.