diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cbd04d7..663671b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,27 +2,38 @@ on: pull_request: workflow_dispatch: -name: Test examples on Ubuntu +name: Test examples without nix jobs: test-examples: runs-on: ${{ matrix.operating-system }} strategy: matrix: - operating-system: [ubuntu-20.04, ubuntu-24.04] + operating-system: [ubuntu-20.04, ubuntu-24.04, macos-13, macos-14] timeout-minutes: 90 steps: - uses: actions/checkout@v3 + - name: Set OS-specific variables + id: vars + run: | + if [[ "${{ matrix.operating-system }}" =~ ^ubuntu- ]]; then + echo "os_pattern=linux_x86_64" >> $GITHUB_OUTPUT + elif [ "${{ matrix.operating-system }}" = "macos-13" ]; then + echo "os_pattern=macos_x86_64" >> $GITHUB_OUTPUT + else + echo "os_pattern=macos_apple_silicon" >> $GITHUB_OUTPUT + fi + - id: try_fetching_testing_release continue-on-error: true run: | - curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-TESTING.tar.gz - + curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-${{ steps.vars.outputs.os_pattern }}-TESTING.tar.gz + - name: There are no TESTING releases, checking regular releases instead if: steps.try_fetching_testing_release.outcome == 'failure' run: | - curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz + curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-${{ steps.vars.outputs.os_pattern }}-latest.tar.gz - name: rename nightly tar run: mv $(ls | grep "roc_nightly.*tar\.gz") roc_nightly.tar.gz @@ -38,16 +49,28 @@ jobs: - name: get short commit SHA run: echo "SHORT_COMMIT_SHA=$(./roc_nightly/roc version | grep -oP 'commit \K[a-f0-9]+' )" >> $GITHUB_ENV - - name: install expect # used to check if output is correct + - name: Install dependencies (Ubuntu) + if: startsWith(matrix.operating-system, 'ubuntu-') run: sudo apt install -y expect - - name: print runner CPU + - name: Install dependencies (macOS) + if: startsWith(matrix.operating-system, 'macos-') + run: | + brew install expect + brew install z3 + + - name: print runner CPU (Ubuntu) + if: startsWith(matrix.operating-system, 'ubuntu-') run: lscpu + - name: print runner CPU (macOS) + if: startsWith(matrix.operating-system, 'macos-') + run: sysctl -n machdep.cpu.brand_string + - name: check if roc files are properly formatted run: ROC=./roc_nightly/roc ./ci_scripts/check_format.sh - run: ROC=./roc_nightly/roc ./ci_scripts/all_tests.sh - name: Checks if every folder in examples is mentioned in ./examples/index.md - run: bash ./ci_scripts/check_index.sh + run: bash ./ci_scripts/check_index.sh \ No newline at end of file diff --git a/ci_scripts/all_tests.sh b/ci_scripts/all_tests.sh index 071a931..c2a1cbb 100755 --- a/ci_scripts/all_tests.sh +++ b/ci_scripts/all_tests.sh @@ -51,9 +51,6 @@ expect ci_scripts/expect_scripts/CommandLineArgs.exp $ROC build ./examples/CommandLineArgsFile/main.roc expect ci_scripts/expect_scripts/CommandLineArgsFile.exp -$ROC build ./examples/DesugaringAwait/main.roc -expect ci_scripts/expect_scripts/DesugaringAwait.exp - $ROC build ./examples/DesugaringTry/main.roc $ROC test ./examples/DesugaringTry/main.roc expect ci_scripts/expect_scripts/DesugaringTry.exp @@ -66,11 +63,11 @@ $ROC test ./examples/TowersOfHanoi/Hanoi.roc $ROC build ./examples/Results/main.roc expect ci_scripts/expect_scripts/Results.exp -$ROC build ./examples/Tasks/main.roc -expect ci_scripts/expect_scripts/Tasks.exp +$ROC build ./examples/ErrorHandling/main.roc +expect ci_scripts/expect_scripts/ErrorHandling.exp -$ROC build ./examples/TaskLoop/main.roc -expect ci_scripts/expect_scripts/TaskLoop.exp +$ROC build ./examples/LoopEffect/main.roc +expect ci_scripts/expect_scripts/LoopEffect.exp $ROC test ./examples/RecordBuilder/DateParser.roc @@ -95,26 +92,30 @@ expect ci_scripts/expect_scripts/HelloWeb.exp $ROC build ./examples/ImportPackageFromModule/main.roc expect ci_scripts/expect_scripts/ImportPackageFromModule.exp -$ROC build --lib ./examples/GoPlatform/main.roc --output examples/GoPlatform/platform/libapp.so -go build -C examples/GoPlatform/platform -buildmode=pie -o dynhost - -$ROC preprocess-host ./examples/GoPlatform/platform/dynhost ./examples/GoPlatform/platform/main.roc ./examples/GoPlatform/platform/libapp.so -$ROC build ./examples/GoPlatform/main.roc - -# temporarily allow failure of lsb_release in case it is not installed -set +e -os_info=$(lsb_release -a 2>/dev/null) -set -e - -# Skip Go tests if os is Ubuntu and we're not inside nix. This avoids a segfault on CI. See https://github.com/roc-lang/examples/issues/164 -if echo "$os_info" | grep -q "Ubuntu" && [ -z "${IN_NIX_SHELL}" ]; then - echo "Skipping Go test due to https://github.com/roc-lang/examples/issues/164" -else - echo "Running Go test..." - expect ci_scripts/expect_scripts/GoPlatform.exp -fi - $ROC test ./examples/CustomInspect/OpaqueTypes.roc -$ROC build ./examples/DotNetPlatform/main.roc --lib --output ./examples/DotNetPlatform/platform/interop -expect ci_scripts/expect_scripts/DotNetPlatform.exp +# Check if we're not on macOS, these examples don't work on macos yet +if [[ "$(uname)" != "Darwin" ]]; then + $ROC build --lib ./examples/GoPlatform/main.roc --output examples/GoPlatform/platform/libapp.so + go build -C examples/GoPlatform/platform -buildmode=pie -o dynhost + + $ROC preprocess-host ./examples/GoPlatform/platform/dynhost ./examples/GoPlatform/platform/main.roc ./examples/GoPlatform/platform/libapp.so + $ROC build ./examples/GoPlatform/main.roc + + # temporarily allow failure of lsb_release in case it is not installed + set +e + os_info=$(lsb_release -a 2>/dev/null) + set -e + + # Skip Go tests if os is Ubuntu and we're not inside nix. This avoids a segfault on CI. See https://github.com/roc-lang/examples/issues/164 + if echo "$os_info" | grep -q "Ubuntu" && [ -z "${IN_NIX_SHELL}" ]; then + echo "Skipping Go test due to https://github.com/roc-lang/examples/issues/164" + else + echo "Running Go test..." + expect ci_scripts/expect_scripts/GoPlatform.exp + fi + + + $ROC build ./examples/DotNetPlatform/main.roc --lib --output ./examples/DotNetPlatform/platform/interop + expect ci_scripts/expect_scripts/DotNetPlatform.exp +fi diff --git a/ci_scripts/expect_scripts/DesugaringAwait.exp b/ci_scripts/expect_scripts/DesugaringAwait.exp deleted file mode 100644 index 6091a1d..0000000 --- a/ci_scripts/expect_scripts/DesugaringAwait.exp +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/expect - -# uncomment line below for debugging -# exp_internal 1 - -set timeout 7 - -spawn ./examples/DesugaringAwait/main - -expect "Hello Alice\r\n" -expect "Hello Bob\r\n" -expect "Hello Alice\r\n" -expect "Hello Bob\r\n" - -expect "Type in something and press Enter:\r\n" -send "hello\r\n" -expect "Your input was: hello\r\n" -expect "Type in something and press Enter:\r\n" -send "yo\r\n" -expect "Your input was: \r\nyo\r\n" { - expect eof - exit 0 -} - -puts stderr "\nError: output was different from expected value." -exit 1 diff --git a/ci_scripts/expect_scripts/DesugaringTry.exp b/ci_scripts/expect_scripts/DesugaringTry.exp index 615d0c0..6314e54 100644 --- a/ci_scripts/expect_scripts/DesugaringTry.exp +++ b/ci_scripts/expect_scripts/DesugaringTry.exp @@ -7,7 +7,7 @@ set timeout 7 spawn ./examples/DesugaringTry/main -expect -exact "(Ok {birthYear: 1990, name: \"Alice\"})\r\n(Ok {birthYear: 1990, name: \"Alice\"})\r\n" { +expect -exact "(Ok {birth_year: 1990, name: \"Alice\"})\r\n(Ok {birth_year: 1990, name: \"Alice\"})\r\n" { expect eof exit 0 } diff --git a/ci_scripts/expect_scripts/Tasks.exp b/ci_scripts/expect_scripts/ErrorHandling.exp similarity index 74% rename from ci_scripts/expect_scripts/Tasks.exp rename to ci_scripts/expect_scripts/ErrorHandling.exp index 0779aab..9301219 100644 --- a/ci_scripts/expect_scripts/Tasks.exp +++ b/ci_scripts/expect_scripts/ErrorHandling.exp @@ -5,7 +5,7 @@ set timeout 7 -spawn ./examples/Tasks/main "https://www.roc-lang.org" roc.html +spawn ./examples/ErrorHandling/main "https://www.roc-lang.org" roc.html expect "Done\r\n" { expect eof diff --git a/ci_scripts/expect_scripts/TaskLoop.exp b/ci_scripts/expect_scripts/LoopEffect.exp similarity index 91% rename from ci_scripts/expect_scripts/TaskLoop.exp rename to ci_scripts/expect_scripts/LoopEffect.exp index bd03cb2..30f881c 100644 --- a/ci_scripts/expect_scripts/TaskLoop.exp +++ b/ci_scripts/expect_scripts/LoopEffect.exp @@ -5,7 +5,7 @@ set timeout 7 -spawn ./examples/TaskLoop/main +spawn ./examples/LoopEffect/main expect "Enter some numbers on different lines, then press Ctrl-D to sum them up.\r\n" @@ -18,7 +18,7 @@ send "\004" expect "Sum: 2\r\n" { expect eof { - spawn ./examples/TaskLoop/main + spawn ./examples/LoopEffect/main expect "Enter some numbers on different lines, then press Ctrl-D to sum them up.\r\n" diff --git a/ci_scripts/expect_scripts/RandomNumbers.exp b/ci_scripts/expect_scripts/RandomNumbers.exp index f4292b5..57041d6 100644 --- a/ci_scripts/expect_scripts/RandomNumbers.exp +++ b/ci_scripts/expect_scripts/RandomNumbers.exp @@ -7,11 +7,10 @@ set timeout 7 spawn ./examples/RandomNumbers/main - -expect "Random numbers are: 29,30,71,64,48,33,55,68,53,28\r\n" { +expect "52\r\n34\r\n26\r\n69\r\n34\r\n35\r\n51\r\n74\r\n70\r\n39\r\n" { expect eof exit 0 } puts stderr "\nError: output was different from expected value." -exit 1 \ No newline at end of file +exit 1 diff --git a/ci_scripts/expect_scripts/Tuples.exp b/ci_scripts/expect_scripts/Tuples.exp index 46d1865..ab47659 100644 --- a/ci_scripts/expect_scripts/Tuples.exp +++ b/ci_scripts/expect_scripts/Tuples.exp @@ -7,10 +7,10 @@ set timeout 7 spawn ./examples/Tuples/main -expect "First is: A String,\r\nSecond is: true, \r\nThird is: 15000000.\r\nYou also have some pears.\r\n" { +expect "First is: A String,\r\nSecond is: true,\r\nThird is: 15000000.\r\nYou also have some pears.\r\n" { expect eof exit 0 } puts stderr "\nError: output was different from expected value." -exit 1 \ No newline at end of file +exit 1 diff --git a/examples/Arithmetic/main.roc b/examples/Arithmetic/main.roc index 430c1ea..1eb5679 100644 --- a/examples/Arithmetic/main.roc +++ b/examples/Arithmetic/main.roc @@ -1,37 +1,30 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } +app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br" } import pf.Stdout -import pf.Arg +import pf.Arg exposing [Arg] -TaskErrors : [InvalidArg, InvalidNumStr] +main! : List Arg.Arg => Result {} _ +main! = \raw_args -> -main = - task = - args = readArgs! + args : { a : I32, b : I32 } + args = try read_args raw_args - formatResult = \(operation, result) -> - resultStr = Num.toStr result + result = + [ + ("sum", args.a + args.b), + ("difference", args.a - args.b), + ("product", args.a * args.b), + ("integer quotient", args.a // args.b), + ("remainder", args.a % args.b), + ("exponentiation", Num.powInt args.a args.b), + ] + |> List.map \(operation, answer) -> + answer_str = Num.toStr answer - "$(operation): $(resultStr)" + "$(operation): $(answer_str)" + |> Str.joinWith "\n" - results = - [ - ("sum", args.a + args.b), - ("difference", args.a - args.b), - ("product", args.a * args.b), - ("integer quotient", args.a // args.b), - ("remainder", args.a % args.b), - ("exponentiation", Num.powInt args.a args.b), - ] - |> List.map formatResult - |> Str.joinWith "\n" - - Task.ok results - - when Task.result! task is - Ok result -> Stdout.line result - Err InvalidArg -> Task.err (Exit 1 "Error: Please provide two integers between -1000 and 1000 as arguments.") - Err InvalidNumStr -> Task.err (Exit 1 "Error: Invalid number format. Please provide integers between -1000 and 1000.") + Stdout.line! result ## Reads two command-line arguments, attempts to parse them as `I32` numbers, ## and returns a task containing a record with two fields, `a` and `b`, holding @@ -41,21 +34,26 @@ main = ## as `I32` numbers, or if the parsed numbers are outside the expected range ## (-1000 to 1000), the function will return a task that fails with an ## error `InvalidArg` or `InvalidNumStr`. -readArgs : Task.Task { a : I32, b : I32 } TaskErrors -readArgs = +read_args : List Arg -> Result { a : I32, b : I32 } [Exit I32 Str] +read_args = \raw_args -> + + invalid_args = Exit 1 "Error: Please provide two integers between -1000 and 1000 as arguments." + invalid_num_str = Exit 1 "Error: Invalid number format. Please provide integers between -1000 and 1000." args = - Arg.list {} - |> Task.mapErr! \_ -> InvalidArg + if List.len raw_args != 3 then + return Err invalid_args + else + List.map raw_args Arg.display - aResult = List.get args 1 |> Result.try Str.toI32 - bResult = List.get args 2 |> Result.try Str.toI32 + a_result = List.get args 1 |> Result.try Str.toI32 + b_result = List.get args 2 |> Result.try Str.toI32 - when (aResult, bResult) is + when (a_result, b_result) is (Ok a, Ok b) -> if a < -1000 || a > 1000 || b < -1000 || b > 1000 then - Task.err InvalidNumStr + Err invalid_num_str else - Task.ok { a, b } + Ok { a, b } - _ -> Task.err InvalidNumStr + _ -> Err invalid_num_str diff --git a/examples/BasicDict/BasicDict.roc b/examples/BasicDict/BasicDict.roc index 897112a..2d510b0 100644 --- a/examples/BasicDict/BasicDict.roc +++ b/examples/BasicDict/BasicDict.roc @@ -4,8 +4,8 @@ module [] # Both key and value are type variables # Below we use a Str key for the fruit name, and a U64 value for the fruit count. -fruitDict : Dict Str U64 -fruitDict = +fruit_dict : Dict Str U64 +fruit_dict = Dict.empty {} |> Dict.insert "Apple" 3 |> Dict.insert "Banana" 2 @@ -13,49 +13,49 @@ fruitDict = expect # get the value for a key # Dict.get returns a Result with either `Ok value` or `Err KeyNotFound` - Dict.get fruitDict "Apple" == (Ok 3) + Dict.get fruit_dict "Apple" == (Ok 3) expect # get the length (number of key-value pairs) of a Dict - Dict.len fruitDict == 2 + Dict.len fruit_dict == 2 expect # convert Dict to a Str - Inspect.toStr fruitDict == "{\"Apple\": 3, \"Banana\": 2}" + Inspect.toStr fruit_dict == "{\"Apple\": 3, \"Banana\": 2}" expect # get all the keys - Dict.keys fruitDict == ["Apple", "Banana"] + Dict.keys fruit_dict == ["Apple", "Banana"] expect # get all the values - Dict.values fruitDict == [3, 2] + Dict.values fruit_dict == [3, 2] expect # convert to a list of tuples - Dict.toList fruitDict == [("Apple", 3), ("Banana", 2)] + Dict.toList fruit_dict == [("Apple", 3), ("Banana", 2)] expect # remove a key-value pair - Dict.remove fruitDict "Apple" + Dict.remove fruit_dict "Apple" |> Dict.remove "Banana" |> Dict.isEmpty expect # update the value of a Dict - updatedDict = - Dict.update fruitDict "Apple" addFruit + updated_dict = + Dict.update fruit_dict "Apple" add_fruit # We need to account for the case when a key (=fruit) is not in the Dict. # So we need a function like this: - addFruit : Result U64 [Missing] -> Result U64 [Missing] - addFruit = \valueTag -> - when valueTag is + add_fruit : Result U64 [Missing] -> Result U64 [Missing] + add_fruit = \value_tag -> + when value_tag is # If the fruit is not in the dict (=missing), we set the count to 1 Err Missing -> Ok 1 # If the fruit is in the dict (=present), we increase the count Ok count -> Ok (count + 1) - Dict.get updatedDict "Apple" == (Ok 4) + Dict.get updated_dict "Apple" == (Ok 4) # see https://www.roc-lang.org/builtins/Dict for more diff --git a/examples/CommandLineArgs/main.roc b/examples/CommandLineArgs/main.roc index 7354855..4f3644d 100644 --- a/examples/CommandLineArgs/main.roc +++ b/examples/CommandLineArgs/main.roc @@ -1,21 +1,21 @@ # Run with `roc ./examples/CommandLineArgs/main.roc some_argument` # !! This currently does not work in combination with --linker=legacy, see https://github.com/roc-lang/basic-cli/issues/82 -app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", +app [main!] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", } import pf.Stdout import pf.Arg -main = - args = Arg.list! {} # {} is necessary as a temporary workaround +main! = \raw_args -> + args = List.map raw_args Arg.display # get the second argument, the first is the executable's path - argResult = List.get args 1 |> Result.mapErr (\_ -> ZeroArgsGiven) + arg_result = List.get args 1 |> Result.mapErr (\_ -> ZeroArgsGiven) - when argResult is + when arg_result is Err ZeroArgsGiven -> - Task.err (Exit 1 "Error ZeroArgsGiven:\n\tI expected one argument, but I got none.\n\tRun the app like this: `roc main.roc -- input.txt`") + Err (Exit 1 "Error ZeroArgsGiven:\n\tI expected one argument, but I got none.\n\tRun the app like this: `roc main.roc -- input.txt`") - Ok firstArgument -> - Stdout.line "received argument: $(firstArgument)" + Ok first_argument -> + Stdout.line! "received argument: $(first_argument)" diff --git a/examples/CommandLineArgsFile/main.roc b/examples/CommandLineArgsFile/main.roc index 156c4fa..c3ab3ed 100644 --- a/examples/CommandLineArgsFile/main.roc +++ b/examples/CommandLineArgsFile/main.roc @@ -1,45 +1,44 @@ # Run with `roc ./examples/CommandLineArgsFile/main.roc -- examples/CommandLineArgsFile/input.txt` # This currently does not work in combination with --linker=legacy, see https://github.com/roc-lang/basic-cli/issues/82 -app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", +app [main!] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", } import pf.Stdout import pf.Path exposing [Path] import pf.Arg -main = +main! = \raw_args -> + # read all command line arguments - args = Arg.list! {} # {} is necessary as a temporary workaround + args = List.map raw_args Arg.display # get the second argument, the first is the executable's path - argResult = List.get args 1 |> Result.mapErr (\_ -> ZeroArgsGiven) + arg_result = List.get args 1 |> Result.mapErr \_ -> ZeroArgsGiven - when argResult is + when arg_result is Ok arg -> - fileContentStr = readFileToStr! (Path.fromStr arg) + file_content_str = try read_file_to_str! (Path.from_str arg) - Stdout.line! "file content: $(fileContentStr)" + Stdout.line! "file content: $(file_content_str)" Err ZeroArgsGiven -> - Task.err (Exit 1 "Error ZeroArgsGiven:\n\tI expected one argument, but I got none.\n\tRun the app like this: `roc main.roc -- path/to/input.txt`") + Err (Exit 1 "Error ZeroArgsGiven:\n\tI expected one argument, but I got none.\n\tRun the app like this: `roc main.roc -- path/to/input.txt`") # reads a file and puts all lines in one Str -readFileToStr : Path -> Task Str [ReadFileErr Str]_ -readFileToStr = \path -> +read_file_to_str! : Path => Result Str [ReadFileErr Str]_ +read_file_to_str! = \path -> path - |> Path.readUtf8 - |> Task.mapErr # Make a nice error message - (\fileReadErr -> - pathStr = Path.display path + |> Path.read_utf8! + |> Result.mapErr \file_read_err -> + path_str = Path.display path - when fileReadErr is - FileReadErr _ readErr -> - readErrStr = Inspect.toStr readErr + when file_read_err is + FileReadErr _ read_err -> + read_err_str = Inspect.toStr read_err - ReadFileErr "Failed to read file:\n\t$(pathStr)\nWith error:\n\t$(readErrStr)" + ReadFileErr "Failed to read file:\n\t$(path_str)\nWith error:\n\t$(read_err_str)" - FileReadUtf8Err _ _ -> - ReadFileErr "I could not read the file:\n\t$(pathStr)\nIt contains charcaters that are not valid UTF-8:\n\t- Check if the file is encoded using a different format and convert it to UTF-8.\n\t- Check if the file is corrupted.\n\t- Find the characters that are not valid UTF-8 and fix or remove them." - ) + FileReadUtf8Err _ _ -> + ReadFileErr "I could not read the file:\n\t$(path_str)\nIt contains charcaters that are not valid UTF-8:\n\t- Check if the file is encoded using a different format and convert it to UTF-8.\n\t- Check if the file is corrupted.\n\t- Find the characters that are not valid UTF-8 and fix or remove them." diff --git a/examples/CustomInspect/OpaqueTypes.roc b/examples/CustomInspect/OpaqueTypes.roc index 3f20437..93cf30f 100644 --- a/examples/CustomInspect/OpaqueTypes.roc +++ b/examples/CustomInspect/OpaqueTypes.roc @@ -7,11 +7,11 @@ Color := [ Blue, ] implements [ - Inspect { toInspector: colorInspector }, + Inspect { toInspector: color_inspector }, ] -colorInspector : Color -> Inspector f where f implements InspectFormatter -colorInspector = \@Color color -> +color_inspector : Color -> Inspector f where f implements InspectFormatter +color_inspector = \@Color color -> when color is Red -> Inspect.str "_RED_" Green -> Inspect.str "_GREEN_" @@ -24,11 +24,11 @@ expect Inspect.toStr (@Color Blue) == "\"_BLUE_\"" ### start snippet secret MySecret := Str implements [ - Inspect { toInspector: mySecretInspector }, + Inspect { toInspector: my_secret_inspector }, ] -mySecretInspector : MySecret -> Inspector f where f implements InspectFormatter -mySecretInspector = \@MySecret _ -> Inspect.str "******* REDACTED *******" +my_secret_inspector : MySecret -> Inspector f where f implements InspectFormatter +my_secret_inspector = \@MySecret _ -> Inspect.str "******* REDACTED *******" expect Inspect.toStr (@MySecret "password1234") == "\"******* REDACTED *******\"" ### end snippet secret diff --git a/examples/DesugaringAwait/README.md b/examples/DesugaringAwait/README.md deleted file mode 100644 index f6ca03e..0000000 --- a/examples/DesugaringAwait/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Desugaring ! - -
- What's syntax sugar? - - Syntax within a programming language that is designed to make things easier - to read or express. It allows developers to write code in a more concise, readable, or - convenient way without adding new functionality to the language itself. -
- -Desugaring converts syntax sugar (like `x + 1`) into more fundamental operations (like `Num.add x 1`). - -Let's see how `!` is desugared, we'll start with a simple example: -```roc -file:main.roc:snippet:bang -``` -After desugaring, this becomes: -```roc -file:main.roc:snippet:await -``` -[Task.await](https://www.roc-lang.org/builtins/Task#await) takes the success value from a given -Task and uses that to generate a new Task. -It's type is `Task a b, (a -> Task c b) -> Task c b`. - -The type of `Stdout.line` is `Str -> Task {} [StdoutErr Err]`. -Because `Stdout.line` does not return anything upon success, we use `\_` in the desugared version, -there is nothing to pass to the next Task. - -You'll see that the version with `!` looks a lot simpler! - -Note that for the last line in the first snippet `Stdout.line "Hello Bob"`, you could have also written -`Stdout.line! "Hello Bob"`. `!` is not necessary on the last line but we allow it for consistency and -to prevent confusion for beginners. - -`!` also makes it easy to work with variables, let's take a look: -```roc -file:main.roc:snippet:bangInput -``` - -This gets desugared to: -```roc -file:main.roc:snippet:awaitInput -``` - -This is similar to before but now the `input = Stdin.line!` gets converted to `Task.await Stdin.line \input ->`. -With `!` you can write code in a mostly familiar way while also getting the benefits of Roc's -error handling and the seperation of pure and effectful code. - -Note: this desugaring is very similar to that of [`?`](https://www.roc-lang.org/examples/DesugaringTry/README.html). - -## Full Code -```roc -file:main.roc -``` diff --git a/examples/DesugaringAwait/main.roc b/examples/DesugaringAwait/main.roc deleted file mode 100644 index 7325c3f..0000000 --- a/examples/DesugaringAwait/main.roc +++ /dev/null @@ -1,36 +0,0 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } - -import pf.Stdin -import pf.Stdout - -main = - helloBang! - helloAwait! - readInputBang! - readInputAwait! - -### start snippet bang -helloBang = - Stdout.line! "Hello Alice" - Stdout.line "Hello Bob" -### end snippet bang - -helloAwait = - ### start snippet await - Task.await (Stdout.line "Hello Alice") \_ -> - Stdout.line "Hello Bob" -### end snippet await - -### start snippet bangInput -readInputBang = - Stdout.line! "Type in something and press Enter:" - input = Stdin.line! - Stdout.line! "Your input was: $(input)" -### end snippet bangInput - -### start snippet awaitInput -readInputAwait = - Task.await (Stdout.line "Type in something and press Enter:") \_ -> - Task.await Stdin.line \input -> - Stdout.line "Your input was: $(input)" -### end snippet awaitInput diff --git a/examples/DesugaringTry/README.md b/examples/DesugaringTry/README.md index 1e15cfb..f94a0e3 100644 --- a/examples/DesugaringTry/README.md +++ b/examples/DesugaringTry/README.md @@ -31,8 +31,6 @@ As you can see, the first version is a lot nicer! Thanks to `?`, you can write code in a mostly familiar way while also getting the benefits of Roc's error handling. -Note: this desugaring is very similar to that of [`!`](https://www.roc-lang.org/examples/DesugaringAwait/README.html). - ## Full Code ```roc file:main.roc diff --git a/examples/DesugaringTry/main.roc b/examples/DesugaringTry/main.roc index 1341753..bec2ddd 100644 --- a/examples/DesugaringTry/main.roc +++ b/examples/DesugaringTry/main.roc @@ -1,31 +1,33 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } +app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br" } import pf.Stdout -main = - Stdout.line! (Inspect.toStr (parseNameAndYear "Alice was born in 1990")) - Stdout.line! (Inspect.toStr (parseNameAndYearTry "Alice was born in 1990")) +main! = \_args -> + try Stdout.line! (Inspect.toStr (parse_name_and_year "Alice was born in 1990")) + try Stdout.line! (Inspect.toStr (parse_name_and_year_try "Alice was born in 1990")) + + Ok {} ### start snippet question -parseNameAndYear : Str -> Result { name : Str, birthYear : U16 } _ -parseNameAndYear = \str -> - { before: name, after: birthYearStr } = Str.splitFirst? str " was born in " - birthYear = Str.toU16? birthYearStr - Ok { name, birthYear } +parse_name_and_year : Str -> Result { name : Str, birth_year : U16 } _ +parse_name_and_year = \str -> + { before: name, after: birth_year_str } = Str.splitFirst? str " was born in " + birth_year = Str.toU16? birth_year_str + Ok { name, birth_year } ### end snippet question -parseNameAndYearTry = \str -> +parse_name_and_year_try = \str -> ### start snippet try str |> Str.splitFirst " was born in " - |> Result.try \{ before: name, after: birthYearStr } -> - Str.toU16 birthYearStr - |> Result.try \birthYear -> - Ok { name, birthYear } + |> Result.try \{ before: name, after: birth_year_str } -> + Str.toU16 birth_year_str + |> Result.try \birth_year -> + Ok { name, birth_year } ### end snippet try expect - parseNameAndYear "Alice was born in 1990" == Ok { name: "Alice", birthYear: 1990 } + parse_name_and_year "Alice was born in 1990" == Ok { name: "Alice", birth_year: 1990 } expect - parseNameAndYearTry "Alice was born in 1990" == Ok { name: "Alice", birthYear: 1990 } + parse_name_and_year_try "Alice was born in 1990" == Ok { name: "Alice", birth_year: 1990 } diff --git a/examples/EncodeDecode/main.roc b/examples/EncodeDecode/main.roc index 74eb4d5..d436efc 100644 --- a/examples/EncodeDecode/main.roc +++ b/examples/EncodeDecode/main.roc @@ -1,5 +1,5 @@ -app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", +app [main!] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.11.0/z45Wzc-J39TLNweQUoLw3IGZtkQiEN3lTBv3BXErRjQ.tar.br", } @@ -20,26 +20,26 @@ ItemKind := [ Property, ] implements [ - Decoding { decoder: decodeItems }, - Encoding { toEncoder: encodeItems }, + Decoding { decoder: decode_items }, + Encoding { toEncoder: encode_items }, Inspect, Eq, ] -tryMapResult : DecodeResult U32, (U32 -> Result ItemKind DecodeError) -> DecodeResult ItemKind -tryMapResult = \decoded, mapper -> +try_map_result : DecodeResult U32, (U32 -> Result ItemKind DecodeError) -> DecodeResult ItemKind +try_map_result = \decoded, mapper -> when decoded.result is Err e -> { result: Err e, rest: decoded.rest } Ok res -> { result: mapper res, rest: decoded.rest } -decodeItems : Decoder ItemKind fmt where fmt implements DecoderFormatting -decodeItems = Decode.custom \bytes, fmt -> +decode_items : Decoder ItemKind fmt where fmt implements DecoderFormatting +decode_items = Decode.custom \bytes, fmt -> # Helper function to wrap our tag ok = \tag -> Ok (@ItemKind tag) bytes |> Decode.fromBytesPartial fmt - |> tryMapResult \val -> + |> try_map_result \val -> when val is 1 -> ok Text 2 -> ok Method @@ -53,29 +53,30 @@ decodeItems = Decode.custom \bytes, fmt -> 10 -> ok Property _ -> Err TooShort -encodeItems : ItemKind -> Encoder fmt where fmt implements EncoderFormatting -encodeItems = \@ItemKind val -> - num = - when val is - Text -> 1 - Method -> 2 - Function -> 3 - Constructor -> 4 - Field -> 5 - Variable -> 6 - Class -> 7 - Interface -> 8 - Module -> 9 - Property -> 10 - Encode.u32 num +encode_items : ItemKind -> Encoder fmt where fmt implements EncoderFormatting +encode_items = \@ItemKind val -> + Encode.u32 + ( + when val is + Text -> 1 + Method -> 2 + Function -> 3 + Constructor -> 4 + Field -> 5 + Variable -> 6 + Class -> 7 + Interface -> 8 + Module -> 9 + Property -> 10 + ) ### end snippet impl ### start snippet demo # make a list of ItemKind's -originalList : List ItemKind -originalList = [ +original_list : List ItemKind +original_list = [ @ItemKind Text, @ItemKind Method, @ItemKind Function, @@ -89,28 +90,28 @@ originalList = [ ] # encode them into JSON -encodedBytes : List U8 -encodedBytes = Encode.toBytes originalList Json.utf8 +encoded_bytes : List U8 +encoded_bytes = Encode.toBytes original_list Json.utf8 # test we have encoded correctly -expect encodedBytes == originalBytes +expect encoded_bytes == original_bytes # take a JSON encoded list -originalBytes : List U8 -originalBytes = "[1,2,3,4,5,6,7,8,9,10]" |> Str.toUtf8 +original_bytes : List U8 +original_bytes = "[1,2,3,4,5,6,7,8,9,10]" |> Str.toUtf8 # decode into a list of ItemKind's -decodedList : List ItemKind -decodedList = Decode.fromBytes originalBytes Json.utf8 |> Result.withDefault [] +decoded_list : List ItemKind +decoded_list = Decode.fromBytes original_bytes Json.utf8 |> Result.withDefault [] # test we have decoded correctly -expect decodedList == originalList +expect decoded_list == original_list -main = +main! = \_args -> # debug print decoded items to stdio - decodedList + decoded_list |> List.map Inspect.toStr |> Str.joinWith "\n" - |> Stdout.line + |> Stdout.line! ### end snippet demo diff --git a/examples/ErrorHandling/README.md b/examples/ErrorHandling/README.md new file mode 100644 index 0000000..f0a2aef --- /dev/null +++ b/examples/ErrorHandling/README.md @@ -0,0 +1,24 @@ +# Error handling with try and Result + +A more complex "real world" example that demonstrates the use of `try`, `Result` and error handling in Roc. + +## Full Code + +```roc +file:main.roc +``` + +## Output + +Run this from the directory that has `main.roc` in it: + +```sh +$ HELLO=1 roc examples/Tasks/main.roc -- "https://www.roc-lang.org" roc.html +HELLO env var was set to 1 +Fetching content from https://www.roc-lang.org... +Saving url HTML to roc.html... +Contents of current directory: [...] +Run time: 329 ms +Done +``` + diff --git a/examples/ErrorHandling/main.roc b/examples/ErrorHandling/main.roc new file mode 100644 index 0000000..b1a854c --- /dev/null +++ b/examples/ErrorHandling/main.roc @@ -0,0 +1,91 @@ +app [main!] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", +} + +import pf.Stdout +import pf.Arg exposing [Arg] +import pf.Env +import pf.Http +import pf.Dir +import pf.Utc exposing [Utc] +import pf.Path exposing [Path] + +usage = "HELLO=1 roc main.roc -- \"https://www.roc-lang.org\" roc.html" + +main! : List Arg => Result {} _ +main! = \args -> + + # Get time since [Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) + start_time : Utc + start_time = Utc.now! {} + + # Read the HELLO environment variable + hello_env : Str + hello_env = + read_env_var! "HELLO" + |> try + |> \msg -> if Str.isEmpty msg then "was empty" else "was set to $(msg)" + + try Stdout.line! "HELLO env var $(hello_env)" + + # Read command line arguments + { url, output_path } = try parse_args! args + + try Stdout.line! "Fetching content from $(url)..." + + # Fetch the provided url using HTTP + html_str : Str + html_str = try fetch_html! url + + try Stdout.line! "Saving url HTML to $(Path.display output_path)..." + + # Write HTML string to a file + Path.write_utf8! html_str output_path + |> Result.mapErr \_ -> FailedToWriteFile "Failed to write to file $(Path.display output_path), usage: $(usage)" + |> try + + # Print contents of current working directory + try list_cwd_contents! {} + + end_time : Utc + end_time = Utc.now! {} + + run_duration = Utc.delta_as_millis start_time end_time + + try Stdout.line! "Run time: $(Num.toStr run_duration) ms" + + try Stdout.line! "Done" + + Ok {} + +parse_args! : List Arg => Result { url : Str, output_path : Path } _ +parse_args! = \args -> + when List.map args Arg.display is + [_, first, second, ..] -> + Ok { url: first, output_path: Path.from_str second } + + _ -> + Err (FailedToReadArgs "Failed to read command line arguments, usage: $(usage)") + +read_env_var! : Str => Result Str [] +read_env_var! = \envVarName -> + when Env.var! envVarName is + Ok envVarStr if !(Str.isEmpty envVarStr) -> Ok envVarStr + _ -> Ok "" + +fetch_html! : Str => Result Str _ +fetch_html! = \url -> + Http.get_utf8! url + |> Result.mapErr \err -> FailedToFetchHtml "Failed to fetch URL $(Inspect.toStr err), usage: $(usage)" + +list_cwd_contents! : {} => Result {} _ +list_cwd_contents! = \_ -> + + dirContents = + Dir.list! "." + |> Result.mapErr \_ -> FailedToListCwd "Failed to list contents of current directory, usage: $(usage)" + |> try + + contentsStr = List.map dirContents Path.display |> Str.joinWith "," + + Stdout.line! "Contents of current directory: $(contentsStr)" diff --git a/examples/FizzBuzz/main.roc b/examples/FizzBuzz/main.roc index da0e67f..9f85712 100644 --- a/examples/FizzBuzz/main.roc +++ b/examples/FizzBuzz/main.roc @@ -1,19 +1,19 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } +app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br" } import pf.Stdout -main = +main! = \_args -> List.range { start: At 1, end: At 100 } - |> List.map fizzBuzz + |> List.map fizz_buzz |> Str.joinWith "," - |> Stdout.line + |> Stdout.line! ## Determine the FizzBuzz value for a given integer. ## Returns "Fizz" for multiples of 3, "Buzz" for ## multiples of 5, "FizzBuzz" for multiples of both ## 3 and 5, and the original number for anything else. -fizzBuzz : I32 -> Str -fizzBuzz = \n -> +fizz_buzz : I32 -> Str +fizz_buzz = \n -> fizz = n % 3 == 0 buzz = n % 5 == 0 @@ -27,17 +27,17 @@ fizzBuzz = \n -> Num.toStr n ## Test Case 1: not a multiple of 3 or 5 -expect fizzBuzz 1 == "1" -expect fizzBuzz 7 == "7" +expect fizz_buzz 1 == "1" +expect fizz_buzz 7 == "7" ## Test Case 2: multiple of 3 -expect fizzBuzz 3 == "Fizz" -expect fizzBuzz 9 == "Fizz" +expect fizz_buzz 3 == "Fizz" +expect fizz_buzz 9 == "Fizz" ## Test Case 3: multiple of 5 -expect fizzBuzz 5 == "Buzz" -expect fizzBuzz 20 == "Buzz" +expect fizz_buzz 5 == "Buzz" +expect fizz_buzz 20 == "Buzz" ## Test Case 4: multiple of both 3 and 5 -expect fizzBuzz 15 == "FizzBuzz" -expect fizzBuzz 45 == "FizzBuzz" +expect fizz_buzz 15 == "FizzBuzz" +expect fizz_buzz 45 == "FizzBuzz" diff --git a/examples/GraphTraversal/Graph.roc b/examples/GraphTraversal/Graph.roc index f7aed76..21f0811 100644 --- a/examples/GraphTraversal/Graph.roc +++ b/examples/GraphTraversal/Graph.roc @@ -4,8 +4,8 @@ ## performing a depth-first or breadth-first search. module [ Graph, - fromList, - fromDict, + from_list, + from_dict, dfs, bfs, ] @@ -15,19 +15,18 @@ module [ Graph a := Dict a (List a) where a implements Eq ## Create a Graph from an adjacency list. -fromList : List (a, List a) -> Graph a -fromList = \adjacencyList -> - emptyDict = Dict.withCapacity (List.len adjacencyList) +from_list : List (a, List a) -> Graph a +from_list = \adjacency_list -> + empty_dict = Dict.withCapacity (List.len adjacency_list) update = \dict, (vertex, edges) -> Dict.insert dict vertex edges - List.walk adjacencyList emptyDict update - |> @Graph + @Graph (List.walk adjacency_list empty_dict update) ## Create a Graph from an adjacency list. -fromDict : Dict a (List a) -> Graph a -fromDict = @Graph +from_dict : Dict a (List a) -> Graph a +from_dict = @Graph ## Perform a depth-first search on a graph to find a target vertex. ## [Algorithm animation](https://en.wikipedia.org/wiki/Depth-first_search#/media/File:Depth-First-Search.gif) @@ -36,8 +35,8 @@ fromDict = @Graph ## - `root` : The starting vertex for the search. ## - `graph` : The graph to perform the search on. dfs : (a -> Bool), a, Graph a -> Result a [NotFound] -dfs = \isTarget, root, @Graph graph -> - dfsHelper isTarget [root] (Set.empty {}) graph +dfs = \is_target, root, @Graph graph -> + dfs_helper is_target [root] (Set.empty {}) graph # A helper function for performing the depth-first search. # @@ -45,8 +44,8 @@ dfs = \isTarget, root, @Graph graph -> # `stack` : A List of vertices to visit. # `visited` : A Set of visited vertices. # `graph` : The graph to perform the search on. -dfsHelper : (a -> Bool), List a, Set a, Dict a (List a) -> Result a [NotFound] -dfsHelper = \isTarget, stack, visited, graph -> +dfs_helper : (a -> Bool), List a, Set a, Dict a (List a) -> Result a [NotFound] +dfs_helper = \is_target, stack, visited, graph -> when stack is [] -> Err NotFound @@ -54,28 +53,28 @@ dfsHelper = \isTarget, stack, visited, graph -> [.., current] -> rest = List.dropLast stack 1 - if isTarget current then + if is_target current then Ok current else if Set.contains visited current then - dfsHelper isTarget rest visited graph + dfs_helper is_target rest visited graph else - newVisited = Set.insert visited current + new_visited = Set.insert visited current when Dict.get graph current is Ok neighbors -> # filter out all visited neighbors filtered = neighbors - |> List.keepIf (\n -> !(Set.contains newVisited n)) + |> List.keepIf (\n -> !(Set.contains new_visited n)) |> List.reverse # newly explored nodes are added to LIFO stack - newStack = List.concat rest filtered + new_stack = List.concat rest filtered - dfsHelper isTarget newStack newVisited graph + dfs_helper is_target new_stack new_visited graph Err KeyNotFound -> - dfsHelper isTarget rest newVisited graph + dfs_helper is_target rest new_visited graph ## Perform a breadth-first search on a graph to find a target vertex. ## [Algorithm animation](https://en.wikipedia.org/wiki/Breadth-first_search#/media/File:Animated_BFS.gif) @@ -84,8 +83,8 @@ dfsHelper = \isTarget, stack, visited, graph -> ## - `root` : The starting vertex for the search. ## - `graph` : The graph to perform the search on. bfs : (a -> Bool), a, Graph a -> Result a [NotFound] -bfs = \isTarget, root, @Graph graph -> - bfsHelper isTarget [root] (Set.single root) graph +bfs = \is_target, root, @Graph graph -> + bfs_helper is_target [root] (Set.single root) graph # A helper function for performing the breadth-first search. # @@ -93,8 +92,8 @@ bfs = \isTarget, root, @Graph graph -> # `queue` : A List of vertices to visit. # `seen` : A Set of all seen vertices. # `graph` : The graph to perform the search on. -bfsHelper : (a -> Bool), List a, Set a, Dict a (List a) -> Result a [NotFound] -bfsHelper = \isTarget, queue, seen, graph -> +bfs_helper : (a -> Bool), List a, Set a, Dict a (List a) -> Result a [NotFound] +bfs_helper = \is_target, queue, seen, graph -> when queue is [] -> Err NotFound @@ -102,7 +101,7 @@ bfsHelper = \isTarget, queue, seen, graph -> [current, ..] -> rest = List.dropFirst queue 1 - if isTarget current then + if is_target current then Ok current else when Dict.get graph current is @@ -111,74 +110,74 @@ bfsHelper = \isTarget, queue, seen, graph -> filtered = List.keepIf neighbors (\n -> !(Set.contains seen n)) # newly explored nodes are added to the FIFO queue - newQueue = List.concat rest filtered + new_queue = List.concat rest filtered # the new nodes are also added to the seen set - newSeen = List.walk filtered seen Set.insert + new_seen = List.walk filtered seen Set.insert - bfsHelper isTarget newQueue newSeen graph + bfs_helper is_target new_queue new_seen graph Err KeyNotFound -> - bfsHelper isTarget rest seen graph + bfs_helper is_target rest seen graph # Test DFS with multiple paths expect - actual = dfs (\v -> Str.startsWith v "C") "A" testGraphMultipath + actual = dfs (\v -> Str.startsWith v "C") "A" test_graph_multipath expected = Ok "Ccorrect" actual == expected # Test BFS with multiple paths expect - actual = bfs (\v -> Str.startsWith v "C") "A" testGraphMultipath + actual = bfs (\v -> Str.startsWith v "C") "A" test_graph_multipath expected = Ok "Ccorrect" actual == expected # Test DFS expect - actual = dfs (\v -> Str.startsWith v "F") "A" testGraphSmall + actual = dfs (\v -> Str.startsWith v "F") "A" test_graph_small expected = Ok "F-DFS" actual == expected ## Test BFS expect - actual = bfs (\v -> Str.startsWith v "F") "A" testGraphSmall + actual = bfs (\v -> Str.startsWith v "F") "A" test_graph_small expected = Ok "F-BFS" actual == expected # Test NotFound DFS expect - actual = dfs (\v -> v == "not a node") "A" testGraphSmall + actual = dfs (\v -> v == "not a node") "A" test_graph_small expected = Err NotFound actual == expected # Test NotFound BFS expect - actual = dfs (\v -> v == "not a node") "A" testGraphSmall + actual = dfs (\v -> v == "not a node") "A" test_graph_small expected = Err NotFound actual == expected # Test DFS large expect - actual = dfs (\v -> v == "AE") "A" testGraphLarge + actual = dfs (\v -> v == "AE") "A" test_graph_large expected = Ok "AE" actual == expected ## Test BFS large expect - actual = bfs (\v -> v == "AE") "A" testGraphLarge + actual = bfs (\v -> v == "AE") "A" test_graph_large expected = Ok "AE" actual == expected # Some helpers for testing -testGraphSmall = +test_graph_small = [ ("A", ["B", "C", "F-BFS"]), ("B", ["D", "E"]), @@ -188,9 +187,9 @@ testGraphSmall = ("F-BFS", []), ("F-DFS", []), ] - |> fromList + |> from_list -testGraphLarge = +test_graph_large = [ ("A", ["B", "C", "D"]), ("B", ["E", "F", "G"]), @@ -224,13 +223,13 @@ testGraphLarge = ("AD", []), ("AE", []), ] - |> fromList + |> from_list -testGraphMultipath = +test_graph_multipath = [ ("A", ["B", "Ccorrect"]), ("B", ["Ccorrect", "Cwrong"]), ("Ccorrect", []), ("Cwrong", []), ] - |> fromList + |> from_list diff --git a/examples/HelloWeb/main.roc b/examples/HelloWeb/main.roc index a496cdc..3b3f351 100644 --- a/examples/HelloWeb/main.roc +++ b/examples/HelloWeb/main.roc @@ -1,4 +1,4 @@ -app [Model, server] { pf: platform "https://github.com/roc-lang/basic-webserver/releases/download/0.10.0/BgDDIykwcg51W8HA58FE_BjdzgXVk--ucv6pVb_Adik.tar.br" } +app [Model, init!, respond!] { pf: platform "https://github.com/roc-lang/basic-webserver/releases/download/0.11.0/yWHkcVUt_WydE1VswxKFmKFM5Tlu9uMn6ctPVYaas7I.tar.br" } import pf.Stdout import pf.Http exposing [Request, Response] @@ -11,13 +11,18 @@ Model : {} # generate css by running `tailwindcss`,... # In this case we don't have anything to initialize, so it is just `Task.ok {}`. -server = { init: Task.ok {}, respond } +init! : {} => Result Model [] +init! = \_ -> Ok {} -respond : Request, Model -> Task Response [ServerErr Str]_ -respond = \req, _ -> +respond! : Request, Model => Result Response [ServerErr Str]_ +respond! = \req, _ -> # Log request datetime, method and url - datetime = Utc.now! |> Utc.toIso8601Str + datetime = Utc.to_iso_8601 (Utc.now! {}) - Stdout.line! "$(datetime) $(Http.methodToStr req.method) $(req.url)" + try Stdout.line! "$(datetime) $(Inspect.toStr req.method) $(req.uri)" - Task.ok { status: 200, headers: [], body: Str.toUtf8 "Hello, web!
" } + Ok { + status: 200, + headers: [], + body: Str.toUtf8 "Hello, web!
", + } diff --git a/examples/HelloWorld/main.roc b/examples/HelloWorld/main.roc index 5157acb..66edde3 100644 --- a/examples/HelloWorld/main.roc +++ b/examples/HelloWorld/main.roc @@ -1,6 +1,6 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } +app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br" } import pf.Stdout -main = +main! = \_args -> Stdout.line! "Hello, World!" diff --git a/examples/ImportFromDirectory/Dir/Hello.roc b/examples/ImportFromDirectory/Dir/Hello.roc index 80a62b3..fae9d72 100644 --- a/examples/ImportFromDirectory/Dir/Hello.roc +++ b/examples/ImportFromDirectory/Dir/Hello.roc @@ -1,6 +1,5 @@ -module - # Only what's listed here is accessible to other modules - [hello] +# Only what's listed here is accessible to other modules +module [hello] hello : Str -> Str hello = \name -> diff --git a/examples/ImportFromDirectory/main.roc b/examples/ImportFromDirectory/main.roc index 2cbf0cd..cce91ee 100644 --- a/examples/ImportFromDirectory/main.roc +++ b/examples/ImportFromDirectory/main.roc @@ -1,7 +1,8 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } +app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br" } import pf.Stdout import Dir.Hello exposing [hello] -main = +main! = \_args -> + # here we're calling the `hello` function from the Hello module Stdout.line! (hello "World") diff --git a/examples/ImportPackageFromModule/Module.roc b/examples/ImportPackageFromModule/Module.roc index 7b7c3de..bb62ffa 100644 --- a/examples/ImportPackageFromModule/Module.roc +++ b/examples/ImportPackageFromModule/Module.roc @@ -1,6 +1,5 @@ -module [splitGraphemes] +module [split_graphemes] import unicode.Grapheme -splitGraphemes = \string -> Grapheme.split string - +split_graphemes = \string -> Grapheme.split string diff --git a/examples/ImportPackageFromModule/main.roc b/examples/ImportPackageFromModule/main.roc index 4e0432d..038fcbe 100644 --- a/examples/ImportPackageFromModule/main.roc +++ b/examples/ImportPackageFromModule/main.roc @@ -1,6 +1,6 @@ ### start snippet header -app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", +app [main!] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", unicode: "https://github.com/roc-lang/unicode/releases/download/0.1.2/vH5iqn04ShmqP-pNemgF773f86COePSqMWHzVGrAKNo.tar.br", } ### end snippet header @@ -8,5 +8,5 @@ app [main] { import pf.Stdout import Module -main = - Stdout.line! (Inspect.toStr (Module.splitGraphemes "hello")) +main! = \_args -> + Stdout.line! (Inspect.toStr (Module.split_graphemes "hello")) diff --git a/examples/IngestFiles/main.roc b/examples/IngestFiles/main.roc index cc2d06e..f78c382 100644 --- a/examples/IngestFiles/main.roc +++ b/examples/IngestFiles/main.roc @@ -1,7 +1,7 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } +app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br" } import pf.Stdout import "sample.txt" as sample : Str -main = +main! = \_args -> Stdout.line! "$(sample)" diff --git a/examples/Json/main.roc b/examples/Json/main.roc index 98bd54f..0200ae2 100644 --- a/examples/Json/main.roc +++ b/examples/Json/main.roc @@ -1,14 +1,13 @@ -app [main] { - cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", +app [main!] { + cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.11.0/z45Wzc-J39TLNweQUoLw3IGZtkQiEN3lTBv3BXErRjQ.tar.br", } import cli.Stdout import json.Json -import Decode exposing [fromBytesPartial] -main = - requestBody = Str.toUtf8 "{\"Image\":{\"Animated\":false,\"Height\":600,\"Ids\":[116,943,234,38793],\"Thumbnail\":{\"Height\":125,\"Url\":\"http:\\/\\/www.example.com\\/image\\/481989943\",\"Width\":100},\"Title\":\"View from 15th Floor\",\"Width\":800}}" +main! = \_args -> + request_body = Str.toUtf8 "{\"Image\":{\"Animated\":false,\"Height\":600,\"Ids\":[116,943,234,38793],\"Thumbnail\":{\"Height\":125,\"Url\":\"http:\\/\\/www.example.com\\/image\\/481989943\",\"Width\":100},\"Title\":\"View from 15th Floor\",\"Width\":800}}" # This { fieldNameMapping: PascalCase } setting translates # incoming JSON fields from PascalCase (first letter capitalized) @@ -17,11 +16,11 @@ main = decoder = Json.utf8With { fieldNameMapping: PascalCase } decoded : DecodeResult ImageRequest - decoded = fromBytesPartial requestBody decoder + decoded = Decode.fromBytesPartial request_body decoder when decoded.result is - Ok record -> Stdout.line "Successfully decoded image, title:\"$(record.image.title)\"" - Err _ -> Task.err (Exit 1 "Error, failed to decode image") + Ok record -> Stdout.line! "Successfully decoded image, title:\"$(record.image.title)\"" + Err _ -> Err (Exit 1 "Error, failed to decode image") ImageRequest : { image : { diff --git a/examples/LeastSquares/main.roc b/examples/LeastSquares/main.roc index ace6e19..1ed6113 100644 --- a/examples/LeastSquares/main.roc +++ b/examples/LeastSquares/main.roc @@ -1,11 +1,11 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } +app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br" } import pf.Stdout -main = - nStr = Num.toStr (leastSquareDifference {}) +main! = \_args -> + nStr = Num.toStr (least_square_difference {}) - Stdout.line "The least positive integer n, where the difference of n*n and (n-1)*(n-1) is greater than 1000, is $(nStr)" + Stdout.line! "The least positive integer n, where the difference of n*n and (n-1)*(n-1) is greater than 1000, is $(nStr)" ## A recursive function that takes an `U32` as its input and returns the least ## positive integer number `n`, where the difference of `n*n` and `(n-1)*(n-1)` @@ -14,16 +14,16 @@ main = ## The input `n` should be a positive integer, and the function will return an ## `U32`representing the least positive integer that satisfies the condition. ## -leastSquareDifference : {} -> U32 -leastSquareDifference = \_ -> - findNumber = \n -> +least_square_difference : {} -> U32 +least_square_difference = \_ -> + find_number = \n -> difference = (Num.powInt n 2) - (Num.powInt (n - 1) 2) if difference > 1000 then n else - findNumber (n + 1) + find_number (n + 1) - findNumber 1 + find_number 1 -expect leastSquareDifference {} == 501 +expect least_square_difference {} == 501 diff --git a/examples/LoopEffect/README.md b/examples/LoopEffect/README.md new file mode 100644 index 0000000..cf527cf --- /dev/null +++ b/examples/LoopEffect/README.md @@ -0,0 +1,20 @@ +# Loop Effects +Sometimes, you need to repeat an [effectful](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) function, multiple times until a particular event occurs. In roc, you can use a [recursive function](https://en.wikipedia.org/wiki/Recursion_(computer_science)) to do this. + +We'll demonstrate this by adding numbers read from stdin until the end of input (Ctrl-D or [end of file](https://en.wikipedia.org/wiki/End-of-file)). + +## Full Code + +```roc +file:main.roc +``` + +## Output + +Run this from the directory that has `main.roc` in it: + +``` +$ roc main.roc < numbers.txt +Enter some numbers on different lines, then press Ctrl-D to sum them up. +Sum: 178 +``` diff --git a/examples/LoopEffect/main.roc b/examples/LoopEffect/main.roc new file mode 100644 index 0000000..754b43d --- /dev/null +++ b/examples/LoopEffect/main.roc @@ -0,0 +1,37 @@ +app [main!] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", +} + +import pf.Stdin +import pf.Stdout +import pf.Stderr + +main! = \_args -> + when run! {} is + Ok {} -> Ok {} + Err err -> print_err! err + +run! : {} => Result {} _ +run! = \_ -> + try Stdout.line! "Enter some numbers on different lines, then press Ctrl-D to sum them up." + + sum = try add_number_from_stdin! 0 + + Stdout.line! "Sum: $(Num.toStr sum)" + +add_number_from_stdin! : I64 => Result I64 _ +add_number_from_stdin! = \sum -> + when Stdin.line! {} is + Ok input -> + when Str.toI64 input is + Ok num -> add_number_from_stdin! (sum + num) + Err _ -> Err (NotNum input) + + Err EndOfFile -> Ok sum + Err err -> err |> Inspect.toStr |> NotNum |> Err + +print_err! : _ => Result {} _ +print_err! = \err -> + when err is + NotNum text -> Stderr.line! "Error: \"$(text)\" is not a valid I64 number." + _ -> Stderr.line! "Error: $(Inspect.toStr err)" diff --git a/examples/TaskLoop/numbers.txt b/examples/LoopEffect/numbers.txt similarity index 100% rename from examples/TaskLoop/numbers.txt rename to examples/LoopEffect/numbers.txt diff --git a/examples/MultipleRocFiles/Hello.roc b/examples/MultipleRocFiles/Hello.roc index 38fb1fd..602ab0a 100644 --- a/examples/MultipleRocFiles/Hello.roc +++ b/examples/MultipleRocFiles/Hello.roc @@ -1,6 +1,5 @@ -module - # Only what's listed here is accessible/exposed to other modules - [hello] +# Only what's listed here is accessible/exposed to other modules +module [hello] hello : Str -> Str hello = \name -> diff --git a/examples/MultipleRocFiles/main.roc b/examples/MultipleRocFiles/main.roc index 3ea6a1f..f5923e2 100644 --- a/examples/MultipleRocFiles/main.roc +++ b/examples/MultipleRocFiles/main.roc @@ -1,7 +1,7 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } +app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br" } import pf.Stdout import Hello -main = +main! = \_args -> Stdout.line! (Hello.hello "World") diff --git a/examples/Parser/main.roc b/examples/Parser/main.roc index 6a8e747..2d4690e 100644 --- a/examples/Parser/main.roc +++ b/examples/Parser/main.roc @@ -1,5 +1,5 @@ -app [main] { - cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", +app [main!] { + cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.9.0/w8YKp2YAgQt5REYk912HfKAHBjcXsrnvtjI0CBzoAT4.tar.br", } @@ -7,31 +7,34 @@ import cli.Stdout import parser.Parser exposing [Parser, many, oneOf, map] import parser.String exposing [parseStr, codeunit, anyCodeunit] -main = - many letterParser - |> parseStr inputStr - |> Result.map countLetterAs - |> Result.map \count -> "I counted $(count) letter A's!" - |> Result.withDefault "Ooops, something went wrong parsing" - |> Stdout.line! +main! = \_args -> + + letters = try parseStr (many letter_parser) input_str + + msg = + letters + |> count_letter_a + |> \count -> "I counted $(count) letter A's!" + + Stdout.line! msg Letter : [A, B, C, Other] -inputStr = "AAAiBByAABBwBtCCCiAyArBBx" +input_str = "AAAiBByAABBwBtCCCiAyArBBx" # Helper to check if a letter is an A tag isA = \l -> l == A # Count the number of Letter A's -countLetterAs : List Letter -> Str -countLetterAs = \letters -> +count_letter_a : List Letter -> Str +count_letter_a = \letters -> letters |> List.countIf isA |> Num.toStr # Parser to convert utf8 input into Letter tags -letterParser : Parser (List U8) Letter -letterParser = +letter_parser : Parser (List U8) Letter +letter_parser = oneOf [ codeunit 'A' |> map \_ -> A, codeunit 'B' |> map \_ -> B, @@ -42,13 +45,13 @@ letterParser = # Test we can parse a single B letter expect input = "B" - parser = letterParser + parser = letter_parser result = parseStr parser input result == Ok B # Test we can parse a number of different letters expect input = "BCXA" - parser = many letterParser + parser = many letter_parser result = parseStr parser input result == Ok [B, C, Other, A] diff --git a/examples/RandomNumbers/README.md b/examples/RandomNumbers/README.md index 09e87f6..e5b4ef9 100644 --- a/examples/RandomNumbers/README.md +++ b/examples/RandomNumbers/README.md @@ -19,5 +19,14 @@ Run this from the directory that has `main.roc` in it: ``` $ roc main.roc -Random numbers are: 29,30,71,64,48,33,55,68,53,28 +52 +34 +26 +69 +34 +35 +51 +74 +70 +39 ``` diff --git a/examples/RandomNumbers/main.roc b/examples/RandomNumbers/main.roc index 7d1b58d..6608d7f 100644 --- a/examples/RandomNumbers/main.roc +++ b/examples/RandomNumbers/main.roc @@ -1,42 +1,35 @@ -app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", - rand: "https://github.com/lukewilliamboswell/roc-random/releases/download/0.0.1/x_XwrgehcQI4KukXligrAkWTavqDAdE5jGamURpaX-M.tar.br", +app [main!] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", + rand: "https://github.com/lukewilliamboswell/roc-random/releases/download/0.4.0/Ai2KfHOqOYXZmwdHX3g3ytbOUjTmZQmy0G2R9NuPBP0.tar.br", } import pf.Stdout import rand.Random -# Print a list of 10 random numbers in the range 25-75 inclusive. -main = +main! = \_args -> - # Initialize "randomness" - initialSeed = Random.seed 42 - - # Create a generator for values from 25-75 (inclusive) - generator = Random.int 25 75 - - # Create a list of random numbers - result = randomList initialSeed generator - - # Format as a string - numbersListStr = - result.numbers + # Print a list of 10 random numbers in the range 25-75 inclusive. + numbers_str = + randomNumbers |> List.map Num.toStr - |> Str.joinWith "," - Stdout.line! "Random numbers are: $(numbersListStr)" + |> Str.joinWith "\n" -# Generate a list of numbers using the seed and generator provided + Stdout.line! numbers_str + +# Generate a list random numbers using the seed `1234`. # This is NOT cryptograhpically secure! -randomList = \initialSeed, generator -> - List.range { start: At 0, end: Before 10 } - |> List.walk { seed: initialSeed, numbers: [] } \state, _ -> - # Use the generator to get a new seed and value - random = generator state.seed +randomNumbers : List U32 +randomNumbers = + { value: numbers } = Random.step (Random.seed 1234) numbersGenerator - # Update seed so it can be used to generate the next value - seed = random.state + numbers - # Append the latest random value to the list of numbers - numbers = List.append state.numbers random.value +# A generator that will produce a list of 10 random numbers in the range 25-75 inclusive. +# This is NOT cryptograhpically secure! +numbersGenerator : Random.Generator (List U32) +numbersGenerator = + Random.list (Random.boundedU32 25 75) 10 - { seed, numbers } +expect + actual = randomNumbers + actual == [52, 34, 26, 69, 34, 35, 51, 74, 70, 39] diff --git a/examples/RecordBuilder/DateParser.roc b/examples/RecordBuilder/DateParser.roc index 908f2cb..7790ce3 100644 --- a/examples/RecordBuilder/DateParser.roc +++ b/examples/RecordBuilder/DateParser.roc @@ -1,17 +1,17 @@ module [ ParserGroup, ParserErr, - parseWith, - chainParsers, - buildSegmentParser, + parse_with, + chain_parsers, + build_segment_parser, ] ParserErr : [InvalidNumStr, OutOfSegments] ParserGroup a := List Str -> Result (a, List Str) ParserErr -parseWith : (Str -> Result a ParserErr) -> ParserGroup a -parseWith = \parser -> +parse_with : (Str -> Result a ParserErr) -> ParserGroup a +parse_with = \parser -> @ParserGroup \segments -> when segments is [] -> Err OutOfSegments @@ -19,31 +19,31 @@ parseWith = \parser -> parsed = parser? first Ok (parsed, rest) -chainParsers : ParserGroup a, ParserGroup b, (a, b -> c) -> ParserGroup c -chainParsers = \@ParserGroup first, @ParserGroup second, combiner -> +chain_parsers : ParserGroup a, ParserGroup b, (a, b -> c) -> ParserGroup c +chain_parsers = \@ParserGroup first, @ParserGroup second, combiner -> @ParserGroup \segments -> - (a, afterFirst) = first? segments - (b, afterSecond) = second? afterFirst + (a, after_first) = first? segments + (b, after_second) = second? after_first - Ok (combiner a b, afterSecond) + Ok (combiner a b, after_second) -buildSegmentParser : ParserGroup a -> (Str -> Result a ParserErr) -buildSegmentParser = \@ParserGroup parserGroup -> +build_segment_parser : ParserGroup a -> (Str -> Result a ParserErr) +build_segment_parser = \@ParserGroup parser_group -> \text -> segments = Str.splitOn text "-" - (date, _remaining) = parserGroup? segments + (date, _remaining) = parser_group? segments Ok date expect - dateParser = - { chainParsers <- - month: parseWith Ok, - day: parseWith Str.toU64, - year: parseWith Str.toU64, + date_parser = + { chain_parsers <- + month: parse_with Ok, + day: parse_with Str.toU64, + year: parse_with Str.toU64, } - |> buildSegmentParser + |> build_segment_parser - date = dateParser "Mar-10-2015" + date = date_parser "Mar-10-2015" date == Ok { month: "Mar", day: 10, year: 2015 } diff --git a/examples/Results/README.md b/examples/Results/README.md index aac0d6d..3b42141 100644 --- a/examples/Results/README.md +++ b/examples/Results/README.md @@ -1,5 +1,7 @@ # Results & Error Handling +TODO update this example with a snippet using the `try` keyword, see issue #227 + This example shows how to use [`Result`](https://www.roc-lang.org/builtins/Result) in functions that can return errors. We will see how to use `Result.try` or the try operator `?` to chain functions and return the first error if any occurs. ## Code diff --git a/examples/Results/main.roc b/examples/Results/main.roc index bae6dc6..7d1a380 100644 --- a/examples/Results/main.roc +++ b/examples/Results/main.roc @@ -1,5 +1,5 @@ -app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", +app [main!] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br", } import pf.Stdout @@ -8,14 +8,14 @@ import pf.Stdout ## and if successful returns `Ok {firstName, lastName, birthYear}`. Otherwise ## it returns an `Err` containing a descriptive tag. ## This is the most verbose version, we will do better below. -parseVerbose = \line -> +parse_verbose = \line -> when line |> Str.splitFirst " was born in " is - Ok { before: fullName, after: birthYearStr } -> - when fullName |> Str.splitFirst " " is - Ok { before: firstName, after: lastName } -> - when Str.toU16 birthYearStr is - Ok birthYear -> - Ok { firstName, lastName, birthYear } + Ok { before: full_name, after: birth_year_str } -> + when full_name |> Str.splitFirst " " is + Ok { before: first_name, after: last_name } -> + when Str.toU16 birth_year_str is + Ok birth_year -> + Ok { first_name, last_name, birth_year } Err _ -> Err InvalidBirthYearFormat @@ -26,67 +26,67 @@ parseVerbose = \line -> ## Here's a very slightly shorter version using `Result.try` to chain multiple ## functions that each could return an error. It's a bit nicer, don't you think? ## Note: this version returns "raw" errors (`Err NotFound` or `Err InvalidNumStr`). -parseWithTry = \line -> +parse_with_try = \line -> line |> Str.splitFirst " was born in " - |> Result.try \{ before: fullName, after: birthYearStr } -> - fullName + |> Result.try \{ before: full_name, after: birth_year_str } -> + full_name |> Str.splitFirst " " - |> Result.try \{ before: firstName, after: lastName } -> - Str.toU16 birthYearStr - |> Result.try \birthYear -> - Ok { firstName, lastName, birthYear } + |> Result.try \{ before: first_name, after: last_name } -> + Str.toU16 birth_year_str + |> Result.try \birth_year -> + Ok { first_name, last_name, birth_year } ## This version is like `parseWithTry`, except it uses `Result.mapErr` ## to return more informative errors, just like the ones in `parseVerbose`. -parseWithTryV2 = \line -> +parse_with_try_v2 = \line -> line |> Str.splitFirst " was born in " |> Result.mapErr \_ -> Err InvalidRecordFormat - |> Result.try \{ before: fullName, after: birthYearStr } -> - fullName + |> Result.try \{ before: full_name, after: birth_year_str } -> + full_name |> Str.splitFirst " " |> Result.mapErr \_ -> Err InvalidNameFormat - |> Result.try \{ before: firstName, after: lastName } -> - Str.toU16 birthYearStr + |> Result.try \{ before: first_name, after: last_name } -> + Str.toU16 birth_year_str |> Result.mapErr \_ -> Err InvalidBirthYearFormat - |> Result.try \birthYear -> - Ok { firstName, lastName, birthYear } + |> Result.try \birth_year -> + Ok { first_name, last_name, birth_year } ## The `?` operator, called the "try operator", is ## [syntactic sugar](en.wikipedia.org/wiki/Syntactic_sugar) for `Result.try`. ## It makes the code much less nested and easier to read. ## The following function is equivalent to `parseWithTry`: -parseWithTryOp = \line -> - { before: fullName, after: birthYearStr } = Str.splitFirst? line " was born in " - { before: firstName, after: lastName } = Str.splitFirst? fullName " " - birthYear = Str.toU16? birthYearStr - Ok { firstName, lastName, birthYear } +parse_with_try_op = \line -> + { before: full_name, after: birth_year_str } = Str.splitFirst? line " was born in " + { before: first_name, after: last_name } = Str.splitFirst? full_name " " + birth_year = Str.toU16? birth_year_str + Ok { first_name, last_name, birth_year } ## And lastly the following function is equivalent to `parseWithTryV2`. ## Note that the `?` operator has moved from `splitFirst` & `toU16` to `mapErr`: -parseWithTryOpV2 = \line -> - { before: fullName, after: birthYearStr } = +parse_with_try_op_v2 = \line -> + { before: full_name, after: birth_year_str } = line |> Str.splitFirst " was born in " |> Result.mapErr? \_ -> Err InvalidRecordFormat - { before: firstName, after: lastName } = - fullName + { before: first_name, after: last_name } = + full_name |> Str.splitFirst " " |> Result.mapErr? \_ -> Err InvalidNameFormat - birthYear = - Str.toU16 birthYearStr + birth_year = + Str.toU16 birth_year_str |> Result.mapErr? \_ -> Err InvalidBirthYearFormat - Ok { firstName, lastName, birthYear } + Ok { first_name, last_name, birth_year } ## This function parses a string using a given parser and returns a string to ## display to the user. Note how we can handle errors individually or in bulk. parse = \line, parser -> when parser line is - Ok { firstName, lastName, birthYear } -> + Ok { first_name, last_name, birth_year } -> """ - Name: $(lastName), $(firstName) - Born: $(birthYear |> Num.toStr) + Name: $(last_name), $(first_name) + Born: $(birth_year |> Num.toStr) """ @@ -95,9 +95,11 @@ parse = \line, parser -> Err InvalidRecordFormat -> "Oh wow, that's a weird looking record!" _ -> "Something unexpected happened" # Err NotFound or Err InvalidNumStr -main = - "George Harrison was born in 1943" |> parse parseVerbose |> Stdout.line! - "John Lennon was born in 1940" |> parse parseWithTry |> Stdout.line! - "Paul McCartney was born in 1942" |> parse parseWithTryV2 |> Stdout.line! - "Ringo Starr was born in 1940" |> parse parseWithTryOp |> Stdout.line! - "Stuart Sutcliffe was born in 1940" |> parse parseWithTryOpV2 |> Stdout.line! +main! = \_args -> + try Stdout.line! (parse "George Harrison was born in 1943" parse_verbose) + try Stdout.line! (parse "John Lennon was born in 1940" parse_with_try) + try Stdout.line! (parse "Paul McCartney was born in 1942" parse_with_try_v2) + try Stdout.line! (parse "Ringo Starr was born in 1940" parse_with_try_op) + try Stdout.line! (parse "Stuart Sutcliffe was born in 1940" parse_with_try_op_v2) + + Ok {} diff --git a/examples/SafeMath/main.roc b/examples/SafeMath/main.roc index b560703..e44de01 100644 --- a/examples/SafeMath/main.roc +++ b/examples/SafeMath/main.roc @@ -1,4 +1,4 @@ -app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } +app [main!] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br" } import cli.Stdout @@ -13,23 +13,23 @@ import cli.Stdout ## ## Performance note: safe or checked math prevents crashes but also runs slower. ## -safeVariance : List (Frac a) -> Result (Frac a) [EmptyInputList, Overflow] -safeVariance = \maybeEmptyList -> +safe_variance : List (Frac a) -> Result (Frac a) [EmptyInputList, Overflow] +safe_variance = \maybe_empty_list -> # Check length to prevent DivByZero - when List.len maybeEmptyList is + when List.len maybe_empty_list is 0 -> Err EmptyInputList _ -> - nonEmptyList = maybeEmptyList + non_empty_list = maybe_empty_list - n = nonEmptyList |> List.len |> Num.toFrac + n = non_empty_list |> List.len |> Num.toFrac mean = - nonEmptyList # sum of all elements: + non_empty_list # sum of all elements: |> List.walkTry 0.0 (\state, elem -> Num.addChecked state elem) |> Result.map (\x -> x / n) - nonEmptyList + non_empty_list |> List.walkTry 0.0 (\state, elem -> @@ -39,24 +39,24 @@ safeVariance = \maybeEmptyList -> |> Result.try (\z -> Num.addChecked z state)) # ∑ |> Result.map (\x -> x / n) -main = +main! = \_args -> - varianceResult = + variance_result = [46, 69, 32, 60, 52, 41] - |> safeVariance + |> safe_variance |> Result.map Num.toStr |> Result.map (\v -> "σ² = $(v)") - outputStr = - when varianceResult is + output_str = + when variance_result is Ok str -> str Err EmptyInputList -> "Error: EmptyInputList: I can't calculate the variance over an empty list." Err Overflow -> "Error: Overflow: When calculating the variance, a number got too large to store in the available memory for the type." - Stdout.line outputStr + Stdout.line! output_str -expect (safeVariance []) == Err EmptyInputList -expect (safeVariance [0]) == Ok 0 -expect (safeVariance [100]) == Ok 0 -expect (safeVariance [4, 22, 99, 204, 18, 20]) == Ok 5032.138888888888888888 -expect (safeVariance [46, 69, 32, 60, 52, 41]) == Ok 147.666666666666666666 +expect (safe_variance []) == Err EmptyInputList +expect (safe_variance [0]) == Ok 0 +expect (safe_variance [100]) == Ok 0 +expect (safe_variance [4, 22, 99, 204, 18, 20]) == Ok 5032.138888888888888888 +expect (safe_variance [46, 69, 32, 60, 52, 41]) == Ok 147.666666666666666666 diff --git a/examples/TaskLoop/README.md b/examples/TaskLoop/README.md deleted file mode 100644 index 95854b1..0000000 --- a/examples/TaskLoop/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Looping Tasks - -Sometimes, you need to repeat a task, or a chain of tasks, multiple times until a particular event occurs. In roc, you can use `Task.loop` to do this. - -We'll demonstrate this by adding numbers read from stdin until the end of input (Ctrl-D or [end of file](https://en.wikipedia.org/wiki/End-of-file)). - -`Task.loop` starts from an initial state and sends it to a provided function that will return a new Task. -- If this new Task matches `Task.ok (Step newState)` then the loop continues with a new call of the same function, now using `newState`. -- If the Task is of the form `Task.ok (Done finalState)` then the loop stops and returns `finalState`. -- If the Task is a `Task.err err`, then the loop stops and returns the error. - -## Code step by step - -```roc -Task.loop 0 addNumberFromStdin -``` -We provide `loop` with: -- our initial state; the number 0 -- a function that can ingest a number and return a Task; `addNumberFromStdin` - -This function takes our current sum total, reads a line from stdin and returns one of the following: - -- `Task.ok (Step newSum)` -- `Task.ok (Done finalSum)` on Ctrl-D or [end of file](https://en.wikipedia.org/wiki/End-of-file). -- `Task.err (NotNum Str)` if something other than a number was provided to stdin. - -Take a moment to match this behavior with the type signature of `addNumberFromStdin`: -```roc -I64 -> Task [Done I64, Step I64] [NotNum Str] -``` - -This is where the action happens: -```roc -addNumberFromStdin = \sum -> - input = Stdin.line! - - addResult = - when input is - Input text -> - when Str.toI64 text is - Ok num -> - Ok (Step (sum + num)) - - Err InvalidNumStr -> - Err (NotNum text) - - End -> Ok (Done sum) - - Task.fromResult addResult -``` - -## Full Code - -```roc -file:main.roc -``` - -## Output - -Run this from the directory that has `main.roc` in it: - -``` -$ roc main.roc < numbers.txt -Enter some numbers on different lines, then press Ctrl-D to sum them up. -Sum: 178 -``` diff --git a/examples/TaskLoop/main.roc b/examples/TaskLoop/main.roc deleted file mode 100644 index de5f24a..0000000 --- a/examples/TaskLoop/main.roc +++ /dev/null @@ -1,34 +0,0 @@ -app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", -} - -import pf.Stdin -import pf.Stdout -import pf.Stderr - -main = run |> Task.onErr printErr - -run : Task {} _ -run = - Stdout.line! "Enter some numbers on different lines, then press Ctrl-D to sum them up." - - sum = Task.loop! 0 addNumberFromStdin - Stdout.line! "Sum: $(Num.toStr sum)" - -addNumberFromStdin : I64 -> Task [Done I64, Step I64] _ -addNumberFromStdin = \sum -> - when Stdin.line |> Task.result! is - Ok input -> - when Str.toI64 input is - Ok num -> Task.ok (Step (sum + num)) - Err _ -> Task.err (NotNum input) - - Err (StdinErr EndOfFile) -> Task.ok (Done sum) - Err err -> err |> Inspect.toStr |> NotNum |> Task.err - -printErr : _ -> Task {} _ -printErr = \err -> - when err is - NotNum text -> Stderr.line "Error: \"$(text)\" is not a valid I64 number." - _ -> Stderr.line "Error: $(Inspect.toStr err)" - diff --git a/examples/Tasks/README.md b/examples/Tasks/README.md deleted file mode 100644 index 2c25a42..0000000 --- a/examples/Tasks/README.md +++ /dev/null @@ -1,159 +0,0 @@ -# Tasks & Error Handling - -This example shows how to use `Task` with the [basic-cli platform](https://github.com/roc-lang/basic-cli). We'll explain how tasks work while demonstrating how to read command line arguments and environment variables, write files, and fetch content through HTTP. - -We recommend you read the [tasks and backpassings sections in the tutorial](https://www.roc-lang.org/tutorial#tasks) first and open up the [documentation for the basic-cli platform](https://www.roc-lang.org/packages/basic-cli/Task) on the side. - -Remember; a Task represents an [effect](https://en.wikipedia.org/wiki/Side_effect_(computer_science)); an interaction with state outside your Roc program, such as the terminal's standard output, or a file. - -Below we'll introduce the example code step by step, you can check out the full code at any time at the bottom. - -### main - -The [roc-lang/basic-cli](https://github.com/roc-lang/basic-cli) platform requires an application to provide a Task, namely `main : Task {} *`. This task usually represents a sequence or combination of `Tasks`, and will resolve to an empty record `{}`. This is similar to `void` or `unit` in other programming languages. - -The `main` task is run by the platform when the application is executed. It cannot return errors, which is indicated by the `*`. - -For this example, we'll be using the following main: -```roc -main : Task {} * -main = - run - |> Task.onErr handleErr - -# Error : [ FailedToReadArgs, FailedToFetchHtml Str, ... ] - -handleErr : Error -> Task {} * - -run : Task {} Error -``` - -The `run : Task {} Error` task resolves to a success value of an empty record, and if it fails, returns with our custom `Error` type. - -This simplifies error handling so that a single `handleErr` function can be used to handle all the `Error` values that could occur. - -### run - -We want to see how fast our app runs, so we'll start our `run` `Task` by getting the current time. - -```roc -startTime = Utc.now! -``` - -To get the current time, we need to interact with state outside of the roc program. -We can not just calculate the current time, so we use a task, `Utc.now`. -It's type is `Task Utc *`. The task resolves to the [UTC time](https://en.wikipedia.org/wiki/Coordinated_Universal_Time) (since [Epoch](https://en.wikipedia.org/wiki/Unix_time)). - -#### Read an environment variable - -Next up in the task chain we'll read the environment variable `HELLO`: - -```roc -helloEnvVar = readEnvVar! "HELLO" - -# … - -readEnvVar : Str -> Task Str * -``` -And print it (to [stdout](https://en.wikipedia.org/wiki/Standard_streams)): - -```roc -Stdout.line! "HELLO env var was set to $(helloEnvVar)" -``` - -### Command line arguments - - -When reading command line arguments, it's nice to be able to read multiple arguments. We can use [record destructuring](https://www.roc-lang.org/tutorial#record-destructuring) to fit these multiple arguments nicely in our chain: - -```roc -{ url, outputPath } = readArgs! - -# … - -readArgs : Task { url: Str, outputPath: Path } [FailedToReadArgs]_ -``` - -Notice that `readArgs` can actually return an error unlike the previous tasks, namely `FailedToReadArgs`. -By using `!` syntax to chain our tasks we can deal with errors at the end so it doesn't interrupt the flow of our code right now. - -The underscore (`_`) at the end of `[FailedToReadArgs]` is a temporary workaround for [an issue](https://github.com/roc-lang/roc/issues/5660). - -Note: running the formatter on the `readArgs` implementation currently results in a [parser issue](https://github.com/roc-lang/roc/issues/6074), so skip formatting as a temporary workaround until it's fixed. - -### Fetch website content - -We'll use the `url` we obtained in the previous step and retrieve its contents: - -```roc -strHTML = fetchHtml! url -``` - -### Write to a file - -Next up, we'll write our strHTML to a file located at `outputPath`. - -```roc -File.writeUtf8 outputPath strHTML -|> Task.onErr! \_ -> Task.err (FailedToWriteFile outputPath) -``` - -The `File.writeUtf8` task resolves to an empty record if the provided `Str` is sucessfully written. The error type for `writeUtf8` is `[FileWriteErr Path WriteErr]` but we'd like to replace it with our own simpler error here. For that we use `Task.onErr`. - -### List the contents of a directory - -We're going to finish up with something more involved: - -```roc -listCwdContent -|> Task.map \dirContents -> - List.map dirContents Path.display - |> Str.joinWith "," - -|> Task.await! \contentsStr -> - Stdout.line "Contents of current directory: $(contentsStr)" - -# … - -listCwdContent : Task (List Path) [FailedToListCwd]_ -``` - -We call `listCwdContent` to list all files and folders in the current directory. -Next, we take this list of paths, turn them all into `Str` using `Path.display`, and join/concatenate this list with a ",". - -We use `Task.map` to transform the success value of a `Task` into something that is not a `Task`, a `Str` in this case. - -Take a minute to look at the similarities and differences of `Task.map` and `Task.await`: - -```roc -Task.map : Task a b, (a -> c) -> Task c b - -Task.await : Task a b, (a -> Task c b) -> Task c b -``` - -Next, we write our `Str` of combined `dirContents` to `Stdout`. We use `Task.await` because we're passing it a function that returns a `Task` with `Stdout.line`. - -### Feedback - -Tasks are important in roc, we'd love to hear how we can further improve this example. Get in touch on our [group chat](https://roc.zulipchat.com) or [create an issue](https://github.com/roc-lang/examples/issues). - -## Full Code - -```roc -file:main.roc -``` - -## Output - -Run this from the directory that has `main.roc` in it: - -```sh -$ HELLO=1 roc examples/Tasks/main.roc -- "https://www.roc-lang.org" roc.html -HELLO env var was set to 1 -Fetching content from https://www.roc-lang.org... -Saving url HTML to roc.html... -Contents of current directory: [...] -Run time: 329 ms -Done -``` - diff --git a/examples/Tasks/main.roc b/examples/Tasks/main.roc deleted file mode 100644 index 73760a4..0000000 --- a/examples/Tasks/main.roc +++ /dev/null @@ -1,98 +0,0 @@ -app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", -} - -import pf.Stdout -import pf.Stderr -import pf.Arg -import pf.Env -import pf.Http -import pf.Dir -import pf.Utc -import pf.Path exposing [Path] - -main : Task {} _ -main = run |> Task.onErr handleErr - -run : Task {} _ -run = - - # Get time since [Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) - startTime = Utc.now! {} - - # Read the HELLO environment variable - helloEnvVar = - readEnvVar "HELLO" - |> Task.map! \msg -> if Str.isEmpty msg then "was empty" else "was set to $(msg)" - Stdout.line! "HELLO env var $(helloEnvVar)" - - # Read command line arguments - { url, outputPath } = readArgs! - Stdout.line! "Fetching content from $(url)..." - - # Fetch the provided url using HTTP - strHTML = fetchHtml! url - Stdout.line! "Saving url HTML to $(Path.display outputPath)..." - # Write HTML string to a file - Path.writeUtf8 strHTML outputPath - |> Task.onErr! \_ -> Task.err (FailedToWriteFile outputPath) - # Print contents of current working directory - listCwdContent - |> Task.map \dirContents -> - List.map dirContents Path.display - |> Str.joinWith "," - |> Task.await! \contentsStr -> - Stdout.line "Contents of current directory: $(contentsStr)" - - endTime = Utc.now! {} - runTime = Utc.deltaAsMillis startTime endTime |> Num.toStr - Stdout.line! "Run time: $(runTime) ms" - # Final task doesn't need to be awaited - Stdout.line! "Done" - -# NOTE in the future the trailing underscore `_` character will not be necessary. -# This is a temporary workaround until [this issue](https://github.com/roc-lang/roc/issues/5660) -# is resolved. - -readArgs : Task { url : Str, outputPath : Path } [FailedToReadArgs]_ -readArgs = - when Arg.list! {} is - [_, first, second, ..] -> - Task.ok { url: first, outputPath: Path.fromStr second } - - _ -> - Task.err FailedToReadArgs - -readEnvVar : Str -> Task Str []_ -readEnvVar = \envVarName -> - when Env.var envVarName |> Task.result! is - Ok envVarStr if !(Str.isEmpty envVarStr) -> - Task.ok envVarStr - - _ -> - Task.ok "" - -fetchHtml : Str -> Task Str [FailedToFetchHtml _]_ -fetchHtml = \url -> - { Http.defaultRequest & url } - |> Http.send - |> Task.await \resp -> resp |> Http.handleStringResponse |> Task.fromResult - |> Task.mapErr FailedToFetchHtml - -listCwdContent : Task (List Path) [FailedToListCwd]_ -listCwdContent = - Dir.list "." - |> Task.onErr \_ -> Task.err FailedToListCwd - -handleErr : _ -> Task {} _ -handleErr = \err -> - usage = "HELLO=1 roc main.roc -- \"https://www.roc-lang.org\" roc.html" - - errorMsg = - when err is - FailedToReadArgs -> "Failed to read command line arguments, usage: $(usage)" - FailedToFetchHtml httpErr -> "Failed to fetch URL $(Inspect.toStr httpErr), usage: $(usage)" - FailedToWriteFile path -> "Failed to write to file $(Path.display path), usage: $(usage)" - FailedToListCwd -> "Failed to list contents of current directory, usage: $(usage)" - _ -> Inspect.toStr err - Stderr.line! "Error: $(errorMsg)" diff --git a/examples/TowersOfHanoi/Hanoi.roc b/examples/TowersOfHanoi/Hanoi.roc index 744135c..1765c30 100644 --- a/examples/TowersOfHanoi/Hanoi.roc +++ b/examples/TowersOfHanoi/Hanoi.roc @@ -2,23 +2,23 @@ module [ hanoi, ] +State : { + num_disks : U32, # number of disks in the Tower of Hanoi problem + from : Str, # identifier of the source rod + to : Str, # identifier of the target rod + using : Str, # identifier of the auxiliary rod + moves : List (Str, Str), # list of moves accumulated so far +} + ## Solves the Tower of Hanoi problem using recursion. Returns a list of moves ## which represent the solution. -hanoi : - { - numDisks : U32, # number of disks in the Tower of Hanoi problem - from : Str, # identifier of the source rod - to : Str, # identifier of the target rod - using : Str, # identifier of the auxiliary rod - moves : List (Str, Str), # list of moves accumulated so far - } - -> List (Str, Str) -hanoi = \{ numDisks, from, to, using, moves } -> - if numDisks == 1 then +hanoi : State -> List (Str, Str) +hanoi = \{ num_disks, from, to, using, moves } -> + if num_disks == 1 then List.concat moves [(from, to)] else moves1 = hanoi { - numDisks: (numDisks - 1), + num_disks: (num_disks - 1), from, to: using, using: to, @@ -28,20 +28,43 @@ hanoi = \{ numDisks, from, to, using, moves } -> moves2 = List.concat moves1 [(from, to)] hanoi { - numDisks: (numDisks - 1), + num_disks: (num_disks - 1), from: using, to, using: from, moves: moves2, } -start = { numDisks: 0, from: "A", to: "B", using: "C", moves: [] } +start = { num_disks: 0, from: "A", to: "B", using: "C", moves: [] } ## Test Case 1: Tower of Hanoi with 1 disk -expect hanoi { start & numDisks: 1 } == [("A", "B")] +expect + actual = hanoi { start & num_disks: 1 } + actual + == [ + ("A", "B"), + ] ## Test Case 2: Tower of Hanoi with 2 disks -expect hanoi { start & numDisks: 2 } == [("A", "C"), ("A", "B"), ("C", "B")] +expect + actual = hanoi { start & num_disks: 2 } + actual + == [ + ("A", "C"), + ("A", "B"), + ("C", "B"), + ] ## Test Case 3: Tower of Hanoi with 3 disks -expect hanoi { start & numDisks: 3 } == [("A", "B"), ("A", "C"), ("B", "C"), ("A", "B"), ("C", "A"), ("C", "B"), ("A", "B")] +expect + actual = hanoi { start & num_disks: 3 } + actual + == [ + ("A", "B"), + ("A", "C"), + ("B", "C"), + ("A", "B"), + ("C", "A"), + ("C", "B"), + ("A", "B"), + ] diff --git a/examples/Tuples/main.roc b/examples/Tuples/main.roc index d91419f..5441bd7 100644 --- a/examples/Tuples/main.roc +++ b/examples/Tuples/main.roc @@ -1,40 +1,35 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" } +app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br" } import pf.Stdout -main = +main! = \_args -> # a tuple that contains different types - simpleTuple : (Str, Bool, I64) - simpleTuple = ("A String", Bool.true, 15_000_000) + simple_tuple : (Str, Bool, I64) + simple_tuple = ("A String", Bool.true, 15_000_000) # access the items in a tuple by index (starts at 0) - firstItem = simpleTuple.0 - secondItem = if simpleTuple.1 then "true" else "false" - thirdItem = Num.toStr simpleTuple.2 - Stdout.line! + first_item = simple_tuple.0 + second_item = if simple_tuple.1 then "true" else "false" + third_item = Num.toStr simple_tuple.2 + + try + Stdout.line! """ - First is: $(firstItem), - Second is: $(secondItem), - Third is: $(thirdItem). + First is: $(first_item), + Second is: $(second_item), + Third is: $(third_item). """ # You can also use tuples with `when`: - fruitSelection : [Apple, Pear, Banana] - fruitSelection = Pear + fruit_selection : [Apple, Pear, Banana] + fruit_selection = Pear quantity = 12 - when (fruitSelection, quantity) is + when (fruit_selection, quantity) is # TODO re-enable when github.com/roc-lang/roc/issues/5530 is fixed. - # (_, qty) if qty == 0 -> - # Stdout.line! "You also have no fruit." - (Apple, _) -> - Stdout.line! "You also have some apples." - - (Pear, _) -> - Stdout.line! "You also have some pears." - - (Banana, _) -> - Stdout.line! "You also have some bananas." - + # (_, qty) if qty == 0 -> Stdout.line! "You also have no fruit." + (Apple, _) -> Stdout.line! "You also have some apples." + (Pear, _) -> Stdout.line! "You also have some pears." + (Banana, _) -> Stdout.line! "You also have some bananas." diff --git a/examples/index.md b/examples/index.md index bdcf752..de28145 100644 --- a/examples/index.md +++ b/examples/index.md @@ -9,8 +9,8 @@ You can find the source code for all of these at [github.com/roc-lang/examples]( - [Basic Dict Usage](/BasicDict/README.html) - [Tuples](/Tuples/README.html) - [Pattern Matching on Lists](/PatternMatching/README.html) -- [Results & Error Handling](/Results/README.html) -- [Tasks & Error Handling](/Tasks/README.html) +- [Error Handling Basic (Result)](/Results/README.html) +- [Error Handling Real World (Result, try)](/ErrorHandling/README.html) - [Import Files](/IngestFiles/README.html) - [Import from Directory](/ImportFromDirectory/README.html) - [Import Package from Module](/ImportPackageFromModule/README.html) @@ -24,10 +24,9 @@ You can find the source code for all of these at [github.com/roc-lang/examples]( - [Graph Traversal](/GraphTraversal/README.html) - [Parser](/Parser/README.html) - [Arithmetic](/Arithmetic/README.html) -- [Looping Tasks](/TaskLoop/README.html) +- [Looping Tasks](/LoopEffect/README.html) - [Record Builder](/RecordBuilder/README.html) - [Encoding & Decoding Abilities](/EncodeDecode/README.html) -- [Desugaring !](/DesugaringAwait/README.html) - [Desugaring ?](/DesugaringTry/README.html) - [Custom Inspect](/CustomInspect/README.html) - [Least Squares](/LeastSquares/README.html) diff --git a/flake.lock b/flake.lock index 09f6844..ef4af2b 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "lastModified": 1732722421, + "narHash": "sha256-HRJ/18p+WoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg=", "owner": "edolstra", "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "rev": "9ed2ac151eada2306ca8c418ebd97807bb08f6ac", "type": "github" }, "original": { @@ -39,11 +39,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1726560853, - "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -79,17 +79,17 @@ }, "nixpkgs": { "locked": { - "lastModified": 1712163089, - "narHash": "sha256-Um+8kTIrC19vD4/lUCN9/cU9kcOsD1O1m+axJqQPyMM=", + "lastModified": 1722403750, + "narHash": "sha256-tRmn6UiFAPX0m9G1AVcEPjWEOc9BtGsxGcs7Bz3MpsM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "fd281bd6b7d3e32ddfa399853946f782553163b5", + "rev": "184957277e885c06a505db112b35dfbec7c60494", "type": "github" }, "original": { "owner": "nixos", "repo": "nixpkgs", - "rev": "fd281bd6b7d3e32ddfa399853946f782553163b5", + "rev": "184957277e885c06a505db112b35dfbec7c60494", "type": "github" } }, @@ -102,11 +102,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1733120124, - "narHash": "sha256-xqhX2erDfsjBrbT61LJzH124QtOf7/KZyTBpeJOBKas=", + "lastModified": 1735910140, + "narHash": "sha256-v1/8HGe29QO0M9SfZruAMvHgWgm3SJPg7GFEVrXzEPM=", "owner": "roc-lang", "repo": "roc", - "rev": "a7168a4ad6ea9440bc8a8538672c633cf3a2261d", + "rev": "2263d8821a213629ee62193cdf56af9a2796c1c1", "type": "github" }, "original": { @@ -133,11 +133,11 @@ ] }, "locked": { - "lastModified": 1727490462, - "narHash": "sha256-OrrPiNBiikv9BR464XTT75FzOq7tKAvMbMi7YOKVIeg=", + "lastModified": 1732802692, + "narHash": "sha256-kFrxb45qj52TT/OFUFyTdmvXkn/KXDUL0/DOtjHEQvs=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "11a13e50debafae4ae802f1d6b8585101516dd93", + "rev": "34971069ec33755b2adf2481851f66d8ec9a6bfa", "type": "github" }, "original": {