Update to basic-cli 0.8.1

This commit is contained in:
Richard Feldman 2024-01-26 12:05:28 -05:00
parent 7f2e2d0803
commit 09574203ce
No known key found for this signature in database
GPG key ID: F1F21AA5B1D9E43B
18 changed files with 156 additions and 146 deletions

View file

@ -872,10 +872,10 @@ mod cli_run {
&[], &[],
indoc!( indoc!(
r#" r#"
This roc file can print it's own source code. The source is: This roc file can print its own source code. The source is:
app "ingested-file" app "ingested-file"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [ imports [
pf.Stdout, pf.Stdout,
"ingested-file.roc" as ownCode : Str, "ingested-file.roc" as ownCode : Str,
@ -883,7 +883,7 @@ mod cli_run {
provides [main] to pf provides [main] to pf
main = main =
Stdout.line "\nThis roc file can print it's own source code. The source is:\n\n\(ownCode)" Stdout.line "\nThis roc file can print its own source code. The source is:\n\n$(ownCode)"
"# "#
), ),

View file

@ -1,7 +1,7 @@
app "hello" app "hello"
packages { packages {
pf: pf:
"https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br", "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br",
} }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf

View file

@ -24,7 +24,7 @@ Full {
Newline, Newline,
], ],
package_name: @31-145 PackageName( package_name: @31-145 PackageName(
"https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br", "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br",
), ),
}, },
[ [

View file

@ -1,6 +1,6 @@
app "hello" app "hello"
packages { pf: packages { pf:
"https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br"
} }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf

View file

@ -1,5 +1,5 @@
app "args" app "args"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout, pf.Arg, pf.Task.{ Task }] imports [pf.Stdout, pf.Arg, pf.Task.{ Task }]
provides [main] to pf provides [main] to pf

View file

@ -1,5 +1,5 @@
app "countdown" app "countdown"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdin, pf.Stdout, pf.Task.{ await, loop }] imports [pf.Stdin, pf.Stdout, pf.Task.{ await, loop }]
provides [main] to pf provides [main] to pf

View file

@ -1,5 +1,5 @@
app "echo" app "echo"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdin, pf.Stdout, pf.Task.{ Task }] imports [pf.Stdin, pf.Stdout, pf.Task.{ Task }]
provides [main] to pf provides [main] to pf

View file

@ -1,5 +1,5 @@
app "env" app "env"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout, pf.Stderr, pf.Env, pf.Task.{ Task }] imports [pf.Stdout, pf.Stderr, pf.Env, pf.Task.{ Task }]
provides [main] to pf provides [main] to pf

View file

@ -1,5 +1,5 @@
app "file-io" app "file-io"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [ imports [
pf.Stdout, pf.Stdout,
pf.Stderr, pf.Stderr,

View file

@ -1,5 +1,5 @@
app "form" app "form"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdin, pf.Stdout, pf.Task.{ await, Task }] imports [pf.Stdin, pf.Stdout, pf.Task.{ await, Task }]
provides [main] to pf provides [main] to pf

View file

@ -1,5 +1,5 @@
app "http-get" app "http-get"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Http, pf.Task.{ Task }, pf.Stdin, pf.Stdout] imports [pf.Http, pf.Task.{ Task }, pf.Stdin, pf.Stdout]
provides [main] to pf provides [main] to pf

View file

