{}", buf.join("")) +} + +pub fn highlight_roc_code_inline(code: &str) -> String { + let buf = highlight(code); + + format!("
{}
", buf.join(""))
+}
+
+pub fn highlight(code: &str) -> Vec{}", &to_highlight) + } + + // And put it into the vector + parser_with_highlighting.push(pulldown_cmark::Event::Html( + pulldown_cmark::CowStr::from(highlighted_html), + )); + to_highlight = String::new(); + in_code_block = false; + } + } + pulldown_cmark::Event::Text(t) => { + if in_code_block { + // If we're in a code block, build up the string of text + to_highlight.push_str(&t); + } else { + parser_with_highlighting.push(pulldown_cmark::Event::Text(t)) + } + } + e => { + parser_with_highlighting.push(e); + } + } + } + + html::push_html(&mut content_html, parser_with_highlighting.into_iter()); let roc_relpath = RocStr::from(output_relpath.to_str().unwrap()); let roc_content_html = RocStr::from(content_html.as_str()); @@ -214,6 +268,12 @@ fn process_file(input_dir: &Path, output_dir: &Path, input_file: &Path) -> Resul println!("{} -> {}", input_file.display(), output_file.display()); + // Create parent directory if it doesn't exist + let parent_dir = output_file.parent().unwrap(); + if !parent_dir.exists() { + fs::create_dir_all(&parent_dir).unwrap(); + } + fs::write(output_file, rust_output_str).map_err(|e| format!("{}", e)) } @@ -240,3 +300,16 @@ pub fn strip_windows_prefix(path_buf: PathBuf) -> std::path::PathBuf { std::path::Path::new(path_str.trim_start_matches(r"\\?\")).to_path_buf() } + +fn is_roc_code_block(cbk: &pulldown_cmark::CodeBlockKind) -> bool { + match cbk { + pulldown_cmark::CodeBlockKind::Indented => false, + pulldown_cmark::CodeBlockKind::Fenced(cow_str) => { + if cow_str.contains("roc") { + true + } else { + false + } + } + } +} diff --git a/flake.nix b/flake.nix index f7a59ceb5c..8f201c739e 100644 --- a/flake.nix +++ b/flake.nix @@ -44,7 +44,6 @@ xorg.libXrandr xorg.libXi xorg.libxcb - alsa-lib ]; darwinInputs = with pkgs; diff --git a/nightly_benches/Cargo.toml b/nightly_benches/Cargo.toml index a79266821d..7bdb94afa6 100644 --- a/nightly_benches/Cargo.toml +++ b/nightly_benches/Cargo.toml @@ -1,17 +1,19 @@ [package] name = "nightly_benches" -version = "0.0.1" -edition = "2021" -authors = ["The Roc Contributors"] -license = "UPL-1.0" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true [dependencies] [dev-dependencies] -criterion = { git = "https://github.com/Anton-4/criterion.rs"} cli_utils = { path = "../cli/cli_utils" } -criterion-perf-events ={ git = "https://github.com/Anton-4/criterion-perf-events" } -perfcnt = "0.7.1" + +criterion-perf-events.workspace = true +criterion.workspace = true +perfcnt.workspace = true [[bench]] name = "events_bench" diff --git a/www/generate_tutorial/src/input/tutorial.md b/www/generate_tutorial/src/input/tutorial.md index ebd021ae66..322e0ad772 100644 --- a/www/generate_tutorial/src/input/tutorial.md +++ b/www/generate_tutorial/src/input/tutorial.md @@ -20,7 +20,7 @@ Try typing this in the REPL and pressing Enter: The REPL should cheerfully display the following: -
"Hello, World!" : Str # val1+
"Hello, World!" : Str # val1Congratulations! You've just written your first Roc code. @@ -38,8 +38,8 @@ You should see the same `"Hello, World!"` line as before. You can also assign specific names to expressions. Try entering these lines: -
greeting = "Hi"
-audience = "World"
+greeting = "Hi"
+audience = "World"
From now until you exit the REPL, you can refer to either `greeting` or `audience` by those names!
@@ -47,11 +47,11 @@ From now until you exit the REPL, you can refer to either `greeting` or `audienc
You can combine named strings together using _string interpolation_, like so:
-"\(greeting) there, \(audience)!"
+"\(greeting) there, \(audience)!"
If you put this into the REPL, you should see this output:
-"Hi there, World!" : Str # val2+
"Hi there, World!" : Str # val2Notice that the REPL printed `# val2` here. This works just like `# val1` did before, but it chose the name `val2` for this expression because `val1` was already taken. As we continue entering more expressions into the REPL, you'll see more and more of these generated names—but they won't be mentioned again in this tutorial, since they're just a convenience. @@ -83,7 +83,7 @@ Remember back in the [string interpolation](#string-interpolation) section when
Str.concat "Hi " "there!" -"Hi there!" : Str +"Hi there!" : StrHere we're calling the `Str.concat` function and passing two arguments: the string `"Hi "` and the string `"there!"`. This _concatenates_ the two strings together (that is, it puts one after the other) and returns the resulting combined string of `"Hi there!"`. @@ -94,7 +94,7 @@ That said, just like in the arithmetic example above, we can use parentheses to
Str.concat "Birds: " (Num.toStr 42) -"Birds: 42" : Str +"Birds: 42" : StrThis calls `Num.toStr` on the number `42`, which converts it into the string `"42"`, and then passes that string as the second argument to `Str.concat`. @@ -124,14 +124,15 @@ Let's move out of the REPL and create our first Roc application! Make a file named `main.roc` and put this in it: -
app "hello" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } - imports [pf.Stdout] - provides [main] to pf +```roc +app "hello" + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } + imports [pf.Stdout] + provides [main] to pf -main = - Stdout.line "I'm a Roc application!" -+main = + Stdout.line "I'm a Roc application!" +``` Try running this with: @@ -147,17 +148,18 @@ Congratulations, you've written your first Roc application! We'll go over what t Try replacing the `main` line with this: -
birds = 3 +```roc +birds = 3 -iguanas = 2 +iguanas = 2 -total = Num.toStr (birds + iguanas) +total = Num.toStr (birds + iguanas) -main = - Stdout.line "There are \(total) animals." -+main = + Stdout.line "There are \(total) animals." +``` -Now run `roc dev` again. This time the "Downloading …" message won't appear; the file has been cached from last time, and won't need to be downloaded again. +Now run `roc dev` again. This time the "Downloading ..." message won't appear; the file has been cached from last time, and won't need to be downloaded again. You should see this: @@ -175,28 +177,30 @@ Once we have a def, we can use its name in other expressions. For example, the ` You can name a def using any combination of letters and numbers, but they have to start with a letter. - +**Note:** Defs are constant; they can't be reassigned. We'd get an error if we wrote these two defs in the same scope: + +```roc +birds = 3 +birds = 2 +``` ### [Defining Functions](#defining-functions) {#defining-functions} So far we've called functions like `Num.toStr`, `Str.concat`, and `Stdout.line`. Next let's try defining a function of our own. -
birds = 3
+```roc
+birds = 3
-iguanas = 2
+iguanas = 2
-total = Num.toStr (birds + iguanas)
+total = addAndStringify birds iguanas
-main =
- Stdout.line "There are \(total) animals."
+main =
+ Stdout.line "There are \(total) animals."
-addAndStringify = \num1, num2 ->
- Num.toStr (num1 + num2)
-
+addAndStringify = \num1, num2 ->
+ Num.toStr (num1 + num2)
+```
This new `addAndStringify` function we've defined accepts two numbers, adds them, calls `Num.toStr` on the result, and returns that.
@@ -206,14 +210,15 @@ The `\num1, num2 ->` syntax defines a function's arguments, and the expression a
Let's modify this function to return an empty string if the numbers add to zero.
-addAndStringify = \num1, num2 -> - sum = num1 + num2 +```roc +addAndStringify = \num1, num2 -> + sum = num1 + num2 - if sum == 0 then - "" - else - Num.toStr (num1 + num2) -+ if sum == 0 then + "" + else + Num.toStr (num1 + num2) +``` We did two things here: @@ -226,30 +231,32 @@ Every `if` must be accompanied by both `then` and also `else`. Having an `if` wi We can combine `if` and `else` to get `else if`, like so: -
addAndStringify = \num1, num2 -> - sum = num1 + num2 +```roc +addAndStringify = \num1, num2 -> + sum = num1 + num2 - if sum == 0 then - "" - else if sum < 0 then - "negative" - else - Num.toStr (num1 + num2) -+ if sum == 0 then + "" + else if sum < 0 then + "negative" + else + Num.toStr (num1 + num2) +``` Note that `else if` is not a separate language keyword! It's just an `if`/`else` where the `else` branch contains another `if`/`else`. This is easier to see with different indentation: -
addAndStringify = \num1, num2 -> - sum = num1 + num2 +```roc +addAndStringify = \num1, num2 -> + sum = num1 + num2 - if sum == 0 then - "" - else - if sum < 0 then - "negative" - else - Num.toStr (num1 + num2) -+ if sum == 0 then + "" + else + if sum < 0 then + "negative" + else + Num.toStr (num1 + num2) +``` This differently-indented version is equivalent to writing `else if sum < 0 then` on the same line, although the convention is to use the original version's style. @@ -257,7 +264,9 @@ This differently-indented version is equivalent to writing `else if sum < 0 then This is a comment in Roc: -# The 'name' field is unused by addAndStringify +```roc +# The 'name' field is unused by addAndStringify +``` Whenever you write `#` it means that the rest of the line is a comment, and will not affect the running program. Roc does not have multiline comment syntax. @@ -266,11 +275,12 @@ running program. Roc does not have multiline comment syntax. Comments that begin with `##` are "doc comments" which will be included in generated documentation (`roc docs`). They can include code blocks by adding five spaces after `##`. -
## This is a comment for documentation, and includes a code block.
+```roc
+## This is a comment for documentation, and includes a code block.
##
## x = 2
-## expect x == 2
-
+## expect x == 2
+```
Like other comments, doc comments do not affect the running program.
@@ -278,14 +288,15 @@ Like other comments, doc comments do not affect the running program.
[Print debugging](https://en.wikipedia.org/wiki/Debugging#Techniques) is the most common debugging technique in the history of programming, and Roc has a `dbg` keyword to facilitate it. Here's an example of how to use `dbg`:
-pluralize = \singular, plural, count -> - dbg count +```roc +pluralize = \singular, plural, count -> + dbg count - if count == 1 then + if count == 1 then singular - else + else plural -+``` Whenever this `dbg` line of code is reached, the value of `count` will be printed to [stderr](
total = addAndStringify { birds: 5, iguanas: 7 } +```roc +total = addAndStringify { birds: 5, iguanas: 7 } -addAndStringify = \counts -> - Num.toStr (counts.birds + counts.iguanas) -+addAndStringify = \counts -> + 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. @@ -327,13 +343,15 @@ When we use [`==`](/builtins/Bool#isEq) on records, it compares all the fields i The `addAndStringify` function will accept any record with at least the fields `birds` and `iguanas`, but it will also accept records with more fields. For example: -
total = addAndStringify { birds: 5, iguanas: 7 } - -# The `name` field is unused by addAndStringify -totalWithNote = addAndStringify { birds: 4, iguanas: 3, name: "Whee!" } +```roc +total = addAndStringify { birds: 5, iguanas: 7 } -addAndStringify = \counts -> - Num.toStr (counts.birds + counts.iguanas)+# The `name` field is unused by addAndStringify +totalWithNote = addAndStringify { birds: 4, iguanas: 3, name: "Whee!" } + +addAndStringify = \counts -> + Num.toStr (counts.birds + counts.iguanas) +``` This works because `addAndStringify` only uses `counts.birds` and `counts.iguanas`. If we were to use `counts.note` inside `addAndStringify`, then we would get an error because `total` is calling `addAndStringify` passing a record that doesn't have a `note` field. @@ -343,13 +361,14 @@ Roc has a couple of shorthands you can use to express some record-related operat Instead of writing `\record -> record.x` we can write `.x` and it will evaluate to the same thing: a function that takes a record and returns its `x` field. You can do this with any field you want. For example: -
# returnFoo is a function that takes a record -# and returns the `foo` field of that record. -returnFoo = .foo +```roc +# returnFoo is a function that takes a record +# and returns the `foo` field of that record. +returnFoo = .foo -returnFoo { foo: "hi!", bar: "blah" } -# returns "hi!" -+returnFoo { foo: "hi!", bar: "blah" } +# returns "hi!" +``` 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: @@ -363,30 +382,35 @@ In these cases, we shorten it to writing the name of the def alone—for example We can use _destructuring_ to avoid naming a record in a function argument, instead giving names to its individual fields: -
addAndStringify = \{ birds, iguanas } -> - Num.toStr (birds + iguanas) -+```roc +addAndStringify = \{ birds, iguanas } -> + Num.toStr (birds + iguanas) +``` Here, we've _destructured_ the record to create a `birds` def that's assigned to its `birds` field, and an `iguanas` def that's assigned to its `iguanas` field. We can customize this if we like: -
addAndStringify = \{ birds, iguanas: lizards } -> - Num.toStr (birds + lizards) -+```roc +addAndStringify = \{ birds, iguanas: lizards } -> + Num.toStr (birds + lizards) +``` In this version, we created a `lizards` def that's assigned to the record's `iguanas` field. (We could also do something similar with the `birds` field if we like.) Finally, destructuring can be used in defs too: -
{ x, y } = { x: 5, y: 10 }+```roc +{ x, y } = { x: 5, y: 10 } +``` ### [Making records from other records](#making-records-from-other-records) {#making-records-from-other-records} So far we've only constructed records from scratch, by specifying all of their fields. We can also construct new records by using another record to use as a starting point, and then specifying only the fields we want to be different. For example, here are two ways to get the same record: -
original = { birds: 5, zebras: 2, iguanas: 7, goats: 1 } -fromScratch = { birds: 4, zebras: 2, iguanas: 3, goats: 1 } -fromOriginal = { original & birds: 4, iguanas: 3 } -+```roc +original = { birds: 5, zebras: 2, iguanas: 7, goats: 1 } +fromScratch = { birds: 4, zebras: 2, iguanas: 3, goats: 1 } +fromOriginal = { original & birds: 4, iguanas: 3 } +``` The `fromScratch` and `fromOriginal` records are equal, although they're defined in different ways. @@ -402,29 +426,31 @@ Roc supports optional record fields using the `?` operator. This can be a useful In Roc you can write a function like: -
table = \{ +```roc +table = \{ height, width, - title? "oak", - description? "a wooden table" - } - -> -+ title? "oak", + description? "a wooden table" + } + -> +``` This is using *optional field destructuring* to destructure a record while also providing default values for any fields that might be missing. Here's the type of `table`: -
table : - { - height : Pixels, - width : Pixels, - title ? Str, - description ? Str, - } - -> Table -+```roc +table : + { + height: Pixels, + width: Pixels, + title? Str, + description? Str, + } + -> Table +``` 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 @@ -449,14 +475,15 @@ ergonomics of destructuring mean this wouldn't be a good fit for data modeling, Sometimes we want to represent that something can have one of several values. For example: -
stoplightColor = - if something > 0 then +```roc +stoplightColor = + if something > 0 then Red - else if something == 0 then + else if something == 0 then Yellow - else + else Green -+``` Here, `stoplightColor` can have one of three values: `Red`, `Yellow`, or `Green`. The capitalization is very important! If these were lowercase (`red`, `yellow`, `green`), then they would refer to defs. However, because they are capitalized, they instead refer to _tags_. @@ -464,74 +491,80 @@ A tag is a literal value just like a number or a string. Similarly to how I can Let's say we wanted to turn `stoplightColor` from a `Red`, `Green`, or `Yellow` into a string. Here's one way we could do that: -
stoplightStr = - if stoplightColor == Red then - "red" - else if stoplightColor == Green then - "green" - else - "yellow" -+```roc +stoplightStr = + if stoplightColor == Red then + "red" + else if stoplightColor == Green then + "green" + else + "yellow" +``` We can express this logic more concisely using `when`/`is` instead of `if`/`then`: -
stoplightStr = - when stoplightColor is - Red -> "red" - Green -> "green" - Yellow -> "yellow" -+```roc +stoplightStr = + when stoplightColor is + Red -> "red" + Green -> "green" + Yellow -> "yellow" +``` This results in the same value for `stoplightStr`. In both the `when` version and the `if` version, we have three conditional branches, and each of them evaluates to a string. The difference is how the conditions are specified; here, we specify between `when` and `is` that we're making comparisons against `stoplightColor`, and then we specify the different things we're comparing it to: `Red`, `Green`, and `Yellow`. Besides being more concise, there are other advantages to using `when` here. 1. We don't have to specify an `else` branch, so the code can be more self-documenting about exactly what all the options are. -2. We get more compiler help. If we try deleting any of these branches, we'll get a compile-time error saying that we forgot to cover a case that could come up. For example, if we delete the `Green ->` branch, the compiler will say that we didn't handle the possibility that `stoplightColor` could be `Green`. It knows this because `Green` is one of the possibilities in our `stoplightColor = if …` definition. +2. We get more compiler help. If we try deleting any of these branches, we'll get a compile-time error saying that we forgot to cover a case that could come up. For example, if we delete the `Green ->` branch, the compiler will say that we didn't handle the possibility that `stoplightColor` could be `Green`. It knows this because `Green` is one of the possibilities in our `stoplightColor = if ...` definition. We can still have the equivalent of an `else` branch in our `when` if we like. Instead of writing `else`, we write `_ ->` like so: -
stoplightStr =
- when stoplightColor is
- Red -> "red"
- _ -> "not red"
-
+```roc
+stoplightStr =
+ when stoplightColor is
+ Red -> "red"
+ _ -> "not red"
+```
This lets us more concisely handle multiple cases. However, it has the downside that if we add a new case - for example, if we introduce the possibility of `stoplightColor` being `Orange`, the compiler can no longer tell us we forgot to handle that possibility in our `when`. After all, we are handling it - just maybe not in the way we'd decide to if the compiler had drawn our attention to it!
We can make this `when` _exhaustive_ (that is, covering all possibilities) without using `_ ->` by using `|` to specify multiple matching conditions for the same branch:
-stoplightStr = - when stoplightColor is - Red -> "red" - Green | Yellow -> "not red" -+```roc +stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow -> "not red" +``` You can read `Green | Yellow` as "either `Green` or `Yellow`". By writing it this way, if we introduce the possibility that `stoplightColor` can be `Orange`, we'll get a compiler error telling us we forgot to cover that case in this `when`, and then we can handle it however we think is best. We can also combine `if` and `when` to make branches more specific: -
stoplightStr = - when stoplightColor is - Red -> "red" - Green | Yellow if contrast > 75 -> "not red, but very high contrast" - Green | Yellow if contrast > 50 -> "not red, but high contrast" - Green | Yellow -> "not red" -+```roc +stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow if contrast > 75 -> "not red, but very high contrast" + Green | Yellow if contrast > 50 -> "not red, but high contrast" + Green | Yellow -> "not red" +``` This will give the same answer for `stoplightStr` as if we had written the following: -
stoplightStr = - when stoplightColor is - Red -> "red" - Green | Yellow -> - if contrast > 75 then - "not red, but very high contrast" - else if contrast > 50 then - "not red, but high contrast" - else - "not red" -+```roc +stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow -> + if contrast > 75 then + "not red, but very high contrast" + else if contrast > 50 then + "not red, but high contrast" + else + "not red" +``` Either style can be a reasonable choice depending on the circumstances. @@ -539,22 +572,23 @@ Either style can be a reasonable choice depending on the circumstances. Tags can have _payloads_—that is, values inside them. For example: -
stoplightColor = - if something > 100 then +```roc +stoplightColor = + if something > 100 then Red - else if something > 0 then + else if something > 0 then Yellow - else if something == 0 then + else if something == 0 then Green - else - Custom "some other color" + else + Custom "some other color" -stoplightStr = - when stoplightColor is - Red -> "red" - Green | Yellow -> "not red" - Custom description -> description -+stoplightStr = + when stoplightColor is + Red -> "red" + Green | Yellow -> "not red" + Custom description -> description +``` This makes two changes to our earlier `stoplightColor` / `stoplightStr` example. @@ -571,17 +605,18 @@ We refer to whatever comes before a `->` in a `when` expression as a _pattern_ You can also pattern match on lists, like so: -
when myList is - [] -> 0 # the list is empty - [Foo, ..] -> 1 # it starts with a Foo tag - [_, ..] -> 2 # it contains at least one element, which we ignore - [Foo, Bar, ..] -> 3 # it starts with a Foo tag followed by a Bar tag - [Foo, Bar, Baz] -> 4 # it has exactly 3 elements: Foo, Bar, and Baz - [Foo, a, ..] -> 5 # its first element is Foo, and its second we name `a` - [Ok a, ..] -> 6 # it starts with an Ok containing a payload named `a` - [.., Foo] -> 7 # it ends with a Foo tag - [A, B, .., C, D] -> 8 # it has certain elements at the beginning and end -+```roc +when myList is + [] -> 0 # the list is empty + [Foo, ..] -> 1 # it starts with a Foo tag + [_, ..] -> 2 # it contains at least one element, which we ignore + [Foo, Bar, ..] -> 3 # it starts with a Foo tag followed by a Bar tag + [Foo, Bar, Baz] -> 4 # it has exactly 3 elements: Foo, Bar, and Baz + [Foo, a, ..] -> 5 # its first element is Foo, and its second we name `a` + [Ok a, ..] -> 6 # it starts with an Ok containing a payload named `a` + [.., Foo] -> 7 # it ends with a Foo tag + [A, B, .., C, D] -> 8 # it has certain elements at the beginning and end +``` This can be both more concise and more efficient (at runtime) than calling [`List.get`](https://www.roc-lang.org/builtins/List#get) multiple times, since each call to `get` requires a separate conditional to handle the different `Result`s they return. @@ -599,11 +634,15 @@ As an example of why tags are encouraged for data modeling, in many languages it Another thing we can do in Roc is to make a _list_ of values. Here's an example: -names = ["Sam", "Lee", "Ari"] +```roc +names = ["Sam", "Lee", "Ari"] +``` This is a list with three elements in it, all strings. We can add a fourth element using `List.append` like so: -List.append names "Jess" +```roc +List.append names "Jess" +``` This returns a **new** list with `"Jess"` after `"Ari"`, and doesn't modify the original list at all. All values in Roc (including lists, but also records, strings, numbers, and so on) are immutable, meaning whenever we want to "change" them, we want to instead pass them to a function which returns some variation of what was passed in. @@ -611,8 +650,9 @@ This returns a **new** list with `"Jess"` after `"Ari"`, and doesn't modify the A common way to transform one list into another is to use `List.map`. Here's an example of how to use it: -List.map [1, 2, 3] \num -> num * 2 - +```roc +List.map [1, 2, 3] \num -> num * 2 +``` This returns `[2, 4, 6]`. @@ -625,9 +665,11 @@ It then returns a list which it creates by calling the given function on each el We can also give `List.map` a named function, instead of an anonymous one: -List.map [1, 2, 3] Num.isOdd +```roc +List.map [1, 2, 3] Num.isOdd +``` -This `Num.isOdd` function returns `Bool.true` if it's given an odd number, and `Bool.false` otherwise. So `Num.isOdd 5` returns true and `Num.isOdd 2` returns false. +This `Num.isOdd` function returns `Bool.true` if it's given an odd number, and `Bool.false` otherwise. So `Num.isOdd 5` returns `Bool.true` and `Num.isOdd 2` returns `Bool.false`. As such, calling `List.map [1, 2, 3] Num.isOdd` returns a new list of `[Bool.true, Bool.false, Bool.true]`. @@ -635,22 +677,25 @@ As such, calling `List.map [1, 2, 3] Num.isOdd` returns a new list of `[Bool.tru If we tried to give `List.map` a function that didn't work on the elements in the list, then we'd get an error at compile time. Here's a valid, and then an invalid example: -
# working example
-List.map [-1, 2, 3, -4] Num.isNegative
-# returns [Bool.true, Bool.false, Bool.false, Bool.true]
-
+```roc
+# working example
+List.map [-1, 2, 3, -4] Num.isNegative
+# returns [Bool.true, Bool.false, Bool.false, Bool.true]
+```
-# invalid example -List.map ["A", "B", "C"] Num.isNegative -# error: isNegative doesn't work on strings! -+```roc +# invalid example +List.map ["A", "B", "C"] Num.isNegative +# error: isNegative doesn't work on strings! +``` Because `Num.isNegative` works on numbers and not strings, calling `List.map` with `Num.isNegative` and a list of numbers works, but doing the same with a list of strings doesn't work. This wouldn't work either: -
List.map ["A", "B", "C", 1, 2, 3] Num.isNegative -+```roc +List.map ["A", "B", "C", 1, 2, 3] Num.isNegative +``` Every element in a Roc list has to share the same type. For example, we can have a list of strings like `["Sam", "Lee", "Ari"]`, or a list of numbers like `[1, 2, 3, 4, 5]` but we can't have a list which mixes strings and numbers like `["Sam", 1, "Lee", 2, 3]`, that would be a compile-time error. @@ -660,17 +705,19 @@ Ensuring that all elements in a list share a type eliminates entire categories o We can use tags with payloads to make a list that contains a mixture of different types. For example: -
List.map [StrElem "A", StrElem "b", NumElem 1, StrElem "c", NumElem -3] \elem -> - when elem is - NumElem num -> Num.isNegative num - StrElem str -> Str.isCapitalized str -# returns [Bool.true, Bool.false, Bool.false, Bool.false, Bool.true] -+```roc +List.map [StrElem "A", StrElem "b", NumElem 1, StrElem "c", NumElem -3] \elem -> + when elem is + NumElem num -> Num.isNegative num + StrElem str -> Str.isCapitalized str +# returns [Bool.true, Bool.false, Bool.false, Bool.false, Bool.true] +``` Compare this with the example from earlier, which caused a compile-time error: -
List.map ["A", "B", "C", 1, 2, 3] Num.isNegative -+```roc +List.map ["A", "B", "C", 1, 2, 3] Num.isNegative +``` The version that uses tags works because we aren't trying to call `Num.isNegative` on each element. Instead, we're using a `when` to tell when we've got a string or a number, and then calling either `Num.isNegative` or `Str.isCapitalized` depending on which type we have. @@ -680,57 +727,66 @@ We could take this as far as we like, adding more different tags (e.g. `BoolElem Let's say I want to apply a tag to a bunch of elements in a list. For example: -
List.map ["a", "b", "c"] \str -> Foo str -+```roc +List.map ["a", "b", "c"] \str -> Foo str +``` This is a perfectly reasonable way to write it, but I can also write it like this: -
List.map ["a", "b", "c"] Foo
-
+```roc
+List.map ["a", "b", "c"] Foo
+```
These two versions compile to the same thing. As a convenience, Roc lets you specify a tag name where a function is expected; when you do this, the compiler infers that you want a function which uses all of its arguments as the payload to the given tag.
-### [`List.any` and `List.all`](#list-any-and-list-all) {#list-any-and-list-all}
+### [List.any and List.all](#list-any-and-list-all) {#list-any-and-list-all}
-There are several functions that work like `List.map`, they walk through each element of a list and do something with it. Another is `List.any`, which returns `Bool.true` if calling the given function on any element in the list returns `true`:
+There are several functions that work like `List.map`, they walk through each element of a list and do something with it. Another is `List.any`, which returns `Bool.true` if calling the given function on any element in the list returns `Bool.true`:
-List.any [1, 2, 3] Num.isOdd -# returns `Bool.true` because 1 and 3 are odd -+```roc +List.any [1, 2, 3] Num.isOdd +# returns `Bool.true` because 1 and 3 are odd +``` -
List.any [1, 2, 3] Num.isNegative -# returns `Bool.false` because none of these is negative -+```roc +List.any [1, 2, 3] Num.isNegative +# returns `Bool.false` because none of these is negative +``` -There's also `List.all` which only returns `true` if all the elements in the list pass the test: +There's also `List.all` which only returns `Bool.true` if all the elements in the list pass the test: -
List.all [1, 2, 3] Num.isOdd
-# returns `Bool.false` because 2 is not odd
-
+```roc
+List.all [1, 2, 3] Num.isOdd
+# returns `Bool.false` because 2 is not odd
+```
-List.all [1, 2, 3] Num.isPositive
-# returns `Bool.true` because all of these are positive
-
+```roc
+List.all [1, 2, 3] Num.isPositive
+# returns `Bool.true` because all of these are positive
+```
### [Removing elements from a list](#removing-elements-from-a-list) {#removing-elements-from-a-list}
You can also drop elements from a list. One way is `List.dropAt` - for example:
-List.dropAt ["Sam", "Lee", "Ari"] 1 -# drops the element at offset 1 ("Lee") and returns ["Sam", "Ari"] -+```roc +List.dropAt ["Sam", "Lee", "Ari"] 1 +# drops the element at offset 1 ("Lee") and returns ["Sam", "Ari"] +``` -Another way is to use `List.keepIf`, which passes each of the list's elements to the given function, and then keeps them only if that function returns `true`. +Another way is to use `List.keepIf`, which passes each of the list's elements to the given function, and then keeps them only if that function returns `Bool.true`. -
List.keepIf [1, 2, 3, 4, 5] Num.isEven -# returns [2, 4] -+```roc +List.keepIf [1, 2, 3, 4, 5] Num.isEven +# returns [2, 4] +``` There's also `List.dropIf`, which does the opposite: -
List.dropIf [1, 2, 3, 4, 5] Num.isEven -# returns [1, 3, 5] -+```roc +List.dropIf [1, 2, 3, 4, 5] Num.isEven +# returns [1, 3, 5] +``` ### [Getting an individual element from a list](#getting-an-individual-element-from-a-list) {#getting-an-individual-element-from-a-list} @@ -738,32 +794,38 @@ Another thing we can do with a list is to get an individual element out of it. ` For example, what do each of these return? -
List.get ["a", "b", "c"] 1 - -List.get ["a", "b", "c"] 100 -+```roc +List.get ["a", "b", "c"] 1 +``` + +```roc +List.get ["a", "b", "c"] 100 +``` The answer is that the first one returns `Ok "b"` and the second one returns `Err OutOfBounds`. They both return tags! This is done so that the caller becomes responsible for handling the possibility that the index is outside the bounds of that particular list. Here's how calling `List.get` can look in practice: -
when List.get ["a", "b", "c"] index is - Ok str -> "I got this string: \(str)" - Err OutOfBounds -> "That index was out of bounds, sorry!" -+```roc +when List.get ["a", "b", "c"] index is + Ok str -> "I got this string: \(str)" + Err OutOfBounds -> "That index was out of bounds, sorry!" +``` There's also `List.first`, which always gets the first element, and `List.last` which always gets the last. They return `Err ListWasEmpty` instead of `Err OutOfBounds`, because the only way they can fail is if you pass them an empty list! These functions demonstrate a common pattern in Roc: operations that can fail returning either an `Ok` tag with the answer (if successful), or an `Err` tag with another tag describing what went wrong (if unsuccessful). In fact, it's such a common pattern that there's a whole module called `Result` which deals with these two tags. Here are some examples of `Result` functions: -
Result.withDefault (List.get ["a", "b", "c"] 100) ""
-# returns "" because that's the default we said to use if List.get returned an Err
-
-Result.isOk (List.get ["a", "b", "c"] 1)
-# returns `Bool.true` because `List.get` returned an `Ok` tag. (The payload gets ignored.)
+```roc
+Result.withDefault (List.get ["a", "b", "c"] 100) ""
+# returns "" because that's the default we said to use if List.get returned an Err
+```
+```roc
+Result.isOk (List.get ["a", "b", "c"] 1)
+# returns `Bool.true` because `List.get` returned an `Ok` tag. (The payload gets ignored.)
-# Note: There's a Result.isErr function that works similarly.
-
+# Note: There's a Result.isErr function that works similarly.
+```
### [Walking the elements in a list](#walking-the-elements-in-a-list) {#walking-the-elements-in-a-list}
@@ -782,14 +844,15 @@ because it's more concise, runs faster, and doesn't give you any `Result`s to de
Here's an example:
-List.walk [1, 2, 3, 4, 5] { evens: [], odds: [] } \state, elem -> - if Num.isEven elem then - { state & evens: List.append state.evens elem } - else - { state & odds: List.append state.odds elem } +```roc +List.walk [1, 2, 3, 4, 5] { evens: [], odds: [] } \state, elem -> + if Num.isEven elem then + { state & evens: List.append state.evens elem } + else + { state & odds: List.append state.odds elem } -# returns { evens: [2, 4], odds: [1, 3, 5] } -+# returns { evens: [2, 4], odds: [1, 3, 5] } +``` In this example, we walk over the list `[1, 2, 3, 4, 5]` and add each element to either the `evens` or `odds` field of a `state` record: `{ evens, odds }`. By the end, that record has a list of all the even numbers in the list and a list of all the odd numbers. @@ -797,48 +860,17 @@ In this example, we walk over the list `[1, 2, 3, 4, 5]` and add each element to 1. A list. (`[1, 2, 3, 4, 5]`) 2. An initial `state` value. (`{ evens: [], odds: [] }`) -3. A function which takes the current `state` and element, and returns a new `state`. (`\state, elem -> …`) +3. A function which takes the current `state` and element, and returns a new `state`. (`\state, elem -> ...`) 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 | -
---|---|---|
{ evens: [], odds: [] } |
- 1 |
- { evens: [], 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] } |
- 4 |
- { evens: [2, 4], odds: [1, 3] } |
-
{ evens: [2, 4], odds: [1, 3] } |
- 4 |
- { evens: [2, 4], odds: [1, 3, 5] } |
-
Result.withDefault (List.get ["a", "b", "c"] 1) "" - -List.get ["a", "b", "c"] 1 -|> Result.withDefault "" -+```roc +Result.withDefault (List.get ["a", "b", "c"] 1) "" +``` +```roc +List.get ["a", "b", "c"] 1 +|> Result.withDefault "" +``` The `|>` operator takes the value that comes before the `|>` and passes it as the first argument to whatever comes after the `|>`. So in the example above, the `|>` takes `List.get ["a", "b", "c"] 1` and passes that value as the first argument to `Result.withDefault`, making `""` the second argument to `Result.withDefault`. We can take this a step further like so: -
["a", "b", "c"] -|> List.get 1 -|> Result.withDefault "" -+```roc +["a", "b", "c"] +|> List.get 1 +|> Result.withDefault "" +``` This is still equivalent to the first expression. Since `|>` is known as the "pipe operator," we can read this as "start with `["a", "b", "c"]`, then pipe it to `List.get`, then pipe it to `Result.withDefault`." One reason the `|>` operator injects the value as the first argument is to make it work better with functions where argument order matters. For example, these two uses of `List.append` are equivalent: -
List.append ["a", "b", "c"] "d" - -["a", "b", "c"] -|> List.append "d" -+```roc +List.append ["a", "b", "c"] "d" +``` +```roc +["a", "b", "c"] +|> List.append "d" +``` Another example is `Num.div`. All three of the following do the same thing, because `a / b` in Roc is syntax sugar for `Num.div a b`: -
first / second - -Num.div first second - -first -|> Num.div second -+```roc +first / second +``` +```roc +Num.div first second +``` All operators in Roc are syntax sugar for normal function calls. See the [Operator Desugaring Table](https://www.roc-lang.org/tutorial#operator-desugaring-table) at the end of this tutorial for a complete list of them. @@ -897,10 +933,11 @@ All operators in Roc are syntax sugar for normal function calls. See the [Operat Sometimes you may want to document the type of a definition. For example, you might write: -
# Takes a firstName string and a lastName string, and returns a string
-fullName = \firstName, lastName ->
- "\(firstName) \(lastName)"
-
+```roc
+# Takes a firstName string and a lastName string, and returns a string
+fullName = \firstName, lastName ->
+ "\(firstName) \(lastName)"
+```
Comments can be valuable documentation, but they can also get out of date and become misleading. If someone changes this function and forgets to update the comment, it will no longer be accurate.
@@ -908,10 +945,11 @@ Comments can be valuable documentation, but they can also get out of date and be
Here's another way to document this function's type, which doesn't have that problem:
-fullName : Str, Str -> Str -fullName = \firstName, lastName -> - "\(firstName) \(lastName)" -+```roc +fullName : Str, Str -> Str +fullName = \firstName, lastName -> + "\(firstName) \(lastName)" +``` The `fullName :` line is a _type annotation_. It's a strictly optional piece of metadata we can add above a def to describe its type. Unlike a comment, the Roc compiler will check type annotations for accuracy. If the annotation ever doesn't fit with the implementation, we'll get a compile-time error. @@ -919,36 +957,39 @@ The annotation `fullName : Str, Str -> Str` says "`fullName` is a function that We can give type annotations to any value, not just functions. For example: -
firstName : Str
-firstName = "Amy"
+```roc
+firstName : Str
+firstName = "Amy"
-lastName : Str
-lastName = "Lee"
-
+lastName : Str
+lastName = "Lee"
+```
These annotations say that both `firstName` and `lastName` have the type `Str`.
We can annotate records similarly. For example, we could move `firstName` and `lastName` into a record like so:
-amy : { firstName : Str, lastName : Str } -amy = { firstName: "Amy", lastName: "Lee" } +```roc +amy : { firstName : Str, lastName : Str } +amy = { firstName: "Amy", lastName: "Lee" } -jen : { firstName : Str, lastName : Str } -jen = { firstName: "Jen", lastName: "Majura" } -+jen : { firstName : Str, lastName : Str } +jen = { firstName: "Jen", lastName: "Majura" } +``` ### [Type Aliases](#type-aliases) {#type-aliases} When we have a recurring type annotation like this, it can be nice to give it its own name. We do this like so: -
Musician : { firstName : Str, lastName : Str } +```roc +Musician : { firstName : Str, lastName : Str } -amy : Musician -amy = { firstName: "Amy", lastName: "Lee" } +amy : Musician +amy = { firstName: "Amy", lastName: "Lee" } -simone : Musician -simone = { firstName: "Simone", lastName: "Simons" } -+simone : Musician +simone = { firstName: "Simone", lastName: "Simons" } +``` Here, `Musician` is a _type alias_. A type alias is like a def, except it gives a name to a type instead of to a value. Just like how you can read `name : Str` as "`name` has the type `Str`," you can also read `Musician : { firstName : Str, lastName : Str }` as "`Musician` has the type `{ firstName : Str, lastName : Str }`." @@ -956,9 +997,10 @@ Here, `Musician` is a _type alias_. A type alias is like a def, except it gives Annotations for lists must specify what type the list's elements have: -
names : List Str -names = ["Amy", "Simone", "Tarja"] -+```roc +names : List Str +names = ["Amy", "Simone", "Tarja"] +``` You can read `List Str` as "a list of strings." Here, `Str` is a _type parameter_ that tells us what type of `List` we're dealing with. `List` is a _parameterized type_, which means it's a type that requires a type parameter. There's no way to give something a type of `List` without a type parameter. You have to specify what type of list it is, such as `List Str` or `List Bool` or `List { firstName : Str, lastName : Str }`. @@ -966,8 +1008,9 @@ You can read `List Str` as "a list of strings." Here, `Str` is a _type parameter There are some functions that work on any list, regardless of its type parameter. For example, `List.isEmpty` has this type: -
isEmpty : List * -> Bool -+```roc +isEmpty : List * -> Bool +``` The `*` is a _wildcard type_; a type that's compatible with any other type. `List *` is compatible with any type of `List` like `List Str`, `List Bool`, and so on. So you can call `List.isEmpty ["I am a List Str"]` as well as `List.isEmpty [Bool.true]`, and they will both work fine. @@ -977,12 +1020,13 @@ The wildcard type also comes up with empty lists. Suppose we have one function t `List.reverse` works similarly to `List.isEmpty`, but with an important distinction. As with `isEmpty`, we can call `List.reverse` on any list, regardless of its type parameter. However, consider these calls: -
strings : List Str -strings = List.reverse ["a", "b"] +```roc +strings : List Str +strings = List.reverse ["a", "b"] -bools : List Bool -bools = List.reverse [Bool.true, Bool.false] -+bools : List Bool +bools = List.reverse [Bool.true, Bool.false] +``` In the `strings` example, we have `List.reverse` returning a `List Str`. In the `bools` example, it's returning a `List Bool`. So what's the type of `List.reverse`? @@ -990,12 +1034,17 @@ We saw that `List.isEmpty` has the type `List * -> Bool`, so we might think the What we want is something like one of these: -
reverse : List elem -> List elem
-
-reverse : List value -> List value
-
-reverse : List a -> List a
-
+```roc
+reverse : List elem -> List elem
+```
+
+```roc
+reverse : List value -> List value
+```
+
+```roc
+reverse : List a -> List a
+```
Any of these will work, because `elem`, `value`, and `a` are all _type variables_. A type variable connects two or more types in the same annotation. So you can read `List elem -> List elem` as "takes a list and returns a list that has **the same element type**." Just like `List.reverse` does!
@@ -1009,47 +1058,52 @@ Similarly, the only way to have a function whose type is `a -> a` is if the func
We can also annotate types that include tags:
-colorFromStr : Str -> [Red, Green, Yellow] -colorFromStr = \string -> - when string is - "red" -> Red - "green" -> Green - _ -> Yellow -+```roc +colorFromStr : Str -> [Red, Green, Yellow] +colorFromStr = \string -> + when string is + "red" -> Red + "green" -> Green + _ -> Yellow +``` You can read the type `[Red, Green, Yellow]` as "a tag union of the tags `Red`, `Green`, and `Yellow`." Some tag unions have only one tag in them. For example: -
redTag : [Red] -redTag = Red -+```roc +redTag : [Red] +redTag = Red +``` ### [Accumulating Tag Types](#accumulating-tag-types) {#accumulating-tag-types} Tag union types can accumulate more tags based on how they're used. Consider this `if` expression: -
\str -> - if Str.isEmpty str then - Ok "it was empty" - else - Err ["it was not empty"] -+```roc +\str -> + if Str.isEmpty str then + Ok "it was empty" + else + Err ["it was not empty"] +``` Here, Roc sees that the first branch has the type `[Ok Str]` and that the `else` branch has the type `[Err (List Str)]`, so it concludes that the whole `if` expression evaluates to the combination of those two tag unions: `[Ok Str, Err (List Str)]`. -This means this entire `\str -> …` function has the type `Str -> [Ok Str, Err (List Str)]`. However, it would be most common to annotate it as `Result Str (List Str)` instead, because the `Result` type (for operations like `Result.withDefault`, which we saw earlier) is a type alias for a tag union with `Ok` and `Err` tags that each have one payload: +This means this entire `\str -> ...` function has the type `Str -> [Ok Str, Err (List Str)]`. However, it would be most common to annotate it as `Result Str (List Str)` instead, because the `Result` type (for operations like `Result.withDefault`, which we saw earlier) is a type alias for a tag union with `Ok` and `Err` tags that each have one payload: -
Result ok err : [Ok ok, Err err] -+```roc +Result ok err : [Ok ok, Err err] +``` We just saw how tag unions get combined when different branches of a conditional return different tags. Another way tag unions can get combined is through pattern matching. For example: -
when color is - Red -> "red" - Yellow -> "yellow" - Green -> "green" -+```roc +when color is + Red -> "red" + Yellow -> "yellow" + Green -> "green" +``` Here, Roc's compiler will infer that `color`'s type is `[Red, Yellow, Green]`, because those are the three possibilities this `when` handles. @@ -1062,16 +1116,17 @@ A type can be defined to be opaque to hide its internal structure. This is a lot You can create an opaque type with the `:=` operator. Let's make one called `Username`: -
Username := Str +```roc +Username := Str -fromStr : Str -> Username -fromStr = \str -> +fromStr : Str -> Username +fromStr = \str -> @Username str -toStr : Username -> Str -toStr = \@Username str -> +toStr : Username -> Str +toStr = \@Username str -> str -+``` The `fromStr` function turns a string into a `Username` by calling `@Username` on that string. The `toStr` function turns a `Username` back into a string by pattern matching `@Username str` to unwrap the string from the `Username` opaque type. @@ -1105,57 +1160,18 @@ Choosing a size depends on your performance needs and the range of numbers you w Here are the different fixed-size integer types that Roc supports: -
Range | -Type | -
---|---|
-128 127 |
-I8 |
-
0 255 |
-U8 |
-
-32_768 32_767 |
-I16 |
-
0 65_535 |
-U16 |
-
-2_147_483_648 2_147_483_647 |
-I32 |
-
0 (over 4 billion) 4_294_967_295 |
-U32 |
-
-9_223_372_036_854_775_808 9_223_372_036_854_775_807 |
-I64 |
-
0 (over 18 quintillion) 18_446_744_073_709_551_615 |
-U64 |
-
-170_141_183_460_469_231_731_687_303_715_884_105_728 170_141_183_460_469_231_731_687_303_715_884_105_727 |
-I128 |
-
0 (over 340 undecillion) 340_282_366_920_938_463_463_374_607_431_768_211_455 |
-U128 |
-
abs : Num a -> Num a -+```roc +abs : Num a -> Num a +``` This type says `abs` takes a number and then returns a number of the same type. Remember that we can see the type of number is the same because the [type variable](#type-variables) `a` is used on both sides. That's because the `Num` type is compatible with both integers and fractions. There's also an `Int` type which is only compatible with integers, and a `Frac` type which is only compatible with fractions. For example: -
Num.xor : Int a, Int a -> Int a
-
-Num.cos : Frac a -> Frac a
-
-
+```roc
+Num.xor : Int a, Int a -> Int a
+```
+```roc
+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`.
### [Number Literals](#number-literals) {#number-literals}
@@ -1229,12 +1247,13 @@ Crashes in Roc are not like [try/catch exceptions](https://en.wikipedia.org/wiki
You can intentionally crash a Roc program, for example inside a conditional branch that you believe is unreachable. Suppose you're certain that a particular `List U8` contains valid UTF-8 bytes, which means when you call `Str.fromUtf8` on it, the `Result` it returns will always be `Ok`. In that scenario, you can use the `crash` keyword to handle the `Err` case like so:
-answer : Str -answer = - when Str.fromUtf8 definitelyValidUtf8 is - Ok str -> str - Err _ -> crash "This should never happen!" -+```roc +answer : Str +answer = + when Str.fromUtf8 definitelyValidUtf8 is + Ok str -> str + 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. @@ -1244,11 +1263,12 @@ If the unthinkable happens, and somehow the program reaches this `Err` branch ev Another use for `crash` is as a TODO marker when you're in the middle of building something: -
if x > y then - transmogrify (x * 2) -else - crash "TODO handle the x <= y case" -+```roc +if x > y then + transmogrify (x * 2) +else + crash "TODO handle the x <= y case" +``` This lets you do things like write tests for the non-`crash` branch, and then come back and finish the other branch later. @@ -1264,43 +1284,46 @@ Errors that are recoverable should be represented using normal Roc types (like [ You can write automated tests for your Roc code like so: -
pluralize = \singular, plural, count ->
- countStr = Num.toStr count
+```roc
+pluralize = \singular, plural, count ->
+ countStr = Num.toStr count
- if count == 1 then
- "\(countStr) \(singular)"
- else
- "\(countStr) \(plural)"
+ if count == 1 then
+ "\(countStr) \(singular)"
+ else
+ "\(countStr) \(plural)"
-expect pluralize "cactus" "cacti" 1 == "1 cactus"
+expect pluralize "cactus" "cacti" 1 == "1 cactus"
-expect pluralize "cactus" "cacti" 2 == "2 cacti"
-
+expect pluralize "cactus" "cacti" 2 == "2 cacti"
+```
-If you put this in a file named `main.roc` and run `roc test`, Roc will execute the two `expect` expressions (that is, the two `pluralize` calls) and report any that returned `false`.
+If you put this in a file named `main.roc` and run `roc test`, Roc will execute the two `expect` expressions (that is, the two `pluralize` calls) and report any that returned `Bool.false`.
If a test fails, it will not show the actual value that differs from the expected value. To show the actual value, you can write the expect like this:
-expect - funcOut = pluralize "cactus" "cacti" 1 +```roc +expect + funcOut = pluralize "cactus" "cacti" 1 - funcOut == "2 cactus" -+ funcOut == "2 cactus" +``` ### [Inline Expectations](#inline-expects) {#inline-expects} Expects do not have to be at the top level: -
pluralize = \singular, plural, count ->
- countStr = Num.toStr count
+```roc
+pluralize = \singular, plural, count ->
+ countStr = Num.toStr count
- if count == 1 then
- "\(countStr) \(singular)"
- else
- expect count > 0
+ if count == 1 then
+ "\(countStr) \(singular)"
+ else
+ expect count > 0
- "\(countStr) \(plural)"
-
+ "\(countStr) \(plural)"
+```
This `expect` will fail if you call `pluralize` passing a count of 0.
@@ -1343,11 +1366,12 @@ Besides being built into the compiler, the builtin modules are different from ot
Let's take a closer look at the part of `main.roc` above the `main` def:
-app "hello"
- packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" }
- imports [pf.Stdout]
- provides main to pf
-
+```roc
+app "hello"
+ packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" }
+ imports [pf.Stdout]
+ provides main to pf
+```
This is known as a _module header_. Every `.roc` file is a _module_, and there are different types of modules. We know this particular one is an _application module_ because it begins with the `app` keyword.
@@ -1355,14 +1379,15 @@ The line `app "hello"` states that this module defines a Roc application, and th
The remaining lines all involve the [platform](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform) this application is built on:
-packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } - imports [pf.Stdout] - provides [main] to pf -+```roc +packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } + imports [pf.Stdout] + 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](
main = Stdout.line "I'm a Roc application!" -+```roc +main = Stdout.line "I'm a Roc application!" +``` Here, `main` is calling a function called `Stdout.line`. More specifically, it's calling a function named `line` which is exposed by a module named `Stdout`. -When we write `imports [pf.Stdout]`, it specifies that the `Stdout` module comes from the package we named `pf` in the `packages { pf: … }` section. +When we write `imports [pf.Stdout]`, it specifies that the `Stdout` module comes from the package we named `pf` in the `packages { pf: ... }` section. If we would like to include other modules in our application, say `AdditionalModule.roc` and `AnotherModule.roc`, then they can be imported directly in `imports` like this: -
imports [pf.Stdout, AdditionalModule, AnotherModule]
-
+```roc
+imports [pf.Stdout, AdditionalModule, AnotherModule]
+```
You can find documentation for the `Stdout.line` function in the [Stdout](https://www.roc-lang.org/packages/basic-cli/Stdout#line) module documentation.
### [Package Modules](#interface-modules) {#interface-modules}
-\[This part of the tutorial has not been written yet. Coming soon!\]
+Package modules enable Roc code to be easily re-used and shared. This is achieved by organizing code into different Interface modules and then including these in the `exposes` field of the package file structure, `package "name" exposes [ MyInterface ] packages {}`. The modules that are listed in the `exposes` field are then available for use in applications, platforms, or other packages. Internal modules that are not listed will be unavailable for use outside of the package.
See [Parser Package](https://github.com/roc-lang/roc/tree/main/examples/parser/package) for an example.
+Package documentation can be generated using the Roc cli with `roc docs /package/*.roc`.
+
+Build a package for distribution with `roc build --bundle .tar.br /package/main.roc`. This will create a single tarball that can then be easily shared online using a URL.
+
+You can import a package that is available either locally, or from a URL into a Roc application or platform. This is achieved by specifying the package in the `packages` section of the application or platform file structure. For example, `packages { .., parser: "app "cli-tutorial" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } - imports [pf.Stdout] - provides [main] to pf +```roc +app "cli-tutorial" + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } + imports [pf.Stdout] + provides [main] to pf -main = - Stdout.line "Hello, World!" -+main = + Stdout.line "Hello, World!" +``` The `Stdout.line` function takes a `Str` and writes it to [standard output](
Stdout.line : Str -> Task {} * -+```roc +Stdout.line : Str -> Task {} * +``` A `Task` represents an _effect_; an interaction with state outside your Roc program, such as the terminal's standard output, or a file. @@ -1439,20 +1488,22 @@ When we set `main` to be a `Task`, the task will get run when we run our program In contrast, `Stdin.line` produces a `Str` when it finishes reading from [standard input](
Stdin.line : Task Str *
-
+```roc
+Stdin.line : Task Str *
+```
Let's change `main` to read a line from `stdin`, and then print it back out again:
-app "cli-tutorial" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } - imports [pf.Stdout, pf.Stdin, pf.Task] - provides [main] to pf +```roc +app "cli-tutorial" + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.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)" -+main = + Task.await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` 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. @@ -1460,37 +1511,41 @@ The `Task.await` function combines two tasks into one bigger `Task` which first The type of `Task.await` is: -
Task.await : Task a err, (a -> Task b err) -> Task b err
-
+```roc
+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 `\text -> ...` callback function here:
-\text -> - Stdout.line "You just entered: \(text)" -+```roc +\text -> + Stdout.line "You just entered: \(text)" +``` 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`. For example, we can print a prompt before we pause to read from `stdin`, so it no longer looks like the program isn't doing anything when we start it up: -
task = - Task.await (Stdout.line "Type something press Enter:") \_ -> - Task.await Stdin.line \text -> - Stdout.line "You just entered: \(text)" -+```roc +task = + Task.await (Stdout.line "Type something press Enter:") \_ -> + Task.await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` This works, but we can make it a little nicer to read. Let's change it to the following: -
app "cli-tutorial" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } - imports [pf.Stdout, pf.Stdin, pf.Task.{ await }] - provides [main] to pf +```roc +app "cli-tutorial" + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.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)" -+main = + await (Stdout.line "Type something press Enter:") \_ -> + await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` 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`. @@ -1498,27 +1553,30 @@ It's most common in Roc to call functions from other modules in a _qualified_ wa Speaking of calling `await` repeatedly, if we keep calling it more and more on this code, we'll end up doing a lot of indenting. If we'd rather not indent so much, we can rewrite `task` into this style which looks different but does the same thing: -
task = - _ <- await (Stdout.line "Type something press Enter:") - text <- await Stdin.line +```roc +task = + _ <- await (Stdout.line "Type something press Enter:") + text <- await Stdin.line - Stdout.line "You just entered: \(text)" -+ Stdout.line "You just entered: \(text)" +``` -This `<-` syntax is called _backpassing_. The `<-` is a way to define an anonymous function, just like `\ … ->` is. +This `<-` syntax is called _backpassing_. The `<-` is a way to define an anonymous function, just like `\ ... ->` is. Here, we're using backpassing to define two anonymous functions. Here's one of them: -
text <- +```roc +text <- -Stdout.line "You just entered: \(text)" -+Stdout.line "You just entered: \(text)" +``` 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: -
\text -> - Stdout.line "You just entered: \(text)" -+```roc +\text -> + Stdout.line "You just entered: \(text)" +``` These two anonymous functions are the same, just defined using different syntax. @@ -1528,43 +1586,48 @@ Let's look at these two complete expressions side by side. They are both saying Here's the original: -
await Stdin.line \text -> - Stdout.line "You just entered: \(text)" -+```roc +await Stdin.line \text -> + Stdout.line "You just entered: \(text)" +``` And here's the equivalent expression with backpassing syntax: -
text <- await Stdin.line +```roc +text <- await Stdin.line -Stdout.line "You just entered: \(text)" -+Stdout.line "You just entered: \(text)" +``` Here's the other function we're defining with backpassing: -
_ <- -text <- await Stdin.line +```roc +_ <- +text <- await Stdin.line -Stdout.line "You just entered: \(text)" -+Stdout.line "You just entered: \(text)" +``` 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 \text -> + Stdout.line "You just entered: \(text)" +``` 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. 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: -
task = - _ <- await (Stdout.line "Type something press Enter:") - text <- await Stdin.line +```roc +task = + _ <- await (Stdout.line "Type something press Enter:") + text <- await Stdin.line - Stdout.line "You just entered: \(text)" -+ Stdout.line "You just entered: \(text)" +``` This way, it reads like a series of instructions: @@ -1590,9 +1653,10 @@ Here are some concepts you likely won't need as a beginner, but may want to know Let's say I write a function which takes a record with a `firstName` and `lastName` field, and puts them together with a space in between: -
fullName = \user -> - "\(user.firstName) \(user.lastName)" -+```roc +fullName = \user -> + "\(user.firstName) \(user.lastName)" +``` 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: @@ -1606,16 +1670,19 @@ In contrast, a _closed record_ is one that requires an exact set of fields (and If we add a type annotation to this `fullName` function, we can choose to have it accept either an open record or a closed record: -
# Closed record
-fullName : { firstName : Str, lastName : Str } -> Str
-fullName = \user ->
- "\(user.firstName) \(user.lastName)"
-
-# Open record (because of the `*`)
-fullName : { firstName : Str, lastName : Str }* -> Str
-fullName = \user ->
- "\(user.firstName) \(user.lastName)"
-
+```roc
+# Closed record
+fullName : { firstName : Str, lastName : Str } -> Str
+fullName = \user ->
+ "\(user.firstName) \(user.lastName)"
+```
+
+```roc
+# Open record (because of the `*`)
+fullName : { firstName : Str, lastName : Str }* -> Str
+fullName = \user ->
+ "\(user.firstName) \(user.lastName)"
+```
The `*` in the type `{ firstName : Str, lastName : Str }*` is what makes it an open record type. This `*` is the _wildcard type_ we saw earlier with empty lists. (An empty list has the type `List *`, in contrast to something like `List Str` which is a list of strings.)
@@ -1627,10 +1694,11 @@ If the type variable in a record type is a `*` (such as in `{ first : Str, last
The type variable can also be a named type variable, like so:
-addHttps : { url : Str }a -> { url : Str }a
-addHttps = \record ->
- { record & url: "https://\(record.url)" }
-
+```roc
+addHttps : { url : Str }a -> { url : Str }a
+addHttps = \record ->
+ { record & url: "https://\(record.url)" }
+```
This function uses _constrained records_ in its type. The annotation is saying:
@@ -1658,53 +1726,61 @@ You can add type annotations to make record types less flexible than what the co
If you like, you can always annotate your functions as accepting open records. However, in practice this may not always be the nicest choice. For example, let's say you have a `User` type alias, like so:
-User : {
- email : Str,
- firstName : Str,
- lastName : Str,
+```roc
+User : {
+ email : Str,
+ firstName : Str,
+ lastName : Str,
}
-
+```
This defines `User` to be a closed record, which in practice is the most common way records named `User` tend to be defined.
If you want to have a function take a `User`, you might write its type like so:
-isValid : User -> Bool+```roc +isValid : User -> Bool +``` If you want to have a function return a `User`, you might write its type like so: -
userFromEmail : Str -> User -+```roc +userFromEmail : Str -> User +``` A function which takes a user and returns a user might look like this: -
capitalizeNames : User -> User -+```roc +capitalizeNames : User -> User +``` This is a perfectly reasonable way to write all of these functions. However, I might decide that I really want the `isValid` function to take an open record; a record with _at least_ the fields of this `User` record, but possibly others as well. -Since open records have a type variable (like `*` in `{ email: Str }*` or `a` in `{ email: Str }a -> { email: Str }a`), in order to do this I'd need to add a type variable to the `User` type alias: +Since open records have a type variable (like `*` in `{ email : Str }*` or `a` in `{ email : Str }a -> { email : Str }a`), in order to do this I'd need to add a type variable to the `User` type alias: -
User a : { - email : Str - firstName : Str - lastName : Str +```roc +User a : { + email : Str + firstName : Str + lastName : Str }a -+``` Notice that the `a` type variable appears not only in `User a` but also in `}a` at the end of the record type! Using `User a` type alias, I can still write the same three functions, but now their types need to look different. This is what the first one would look like: -
isValid : User * -> Bool -+```roc +isValid : User * -> Bool +``` -Here, the `User *` type alias substitutes `*` for the type variable `a` in the type alias, which takes it from `{ email : Str, … }a` to `{ email : Str, … }*`. Now I can pass it any record that has at least the fields in `User`, and possibly others as well, which was my goal. +Here, the `User *` type alias substitutes `*` for the type variable `a` in the type alias, which takes it from `{ email : Str, ... }a` to `{ email : Str, ... }*`. Now I can pass it any record that has at least the fields in `User`, and possibly others as well, which was my goal. -
userFromEmail : Str -> User {} -+```roc +userFromEmail : Str -> User {} +``` -Here, the `User {}` type alias substitutes `{}` for the type variable `a` in the type alias, which takes it from `{ email : Str, … }a` to `{ email : Str, … }{}`. As noted earlier, this is another way to specify a closed record: putting a `{}` after it, in the same place that you'd find a `*` in an open record. +Here, the `User {}` type alias substitutes `{}` for the type variable `a` in the type alias, which takes it from `{ email : Str, ... }a` to `{ email : Str, ... }{}`. As noted earlier, this is another way to specify a closed record: putting a `{}` after it, in the same place that you'd find a `*` in an open record. > **Aside:** This works because you can form new record types by replacing the type variable with other record types. For example, `{ a : Str, b : Str }` can also be written `{ a : Str }{ b : Str }`. You can chain these more than once, e.g. `{ a : Str }{ b : Str }{ c : Str, d : Str }`. This is more useful when used with type annotations; for example, `{ a : Str, b : Str }User` describes a closed record consisting of all the fields in the closed record `User`, plus `a : Str` and `b : Str`. @@ -1712,15 +1788,17 @@ This function still returns the same record as it always did, it just needs to b The third function might need to use a named type variable: -
capitalizeNames : User a -> User a -+```roc +capitalizeNames : User a -> User a +``` If this function does a record update on the given user, and returns that - for example, if its definition were `capitalizeNames = \user -> { user & email: "blah" }` - then it needs to use the same named type variable for both the argument and return value. However, if returns a new `User` that it created from scratch, then its type could instead be: -
capitalizeNames : User * -> User {} -+```roc +capitalizeNames : User * -> User {} +``` This says that it takes a record with at least the fields specified in the `User` type alias, and possibly others...and then returns a record with exactly the fields specified in the `User` type alias, and no others. @@ -1736,22 +1814,24 @@ The _open tag union_ (or _open union_ for short) `[Foo Str, Bar Bool]*` represen Because an open union represents possibilities that are impossible to know ahead of time, any `when` I use on a `[Foo Str, Bar Bool]*` value must include a catch-all `_ ->` branch. Otherwise, if one of those unknown tags were to come up, the `when` would not know what to do with it! For example: -
example : [Foo Str, Bar Bool]* -> Bool
-example = \tag ->
- when tag is
- Foo str -> Str.isEmpty str
- Bar bool -> bool
- _ -> Bool.false
-
+```roc
+example : [Foo Str, Bar Bool]* -> Bool
+example = \tag ->
+ when tag is
+ Foo str -> Str.isEmpty str
+ Bar bool -> bool
+ _ -> Bool.false
+```
In contrast, a _closed tag union_ (or _closed union_) like `[Foo Str, Bar Bool]` (without the `*`) represents the set of all possible tags. If I use a `when` on one of these, I can match on `Foo` only and then on `Bar` only, with no need for a catch-all branch. For example:
-example : [Foo Str, Bar Bool] -> Bool -example = \tag -> - when tag is - Foo str -> Str.isEmpty str - Bar bool -> bool -+```roc +example : [Foo Str, Bar Bool] -> Bool +example = \tag -> + when tag is + Foo str -> Str.isEmpty str + Bar bool -> bool +``` If we were to remove the type annotations from the previous two code examples, Roc would infer the same types for them anyway. @@ -1769,19 +1849,21 @@ When we make a new record, it's inferred to be a closed record. For example, in This is because open unions can accumulate additional tags based on how they're used in the program, whereas closed unions cannot. For example, let's look at this conditional: -
if x > 5 then - "foo" -else - 7 -+```roc +if x > 5 then + "foo" +else + 7 +``` This will be a type mismatch because the two branches have incompatible types. Strings and numbers are not type-compatible! Now let's look at another example: -
if x > 5 then
- Ok "foo"
-else
- Err "bar"
-
+```roc
+if x > 5 then
+ Ok "foo"
+else
+ 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.
@@ -1791,14 +1873,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):
-- `[Ok Str]* -> Bool`
-- `[Ok Str] -> Bool`
-- `[Ok Str, Err Bool]* -> Bool`
-- `[Ok Str, Err Bool] -> Bool`
-- `[Ok Str, Err Bool, Whatever]* -> Bool`
-- `[Ok Str, Err Bool, Whatever] -> Bool`
-- `Result Str Bool -> Bool`
-- `[Err Bool, Whatever]* -> Bool`
+| Function Type | Can it receive `[Ok Str]*`? |
+| --------------------------------------- | --------------------------- |
+| `[Ok Str]* -> Bool` | Yes |
+| `[Ok Str] -> 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 |
+| `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.
@@ -1819,29 +1903,33 @@ In summary, here's a way to think about the difference between open unions in a
Earlier we saw these two examples, one with an open tag union and the other with a closed one:
-example : [Foo Str, Bar Bool]* -> Bool
-example = \tag ->
- when tag is
- Foo str -> Str.isEmpty str
- Bar bool -> bool
- _ -> Bool.false
-
-example : [Foo Str, Bar Bool] -> Bool
-example = \tag ->
- when tag is
- Foo str -> Str.isEmpty str
- Bar bool -> bool
-
+```roc
+example : [Foo Str, Bar Bool]* -> Bool
+example = \tag ->
+ when tag is
+ Foo str -> Str.isEmpty str
+ Bar bool -> bool
+ _ -> Bool.false
+```
+
+```roc
+example : [Foo Str, Bar Bool] -> Bool
+example = \tag ->
+ when tag is
+ Foo str -> Str.isEmpty str
+ Bar bool -> bool
+```
Similarly to how there are open records with a `*`, closed records with nothing, and constrained records with a named type variable, we can also have _constrained tag unions_ with a named type variable. Here's an example:
-example : [Foo Str, Bar Bool]a -> [Foo Str, Bar Bool]a -example = \tag -> - when tag is - Foo str -> Bar (Str.isEmpty str) - Bar bool -> Bar Bool.false - other -> other -+```roc +example : [Foo Str, Bar Bool]a -> [Foo Str, Bar Bool]a +example = \tag -> + when tag is + Foo str -> Bar (Str.isEmpty str) + Bar bool -> Bar Bool.false + other -> other +``` This type says that the `example` function will take either a `Foo Str` tag, or a `Bar Bool` tag, or possibly another tag we don't know about at compile time and it also says that the function's return type is the same as the type of its argument. @@ -1868,91 +1956,25 @@ For this reason, any time you see a function that only runs a `when` on its only Here are various Roc expressions involving operators, and what they desugar to. -
-
Expression | -Desugars to | -
---|---|
a + b |
- Num.add a b |
-
a - b |
- Num.sub a b |
-
a * b |
- Num.mul a b |
-
a / b |
- Num.div a b |
-
a // b |
- Num.divTrunc a b |
-
a ^ b |
- Num.pow a b |
-
a % b |
- Num.rem a b |
-
a >> b |
- Num.shr a b |
-
a << b |
- Num.shl a b |
-
-a |
- Num.neg a |
-
-f x y |
- Num.neg (f x y) |
-
a == b |
- Bool.isEq a b |
-
a != b |
- Bool.isNotEq a b |
-
a && b |
- Bool.and a b |
-
a || b |
- Bool.or a b |
-
!a |
- Bool.not a |
-
!f x y |
- Bool.not (f x y) |
-
a |> b
- | b a |
-
a b c |> f x y |
- f (a b c) x y |
-
a \|\| b
| `Bool.or a b` |
+| `!a` | `Bool.not a` |
+| `!f x y` | `Bool.not (f x y)` |
+| a \|> b
| `b a` |
+| a b c \|> f x y
| `f (a b c) x y` |
+
diff --git a/www/generate_tutorial/src/tutorial.roc b/www/generate_tutorial/src/tutorial.roc
index f7939210f0..f8bc3117ef 100644
--- a/www/generate_tutorial/src/tutorial.roc
+++ b/www/generate_tutorial/src/tutorial.roc
@@ -95,7 +95,7 @@ tutorialIntro =
label [id "tutorial-toc-toggle-label", for "tutorial-toc-toggle"] [text "contents"],
],
p [] [text "Welcome to Roc!"],
- p [] [text "This tutorial will teach you how to build Roc applications. Along the way, you'll learn how to write tests, use the REPL, and much more!"],
+ p [] [text "This tutorial will teach you how to build Roc applications. Along the way, you'll learn how to write tests, use the REPL, and more!"],
],
section [] [
h2 [ id "installation" ] [
diff --git a/www/public/site.css b/www/public/site.css
index c9f2d9128e..5c4c1a1356 100644
--- a/www/public/site.css
+++ b/www/public/site.css
@@ -1,23 +1,31 @@
:root {
+ /* WCAG AAA Compliant colors */
+ --code-bg: #f4f8f9;
+ --gray: #717171;
+ --orange: #BF5000;
+ --green: #0B8400;
+ --cyan: #067C94;
+ --blue: #05006d;
+ --magenta: #a20031;
+
--body-max-width: 900px;
--text-color: #121212;
--top-bar-bg: #222;
--top-bar-fg: #eee;
- --top-bar-logo-hover: hsl(258, 73%, 70%);
- --header-link-color: #17b9b0;
+ --top-bar-logo-hover: #8055E4;
+ --header-link-color: #107F79;
--header-link-hover: #222;
- --link-color: hsl(258, 73%, 58%);
- --h1-color: hsl(258, 73%, 70%);
+ --link-color: #7546e2;
+ --h1-color: #8055E4;
--repl-prompt: #0064ff;
--body-bg: #fff;
- --code-bg: #e7e7e7;
- --code-snippet-bg: #fcfcfc;
- --code-snippet-border: #bbb;
+
--code-color: #303030;
- --toc-background: #f3f3f3;
- --toc-border: #ddd;
+ --toc-background: var(--code-bg);
+ --toc-border: var(--gray);
--toc-search-bg: #fcfcfc;
- --toc-search-border: #bbb;
+ --toc-search-border: var(--gray);
+ --font-mono: "Source Code Pro", monospace;
}
html {
@@ -26,11 +34,13 @@ html {
color: var(--text-color);
}
-html, #tutorial-toc-toggle-label {
- font-family: 'Lato', sans-serif;
+html,
+#tutorial-toc-toggle-label {
+ font-family: "Lato", sans-serif;
}
-html, body {
+html,
+body {
margin: 0;
padding: 0;
}
@@ -104,32 +114,46 @@ main {
padding: 0 12px;
}
-code {
- margin: 0 0.2rem;
- background: var(--code-bg);
- padding: 0.1rem 0.5rem;
+code,
+samp {
+ font-family: var(--font-mono);
+ color: var(--code-color);
+ background-color: var(--code-bg);
+ display: inline-block;
+}
+
+p code,
+td code,
+li code,
+th code,
+samp {
+ padding: 0 8px;
border-radius: 4px;
}
-code, samp {
- font-family: 'Source Code Pro', monospace;
- color: var(--code-color);
+code a,
+a code {
+ text-decoration: none;
+ color: var(--code-link-color);
+ background: none;
+ padding: 0;
}
-.code-snippet, samp {
- display: block;
- overflow: auto;
- white-space: pre;
- padding: 10px 16px;
- background-color: var(--code-snippet-bg);
- margin-bottom: 16px;
- font-size: 1.2rem;
- line-height: 1.76rem;
- border: 1px solid var(--code-snippet-border);
+code a:visited,
+a:visited code {
+ color: var(--code-link-color);
}
pre {
- white-space: pre-wrap;
+ margin-bottom: 16px;
+ padding: 8px 16px;
+ box-sizing: border-box;
+ border-radius: 8px;
+ background-color: var(--code-bg);
+ overflow-x: auto;
+ font-size: 1.2rem;
+ line-height: 1.76em;
+ white-space: pre;
}
.repl-prompt:before {
@@ -139,50 +163,7 @@ pre {
}
.repl-err {
- color: #c20000;
-}
-
-samp .ann {
- /* type annotation - purple in the repl */
- color: #f384fd;
-}
-
-samp .autovar {
- /* automatic variable names in the repl, e.g. # val1 */
- color: #338545;
-}
-
-samp .kw {
- /* language keywords, e.g. `if`*/
- color: #004cc2;
-}
-
-samp .op, samp .paren, samp .brace, samp .comma, samp .colon {
- /* operators, e.g. `+` */
- color: #c20000;
-}
-
-samp .number {
- /* number literals */
- color: #158086;
-}
-
-samp .str {
- /* string literals */
- color: #158086;
-}
-
-samp .str-esc, samp .str-interp {
- /* escapes inside string literals, e.g. \t */
- color: #3474db;
-}
-
-samp .dim {
- opacity: 0.55;
-}
-
-samp .comment {
- color: #338545;
+ color: var(--magenta);
}
/* Tables */
@@ -217,7 +198,8 @@ td:last-child {
width: 100%;
}
-#integer-types th:first-of-type, #integer-types td:first-of-type {
+#integer-types th:first-of-type,
+#integer-types td:first-of-type {
text-align: right;
}
@@ -225,16 +207,11 @@ td:last-child {
background-color: inherit;
}
-#integer-types th:last-of-type, #integer-types td:last-of-type {
+#integer-types th:last-of-type,
+#integer-types td:last-of-type {
text-align: left;
}
-@media (prefers-color-scheme: dark) {
- table, tr, th, td {
- border-color: #3b3f47;
- }
-}
-
/* Tutorial Specific */
#tutorial-start {
@@ -248,8 +225,8 @@ td:last-child {
#tutorial-toc {
margin-top: 18px;
- background: var(--toc-background);
- border: 1px solid var(--toc-border);
+ background: var(--code-bg);
+ border-radius: 8px;
padding: 12px 24px;
margin-left: 64px;
}
@@ -270,7 +247,6 @@ td:last-child {
}
#tutorial-toc h2 {
- color: #686868;
font-family: inherit;
font-size: 2em;
text-shadow: none;
@@ -290,11 +266,14 @@ td:last-child {
font-size: inherit;
}
-#tutorial-toc-toggle, #tutorial-toc-toggle-label, #close-tutorial-toc {
+#tutorial-toc-toggle,
+#tutorial-toc-toggle-label,
+#close-tutorial-toc {
display: none; /* This may be overridden on mobile-friendly screen widths */
}
-#tutorial-toc-toggle, #tutorial-toc-toggle-label {
+#tutorial-toc-toggle,
+#tutorial-toc-toggle-label {
font-size: 1.1rem;
float: right;
}
@@ -307,21 +286,38 @@ td:last-child {
padding: 12px 24px;
}
-p, aside, li, footer {
+p,
+aside,
+li,
+footer {
font-size: 1.2rem;
line-height: 1.85rem;
}
/* Mobile-friendly screen width */
@media only screen and (max-device-width: 480px) and (orientation: portrait) {
- p, aside, li, footer, code, samp, .code-snippet {
+ p,
+ aside,
+ li,
+ footer,
+ code,
+ samp,
+ .code-snippet {
font-size: 16px;
}
- h1 code, h2 code, h3 code, h4 code, h5 code {
+ h1 code,
+ h2 code,
+ h3 code,
+ h4 code,
+ h5 code {
font-size: inherit;
}
+ code {
+ white-space: normal;
+ }
+
#tutorial-toc-toggle-label,
#close-tutorial-toc {
display: block;
@@ -341,10 +337,21 @@ p, aside, li, footer {
padding-right: 120px;
border: 0;
}
- h1, h2, h3, h4, h5, h6, p, code {
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6,
+ p,
+ code {
word-break: break-word !important;
}
- h1, h2, h3, h4, h5 {
+ h1,
+ h2,
+ h3,
+ h4,
+ h5 {
line-height: 1.2em !important;
font-size: 2rem !important;
}
@@ -357,12 +364,14 @@ p, aside, li, footer {
/* Used on on the different-names page. */
-th, td {
+th,
+td {
text-align: left;
padding-right: 24px;
}
-#different-names-body a, #different-names-body li {
+#different-names-body a,
+#different-names-body li {
font-family: monospace;
font-size: 16px;
}
@@ -381,32 +390,50 @@ th, td {
list-style-type: none;
}
-h1, h2, h3, h4, h5 {
- font-family: 'Permanent Marker';
+h1,
+h2,
+h3,
+h4,
+h5 {
+ font-family: "Permanent Marker";
line-height: 1rem;
margin-top: 1.75rem;
margin-bottom: 0;
}
-#tutorial-toc-toggle-label, #close-tutorial-toc {
+#tutorial-toc-toggle-label,
+#close-tutorial-toc {
color: var(--header-link-color);
}
-#tutorial-toc-toggle-label:hover, #close-tutorial-toc:hover {
+#tutorial-toc-toggle-label:hover,
+#close-tutorial-toc:hover {
color: var(--header-link-hover);
cursor: pointer;
}
-h1 a, h2 a, h3 a, h4 a, h5 a {
+h1 a,
+h2 a,
+h3 a,
+h4 a,
+h5 a {
color: var(--header-link-color);
}
-h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover {
+h1 a:hover,
+h2 a:hover,
+h3 a:hover,
+h4 a:hover,
+h5 a:hover {
text-decoration: none;
color: var(--header-link-hover);
}
-h1 code, h2 code, h3 code, h4 code, h5 code {
+h1 code,
+h2 code,
+h3 code,
+h4 code,
+h5 code {
color: inherit;
background-color: inherit;
padding: 0;
@@ -440,112 +467,154 @@ h4 {
}
@font-face {
- font-family: 'Permanent Marker';
- font-style: normal;
- font-weight: 400;
- src: url('/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff2') format('woff2'),
- url('/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff') format('woff');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ font-family: "Permanent Marker";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff2")
+ format("woff2"),
+ url("/fonts/permanent-marker-v16-latin/permanent-marker-v16-latin-regular.woff")
+ format("woff");
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
+ U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
+ U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
- font-family: 'Merriweather';
- font-style: normal;
- font-weight: 400;
- src: url('/fonts/merriweather-v30-latin-ext_latin/merriweather-v30-latin-ext_latin-regular.woff2') format('woff2'),
- url('/fonts/merriweather-v30-latin-ext_latin/merriweather-v30-latin-ext_latin-regular.woff') format('woff');
- unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+ font-family: "Merriweather";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/merriweather-v30-latin-ext_latin/merriweather-v30-latin-ext_latin-regular.woff2")
+ format("woff2"),
+ url("/fonts/merriweather-v30-latin-ext_latin/merriweather-v30-latin-ext_latin-regular.woff")
+ format("woff");
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
+ U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
- font-family: 'Merriweather';
- font-style: normal;
- font-weight: 400;
- src: url('/fonts/merriweather-v30-latin/merriweather-v30-latin-regular.woff2') format('woff2'),
- url('/fonts/merriweather-v30-latin/merriweather-v30-latin-regular.woff') format('woff');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ font-family: "Merriweather";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/merriweather-v30-latin/merriweather-v30-latin-regular.woff2")
+ format("woff2"),
+ url("/fonts/merriweather-v30-latin/merriweather-v30-latin-regular.woff")
+ format("woff");
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
+ U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
+ U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
- font-family: 'Merriweather Sans';
- font-style: normal;
- font-weight: 400;
- src: url('/fonts/merriweather-sans-v22-latin-ext_latin/merriweather-sans-v22-latin-ext_latin-regular.woff2') format('woff2'),
- url('/fonts/merriweather-sans-v22-latin-ext_latin/merriweather-sans-v22-latin-ext_latin-regular.woff') format('woff');
- unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+ font-family: "Merriweather Sans";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/merriweather-sans-v22-latin-ext_latin/merriweather-sans-v22-latin-ext_latin-regular.woff2")
+ format("woff2"),
+ url("/fonts/merriweather-sans-v22-latin-ext_latin/merriweather-sans-v22-latin-ext_latin-regular.woff")
+ format("woff");
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
+ U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
- font-family: 'Merriweather Sans';
- font-style: normal;
- font-weight: 400;
- src: url('/fonts/merriweather-sans-v22-latin/merriweather-sans-v22-latin-regular.woff2') format('woff2'),
- url('/fonts/merriweather-sans-v22-latin/merriweather-sans-v22-latin-regular.woff') format('woff');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ font-family: "Merriweather Sans";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/merriweather-sans-v22-latin/merriweather-sans-v22-latin-regular.woff2")
+ format("woff2"),
+ url("/fonts/merriweather-sans-v22-latin/merriweather-sans-v22-latin-regular.woff")
+ format("woff");
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
+ U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
+ U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
- font-family: 'Lato';
- font-style: normal;
- font-weight: 400;
- src: url('/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff2') format('woff2'),
- url('/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff') format('woff');
- unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+ font-family: "Lato";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff2")
+ format("woff2"),
+ url("/fonts/lato-v23-latin-ext_latin/lato-v23-latin-ext_latin-regular.woff")
+ format("woff");
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
+ U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
- font-family: 'Lato';
- font-style: normal;
- font-weight: 400;
- src: url('/fonts/lato-v23-latin/lato-v23-latin-regular.woff2') format('woff2'),
- url('/fonts/lato-v23-latin/lato-v23-latin-regular.woff') format('woff');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ font-family: "Lato";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/lato-v23-latin/lato-v23-latin-regular.woff2")
+ format("woff2"),
+ url("/fonts/lato-v23-latin/lato-v23-latin-regular.woff") format("woff");
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
+ U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
+ U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
- font-family: 'Source Code Pro';
- font-style: normal;
- font-weight: 400;
- src: url('/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff2') format('woff2'),
- url('/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff') format('woff');
- unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
+ font-family: "Source Code Pro";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff2")
+ format("woff2"),
+ url("/fonts/source-code-pro-v22-latin-ext_latin/source-code-pro-v22-latin-ext_latin-regular.woff")
+ format("woff");
+ unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
+ U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
- font-family: 'Source Code Pro';
- font-style: normal;
- font-weight: 400;
- src: url('/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff2') format('woff2'),
- url('/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff') format('woff');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ font-family: "Source Code Pro";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff2")
+ format("woff2"),
+ url("/fonts/source-code-pro-v22-latin/source-code-pro-v22-latin-regular.woff")
+ format("woff");
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
+ U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
+ U+2215, U+FEFF, U+FFFD;
}
@media (prefers-color-scheme: dark) {
:root {
+ /* WCAG AAA Compliant colors */
+ --code-bg: #202746;
+ --gray: #b6b6b6;
+ --orange: #fd6e08;
+ --green: #8ecc88;
+ --cyan: #12c9be;
+ --blue: #b1afdf;
+ --magenta: #f39bac;
+
--text-color: #cdcdcd;
--top-bar-bg: #2a2a2a;
- --header-link-color: hsl(258, 73%, 70%);
+ --header-link-color: #9C7CEA;
--header-link-hover: #ddd;
--h1-color: #1bc6bd;
--link-color: #1bc6bd;
- --repl-prompt: var(--link-color);
+ --repl-prompt: #1bc6bd;
--body-bg: #0e0e0f;
- --code-bg: #303030;
- --code-snippet-bg: #1a1a1a;
--code-snippet-border: #444;
--code-color: #cdcdcd;
- --toc-background: var(--code-snippet-bg);
+ --toc-background: var(--code-bg);
--toc-border: var(--code-snippet-border);
--toc-search-bg: #333;
- --toc-search-border: #555;
+ --toc-search-border: var(--gray);
}
- h1, h2, h3, h4, h5 {
+ h1,
+ h2,
+ h3,
+ h4,
+ h5 {
text-shadow: none;
}
@@ -553,32 +622,54 @@ h4 {
scrollbar-color: #444444 #2f2f2f;
}
- samp .kw {
- /* language keywords, e.g. `if` */
- color: #00c3ff;
- }
-
- samp .colon, samp .op, samp .paren, samp .brace, samp .comma, .repl-err {
- /* operators, e.g. `+` */
- color: #ff3966;
- }
-
- samp .str {
- /* string literals */
- color: var(--link-color);
- }
-
- code .str {
- /* string literals */
- color: var(--link-color);
- }
-
- /* autovar = automatic variable names in the repl, e.g. # val1 */
- samp .comment, samp .autovar {
- color: #4ed86c;
- }
-
- samp .number {
- color: #00c3ff;
+ table,
+ tr,
+ th,
+ td {
+ border-color: var(--gray);
}
}
+
+/* Comments `#` and Documentation comments `##` */
+samp .comment,
+code .comment {
+ color: var(--green);
+}
+/* Number, String, Tag, Type literals */
+samp .literal,
+code .literal {
+ color: var(--cyan);
+}
+/* Keywords and punctuation */
+samp .kw,
+code .kw {
+ color: var(--magenta);
+}
+/* Operators */
+samp .op,
+code .op {
+ color: var(--orange);
+}
+
+/* Delimieters */
+samp .delimeter,
+code .delimeter {
+ color: var(--gray);
+}
+
+/* Variables modules and field names */
+samp .lowerident,
+code .lowerident {
+ color: var(--blue);
+}
+
+/* Types, Tags, and Modules */
+samp .upperident,
+code .upperident {
+ color: var(--green);
+}
+
+samp .dim,
+code .dim {
+ opacity: 0.55;
+}