Skip to content

Commit

Permalink
Merge pull request #6175 from roc-lang/tutorial-input-inspect
Browse files Browse the repository at this point in the history
Use Inspect in the tutorial
  • Loading branch information
rtfeldman committed Dec 4, 2023
2 parents a187d14 + c6d1a8e commit 7b1c009
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 44 deletions.
2 changes: 1 addition & 1 deletion www/content/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Here is a Roc application that prints `"Hello, World!"` to the command line:

```roc
app "hello"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" }
imports [pf.Stdout]
provides [main] to pf
Expand Down
90 changes: 47 additions & 43 deletions www/content/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ Make a file named `main.roc` and put this in it:

```roc
app "hello"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" }
imports [pf.Stdout]
provides [main] to pf
Expand Down Expand Up @@ -1412,7 +1412,7 @@ Let's take a closer look at the part of `main.roc` above the `main` def:

```roc
app "hello"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" }
imports [pf.Stdout]
provides [main] to pf
```
Expand All @@ -1424,7 +1424,7 @@ The line `app "hello"` shows that this module is a Roc application. The "hello"
The remaining lines all involve the [platform](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform) this application is built on:

```roc
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" }
imports [pf.Stdout]
provides [main] to pf
```
Expand Down Expand Up @@ -1523,7 +1523,7 @@ Let's start with a basic "Hello World" program.

```roc
app "cli-tutorial"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" }
imports [pf.Stdout]
provides [main] to pf
Expand All @@ -1543,26 +1543,34 @@ When we set `main` to be a `Task`, the task will get run when we run our program

`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 `*`).

In contrast, `Stdin.line` produces a `Str` when it finishes reading from [standard input](<https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)>). That `Str` is reflected in its type:
In contrast, when `Stdin.line` finishes reading a line from [standard input](<https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)>), it produces either a `Str` or else `End` if standard input reached its end (which can happen if the user types Ctrl+D on UNIX systems or Ctrl+Z on Windows). Those two possibilities are reflected in its type:

```roc
Stdin.line : Task Str *
Stdin.line : Task [Input Str, End] *
```

Let's change `main` to read a line from `stdin`, and then print it back out again:
Once this task runs, we'll end up with the [tag union](https://www.roc-lang.org/tutorial#tags-with-payloads) `[Input Str, End]`. Then we can check whether we got an `End` or some actual `Input`, and print out a message accordingly.

### [Printing Roc values with `Inspect.toStr`](#inspect) {#inspect}

Let's change `main` to read a line from `stdin`, and then print what we got:

```roc
app "cli-tutorial"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" }
imports [pf.Stdout, pf.Stdin, pf.Task]
provides [main] to pf
main =
Task.await Stdin.line \text ->
Stdout.line "You just entered: \(text)"
Task.await Stdin.line \input ->
Stdout.line "Your input was: \(Inspect.toStr input)"
```

