Update TUTORIAL and roc-for-elm-programmers

Changed formatting to remove spaces inside square braces
This commit is contained in:
Richard Feldman 2022-05-22 18:33:34 -04:00
parent 8fa80fa69b
commit 47f5015fdf
No known key found for this signature in database
GPG key ID: 7E4127D1E4241798
2 changed files with 171 additions and 159 deletions

View file

@ -116,8 +116,8 @@ Create a new file called `Hello.roc` and put this inside it:
```coffee
app "hello"
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
provides [ main ] to pf
imports [pf.Stdout]
provides [main] to pf
main = Stdout.line "I'm a Roc application!"
```
@ -580,7 +580,7 @@ suggest making a `when` branch that begins with something like `Custom descripti
Another thing we can do in Roc is to make a *list* of values. Here's an example:
```coffee
names = [ "Sam", "Lee", "Ari" ]
names = ["Sam", "Lee", "Ari"]
```
This is a list with three elements in it, all strings. We can add a fourth
@ -601,24 +601,24 @@ A common way to transform one list into another is to use `List.map`. Here's an
use it:
```coffee
List.map [ 1, 2, 3 ] \num -> num * 2
List.map [1, 2, 3] \num -> num * 2
```
This returns `[ 2, 4, 6 ]`. `List.map` takes two arguments:
This returns `[2, 4, 6]`. `List.map` takes two arguments:
1. An input list
2. A function that will be called on each element of that list
It then returns a list which it creates by calling the given function on each element in the input list.
In this example, `List.map` calls the function `\num -> num * 2` on each element in
`[ 1, 2, 3 ]` to get a new list of `[ 2, 4, 6 ]`.
`[1, 2, 3]` to get a new list of `[2, 4, 6]`.
We can also give `List.map` a named function, instead of an anonymous one:
For example, the `Num.isOdd` function returns `True` if it's given an odd number, and `False` otherwise.
So `Num.isOdd 5` returns `True` and `Num.isOdd 2` returns `False`.
So calling `List.map [ 1, 2, 3 ] Num.isOdd` returns a new list of `[ True, False, True ]`.
So calling `List.map [1, 2, 3] Num.isOdd` returns a new list of `[True, False, True]`.
### List element type compatibility
@ -627,13 +627,13 @@ an error at compile time. Here's a valid, and then an invalid example:
```coffee
# working example
List.map [ -1, 2, 3, -4 ] Num.isNegative
# returns [ True, False, False, True ]
List.map [-1, 2, 3, -4] Num.isNegative
# returns [True, False, False, True]
```
```coffee
# invalid example
List.map [ "A", "B", "C" ] Num.isNegative
List.map ["A", "B", "C"] Num.isNegative
# error: isNegative doesn't work on strings!
```
@ -643,12 +643,12 @@ list of numbers works, but doing the same with a list of strings doesn't work.
This wouldn't work either:
```coffee
List.map [ "A", "B", "C", 1, 2, 3 ] Num.isNegative
List.map ["A", "B", "C", 1, 2, 3] Num.isNegative
```
In fact, this wouldn't work for a more fundamental reason: 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 ]` -
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.
Ensuring all elements in a list share a type eliminates entire categories of problems.
@ -663,18 +663,18 @@ incompatibility (like `Num.negate` on a list of strings).
We can use tags with payloads to make a list that contains a mixture of different types. For example:
```coffee
List.map [ StrElem "A", StrElem "b", NumElem 1, StrElem "c", NumElem -3 ] \elem ->
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 [ True, False, False, False, True ]
# returns [True, False, False, False, True]
```
Compare this with the example from earlier, which caused a compile-time error:
```coffee
List.map [ "A", "B", "C", 1, 2, 3 ] Num.isNegative
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.
@ -689,13 +689,13 @@ more branches to the `when` to handle them appropriately.
Let's say I want to apply a tag to a bunch of elements in a list. For example:
```elm
List.map [ "a", "b", "c", ] \str -> Foo str
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:
```elm
List.map [ "a", "b", "c", ] Foo
List.map ["a", "b", "c"] Foo
```
These two versions compile to the same thing. As a convenience, Roc lets you specify
@ -709,22 +709,22 @@ something with it. Another is `List.any`, which returns `True` if calling the gi
in the list returns `True`:
```coffee
List.any [ 1, 2, 3 ] Num.isOdd
List.any [1, 2, 3] Num.isOdd
# returns True because 1 and 3 are odd
```
```coffee
List.any [ 1, 2, 3 ] Num.isNegative
List.any [1, 2, 3] Num.isNegative
# returns 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:
```coffee
List.all [ 1, 2, 3 ] Num.isOdd
List.all [1, 2, 3] Num.isOdd
# returns False because 2 is not odd
```
```coffee
List.all [ 1, 2, 3 ] Num.isPositive
List.all [1, 2, 3] Num.isPositive
# returns True because all of these are positive
```
@ -733,23 +733,23 @@ List.all [ 1, 2, 3 ] Num.isPositive
You can also drop elements from a list. One way is `List.dropAt` - for example:
```coffee
List.dropAt [ "Sam", "Lee", "Ari" ] 1
# drops the element at offset 1 ("Lee") and returns [ "Sam", "Ari" ]
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`.
```coffee
List.keepIf [ 1, 2, 3, 4, 5 ] Num.isEven
# returns [ 2, 4 ]
List.keepIf [1, 2, 3, 4, 5] Num.isEven
# returns [2, 4]
```
There's also `List.dropIf`, which does the reverse:
```coffee
List.dropIf [ 1, 2, 3, 4, 5 ] Num.isEven
# returns [ 1, 3, 5 ]
List.dropIf [1, 2, 3, 4, 5] Num.isEven
# returns [1, 3, 5]
```
### Custom operations that walk over a list
@ -758,13 +758,13 @@ You can make your own custom operations that walk over all the elements in a lis
Let's look at an example and then walk (ha!) through it.
```coffee
List.walk [ 1, 2, 3, 4, 5 ] { evens: [], odds: [] } \state, elem ->
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] }
```
`List.walk` walks through each element of the list, building up a state as it goes. At the end,
@ -772,7 +772,7 @@ it returns the final state - whatever it ended up being after processing the las
function it takes as its last argument accepts both the current state as well as the current list element
it's looking at, and then returns the new state based on whatever it decides to do with that element.
In this example, we walk over the list `[ 1, 2, 3, 4, 5 ]` and add each element to either the `evens` or `odds`
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 as well as a list of all the odd numbers.
@ -800,10 +800,10 @@ it takes a list and an index, and then returns the element at that index...if th
For example, what do each of these return?
```coffee
List.get [ "a", "b", "c" ] 1
List.get ["a", "b", "c"] 1
```
```coffee
List.get [ "a", "b", "c" ] 100
List.get ["a", "b", "c"] 100
```
The answer is that the first one returns `Ok "b"` and the second one returns `Err OutOfBounds`.
@ -813,7 +813,7 @@ the index is outside the bounds of that particular list.
Here's how calling `List.get` can look in practice:
```coffee
when List.get [ "a", "b", "c" ] index is
when List.get ["a", "b", "c"] index is
Ok str -> "I got this string: \(str)"
Err OutOfBounds -> "That index was out of bounds, sorry!"
```
@ -828,12 +828,12 @@ In fact, it's such a common pattern that there's a whole module called `Result`
Here are some examples of `Result` functions:
```coffee
Result.withDefault (List.get [ "a", "b", "c" ] 100) ""
Result.withDefault (List.get ["a", "b", "c"] 100) ""
# returns "" because that's the default we said to use if List.get returned an Err
```
```coffee
Result.isOk (List.get [ "a", "b", "c" ] 1)
Result.isOk (List.get ["a", "b", "c"] 1)
# returns True because `List.get` returned an `Ok` tag. (The payload gets ignored.)
# Note: There's a Result.isErr function that works similarly.
@ -846,37 +846,37 @@ style using the `|>` operator. Here are three examples of writing the same expre
compile to exactly the same thing, but two of them use the `|>` operator to change how the calls look.
```coffee
Result.withDefault (List.get [ "a", "b", "c" ] 1) ""
Result.withDefault (List.get ["a", "b", "c"] 1) ""
```
```coffee
List.get [ "a", "b", "c" ] 1
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
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:
```coffee
[ "a", "b", "c" ]
["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`."
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:
```coffee
List.append [ "a", "b", "c" ] "d"
List.append ["a", "b", "c"] "d"
```
```coffee
[ "a", "b", "c" ]
["a", "b", "c"]
|> List.append "d"
```
@ -970,7 +970,7 @@ you can also read `Musician : { firstName : Str, lastName : Str }` as "`Musician
We can also give type annotations to tag unions:
```coffee
colorFromStr : Str -> [ Red, Green, Yellow ]
colorFromStr : Str -> [Red, Green, Yellow]
colorFromStr = \string ->
when string is
"red" -> Red
@ -978,13 +978,13 @@ colorFromStr = \string ->
_ -> Yellow
```
You can read the type `[ Red, Green, Yellow ]` as "a tag union of the tags `Red`, `Green`, and `Yellow`."
You can read the type `[Red, Green, Yellow]` as "a tag union of the tags `Red`, `Green`, and `Yellow`."
When we annotate a list type, we have to specify the type of its elements:
```coffee
names : List Str
names = [ "Amy", "Simone", "Tarja" ]
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
@ -1001,7 +1001,7 @@ isEmpty : List * -> Bool
The `*` is a *wildcard type* - that is, a type that's compatible with any other type. `List *` is compatible
with any type of `List` - so, `List Str`, `List Bool`, and so on. So you can call
`List.isEmpty [ "I am a List Str" ]` as well as `List.isEmpty [ True ]`, and they will both work fine.
`List.isEmpty ["I am a List Str"]` as well as `List.isEmpty [True]`, and they will both work fine.
The wildcard type also comes up with empty lists. Suppose we have one function that takes a `List Str` and another
function that takes a `List Bool`. We might reasonably expect to be able to pass an empty list (that is, `[]`) to
@ -1013,10 +1013,10 @@ call `List.reverse` on any list, regardless of its type parameter. However, cons
```coffee
strings : List Str
strings = List.reverse [ "a", "b" ]
strings = List.reverse ["a", "b"]
bools : List Bool
bools = List.reverse [ True, False ]
bools = List.reverse [True, False]
```
In the `strings` example, we have `List.reverse` returning a `List Str`. In the `bools` example, it's returning a
@ -1223,7 +1223,7 @@ so `0b0100u8` evaluates to decimal `4` as an unsigned 8-bit integer.
## Interface modules
[ This part of the tutorial has not been written yet. Coming soon! ]
[This part of the tutorial has not been written yet. Coming soon!]
## Builtin modules
@ -1247,7 +1247,7 @@ Roc compiler. That's why they're called "builtins!"
Besides being built into the compiler, the builtin modules are different from other modules in that:
* They are always imported. You never need to add them to `imports`.
* All their types are imported unqualified automatically. So you never need to write `Num.Nat`, because it's as if the `Num` module was imported using `imports [ Num.{ Nat } ]` (and the same for all the other types in the `Num` module).
* All their types are imported unqualified automatically. So you never need to write `Num.Nat`, because it's as if the `Num` module was imported using `imports [Num.{ Nat }]` (and the same for all the other types in the `Num` module).
## The app module header
@ -1256,7 +1256,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`:
```coffee
app "hello"
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
imports [pf.Stdout]
provides main to pf
```
@ -1274,7 +1274,7 @@ The remaining lines all involve the *platform* this application is built on:
```coffee
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
imports [pf.Stdout]
provides main to pf
```
@ -1283,7 +1283,7 @@ The `packages { pf: "examples/interactive/cli-platform" }` part says two things:
- We're going to be using a *package* (that is, a collection of modules) called `"examples/interactive/cli-platform"`
- We're going to name that package `pf` so we can refer to it more concisely in the future.
The `imports [ pf.Stdout ]` line says that we want to import the `Stdout` module
The `imports [pf.Stdout]` line says that we want to import the `Stdout` module
from the `pf` package, and make it available in the current module.
This import has a direct interaction with our definition of `main`. Let's look
@ -1297,7 +1297,7 @@ 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`
When we write `imports [pf.Stdout]`, it specifies that the `Stdout`
module comes from the `pf` package.
Since `pf` was the name we chose for the `examples/interactive/cli-platform`
@ -1327,8 +1327,8 @@ First, let's do a basic "Hello World" using the tutorial app.
```coffee
app "cli-tutorial"
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout ]
provides [ main ] to pf
imports [pf.Stdout]
provides [main] to pf
main =
Stdout.line "Hello, World!"
@ -1364,8 +1364,8 @@ Let's change `main` to read a line from `stdin`, and then print it back out agai
```swift
app "cli-tutorial"
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout, pf.Stdin, pf.Task ]
provides [ main ] to pf
imports [pf.Stdout, pf.Stdin, pf.Task]
provides [main] to pf
main =
Task.await Stdin.line \text ->
@ -1414,8 +1414,8 @@ This works, but we can make it a little nicer to read. Let's change it to the fo
```haskell
app "cli-tutorial"
packages { pf: "examples/interactive/cli-platform" }
imports [ pf.Stdout, pf.Stdin, pf.Task.{ await } ]
provides [ main ] to pf
imports [pf.Stdout, pf.Stdin, pf.Task.{ await }]
provides [main] to pf
main =
await (Stdout.line "Type something press Enter:") \_ ->
@ -1754,15 +1754,15 @@ type that accumulates more and more fields as it progresses through a series of
Just like how Roc has open records and closed records, it also has open and closed tag unions.
The *open tag union* (or *open union* for short) `[ Foo Str, Bar Bool ]*` represents a tag that might
The *open tag union* (or *open union* for short) `[Foo Str, Bar Bool]*` represents a tag that might
be `Foo Str` and might be `Bar Bool`, but might also be some other tag whose type isn't known at compile time.
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
`[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:
```coffee
example : [ Foo Str, Bar Bool ]* -> Bool
example : [Foo Str, Bar Bool]* -> Bool
example = \tag ->
when tag is
Foo str -> Str.isEmpty str
@ -1770,12 +1770,12 @@ example = \tag ->
_ -> False
```
In contrast, a *closed tag union* (or *closed union*) like `[ Foo Str, Bar Bool ]` (without the `*`)
In contrast, a *closed tag union* (or *closed union*) like `[Foo Str, Bar Bool]` (without the `*`)
represents an exhaustive set of 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:
```coffee
example : [ Foo Str, Bar Bool ] -> Bool
example : [Foo Str, Bar Bool] -> Bool
example = \tag ->
when tag is
Foo str -> Str.isEmpty str
@ -1785,11 +1785,11 @@ example = \tag ->
If we were to remove the type annotations from the previous two code examples, Roc would infer the same
types for them anyway.
It would infer `tag : [ Foo Str, Bar Bool ]` for the latter example because the `when tag is` expression
It would infer `tag : [Foo Str, Bar Bool]` for the latter example because the `when tag is` expression
only includes a `Foo Str` branch and a `Bar Bool` branch, and nothing else. Since the `when` doesn't handle
any other possibilities, these two tags must be the only possible ones the `tag` argument could be.
It would infer `tag : [ Foo Str, Bar Bool ]*` for the former example because the `when tag is` expression
It would infer `tag : [Foo Str, Bar Bool]*` for the former example because the `when tag is` expression
includes a `Foo Str` branch and a `Bar Bool` branch - meaning we know about at least those two specific
possibilities - but also a `_ ->` branch, indicating that there may be other tags we don't know about. Since
the `when` is flexible enough to handle all possible tags, `tag` gets inferred as an open union.
@ -1799,14 +1799,14 @@ the implementation actually handles.
> **Aside:** As with open and closed records, we can use type annotations to make tag union types less flexible
> than what would be inferred. If we added a `_ ->` branch to the second example above, the compiler would still
> accept `example : [ Foo Str, Bar Bool ] -> Bool` as the type annotation, even though the catch-all branch
> would permit the more flexible `example : [ Foo Str, Bar Bool ]* -> Bool` annotation instead.
> accept `example : [Foo Str, Bar Bool] -> Bool` as the type annotation, even though the catch-all branch
> would permit the more flexible `example : [Foo Str, Bar Bool]* -> Bool` annotation instead.
## Combining Open Unions
When we make a new record, it's inferred to be a closed record. For example, in `foo { a: "hi" }`,
the type of `{ a: "hi" }` is inferred to be `{ a : Str }`. In contrast, when we make a new tag, it's inferred
to be an open union. So in `foo (Bar "hi")`, the type of `Bar "hi"` is inferred to be `[ Bar Str ]*`.
to be an open union. So in `foo (Bar "hi")`, the type of `Bar "hi"` is inferred to be `[Bar Str]*`.
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:
@ -1830,50 +1830,50 @@ else
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
the closed union `[Ok Str]`, and likewise for `Err "bar"` and `[Err Str]`, then this would have to be
a type mismatch - because those two closed unions are incompatible.
Instead, the compiler infers `Ok "foo"` to be the open union `[ Ok Str ]*`, and `Err "bar"` to be the open
union `[ Err Str ]*`. Then, when using them together in this conditional, the inferred type of the conditional
becomes `[ Ok Str, Err Str ]*` - that is, the combination of the unions in each of its branches. (Branches in
Instead, the compiler infers `Ok "foo"` to be the open union `[Ok Str]*`, and `Err "bar"` to be the open
union `[Err Str]*`. Then, when using them together in this conditional, the inferred type of the conditional
becomes `[Ok Str, Err Str]*` - that is, the combination of the unions in each of its branches. (Branches in
a `when` work the same way with open unions.)
Earlier we saw how a function which accepts an open union must account for more possibilities, by including
catch-all `_ ->` patterns in its `when` expressions. So *accepting* an open union means you have more requirements.
In contrast, when you already *have* a value which is an open union, you have fewer requirements. A value
which is an open union (like `Ok "foo"`, which has the type `[ Ok Str ]*`) can be provided to anything that's
which is an open union (like `Ok "foo"`, which has the type `[Ok Str]*`) can be provided to anything that's
expecting a tag union (no matter whether it's open or closed), as long as the expected tag union includes at least
the tags in the open union you're providing.
So if I have an `[ Ok Str ]*` value, I can pass it to functions with any of these types (among others):
So if I have an `[Ok Str]*` value, I can pass it to functions with any of these types (among others):
* `[ 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`
* `[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`
* `[Err Bool, Whatever]* -> Bool`
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
`Ok Str` - even though it is not mentioned as one of the tags in `[Err Bool, Whatever]*`! Remember, when
a function accepts an open tag union, any `when` branches on that union must include a catch-all `_ ->` branch,
which is the branch that will end up handling the `Ok Str` value we pass in.
However, I could not pass an `[ Ok Str ]*` to a function with a *closed* tag union argument that did not
mention `Ok Str` as one of its tags. So if I tried to pass `[ Ok Str ]*` to a function with the type
`[ Err Bool, Whatever ] -> Str`, I would get a type mismatch - because a `when` in that function could
However, I could not pass an `[Ok Str]*` to a function with a *closed* tag union argument that did not
mention `Ok Str` as one of its tags. So if I tried to pass `[Ok Str]*` to a function with the type
`[Err Bool, Whatever] -> Str`, I would get a type mismatch - because a `when` in that function could
be handling the `Err Bool` possibility and the `Whatever` possibility, and since it would not necessarily have
a catch-all `_ ->` branch, it might not know what to do with an `Ok Str` if it received one.
> **Note:** It wouldn't be accurate to say that a function which accepts an open union handles
> "all possible tags." For example, if I have a function `[ Ok Str ]* -> Bool` and I pass it
> "all possible tags." For example, if I have a function `[Ok Str]* -> Bool` and I pass it
> `Ok 5`, that will still be a type mismatch. If you think about it, a `when` in that function might
> have the branch `Ok str ->` which assumes there's a string inside that `Ok`, and if `Ok 5` type-checked,
> then that assumption would be false and things would break!
>
> So `[ Ok Str ]*` is more restrictive than `[]*`. It's basically saying "this may or may not be an `Ok` tag,
> So `[Ok Str]*` is more restrictive than `[]*`. It's basically saying "this may or may not be an `Ok` tag,
> but if it is an `Ok` tag, then it's guaranteed to have a payload of exactly `Str`."
In summary, here's a way to think about the difference between open unions in a value you have, compared to a value you're accepting:
@ -1888,7 +1888,7 @@ 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:
```coffee
example : [ Foo Str, Bar Bool ]* -> Bool
example : [Foo Str, Bar Bool]* -> Bool
example = \tag ->
when tag is
Foo str -> Str.isEmpty str
@ -1897,7 +1897,7 @@ example = \tag ->
```
```coffee
example : [ Foo Str, Bar Bool ] -> Bool
example : [Foo Str, Bar Bool] -> Bool
example = \tag ->
when tag is
Foo str -> Str.isEmpty str
@ -1909,7 +1909,7 @@ and constrained records with a named type variable, we can also have *constraine
with a named type variable. Here's an example:
```coffee
example : [ Foo Str, Bar Bool ]a -> [ Foo Str, Bar Bool ]a
example : [Foo Str, Bar Bool]a -> [Foo Str, Bar Bool]a
example = \tag ->
when tag is
Foo str -> Bar (Str.isEmpty str)
@ -1921,15 +1921,15 @@ This type says that the `example` function will take either a `Foo Str` tag, or
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.
So if we give this function a `[ Foo Str, Bar Bool, Baz (List Str) ]` argument, then it will be guaranteed
to return a `[ Foo Str, Bar Bool, Baz (List Str) ]` value. This is more constrained than a function that
returned `[ Foo Str, Bar Bool ]*` because that would say it could return *any* other tag (in addition to
So if we give this function a `[Foo Str, Bar Bool, Baz (List Str)]` argument, then it will be guaranteed
to return a `[Foo Str, Bar Bool, Baz (List Str)]` value. This is more constrained than a function that
returned `[Foo Str, Bar Bool]*` because that would say it could return *any* other tag (in addition to
the `Foo Str` and `Bar Bool` we already know about).
If we removed the type annotation from `example` above, Roc's compiler would infer the same type anyway.
This may be surprising if you look closely at the body of the function, because:
* The return type includes `Foo Str`, but no branch explicitly returns `Foo`. Couldn't the return type be `[ Bar Bool ]a` instead?
* The return type includes `Foo Str`, but no branch explicitly returns `Foo`. Couldn't the return type be `[Bar Bool]a` instead?
* The argument type includes `Bar Bool` even though we never look at `Bar`'s payload. Couldn't the argument type be inferred to be `Bar *` instead of `Bar Bool`, since we never look at it?
The reason it has this type is the `other -> other` branch. Take a look at that branch, and ask this question:
@ -1942,14 +1942,14 @@ includes a branch like `x -> x` or `other -> other`, the function's argument typ
be equivalent.
> **Note:** Just like with records, you can also replace the type variable in tag union types with a concrete type.
> For example, `[ Foo Str ][ Bar Bool ][ Baz (List Str) ]` is equivalent to `[ Foo Str, Bar Bool, Baz (List Str) ]`.
> For example, `[Foo Str][Bar Bool][Baz (List Str)]` is equivalent to `[Foo Str, Bar Bool, Baz (List Str)]`.
>
> Also just like with records, you can use this to compose tag union type aliases. For example, you can write
> `NetworkError : [ Timeout, Disconnected ]` and then `Problem : [ InvalidInput, UnknownFormat ]NetworkError`
> `NetworkError : [Timeout, Disconnected]` and then `Problem : [InvalidInput, UnknownFormat]NetworkError`
## Phantom Types
[ This part of the tutorial has not been written yet. Coming soon! ]
[This part of the tutorial has not been written yet. Coming soon!]
## Operator Desugaring Table

View file

@ -70,33 +70,46 @@ the `let` and `in` keywords. That's how it works in Roc.
For example, this Elm code computes `someNumber` to be `1234`:
```elm
someNumber =
numbers =
let
foo =
1000
num1 =
123
blah =
234
num2 =
456
in
foo + blah
[num1, num2]
```
Here's the equivalent Roc code:
```elm
someNumber =
foo =
1000
numbers =
num1 =
123
blah =
234
num2 =
456
foo + blah
[num1, num2]
```
Like `let`...`in` in Elm, this is indentation-sensitive. Each of the definitions
("defs" for short) must have the same indentation as the ending expression.
Roc has a built-in formatter that has a lot in common with `elm-format` (e.g. no configuration,
no enforced line length) but also some stylistic differences. One notable difference is that
it doesn't use as much spacing. For example, if you ran `roc format` on the following Roc
code, the formatter would not change it:
```elm
numbers =
num1 = 123
num2 = 456
[num1, num2]
```
## Function definitions
Roc only has one syntax for defining functions, and it looks almost exactly
@ -513,25 +526,25 @@ Here are some examples of using tags in a REPL:
```
> True
True : [ True ]*
True : [True]*
> False
False : [ False ]*
False : [False]*
> Ok "hi"
Ok "hi" : [ Ok Str ]*
Ok "hi" : [Ok Str]*
> SomethingIJustMadeUp "hi" "there"
SomethingIJustMadeUp "hi" "there" : [ SomethingIJustMadeUp Str Str ]*
SomethingIJustMadeUp "hi" "there" : [SomethingIJustMadeUp Str Str]*
> x = Foo
Foo : [ Foo ]*
Foo : [Foo]*
> y = Foo "hi" Bar
Foo "hi" 5 : [ Foo Str [ Bar ]* ]*
Foo "hi" 5 : [Foo Str [Bar]*]*
> z = Foo [ "str1", "str2" ]
Foo [ "str1", "str2" ] : [ Foo (List Str) ]*
> z = Foo ["str1", "str2"]
Foo ["str1", "str2"] : [Foo (List Str)]*
```
The `[` `]`s in the types are tag *unions*, and they list all the possible
@ -542,7 +555,7 @@ we saw earlier.
Similarly to how if you put `{ name = "" }` into `elm repl`, it will
infer a type of `{ a | name : String }` - that is, an *open record* with an
unbound type variable and `name : Str` field - if you put a tag `Foo ""` into
`roc repl`, it will infer a type of `[ Foo Str ]*` - that is, an *open tag union*
`roc repl`, it will infer a type of `[Foo Str]*` - that is, an *open tag union*
with one alternative: a `Foo` tag with a `Str` payload.
The same tag can be used with different arities and types. In the REPL above,
@ -561,7 +574,7 @@ when blah is
MyBool bool -> Bool.not bool
```
The inferred type of this expression would be `[ MyStr Str, MyBool Bool ]`.
The inferred type of this expression would be `[MyStr Str, MyBool Bool]`.
> Exhaustiveness checking is still in full effect here. It's based on usage;
> if any code pathways led to `blah` being set to the tag `Foo`, I'd get
@ -571,13 +584,13 @@ There's an important interaction here between the inferred type of a *when-expre
the inferred type of a tag value. Note which types have a `*` and which do not.
```elm
x : [ Foo ]*
x : [Foo]*
x = Foo
y : [ Bar Str ]*
y : [Bar Str]*
y = Bar "stuff"
tagToStr : [ Foo, Bar Str ] -> Str
tagToStr : [Foo, Bar Str] -> Str
tagToStr = \tag ->
when tag is
Foo -> "hi"
@ -586,8 +599,8 @@ tagToStr = \tag ->
Each of these type annotations involves a *tag union* - a collection of tags bracketed by `[` and `]`.
* The type `[ Foo, Bar Str ]` is a **closed** tag union.
* The type `[ Foo ]*` is an **open** tag union.
* The type `[Foo, Bar Str]` is a **closed** tag union.
* The type `[Foo]*` is an **open** tag union.
You can pass `x` to `tagToStr` because an open tag union is type-compatible with
any closed tag union which contains its tags (in this case, the `Foo` tag). You can also
@ -598,7 +611,7 @@ Using `when` *can* get you a closed union (a union without a `*`) but that's not
always what happens. Here's a `when` in which the inferred type is an open tag union:
```elm
alwaysFoo : [ Foo Str ]* -> [ Foo Str ]*
alwaysFoo : [Foo Str]* -> [Foo Str]*
alwaysFoo = \tag ->
when tag is
Foo str -> Foo (Str.concat str "!")
@ -618,14 +631,14 @@ can pass the function some totally nonsensical tag, and it will still compile.
> You could, if you wanted, change the argument's annotation to be `[]*` and
> it would compile. After all, its default branch means it will accept any tag!
>
> Still, the compiler will infer `[ Foo Str ]*` based on usage.
> Still, the compiler will infer `[Foo Str]*` based on usage.
Just because `[ Foo Str ]*` is the inferred type of this argument,
Just because `[Foo Str]*` is the inferred type of this argument,
doesn't mean you have to accept that much flexibility. You can restrict it
by removing the `*`. For example, if you changed the annotation to this...
```elm
alwaysFoo : [ Foo Str, Bar Bool ] -> [ Foo Str ]*
alwaysFoo : [Foo Str, Bar Bool] -> [Foo Str]*
```
...then the function would only accept tags like `Foo "hi"` and `Bar False`. By writing
@ -639,27 +652,27 @@ functionality by making (and then using) a type alias for a closed tag union.
Here's exactly how `Result` is defined using tags in Roc's standard library:
```elm
Result ok err : [ Ok ok, Err err ]
Result ok err : [Ok ok, Err err]
```
You can also use tags to define recursive data structures, because recursive
type aliases are allowed as long as the recursion happens within a tag. For example:
```elm
LinkedList a : [ Nil, Cons a (LinkedList a) ]
LinkedList a : [Nil, Cons a (LinkedList a)]
```
> Inferred recursive tags use the `as` keyword. For example, the
> inferred version of the above type alias would be:
>
> `[ Nil, Cons a b ] as b`
> `[Nil, Cons a b] as b`
The `*` in open tag unions is actually an unbound ("wildcard") type variable.
It can be bound too, with a lowercase letter like any other bound type variable.
Here's an example:
```elm
exclaimFoo : [ Foo Str ]a -> [ Foo Str ]a
exclaimFoo : [Foo Str]a -> [Foo Str]a
exclaimFoo = \tag ->
when tag is
Foo str -> Foo (Str.concat str "!")
@ -730,7 +743,7 @@ Roc application modules (where the equivalent of `main` lives) begin with the
Here's how the above module header imports section would look in Roc:
```elm
app imports [ Parser, Http.{ Request }, Task.{ Task, await } ]
app imports [Parser, Http.{ Request }, Task.{ Task, await }]
```
`app` modules are application entry points, and they don't formally expose anything.
@ -740,8 +753,8 @@ Modules that *can* be imported are `interface` modules. Their headers look like
```elm
interface Parser
exposes [ Parser, map, oneOf, parse ]
imports [ Utf8 ]
exposes [Parser, map, oneOf, parse]
imports [Utf8]
```
The name `interface` is intended to draw attention to the fact that the interface
@ -791,14 +804,14 @@ Roc functions aren't curried. Calling `(List.append foo)` is a type mismatch
because `List.append` takes 2 arguments, not 1.
For this reason, function type annotations separate arguments with `,` instead of `->`.
In Roc, the type of `Set.add` is:
In Roc, the type of `List.map` is:
```elm
Set.add : Set 'elem, 'elem -> Set 'elem
List.map : List a, (a -> b) -> List b
```
You might notice that Roc's `Set.add` takes its arguments in the reverse order
from how they are in Elm; the `Set` is the first argument in Roc, whereas it would
You might notice that Roc's `List.map` takes its arguments in the reverse order
from how they are in Elm; the `List` is the first argument in Roc, whereas it would
be the last argument in Elm. This is because Roc's `|>` operator works like Elixir's
rather than like Elm's; here is an example of what it does in Roc:
@ -834,32 +847,32 @@ rather than the denominator:
Another example is `List.append`, which is called `List.concat` in Roc:
```elixir
[ 1, 2 ]
|> List.concat [ 3, 4 ]
[1, 2]
|> List.concat [3, 4]
# [ 1, 2, 3, 4 ]
# [1, 2, 3, 4]
```
In Elm:
```elm
[ 1, 2 ]
|> List.append [ 3, 4 ]
[1, 2]
|> List.append [3, 4]
# [ 3, 4, 1, 2 ]
# [3, 4, 1, 2]
```
> There are various trade-offs here, of course. Elm's `|>` has a [very elegant implementation](https://github.com/elm/core/blob/665624859a7a432107059411737e16d1d5cb6373/src/Basics.elm#L873-L874), and `(|>)` in Elm can be usefully passed to other
> functions (e.g. `fold`) whereas in Roc it's not even possible to express the type of `|>`.
As a consequence of `|>` working differently, "pipe-friendly" argument ordering is also
different. That's why `Set.add` has a "flipped" signature in Roc; otherwise, `|> Set.add 5` wouldn't work. Here's the type of Roc's `Set.add` again, and also a pipeline using it:
different. That's why `List.map` has a "flipped" signature in Roc; otherwise, `|> List.map Num.abs` wouldn't work on a list of numbers. Here's the type of Roc's `List.map` again, and also a pipeline using it:
```coffeescript
Set.add : Set 'elem, 'elem -> Set 'elem
List.map : List a, (a -> b) -> List b
[: "a", "b", "c" :]
|> Set.add "d"
[-1, 2, 3, -4]
|> List.map Num.abs
```
Roc has no `<<` or `>>` operators, and there are no functions in the standard library
@ -896,7 +909,6 @@ of expressions with anonymous functions - e.g.
modifiedNums =
List.map nums \num ->
doubled = num * 2
modified = modify doubled
modified / 2
@ -1072,7 +1084,7 @@ the `|>` and the other provided by the `<-`, like so:
```elm
incrementedNumbers =
num <-
[ 1, 2, 3 ]
[1, 2, 3]
|> List.reverse
|> List.map
@ -1080,7 +1092,7 @@ incrementedNumbers =
```
Here, the first argument to `List.map` is provided by the `|>`
(namely the reversed `[ 1, 2, 3 ]` list), and the second argument is provided by +the `<-` (namely the `\num -> …` function).
(namely the reversed `[1, 2, 3]` list), and the second argument is provided by +the `<-` (namely the `\num -> …` function).
Backpassing can also be used with functions that take multiple arguments; for
example, you could write `key, value <- Dict.map dictionary` similarly to how
@ -1156,7 +1168,7 @@ target (for example, WebAssembly) at runtime it will be the same as `U32` instea
For example:
* `List.len : List * -> Nat`
* `List.get : List elem, Nat -> Result elem [ OutOfBounds ]*`
* `List.get : List elem, Nat -> Result elem [OutOfBounds]*`
* `List.set : List elem, Nat, elem -> List elem`
As with floats, which integer type to use depends on the values you want to support
@ -1205,7 +1217,7 @@ If you encounter overflow with either integers or floats in Roc, you get a runti
exception rather than wrapping overflow behavior (or a float becoming `Infinity`
or `-Infinity`). You can opt into wrapping overflow instead with functions like
`Num.addWrap : Int a, Int a -> Int a`, or use a function that gives `Err` if it
overflows, like `Num.addChecked : Num a, Num a -> Result (Num a) [ Overflow ]*`.
overflows, like `Num.addChecked : Num a, Num a -> Result (Num a) [Overflow]*`.
## `comparable`, `appendable`, and `number`
@ -1284,10 +1296,10 @@ Some differences to note:
* `List` refers to something more like Elm's `Array`, as noted earlier.
* No `Char`. This is by design. What most people think of as a "character" is a rendered glyph. However, rendered glyphs are comprised of [grapheme clusters](https://stackoverflow.com/a/27331885), which are a variable number of Unicode code points - and there's no upper bound on how many code points there can be in a single cluster. In a world of emoji, I think this makes `Char` error-prone and it's better to have `Str` be the only first-class unit. For convenience when working with unicode code points (e.g. for performance-critical tasks like parsing), the single-quote syntax is sugar for the corresponding `U32` code point - for example, writing `'鹏'` is exactly the same as writing `40527`. Like Rust, you get a compiler error if you put something in single quotes that's not a valid [Unicode scalar value](http://www.unicode.org/glossary/#unicode_scalar_value).
* No `Basics`. You use everything from the standard library fully-qualified; e.g. `Bool.not` or `Num.negate` or `Num.ceiling`. There is no `Never` because `[]` already serves that purpose. (Roc's standard library doesn't include an equivalent of `Basics.never`, but it's one line of code and anyone can implement it: `never = \a -> never a`.)
* No `Tuple`. Roc doesn't have tuple syntax. As a convention, `Pair` can be used to represent tuples (e.g. `List.zip : List a, List b -> List [ Pair a b ]*`), but this comes up infrequently compared to languages that have dedicated syntax for it.
* No `Tuple`. Roc doesn't have tuple syntax. As a convention, `Pair` can be used to represent tuples (e.g. `List.zip : List a, List b -> List [Pair a b]*`), but this comes up infrequently compared to languages that have dedicated syntax for it.
* No `Task`. By design, platform authors implement `Task` (or don't; it's up to them) - it's not something that really *could* be usefully present in Roc's standard library.
* No `Process`, `Platform`, `Cmd`, or `Sub` - similarly to `Task`, these are things platform authors would include, or not.
* No `Maybe`. This is by design. If a function returns a potential error, use `Result` with an error type that uses a zero-arg tag to describe what went wrong. (For example, `List.first : List a -> Result a [ ListWasEmpty ]*` instead of `List.first : List a -> Maybe a`.) If you want to have a record field be optional, use an Optional Record Field directly (see earlier). If you want to describe something that's neither an operation that can fail nor an optional field, use a more descriptive tag - e.g. for a nullable JSON decoder, instead of `nullable : Decoder a -> Decoder (Maybe a)`, make a self-documenting API like `nullable : Decoder a -> Decoder [ Null, NonNull a ]*`.
* No `Maybe`. This is by design. If a function returns a potential error, use `Result` with an error type that uses a zero-arg tag to describe what went wrong. (For example, `List.first : List a -> Result a [ListWasEmpty]*` instead of `List.first : List a -> Maybe a`.) If you want to have a record field be optional, use an Optional Record Field directly (see earlier). If you want to describe something that's neither an operation that can fail nor an optional field, use a more descriptive tag - e.g. for a nullable JSON decoder, instead of `nullable : Decoder a -> Decoder (Maybe a)`, make a self-documenting API like `nullable : Decoder a -> Decoder [Null, NonNull a]*`.
## Operator Desugaring Table