interface InteractiveExample exposes [view] imports [pf.Html.{ pre, samp, div, text, a, p }, pf.Html.Attributes.{ class, role, href, id }] Section : [Desc (List Token) Str, Indent, Outdent, Newline] Token : [ Kw Str, Ident Str, Str Str, Num Str, Comment Str, Literal Str, ParensAround (List Token), Lambda (List Str), StrInterpolation Str Str Str, ] view : Html.Node view = output = sectionsToStr [ Desc [Comment "Click anything here to see an explanation.Tap anything here to\n# see an explanation."] "

Comments in Roc begin with # and go to the end of the line.

", Newline, Desc [Ident "main", Kw "="] "

This defines main, which is where our program will begin.

In Roc, all definitions are constant, so writing main = again in the same scope would give an error.

", Indent, Desc [Ident "Path.fromStr", Str "\"url.txt\""] "

This converts the string \"url.txt\" into a Path by passing it to Path.fromStr.

Function arguments are separated with whitespace. Parentheses are only needed in nested function calls.

", Newline, Desc [Kw "|>", Ident "storeEmail"] "

The pipe operator (|>) is syntax sugar for passing the previous value to the next function in the \"pipeline.\"

This line takes the value that Path.fromStr \"url.txt\" returns and passes it to storeEmail.

The next |> continues the pipeline.

", Newline, Desc [Kw "|>", Ident "Task.onErr", Ident "handleErr"] "

If the task returned by the previous step in the pipeline fails, pass its error to handleErr.

The pipeline essentially does this:

a = Path.fromStr \"url.txt\"\nb = storeEmail a\n\nTask.onErr b handleErr

It creates a Path from a string, passes it to storeEmail, and specifies how to handle errors if storing the email fails.

", Outdent, Newline, Desc [Ident "storeEmail", Kw "=", Lambda ["path"]] "

This defines a function named storeEmail. It takes one argument, named path.

In Roc, functions are ordinary values, so we assign names to them using = like with any other value.

The \\arg1, arg2 -> syntax begins a function, and the part after -> is the function's body.

", Indent, Desc [Ident "url", Kw "=", Ident "File.readUtf8!", Ident "path"] "

This passes path to the File.readUtf8 function, which reads the contents of the file (as a UTF-8 string) into url.

The ! operator is similar to await in other languages. It means “wait until the asynchronous File.readUtf8 operation successfully completes.”

If the file read fails (maybe because path refers to a missing file), the rest of this function will be skipped, and the handleErr function will take over.

", Newline, Desc [Ident "user", Kw "=", Ident "Http.get!", Ident "url", Ident "Json.utf8"] "

This fetches the contents of the URL and decodes them as JSON.

If the shape of the JSON isn't compatible with the type of user (based on type inference), this will give a decoding error immediately.

As with all the other function calls involving the ! operator, if there's an error, nothing else in storeEmail will be run, and handleErr will run.

", Newline, Desc [Ident "dest", Kw "=", Ident "Path.fromStr", StrInterpolation "\"" "user.name" ".txt\""] "

The \$(user.name) in this string literal will be replaced with the value stored in the user record's name field. This is string interpolation.

Note that this function call doesn't involve the ! operator. That's because Path.fromStr doesn't involve any Tasks, so there's no need to use ! to wait for it to finish.

", Newline, Desc [Ident "File.writeUtf8!", Ident "dest", Ident "user.email"] "

This writes user.email to the file, encoded as UTF-8.

Since File.writeUtf8 doesn't produce any information on success, we don't bother using = like we did on the other lines.

", Newline, Desc [Ident "Stdout.line!", StrInterpolation "\"Wrote email to " "Path.display dest" "\""] "

This prints what we did to stdout.

Notice that this does a function call inside the string interpolation. Any valid Roc expression is allowed inside string interpolation, as long as it doesn't contain any newlines.

", Outdent, Newline, Desc [Ident "handleErr", Kw "=", Lambda ["err"]] "

Like storeEmail, handleErr is also a function.

Although type annotations are optional everywhere in Roc—because the language has 100% type inference—you could add type annotations to main, storeEmail, and handleErr if you wanted to.

", Indent, Desc [Kw "when", Ident "err", Kw "is"] "

This will run one of the following lines depending on what value the err argument has.

Each line does a pattern match on the shape of the error to decide whether to run, or to move on and try the next line's pattern.

Roc will do compile-time exhaustiveness checking and tell you if you forgot to handle any error cases here that could have occurred, based on the tasks that were run in storeEmail.