@ -1,5 +1,5 @@
app "ingested-file-bytes" app "ingested-file-bytes"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [ imports [
pf.Stdout, pf.Stdout,
"../../LICENSE" as license : _, # A type hole can also be used here. "../../LICENSE" as license : _, # A type hole can also be used here.

View file

@ -1,5 +1,5 @@
app "ingested-file" app "ingested-file"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [ imports [
pf.Stdout, pf.Stdout,
"ingested-file.roc" as ownCode : Str, "ingested-file.roc" as ownCode : Str,
@ -7,4 +7,4 @@ app "ingested-file"
provides [main] to pf provides [main] to pf
main = main =
Stdout.line "\nThis roc file can print it's own source code. The source is:\n\n\(ownCode)" Stdout.line "\nThis roc file can print its own source code. The source is:\n\n$(ownCode)"

View file

@ -1,5 +1,5 @@
app "helloWorld" app "helloWorld"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf

View file

@ -2,7 +2,7 @@
# Shows how Roc values can be logged # Shows how Roc values can be logged
# #
app "inspect-logging" app "inspect-logging"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [ imports [
pf.Stdout, pf.Stdout,
Community, Community,

View file

@ -1,6 +1,6 @@
app "example" app "example"
packages { packages {
cli: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br", cli: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br",
parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.5/KB-TITJ4DfunB88sFBWjCtCGV7LRRDdTH5JUXp4gIb8.tar.br", parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.5/KB-TITJ4DfunB88sFBWjCtCGV7LRRDdTH5JUXp4gIb8.tar.br",
} }
imports [ imports [

View file

@ -1,6 +1,6 @@
# Platforms # Platforms
Something that sets Roc apart from other programming languages is its <span class="nowrap">*platforms and applications*</span> architecture. Something that sets Roc apart from other programming languages is its <span class="nowrap">_platforms and applications_</span> architecture.
## [Applications](#applications) {#applications} ## [Applications](#applications) {#applications}
@ -8,7 +8,7 @@ Here is a Roc application that prints `"Hello, World!"` to the command line:
```roc ```roc
app "hello" app "hello"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf
@ -26,19 +26,20 @@ Also like many game engines and Web frameworks, Roc platforms have a high-level
Here are some example Roc platforms, and functionality they might provide: Here are some example Roc platforms, and functionality they might provide:
* A Roc game engine platform might provide functionality for rendering and sound. - A Roc game engine platform might provide functionality for rendering and sound.
* A Roc Web server platform (like [basic-webserver](https://github.com/roc-lang/basic-webserver)) probably would not provide functionality for rendering and sound, but it might provide functionality for responding to incoming HTTP requests—which a game engine platform likely would not. - A Roc Web server platform (like [basic-webserver](https://github.com/roc-lang/basic-webserver)) probably would not provide functionality for rendering and sound, but it might provide functionality for responding to incoming HTTP requests—which a game engine platform likely would not.
* A Roc native [GUI](https://en.wikipedia.org/wiki/Graphical_user_interface) platform might provide functionality for defining native operating system UI elements, whereas a game engine platform might focus more on rendering with [shaders](https://en.wikipedia.org/wiki/Shader), and a Web server platform would not have GUI functionality at all. - A Roc native [GUI](https://en.wikipedia.org/wiki/Graphical_user_interface) platform might provide functionality for defining native operating system UI elements, whereas a game engine platform might focus more on rendering with [shaders](https://en.wikipedia.org/wiki/Shader), and a Web server platform would not have GUI functionality at all.
These are broad domains, but platforms can be much more specific than this. For example, anyone could make a platform for writing [Vim](https://en.wikipedia.org/wiki/Vim_(text_editor)) plugins, or [Postgres](https://en.wikipedia.org/wiki/PostgreSQL) extensions, or robots ([which has already happened](https://roc.zulipchat.com/#narrow/stream/304902-show-and-tell/topic/Roc.20on.20a.20microcontroller/near/286678630)), or even [implementing servo logic for a clock that physically turns panels to simulate an LCD](https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/Roc.20Clock/near/327939600). You really can get as specific as you like! These are broad domains, but platforms can be much more specific than this. For example, anyone could make a platform for writing [Vim](<https://en.wikipedia.org/wiki/Vim_(text_editor)>) plugins, or [Postgres](https://en.wikipedia.org/wiki/PostgreSQL) extensions, or robots ([which has already happened](https://roc.zulipchat.com/#narrow/stream/304902-show-and-tell/topic/Roc.20on.20a.20microcontroller/near/286678630)), or even [implementing servo logic for a clock that physically turns panels to simulate an LCD](https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/Roc.20Clock/near/327939600). You really can get as specific as you like!
Platforms can also be designed to have a single, specific application run on them. For example, you can make a platform that is essentially "your entire existing code base in another language," and then use Roc as an embedded language within that code base. For example, [Vendr](https://www.vendr.com/careers) is using this strategy to call Roc functions from their [Node.js](https://nodejs.org/en) backend using [roc-esbuild](https://github.com/vendrinc/roc-esbuild), as a way to incrementally transition code from Node to Roc. Platforms can also be designed to have a single, specific application run on them. For example, you can make a platform that is essentially "your entire existing code base in another language," and then use Roc as an embedded language within that code base. For example, [Vendr](https://www.vendr.com/careers) is using this strategy to call Roc functions from their [Node.js](https://nodejs.org/en) backend using [roc-esbuild](https://github.com/vendrinc/roc-esbuild), as a way to incrementally transition code from Node to Roc.
## [Platform scope](#scope) {#scope} ## [Platform scope](#scope) {#scope}
Roc platforms have a broader scope of responsibility than game engines or Web frameworks. In addition to providing a nice domain-specific interface, platforms are also responsible for: Roc platforms have a broader scope of responsibility than game engines or Web frameworks. In addition to providing a nice domain-specific interface, platforms are also responsible for:
* Tailoring memory management to that domain (more on this later)
* Providing all I/O primitives - Tailoring memory management to that domain (more on this later)
- Providing all I/O primitives
In most languages, I/O primitives come with the standard library. In Roc, the [standard library](https://www.roc-lang.org/builtins/) contains only data structures; an application gets all of its I/O primitives from its platform. For example, in the "Hello, World" application above, the `Stdout.line` function comes from the `basic-cli` platform itself, not from Roc's standard library. In most languages, I/O primitives come with the standard library. In Roc, the [standard library](https://www.roc-lang.org/builtins/) contains only data structures; an application gets all of its I/O primitives from its platform. For example, in the "Hello, World" application above, the `Stdout.line` function comes from the `basic-cli` platform itself, not from Roc's standard library.
@ -48,7 +49,7 @@ This design has a few benefits.
Some I/O operations make sense in some use cases but not others. Some I/O operations make sense in some use cases but not others.
For example, suppose I'm building an application on a platform for command-line interfaces, and I use a third-party package which sometimes blocks the program while it waits for [standard input](https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)). This might be fine for my command-line application, but it would probably be a very bad fit if I'm using a webserver. Similarly, a package which does some occasional file I/O for caching might work fine on either of those platforms, but might break in surprising ways when used in a platform that's designed to run in a browser on WebAssembly—since browsers don't offer arbitrary file I/O access! For example, suppose I'm building an application on a platform for command-line interfaces, and I use a third-party package which sometimes blocks the program while it waits for [standard input](<https://en.wikipedia.org/wiki/Standard_streams#Standard_input_(stdin)>). This might be fine for my command-line application, but it would probably be a very bad fit if I'm using a webserver. Similarly, a package which does some occasional file I/O for caching might work fine on either of those platforms, but might break in surprising ways when used in a platform that's designed to run in a browser on WebAssembly—since browsers don't offer arbitrary file I/O access!
Because Roc's I/O primitives come from platforms, these mismatches can be prevented at build time. The browser-based platform would not expose file I/O primitives, the webserver wouldn't expose a way to block on reading from standard input, and so on. (Note that there's a design in the works for allowing packages which perform I/O to work across multiple platforms—but only platforms which support the I/O primitives it requires—but this design has not yet been implemented.) Because Roc's I/O primitives come from platforms, these mismatches can be prevented at build time. The browser-based platform would not expose file I/O primitives, the webserver wouldn't expose a way to block on reading from standard input, and so on. (Note that there's a design in the works for allowing packages which perform I/O to work across multiple platforms—but only platforms which support the I/O primitives it requires—but this design has not yet been implemented.)
@ -58,7 +59,7 @@ Since platforms have exclusive control over all I/O primitives, one of the thing
[This talk](https://www.youtube.com/watch?v=cpQwtwVKAfU&t=75s) shows an example of taking this idea a step further with a "safe scripting" platform for writing command-line scripts. The idea is that you could download a script from the Internet and run it on this platform without worrying that the script would do bad things to your computer, because the platform would (much like a Web browser) show you specific prompts before allowing the script to do potentially harmful I/O, such as filesystem operations. [This talk](https://www.youtube.com/watch?v=cpQwtwVKAfU&t=75s) shows an example of taking this idea a step further with a "safe scripting" platform for writing command-line scripts. The idea is that you could download a script from the Internet and run it on this platform without worrying that the script would do bad things to your computer, because the platform would (much like a Web browser) show you specific prompts before allowing the script to do potentially harmful I/O, such as filesystem operations.
These security guarantees can be relied on because platforms have *exclusive* control over all I/O primitives, including how they are implemented. There are no escape hatches that a malicious program could use to get around these, either; for example, Roc programs that want to call functions in other languages must do so using primitives provided by the platform, which the platform can disallow (or sandbox with end-user prompts) in the same way. These security guarantees can be relied on because platforms have _exclusive_ control over all I/O primitives, including how they are implemented. There are no escape hatches that a malicious program could use to get around these, either; for example, Roc programs that want to call functions in other languages must do so using primitives provided by the platform, which the platform can disallow (or sandbox with end-user prompts) in the same way.
### [Performance benefits](#performance) {#performance} ### [Performance benefits](#performance) {#performance}
@ -75,8 +76,9 @@ To understand how platforms can tailor automatic memory management to their part
### [The Host and the Roc API](#host-and-roc-api) {#host-and-roc-api} ### [The Host and the Roc API](#host-and-roc-api) {#host-and-roc-api}
Each platform consists of two parts: Each platform consists of two parts:
* **The Roc API** is the part that application authors see. For example, `Stdout.line` is part of basic-cli's Roc API.
* **The Host** is the under-the-hood implementation written in a language other than Roc. For example, basic-cli's host is written in Rust. It has a Rust function which implements the behavior of the `Stdout.line` operation, and all the other I/O operations it supports. - **The Roc API** is the part that application authors see. For example, `Stdout.line` is part of basic-cli's Roc API.
- **The Host** is the under-the-hood implementation written in a language other than Roc. For example, basic-cli's host is written in Rust. It has a Rust function which implements the behavior of the `Stdout.line` operation, and all the other I/O operations it supports.
This design means that application authors don't necessarily need to know (or care) about the non-Roc language being used to implement the platform's host. That can be a behind-the-scenes implementation detail that only the platform's author(s) are concerned with. Application authors only interact with the public-facing Roc API. This design means that application authors don't necessarily need to know (or care) about the non-Roc language being used to implement the platform's host. That can be a behind-the-scenes implementation detail that only the platform's author(s) are concerned with. Application authors only interact with the public-facing Roc API.
@ -97,6 +99,7 @@ When a compiled Roc program runs, it's actually the host—not the Roc applicati
Knowing this, a useful mental model for how Roc platforms and applications interact at the implementation level is: the Roc application compiles down to a C library which the platform can choose to call (or not). Knowing this, a useful mental model for how Roc platforms and applications interact at the implementation level is: the Roc application compiles down to a C library which the platform can choose to call (or not).
This is essentially what's happening behind the scenes when you run `roc build`. Specifically: This is essentially what's happening behind the scenes when you run `roc build`. Specifically:
1. The Roc compiler builds the Roc application into a binary [object file](https://en.wikipedia.org/wiki/Object_file) 1. The Roc compiler builds the Roc application into a binary [object file](https://en.wikipedia.org/wiki/Object_file)
2. Since that application specified its platform, the compiler then looks up the platform's host implementation (which the platform will have provided as an already-compiled binary) 2. Since that application specified its platform, the compiler then looks up the platform's host implementation (which the platform will have provided as an already-compiled binary)
3. Now that it has a binary for the Roc application and a binary for the host, it links them together into one combined binary in which the host portion calls the application portion as many times as it likes. 3. Now that it has a binary for the Roc application and a binary for the host, it links them together into one combined binary in which the host portion calls the application portion as many times as it likes.
@ -109,6 +112,6 @@ Every Roc application has exactly one platform. That platform provides all the I
This I/O design has [security benefits](#security), [ecosystem benefits](#ecosystem), and [performance benefits](#performance). The [domain-specific memory management](#memory) platforms can implement can offer additional benefits as well. This I/O design has [security benefits](#security), [ecosystem benefits](#ecosystem), and [performance benefits](#performance). The [domain-specific memory management](#memory) platforms can implement can offer additional benefits as well.
Applications only interact with the *Roc API* portion of a platform, but there is also a *host* portion (written in a different language) that works behind the scenes. The host determines how the program starts, how memory is allocated and deallocated, and how I/O primitives are implemented. Applications only interact with the _Roc API_ portion of a platform, but there is also a _host_ portion (written in a different language) that works behind the scenes. The host determines how the program starts, how memory is allocated and deallocated, and how I/O primitives are implemented.
Anyone can implement their own platform. There isn't yet an official guide about how to do this, so the best way to get help if you'd like to create a platform is to [say hi in the `#beginners` channel](https://roc.zulipchat.com/#narrow/stream/231634-beginners) on [Roc Zulip!](https://roc.zulipchat.com) Anyone can implement their own platform. There isn't yet an official guide about how to do this, so the best way to get help if you'd like to create a platform is to [say hi in the `#beginners` channel](https://roc.zulipchat.com/#narrow/stream/231634-beginners) on [Roc Zulip!](https://roc.zulipchat.com)

View file

@ -164,7 +164,7 @@ Make a file named `main.roc` and put this in it:
```roc ```roc
app "hello" app "hello"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf
@ -207,9 +207,9 @@ You should see this:
A definition names an expression. A definition names an expression.
- The first two defs assign the names `birds` and `iguanas` to the expressions `3` and `2`. - The first two defs assign the names `birds` and `iguanas` to the expressions `3` and `2`.
- The next def assigns the name `total` to the expression `Num.toStr (birds + iguanas)`. - The next def assigns the name `total` to the expression `Num.toStr (birds + iguanas)`.
- The last def assigns the name `main` to an expression which returns a `Task`. We'll [discuss tasks later](#tasks). - The last def assigns the name `main` to an expression which returns a `Task`. We'll [discuss tasks later](#tasks).
Once we have a def, we can use its name in other expressions. For example, the `total` expression refers to `birds` and `iguanas`, and `Stdout.line "There are $(total) animals."` refers to `total`. Once we have a def, we can use its name in other expressions. For example, the `total` expression refers to `birds` and `iguanas`, and `Stdout.line "There are $(total) animals."` refers to `total`.
@ -260,8 +260,8 @@ addAndStringify = \num1, num2 ->
We did two things here: We did two things here:
- We introduced a _local def_ named `sum`, and set it equal to `num1 + num2`. Because we defined `sum` inside `addAndStringify`, it's _local_ to that scope and can't be accessed outside that function. - We introduced a _local def_ named `sum`, and set it equal to `num1 + num2`. Because we defined `sum` inside `addAndStringify`, it's _local_ to that scope and can't be accessed outside that function.
- We added an `if`\-`then`\-`else` conditional to return either `""` or `Num.toStr sum` depending on whether `sum == 0`. - We added an `if`\-`then`\-`else` conditional to return either `""` or `Num.toStr sum` depending on whether `sum == 0`.
Every `if` must be accompanied by both `then` and also `else`. Having an `if` without an `else` is an error, because `if` is an expression, and all expressions must evaluate to a value. If there were ever an `if` without an `else`, that would be an expression that might not evaluate to a value! Every `if` must be accompanied by both `then` and also `else`. Having an `if` without an `else` is an error, because `if` is an expression, and all expressions must evaluate to a value. If there were ever an `if` without an `else`, that would be an expression that might not evaluate to a value!
@ -367,7 +367,7 @@ addAndStringify = \counts ->
Num.toStr (counts.birds + counts.iguanas) Num.toStr (counts.birds + counts.iguanas)
``` ```
The function now takes a _record_, which is a group of named values. Records are not [objects](https://en.wikipedia.org/wiki/Object_(computer_science)); they don't have methods or inheritance, they just store information. The function now takes a _record_, which is a group of named values. Records are not [objects](<https://en.wikipedia.org/wiki/Object_(computer_science)>); they don't have methods or inheritance, they just store information.
The expression `{ birds: 5, iguanas: 7 }` defines a record with two _fields_ (the `birds` field and the `iguanas` field) and then assigns the number `5` to the `birds` field and the number `7` to the `iguanas` field. Order doesn't matter with record fields; we could have also specified `iguanas` first and `birds` second, and Roc would consider it the exact same record. The expression `{ birds: 5, iguanas: 7 }` defines a record with two _fields_ (the `birds` field and the `iguanas` field) and then assigns the number `5` to the `birds` field and the number `7` to the `iguanas` field. Order doesn't matter with record fields; we could have also specified `iguanas` first and `birds` second, and Roc would consider it the exact same record.
@ -411,10 +411,10 @@ returnFoo { foo: "hi!", bar: "blah" }
Sometimes we assign a def to a field that happens to have the same name—for example, `{ x: x }`. Sometimes we assign a def to a field that happens to have the same name—for example, `{ x: x }`.
In these cases, we shorten it to writing the name of the def alone—for example, `{ x }`. We can do this with as many fields as we like; here are several different ways to define the same record: In these cases, we shorten it to writing the name of the def alone—for example, `{ x }`. We can do this with as many fields as we like; here are several different ways to define the same record:
- `{ x: x, y: y }` - `{ x: x, y: y }`
- `{ x, y }` - `{ x, y }`
- `{ x: x, y }` - `{ x: x, y }`
- `{ x, y: y }` - `{ x, y: y }`
### [Record destructuring](#record-destructuring) {#record-destructuring} ### [Record destructuring](#record-destructuring) {#record-destructuring}
@ -452,8 +452,8 @@ fromOriginal = { original & birds: 4, iguanas: 3 }
The `fromScratch` and `fromOriginal` records are equal, although they're defined in different ways. The `fromScratch` and `fromOriginal` records are equal, although they're defined in different ways.
- `fromScratch` was built using the same record syntax we've been using up to this point. - `fromScratch` was built using the same record syntax we've been using up to this point.
- `fromOriginal` created a new record using the contents of `original` as defaults for fields that it didn't specify after the `&`. - `fromOriginal` created a new record using the contents of `original` as defaults for fields that it didn't specify after the `&`.
Note that `&` can't introduce new fields to a record, or change the types of existing fields. Note that `&` can't introduce new fields to a record, or change the types of existing fields.
(Trying to do either of these will result in an error at build time!) (Trying to do either of these will result in an error at build time!)
@ -474,7 +474,7 @@ table = \{
-> ->
``` ```
This is using *optional field destructuring* to destructure a record while This is using _optional field destructuring_ to destructure a record while
also providing default values for any fields that might be missing. also providing default values for any fields that might be missing.
Here's the type of `table`: Here's the type of `table`:
@ -490,8 +490,8 @@ table :
-> Table -> Table
``` ```
This says that `table` takes a record with two *required* fields, `height` and This says that `table` takes a record with two _required_ fields, `height` and
`width`, and two *optional* fields, `title` and `description`. It also says that `width`, and two _optional_ fields, `title` and `description`. It also says that
the `height` and `width` fields have the type `Pixels`, a type alias for some the `height` and `width` fields have the type `Pixels`, a type alias for some
numeric type, and the `title` and `description` fields have the type `Str`. numeric type, and the `title` and `description` fields have the type `Str`.
This means you can choose to omit the `title`, `description`, or both fields, when calling the function... but if you provide them, they must have the type `Str`. This means you can choose to omit the `title`, `description`, or both fields, when calling the function... but if you provide them, they must have the type `Str`.
@ -504,7 +504,7 @@ These default values can reference other expressions in the record destructure;
Destructuring is the only way to implement a record with optional fields. For example, if you write the expression `config.title` and `title` is an Destructuring is the only way to implement a record with optional fields. For example, if you write the expression `config.title` and `title` is an
optional field, you'll get a compile error. optional field, you'll get a compile error.
This means it's never possible to end up with an *optional value* that exists This means it's never possible to end up with an _optional value_ that exists
outside a record field. Optionality is a concept that exists only in record outside a record field. Optionality is a concept that exists only in record
fields, and it's intended for the use case of config records like this. The fields, and it's intended for the use case of config records like this. The
ergonomics of destructuring mean this wouldn't be a good fit for data modeling, consider using a `Result` type instead. ergonomics of destructuring mean this wouldn't be a good fit for data modeling, consider using a `Result` type instead.
@ -861,6 +861,7 @@ These functions demonstrate a common pattern in Roc: operations that can fail re
Result.withDefault (List.get ["a", "b", "c"] 100) "" Result.withDefault (List.get ["a", "b", "c"] 100) ""
# returns "" because that's the default we said to use if List.get returned an Err # returns "" because that's the default we said to use if List.get returned an Err
``` ```
```roc ```roc
Result.isOk (List.get ["a", "b", "c"] 1) Result.isOk (List.get ["a", "b", "c"] 1)
# returns `Bool.true` because `List.get` returned an `Ok` tag. (The payload gets ignored.) # returns `Bool.true` because `List.get` returned an `Ok` tag. (The payload gets ignored.)
@ -875,9 +876,9 @@ that quite does what you want, and you might find yourself calling `List.get` re
retrieve every element in the list and use it to build up the new value you want. That approach retrieve every element in the list and use it to build up the new value you want. That approach
can work, but it has a few downsides: can work, but it has a few downsides:
* Each `List.get` call returns a `Result` that must be dealt with, even though you plan to use every element in the list anyway - Each `List.get` call returns a `Result` that must be dealt with, even though you plan to use every element in the list anyway
* There's a runtime performance overhead associated with each of these `Result`s, which you won't find in other "look at every element in the list" operations like `List.keepIf`. - There's a runtime performance overhead associated with each of these `Result`s, which you won't find in other "look at every element in the list" operations like `List.keepIf`.
* It's more verbose than the alternative we're about to discuss - It's more verbose than the alternative we're about to discuss
The `List.walk` function gives you a way to walk over the elements in a list and build up whatever The `List.walk` function gives you a way to walk over the elements in a list and build up whatever
return value you like. It's a great alternative to calling `List.get` on every element in the list return value you like. It's a great alternative to calling `List.get` on every element in the list
@ -905,13 +906,13 @@ In this example, we walk over the list `[1, 2, 3, 4, 5]` and add each element to
It then proceeds to walk over each element in the list and call that function. Each time, the state that function returns becomes the argument to the next function call. Here are the arguments the function will receive, and what it will return, as `List.walk` walks over the list `[1, 2, 3, 4, 5]`: It then proceeds to walk over each element in the list and call that function. Each time, the state that function returns becomes the argument to the next function call. Here are the arguments the function will receive, and what it will return, as `List.walk` walks over the list `[1, 2, 3, 4, 5]`:
| State | Element | Return Value | | State | Element | Return Value |
| --------------------------------- | ------- | ------------------------------------ | | --------------------------------- | ------- | ------------------------------------ |
| `{ evens: [], odds: [] }` | `1` | `{ evens: [], odds: [1] }` | | `{ evens: [], odds: [] }` | `1` | `{ evens: [], odds: [1] }` |
| `{ evens: [], odds: [1] }` | `2` | `{ evens: [2], odds: [1] }` | | `{ evens: [], odds: [1] }` | `2` | `{ evens: [2], odds: [1] }` |
| `{ evens: [2], odds: [1] }` | `3` | `{ evens: [2], odds: [1, 3] }` | | `{ evens: [2], odds: [1] }` | `3` | `{ evens: [2], odds: [1, 3] }` |
| `{ evens: [2], odds: [1, 3] }` | `4` | `{ evens: [2, 4], odds: [1, 3] }` | | `{ evens: [2], odds: [1, 3] }` | `4` | `{ evens: [2, 4], odds: [1, 3] }` |
| `{ evens: [2, 4], odds: [1, 3] }` | `5` | `{ evens: [2, 4], odds: [1, 3, 5] }` | | `{ evens: [2, 4], odds: [1, 3] }` | `5` | `{ evens: [2, 4], odds: [1, 3, 5] }` |
Note that the initial `state` argument is `{ evens: [], odds: [] }` because that's the argument Note that the initial `state` argument is `{ evens: [], odds: [] }` because that's the argument
we passed `List.walk` for its initial state. From then on, each `state` argument is whatever the we passed `List.walk` for its initial state. From then on, each `state` argument is whatever the
@ -932,6 +933,7 @@ When you have nested function calls, sometimes it can be clearer to write them i
```roc ```roc
Result.withDefault (List.get ["a", "b", "c"] 1) "" Result.withDefault (List.get ["a", "b", "c"] 1) ""
``` ```
```roc ```roc
List.get ["a", "b", "c"] 1 List.get ["a", "b", "c"] 1
|> Result.withDefault "" |> Result.withDefault ""
@ -954,6 +956,7 @@ One reason the `|>` operator injects the value as the first argument is to make
```roc ```roc
List.append ["a", "b", "c"] "d" List.append ["a", "b", "c"] "d"
``` ```
```roc ```roc
["a", "b", "c"] ["a", "b", "c"]
|> List.append "d" |> List.append "d"
@ -964,9 +967,11 @@ Another example is `Num.div`. All three of the following do the same thing, beca
```roc ```roc
first / second first / second
``` ```
```roc ```roc
Num.div first second Num.div first second
``` ```
```roc ```roc
first |> Num.div second first |> Num.div second
``` ```
@ -1154,9 +1159,10 @@ Here, Roc's compiler will infer that `color`'s type is `[Red, Yellow, Green]`, b
### [Opaque Types](#opaque-types) {#opaque-types} ### [Opaque Types](#opaque-types) {#opaque-types}
A type can be defined to be opaque to hide its internal structure. This is a lot more amazing than it may seem. It can make your code more modular, robust, and easier to read: A type can be defined to be opaque to hide its internal structure. This is a lot more amazing than it may seem. It can make your code more modular, robust, and easier to read:
- If a type is opaque you can modify its internal structure and be certain that no dependencies need to be updated.
- You can prevent that data needs to be checked multiple times. For example, you can create an opaque `NonEmptyList` from a `List` after you've checked it. Now all functions that you pass this `NonEmptyList` to do not need to handle the empty list case. - If a type is opaque you can modify its internal structure and be certain that no dependencies need to be updated.
- Having the type `Username` in a type signature gives you more context compared to `Str`. Even if the `Username` is an opaque type for `Str`. - You can prevent that data needs to be checked multiple times. For example, you can create an opaque `NonEmptyList` from a `List` after you've checked it. Now all functions that you pass this `NonEmptyList` to do not need to handle the empty list case.
- Having the type `Username` in a type signature gives you more context compared to `Str`. Even if the `Username` is an opaque type for `Str`.
You can create an opaque type with the `:=` operator. Let's make one called `Username`: You can create an opaque type with the `:=` operator. Let's make one called `Username`:
@ -1198,24 +1204,24 @@ Following this pattern, the 16 in `I16` means that it's a signed 16-bit integer.
Choosing a size depends on your performance needs and the range of numbers you want to represent. Consider: Choosing a size depends on your performance needs and the range of numbers you want to represent. Consider:
- Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them! - Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them!
- Smaller integer sizes take up less memory. These savings rarely matter in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can easily be a performance bottleneck. - Smaller integer sizes take up less memory. These savings rarely matter in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can easily be a performance bottleneck.
- Certain processors work faster on some numeric sizes than others. There isn't even a general rule like "larger numeric sizes run slower" (or the reverse, for that matter) that applies to all processors. In fact, if the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly! - Certain processors work faster on some numeric sizes than others. There isn't even a general rule like "larger numeric sizes run slower" (or the reverse, for that matter) that applies to all processors. In fact, if the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly!
Here are the different fixed-size integer types that Roc supports: Here are the different fixed-size integer types that Roc supports:
| Range | Type | | Range | Type |
|-------------------------------------------------------------------------------------------------------------------|--------| | ----------------------------------------------------------------------------------------------------------------- | ------ |
| `-128` <br> `127` | `I8` | | `-128` <br> `127` | `I8` |
| `0` <br> `255` | `U8` | | `0` <br> `255` | `U8` |
| `-32_768` <br> `32_767` | `I16` | | `-32_768` <br> `32_767` | `I16` |
| `0` <br> `65_535` | `U16` | | `0` <br> `65_535` | `U16` |
| `-2_147_483_648` <br> `2_147_483_647` | `I32` | | `-2_147_483_648` <br> `2_147_483_647` | `I32` |
| `0` <br> (over 4 billion) `4_294_967_295` | `U32` | | `0` <br> (over 4 billion) `4_294_967_295` | `U32` |
| `-9_223_372_036_854_775_808` <br> `9_223_372_036_854_775_807` | `I64` | | `-9_223_372_036_854_775_808` <br> `9_223_372_036_854_775_807` | `I64` |
| `0` <br> _(over 18 quintillion)_`18_446_744_073_709_551_615` | `U64` | | `0` <br> _(over 18 quintillion)_`18_446_744_073_709_551_615` | `U64` |
| `-170_141_183_460_469_231_731_687_303_715_884_105_728` <br> `170_141_183_460_469_231_731_687_303_715_884_105_727` | `I128` | | `-170_141_183_460_469_231_731_687_303_715_884_105_728` <br> `170_141_183_460_469_231_731_687_303_715_884_105_727` | `I128` |
| `0` <br> _(over 340 undecillion)_`340_282_366_920_938_463_463_374_607_431_768_211_455` | `U128` | | `0` <br> _(over 340 undecillion)_`340_282_366_920_938_463_463_374_607_431_768_211_455` | `U128` |
Roc also has one variable-size integer type: `Nat` (short for "natural number"). The size of `Nat` is equal to the size of a memory address, which varies by system. For example, when compiling for a 64-bit system, `Nat` works the same way as `U64`. When compiling for a 32-bit system, it works the same way as `U32`. Most popular computing devices today are 64-bit, so `Nat` is usually the same as `U64`, but Web Assembly is typically 32-bit - so when running a Roc program built for Web Assembly, `Nat` will work like a `U32` in that program. Roc also has one variable-size integer type: `Nat` (short for "natural number"). The size of `Nat` is equal to the size of a memory address, which varies by system. For example, when compiling for a 64-bit system, `Nat` works the same way as `U64`. When compiling for a 32-bit system, it works the same way as `U32`. Most popular computing devices today are 64-bit, so `Nat` is usually the same as `U64`, but Web Assembly is typically 32-bit - so when running a Roc program built for Web Assembly, `Nat` will work like a `U32` in that program.
@ -1229,9 +1235,9 @@ As such, it's very important to design your integer operations not to exceed the
Roc has three fractional types: Roc has three fractional types:
- `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) - `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
- `F64`, a 64-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) - `F64`, a 64-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
- `Dec`, a 128-bit decimal [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) - `Dec`, a 128-bit decimal [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
These are different from integers, they can represent numbers with fractional components, such as 1.5 and -0.123. These are different from integers, they can represent numbers with fractional components, such as 1.5 and -0.123.
@ -1260,9 +1266,11 @@ There's also an `Int` type which is only compatible with integers, and a `Frac`
```roc ```roc
Num.xor : Int a, Int a -> Int a Num.xor : Int a, Int a -> Int a
``` ```
```roc ```roc
Num.cos : Frac a -> Frac a Num.cos : Frac a -> Frac a
``` ```
When you write a number literal in Roc, it has the type `Num *`. So you could call `Num.xor 1 1` and also `Num.cos 1` and have them all work as expected; the number literal `1` has the type `Num *`, which is compatible with the more constrained types `Int` and `Frac`. For the same reason, you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`. When you write a number literal in Roc, it has the type `Num *`. So you could call `Num.xor 1 1` and also `Num.cos 1` and have them all work as expected; the number literal `1` has the type `Num *`, which is compatible with the more constrained types `Int` and `Frac`. For the same reason, you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`.
### [Number Literals](#number-literals) {#number-literals} ### [Number Literals](#number-literals) {#number-literals}
@ -1378,12 +1386,12 @@ So you'll want to use `roc dev` or `roc test` to get the output for `expect`.
Each `.roc` file is a separate module and contains Roc code for different purposes. Here are all of the different types of modules that Roc supports; Each `.roc` file is a separate module and contains Roc code for different purposes. Here are all of the different types of modules that Roc supports;
- **Builtins** provide functions that are automatically imported into every module. - **Builtins** provide functions that are automatically imported into every module.
- **Applications** are combined with a platform and compiled into an executable. - **Applications** are combined with a platform and compiled into an executable.
- **Interfaces** provide functions which can be imported into other modules. - **Interfaces** provide functions which can be imported into other modules.
- **Packages** organise modules to share functionality across applications and platforms. - **Packages** organise modules to share functionality across applications and platforms.
- **Platforms** provide effects such as IO to interface with the outside world. - **Platforms** provide effects such as IO to interface with the outside world.
- **Hosted** *note this module type is likely to be deprecated soon*. - **Hosted** _note this module type is likely to be deprecated soon_.
### [Builtin Modules](#builtin-modules) {#builtin-modules} ### [Builtin Modules](#builtin-modules) {#builtin-modules}
@ -1403,8 +1411,8 @@ These modules are not ordinary `.roc` files that live on your filesystem. Rather
Besides being built into the compiler, the builtin modules are different from other modules in that: Besides being built into the compiler, the builtin modules are different from other modules in that:
- They are always imported. You never need to add them to `imports`. - They are always imported. You never need to add them to `imports`.
- All their types are imported unqualified automatically. So you never need to write `Num.Nat`, because it's as if the `Num` module was imported using `imports [Num.{ Nat }]` (the same is true for all the other types in the `Num` module). - All their types are imported unqualified automatically. So you never need to write `Num.Nat`, because it's as if the `Num` module was imported using `imports [Num.{ Nat }]` (the same is true for all the other types in the `Num` module).
### [App Module Header](#app-module-header) {#app-module-header} ### [App Module Header](#app-module-header) {#app-module-header}
@ -1412,7 +1420,7 @@ Let's take a closer look at the part of `main.roc` above the `main` def:
```roc ```roc
app "hello" app "hello"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf
``` ```
@ -1424,16 +1432,16 @@ 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: The remaining lines all involve the [platform](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform) this application is built on:
```roc ```roc
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf
``` ```
The `packages { pf: "https://...tar.br" }` part says three things: The `packages { pf: "https://...tar.br" }` part says three things:
- We're going to be using a _package_ (a collection of modules) that can be downloaded from the URL `"https://...tar.br"` - We're going to be using a _package_ (a collection of modules) that can be downloaded from the URL `"https://...tar.br"`
- That package's [base64](https://en.wikipedia.org/wiki/Base64#URL_applications)\-encoded [BLAKE3](<https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE3>) cryptographic hash is the long string at the end (before the `.tar.br` file extension). Once the file has been downloaded, its contents will be verified against this hash, and it will only be installed if they match. This way, you can be confident the download was neither corrupted nor changed since it was originally published. - That package's [base64](https://en.wikipedia.org/wiki/Base64#URL_applications)\-encoded [BLAKE3](<https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE3>) cryptographic hash is the long string at the end (before the `.tar.br` file extension). Once the file has been downloaded, its contents will be verified against this hash, and it will only be installed if they match. This way, you can be confident the download was neither corrupted nor changed since it was originally published.
- We're going to name that package `pf` so we can refer to it more concisely in the future. - We're going to name that package `pf` so we can refer to it more concisely in the future.
The `imports [pf.Stdout]` line says that we want to import the `Stdout` module from the `pf` package, and make it available in the current module. The `imports [pf.Stdout]` line says that we want to import the `Stdout` module from the `pf` package, and make it available in the current module.
@ -1512,10 +1520,10 @@ Tasks are technically not part of the Roc language, but they're very common in p
In the `basic-cli` platform, we have four operations we can do: In the `basic-cli` platform, we have four operations we can do:
- Write a string to the terminal - Write a string to the terminal
- Read a string from user input - Read a string from user input
- Write a string to a file - Write a string to a file
- Read a string from a file - Read a string from a file
We'll use these four operations to learn about tasks. We'll use these four operations to learn about tasks.
@ -1523,7 +1531,7 @@ Let's start with a basic "Hello World" program.
```roc ```roc
app "cli-tutorial" app "cli-tutorial"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf
@ -1557,7 +1565,7 @@ Let's change `main` to read a line from `stdin`, and then print what we got:
```roc ```roc
app "cli-tutorial" app "cli-tutorial"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout, pf.Stdin, pf.Task] imports [pf.Stdout, pf.Stdin, pf.Task]
provides [main] to pf provides [main] to pf
@ -1602,7 +1610,7 @@ This works, but we can make it a little nicer to read. Let's change it to the fo
```roc ```roc
app "cli-tutorial" app "cli-tutorial"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.7.1/Icc3xJoIixF3hCcfXrDwLCu4wQHtNdPyoJkEbkgIElA.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.8.1/x8URkvfyi9I0QhmVG98roKBUs_AZRkLFwFJVJ3942YA.tar.br" }
imports [pf.Stdout, pf.Stdin, pf.Task.{ await }] imports [pf.Stdout, pf.Stdin, pf.Task.{ await }]
provides [main] to pf provides [main] to pf
@ -1704,9 +1712,9 @@ This way, it reads like a series of instructions:
Some important things to note about backpassing and `await`: 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.) - `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. - 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 `\input ->` and `input <-` 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. 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.
@ -1733,9 +1741,9 @@ fullName = \user ->
I can pass this function a record that has more fields than just `firstName` and `lastName`, as long as it has _at least_ both of those fields (and both of them are strings). So any of these calls would work: I can pass this function a record that has more fields than just `firstName` and `lastName`, as long as it has _at least_ both of those fields (and both of them are strings). So any of these calls would work:
- `fullName { firstName: "Sam", lastName: "Sample" }` - `fullName { firstName: "Sam", lastName: "Sample" }`
- `fullName { firstName: "Sam", lastName: "Sample", email: "blah@example.com" }` - `fullName { firstName: "Sam", lastName: "Sample", email: "blah@example.com" }`
- `fullName { age: 5, firstName: "Sam", things: 3, lastName: "Sample", role: Admin }` - `fullName { age: 5, firstName: "Sam", things: 3, lastName: "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. 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.
@ -1775,9 +1783,9 @@ addHttps = \record ->
This function uses _constrained records_ in its type. The annotation is saying: This function uses _constrained records_ in its type. The annotation is saying:
- This function takes a record which has at least a `url` field, and possibly others - This function takes a record which has at least a `url` field, and possibly others
- That `url` field has the type `Str` - That `url` field has the type `Str`
- It returns a record of exactly the same type as the one it was given - It returns a record of exactly the same type as the one it was given
So if we give this function a record with five fields, it will return a record with those same five fields. The only requirement is that one of those fields must be `url: Str`. So if we give this function a record with five fields, it will return a record with those same five fields. The only requirement is that one of those fields must be `url: Str`.
@ -1785,9 +1793,9 @@ In practice, constrained records appear in type annotations much less often than
Here's when you can typically expect to encounter these three flavors of type variables in records: Here's when you can typically expect to encounter these three flavors of type variables in records:
- _Open records_ are what the compiler infers when you use a record as an argument, or when destructuring it (for example, `{ x, y } =`). - _Open records_ are what the compiler infers when you use a record as an argument, or when destructuring it (for example, `{ x, y } =`).
- _Closed records_ are what the compiler infers when you create a new record (for example, `{ x: 5, y: 6 }`) - _Closed records_ are what the compiler infers when you create a new record (for example, `{ x: 5, y: 6 }`)
- _Constrained records_ are what the compiler infers when you do a record update (for example, `{ user & email: newEmail }`) - _Constrained records_ are what the compiler infers when you do a record update (for example, `{ user & email: newEmail }`)
Of note, you can pass a closed record to a function that accepts a smaller open record, but not the reverse. So a function `{ a : Str, b : Bool }* -> Str` can accept an `{ a : Str, b : Bool, c : Bool }` record, but a function `{ a : Str, b : Bool, c : Bool } -> Str` would not accept an `{ a : Str, b : Bool }*` record. Of note, you can pass a closed record to a function that accepts a smaller open record, but not the reverse. So a function `{ a : Str, b : Bool }* -> Str` can accept an `{ a : Str, b : Bool, c : Bool }` record, but a function `{ a : Str, b : Bool, c : Bool } -> Str` would not accept an `{ a : Str, b : Bool }*` record.
@ -1946,16 +1954,16 @@ Earlier we saw how a function which accepts an open union must account for more
So if I have an `[Ok Str]*` value, I can pass it to functions with any of these types (among others): So if I have an `[Ok Str]*` value, I can pass it to functions with any of these types (among others):
| Function Type | Can it receive `[Ok Str]*`? | | Function Type | Can it receive `[Ok Str]*`? |
| --------------------------------------- | --------------------------- | | --------------------------------------- | --------------------------- |
| `[Ok Str]* -> Bool` | Yes | | `[Ok Str]* -> Bool` | Yes |
| `[Ok Str] -> Bool` | Yes | | `[Ok Str] -> Bool` | Yes |
| `[Ok Str, Err Bool]* -> Bool` | Yes | | `[Ok Str, Err Bool]* -> Bool` | Yes |
| `[Ok Str, Err Bool] -> Bool` | Yes | | `[Ok Str, Err Bool] -> Bool` | Yes |
| `[Ok Str, Err Bool, Whatever]* -> Bool` | Yes | | `[Ok Str, Err Bool, Whatever]* -> 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 | | `[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. 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.
@ -1967,10 +1975,10 @@ However, I could not pass an `[Ok Str]*` to a function with a _closed_ tag union
In summary, here's a way to think about the difference between open unions in a value you have, compared to a value you're accepting: In summary, here's a way to think about the difference between open unions in a value you have, compared to a value you're accepting:
- If you _have_ a closed union, that means it has all the tags it ever will, and can't accumulate more. - If you _have_ a closed union, that means it has all the tags it ever will, and can't accumulate more.
- If you _have_ an open union, that means it can accumulate more tags through conditional branches. - If you _have_ an open union, that means it can accumulate more tags through conditional branches.
- If you _accept_ a closed union, that means you only have to handle the possibilities listed in the union. - If you _accept_ a closed union, that means you only have to handle the possibilities listed in the union.
- If you _accept_ an open union, that means you have to handle the possibility that it has a tag you can't know about. - If you _accept_ an open union, that means you have to handle the possibility that it has a tag you can't know about.
### [Type Variables in Tag Unions](#type-variables-in-tag-unions) {#type-variables-in-tag-unions} ### [Type Variables in Tag Unions](#type-variables-in-tag-unions) {#type-variables-in-tag-unions}
@ -2010,8 +2018,8 @@ So if we give this function a `[Foo Str, Bar Bool, Baz (List Str)]` argument, th
If we removed the type annotation from `example` above, Roc's compiler would infer the same type anyway. This may be surprising if you look closely at the body of the function, because: If we removed the type annotation from `example` above, Roc's compiler would infer the same type anyway. This may be surprising if you look closely at the body of the function, because:
- The return type includes `Foo Str`, but no branch explicitly returns `Foo`. Couldn't the return type be `[Bar Bool]a` instead? - The return type includes `Foo Str`, but no branch explicitly returns `Foo`. Couldn't the return type be `[Bar Bool]a` instead?
- The argument type includes `Bar Bool` even though we never look at `Bar`'s payload. Couldn't the argument type be inferred to be `Bar *` instead of `Bar Bool`, since we never look at it? - The argument type includes `Bar Bool` even though we never look at `Bar`'s payload. Couldn't the argument type be inferred to be `Bar *` instead of `Bar Bool`, since we never look at it?
The reason it has this type is the `other -> other` branch. Take a look at that branch, and ask this question: "What is the type of `other`?" There has to be exactly one answer! It can't be the case that `other` has one type before the `->` and another type after it; whenever you see a named value in Roc, it is guaranteed to have the same type everywhere it appears in that scope. The reason it has this type is the `other -> other` branch. Take a look at that branch, and ask this question: "What is the type of `other`?" There has to be exactly one answer! It can't be the case that `other` has one type before the `->` and another type after it; whenever you see a named value in Roc, it is guaranteed to have the same type everywhere it appears in that scope.
@ -2061,24 +2069,23 @@ These are all the reserved keywords in Roc. You can't choose any of these as nam
Here are various Roc expressions involving operators, and what they desugar to. Here are various Roc expressions involving operators, and what they desugar to.
| Expression | Desugars To | | Expression | Desugars To |
| ----------------------------- | ------------------ | | ---------------------------- | ------------------ |
| `a + b` | `Num.add a b` | | `a + b` | `Num.add a b` |
| `a - b` | `Num.sub a b` | | `a - b` | `Num.sub a b` |
| `a * b` | `Num.mul a b` | | `a * b` | `Num.mul a b` |
| `a / b` | `Num.div a b` | | `a / b` | `Num.div a b` |
| `a // b` | `Num.divTrunc a b` | | `a // b` | `Num.divTrunc a b` |
| `a ^ b` | `Num.pow a b` | | `a ^ b` | `Num.pow a b` |
| `a % b` | `Num.rem a b` | | `a % b` | `Num.rem a b` |
| `-a` | `Num.neg a` | | `-a` | `Num.neg a` |
| `a == b` | `Bool.isEq a b` | | `a == b` | `Bool.isEq a b` |
| `a != b` | `Bool.isNotEq a b` | | `a != b` | `Bool.isNotEq a b` |
| `a && b` | `Bool.and a b` | | `a && b` | `Bool.and a b` |
| <code>a \|\| b</code> | `Bool.or a b` | | <code>a \|\| b</code> | `Bool.or a b` |
| `!a` | `Bool.not a` | | `!a` | `Bool.not a` |
| <code>a \|> f</code> | `f a` | | <code>a \|> f</code> | `f a` |
| <code>f a b \|> g x y</code> | `g (f a b) x y` | | <code>f a b \|> g x y</code> | `g (f a b) x y` |
</section> </section>
<script type="text/javascript" src="/builtins/search.js" defer></script> <script type="text/javascript" src="/builtins/search.js" defer></script>