Declare Functions in Reason

Nik Graf
InstructorNik Graf
Share this video with your friends

Social Share Links

Send Tweet
Published 6 years ago
Updated 5 years ago

In this lesson we walk through how to declare functions in ReasonML. Declaring and using functions is straightforward. Still there are a handful great features not available in a lot of other languages like labeled arguments or auto currying. Both are explained in this lesson.

Instructor: [00:00] This is how an anonymous function looks like. A function is defined by one or more parameters inside parenthesis, then an arrow followed by the body. In case it's only one parameter, the parenthesis can be omitted.

[00:13] Using let, we can bind the function to the name plus one, call the function -- we use its name -- followed by the arguments within parenthesis. Before we move on, one brief remark on the wording.

[00:26] In casual conversations, the terms parameter and argument are often used interchangeably. Technically, they have a different meaning. A function accepts parameters. When calling the function with concrete values, these are the arguments. In this lesson, I try my best to use these terms correctly. Let's get back to it.

[00:46] Not sure if you already noticed it, but it wasn't necessary to declare a type of the parameter, nor the type of the return expression. If we want to, we can provide the types explicitly. The type of a parameter is declared after a colon, right after a parameter's name. The return type comes after the closing parenthesis and before the arrow.

[01:10] Keep in mind we can leave out each of these type declarations independently from each other. For example, we could let the parameter Y be inferred, or we could remove the return type. In case of more complex functions, we can open a local scope by surrounding the body with a block.

[01:27] Here, we want to add an integer to a float, and the result should be a float. When using a block, the last expression is always returned, and since there is no return keyword, there is no such concept as an early return inside a function. So far so good.

[01:43] Another feature of the language its partial application of arguments. To demonstrate it, we use an add function. We can add two integers together by calling the function with two arguments. Providing one argument is also possible.

[01:59] Instead of throwing an error, it will return as a function where only one, in our case the second parameter, needs to be provided. Let's bind the function to a name and use it. Voila. Same result, different path.

[02:16] Alternatively, we could call the functions directly one after another. This functionality comes out of the box for every function in Reason, and it's called carry. It turns this definition into this one. In fact, as you can see, [inaudible] even renders the output of both the same way. Of course, this works with more parameters as well.

[02:45] We can provide all arguments or just partially apply them. By the way, partially applying multiple arguments works as well. Now, you might wonder why is this useful. Let me demonstrate to give you an example.

[03:03] We have a list of numbers and we want to increase every item by four. To do so, we use our add function. We can use the function list.map. It accepts a function as a first parameter and the list as the second.

[03:17] Next up, we want to achieve the same result leveraging partial application. If we use add with the argument four, we get back a function accepting one parameter. In our case, this matches exactly the signature that list.map accepts, meaning we can write this. Is it better? That depends on your taste. This version is certainly more concise compared to this one.

[03:50] While this being a simple example, partial application of arguments makes functions more versatile, and can come in quite handy. In case you heard about partial application and carrying for the first time and think it's a lot to take in, don't worry. Let it sink in a bit. In the meantime, move on to another feature, label parameter.

[04:09] So far, we have only seen position parameters. Add a tilde sign before a parameter, and that's all you need to declare a parameter as labeled. When invoking the function, we can name the arguments then.

[04:29] The benefit here is that we can provide them in any order. This helps us a great deal to not screw up when invoking a function. Where labeled parameters really start to shine, though, is in combination with partial application.

[04:43] We also can choose which argument to apply first when labelling. It's also possible to expose a label parameter with a specific name and use the s-something syntax to refer to the parameter in a function body with a different name.

[05:01] In addition, labeled, but only labeled parameters, can have a default value. If we call the function with all three arguments, the output is, as expected, the full name. There's one problem, though. If we leave out the middle name, we get back a function.

[05:36] We get back a function because the original function is carried, and we applied the two arguments partially. It expects the middle name to be provided where we don't intend to pass it in. Since Francis, its default value, we expected the full name with the middle name being Francis.

[05:53] In a nutshell, there's a conflict of expectations. Therefore, the Reason core team's recommended practice is to add a positional parameter at the end. Conventionally, this parameter is of type unit. This way, we can call the function with or without the middle name.

[06:11] In addition, labeled parameters, but again, only labeled parameters, can be completely optional using a question mark after the equal sign. When using the syntax, middle name is wrapped in the standard library's option type and defaulting to none.

[06:28] This means we need to change the body of our function. We use the switch expression to add the middle name value in case the parameter is provided and ignore it in case the parameter is omitted.

[06:40] Since we have faced the same conflict of carrying and optional parameters as before, we keep the only parameter at the end. Invoking name with or without the middle name now works as expected.

[06:53] To avoid a lot of tedious switch expressions, Reason comes with a shortcut syntax to explicitly pass in an option. Reason also provides punning syntax for arguments. Even in case for explicitly passing in an option, here the question mark has to be placed after the name.

[07:18] There are two more gotchas I want to make you aware of regarding parameters. For once, it's possible to pass positional arguments to labeled parameters. For example, when we define name like this, we can call it like this.

[07:38] While exploring the language, I discovered some edge cases. In general, I recommend you just stay away from it, and rather explicitly label your arguments. The second gotcha is that functions technically always need at least one parameter.

[07:53] Sometimes, though, we need functions that don't expect any parameter. That's why, out of the box, Reason comes with syntax sugar to accept the unit type in case no parameter has been provided. Instead of this, we can write this. The same syntax sugar exists for calling the function. Very convenient.

[08:16] One last thing about parameters. In some cases, we have to implement functions that match our parameters to the call list arguments. One example would be list.map. List.map expects a function that always accepts one parameter.

[08:32] What about the case when we just want to reset all list items to zero? We don't need the item. Here, the parameter should be prepended with an underscore. As an alternative, we could even only use an underscore. Personally, I prefer underscore item since it's more descriptive.

jmmendivil
jmmendivil
~ 5 years ago

What do you mean with avoid a lot of tedious switch expressions (06:53)? The example uses a function with a switch in it, can you please provide another example?

Nik Graf
Nik Grafinstructor
~ 5 years ago

The example uses ~middleName=?someName. Without that you would need to switch on someName. Does that help?

JP Lew
JP Lew
~ 4 years ago

I believe the behaviour described in the video has become outdated.

In the latest version of rtop (2.2.0), I get the following output:

Reason # let name = (~firstName, ~middleName="Francis", ~lastName) => {
  firstName ++ " " ++ middleName ++ " " ++ lastName;
};
let name:
  (~firstName: string, ~middleName: string=?, ~lastName: string) => string =
  <fun>;
Reason # name(~firstName="Jane", ~middleName="Kim", ~lastName="Doe");
- : string = "Jane Kim Doe"
Reason # name(~firstName="Jane", ~lastName="Doe");
- : (~middleName: string=?) => string = <fun>
Reason # name(~firstName="Jane", ~lastName="Doe")();
Error: The function applied to this argument has type
         (~middleName: string=?) => string
This argument cannot be applied without label
Reason # name(~firstName="Jane", ~lastName="Doe", ());
Error: The function applied to this argument has type
         (~middleName: string=?) => string
This argument cannot be applied without label
JP Lew
JP Lew
~ 4 years ago

sorry, please ignore the above comment, I didn't notice the positional parameter () at the end! ```name(~firstName="Jane", ~middleName="Kim", ~lastName="Doe", ());`

Markdown supported.
Become a member to join the discussionEnroll Today