mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 11:52:19 +00:00
Update the tutorial to add the ? operator
This commit is contained in:
parent
9a4d556725
commit
07cf9eb8e1
1 changed files with 24 additions and 7 deletions
|
@ -805,7 +805,8 @@ when List.get ["a", "b", "c"] index is
|
|||
|
||||
There's also `List.first`, which always gets the first element, and `List.last` which always gets the last. They return `Err ListWasEmpty` instead of `Err OutOfBounds`, because the only way they can fail is if you pass them an empty list!
|
||||
|
||||
These functions demonstrate a common pattern in Roc: operations that can fail returning either an `Ok` tag with the answer (if successful), or an `Err` tag with another tag describing what went wrong (if unsuccessful). In fact, it's such a common pattern that there's a whole module called `Result` which deals with these two tags. Here are some examples of `Result` functions:
|
||||
### [Error Handling](#error-handling) {#error-handling}
|
||||
The `List` functions such as `List.get`, `List.first`, and `List.last` demonstrate a common pattern in Roc: operations that can fail returning either an `Ok` tag with the answer (if successful), or an `Err` tag with another tag describing what went wrong (if unsuccessful). In fact, it's such a common pattern that there's a whole module called `Result` which deals with these two tags. Here are some examples of `Result` functions:
|
||||
|
||||
```roc
|
||||
Result.withDefault (List.get ["a", "b", "c"] 100) ""
|
||||
|
@ -837,11 +838,25 @@ listGet = \index ->
|
|||
|
||||
`Result.try` is often used to chain two functions that return `Result` (as in the example above). This prevents you from needing to add error handling code at every intermediate step.
|
||||
|
||||
<details>
|
||||
<summary>Upcoming feature</summary>
|
||||
|
||||
We plan to introduce syntax sugar to make `Result.try` nicer to use, follow [this issue](https://github.com/roc-lang/roc/issues/6828) for more.
|
||||
</details>
|
||||
Roc also has a special "try" operator `?`, which is convenient syntax sugar for `Result.try`. For example, consider the following `getLetter` function:
|
||||
|
||||
```roc
|
||||
getLetter : Str -> Result Str [OutOfBounds, InvalidNumStr]
|
||||
getLetter = \indexStr ->
|
||||
index = Str.toU64? indexStr
|
||||
List.get ["a", "b", "c", "d"] index
|
||||
```
|
||||
|
||||
Notice that we appended `?` to the function name `Str.toU64`. Here's what this does:
|
||||
* If the `Str.toU64` function returns an `Ok` value, then its payload is unwrapped.
|
||||
- For example, if we call `getLetter "2"`, then `Str.toU64` returns `Ok 2`, and the `?` operator unwraps the integer 2, so `index` is set to 2 (not `Ok 2`). Then the `List.get` function is called and returns `Ok "c"`.
|
||||
* If the `Str.toU64` function returns an `Err` value, then the `?` operator immediately interrupts the `getLetter` function and makes it return this error.
|
||||
- For example, if we call `getLetter "abc"`, then the call to `Str.toU64` returns `Err InvalidNumStr`, and the `?` operator ensures that the `getLetter` function returns this error immediately, without executing the rest of the function.
|
||||
|
||||
Thanks to the `?` operator, your code can focus on the "happy path" (where nothing fails) and simply bubble up to the caller any error that might occur. Your error handling code can be neatly separated, and you can rest assured that you won't forget to handle any errors, since the compiler will let you know. See this [code example](https://github.com/roc-lang/examples/blob/main/examples/Results/main.roc) for more details on error handling.
|
||||
|
||||
Now let's get back to lists!
|
||||
|
||||
|
||||
### [Walking the elements in a list](#walking-the-elements-in-a-list) {#walking-the-elements-in-a-list}
|
||||
|
||||
|
@ -1612,7 +1627,7 @@ main =
|
|||
Stdout.line! "Hello, World!"
|
||||
```
|
||||
|
||||
The `Stdout.line` function takes a `Str` and writes it to [standard output](<https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)>), we'll [discuss the `!` part later](https://www.roc-lang.org/tutorial#the-!-suffix). `Stdout.line` has this type:
|
||||
This code prints "Hello, World!" to the [standard output](<https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)>). `Stdout.line` has this type:
|
||||
|
||||
```roc
|
||||
Stdout.line : Str -> Task {} *
|
||||
|
@ -1620,6 +1635,8 @@ Stdout.line : Str -> Task {} *
|
|||
|
||||
A `Task` represents an _effect_; an interaction with state outside your Roc program, such as the terminal's standard output, or a file.
|
||||
|
||||
Did you notice the `!` suffix after `Stdout.line`? This operator is similar to the [`?` try operator](https://www.roc-lang.org/tutorial#error-handling), but it is used on functions that return `Task`s instead of `Result`s (we'll discuss [the `!` operator in more depth](https://www.roc-lang.org/tutorial#the-!-suffix) later in this tutorial).
|
||||
|
||||
When we set `main` to be a `Task`, the task will get run when we run our program. Here, we've set `main` to be a task that writes `"Hello, World!"` to `stdout` when it gets run, so that's what our program does!
|
||||
|
||||
`Task` has two type parameters: the type of value it produces when it finishes running, and any errors that might happen when running it. `Stdout.line` has the type `Task {} *` because it doesn't produce any values when it finishes (hence the `{}`) and there aren't any errors that can happen when it runs (hence the `*`).
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue