Address review feedback

This commit is contained in:
Anthony Bullard 2025-01-24 13:52:28 -06:00
parent 8f420d89ad
commit 8be4982af5
No known key found for this signature in database
4 changed files with 47 additions and 51 deletions

View file

@ -53,7 +53,7 @@ view =
Desc(
[Ident("store_email!"), Kw("="), Lambda(["path"])],
"""
<p>This <a href=\"/tutorial#defining-functions\">defines a function</a> named <code>store_email</code>.
<p>This <a href=\"/tutorial#defining-functions\">defines a function</a> named <code>store_email!</code>.
It takes one argument, named <code>path</code>.</p>
<p>In Roc, functions are ordinary values, so we assign names to them using <code>=</code> like with any other value.</p>
<p>The <code>|arg1, arg2|</code> syntax begins a function, and the part after the final <code>|</code> is the function's body.</p>

View file

@ -18,4 +18,4 @@ mkdir -p dist
cp -r public/* dist/
cargo run --bin roc -- main.roc --linker=legacy -- content/ dist/
npx http-server -p 8080 --nocache --cors --index -- dist/
simple-http-server -p 8080 --nocache --cors --index -- dist/

View file

@ -6,7 +6,7 @@
<p id="homepage-tagline">A fast, friendly, functional language.</p>
<pre id="first-code-sample"><samp class="code-snippet">credits <span class="kw">=</span> List<span class="punctuation section">.</span>map<span class="punctuation section">(</span>songs<span class="punctuation section">,</span> <span class="kw">|</span>song<span class="kw">|</span>
<span class="string">"Performed by </span><span class="kw">${</span>song<span class="punctuation section">.</span>artist<span class="kw">}</span><span class="string">"</span><span class="punctuation section">)</span></samp></pre>
<span class="string">"Performed by </span><span class="kw">${</span>song<span class="punctuation section">.</span>artist<span class="kw">}</span><span class="string">"</span><br><span class="punctuation section">)</span></samp></pre>
</div>
</div>
@ -72,7 +72,7 @@ Here are some examples of how it can be used today.
<div role="presentation" class="home-examples-container">
<div role="presentation" class="home-examples-column">
<h3 class="home-examples-title">Command-Line Interfaces</h3>
<pre><samp class="code-snippet">main <span class="kw">=</span>
<pre><samp class="code-snippet">main! <span class="kw">=</span> <span class="kw">|</span>args<span class="kw">|</span>
Stdout<span class="punctuation section">.</span>line<span class="punctuation section">!</span><span class="punctuation section">(</span><span class="literal">"Hello!"</span><span class="punctuation section">)</span></samp></pre>
<p>You can use Roc to create scripts and command-line interfaces (CLIs). The compiler produces binary executables, so Roc programs can run on devices that don't have Roc itself installed.</p>
<p>As an example, the HTML for this website is generated using a simple Roc script. You can see <a href="https://github.com/roc-lang/roc/blob/main/www/main.roc">the code for it</a> in the main Roc code repository.</p>
@ -80,7 +80,7 @@ Here are some examples of how it can be used today.
</div>
<div role="presentation" class="home-examples-column">
<h3 class="home-examples-title">Web Servers</h3>
<pre><samp class="code-snippet">handleReq <span class="kw">=</span> <span class="kw">|</span>request<span class="kw">|</span>
<pre><samp class="code-snippet">handle_req! <span class="kw">=</span> <span class="kw">|</span>request<span class="kw">|</span>
Ok<span class="punctuation section">(</span><span class="literal">{</span> body: <span class="comment"></span> <span class="literal">}</span><span class="punctuation section">)</span></samp></pre>
<p>You can also build web servers in Roc. <a href="https://github.com/roc-lang/basic-webserver">basic-webserver</a> is a <a href="/platforms">platform</a> with
a simple interface: you write a function which takes a <code>Request</code>, does some I/O, and returns a <code>Response</code>.</p>

View file

@ -1213,14 +1213,14 @@ Username := Str
from_str : Str -> Username
from_str = |str|
@Username str
@Username(str)
to_str : Username -> Str
to_str = \@Username(str) ->
str
```
The `from_str` function turns a string into a `Username` by calling `@Username` on that string. The `to_str` function turns a `Username` back into a string by pattern matching `@Username str` to unwrap the string from the `Username` opaque type.
The `from_str` function turns a string into a `Username` by calling `@Username` on that string. The `to_str` function turns a `Username` back into a string by pattern matching `@Username(str)` to unwrap the string from the `Username` opaque type.
Now we can expose the `Username` opaque type so that other modules can use it in type annotations. However, other modules can't use the `@Username` syntax to wrap or unwrap `Username` values. That operation is only available in the same scope where `Username` itself was defined; trying to use it outside that scope will give an error.
@ -1328,22 +1328,22 @@ Sometimes you may want to write a function that accepts configuration options. T
For example:
```roc
table = \{ height, width, title ? "oak", description ? "a wooden table" } ->
table = \{ height, width, title ?? "oak", description ?? "a wooden table" } ->
```
This is using _default value field destructuring_ to destructure a record while
providing default values for any fields that the caller didn't provide. Here, the `?` operator
essentially means "if the caller did not provide this field, default it to the following value."
The type of `table` uses `?` instead of `:` to indicate which fields the caller can omit:
The type of `table` uses `??` instead of `:` to indicate which fields the caller can omit:
```roc
table :
{
height : U64,
width : U64,
title ? Str,
description ? Str,
title ?? Str,
description ?? Str,
}
-> Table
```
@ -1355,7 +1355,7 @@ the type `Str`. This means you can choose to omit the `title`, `description`, or
when calling the function…but if you provide them, they must have the type `Str`.
This is also the type that would have been inferred for `table` if it had no annotation.
Roc's compiler can tell from the destructuring syntax `title ? "oak"` that `title` is a field with a default,
Roc's compiler can tell from the destructuring syntax `title ?? "oak"` that `title` is a field with a default,
and that it has the type `Str`.
Destructuring is the only way to implement a record with default value fields! For example,
@ -1386,7 +1386,7 @@ answer : Str
answer =
when Str.from_utf8(definitely_valid_utf8) is
Ok(str) -> str
Err _ -> crash "This should never happen!"
Err(_) -> crash "This should never happen!"
```
If the unthinkable happens, and somehow the program reaches this `Err` branch even though that was thought to be impossible, then it will crash - just like if the system had run out of memory. The string passed to `crash` will be provided to the platform as context; each platform may do something different with it.
@ -1438,9 +1438,9 @@ If a test fails, it will not show the actual value that differs from the expecte
```roc
expect
funcOut = pluralize("cactus", "cacti", 1)
func_out = pluralize("cactus", "cacti", 1)
funcOut == "2 cactus"
func_out == "2 cactus"
```
### [Inline Expectations](#inline-expects) {#inline-expects}
@ -1861,9 +1861,9 @@ main! = |_args|
when Stdin.line!({}) is
Ok(input) ->
_ = Stdout.line!("Your input was: ${input}")?
Ok {}
Err _ ->
Ok {}
Ok({})
Err(_) ->
Ok({})
```
### [Tagging errors](#tagging-errors) {#tagging-errors}
@ -1892,19 +1892,16 @@ import pf.Stdin
main! = |_args|
Stdout.line!("Type something and press Enter.")
|> Result.map_err(UnableToPrintPrompt)
|> try
|> Result.map_err(UnableToPrintPrompt)?
input =
Stdin.line!({})
|> Result.map_err(UnableToReadInput)
|> try
|> Result.map_err(UnableToReadInput)?
Stdout.line!("You entered: ${input}")
|> Result.map_err(UnableToPrintInput)
|> try
|> Result.map_err(UnableToPrintInput)?
Ok {}
Ok({})
```
The `map_err` function has this type:
@ -1936,7 +1933,7 @@ main! = |_args|
Stdout.line!("Type something and press Enter.") ? UnableToPrintPrompt
input = Stdin.line!({}) ? UnableToReadInput
Stdout.line!("You entered: ${input}") ? UnableToPrintInput
Ok {}
Ok({})
```
Here the `?` is just syntax sugar that does the same thing as the `?` postfix
@ -2001,9 +1998,9 @@ full_name = |user|
I can pass this function a record that has more fields than just `first_name` and `last_name`, as long as it has _at least_ both of those fields (and both of them are strings). So any of these calls would work:
- `full_name { first_name: "Sam", last_name: "Sample" }`
- `full_name { first_name: "Sam", last_name: "Sample", email: "blah@example.com" }`
- `full_name { age: 5, first_name: "Sam", things: 3, last_name: "Sample", role: Admin }`
- `full_name({ first_name: "Sam", last_name: "Sample" })`
- `full_name({ first_name: "Sam", last_name: "Sample", email: "blah@example.com" })`
- `full_name({ age: 5, first_name: "Sam", things: 3, last_name: "Sample", role: Admin })`
This `user` argument is an _open record_ - that is, a description of a minimum set of fields on a record, and their types. When a function takes an open record as an argument, it's okay if you pass it a record with more fields than just the ones specified.
@ -2201,14 +2198,14 @@ This will be a type mismatch because the two branches have incompatible types. S
```roc
if x > 5 then
Ok "foo"
Ok("foo")
else
Err "bar"
Err("bar")
```
This shouldn't be a type mismatch, because we can see that the two branches are compatible; they are both tags that could easily coexist in the same tag union. But if the compiler inferred the type of `Ok "foo"` to be the closed union `[Ok Str]`, and likewise for `Err "bar"` and `[Err Str]`, then this would have to be a type mismatch - because those two closed unions are incompatible.
This shouldn't be a type mismatch, because we can see that the two branches are compatible; they are both tags that could easily coexist in the same tag union. But if the compiler inferred the type of `Ok("foo")` to be the closed union `[Ok Str]`, and likewise for `Err("bar")` and `[Err Str]`, then this would have to be a type mismatch - because those two closed unions are incompatible.
Instead, the compiler infers `Ok "foo"` to be the open union `[Ok Str]*`, and `Err "bar"` to be the open union `[Err Str]*`. Then, when using them together in this conditional, the inferred type of the conditional becomes `[Ok Str, Err Str]*` - that is, the combination of the unions in each of its branches. (Branches in a `when` work the same way with open unions.)
Instead, the compiler infers `Ok("foo")` to be the open union `[Ok Str]*`, and `Err("bar")` to be the open union `[Err Str]*`. Then, when using them together in this conditional, the inferred type of the conditional becomes `[Ok Str, Err Str]*` - that is, the combination of the unions in each of its branches. (Branches in a `when` work the same way with open unions.)
Earlier we saw how a function which accepts an open union must account for more possibilities, by including catch-all `_ ->` patterns in its `when` expressions. So _accepting_ an open union means you have more requirements. In contrast, when you already _have_ a value which is an open union, you have fewer requirements. A value which is an open union (like `Ok "foo"`, which has the type `[Ok Str]*`) can be provided to anything that's expecting a tag union (no matter whether it's open or closed), as long as the expected tag union includes at least the tags in the open union you're providing.
@ -2222,14 +2219,14 @@ So if I have an `[Ok Str]*` value, I can pass it to functions with any of these
| `[Ok Str, Err Bool] -> Bool` | Yes |
| `[Ok Str, Err Bool, Whatever]* -> Bool` | Yes |
| `[Ok Str, Err Bool, Whatever] -> Bool` | Yes |
| `Result Str(Bool) -> Bool` | Yes |
| `Result Str Bool -> Bool` | Yes |
| `[Err Bool, Whatever]* -> Bool` | Yes |
That last one works because a function accepting an open union can accept any unrecognized tag (including `Ok Str`) even though it is not mentioned as one of the tags in `[Err Bool, Whatever]*`! Remember, when a function accepts an open tag union, any `when` branches on that union must include a catch-all `_ ->` branch, which is the branch that will end up handling the `Ok Str` value we pass in.
However, I could not pass an `[Ok Str]*` to a function with a _closed_ tag union argument that did not mention `Ok Str` as one of its tags. So if I tried to pass `[Ok Str]*` to a function with the type `[Err Bool, Whatever] -> Str`, I would get a type mismatch - because a `when` in that function could be handling the `Err Bool` possibility and the `Whatever` possibility, and since it would not necessarily have a catch-all `_ ->` branch, it might not know what to do with an `Ok Str` if it received one.
> **Note:** It wouldn't be accurate to say that a function which accepts an open union handles "all possible tags." For example, if I have a function `[Ok Str]* -> Bool` and I pass it `Ok 5`, that will still be a type mismatch. If you think about it, a `when` in that function might have the branch `Ok(str) ->` which assumes there's a string inside that `Ok`, and if `Ok 5` type-checked, then that assumption would be false and things would break!
> **Note:** It wouldn't be accurate to say that a function which accepts an open union handles "all possible tags." For example, if I have a function `[Ok Str]* -> Bool` and I pass it `Ok(5)`, that will still be a type mismatch. If you think about it, a `when` in that function might have the branch `Ok(str) ->` which assumes there's a string inside that `Ok`, and if `Ok(5)` type-checked, then that assumption would be false and things would break!
>
> So `[Ok Str]*` is more restrictive than `[]*`. It's basically saying "this may or may not be an `Ok` tag, but if it is an `Ok` tag, then it's guaranteed to have a payload of exactly `Str`."
@ -2302,30 +2299,30 @@ combine_matchers = |matcher_a, matcher_b, combiner| ...
user_tab_matcher : UrlMatcher { users: {}, user_id: U64, tab: Str }
user_tab_matcher =
{ combine_matchers <-
users: exact_segment "users",
users: exact_segment("users"),
user_id: u64_segment,
tab: any_segment,
}
expect
user_tab_matcher
|> match_on_url "/users/123/account"
== Ok { users: {}, user_id: 123, tab: "account" }
|> match_on_url("/users/123/account")
== Ok({ users: {}, user_id: 123, tab: "account" })
```
The `user_tab_matcher` record builder desugars to the following:
```roc
user_tab_matcher =
combine_matchers
(exact_segment "users")
(
combine_matchers
u64_segment
any_segment
|user_id, tab| (user_id, tab)
)
|users, (user_id, tab)| { users, user_id, tab }
combine_matchers(
exact_segment("users"),
combine_matchers(
u64_segment,
any_segment,
|user_id, tab| (user_id, tab),
),
|users, (user_id, tab)| { users, user_id, tab },
)
```
You can see that the `combine_matchers` builder function is simply applied in sequence, pairing up all fields until a record is created.
@ -2336,15 +2333,15 @@ You'll notice that the `users` field above holds an empty record, and isn't a us
user_tab_matcher : UrlMatcher { user_id: U64 }
user_tab_matcher =
{ combine_matchers <-
_: exact_segment "users",
_: exact_segment("users"),
user_id: u64_segment,
_tab: any_segment,
}
expect
user_tab_matcher
|> match_on_url "/users/123/account"
== Ok { user_id: 123 }
|> match_on_url("/users/123/account")
== Ok({ user_id: 123 })
```
If you want to see other examples of using record builders, look at the [Record Builder Example](https://www.roc-lang.org/examples/RecordBuilder/README.html).
@ -2380,7 +2377,6 @@ Here are various Roc expressions involving operators, and what they desugar to.
| `!a` | `Bool.not(a)` |
| <code>a \|> f</code> | `f(a)` |
| <code>f a b \|> g x y</code> | `g(f(a, b), x y)` |
| `f!` | [see example](https://www.roc-lang.org/examples/DesugaringAwait/README.html) |
| `f?` | [see example](https://www.roc-lang.org/examples/DesugaringTry/README.html) |
### [Additional Resources](#additional-resources) {#additional-resources}