If you run this program, at first it won't do anything. It's waiting for you to type something in and press Enter! Once you do, it should print back out what you entered.
The [`Inspect.toStr`](https://www.roc-lang.org/builtins/Inspect#toStr) function returns a `Str` representation of any Roc value. It's useful for things like debugging and logging (although [`dbg`](https://www.roc-lang.org/tutorial#debugging) is often nicer for debugging), but its output is almost never something that should be shown to end users! In this case we're just using it for our own learning, but in a real program we'd run a `when` on `answer` and do something different depending on whether we got an `End` or `Input` tag.

If you run this program, at first it won't do anything. It's waiting for you to type something in and press Enter! Once you do, it should print back out what you entered—either `Your input was: End` or `Your input was: Input <whatever you entered>` depending on whether you pressed Enter or the key combination to close stdin (namely Ctrl+D on UNIX or Ctrl+Z on Windows). Try doing it both ways to watch the output change!

### [Chaining tasks with `Task.await`](#await) {#await}

The `Task.await` function combines two tasks into one bigger `Task` which first runs one of the given tasks and then the other. In this case, it's combining a `Stdin.line` task with a `Stdout.line` task into one bigger `Task`, and then setting `main` to be that bigger task.

Expand All @@ -1572,11 +1580,11 @@ The type of `Task.await` is:
Task.await : Task a err, (a -> Task b err) -> Task b err
```

The second argument to `Task.await` is a "callback function" which runs after the first task completes. This callback function receives the output of that first task, and then returns the second task. This means the second task can make use of output from the first task, like we did in our `\text -> ...` callback function here:
The second argument to `Task.await` is a "callback function" which runs after the first task completes. This callback function receives the output of that first task, and then returns the second task. This means the second task can make use of output from the first task, like we did in our `\input -> ...` callback function here:

```roc
\text ->
Stdout.line "You just entered: \(text)"
\input ->
Stdout.line "Your input was: \(Inspect.toStr input)"
```

Notice that, just like before, we're still building `main` from a single `Task`. This is how we'll always do it! We'll keep building up bigger and bigger `Task`s out of smaller tasks, and then setting `main` to be that one big `Task`.
Expand All @@ -1586,22 +1594,22 @@ For example, we can print a prompt before we pause to read from `stdin`, so it n
```roc
main =
Task.await (Stdout.line "Type something press Enter:") \_ ->
Task.await Stdin.line \text ->
Stdout.line "You just entered: \(text)"
Task.await Stdin.line \input ->
Stdout.line "Your input was: \(Inspect.toStr input)"
```

This works, but we can make it a little nicer to read. Let's change it to the following:

```roc
app "cli-tutorial"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.0/bkGby8jb0tmZYsy2hg1E_B2QrCgcSTxdUlHtETwm5m4.tar.br" }
imports [pf.Stdout, pf.Stdin, pf.Task.{ await }]
provides [main] to pf
main =
await (Stdout.line "Type something press Enter:") \_ ->
await Stdin.line \text ->
Stdout.line "You just entered: \(text)"
await Stdin.line \input ->
Stdout.line "Your input was: \(Inspect.toStr input)"
```

Here we've changed how we're importing the `Task` module. Before it was `pf.Task` and now it's `pf.Task.{ await }`. The difference is that we're importing `await` in an _unqualified_ way, meaning that whenever we write `await` in this module, it will refer to `Task.await`. Now we no longer need to write `Task.` every time we want to use `await`.
Expand All @@ -1613,9 +1621,9 @@ Speaking of calling `await` repeatedly, if we keep calling it more and more on t
```roc
main =
_ <- await (Stdout.line "Type something press Enter:")
text <- await Stdin.line
input <- await Stdin.line
Stdout.line "You just entered: \(text)"
Stdout.line "Your input was: \(Inspect.toStr input)"
```

## [Backpassing](#backpassing) {#backpassing}
Expand All @@ -1625,16 +1633,16 @@ This `<-` syntax is called _backpassing_. The `<-` is a way to define an anonymo
Here, we're using backpassing to define two anonymous functions. Here's one of them:

```roc
text <-
input <-
Stdout.line "You just entered: \(text)"
Stdout.line "Your input was: \(Inspect.toStr input)"
```

It may not look like it, but this code is defining an anonymous function! You might remember it as the anonymous function we previously defined like this:

```roc
\text ->
Stdout.line "You just entered: \(text)"
\input ->
Stdout.line "Your input was: \(Inspect.toStr input)"
```

These two anonymous functions are the same, just defined using different syntax.
Expand All @@ -1646,66 +1654,62 @@ Let's look at these two complete expressions side by side. They are both saying
Here's the original:

```roc
await Stdin.line \text ->
Stdout.line "You just entered: \(text)"
await Stdin.line \input ->
Stdout.line "Your input was: \(Inspect.toStr input)"
```

And here's the equivalent expression with backpassing syntax:

```roc
text <- await Stdin.line
input <- await Stdin.line
Stdout.line "You just entered: \(text)"
Stdout.line "Your input was: \(Inspect.toStr input)"
```

Here's the other function we're defining with backpassing:

```roc
_ <-
text <- await Stdin.line
input <- await Stdin.line
Stdout.line "You just entered: \(text)"
Stdout.line "Your input was: \(Inspect.toStr input)"
```

We could also have written that function this way if we preferred:

```roc
_ <-
await Stdin.line \text ->
Stdout.line "You just entered: \(text)"
await Stdin.line \input ->
Stdout.line "Your input was: \(Inspect.toStr input)"
```

This is using a mix of a backpassing function `_ <-` and a normal function `\text ->`, which is totally allowed! Since backpassing is nothing more than syntax sugar for defining a function and passing back as an argument to another function, there's no reason we can't mix and match if we like.
This is using a mix of a backpassing function `_ <-` and a normal function `\input ->`, which is totally allowed! Since backpassing is nothing more than syntax sugar for defining a function and passing back as an argument to another function, there's no reason we can't mix and match if we like.

That said, the typical style in which this `task` would be written in Roc is using backpassing for all the `await` calls, like we had above:

```roc
main =
_ <- await (Stdout.line "Type something press Enter:")
text <- await Stdin.line
input <- await Stdin.line
Stdout.line "You just entered: \(text)"
Stdout.line "Your input was: \(Inspect.toStr input)"
```

This way, it reads like a series of instructions:

1. First, run the `Stdout.line` task and await its completion. Ignore its output (hence the underscore in `_ <-`)
2. Next, run the `Stdin.line` task and await its completion. Name its output `text`.
3. Finally, run the `Stdout.line` task again, using the `text` value we got from the `Stdin.line` effect.
2. Next, run the `Stdin.line` task and await its completion. Name its output `input`.
3. Finally, run the `Stdout.line` task again, using the `input` value we got from the `Stdin.line` effect.

Some important things to note about backpassing and `await`:

- `await` is not a language keyword in Roc! It's referring to the `Task.await` function, which we imported unqualified by writing `Task.{ await }` in our module imports. (That said, it is playing a similar role here to the `await` keyword in languages that have `async`/`await` keywords, even though in this case it's a function instead of a special keyword.)
- Backpassing syntax does not need to be used with `await` in particular. It can be used with any function.
- Roc's compiler treats functions defined with backpassing exactly the same way as functions defined the other way. The only difference between `\text ->` and `text <-` is how they look, so feel free to use whichever looks nicer to you!
- Roc's compiler treats functions defined with backpassing exactly the same way as functions defined the other way. The only difference between `\input ->` and `input <-` is how they look, so feel free to use whichever looks nicer to you!

See the [Task & Error Handling example](https://www.roc-lang.org/examples/Tasks/README.html) for a more detailed explanation of how to use tasks to help with error handling in a larger program.

## [Abilities](#abilities) {#abilities}

\[This part of the tutorial has not been written yet. Coming soon!\]

## Examples

Well done on making it this far!
Expand Down

0 comments on commit 7b1c009

Please sign in to comment.