diff --git a/www/generate_tutorial/src/input/tutorial.md b/www/generate_tutorial/src/input/tutorial.md index 12e2068d91..eeb7c56dac 100644 --- a/www/generate_tutorial/src/input/tutorial.md +++ b/www/generate_tutorial/src/input/tutorial.md @@ -935,10 +935,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.
@@ -946,10 +947,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. @@ -957,36 +959,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 }`." @@ -994,9 +999,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 }`. @@ -1004,8 +1010,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. @@ -1015,12 +1022,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`? @@ -1028,12 +1036,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!
@@ -1047,47 +1060,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: -
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. @@ -1100,16 +1118,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. @@ -1143,57 +1162,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}
@@ -1267,12 +1249,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. @@ -1282,11 +1265,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. @@ -1302,43 +1286,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 `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.
@@ -1381,11 +1368,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.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.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.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.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.
@@ -1393,10 +1381,11 @@ 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.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.tar.br" } - imports [pf.Stdout] - provides [main] to pf -+```roc +packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.0/8tCohJeXMBUnjo_zdMq0jSaqdYoCWJkWazBd4wa8cQU.tar.br" } + imports [pf.Stdout] + provides [main] to pf +``` The `packages { pf: "https://…tar.br" }` part says three things: @@ -1408,8 +1397,9 @@ The `imports [pf.Stdout]` line says that we want to import the `Stdout` module f This import has a direct interaction with our definition of `main`. Let's look at that again: -
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`. @@ -1417,8 +1407,9 @@ When we write `imports [pf.Stdout]`, it specifies that the `Stdout` module comes 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.
diff --git a/www/public/site.css b/www/public/site.css
index 4c4d32dcf9..61ac72aab1 100644
--- a/www/public/site.css
+++ b/www/public/site.css
@@ -153,12 +153,12 @@ samp .autovar {
color: #338545;
}
-samp .kw {
+samp .kw, samp .pipe, samp .backslash, samp .arrow, samp .backpass, samp .brace, samp .bracket, samp .paren {
/* language keywords, e.g. `if`*/
color: #004cc2;
}
-samp .op, samp .paren, samp .brace, samp .comma, samp .colon {
+samp .op, samp .comma, samp .qmark, samp .bar, samp .colon {
/* operators, e.g. `+` */
color: #c20000;
}
@@ -558,12 +558,12 @@ h4 {
scrollbar-color: #444444 #2f2f2f;
}
- samp .kw {
+ samp .kw, samp .pipe, samp .backslash, samp .arrow, samp .backpass, samp .brace, samp .bracket, samp .paren {
/* language keywords, e.g. `if` */
color: #00c3ff;
}
- samp .colon, samp .op, samp .paren, samp .brace, samp .comma, .repl-err {
+ samp .op, samp .comma, samp .qmark, samp .bar, samp .colon {
/* operators, e.g. `+` */
color: #ff3966;
}