", Indent, Desc [Literal "HttpErr", Ident "url", Kw "_", Kw "->", Ident "Stderr.line!", StrInterpolation "\"Error fetching URL " "url" "\""] "

This line will run if the Http.get request from earlier encountered an HTTP error.

It handles the error by printing an error message to stderr.

The _ is where more information about the error is stored in the HttpErr. If we wanted to print more detail about what the error was, we'd name that something other than _ and actually use it.

", Newline, Desc [Literal "FileReadErr", Ident "path", Kw "_", Kw "->", Ident "Stderr.line!", StrInterpolation "\"Error reading from " "Path.display path" "\""] "

This line will run if the File.readUtf8 from earlier encountered a file I/O error.

It handles the error by printing an error message to stderr.

The _ is where more information about the error is stored in the FileReadErr. If we wanted to print more detail about what the error was, we'd name that something other than _ and actually use it.

", Newline, Desc [Literal "FileWriteErr", Ident "path", Kw "_", Kw "->", Ident "Stderr.line!", StrInterpolation "\"Error writing to " "Path.display path" "\""] "

This line will run if the File.writeUtf8 from earlier encountered a file I/O error.

It handles the error by printing an error message to stderr.

The _ is where more information about the error is stored in the FileWriteErr. If we wanted to print more detail about what the error was, we'd name that something other than _ and actually use it.

", ] div [role "presentation"] [ pre [class "interactive-example"] [ samp [] [text output], ], p [] [ text "To get started with the language, try the ", a [href "/tutorial"] [text "tutorial"], text "!", ], p [id "final-tutorial-link"] [ a [class "btn-small", href "/tutorial"] [text "Start Tutorial"], ], ] tokensToStr : List Token -> Str tokensToStr = \tokens -> List.walk tokens "" \buf, token -> bufWithSpace = if Str.isEmpty buf || token == Literal "," then buf else Str.concat buf " " when token is ParensAround wrapped -> # Don't put spaces after opening parens or before closing parens bufWithSpace |> Str.concat "(" |> Str.concat (tokensToStr wrapped) |> Str.concat ")" Lambda args -> # Don't put spaces after opening parens or before closing parens argsWithCommas = args |> List.map \ident -> "$(ident)" |> Str.joinWith ", " bufWithSpace |> Str.concat "\\" |> Str.concat argsWithCommas |> Str.concat " ->" Kw str -> Str.concat bufWithSpace "$(str)" Num str | Str str | Literal str -> # We may render these differently in the future Str.concat bufWithSpace "$(str)" Comment str -> Str.concat bufWithSpace "# $(str)" Ident str -> Str.concat bufWithSpace (identToHtml str) StrInterpolation before interp after -> bufWithSpace |> Str.concat (if Str.isEmpty before then "" else "$(before)") |> Str.concat "\$($(identToHtml interp))" |> Str.concat (if Str.isEmpty after then "" else "$(after)") identToHtml : Str -> Str identToHtml = \str -> List.walk (Str.splitOn str ".") "" \accum, ident -> len = Str.countUtf8Bytes ident withoutSuffix = ident |> Str.replaceLast "!" "" identHtml = "$(withoutSuffix)" html = # If removing a trailing "!" changed the length, then there must have been a trailing "!" if len > Str.countUtf8Bytes withoutSuffix then "$(identHtml)!" else identHtml if Str.isEmpty accum then html else "$(accum).$(html)" sectionsToStr : List Section -> Str sectionsToStr = \sections -> answer = List.walk sections { buf: "", count: 0, indent: 0 } \{ buf, count, indent }, section -> bufWithSpace = if Str.isEmpty buf then buf else if buf |> Str.endsWith "\n" then Str.concat buf (Str.repeat " " indent) else Str.concat buf " " (afterSpaces, nextCount) = when section is Newline | Indent | Outdent -> # Indent and outdent changes happen on the next iteration, # so we only need a newline for them here. (Str.concat buf "\n", count) Desc tokens str -> html = radio count (tokensToStr tokens) str (Str.concat bufWithSpace html, count + 1) nextIndent = when section is Indent -> indent + 4 Outdent -> indent - 4 Newline | Desc _ _ -> indent { buf: afterSpaces, count: nextCount, indent: nextIndent, } answer.buf radio : U16, Str, Str -> Str radio = \index, labelHtml, descHtml -> # The first radio button should always be checked, and none of the others should be. checkedHtml = if index == 0 then " checked" else "" """ $(descHtml) """