mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
Update TUTORIAL and roc-for-elm-programmers
Changed formatting to remove spaces inside square braces
This commit is contained in:
parent
8fa80fa69b
commit
47f5015fdf
2 changed files with 171 additions and 159 deletions
200
TUTORIAL.md
200
TUTORIAL.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue