From d66659f49ff8b5394866d3ced0f2dc6c398a3f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 9 Jun 2026 16:55:28 +1200 Subject: [PATCH 001/162] Update exercise to new compiler: accumulate --- .../practice/accumulate/.meta/Example.roc | 24 ++--- .../practice/accumulate/.meta/template.j2 | 56 ++++++----- exercises/practice/accumulate/Accumulate.roc | 11 ++- .../practice/accumulate/accumulate-test.roc | 93 ++++++++++--------- 4 files changed, 102 insertions(+), 82 deletions(-) diff --git a/exercises/practice/accumulate/.meta/Example.roc b/exercises/practice/accumulate/.meta/Example.roc index f62ad429..244efcc3 100644 --- a/exercises/practice/accumulate/.meta/Example.roc +++ b/exercises/practice/accumulate/.meta/Example.roc @@ -1,12 +1,12 @@ -module [accumulate] - -accumulate : List a, (a -> b) -> List b -accumulate = |list, func| - help = |output, input| - when input is - [] -> output - [first, .. as rest] -> - List.append(output, func(first)) - |> help(rest) - - help([], list) +Accumulate :: {}.{ + accumulate : List(a), (a -> b) -> List(b) + accumulate = |list, func| { + help = |output, input| { + match input { + [] => output + [first, .. as rest] => output.append(func(first))->help(rest) + } + } + help([], list) + } +} diff --git a/exercises/practice/accumulate/.meta/template.j2 b/exercises/practice/accumulate/.meta/template.j2 index 3b652ad1..bee045c8 100644 --- a/exercises/practice/accumulate/.meta/template.j2 +++ b/exercises/practice/accumulate/.meta/template.j2 @@ -2,39 +2,51 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake }}] +import {{ exercise | to_pascal }} {% set accumulators = { - "(x) => x * x": "|x|\n x * x", + "(x) => x * x": "|x| { x * x }", "(x) => upcase(x)": "to_upper", "(x) => reverse(x)": "reverse", - "(x) => accumulate([\"1\", \"2\", \"3\"], (y) => x + y)": "|x|\n accumulate([\"1\", \"2\", \"3\"], |y| Str.concat(x, y))" + "(x) => accumulate([\"1\", \"2\", \"3\"], (y) => x + y)": "|x| {\n Accumulate.accumulate([\"1\", \"2\", \"3\"], |y| { Str.concat(x, y) })\n }" } -%} {% for case in cases -%} # {{ case["description"] }} -expect - result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}, {{ accumulators[case["input"]["accumulator"]] }}) +expect { + result = {{ exercise | to_pascal }}.{{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}, {{ accumulators[case["input"]["accumulator"]] }}) result == {{ case["expected"] | to_roc }} +} -{% endfor %} +{% endfor -%} + +reverse_list : List(a) -> List(a) +reverse_list = |list| { + match list { + [] => [] + [first, .. as rest] => reverse_list(rest).append(first) + } +} reverse : Str -> Str -reverse = |str| - Str.to_utf8(str) - |> List.reverse - |> Str.from_utf8 - |> Result.with_default("") +reverse = |str| { + str + .to_utf8() + ->reverse_list + ->Str.from_utf8 + ?? "" +} + +to_upper_char : U8 -> U8 +to_upper_char = |byte| { + if 'a' <= byte and byte <= 'z' { + byte - 'a' + 'A' + } else { + byte + } +} to_upper : Str -> Str -to_upper = |str| - Str.to_utf8(str) - |> List.map( - |byte| - if 'a' <= byte && byte <= 'z' then - byte - 'a' + 'A' - else - byte - ) - |> Str.from_utf8 - |> Result.with_default("") +to_upper = |str| { + str.to_utf8().map(to_upper_char)->Str.from_utf8 ?? "" +} diff --git a/exercises/practice/accumulate/Accumulate.roc b/exercises/practice/accumulate/Accumulate.roc index c06a2698..89634dbf 100644 --- a/exercises/practice/accumulate/Accumulate.roc +++ b/exercises/practice/accumulate/Accumulate.roc @@ -1,5 +1,6 @@ -module [accumulate] - -accumulate : List a, (a -> b) -> List b -accumulate = |list, func| - crash("Please implement 'accumulate'") +Accumulate :: {}.{ + accumulate : List(a), (a -> b) -> List(b) + accumulate = |list, func| { + crash "Please implement 'accumulate'" + } +} diff --git a/exercises/practice/accumulate/accumulate-test.roc b/exercises/practice/accumulate/accumulate-test.roc index c5ccace4..4f03c54e 100644 --- a/exercises/practice/accumulate/accumulate-test.roc +++ b/exercises/practice/accumulate/accumulate-test.roc @@ -1,70 +1,77 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/accumulate/canonical-data.json -# File last updated on 2025-09-15 +# File last updated on 2026-06-09 app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", } import pf.Stdout -main! = |_args| - Stdout.line!("") +main! = |_args| { + Ok({}) +} -import Accumulate exposing [accumulate] +import Accumulate # accumulate empty -expect - result = accumulate( - [], - |x| - x * x, - ) +expect { + result = Accumulate.accumulate([], |x| { x * x }) result == [] +} # accumulate squares -expect - result = accumulate( - [1, 2, 3], - |x| - x * x, - ) +expect { + result = Accumulate.accumulate([1, 2, 3], |x| { x * x }) result == [1, 4, 9] +} # accumulate upcases -expect - result = accumulate(["Hello", "world"], to_upper) +expect { + result = Accumulate.accumulate(["Hello", "world"], to_upper) result == ["HELLO", "WORLD"] +} # accumulate reversed strings -expect - result = accumulate(["the", "quick", "brown", "fox", "etc"], reverse) +expect { + result = Accumulate.accumulate(["the", "quick", "brown", "fox", "etc"], reverse) result == ["eht", "kciuq", "nworb", "xof", "cte"] +} # accumulate recursively -expect - result = accumulate( - ["a", "b", "c"], - |x| - accumulate(["1", "2", "3"], |y| Str.concat(x, y)), - ) +expect { + result = Accumulate.accumulate(["a", "b", "c"], |x| { + Accumulate.accumulate(["1", "2", "3"], |y| { Str.concat(x, y) }) + }) result == [["a1", "a2", "a3"], ["b1", "b2", "b3"], ["c1", "c2", "c3"]] +} + +reverse_list : List(a) -> List(a) +reverse_list = |list| { + match list { + [] => [] + [first, .. as rest] => reverse_list(rest).append(first) + } +} reverse : Str -> Str -reverse = |str| - Str.to_utf8(str) - |> List.reverse - |> Str.from_utf8 - |> Result.with_default("") +reverse = |str| { + str + .to_utf8() + ->reverse_list + ->Str.from_utf8 + ?? "" +} + +to_upper_char : U8 -> U8 +to_upper_char = |byte| { + if 'a' <= byte and byte <= 'z' { + byte - 'a' + 'A' + } else { + byte + } +} to_upper : Str -> Str -to_upper = |str| - Str.to_utf8(str) - |> List.map( - |byte| - if 'a' <= byte and byte <= 'z' then - byte - 'a' + 'A' - else - byte, - ) - |> Str.from_utf8 - |> Result.with_default("") +to_upper = |str| { + str.to_utf8().map(to_upper_char)->Str.from_utf8 ?? "" +} From 622304234e2e82e6e22dc3043b4683da798379b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 9 Jun 2026 17:53:32 +1200 Subject: [PATCH 002/162] Update bin/*, config/*, and docs/* to new Roc --- bin/add-exercise | 9 +++++---- bin/generate_tests.py | 12 +++++++----- bin/install-roc | 9 ++++----- config/generator_macros.j2 | 8 ++++---- docs/SNIPPET.txt | 28 ++++++++++++++-------------- docs/TESTS.md | 4 ++-- 6 files changed, 36 insertions(+), 34 deletions(-) diff --git a/bin/add-exercise b/bin/add-exercise index b813d0ab..8b36a1a8 100755 --- a/bin/add-exercise +++ b/bin/add-exercise @@ -88,10 +88,11 @@ cat << END_TEST > "exercises/practice/${slug}/${slug}-test.roc" END_TEST cat << END_STUB > "exercises/practice/${slug}/${slug_pascal}.roc" -module [${slug_camel}] - -${slug_camel} = |my_arg| - crash "Please implement the '${slug_camel}' function" +${slug_pascal} :: {}.{ + ${slug_camel} = |my_arg| { + crash "Please implement the '${slug_camel}' function" + } +} END_STUB cp "exercises/practice/${slug}/${slug_pascal}.roc" "exercises/practice/${slug}/.meta/Example.roc" diff --git a/bin/generate_tests.py b/bin/generate_tests.py index 25680fef..00147ef3 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -158,8 +158,8 @@ def to_roc_multiline_string(lines: Union[str, List[str]]) -> str: return to_roc_string(lines[0]) else: return "\n".join( - ["", '"""'] + [escape_roc_string_content(line) for line in lines] + ['"""'] - ).replace("$(", "\\$(") + [r'\\' + escape_roc_string_content(line) for line in lines] + ).replace("${", r"\${") def to_roc_tuple(values: Any): @@ -177,7 +177,7 @@ def to_roc_record(obj: Dict[str, Any]): def to_roc_bool(value: bool): - return "Bool.true" if value else "Bool.false" + return "Bool.True" if value else "Bool.False" def to_roc_list(values: Any): @@ -187,7 +187,7 @@ def to_roc_list(values: Any): def to_roc_float(value: Union[int, float]): value = float(value) - return f"{value!r}f64".replace("+", "") + return f"{value!r}.F64".replace("+", "") def to_roc(value: Any) -> str: @@ -379,7 +379,9 @@ def format_file(path: Path) -> NoReturn: """ Runs roc format on file at path """ - subprocess.check_call(["roc", "format", path]) + # TODO: re-enable formatting once it's re-implemented in Roc + #subprocess.check_call(["roc", "format", path]) + pass def drop_timestamp(lines): diff --git a/bin/install-roc b/bin/install-roc index 378b6c18..69c628ee 100755 --- a/bin/install-roc +++ b/bin/install-roc @@ -5,12 +5,11 @@ function download_alpha_release() { local install_dir="${1}" - local install_file="${install_dir}/roc.tar.gz" - local release_url="https://github.com/roc-lang/roc/releases/download/alpha4-rolling/roc-linux_x86_64-alpha4-rolling.tar.gz" + rm -rf "${install_dir}" mkdir -p "${install_dir}" - wget -q -O ${install_file} ${release_url} - tar -xzf "${install_file}" -C "${install_dir}" --strip-components 1 - rm -f "${install_file}" + cd "${install_dir}" + wget --secure-protocol=TLSv1_2 -qO- https://roc-lang.org/install_roc.sh | ROC_CONTINUE_IF_STALE=y ROC_ADD_TO_PATH=n sh + ln -s roc_nightly*/roc roc } function update_shell { diff --git a/config/generator_macros.j2 b/config/generator_macros.j2 index 977c431b..43d58399 100644 --- a/config/generator_macros.j2 +++ b/config/generator_macros.j2 @@ -18,7 +18,7 @@ {% macro header(imports=[], ignore=[]) -%} app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br" + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", {%- if imports -%} {%- for name in imports -%}, {% if name == "unicode" -%} @@ -36,8 +36,8 @@ app [main!] { import pf.Stdout -main! = |_args| - Stdout.line!("") +main! = |_args| { + Ok({}) +} {%- endmacro %} - diff --git a/docs/SNIPPET.txt b/docs/SNIPPET.txt index 6dd0a7c3..b48912cf 100644 --- a/docs/SNIPPET.txt +++ b/docs/SNIPPET.txt @@ -1,14 +1,14 @@ -app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br" } - -import pf.Stdout - -factorial = |number| - when number is - 1 -> 1 - n -> n * factorial (n - 1) - -expect factorial 5 == 1 * 2 * 3 * 4 * 5 - -main! = |_args| - result = factorial 20 |> Num.to_str - Stdout.line! "factorial 20 = ${result}" +factorial = |number| { + match number { + 1 => 1 + n => n * factorial(n - 1) + } +} + +expect factorial(5) == 1 * 2 * 3 * 4 * 5 + +main! = |_args| { + result = factorial(20) + echo!("factorial 20 = ${result.to_str()}") + Ok({}) +} diff --git a/docs/TESTS.md b/docs/TESTS.md index 9c0f85ab..33ffd995 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -22,7 +22,7 @@ roc test hello-world-test.roc If you've solved the exercise, you should see 0 failed tests, for example: ``` -0 failed and 1 passed in 583 ms. +All (5) tests passed in 123.4 ms. ``` However, if your code has any errors, they will look like this: @@ -36,7 +36,7 @@ This expectation failed: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -1 failed and 0 passed in 1264 ms. +1 failed and 0 passed in 123.4 ms. ``` This should help you fix your code. Once your code works, you can submit it using the `exercism submit` command (see `HELP.md` in the exercise directory for more details). From 30ff50e02cffff2783df4baa0d619ae0b277ce1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 9 Jun 2026 18:37:04 +1200 Subject: [PATCH 003/162] Replace 'roc format' with 'roc fmt' and format accumulate-test.roc --- bin/add-exercise | 2 +- bin/generate_tests.py | 5 +- .../practice/accumulate/accumulate-test.roc | 76 ++++++++++++------- 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/bin/add-exercise b/bin/add-exercise index 8b36a1a8..dd464f8a 100755 --- a/bin/add-exercise +++ b/bin/add-exercise @@ -108,7 +108,7 @@ Your next steps are: - Create the example solution in 'exercises/practice/${slug}/.meta/Example.roc' - Verify the example solution by running 'bin/verify-exercises ${slug}' - Edit the stub solution in 'exercises/practice/${slug}/${slug_pascal}.roc' -- Format all your Roc code by running 'roc format' on each .roc file +- Format all your Roc code by running 'roc fmt' on each .roc file - Update the 'difficulty' value for the exercise's entry in the 'config.json' file - Validate CI using 'bin/configlet lint' and 'bin/configlet fmt' NEXT_STEPS diff --git a/bin/generate_tests.py b/bin/generate_tests.py index 00147ef3..0f8617c9 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -377,10 +377,9 @@ def load_additional_tests(exercise: Path) -> List[TypeJSON]: def format_file(path: Path) -> NoReturn: """ - Runs roc format on file at path + Runs roc fmt on file at path """ - # TODO: re-enable formatting once it's re-implemented in Roc - #subprocess.check_call(["roc", "format", path]) + subprocess.check_call(["roc", "fmt", path]) pass diff --git a/exercises/practice/accumulate/accumulate-test.roc b/exercises/practice/accumulate/accumulate-test.roc index 4f03c54e..3f734654 100644 --- a/exercises/practice/accumulate/accumulate-test.roc +++ b/exercises/practice/accumulate/accumulate-test.roc @@ -2,76 +2,94 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/accumulate/canonical-data.json # File last updated on 2026-06-09 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", } import pf.Stdout main! = |_args| { - Ok({}) + Ok({}) } import Accumulate # accumulate empty expect { - result = Accumulate.accumulate([], |x| { x * x }) - result == [] + result = Accumulate.accumulate( + [], + |x| { + x * x + }, + ) + result == [] } # accumulate squares expect { - result = Accumulate.accumulate([1, 2, 3], |x| { x * x }) - result == [1, 4, 9] + result = Accumulate.accumulate( + [1, 2, 3], + |x| { + x * x + }, + ) + result == [1, 4, 9] } # accumulate upcases expect { - result = Accumulate.accumulate(["Hello", "world"], to_upper) - result == ["HELLO", "WORLD"] + result = Accumulate.accumulate(["Hello", "world"], to_upper) + result == ["HELLO", "WORLD"] } # accumulate reversed strings expect { - result = Accumulate.accumulate(["the", "quick", "brown", "fox", "etc"], reverse) - result == ["eht", "kciuq", "nworb", "xof", "cte"] + result = Accumulate.accumulate(["the", "quick", "brown", "fox", "etc"], reverse) + result == ["eht", "kciuq", "nworb", "xof", "cte"] } # accumulate recursively expect { - result = Accumulate.accumulate(["a", "b", "c"], |x| { - Accumulate.accumulate(["1", "2", "3"], |y| { Str.concat(x, y) }) - }) - result == [["a1", "a2", "a3"], ["b1", "b2", "b3"], ["c1", "c2", "c3"]] + result = Accumulate.accumulate( + ["a", "b", "c"], + |x| { + Accumulate.accumulate( + ["1", "2", "3"], + |y| { + Str.concat(x, y) + }, + ) + }, + ) + result == [["a1", "a2", "a3"], ["b1", "b2", "b3"], ["c1", "c2", "c3"]] } reverse_list : List(a) -> List(a) reverse_list = |list| { - match list { - [] => [] - [first, .. as rest] => reverse_list(rest).append(first) - } + match list { + [] => [] + [first, .. as rest] => reverse_list(rest).append(first) + } } reverse : Str -> Str reverse = |str| { - str - .to_utf8() - ->reverse_list - ->Str.from_utf8 - ?? "" + str + .to_utf8() + ->reverse_list() + ->Str.from_utf8() + ?? "" } to_upper_char : U8 -> U8 to_upper_char = |byte| { - if 'a' <= byte and byte <= 'z' { - byte - 'a' + 'A' - } else { - byte - } + if 'a' <= byte and byte <= 'z' { + byte - 'a' + 'A' + } else { + byte + } } to_upper : Str -> Str to_upper = |str| { - str.to_utf8().map(to_upper_char)->Str.from_utf8 ?? "" + str.to_utf8().map(to_upper_char)->Str.from_utf8() ?? "" } From 6bf2a73b1aea8eda149c7eda2910e2774a5f5545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 9 Jun 2026 18:39:46 +1200 Subject: [PATCH 004/162] Format Accumulate.roc and its Example.roc --- .../practice/accumulate/.meta/Example.roc | 20 +++++++++---------- exercises/practice/accumulate/Accumulate.roc | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/exercises/practice/accumulate/.meta/Example.roc b/exercises/practice/accumulate/.meta/Example.roc index 244efcc3..0043c1b6 100644 --- a/exercises/practice/accumulate/.meta/Example.roc +++ b/exercises/practice/accumulate/.meta/Example.roc @@ -1,12 +1,12 @@ Accumulate :: {}.{ - accumulate : List(a), (a -> b) -> List(b) - accumulate = |list, func| { - help = |output, input| { - match input { - [] => output - [first, .. as rest] => output.append(func(first))->help(rest) - } - } - help([], list) - } + accumulate : List(a), (a -> b) -> List(b) + accumulate = |list, func| { + help = |output, input| { + match input { + [] => output + [first, .. as rest] => output.append(func(first))->help(rest) + } + } + help([], list) + } } diff --git a/exercises/practice/accumulate/Accumulate.roc b/exercises/practice/accumulate/Accumulate.roc index 89634dbf..7eb2f534 100644 --- a/exercises/practice/accumulate/Accumulate.roc +++ b/exercises/practice/accumulate/Accumulate.roc @@ -1,6 +1,6 @@ Accumulate :: {}.{ - accumulate : List(a), (a -> b) -> List(b) - accumulate = |list, func| { - crash "Please implement 'accumulate'" - } + accumulate : List(a), (a -> b) -> List(b) + accumulate = |list, func| { + crash "Please implement 'accumulate'" + } } From 9438c21ed813565f626940ed6079384fa09a4b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 9 Jun 2026 18:52:56 +1200 Subject: [PATCH 005/162] Update exercise to new compiler: acronym --- exercises/practice/acronym/.meta/Example.roc | 68 ++++++++++-------- exercises/practice/acronym/.meta/template.j2 | 7 +- exercises/practice/acronym/Acronym.roc | 11 +-- exercises/practice/acronym/acronym-test.roc | 75 +++++++++++--------- 4 files changed, 89 insertions(+), 72 deletions(-) diff --git a/exercises/practice/acronym/.meta/Example.roc b/exercises/practice/acronym/.meta/Example.roc index 4f5eff11..ebee1d8c 100644 --- a/exercises/practice/acronym/.meta/Example.roc +++ b/exercises/practice/acronym/.meta/Example.roc @@ -1,36 +1,42 @@ -module [abbreviate] +Acronym :: {}.{ + abbreviate : Str -> Str + abbreviate = |text| { + bytes = text.to_utf8() -abbreviate : Str -> Str -abbreviate = |text| - bytes = Str.to_utf8(text) + { acronym } = bytes.fold( + { acronym: [], ready_for_letter: Bool.True }, + |state, byte| { + if state.ready_for_letter and is_letter(byte) { + { acronym: state.acronym.append(byte), ready_for_letter: Bool.False } + } else if byte == ' ' or byte == '-' { + { acronym: state.acronym, ready_for_letter: Bool.True } + } else { + state + } + } + ) - { acronym } = List.walk( - bytes, - { acronym: [], ready_for_letter: Bool.true }, - |state, byte| - if state.ready_for_letter and is_letter(byte) then - { acronym: List.append(state.acronym, byte), ready_for_letter: Bool.false } - else if byte == ' ' or byte == '-' then - { acronym: state.acronym, ready_for_letter: Bool.true } - else - state, - ) + capitalized = acronym.map(capitalize) - capitalized = List.map(acronym, capitalize) + match capitalized->Str.from_utf8 { + Err(_) => { crash "There was an error converting the bytes to a Str! This should never happen." } + Ok(str) => str + } + } - when Str.from_utf8(capitalized) is - Err(_) -> crash("There was an error converting the bytes to a Str! This should never happen.") - Ok(str) -> str + is_letter : U8 -> Bool + is_letter = |byte| { + ('a' <= byte and byte <= 'z') + or + ('A' <= byte and byte <= 'Z') + } -is_letter : U8 -> Bool -is_letter = |byte| - ('a' <= byte and byte <= 'z') - or - ('A' <= byte and byte <= 'Z') - -capitalize : U8 -> U8 -capitalize = |byte| - if 'a' <= byte and byte <= 'z' then - byte - 'a' + 'A' - else - byte + capitalize : U8 -> U8 + capitalize = |byte| { + if 'a' <= byte and byte <= 'z' { + byte - 'a' + 'A' + } else { + byte + } + } +} diff --git a/exercises/practice/acronym/.meta/template.j2 b/exercises/practice/acronym/.meta/template.j2 index c49cadb6..8dc6fad8 100644 --- a/exercises/practice/acronym/.meta/template.j2 +++ b/exercises/practice/acronym/.meta/template.j2 @@ -2,12 +2,13 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [abbreviate] +import {{ exercise | to_pascal }} {% for case in cases -%} # {{ case["description"] }} -expect - result = {{ case["property"] | to_snake }}({{ case["input"]["phrase"] | to_roc }}) +expect { + result = {{ exercise | to_pascal }}.{{ case["property"] | to_snake }}({{ case["input"]["phrase"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/acronym/Acronym.roc b/exercises/practice/acronym/Acronym.roc index 61f027e6..97fb4167 100644 --- a/exercises/practice/acronym/Acronym.roc +++ b/exercises/practice/acronym/Acronym.roc @@ -1,5 +1,6 @@ -module [abbreviate] - -abbreviate : Str -> Str -abbreviate = |text| - crash("Please implement the 'abbreviate' function") +Acronym :: {}.{ + abbreviate : Str -> Str + abbreviate = |text| { + crash "Please implement the 'abbreviate' function" + } +} diff --git a/exercises/practice/acronym/acronym-test.roc b/exercises/practice/acronym/acronym-test.roc index bd677916..9ace78b9 100644 --- a/exercises/practice/acronym/acronym-test.roc +++ b/exercises/practice/acronym/acronym-test.roc @@ -1,59 +1,68 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json -# File last updated on 2025-09-15 +# File last updated on 2026-06-09 app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", } import pf.Stdout -main! = |_args| - Stdout.line!("") +main! = |_args| { + Ok({}) +} -import Acronym exposing [abbreviate] +import Acronym # basic -expect - result = abbreviate("Portable Network Graphics") - result == "PNG" +expect { + result = Acronym.abbreviate("Portable Network Graphics") + result == "PNG" +} # lowercase words -expect - result = abbreviate("Ruby on Rails") - result == "ROR" +expect { + result = Acronym.abbreviate("Ruby on Rails") + result == "ROR" +} # punctuation -expect - result = abbreviate("First In, First Out") - result == "FIFO" +expect { + result = Acronym.abbreviate("First In, First Out") + result == "FIFO" +} # all caps word -expect - result = abbreviate("GNU Image Manipulation Program") - result == "GIMP" +expect { + result = Acronym.abbreviate("GNU Image Manipulation Program") + result == "GIMP" +} # punctuation without whitespace -expect - result = abbreviate("Complementary metal-oxide semiconductor") - result == "CMOS" +expect { + result = Acronym.abbreviate("Complementary metal-oxide semiconductor") + result == "CMOS" +} # very long abbreviation -expect - result = abbreviate("Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me") - result == "ROTFLSHTMDCOALM" +expect { + result = Acronym.abbreviate("Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me") + result == "ROTFLSHTMDCOALM" +} # consecutive delimiters -expect - result = abbreviate("Something - I made up from thin air") - result == "SIMUFTA" +expect { + result = Acronym.abbreviate("Something - I made up from thin air") + result == "SIMUFTA" +} # apostrophes -expect - result = abbreviate("Halley's Comet") - result == "HC" +expect { + result = Acronym.abbreviate("Halley's Comet") + result == "HC" +} # underscore emphasis -expect - result = abbreviate("The Road _Not_ Taken") - result == "TRNT" - +expect { + result = Acronym.abbreviate("The Road _Not_ Taken") + result == "TRNT" +} From 28f4a811923ff5f09ad141c75de02e7ef971807a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 9 Jun 2026 19:06:10 +1200 Subject: [PATCH 006/162] Format acronym/.meta/Example.roc --- exercises/practice/acronym/.meta/Example.roc | 72 ++++++++++---------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/exercises/practice/acronym/.meta/Example.roc b/exercises/practice/acronym/.meta/Example.roc index ebee1d8c..e0e90107 100644 --- a/exercises/practice/acronym/.meta/Example.roc +++ b/exercises/practice/acronym/.meta/Example.roc @@ -1,42 +1,44 @@ Acronym :: {}.{ - abbreviate : Str -> Str - abbreviate = |text| { - bytes = text.to_utf8() + abbreviate : Str -> Str + abbreviate = |text| { + bytes = text.to_utf8() - { acronym } = bytes.fold( - { acronym: [], ready_for_letter: Bool.True }, - |state, byte| { - if state.ready_for_letter and is_letter(byte) { - { acronym: state.acronym.append(byte), ready_for_letter: Bool.False } - } else if byte == ' ' or byte == '-' { - { acronym: state.acronym, ready_for_letter: Bool.True } - } else { - state - } - } - ) + { acronym } = bytes.fold( + { acronym: [], ready_for_letter: Bool.True }, + |state, byte| { + if state.ready_for_letter and is_letter(byte) { + { acronym: state.acronym.append(byte), ready_for_letter: Bool.False } + } else if byte == ' ' or byte == '-' { + { acronym: state.acronym, ready_for_letter: Bool.True } + } else { + state + } + }, + ) - capitalized = acronym.map(capitalize) + capitalized = acronym.map(capitalize) - match capitalized->Str.from_utf8 { - Err(_) => { crash "There was an error converting the bytes to a Str! This should never happen." } - Ok(str) => str - } - } + match capitalized->Str.from_utf8() { + Err(_) => { + crash "There was an error converting the bytes to a Str! This should never happen." + } + Ok(str) => str + } + } - is_letter : U8 -> Bool - is_letter = |byte| { - ('a' <= byte and byte <= 'z') - or - ('A' <= byte and byte <= 'Z') - } + is_letter : U8 -> Bool + is_letter = |byte| { + ('a' <= byte and byte <= 'z') + or + ('A' <= byte and byte <= 'Z') + } - capitalize : U8 -> U8 - capitalize = |byte| { - if 'a' <= byte and byte <= 'z' { - byte - 'a' + 'A' - } else { - byte - } - } + capitalize : U8 -> U8 + capitalize = |byte| { + if 'a' <= byte and byte <= 'z' { + byte - 'a' + 'A' + } else { + byte + } + } } From bac505e89b96b34b2090f3f4f62f1e8130ec842f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 11 Jun 2026 15:38:18 +1200 Subject: [PATCH 007/162] Use default platform unless additional libraries are used --- config/generator_macros.j2 | 5 ++-- .../practice/accumulate/.meta/template.j2 | 6 ++--- .../practice/accumulate/accumulate-test.roc | 21 ++++++--------- exercises/practice/acronym/.meta/Example.roc | 26 +++++++++--------- exercises/practice/acronym/.meta/template.j2 | 4 +-- exercises/practice/acronym/acronym-test.roc | 27 ++++++++----------- 6 files changed, 40 insertions(+), 49 deletions(-) diff --git a/config/generator_macros.j2 b/config/generator_macros.j2 index 43d58399..2cbf2519 100644 --- a/config/generator_macros.j2 +++ b/config/generator_macros.j2 @@ -17,9 +17,9 @@ {% macro header(imports=[], ignore=[]) -%} +{%- if imports -%} app [main!] { pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", -{%- if imports -%} {%- for name in imports -%}, {% if name == "unicode" -%} unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br" @@ -31,10 +31,11 @@ app [main!] { parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.10.0/6eZYaXkrakq9fJ4oUc0VfdxU1Fap2iTuAN18q9OgQss.tar.br" {%- endif -%} {%- endfor -%} -{%- endif %} } import pf.Stdout +{%- endif %} + main! = |_args| { Ok({}) diff --git a/exercises/practice/accumulate/.meta/template.j2 b/exercises/practice/accumulate/.meta/template.j2 index bee045c8..55c0455e 100644 --- a/exercises/practice/accumulate/.meta/template.j2 +++ b/exercises/practice/accumulate/.meta/template.j2 @@ -2,19 +2,19 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} +import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake }}] {% set accumulators = { "(x) => x * x": "|x| { x * x }", "(x) => upcase(x)": "to_upper", "(x) => reverse(x)": "reverse", - "(x) => accumulate([\"1\", \"2\", \"3\"], (y) => x + y)": "|x| {\n Accumulate.accumulate([\"1\", \"2\", \"3\"], |y| { Str.concat(x, y) })\n }" + "(x) => accumulate([\"1\", \"2\", \"3\"], (y) => x + y)": "|x| {\n accumulate([\"1\", \"2\", \"3\"], |y| { Str.concat(x, y) })\n }" } -%} {% for case in cases -%} # {{ case["description"] }} expect { - result = {{ exercise | to_pascal }}.{{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}, {{ accumulators[case["input"]["accumulator"]] }}) + result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}, {{ accumulators[case["input"]["accumulator"]] }}) result == {{ case["expected"] | to_roc }} } diff --git a/exercises/practice/accumulate/accumulate-test.roc b/exercises/practice/accumulate/accumulate-test.roc index 3f734654..d726d7de 100644 --- a/exercises/practice/accumulate/accumulate-test.roc +++ b/exercises/practice/accumulate/accumulate-test.roc @@ -1,21 +1,16 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/accumulate/canonical-data.json -# File last updated on 2026-06-09 -app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", -} - -import pf.Stdout +# File last updated on 2026-06-11 main! = |_args| { Ok({}) } -import Accumulate +import Accumulate exposing [accumulate] # accumulate empty expect { - result = Accumulate.accumulate( + result = accumulate( [], |x| { x * x @@ -26,7 +21,7 @@ expect { # accumulate squares expect { - result = Accumulate.accumulate( + result = accumulate( [1, 2, 3], |x| { x * x @@ -37,22 +32,22 @@ expect { # accumulate upcases expect { - result = Accumulate.accumulate(["Hello", "world"], to_upper) + result = accumulate(["Hello", "world"], to_upper) result == ["HELLO", "WORLD"] } # accumulate reversed strings expect { - result = Accumulate.accumulate(["the", "quick", "brown", "fox", "etc"], reverse) + result = accumulate(["the", "quick", "brown", "fox", "etc"], reverse) result == ["eht", "kciuq", "nworb", "xof", "cte"] } # accumulate recursively expect { - result = Accumulate.accumulate( + result = accumulate( ["a", "b", "c"], |x| { - Accumulate.accumulate( + accumulate( ["1", "2", "3"], |y| { Str.concat(x, y) diff --git a/exercises/practice/acronym/.meta/Example.roc b/exercises/practice/acronym/.meta/Example.roc index e0e90107..4880325b 100644 --- a/exercises/practice/acronym/.meta/Example.roc +++ b/exercises/practice/acronym/.meta/Example.roc @@ -25,20 +25,20 @@ Acronym :: {}.{ Ok(str) => str } } +} - is_letter : U8 -> Bool - is_letter = |byte| { - ('a' <= byte and byte <= 'z') - or - ('A' <= byte and byte <= 'Z') - } +is_letter : U8 -> Bool +is_letter = |byte| { + ('a' <= byte and byte <= 'z') + or + ('A' <= byte and byte <= 'Z') +} - capitalize : U8 -> U8 - capitalize = |byte| { - if 'a' <= byte and byte <= 'z' { - byte - 'a' + 'A' - } else { - byte - } +capitalize : U8 -> U8 +capitalize = |byte| { + if 'a' <= byte and byte <= 'z' { + byte - 'a' + 'A' + } else { + byte } } diff --git a/exercises/practice/acronym/.meta/template.j2 b/exercises/practice/acronym/.meta/template.j2 index 8dc6fad8..2d77e7db 100644 --- a/exercises/practice/acronym/.meta/template.j2 +++ b/exercises/practice/acronym/.meta/template.j2 @@ -2,12 +2,12 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} +import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake }}] {% for case in cases -%} # {{ case["description"] }} expect { - result = {{ exercise | to_pascal }}.{{ case["property"] | to_snake }}({{ case["input"]["phrase"] | to_roc }}) + result = {{ case["property"] | to_snake }}({{ case["input"]["phrase"] | to_roc }}) result == {{ case["expected"] | to_roc }} } diff --git a/exercises/practice/acronym/acronym-test.roc b/exercises/practice/acronym/acronym-test.roc index 9ace78b9..877551c4 100644 --- a/exercises/practice/acronym/acronym-test.roc +++ b/exercises/practice/acronym/acronym-test.roc @@ -1,68 +1,63 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json -# File last updated on 2026-06-09 -app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", -} - -import pf.Stdout +# File last updated on 2026-06-11 main! = |_args| { Ok({}) } -import Acronym +import Acronym exposing [abbreviate] # basic expect { - result = Acronym.abbreviate("Portable Network Graphics") + result = abbreviate("Portable Network Graphics") result == "PNG" } # lowercase words expect { - result = Acronym.abbreviate("Ruby on Rails") + result = abbreviate("Ruby on Rails") result == "ROR" } # punctuation expect { - result = Acronym.abbreviate("First In, First Out") + result = abbreviate("First In, First Out") result == "FIFO" } # all caps word expect { - result = Acronym.abbreviate("GNU Image Manipulation Program") + result = abbreviate("GNU Image Manipulation Program") result == "GIMP" } # punctuation without whitespace expect { - result = Acronym.abbreviate("Complementary metal-oxide semiconductor") + result = abbreviate("Complementary metal-oxide semiconductor") result == "CMOS" } # very long abbreviation expect { - result = Acronym.abbreviate("Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me") + result = abbreviate("Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me") result == "ROTFLSHTMDCOALM" } # consecutive delimiters expect { - result = Acronym.abbreviate("Something - I made up from thin air") + result = abbreviate("Something - I made up from thin air") result == "SIMUFTA" } # apostrophes expect { - result = Acronym.abbreviate("Halley's Comet") + result = abbreviate("Halley's Comet") result == "HC" } # underscore emphasis expect { - result = Acronym.abbreviate("The Road _Not_ Taken") + result = abbreviate("The Road _Not_ Taken") result == "TRNT" } From d3fd9b22c686a9fb102b324ff12e53b3f8bb313a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 11 Jun 2026 15:38:53 +1200 Subject: [PATCH 008/162] Update exercise to new compiler: affine-cipher --- .../practice/affine-cipher/.meta/Example.roc | 243 ++++++++++++------ .../practice/affine-cipher/.meta/template.j2 | 19 +- .../practice/affine-cipher/AffineCipher.roc | 80 +++++- .../affine-cipher/affine-cipher-test.roc | 217 ++++++++-------- 4 files changed, 369 insertions(+), 190 deletions(-) diff --git a/exercises/practice/affine-cipher/.meta/Example.roc b/exercises/practice/affine-cipher/.meta/Example.roc index 55b4efa4..e7ed0d86 100644 --- a/exercises/practice/affine-cipher/.meta/Example.roc +++ b/exercises/practice/affine-cipher/.meta/Example.roc @@ -1,79 +1,176 @@ -module [encode, decode] +AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) }.{ + alphabet_size : U64 + alphabet_size = 26 -alphabet_size : U64 -alphabet_size = 26 + group_length : U64 + group_length = 5 -group_length : U64 -group_length = 5 + new : { a : U64, b : U64 } -> Try(AffineCipher, [InvalidKey]) + new = |{ a, b }| { + encode_map : List(U8) + encode_map = + 0.to(alphabet_size - 1) + .map( + |index| { + encoded_index = (a * index + b) % alphabet_size + 'a' + encoded_index.to_u8_wrap() + }, + ) + ->collect() -encode : Str, { a : U64, b : U64 } -> Result Str [InvalidKey, BadUtf8 _ ] -encode = |phrase, key| - alphabet = encoded_alphabet(key)? - phrase - |> Str.to_utf8 - |> List.join_map( - |char| - if char >= '0' and char <= '9' then - [char] - else - char_lower = if char >= 'A' and char <= 'Z' then char - 'A' + 'a' else char - if char_lower >= 'a' and char_lower <= 'z' then - index = char_lower - 'a' |> Num.to_u64 - when alphabet |> List.get(index) is - Ok(encoded_char) -> [encoded_char] - Err(OutOfBounds) -> crash("Unreachable: index cannot be out of bounds here") - else - [], - ) - |> List.chunks_of(group_length) - |> List.intersperse([' ']) - |> List.join - |> Str.from_utf8 + if Set.from_list(encode_map).len() < encode_map.len() { + Err(InvalidKey) + } else { + decode_map : List(U8) + decode_map = + encode_map + .map_with_index( + |encoded, decoded_index| { encoded, decoded_index }, + ) + .sort_with( + |{ encoded: encoded1 }, { encoded: encoded2 }| { + if encoded1 < encoded2 { + LT + } else if encoded1 > encoded2 { + GT + } else { + EQ + } + } + ) + .map( + |pair| { + pair.decoded_index.to_u8_wrap() + 'a' + } + ) -encoded_alphabet : { a : U64, b : U64 } -> Result (List U8) [InvalidKey] -encoded_alphabet = |{ a, b }| - encoded = - List.range({ start: At('a'), end: At('z') }) - |> List.map( - |char| - num = (char - 'a') |> Num.to_u64 - index = (a * num + b) % alphabet_size - 'a' + Num.to_u8(index), - ) - if (encoded |> Set.from_list |> Set.len) < alphabet_size then - Err(InvalidKey) - else - Ok(encoded) + Ok({ a, b, encode_map, decode_map }) + } + } -decoded_alphabet : { a : U64, b : U64 } -> Result (List U8) [InvalidKey] -decoded_alphabet = |key| - encoded_alphabet(key)? - |> List.map_with_index(|encoded, decoded_index| { encoded, decoded_index }) - |> List.sort_with( - |{ encoded: encoded1 }, { encoded: encoded2 }| - Num.compare(encoded1, encoded2), - ) - |> List.map(|pair| Num.to_u8(pair.decoded_index) + 'a') - |> Ok + encode : AffineCipher, Str -> Str + encode = |affine_cipher, phrase| { + maybe_result = phrase + .to_utf8() + ->join_map( + |char| { + if char >= '0' and char <= '9' { + [char] + } else { + char_lower = if char >= 'A' and char <= 'Z' { + char - 'A' + 'a' + } else { + char + } + if char_lower >= 'a' and char_lower <= 'z' { + index = U8.to_u64(char_lower) - 'a' + match affine_cipher.encode_map.get(index) { + Ok(encoded_char) => [encoded_char] + Err(OutOfBounds) => { + crash "Unreachable: index cannot be out of bounds here" + } + } + } else { + [] + } + } + } + ) + ->chunks_of(group_length) + ->intersperse([' ']) + ->join() + ->Str.from_utf8() + match maybe_result { + Ok(result) => result + Err(_) => { crash "Unreachable: ASCII characters are always valid UTF-8" } + } + } -decode : Str, { a : U64, b : U64 } -> Result Str [InvalidKey, BadUtf8 _, InvalidCharacter] -decode = |phrase, key| - alphabet = decoded_alphabet(key)? - phrase - |> Str.to_utf8 - |> List.map_try( - |char| - if char == ' ' then - Ok([]) - else if char >= '0' and char <= '9' then - Ok([char]) - else if char >= 'a' and char <= 'z' then - index = char - 'a' |> Num.to_u64 - when alphabet |> List.get(index) is - Ok(decoded_char) -> Ok([decoded_char]) - Err(OutOfBounds) -> crash("Unreachable: index cannot be out of bounds here") - else - Err(InvalidCharacter), - )? - |> List.join - |> Str.from_utf8 + decode : AffineCipher, Str -> Try(Str, [BadUtf8(_), InvalidCharacter]) + decode = |affine_cipher, phrase| { + phrase + .to_utf8() + ->map_try( + |char| { + if char == ' ' { + Ok([]) + } else if char >= '0' and char <= '9' { + Ok([char]) + } else if char >= 'a' and char <= 'z' { + index = U8.to_u64(char) - 'a' + match affine_cipher.decode_map.get(index) { + Ok(decoded_char) => Ok([decoded_char]) + Err(OutOfBounds) => { + crash "Unreachable: index cannot be out of bounds here" + } + } + } else { + Err(InvalidCharacter) + } + }, + )? + ->join() + ->Str.from_utf8() + } +} + +# The following functions should soon be available in Roc's builtins +collect = |iter| { + var $state = [] + for item in iter { + $state = $state.append(item) + } + $state +} + +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} + +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} + +join = |iter| { + var $state = [] + for sublist in iter { + for item in sublist { + $state = $state.append(item) + } + } + $state +} + +chunks_of = |iter, size| { + var $state = [] + var $chunk = [] + for item in iter { + $chunk = $chunk.append(item) + if $chunk.len() == size { + $state = $state.append($chunk) + $chunk = [] + } + } + if $chunk.len() > 0 { + $state = $state.append($chunk) + } + $state +} + +intersperse = |list, sep| { + match list { + [] => [] + [_] => list + [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) + } +} diff --git a/exercises/practice/affine-cipher/.meta/template.j2 b/exercises/practice/affine-cipher/.meta/template.j2 index 764b52b8..c059a050 100644 --- a/exercises/practice/affine-cipher/.meta/template.j2 +++ b/exercises/practice/affine-cipher/.meta/template.j2 @@ -2,7 +2,7 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [encode, decode] +import {{ exercise | to_pascal }} {% for supercase in cases %} ## @@ -11,16 +11,23 @@ import {{ exercise | to_pascal }} exposing [encode, decode] {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect - phrase = {{ case["input"]["phrase"] | to_roc }} - key = {a: {{ case["input"]["key"]["a"] }}, b: {{ case["input"]["key"]["b"] }}} - result = {{ case["property"] | to_snake }}(phrase, key) +expect { {%- if case["expected"]["error"] %} - result |> Result.is_err + affine_cipher = AffineCipher.new({a: {{ case["input"]["key"]["a"] }}, b: {{ case["input"]["key"]["b"] }}}) + affine_cipher.is_err() + # AffineCipher could not be created, so cannot encode or decode {%- else %} + phrase = {{ case["input"]["phrase"] | to_roc }} + affine_cipher = AffineCipher.new({a: {{ case["input"]["key"]["a"] }}, b: {{ case["input"]["key"]["b"] }}})? + result = affine_cipher.{{ case["property"] | to_snake }}(phrase) + {%- if case["property"] == "encode" %} + expected = {{ case["expected"] | to_roc }} + {%- else -%} expected = Ok({{ case["expected"] | to_roc }}) + {%- endif -%} result == expected {%- endif %} +} {% endfor %} {% endfor %} diff --git a/exercises/practice/affine-cipher/AffineCipher.roc b/exercises/practice/affine-cipher/AffineCipher.roc index 1d0dfd78..09f02c0e 100644 --- a/exercises/practice/affine-cipher/AffineCipher.roc +++ b/exercises/practice/affine-cipher/AffineCipher.roc @@ -1,9 +1,75 @@ -module [encode, decode] +AffineCipher :: {}.{ + alphabet_size : U64 + alphabet_size = 26 -encode : Str, { a : U64, b : U64 } -> Result Str _ -encode = |phrase, key| - crash("Please implement the 'encode' function") + group_length : U64 + group_length = 5 -decode : Str, { a : U64, b : U64 } -> Result Str _ -decode = |phrase, key| - crash("Please implement the 'decode' function") + new : { a : U64, b : U64 } -> Try(AffineCipher, [InvalidKey]) + new = |key| { + crash "Please implement the 'new' method" + } + + encode : Str, AffineCipher -> Try(Str, [BadUtf8(_)]) + encode = |affine_cipher, phrase| { + crash "Please implement the 'encode' method" + } + + decode : Str, AffineCipher -> Try(Str, [BadUtf8(_)]) + decode = |affine_cipher, phrase| { + crash "Please implement the 'decode' method" + } +} + +# The following functions should soon be available in Roc's builtins +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} + +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} + +join = |iter| { + var $state = [] + for sublist in iter { + for item in sublist { + $state = $state.append(item) + } + } + $state +} + +chunks_of = |iter, size| { + var $state = [] + var $chunk = [] + for item in iter { + $chunk = $chunk.append(item) + if $chunk.len() == size { + $state = $state.append($chunk) + $chunk = [] + } + } + if $chunk.len() > 0 { + $state = $state.append($chunk) + } + $state +} + +intersperse = |list, sep| { + match list { + [] => [] + [_] => list + [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) + } +} diff --git a/exercises/practice/affine-cipher/affine-cipher-test.roc b/exercises/practice/affine-cipher/affine-cipher-test.roc index de2cd894..c9e64ff6 100644 --- a/exercises/practice/affine-cipher/affine-cipher-test.roc +++ b/exercises/practice/affine-cipher/affine-cipher-test.roc @@ -1,148 +1,157 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/affine-cipher/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout +# File last updated on 2026-06-11 -main! = |_args| - Stdout.line!("") +main! = |_args| { + Ok({}) +} -import AffineCipher exposing [encode, decode] +import AffineCipher ## ## encode ## # encode yes -expect - phrase = "yes" - key = { a: 5, b: 7 } - result = encode(phrase, key) - expected = Ok("xbt") - result == expected +expect { + phrase = "yes" + affine_cipher = AffineCipher.new({ a: 5, b: 7 })? + result = affine_cipher.encode(phrase) + expected = "xbt" + result == expected +} # encode no -expect - phrase = "no" - key = { a: 15, b: 18 } - result = encode(phrase, key) - expected = Ok("fu") - result == expected +expect { + phrase = "no" + affine_cipher = AffineCipher.new({ a: 15, b: 18 })? + result = affine_cipher.encode(phrase) + expected = "fu" + result == expected +} # encode OMG -expect - phrase = "OMG" - key = { a: 21, b: 3 } - result = encode(phrase, key) - expected = Ok("lvz") - result == expected +expect { + phrase = "OMG" + affine_cipher = AffineCipher.new({ a: 21, b: 3 })? + result = affine_cipher.encode(phrase) + expected = "lvz" + result == expected +} # encode O M G -expect - phrase = "O M G" - key = { a: 25, b: 47 } - result = encode(phrase, key) - expected = Ok("hjp") - result == expected +expect { + phrase = "O M G" + affine_cipher = AffineCipher.new({ a: 25, b: 47 })? + result = affine_cipher.encode(phrase) + expected = "hjp" + result == expected +} # encode mindblowingly -expect - phrase = "mindblowingly" - key = { a: 11, b: 15 } - result = encode(phrase, key) - expected = Ok("rzcwa gnxzc dgt") - result == expected +expect { + phrase = "mindblowingly" + affine_cipher = AffineCipher.new({ a: 11, b: 15 })? + result = affine_cipher.encode(phrase) + expected = "rzcwa gnxzc dgt" + result == expected +} # encode numbers -expect - phrase = "Testing,1 2 3, testing." - key = { a: 3, b: 4 } - result = encode(phrase, key) - expected = Ok("jqgjc rw123 jqgjc rw") - result == expected +expect { + phrase = "Testing,1 2 3, testing." + affine_cipher = AffineCipher.new({ a: 3, b: 4 })? + result = affine_cipher.encode(phrase) + expected = "jqgjc rw123 jqgjc rw" + result == expected +} # encode deep thought -expect - phrase = "Truth is fiction." - key = { a: 5, b: 17 } - result = encode(phrase, key) - expected = Ok("iynia fdqfb ifje") - result == expected +expect { + phrase = "Truth is fiction." + affine_cipher = AffineCipher.new({ a: 5, b: 17 })? + result = affine_cipher.encode(phrase) + expected = "iynia fdqfb ifje" + result == expected +} # encode all the letters -expect - phrase = "The quick brown fox jumps over the lazy dog." - key = { a: 17, b: 33 } - result = encode(phrase, key) - expected = Ok("swxtj npvyk lruol iejdc blaxk swxmh qzglf") - result == expected +expect { + phrase = "The quick brown fox jumps over the lazy dog." + affine_cipher = AffineCipher.new({ a: 17, b: 33 })? + result = affine_cipher.encode(phrase) + expected = "swxtj npvyk lruol iejdc blaxk swxmh qzglf" + result == expected +} # encode with a not coprime to m -expect - phrase = "This is a test." - key = { a: 6, b: 17 } - result = encode(phrase, key) - result |> Result.is_err +expect { + affine_cipher = AffineCipher.new({ a: 6, b: 17 }) + affine_cipher.is_err() + # AffineCipher could not be created, so cannot encode or decode +} ## ## decode ## # decode exercism -expect - phrase = "tytgn fjr" - key = { a: 3, b: 7 } - result = decode(phrase, key) - expected = Ok("exercism") - result == expected +expect { + phrase = "tytgn fjr" + affine_cipher = AffineCipher.new({ a: 3, b: 7 })? + result = affine_cipher.decode(phrase) + expected = Ok("exercism") + result == expected +} # decode a sentence -expect - phrase = "qdwju nqcro muwhn odqun oppmd aunwd o" - key = { a: 19, b: 16 } - result = decode(phrase, key) - expected = Ok("anobstacleisoftenasteppingstone") - result == expected +expect { + phrase = "qdwju nqcro muwhn odqun oppmd aunwd o" + affine_cipher = AffineCipher.new({ a: 19, b: 16 })? + result = affine_cipher.decode(phrase) + expected = Ok("anobstacleisoftenasteppingstone") + result == expected +} # decode numbers -expect - phrase = "odpoz ub123 odpoz ub" - key = { a: 25, b: 7 } - result = decode(phrase, key) - expected = Ok("testing123testing") - result == expected +expect { + phrase = "odpoz ub123 odpoz ub" + affine_cipher = AffineCipher.new({ a: 25, b: 7 })? + result = affine_cipher.decode(phrase) + expected = Ok("testing123testing") + result == expected +} # decode all the letters -expect - phrase = "swxtj npvyk lruol iejdc blaxk swxmh qzglf" - key = { a: 17, b: 33 } - result = decode(phrase, key) - expected = Ok("thequickbrownfoxjumpsoverthelazydog") - result == expected +expect { + phrase = "swxtj npvyk lruol iejdc blaxk swxmh qzglf" + affine_cipher = AffineCipher.new({ a: 17, b: 33 })? + result = affine_cipher.decode(phrase) + expected = Ok("thequickbrownfoxjumpsoverthelazydog") + result == expected +} # decode with no spaces in input -expect - phrase = "swxtjnpvyklruoliejdcblaxkswxmhqzglf" - key = { a: 17, b: 33 } - result = decode(phrase, key) - expected = Ok("thequickbrownfoxjumpsoverthelazydog") - result == expected +expect { + phrase = "swxtjnpvyklruoliejdcblaxkswxmhqzglf" + affine_cipher = AffineCipher.new({ a: 17, b: 33 })? + result = affine_cipher.decode(phrase) + expected = Ok("thequickbrownfoxjumpsoverthelazydog") + result == expected +} # decode with too many spaces -expect - phrase = "vszzm cly yd cg qdp" - key = { a: 15, b: 16 } - result = decode(phrase, key) - expected = Ok("jollygreengiant") - result == expected +expect { + phrase = "vszzm cly yd cg qdp" + affine_cipher = AffineCipher.new({ a: 15, b: 16 })? + result = affine_cipher.decode(phrase) + expected = Ok("jollygreengiant") + result == expected +} # decode with a not coprime to m -expect - phrase = "Test" - key = { a: 13, b: 5 } - result = decode(phrase, key) - result |> Result.is_err - +expect { + affine_cipher = AffineCipher.new({ a: 13, b: 5 }) + affine_cipher.is_err() + # AffineCipher could not be created, so cannot encode or decode +} From 09a2d961a225a739afecfaecd49a79e324992986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 11 Jun 2026 15:58:40 +1200 Subject: [PATCH 009/162] Reorder future builtin functions --- .../practice/affine-cipher/.meta/Example.roc | 86 +++++++++---------- .../practice/affine-cipher/AffineCipher.roc | 58 +++++++------ 2 files changed, 76 insertions(+), 68 deletions(-) diff --git a/exercises/practice/affine-cipher/.meta/Example.roc b/exercises/practice/affine-cipher/.meta/Example.roc index e7ed0d86..2145c329 100644 --- a/exercises/practice/affine-cipher/.meta/Example.roc +++ b/exercises/practice/affine-cipher/.meta/Example.roc @@ -115,6 +115,22 @@ AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) } # The following functions should soon be available in Roc's builtins +chunks_of = |iter, size| { + var $state = [] + var $chunk = [] + for item in iter { + $chunk = $chunk.append(item) + if $chunk.len() == size { + $state = $state.append($chunk) + $chunk = [] + } + } + if $chunk.len() > 0 { + $state = $state.append($chunk) + } + $state +} + collect = |iter| { var $state = [] for item in iter { @@ -123,54 +139,38 @@ collect = |iter| { $state } -map_try = |iter, func| { - var $state = [] - for item in iter { - $state = $state.append(func(item)?) - } - Ok($state) -} - -join_map = |iter, func| { - var $state = [] - for item in iter { - for subitem in func(item) { - $state = $state.append(subitem) - } - } - $state +intersperse = |list, sep| { + match list { + [] => [] + [_] => list + [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) + } } join = |iter| { - var $state = [] - for sublist in iter { - for item in sublist { - $state = $state.append(item) - } - } - $state + var $state = [] + for sublist in iter { + for item in sublist { + $state = $state.append(item) + } + } + $state } -chunks_of = |iter, size| { - var $state = [] - var $chunk = [] - for item in iter { - $chunk = $chunk.append(item) - if $chunk.len() == size { - $state = $state.append($chunk) - $chunk = [] - } - } - if $chunk.len() > 0 { - $state = $state.append($chunk) - } - $state +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state } -intersperse = |list, sep| { - match list { - [] => [] - [_] => list - [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) - } +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) } diff --git a/exercises/practice/affine-cipher/AffineCipher.roc b/exercises/practice/affine-cipher/AffineCipher.roc index 09f02c0e..98a44040 100644 --- a/exercises/practice/affine-cipher/AffineCipher.roc +++ b/exercises/practice/affine-cipher/AffineCipher.roc @@ -1,4 +1,4 @@ -AffineCipher :: {}.{ +AffineCipher :: { a : U64, b : U64 }.{ alphabet_size : U64 alphabet_size = 26 @@ -22,22 +22,36 @@ AffineCipher :: {}.{ } # The following functions should soon be available in Roc's builtins -map_try = |iter, func| { +chunks_of = |iter, size| { var $state = [] + var $chunk = [] for item in iter { - $state = $state.append(func(item)?) + $chunk = $chunk.append(item) + if $chunk.len() == size { + $state = $state.append($chunk) + $chunk = [] + } } - Ok($state) + if $chunk.len() > 0 { + $state = $state.append($chunk) + } + $state } -join_map = |iter, func| { - var $state = [] - for item in iter { - for subitem in func(item) { - $state = $state.append(subitem) - } +collect = |iter| { + var $state = [] + for item in iter { + $state = $state.append(item) + } + $state +} + +intersperse = |list, sep| { + match list { + [] => [] + [_] => list + [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) } - $state } join = |iter| { @@ -50,26 +64,20 @@ join = |iter| { $state } -chunks_of = |iter, size| { +join_map = |iter, func| { var $state = [] - var $chunk = [] for item in iter { - $chunk = $chunk.append(item) - if $chunk.len() == size { - $state = $state.append($chunk) - $chunk = [] + for subitem in func(item) { + $state = $state.append(subitem) } } - if $chunk.len() > 0 { - $state = $state.append($chunk) - } $state } -intersperse = |list, sep| { - match list { - [] => [] - [_] => list - [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) } + Ok($state) } From 5163814d3fd16f12a012fb571f367fc0c8331709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 11 Jun 2026 17:34:17 +1200 Subject: [PATCH 010/162] roc fmt affine-cipher .roc files --- .../practice/affine-cipher/.meta/Example.roc | 94 ++++++++-------- .../practice/affine-cipher/AffineCipher.roc | 106 +++++++++--------- 2 files changed, 101 insertions(+), 99 deletions(-) diff --git a/exercises/practice/affine-cipher/.meta/Example.roc b/exercises/practice/affine-cipher/.meta/Example.roc index 2145c329..624fb1bb 100644 --- a/exercises/practice/affine-cipher/.meta/Example.roc +++ b/exercises/practice/affine-cipher/.meta/Example.roc @@ -7,7 +7,7 @@ AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) new : { a : U64, b : U64 } -> Try(AffineCipher, [InvalidKey]) new = |{ a, b }| { - encode_map : List(U8) + encode_map : List(U8) encode_map = 0.to(alphabet_size - 1) .map( @@ -21,7 +21,7 @@ AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) if Set.from_list(encode_map).len() < encode_map.len() { Err(InvalidKey) } else { - decode_map : List(U8) + decode_map : List(U8) decode_map = encode_map .map_with_index( @@ -36,12 +36,12 @@ AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) } else { EQ } - } + }, ) .map( |pair| { pair.decoded_index.to_u8_wrap() + 'a' - } + }, ) Ok({ a, b, encode_map, decode_map }) @@ -74,16 +74,18 @@ AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) [] } } - } + }, ) ->chunks_of(group_length) ->intersperse([' ']) ->join() ->Str.from_utf8() - match maybe_result { - Ok(result) => result - Err(_) => { crash "Unreachable: ASCII characters are always valid UTF-8" } - } + match maybe_result { + Ok(result) => result + Err(_) => { + crash "Unreachable: ASCII characters are always valid UTF-8" + } + } } decode : AffineCipher, Str -> Try(Str, [BadUtf8(_), InvalidCharacter]) @@ -116,19 +118,19 @@ AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) # The following functions should soon be available in Roc's builtins chunks_of = |iter, size| { - var $state = [] - var $chunk = [] - for item in iter { - $chunk = $chunk.append(item) - if $chunk.len() == size { - $state = $state.append($chunk) - $chunk = [] - } - } - if $chunk.len() > 0 { - $state = $state.append($chunk) - } - $state + var $state = [] + var $chunk = [] + for item in iter { + $chunk = $chunk.append(item) + if $chunk.len() == size { + $state = $state.append($chunk) + $chunk = [] + } + } + if $chunk.len() > 0 { + $state = $state.append($chunk) + } + $state } collect = |iter| { @@ -140,37 +142,37 @@ collect = |iter| { } intersperse = |list, sep| { - match list { - [] => [] - [_] => list - [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) - } + match list { + [] => [] + [_] => list + [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) + } } join = |iter| { - var $state = [] - for sublist in iter { - for item in sublist { - $state = $state.append(item) - } - } - $state + var $state = [] + for sublist in iter { + for item in sublist { + $state = $state.append(item) + } + } + $state } join_map = |iter, func| { - var $state = [] - for item in iter { - for subitem in func(item) { - $state = $state.append(subitem) - } - } - $state + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state } map_try = |iter, func| { - var $state = [] - for item in iter { - $state = $state.append(func(item)?) - } - Ok($state) + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) } diff --git a/exercises/practice/affine-cipher/AffineCipher.roc b/exercises/practice/affine-cipher/AffineCipher.roc index 98a44040..7521ddcc 100644 --- a/exercises/practice/affine-cipher/AffineCipher.roc +++ b/exercises/practice/affine-cipher/AffineCipher.roc @@ -1,41 +1,41 @@ AffineCipher :: { a : U64, b : U64 }.{ - alphabet_size : U64 - alphabet_size = 26 + alphabet_size : U64 + alphabet_size = 26 - group_length : U64 - group_length = 5 + group_length : U64 + group_length = 5 - new : { a : U64, b : U64 } -> Try(AffineCipher, [InvalidKey]) - new = |key| { - crash "Please implement the 'new' method" - } + new : { a : U64, b : U64 } -> Try(AffineCipher, [InvalidKey]) + new = |key| { + crash "Please implement the 'new' method" + } - encode : Str, AffineCipher -> Try(Str, [BadUtf8(_)]) - encode = |affine_cipher, phrase| { - crash "Please implement the 'encode' method" - } + encode : Str, AffineCipher -> Try(Str, [BadUtf8(_)]) + encode = |affine_cipher, phrase| { + crash "Please implement the 'encode' method" + } - decode : Str, AffineCipher -> Try(Str, [BadUtf8(_)]) - decode = |affine_cipher, phrase| { - crash "Please implement the 'decode' method" - } + decode : Str, AffineCipher -> Try(Str, [BadUtf8(_)]) + decode = |affine_cipher, phrase| { + crash "Please implement the 'decode' method" + } } # The following functions should soon be available in Roc's builtins chunks_of = |iter, size| { - var $state = [] - var $chunk = [] - for item in iter { - $chunk = $chunk.append(item) - if $chunk.len() == size { - $state = $state.append($chunk) - $chunk = [] - } - } - if $chunk.len() > 0 { - $state = $state.append($chunk) - } - $state + var $state = [] + var $chunk = [] + for item in iter { + $chunk = $chunk.append(item) + if $chunk.len() == size { + $state = $state.append($chunk) + $chunk = [] + } + } + if $chunk.len() > 0 { + $state = $state.append($chunk) + } + $state } collect = |iter| { @@ -47,37 +47,37 @@ collect = |iter| { } intersperse = |list, sep| { - match list { - [] => [] - [_] => list - [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) - } + match list { + [] => [] + [_] => list + [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) + } } join = |iter| { - var $state = [] - for sublist in iter { - for item in sublist { - $state = $state.append(item) - } - } - $state + var $state = [] + for sublist in iter { + for item in sublist { + $state = $state.append(item) + } + } + $state } join_map = |iter, func| { - var $state = [] - for item in iter { - for subitem in func(item) { - $state = $state.append(subitem) - } - } - $state + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state } map_try = |iter, func| { - var $state = [] - for item in iter { - $state = $state.append(func(item)?) - } - Ok($state) + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) } From a85d19725514d6ab6d7335d30bebe64b12015da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 11 Jun 2026 17:35:27 +1200 Subject: [PATCH 011/162] Update exercise to new compiler: all-your-base --- .../practice/all-your-base/.meta/Example.roc | 75 +++++----- .../practice/all-your-base/.meta/template.j2 | 5 +- .../practice/all-your-base/AllYourBase.roc | 11 +- .../all-your-base/all-your-base-test.roc | 132 ++++++++++-------- 4 files changed, 113 insertions(+), 110 deletions(-) diff --git a/exercises/practice/all-your-base/.meta/Example.roc b/exercises/practice/all-your-base/.meta/Example.roc index 75bdf080..42265136 100644 --- a/exercises/practice/all-your-base/.meta/Example.roc +++ b/exercises/practice/all-your-base/.meta/Example.roc @@ -1,44 +1,33 @@ -module [rebase] +AllYourBase :: {}.{ + rebase : { input_base : U64, output_base : U64, digits : List(U64) } -> Try(List(U64), _) + rebase = |{ input_base, output_base, digits }| { + if input_base <= 1 { + Err(InputBaseMustBeGreaterThanOne) + } else if output_base <= 1 { + Err(OutputBaseMustBeGreaterThanOne) + } else if digits.any( + |digit| { + digit >= input_base + }, + ) { + Err(DigitsMustBeLessThanInputBase) + } else { + number = digits.fold( + 0, + |acc, digit| { + acc * input_base + digit + }, + ) + to_digits(number, output_base)->Ok + } + } +} -rebase : { input_base : U64, output_base : U64, digits : List U64 } -> Result (List U64) _ -rebase = |{ input_base, output_base, digits }| - if input_base <= 1 then - Err(InputBaseMustBeGreaterThanOne) - else if output_base <= 1 then - Err(OutputBaseMustBeGreaterThanOne) - else if List.any(digits, |digit| digit >= input_base) then - Err(DigitsMustBeLessThanInputBase) - else - number_in_base_10 = - List.reverse(digits) - |> List.map_with_index( - |digit, index| - digit * (Num.pow_int(input_base, index)), - ) - |> List.sum - - to_digits(number_in_base_10, output_base) |> Ok - -to_digits : U64, U64 -> List U64 -to_digits = |number, base| - help = |digits, remaining, exponent| - if exponent == 0 then - digits |> List.append(remaining) - else - power_of_base = Num.pow_int(base, exponent) - digit = remaining // power_of_base - List.append(digits, digit) - |> help(Num.rem(remaining, power_of_base), (exponent - 1)) - - leading_exponent = int_log(number, base) - help([], number, leading_exponent) - -# Right now the builtins only have natural log so we have to implement this behaviour ourselves. https://github.com/roc-lang/roc/issues/5107 -int_log : U64, U64 -> U64 -int_log = |number, base| - help = |remaining, exponent| - if remaining < base then - exponent - else - help((remaining // base), (exponent + 1)) - help(number, 0) +to_digits : U64, U64 -> List(U64) +to_digits = |number, base| { + if number < base { + [number] + } else { + to_digits(number // base, base).append(number % base) + } +} diff --git a/exercises/practice/all-your-base/.meta/template.j2 b/exercises/practice/all-your-base/.meta/template.j2 index dbba8591..300d2261 100644 --- a/exercises/practice/all-your-base/.meta/template.j2 +++ b/exercises/practice/all-your-base/.meta/template.j2 @@ -6,12 +6,13 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({ input_base: {{ case["input"]["inputBase"] | to_roc }}, output_base: {{ case["input"]["outputBase"] | to_roc }}, digits: {{ case["input"]["digits"] | to_roc }} }) {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} result == Ok({{ case["expected"] }}) {%- endif %} +} {% endfor %} \ No newline at end of file diff --git a/exercises/practice/all-your-base/AllYourBase.roc b/exercises/practice/all-your-base/AllYourBase.roc index 8d70e5ee..b79fb225 100644 --- a/exercises/practice/all-your-base/AllYourBase.roc +++ b/exercises/practice/all-your-base/AllYourBase.roc @@ -1,5 +1,6 @@ -module [rebase] - -rebase : { input_base : U64, output_base : U64, digits : List U64 } -> Result (List U64) _ -rebase = |{ input_base, output_base, digits }| - crash("Please implement 'rebase'") +AllYourBase :: {}.{ + rebase : { input_base : U64, output_base : U64, digits : List(U64) } -> Try(List(U64), _) + rebase = |{ input_base, output_base, digits }| { + crash "Please implement 'rebase'" + } +} diff --git a/exercises/practice/all-your-base/all-your-base-test.roc b/exercises/practice/all-your-base/all-your-base-test.roc index 98d5ada4..c057de8a 100644 --- a/exercises/practice/all-your-base/all-your-base-test.roc +++ b/exercises/practice/all-your-base/all-your-base-test.roc @@ -1,99 +1,111 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/all-your-base/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout +# File last updated on 2026-06-11 -main! = |_args| - Stdout.line!("") +main! = |_args| { + Ok({}) +} import AllYourBase exposing [rebase] # single bit one to decimal -expect - result = rebase({ input_base: 2, output_base: 10, digits: [1] }) - result == Ok([1]) +expect { + result = rebase({ input_base: 2, output_base: 10, digits: [1] }) + result == Ok([1]) +} # binary to single decimal -expect - result = rebase({ input_base: 2, output_base: 10, digits: [1, 0, 1] }) - result == Ok([5]) +expect { + result = rebase({ input_base: 2, output_base: 10, digits: [1, 0, 1] }) + result == Ok([5]) +} # single decimal to binary -expect - result = rebase({ input_base: 10, output_base: 2, digits: [5] }) - result == Ok([1, 0, 1]) +expect { + result = rebase({ input_base: 10, output_base: 2, digits: [5] }) + result == Ok([1, 0, 1]) +} # binary to multiple decimal -expect - result = rebase({ input_base: 2, output_base: 10, digits: [1, 0, 1, 0, 1, 0] }) - result == Ok([4, 2]) +expect { + result = rebase({ input_base: 2, output_base: 10, digits: [1, 0, 1, 0, 1, 0] }) + result == Ok([4, 2]) +} # decimal to binary -expect - result = rebase({ input_base: 10, output_base: 2, digits: [4, 2] }) - result == Ok([1, 0, 1, 0, 1, 0]) +expect { + result = rebase({ input_base: 10, output_base: 2, digits: [4, 2] }) + result == Ok([1, 0, 1, 0, 1, 0]) +} # trinary to hexadecimal -expect - result = rebase({ input_base: 3, output_base: 16, digits: [1, 1, 2, 0] }) - result == Ok([2, 10]) +expect { + result = rebase({ input_base: 3, output_base: 16, digits: [1, 1, 2, 0] }) + result == Ok([2, 10]) +} # hexadecimal to trinary -expect - result = rebase({ input_base: 16, output_base: 3, digits: [2, 10] }) - result == Ok([1, 1, 2, 0]) +expect { + result = rebase({ input_base: 16, output_base: 3, digits: [2, 10] }) + result == Ok([1, 1, 2, 0]) +} # 15-bit integer -expect - result = rebase({ input_base: 97, output_base: 73, digits: [3, 46, 60] }) - result == Ok([6, 10, 45]) +expect { + result = rebase({ input_base: 97, output_base: 73, digits: [3, 46, 60] }) + result == Ok([6, 10, 45]) +} # empty list -expect - result = rebase({ input_base: 2, output_base: 10, digits: [] }) - result == Ok([0]) +expect { + result = rebase({ input_base: 2, output_base: 10, digits: [] }) + result == Ok([0]) +} # single zero -expect - result = rebase({ input_base: 10, output_base: 2, digits: [0] }) - result == Ok([0]) +expect { + result = rebase({ input_base: 10, output_base: 2, digits: [0] }) + result == Ok([0]) +} # multiple zeros -expect - result = rebase({ input_base: 10, output_base: 2, digits: [0, 0, 0] }) - result == Ok([0]) +expect { + result = rebase({ input_base: 10, output_base: 2, digits: [0, 0, 0] }) + result == Ok([0]) +} # leading zeros -expect - result = rebase({ input_base: 7, output_base: 10, digits: [0, 6, 0] }) - result == Ok([4, 2]) +expect { + result = rebase({ input_base: 7, output_base: 10, digits: [0, 6, 0] }) + result == Ok([4, 2]) +} # input base is one -expect - result = rebase({ input_base: 1, output_base: 10, digits: [0] }) - result |> Result.is_err +expect { + result = rebase({ input_base: 1, output_base: 10, digits: [0] }) + result.is_err() +} # input base is zero -expect - result = rebase({ input_base: 0, output_base: 10, digits: [] }) - result |> Result.is_err +expect { + result = rebase({ input_base: 0, output_base: 10, digits: [] }) + result.is_err() +} # invalid positive digit -expect - result = rebase({ input_base: 2, output_base: 10, digits: [1, 2, 1, 0, 1, 0] }) - result |> Result.is_err +expect { + result = rebase({ input_base: 2, output_base: 10, digits: [1, 2, 1, 0, 1, 0] }) + result.is_err() +} # output base is one -expect - result = rebase({ input_base: 2, output_base: 1, digits: [1, 0, 1, 0, 1, 0] }) - result |> Result.is_err +expect { + result = rebase({ input_base: 2, output_base: 1, digits: [1, 0, 1, 0, 1, 0] }) + result.is_err() +} # output base is zero -expect - result = rebase({ input_base: 10, output_base: 0, digits: [7] }) - result |> Result.is_err - +expect { + result = rebase({ input_base: 10, output_base: 0, digits: [7] }) + result.is_err() +} From 5fe5896fdd2f01c10d39ad71abd77d8f045b6c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 11 Jun 2026 18:05:56 +1200 Subject: [PATCH 012/162] Update exercise to new compiler: allergies --- .../practice/allergies/.meta/Example.roc | 36 +- .../practice/allergies/.meta/template.j2 | 7 +- exercises/practice/allergies/Allergies.roc | 20 +- .../practice/allergies/allergies-test.roc | 363 ++++++++++-------- 4 files changed, 238 insertions(+), 188 deletions(-) diff --git a/exercises/practice/allergies/.meta/Example.roc b/exercises/practice/allergies/.meta/Example.roc index 342f0894..634116ca 100644 --- a/exercises/practice/allergies/.meta/Example.roc +++ b/exercises/practice/allergies/.meta/Example.roc @@ -1,19 +1,21 @@ -module [allergic_to, set] +Allergies :: {}.{ + Allergen : [Eggs, Peanuts, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats] -Allergen : [Eggs, Peanuts, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats] + allergic_to : Allergen, U64 -> Bool + allergic_to = |allergen, score| { + set(score).contains(allergen) + } -allergic_to : Allergen, U64 -> Bool -allergic_to = |allergen, score| - set(score) - |> Set.contains(allergen) - -set : U64 -> Set Allergen -set = |score| - [Eggs, Peanuts, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats] - |> List.walk( - { bits: score, allergies: Set.empty({}) }, - |{ bits, allergies }, allergy| - updated_allergies = if bits % 2 == 0 then allergies else allergies |> Set.insert(allergy) - { bits: bits // 2, allergies: updated_allergies }, - ) - |> .allergies + set : U64 -> Set(Allergen) + set = |score| { + [Eggs, Peanuts, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats] + .fold( + { bits: score, allergies: Set.empty() }, + |{ bits, allergies }, allergy| { + updated_allergies = if bits % 2 == 0 allergies else allergies.insert(allergy) + { bits: bits // 2, allergies: updated_allergies } + }, + ) + .allergies + } +} diff --git a/exercises/practice/allergies/.meta/template.j2 b/exercises/practice/allergies/.meta/template.j2 index 33f1aa05..4a0679e2 100644 --- a/exercises/practice/allergies/.meta/template.j2 +++ b/exercises/practice/allergies/.meta/template.j2 @@ -7,14 +7,15 @@ import {{ exercise | to_pascal }} exposing [allergic_to, set] {% for outerCase in cases -%} {% for case in outerCase["cases"] %} # {{ outerCase["description"] }} {{ case["description"] | default('') }} -expect +expect { {%- if case["property"] == "allergicTo" %} result = allergic_to({{ case["input"]["item"] | to_pascal }}, {{ case["input"]["score"] | to_roc }}) result == {{ case["expected"] | to_roc }} {%- else %} result = set({{ case["input"]["score"] }}) - result == Set.from_list([{{ case["expected"] | map('to_pascal') | join(", ") }}]) + result == [{{ case["expected"] | map('to_pascal') | join(", ") }}]->Set.from_list {%- endif %} +} {% endfor %} -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/exercises/practice/allergies/Allergies.roc b/exercises/practice/allergies/Allergies.roc index 3b437e64..a9f134c5 100644 --- a/exercises/practice/allergies/Allergies.roc +++ b/exercises/practice/allergies/Allergies.roc @@ -1,11 +1,13 @@ -module [allergic_to, set] +Allergies :: {}.{ + Allergen : [Eggs, Peanuts, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats] -Allergen : [Eggs, Peanuts, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats] + allergic_to : Allergen, U64 -> Bool + allergic_to = |allergen, score| { + crash "Please implement 'allergic_to'" + } -allergic_to : Allergen, U64 -> Bool -allergic_to = |allergen, score| - crash("Please implement 'allergic_to'") - -set : U64 -> Set Allergen -set = |score| - crash("Please implement 'set'") + set : U64 -> Set(Allergen) + set = |score| { + crash "Please implement 'set'" + } +} diff --git a/exercises/practice/allergies/allergies-test.roc b/exercises/practice/allergies/allergies-test.roc index c0d481c7..df6fb90b 100644 --- a/exercises/practice/allergies/allergies-test.roc +++ b/exercises/practice/allergies/allergies-test.roc @@ -1,264 +1,309 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/allergies/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout +# File last updated on 2026-06-11 -main! = |_args| - Stdout.line!("") +main! = |_args| { + Ok({}) +} import Allergies exposing [allergic_to, set] # testing for eggs allergy not allergic to anything -expect - result = allergic_to(Eggs, 0) - result == Bool.false +expect { + result = allergic_to(Eggs, 0) + result == Bool.False +} # testing for eggs allergy allergic only to eggs -expect - result = allergic_to(Eggs, 1) - result == Bool.true +expect { + result = allergic_to(Eggs, 1) + result == Bool.True +} # testing for eggs allergy allergic to eggs and something else -expect - result = allergic_to(Eggs, 3) - result == Bool.true +expect { + result = allergic_to(Eggs, 3) + result == Bool.True +} # testing for eggs allergy allergic to something, but not eggs -expect - result = allergic_to(Eggs, 2) - result == Bool.false +expect { + result = allergic_to(Eggs, 2) + result == Bool.False +} # testing for eggs allergy allergic to everything -expect - result = allergic_to(Eggs, 255) - result == Bool.true +expect { + result = allergic_to(Eggs, 255) + result == Bool.True +} # testing for peanuts allergy not allergic to anything -expect - result = allergic_to(Peanuts, 0) - result == Bool.false +expect { + result = allergic_to(Peanuts, 0) + result == Bool.False +} # testing for peanuts allergy allergic only to peanuts -expect - result = allergic_to(Peanuts, 2) - result == Bool.true +expect { + result = allergic_to(Peanuts, 2) + result == Bool.True +} # testing for peanuts allergy allergic to peanuts and something else -expect - result = allergic_to(Peanuts, 7) - result == Bool.true +expect { + result = allergic_to(Peanuts, 7) + result == Bool.True +} # testing for peanuts allergy allergic to something, but not peanuts -expect - result = allergic_to(Peanuts, 5) - result == Bool.false +expect { + result = allergic_to(Peanuts, 5) + result == Bool.False +} # testing for peanuts allergy allergic to everything -expect - result = allergic_to(Peanuts, 255) - result == Bool.true +expect { + result = allergic_to(Peanuts, 255) + result == Bool.True +} # testing for shellfish allergy not allergic to anything -expect - result = allergic_to(Shellfish, 0) - result == Bool.false +expect { + result = allergic_to(Shellfish, 0) + result == Bool.False +} # testing for shellfish allergy allergic only to shellfish -expect - result = allergic_to(Shellfish, 4) - result == Bool.true +expect { + result = allergic_to(Shellfish, 4) + result == Bool.True +} # testing for shellfish allergy allergic to shellfish and something else -expect - result = allergic_to(Shellfish, 14) - result == Bool.true +expect { + result = allergic_to(Shellfish, 14) + result == Bool.True +} # testing for shellfish allergy allergic to something, but not shellfish -expect - result = allergic_to(Shellfish, 10) - result == Bool.false +expect { + result = allergic_to(Shellfish, 10) + result == Bool.False +} # testing for shellfish allergy allergic to everything -expect - result = allergic_to(Shellfish, 255) - result == Bool.true +expect { + result = allergic_to(Shellfish, 255) + result == Bool.True +} # testing for strawberries allergy not allergic to anything -expect - result = allergic_to(Strawberries, 0) - result == Bool.false +expect { + result = allergic_to(Strawberries, 0) + result == Bool.False +} # testing for strawberries allergy allergic only to strawberries -expect - result = allergic_to(Strawberries, 8) - result == Bool.true +expect { + result = allergic_to(Strawberries, 8) + result == Bool.True +} # testing for strawberries allergy allergic to strawberries and something else -expect - result = allergic_to(Strawberries, 28) - result == Bool.true +expect { + result = allergic_to(Strawberries, 28) + result == Bool.True +} # testing for strawberries allergy allergic to something, but not strawberries -expect - result = allergic_to(Strawberries, 20) - result == Bool.false +expect { + result = allergic_to(Strawberries, 20) + result == Bool.False +} # testing for strawberries allergy allergic to everything -expect - result = allergic_to(Strawberries, 255) - result == Bool.true +expect { + result = allergic_to(Strawberries, 255) + result == Bool.True +} # testing for tomatoes allergy not allergic to anything -expect - result = allergic_to(Tomatoes, 0) - result == Bool.false +expect { + result = allergic_to(Tomatoes, 0) + result == Bool.False +} # testing for tomatoes allergy allergic only to tomatoes -expect - result = allergic_to(Tomatoes, 16) - result == Bool.true +expect { + result = allergic_to(Tomatoes, 16) + result == Bool.True +} # testing for tomatoes allergy allergic to tomatoes and something else -expect - result = allergic_to(Tomatoes, 56) - result == Bool.true +expect { + result = allergic_to(Tomatoes, 56) + result == Bool.True +} # testing for tomatoes allergy allergic to something, but not tomatoes -expect - result = allergic_to(Tomatoes, 40) - result == Bool.false +expect { + result = allergic_to(Tomatoes, 40) + result == Bool.False +} # testing for tomatoes allergy allergic to everything -expect - result = allergic_to(Tomatoes, 255) - result == Bool.true +expect { + result = allergic_to(Tomatoes, 255) + result == Bool.True +} # testing for chocolate allergy not allergic to anything -expect - result = allergic_to(Chocolate, 0) - result == Bool.false +expect { + result = allergic_to(Chocolate, 0) + result == Bool.False +} # testing for chocolate allergy allergic only to chocolate -expect - result = allergic_to(Chocolate, 32) - result == Bool.true +expect { + result = allergic_to(Chocolate, 32) + result == Bool.True +} # testing for chocolate allergy allergic to chocolate and something else -expect - result = allergic_to(Chocolate, 112) - result == Bool.true +expect { + result = allergic_to(Chocolate, 112) + result == Bool.True +} # testing for chocolate allergy allergic to something, but not chocolate -expect - result = allergic_to(Chocolate, 80) - result == Bool.false +expect { + result = allergic_to(Chocolate, 80) + result == Bool.False +} # testing for chocolate allergy allergic to everything -expect - result = allergic_to(Chocolate, 255) - result == Bool.true +expect { + result = allergic_to(Chocolate, 255) + result == Bool.True +} # testing for pollen allergy not allergic to anything -expect - result = allergic_to(Pollen, 0) - result == Bool.false +expect { + result = allergic_to(Pollen, 0) + result == Bool.False +} # testing for pollen allergy allergic only to pollen -expect - result = allergic_to(Pollen, 64) - result == Bool.true +expect { + result = allergic_to(Pollen, 64) + result == Bool.True +} # testing for pollen allergy allergic to pollen and something else -expect - result = allergic_to(Pollen, 224) - result == Bool.true +expect { + result = allergic_to(Pollen, 224) + result == Bool.True +} # testing for pollen allergy allergic to something, but not pollen -expect - result = allergic_to(Pollen, 160) - result == Bool.false +expect { + result = allergic_to(Pollen, 160) + result == Bool.False +} # testing for pollen allergy allergic to everything -expect - result = allergic_to(Pollen, 255) - result == Bool.true +expect { + result = allergic_to(Pollen, 255) + result == Bool.True +} # testing for cats allergy not allergic to anything -expect - result = allergic_to(Cats, 0) - result == Bool.false +expect { + result = allergic_to(Cats, 0) + result == Bool.False +} # testing for cats allergy allergic only to cats -expect - result = allergic_to(Cats, 128) - result == Bool.true +expect { + result = allergic_to(Cats, 128) + result == Bool.True +} # testing for cats allergy allergic to cats and something else -expect - result = allergic_to(Cats, 192) - result == Bool.true +expect { + result = allergic_to(Cats, 192) + result == Bool.True +} # testing for cats allergy allergic to something, but not cats -expect - result = allergic_to(Cats, 64) - result == Bool.false +expect { + result = allergic_to(Cats, 64) + result == Bool.False +} # testing for cats allergy allergic to everything -expect - result = allergic_to(Cats, 255) - result == Bool.true +expect { + result = allergic_to(Cats, 255) + result == Bool.True +} # list when: no allergies -expect - result = set(0) - result == Set.from_list([]) +expect { + result = set(0) + result == []->Set.from_list() +} # list when: just eggs -expect - result = set(1) - result == Set.from_list([Eggs]) +expect { + result = set(1) + result == [Eggs]->Set.from_list() +} # list when: just peanuts -expect - result = set(2) - result == Set.from_list([Peanuts]) +expect { + result = set(2) + result == [Peanuts]->Set.from_list() +} # list when: just strawberries -expect - result = set(8) - result == Set.from_list([Strawberries]) +expect { + result = set(8) + result == [Strawberries]->Set.from_list() +} # list when: eggs and peanuts -expect - result = set(3) - result == Set.from_list([Eggs, Peanuts]) +expect { + result = set(3) + result == [Eggs, Peanuts]->Set.from_list() +} # list when: more than eggs but not peanuts -expect - result = set(5) - result == Set.from_list([Eggs, Shellfish]) +expect { + result = set(5) + result == [Eggs, Shellfish]->Set.from_list() +} # list when: lots of stuff -expect - result = set(248) - result == Set.from_list([Strawberries, Tomatoes, Chocolate, Pollen, Cats]) +expect { + result = set(248) + result == [Strawberries, Tomatoes, Chocolate, Pollen, Cats]->Set.from_list() +} # list when: everything -expect - result = set(255) - result == Set.from_list([Eggs, Peanuts, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats]) +expect { + result = set(255) + result == [Eggs, Peanuts, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats]->Set.from_list() +} # list when: no allergen score parts -expect - result = set(509) - result == Set.from_list([Eggs, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats]) +expect { + result = set(509) + result == [Eggs, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats]->Set.from_list() +} # list when: no allergen score parts without highest valid score -expect - result = set(257) - result == Set.from_list([Eggs]) - +expect { + result = set(257) + result == [Eggs]->Set.from_list() +} From d2afec30907e7ce4c1409dfe181cd76195a8ff63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 11 Jun 2026 21:59:12 +1200 Subject: [PATCH 013/162] Simplify match --- .../practice/accumulate/.meta/template.j2 | 49 ++++++++++--------- .../practice/accumulate/accumulate-test.roc | 37 +++++++------- .../practice/affine-cipher/.meta/Example.roc | 3 +- 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/exercises/practice/accumulate/.meta/template.j2 b/exercises/practice/accumulate/.meta/template.j2 index 55c0455e..79c34ec7 100644 --- a/exercises/practice/accumulate/.meta/template.j2 +++ b/exercises/practice/accumulate/.meta/template.j2 @@ -7,7 +7,7 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% set accumulators = { "(x) => x * x": "|x| { x * x }", "(x) => upcase(x)": "to_upper", - "(x) => reverse(x)": "reverse", + "(x) => reverse(x)": "str_reverse", "(x) => accumulate([\"1\", \"2\", \"3\"], (y) => x + y)": "|x| {\n accumulate([\"1\", \"2\", \"3\"], |y| { Str.concat(x, y) })\n }" } -%} @@ -20,33 +20,36 @@ expect { {% endfor -%} -reverse_list : List(a) -> List(a) -reverse_list = |list| { - match list { - [] => [] - [first, .. as rest] => reverse_list(rest).append(first) - } -} - -reverse : Str -> Str -reverse = |str| { - str - .to_utf8() - ->reverse_list - ->Str.from_utf8 - ?? "" -} to_upper_char : U8 -> U8 to_upper_char = |byte| { - if 'a' <= byte and byte <= 'z' { - byte - 'a' + 'A' - } else { - byte - } + if 'a' <= byte and byte <= 'z' { + byte - 'a' + 'A' + } else { + byte + } } to_upper : Str -> Str to_upper = |str| { - str.to_utf8().map(to_upper_char)->Str.from_utf8 ?? "" + str.to_utf8().map(to_upper_char)->Str.from_utf8() ?? "" +} + +str_reverse : Str -> Str +str_reverse = |str| { + str + .to_utf8() + ->reverse() + ->Str.from_utf8() + ?? "" +} + +# List.reverse should soon be available in Roc's builtins +reverse : List(a) -> List(a) +reverse = |list| { + match list { + [] => [] + [first, .. as rest] => reverse(rest).append(first) + } } + diff --git a/exercises/practice/accumulate/accumulate-test.roc b/exercises/practice/accumulate/accumulate-test.roc index d726d7de..7574a341 100644 --- a/exercises/practice/accumulate/accumulate-test.roc +++ b/exercises/practice/accumulate/accumulate-test.roc @@ -38,7 +38,7 @@ expect { # accumulate reversed strings expect { - result = accumulate(["the", "quick", "brown", "fox", "etc"], reverse) + result = accumulate(["the", "quick", "brown", "fox", "etc"], str_reverse) result == ["eht", "kciuq", "nworb", "xof", "cte"] } @@ -58,23 +58,6 @@ expect { result == [["a1", "a2", "a3"], ["b1", "b2", "b3"], ["c1", "c2", "c3"]] } -reverse_list : List(a) -> List(a) -reverse_list = |list| { - match list { - [] => [] - [first, .. as rest] => reverse_list(rest).append(first) - } -} - -reverse : Str -> Str -reverse = |str| { - str - .to_utf8() - ->reverse_list() - ->Str.from_utf8() - ?? "" -} - to_upper_char : U8 -> U8 to_upper_char = |byte| { if 'a' <= byte and byte <= 'z' { @@ -88,3 +71,21 @@ to_upper : Str -> Str to_upper = |str| { str.to_utf8().map(to_upper_char)->Str.from_utf8() ?? "" } + +str_reverse : Str -> Str +str_reverse = |str| { + str + .to_utf8() + ->reverse() + ->Str.from_utf8() + ?? "" +} + +# List.reverse should soon be available in Roc's builtins +reverse : List(a) -> List(a) +reverse = |list| { + match list { + [] => [] + [first, .. as rest] => reverse(rest).append(first) + } +} diff --git a/exercises/practice/affine-cipher/.meta/Example.roc b/exercises/practice/affine-cipher/.meta/Example.roc index 624fb1bb..8086b9cf 100644 --- a/exercises/practice/affine-cipher/.meta/Example.roc +++ b/exercises/practice/affine-cipher/.meta/Example.roc @@ -143,8 +143,7 @@ collect = |iter| { intersperse = |list, sep| { match list { - [] => [] - [_] => list + [] | [_] => list [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) } } From 0d9e3366d6ce04e801fe45746aff55c9c732e4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 11 Jun 2026 23:10:17 +1200 Subject: [PATCH 014/162] Update exercise to new compiler: armstrong-numbers --- .../armstrong-numbers/.meta/Example.roc | 49 ++++++++---- .../armstrong-numbers/.meta/template.j2 | 5 +- .../armstrong-numbers/ArmstrongNumbers.roc | 11 +-- .../armstrong-numbers-test.roc | 76 ++++++++++--------- 4 files changed, 83 insertions(+), 58 deletions(-) diff --git a/exercises/practice/armstrong-numbers/.meta/Example.roc b/exercises/practice/armstrong-numbers/.meta/Example.roc index 87894170..405a1e19 100644 --- a/exercises/practice/armstrong-numbers/.meta/Example.roc +++ b/exercises/practice/armstrong-numbers/.meta/Example.roc @@ -1,17 +1,36 @@ -module [is_armstrong_number] +ArmstrongNumbers :: {}.{ + is_armstrong_number : U64 -> Bool + is_armstrong_number = |number| { + digits = list_digits(number) + len = digits.len() + candidate = + digits + .map( + |digit| { + digit->pow_int(len) + }, + ) + .sum() + candidate == number + } +} -list_digits = |number| - if number < 10 then - [number] - else - (list_digits((number // 10))) |> List.append((number % 10)) +list_digits : U64 -> List(U64) +list_digits = |number| { + if number < 10 { + [number] + } else { + list_digits((number // 10)).append((number % 10)) + } +} -is_armstrong_number : U64 -> Bool -is_armstrong_number = |number| - digits = list_digits(number) - len = List.len(digits) - candidate = - digits - |> List.map(|digit| digit |> Num.pow_int(len)) - |> List.sum - candidate == number +# This function should soon be available in Roc's builtins +pow_int : U64, U64 -> U64 +pow_int = |number, pow| { + 1.to(pow).fold( + 1, + |acc, _| { + acc * number + }, + ) +} diff --git a/exercises/practice/armstrong-numbers/.meta/template.j2 b/exercises/practice/armstrong-numbers/.meta/template.j2 index 3bb36c52..bfcb0467 100644 --- a/exercises/practice/armstrong-numbers/.meta/template.j2 +++ b/exercises/practice/armstrong-numbers/.meta/template.j2 @@ -2,12 +2,13 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [is_armstrong_number] +import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake }}] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["number"] }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/armstrong-numbers/ArmstrongNumbers.roc b/exercises/practice/armstrong-numbers/ArmstrongNumbers.roc index 7f3708af..e59f3046 100644 --- a/exercises/practice/armstrong-numbers/ArmstrongNumbers.roc +++ b/exercises/practice/armstrong-numbers/ArmstrongNumbers.roc @@ -1,5 +1,6 @@ -module [is_armstrong_number] - -is_armstrong_number : U64 -> Bool -is_armstrong_number = |number| - crash("Please implement the 'is_armstrong_number' function") +ArmstrongNumbers :: {}.{ + is_armstrong_number : U64 -> Bool + is_armstrong_number = |number| { + crash "Please implement the 'is_armstrong_number' function" + } +} diff --git a/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc b/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc index 694cb99c..ecd84619 100644 --- a/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc +++ b/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc @@ -1,59 +1,63 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout +# File last updated on 2026-06-11 -main! = |_args| - Stdout.line!("") +main! = |_args| { + Ok({}) +} import ArmstrongNumbers exposing [is_armstrong_number] # Zero is an Armstrong number -expect - result = is_armstrong_number(0) - result == Bool.true +expect { + result = is_armstrong_number(0) + result == Bool.True +} # Single-digit numbers are Armstrong numbers -expect - result = is_armstrong_number(5) - result == Bool.true +expect { + result = is_armstrong_number(5) + result == Bool.True +} # There are no two-digit Armstrong numbers -expect - result = is_armstrong_number(10) - result == Bool.false +expect { + result = is_armstrong_number(10) + result == Bool.False +} # Three-digit number that is an Armstrong number -expect - result = is_armstrong_number(153) - result == Bool.true +expect { + result = is_armstrong_number(153) + result == Bool.True +} # Three-digit number that is not an Armstrong number -expect - result = is_armstrong_number(100) - result == Bool.false +expect { + result = is_armstrong_number(100) + result == Bool.False +} # Four-digit number that is an Armstrong number -expect - result = is_armstrong_number(9474) - result == Bool.true +expect { + result = is_armstrong_number(9474) + result == Bool.True +} # Four-digit number that is not an Armstrong number -expect - result = is_armstrong_number(9475) - result == Bool.false +expect { + result = is_armstrong_number(9475) + result == Bool.False +} # Seven-digit number that is an Armstrong number -expect - result = is_armstrong_number(9926315) - result == Bool.true +expect { + result = is_armstrong_number(9926315) + result == Bool.True +} # Seven-digit number that is not an Armstrong number -expect - result = is_armstrong_number(9926314) - result == Bool.false - +expect { + result = is_armstrong_number(9926314) + result == Bool.False +} From 8047962bbcb4ff16dfc5ca1b9688c2c62f333f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 12 Jun 2026 19:20:16 +1200 Subject: [PATCH 015/162] Move main! to the end of the *-test.roc files and explain why it does nothing --- config/generator_macros.j2 | 11 ++++++----- exercises/practice/accumulate/.meta/template.j2 | 1 + exercises/practice/accumulate/accumulate-test.roc | 11 ++++++----- exercises/practice/acronym/.meta/template.j2 | 2 ++ exercises/practice/acronym/acronym-test.roc | 11 ++++++----- exercises/practice/affine-cipher/.meta/template.j2 | 2 ++ .../practice/affine-cipher/affine-cipher-test.roc | 11 ++++++----- exercises/practice/all-your-base/.meta/template.j2 | 4 +++- .../practice/all-your-base/all-your-base-test.roc | 11 ++++++----- exercises/practice/allergies/.meta/template.j2 | 3 +++ exercises/practice/allergies/allergies-test.roc | 11 ++++++----- .../practice/armstrong-numbers/.meta/template.j2 | 2 ++ .../armstrong-numbers/armstrong-numbers-test.roc | 11 ++++++----- 13 files changed, 55 insertions(+), 36 deletions(-) diff --git a/config/generator_macros.j2 b/config/generator_macros.j2 index 2cbf2519..c0c1fa5c 100644 --- a/config/generator_macros.j2 +++ b/config/generator_macros.j2 @@ -19,7 +19,7 @@ {%- if imports -%} app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst" {%- for name in imports -%}, {% if name == "unicode" -%} unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br" @@ -30,15 +30,16 @@ app [main!] { {%- elif name == "parser" -%} parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.10.0/6eZYaXkrakq9fJ4oUc0VfdxU1Fap2iTuAN18q9OgQss.tar.br" {%- endif -%} -{%- endfor -%} +{%- endfor %} } import pf.Stdout {%- endif %} +{%- endmacro %} +{% macro footer() -%} -main! = |_args| { - Ok({}) -} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { Ok({}) } {%- endmacro %} diff --git a/exercises/practice/accumulate/.meta/template.j2 b/exercises/practice/accumulate/.meta/template.j2 index 79c34ec7..47015ff6 100644 --- a/exercises/practice/accumulate/.meta/template.j2 +++ b/exercises/practice/accumulate/.meta/template.j2 @@ -53,3 +53,4 @@ reverse = |list| { } } +{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/accumulate/accumulate-test.roc b/exercises/practice/accumulate/accumulate-test.roc index 7574a341..14544234 100644 --- a/exercises/practice/accumulate/accumulate-test.roc +++ b/exercises/practice/accumulate/accumulate-test.roc @@ -1,10 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/accumulate/canonical-data.json -# File last updated on 2026-06-11 - -main! = |_args| { - Ok({}) -} +# File last updated on 2026-06-12 import Accumulate exposing [accumulate] @@ -89,3 +85,8 @@ reverse = |list| { [first, .. as rest] => reverse(rest).append(first) } } + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/acronym/.meta/template.j2 b/exercises/practice/acronym/.meta/template.j2 index 2d77e7db..3e36675e 100644 --- a/exercises/practice/acronym/.meta/template.j2 +++ b/exercises/practice/acronym/.meta/template.j2 @@ -12,3 +12,5 @@ expect { } {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/acronym/acronym-test.roc b/exercises/practice/acronym/acronym-test.roc index 877551c4..3fbbeccb 100644 --- a/exercises/practice/acronym/acronym-test.roc +++ b/exercises/practice/acronym/acronym-test.roc @@ -1,10 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json -# File last updated on 2026-06-11 - -main! = |_args| { - Ok({}) -} +# File last updated on 2026-06-12 import Acronym exposing [abbreviate] @@ -61,3 +57,8 @@ expect { result = abbreviate("The Road _Not_ Taken") result == "TRNT" } + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/affine-cipher/.meta/template.j2 b/exercises/practice/affine-cipher/.meta/template.j2 index c059a050..c4985b1f 100644 --- a/exercises/practice/affine-cipher/.meta/template.j2 +++ b/exercises/practice/affine-cipher/.meta/template.j2 @@ -31,3 +31,5 @@ expect { {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/affine-cipher/affine-cipher-test.roc b/exercises/practice/affine-cipher/affine-cipher-test.roc index c9e64ff6..6f351bf3 100644 --- a/exercises/practice/affine-cipher/affine-cipher-test.roc +++ b/exercises/practice/affine-cipher/affine-cipher-test.roc @@ -1,10 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/affine-cipher/canonical-data.json -# File last updated on 2026-06-11 - -main! = |_args| { - Ok({}) -} +# File last updated on 2026-06-12 import AffineCipher @@ -155,3 +151,8 @@ expect { affine_cipher.is_err() # AffineCipher could not be created, so cannot encode or decode } + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/all-your-base/.meta/template.j2 b/exercises/practice/all-your-base/.meta/template.j2 index 300d2261..43c2f13b 100644 --- a/exercises/practice/all-your-base/.meta/template.j2 +++ b/exercises/practice/all-your-base/.meta/template.j2 @@ -15,4 +15,6 @@ expect { {%- endif %} } -{% endfor %} \ No newline at end of file +{% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/all-your-base/all-your-base-test.roc b/exercises/practice/all-your-base/all-your-base-test.roc index c057de8a..02853fab 100644 --- a/exercises/practice/all-your-base/all-your-base-test.roc +++ b/exercises/practice/all-your-base/all-your-base-test.roc @@ -1,10 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/all-your-base/canonical-data.json -# File last updated on 2026-06-11 - -main! = |_args| { - Ok({}) -} +# File last updated on 2026-06-12 import AllYourBase exposing [rebase] @@ -109,3 +105,8 @@ expect { result = rebase({ input_base: 10, output_base: 0, digits: [7] }) result.is_err() } + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/allergies/.meta/template.j2 b/exercises/practice/allergies/.meta/template.j2 index 4a0679e2..d6fa4a2b 100644 --- a/exercises/practice/allergies/.meta/template.j2 +++ b/exercises/practice/allergies/.meta/template.j2 @@ -19,3 +19,6 @@ expect { {% endfor %} {% endfor %} + +{{ macros.footer() }} + diff --git a/exercises/practice/allergies/allergies-test.roc b/exercises/practice/allergies/allergies-test.roc index df6fb90b..fcefcec0 100644 --- a/exercises/practice/allergies/allergies-test.roc +++ b/exercises/practice/allergies/allergies-test.roc @@ -1,10 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/allergies/canonical-data.json -# File last updated on 2026-06-11 - -main! = |_args| { - Ok({}) -} +# File last updated on 2026-06-12 import Allergies exposing [allergic_to, set] @@ -307,3 +303,8 @@ expect { result = set(257) result == [Eggs]->Set.from_list() } + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/armstrong-numbers/.meta/template.j2 b/exercises/practice/armstrong-numbers/.meta/template.j2 index bfcb0467..dbae90c8 100644 --- a/exercises/practice/armstrong-numbers/.meta/template.j2 +++ b/exercises/practice/armstrong-numbers/.meta/template.j2 @@ -12,3 +12,5 @@ expect { } {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc b/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc index ecd84619..84335ae8 100644 --- a/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc +++ b/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc @@ -1,10 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json -# File last updated on 2026-06-11 - -main! = |_args| { - Ok({}) -} +# File last updated on 2026-06-12 import ArmstrongNumbers exposing [is_armstrong_number] @@ -61,3 +57,8 @@ expect { result = is_armstrong_number(9926314) result == Bool.False } + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} From dfd24c79024fae07395975fe820debd947978b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 12 Jun 2026 19:21:27 +1200 Subject: [PATCH 016/162] Update exercise to new compiler: atbash-cipher --- .../practice/atbash-cipher/.meta/Example.roc | 129 ++++++++++---- .../practice/atbash-cipher/.meta/template.j2 | 11 +- .../practice/atbash-cipher/AtbashCipher.roc | 18 +- .../atbash-cipher/atbash-cipher-test.roc | 168 ++++++++++-------- 4 files changed, 203 insertions(+), 123 deletions(-) diff --git a/exercises/practice/atbash-cipher/.meta/Example.roc b/exercises/practice/atbash-cipher/.meta/Example.roc index c28723a6..052dfeab 100644 --- a/exercises/practice/atbash-cipher/.meta/Example.roc +++ b/exercises/practice/atbash-cipher/.meta/Example.roc @@ -1,36 +1,101 @@ -module [encode, decode] +AtbashCipher :: {}.{ + encode : Str -> Try(Str, _) + encode = |phrase| { + phrase + .to_utf8() + ->join_map( + |char| { + if char >= 'A' and char <= 'Z' { + [invert((char - 'A' + 'a'))] + } else if char >= 'a' and char <= 'z' { + [invert(char)] + } else if char >= '0' and char <= '9' { + [char] + } else { + [] + } + }, + ) + ->chunks_of(5) + ->intersperse([' ']) + ->join() + ->Str.from_utf8() + } + + decode : Str -> Try(Str, _) + decode = |phrase| { + phrase + .to_utf8() + .drop_if( + |c| { + c == ' ' + }, + ) + .map( + invert, + ) + ->Str.from_utf8() + } +} invert : U8 -> U8 -invert = |char| - if char >= 'a' and char <= 'z' then - 'z' - char + 'a' - else - char +invert = |char| { + if char >= 'a' and char <= 'z' { + 'z' - char + 'a' + } else { + char + } +} + +# The following functions should soon be available in Roc's builtins +chunks_of = |iter, size| { + var $state = [] + var $chunk = [] + for item in iter { + $chunk = $chunk.append(item) + if $chunk.len() == size { + $state = $state.append($chunk) + $chunk = [] + } + } + if $chunk.len() > 0 { + $state = $state.append($chunk) + } + $state +} + +collect = |iter| { + var $state = [] + for item in iter { + $state = $state.append(item) + } + $state +} + +intersperse = |list, sep| { + match list { + [] => [] + [_] => list + [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) + } +} -encode : Str -> Result Str _ -encode = |phrase| - phrase - |> Str.to_utf8 - |> List.join_map( - |char| - if char >= 'A' and char <= 'Z' then - [invert((char - 'A' + 'a'))] - else if char >= 'a' and char <= 'z' then - [invert(char)] - else if char >= '0' and char <= '9' then - [char] - else - [], - ) - |> List.chunks_of(5) - |> List.intersperse([' ']) - |> List.join - |> Str.from_utf8 +join = |iter| { + var $state = [] + for sublist in iter { + for item in sublist { + $state = $state.append(item) + } + } + $state +} -decode : Str -> Result Str _ -decode = |phrase| - phrase - |> Str.to_utf8 - |> List.drop_if(|c| c == ' ') - |> List.map(invert) - |> Str.from_utf8 +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} diff --git a/exercises/practice/atbash-cipher/.meta/template.j2 b/exercises/practice/atbash-cipher/.meta/template.j2 index 5c7ae3ef..010309da 100644 --- a/exercises/practice/atbash-cipher/.meta/template.j2 +++ b/exercises/practice/atbash-cipher/.meta/template.j2 @@ -11,11 +11,14 @@ import {{ exercise | to_pascal }} exposing [encode, decode] {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect - phrase = {{ case["input"]["phrase"] | to_roc }} - result = phrase |> {{ case["property"] | to_snake }} - expected = {{ case["expected"] | to_roc }} +expect { + phrase = {{ case["input"]["phrase"] | to_roc }} + result = phrase->AtbashCipher.{{ case["property"] | to_snake }} + expected = {{ case["expected"] | to_roc }} result == Ok(expected) +} {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/atbash-cipher/AtbashCipher.roc b/exercises/practice/atbash-cipher/AtbashCipher.roc index 789132c8..f854d09b 100644 --- a/exercises/practice/atbash-cipher/AtbashCipher.roc +++ b/exercises/practice/atbash-cipher/AtbashCipher.roc @@ -1,9 +1,11 @@ -module [encode, decode] +AtbashCipher :: {}.{ + encode : Str -> Try(Str, _) + encode = |phrase| { + crash "Please implement the 'encode' function" + } -encode : Str -> Result Str _ -encode = |phrase| - crash("Please implement the 'encode' function") - -decode : Str -> Result Str _ -decode = |phrase| - crash("Please implement the 'decode' function") + decode : Str -> Try(Str, _) + decode = |phrase| { + crash "Please implement the 'decode' function" + } +} diff --git a/exercises/practice/atbash-cipher/atbash-cipher-test.roc b/exercises/practice/atbash-cipher/atbash-cipher-test.roc index d5deff3d..f9566b57 100644 --- a/exercises/practice/atbash-cipher/atbash-cipher-test.roc +++ b/exercises/practice/atbash-cipher/atbash-cipher-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/atbash-cipher/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-12 import AtbashCipher exposing [encode, decode] @@ -17,104 +9,122 @@ import AtbashCipher exposing [encode, decode] ## # encode yes -expect - phrase = "yes" - result = phrase |> encode - expected = "bvh" - result == Ok(expected) +expect { + phrase = "yes" + result = phrase->AtbashCipher.encode() + expected = "bvh" + result == Ok(expected) +} # encode no -expect - phrase = "no" - result = phrase |> encode - expected = "ml" - result == Ok(expected) +expect { + phrase = "no" + result = phrase->AtbashCipher.encode() + expected = "ml" + result == Ok(expected) +} # encode OMG -expect - phrase = "OMG" - result = phrase |> encode - expected = "lnt" - result == Ok(expected) +expect { + phrase = "OMG" + result = phrase->AtbashCipher.encode() + expected = "lnt" + result == Ok(expected) +} # encode spaces -expect - phrase = "O M G" - result = phrase |> encode - expected = "lnt" - result == Ok(expected) +expect { + phrase = "O M G" + result = phrase->AtbashCipher.encode() + expected = "lnt" + result == Ok(expected) +} # encode mindblowingly -expect - phrase = "mindblowingly" - result = phrase |> encode - expected = "nrmwy oldrm tob" - result == Ok(expected) +expect { + phrase = "mindblowingly" + result = phrase->AtbashCipher.encode() + expected = "nrmwy oldrm tob" + result == Ok(expected) +} # encode numbers -expect - phrase = "Testing,1 2 3, testing." - result = phrase |> encode - expected = "gvhgr mt123 gvhgr mt" - result == Ok(expected) +expect { + phrase = "Testing,1 2 3, testing." + result = phrase->AtbashCipher.encode() + expected = "gvhgr mt123 gvhgr mt" + result == Ok(expected) +} # encode deep thought -expect - phrase = "Truth is fiction." - result = phrase |> encode - expected = "gifgs rhurx grlm" - result == Ok(expected) +expect { + phrase = "Truth is fiction." + result = phrase->AtbashCipher.encode() + expected = "gifgs rhurx grlm" + result == Ok(expected) +} # encode all the letters -expect - phrase = "The quick brown fox jumps over the lazy dog." - result = phrase |> encode - expected = "gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt" - result == Ok(expected) +expect { + phrase = "The quick brown fox jumps over the lazy dog." + result = phrase->AtbashCipher.encode() + expected = "gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt" + result == Ok(expected) +} ## ## decode ## # decode exercism -expect - phrase = "vcvix rhn" - result = phrase |> decode - expected = "exercism" - result == Ok(expected) +expect { + phrase = "vcvix rhn" + result = phrase->AtbashCipher.decode() + expected = "exercism" + result == Ok(expected) +} # decode a sentence -expect - phrase = "zmlyh gzxov rhlug vmzhg vkkrm thglm v" - result = phrase |> decode - expected = "anobstacleisoftenasteppingstone" - result == Ok(expected) +expect { + phrase = "zmlyh gzxov rhlug vmzhg vkkrm thglm v" + result = phrase->AtbashCipher.decode() + expected = "anobstacleisoftenasteppingstone" + result == Ok(expected) +} # decode numbers -expect - phrase = "gvhgr mt123 gvhgr mt" - result = phrase |> decode - expected = "testing123testing" - result == Ok(expected) +expect { + phrase = "gvhgr mt123 gvhgr mt" + result = phrase->AtbashCipher.decode() + expected = "testing123testing" + result == Ok(expected) +} # decode all the letters -expect - phrase = "gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt" - result = phrase |> decode - expected = "thequickbrownfoxjumpsoverthelazydog" - result == Ok(expected) +expect { + phrase = "gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt" + result = phrase->AtbashCipher.decode() + expected = "thequickbrownfoxjumpsoverthelazydog" + result == Ok(expected) +} # decode with too many spaces -expect - phrase = "vc vix r hn" - result = phrase |> decode - expected = "exercism" - result == Ok(expected) +expect { + phrase = "vc vix r hn" + result = phrase->AtbashCipher.decode() + expected = "exercism" + result == Ok(expected) +} # decode with no spaces -expect - phrase = "zmlyhgzxovrhlugvmzhgvkkrmthglmv" - result = phrase |> decode - expected = "anobstacleisoftenasteppingstone" - result == Ok(expected) +expect { + phrase = "zmlyhgzxovrhlugvmzhgvkkrmthglmv" + result = phrase->AtbashCipher.decode() + expected = "anobstacleisoftenasteppingstone" + result == Ok(expected) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} From 02aeed9b19bca14a12fed7500c06a9ddd7060c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 12 Jun 2026 21:40:10 +1200 Subject: [PATCH 017/162] Update exercise to new compiler: binary --- exercises/practice/binary/.meta/Example.roc | 49 +++++--- exercises/practice/binary/.meta/template.j2 | 7 +- exercises/practice/binary/Binary.roc | 11 +- exercises/practice/binary/binary-test.roc | 119 +++++++++++--------- 4 files changed, 107 insertions(+), 79 deletions(-) diff --git a/exercises/practice/binary/.meta/Example.roc b/exercises/practice/binary/.meta/Example.roc index 044fb406..e2cc3381 100644 --- a/exercises/practice/binary/.meta/Example.roc +++ b/exercises/practice/binary/.meta/Example.roc @@ -1,19 +1,32 @@ -module [decimal] +Binary :: {}.{ + decimal : Str -> Try(U64, [InvalidCharacter(U8)]) + decimal = |binary_str| { + binary_str + .to_utf8() + ->map_try( + |char| { + match char { + '0' => Ok(0) + '1' => Ok(1) + c => Err(InvalidCharacter(c)) + } + }, + )? + .fold( + 0, + |dec, bit| { + dec * 2 + bit + }, + ) + ->Ok + } +} -decimal : Str -> Result U64 [InvalidCharacter U8] -decimal = |binary_str| - binary_str - |> Str.to_utf8 - |> List.map_try( - |char| - when char is - '0' -> Ok(0) - '1' -> Ok(1) - c -> Err(InvalidCharacter(c)), - )? - |> List.walk( - 0, - |dec, bit| - dec * 2 + bit, - ) - |> Ok +# The following function should soon be available in Roc's builtins +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/binary/.meta/template.j2 b/exercises/practice/binary/.meta/template.j2 index 4cca58ab..6b787ece 100644 --- a/exercises/practice/binary/.meta/template.j2 +++ b/exercises/practice/binary/.meta/template.j2 @@ -6,12 +6,15 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["binary"] | to_roc }}) {%- if case["expected"] is not none %} result == Ok({{ case["expected"] | to_roc }}) {%- else %} - result |> Result.is_err + result.is_err() {%- endif %} +} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/binary/Binary.roc b/exercises/practice/binary/Binary.roc index e23cbbca..c8c6c246 100644 --- a/exercises/practice/binary/Binary.roc +++ b/exercises/practice/binary/Binary.roc @@ -1,5 +1,6 @@ -module [decimal] - -decimal : Str -> Result U64 _ -decimal = |binary_str| - crash("Please implement the 'decimal' function") +Binary :: {}.{ + decimal : Str -> Try(U64, _) + decimal = |binary_str| { + crash ("Please implement the 'decimal' function") + } +} diff --git a/exercises/practice/binary/binary-test.roc b/exercises/practice/binary/binary-test.roc index 4686cbc3..9a28bbae 100644 --- a/exercises/practice/binary/binary-test.roc +++ b/exercises/practice/binary/binary-test.roc @@ -1,89 +1,100 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-12 import Binary exposing [decimal] # binary 0 is decimal 0 -expect - result = decimal("0") - result == Ok(0) +expect { + result = decimal("0") + result == Ok(0) +} # binary 1 is decimal 1 -expect - result = decimal("1") - result == Ok(1) +expect { + result = decimal("1") + result == Ok(1) +} # binary 10 is decimal 2 -expect - result = decimal("10") - result == Ok(2) +expect { + result = decimal("10") + result == Ok(2) +} # binary 11 is decimal 3 -expect - result = decimal("11") - result == Ok(3) +expect { + result = decimal("11") + result == Ok(3) +} # binary 100 is decimal 4 -expect - result = decimal("100") - result == Ok(4) +expect { + result = decimal("100") + result == Ok(4) +} # binary 1001 is decimal 9 -expect - result = decimal("1001") - result == Ok(9) +expect { + result = decimal("1001") + result == Ok(9) +} # binary 11010 is decimal 26 -expect - result = decimal("11010") - result == Ok(26) +expect { + result = decimal("11010") + result == Ok(26) +} # binary 10001101000 is decimal 1128 -expect - result = decimal("10001101000") - result == Ok(1128) +expect { + result = decimal("10001101000") + result == Ok(1128) +} # binary ignores leading zeros -expect - result = decimal("000011111") - result == Ok(31) +expect { + result = decimal("000011111") + result == Ok(31) +} # 2 is not a valid binary digit -expect - result = decimal("2") - result |> Result.is_err +expect { + result = decimal("2") + result.is_err() +} # a number containing a non-binary digit is invalid -expect - result = decimal("01201") - result |> Result.is_err +expect { + result = decimal("01201") + result.is_err() +} # a number with trailing non-binary characters is invalid -expect - result = decimal("10nope") - result |> Result.is_err +expect { + result = decimal("10nope") + result.is_err() +} # a number with leading non-binary characters is invalid -expect - result = decimal("nope10") - result |> Result.is_err +expect { + result = decimal("nope10") + result.is_err() +} # a number with internal non-binary characters is invalid -expect - result = decimal("10nope10") - result |> Result.is_err +expect { + result = decimal("10nope10") + result.is_err() +} # a number and a word whitespace separated is invalid -expect - result = decimal("001 nope") - result |> Result.is_err +expect { + result = decimal("001 nope") + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} From cc50a1246a519f5331a9a07aa7d1ac582ee802bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 12 Jun 2026 21:59:39 +1200 Subject: [PATCH 018/162] Remove unneeded parentheses --- exercises/practice/binary/Binary.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/binary/Binary.roc b/exercises/practice/binary/Binary.roc index c8c6c246..c8870095 100644 --- a/exercises/practice/binary/Binary.roc +++ b/exercises/practice/binary/Binary.roc @@ -1,6 +1,6 @@ Binary :: {}.{ decimal : Str -> Try(U64, _) decimal = |binary_str| { - crash ("Please implement the 'decimal' function") + crash "Please implement the 'decimal' function" } } From d4e75a05e57c351b77a4f3d549374053309d7532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 12 Jun 2026 22:04:30 +1200 Subject: [PATCH 019/162] Always use the same 'Please implement' message --- exercises/practice/accumulate/Accumulate.roc | 2 +- exercises/practice/all-your-base/AllYourBase.roc | 2 +- exercises/practice/allergies/Allergies.roc | 4 ++-- exercises/practice/bob/Bob.roc | 2 +- exercises/practice/collatz-conjecture/CollatzConjecture.roc | 2 +- exercises/practice/darts/Darts.roc | 2 +- .../practice/difference-of-squares/DifferenceOfSquares.roc | 6 +++--- exercises/practice/eliuds-eggs/EliudsEggs.roc | 2 +- exercises/practice/food-chain/FoodChain.roc | 2 +- exercises/practice/forth/Forth.roc | 2 +- exercises/practice/grep/Grep.roc | 2 +- exercises/practice/leap/Leap.roc | 2 +- exercises/practice/luhn/Luhn.roc | 2 +- exercises/practice/micro-blog/MicroBlog.roc | 2 +- exercises/practice/nucleotide-count/NucleotideCount.roc | 2 +- exercises/practice/reverse-string/ReverseString.roc | 2 +- exercises/practice/zebra-puzzle/ZebraPuzzle.roc | 4 ++-- 17 files changed, 21 insertions(+), 21 deletions(-) diff --git a/exercises/practice/accumulate/Accumulate.roc b/exercises/practice/accumulate/Accumulate.roc index 7eb2f534..98251076 100644 --- a/exercises/practice/accumulate/Accumulate.roc +++ b/exercises/practice/accumulate/Accumulate.roc @@ -1,6 +1,6 @@ Accumulate :: {}.{ accumulate : List(a), (a -> b) -> List(b) accumulate = |list, func| { - crash "Please implement 'accumulate'" + crash "Please implement the 'accumulate' function" } } diff --git a/exercises/practice/all-your-base/AllYourBase.roc b/exercises/practice/all-your-base/AllYourBase.roc index b79fb225..2c61a6c7 100644 --- a/exercises/practice/all-your-base/AllYourBase.roc +++ b/exercises/practice/all-your-base/AllYourBase.roc @@ -1,6 +1,6 @@ AllYourBase :: {}.{ rebase : { input_base : U64, output_base : U64, digits : List(U64) } -> Try(List(U64), _) rebase = |{ input_base, output_base, digits }| { - crash "Please implement 'rebase'" + crash "Please implement the 'rebase' function" } } diff --git a/exercises/practice/allergies/Allergies.roc b/exercises/practice/allergies/Allergies.roc index a9f134c5..67381c72 100644 --- a/exercises/practice/allergies/Allergies.roc +++ b/exercises/practice/allergies/Allergies.roc @@ -3,11 +3,11 @@ Allergies :: {}.{ allergic_to : Allergen, U64 -> Bool allergic_to = |allergen, score| { - crash "Please implement 'allergic_to'" + crash "Please implement the 'allergic_to' function" } set : U64 -> Set(Allergen) set = |score| { - crash "Please implement 'set'" + crash "Please implement the 'set' function" } } diff --git a/exercises/practice/bob/Bob.roc b/exercises/practice/bob/Bob.roc index 1501f4cb..5c08722a 100644 --- a/exercises/practice/bob/Bob.roc +++ b/exercises/practice/bob/Bob.roc @@ -2,4 +2,4 @@ module [response] response : Str -> Str response = |hey_bob| - crash("Please implement the `response` function") + crash("Please implement the 'response' function") diff --git a/exercises/practice/collatz-conjecture/CollatzConjecture.roc b/exercises/practice/collatz-conjecture/CollatzConjecture.roc index f9d24564..f639c340 100644 --- a/exercises/practice/collatz-conjecture/CollatzConjecture.roc +++ b/exercises/practice/collatz-conjecture/CollatzConjecture.roc @@ -2,4 +2,4 @@ module [steps] steps : U64 -> Result U64 _ steps = |number| - crash("Please implement the `steps` function") + crash("Please implement the 'steps' function") diff --git a/exercises/practice/darts/Darts.roc b/exercises/practice/darts/Darts.roc index aaacb58e..5e23f36a 100644 --- a/exercises/practice/darts/Darts.roc +++ b/exercises/practice/darts/Darts.roc @@ -2,5 +2,5 @@ module [score] score : F64, F64 -> U64 score = |x, y| - crash("Please implement the `score` function") + crash("Please implement the 'score' function") diff --git a/exercises/practice/difference-of-squares/DifferenceOfSquares.roc b/exercises/practice/difference-of-squares/DifferenceOfSquares.roc index 313925d2..132797b8 100644 --- a/exercises/practice/difference-of-squares/DifferenceOfSquares.roc +++ b/exercises/practice/difference-of-squares/DifferenceOfSquares.roc @@ -2,12 +2,12 @@ module [square_of_sum, sum_of_squares, difference_of_squares] square_of_sum : U64 -> U64 square_of_sum = |number| - crash("Please implement the `square_of_sum` function") + crash("Please implement the 'square_of_sum' function") sum_of_squares : U64 -> U64 sum_of_squares = |number| - crash("Please implement the `sum_of_squares` function") + crash("Please implement the 'sum_of_squares' function") difference_of_squares : U64 -> U64 difference_of_squares = |number| - crash("Please implement the `difference_of_squares` function") + crash("Please implement the 'difference_of_squares' function") diff --git a/exercises/practice/eliuds-eggs/EliudsEggs.roc b/exercises/practice/eliuds-eggs/EliudsEggs.roc index dcaa72d7..6a4477b2 100644 --- a/exercises/practice/eliuds-eggs/EliudsEggs.roc +++ b/exercises/practice/eliuds-eggs/EliudsEggs.roc @@ -2,4 +2,4 @@ module [egg_count] egg_count : U64 -> U64 egg_count = |number| - crash("Please implement 'egg_count'") + crash("Please implement the 'egg_count' function") diff --git a/exercises/practice/food-chain/FoodChain.roc b/exercises/practice/food-chain/FoodChain.roc index 32b4e738..90ae8aa3 100644 --- a/exercises/practice/food-chain/FoodChain.roc +++ b/exercises/practice/food-chain/FoodChain.roc @@ -2,4 +2,4 @@ module [recite] recite : U64, U64 -> Str recite = |start_verse, end_verse| - crash("Please implement 'recite'") + crash("Please implement the 'recite' function") diff --git a/exercises/practice/forth/Forth.roc b/exercises/practice/forth/Forth.roc index 219dddbf..9fa226b7 100644 --- a/exercises/practice/forth/Forth.roc +++ b/exercises/practice/forth/Forth.roc @@ -4,4 +4,4 @@ Stack : List I16 evaluate : Str -> Result Stack _ evaluate = |program| - crash("Please implement 'evaluate'") + crash("Please implement the 'evaluate' function") diff --git a/exercises/practice/grep/Grep.roc b/exercises/practice/grep/Grep.roc index dd939f61..1cfe39f9 100644 --- a/exercises/practice/grep/Grep.roc +++ b/exercises/practice/grep/Grep.roc @@ -6,4 +6,4 @@ import "paradise-lost.txt" as paradise_lost : Str grep : Str, List Str, List Str -> Result Str _ grep = |pattern, flags, files| - crash("Please implement 'grep'") + crash("Please implement the 'grep' function") diff --git a/exercises/practice/leap/Leap.roc b/exercises/practice/leap/Leap.roc index 03943d22..00789433 100644 --- a/exercises/practice/leap/Leap.roc +++ b/exercises/practice/leap/Leap.roc @@ -2,4 +2,4 @@ module [is_leap_year] is_leap_year : I64 -> Bool is_leap_year = |year| - crash("Please implement the `is_leap_year` function") + crash("Please implement the 'is_leap_year' function") diff --git a/exercises/practice/luhn/Luhn.roc b/exercises/practice/luhn/Luhn.roc index 4373355e..90952c5b 100644 --- a/exercises/practice/luhn/Luhn.roc +++ b/exercises/practice/luhn/Luhn.roc @@ -2,4 +2,4 @@ module [valid] valid : Str -> Bool valid = |digits| - crash("Please implement 'valid'") + crash("Please implement the 'valid' function") diff --git a/exercises/practice/micro-blog/MicroBlog.roc b/exercises/practice/micro-blog/MicroBlog.roc index f8b7118c..c3f0d9c3 100644 --- a/exercises/practice/micro-blog/MicroBlog.roc +++ b/exercises/practice/micro-blog/MicroBlog.roc @@ -4,4 +4,4 @@ import unicode.Grapheme truncate : Str -> Result Str _ truncate = |input| - crash("Please implement 'truncate'") + crash("Please implement the 'truncate' function") diff --git a/exercises/practice/nucleotide-count/NucleotideCount.roc b/exercises/practice/nucleotide-count/NucleotideCount.roc index cb009330..4a3fc337 100644 --- a/exercises/practice/nucleotide-count/NucleotideCount.roc +++ b/exercises/practice/nucleotide-count/NucleotideCount.roc @@ -2,4 +2,4 @@ module [nucleotide_counts] nucleotide_counts : Str -> Result { a : U64, c : U64, g : U64, t : U64 } _ nucleotide_counts = |input| - crash("Please implement 'nucleotide_counts'") + crash("Please implement the 'nucleotide_counts' function") diff --git a/exercises/practice/reverse-string/ReverseString.roc b/exercises/practice/reverse-string/ReverseString.roc index 40d508e0..4a453977 100644 --- a/exercises/practice/reverse-string/ReverseString.roc +++ b/exercises/practice/reverse-string/ReverseString.roc @@ -2,7 +2,7 @@ module [reverse] reverse : Str -> Str reverse = |string| - crash("Please implement the `reverse` function") + crash("Please implement the 'reverse' function") # HINT: we have added the `unicode` package to the app's header in # reverse-string-test.roc, so you can use it here if you need to. diff --git a/exercises/practice/zebra-puzzle/ZebraPuzzle.roc b/exercises/practice/zebra-puzzle/ZebraPuzzle.roc index 2d0677ee..6be56aa5 100644 --- a/exercises/practice/zebra-puzzle/ZebraPuzzle.roc +++ b/exercises/practice/zebra-puzzle/ZebraPuzzle.roc @@ -4,8 +4,8 @@ Person : [Englishman, Spaniard, Ukrainian, Norwegian, Japanese] owns_zebra : Result Person _ owns_zebra = - crash("Please implement 'owns_zebra'") + crash("Please implement the 'owns_zebra' function") drinks_water : Result Person _ drinks_water = - crash("Please implement 'drinks_water'") + crash("Please implement the 'drinks_water' function") From 22d5c883a1caba449d8bef1f8167fb3dc63b962b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 12 Jun 2026 22:16:29 +1200 Subject: [PATCH 020/162] Add footer at the end of each template.j2 --- exercises/practice/binary-search-tree/.meta/template.j2 | 2 +- exercises/practice/binary-search/.meta/template.j2 | 2 ++ exercises/practice/bob/.meta/template.j2 | 4 +++- exercises/practice/bowling/.meta/template.j2 | 4 +++- exercises/practice/change/.meta/template.j2 | 2 ++ exercises/practice/circular-buffer/.meta/template.j2 | 2 ++ exercises/practice/clock/.meta/template.j2 | 2 ++ exercises/practice/collatz-conjecture/.meta/template.j2 | 2 ++ exercises/practice/complex-numbers/.meta/template.j2 | 1 + exercises/practice/connect/.meta/template.j2 | 2 ++ exercises/practice/crypto-square/.meta/template.j2 | 2 ++ exercises/practice/custom-set/.meta/template.j2 | 4 +++- exercises/practice/darts/.meta/template.j2 | 2 ++ exercises/practice/diamond/.meta/template.j2 | 2 ++ exercises/practice/difference-of-squares/.meta/template.j2 | 2 ++ exercises/practice/dominoes/.meta/template.j2 | 2 ++ exercises/practice/eliuds-eggs/.meta/template.j2 | 2 ++ exercises/practice/etl/.meta/template.j2 | 2 ++ exercises/practice/flatten-array/.meta/template.j2 | 2 ++ exercises/practice/flower-field/.meta/template.j2 | 2 ++ exercises/practice/food-chain/.meta/template.j2 | 4 +++- exercises/practice/forth/.meta/template.j2 | 2 ++ exercises/practice/gigasecond/.meta/template.j2 | 2 ++ exercises/practice/go-counting/.meta/template.j2 | 2 ++ exercises/practice/grains/.meta/template.j2 | 2 ++ exercises/practice/grep/.meta/template.j2 | 2 ++ exercises/practice/hamming/.meta/template.j2 | 2 ++ exercises/practice/hello-world/.meta/template.j2 | 2 ++ exercises/practice/high-scores/.meta/template.j2 | 2 ++ exercises/practice/house/.meta/template.j2 | 2 ++ exercises/practice/isbn-verifier/.meta/template.j2 | 2 ++ exercises/practice/isogram/.meta/template.j2 | 2 ++ exercises/practice/killer-sudoku-helper/.meta/template.j2 | 1 + exercises/practice/kindergarten-garden/.meta/template.j2 | 4 +++- exercises/practice/knapsack/.meta/template.j2 | 4 +++- exercises/practice/largest-series-product/.meta/template.j2 | 2 ++ exercises/practice/leap/.meta/template.j2 | 2 ++ exercises/practice/list-ops/.meta/template.j2 | 2 ++ exercises/practice/luhn/.meta/template.j2 | 2 ++ exercises/practice/matching-brackets/.meta/template.j2 | 2 ++ exercises/practice/matrix/.meta/template.j2 | 2 ++ exercises/practice/meetup/.meta/template.j2 | 2 ++ exercises/practice/micro-blog/.meta/template.j2 | 2 ++ exercises/practice/minesweeper/.meta/template.j2 | 2 ++ exercises/practice/nth-prime/.meta/template.j2 | 2 ++ exercises/practice/nucleotide-count/.meta/template.j2 | 2 ++ exercises/practice/ocr-numbers/.meta/template.j2 | 2 ++ exercises/practice/palindrome-products/.meta/template.j2 | 1 + exercises/practice/pangram/.meta/template.j2 | 2 ++ exercises/practice/pascals-triangle/.meta/template.j2 | 2 ++ exercises/practice/perfect-numbers/.meta/template.j2 | 2 ++ exercises/practice/phone-number/.meta/template.j2 | 2 ++ exercises/practice/pig-latin/.meta/template.j2 | 2 ++ exercises/practice/poker/.meta/template.j2 | 2 ++ exercises/practice/prime-factors/.meta/template.j2 | 2 ++ exercises/practice/protein-translation/.meta/template.j2 | 1 + exercises/practice/proverb/.meta/template.j2 | 1 + exercises/practice/pythagorean-triplet/.meta/template.j2 | 2 ++ exercises/practice/queen-attack/.meta/template.j2 | 2 ++ exercises/practice/rail-fence-cipher/.meta/template.j2 | 2 ++ exercises/practice/raindrops/.meta/template.j2 | 2 ++ exercises/practice/rational-numbers/.meta/template.j2 | 2 ++ exercises/practice/rectangles/.meta/template.j2 | 1 + exercises/practice/resistor-color-duo/.meta/template.j2 | 2 ++ exercises/practice/resistor-color/.meta/template.j2 | 2 ++ exercises/practice/rest-api/.meta/template.j2 | 2 ++ exercises/practice/reverse-string/.meta/template.j2 | 2 ++ exercises/practice/rna-transcription/.meta/template.j2 | 2 ++ exercises/practice/robot-simulator/.meta/template.j2 | 2 ++ exercises/practice/roman-numerals/.meta/template.j2 | 2 ++ exercises/practice/rotational-cipher/.meta/template.j2 | 2 ++ exercises/practice/run-length-encoding/.meta/template.j2 | 1 + exercises/practice/saddle-points/.meta/template.j2 | 2 ++ exercises/practice/say/.meta/template.j2 | 2 ++ exercises/practice/scrabble-score/.meta/template.j2 | 1 + exercises/practice/secret-handshake/.meta/template.j2 | 2 ++ exercises/practice/series/.meta/template.j2 | 2 ++ exercises/practice/sgf-parsing/.meta/template.j2 | 2 ++ exercises/practice/sieve/.meta/template.j2 | 2 ++ exercises/practice/space-age/.meta/template.j2 | 2 ++ exercises/practice/spiral-matrix/.meta/template.j2 | 2 ++ exercises/practice/square-root/.meta/template.j2 | 2 ++ exercises/practice/strain/.meta/template.j2 | 2 ++ exercises/practice/sublist/.meta/template.j2 | 2 ++ exercises/practice/sum-of-multiples/.meta/template.j2 | 2 ++ exercises/practice/tournament/.meta/template.j2 | 2 ++ exercises/practice/transpose/.meta/template.j2 | 2 ++ exercises/practice/triangle/.meta/template.j2 | 4 +++- exercises/practice/two-bucket/.meta/template.j2 | 2 ++ exercises/practice/two-fer/.meta/template.j2 | 2 ++ exercises/practice/variable-length-quantity/.meta/template.j2 | 2 ++ exercises/practice/word-count/.meta/template.j2 | 2 ++ exercises/practice/word-search/.meta/template.j2 | 2 ++ exercises/practice/wordy/.meta/template.j2 | 2 ++ exercises/practice/yacht/.meta/template.j2 | 2 ++ exercises/practice/zebra-puzzle/.meta/template.j2 | 2 ++ 96 files changed, 190 insertions(+), 8 deletions(-) diff --git a/exercises/practice/binary-search-tree/.meta/template.j2 b/exercises/practice/binary-search-tree/.meta/template.j2 index 1abf72ce..82c04750 100644 --- a/exercises/practice/binary-search-tree/.meta/template.j2 +++ b/exercises/practice/binary-search-tree/.meta/template.j2 @@ -47,4 +47,4 @@ expect {% endfor %} - +{{ macros.footer() }} diff --git a/exercises/practice/binary-search/.meta/template.j2 b/exercises/practice/binary-search/.meta/template.j2 index c24afc3a..3fe1a8f8 100644 --- a/exercises/practice/binary-search/.meta/template.j2 +++ b/exercises/practice/binary-search/.meta/template.j2 @@ -15,3 +15,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/bob/.meta/template.j2 b/exercises/practice/bob/.meta/template.j2 index 9995b77c..2cf3e9e0 100644 --- a/exercises/practice/bob/.meta/template.j2 +++ b/exercises/practice/bob/.meta/template.j2 @@ -10,4 +10,6 @@ expect result = {{ case["property"] | to_snake }}({{ case["input"]["heyBob"] | to_roc }}) result == {{ case["expected"] | to_roc }} -{% endfor %} \ No newline at end of file +{% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/bowling/.meta/template.j2 b/exercises/practice/bowling/.meta/template.j2 index 17e2f490..8f1244b9 100644 --- a/exercises/practice/bowling/.meta/template.j2 +++ b/exercises/practice/bowling/.meta/template.j2 @@ -47,4 +47,6 @@ expect {%- endif %} -{% endfor %} \ No newline at end of file +{% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/change/.meta/template.j2 b/exercises/practice/change/.meta/template.j2 index 1aa34c3d..f698fed9 100644 --- a/exercises/practice/change/.meta/template.j2 +++ b/exercises/practice/change/.meta/template.j2 @@ -16,3 +16,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/circular-buffer/.meta/template.j2 b/exercises/practice/circular-buffer/.meta/template.j2 index a03dbc83..6046d84e 100644 --- a/exercises/practice/circular-buffer/.meta/template.j2 +++ b/exercises/practice/circular-buffer/.meta/template.j2 @@ -44,3 +44,5 @@ expect result |> Result.is_ok {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/clock/.meta/template.j2 b/exercises/practice/clock/.meta/template.j2 index 0455601a..61623fd9 100644 --- a/exercises/practice/clock/.meta/template.j2 +++ b/exercises/practice/clock/.meta/template.j2 @@ -48,3 +48,5 @@ expect {% for case in additional_cases -%} {{ test_case(case) }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/collatz-conjecture/.meta/template.j2 b/exercises/practice/collatz-conjecture/.meta/template.j2 index ce67bec3..307d8063 100644 --- a/exercises/practice/collatz-conjecture/.meta/template.j2 +++ b/exercises/practice/collatz-conjecture/.meta/template.j2 @@ -15,3 +15,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2 index 64464d1e..273a2d95 100644 --- a/exercises/practice/complex-numbers/.meta/template.j2 +++ b/exercises/practice/complex-numbers/.meta/template.j2 @@ -46,3 +46,4 @@ expect {% endfor %} {% endfor %} +{{ macros.footer() }} diff --git a/exercises/practice/connect/.meta/template.j2 b/exercises/practice/connect/.meta/template.j2 index 25aa62ac..d1cddd67 100644 --- a/exercises/practice/connect/.meta/template.j2 +++ b/exercises/practice/connect/.meta/template.j2 @@ -12,3 +12,5 @@ expect result == {% if case["expected"] == "" %}Err(NotFinished){% else %}Ok(Player{{ case["expected"] }}){% endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/crypto-square/.meta/template.j2 b/exercises/practice/crypto-square/.meta/template.j2 index be3e8b42..cd21e819 100644 --- a/exercises/practice/crypto-square/.meta/template.j2 +++ b/exercises/practice/crypto-square/.meta/template.j2 @@ -13,3 +13,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/custom-set/.meta/template.j2 b/exercises/practice/custom-set/.meta/template.j2 index e99d682e..904d3f8e 100644 --- a/exercises/practice/custom-set/.meta/template.j2 +++ b/exercises/practice/custom-set/.meta/template.j2 @@ -67,4 +67,6 @@ expect expected = {{ case["expected"] | to_roc }} result == expected -{% endfor %} \ No newline at end of file +{% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/darts/.meta/template.j2 b/exercises/practice/darts/.meta/template.j2 index 1aa085d5..26a213f1 100644 --- a/exercises/practice/darts/.meta/template.j2 +++ b/exercises/practice/darts/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/diamond/.meta/template.j2 b/exercises/practice/diamond/.meta/template.j2 index 348be2e9..364c8bf7 100644 --- a/exercises/practice/diamond/.meta/template.j2 +++ b/exercises/practice/diamond/.meta/template.j2 @@ -12,3 +12,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/difference-of-squares/.meta/template.j2 b/exercises/practice/difference-of-squares/.meta/template.j2 index 23bf22a3..f681252e 100644 --- a/exercises/practice/difference-of-squares/.meta/template.j2 +++ b/exercises/practice/difference-of-squares/.meta/template.j2 @@ -17,3 +17,5 @@ expect {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/dominoes/.meta/template.j2 b/exercises/practice/dominoes/.meta/template.j2 index cd28e4a8..0243af71 100644 --- a/exercises/practice/dominoes/.meta/template.j2 +++ b/exercises/practice/dominoes/.meta/template.j2 @@ -60,3 +60,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/eliuds-eggs/.meta/template.j2 b/exercises/practice/eliuds-eggs/.meta/template.j2 index bfe5d436..9d1138a9 100644 --- a/exercises/practice/eliuds-eggs/.meta/template.j2 +++ b/exercises/practice/eliuds-eggs/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/etl/.meta/template.j2 b/exercises/practice/etl/.meta/template.j2 index eb107999..6b3771ed 100644 --- a/exercises/practice/etl/.meta/template.j2 +++ b/exercises/practice/etl/.meta/template.j2 @@ -22,3 +22,5 @@ expect transform(legacy) == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/flatten-array/.meta/template.j2 b/exercises/practice/flatten-array/.meta/template.j2 index 8314785c..fe4a2168 100644 --- a/exercises/practice/flatten-array/.meta/template.j2 +++ b/exercises/practice/flatten-array/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/flower-field/.meta/template.j2 b/exercises/practice/flower-field/.meta/template.j2 index 14eafc1f..6e7080e6 100644 --- a/exercises/practice/flower-field/.meta/template.j2 +++ b/exercises/practice/flower-field/.meta/template.j2 @@ -13,3 +13,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/food-chain/.meta/template.j2 b/exercises/practice/food-chain/.meta/template.j2 index 18bdbc29..14d32089 100644 --- a/exercises/practice/food-chain/.meta/template.j2 +++ b/exercises/practice/food-chain/.meta/template.j2 @@ -10,4 +10,6 @@ expect result = {{ case["property"] | to_snake }}({{ case["input"]["startVerse"] | to_roc }}, {{ case["input"]["endVerse"] | to_roc }}) result == {{ case["expected"] | join('\n') | to_roc_multiline_string | indent(8) }} -{% endfor %} \ No newline at end of file +{% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/forth/.meta/template.j2 b/exercises/practice/forth/.meta/template.j2 index 2a51879e..01fb2912 100644 --- a/exercises/practice/forth/.meta/template.j2 +++ b/exercises/practice/forth/.meta/template.j2 @@ -16,3 +16,5 @@ expect {%- endif %} {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/gigasecond/.meta/template.j2 b/exercises/practice/gigasecond/.meta/template.j2 index 017461a6..869f3c67 100644 --- a/exercises/practice/gigasecond/.meta/template.j2 +++ b/exercises/practice/gigasecond/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/go-counting/.meta/template.j2 b/exercises/practice/go-counting/.meta/template.j2 index c9ded94a..628486fd 100644 --- a/exercises/practice/go-counting/.meta/template.j2 +++ b/exercises/practice/go-counting/.meta/template.j2 @@ -55,3 +55,5 @@ expect {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/grains/.meta/template.j2 b/exercises/practice/grains/.meta/template.j2 index 70005fdf..38d6ae83 100644 --- a/exercises/practice/grains/.meta/template.j2 +++ b/exercises/practice/grains/.meta/template.j2 @@ -26,3 +26,5 @@ expect {% endfor %} {%- endif -%} {%- endfor -%} + +{{ macros.footer() }} diff --git a/exercises/practice/grep/.meta/template.j2 b/exercises/practice/grep/.meta/template.j2 index 77270f17..926603e5 100644 --- a/exercises/practice/grep/.meta/template.j2 +++ b/exercises/practice/grep/.meta/template.j2 @@ -13,3 +13,5 @@ expect {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/hamming/.meta/template.j2 b/exercises/practice/hamming/.meta/template.j2 index 3b6eb393..fbeaf057 100644 --- a/exercises/practice/hamming/.meta/template.j2 +++ b/exercises/practice/hamming/.meta/template.j2 @@ -15,3 +15,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/hello-world/.meta/template.j2 b/exercises/practice/hello-world/.meta/template.j2 index ccfb51dc..abed1989 100644 --- a/exercises/practice/hello-world/.meta/template.j2 +++ b/exercises/practice/hello-world/.meta/template.j2 @@ -12,3 +12,5 @@ expect result == {{ case ["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/high-scores/.meta/template.j2 b/exercises/practice/high-scores/.meta/template.j2 index 0c7b1cc5..34fbaab4 100644 --- a/exercises/practice/high-scores/.meta/template.j2 +++ b/exercises/practice/high-scores/.meta/template.j2 @@ -21,3 +21,5 @@ expect {% endfor -%} {%- endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/house/.meta/template.j2 b/exercises/practice/house/.meta/template.j2 index 1ad789e0..fc09e0ba 100644 --- a/exercises/practice/house/.meta/template.j2 +++ b/exercises/practice/house/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ "\n".join(case["expected"]) | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/isbn-verifier/.meta/template.j2 b/exercises/practice/isbn-verifier/.meta/template.j2 index 6cd86f30..41ff8a91 100644 --- a/exercises/practice/isbn-verifier/.meta/template.j2 +++ b/exercises/practice/isbn-verifier/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/isogram/.meta/template.j2 b/exercises/practice/isogram/.meta/template.j2 index 86dc2d01..3f6d4f5c 100644 --- a/exercises/practice/isogram/.meta/template.j2 +++ b/exercises/practice/isogram/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/killer-sudoku-helper/.meta/template.j2 b/exercises/practice/killer-sudoku-helper/.meta/template.j2 index 8d4e33e3..5d2b8865 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/template.j2 +++ b/exercises/practice/killer-sudoku-helper/.meta/template.j2 @@ -29,3 +29,4 @@ expect {% endfor -%} {%- endfor %} +{{ macros.footer() }} diff --git a/exercises/practice/kindergarten-garden/.meta/template.j2 b/exercises/practice/kindergarten-garden/.meta/template.j2 index 2a2355d8..cf291df5 100644 --- a/exercises/practice/kindergarten-garden/.meta/template.j2 +++ b/exercises/practice/kindergarten-garden/.meta/template.j2 @@ -27,4 +27,6 @@ expect {% endfor %} {% endfor %} -{% endfor %} \ No newline at end of file +{% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/knapsack/.meta/template.j2 b/exercises/practice/knapsack/.meta/template.j2 index a7a2d839..336772d1 100644 --- a/exercises/practice/knapsack/.meta/template.j2 +++ b/exercises/practice/knapsack/.meta/template.j2 @@ -19,4 +19,6 @@ expect result = {{ case["property"] | to_snake }}({ items, maximum_weight: {{ case["input"]["maximumWeight"] }} }) result == {{ case["expected"] | to_roc }} -{% endfor %} \ No newline at end of file +{% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/largest-series-product/.meta/template.j2 b/exercises/practice/largest-series-product/.meta/template.j2 index c0cf1475..de9a3fc7 100644 --- a/exercises/practice/largest-series-product/.meta/template.j2 +++ b/exercises/practice/largest-series-product/.meta/template.j2 @@ -17,3 +17,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/leap/.meta/template.j2 b/exercises/practice/leap/.meta/template.j2 index c974c801..07751eb7 100644 --- a/exercises/practice/leap/.meta/template.j2 +++ b/exercises/practice/leap/.meta/template.j2 @@ -12,3 +12,5 @@ expect result == {{ case ["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2 index aaa0c48f..6e572d94 100644 --- a/exercises/practice/list-ops/.meta/template.j2 +++ b/exercises/practice/list-ops/.meta/template.j2 @@ -47,3 +47,5 @@ expect {% endfor -%} {% endfor -%} + +{{ macros.footer() }} diff --git a/exercises/practice/luhn/.meta/template.j2 b/exercises/practice/luhn/.meta/template.j2 index 2f2e4ef6..2d95ed83 100644 --- a/exercises/practice/luhn/.meta/template.j2 +++ b/exercises/practice/luhn/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/matching-brackets/.meta/template.j2 b/exercises/practice/matching-brackets/.meta/template.j2 index 0b4214c4..2748c149 100644 --- a/exercises/practice/matching-brackets/.meta/template.j2 +++ b/exercises/practice/matching-brackets/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/matrix/.meta/template.j2 b/exercises/practice/matrix/.meta/template.j2 index 465f298d..aec384ed 100644 --- a/exercises/practice/matrix/.meta/template.j2 +++ b/exercises/practice/matrix/.meta/template.j2 @@ -12,3 +12,5 @@ expect result == Ok({{ case["expected"] | to_roc }}) {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/meetup/.meta/template.j2 b/exercises/practice/meetup/.meta/template.j2 index 0519cd73..30e4bcb0 100644 --- a/exercises/practice/meetup/.meta/template.j2 +++ b/exercises/practice/meetup/.meta/template.j2 @@ -17,3 +17,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/micro-blog/.meta/template.j2 b/exercises/practice/micro-blog/.meta/template.j2 index 32cb0476..1473238d 100644 --- a/exercises/practice/micro-blog/.meta/template.j2 +++ b/exercises/practice/micro-blog/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == Ok({{ case["expected"] | to_roc }}) {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/minesweeper/.meta/template.j2 b/exercises/practice/minesweeper/.meta/template.j2 index 7e04ffab..3f7155c9 100644 --- a/exercises/practice/minesweeper/.meta/template.j2 +++ b/exercises/practice/minesweeper/.meta/template.j2 @@ -13,3 +13,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/nth-prime/.meta/template.j2 b/exercises/practice/nth-prime/.meta/template.j2 index e815e36b..1934f692 100644 --- a/exercises/practice/nth-prime/.meta/template.j2 +++ b/exercises/practice/nth-prime/.meta/template.j2 @@ -15,3 +15,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/nucleotide-count/.meta/template.j2 b/exercises/practice/nucleotide-count/.meta/template.j2 index 0594f9d9..faad29b1 100644 --- a/exercises/practice/nucleotide-count/.meta/template.j2 +++ b/exercises/practice/nucleotide-count/.meta/template.j2 @@ -15,3 +15,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/ocr-numbers/.meta/template.j2 b/exercises/practice/ocr-numbers/.meta/template.j2 index 85b49b0b..620860e5 100644 --- a/exercises/practice/ocr-numbers/.meta/template.j2 +++ b/exercises/practice/ocr-numbers/.meta/template.j2 @@ -17,3 +17,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/palindrome-products/.meta/template.j2 b/exercises/practice/palindrome-products/.meta/template.j2 index 7c80bd28..7f769da4 100644 --- a/exercises/practice/palindrome-products/.meta/template.j2 +++ b/exercises/practice/palindrome-products/.meta/template.j2 @@ -29,3 +29,4 @@ expect {% endfor %} +{{ macros.footer() }} diff --git a/exercises/practice/pangram/.meta/template.j2 b/exercises/practice/pangram/.meta/template.j2 index c0511911..f70258e1 100644 --- a/exercises/practice/pangram/.meta/template.j2 +++ b/exercises/practice/pangram/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/pascals-triangle/.meta/template.j2 b/exercises/practice/pascals-triangle/.meta/template.j2 index db72aca9..71d4abfe 100644 --- a/exercises/practice/pascals-triangle/.meta/template.j2 +++ b/exercises/practice/pascals-triangle/.meta/template.j2 @@ -14,3 +14,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/perfect-numbers/.meta/template.j2 b/exercises/practice/perfect-numbers/.meta/template.j2 index ec51e9f2..93f14ecd 100644 --- a/exercises/practice/perfect-numbers/.meta/template.j2 +++ b/exercises/practice/perfect-numbers/.meta/template.j2 @@ -21,3 +21,5 @@ expect {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/phone-number/.meta/template.j2 b/exercises/practice/phone-number/.meta/template.j2 index 45f4dec9..db676f1b 100644 --- a/exercises/practice/phone-number/.meta/template.j2 +++ b/exercises/practice/phone-number/.meta/template.j2 @@ -16,3 +16,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/pig-latin/.meta/template.j2 b/exercises/practice/pig-latin/.meta/template.j2 index b4ae2f04..1c20e627 100644 --- a/exercises/practice/pig-latin/.meta/template.j2 +++ b/exercises/practice/pig-latin/.meta/template.j2 @@ -17,3 +17,5 @@ expect {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/poker/.meta/template.j2 b/exercises/practice/poker/.meta/template.j2 index b3dcde9d..5b795e32 100644 --- a/exercises/practice/poker/.meta/template.j2 +++ b/exercises/practice/poker/.meta/template.j2 @@ -12,3 +12,5 @@ expect result == Ok({{ case["expected"] | to_roc }}) {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/prime-factors/.meta/template.j2 b/exercises/practice/prime-factors/.meta/template.j2 index b4b90411..d3828ccd 100644 --- a/exercises/practice/prime-factors/.meta/template.j2 +++ b/exercises/practice/prime-factors/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/protein-translation/.meta/template.j2 b/exercises/practice/protein-translation/.meta/template.j2 index 9b602769..3f03f6b7 100644 --- a/exercises/practice/protein-translation/.meta/template.j2 +++ b/exercises/practice/protein-translation/.meta/template.j2 @@ -17,3 +17,4 @@ expect {% endfor %} +{{ macros.footer() }} diff --git a/exercises/practice/proverb/.meta/template.j2 b/exercises/practice/proverb/.meta/template.j2 index 5c274011..c3520633 100644 --- a/exercises/practice/proverb/.meta/template.j2 +++ b/exercises/practice/proverb/.meta/template.j2 @@ -13,3 +13,4 @@ expect {% endfor %} +{{ macros.footer() }} diff --git a/exercises/practice/pythagorean-triplet/.meta/template.j2 b/exercises/practice/pythagorean-triplet/.meta/template.j2 index e7b51fc9..b447f763 100644 --- a/exercises/practice/pythagorean-triplet/.meta/template.j2 +++ b/exercises/practice/pythagorean-triplet/.meta/template.j2 @@ -14,3 +14,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/queen-attack/.meta/template.j2 b/exercises/practice/queen-attack/.meta/template.j2 index 34720f73..780b0deb 100644 --- a/exercises/practice/queen-attack/.meta/template.j2 +++ b/exercises/practice/queen-attack/.meta/template.j2 @@ -40,3 +40,5 @@ expect {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/rail-fence-cipher/.meta/template.j2 b/exercises/practice/rail-fence-cipher/.meta/template.j2 index c05a1a87..d8ed6612 100644 --- a/exercises/practice/rail-fence-cipher/.meta/template.j2 +++ b/exercises/practice/rail-fence-cipher/.meta/template.j2 @@ -19,3 +19,5 @@ expect {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/raindrops/.meta/template.j2 b/exercises/practice/raindrops/.meta/template.j2 index e8b3010e..95233d7a 100644 --- a/exercises/practice/raindrops/.meta/template.j2 +++ b/exercises/practice/raindrops/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/rational-numbers/.meta/template.j2 b/exercises/practice/rational-numbers/.meta/template.j2 index 6d0855cf..25007435 100644 --- a/exercises/practice/rational-numbers/.meta/template.j2 +++ b/exercises/practice/rational-numbers/.meta/template.j2 @@ -55,3 +55,5 @@ expect {%- endif %} {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/rectangles/.meta/template.j2 b/exercises/practice/rectangles/.meta/template.j2 index 53918371..62712bac 100644 --- a/exercises/practice/rectangles/.meta/template.j2 +++ b/exercises/practice/rectangles/.meta/template.j2 @@ -12,3 +12,4 @@ expect {% endfor %} +{{ macros.footer() }} diff --git a/exercises/practice/resistor-color-duo/.meta/template.j2 b/exercises/practice/resistor-color-duo/.meta/template.j2 index b548a358..52e62a4b 100644 --- a/exercises/practice/resistor-color-duo/.meta/template.j2 +++ b/exercises/practice/resistor-color-duo/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/resistor-color/.meta/template.j2 b/exercises/practice/resistor-color/.meta/template.j2 index c8e32f9f..6df5e065 100644 --- a/exercises/practice/resistor-color/.meta/template.j2 +++ b/exercises/practice/resistor-color/.meta/template.j2 @@ -26,3 +26,5 @@ expect {% endfor %} {%- endif -%} {%- endfor -%} + +{{ macros.footer() }} diff --git a/exercises/practice/rest-api/.meta/template.j2 b/exercises/practice/rest-api/.meta/template.j2 index 109b5ee3..bd23100b 100644 --- a/exercises/practice/rest-api/.meta/template.j2 +++ b/exercises/practice/rest-api/.meta/template.j2 @@ -55,3 +55,5 @@ expect {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/reverse-string/.meta/template.j2 b/exercises/practice/reverse-string/.meta/template.j2 index 07e0a8df..f0c664e9 100644 --- a/exercises/practice/reverse-string/.meta/template.j2 +++ b/exercises/practice/reverse-string/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/rna-transcription/.meta/template.j2 b/exercises/practice/rna-transcription/.meta/template.j2 index f88481ec..c0d44d79 100644 --- a/exercises/practice/rna-transcription/.meta/template.j2 +++ b/exercises/practice/rna-transcription/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/robot-simulator/.meta/template.j2 b/exercises/practice/robot-simulator/.meta/template.j2 index 71c11f7b..e71520b3 100644 --- a/exercises/practice/robot-simulator/.meta/template.j2 +++ b/exercises/practice/robot-simulator/.meta/template.j2 @@ -22,3 +22,5 @@ expect {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/roman-numerals/.meta/template.j2 b/exercises/practice/roman-numerals/.meta/template.j2 index d2fb0f03..f02a5f26 100644 --- a/exercises/practice/roman-numerals/.meta/template.j2 +++ b/exercises/practice/roman-numerals/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == Ok({{ case["expected"] | to_roc }}) {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/rotational-cipher/.meta/template.j2 b/exercises/practice/rotational-cipher/.meta/template.j2 index 6fb226c6..adefef0e 100644 --- a/exercises/practice/rotational-cipher/.meta/template.j2 +++ b/exercises/practice/rotational-cipher/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/run-length-encoding/.meta/template.j2 b/exercises/practice/run-length-encoding/.meta/template.j2 index 61c5c793..10a2af2d 100644 --- a/exercises/practice/run-length-encoding/.meta/template.j2 +++ b/exercises/practice/run-length-encoding/.meta/template.j2 @@ -25,3 +25,4 @@ expect {% endfor %} {% endfor %} +{{ macros.footer() }} diff --git a/exercises/practice/saddle-points/.meta/template.j2 b/exercises/practice/saddle-points/.meta/template.j2 index f8b9d3ad..1cb43abd 100644 --- a/exercises/practice/saddle-points/.meta/template.j2 +++ b/exercises/practice/saddle-points/.meta/template.j2 @@ -21,3 +21,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/say/.meta/template.j2 b/exercises/practice/say/.meta/template.j2 index 2e8c177e..3a825ceb 100644 --- a/exercises/practice/say/.meta/template.j2 +++ b/exercises/practice/say/.meta/template.j2 @@ -15,3 +15,5 @@ expect {% endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/scrabble-score/.meta/template.j2 b/exercises/practice/scrabble-score/.meta/template.j2 index 26c059c0..1aeb76f2 100644 --- a/exercises/practice/scrabble-score/.meta/template.j2 +++ b/exercises/practice/scrabble-score/.meta/template.j2 @@ -12,3 +12,4 @@ expect {% endfor %} +{{ macros.footer() }} diff --git a/exercises/practice/secret-handshake/.meta/template.j2 b/exercises/practice/secret-handshake/.meta/template.j2 index a4eea5d5..b75580df 100644 --- a/exercises/practice/secret-handshake/.meta/template.j2 +++ b/exercises/practice/secret-handshake/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/series/.meta/template.j2 b/exercises/practice/series/.meta/template.j2 index bc3bea6a..aeb61d0d 100644 --- a/exercises/practice/series/.meta/template.j2 +++ b/exercises/practice/series/.meta/template.j2 @@ -16,3 +16,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/sgf-parsing/.meta/template.j2 b/exercises/practice/sgf-parsing/.meta/template.j2 index 3f4e160d..c45199d3 100644 --- a/exercises/practice/sgf-parsing/.meta/template.j2 +++ b/exercises/practice/sgf-parsing/.meta/template.j2 @@ -32,3 +32,5 @@ expect {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/sieve/.meta/template.j2 b/exercises/practice/sieve/.meta/template.j2 index 353a7c2a..ba6db06d 100644 --- a/exercises/practice/sieve/.meta/template.j2 +++ b/exercises/practice/sieve/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/space-age/.meta/template.j2 b/exercises/practice/space-age/.meta/template.j2 index d589ff9d..1a810b91 100644 --- a/exercises/practice/space-age/.meta/template.j2 +++ b/exercises/practice/space-age/.meta/template.j2 @@ -11,3 +11,5 @@ expect Num.is_approx_eq(result, {{ case["expected"] }}, { atol: 0.01 }) {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/spiral-matrix/.meta/template.j2 b/exercises/practice/spiral-matrix/.meta/template.j2 index 9f973930..be3fe425 100644 --- a/exercises/practice/spiral-matrix/.meta/template.j2 +++ b/exercises/practice/spiral-matrix/.meta/template.j2 @@ -20,3 +20,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/square-root/.meta/template.j2 b/exercises/practice/square-root/.meta/template.j2 index 9f905aa8..9a336343 100644 --- a/exercises/practice/square-root/.meta/template.j2 +++ b/exercises/practice/square-root/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/strain/.meta/template.j2 b/exercises/practice/strain/.meta/template.j2 index 4635e345..9a798221 100644 --- a/exercises/practice/strain/.meta/template.j2 +++ b/exercises/practice/strain/.meta/template.j2 @@ -23,3 +23,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/sublist/.meta/template.j2 b/exercises/practice/sublist/.meta/template.j2 index 9b1776c0..15eb919a 100644 --- a/exercises/practice/sublist/.meta/template.j2 +++ b/exercises/practice/sublist/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_pascal }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/sum-of-multiples/.meta/template.j2 b/exercises/practice/sum-of-multiples/.meta/template.j2 index 52e64a81..299d1ad9 100644 --- a/exercises/practice/sum-of-multiples/.meta/template.j2 +++ b/exercises/practice/sum-of-multiples/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/tournament/.meta/template.j2 b/exercises/practice/tournament/.meta/template.j2 index 0a8f88be..261f24c9 100644 --- a/exercises/practice/tournament/.meta/template.j2 +++ b/exercises/practice/tournament/.meta/template.j2 @@ -13,3 +13,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/transpose/.meta/template.j2 b/exercises/practice/transpose/.meta/template.j2 index 4ea165d5..d524fdc1 100644 --- a/exercises/practice/transpose/.meta/template.j2 +++ b/exercises/practice/transpose/.meta/template.j2 @@ -13,3 +13,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/triangle/.meta/template.j2 b/exercises/practice/triangle/.meta/template.j2 index d32731f6..39d7277c 100644 --- a/exercises/practice/triangle/.meta/template.j2 +++ b/exercises/practice/triangle/.meta/template.j2 @@ -16,4 +16,6 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} -{% endfor %} \ No newline at end of file +{% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/two-bucket/.meta/template.j2 b/exercises/practice/two-bucket/.meta/template.j2 index 0847d62c..1251fe41 100644 --- a/exercises/practice/two-bucket/.meta/template.j2 +++ b/exercises/practice/two-bucket/.meta/template.j2 @@ -25,3 +25,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/two-fer/.meta/template.j2 b/exercises/practice/two-fer/.meta/template.j2 index e1673c82..c770bac0 100644 --- a/exercises/practice/two-fer/.meta/template.j2 +++ b/exercises/practice/two-fer/.meta/template.j2 @@ -15,3 +15,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/variable-length-quantity/.meta/template.j2 b/exercises/practice/variable-length-quantity/.meta/template.j2 index 98023e7d..6f2122ca 100644 --- a/exercises/practice/variable-length-quantity/.meta/template.j2 +++ b/exercises/practice/variable-length-quantity/.meta/template.j2 @@ -31,3 +31,5 @@ expect {% endfor %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/word-count/.meta/template.j2 b/exercises/practice/word-count/.meta/template.j2 index 1a6dccc4..8975a757 100644 --- a/exercises/practice/word-count/.meta/template.j2 +++ b/exercises/practice/word-count/.meta/template.j2 @@ -16,3 +16,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/word-search/.meta/template.j2 b/exercises/practice/word-search/.meta/template.j2 index 40fcd147..25d4c5c0 100644 --- a/exercises/practice/word-search/.meta/template.j2 +++ b/exercises/practice/word-search/.meta/template.j2 @@ -22,3 +22,5 @@ expect result == expected {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/wordy/.meta/template.j2 b/exercises/practice/wordy/.meta/template.j2 index d814b72c..3037f73c 100644 --- a/exercises/practice/wordy/.meta/template.j2 +++ b/exercises/practice/wordy/.meta/template.j2 @@ -15,3 +15,5 @@ expect {%- endif %} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/yacht/.meta/template.j2 b/exercises/practice/yacht/.meta/template.j2 index a429ba1b..d4a1665c 100644 --- a/exercises/practice/yacht/.meta/template.j2 +++ b/exercises/practice/yacht/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == {{ case["expected"] | to_roc }} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/zebra-puzzle/.meta/template.j2 b/exercises/practice/zebra-puzzle/.meta/template.j2 index 70da5c15..968597b5 100644 --- a/exercises/practice/zebra-puzzle/.meta/template.j2 +++ b/exercises/practice/zebra-puzzle/.meta/template.j2 @@ -11,3 +11,5 @@ expect result == Ok({{ case["expected"] }}) {% endfor %} + +{{ macros.footer() }} From 6692385e74784e11a247df473428f9aa3d168563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 11:20:39 +1200 Subject: [PATCH 021/162] Update exercise to new compiler: circular-buffer --- .../circular-buffer/.meta/Example.roc | 93 ++-- .../circular-buffer/.meta/template.j2 | 52 +- .../circular-buffer/CircularBuffer.roc | 41 +- .../circular-buffer/circular-buffer-test.roc | 472 ++++++++---------- 4 files changed, 323 insertions(+), 335 deletions(-) diff --git a/exercises/practice/circular-buffer/.meta/Example.roc b/exercises/practice/circular-buffer/.meta/Example.roc index a181b6dc..660c41e7 100644 --- a/exercises/practice/circular-buffer/.meta/Example.roc +++ b/exercises/practice/circular-buffer/.meta/Example.roc @@ -1,41 +1,60 @@ -module [create, read, write, overwrite, clear] +CircularBuffer :: { data : List(I64), start : U64, length : U64 }.{ + create : { capacity : U64 } -> CircularBuffer + create = |{ capacity }| { + { data: List.repeat(0, capacity), start: 0, length: 0 } + } -CircularBuffer : { data : List I64, start : U64, length : U64 } + read : CircularBuffer -> Try({ updated_buffer : CircularBuffer, value : I64 }, [BufferEmpty]) + read = |{ data, start, length }| { + if length == 0 { + Err(BufferEmpty) + } else { + increment_start = (start + 1) % data.len() + updated_buffer = { data, start: increment_start, length: length - 1 } + match data.get(start) { + Ok(value) => Ok({ updated_buffer, value }) + Err(OutOfBounds) => { + crash "Unreachable: start should never be out of bounds" + } + } + } + } -create : { capacity : U64 } -> CircularBuffer -create = |{ capacity }| - { data: List.repeat(0, capacity), start: 0, length: 0 } + write : CircularBuffer, I64 -> Try(CircularBuffer, [BufferFull]) + write = |{ data, start, length }, value| { + if length == data.len() { + Err(BufferFull) + } else { + index = (start + length) % data.len() + new_data = match data.replace(index, value) { + Ok(d) => d + Err(_) => { + crash "Unreachable: the index is guaranteed to be within bounds." + } + }.list + Ok({ data: new_data, start, length: length + 1 }) + } + } -read : CircularBuffer -> Result { new_buffer : CircularBuffer, value : I64 } [BufferEmpty] -read = |{ data, start, length }| - if length == 0 then - Err(BufferEmpty) - else - increment_start = (start + 1) % List.len(data) - new_buffer = { data, start: increment_start, length: length - 1 } - when data |> List.get(start) is - Ok(value) -> Ok({ new_buffer, value }) - Err(OutOfBounds) -> crash("Unreachable: start should never be out of bounds") + overwrite : CircularBuffer, I64 -> CircularBuffer + overwrite = |{ data, start, length }, value| { + index = (start + length) % data.len() + new_data = match data.replace(index, value) { + Ok(d) => d + Err(_) => { + crash "Unreachable: the index is guaranteed to be within bounds." + } + }.list + if length == data.len() { + inc_start = (start + 1) % data.len() + { data: new_data, start: inc_start, length: length } + } else { + { data: new_data, start, length: length + 1 } + } + } -write : CircularBuffer, I64 -> Result CircularBuffer [BufferFull] -write = |{ data, start, length }, value| - if length == List.len(data) then - Err(BufferFull) - else - index = (start + length) % List.len(data) - new_data = data |> List.replace(index, value) |> .list - Ok({ data: new_data, start, length: length + 1 }) - -overwrite : CircularBuffer, I64 -> CircularBuffer -overwrite = |{ data, start, length }, value| - index = (start + length) % List.len(data) - new_data = data |> List.replace(index, value) |> .list - if length == List.len(data) then - inc_start = (start + 1) % List.len(data) - { data: new_data, start: inc_start, length: length } - else - { data: new_data, start, length: length + 1 } - -clear : CircularBuffer -> CircularBuffer -clear = |circular_buffer| - { data: circular_buffer.data, start: 0, length: 0 } + clear : CircularBuffer -> CircularBuffer + clear = |circular_buffer| { + { data: circular_buffer.data, start: 0, length: 0 } + } +} diff --git a/exercises/practice/circular-buffer/.meta/template.j2 b/exercises/practice/circular-buffer/.meta/template.j2 index 6046d84e..8125594e 100644 --- a/exercises/practice/circular-buffer/.meta/template.j2 +++ b/exercises/practice/circular-buffer/.meta/template.j2 @@ -2,46 +2,52 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [create, read, write, overwrite, clear] +import {{ exercise | to_pascal }} + +expect_value = |read_result, expected| { + expect read_result.value == expected + read_result.updated_buffer +} {% for case in cases -%} # {{ case["description"] }} -run_operations{{ loop.index }} = |_| - result = - create({ capacity: {{ case["input"]["capacity"] }} }) +expect { + {% if case["input"]["operations"][-1]["should_succeed"] -%} + _ + {%- endif -%} + result = {{ exercise | to_pascal }}.create({ capacity: {{ case["input"]["capacity"] }} }) {%- for op in case["input"]["operations"] -%} {%- if op["operation"] == "clear" %} - |> clear + .clear() {%- elif op["operation"] == "overwrite" %} - |> overwrite({{ op["item"] }}) + .overwrite({{ op["item"] }}) {%- elif op["operation"] == "write" %} {%- if op["should_succeed"] %} - |> write({{ op["item"] }})? + .write({{ op["item"] }})? {%- else %} - |> |buffer_before_write| - write_result = buffer_before_write |> write({{ op["item"] }}) - expect write_result == Err(BufferFull) - buffer_before_write + .write({{ op["item"] }}) + + result == Err(BufferFull) +} {%- endif %} {%- elif op["operation"] == "read" %} {%- if op["should_succeed"] %} - |> read? |> |read_result| - expect read_result.value == {{ op["expected"] }} - read_result.new_buffer + .read()? + -> expect_value({{ op["expected"] }}) {%- else %} - |> |buffer_before_read| - read_result = buffer_before_read |> read - expect read_result == Err(BufferEmpty) - buffer_before_read + .read() + + result == Err(BufferEmpty) +} {%- endif %} {%- endif %} {%- endfor %} - Ok(result) -expect - - result = run_operations{{ loop.index }}({}) - result |> Result.is_ok + {% if case["input"]["operations"][-1]["should_succeed"] -%} + Bool.True +} + + {% endif -%} {% endfor %} diff --git a/exercises/practice/circular-buffer/CircularBuffer.roc b/exercises/practice/circular-buffer/CircularBuffer.roc index 9fffc10b..1cb4f174 100644 --- a/exercises/practice/circular-buffer/CircularBuffer.roc +++ b/exercises/practice/circular-buffer/CircularBuffer.roc @@ -1,23 +1,26 @@ -module [create, read, write, overwrite, clear] +CircularBuffer :: { data : List(I64), start : U64, length : U64 }.{ + create : { capacity : U64 } -> CircularBuffer + create = |{ capacity }| { + crash "Please implement the 'create' function" + } -CircularBuffer : { data : List I64, start : U64, length : U64 } + read : CircularBuffer -> Try({ updated_buffer : CircularBuffer, value : I64 }, [BufferEmpty]) + read = |circular_buffer| { + crash "Please implement the 'read' function" + } -create : { capacity : U64 } -> CircularBuffer -create = |{ capacity }| - crash("Please implement the 'create' function") + write : CircularBuffer, I64 -> Try(CircularBuffer, [BufferFull]) + write = |circular_buffer, value| { + crash "Please implement the 'write' function" + } -read : CircularBuffer -> Result { new_buffer : CircularBuffer, value : I64 } [BufferEmpty] -read = |{ data, start, length }| - crash("Please implement the 'read' function") + overwrite : CircularBuffer, I64 -> CircularBuffer + overwrite = |circular_buffer, value| { + crash "Please implement the 'overwrite' function" + } -write : CircularBuffer, I64 -> Result CircularBuffer [BufferFull] -write = |circular_buffer, value| - crash("Please implement the 'write' function") - -overwrite : CircularBuffer, I64 -> CircularBuffer -overwrite = |{ data, start, length }, value| - crash("Please implement the 'overwrite' function") - -clear : CircularBuffer -> CircularBuffer -clear = |circular_buffer| - crash("Please implement the 'clear' function") + clear : CircularBuffer -> CircularBuffer + clear = |circular_buffer| { + crash "Please implement the 'clear' function" + } +} diff --git a/exercises/practice/circular-buffer/circular-buffer-test.roc b/exercises/practice/circular-buffer/circular-buffer-test.roc index 2409d32b..79b13202 100644 --- a/exercises/practice/circular-buffer/circular-buffer-test.roc +++ b/exercises/practice/circular-buffer/circular-buffer-test.roc @@ -1,290 +1,250 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/circular-buffer/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout +# File last updated on 2026-06-12 -main! = |_args| - Stdout.line!("") +import CircularBuffer -import CircularBuffer exposing [create, read, write, overwrite, clear] +expect_value = |read_result, expected| { + expect read_result.value == expected + read_result.updated_buffer +} # reading empty buffer should fail -run_operations1 = |_| - result = - create({ capacity: 1 }) - |> |buffer_before_read| - read_result = buffer_before_read |> read - expect read_result == Err(BufferEmpty) - buffer_before_read - Ok(result) +expect { + result = CircularBuffer.create({ capacity: 1 }) + .read() -expect - result = run_operations1({}) - result |> Result.is_ok + result == Err(BufferEmpty) +} # can read an item just written -run_operations2 = |_| - result = - create({ capacity: 1 }) - |> write(1)? - |> read? - |> |read_result| - expect read_result.value == 1 - read_result.new_buffer - Ok(result) - -expect - result = run_operations2({}) - result |> Result.is_ok +expect { + _result = CircularBuffer.create({ capacity: 1 }) + .write( + 1, + )? + .read()? + ->expect_value(1) + + Bool.True +} # each item may only be read once -run_operations3 = |_| - result = - create({ capacity: 1 }) - |> write(1)? - |> read? - |> |read_result| - expect read_result.value == 1 - read_result.new_buffer - |> |buffer_before_read| - read_result = buffer_before_read |> read - expect read_result == Err(BufferEmpty) - buffer_before_read - Ok(result) - -expect - result = run_operations3({}) - result |> Result.is_ok +expect { + result = CircularBuffer.create({ capacity: 1 }) + .write( + 1, + )? + .read()? + ->expect_value(1) + .read() + + result == Err(BufferEmpty) +} # items are read in the order they are written -run_operations4 = |_| - result = - create({ capacity: 2 }) - |> write(1)? - |> write(2)? - |> read? - |> |read_result| - expect read_result.value == 1 - read_result.new_buffer - |> read? - |> |read_result| - expect read_result.value == 2 - read_result.new_buffer - Ok(result) - -expect - result = run_operations4({}) - result |> Result.is_ok +expect { + _result = CircularBuffer.create({ capacity: 2 }) + .write( + 1, + )? + .write( + 2, + )? + .read()? + ->expect_value(1) + .read()? + ->expect_value(2) + + Bool.True +} # full buffer can't be written to -run_operations5 = |_| - result = - create({ capacity: 1 }) - |> write(1)? - |> |buffer_before_write| - write_result = buffer_before_write |> write(2) - expect write_result == Err(BufferFull) - buffer_before_write - Ok(result) - -expect - result = run_operations5({}) - result |> Result.is_ok +expect { + result = CircularBuffer.create({ capacity: 1 }) + .write( + 1, + )? + .write( + 2, + ) + + result == Err(BufferFull) +} # a read frees up capacity for another write -run_operations6 = |_| - result = - create({ capacity: 1 }) - |> write(1)? - |> read? - |> |read_result| - expect read_result.value == 1 - read_result.new_buffer - |> write(2)? - |> read? - |> |read_result| - expect read_result.value == 2 - read_result.new_buffer - Ok(result) - -expect - result = run_operations6({}) - result |> Result.is_ok +expect { + _result = CircularBuffer.create({ capacity: 1 }) + .write( + 1, + )? + .read()? + ->expect_value(1) + .write( + 2, + )? + .read()? + ->expect_value(2) + + Bool.True +} # read position is maintained even across multiple writes -run_operations7 = |_| - result = - create({ capacity: 3 }) - |> write(1)? - |> write(2)? - |> read? - |> |read_result| - expect read_result.value == 1 - read_result.new_buffer - |> write(3)? - |> read? - |> |read_result| - expect read_result.value == 2 - read_result.new_buffer - |> read? - |> |read_result| - expect read_result.value == 3 - read_result.new_buffer - Ok(result) - -expect - result = run_operations7({}) - result |> Result.is_ok +expect { + _result = CircularBuffer.create({ capacity: 3 }) + .write( + 1, + )? + .write( + 2, + )? + .read()? + ->expect_value(1) + .write( + 3, + )? + .read()? + ->expect_value(2) + .read()? + ->expect_value(3) + + Bool.True +} # items cleared out of buffer can't be read -run_operations8 = |_| - result = - create({ capacity: 1 }) - |> write(1)? - |> clear - |> |buffer_before_read| - read_result = buffer_before_read |> read - expect read_result == Err(BufferEmpty) - buffer_before_read - Ok(result) - -expect - result = run_operations8({}) - result |> Result.is_ok +expect { + result = CircularBuffer.create({ capacity: 1 }) + .write( + 1, + )? + .clear() + .read() + + result == Err(BufferEmpty) +} # clear frees up capacity for another write -run_operations9 = |_| - result = - create({ capacity: 1 }) - |> write(1)? - |> clear - |> write(2)? - |> read? - |> |read_result| - expect read_result.value == 2 - read_result.new_buffer - Ok(result) - -expect - result = run_operations9({}) - result |> Result.is_ok +expect { + _result = CircularBuffer.create({ capacity: 1 }) + .write( + 1, + )? + .clear() + .write( + 2, + )? + .read()? + ->expect_value(2) + + Bool.True +} # clear does nothing on empty buffer -run_operations10 = |_| - result = - create({ capacity: 1 }) - |> clear - |> write(1)? - |> read? - |> |read_result| - expect read_result.value == 1 - read_result.new_buffer - Ok(result) - -expect - result = run_operations10({}) - result |> Result.is_ok +expect { + _result = CircularBuffer.create({ capacity: 1 }) + .clear() + .write( + 1, + )? + .read()? + ->expect_value(1) + + Bool.True +} # overwrite acts like write on non-full buffer -run_operations11 = |_| - result = - create({ capacity: 2 }) - |> write(1)? - |> overwrite(2) - |> read? - |> |read_result| - expect read_result.value == 1 - read_result.new_buffer - |> read? - |> |read_result| - expect read_result.value == 2 - read_result.new_buffer - Ok(result) - -expect - result = run_operations11({}) - result |> Result.is_ok +expect { + _result = CircularBuffer.create({ capacity: 2 }) + .write( + 1, + )? + .overwrite( + 2, + ) + .read()? + ->expect_value(1) + .read()? + ->expect_value(2) + + Bool.True +} # overwrite replaces the oldest item on full buffer -run_operations12 = |_| - result = - create({ capacity: 2 }) - |> write(1)? - |> write(2)? - |> overwrite(3) - |> read? - |> |read_result| - expect read_result.value == 2 - read_result.new_buffer - |> read? - |> |read_result| - expect read_result.value == 3 - read_result.new_buffer - Ok(result) - -expect - result = run_operations12({}) - result |> Result.is_ok +expect { + _result = CircularBuffer.create({ capacity: 2 }) + .write( + 1, + )? + .write( + 2, + )? + .overwrite( + 3, + ) + .read()? + ->expect_value(2) + .read()? + ->expect_value(3) + + Bool.True +} # overwrite replaces the oldest item remaining in buffer following a read -run_operations13 = |_| - result = - create({ capacity: 3 }) - |> write(1)? - |> write(2)? - |> write(3)? - |> read? - |> |read_result| - expect read_result.value == 1 - read_result.new_buffer - |> write(4)? - |> overwrite(5) - |> read? - |> |read_result| - expect read_result.value == 3 - read_result.new_buffer - |> read? - |> |read_result| - expect read_result.value == 4 - read_result.new_buffer - |> read? - |> |read_result| - expect read_result.value == 5 - read_result.new_buffer - Ok(result) - -expect - result = run_operations13({}) - result |> Result.is_ok +expect { + _result = CircularBuffer.create({ capacity: 3 }) + .write( + 1, + )? + .write( + 2, + )? + .write( + 3, + )? + .read()? + ->expect_value(1) + .write( + 4, + )? + .overwrite( + 5, + ) + .read()? + ->expect_value(3) + .read()? + ->expect_value(4) + .read()? + ->expect_value(5) + + Bool.True +} # initial clear does not affect wrapping around -run_operations14 = |_| - result = - create({ capacity: 2 }) - |> clear - |> write(1)? - |> write(2)? - |> overwrite(3) - |> overwrite(4) - |> read? - |> |read_result| - expect read_result.value == 3 - read_result.new_buffer - |> read? - |> |read_result| - expect read_result.value == 4 - read_result.new_buffer - |> |buffer_before_read| - read_result = buffer_before_read |> read - expect read_result == Err(BufferEmpty) - buffer_before_read - Ok(result) - -expect - result = run_operations14({}) - result |> Result.is_ok +expect { + result = CircularBuffer.create({ capacity: 2 }) + .clear() + .write( + 1, + )? + .write( + 2, + )? + .overwrite( + 3, + ) + .overwrite( + 4, + ) + .read()? + ->expect_value(3) + .read()? + ->expect_value(4) + .read() + + result == Err(BufferEmpty) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} From ac32d807e814f5bb59c7a49174411e908df502c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 14:59:20 +1200 Subject: [PATCH 022/162] Upgrade all .meta/template.j2 and *-test.roc files to new compiler --- .../practice/accumulate/accumulate-test.roc | 2 +- exercises/practice/acronym/acronym-test.roc | 2 +- .../affine-cipher/affine-cipher-test.roc | 2 +- .../all-your-base/all-your-base-test.roc | 2 +- .../practice/allergies/allergies-test.roc | 2 +- .../practice/alphametics/.meta/template.j2 | 12 +- .../practice/alphametics/alphametics-test.roc | 250 +- exercises/practice/anagram/.meta/template.j2 | 6 +- exercises/practice/anagram/anagram-test.roc | 148 +- .../armstrong-numbers-test.roc | 2 +- .../atbash-cipher/atbash-cipher-test.roc | 2 +- .../binary-search-tree/.meta/template.j2 | 3 +- .../binary-search-tree-test.roc | 357 +-- .../practice/binary-search/.meta/template.j2 | 7 +- .../binary-search/binary-search-test.roc | 91 +- exercises/practice/binary/binary-test.roc | 2 +- exercises/practice/bob/.meta/template.j2 | 3 +- exercises/practice/bob/bob-test.roc | 189 +- exercises/practice/bowling/.meta/template.j2 | 43 +- exercises/practice/bowling/bowling-test.roc | 545 ++-- exercises/practice/change/.meta/template.j2 | 7 +- exercises/practice/change/change-test.roc | 122 +- .../circular-buffer/circular-buffer-test.roc | 2 +- exercises/practice/clock/.meta/template.j2 | 13 +- exercises/practice/clock/clock-test.roc | 620 ++--- .../collatz-conjecture/.meta/template.j2 | 5 +- .../collatz-conjecture-test.roc | 49 +- .../complex-numbers/.meta/template.j2 | 23 +- .../complex-numbers/complex-numbers-test.roc | 470 ++-- exercises/practice/connect/.meta/template.j2 | 5 +- exercises/practice/connect/connect-test.roc | 214 +- .../practice/crypto-square/.meta/template.j2 | 3 +- .../crypto-square/crypto-square-test.roc | 102 +- .../practice/custom-set/.meta/template.j2 | 16 +- .../practice/custom-set/custom-set-test.roc | 573 +++-- exercises/practice/darts/.meta/template.j2 | 3 +- exercises/practice/darts/darts-test.roc | 105 +- exercises/practice/diamond/.meta/template.j2 | 5 +- exercises/practice/diamond/diamond-test.roc | 219 +- .../difference-of-squares/.meta/template.j2 | 3 +- .../difference-of-squares-test.roc | 77 +- exercises/practice/dominoes/.meta/template.j2 | 72 +- exercises/practice/dominoes/dominoes-test.roc | 313 ++- .../practice/eliuds-eggs/.meta/template.j2 | 3 +- .../practice/eliuds-eggs/eliuds-eggs-test.roc | 42 +- .../error-handling/error-handling-test.roc | 263 +- exercises/practice/etl/.meta/template.j2 | 3 +- exercises/practice/etl/etl-test.roc | 208 +- .../practice/flatten-array/.meta/template.j2 | 3 +- .../flatten-array/flatten-array-test.roc | 531 ++-- .../practice/flower-field/.meta/template.j2 | 7 +- .../flower-field/flower-field-test.roc | 390 +-- .../practice/food-chain/.meta/template.j2 | 3 +- .../practice/food-chain/food-chain-test.roc | 336 ++- exercises/practice/forth/.meta/template.j2 | 9 +- exercises/practice/forth/forth-test.roc | 571 +++-- .../practice/gigasecond/.meta/template.j2 | 3 +- .../practice/gigasecond/gigasecond-test.roc | 48 +- .../practice/go-counting/.meta/template.j2 | 36 +- .../practice/go-counting/go-counting-test.roc | 394 +-- exercises/practice/grains/.meta/template.j2 | 8 +- exercises/practice/grains/grains-test.roc | 84 +- exercises/practice/grep/.meta/template.j2 | 3 +- exercises/practice/grep/grep-test.roc | 397 ++- exercises/practice/hamming/.meta/template.j2 | 5 +- exercises/practice/hamming/hamming-test.roc | 77 +- .../practice/hello-world/.meta/template.j2 | 3 +- .../practice/hello-world/hello-world-test.roc | 22 +- .../practice/hexadecimal/hexadecimal-test.roc | 336 +-- .../practice/high-scores/.meta/template.j2 | 3 +- .../practice/high-scores/high-scores-test.roc | 63 +- exercises/practice/house/.meta/template.j2 | 3 +- exercises/practice/house/house-test.roc | 112 +- .../practice/isbn-verifier/.meta/template.j2 | 3 +- .../isbn-verifier/isbn-verifier-test.roc | 147 +- exercises/practice/isogram/.meta/template.j2 | 3 +- exercises/practice/isogram/isogram-test.roc | 112 +- .../killer-sudoku-helper/.meta/template.j2 | 3 +- .../killer-sudoku-helper-test.roc | 105 +- .../kindergarten-garden/.meta/template.j2 | 5 +- .../kindergarten-garden-test.roc | 405 +-- exercises/practice/knapsack/.meta/template.j2 | 3 +- exercises/practice/knapsack/knapsack-test.roc | 165 +- .../largest-series-product/.meta/template.j2 | 7 +- .../largest-series-product-test.roc | 162 +- exercises/practice/leap/.meta/template.j2 | 3 +- exercises/practice/leap/leap-test.roc | 77 +- exercises/practice/list-ops/.meta/template.j2 | 17 +- exercises/practice/list-ops/list-ops-test.roc | 168 +- exercises/practice/luhn/.meta/template.j2 | 3 +- exercises/practice/luhn/luhn-test.roc | 168 +- .../matching-brackets/.meta/template.j2 | 5 +- .../matching-brackets-test.roc | 154 +- exercises/practice/matrix/.meta/template.j2 | 5 +- exercises/practice/matrix/matrix-test.roc | 138 +- exercises/practice/meetup/.meta/template.j2 | 3 +- exercises/practice/meetup/meetup-test.roc | 2198 +++++++++-------- .../practice/micro-blog/.meta/template.j2 | 3 +- .../practice/micro-blog/micro-blog-test.roc | 97 +- .../practice/minesweeper/.meta/template.j2 | 7 +- .../practice/minesweeper/minesweeper-test.roc | 390 +-- .../practice/nth-prime/.meta/template.j2 | 5 +- .../practice/nth-prime/nth-prime-test.roc | 49 +- .../nucleotide-count/.meta/template.j2 | 5 +- .../nucleotide-count-test.roc | 49 +- .../practice/ocr-numbers/.meta/template.j2 | 5 +- .../practice/ocr-numbers/ocr-numbers-test.roc | 398 ++- exercises/practice/octal/octal-test.roc | 308 ++- .../palindrome-products/.meta/template.j2 | 20 +- .../palindrome-products-test.roc | 335 +-- exercises/practice/pangram/.meta/template.j2 | 3 +- exercises/practice/pangram/pangram-test.roc | 84 +- .../pascals-triangle/.meta/template.j2 | 3 +- .../pascals-triangle-test.roc | 162 +- .../perfect-numbers/.meta/template.j2 | 5 +- .../perfect-numbers/perfect-numbers-test.roc | 98 +- .../practice/phone-number/.meta/template.j2 | 5 +- .../phone-number/phone-number-test.roc | 150 +- .../practice/pig-latin/.meta/template.j2 | 3 +- .../practice/pig-latin/pig-latin-test.roc | 175 +- exercises/practice/poker/.meta/template.j2 | 3 +- exercises/practice/poker/poker-test.roc | 347 +-- .../practice/prime-factors/.meta/template.j2 | 3 +- .../prime-factors/prime-factors-test.roc | 98 +- .../protein-translation/.meta/template.j2 | 7 +- .../protein-translation-test.roc | 385 ++- exercises/practice/proverb/.meta/template.j2 | 3 +- exercises/practice/proverb/proverb-test.roc | 112 +- .../pythagorean-triplet/.meta/template.j2 | 3 +- .../pythagorean-triplet-test.roc | 149 +- .../practice/queen-attack/.meta/template.j2 | 28 +- .../queen-attack/queen-attack-test.roc | 230 +- .../rail-fence-cipher/.meta/template.j2 | 5 +- .../rail-fence-cipher-test.roc | 80 +- .../practice/raindrops/.meta/template.j2 | 3 +- .../practice/raindrops/raindrops-test.roc | 140 +- .../rational-numbers/.meta/template.j2 | 17 +- .../rational-numbers-test.roc | 319 +-- .../practice/rectangles/.meta/template.j2 | 3 +- .../practice/rectangles/rectangles-test.roc | 267 +- .../resistor-color-duo/.meta/template.j2 | 3 +- .../resistor-color-duo-test.roc | 56 +- .../practice/resistor-color/.meta/template.j2 | 11 +- .../resistor-color/resistor-color-test.roc | 65 +- exercises/practice/rest-api/.meta/template.j2 | 25 +- exercises/practice/rest-api/rest-api-test.roc | 585 +++-- .../practice/reverse-string/.meta/template.j2 | 3 +- .../reverse-string/reverse-string-test.roc | 76 +- .../rna-transcription/.meta/template.j2 | 3 +- .../rna-transcription-test.roc | 56 +- .../robot-simulator/.meta/template.j2 | 5 +- .../robot-simulator/robot-simulator-test.roc | 172 +- .../practice/roman-numerals/.meta/template.j2 | 3 +- .../roman-numerals/roman-numerals-test.roc | 203 +- .../rotational-cipher/.meta/template.j2 | 3 +- .../rotational-cipher-test.roc | 84 +- .../run-length-encoding/.meta/template.j2 | 11 +- .../run-length-encoding-test.roc | 155 +- .../practice/saddle-points/.meta/template.j2 | 5 +- .../saddle-points/saddle-points-test.roc | 257 +- exercises/practice/say/.meta/template.j2 | 5 +- exercises/practice/say/say-test.roc | 157 +- .../practice/scrabble-score/.meta/template.j2 | 3 +- .../scrabble-score/scrabble-score-test.roc | 91 +- .../secret-handshake/.meta/template.j2 | 3 +- .../secret-handshake-test.roc | 91 +- exercises/practice/series/.meta/template.j2 | 5 +- exercises/practice/series/series-test.roc | 84 +- .../practice/sgf-parsing/.meta/template.j2 | 6 +- .../practice/sgf-parsing/sgf-parsing-test.roc | 730 +++--- exercises/practice/sieve/.meta/template.j2 | 3 +- exercises/practice/sieve/sieve-test.roc | 49 +- .../simple-linked-list-test.roc | 105 +- .../practice/space-age/.meta/template.j2 | 3 +- .../practice/space-age/space-age-test.roc | 70 +- .../practice/spiral-matrix/.meta/template.j2 | 3 +- .../spiral-matrix/spiral-matrix-test.roc | 106 +- .../practice/square-root/.meta/template.j2 | 3 +- .../practice/square-root/square-root-test.roc | 56 +- exercises/practice/strain/.meta/template.j2 | 17 +- exercises/practice/strain/strain-test.roc | 168 +- exercises/practice/sublist/.meta/template.j2 | 5 +- exercises/practice/sublist/sublist-test.roc | 140 +- .../sum-of-multiples/.meta/template.j2 | 5 +- .../sum-of-multiples-test.roc | 126 +- .../practice/tournament/.meta/template.j2 | 3 +- .../practice/tournament/tournament-test.roc | 358 ++- .../practice/transpose/.meta/template.j2 | 7 +- .../practice/transpose/transpose-test.roc | 487 ++-- exercises/practice/triangle/.meta/template.j2 | 3 +- exercises/practice/triangle/triangle-test.roc | 126 +- .../practice/two-bucket/.meta/template.j2 | 5 +- .../practice/two-bucket/two-bucket-test.roc | 371 +-- exercises/practice/two-fer/.meta/template.j2 | 3 +- exercises/practice/two-fer/two-fer-test.roc | 35 +- .../.meta/template.j2 | 9 +- .../variable-length-quantity-test.roc | 359 +-- .../practice/word-count/.meta/template.j2 | 3 +- .../practice/word-count/word-count-test.roc | 332 +-- .../practice/word-search/.meta/template.j2 | 5 +- .../practice/word-search/word-search-test.roc | 908 ++++--- exercises/practice/wordy/.meta/template.j2 | 5 +- exercises/practice/wordy/wordy-test.roc | 203 +- exercises/practice/yacht/.meta/template.j2 | 5 +- exercises/practice/yacht/yacht-test.roc | 217 +- .../practice/zebra-puzzle/.meta/template.j2 | 3 +- .../zebra-puzzle/zebra-puzzle-test.roc | 28 +- 207 files changed, 13218 insertions(+), 11349 deletions(-) diff --git a/exercises/practice/accumulate/accumulate-test.roc b/exercises/practice/accumulate/accumulate-test.roc index 14544234..6baf595a 100644 --- a/exercises/practice/accumulate/accumulate-test.roc +++ b/exercises/practice/accumulate/accumulate-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/accumulate/canonical-data.json -# File last updated on 2026-06-12 +# File last updated on 2026-06-13 import Accumulate exposing [accumulate] diff --git a/exercises/practice/acronym/acronym-test.roc b/exercises/practice/acronym/acronym-test.roc index 3fbbeccb..78fb3962 100644 --- a/exercises/practice/acronym/acronym-test.roc +++ b/exercises/practice/acronym/acronym-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json -# File last updated on 2026-06-12 +# File last updated on 2026-06-13 import Acronym exposing [abbreviate] diff --git a/exercises/practice/affine-cipher/affine-cipher-test.roc b/exercises/practice/affine-cipher/affine-cipher-test.roc index 6f351bf3..feee6eb0 100644 --- a/exercises/practice/affine-cipher/affine-cipher-test.roc +++ b/exercises/practice/affine-cipher/affine-cipher-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/affine-cipher/canonical-data.json -# File last updated on 2026-06-12 +# File last updated on 2026-06-13 import AffineCipher diff --git a/exercises/practice/all-your-base/all-your-base-test.roc b/exercises/practice/all-your-base/all-your-base-test.roc index 02853fab..e7524f8c 100644 --- a/exercises/practice/all-your-base/all-your-base-test.roc +++ b/exercises/practice/all-your-base/all-your-base-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/all-your-base/canonical-data.json -# File last updated on 2026-06-12 +# File last updated on 2026-06-13 import AllYourBase exposing [rebase] diff --git a/exercises/practice/allergies/allergies-test.roc b/exercises/practice/allergies/allergies-test.roc index fcefcec0..b176d198 100644 --- a/exercises/practice/allergies/allergies-test.roc +++ b/exercises/practice/allergies/allergies-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/allergies/canonical-data.json -# File last updated on 2026-06-12 +# File last updated on 2026-06-13 import Allergies exposing [allergic_to, set] diff --git a/exercises/practice/alphametics/.meta/template.j2 b/exercises/practice/alphametics/.meta/template.j2 index 543145f4..b9613a9d 100644 --- a/exercises/practice/alphametics/.meta/template.j2 +++ b/exercises/practice/alphametics/.meta/template.j2 @@ -6,16 +6,20 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect - result = {{ case["property"] | to_snake }}({{ case["input"]["puzzle"] | to_roc }}) +expect { {%- if case["expected"] %} - Result.with_default(result, []) |> Set.from_list == Set.from_list([ + result = {{ case["property"] | to_snake }}({{ case["input"]["puzzle"] | to_roc }}) + Set.from_list(result ?? []) == Set.from_list([ {%- for letter, value in case["expected"].items() %} ('{{ letter }}', {{ value }}), {%- endfor %} ]) {%- else %} - Result.is_err(result) + result = {{ case["property"] | to_snake }}({{ case["input"]["puzzle"] | to_roc }}) + result.is_err() {%- endif %} +} {% endfor %} + +{{ macros.footer() }} diff --git a/exercises/practice/alphametics/alphametics-test.roc b/exercises/practice/alphametics/alphametics-test.roc index 6fdbe00b..dcd059f5 100644 --- a/exercises/practice/alphametics/alphametics-test.roc +++ b/exercises/practice/alphametics/alphametics-test.roc @@ -1,155 +1,145 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/alphametics/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Alphametics exposing [solve] # puzzle with three letters -expect - result = solve("I + BB == ILL") - Result.with_default(result, []) - |> Set.from_list - == Set.from_list( - [ - ('I', 1), - ('B', 9), - ('L', 0), - ], - ) +expect { + result = solve("I + BB == ILL") + Set.from_list(result ?? []) == Set.from_list( + [ + ('I', 1), + ('B', 9), + ('L', 0), + ], + ) +} # solution must have unique value for each letter -expect - result = solve("A == B") - Result.is_err(result) +expect { + result = solve("A == B") + result.is_err() +} # leading zero solution is invalid -expect - result = solve("ACA + DD == BD") - Result.is_err(result) +expect { + result = solve("ACA + DD == BD") + result.is_err() +} # puzzle with two digits final carry -expect - result = solve("A + A + A + A + A + A + A + A + A + A + A + B == BCC") - Result.with_default(result, []) - |> Set.from_list - == Set.from_list( - [ - ('A', 9), - ('B', 1), - ('C', 0), - ], - ) +expect { + result = solve("A + A + A + A + A + A + A + A + A + A + A + B == BCC") + Set.from_list(result ?? []) == Set.from_list( + [ + ('A', 9), + ('B', 1), + ('C', 0), + ], + ) +} # puzzle with four letters -expect - result = solve("AS + A == MOM") - Result.with_default(result, []) - |> Set.from_list - == Set.from_list( - [ - ('A', 9), - ('S', 2), - ('M', 1), - ('O', 0), - ], - ) +expect { + result = solve("AS + A == MOM") + Set.from_list(result ?? []) == Set.from_list( + [ + ('A', 9), + ('S', 2), + ('M', 1), + ('O', 0), + ], + ) +} # puzzle with six letters -expect - result = solve("NO + NO + TOO == LATE") - Result.with_default(result, []) - |> Set.from_list - == Set.from_list( - [ - ('N', 7), - ('O', 4), - ('T', 9), - ('L', 1), - ('A', 0), - ('E', 2), - ], - ) +expect { + result = solve("NO + NO + TOO == LATE") + Set.from_list(result ?? []) == Set.from_list( + [ + ('N', 7), + ('O', 4), + ('T', 9), + ('L', 1), + ('A', 0), + ('E', 2), + ], + ) +} # puzzle with seven letters -expect - result = solve("HE + SEES + THE == LIGHT") - Result.with_default(result, []) - |> Set.from_list - == Set.from_list( - [ - ('E', 4), - ('G', 2), - ('H', 5), - ('I', 0), - ('L', 1), - ('S', 9), - ('T', 7), - ], - ) +expect { + result = solve("HE + SEES + THE == LIGHT") + Set.from_list(result ?? []) == Set.from_list( + [ + ('E', 4), + ('G', 2), + ('H', 5), + ('I', 0), + ('L', 1), + ('S', 9), + ('T', 7), + ], + ) +} # puzzle with eight letters -expect - result = solve("SEND + MORE == MONEY") - Result.with_default(result, []) - |> Set.from_list - == Set.from_list( - [ - ('S', 9), - ('E', 5), - ('N', 6), - ('D', 7), - ('M', 1), - ('O', 0), - ('R', 8), - ('Y', 2), - ], - ) +expect { + result = solve("SEND + MORE == MONEY") + Set.from_list(result ?? []) == Set.from_list( + [ + ('S', 9), + ('E', 5), + ('N', 6), + ('D', 7), + ('M', 1), + ('O', 0), + ('R', 8), + ('Y', 2), + ], + ) +} # puzzle with ten letters -expect - result = solve("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE") - Result.with_default(result, []) - |> Set.from_list - == Set.from_list( - [ - ('A', 5), - ('D', 3), - ('E', 4), - ('F', 7), - ('G', 8), - ('N', 0), - ('O', 2), - ('R', 1), - ('S', 6), - ('T', 9), - ], - ) +expect { + result = solve("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE") + Set.from_list(result ?? []) == Set.from_list( + [ + ('A', 5), + ('D', 3), + ('E', 4), + ('F', 7), + ('G', 8), + ('N', 0), + ('O', 2), + ('R', 1), + ('S', 6), + ('T', 9), + ], + ) +} # puzzle with ten letters and 199 addends -expect - result = solve("THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES") - Result.with_default(result, []) - |> Set.from_list - == Set.from_list( - [ - ('A', 1), - ('E', 0), - ('F', 5), - ('H', 8), - ('I', 7), - ('L', 2), - ('O', 6), - ('R', 3), - ('S', 4), - ('T', 9), - ], - ) +expect { + result = solve("THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES") + Set.from_list(result ?? []) == Set.from_list( + [ + ('A', 1), + ('E', 0), + ('F', 5), + ('H', 8), + ('I', 7), + ('L', 2), + ('O', 6), + ('R', 3), + ('S', 4), + ('T', 9), + ], + ) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/anagram/.meta/template.j2 b/exercises/practice/anagram/.meta/template.j2 index 3af1c1ad..a19db5a1 100644 --- a/exercises/practice/anagram/.meta/template.j2 +++ b/exercises/practice/anagram/.meta/template.j2 @@ -6,8 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["subject"] | to_roc }}, {{ case["input"]["candidates"] | to_roc }}) result == {{ case["expected"] | to_roc }} - +} {% endfor %} + +{{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/anagram/anagram-test.roc b/exercises/practice/anagram/anagram-test.roc index ba392366..06873c45 100644 --- a/exercises/practice/anagram/anagram-test.roc +++ b/exercises/practice/anagram/anagram-test.roc @@ -1,100 +1,102 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json -# File last updated on 2025-09-15 +# File last updated on 2026-06-13 app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", - unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", + unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br", } import pf.Stdout -main! = |_args| - Stdout.line!("") - import Anagram exposing [find_anagrams] # no matches -expect - result = find_anagrams("diaper", ["hello", "world", "zombies", "pants"]) - result == [] - +expect { + result = find_anagrams("diaper", ["hello", "world", "zombies", "pants"]) + result == [] +} # detects two anagrams -expect - result = find_anagrams("solemn", ["lemons", "cherry", "melons"]) - result == ["lemons", "melons"] - +expect { + result = find_anagrams("solemn", ["lemons", "cherry", "melons"]) + result == ["lemons", "melons"] +} # does not detect anagram subsets -expect - result = find_anagrams("good", ["dog", "goody"]) - result == [] - +expect { + result = find_anagrams("good", ["dog", "goody"]) + result == [] +} # detects anagram -expect - result = find_anagrams("listen", ["enlists", "google", "inlets", "banana"]) - result == ["inlets"] - +expect { + result = find_anagrams("listen", ["enlists", "google", "inlets", "banana"]) + result == ["inlets"] +} # detects three anagrams -expect - result = find_anagrams("allergy", ["gallery", "ballerina", "regally", "clergy", "largely", "leading"]) - result == ["gallery", "regally", "largely"] - +expect { + result = find_anagrams("allergy", ["gallery", "ballerina", "regally", "clergy", "largely", "leading"]) + result == ["gallery", "regally", "largely"] +} # detects multiple anagrams with different case -expect - result = find_anagrams("nose", ["Eons", "ONES"]) - result == ["Eons", "ONES"] - +expect { + result = find_anagrams("nose", ["Eons", "ONES"]) + result == ["Eons", "ONES"] +} # does not detect non-anagrams with identical checksum -expect - result = find_anagrams("mass", ["last"]) - result == [] - +expect { + result = find_anagrams("mass", ["last"]) + result == [] +} # detects anagrams case-insensitively -expect - result = find_anagrams("Orchestra", ["cashregister", "Carthorse", "radishes"]) - result == ["Carthorse"] - +expect { + result = find_anagrams("Orchestra", ["cashregister", "Carthorse", "radishes"]) + result == ["Carthorse"] +} # detects anagrams using case-insensitive subject -expect - result = find_anagrams("Orchestra", ["cashregister", "carthorse", "radishes"]) - result == ["carthorse"] - +expect { + result = find_anagrams("Orchestra", ["cashregister", "carthorse", "radishes"]) + result == ["carthorse"] +} # detects anagrams using case-insensitive possible matches -expect - result = find_anagrams("orchestra", ["cashregister", "Carthorse", "radishes"]) - result == ["Carthorse"] - +expect { + result = find_anagrams("orchestra", ["cashregister", "Carthorse", "radishes"]) + result == ["Carthorse"] +} # does not detect an anagram if the original word is repeated -expect - result = find_anagrams("go", ["goGoGO"]) - result == [] - +expect { + result = find_anagrams("go", ["goGoGO"]) + result == [] +} # anagrams must use all letters exactly once -expect - result = find_anagrams("tapper", ["patter"]) - result == [] - +expect { + result = find_anagrams("tapper", ["patter"]) + result == [] +} # words are not anagrams of themselves -expect - result = find_anagrams("BANANA", ["BANANA"]) - result == [] - +expect { + result = find_anagrams("BANANA", ["BANANA"]) + result == [] +} # words are not anagrams of themselves even if letter case is partially different -expect - result = find_anagrams("BANANA", ["Banana"]) - result == [] - +expect { + result = find_anagrams("BANANA", ["Banana"]) + result == [] +} # words are not anagrams of themselves even if letter case is completely different -expect - result = find_anagrams("BANANA", ["banana"]) - result == [] - +expect { + result = find_anagrams("BANANA", ["banana"]) + result == [] +} # words other than themselves can be anagrams -expect - result = find_anagrams("LISTEN", ["LISTEN", "Silent"]) - result == ["Silent"] - +expect { + result = find_anagrams("LISTEN", ["LISTEN", "Silent"]) + result == ["Silent"] +} # different characters may have the same bytes -expect - result = find_anagrams("a⬂", ["€a"]) - result == [] +expect { + result = find_anagrams("a⬂", ["€a"]) + result == [] +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc b/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc index 84335ae8..5f8cd885 100644 --- a/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc +++ b/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json -# File last updated on 2026-06-12 +# File last updated on 2026-06-13 import ArmstrongNumbers exposing [is_armstrong_number] diff --git a/exercises/practice/atbash-cipher/atbash-cipher-test.roc b/exercises/practice/atbash-cipher/atbash-cipher-test.roc index f9566b57..e28ddab6 100644 --- a/exercises/practice/atbash-cipher/atbash-cipher-test.roc +++ b/exercises/practice/atbash-cipher/atbash-cipher-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/atbash-cipher/canonical-data.json -# File last updated on 2026-06-12 +# File last updated on 2026-06-13 import AtbashCipher exposing [encode, decode] diff --git a/exercises/practice/binary-search-tree/.meta/template.j2 b/exercises/practice/binary-search-tree/.meta/template.j2 index 82c04750..c24133f2 100644 --- a/exercises/practice/binary-search-tree/.meta/template.j2 +++ b/exercises/practice/binary-search-tree/.meta/template.j2 @@ -30,7 +30,7 @@ import {{ exercise | to_pascal }} exposing [from_list, to_list] {%- if subcase["description"] != supercase["description"] %} # {{ subcase["description"] }} {%- endif %} -expect +expect { data = {{ to_int_list(subcase["input"]["treeData"]) }} {%- if subcase["property"] == "data" %} result = from_list(data) @@ -42,6 +42,7 @@ expect expected = {{ to_int_list(subcase["expected"]) }} result == expected {%- endif %} +} {% endfor %} diff --git a/exercises/practice/binary-search-tree/binary-search-tree-test.roc b/exercises/practice/binary-search-tree/binary-search-tree-test.roc index 08e1e714..ba93ff83 100644 --- a/exercises/practice/binary-search-tree/binary-search-tree-test.roc +++ b/exercises/practice/binary-search-tree/binary-search-tree-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search-tree/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import BinarySearchTree exposing [from_list, to_list] @@ -16,172 +8,241 @@ import BinarySearchTree exposing [from_list, to_list] ## data is retained ## -expect - data = [4] - result = from_list(data) - expected = Node( - { - value: 4, - left: Nil, - right: Nil, - }, - ) - result == expected +expect { + data = [ + 4, + ] + result = from_list(data) + expected = Node( + { + value: 4, + left: Nil, + right: Nil, + }, + ) + result == expected +} ## ## insert data at proper node ## # smaller number at left node -expect - data = [4, 2] - result = from_list(data) - expected = Node( - { - value: 4, - left: Node( - { - value: 2, - left: Nil, - right: Nil, - }, - ), - right: Nil, - }, - ) - result == expected +expect { + data = [ + 4, + 2, + ] + result = from_list(data) + expected = Node( + { + value: 4, + left: Node( + { + value: 2, + left: Nil, + right: Nil, + }, + ), + right: Nil, + }, + ) + result == expected +} # same number at left node -expect - data = [4, 4] - result = from_list(data) - expected = Node( - { - value: 4, - left: Node( - { - value: 4, - left: Nil, - right: Nil, - }, - ), - right: Nil, - }, - ) - result == expected +expect { + data = [ + 4, + 4, + ] + result = from_list(data) + expected = Node( + { + value: 4, + left: Node( + { + value: 4, + left: Nil, + right: Nil, + }, + ), + right: Nil, + }, + ) + result == expected +} # greater number at right node -expect - data = [4, 5] - result = from_list(data) - expected = Node( - { - value: 4, - left: Nil, - right: Node( - { - value: 5, - left: Nil, - right: Nil, - }, - ), - }, - ) - result == expected +expect { + data = [ + 4, + 5, + ] + result = from_list(data) + expected = Node( + { + value: 4, + left: Nil, + right: Node( + { + value: 5, + left: Nil, + right: Nil, + }, + ), + }, + ) + result == expected +} ## ## can create complex tree ## -expect - data = [4, 2, 6, 1, 3, 5, 7] - result = from_list(data) - expected = Node( - { - value: 4, - left: Node( - { - value: 2, - left: Node( - { - value: 1, - left: Nil, - right: Nil, - }, - ), - right: Node( - { - value: 3, - left: Nil, - right: Nil, - }, - ), - }, - ), - right: Node( - { - value: 6, - left: Node( - { - value: 5, - left: Nil, - right: Nil, - }, - ), - right: Node( - { - value: 7, - left: Nil, - right: Nil, - }, - ), - }, - ), - }, - ) - result == expected +expect { + data = [ + 4, + 2, + 6, + 1, + 3, + 5, + 7, + ] + result = from_list(data) + expected = Node( + { + value: 4, + left: Node( + { + value: 2, + left: Node( + { + value: 1, + left: Nil, + right: Nil, + }, + ), + right: Node( + { + value: 3, + left: Nil, + right: Nil, + }, + ), + }, + ), + right: Node( + { + value: 6, + left: Node( + { + value: 5, + left: Nil, + right: Nil, + }, + ), + right: Node( + { + value: 7, + left: Nil, + right: Nil, + }, + ), + }, + ), + }, + ) + result == expected +} ## ## can sort data ## # can sort single number -expect - data = [2] - tree = from_list(data) - result = to_list(tree) - expected = [2] - result == expected +expect { + data = [ + 2, + ] + tree = from_list(data) + result = to_list(tree) + expected = [ + 2, + ] + result == expected +} # can sort if second number is smaller than first -expect - data = [2, 1] - tree = from_list(data) - result = to_list(tree) - expected = [1, 2] - result == expected +expect { + data = [ + 2, + 1, + ] + tree = from_list(data) + result = to_list(tree) + expected = [ + 1, + 2, + ] + result == expected +} # can sort if second number is same as first -expect - data = [2, 2] - tree = from_list(data) - result = to_list(tree) - expected = [2, 2] - result == expected +expect { + data = [ + 2, + 2, + ] + tree = from_list(data) + result = to_list(tree) + expected = [ + 2, + 2, + ] + result == expected +} # can sort if second number is greater than first -expect - data = [2, 3] - tree = from_list(data) - result = to_list(tree) - expected = [2, 3] - result == expected +expect { + data = [ + 2, + 3, + ] + tree = from_list(data) + result = to_list(tree) + expected = [ + 2, + 3, + ] + result == expected +} # can sort complex tree -expect - data = [2, 1, 3, 6, 7, 5] - tree = from_list(data) - result = to_list(tree) - expected = [1, 2, 3, 5, 6, 7] - result == expected +expect { + data = [ + 2, + 1, + 3, + 6, + 7, + 5, + ] + tree = from_list(data) + result = to_list(tree) + expected = [ + 1, + 2, + 3, + 5, + 6, + 7, + ] + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/binary-search/.meta/template.j2 b/exercises/practice/binary-search/.meta/template.j2 index 3fe1a8f8..63a128e7 100644 --- a/exercises/practice/binary-search/.meta/template.j2 +++ b/exercises/practice/binary-search/.meta/template.j2 @@ -6,13 +6,14 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect - result = {{ case["input"]["array"] | to_roc }} |> {{ case["property"] | to_snake }}({{ case["input"]["value"] }}) +expect { + result = {{ case["input"]["array"] | to_roc }} -> {{ case["property"] | to_snake }}({{ case["input"]["value"] }}) {%- if case["expected"]["error"] %} - Result.is_err(result) + result.is_err() {%- else %} result == Ok({{ case["expected"] }}) {%- endif %} +} {% endfor %} diff --git a/exercises/practice/binary-search/binary-search-test.roc b/exercises/practice/binary-search/binary-search-test.roc index 9da25f04..3a5231f1 100644 --- a/exercises/practice/binary-search/binary-search-test.roc +++ b/exercises/practice/binary-search/binary-search-test.roc @@ -1,69 +1,76 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import BinarySearch exposing [find] # finds a value in an array with one element -expect - result = [6] |> find(6) - result == Ok(0) +expect { + result = [6]->find(6) + result == Ok(0) +} # finds a value in the middle of an array -expect - result = [1, 3, 4, 6, 8, 9, 11] |> find(6) - result == Ok(3) +expect { + result = [1, 3, 4, 6, 8, 9, 11]->find(6) + result == Ok(3) +} # finds a value at the beginning of an array -expect - result = [1, 3, 4, 6, 8, 9, 11] |> find(1) - result == Ok(0) +expect { + result = [1, 3, 4, 6, 8, 9, 11]->find(1) + result == Ok(0) +} # finds a value at the end of an array -expect - result = [1, 3, 4, 6, 8, 9, 11] |> find(11) - result == Ok(6) +expect { + result = [1, 3, 4, 6, 8, 9, 11]->find(11) + result == Ok(6) +} # finds a value in an array of odd length -expect - result = [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634] |> find(144) - result == Ok(9) +expect { + result = [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634]->find(144) + result == Ok(9) +} # finds a value in an array of even length -expect - result = [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377] |> find(21) - result == Ok(5) +expect { + result = [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]->find(21) + result == Ok(5) +} # identifies that a value is not included in the array -expect - result = [1, 3, 4, 6, 8, 9, 11] |> find(7) - Result.is_err(result) +expect { + result = [1, 3, 4, 6, 8, 9, 11]->find(7) + result.is_err() +} # a value smaller than the array's smallest value is not found -expect - result = [1, 3, 4, 6, 8, 9, 11] |> find(0) - Result.is_err(result) +expect { + result = [1, 3, 4, 6, 8, 9, 11]->find(0) + result.is_err() +} # a value larger than the array's largest value is not found -expect - result = [1, 3, 4, 6, 8, 9, 11] |> find(13) - Result.is_err(result) +expect { + result = [1, 3, 4, 6, 8, 9, 11]->find(13) + result.is_err() +} # nothing is found in an empty array -expect - result = [] |> find(1) - Result.is_err(result) +expect { + result = []->find(1) + result.is_err() +} # nothing is found when the left and right bounds cross -expect - result = [1, 2] |> find(0) - Result.is_err(result) +expect { + result = [1, 2]->find(0) + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/binary/binary-test.roc b/exercises/practice/binary/binary-test.roc index 9a28bbae..cea81d5a 100644 --- a/exercises/practice/binary/binary-test.roc +++ b/exercises/practice/binary/binary-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary/canonical-data.json -# File last updated on 2026-06-12 +# File last updated on 2026-06-13 import Binary exposing [decimal] diff --git a/exercises/practice/bob/.meta/template.j2 b/exercises/practice/bob/.meta/template.j2 index 2cf3e9e0..d9f72605 100644 --- a/exercises/practice/bob/.meta/template.j2 +++ b/exercises/practice/bob/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [response] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["heyBob"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/bob/bob-test.roc b/exercises/practice/bob/bob-test.roc index d8da4762..6c366073 100644 --- a/exercises/practice/bob/bob-test.roc +++ b/exercises/practice/bob/bob-test.roc @@ -1,139 +1,160 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bob/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Bob exposing [response] # stating something -expect - result = response("Tom-ay-to, tom-aaaah-to.") - result == "Whatever." +expect { + result = response("Tom-ay-to, tom-aaaah-to.") + result == "Whatever." +} # shouting -expect - result = response("WATCH OUT!") - result == "Whoa, chill out!" +expect { + result = response("WATCH OUT!") + result == "Whoa, chill out!" +} # shouting gibberish -expect - result = response("FCECDFCAAB") - result == "Whoa, chill out!" +expect { + result = response("FCECDFCAAB") + result == "Whoa, chill out!" +} # asking a question -expect - result = response("Does this cryogenic chamber make me look fat?") - result == "Sure." +expect { + result = response("Does this cryogenic chamber make me look fat?") + result == "Sure." +} # asking a numeric question -expect - result = response("You are, what, like 15?") - result == "Sure." +expect { + result = response("You are, what, like 15?") + result == "Sure." +} # asking gibberish -expect - result = response("fffbbcbeab?") - result == "Sure." +expect { + result = response("fffbbcbeab?") + result == "Sure." +} # talking forcefully -expect - result = response("Hi there!") - result == "Whatever." +expect { + result = response("Hi there!") + result == "Whatever." +} # using acronyms in regular speech -expect - result = response("It's OK if you don't want to go work for NASA.") - result == "Whatever." +expect { + result = response("It's OK if you don't want to go work for NASA.") + result == "Whatever." +} # forceful question -expect - result = response("WHAT'S GOING ON?") - result == "Calm down, I know what I'm doing!" +expect { + result = response("WHAT'S GOING ON?") + result == "Calm down, I know what I'm doing!" +} # shouting numbers -expect - result = response("1, 2, 3 GO!") - result == "Whoa, chill out!" +expect { + result = response("1, 2, 3 GO!") + result == "Whoa, chill out!" +} # no letters -expect - result = response("1, 2, 3") - result == "Whatever." +expect { + result = response("1, 2, 3") + result == "Whatever." +} # question with no letters -expect - result = response("4?") - result == "Sure." +expect { + result = response("4?") + result == "Sure." +} # shouting with special characters -expect - result = response("ZOMG THE %^*@#\$(*^ ZOMBIES ARE COMING!!11!!1!") - result == "Whoa, chill out!" +expect { + result = response("ZOMG THE %^*@#\$(*^ ZOMBIES ARE COMING!!11!!1!") + result == "Whoa, chill out!" +} # shouting with no exclamation mark -expect - result = response("I HATE THE DENTIST") - result == "Whoa, chill out!" +expect { + result = response("I HATE THE DENTIST") + result == "Whoa, chill out!" +} # statement containing question mark -expect - result = response("Ending with ? means a question.") - result == "Whatever." +expect { + result = response("Ending with ? means a question.") + result == "Whatever." +} # non-letters with question -expect - result = response(":) ?") - result == "Sure." +expect { + result = response(":) ?") + result == "Sure." +} # prattling on -expect - result = response("Wait! Hang on. Are you going to be OK?") - result == "Sure." +expect { + result = response("Wait! Hang on. Are you going to be OK?") + result == "Sure." +} # silence -expect - result = response("") - result == "Fine. Be that way!" +expect { + result = response("") + result == "Fine. Be that way!" +} # prolonged silence -expect - result = response(" ") - result == "Fine. Be that way!" +expect { + result = response(" ") + result == "Fine. Be that way!" +} # alternate silence -expect - result = response("\t\t\t\t\t\t\t\t\t\t") - result == "Fine. Be that way!" +expect { + result = response("\t\t\t\t\t\t\t\t\t\t") + result == "Fine. Be that way!" +} # starting with whitespace -expect - result = response(" hmmmmmmm...") - result == "Whatever." +expect { + result = response(" hmmmmmmm...") + result == "Whatever." +} # ending with whitespace -expect - result = response("Okay if like my spacebar quite a bit? ") - result == "Sure." +expect { + result = response("Okay if like my spacebar quite a bit? ") + result == "Sure." +} # other whitespace -expect - result = response("\n\r \t") - result == "Fine. Be that way!" +expect { + result = response("\n\r \t") + result == "Fine. Be that way!" +} # non-question ending with whitespace -expect - result = response("This is a statement ending with whitespace ") - result == "Whatever." +expect { + result = response("This is a statement ending with whitespace ") + result == "Whatever." +} # multiple line question -expect - result = response("\nDoes this cryogenic chamber make\n me look fat?") - result == "Sure." +expect { + result = response("\nDoes this cryogenic chamber make\n me look fat?") + result == "Sure." +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/bowling/.meta/template.j2 b/exercises/practice/bowling/.meta/template.j2 index 8f1244b9..c6a91368 100644 --- a/exercises/practice/bowling/.meta/template.j2 +++ b/exercises/practice/bowling/.meta/template.j2 @@ -4,45 +4,52 @@ import {{ exercise | to_pascal }} exposing [Game, create, roll, score] -replay_game : List U64 -> Result Game _ -replay_game = |rolls| +replay_game : List(U64) -> Try(Game, _) +replay_game = |rolls| { new_game = create({})? rolls - |> List.walk_until( + -> walk_until( Ok(new_game), - |state, pins| - when state is - Ok(game) -> - when game |> roll(pins) is - Ok(updated_game) -> Continue(Ok(updated_game)) - Err(err) -> Break(Err(err)) - - Err(_) -> crash "Impossible, we don't start or Continue with an Err" + |state, pins| { + match state { + Ok(game) => { + match game -> roll(pins) { + Ok(updated_game) => Continue(Ok(updated_game)) + Err(err) => Break(Err(err)) + } + } + Err(_) => { crash "Impossible, we don't start or Continue with an Err" } + } + } ) +} {% for case in cases -%} # {{ case["description"] }} -expect +expect { maybe_game = create({ previous_rolls : {{ case["input"]["previousRolls"] | to_roc }} }) {%- if case["property"] == "score" %} - result = maybe_game |> Result.try(|game| score(game)) + result = maybe_game -> Result.try(|game| score(game)) {%- else %} - result = maybe_game |> Result.try(|game| - game |> {{ case["property"] | to_snake }}({{ case["input"]["roll"] }})) + result = maybe_game -> Result.try(|game| { + game -> {{ case["property"] | to_snake }}({{ case["input"]["roll"] }}) + }) {%- endif %} {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} result == Ok({{ case["expected"] | to_roc }}) {%- endif %} +} {%- if case["property"] == "score" and not case["expected"]["error"] %} # should be able to replay this finished game from the start -expect +expect { rolls = {{ case["input"]["previousRolls"] | to_roc }} result = replay_game(rolls) - result |> Result.is_ok + result.is_ok() +} {%- endif %} diff --git a/exercises/practice/bowling/bowling-test.roc b/exercises/practice/bowling/bowling-test.roc index abf9a29b..b6f345a0 100644 --- a/exercises/practice/bowling/bowling-test.roc +++ b/exercises/practice/bowling/bowling-test.roc @@ -1,351 +1,390 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bowling/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Bowling exposing [Game, create, roll, score] -replay_game : List U64 -> Result Game _ -replay_game = |rolls| - new_game = create({})? - rolls - |> List.walk_until( - Ok(new_game), - |state, pins| - when state is - Ok(game) -> - when game |> roll(pins) is - Ok(updated_game) -> Continue(Ok(updated_game)) - Err(err) -> Break(Err(err)) - - Err(_) -> crash "Impossible, we don't start or Continue with an Err", - ) +replay_game : List(U64) -> Try(Game, _) +replay_game = |rolls| { + new_game = create({})? + rolls + ->walk_until( + Ok(new_game), + |state, pins| { + match state { + Ok(game) => { + match game->roll(pins) { + Ok(updated_game) => Continue(Ok(updated_game)) + Err(err) => Break(Err(err)) + } + } + Err(_) => { + crash "Impossible, we don't start or Continue with an Err" + } + } + }, + ) +} # should be able to score a game with all zeros -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(0) +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(0) +} # should be able to replay this finished game from the start -expect - rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + result = replay_game(rolls) + result.is_ok() +} # should be able to score a game with no strikes or spares -expect - maybe_game = create({ previous_rolls: [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(90) +expect { + maybe_game = create({ previous_rolls: [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(90) +} # should be able to replay this finished game from the start -expect - rolls = [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6] + result = replay_game(rolls) + result.is_ok() +} # a spare followed by zeros is worth ten points -expect - maybe_game = create({ previous_rolls: [6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(10) +expect { + maybe_game = create({ previous_rolls: [6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(10) +} # should be able to replay this finished game from the start -expect - rolls = [6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + result = replay_game(rolls) + result.is_ok() +} # points scored in the roll after a spare are counted twice -expect - maybe_game = create({ previous_rolls: [6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(16) +expect { + maybe_game = create({ previous_rolls: [6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(16) +} # should be able to replay this finished game from the start -expect - rolls = [6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + result = replay_game(rolls) + result.is_ok() +} # consecutive spares each get a one roll bonus -expect - maybe_game = create({ previous_rolls: [5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(31) +expect { + maybe_game = create({ previous_rolls: [5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(31) +} # should be able to replay this finished game from the start -expect - rolls = [5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + result = replay_game(rolls) + result.is_ok() +} # a spare in the last frame gets a one roll bonus that is counted once -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(17) +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(17) +} # should be able to replay this finished game from the start -expect - rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7] + result = replay_game(rolls) + result.is_ok() +} # a strike earns ten points in a frame with a single roll -expect - maybe_game = create({ previous_rolls: [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(10) +expect { + maybe_game = create({ previous_rolls: [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(10) +} # should be able to replay this finished game from the start -expect - rolls = [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + result = replay_game(rolls) + result.is_ok() +} # points scored in the two rolls after a strike are counted twice as a bonus -expect - maybe_game = create({ previous_rolls: [10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(26) +expect { + maybe_game = create({ previous_rolls: [10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(26) +} # should be able to replay this finished game from the start -expect - rolls = [10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + result = replay_game(rolls) + result.is_ok() +} # consecutive strikes each get the two roll bonus -expect - maybe_game = create({ previous_rolls: [10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(81) +expect { + maybe_game = create({ previous_rolls: [10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(81) +} # should be able to replay this finished game from the start -expect - rolls = [10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + result = replay_game(rolls) + result.is_ok() +} # a strike in the last frame gets a two roll bonus that is counted once -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(18) +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(18) +} # should be able to replay this finished game from the start -expect - rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1] + result = replay_game(rolls) + result.is_ok() +} # rolling a spare with the two roll bonus does not get a bonus roll -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(20) +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(20) +} # should be able to replay this finished game from the start -expect - rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3] + result = replay_game(rolls) + result.is_ok() +} # strikes with the two roll bonus do not get bonus rolls -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(30) +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(30) +} # should be able to replay this finished game from the start -expect - rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10] + result = replay_game(rolls) + result.is_ok() +} # last two strikes followed by only last bonus with non strike points -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(31) +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(31) +} # should be able to replay this finished game from the start -expect - rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1] + result = replay_game(rolls) + result.is_ok() +} # a strike with the one roll bonus after a spare in the last frame does not get a bonus -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(20) +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(20) +} # should be able to replay this finished game from the start -expect - rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10] + result = replay_game(rolls) + result.is_ok() +} # all strikes is a perfect game -expect - maybe_game = create({ previous_rolls: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(300) +expect { + maybe_game = create({ previous_rolls: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(300) +} # should be able to replay this finished game from the start -expect - rolls = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] + result = replay_game(rolls) + result.is_ok() +} # a roll cannot score more than 10 points -expect - maybe_game = create({ previous_rolls: [] }) - result = - maybe_game - |> Result.try( - |game| - game |> roll(11), - ) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [] }) + result = maybe_game->Result.try( + |game| { + game->roll(11) + }, + ) + result.is_err() +} # two rolls in a frame cannot score more than 10 points -expect - maybe_game = create({ previous_rolls: [5] }) - result = - maybe_game - |> Result.try( - |game| - game |> roll(6), - ) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [5] }) + result = maybe_game->Result.try( + |game| { + game->roll(6) + }, + ) + result.is_err() +} # bonus roll after a strike in the last frame cannot score more than 10 points -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] }) - result = - maybe_game - |> Result.try( - |game| - game |> roll(11), - ) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] }) + result = maybe_game->Result.try( + |game| { + game->roll(11) + }, + ) + result.is_err() +} # two bonus rolls after a strike in the last frame cannot score more than 10 points -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 5] }) - result = - maybe_game - |> Result.try( - |game| - game |> roll(6), - ) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 5] }) + result = maybe_game->Result.try( + |game| { + game->roll(6) + }, + ) + result.is_err() +} # two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 6] }) - result = maybe_game |> Result.try(|game| score(game)) - result == Ok(26) +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 6] }) + result = maybe_game->Result.try(|game| score(game)) + result == Ok(26) +} # should be able to replay this finished game from the start -expect - rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 6] - result = replay_game(rolls) - result |> Result.is_ok +expect { + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 6] + result = replay_game(rolls) + result.is_ok() +} # the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 6] }) - result = - maybe_game - |> Result.try( - |game| - game |> roll(10), - ) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 6] }) + result = maybe_game->Result.try( + |game| { + game->roll(10) + }, + ) + result.is_err() +} # second bonus roll after a strike in the last frame cannot score more than 10 points -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10] }) - result = - maybe_game - |> Result.try( - |game| - game |> roll(11), - ) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10] }) + result = maybe_game->Result.try( + |game| { + game->roll(11) + }, + ) + result.is_err() +} # an unstarted game cannot be scored -expect - maybe_game = create({ previous_rolls: [] }) - result = maybe_game |> Result.try(|game| score(game)) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [] }) + result = maybe_game->Result.try(|game| score(game)) + result.is_err() +} # an incomplete game cannot be scored -expect - maybe_game = create({ previous_rolls: [0, 0] }) - result = maybe_game |> Result.try(|game| score(game)) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0] }) + result = maybe_game->Result.try(|game| score(game)) + result.is_err() +} # cannot roll if game already has ten frames -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = - maybe_game - |> Result.try( - |game| - game |> roll(0), - ) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) + result = maybe_game->Result.try( + |game| { + game->roll(0) + }, + ) + result.is_err() +} # bonus rolls for a strike in the last frame must be rolled before score can be calculated -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] }) - result = maybe_game |> Result.try(|game| score(game)) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] }) + result = maybe_game->Result.try(|game| score(game)) + result.is_err() +} # both bonus rolls for a strike in the last frame must be rolled before score can be calculated -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10] }) - result = maybe_game |> Result.try(|game| score(game)) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10] }) + result = maybe_game->Result.try(|game| score(game)) + result.is_err() +} # bonus roll for a spare in the last frame must be rolled before score can be calculated -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3] }) - result = maybe_game |> Result.try(|game| score(game)) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3] }) + result = maybe_game->Result.try(|game| score(game)) + result.is_err() +} # cannot roll after bonus roll for spare -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 2] }) - result = - maybe_game - |> Result.try( - |game| - game |> roll(2), - ) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 2] }) + result = maybe_game->Result.try( + |game| { + game->roll(2) + }, + ) + result.is_err() +} # cannot roll after bonus rolls for strike -expect - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 3, 2] }) - result = - maybe_game - |> Result.try( - |game| - game |> roll(2), - ) - result |> Result.is_err +expect { + maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 3, 2] }) + result = maybe_game->Result.try( + |game| { + game->roll(2) + }, + ) + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/change/.meta/template.j2 b/exercises/practice/change/.meta/template.j2 index f698fed9..54a272af 100644 --- a/exercises/practice/change/.meta/template.j2 +++ b/exercises/practice/change/.meta/template.j2 @@ -6,14 +6,15 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { coins = {{ case["input"]["coins"] | to_roc }} - result = coins |> {{ case["property"] | to_snake }}({{ case["input"]["target"] }}) + result = coins -> {{ case["property"] | to_snake }}({{ case["input"]["target"] }}) {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} result == Ok({{ case["expected"] | to_roc }}) {%- endif %} +} {% endfor %} diff --git a/exercises/practice/change/change-test.roc b/exercises/practice/change/change-test.roc index 4a5d2576..0bbdea25 100644 --- a/exercises/practice/change/change-test.roc +++ b/exercises/practice/change/change-test.roc @@ -1,86 +1,94 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Change exposing [find_fewest_coins] # change for 1 cent -expect - coins = [1, 5, 10, 25] - result = coins |> find_fewest_coins(1) - result == Ok([1]) +expect { + coins = [1, 5, 10, 25] + result = coins->find_fewest_coins(1) + result == Ok([1]) +} # single coin change -expect - coins = [1, 5, 10, 25, 100] - result = coins |> find_fewest_coins(25) - result == Ok([25]) +expect { + coins = [1, 5, 10, 25, 100] + result = coins->find_fewest_coins(25) + result == Ok([25]) +} # multiple coin change -expect - coins = [1, 5, 10, 25, 100] - result = coins |> find_fewest_coins(15) - result == Ok([5, 10]) +expect { + coins = [1, 5, 10, 25, 100] + result = coins->find_fewest_coins(15) + result == Ok([5, 10]) +} # change with Lilliputian Coins -expect - coins = [1, 4, 15, 20, 50] - result = coins |> find_fewest_coins(23) - result == Ok([4, 4, 15]) +expect { + coins = [1, 4, 15, 20, 50] + result = coins->find_fewest_coins(23) + result == Ok([4, 4, 15]) +} # change with Lower Elbonia Coins -expect - coins = [1, 5, 10, 21, 25] - result = coins |> find_fewest_coins(63) - result == Ok([21, 21, 21]) +expect { + coins = [1, 5, 10, 21, 25] + result = coins->find_fewest_coins(63) + result == Ok([21, 21, 21]) +} # large target values -expect - coins = [1, 2, 5, 10, 20, 50, 100] - result = coins |> find_fewest_coins(999) - result == Ok([2, 2, 5, 20, 20, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100]) +expect { + coins = [1, 2, 5, 10, 20, 50, 100] + result = coins->find_fewest_coins(999) + result == Ok([2, 2, 5, 20, 20, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100]) +} # possible change without unit coins available -expect - coins = [2, 5, 10, 20, 50] - result = coins |> find_fewest_coins(21) - result == Ok([2, 2, 2, 5, 10]) +expect { + coins = [2, 5, 10, 20, 50] + result = coins->find_fewest_coins(21) + result == Ok([2, 2, 2, 5, 10]) +} # another possible change without unit coins available -expect - coins = [4, 5] - result = coins |> find_fewest_coins(27) - result == Ok([4, 4, 4, 5, 5, 5]) +expect { + coins = [4, 5] + result = coins->find_fewest_coins(27) + result == Ok([4, 4, 4, 5, 5, 5]) +} # a greedy approach is not optimal -expect - coins = [1, 10, 11] - result = coins |> find_fewest_coins(20) - result == Ok([10, 10]) +expect { + coins = [1, 10, 11] + result = coins->find_fewest_coins(20) + result == Ok([10, 10]) +} # no coins make 0 change -expect - coins = [1, 5, 10, 21, 25] - result = coins |> find_fewest_coins(0) - result == Ok([]) +expect { + coins = [1, 5, 10, 21, 25] + result = coins->find_fewest_coins(0) + result == Ok([]) +} # error testing for change smaller than the smallest of coins -expect - coins = [5, 10] - result = coins |> find_fewest_coins(3) - result |> Result.is_err +expect { + coins = [5, 10] + result = coins->find_fewest_coins(3) + result.is_err() +} # error if no combination can add up to target -expect - coins = [5, 10] - result = coins |> find_fewest_coins(94) - result |> Result.is_err +expect { + coins = [5, 10] + result = coins->find_fewest_coins(94) + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/circular-buffer/circular-buffer-test.roc b/exercises/practice/circular-buffer/circular-buffer-test.roc index 79b13202..c4cbf21a 100644 --- a/exercises/practice/circular-buffer/circular-buffer-test.roc +++ b/exercises/practice/circular-buffer/circular-buffer-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/circular-buffer/canonical-data.json -# File last updated on 2026-06-12 +# File last updated on 2026-06-13 import CircularBuffer diff --git a/exercises/practice/clock/.meta/template.j2 b/exercises/practice/clock/.meta/template.j2 index 61623fd9..130a425b 100644 --- a/exercises/practice/clock/.meta/template.j2 +++ b/exercises/practice/clock/.meta/template.j2 @@ -8,22 +8,25 @@ import {{ exercise | to_pascal }} exposing [create, add, subtract, to_str] # {{ case["description"] }} {%- if case["property"] == "create" %} -expect +expect { clock = create({{ plugins.to_hours_minutes_record(case["input"]) }}) - result = clock |> to_str + result = clock.to_str() expected = {{ case["expected"] | to_roc }} result == expected +} {%- elif case["property"] in ["add", "subtract"] %} -expect +expect { clock = create({{ plugins.to_hours_minutes_record(case["input"]) }}) - result = clock |> {{ case["property"] | to_snake }}({ minutes: {{ case["input"]["value"] }} }) |> to_str + result = clock.{{ case["property"] | to_snake }}({ minutes: {{ case["input"]["value"] }} }).to_str() expected = {{ case["expected"] | to_roc }} result == expected +} {%- elif case["property"] == "equal" %} -expect +expect { clock1 = create({{ plugins.to_hours_minutes_record(case["input"]["clock1"]) }}) clock2 = create({{ plugins.to_hours_minutes_record(case["input"]["clock2"]) }}) clock1 {%- if case["expected"] %} == {%- else %} != {% endif %} clock2 +} {%- else %} # this test case is not implemented yet. Perhaps you can give it a try? diff --git a/exercises/practice/clock/clock-test.roc b/exercises/practice/clock/clock-test.roc index 4630af40..558f9af4 100644 --- a/exercises/practice/clock/clock-test.roc +++ b/exercises/practice/clock/clock-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/clock/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Clock exposing [create, add, subtract, to_str] @@ -17,408 +9,470 @@ import Clock exposing [create, add, subtract, to_str] ## # on the hour -expect - clock = create({ hours: 8 }) - result = clock |> to_str - expected = "08:00" - result == expected +expect { + clock = create({ hours: 8 }) + result = clock.to_str() + expected = "08:00" + result == expected +} # past the hour -expect - clock = create({ hours: 11, minutes: 9 }) - result = clock |> to_str - expected = "11:09" - result == expected +expect { + clock = create({ hours: 11, minutes: 9 }) + result = clock.to_str() + expected = "11:09" + result == expected +} # midnight is zero hours -expect - clock = create({ hours: 24 }) - result = clock |> to_str - expected = "00:00" - result == expected +expect { + clock = create({ hours: 24 }) + result = clock.to_str() + expected = "00:00" + result == expected +} # hour rolls over -expect - clock = create({ hours: 25 }) - result = clock |> to_str - expected = "01:00" - result == expected +expect { + clock = create({ hours: 25 }) + result = clock.to_str() + expected = "01:00" + result == expected +} # hour rolls over continuously -expect - clock = create({ hours: 100 }) - result = clock |> to_str - expected = "04:00" - result == expected +expect { + clock = create({ hours: 100 }) + result = clock.to_str() + expected = "04:00" + result == expected +} # sixty minutes is next hour -expect - clock = create({ hours: 1, minutes: 60 }) - result = clock |> to_str - expected = "02:00" - result == expected +expect { + clock = create({ hours: 1, minutes: 60 }) + result = clock.to_str() + expected = "02:00" + result == expected +} # minutes roll over -expect - clock = create({ minutes: 160 }) - result = clock |> to_str - expected = "02:40" - result == expected +expect { + clock = create({ minutes: 160 }) + result = clock.to_str() + expected = "02:40" + result == expected +} # minutes roll over continuously -expect - clock = create({ minutes: 1723 }) - result = clock |> to_str - expected = "04:43" - result == expected +expect { + clock = create({ minutes: 1723 }) + result = clock.to_str() + expected = "04:43" + result == expected +} # hour and minutes roll over -expect - clock = create({ hours: 25, minutes: 160 }) - result = clock |> to_str - expected = "03:40" - result == expected +expect { + clock = create({ hours: 25, minutes: 160 }) + result = clock.to_str() + expected = "03:40" + result == expected +} # hour and minutes roll over continuously -expect - clock = create({ hours: 201, minutes: 3001 }) - result = clock |> to_str - expected = "11:01" - result == expected +expect { + clock = create({ hours: 201, minutes: 3001 }) + result = clock.to_str() + expected = "11:01" + result == expected +} # hour and minutes roll over to exactly midnight -expect - clock = create({ hours: 72, minutes: 8640 }) - result = clock |> to_str - expected = "00:00" - result == expected +expect { + clock = create({ hours: 72, minutes: 8640 }) + result = clock.to_str() + expected = "00:00" + result == expected +} # negative hour -expect - clock = create({ hours: -1, minutes: 15 }) - result = clock |> to_str - expected = "23:15" - result == expected +expect { + clock = create({ hours: -1, minutes: 15 }) + result = clock.to_str() + expected = "23:15" + result == expected +} # negative hour rolls over -expect - clock = create({ hours: -25 }) - result = clock |> to_str - expected = "23:00" - result == expected +expect { + clock = create({ hours: -25 }) + result = clock.to_str() + expected = "23:00" + result == expected +} # negative hour rolls over continuously -expect - clock = create({ hours: -91 }) - result = clock |> to_str - expected = "05:00" - result == expected +expect { + clock = create({ hours: -91 }) + result = clock.to_str() + expected = "05:00" + result == expected +} # negative minutes -expect - clock = create({ hours: 1, minutes: -40 }) - result = clock |> to_str - expected = "00:20" - result == expected +expect { + clock = create({ hours: 1, minutes: -40 }) + result = clock.to_str() + expected = "00:20" + result == expected +} # negative minutes roll over -expect - clock = create({ hours: 1, minutes: -160 }) - result = clock |> to_str - expected = "22:20" - result == expected +expect { + clock = create({ hours: 1, minutes: -160 }) + result = clock.to_str() + expected = "22:20" + result == expected +} # negative minutes roll over continuously -expect - clock = create({ hours: 1, minutes: -4820 }) - result = clock |> to_str - expected = "16:40" - result == expected +expect { + clock = create({ hours: 1, minutes: -4820 }) + result = clock.to_str() + expected = "16:40" + result == expected +} # negative sixty minutes is previous hour -expect - clock = create({ hours: 2, minutes: -60 }) - result = clock |> to_str - expected = "01:00" - result == expected +expect { + clock = create({ hours: 2, minutes: -60 }) + result = clock.to_str() + expected = "01:00" + result == expected +} # negative hour and minutes both roll over -expect - clock = create({ hours: -25, minutes: -160 }) - result = clock |> to_str - expected = "20:20" - result == expected +expect { + clock = create({ hours: -25, minutes: -160 }) + result = clock.to_str() + expected = "20:20" + result == expected +} # negative hour and minutes both roll over continuously -expect - clock = create({ hours: -121, minutes: -5810 }) - result = clock |> to_str - expected = "22:10" - result == expected +expect { + clock = create({ hours: -121, minutes: -5810 }) + result = clock.to_str() + expected = "22:10" + result == expected +} ## ## Add minutes ## # add minutes -expect - clock = create({ hours: 10 }) - result = clock |> add({ minutes: 3 }) |> to_str - expected = "10:03" - result == expected +expect { + clock = create({ hours: 10 }) + result = clock.add({ minutes: 3 }).to_str() + expected = "10:03" + result == expected +} # add no minutes -expect - clock = create({ hours: 6, minutes: 41 }) - result = clock |> add({ minutes: 0 }) |> to_str - expected = "06:41" - result == expected +expect { + clock = create({ hours: 6, minutes: 41 }) + result = clock.add({ minutes: 0 }).to_str() + expected = "06:41" + result == expected +} # add to next hour -expect - clock = create({ minutes: 45 }) - result = clock |> add({ minutes: 40 }) |> to_str - expected = "01:25" - result == expected +expect { + clock = create({ minutes: 45 }) + result = clock.add({ minutes: 40 }).to_str() + expected = "01:25" + result == expected +} # add more than one hour -expect - clock = create({ hours: 10 }) - result = clock |> add({ minutes: 61 }) |> to_str - expected = "11:01" - result == expected +expect { + clock = create({ hours: 10 }) + result = clock.add({ minutes: 61 }).to_str() + expected = "11:01" + result == expected +} # add more than two hours with carry -expect - clock = create({ minutes: 45 }) - result = clock |> add({ minutes: 160 }) |> to_str - expected = "03:25" - result == expected +expect { + clock = create({ minutes: 45 }) + result = clock.add({ minutes: 160 }).to_str() + expected = "03:25" + result == expected +} # add across midnight -expect - clock = create({ hours: 23, minutes: 59 }) - result = clock |> add({ minutes: 2 }) |> to_str - expected = "00:01" - result == expected +expect { + clock = create({ hours: 23, minutes: 59 }) + result = clock.add({ minutes: 2 }).to_str() + expected = "00:01" + result == expected +} # add more than one day (1500 min = 25 hrs) -expect - clock = create({ hours: 5, minutes: 32 }) - result = clock |> add({ minutes: 1500 }) |> to_str - expected = "06:32" - result == expected +expect { + clock = create({ hours: 5, minutes: 32 }) + result = clock.add({ minutes: 1500 }).to_str() + expected = "06:32" + result == expected +} # add more than two days -expect - clock = create({ hours: 1, minutes: 1 }) - result = clock |> add({ minutes: 3500 }) |> to_str - expected = "11:21" - result == expected +expect { + clock = create({ hours: 1, minutes: 1 }) + result = clock.add({ minutes: 3500 }).to_str() + expected = "11:21" + result == expected +} ## ## Subtract minutes ## # subtract minutes -expect - clock = create({ hours: 10, minutes: 3 }) - result = clock |> subtract({ minutes: 3 }) |> to_str - expected = "10:00" - result == expected +expect { + clock = create({ hours: 10, minutes: 3 }) + result = clock.subtract({ minutes: 3 }).to_str() + expected = "10:00" + result == expected +} # subtract to previous hour -expect - clock = create({ hours: 10, minutes: 3 }) - result = clock |> subtract({ minutes: 30 }) |> to_str - expected = "09:33" - result == expected +expect { + clock = create({ hours: 10, minutes: 3 }) + result = clock.subtract({ minutes: 30 }).to_str() + expected = "09:33" + result == expected +} # subtract more than an hour -expect - clock = create({ hours: 10, minutes: 3 }) - result = clock |> subtract({ minutes: 70 }) |> to_str - expected = "08:53" - result == expected +expect { + clock = create({ hours: 10, minutes: 3 }) + result = clock.subtract({ minutes: 70 }).to_str() + expected = "08:53" + result == expected +} # subtract across midnight -expect - clock = create({ minutes: 3 }) - result = clock |> subtract({ minutes: 4 }) |> to_str - expected = "23:59" - result == expected +expect { + clock = create({ minutes: 3 }) + result = clock.subtract({ minutes: 4 }).to_str() + expected = "23:59" + result == expected +} # subtract more than two hours -expect - clock = create({}) - result = clock |> subtract({ minutes: 160 }) |> to_str - expected = "21:20" - result == expected +expect { + clock = create({}) + result = clock.subtract({ minutes: 160 }).to_str() + expected = "21:20" + result == expected +} # subtract more than two hours with borrow -expect - clock = create({ hours: 6, minutes: 15 }) - result = clock |> subtract({ minutes: 160 }) |> to_str - expected = "03:35" - result == expected +expect { + clock = create({ hours: 6, minutes: 15 }) + result = clock.subtract({ minutes: 160 }).to_str() + expected = "03:35" + result == expected +} # subtract more than one day (1500 min = 25 hrs) -expect - clock = create({ hours: 5, minutes: 32 }) - result = clock |> subtract({ minutes: 1500 }) |> to_str - expected = "04:32" - result == expected +expect { + clock = create({ hours: 5, minutes: 32 }) + result = clock.subtract({ minutes: 1500 }).to_str() + expected = "04:32" + result == expected +} # subtract more than two days -expect - clock = create({ hours: 2, minutes: 20 }) - result = clock |> subtract({ minutes: 3000 }) |> to_str - expected = "00:20" - result == expected +expect { + clock = create({ hours: 2, minutes: 20 }) + result = clock.subtract({ minutes: 3000 }).to_str() + expected = "00:20" + result == expected +} ## ## Compare two clocks for equality ## # clocks with same time -expect - clock1 = create({ hours: 15, minutes: 37 }) - clock2 = create({ hours: 15, minutes: 37 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 15, minutes: 37 }) + clock2 = create({ hours: 15, minutes: 37 }) + clock1 == clock2 +} # clocks a minute apart -expect - clock1 = create({ hours: 15, minutes: 36 }) - clock2 = create({ hours: 15, minutes: 37 }) - clock1 != clock2 +expect { + clock1 = create({ hours: 15, minutes: 36 }) + clock2 = create({ hours: 15, minutes: 37 }) + clock1 != clock2 +} # clocks an hour apart -expect - clock1 = create({ hours: 14, minutes: 37 }) - clock2 = create({ hours: 15, minutes: 37 }) - clock1 != clock2 +expect { + clock1 = create({ hours: 14, minutes: 37 }) + clock2 = create({ hours: 15, minutes: 37 }) + clock1 != clock2 +} # clocks with hour overflow -expect - clock1 = create({ hours: 10, minutes: 37 }) - clock2 = create({ hours: 34, minutes: 37 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 10, minutes: 37 }) + clock2 = create({ hours: 34, minutes: 37 }) + clock1 == clock2 +} # clocks with hour overflow by several days -expect - clock1 = create({ hours: 3, minutes: 11 }) - clock2 = create({ hours: 99, minutes: 11 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 3, minutes: 11 }) + clock2 = create({ hours: 99, minutes: 11 }) + clock1 == clock2 +} # clocks with negative hour -expect - clock1 = create({ hours: 22, minutes: 40 }) - clock2 = create({ hours: -2, minutes: 40 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 22, minutes: 40 }) + clock2 = create({ hours: -2, minutes: 40 }) + clock1 == clock2 +} # clocks with negative hour that wraps -expect - clock1 = create({ hours: 17, minutes: 3 }) - clock2 = create({ hours: -31, minutes: 3 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 17, minutes: 3 }) + clock2 = create({ hours: -31, minutes: 3 }) + clock1 == clock2 +} # clocks with negative hour that wraps multiple times -expect - clock1 = create({ hours: 13, minutes: 49 }) - clock2 = create({ hours: -83, minutes: 49 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 13, minutes: 49 }) + clock2 = create({ hours: -83, minutes: 49 }) + clock1 == clock2 +} # clocks with minute overflow -expect - clock1 = create({ minutes: 1 }) - clock2 = create({ minutes: 1441 }) - clock1 == clock2 +expect { + clock1 = create({ minutes: 1 }) + clock2 = create({ minutes: 1441 }) + clock1 == clock2 +} # clocks with minute overflow by several days -expect - clock1 = create({ hours: 2, minutes: 2 }) - clock2 = create({ hours: 2, minutes: 4322 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 2, minutes: 2 }) + clock2 = create({ hours: 2, minutes: 4322 }) + clock1 == clock2 +} # clocks with negative minute -expect - clock1 = create({ hours: 2, minutes: 40 }) - clock2 = create({ hours: 3, minutes: -20 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 2, minutes: 40 }) + clock2 = create({ hours: 3, minutes: -20 }) + clock1 == clock2 +} # clocks with negative minute that wraps -expect - clock1 = create({ hours: 4, minutes: 10 }) - clock2 = create({ hours: 5, minutes: -1490 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 4, minutes: 10 }) + clock2 = create({ hours: 5, minutes: -1490 }) + clock1 == clock2 +} # clocks with negative minute that wraps multiple times -expect - clock1 = create({ hours: 6, minutes: 15 }) - clock2 = create({ hours: 6, minutes: -4305 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 6, minutes: 15 }) + clock2 = create({ hours: 6, minutes: -4305 }) + clock1 == clock2 +} # clocks with negative hours and minutes -expect - clock1 = create({ hours: 7, minutes: 32 }) - clock2 = create({ hours: -12, minutes: -268 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 7, minutes: 32 }) + clock2 = create({ hours: -12, minutes: -268 }) + clock1 == clock2 +} # clocks with negative hours and minutes that wrap -expect - clock1 = create({ hours: 18, minutes: 7 }) - clock2 = create({ hours: -54, minutes: -11513 }) - clock1 == clock2 +expect { + clock1 = create({ hours: 18, minutes: 7 }) + clock2 = create({ hours: -54, minutes: -11513 }) + clock1 == clock2 +} # full clock and zeroed clock -expect - clock1 = create({ hours: 24 }) - clock2 = create({}) - clock1 == clock2 +expect { + clock1 = create({ hours: 24 }) + clock2 = create({}) + clock1 == clock2 +} ## ## Extreme I64 values should not crash with overflow errors ## # Can create a clock with max I64 values -expect - clock = create({ hours: 9223372036854775807, minutes: 9223372036854775807 }) - result = clock |> to_str - expected = "01:07" - result == expected +expect { + clock = create({ hours: 9223372036854775807, minutes: 9223372036854775807 }) + result = clock.to_str() + expected = "01:07" + result == expected +} # Can create a clock with min I64 values -expect - clock = create({ hours: -9223372036854775808, minutes: -9223372036854775808 }) - result = clock |> to_str - expected = "21:52" - result == expected +expect { + clock = create({ hours: -9223372036854775808, minutes: -9223372036854775808 }) + result = clock.to_str() + expected = "21:52" + result == expected +} # Can add max I64 values to a clock -expect - clock = create({ hours: 23, minutes: 59 }) - result = clock |> add({ minutes: 9223372036854775807 }) |> to_str - expected = "18:06" - result == expected +expect { + clock = create({ hours: 23, minutes: 59 }) + result = clock.add({ minutes: 9223372036854775807 }).to_str() + expected = "18:06" + result == expected +} # Can add min I64 values to a clock -expect - clock = create({ hours: 23, minutes: 59 }) - result = clock |> add({ minutes: -9223372036854775808 }) |> to_str - expected = "05:51" - result == expected +expect { + clock = create({ hours: 23, minutes: 59 }) + result = clock.add({ minutes: -9223372036854775808 }).to_str() + expected = "05:51" + result == expected +} # Can subtract max I64 values from a clock -expect - clock = create({ hours: 23, minutes: 59 }) - result = clock |> subtract({ minutes: 9223372036854775807 }) |> to_str - expected = "05:52" - result == expected +expect { + clock = create({ hours: 23, minutes: 59 }) + result = clock.subtract({ minutes: 9223372036854775807 }).to_str() + expected = "05:52" + result == expected +} # Can subtract min I64 values from a clock -expect - clock = create({ hours: 23, minutes: 59 }) - result = clock |> subtract({ minutes: -9223372036854775808 }) |> to_str - expected = "18:07" - result == expected +expect { + clock = create({ hours: 23, minutes: 59 }) + result = clock.subtract({ minutes: -9223372036854775808 }).to_str() + expected = "18:07" + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/collatz-conjecture/.meta/template.j2 b/exercises/practice/collatz-conjecture/.meta/template.j2 index 307d8063..ed9ef422 100644 --- a/exercises/practice/collatz-conjecture/.meta/template.j2 +++ b/exercises/practice/collatz-conjecture/.meta/template.j2 @@ -6,13 +6,14 @@ import {{ exercise | to_pascal }} exposing [steps] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["number"] }}) {%- if case["expected"]["error"] %} - Result.is_err(result) + result.is_err() {%- else %} result == Ok({{ case["expected"] }}) {%- endif %} +} {% endfor %} diff --git a/exercises/practice/collatz-conjecture/collatz-conjecture-test.roc b/exercises/practice/collatz-conjecture/collatz-conjecture-test.roc index d8044726..157ccb1f 100644 --- a/exercises/practice/collatz-conjecture/collatz-conjecture-test.roc +++ b/exercises/practice/collatz-conjecture/collatz-conjecture-test.roc @@ -1,39 +1,40 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/collatz-conjecture/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import CollatzConjecture exposing [steps] # zero steps for one -expect - result = steps(1) - result == Ok(0) +expect { + result = steps(1) + result == Ok(0) +} # divide if even -expect - result = steps(16) - result == Ok(4) +expect { + result = steps(16) + result == Ok(4) +} # even and odd steps -expect - result = steps(12) - result == Ok(9) +expect { + result = steps(12) + result == Ok(9) +} # large number of even and odd steps -expect - result = steps(1000000) - result == Ok(152) +expect { + result = steps(1000000) + result == Ok(152) +} # zero is an error -expect - result = steps(0) - Result.is_err(result) +expect { + result = steps(0) + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2 index 273a2d95..b8e67e3a 100644 --- a/exercises/practice/complex-numbers/.meta/template.j2 +++ b/exercises/practice/complex-numbers/.meta/template.j2 @@ -4,8 +4,13 @@ import {{ exercise | to_pascal }} exposing [real, imaginary, add, sub, mul, div, conjugate, abs, exp] -is_approx_eq = |z1, z2| - z1.re |> Num.is_approx_eq(z2.re, {}) && z1.im |> Num.is_approx_eq(z2.im, {}) +is_approx_eq = |x1, x2| { + (x1 * 1e9 + 0.5).to_u64_wrap() == (x2 * 1e9 + 0.5).to_u64_wrap() +} + +complex_is_approx_eq = |z1, z2| { + is_approx_eq(z1.re, z2.re) and is_approx_eq(z1.im, z2.im) +} {% for supercase in cases %} ### @@ -23,22 +28,24 @@ is_approx_eq = |z1, z2| {% for subcase in subcases -%} # {{ subcase["description"] }} {%- if subcase["input"]["z"] %} -expect +expect { z = {{ plugins.to_complex_number(subcase["input"]["z"]) }} result = {{ subcase["property"] }}(z) {%- if subcase["expected"] is iterable and subcase["expected"] is not string %} expected = {{ plugins.to_complex_number(subcase["expected"]) }} - result |> is_approx_eq(expected) + result -> complex_is_approx_eq(expected) {%- else %} - result |> Num.is_approx_eq({{ subcase["expected"] | to_roc }}, {}) + result -> is_approx_eq({{ subcase["expected"] | to_roc }}) {%- endif %} +} {%- elif subcase["input"]["z1"] %} -expect +expect { z1 = {{ plugins.to_complex_number(subcase["input"]["z1"]) }} z2 = {{ plugins.to_complex_number(subcase["input"]["z2"]) }} - result = z1 |> {{ subcase["property"] }}(z2) + result = z1.{{ subcase["property"] }}(z2) expected = {{ plugins.to_complex_number(subcase["expected"]) }} - result |> is_approx_eq(expected) + result -> complex_is_approx_eq(expected) +} {%- endif %} {% endfor %} diff --git a/exercises/practice/complex-numbers/complex-numbers-test.roc b/exercises/practice/complex-numbers/complex-numbers-test.roc index 3b02296a..d37baa78 100644 --- a/exercises/practice/complex-numbers/complex-numbers-test.roc +++ b/exercises/practice/complex-numbers/complex-numbers-test.roc @@ -1,63 +1,66 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import ComplexNumbers exposing [real, imaginary, add, sub, mul, div, conjugate, abs, exp] -is_approx_eq = |z1, z2| - z1.re |> Num.is_approx_eq(z2.re, {}) and z1.im |> Num.is_approx_eq(z2.im, {}) +is_approx_eq = |x1, x2| { + (x1 * 1e9 + 0.5).to_u64_wrap() == (x2 * 1e9 + 0.5).to_u64_wrap() +} + +complex_is_approx_eq = |z1, z2| { + is_approx_eq(z1.re, z2.re) and is_approx_eq(z1.im, z2.im) +} ### ### Real part ### # Real part of a purely real number -expect - z = { re: 1, im: 0 } - result = real(z) - result |> Num.is_approx_eq(1, {}) +expect { + z = { re: 1, im: 0 } + result = real(z) + result->is_approx_eq(1) +} # Real part of a purely imaginary number -expect - z = { re: 0, im: 1 } - result = real(z) - result |> Num.is_approx_eq(0, {}) +expect { + z = { re: 0, im: 1 } + result = real(z) + result->is_approx_eq(0) +} # Real part of a number with real and imaginary part -expect - z = { re: 1, im: 2 } - result = real(z) - result |> Num.is_approx_eq(1, {}) +expect { + z = { re: 1, im: 2 } + result = real(z) + result->is_approx_eq(1) +} ### ### Imaginary part ### # Imaginary part of a purely real number -expect - z = { re: 1, im: 0 } - result = imaginary(z) - result |> Num.is_approx_eq(0, {}) +expect { + z = { re: 1, im: 0 } + result = imaginary(z) + result->is_approx_eq(0) +} # Imaginary part of a purely imaginary number -expect - z = { re: 0, im: 1 } - result = imaginary(z) - result |> Num.is_approx_eq(1, {}) +expect { + z = { re: 0, im: 1 } + result = imaginary(z) + result->is_approx_eq(1) +} # Imaginary part of a number with real and imaginary part -expect - z = { re: 1, im: 2 } - result = imaginary(z) - result |> Num.is_approx_eq(2, {}) +expect { + z = { re: 1, im: 2 } + result = imaginary(z) + result->is_approx_eq(2) +} ### ### Imaginary unit @@ -70,270 +73,307 @@ expect ## Addition # Add purely real numbers -expect - z1 = { re: 1, im: 0 } - z2 = { re: 2, im: 0 } - result = z1 |> add(z2) - expected = { re: 3, im: 0 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 1, im: 0 } + z2 = { re: 2, im: 0 } + result = z1.add(z2) + expected = { re: 3, im: 0 } + result->complex_is_approx_eq(expected) +} # Add purely imaginary numbers -expect - z1 = { re: 0, im: 1 } - z2 = { re: 0, im: 2 } - result = z1 |> add(z2) - expected = { re: 0, im: 3 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 0, im: 1 } + z2 = { re: 0, im: 2 } + result = z1.add(z2) + expected = { re: 0, im: 3 } + result->complex_is_approx_eq(expected) +} # Add numbers with real and imaginary part -expect - z1 = { re: 1, im: 2 } - z2 = { re: 3, im: 4 } - result = z1 |> add(z2) - expected = { re: 4, im: 6 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 1, im: 2 } + z2 = { re: 3, im: 4 } + result = z1.add(z2) + expected = { re: 4, im: 6 } + result->complex_is_approx_eq(expected) +} ## Subtraction # Subtract purely real numbers -expect - z1 = { re: 1, im: 0 } - z2 = { re: 2, im: 0 } - result = z1 |> sub(z2) - expected = { re: -1, im: 0 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 1, im: 0 } + z2 = { re: 2, im: 0 } + result = z1.sub(z2) + expected = { re: -1, im: 0 } + result->complex_is_approx_eq(expected) +} # Subtract purely imaginary numbers -expect - z1 = { re: 0, im: 1 } - z2 = { re: 0, im: 2 } - result = z1 |> sub(z2) - expected = { re: 0, im: -1 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 0, im: 1 } + z2 = { re: 0, im: 2 } + result = z1.sub(z2) + expected = { re: 0, im: -1 } + result->complex_is_approx_eq(expected) +} # Subtract numbers with real and imaginary part -expect - z1 = { re: 1, im: 2 } - z2 = { re: 3, im: 4 } - result = z1 |> sub(z2) - expected = { re: -2, im: -2 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 1, im: 2 } + z2 = { re: 3, im: 4 } + result = z1.sub(z2) + expected = { re: -2, im: -2 } + result->complex_is_approx_eq(expected) +} ## Multiplication # Multiply purely real numbers -expect - z1 = { re: 1, im: 0 } - z2 = { re: 2, im: 0 } - result = z1 |> mul(z2) - expected = { re: 2, im: 0 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 1, im: 0 } + z2 = { re: 2, im: 0 } + result = z1.mul(z2) + expected = { re: 2, im: 0 } + result->complex_is_approx_eq(expected) +} # Multiply purely imaginary numbers -expect - z1 = { re: 0, im: 1 } - z2 = { re: 0, im: 2 } - result = z1 |> mul(z2) - expected = { re: -2, im: 0 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 0, im: 1 } + z2 = { re: 0, im: 2 } + result = z1.mul(z2) + expected = { re: -2, im: 0 } + result->complex_is_approx_eq(expected) +} # Multiply numbers with real and imaginary part -expect - z1 = { re: 1, im: 2 } - z2 = { re: 3, im: 4 } - result = z1 |> mul(z2) - expected = { re: -5, im: 10 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 1, im: 2 } + z2 = { re: 3, im: 4 } + result = z1.mul(z2) + expected = { re: -5, im: 10 } + result->complex_is_approx_eq(expected) +} ## Division # Divide purely real numbers -expect - z1 = { re: 1, im: 0 } - z2 = { re: 2, im: 0 } - result = z1 |> div(z2) - expected = { re: 0.5, im: 0 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 1, im: 0 } + z2 = { re: 2, im: 0 } + result = z1.div(z2) + expected = { re: 0.5, im: 0 } + result->complex_is_approx_eq(expected) +} # Divide purely imaginary numbers -expect - z1 = { re: 0, im: 1 } - z2 = { re: 0, im: 2 } - result = z1 |> div(z2) - expected = { re: 0.5, im: 0 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 0, im: 1 } + z2 = { re: 0, im: 2 } + result = z1.div(z2) + expected = { re: 0.5, im: 0 } + result->complex_is_approx_eq(expected) +} # Divide numbers with real and imaginary part -expect - z1 = { re: 1, im: 2 } - z2 = { re: 3, im: 4 } - result = z1 |> div(z2) - expected = { re: 0.44, im: 0.08 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 1, im: 2 } + z2 = { re: 3, im: 4 } + result = z1.div(z2) + expected = { re: 0.44, im: 0.08 } + result->complex_is_approx_eq(expected) +} ### ### Absolute value ### # Absolute value of a positive purely real number -expect - z = { re: 5, im: 0 } - result = abs(z) - result |> Num.is_approx_eq(5, {}) +expect { + z = { re: 5, im: 0 } + result = abs(z) + result->is_approx_eq(5) +} # Absolute value of a negative purely real number -expect - z = { re: -5, im: 0 } - result = abs(z) - result |> Num.is_approx_eq(5, {}) +expect { + z = { re: -5, im: 0 } + result = abs(z) + result->is_approx_eq(5) +} # Absolute value of a purely imaginary number with positive imaginary part -expect - z = { re: 0, im: 5 } - result = abs(z) - result |> Num.is_approx_eq(5, {}) +expect { + z = { re: 0, im: 5 } + result = abs(z) + result->is_approx_eq(5) +} # Absolute value of a purely imaginary number with negative imaginary part -expect - z = { re: 0, im: -5 } - result = abs(z) - result |> Num.is_approx_eq(5, {}) +expect { + z = { re: 0, im: -5 } + result = abs(z) + result->is_approx_eq(5) +} # Absolute value of a number with real and imaginary part -expect - z = { re: 3, im: 4 } - result = abs(z) - result |> Num.is_approx_eq(5, {}) +expect { + z = { re: 3, im: 4 } + result = abs(z) + result->is_approx_eq(5) +} ### ### Complex conjugate ### # Conjugate a purely real number -expect - z = { re: 5, im: 0 } - result = conjugate(z) - expected = { re: 5, im: 0 } - result |> is_approx_eq(expected) +expect { + z = { re: 5, im: 0 } + result = conjugate(z) + expected = { re: 5, im: 0 } + result->complex_is_approx_eq(expected) +} # Conjugate a purely imaginary number -expect - z = { re: 0, im: 5 } - result = conjugate(z) - expected = { re: 0, im: -5 } - result |> is_approx_eq(expected) +expect { + z = { re: 0, im: 5 } + result = conjugate(z) + expected = { re: 0, im: -5 } + result->complex_is_approx_eq(expected) +} # Conjugate a number with real and imaginary part -expect - z = { re: 1, im: 1 } - result = conjugate(z) - expected = { re: 1, im: -1 } - result |> is_approx_eq(expected) +expect { + z = { re: 1, im: 1 } + result = conjugate(z) + expected = { re: 1, im: -1 } + result->complex_is_approx_eq(expected) +} ### ### Complex exponential function ### # Euler's identity/formula -expect - z = { re: 0, im: Num.pi } - result = exp(z) - expected = { re: -1, im: 0 } - result |> is_approx_eq(expected) +expect { + z = { re: 0, im: 3.141592653589793.F64 } + result = exp(z) + expected = { re: -1, im: 0 } + result->complex_is_approx_eq(expected) +} # Exponential of 0 -expect - z = { re: 0, im: 0 } - result = exp(z) - expected = { re: 1, im: 0 } - result |> is_approx_eq(expected) +expect { + z = { re: 0, im: 0 } + result = exp(z) + expected = { re: 1, im: 0 } + result->complex_is_approx_eq(expected) +} # Exponential of a purely real number -expect - z = { re: 1, im: 0 } - result = exp(z) - expected = { re: Num.e, im: 0 } - result |> is_approx_eq(expected) +expect { + z = { re: 1, im: 0 } + result = exp(z) + expected = { re: 2.718281828459045.F64, im: 0 } + result->complex_is_approx_eq(expected) +} # Exponential of a number with real and imaginary part -expect - z = { re: Num.log(2f64), im: Num.pi } - result = exp(z) - expected = { re: -2, im: 0 } - result |> is_approx_eq(expected) +expect { + z = { re: 0.6931471805599453.F64, im: 3.141592653589793.F64 } + result = exp(z) + expected = { re: -2, im: 0 } + result->complex_is_approx_eq(expected) +} # Exponential resulting in a number with real and imaginary part -expect - z = { re: Num.log(2f64) / 2, im: Num.pi / 4 } - result = exp(z) - expected = { re: 1, im: 1 } - result |> is_approx_eq(expected) +expect { + z = { re: 0.6931471805599453.F64 / 2, im: 3.141592653589793.F64 / 4 } + result = exp(z) + expected = { re: 1, im: 1 } + result->complex_is_approx_eq(expected) +} ### ### Operations between real numbers and complex numbers ### # Add real number to complex number -expect - z1 = { re: 1, im: 2 } - z2 = { re: 5, im: 0 } - result = z1 |> add(z2) - expected = { re: 6, im: 2 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 1, im: 2 } + z2 = { re: 5, im: 0 } + result = z1.add(z2) + expected = { re: 6, im: 2 } + result->complex_is_approx_eq(expected) +} # Add complex number to real number -expect - z1 = { re: 5, im: 0 } - z2 = { re: 1, im: 2 } - result = z1 |> add(z2) - expected = { re: 6, im: 2 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 5, im: 0 } + z2 = { re: 1, im: 2 } + result = z1.add(z2) + expected = { re: 6, im: 2 } + result->complex_is_approx_eq(expected) +} # Subtract real number from complex number -expect - z1 = { re: 5, im: 7 } - z2 = { re: 4, im: 0 } - result = z1 |> sub(z2) - expected = { re: 1, im: 7 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 5, im: 7 } + z2 = { re: 4, im: 0 } + result = z1.sub(z2) + expected = { re: 1, im: 7 } + result->complex_is_approx_eq(expected) +} # Subtract complex number from real number -expect - z1 = { re: 4, im: 0 } - z2 = { re: 5, im: 7 } - result = z1 |> sub(z2) - expected = { re: -1, im: -7 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 4, im: 0 } + z2 = { re: 5, im: 7 } + result = z1.sub(z2) + expected = { re: -1, im: -7 } + result->complex_is_approx_eq(expected) +} # Multiply complex number by real number -expect - z1 = { re: 2, im: 5 } - z2 = { re: 5, im: 0 } - result = z1 |> mul(z2) - expected = { re: 10, im: 25 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 2, im: 5 } + z2 = { re: 5, im: 0 } + result = z1.mul(z2) + expected = { re: 10, im: 25 } + result->complex_is_approx_eq(expected) +} # Multiply real number by complex number -expect - z1 = { re: 5, im: 0 } - z2 = { re: 2, im: 5 } - result = z1 |> mul(z2) - expected = { re: 10, im: 25 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 5, im: 0 } + z2 = { re: 2, im: 5 } + result = z1.mul(z2) + expected = { re: 10, im: 25 } + result->complex_is_approx_eq(expected) +} # Divide complex number by real number -expect - z1 = { re: 10, im: 100 } - z2 = { re: 10, im: 0 } - result = z1 |> div(z2) - expected = { re: 1, im: 10 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 10, im: 100 } + z2 = { re: 10, im: 0 } + result = z1.div(z2) + expected = { re: 1, im: 10 } + result->complex_is_approx_eq(expected) +} # Divide real number by complex number -expect - z1 = { re: 5, im: 0 } - z2 = { re: 1, im: 1 } - result = z1 |> div(z2) - expected = { re: 2.5, im: -2.5 } - result |> is_approx_eq(expected) +expect { + z1 = { re: 5, im: 0 } + z2 = { re: 1, im: 1 } + result = z1.div(z2) + expected = { re: 2.5, im: -2.5 } + result->complex_is_approx_eq(expected) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/connect/.meta/template.j2 b/exercises/practice/connect/.meta/template.j2 index d1cddd67..a9eba0a2 100644 --- a/exercises/practice/connect/.meta/template.j2 +++ b/exercises/practice/connect/.meta/template.j2 @@ -6,10 +6,11 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { board = {{ case["input"]["board"] | to_roc_multiline_string | indent(8) }} - result = board |> {{ case["property"] | to_snake }} + result = board -> {{ case["property"] | to_snake }} result == {% if case["expected"] == "" %}Err(NotFinished){% else %}Ok(Player{{ case["expected"] }}){% endif %} +} {% endfor %} diff --git a/exercises/practice/connect/connect-test.roc b/exercises/practice/connect/connect-test.roc index 237fa492..df50a83c 100644 --- a/exercises/practice/connect/connect-test.roc +++ b/exercises/practice/connect/connect-test.roc @@ -1,133 +1,131 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/connect/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Connect exposing [winner] # an empty board has no winner -expect - board = - """ - . . . . . - . . . . . - . . . . . - . . . . . - . . . . . - """ - result = board |> winner - result == Err(NotFinished) +expect { + board = + \\. . . . . + \\ . . . . . + \\ . . . . . + \\ . . . . . + \\ . . . . . + + result = board->winner() + result == Err(NotFinished) +} # X can win on a 1x1 board -expect - board = "X" - result = board |> winner - result == Ok(PlayerX) +expect { + board = "X" + result = board->winner() + result == Ok(PlayerX) +} # O can win on a 1x1 board -expect - board = "O" - result = board |> winner - result == Ok(PlayerO) +expect { + board = "O" + result = board->winner() + result == Ok(PlayerO) +} # only edges does not make a winner -expect - board = - """ - O O O X - X . . X - X . . X - X O O O - """ - result = board |> winner - result == Err(NotFinished) +expect { + board = + \\O O O X + \\ X . . X + \\ X . . X + \\ X O O O + + result = board->winner() + result == Err(NotFinished) +} # illegal diagonal does not make a winner -expect - board = - """ - X O . . - O X X X - O X O . - . O X . - X X O O - """ - result = board |> winner - result == Err(NotFinished) +expect { + board = + \\X O . . + \\ O X X X + \\ O X O . + \\ . O X . + \\ X X O O + + result = board->winner() + result == Err(NotFinished) +} # nobody wins crossing adjacent angles -expect - board = - """ - X . . . - . X O . - O . X O - . O . X - . . O . - """ - result = board |> winner - result == Err(NotFinished) +expect { + board = + \\X . . . + \\ . X O . + \\ O . X O + \\ . O . X + \\ . . O . + + result = board->winner() + result == Err(NotFinished) +} # X wins crossing from left to right -expect - board = - """ - . O . . - O X X X - O X O . - X X O X - . O X . - """ - result = board |> winner - result == Ok(PlayerX) +expect { + board = + \\. O . . + \\ O X X X + \\ O X O . + \\ X X O X + \\ . O X . + + result = board->winner() + result == Ok(PlayerX) +} # O wins crossing from top to bottom -expect - board = - """ - . O . . - O X X X - O O O . - X X O X - . O X . - """ - result = board |> winner - result == Ok(PlayerO) +expect { + board = + \\. O . . + \\ O X X X + \\ O O O . + \\ X X O X + \\ . O X . + + result = board->winner() + result == Ok(PlayerO) +} # X wins using a convoluted path -expect - board = - """ - . X X . . - X . X . X - . X . X . - . X X . . - O O O O O - """ - result = board |> winner - result == Ok(PlayerX) +expect { + board = + \\. X X . . + \\ X . X . X + \\ . X . X . + \\ . X X . . + \\ O O O O O + + result = board->winner() + result == Ok(PlayerX) +} # X wins using a spiral path -expect - board = - """ - O X X X X X X X X - O X O O O O O O O - O X O X X X X X O - O X O X O O O X O - O X O X X X O X O - O X O O O X O X O - O X X X X X O X O - O O O O O O O X O - X X X X X X X X O - """ - result = board |> winner - result == Ok(PlayerX) +expect { + board = + \\O X X X X X X X X + \\ O X O O O O O O O + \\ O X O X X X X X O + \\ O X O X O O O X O + \\ O X O X X X O X O + \\ O X O O O X O X O + \\ O X X X X X O X O + \\ O O O O O O O X O + \\ X X X X X X X X O + result = board->winner() + result == Ok(PlayerX) +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/crypto-square/.meta/template.j2 b/exercises/practice/crypto-square/.meta/template.j2 index cd21e819..70c76336 100644 --- a/exercises/practice/crypto-square/.meta/template.j2 +++ b/exercises/practice/crypto-square/.meta/template.j2 @@ -6,11 +6,12 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { text = {{ case["input"]["plaintext"] | to_roc }} result = {{ case["property"] | to_snake }}(text) expected = Ok({{ case["expected"] | to_roc }}) result == expected +} {% endfor %} diff --git a/exercises/practice/crypto-square/crypto-square-test.roc b/exercises/practice/crypto-square/crypto-square-test.roc index c9109ae2..e4366e33 100644 --- a/exercises/practice/crypto-square/crypto-square-test.roc +++ b/exercises/practice/crypto-square/crypto-square-test.roc @@ -1,70 +1,74 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/crypto-square/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import CryptoSquare exposing [ciphertext] # empty plaintext results in an empty ciphertext -expect - text = "" - result = ciphertext(text) - expected = Ok("") - result == expected +expect { + text = "" + result = ciphertext(text) + expected = Ok("") + result == expected +} # normalization results in empty plaintext -expect - text = "... --- ..." - result = ciphertext(text) - expected = Ok("") - result == expected +expect { + text = "... --- ..." + result = ciphertext(text) + expected = Ok("") + result == expected +} # Lowercase -expect - text = "A" - result = ciphertext(text) - expected = Ok("a") - result == expected +expect { + text = "A" + result = ciphertext(text) + expected = Ok("a") + result == expected +} # Remove spaces -expect - text = " b " - result = ciphertext(text) - expected = Ok("b") - result == expected +expect { + text = " b " + result = ciphertext(text) + expected = Ok("b") + result == expected +} # Remove punctuation -expect - text = "@1,%!" - result = ciphertext(text) - expected = Ok("1") - result == expected +expect { + text = "@1,%!" + result = ciphertext(text) + expected = Ok("1") + result == expected +} # 9 character plaintext results in 3 chunks of 3 characters -expect - text = "This is fun!" - result = ciphertext(text) - expected = Ok("tsf hiu isn") - result == expected +expect { + text = "This is fun!" + result = ciphertext(text) + expected = Ok("tsf hiu isn") + result == expected +} # 8 character plaintext results in 3 chunks, the last one with a trailing space -expect - text = "Chill out." - result = ciphertext(text) - expected = Ok("clu hlt io ") - result == expected +expect { + text = "Chill out." + result = ciphertext(text) + expected = Ok("clu hlt io ") + result == expected +} # 54 character plaintext results in 8 chunks, the last two with trailing spaces -expect - text = "If man was meant to stay on the ground, god would have given us roots." - result = ciphertext(text) - expected = Ok("imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ") - result == expected +expect { + text = "If man was meant to stay on the ground, god would have given us roots." + result = ciphertext(text) + expected = Ok("imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ") + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/custom-set/.meta/template.j2 b/exercises/practice/custom-set/.meta/template.j2 index 904d3f8e..ad127cfe 100644 --- a/exercises/practice/custom-set/.meta/template.j2 +++ b/exercises/practice/custom-set/.meta/template.j2 @@ -34,23 +34,24 @@ set property_map = { {% for case in supercase["cases"] -%} # {{ case["description"] }} {% set property = property_map.get(case["property"], case["property"]) %} -expect +expect { {%- if "set" in case["input"] %} set = from_list({{ case["input"]["set"] | to_roc }}) - result = set |> {{ property | to_snake }} + result = set.{{ property | to_snake }}() {%- if "element" in case["input"] %}({{(case["input"]["element"])}}){%- endif %} {%- else %} set1 = from_list({{ case["input"]["set1"] | to_roc }}) set2 = from_list({{ case["input"]["set2"] | to_roc }}) - result = set1 |> {{ property | to_snake }}(set2) + result = set1.{{ property | to_snake }}(set2) {%- endif %} {%- if case["expected"] is iterable %} - expected = {{ case["expected"] | to_roc }} |> from_list - result |> is_eq(expected) + expected = {{ case["expected"] | to_roc }} -> from_list() + result.is_eq(expected) {%- else %} expected = {{ case["expected"] | to_roc }} result == expected {%- endif %} +} {% endfor %} {% endfor %} @@ -61,11 +62,12 @@ expect {% for case in additional_cases -%} # {{ case["description"] }} -expect +expect { set = from_list({{ case["input"]["set"] | to_roc }}) - result = set |> to_list |> List.sort_asc + result = set.to_list().sort_asc() expected = {{ case["expected"] | to_roc }} result == expected +} {% endfor %} diff --git a/exercises/practice/custom-set/custom-set-test.roc b/exercises/practice/custom-set/custom-set-test.roc index a9cdf184..95ec53cf 100644 --- a/exercises/practice/custom-set/custom-set-test.roc +++ b/exercises/practice/custom-set/custom-set-test.roc @@ -1,27 +1,19 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import CustomSet exposing [ - contains, - difference, - from_list, - insert, - intersection, - is_disjoint_with, - is_empty, - is_eq, - is_subset_of, - to_list, - union, + contains, + difference, + from_list, + insert, + intersection, + is_disjoint_with, + is_empty, + is_eq, + is_subset_of, + to_list, + union, ] ## @@ -30,19 +22,21 @@ import CustomSet exposing [ # sets with no elements are empty -expect - set = from_list([]) - result = set |> is_empty - expected = Bool.true - result == expected +expect { + set = from_list([]) + result = set.is_empty() + expected = Bool.True + result == expected +} # sets with elements are not empty -expect - set = from_list([1]) - result = set |> is_empty - expected = Bool.false - result == expected +expect { + set = from_list([1]) + result = set.is_empty() + expected = Bool.False + result == expected +} ## ## Sets can report if they contain an element @@ -50,27 +44,30 @@ expect # nothing is contained in an empty set -expect - set = from_list([]) - result = set |> contains(1) - expected = Bool.false - result == expected +expect { + set = from_list([]) + result = set.contains()(1) + expected = Bool.False + result == expected +} # when the element is in the set -expect - set = from_list([1, 2, 3]) - result = set |> contains(1) - expected = Bool.true - result == expected +expect { + set = from_list([1, 2, 3]) + result = set.contains()(1) + expected = Bool.True + result == expected +} # when the element is not in the set -expect - set = from_list([1, 2, 3]) - result = set |> contains(4) - expected = Bool.false - result == expected +expect { + set = from_list([1, 2, 3]) + result = set.contains()(4) + expected = Bool.False + result == expected +} ## ## A set is a subset if all of its elements are contained in the other set @@ -78,57 +75,63 @@ expect # empty set is a subset of another empty set -expect - set1 = from_list([]) - set2 = from_list([]) - result = set1 |> is_subset_of(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([]) + set2 = from_list([]) + result = set1.is_subset_of(set2) + expected = Bool.True + result == expected +} # empty set is a subset of non-empty set -expect - set1 = from_list([]) - set2 = from_list([1]) - result = set1 |> is_subset_of(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([]) + set2 = from_list([1]) + result = set1.is_subset_of(set2) + expected = Bool.True + result == expected +} # non-empty set is not a subset of empty set -expect - set1 = from_list([1]) - set2 = from_list([]) - result = set1 |> is_subset_of(set2) - expected = Bool.false - result == expected +expect { + set1 = from_list([1]) + set2 = from_list([]) + result = set1.is_subset_of(set2) + expected = Bool.False + result == expected +} # set is a subset of set with exact same elements -expect - set1 = from_list([1, 2, 3]) - set2 = from_list([1, 2, 3]) - result = set1 |> is_subset_of(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([1, 2, 3]) + set2 = from_list([1, 2, 3]) + result = set1.is_subset_of(set2) + expected = Bool.True + result == expected +} # set is a subset of larger set with same elements -expect - set1 = from_list([1, 2, 3]) - set2 = from_list([4, 1, 2, 3]) - result = set1 |> is_subset_of(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([1, 2, 3]) + set2 = from_list([4, 1, 2, 3]) + result = set1.is_subset_of(set2) + expected = Bool.True + result == expected +} # set is not a subset of set that does not contain its elements -expect - set1 = from_list([1, 2, 3]) - set2 = from_list([4, 1, 3]) - result = set1 |> is_subset_of(set2) - expected = Bool.false - result == expected +expect { + set1 = from_list([1, 2, 3]) + set2 = from_list([4, 1, 3]) + result = set1.is_subset_of(set2) + expected = Bool.False + result == expected +} ## ## Sets are disjoint if they share no elements @@ -136,48 +139,53 @@ expect # the empty set is disjoint with itself -expect - set1 = from_list([]) - set2 = from_list([]) - result = set1 |> is_disjoint_with(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([]) + set2 = from_list([]) + result = set1.is_disjoint_with(set2) + expected = Bool.True + result == expected +} # empty set is disjoint with non-empty set -expect - set1 = from_list([]) - set2 = from_list([1]) - result = set1 |> is_disjoint_with(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([]) + set2 = from_list([1]) + result = set1.is_disjoint_with(set2) + expected = Bool.True + result == expected +} # non-empty set is disjoint with empty set -expect - set1 = from_list([1]) - set2 = from_list([]) - result = set1 |> is_disjoint_with(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([1]) + set2 = from_list([]) + result = set1.is_disjoint_with(set2) + expected = Bool.True + result == expected +} # sets are not disjoint if they share an element -expect - set1 = from_list([1, 2]) - set2 = from_list([2, 3]) - result = set1 |> is_disjoint_with(set2) - expected = Bool.false - result == expected +expect { + set1 = from_list([1, 2]) + set2 = from_list([2, 3]) + result = set1.is_disjoint_with(set2) + expected = Bool.False + result == expected +} # sets are disjoint if they share no elements -expect - set1 = from_list([1, 2]) - set2 = from_list([3, 4]) - result = set1 |> is_disjoint_with(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([1, 2]) + set2 = from_list([3, 4]) + result = set1.is_disjoint_with(set2) + expected = Bool.True + result == expected +} ## ## Sets with the same elements are equal @@ -185,66 +193,73 @@ expect # empty sets are equal -expect - set1 = from_list([]) - set2 = from_list([]) - result = set1 |> is_eq(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([]) + set2 = from_list([]) + result = set1.is_eq(set2) + expected = Bool.True + result == expected +} # empty set is not equal to non-empty set -expect - set1 = from_list([]) - set2 = from_list([1, 2, 3]) - result = set1 |> is_eq(set2) - expected = Bool.false - result == expected +expect { + set1 = from_list([]) + set2 = from_list([1, 2, 3]) + result = set1.is_eq(set2) + expected = Bool.False + result == expected +} # non-empty set is not equal to empty set -expect - set1 = from_list([1, 2, 3]) - set2 = from_list([]) - result = set1 |> is_eq(set2) - expected = Bool.false - result == expected +expect { + set1 = from_list([1, 2, 3]) + set2 = from_list([]) + result = set1.is_eq(set2) + expected = Bool.False + result == expected +} # sets with the same elements are equal -expect - set1 = from_list([1, 2]) - set2 = from_list([2, 1]) - result = set1 |> is_eq(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([1, 2]) + set2 = from_list([2, 1]) + result = set1.is_eq(set2) + expected = Bool.True + result == expected +} # sets with different elements are not equal -expect - set1 = from_list([1, 2, 3]) - set2 = from_list([1, 2, 4]) - result = set1 |> is_eq(set2) - expected = Bool.false - result == expected +expect { + set1 = from_list([1, 2, 3]) + set2 = from_list([1, 2, 4]) + result = set1.is_eq(set2) + expected = Bool.False + result == expected +} # set is not equal to larger set with same elements -expect - set1 = from_list([1, 2, 3]) - set2 = from_list([1, 2, 3, 4]) - result = set1 |> is_eq(set2) - expected = Bool.false - result == expected +expect { + set1 = from_list([1, 2, 3]) + set2 = from_list([1, 2, 3, 4]) + result = set1.is_eq(set2) + expected = Bool.False + result == expected +} # set is equal to a set constructed from an array with duplicates -expect - set1 = from_list([1]) - set2 = from_list([1, 1]) - result = set1 |> is_eq(set2) - expected = Bool.true - result == expected +expect { + set1 = from_list([1]) + set2 = from_list([1, 1]) + result = set1.is_eq(set2) + expected = Bool.True + result == expected +} ## ## Unique elements can be added to a set @@ -252,27 +267,30 @@ expect # add to empty set -expect - set = from_list([]) - result = set |> insert(3) - expected = [3] |> from_list - result |> is_eq(expected) +expect { + set = from_list([]) + result = set.insert()(3) + expected = [3]->from_list() + result.is_eq(expected) +} # add to non-empty set -expect - set = from_list([1, 2, 4]) - result = set |> insert(3) - expected = [1, 2, 3, 4] |> from_list - result |> is_eq(expected) +expect { + set = from_list([1, 2, 4]) + result = set.insert()(3) + expected = [1, 2, 3, 4]->from_list() + result.is_eq(expected) +} # adding an existing element does not change the set -expect - set = from_list([1, 2, 3]) - result = set |> insert(3) - expected = [1, 2, 3] |> from_list - result |> is_eq(expected) +expect { + set = from_list([1, 2, 3]) + result = set.insert()(3) + expected = [1, 2, 3]->from_list() + result.is_eq(expected) +} ## ## Intersection returns a set of all shared elements @@ -280,48 +298,53 @@ expect # intersection of two empty sets is an empty set -expect - set1 = from_list([]) - set2 = from_list([]) - result = set1 |> intersection(set2) - expected = [] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([]) + set2 = from_list([]) + result = set1.intersection(set2) + expected = []->from_list() + result.is_eq(expected) +} # intersection of an empty set and non-empty set is an empty set -expect - set1 = from_list([]) - set2 = from_list([3, 2, 5]) - result = set1 |> intersection(set2) - expected = [] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([]) + set2 = from_list([3, 2, 5]) + result = set1.intersection(set2) + expected = []->from_list() + result.is_eq(expected) +} # intersection of a non-empty set and an empty set is an empty set -expect - set1 = from_list([1, 2, 3, 4]) - set2 = from_list([]) - result = set1 |> intersection(set2) - expected = [] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([1, 2, 3, 4]) + set2 = from_list([]) + result = set1.intersection(set2) + expected = []->from_list() + result.is_eq(expected) +} # intersection of two sets with no shared elements is an empty set -expect - set1 = from_list([1, 2, 3]) - set2 = from_list([4, 5, 6]) - result = set1 |> intersection(set2) - expected = [] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([1, 2, 3]) + set2 = from_list([4, 5, 6]) + result = set1.intersection(set2) + expected = []->from_list() + result.is_eq(expected) +} # intersection of two sets with shared elements is a set of the shared elements -expect - set1 = from_list([1, 2, 3, 4]) - set2 = from_list([3, 2, 5]) - result = set1 |> intersection(set2) - expected = [2, 3] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([1, 2, 3, 4]) + set2 = from_list([3, 2, 5]) + result = set1.intersection(set2) + expected = [2, 3]->from_list() + result.is_eq(expected) +} ## ## Difference (or Complement) of a set is a set of all elements that are only in the first set @@ -329,48 +352,53 @@ expect # difference of two empty sets is an empty set -expect - set1 = from_list([]) - set2 = from_list([]) - result = set1 |> difference(set2) - expected = [] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([]) + set2 = from_list([]) + result = set1.difference(set2) + expected = []->from_list() + result.is_eq(expected) +} # difference of empty set and non-empty set is an empty set -expect - set1 = from_list([]) - set2 = from_list([3, 2, 5]) - result = set1 |> difference(set2) - expected = [] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([]) + set2 = from_list([3, 2, 5]) + result = set1.difference(set2) + expected = []->from_list() + result.is_eq(expected) +} # difference of a non-empty set and an empty set is the non-empty set -expect - set1 = from_list([1, 2, 3, 4]) - set2 = from_list([]) - result = set1 |> difference(set2) - expected = [1, 2, 3, 4] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([1, 2, 3, 4]) + set2 = from_list([]) + result = set1.difference(set2) + expected = [1, 2, 3, 4]->from_list() + result.is_eq(expected) +} # difference of two non-empty sets is a set of elements that are only in the first set -expect - set1 = from_list([3, 2, 1]) - set2 = from_list([2, 4]) - result = set1 |> difference(set2) - expected = [1, 3] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([3, 2, 1]) + set2 = from_list([2, 4]) + result = set1.difference(set2) + expected = [1, 3]->from_list() + result.is_eq(expected) +} # difference removes all duplicates in the first set -expect - set1 = from_list([1, 1]) - set2 = from_list([1]) - result = set1 |> difference(set2) - expected = [] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([1, 1]) + set2 = from_list([1]) + result = set1.difference(set2) + expected = []->from_list() + result.is_eq(expected) +} ## ## Union returns a set of all elements in either set @@ -378,62 +406,73 @@ expect # union of empty sets is an empty set -expect - set1 = from_list([]) - set2 = from_list([]) - result = set1 |> union(set2) - expected = [] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([]) + set2 = from_list([]) + result = set1.union(set2) + expected = []->from_list() + result.is_eq(expected) +} # union of an empty set and non-empty set is the non-empty set -expect - set1 = from_list([]) - set2 = from_list([2]) - result = set1 |> union(set2) - expected = [2] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([]) + set2 = from_list([2]) + result = set1.union(set2) + expected = [2]->from_list() + result.is_eq(expected) +} # union of a non-empty set and empty set is the non-empty set -expect - set1 = from_list([1, 3]) - set2 = from_list([]) - result = set1 |> union(set2) - expected = [1, 3] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([1, 3]) + set2 = from_list([]) + result = set1.union(set2) + expected = [1, 3]->from_list() + result.is_eq(expected) +} # union of non-empty sets contains all unique elements -expect - set1 = from_list([1, 3]) - set2 = from_list([2, 3]) - result = set1 |> union(set2) - expected = [3, 2, 1] |> from_list - result |> is_eq(expected) +expect { + set1 = from_list([1, 3]) + set2 = from_list([2, 3]) + result = set1.union(set2) + expected = [3, 2, 1]->from_list() + result.is_eq(expected) +} ## ## A set can be converted to a list of items ## # an empty set has an empty list of items -expect - set = from_list([]) - result = set |> to_list |> List.sort_asc - expected = [] - result == expected +expect { + set = from_list([]) + result = set.to_list().sort_asc() + expected = [] + result == expected +} # a set can provide the list of its items -expect - set = from_list([1, 2, 3, 4]) - result = set |> to_list |> List.sort_asc - expected = [1, 2, 3, 4] - result == expected +expect { + set = from_list([1, 2, 3, 4]) + result = set.to_list().sort_asc() + expected = [1, 2, 3, 4] + result == expected +} # duplicate items must be removed -expect - set = from_list([1, 2, 2, 3, 3, 3, 4, 4, 4, 4]) - result = set |> to_list |> List.sort_asc - expected = [1, 2, 3, 4] - result == expected +expect { + set = from_list([1, 2, 2, 3, 3, 3, 4, 4, 4, 4]) + result = set.to_list().sort_asc() + expected = [1, 2, 3, 4] + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/darts/.meta/template.j2 b/exercises/practice/darts/.meta/template.j2 index 26a213f1..346a8d66 100644 --- a/exercises/practice/darts/.meta/template.j2 +++ b/exercises/practice/darts/.meta/template.j2 @@ -6,9 +6,10 @@ import Darts exposing [score] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["x"] | to_roc_float }}, {{ case["input"]["y"] | to_roc_float }}) result == {{ case["expected"] }} +} {% endfor %} diff --git a/exercises/practice/darts/darts-test.roc b/exercises/practice/darts/darts-test.roc index 57830798..a0659bc0 100644 --- a/exercises/practice/darts/darts-test.roc +++ b/exercises/practice/darts/darts-test.roc @@ -1,79 +1,88 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/darts/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Darts exposing [score] # Missed target -expect - result = score(-9.0f64, 9.0f64) - result == 0 +expect { + result = score(-9.0.F64, 9.0.F64) + result == 0 +} # On the outer circle -expect - result = score(0.0f64, 10.0f64) - result == 1 +expect { + result = score(0.0.F64, 10.0.F64) + result == 1 +} # On the middle circle -expect - result = score(-5.0f64, 0.0f64) - result == 5 +expect { + result = score(-5.0.F64, 0.0.F64) + result == 5 +} # On the inner circle -expect - result = score(0.0f64, -1.0f64) - result == 10 +expect { + result = score(0.0.F64, -1.0.F64) + result == 10 +} # Exactly on center -expect - result = score(0.0f64, 0.0f64) - result == 10 +expect { + result = score(0.0.F64, 0.0.F64) + result == 10 +} # Near the center -expect - result = score(-0.1f64, -0.1f64) - result == 10 +expect { + result = score(-0.1.F64, -0.1.F64) + result == 10 +} # Just within the inner circle -expect - result = score(0.7f64, 0.7f64) - result == 10 +expect { + result = score(0.7.F64, 0.7.F64) + result == 10 +} # Just outside the inner circle -expect - result = score(0.8f64, -0.8f64) - result == 5 +expect { + result = score(0.8.F64, -0.8.F64) + result == 5 +} # Just within the middle circle -expect - result = score(-3.5f64, 3.5f64) - result == 5 +expect { + result = score(-3.5.F64, 3.5.F64) + result == 5 +} # Just outside the middle circle -expect - result = score(-3.6f64, -3.6f64) - result == 1 +expect { + result = score(-3.6.F64, -3.6.F64) + result == 1 +} # Just within the outer circle -expect - result = score(-7.0f64, 7.0f64) - result == 1 +expect { + result = score(-7.0.F64, 7.0.F64) + result == 1 +} # Just outside the outer circle -expect - result = score(7.1f64, -7.1f64) - result == 0 +expect { + result = score(7.1.F64, -7.1.F64) + result == 0 +} # Asymmetric position between the inner and middle circles -expect - result = score(0.5f64, -4.0f64) - result == 5 +expect { + result = score(0.5.F64, -4.0.F64) + result == 5 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/diamond/.meta/template.j2 b/exercises/practice/diamond/.meta/template.j2 index 364c8bf7..a0bbc81e 100644 --- a/exercises/practice/diamond/.meta/template.j2 +++ b/exercises/practice/diamond/.meta/template.j2 @@ -6,10 +6,11 @@ import {{ exercise | to_pascal }} exposing [{{ exercise | to_snake }}] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ exercise | to_snake }}('{{ case["input"]["letter"] }}') - expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }} |> Str.replace_each("·", " ") + expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") result == expected +} {% endfor %} diff --git a/exercises/practice/diamond/diamond-test.roc b/exercises/practice/diamond/diamond-test.roc index 46e8b7d3..8d3b6c37 100644 --- a/exercises/practice/diamond/diamond-test.roc +++ b/exercises/practice/diamond/diamond-test.roc @@ -1,122 +1,127 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/diamond/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Diamond exposing [diamond] # Degenerate case with a single 'A' row -expect - result = diamond('A') - expected = "A" |> Str.replace_each("·", " ") - result == expected +expect { + result = diamond('A') + expected = "A".replace_each("·", " ") + result == expected +} # Degenerate case with no row containing 3 distinct groups of spaces -expect - result = diamond('B') - expected = - """ - ·A· - B·B - ·A· - """ - |> Str.replace_each("·", " ") - result == expected +expect { + result = diamond('B') + expected = + \\·A· + \\B·B + \\·A· + .replace_each( + "·", + " ", + ) + result == expected +} # Smallest non-degenerate case with odd diamond side length -expect - result = diamond('C') - expected = - """ - ··A·· - ·B·B· - C···C - ·B·B· - ··A·· - """ - |> Str.replace_each("·", " ") - result == expected +expect { + result = diamond('C') + expected = + \\··A·· + \\·B·B· + \\C···C + \\·B·B· + \\··A·· + .replace_each( + "·", + " ", + ) + result == expected +} # Smallest non-degenerate case with even diamond side length -expect - result = diamond('D') - expected = - """ - ···A··· - ··B·B·· - ·C···C· - D·····D - ·C···C· - ··B·B·· - ···A··· - """ - |> Str.replace_each("·", " ") - result == expected +expect { + result = diamond('D') + expected = + \\···A··· + \\··B·B·· + \\·C···C· + \\D·····D + \\·C···C· + \\··B·B·· + \\···A··· + .replace_each( + "·", + " ", + ) + result == expected +} # Largest possible diamond -expect - result = diamond('Z') - expected = - """ - ·························A························· - ························B·B························ - ·······················C···C······················· - ······················D·····D······················ - ·····················E·······E····················· - ····················F·········F···················· - ···················G···········G··················· - ··················H·············H·················· - ·················I···············I················· - ················J·················J················ - ···············K···················K··············· - ··············L·····················L·············· - ·············M·······················M············· - ············N·························N············ - ···········O···························O··········· - ··········P·····························P·········· - ·········Q·······························Q········· - ········R·································R········ - ·······S···································S······· - ······T·····································T······ - ·····U·······································U····· - ····V·········································V···· - ···W···········································W··· - ··X·············································X·· - ·Y···············································Y· - Z·················································Z - ·Y···············································Y· - ··X·············································X·· - ···W···········································W··· - ····V·········································V···· - ·····U·······································U····· - ······T·····································T······ - ·······S···································S······· - ········R·································R········ - ·········Q·······························Q········· - ··········P·····························P·········· - ···········O···························O··········· - ············N·························N············ - ·············M·······················M············· - ··············L·····················L·············· - ···············K···················K··············· - ················J·················J················ - ·················I···············I················· - ··················H·············H·················· - ···················G···········G··················· - ····················F·········F···················· - ·····················E·······E····················· - ······················D·····D······················ - ·······················C···C······················· - ························B·B························ - ·························A························· - """ - |> Str.replace_each("·", " ") - result == expected +expect { + result = diamond('Z') + expected = + \\·························A························· + \\························B·B························ + \\·······················C···C······················· + \\······················D·····D······················ + \\·····················E·······E····················· + \\····················F·········F···················· + \\···················G···········G··················· + \\··················H·············H·················· + \\·················I···············I················· + \\················J·················J················ + \\···············K···················K··············· + \\··············L·····················L·············· + \\·············M·······················M············· + \\············N·························N············ + \\···········O···························O··········· + \\··········P·····························P·········· + \\·········Q·······························Q········· + \\········R·································R········ + \\·······S···································S······· + \\······T·····································T······ + \\·····U·······································U····· + \\····V·········································V···· + \\···W···········································W··· + \\··X·············································X·· + \\·Y···············································Y· + \\Z·················································Z + \\·Y···············································Y· + \\··X·············································X·· + \\···W···········································W··· + \\····V·········································V···· + \\·····U·······································U····· + \\······T·····································T······ + \\·······S···································S······· + \\········R·································R········ + \\·········Q·······························Q········· + \\··········P·····························P·········· + \\···········O···························O··········· + \\············N·························N············ + \\·············M·······················M············· + \\··············L·····················L·············· + \\···············K···················K··············· + \\················J·················J················ + \\·················I···············I················· + \\··················H·············H·················· + \\···················G···········G··················· + \\····················F·········F···················· + \\·····················E·······E····················· + \\······················D·····D······················ + \\·······················C···C······················· + \\························B·B························ + \\·························A························· + .replace_each( + "·", + " ", + ) + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/difference-of-squares/.meta/template.j2 b/exercises/practice/difference-of-squares/.meta/template.j2 index f681252e..99af89c3 100644 --- a/exercises/practice/difference-of-squares/.meta/template.j2 +++ b/exercises/practice/difference-of-squares/.meta/template.j2 @@ -11,9 +11,10 @@ import DifferenceOfSquares exposing [square_of_sum, sum_of_squares, difference_o {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["number"] }}) result == {{ case["expected"] }} +} {% endfor %} {% endfor %} diff --git a/exercises/practice/difference-of-squares/difference-of-squares-test.roc b/exercises/practice/difference-of-squares/difference-of-squares-test.roc index 020e8d09..4681fa2b 100644 --- a/exercises/practice/difference-of-squares/difference-of-squares-test.roc +++ b/exercises/practice/difference-of-squares/difference-of-squares-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/difference-of-squares/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import DifferenceOfSquares exposing [square_of_sum, sum_of_squares, difference_of_squares] @@ -17,55 +9,68 @@ import DifferenceOfSquares exposing [square_of_sum, sum_of_squares, difference_o ## # square of sum 1 -expect - result = square_of_sum(1) - result == 1 +expect { + result = square_of_sum(1) + result == 1 +} # square of sum 5 -expect - result = square_of_sum(5) - result == 225 +expect { + result = square_of_sum(5) + result == 225 +} # square of sum 100 -expect - result = square_of_sum(100) - result == 25502500 +expect { + result = square_of_sum(100) + result == 25502500 +} ## ## Sum the squares of the numbers up to the given number ## # sum of squares 1 -expect - result = sum_of_squares(1) - result == 1 +expect { + result = sum_of_squares(1) + result == 1 +} # sum of squares 5 -expect - result = sum_of_squares(5) - result == 55 +expect { + result = sum_of_squares(5) + result == 55 +} # sum of squares 100 -expect - result = sum_of_squares(100) - result == 338350 +expect { + result = sum_of_squares(100) + result == 338350 +} ## ## Subtract sum of squares from square of sums ## # difference of squares 1 -expect - result = difference_of_squares(1) - result == 0 +expect { + result = difference_of_squares(1) + result == 0 +} # difference of squares 5 -expect - result = difference_of_squares(5) - result == 170 +expect { + result = difference_of_squares(5) + result == 170 +} # difference of squares 100 -expect - result = difference_of_squares(100) - result == 25164150 +expect { + result = difference_of_squares(100) + result == 25164150 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/dominoes/.meta/template.j2 b/exercises/practice/dominoes/.meta/template.j2 index 0243af71..fbee614b 100644 --- a/exercises/practice/dominoes/.meta/template.j2 +++ b/exercises/practice/dominoes/.meta/template.j2 @@ -2,7 +2,7 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [find_chain] +import {{ exercise | to_pascal }} exposing [Domino, find_chain] {% macro to_list_of_pairs(dominoes) -%} [ @@ -12,52 +12,62 @@ import {{ exercise | to_pascal }} exposing [find_chain] ] {%- endmacro %} -Domino : (U8, U8) - ## Rotate each domino if needed to ensure that the small side is on the left -canonicalize : List Domino -> List Domino -canonicalize = |dominoes| +canonicalize : List(Domino) -> List(Domino) +canonicalize = |dominoes| { dominoes - |> List.map(|domino| - if domino.0 > domino.1 then (domino.1, domino.0) else domino - ) + .map(|domino| { + if domino.0 > domino.1 (domino.1, domino.0) else domino + }) +} ## Ensure that the given result is Ok and is a valid chain for the ## given list of dominoes -is_valid_chain_for : Result (List Domino) _, List Domino -> Bool -is_valid_chain_for = |maybe_chain, dominoes| - when maybe_chain is - Err(_) -> Bool.false - Ok(chain) -> - if Set.from_list(canonicalize(chain)) == Set.from_list(canonicalize(dominoes)) then - when chain is - [] -> Bool.true - [.., last] -> +is_valid_chain_for : Try(List(Domino), _), List(Domino) -> Bool +is_valid_chain_for = |maybe_chain, dominoes| { + match maybe_chain { + Err(_) => Bool.False + Ok(chain) => { + if Set.from_list(canonicalize(chain)) == Set.from_list(canonicalize(dominoes)) { + match chain { + [] => Bool.True + [.., last] => { chain - |> List.walk_until( + .fold_until( Ok(last), - |state, domino| - when state is - Err(InvalidChain) -> crash "Unreachable" - Ok(previous) -> - if previous.1 == domino.0 then + |state, domino| { + match state { + Err(InvalidChain) => { crash "Unreachable" } + Ok(previous) => { + if previous.1 == domino.0 { Continue(Ok(domino)) - else + } else { Break(Err(InvalidChain)) + } + } + } + } ) - |> Result.is_ok - else - Bool.false + .is_ok() + } + } + } else { + Bool.False + } + } + } +} {% for case in cases -%} # {{ case["description"] }} -expect - result = find_chain({{ to_list_of_pairs(case["input"]["dominoes"]) }}) +expect { + result = Domino.find_chain({{ to_list_of_pairs(case["input"]["dominoes"]) }}) {%- if case["expected"] %} - result |> is_valid_chain_for({{ to_list_of_pairs(case["input"]["dominoes"]) }}) + result -> is_valid_chain_for({{ to_list_of_pairs(case["input"]["dominoes"]) }}) {%- else %} - result |> Result.is_err + result.is_err() {%- endif %} +} {% endfor %} diff --git a/exercises/practice/dominoes/dominoes-test.roc b/exercises/practice/dominoes/dominoes-test.roc index bffbeb94..a3543c99 100644 --- a/exercises/practice/dominoes/dominoes-test.roc +++ b/exercises/practice/dominoes/dominoes-test.roc @@ -1,117 +1,266 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/dominoes/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 -import Dominoes exposing [find_chain] - -Domino : (U8, U8) +import Dominoes exposing [Domino, find_chain] ## Rotate each domino if needed to ensure that the small side is on the left -canonicalize : List Domino -> List Domino -canonicalize = |dominoes| - dominoes - |> List.map( - |domino| - if domino.0 > domino.1 then (domino.1, domino.0) else domino, - ) +canonicalize : List(Domino) -> List(Domino) +canonicalize = |dominoes| { + dominoes + .map( + |domino| { + if domino.0 > domino.1 (domino.1, domino.0) else domino + }, + ) +} ## Ensure that the given result is Ok and is a valid chain for the ## given list of dominoes -is_valid_chain_for : Result (List Domino) _, List Domino -> Bool -is_valid_chain_for = |maybe_chain, dominoes| - when maybe_chain is - Err(_) -> Bool.false - Ok(chain) -> - if Set.from_list(canonicalize(chain)) == Set.from_list(canonicalize(dominoes)) then - when chain is - [] -> Bool.true - [.., last] -> - chain - |> List.walk_until( - Ok(last), - |state, domino| - when state is - Err(InvalidChain) -> crash "Unreachable" - Ok(previous) -> - if previous.1 == domino.0 then - Continue(Ok(domino)) - else - Break(Err(InvalidChain)), - ) - |> Result.is_ok - else - Bool.false +is_valid_chain_for : Try(List(Domino), _), List(Domino) -> Bool +is_valid_chain_for = |maybe_chain, dominoes| { + match maybe_chain { + Err(_) => Bool.False + Ok(chain) => { + if Set.from_list(canonicalize(chain)) == Set.from_list(canonicalize(dominoes)) { + match chain { + [] => Bool.True + [.., last] => { + chain + .fold_until( + Ok(last), + |state, domino| { + match state { + Err(InvalidChain) => { + crash "Unreachable" + } + Ok(previous) => { + if previous.1 == domino.0 { + Continue(Ok(domino)) + } else { + Break(Err(InvalidChain)) + } + } + } + }, + ) + .is_ok() + } + } + } else { + Bool.False + } + } + } +} # empty input = empty output -expect - result = find_chain([]) - result |> is_valid_chain_for([]) +expect { + result = Domino.find_chain([]) + result->is_valid_chain_for([]) +} # singleton input = singleton output -expect - result = find_chain([(1, 1)]) - result |> is_valid_chain_for([(1, 1)]) +expect { + result = Domino.find_chain( + [ + (1, 1), + ], + ) + result->is_valid_chain_for( + [ + (1, 1), + ], + ) +} # singleton that can't be chained -expect - result = find_chain([(1, 2)]) - result |> Result.is_err +expect { + result = Domino.find_chain( + [ + (1, 2), + ], + ) + result.is_err() +} # three elements -expect - result = find_chain([(1, 2), (3, 1), (2, 3)]) - result |> is_valid_chain_for([(1, 2), (3, 1), (2, 3)]) +expect { + result = Domino.find_chain( + [ + (1, 2), + (3, 1), + (2, 3), + ], + ) + result->is_valid_chain_for( + [ + (1, 2), + (3, 1), + (2, 3), + ], + ) +} # can reverse dominoes -expect - result = find_chain([(1, 2), (1, 3), (2, 3)]) - result |> is_valid_chain_for([(1, 2), (1, 3), (2, 3)]) +expect { + result = Domino.find_chain( + [ + (1, 2), + (1, 3), + (2, 3), + ], + ) + result->is_valid_chain_for( + [ + (1, 2), + (1, 3), + (2, 3), + ], + ) +} # can't be chained -expect - result = find_chain([(1, 2), (4, 1), (2, 3)]) - result |> Result.is_err +expect { + result = Domino.find_chain( + [ + (1, 2), + (4, 1), + (2, 3), + ], + ) + result.is_err() +} # disconnected - simple -expect - result = find_chain([(1, 1), (2, 2)]) - result |> Result.is_err +expect { + result = Domino.find_chain( + [ + (1, 1), + (2, 2), + ], + ) + result.is_err() +} # disconnected - double loop -expect - result = find_chain([(1, 2), (2, 1), (3, 4), (4, 3)]) - result |> Result.is_err +expect { + result = Domino.find_chain( + [ + (1, 2), + (2, 1), + (3, 4), + (4, 3), + ], + ) + result.is_err() +} # disconnected - single isolated -expect - result = find_chain([(1, 2), (2, 3), (3, 1), (4, 4)]) - result |> Result.is_err +expect { + result = Domino.find_chain( + [ + (1, 2), + (2, 3), + (3, 1), + (4, 4), + ], + ) + result.is_err() +} # need backtrack -expect - result = find_chain([(1, 2), (2, 3), (3, 1), (2, 4), (2, 4)]) - result |> is_valid_chain_for([(1, 2), (2, 3), (3, 1), (2, 4), (2, 4)]) +expect { + result = Domino.find_chain( + [ + (1, 2), + (2, 3), + (3, 1), + (2, 4), + (2, 4), + ], + ) + result->is_valid_chain_for( + [ + (1, 2), + (2, 3), + (3, 1), + (2, 4), + (2, 4), + ], + ) +} # separate loops -expect - result = find_chain([(1, 2), (2, 3), (3, 1), (1, 1), (2, 2), (3, 3)]) - result |> is_valid_chain_for([(1, 2), (2, 3), (3, 1), (1, 1), (2, 2), (3, 3)]) +expect { + result = Domino.find_chain( + [ + (1, 2), + (2, 3), + (3, 1), + (1, 1), + (2, 2), + (3, 3), + ], + ) + result->is_valid_chain_for( + [ + (1, 2), + (2, 3), + (3, 1), + (1, 1), + (2, 2), + (3, 3), + ], + ) +} # nine elements -expect - result = find_chain([(1, 2), (5, 3), (3, 1), (1, 2), (2, 4), (1, 6), (2, 3), (3, 4), (5, 6)]) - result |> is_valid_chain_for([(1, 2), (5, 3), (3, 1), (1, 2), (2, 4), (1, 6), (2, 3), (3, 4), (5, 6)]) +expect { + result = Domino.find_chain( + [ + (1, 2), + (5, 3), + (3, 1), + (1, 2), + (2, 4), + (1, 6), + (2, 3), + (3, 4), + (5, 6), + ], + ) + result->is_valid_chain_for( + [ + (1, 2), + (5, 3), + (3, 1), + (1, 2), + (2, 4), + (1, 6), + (2, 3), + (3, 4), + (5, 6), + ], + ) +} # separate three-domino loops -expect - result = find_chain([(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]) - result |> Result.is_err +expect { + result = Domino.find_chain( + [ + (1, 2), + (2, 3), + (3, 1), + (4, 5), + (5, 6), + (6, 4), + ], + ) + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/eliuds-eggs/.meta/template.j2 b/exercises/practice/eliuds-eggs/.meta/template.j2 index 9d1138a9..d21aa9e8 100644 --- a/exercises/practice/eliuds-eggs/.meta/template.j2 +++ b/exercises/practice/eliuds-eggs/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["number"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/eliuds-eggs/eliuds-eggs-test.roc b/exercises/practice/eliuds-eggs/eliuds-eggs-test.roc index 50ba292c..447ddb26 100644 --- a/exercises/practice/eliuds-eggs/eliuds-eggs-test.roc +++ b/exercises/practice/eliuds-eggs/eliuds-eggs-test.roc @@ -1,34 +1,34 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/eliuds-eggs/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import EliudsEggs exposing [egg_count] # 0 eggs -expect - result = egg_count(0) - result == 0 +expect { + result = egg_count(0) + result == 0 +} # 1 egg -expect - result = egg_count(16) - result == 1 +expect { + result = egg_count(16) + result == 1 +} # 4 eggs -expect - result = egg_count(89) - result == 4 +expect { + result = egg_count(89) + result == 4 +} # 13 eggs -expect - result = egg_count(2000000000) - result == 13 +expect { + result = egg_count(2000000000) + result == 13 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/error-handling/error-handling-test.roc b/exercises/practice/error-handling/error-handling-test.roc index f6bb817d..ffe9217a 100644 --- a/exercises/practice/error-handling/error-handling-test.roc +++ b/exercises/practice/error-handling/error-handling-test.roc @@ -1,12 +1,4 @@ -# File last updated on 2024-09-12 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import ErrorHandling exposing [get_user, parse_user_id, get_page, error_message] @@ -15,24 +7,28 @@ import ErrorHandling exposing [get_user, parse_user_id, get_page, error_message] ## # get_user 123 should return Alice -expect - result = get_user(123) - result == Ok({ name: "Alice" }) +expect { + result = get_user(123) + result == Ok({ name: "Alice" }) +} # get_user 456 should return Bob -expect - result = get_user(456) - result == Ok({ name: "Bob" }) +expect { + result = get_user(456) + result == Ok({ name: "Bob" }) +} # get_user 789 should return Charlie -expect - result = get_user(789) - result == Ok({ name: "Charlie" }) +expect { + result = get_user(789) + result == Ok({ name: "Charlie" }) +} # get_user 42 should return an error -expect - result = get_user(42) - result == Err(UserNotFound(42)) +expect { + result = get_user(42) + result == Err(UserNotFound(42)) +} ## ## parse_user_id should parse a string to a positive integer and return @@ -40,39 +36,46 @@ expect ## # Parsing a valid userId should return Ok -expect - result = parse_user_id("123") - result == Ok(123) +expect { + result = parse_user_id("123") + result == Ok(123) +} # Parsing an empty string should fail -expect - result = parse_user_id("") - result == Err(InvalidUserId("")) +expect { + result = parse_user_id("") + result == Err(InvalidUserId("")) +} # Parsing a negative number should fail -expect - result = parse_user_id("-123") - result == Err(InvalidUserId("-123")) +expect { + result = parse_user_id("-123") + result == Err(InvalidUserId("-123")) +} # Parsing a fractional number should fail -expect - result = parse_user_id("123.456") - result == Err(InvalidUserId("123.456")) +expect { + result = parse_user_id("123.456") + result == Err(InvalidUserId("123.456")) +} # Parsing a number in scientific format should fail -expect - result = parse_user_id("1e03") - result == Err(InvalidUserId("1e03")) +expect { + result = parse_user_id("1e03") + result == Err(InvalidUserId("1e03")) +} # Parsing a string containing letters should fail -expect - result = parse_user_id("abc") - result == Err(InvalidUserId("abc")) +expect { + result = parse_user_id("abc") + result == Err(InvalidUserId("abc")) +} # Parsing a string containing a valid userId followed by junk should fail -expect - result = parse_user_id("123 abc") - result == Err(InvalidUserId("123 abc")) +expect { + result = parse_user_id("123 abc") + result == Err(InvalidUserId("123 abc")) +} ## ## get_page should return Ok with the desired page if it exists @@ -80,54 +83,64 @@ expect ## # No error for root URL -expect - result = get_page("https://example.com/") - result == Ok("Home page") +expect { + result = get_page("https://example.com/") + result == Ok("Home page") +} # No error for users URL -expect - result = get_page("https://example.com/users/") - result == Ok("Users page") +expect { + result = get_page("https://example.com/users/") + result == Ok("Users page") +} # No error for specific user URL -expect - result = get_page("https://example.com/users/123") - result == Ok("Alice's page") +expect { + result = get_page("https://example.com/users/123") + result == Ok("Alice's page") +} # No error for specific user URL -expect - result = get_page("https://example.com/users/456") - result == Ok("Bob's page") +expect { + result = get_page("https://example.com/users/456") + result == Ok("Bob's page") +} # No error for specific user URL -expect - result = get_page("https://example.com/users/789") - result == Ok("Charlie's page") +expect { + result = get_page("https://example.com/users/789") + result == Ok("Charlie's page") +} # Error: insecure connection -expect - result = get_page("http://example.com/users/789") - result == Err(InsecureConnection("http://example.com/users/789")) +expect { + result = get_page("http://example.com/users/789") + result == Err(InsecureConnection("http://example.com/users/789")) +} # Error: invalid domain name -expect - result = get_page("https://google.com/wrong") - result == Err(InvalidDomain("https://google.com/wrong")) +expect { + result = get_page("https://google.com/wrong") + result == Err(InvalidDomain("https://google.com/wrong")) +} # Error: page not found -expect - result = get_page("https://example.com/oops") - result == Err(PageNotFound("/oops")) +expect { + result = get_page("https://example.com/oops") + result == Err(PageNotFound("/oops")) +} # Error: invalid userId -expect - result = get_page("https://example.com/users/abc") - result == Err(InvalidUserId("abc")) +expect { + result = get_page("https://example.com/users/abc") + result == Err(InvalidUserId("abc")) +} # Error: user not found -expect - result = get_page("https://example.com/users/42") - result == Err(UserNotFound(42)) +expect { + result = get_page("https://example.com/users/42") + result == Err(UserNotFound(42)) +} ## ## Handle errors and return a clear message to the user, in the user's language @@ -136,64 +149,90 @@ expect ## # No error for root URL: just return the Ok result -expect - page_result = get_page("https://example.com/") - result = page_result |> Result.map_err(|err| err |> error_message(English)) - result == Ok("Home page") +expect { + page_result = get_page("https://example.com/") + result = page_result? + |err| err->error_message(English) + result == Ok("Home page") +} # No error for users URL: just return the Ok result -expect - page_result = get_page("https://example.com/users/") - result = page_result |> Result.map_err(|err| err |> error_message(English)) - result == Ok("Users page") +expect { + page_result = get_page("https://example.com/users/") + result = page_result? + |err| err->error_message(English) + result == Ok("Users page") +} # No error for specific user URL: just return the Ok result -expect - page_result = get_page("https://example.com/users/123") - result = page_result |> Result.map_err(|err| err |> error_message(English)) - result == Ok("Alice's page") +expect { + page_result = get_page("https://example.com/users/123") + result = page_result? + |err| err->error_message(English) + result == Ok("Alice's page") +} # No error for specific user URL: just return the Ok result -expect - page_result = get_page("https://example.com/users/456") - result = page_result |> Result.map_err(|err| err |> error_message(English)) - result == Ok("Bob's page") +expect { + page_result = get_page("https://example.com/users/456") + result = page_result? + |err| err->error_message(English) + result == Ok("Bob's page") +} # No error for specific user URL: just return the Ok result -expect - page_result = get_page("https://example.com/users/789") - result = page_result |> Result.map_err(|err| err |> error_message(English)) - result == Ok("Charlie's page") +expect { + page_result = get_page("https://example.com/users/789") + result = page_result? + |err| err->error_message(English) + result == Ok("Charlie's page") +} # Error: insecure connection # Note: instead of displaying an error message, the server could automatically + # redirect the user to the HTTPS URL. This is an example of a recoverable error # which would be easy to handle because the error payload is machine-friendly -expect - page_result = get_page("http://example.com/users/789") - result = page_result |> Result.map_err(|err| err |> error_message(English)) - result == Err("Insecure connection (non HTTPS): http://example.com/users/789") +expect { + page_result = get_page("http://example.com/users/789") + result = page_result? + |err| err->error_message(English) + result == Err("Insecure connection (non HTTPS): http://example.com/users/789") +} # Error: invalid domain name -expect - page_result = get_page("https://google.com/wrong") - result = page_result |> Result.map_err(|err| err |> error_message(English)) - result == Err("Invalid domain name: https://google.com/wrong") +expect { + page_result = get_page("https://google.com/wrong") + result = page_result? + |err| err->error_message(English) + result == Err("Invalid domain name: https://google.com/wrong") +} # Error: page not found -expect - page_result = get_page("https://example.com/oops") - result = page_result |> Result.map_err(|err| err |> error_message(English)) - result == Err("Page not found: /oops") +expect { + page_result = get_page("https://example.com/oops") + result = page_result? + |err| err->error_message(English) + result == Err("Page not found: /oops") +} # Error: invalid userId -expect - page_result = get_page("https://example.com/users/abc") - result = page_result |> Result.map_err(|err| err |> error_message(English)) - result == Err("User ID is not a positive integer: abc") +expect { + page_result = get_page("https://example.com/users/abc") + result = page_result? + |err| err->error_message(English) + result == Err("User ID is not a positive integer: abc") +} # Error: user not found -expect - page_result = get_page("https://example.com/users/42") - result = page_result |> Result.map_err(|err| err |> error_message(English)) - result == Err("User #42 was not found") +expect { + page_result = get_page("https://example.com/users/42") + result = page_result? + |err| err->error_message(English) + result == Err("User #42 was not found") +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/etl/.meta/template.j2 b/exercises/practice/etl/.meta/template.j2 index 6b3771ed..5d64e73a 100644 --- a/exercises/practice/etl/.meta/template.j2 +++ b/exercises/practice/etl/.meta/template.j2 @@ -6,7 +6,7 @@ import {{ exercise | to_pascal }} exposing [transform] {% for case in cases -%} # {{ case["description"] }} -expect +expect { legacy = Dict.from_list([ {%- for score, letters in case["input"]["legacy"].items() %} @@ -20,6 +20,7 @@ expect {%- endfor %} ]) transform(legacy) == expected +} {% endfor %} diff --git a/exercises/practice/etl/etl-test.roc b/exercises/practice/etl/etl-test.roc index e8cc3f08..f1d3ff2f 100644 --- a/exercises/practice/etl/etl-test.roc +++ b/exercises/practice/etl/etl-test.roc @@ -1,117 +1,117 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/etl/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Etl exposing [transform] # single letter -expect - legacy = - Dict.from_list( - [ - (1, ['A']), - ], - ) - expected = - Dict.from_list( - [ - ('a', 1), - ], - ) - transform(legacy) == expected +expect { + legacy = + Dict.from_list( + [ + (1, ['A']), + ], + ) + expected = + Dict.from_list( + [ + ('a', 1), + ], + ) + transform(legacy) == expected +} # single score with multiple letters -expect - legacy = - Dict.from_list( - [ - (1, ['A', 'E', 'I', 'O', 'U']), - ], - ) - expected = - Dict.from_list( - [ - ('a', 1), - ('e', 1), - ('i', 1), - ('o', 1), - ('u', 1), - ], - ) - transform(legacy) == expected +expect { + legacy = + Dict.from_list( + [ + (1, ['A', 'E', 'I', 'O', 'U']), + ], + ) + expected = + Dict.from_list( + [ + ('a', 1), + ('e', 1), + ('i', 1), + ('o', 1), + ('u', 1), + ], + ) + transform(legacy) == expected +} # multiple scores with multiple letters -expect - legacy = - Dict.from_list( - [ - (1, ['A', 'E']), - (2, ['D', 'G']), - ], - ) - expected = - Dict.from_list( - [ - ('a', 1), - ('d', 2), - ('e', 1), - ('g', 2), - ], - ) - transform(legacy) == expected +expect { + legacy = + Dict.from_list( + [ + (1, ['A', 'E']), + (2, ['D', 'G']), + ], + ) + expected = + Dict.from_list( + [ + ('a', 1), + ('d', 2), + ('e', 1), + ('g', 2), + ], + ) + transform(legacy) == expected +} # multiple scores with differing numbers of letters -expect - legacy = - Dict.from_list( - [ - (1, ['A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T']), - (2, ['D', 'G']), - (3, ['B', 'C', 'M', 'P']), - (4, ['F', 'H', 'V', 'W', 'Y']), - (5, ['K']), - (8, ['J', 'X']), - (10, ['Q', 'Z']), - ], - ) - expected = - Dict.from_list( - [ - ('a', 1), - ('b', 3), - ('c', 3), - ('d', 2), - ('e', 1), - ('f', 4), - ('g', 2), - ('h', 4), - ('i', 1), - ('j', 8), - ('k', 5), - ('l', 1), - ('m', 3), - ('n', 1), - ('o', 1), - ('p', 3), - ('q', 10), - ('r', 1), - ('s', 1), - ('t', 1), - ('u', 1), - ('v', 4), - ('w', 4), - ('x', 8), - ('y', 4), - ('z', 10), - ], - ) - transform(legacy) == expected +expect { + legacy = + Dict.from_list( + [ + (1, ['A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T']), + (2, ['D', 'G']), + (3, ['B', 'C', 'M', 'P']), + (4, ['F', 'H', 'V', 'W', 'Y']), + (5, ['K']), + (8, ['J', 'X']), + (10, ['Q', 'Z']), + ], + ) + expected = + Dict.from_list( + [ + ('a', 1), + ('b', 3), + ('c', 3), + ('d', 2), + ('e', 1), + ('f', 4), + ('g', 2), + ('h', 4), + ('i', 1), + ('j', 8), + ('k', 5), + ('l', 1), + ('m', 3), + ('n', 1), + ('o', 1), + ('p', 3), + ('q', 10), + ('r', 1), + ('s', 1), + ('t', 1), + ('u', 1), + ('v', 4), + ('w', 4), + ('x', 8), + ('y', 4), + ('z', 10), + ], + ) + transform(legacy) == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/flatten-array/.meta/template.j2 b/exercises/practice/flatten-array/.meta/template.j2 index fe4a2168..10318665 100644 --- a/exercises/practice/flatten-array/.meta/template.j2 +++ b/exercises/practice/flatten-array/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}(({{ plugins.to_nested(case["input"]["array"]) }})) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/flatten-array/flatten-array-test.roc b/exercises/practice/flatten-array/flatten-array-test.roc index 76197918..88364893 100644 --- a/exercises/practice/flatten-array/flatten-array-test.roc +++ b/exercises/practice/flatten-array/flatten-array-test.roc @@ -1,279 +1,306 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/flatten-array/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import FlattenArray exposing [flatten] # empty -expect - result = flatten( - NestedArray( - [ - ], - ), - ) - result == [] +expect { + result = flatten( + ( + NestedArray( + [], + ), + ), + ) + result == [] +} # no nesting -expect - result = flatten( - NestedArray( - [ - Value(0), - Value(1), - Value(2), - ], - ), - ) - result == [0, 1, 2] +expect { + result = flatten( + ( + NestedArray( + [ + Value(0), + Value(1), + Value(2), + ], + ), + ), + ) + result == [0, 1, 2] +} # flattens a nested array -expect - result = flatten( - NestedArray( - [ - NestedArray( - [ - NestedArray( - [ - ], - ), - ], - ), - ], - ), - ) - result == [] +expect { + result = flatten( + ( + NestedArray( + [ + NestedArray( + [ + NestedArray( + [], + ), + ], + ), + ], + ), + ), + ) + result == [] +} # flattens array with just integers present -expect - result = flatten( - NestedArray( - [ - Value(1), - NestedArray( - [ - Value(2), - Value(3), - Value(4), - Value(5), - Value(6), - Value(7), - ], - ), - Value(8), - ], - ), - ) - result == [1, 2, 3, 4, 5, 6, 7, 8] +expect { + result = flatten( + ( + NestedArray( + [ + Value(1), + NestedArray( + [ + Value(2), + Value(3), + Value(4), + Value(5), + Value(6), + Value(7), + ], + ), + Value(8), + ], + ), + ), + ) + result == [1, 2, 3, 4, 5, 6, 7, 8] +} # 5 level nesting -expect - result = flatten( - NestedArray( - [ - Value(0), - Value(2), - NestedArray( - [ - NestedArray( - [ - Value(2), - Value(3), - ], - ), - Value(8), - Value(100), - Value(4), - NestedArray( - [ - NestedArray( - [ - NestedArray( - [ - Value(50), - ], - ), - ], - ), - ], - ), - ], - ), - Value(-2), - ], - ), - ) - result == [0, 2, 2, 3, 8, 100, 4, 50, -2] +expect { + result = flatten( + ( + NestedArray( + [ + Value(0), + Value(2), + NestedArray( + [ + NestedArray( + [ + Value(2), + Value(3), + ], + ), + Value(8), + Value(100), + Value(4), + NestedArray( + [ + NestedArray( + [ + NestedArray( + [ + Value(50), + ], + ), + ], + ), + ], + ), + ], + ), + Value(-2), + ], + ), + ), + ) + result == [0, 2, 2, 3, 8, 100, 4, 50, -2] +} # 6 level nesting -expect - result = flatten( - NestedArray( - [ - Value(1), - NestedArray( - [ - Value(2), - NestedArray( - [ - NestedArray( - [ - Value(3), - ], - ), - ], - ), - NestedArray( - [ - Value(4), - NestedArray( - [ - NestedArray( - [ - Value(5), - ], - ), - ], - ), - ], - ), - Value(6), - Value(7), - ], - ), - Value(8), - ], - ), - ) - result == [1, 2, 3, 4, 5, 6, 7, 8] +expect { + result = flatten( + ( + NestedArray( + [ + Value(1), + NestedArray( + [ + Value(2), + NestedArray( + [ + NestedArray( + [ + Value(3), + ], + ), + ], + ), + NestedArray( + [ + Value(4), + NestedArray( + [ + NestedArray( + [ + Value(5), + ], + ), + ], + ), + ], + ), + Value(6), + Value(7), + ], + ), + Value(8), + ], + ), + ), + ) + result == [1, 2, 3, 4, 5, 6, 7, 8] +} # null values are omitted from the final result -expect - result = flatten( - NestedArray( - [ - Value(1), - Value(2), - Null, - ], - ), - ) - result == [1, 2] +expect { + result = flatten( + ( + NestedArray( + [ + Value(1), + Value(2), + Null, + ], + ), + ), + ) + result == [1, 2] +} # consecutive null values at the front of the array are omitted from the final result -expect - result = flatten( - NestedArray( - [ - Null, - Null, - Value(3), - ], - ), - ) - result == [3] +expect { + result = flatten( + ( + NestedArray( + [ + Null, + Null, + Value(3), + ], + ), + ), + ) + result == [3] +} # consecutive null values in the middle of the array are omitted from the final result -expect - result = flatten( - NestedArray( - [ - Value(1), - Null, - Null, - Value(4), - ], - ), - ) - result == [1, 4] +expect { + result = flatten( + ( + NestedArray( + [ + Value(1), + Null, + Null, + Value(4), + ], + ), + ), + ) + result == [1, 4] +} # 6 level nested array with null values -expect - result = flatten( - NestedArray( - [ - Value(0), - Value(2), - NestedArray( - [ - NestedArray( - [ - Value(2), - Value(3), - ], - ), - Value(8), - NestedArray( - [ - NestedArray( - [ - Value(100), - ], - ), - ], - ), - Null, - NestedArray( - [ - NestedArray( - [ - Null, - ], - ), - ], - ), - ], - ), - Value(-2), - ], - ), - ) - result == [0, 2, 2, 3, 8, 100, -2] +expect { + result = flatten( + ( + NestedArray( + [ + Value(0), + Value(2), + NestedArray( + [ + NestedArray( + [ + Value(2), + Value(3), + ], + ), + Value(8), + NestedArray( + [ + NestedArray( + [ + Value(100), + ], + ), + ], + ), + Null, + NestedArray( + [ + NestedArray( + [ + Null, + ], + ), + ], + ), + ], + ), + Value(-2), + ], + ), + ), + ) + result == [0, 2, 2, 3, 8, 100, -2] +} # all values in nested array are null -expect - result = flatten( - NestedArray( - [ - Null, - NestedArray( - [ - NestedArray( - [ - NestedArray( - [ - Null, - ], - ), - ], - ), - ], - ), - Null, - Null, - NestedArray( - [ - NestedArray( - [ - Null, - Null, - ], - ), - Null, - ], - ), - Null, - ], - ), - ) - result == [] +expect { + result = flatten( + ( + NestedArray( + [ + Null, + NestedArray( + [ + NestedArray( + [ + NestedArray( + [ + Null, + ], + ), + ], + ), + ], + ), + Null, + Null, + NestedArray( + [ + NestedArray( + [ + Null, + Null, + ], + ), + Null, + ], + ), + Null, + ], + ), + ), + ) + result == [] +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/flower-field/.meta/template.j2 b/exercises/practice/flower-field/.meta/template.j2 index 6e7080e6..9371ed57 100644 --- a/exercises/practice/flower-field/.meta/template.j2 +++ b/exercises/practice/flower-field/.meta/template.j2 @@ -6,11 +6,12 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect - garden = {{ case["input"]["garden"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }} |> Str.replace_each("·", " ") +expect { + garden = {{ case["input"]["garden"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") result = {{ case["property"] | to_snake }}(garden) - expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }} |> Str.replace_each("·", " ") + expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") result == expected +} {% endfor %} diff --git a/exercises/practice/flower-field/flower-field-test.roc b/exercises/practice/flower-field/flower-field-test.roc index d9cebab3..4591b85c 100644 --- a/exercises/practice/flower-field/flower-field-test.roc +++ b/exercises/practice/flower-field/flower-field-test.roc @@ -1,212 +1,236 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/flower-field/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import FlowerField exposing [annotate] # no rows -expect - garden = "" |> Str.replace_each("·", " ") - result = annotate(garden) - expected = "" |> Str.replace_each("·", " ") - result == expected +expect { + garden = "".replace_each("·", " ") + result = annotate(garden) + expected = "".replace_each("·", " ") + result == expected +} # no columns -expect - garden = "" |> Str.replace_each("·", " ") - result = annotate(garden) - expected = "" |> Str.replace_each("·", " ") - result == expected +expect { + garden = "".replace_each("·", " ") + result = annotate(garden) + expected = "".replace_each("·", " ") + result == expected +} # no flowers -expect - garden = - """ - ··· - ··· - ··· - """ - |> Str.replace_each("·", " ") - result = annotate(garden) - expected = - """ - ··· - ··· - ··· - """ - |> Str.replace_each("·", " ") - result == expected +expect { + garden = + \\··· + \\··· + \\··· + .replace_each( + "·", + " ", + ) + result = annotate(garden) + expected = + \\··· + \\··· + \\··· + .replace_each( + "·", + " ", + ) + result == expected +} # garden full of flowers -expect - garden = - """ - *** - *** - *** - """ - |> Str.replace_each("·", " ") - result = annotate(garden) - expected = - """ - *** - *** - *** - """ - |> Str.replace_each("·", " ") - result == expected +expect { + garden = + \\*** + \\*** + \\*** + .replace_each( + "·", + " ", + ) + result = annotate(garden) + expected = + \\*** + \\*** + \\*** + .replace_each( + "·", + " ", + ) + result == expected +} # flower surrounded by spaces -expect - garden = - """ - ··· - ·*· - ··· - """ - |> Str.replace_each("·", " ") - result = annotate(garden) - expected = - """ - 111 - 1*1 - 111 - """ - |> Str.replace_each("·", " ") - result == expected +expect { + garden = + \\··· + \\·*· + \\··· + .replace_each( + "·", + " ", + ) + result = annotate(garden) + expected = + \\111 + \\1*1 + \\111 + .replace_each( + "·", + " ", + ) + result == expected +} # space surrounded by flowers -expect - garden = - """ - *** - *·* - *** - """ - |> Str.replace_each("·", " ") - result = annotate(garden) - expected = - """ - *** - *8* - *** - """ - |> Str.replace_each("·", " ") - result == expected +expect { + garden = + \\*** + \\*·* + \\*** + .replace_each( + "·", + " ", + ) + result = annotate(garden) + expected = + \\*** + \\*8* + \\*** + .replace_each( + "·", + " ", + ) + result == expected +} # horizontal line -expect - garden = "·*·*·" |> Str.replace_each("·", " ") - result = annotate(garden) - expected = "1*2*1" |> Str.replace_each("·", " ") - result == expected +expect { + garden = "·*·*·".replace_each("·", " ") + result = annotate(garden) + expected = "1*2*1".replace_each("·", " ") + result == expected +} # horizontal line, flowers at edges -expect - garden = "*···*" |> Str.replace_each("·", " ") - result = annotate(garden) - expected = "*1·1*" |> Str.replace_each("·", " ") - result == expected +expect { + garden = "*···*".replace_each("·", " ") + result = annotate(garden) + expected = "*1·1*".replace_each("·", " ") + result == expected +} # vertical line -expect - garden = - """ - · - * - · - * - · - """ - |> Str.replace_each("·", " ") - result = annotate(garden) - expected = - """ - 1 - * - 2 - * - 1 - """ - |> Str.replace_each("·", " ") - result == expected +expect { + garden = + \\· + \\* + \\· + \\* + \\· + .replace_each( + "·", + " ", + ) + result = annotate(garden) + expected = + \\1 + \\* + \\2 + \\* + \\1 + .replace_each( + "·", + " ", + ) + result == expected +} # vertical line, flowers at edges -expect - garden = - """ - * - · - · - · - * - """ - |> Str.replace_each("·", " ") - result = annotate(garden) - expected = - """ - * - 1 - · - 1 - * - """ - |> Str.replace_each("·", " ") - result == expected +expect { + garden = + \\* + \\· + \\· + \\· + \\* + .replace_each( + "·", + " ", + ) + result = annotate(garden) + expected = + \\* + \\1 + \\· + \\1 + \\* + .replace_each( + "·", + " ", + ) + result == expected +} # cross -expect - garden = - """ - ··*·· - ··*·· - ***** - ··*·· - ··*·· - """ - |> Str.replace_each("·", " ") - result = annotate(garden) - expected = - """ - ·2*2· - 25*52 - ***** - 25*52 - ·2*2· - """ - |> Str.replace_each("·", " ") - result == expected +expect { + garden = + \\··*·· + \\··*·· + \\***** + \\··*·· + \\··*·· + .replace_each( + "·", + " ", + ) + result = annotate(garden) + expected = + \\·2*2· + \\25*52 + \\***** + \\25*52 + \\·2*2· + .replace_each( + "·", + " ", + ) + result == expected +} # large garden -expect - garden = - """ - ·*··*· - ··*··· - ····*· - ···*·* - ·*··*· - ······ - """ - |> Str.replace_each("·", " ") - result = annotate(garden) - expected = - """ - 1*22*1 - 12*322 - ·123*2 - 112*4* - 1*22*2 - 111111 - """ - |> Str.replace_each("·", " ") - result == expected +expect { + garden = + \\·*··*· + \\··*··· + \\····*· + \\···*·* + \\·*··*· + \\······ + .replace_each( + "·", + " ", + ) + result = annotate(garden) + expected = + \\1*22*1 + \\12*322 + \\·123*2 + \\112*4* + \\1*22*2 + \\111111 + .replace_each( + "·", + " ", + ) + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/food-chain/.meta/template.j2 b/exercises/practice/food-chain/.meta/template.j2 index 14d32089..ac745ddf 100644 --- a/exercises/practice/food-chain/.meta/template.j2 +++ b/exercises/practice/food-chain/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["startVerse"] | to_roc }}, {{ case["input"]["endVerse"] | to_roc }}) result == {{ case["expected"] | join('\n') | to_roc_multiline_string | indent(8) }} +} {% endfor %} diff --git a/exercises/practice/food-chain/food-chain-test.roc b/exercises/practice/food-chain/food-chain-test.roc index 508c1aa4..354c730f 100644 --- a/exercises/practice/food-chain/food-chain-test.roc +++ b/exercises/practice/food-chain/food-chain-test.roc @@ -1,200 +1,186 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/food-chain/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import FoodChain exposing [recite] # fly -expect - result = recite(1, 1) - result - == - """ - I know an old lady who swallowed a fly. - I don't know why she swallowed the fly. Perhaps she'll die. - """ +expect { + result = recite(1, 1) + result == + \\I know an old lady who swallowed a fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + +} # spider -expect - result = recite(2, 2) - result - == - """ - I know an old lady who swallowed a spider. - It wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - """ +expect { + result = recite(2, 2) + result == + \\I know an old lady who swallowed a spider. + \\It wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + +} # bird -expect - result = recite(3, 3) - result - == - """ - I know an old lady who swallowed a bird. - How absurd to swallow a bird! - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - """ +expect { + result = recite(3, 3) + result == + \\I know an old lady who swallowed a bird. + \\How absurd to swallow a bird! + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + +} # cat -expect - result = recite(4, 4) - result - == - """ - I know an old lady who swallowed a cat. - Imagine that, to swallow a cat! - She swallowed the cat to catch the bird. - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - """ +expect { + result = recite(4, 4) + result == + \\I know an old lady who swallowed a cat. + \\Imagine that, to swallow a cat! + \\She swallowed the cat to catch the bird. + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + +} # dog -expect - result = recite(5, 5) - result - == - """ - I know an old lady who swallowed a dog. - What a hog, to swallow a dog! - She swallowed the dog to catch the cat. - She swallowed the cat to catch the bird. - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - """ +expect { + result = recite(5, 5) + result == + \\I know an old lady who swallowed a dog. + \\What a hog, to swallow a dog! + \\She swallowed the dog to catch the cat. + \\She swallowed the cat to catch the bird. + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + +} # goat -expect - result = recite(6, 6) - result - == - """ - I know an old lady who swallowed a goat. - Just opened her throat and swallowed a goat! - She swallowed the goat to catch the dog. - She swallowed the dog to catch the cat. - She swallowed the cat to catch the bird. - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - """ +expect { + result = recite(6, 6) + result == + \\I know an old lady who swallowed a goat. + \\Just opened her throat and swallowed a goat! + \\She swallowed the goat to catch the dog. + \\She swallowed the dog to catch the cat. + \\She swallowed the cat to catch the bird. + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + +} # cow -expect - result = recite(7, 7) - result - == - """ - I know an old lady who swallowed a cow. - I don't know how she swallowed a cow! - She swallowed the cow to catch the goat. - She swallowed the goat to catch the dog. - She swallowed the dog to catch the cat. - She swallowed the cat to catch the bird. - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - """ +expect { + result = recite(7, 7) + result == + \\I know an old lady who swallowed a cow. + \\I don't know how she swallowed a cow! + \\She swallowed the cow to catch the goat. + \\She swallowed the goat to catch the dog. + \\She swallowed the dog to catch the cat. + \\She swallowed the cat to catch the bird. + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + +} # horse -expect - result = recite(8, 8) - result - == - """ - I know an old lady who swallowed a horse. - She's dead, of course! - """ +expect { + result = recite(8, 8) + result == + \\I know an old lady who swallowed a horse. + \\She's dead, of course! + +} # multiple verses -expect - result = recite(1, 3) - result - == - """ - I know an old lady who swallowed a fly. - I don't know why she swallowed the fly. Perhaps she'll die. - - I know an old lady who swallowed a spider. - It wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - - I know an old lady who swallowed a bird. - How absurd to swallow a bird! - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - """ +expect { + result = recite(1, 3) + result == + \\I know an old lady who swallowed a fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + \\ + \\I know an old lady who swallowed a spider. + \\It wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + \\ + \\I know an old lady who swallowed a bird. + \\How absurd to swallow a bird! + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + +} # full song -expect - result = recite(1, 8) - result - == - """ - I know an old lady who swallowed a fly. - I don't know why she swallowed the fly. Perhaps she'll die. - - I know an old lady who swallowed a spider. - It wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - - I know an old lady who swallowed a bird. - How absurd to swallow a bird! - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - - I know an old lady who swallowed a cat. - Imagine that, to swallow a cat! - She swallowed the cat to catch the bird. - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - - I know an old lady who swallowed a dog. - What a hog, to swallow a dog! - She swallowed the dog to catch the cat. - She swallowed the cat to catch the bird. - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - - I know an old lady who swallowed a goat. - Just opened her throat and swallowed a goat! - She swallowed the goat to catch the dog. - She swallowed the dog to catch the cat. - She swallowed the cat to catch the bird. - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - - I know an old lady who swallowed a cow. - I don't know how she swallowed a cow! - She swallowed the cow to catch the goat. - She swallowed the goat to catch the dog. - She swallowed the dog to catch the cat. - She swallowed the cat to catch the bird. - She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. - She swallowed the spider to catch the fly. - I don't know why she swallowed the fly. Perhaps she'll die. - - I know an old lady who swallowed a horse. - She's dead, of course! - """ +expect { + result = recite(1, 8) + result == + \\I know an old lady who swallowed a fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + \\ + \\I know an old lady who swallowed a spider. + \\It wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + \\ + \\I know an old lady who swallowed a bird. + \\How absurd to swallow a bird! + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + \\ + \\I know an old lady who swallowed a cat. + \\Imagine that, to swallow a cat! + \\She swallowed the cat to catch the bird. + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + \\ + \\I know an old lady who swallowed a dog. + \\What a hog, to swallow a dog! + \\She swallowed the dog to catch the cat. + \\She swallowed the cat to catch the bird. + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + \\ + \\I know an old lady who swallowed a goat. + \\Just opened her throat and swallowed a goat! + \\She swallowed the goat to catch the dog. + \\She swallowed the dog to catch the cat. + \\She swallowed the cat to catch the bird. + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + \\ + \\I know an old lady who swallowed a cow. + \\I don't know how she swallowed a cow! + \\She swallowed the cow to catch the goat. + \\She swallowed the goat to catch the dog. + \\She swallowed the dog to catch the cat. + \\She swallowed the cat to catch the bird. + \\She swallowed the bird to catch the spider that wriggled and jiggled and tickled inside her. + \\She swallowed the spider to catch the fly. + \\I don't know why she swallowed the fly. Perhaps she'll die. + \\ + \\I know an old lady who swallowed a horse. + \\She's dead, of course! +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/forth/.meta/template.j2 b/exercises/practice/forth/.meta/template.j2 index 01fb2912..297fa482 100644 --- a/exercises/practice/forth/.meta/template.j2 +++ b/exercises/practice/forth/.meta/template.j2 @@ -7,13 +7,16 @@ import Forth exposing [evaluate] {% for case in cases -%} {% for innerCase in case["cases"] %} # {{ case["description"] }}: {{ innerCase["description"] }} -expect - result = evaluate({{ innerCase["input"]["instructions"] | join('\n') | to_roc_multiline_string | indent(8) }}) +expect { + result = evaluate( + {{ innerCase["input"]["instructions"] | join('\n') | to_roc_multiline_string | indent(8) }} + ) {%- if innerCase["expected"]["error"] %} - Result.is_err(result) + result.is_err() {%- else %} result == Ok({{ innerCase["expected"] }}) {%- endif %} +} {% endfor %} {% endfor %} diff --git a/exercises/practice/forth/forth-test.roc b/exercises/practice/forth/forth-test.roc index a84e5540..a730a541 100644 --- a/exercises/practice/forth/forth-test.roc +++ b/exercises/practice/forth/forth-test.roc @@ -1,323 +1,466 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/forth/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Forth exposing [evaluate] # parsing and numbers: numbers just get pushed onto the stack -expect - result = evaluate("1 2 3 4 5") - result == Ok([1, 2, 3, 4, 5]) +expect { + result = evaluate( + "1 2 3 4 5", + ) + result == Ok([1, 2, 3, 4, 5]) +} # parsing and numbers: pushes negative numbers onto the stack -expect - result = evaluate("-1 -2 -3 -4 -5") - result == Ok([-1, -2, -3, -4, -5]) +expect { + result = evaluate( + "-1 -2 -3 -4 -5", + ) + result == Ok([-1, -2, -3, -4, -5]) +} # addition: can add two numbers -expect - result = evaluate("1 2 +") - result == Ok([3]) +expect { + result = evaluate( + "1 2 +", + ) + result == Ok([3]) +} # addition: errors if there is nothing on the stack -expect - result = evaluate("+") - Result.is_err(result) +expect { + result = evaluate( + "+", + ) + result.is_err() +} # addition: errors if there is only one value on the stack -expect - result = evaluate("1 +") - Result.is_err(result) +expect { + result = evaluate( + "1 +", + ) + result.is_err() +} # addition: more than two values on the stack -expect - result = evaluate("1 2 3 +") - result == Ok([1, 5]) +expect { + result = evaluate( + "1 2 3 +", + ) + result == Ok([1, 5]) +} # subtraction: can subtract two numbers -expect - result = evaluate("3 4 -") - result == Ok([-1]) +expect { + result = evaluate( + "3 4 -", + ) + result == Ok([-1]) +} # subtraction: errors if there is nothing on the stack -expect - result = evaluate("-") - Result.is_err(result) +expect { + result = evaluate( + "-", + ) + result.is_err() +} # subtraction: errors if there is only one value on the stack -expect - result = evaluate("1 -") - Result.is_err(result) +expect { + result = evaluate( + "1 -", + ) + result.is_err() +} # subtraction: more than two values on the stack -expect - result = evaluate("1 12 3 -") - result == Ok([1, 9]) +expect { + result = evaluate( + "1 12 3 -", + ) + result == Ok([1, 9]) +} # multiplication: can multiply two numbers -expect - result = evaluate("2 4 *") - result == Ok([8]) +expect { + result = evaluate( + "2 4 *", + ) + result == Ok([8]) +} # multiplication: errors if there is nothing on the stack -expect - result = evaluate("*") - Result.is_err(result) +expect { + result = evaluate( + "*", + ) + result.is_err() +} # multiplication: errors if there is only one value on the stack -expect - result = evaluate("1 *") - Result.is_err(result) +expect { + result = evaluate( + "1 *", + ) + result.is_err() +} # multiplication: more than two values on the stack -expect - result = evaluate("1 2 3 *") - result == Ok([1, 6]) +expect { + result = evaluate( + "1 2 3 *", + ) + result == Ok([1, 6]) +} # division: can divide two numbers -expect - result = evaluate("12 3 /") - result == Ok([4]) +expect { + result = evaluate( + "12 3 /", + ) + result == Ok([4]) +} # division: performs integer division -expect - result = evaluate("8 3 /") - result == Ok([2]) +expect { + result = evaluate( + "8 3 /", + ) + result == Ok([2]) +} # division: errors if dividing by zero -expect - result = evaluate("4 0 /") - Result.is_err(result) +expect { + result = evaluate( + "4 0 /", + ) + result.is_err() +} # division: errors if there is nothing on the stack -expect - result = evaluate("/") - Result.is_err(result) +expect { + result = evaluate( + "/", + ) + result.is_err() +} # division: errors if there is only one value on the stack -expect - result = evaluate("1 /") - Result.is_err(result) +expect { + result = evaluate( + "1 /", + ) + result.is_err() +} # division: more than two values on the stack -expect - result = evaluate("1 12 3 /") - result == Ok([1, 4]) +expect { + result = evaluate( + "1 12 3 /", + ) + result == Ok([1, 4]) +} # combined arithmetic: addition and subtraction -expect - result = evaluate("1 2 + 4 -") - result == Ok([-1]) +expect { + result = evaluate( + "1 2 + 4 -", + ) + result == Ok([-1]) +} # combined arithmetic: multiplication and division -expect - result = evaluate("2 4 * 3 /") - result == Ok([2]) +expect { + result = evaluate( + "2 4 * 3 /", + ) + result == Ok([2]) +} # combined arithmetic: multiplication and addition -expect - result = evaluate("1 3 4 * +") - result == Ok([13]) +expect { + result = evaluate( + "1 3 4 * +", + ) + result == Ok([13]) +} # combined arithmetic: addition and multiplication -expect - result = evaluate("1 3 4 + *") - result == Ok([7]) +expect { + result = evaluate( + "1 3 4 + *", + ) + result == Ok([7]) +} # dup: copies a value on the stack -expect - result = evaluate("1 dup") - result == Ok([1, 1]) +expect { + result = evaluate( + "1 dup", + ) + result == Ok([1, 1]) +} # dup: copies the top value on the stack -expect - result = evaluate("1 2 dup") - result == Ok([1, 2, 2]) +expect { + result = evaluate( + "1 2 dup", + ) + result == Ok([1, 2, 2]) +} # dup: errors if there is nothing on the stack -expect - result = evaluate("dup") - Result.is_err(result) +expect { + result = evaluate( + "dup", + ) + result.is_err() +} # drop: removes the top value on the stack if it is the only one -expect - result = evaluate("1 drop") - result == Ok([]) +expect { + result = evaluate( + "1 drop", + ) + result == Ok([]) +} # drop: removes the top value on the stack if it is not the only one -expect - result = evaluate("1 2 drop") - result == Ok([1]) +expect { + result = evaluate( + "1 2 drop", + ) + result == Ok([1]) +} # drop: errors if there is nothing on the stack -expect - result = evaluate("drop") - Result.is_err(result) +expect { + result = evaluate( + "drop", + ) + result.is_err() +} # swap: swaps the top two values on the stack if they are the only ones -expect - result = evaluate("1 2 swap") - result == Ok([2, 1]) +expect { + result = evaluate( + "1 2 swap", + ) + result == Ok([2, 1]) +} # swap: swaps the top two values on the stack if they are not the only ones -expect - result = evaluate("1 2 3 swap") - result == Ok([1, 3, 2]) +expect { + result = evaluate( + "1 2 3 swap", + ) + result == Ok([1, 3, 2]) +} # swap: errors if there is nothing on the stack -expect - result = evaluate("swap") - Result.is_err(result) +expect { + result = evaluate( + "swap", + ) + result.is_err() +} # swap: errors if there is only one value on the stack -expect - result = evaluate("1 swap") - Result.is_err(result) +expect { + result = evaluate( + "1 swap", + ) + result.is_err() +} # over: copies the second element if there are only two -expect - result = evaluate("1 2 over") - result == Ok([1, 2, 1]) +expect { + result = evaluate( + "1 2 over", + ) + result == Ok([1, 2, 1]) +} # over: copies the second element if there are more than two -expect - result = evaluate("1 2 3 over") - result == Ok([1, 2, 3, 2]) +expect { + result = evaluate( + "1 2 3 over", + ) + result == Ok([1, 2, 3, 2]) +} # over: errors if there is nothing on the stack -expect - result = evaluate("over") - Result.is_err(result) +expect { + result = evaluate( + "over", + ) + result.is_err() +} # over: errors if there is only one value on the stack -expect - result = evaluate("1 over") - Result.is_err(result) +expect { + result = evaluate( + "1 over", + ) + result.is_err() +} # user-defined words: can consist of built-in words -expect - result = evaluate( - """ - : dup-twice dup dup ; - 1 dup-twice - """, - ) - result == Ok([1, 1, 1]) +expect { + result = evaluate( + + \\: dup-twice dup dup ; + \\1 dup-twice + , + + ) + result == Ok([1, 1, 1]) +} # user-defined words: execute in the right order -expect - result = evaluate( - """ - : countup 1 2 3 ; - countup - """, - ) - result == Ok([1, 2, 3]) +expect { + result = evaluate( + + \\: countup 1 2 3 ; + \\countup + , + + ) + result == Ok([1, 2, 3]) +} # user-defined words: can override other user-defined words -expect - result = evaluate( - """ - : foo dup ; - : foo dup dup ; - 1 foo - """, - ) - result == Ok([1, 1, 1]) +expect { + result = evaluate( + + \\: foo dup ; + \\: foo dup dup ; + \\1 foo + , + + ) + result == Ok([1, 1, 1]) +} # user-defined words: can override built-in words -expect - result = evaluate( - """ - : swap dup ; - 1 swap - """, - ) - result == Ok([1, 1]) +expect { + result = evaluate( + + \\: swap dup ; + \\1 swap + , + + ) + result == Ok([1, 1]) +} # user-defined words: can override built-in operators -expect - result = evaluate( - """ - : + * ; - 3 4 + - """, - ) - result == Ok([12]) +expect { + result = evaluate( + + \\: + * ; + \\3 4 + + , + + ) + result == Ok([12]) +} # user-defined words: can use different words with the same name -expect - result = evaluate( - """ - : foo 5 ; - : bar foo ; - : foo 6 ; - bar foo - """, - ) - result == Ok([5, 6]) +expect { + result = evaluate( + + \\: foo 5 ; + \\: bar foo ; + \\: foo 6 ; + \\bar foo + , + + ) + result == Ok([5, 6]) +} # user-defined words: can define word that uses word with the same name -expect - result = evaluate( - """ - : foo 10 ; - : foo foo 1 + ; - foo - """, - ) - result == Ok([11]) +expect { + result = evaluate( + + \\: foo 10 ; + \\: foo foo 1 + ; + \\foo + , + + ) + result == Ok([11]) +} # user-defined words: errors if executing a non-existent word -expect - result = evaluate("foo") - Result.is_err(result) +expect { + result = evaluate( + "foo", + ) + result.is_err() +} # case-insensitivity: DUP is case-insensitive -expect - result = evaluate("1 DUP Dup dup") - result == Ok([1, 1, 1, 1]) +expect { + result = evaluate( + "1 DUP Dup dup", + ) + result == Ok([1, 1, 1, 1]) +} # case-insensitivity: DROP is case-insensitive -expect - result = evaluate("1 2 3 4 DROP Drop drop") - result == Ok([1]) +expect { + result = evaluate( + "1 2 3 4 DROP Drop drop", + ) + result == Ok([1]) +} # case-insensitivity: SWAP is case-insensitive -expect - result = evaluate("1 2 SWAP 3 Swap 4 swap") - result == Ok([2, 3, 4, 1]) +expect { + result = evaluate( + "1 2 SWAP 3 Swap 4 swap", + ) + result == Ok([2, 3, 4, 1]) +} # case-insensitivity: OVER is case-insensitive -expect - result = evaluate("1 2 OVER Over over") - result == Ok([1, 2, 1, 2, 1]) +expect { + result = evaluate( + "1 2 OVER Over over", + ) + result == Ok([1, 2, 1, 2, 1]) +} # case-insensitivity: user-defined words are case-insensitive -expect - result = evaluate( - """ - : foo dup ; - 1 FOO Foo foo - """, - ) - result == Ok([1, 1, 1, 1]) +expect { + result = evaluate( + + \\: foo dup ; + \\1 FOO Foo foo + , + + ) + result == Ok([1, 1, 1, 1]) +} # case-insensitivity: definitions are case-insensitive -expect - result = evaluate( - """ - : SWAP DUP Dup dup ; - 1 swap - """, - ) - result == Ok([1, 1, 1, 1]) +expect { + result = evaluate( + \\: SWAP DUP Dup dup ; + \\1 swap + , + + ) + result == Ok([1, 1, 1, 1]) +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/gigasecond/.meta/template.j2 b/exercises/practice/gigasecond/.meta/template.j2 index 869f3c67..42b4f7c8 100644 --- a/exercises/practice/gigasecond/.meta/template.j2 +++ b/exercises/practice/gigasecond/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [add] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["moment"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/gigasecond/gigasecond-test.roc b/exercises/practice/gigasecond/gigasecond-test.roc index 15dc918a..8bd74088 100644 --- a/exercises/practice/gigasecond/gigasecond-test.roc +++ b/exercises/practice/gigasecond/gigasecond-test.roc @@ -1,40 +1,46 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/gigasecond/canonical-data.json -# File last updated on 2025-09-15 +# File last updated on 2026-06-13 app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", - isodate: "https://github.com/imclerran/roc-isodate/releases/download/v0.6.2/73w_H-aSJNcWqtXvMG4JQw_HoaApMBLnE92XD4OcVGU.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", + isodate: "https://github.com/imclerran/roc-isodate/releases/download/v0.6.2/73w_H-aSJNcWqtXvMG4JQw_HoaApMBLnE92XD4OcVGU.tar.br", } import pf.Stdout -main! = |_args| - Stdout.line!("") - import Gigasecond exposing [add] # date only specification of time -expect - result = add("2011-04-25") - result == "2043-01-01T01:46:40" +expect { + result = add("2011-04-25") + result == "2043-01-01T01:46:40" +} # second test for date only specification of time -expect - result = add("1977-06-13") - result == "2009-02-19T01:46:40" +expect { + result = add("1977-06-13") + result == "2009-02-19T01:46:40" +} # third test for date only specification of time -expect - result = add("1959-07-19") - result == "1991-03-27T01:46:40" +expect { + result = add("1959-07-19") + result == "1991-03-27T01:46:40" +} # full time specified -expect - result = add("2015-01-24T22:00:00") - result == "2046-10-02T23:46:40" +expect { + result = add("2015-01-24T22:00:00") + result == "2046-10-02T23:46:40" +} # full time with day roll-over -expect - result = add("2015-01-24T23:59:59") - result == "2046-10-03T01:46:39" +expect { + result = add("2015-01-24T23:59:59") + result == "2046-10-03T01:46:39" +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/go-counting/.meta/template.j2 b/exercises/practice/go-counting/.meta/template.j2 index 628486fd..bdf36331 100644 --- a/exercises/practice/go-counting/.meta/template.j2 +++ b/exercises/practice/go-counting/.meta/template.j2 @@ -20,39 +20,43 @@ import {{ exercise | to_pascal }} exposing [territory, territories] ## comparing tags or records containing sets sometimes returns the wrong result ## depending on the internal order of the set data, so we have to unwrap the sets ## in order to compare them properly. -compare_territory = |maybe_result, maybe_expected| - when (maybe_result, maybe_expected) is - (Ok(result), Ok(expected)) -> result.owner == expected.owner && result.territory == expected.territory - _ -> Bool.false - -compare_territories = |maybe_result, maybe_expected| - when (maybe_result, maybe_expected) is - (Ok(result), Ok(expected)) -> result.black == expected.black && result.white == expected.white && result.none == expected.none - _ -> Bool.false +compare_territory = |maybe_result, maybe_expected| { + match (maybe_result, maybe_expected) { + (Ok(result), Ok(expected)) => result.owner == expected.owner and result.territory == expected.territory + _ => Bool.False + } +} + +compare_territories = |maybe_result, maybe_expected| { + match (maybe_result, maybe_expected) { + (Ok(result), Ok(expected)) => result.black == expected.black and result.white == expected.white and result.none == expected.none + _ => Bool.False + } +} {% for case in cases -%} # {{ case["description"] }} -expect - board = {{ case["input"]["board"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }} |> Str.replace_each("·", " ") - result = board |> {{ case["property"] | to_snake }} +expect { + board = {{ case["input"]["board"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") + result = board -> {{ case["property"] | to_snake }} {%- if case["property"] == "territory" %}({ x : {{ case["input"]["x"] }}, y : {{ case["input"]["y"] }} }){% endif %} {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- elif case["expected"]["owner"] %} expected = Ok({ owner: {{ case["expected"]["owner"] | to_pascal }}, territory: {{ to_territory(case["expected"]["territory"]) }}, }) - result |> compare_territory(expected) + result -> compare_territory(expected) {%- else %} expected = Ok({ black: {{ to_territory(case["expected"]["territoryBlack"]) }}, white: {{ to_territory(case["expected"]["territoryWhite"]) }}, none: {{ to_territory(case["expected"]["territoryNone"]) }}, }) - result |> compare_territories(expected) + result -> compare_territories(expected) {%- endif %} - +} {% endfor %} diff --git a/exercises/practice/go-counting/go-counting-test.roc b/exercises/practice/go-counting/go-counting-test.roc index 180ffb7d..92697e30 100644 --- a/exercises/practice/go-counting/go-counting-test.roc +++ b/exercises/practice/go-counting/go-counting-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/go-counting/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import GoCounting exposing [territory, territories] @@ -16,206 +8,224 @@ import GoCounting exposing [territory, territories] ## comparing tags or records containing sets sometimes returns the wrong result ## depending on the internal order of the set data, so we have to unwrap the sets ## in order to compare them properly. -compare_territory = |maybe_result, maybe_expected| - when (maybe_result, maybe_expected) is - (Ok(result), Ok(expected)) -> result.owner == expected.owner and result.territory == expected.territory - _ -> Bool.false +compare_territory = |maybe_result, maybe_expected| { + match (maybe_result, maybe_expected) { + (Ok(result), Ok(expected)) => result.owner == expected.owner and result.territory == expected.territory + _ => Bool.False + } +} -compare_territories = |maybe_result, maybe_expected| - when (maybe_result, maybe_expected) is - (Ok(result), Ok(expected)) -> result.black == expected.black and result.white == expected.white and result.none == expected.none - _ -> Bool.false +compare_territories = |maybe_result, maybe_expected| { + match (maybe_result, maybe_expected) { + (Ok(result), Ok(expected)) => result.black == expected.black and result.white == expected.white and result.none == expected.none + _ => Bool.False + } +} # Black corner territory on 5x5 board -expect - board = - """ - ··B·· - ·B·B· - B·W·B - ·W·W· - ··W·· - """ - |> Str.replace_each("·", " ") - result = board |> territory({ x: 0, y: 1 }) - expected = Ok( - { - owner: Black, - territory: Set.from_list( - [ - { x: 0, y: 0 }, - { x: 0, y: 1 }, - { x: 1, y: 0 }, - ], - ), - }, - ) - result |> compare_territory(expected) +expect { + board = + \\··B·· + \\·B·B· + \\B·W·B + \\·W·W· + \\··W·· + .replace_each( + "·", + " ", + ) + result = board->territory({ x: 0, y: 1 }) + expected = Ok( + { + owner: Black, + territory: Set.from_list( + [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + { x: 1, y: 0 }, + ], + ), + }, + ) + result->compare_territory(expected) +} # White center territory on 5x5 board -expect - board = - """ - ··B·· - ·B·B· - B·W·B - ·W·W· - ··W·· - """ - |> Str.replace_each("·", " ") - result = board |> territory({ x: 2, y: 3 }) - expected = Ok( - { - owner: White, - territory: Set.from_list( - [ - { x: 2, y: 3 }, - ], - ), - }, - ) - result |> compare_territory(expected) +expect { + board = + \\··B·· + \\·B·B· + \\B·W·B + \\·W·W· + \\··W·· + .replace_each( + "·", + " ", + ) + result = board->territory({ x: 2, y: 3 }) + expected = Ok( + { + owner: White, + territory: Set.from_list( + [ + { x: 2, y: 3 }, + ], + ), + }, + ) + result->compare_territory(expected) +} # Open corner territory on 5x5 board -expect - board = - """ - ··B·· - ·B·B· - B·W·B - ·W·W· - ··W·· - """ - |> Str.replace_each("·", " ") - result = board |> territory({ x: 1, y: 4 }) - expected = Ok( - { - owner: None, - territory: Set.from_list( - [ - { x: 0, y: 3 }, - { x: 0, y: 4 }, - { x: 1, y: 4 }, - ], - ), - }, - ) - result |> compare_territory(expected) +expect { + board = + \\··B·· + \\·B·B· + \\B·W·B + \\·W·W· + \\··W·· + .replace_each( + "·", + " ", + ) + result = board->territory({ x: 1, y: 4 }) + expected = Ok( + { + owner: None, + territory: Set.from_list( + [ + { x: 0, y: 3 }, + { x: 0, y: 4 }, + { x: 1, y: 4 }, + ], + ), + }, + ) + result->compare_territory(expected) +} # A stone and not a territory on 5x5 board -expect - board = - """ - ··B·· - ·B·B· - B·W·B - ·W·W· - ··W·· - """ - |> Str.replace_each("·", " ") - result = board |> territory({ x: 1, y: 1 }) - expected = Ok( - { - owner: None, - territory: Set.empty({}), - }, - ) - result |> compare_territory(expected) +expect { + board = + \\··B·· + \\·B·B· + \\B·W·B + \\·W·W· + \\··W·· + .replace_each( + "·", + " ", + ) + result = board->territory({ x: 1, y: 1 }) + expected = Ok( + { + owner: None, + territory: Set.empty({}), + }, + ) + result->compare_territory(expected) +} # Invalid because X is too high for 5x5 board -expect - board = - """ - ··B·· - ·B·B· - B·W·B - ·W·W· - ··W·· - """ - |> Str.replace_each("·", " ") - result = board |> territory({ x: 5, y: 1 }) - result |> Result.is_err +expect { + board = + \\··B·· + \\·B·B· + \\B·W·B + \\·W·W· + \\··W·· + .replace_each( + "·", + " ", + ) + result = board->territory({ x: 5, y: 1 }) + result.is_err() +} # Invalid because Y is too high for 5x5 board -expect - board = - """ - ··B·· - ·B·B· - B·W·B - ·W·W· - ··W·· - """ - |> Str.replace_each("·", " ") - result = board |> territory({ x: 1, y: 5 }) - result |> Result.is_err +expect { + board = + \\··B·· + \\·B·B· + \\B·W·B + \\·W·W· + \\··W·· + .replace_each( + "·", + " ", + ) + result = board->territory({ x: 1, y: 5 }) + result.is_err() +} # One territory is the whole board -expect - board = "·" |> Str.replace_each("·", " ") - result = board |> territories - expected = Ok( - { - black: Set.empty({}), - - white: Set.empty({}), - - none: Set.from_list( - [ - { x: 0, y: 0 }, - ], - ), - }, - ) - result |> compare_territories(expected) +expect { + board = "·".replace_each("·", " ") + result = board->territories() + expected = Ok( + { + black: Set.empty({}), + white: Set.empty({}), + none: Set.from_list( + [ + { x: 0, y: 0 }, + ], + ), + }, + ) + result->compare_territories(expected) +} # Two territory rectangular board -expect - board = - """ - ·BW· - ·BW· - """ - |> Str.replace_each("·", " ") - result = board |> territories - expected = Ok( - { - black: Set.from_list( - [ - { x: 0, y: 0 }, - { x: 0, y: 1 }, - ], - ), - - white: Set.from_list( - [ - { x: 3, y: 0 }, - { x: 3, y: 1 }, - ], - ), - - none: Set.empty({}), - }, - ) - result |> compare_territories(expected) +expect { + board = + \\·BW· + \\·BW· + .replace_each( + "·", + " ", + ) + result = board->territories() + expected = Ok( + { + black: Set.from_list( + [ + { x: 0, y: 0 }, + { x: 0, y: 1 }, + ], + ), + white: Set.from_list( + [ + { x: 3, y: 0 }, + { x: 3, y: 1 }, + ], + ), + none: Set.empty({}), + }, + ) + result->compare_territories(expected) +} # Two region rectangular board -expect - board = "·B·" |> Str.replace_each("·", " ") - result = board |> territories - expected = Ok( - { - black: Set.from_list( - [ - { x: 0, y: 0 }, - { x: 2, y: 0 }, - ], - ), - - white: Set.empty({}), - - none: Set.empty({}), - }, - ) - result |> compare_territories(expected) +expect { + board = "·B·".replace_each("·", " ") + result = board->territories() + expected = Ok( + { + black: Set.from_list( + [ + { x: 0, y: 0 }, + { x: 2, y: 0 }, + ], + ), + white: Set.empty({}), + none: Set.empty({}), + }, + ) + result->compare_territories(expected) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/grains/.meta/template.j2 b/exercises/practice/grains/.meta/template.j2 index 38d6ae83..a13a7973 100644 --- a/exercises/practice/grains/.meta/template.j2 +++ b/exercises/practice/grains/.meta/template.j2 @@ -9,19 +9,21 @@ import {{ exercise | to_pascal }} exposing [grains_on_square, total_grains] ## {{ case["description"] }} ## {% if case["property"] == "total" %} -expect +expect { result = total_grains result == {{ case["expected"] }} +} {% else %} {% for subcase in case["cases"] -%} # {{ subcase["description"] }} -expect +expect { result = grains_on_square({{ subcase["input"]["square"] }}) {%- if subcase["expected"]["error"] %} - Result.is_err(result) + result.is_err() {%- else %} result == Ok({{ subcase["expected"] }}) {%- endif %} +} {% endfor %} {%- endif -%} diff --git a/exercises/practice/grains/grains-test.roc b/exercises/practice/grains/grains-test.roc index 8525d86e..cb7419e7 100644 --- a/exercises/practice/grains/grains-test.roc +++ b/exercises/practice/grains/grains-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grains/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Grains exposing [grains_on_square, total_grains] @@ -17,54 +9,68 @@ import Grains exposing [grains_on_square, total_grains] ## # grains on square 1 -expect - result = grains_on_square(1) - result == Ok(1) +expect { + result = grains_on_square(1) + result == Ok(1) +} # grains on square 2 -expect - result = grains_on_square(2) - result == Ok(2) +expect { + result = grains_on_square(2) + result == Ok(2) +} # grains on square 3 -expect - result = grains_on_square(3) - result == Ok(4) +expect { + result = grains_on_square(3) + result == Ok(4) +} # grains on square 4 -expect - result = grains_on_square(4) - result == Ok(8) +expect { + result = grains_on_square(4) + result == Ok(8) +} # grains on square 16 -expect - result = grains_on_square(16) - result == Ok(32768) +expect { + result = grains_on_square(16) + result == Ok(32768) +} # grains on square 32 -expect - result = grains_on_square(32) - result == Ok(2147483648) +expect { + result = grains_on_square(32) + result == Ok(2147483648) +} # grains on square 64 -expect - result = grains_on_square(64) - result == Ok(9223372036854775808) +expect { + result = grains_on_square(64) + result == Ok(9223372036854775808) +} # square 0 is invalid -expect - result = grains_on_square(0) - Result.is_err(result) +expect { + result = grains_on_square(0) + result.is_err() +} # square greater than 64 is invalid -expect - result = grains_on_square(65) - Result.is_err(result) +expect { + result = grains_on_square(65) + result.is_err() +} ## ## returns the total number of grains on the board ## -expect - result = total_grains - result == 18446744073709551615 +expect { + result = total_grains + result == 18446744073709551615 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/grep/.meta/template.j2 b/exercises/practice/grep/.meta/template.j2 index 926603e5..2a5023dc 100644 --- a/exercises/practice/grep/.meta/template.j2 +++ b/exercises/practice/grep/.meta/template.j2 @@ -7,9 +7,10 @@ import {{ exercise | to_pascal }} exposing [grep] {% for case in cases -%} {% for innerCase in case["cases"] -%} # {{ case["description"] }} - {{ innerCase["description"] }} -expect +expect { result = {{ innerCase["property"] | to_snake }}({{ innerCase["input"]["pattern"] | to_roc }}, {{ innerCase["input"]["flags"] | to_roc }}, {{ innerCase["input"]["files"] | to_roc }}) result == Ok({{ innerCase["expected"] | join('\n') | to_roc_multiline_string | indent(8) }}) +} {% endfor %} {% endfor %} diff --git a/exercises/practice/grep/grep-test.roc b/exercises/practice/grep/grep-test.roc index d9cb6b88..c4896a5e 100644 --- a/exercises/practice/grep/grep-test.roc +++ b/exercises/practice/grep/grep-test.roc @@ -1,255 +1,252 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grep/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Grep exposing [grep] # Test grepping a single file - One file, one match, no flags -expect - result = grep("Agamemnon", [], ["iliad.txt"]) - result == Ok("Of Atreus, Agamemnon, King of men.") +expect { + result = grep("Agamemnon", [], ["iliad.txt"]) + result == Ok("Of Atreus, Agamemnon, King of men.") +} # Test grepping a single file - One file, one match, print line numbers flag -expect - result = grep("Forbidden", ["-n"], ["paradise-lost.txt"]) - result == Ok("2:Of that Forbidden Tree, whose mortal tast") +expect { + result = grep("Forbidden", ["-n"], ["paradise-lost.txt"]) + result == Ok("2:Of that Forbidden Tree, whose mortal tast") +} # Test grepping a single file - One file, one match, case-insensitive flag -expect - result = grep("FORBIDDEN", ["-i"], ["paradise-lost.txt"]) - result == Ok("Of that Forbidden Tree, whose mortal tast") +expect { + result = grep("FORBIDDEN", ["-i"], ["paradise-lost.txt"]) + result == Ok("Of that Forbidden Tree, whose mortal tast") +} # Test grepping a single file - One file, one match, print file names flag -expect - result = grep("Forbidden", ["-l"], ["paradise-lost.txt"]) - result == Ok("paradise-lost.txt") +expect { + result = grep("Forbidden", ["-l"], ["paradise-lost.txt"]) + result == Ok("paradise-lost.txt") +} # Test grepping a single file - One file, one match, match entire lines flag -expect - result = grep("With loss of Eden, till one greater Man", ["-x"], ["paradise-lost.txt"]) - result == Ok("With loss of Eden, till one greater Man") +expect { + result = grep("With loss of Eden, till one greater Man", ["-x"], ["paradise-lost.txt"]) + result == Ok("With loss of Eden, till one greater Man") +} # Test grepping a single file - One file, one match, multiple flags -expect - result = grep("OF ATREUS, Agamemnon, KIng of MEN.", ["-n", "-i", "-x"], ["iliad.txt"]) - result == Ok("9:Of Atreus, Agamemnon, King of men.") +expect { + result = grep("OF ATREUS, Agamemnon, KIng of MEN.", ["-n", "-i", "-x"], ["iliad.txt"]) + result == Ok("9:Of Atreus, Agamemnon, King of men.") +} # Test grepping a single file - One file, several matches, no flags -expect - result = grep("may", [], ["midsummer-night.txt"]) - result - == Ok( - """ - Nor how it may concern my modesty, - But I beseech your grace that I may know - The worst that may befall me in this case, - """, - ) +expect { + result = grep("may", [], ["midsummer-night.txt"]) + result == Ok( + \\Nor how it may concern my modesty, + \\But I beseech your grace that I may know + \\The worst that may befall me in this case, + , + ) +} # Test grepping a single file - One file, several matches, print line numbers flag -expect - result = grep("may", ["-n"], ["midsummer-night.txt"]) - result - == Ok( - """ - 3:Nor how it may concern my modesty, - 5:But I beseech your grace that I may know - 6:The worst that may befall me in this case, - """, - ) +expect { + result = grep("may", ["-n"], ["midsummer-night.txt"]) + result == Ok( + \\3:Nor how it may concern my modesty, + \\5:But I beseech your grace that I may know + \\6:The worst that may befall me in this case, + , + ) +} # Test grepping a single file - One file, several matches, match entire lines flag -expect - result = grep("may", ["-x"], ["midsummer-night.txt"]) - result == Ok("") +expect { + result = grep("may", ["-x"], ["midsummer-night.txt"]) + result == Ok("") +} # Test grepping a single file - One file, several matches, case-insensitive flag -expect - result = grep("ACHILLES", ["-i"], ["iliad.txt"]) - result - == Ok( - """ - Achilles sing, O Goddess! Peleus' son; - The noble Chief Achilles from the son - """, - ) +expect { + result = grep("ACHILLES", ["-i"], ["iliad.txt"]) + result == Ok( + \\Achilles sing, O Goddess! Peleus' son; + \\The noble Chief Achilles from the son + , + ) +} # Test grepping a single file - One file, several matches, inverted flag -expect - result = grep("Of", ["-v"], ["paradise-lost.txt"]) - result - == Ok( - """ - Brought Death into the World, and all our woe, - With loss of Eden, till one greater Man - Restore us, and regain the blissful Seat, - Sing Heav'nly Muse, that on the secret top - That Shepherd, who first taught the chosen Seed - """, - ) +expect { + result = grep("Of", ["-v"], ["paradise-lost.txt"]) + result == Ok( + \\Brought Death into the World, and all our woe, + \\With loss of Eden, till one greater Man + \\Restore us, and regain the blissful Seat, + \\Sing Heav'nly Muse, that on the secret top + \\That Shepherd, who first taught the chosen Seed + , + ) +} # Test grepping a single file - One file, no matches, various flags -expect - result = grep("Gandalf", ["-n", "-l", "-x", "-i"], ["iliad.txt"]) - result == Ok("") +expect { + result = grep("Gandalf", ["-n", "-l", "-x", "-i"], ["iliad.txt"]) + result == Ok("") +} # Test grepping a single file - One file, one match, file flag takes precedence over line flag -expect - result = grep("ten", ["-n", "-l"], ["iliad.txt"]) - result == Ok("iliad.txt") +expect { + result = grep("ten", ["-n", "-l"], ["iliad.txt"]) + result == Ok("iliad.txt") +} # Test grepping a single file - One file, several matches, inverted and match entire lines flags -expect - result = grep("Illustrious into Ades premature,", ["-x", "-v"], ["iliad.txt"]) - result - == Ok( - """ - Achilles sing, O Goddess! Peleus' son; - His wrath pernicious, who ten thousand woes - Caused to Achaia's host, sent many a soul - And Heroes gave (so stood the will of Jove) - To dogs and to all ravening fowls a prey, - When fierce dispute had separated once - The noble Chief Achilles from the son - Of Atreus, Agamemnon, King of men. - """, - ) +expect { + result = grep("Illustrious into Ades premature,", ["-x", "-v"], ["iliad.txt"]) + result == Ok( + \\Achilles sing, O Goddess! Peleus' son; + \\His wrath pernicious, who ten thousand woes + \\Caused to Achaia's host, sent many a soul + \\And Heroes gave (so stood the will of Jove) + \\To dogs and to all ravening fowls a prey, + \\When fierce dispute had separated once + \\The noble Chief Achilles from the son + \\Of Atreus, Agamemnon, King of men. + , + ) +} # Test grepping multiples files at once - Multiple files, one match, no flags -expect - result = grep("Agamemnon", [], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result == Ok("iliad.txt:Of Atreus, Agamemnon, King of men.") +expect { + result = grep("Agamemnon", [], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok("iliad.txt:Of Atreus, Agamemnon, King of men.") +} # Test grepping multiples files at once - Multiple files, several matches, no flags -expect - result = grep("may", [], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result - == Ok( - """ - midsummer-night.txt:Nor how it may concern my modesty, - midsummer-night.txt:But I beseech your grace that I may know - midsummer-night.txt:The worst that may befall me in this case, - """, - ) +expect { + result = grep("may", [], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok( + \\midsummer-night.txt:Nor how it may concern my modesty, + \\midsummer-night.txt:But I beseech your grace that I may know + \\midsummer-night.txt:The worst that may befall me in this case, + , + ) +} # Test grepping multiples files at once - Multiple files, several matches, print line numbers flag -expect - result = grep("that", ["-n"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result - == Ok( - """ - midsummer-night.txt:5:But I beseech your grace that I may know - midsummer-night.txt:6:The worst that may befall me in this case, - paradise-lost.txt:2:Of that Forbidden Tree, whose mortal tast - paradise-lost.txt:6:Sing Heav'nly Muse, that on the secret top - """, - ) +expect { + result = grep("that", ["-n"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok( + \\midsummer-night.txt:5:But I beseech your grace that I may know + \\midsummer-night.txt:6:The worst that may befall me in this case, + \\paradise-lost.txt:2:Of that Forbidden Tree, whose mortal tast + \\paradise-lost.txt:6:Sing Heav'nly Muse, that on the secret top + , + ) +} # Test grepping multiples files at once - Multiple files, one match, print file names flag -expect - result = grep("who", ["-l"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result - == Ok( - """ - iliad.txt - paradise-lost.txt - """, - ) +expect { + result = grep("who", ["-l"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok( + \\iliad.txt + \\paradise-lost.txt + , + ) +} # Test grepping multiples files at once - Multiple files, several matches, case-insensitive flag -expect - result = grep("TO", ["-i"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result - == Ok( - """ - iliad.txt:Caused to Achaia's host, sent many a soul - iliad.txt:Illustrious into Ades premature, - iliad.txt:And Heroes gave (so stood the will of Jove) - iliad.txt:To dogs and to all ravening fowls a prey, - midsummer-night.txt:I do entreat your grace to pardon me. - midsummer-night.txt:In such a presence here to plead my thoughts; - midsummer-night.txt:If I refuse to wed Demetrius. - paradise-lost.txt:Brought Death into the World, and all our woe, - paradise-lost.txt:Restore us, and regain the blissful Seat, - paradise-lost.txt:Sing Heav'nly Muse, that on the secret top - """, - ) +expect { + result = grep("TO", ["-i"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok( + \\iliad.txt:Caused to Achaia's host, sent many a soul + \\iliad.txt:Illustrious into Ades premature, + \\iliad.txt:And Heroes gave (so stood the will of Jove) + \\iliad.txt:To dogs and to all ravening fowls a prey, + \\midsummer-night.txt:I do entreat your grace to pardon me. + \\midsummer-night.txt:In such a presence here to plead my thoughts; + \\midsummer-night.txt:If I refuse to wed Demetrius. + \\paradise-lost.txt:Brought Death into the World, and all our woe, + \\paradise-lost.txt:Restore us, and regain the blissful Seat, + \\paradise-lost.txt:Sing Heav'nly Muse, that on the secret top + , + ) +} # Test grepping multiples files at once - Multiple files, several matches, inverted flag -expect - result = grep("a", ["-v"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result - == Ok( - """ - iliad.txt:Achilles sing, O Goddess! Peleus' son; - iliad.txt:The noble Chief Achilles from the son - midsummer-night.txt:If I refuse to wed Demetrius. - """, - ) +expect { + result = grep("a", ["-v"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok( + \\iliad.txt:Achilles sing, O Goddess! Peleus' son; + \\iliad.txt:The noble Chief Achilles from the son + \\midsummer-night.txt:If I refuse to wed Demetrius. + , + ) +} # Test grepping multiples files at once - Multiple files, one match, match entire lines flag -expect - result = grep("But I beseech your grace that I may know", ["-x"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result == Ok("midsummer-night.txt:But I beseech your grace that I may know") +expect { + result = grep("But I beseech your grace that I may know", ["-x"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok("midsummer-night.txt:But I beseech your grace that I may know") +} # Test grepping multiples files at once - Multiple files, one match, multiple flags -expect - result = grep("WITH LOSS OF EDEN, TILL ONE GREATER MAN", ["-n", "-i", "-x"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result == Ok("paradise-lost.txt:4:With loss of Eden, till one greater Man") +expect { + result = grep("WITH LOSS OF EDEN, TILL ONE GREATER MAN", ["-n", "-i", "-x"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok("paradise-lost.txt:4:With loss of Eden, till one greater Man") +} # Test grepping multiples files at once - Multiple files, no matches, various flags -expect - result = grep("Frodo", ["-n", "-l", "-x", "-i"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result == Ok("") +expect { + result = grep("Frodo", ["-n", "-l", "-x", "-i"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok("") +} # Test grepping multiples files at once - Multiple files, several matches, file flag takes precedence over line number flag -expect - result = grep("who", ["-n", "-l"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result - == Ok( - """ - iliad.txt - paradise-lost.txt - """, - ) +expect { + result = grep("who", ["-n", "-l"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok( + \\iliad.txt + \\paradise-lost.txt + , + ) +} # Test grepping multiples files at once - Multiple files, several matches, inverted and match entire lines flags -expect - result = grep("Illustrious into Ades premature,", ["-x", "-v"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) - result - == Ok( - """ - iliad.txt:Achilles sing, O Goddess! Peleus' son; - iliad.txt:His wrath pernicious, who ten thousand woes - iliad.txt:Caused to Achaia's host, sent many a soul - iliad.txt:And Heroes gave (so stood the will of Jove) - iliad.txt:To dogs and to all ravening fowls a prey, - iliad.txt:When fierce dispute had separated once - iliad.txt:The noble Chief Achilles from the son - iliad.txt:Of Atreus, Agamemnon, King of men. - midsummer-night.txt:I do entreat your grace to pardon me. - midsummer-night.txt:I know not by what power I am made bold, - midsummer-night.txt:Nor how it may concern my modesty, - midsummer-night.txt:In such a presence here to plead my thoughts; - midsummer-night.txt:But I beseech your grace that I may know - midsummer-night.txt:The worst that may befall me in this case, - midsummer-night.txt:If I refuse to wed Demetrius. - paradise-lost.txt:Of Mans First Disobedience, and the Fruit - paradise-lost.txt:Of that Forbidden Tree, whose mortal tast - paradise-lost.txt:Brought Death into the World, and all our woe, - paradise-lost.txt:With loss of Eden, till one greater Man - paradise-lost.txt:Restore us, and regain the blissful Seat, - paradise-lost.txt:Sing Heav'nly Muse, that on the secret top - paradise-lost.txt:Of Oreb, or of Sinai, didst inspire - paradise-lost.txt:That Shepherd, who first taught the chosen Seed - """, - ) +expect { + result = grep("Illustrious into Ades premature,", ["-x", "-v"], ["iliad.txt", "midsummer-night.txt", "paradise-lost.txt"]) + result == Ok( + \\iliad.txt:Achilles sing, O Goddess! Peleus' son; + \\iliad.txt:His wrath pernicious, who ten thousand woes + \\iliad.txt:Caused to Achaia's host, sent many a soul + \\iliad.txt:And Heroes gave (so stood the will of Jove) + \\iliad.txt:To dogs and to all ravening fowls a prey, + \\iliad.txt:When fierce dispute had separated once + \\iliad.txt:The noble Chief Achilles from the son + \\iliad.txt:Of Atreus, Agamemnon, King of men. + \\midsummer-night.txt:I do entreat your grace to pardon me. + \\midsummer-night.txt:I know not by what power I am made bold, + \\midsummer-night.txt:Nor how it may concern my modesty, + \\midsummer-night.txt:In such a presence here to plead my thoughts; + \\midsummer-night.txt:But I beseech your grace that I may know + \\midsummer-night.txt:The worst that may befall me in this case, + \\midsummer-night.txt:If I refuse to wed Demetrius. + \\paradise-lost.txt:Of Mans First Disobedience, and the Fruit + \\paradise-lost.txt:Of that Forbidden Tree, whose mortal tast + \\paradise-lost.txt:Brought Death into the World, and all our woe, + \\paradise-lost.txt:With loss of Eden, till one greater Man + \\paradise-lost.txt:Restore us, and regain the blissful Seat, + \\paradise-lost.txt:Sing Heav'nly Muse, that on the secret top + \\paradise-lost.txt:Of Oreb, or of Sinai, didst inspire + \\paradise-lost.txt:That Shepherd, who first taught the chosen Seed + , + ) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/hamming/.meta/template.j2 b/exercises/practice/hamming/.meta/template.j2 index fbeaf057..b03c2a16 100644 --- a/exercises/practice/hamming/.meta/template.j2 +++ b/exercises/practice/hamming/.meta/template.j2 @@ -6,13 +6,14 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["strand1"] | to_roc }}, {{ case["input"]["strand2"] | to_roc }}) {%- if case["expected"]["error"] %} - Result.is_err(result) + result.is_err() {%- else %} result == Ok({{ case["expected"] }}) {%- endif %} +} {% endfor %} diff --git a/exercises/practice/hamming/hamming-test.roc b/exercises/practice/hamming/hamming-test.roc index ee0da94b..5ffc80b2 100644 --- a/exercises/practice/hamming/hamming-test.roc +++ b/exercises/practice/hamming/hamming-test.roc @@ -1,59 +1,64 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/hamming/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Hamming exposing [distance] # empty strands -expect - result = distance("", "") - result == Ok(0) +expect { + result = distance("", "") + result == Ok(0) +} # single letter identical strands -expect - result = distance("A", "A") - result == Ok(0) +expect { + result = distance("A", "A") + result == Ok(0) +} # single letter different strands -expect - result = distance("G", "T") - result == Ok(1) +expect { + result = distance("G", "T") + result == Ok(1) +} # long identical strands -expect - result = distance("GGACTGAAATCTG", "GGACTGAAATCTG") - result == Ok(0) +expect { + result = distance("GGACTGAAATCTG", "GGACTGAAATCTG") + result == Ok(0) +} # long different strands -expect - result = distance("GGACGGATTCTG", "AGGACGGATTCT") - result == Ok(9) +expect { + result = distance("GGACGGATTCTG", "AGGACGGATTCT") + result == Ok(9) +} # disallow first strand longer -expect - result = distance("AATG", "AAA") - Result.is_err(result) +expect { + result = distance("AATG", "AAA") + result.is_err() +} # disallow second strand longer -expect - result = distance("ATA", "AGTG") - Result.is_err(result) +expect { + result = distance("ATA", "AGTG") + result.is_err() +} # disallow empty first strand -expect - result = distance("", "G") - Result.is_err(result) +expect { + result = distance("", "G") + result.is_err() +} # disallow empty second strand -expect - result = distance("G", "") - Result.is_err(result) +expect { + result = distance("G", "") + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/hello-world/.meta/template.j2 b/exercises/practice/hello-world/.meta/template.j2 index abed1989..1ac91021 100644 --- a/exercises/practice/hello-world/.meta/template.j2 +++ b/exercises/practice/hello-world/.meta/template.j2 @@ -7,10 +7,11 @@ import HelloWorld exposing [hello] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] }} result == {{ case ["expected"] | to_roc }} {% endfor %} +} {{ macros.footer() }} diff --git a/exercises/practice/hello-world/hello-world-test.roc b/exercises/practice/hello-world/hello-world-test.roc index 8bc634c7..28d66ce9 100644 --- a/exercises/practice/hello-world/hello-world-test.roc +++ b/exercises/practice/hello-world/hello-world-test.roc @@ -1,19 +1,17 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/hello-world/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import HelloWorld exposing [hello] # Say Hi! -expect - result = hello - result == "Hello, World!" +expect { + result = hello + result == "Hello, World!" +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/hexadecimal/hexadecimal-test.roc b/exercises/practice/hexadecimal/hexadecimal-test.roc index 604d0177..1a002d31 100644 --- a/exercises/practice/hexadecimal/hexadecimal-test.roc +++ b/exercises/practice/hexadecimal/hexadecimal-test.roc @@ -1,241 +1,285 @@ -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Hexadecimal exposing [parse] # Parse "0" -expect - result = parse("0") - result == Ok(0) +expect { + result = parse("0") + result == Ok(0) +} # Parse "1" -expect - result = parse("1") - result == Ok(1) +expect { + result = parse("1") + result == Ok(1) +} # Parse "2" -expect - result = parse("2") - result == Ok(2) +expect { + result = parse("2") + result == Ok(2) +} # Parse "3" -expect - result = parse("3") - result == Ok(3) +expect { + result = parse("3") + result == Ok(3) +} # Parse "4" -expect - result = parse("4") - result == Ok(4) +expect { + result = parse("4") + result == Ok(4) +} # Parse "5" -expect - result = parse("5") - result == Ok(5) +expect { + result = parse("5") + result == Ok(5) +} # Parse "6" -expect - result = parse("6") - result == Ok(6) +expect { + result = parse("6") + result == Ok(6) +} # Parse "7" -expect - result = parse("7") - result == Ok(7) +expect { + result = parse("7") + result == Ok(7) +} # Parse "8" -expect - result = parse("8") - result == Ok(8) +expect { + result = parse("8") + result == Ok(8) +} # Parse "9" -expect - result = parse("9") - result == Ok(9) +expect { + result = parse("9") + result == Ok(9) +} # Parse "a" -expect - result = parse("a") - result == Ok(10) +expect { + result = parse("a") + result == Ok(10) +} # Parse "b" -expect - result = parse("b") - result == Ok(11) +expect { + result = parse("b") + result == Ok(11) +} # Parse "c" -expect - result = parse("c") - result == Ok(12) +expect { + result = parse("c") + result == Ok(12) +} # Parse "d" -expect - result = parse("d") - result == Ok(13) +expect { + result = parse("d") + result == Ok(13) +} # Parse "e" -expect - result = parse("e") - result == Ok(14) +expect { + result = parse("e") + result == Ok(14) +} # Parse "f" -expect - result = parse("f") - result == Ok(15) +expect { + result = parse("f") + result == Ok(15) +} # Parse "10" -expect - result = parse("10") - result == Ok(16) +expect { + result = parse("10") + result == Ok(16) +} # Parse "11" -expect - result = parse("11") - result == Ok(17) +expect { + result = parse("11") + result == Ok(17) +} # Parse "1f" -expect - result = parse("1f") - result == Ok(31) +expect { + result = parse("1f") + result == Ok(31) +} # Parse "ff" -expect - result = parse("ff") - result == Ok(255) +expect { + result = parse("ff") + result == Ok(255) +} # Parse "abc" -expect - result = parse("abc") - result == Ok(2748) +expect { + result = parse("abc") + result == Ok(2748) +} # Parse "cafe" -expect - result = parse("cafe") - result == Ok(51966) +expect { + result = parse("cafe") + result == Ok(51966) +} # Parse "deadbeef" -expect - result = parse("deadbeef") - result == Ok(3735928559) +expect { + result = parse("deadbeef") + result == Ok(3735928559) +} # Parse "123456789abcdef" -expect - result = parse("123456789abcdef") - result == Ok(81985529216486895) +expect { + result = parse("123456789abcdef") + result == Ok(81985529216486895) +} # Parse "ffffffffffffffff", the largest U64 value -expect - result = parse("ffffffffffffffff") - result == Ok(18446744073709551615) +expect { + result = parse("ffffffffffffffff") + result == Ok(18446744073709551615) +} # Ignore leading zeros in "00000" -expect - result = parse("00000") - result == Ok(0) +expect { + result = parse("00000") + result == Ok(0) +} # Ignore leading zeros in "00001" -expect - result = parse("00001") - result == Ok(1) +expect { + result = parse("00001") + result == Ok(1) +} # Ignore leading zeros in "0000a" -expect - result = parse("0000a") - result == Ok(10) +expect { + result = parse("0000a") + result == Ok(10) +} # Ignore leading zeros in "000010" -expect - result = parse("000010") - result == Ok(16) +expect { + result = parse("000010") + result == Ok(16) +} # Ignore leading zeros in "0000ff" -expect - result = parse("0000ff") - result == Ok(255) +expect { + result = parse("0000ff") + result == Ok(255) +} # Ignore leading zeros in "0000a0000" -expect - result = parse("0000a0000") - result == Ok(655360) +expect { + result = parse("0000a0000") + result == Ok(655360) +} # Ignore leading zeros even before the largest U64 value -expect - result = parse("0000ffffffffffffffff") - result == Ok(18446744073709551615) +expect { + result = parse("0000ffffffffffffffff") + result == Ok(18446744073709551615) +} # Accept upper case -expect - result = parse("ABCDEF") - result == Ok(11259375) +expect { + result = parse("ABCDEF") + result == Ok(11259375) +} # Accept mixed case -expect - result = parse("aAbBcCdDeEfF") - result == Ok(187723572702975) +expect { + result = parse("aAbBcCdDeEfF") + result == Ok(187723572702975) +} # Empty strings are invalid -expect - result = parse("") - result |> Result.is_err +expect { + result = parse("") + result.is_err() +} # A string with only spaces is invalid -expect - result = parse(" ") - result |> Result.is_err +expect { + result = parse(" ") + result.is_err() +} # Leading spaces are invalid -expect - result = parse(" 12ab") - result |> Result.is_err +expect { + result = parse(" 12ab") + result.is_err() +} # Trailing spaces are invalid -expect - result = parse("12ab ") - result |> Result.is_err +expect { + result = parse("12ab ") + result.is_err() +} # Spaces anywhere are invalid -expect - result = parse("12 ab") - result |> Result.is_err +expect { + result = parse("12 ab") + result.is_err() +} # Invalid character in "1*2ab" -expect - result = parse("1*2ab") - result |> Result.is_err +expect { + result = parse("1*2ab") + result.is_err() +} # Invalid character in "12fg" -expect - result = parse("12fg") - result |> Result.is_err +expect { + result = parse("12fg") + result.is_err() +} # Invalid character in "12/ab" -expect - result = parse("12/ab") - result |> Result.is_err +expect { + result = parse("12/ab") + result.is_err() +} # Invalid character in "12ab\n" -expect - result = parse("12ab\n") - result |> Result.is_err +expect { + result = parse("12ab\n") + result.is_err() +} # Invalid character in "12-ab" -expect - result = parse("12-ab") - result |> Result.is_err +expect { + result = parse("12-ab") + result.is_err() +} # Invalid character in "12+ab" -expect - result = parse("12+ab") - result |> Result.is_err +expect { + result = parse("12+ab") + result.is_err() +} # For numbers that don't fit in an U64, `parse` should return an error # instead of crashing -expect - result = parse("100000000000000000000000000000000") - result |> Result.is_err +expect { + result = parse("100000000000000000000000000000000") + result.is_err() +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/high-scores/.meta/template.j2 b/exercises/practice/high-scores/.meta/template.j2 index 34fbaab4..86f7fad4 100644 --- a/exercises/practice/high-scores/.meta/template.j2 +++ b/exercises/practice/high-scores/.meta/template.j2 @@ -15,9 +15,10 @@ import {{ exercise | to_pascal }} exposing [latest, personal_best, personal_top_ {%- if case["description"] != supercase["description"] %} # {{ case["description"] }} {%- endif %} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["scores"] }}) result == {% if case["property"] != "personalTopThree" %}Ok({{ case["expected"] | to_roc }}){%- else -%}{{ case["expected"] | to_roc }}{% endif %} +} {% endfor -%} {%- endfor %} diff --git a/exercises/practice/high-scores/high-scores-test.roc b/exercises/practice/high-scores/high-scores-test.roc index 34630f97..55ce86b4 100644 --- a/exercises/practice/high-scores/high-scores-test.roc +++ b/exercises/practice/high-scores/high-scores-test.roc @@ -1,50 +1,53 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/high-scores/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import HighScores exposing [latest, personal_best, personal_top_three] ## Latest score -expect - result = latest([100, 0, 90, 30]) - result == Ok(30) +expect { + result = latest([100, 0, 90, 30]) + result == Ok(30) +} ## Personal best -expect - result = personal_best([40, 100, 70]) - result == Ok(100) +expect { + result = personal_best([40, 100, 70]) + result == Ok(100) +} ## Top 3 scores # Personal top three from a list of scores -expect - result = personal_top_three([10, 30, 90, 30, 100, 20, 10, 0, 30, 40, 40, 70, 70]) - result == [100, 90, 70] +expect { + result = personal_top_three([10, 30, 90, 30, 100, 20, 10, 0, 30, 40, 40, 70, 70]) + result == [100, 90, 70] +} # Personal top highest to lowest -expect - result = personal_top_three([20, 10, 30]) - result == [30, 20, 10] +expect { + result = personal_top_three([20, 10, 30]) + result == [30, 20, 10] +} # Personal top when there is a tie -expect - result = personal_top_three([40, 20, 40, 30]) - result == [40, 40, 30] +expect { + result = personal_top_three([40, 20, 40, 30]) + result == [40, 40, 30] +} # Personal top when there are less than 3 -expect - result = personal_top_three([30, 70]) - result == [70, 30] +expect { + result = personal_top_three([30, 70]) + result == [70, 30] +} # Personal top when there is only one -expect - result = personal_top_three([40]) - result == [40] +expect { + result = personal_top_three([40]) + result == [40] +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/house/.meta/template.j2 b/exercises/practice/house/.meta/template.j2 index fc09e0ba..479d6242 100644 --- a/exercises/practice/house/.meta/template.j2 +++ b/exercises/practice/house/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["startVerse"] }}, {{ case["input"]["endVerse"] }}) result == {{ "\n".join(case["expected"]) | to_roc }} +} {% endfor %} diff --git a/exercises/practice/house/house-test.roc b/exercises/practice/house/house-test.roc index 5031a2cf..03225f74 100644 --- a/exercises/practice/house/house-test.roc +++ b/exercises/practice/house/house-test.roc @@ -1,84 +1,94 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/house/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import House exposing [recite] # verse one - the house that jack built -expect - result = recite(1, 1) - result == "This is the house that Jack built." +expect { + result = recite(1, 1) + result == "This is the house that Jack built." +} # verse two - the malt that lay -expect - result = recite(2, 2) - result == "This is the malt that lay in the house that Jack built." +expect { + result = recite(2, 2) + result == "This is the malt that lay in the house that Jack built." +} # verse three - the rat that ate -expect - result = recite(3, 3) - result == "This is the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(3, 3) + result == "This is the rat that ate the malt that lay in the house that Jack built." +} # verse four - the cat that killed -expect - result = recite(4, 4) - result == "This is the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(4, 4) + result == "This is the cat that killed the rat that ate the malt that lay in the house that Jack built." +} # verse five - the dog that worried -expect - result = recite(5, 5) - result == "This is the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(5, 5) + result == "This is the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +} # verse six - the cow with the crumpled horn -expect - result = recite(6, 6) - result == "This is the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(6, 6) + result == "This is the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +} # verse seven - the maiden all forlorn -expect - result = recite(7, 7) - result == "This is the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(7, 7) + result == "This is the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +} # verse eight - the man all tattered and torn -expect - result = recite(8, 8) - result == "This is the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(8, 8) + result == "This is the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +} # verse nine - the priest all shaven and shorn -expect - result = recite(9, 9) - result == "This is the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(9, 9) + result == "This is the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +} # verse 10 - the rooster that crowed in the morn -expect - result = recite(10, 10) - result == "This is the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(10, 10) + result == "This is the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +} # verse 11 - the farmer sowing his corn -expect - result = recite(11, 11) - result == "This is the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(11, 11) + result == "This is the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +} # verse 12 - the horse and the hound and the horn -expect - result = recite(12, 12) - result == "This is the horse and the hound and the horn that belonged to the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(12, 12) + result == "This is the horse and the hound and the horn that belonged to the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +} # multiple verses -expect - result = recite(4, 8) - result == "This is the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(4, 8) + result == "This is the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +} # full rhyme -expect - result = recite(1, 12) - result == "This is the house that Jack built.\nThis is the malt that lay in the house that Jack built.\nThis is the rat that ate the malt that lay in the house that Jack built.\nThis is the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the horse and the hound and the horn that belonged to the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +expect { + result = recite(1, 12) + result == "This is the house that Jack built.\nThis is the malt that lay in the house that Jack built.\nThis is the rat that ate the malt that lay in the house that Jack built.\nThis is the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.\nThis is the horse and the hound and the horn that belonged to the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built." +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/isbn-verifier/.meta/template.j2 b/exercises/practice/isbn-verifier/.meta/template.j2 index 41ff8a91..bc198756 100644 --- a/exercises/practice/isbn-verifier/.meta/template.j2 +++ b/exercises/practice/isbn-verifier/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [is_valid] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["isbn"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/isbn-verifier/isbn-verifier-test.roc b/exercises/practice/isbn-verifier/isbn-verifier-test.roc index b7a53729..ad757d02 100644 --- a/exercises/practice/isbn-verifier/isbn-verifier-test.roc +++ b/exercises/practice/isbn-verifier/isbn-verifier-test.roc @@ -1,109 +1,124 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/isbn-verifier/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import IsbnVerifier exposing [is_valid] # valid isbn -expect - result = is_valid("3-598-21508-8") - result == Bool.true +expect { + result = is_valid("3-598-21508-8") + result == Bool.True +} # invalid isbn check digit -expect - result = is_valid("3-598-21508-9") - result == Bool.false +expect { + result = is_valid("3-598-21508-9") + result == Bool.False +} # valid isbn with a check digit of 10 -expect - result = is_valid("3-598-21507-X") - result == Bool.true +expect { + result = is_valid("3-598-21507-X") + result == Bool.True +} # check digit is a character other than X -expect - result = is_valid("3-598-21507-A") - result == Bool.false +expect { + result = is_valid("3-598-21507-A") + result == Bool.False +} # invalid check digit in isbn is not treated as zero -expect - result = is_valid("4-598-21507-B") - result == Bool.false +expect { + result = is_valid("4-598-21507-B") + result == Bool.False +} # invalid character in isbn is not treated as zero -expect - result = is_valid("3-598-P1581-X") - result == Bool.false +expect { + result = is_valid("3-598-P1581-X") + result == Bool.False +} # X is only valid as a check digit -expect - result = is_valid("3-598-2X507-9") - result == Bool.false +expect { + result = is_valid("3-598-2X507-9") + result == Bool.False +} # valid isbn without separating dashes -expect - result = is_valid("3598215088") - result == Bool.true +expect { + result = is_valid("3598215088") + result == Bool.True +} # isbn without separating dashes and X as check digit -expect - result = is_valid("359821507X") - result == Bool.true +expect { + result = is_valid("359821507X") + result == Bool.True +} # isbn without check digit and dashes -expect - result = is_valid("359821507") - result == Bool.false +expect { + result = is_valid("359821507") + result == Bool.False +} # too long isbn and no dashes -expect - result = is_valid("3598215078X") - result == Bool.false +expect { + result = is_valid("3598215078X") + result == Bool.False +} # too short isbn -expect - result = is_valid("00") - result == Bool.false +expect { + result = is_valid("00") + result == Bool.False +} # isbn without check digit -expect - result = is_valid("3-598-21507") - result == Bool.false +expect { + result = is_valid("3-598-21507") + result == Bool.False +} # check digit of X should not be used for 0 -expect - result = is_valid("3-598-21515-X") - result == Bool.false +expect { + result = is_valid("3-598-21515-X") + result == Bool.False +} # empty isbn -expect - result = is_valid("") - result == Bool.false +expect { + result = is_valid("") + result == Bool.False +} # input is 9 characters -expect - result = is_valid("134456729") - result == Bool.false +expect { + result = is_valid("134456729") + result == Bool.False +} # invalid characters are not ignored after checking length -expect - result = is_valid("3132P34035") - result == Bool.false +expect { + result = is_valid("3132P34035") + result == Bool.False +} # invalid characters are not ignored before checking length -expect - result = is_valid("3598P215088") - result == Bool.false +expect { + result = is_valid("3598P215088") + result == Bool.False +} # input is too long but contains a valid isbn -expect - result = is_valid("98245726788") - result == Bool.false +expect { + result = is_valid("98245726788") + result == Bool.False +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/isogram/.meta/template.j2 b/exercises/practice/isogram/.meta/template.j2 index 3f6d4f5c..e88e90c8 100644 --- a/exercises/practice/isogram/.meta/template.j2 +++ b/exercises/practice/isogram/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [is_isogram] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["phrase"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/isogram/isogram-test.roc b/exercises/practice/isogram/isogram-test.roc index 9e71ff4b..5753510b 100644 --- a/exercises/practice/isogram/isogram-test.roc +++ b/exercises/practice/isogram/isogram-test.roc @@ -1,84 +1,94 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/isogram/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Isogram exposing [is_isogram] # empty string -expect - result = is_isogram("") - result == Bool.true +expect { + result = is_isogram("") + result == Bool.True +} # isogram with only lower case characters -expect - result = is_isogram("isogram") - result == Bool.true +expect { + result = is_isogram("isogram") + result == Bool.True +} # word with one duplicated character -expect - result = is_isogram("eleven") - result == Bool.false +expect { + result = is_isogram("eleven") + result == Bool.False +} # word with one duplicated character from the end of the alphabet -expect - result = is_isogram("zzyzx") - result == Bool.false +expect { + result = is_isogram("zzyzx") + result == Bool.False +} # longest reported english isogram -expect - result = is_isogram("subdermatoglyphic") - result == Bool.true +expect { + result = is_isogram("subdermatoglyphic") + result == Bool.True +} # word with duplicated character in mixed case -expect - result = is_isogram("Alphabet") - result == Bool.false +expect { + result = is_isogram("Alphabet") + result == Bool.False +} # word with duplicated character in mixed case, lowercase first -expect - result = is_isogram("alphAbet") - result == Bool.false +expect { + result = is_isogram("alphAbet") + result == Bool.False +} # hypothetical isogrammic word with hyphen -expect - result = is_isogram("thumbscrew-japingly") - result == Bool.true +expect { + result = is_isogram("thumbscrew-japingly") + result == Bool.True +} # hypothetical word with duplicated character following hyphen -expect - result = is_isogram("thumbscrew-jappingly") - result == Bool.false +expect { + result = is_isogram("thumbscrew-jappingly") + result == Bool.False +} # isogram with duplicated hyphen -expect - result = is_isogram("six-year-old") - result == Bool.true +expect { + result = is_isogram("six-year-old") + result == Bool.True +} # made-up name that is an isogram -expect - result = is_isogram("Emily Jung Schwartzkopf") - result == Bool.true +expect { + result = is_isogram("Emily Jung Schwartzkopf") + result == Bool.True +} # duplicated character in the middle -expect - result = is_isogram("accentor") - result == Bool.false +expect { + result = is_isogram("accentor") + result == Bool.False +} # same first and last characters -expect - result = is_isogram("angola") - result == Bool.false +expect { + result = is_isogram("angola") + result == Bool.False +} # word with duplicated character and with two hyphens -expect - result = is_isogram("up-to-date") - result == Bool.false +expect { + result = is_isogram("up-to-date") + result == Bool.False +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/killer-sudoku-helper/.meta/template.j2 b/exercises/practice/killer-sudoku-helper/.meta/template.j2 index 5d2b8865..162e94fb 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/template.j2 +++ b/exercises/practice/killer-sudoku-helper/.meta/template.j2 @@ -22,9 +22,10 @@ import {{ exercise | to_pascal }} exposing [combinations] {%- if case["description"] != supercase["description"] %} # {{ case["description"] }} {%- endif %} -expect +expect { result = {{ case["property"] }}({{ to_cage(case["input"]["cage"]) }}) result == {{ case["expected"] | to_roc }} +} {% endfor -%} {%- endfor %} diff --git a/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc index e4f175d3..8fcb76d4 100644 --- a/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc +++ b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc @@ -1,80 +1,89 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/killer-sudoku-helper/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import KillerSudokuHelper exposing [combinations] ## Trivial 1-digit cages # 1 -expect - result = combinations({ sum: 1, size: 1 }) - result == [[1]] +expect { + result = combinations({ sum: 1, size: 1 }) + result == [[1]] +} # 2 -expect - result = combinations({ sum: 2, size: 1 }) - result == [[2]] +expect { + result = combinations({ sum: 2, size: 1 }) + result == [[2]] +} # 3 -expect - result = combinations({ sum: 3, size: 1 }) - result == [[3]] +expect { + result = combinations({ sum: 3, size: 1 }) + result == [[3]] +} # 4 -expect - result = combinations({ sum: 4, size: 1 }) - result == [[4]] +expect { + result = combinations({ sum: 4, size: 1 }) + result == [[4]] +} # 5 -expect - result = combinations({ sum: 5, size: 1 }) - result == [[5]] +expect { + result = combinations({ sum: 5, size: 1 }) + result == [[5]] +} # 6 -expect - result = combinations({ sum: 6, size: 1 }) - result == [[6]] +expect { + result = combinations({ sum: 6, size: 1 }) + result == [[6]] +} # 7 -expect - result = combinations({ sum: 7, size: 1 }) - result == [[7]] +expect { + result = combinations({ sum: 7, size: 1 }) + result == [[7]] +} # 8 -expect - result = combinations({ sum: 8, size: 1 }) - result == [[8]] +expect { + result = combinations({ sum: 8, size: 1 }) + result == [[8]] +} # 9 -expect - result = combinations({ sum: 9, size: 1 }) - result == [[9]] +expect { + result = combinations({ sum: 9, size: 1 }) + result == [[9]] +} ## Cage with sum 45 contains all digits 1:9 -expect - result = combinations({ sum: 45, size: 9 }) - result == [[1, 2, 3, 4, 5, 6, 7, 8, 9]] +expect { + result = combinations({ sum: 45, size: 9 }) + result == [[1, 2, 3, 4, 5, 6, 7, 8, 9]] +} ## Cage with only 1 possible combination -expect - result = combinations({ sum: 7, size: 3 }) - result == [[1, 2, 4]] +expect { + result = combinations({ sum: 7, size: 3 }) + result == [[1, 2, 4]] +} ## Cage with several combinations -expect - result = combinations({ sum: 10, size: 2 }) - result == [[1, 9], [2, 8], [3, 7], [4, 6]] +expect { + result = combinations({ sum: 10, size: 2 }) + result == [[1, 9], [2, 8], [3, 7], [4, 6]] +} ## Cage with several combinations that is restricted -expect - result = combinations({ sum: 10, size: 2, exclude: [1, 4] }) - result == [[2, 8], [3, 7]] +expect { + result = combinations({ sum: 10, size: 2, exclude: [1, 4] }) + result == [[2, 8], [3, 7]] +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/kindergarten-garden/.meta/template.j2 b/exercises/practice/kindergarten-garden/.meta/template.j2 index cf291df5..12c8201f 100644 --- a/exercises/practice/kindergarten-garden/.meta/template.j2 +++ b/exercises/practice/kindergarten-garden/.meta/template.j2 @@ -19,10 +19,11 @@ import {{ exercise | to_pascal }} exposing [plants] {% for subcase in subcases -%} # {{ subcase["description"] }} -expect +expect { diagram = {{ subcase["input"]["diagram"] | to_roc_multiline_string | indent(8) }} - result = diagram |> {{ subcase["property"] | to_snake }}({{ subcase["input"]["student"] | to_pascal }}) + result = diagram.{{ subcase["property"] | to_snake }}({{ subcase["input"]["student"] | to_pascal }}) result == Ok([{% for plant in subcase["expected"] %}{{ plant | to_pascal }}, {% endfor %}]) +} {% endfor %} diff --git a/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc b/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc index 33a0f2bf..a43e02d4 100644 --- a/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc +++ b/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/kindergarten-garden/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import KindergartenGarden exposing [plants] @@ -17,178 +9,301 @@ import KindergartenGarden exposing [plants] ### # garden with single student -expect - diagram = - """ - RC - GG - """ - result = diagram |> plants(Alice) - result == Ok([Radishes, Clover, Grass, Grass]) +expect { + diagram = + \\RC + \\GG + + result = diagram.plants(Alice) + result == Ok( + [ + Radishes, + Clover, + Grass, + Grass, + ], + ) +} # different garden with single student -expect - diagram = - """ - VC - RC - """ - result = diagram |> plants(Alice) - result == Ok([Violets, Clover, Radishes, Clover]) +expect { + diagram = + \\VC + \\RC + + result = diagram.plants(Alice) + result == Ok( + [ + Violets, + Clover, + Radishes, + Clover, + ], + ) +} # garden with two students -expect - diagram = - """ - VVCG - VVRC - """ - result = diagram |> plants(Bob) - result == Ok([Clover, Grass, Radishes, Clover]) +expect { + diagram = + \\VVCG + \\VVRC + + result = diagram.plants(Bob) + result == Ok( + [ + Clover, + Grass, + Radishes, + Clover, + ], + ) +} ## multiple students for the same garden with three students # second student's garden -expect - diagram = - """ - VVCCGG - VVCCGG - """ - result = diagram |> plants(Bob) - result == Ok([Clover, Clover, Clover, Clover]) +expect { + diagram = + \\VVCCGG + \\VVCCGG + + result = diagram.plants(Bob) + result == Ok( + [ + Clover, + Clover, + Clover, + Clover, + ], + ) +} # third student's garden -expect - diagram = - """ - VVCCGG - VVCCGG - """ - result = diagram |> plants(Charlie) - result == Ok([Grass, Grass, Grass, Grass]) +expect { + diagram = + \\VVCCGG + \\VVCCGG + + result = diagram.plants(Charlie) + result == Ok( + [ + Grass, + Grass, + Grass, + Grass, + ], + ) +} ### ### full garden ### # for Alice, first student's garden -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Alice) - result == Ok([Violets, Radishes, Violets, Radishes]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Alice) + result == Ok( + [ + Violets, + Radishes, + Violets, + Radishes, + ], + ) +} # for Bob, second student's garden -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Bob) - result == Ok([Clover, Grass, Clover, Clover]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Bob) + result == Ok( + [ + Clover, + Grass, + Clover, + Clover, + ], + ) +} # for Charlie -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Charlie) - result == Ok([Violets, Violets, Clover, Grass]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Charlie) + result == Ok( + [ + Violets, + Violets, + Clover, + Grass, + ], + ) +} # for David -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(David) - result == Ok([Radishes, Violets, Clover, Radishes]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(David) + result == Ok( + [ + Radishes, + Violets, + Clover, + Radishes, + ], + ) +} # for Eve -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Eve) - result == Ok([Clover, Grass, Radishes, Grass]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Eve) + result == Ok( + [ + Clover, + Grass, + Radishes, + Grass, + ], + ) +} # for Fred -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Fred) - result == Ok([Grass, Clover, Violets, Clover]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Fred) + result == Ok( + [ + Grass, + Clover, + Violets, + Clover, + ], + ) +} # for Ginny -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Ginny) - result == Ok([Clover, Grass, Grass, Clover]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Ginny) + result == Ok( + [ + Clover, + Grass, + Grass, + Clover, + ], + ) +} # for Harriet -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Harriet) - result == Ok([Violets, Radishes, Radishes, Violets]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Harriet) + result == Ok( + [ + Violets, + Radishes, + Radishes, + Violets, + ], + ) +} # for Ileana -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Ileana) - result == Ok([Grass, Clover, Violets, Clover]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Ileana) + result == Ok( + [ + Grass, + Clover, + Violets, + Clover, + ], + ) +} # for Joseph -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Joseph) - result == Ok([Violets, Clover, Violets, Grass]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Joseph) + result == Ok( + [ + Violets, + Clover, + Violets, + Grass, + ], + ) +} # for Kincaid, second to last student's garden -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Kincaid) - result == Ok([Grass, Clover, Clover, Grass]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Kincaid) + result == Ok( + [ + Grass, + Clover, + Clover, + Grass, + ], + ) +} # for Larry, last student's garden -expect - diagram = - """ - VRCGVVRVCGGCCGVRGCVCGCGV - VRCCCGCRRGVCGCRVVCVGCGCV - """ - result = diagram |> plants(Larry) - result == Ok([Grass, Violets, Clover, Violets]) +expect { + diagram = + \\VRCGVVRVCGGCCGVRGCVCGCGV + \\VRCCCGCRRGVCGCRVVCVGCGCV + + result = diagram.plants(Larry) + result == Ok( + [ + Grass, + Violets, + Clover, + Violets, + ], + ) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/knapsack/.meta/template.j2 b/exercises/practice/knapsack/.meta/template.j2 index 336772d1..a8df60f7 100644 --- a/exercises/practice/knapsack/.meta/template.j2 +++ b/exercises/practice/knapsack/.meta/template.j2 @@ -6,7 +6,7 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { {%- if case["input"]["items"] == [] %} items = [] {%- else %} @@ -18,6 +18,7 @@ expect {%- endif %} result = {{ case["property"] | to_snake }}({ items, maximum_weight: {{ case["input"]["maximumWeight"] }} }) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/knapsack/knapsack-test.roc b/exercises/practice/knapsack/knapsack-test.roc index 4b77d6f3..e19011cb 100644 --- a/exercises/practice/knapsack/knapsack-test.roc +++ b/exercises/practice/knapsack/knapsack-test.roc @@ -1,100 +1,103 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/knapsack/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Knapsack exposing [maximum_value] # no items -expect - items = [] - result = maximum_value({ items, maximum_weight: 100 }) - result == 0 +expect { + items = [] + result = maximum_value({ items, maximum_weight: 100 }) + result == 0 +} # one item, too heavy -expect - items = [ - { weight: 100, value: 1 }, - ] - result = maximum_value({ items, maximum_weight: 10 }) - result == 0 +expect { + items = [ + { weight: 100, value: 1 }, + ] + result = maximum_value({ items, maximum_weight: 10 }) + result == 0 +} # five items (cannot be greedy by weight) -expect - items = [ - { weight: 2, value: 5 }, - { weight: 2, value: 5 }, - { weight: 2, value: 5 }, - { weight: 2, value: 5 }, - { weight: 10, value: 21 }, - ] - result = maximum_value({ items, maximum_weight: 10 }) - result == 21 +expect { + items = [ + { weight: 2, value: 5 }, + { weight: 2, value: 5 }, + { weight: 2, value: 5 }, + { weight: 2, value: 5 }, + { weight: 10, value: 21 }, + ] + result = maximum_value({ items, maximum_weight: 10 }) + result == 21 +} # five items (cannot be greedy by value) -expect - items = [ - { weight: 2, value: 20 }, - { weight: 2, value: 20 }, - { weight: 2, value: 20 }, - { weight: 2, value: 20 }, - { weight: 10, value: 50 }, - ] - result = maximum_value({ items, maximum_weight: 10 }) - result == 80 +expect { + items = [ + { weight: 2, value: 20 }, + { weight: 2, value: 20 }, + { weight: 2, value: 20 }, + { weight: 2, value: 20 }, + { weight: 10, value: 50 }, + ] + result = maximum_value({ items, maximum_weight: 10 }) + result == 80 +} # example knapsack -expect - items = [ - { weight: 5, value: 10 }, - { weight: 4, value: 40 }, - { weight: 6, value: 30 }, - { weight: 4, value: 50 }, - ] - result = maximum_value({ items, maximum_weight: 10 }) - result == 90 +expect { + items = [ + { weight: 5, value: 10 }, + { weight: 4, value: 40 }, + { weight: 6, value: 30 }, + { weight: 4, value: 50 }, + ] + result = maximum_value({ items, maximum_weight: 10 }) + result == 90 +} # 8 items -expect - items = [ - { weight: 25, value: 350 }, - { weight: 35, value: 400 }, - { weight: 45, value: 450 }, - { weight: 5, value: 20 }, - { weight: 25, value: 70 }, - { weight: 3, value: 8 }, - { weight: 2, value: 5 }, - { weight: 2, value: 5 }, - ] - result = maximum_value({ items, maximum_weight: 104 }) - result == 900 +expect { + items = [ + { weight: 25, value: 350 }, + { weight: 35, value: 400 }, + { weight: 45, value: 450 }, + { weight: 5, value: 20 }, + { weight: 25, value: 70 }, + { weight: 3, value: 8 }, + { weight: 2, value: 5 }, + { weight: 2, value: 5 }, + ] + result = maximum_value({ items, maximum_weight: 104 }) + result == 900 +} # 15 items -expect - items = [ - { weight: 70, value: 135 }, - { weight: 73, value: 139 }, - { weight: 77, value: 149 }, - { weight: 80, value: 150 }, - { weight: 82, value: 156 }, - { weight: 87, value: 163 }, - { weight: 90, value: 173 }, - { weight: 94, value: 184 }, - { weight: 98, value: 192 }, - { weight: 106, value: 201 }, - { weight: 110, value: 210 }, - { weight: 113, value: 214 }, - { weight: 115, value: 221 }, - { weight: 118, value: 229 }, - { weight: 120, value: 240 }, - ] - result = maximum_value({ items, maximum_weight: 750 }) - result == 1458 +expect { + items = [ + { weight: 70, value: 135 }, + { weight: 73, value: 139 }, + { weight: 77, value: 149 }, + { weight: 80, value: 150 }, + { weight: 82, value: 156 }, + { weight: 87, value: 163 }, + { weight: 90, value: 173 }, + { weight: 94, value: 184 }, + { weight: 98, value: 192 }, + { weight: 106, value: 201 }, + { weight: 110, value: 210 }, + { weight: 113, value: 214 }, + { weight: 115, value: 221 }, + { weight: 118, value: 229 }, + { weight: 120, value: 240 }, + ] + result = maximum_value({ items, maximum_weight: 750 }) + result == 1458 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/largest-series-product/.meta/template.j2 b/exercises/practice/largest-series-product/.meta/template.j2 index de9a3fc7..dec6c997 100644 --- a/exercises/practice/largest-series-product/.meta/template.j2 +++ b/exercises/practice/largest-series-product/.meta/template.j2 @@ -6,15 +6,16 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { digits = {{ case["input"]["digits"] | to_roc }} - result = digits |> {{ case["property"] | to_snake }}({{ case["input"]["span"] | to_roc }}) + result = digits -> {{ case["property"] | to_snake }}({{ case["input"]["span"] | to_roc }}) {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} expected = Ok({{ case["expected"] | to_roc }}) result == expected {%- endif %} +} {% endfor %} diff --git a/exercises/practice/largest-series-product/largest-series-product-test.roc b/exercises/practice/largest-series-product/largest-series-product-test.roc index 2e5e1d70..6cd6c929 100644 --- a/exercises/practice/largest-series-product/largest-series-product-test.roc +++ b/exercises/practice/largest-series-product/largest-series-product-test.roc @@ -1,109 +1,119 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/largest-series-product/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import LargestSeriesProduct exposing [largest_product] # finds the largest product if span equals length -expect - digits = "29" - result = digits |> largest_product(2) - expected = Ok(18) - result == expected +expect { + digits = "29" + result = digits->largest_product(2) + expected = Ok(18) + result == expected +} # can find the largest product of 2 with numbers in order -expect - digits = "0123456789" - result = digits |> largest_product(2) - expected = Ok(72) - result == expected +expect { + digits = "0123456789" + result = digits->largest_product(2) + expected = Ok(72) + result == expected +} # can find the largest product of 2 -expect - digits = "576802143" - result = digits |> largest_product(2) - expected = Ok(48) - result == expected +expect { + digits = "576802143" + result = digits->largest_product(2) + expected = Ok(48) + result == expected +} # can find the largest product of 3 with numbers in order -expect - digits = "0123456789" - result = digits |> largest_product(3) - expected = Ok(504) - result == expected +expect { + digits = "0123456789" + result = digits->largest_product(3) + expected = Ok(504) + result == expected +} # can find the largest product of 3 -expect - digits = "1027839564" - result = digits |> largest_product(3) - expected = Ok(270) - result == expected +expect { + digits = "1027839564" + result = digits->largest_product(3) + expected = Ok(270) + result == expected +} # can find the largest product of 5 with numbers in order -expect - digits = "0123456789" - result = digits |> largest_product(5) - expected = Ok(15120) - result == expected +expect { + digits = "0123456789" + result = digits->largest_product(5) + expected = Ok(15120) + result == expected +} # can get the largest product of a big number -expect - digits = "73167176531330624919225119674426574742355349194934" - result = digits |> largest_product(6) - expected = Ok(23520) - result == expected +expect { + digits = "73167176531330624919225119674426574742355349194934" + result = digits->largest_product(6) + expected = Ok(23520) + result == expected +} # reports zero if the only digits are zero -expect - digits = "0000" - result = digits |> largest_product(2) - expected = Ok(0) - result == expected +expect { + digits = "0000" + result = digits->largest_product(2) + expected = Ok(0) + result == expected +} # reports zero if all spans include zero -expect - digits = "99099" - result = digits |> largest_product(3) - expected = Ok(0) - result == expected +expect { + digits = "99099" + result = digits->largest_product(3) + expected = Ok(0) + result == expected +} # rejects span longer than string length -expect - digits = "123" - result = digits |> largest_product(4) - result |> Result.is_err +expect { + digits = "123" + result = digits->largest_product(4) + result.is_err() +} # reports 1 for empty string and empty product (0 span) -expect - digits = "" - result = digits |> largest_product(0) - expected = Ok(1) - result == expected +expect { + digits = "" + result = digits->largest_product(0) + expected = Ok(1) + result == expected +} # reports 1 for nonempty string and empty product (0 span) -expect - digits = "123" - result = digits |> largest_product(0) - expected = Ok(1) - result == expected +expect { + digits = "123" + result = digits->largest_product(0) + expected = Ok(1) + result == expected +} # rejects empty string and nonzero span -expect - digits = "" - result = digits |> largest_product(1) - result |> Result.is_err +expect { + digits = "" + result = digits->largest_product(1) + result.is_err() +} # rejects invalid character in digits -expect - digits = "1234a5" - result = digits |> largest_product(2) - result |> Result.is_err +expect { + digits = "1234a5" + result = digits->largest_product(2) + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/leap/.meta/template.j2 b/exercises/practice/leap/.meta/template.j2 index 07751eb7..266616ec 100644 --- a/exercises/practice/leap/.meta/template.j2 +++ b/exercises/practice/leap/.meta/template.j2 @@ -7,9 +7,10 @@ import Leap exposing [is_leap_year] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = is_leap_year({{ case["input"]["year"] }}) result == {{ case ["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/leap/leap-test.roc b/exercises/practice/leap/leap-test.roc index 1782ac93..5a79aaeb 100644 --- a/exercises/practice/leap/leap-test.roc +++ b/exercises/practice/leap/leap-test.roc @@ -1,59 +1,64 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/leap/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Leap exposing [is_leap_year] # year not divisible by 4 in common year -expect - result = is_leap_year(2015) - result == Bool.false +expect { + result = is_leap_year(2015) + result == Bool.False +} # year divisible by 2, not divisible by 4 in common year -expect - result = is_leap_year(1970) - result == Bool.false +expect { + result = is_leap_year(1970) + result == Bool.False +} # year divisible by 4, not divisible by 100 in leap year -expect - result = is_leap_year(1996) - result == Bool.true +expect { + result = is_leap_year(1996) + result == Bool.True +} # year divisible by 4 and 5 is still a leap year -expect - result = is_leap_year(1960) - result == Bool.true +expect { + result = is_leap_year(1960) + result == Bool.True +} # year divisible by 100, not divisible by 400 in common year -expect - result = is_leap_year(2100) - result == Bool.false +expect { + result = is_leap_year(2100) + result == Bool.False +} # year divisible by 100 but not by 3 is still not a leap year -expect - result = is_leap_year(1900) - result == Bool.false +expect { + result = is_leap_year(1900) + result == Bool.False +} # year divisible by 400 is leap year -expect - result = is_leap_year(2000) - result == Bool.true +expect { + result = is_leap_year(2000) + result == Bool.True +} # year divisible by 400 but not by 125 is still a leap year -expect - result = is_leap_year(2400) - result == Bool.true +expect { + result = is_leap_year(2400) + result == Bool.True +} # year divisible by 200, not divisible by 400 in common year -expect - result = is_leap_year(1800) - result == Bool.false +expect { + result = is_leap_year(1800) + result == Bool.False +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2 index 6e572d94..8e968dfe 100644 --- a/exercises/practice/list-ops/.meta/template.j2 +++ b/exercises/practice/list-ops/.meta/template.j2 @@ -5,11 +5,11 @@ import {{ exercise | to_pascal }} exposing [append, concat, filter, length, map, foldl, foldr, reverse] {% set function_map = { - "(acc, el) -> el * acc": "\\acc, el -> el * acc", - "(acc, el) -> el / acc": "\\acc, el -> el / acc", - "(acc, el) -> el + acc": "\\acc, el -> el + acc", - "(x) -> x + 1": "\\x -> x + 1", - "(x) -> x modulo 2 == 1": "Num.is_odd", + "(acc, el) -> el * acc": "|acc, el| el * acc", + "(acc, el) -> el / acc": "|acc, el| el / acc", + "(acc, el) -> el + acc": "|acc, el| el + acc", + "(x) -> x + 1": "|x| x + 1", + "(x) -> x modulo 2 == 1": "|n| n % 2 == 1", } -%} {% for supercase in cases %} @@ -19,7 +19,7 @@ import {{ exercise | to_pascal }} exposing [append, concat, filter, length, map, {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect +expect { {%- if case["property"] == "append" %} result = {{ case["property"] | to_snake }}({{ case["input"]["list1"] | to_roc }}, {{ case["input"]["list2"] | to_roc }}) result == {{ case["expected"] | to_roc }} @@ -36,14 +36,15 @@ expect result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}, {{ function_map[case["input"]["function"]] }}) result == {{ case["expected"] }} {%- elif case["property"] in ("foldl", "foldr") %} - result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}, {{ case["input"]["initial"] }}, {{ function_map[case["input"]["function"]] }}){% if "/" in function_map[case["input"]["function"]] %}|> Num.round{% endif %} + result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}, {{ case["input"]["initial"] }}, {{ function_map[case["input"]["function"]] }}){% if "/" in function_map[case["input"]["function"]] %} -> round(){% endif %} result == {{ case["expected"] | to_roc }} {%- elif case["property"] == "reverse" %} result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}) result == {{ case["expected"] | to_roc }} {%- else %} - Bool.true # This test case is not yet implemented + Bool.True # This test case is not yet implemented {%- endif %} +} {% endfor -%} {% endfor -%} diff --git a/exercises/practice/list-ops/list-ops-test.roc b/exercises/practice/list-ops/list-ops-test.roc index 44e890b1..ab35154e 100644 --- a/exercises/practice/list-ops/list-ops-test.roc +++ b/exercises/practice/list-ops/list-ops-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/list-ops/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import ListOps exposing [append, concat, filter, length, map, foldl, foldr, reverse] @@ -17,140 +9,166 @@ import ListOps exposing [append, concat, filter, length, map, foldl, foldr, reve ## # empty lists -expect - result = append([], []) - result == [] +expect { + result = append([], []) + result == [] +} # list to empty list -expect - result = append([], [1, 2, 3, 4]) - result == [1, 2, 3, 4] +expect { + result = append([], [1, 2, 3, 4]) + result == [1, 2, 3, 4] +} # empty list to list -expect - result = append([1, 2, 3, 4], []) - result == [1, 2, 3, 4] +expect { + result = append([1, 2, 3, 4], []) + result == [1, 2, 3, 4] +} # non-empty lists -expect - result = append([1, 2], [2, 3, 4, 5]) - result == [1, 2, 2, 3, 4, 5] +expect { + result = append([1, 2], [2, 3, 4, 5]) + result == [1, 2, 2, 3, 4, 5] +} ## ## concatenate a list of lists ## # empty list -expect - result = concat([]) - result == [] +expect { + result = concat([]) + result == [] +} # list of lists -expect - result = concat([[1, 2], [3], [], [4, 5, 6]]) - result == [1, 2, 3, 4, 5, 6] +expect { + result = concat([[1, 2], [3], [], [4, 5, 6]]) + result == [1, 2, 3, 4, 5, 6] +} # list of nested lists -expect - result = concat([[[1], [2]], [[3]], [[]], [[4, 5, 6]]]) - result == [[1], [2], [3], [], [4, 5, 6]] +expect { + result = concat([[[1], [2]], [[3]], [[]], [[4, 5, 6]]]) + result == [[1], [2], [3], [], [4, 5, 6]] +} ## ## filter list returning only values that satisfy the filter function ## # empty list -expect - result = filter([], Num.is_odd) - result == [] +expect { + result = filter([], |n| n % 2 == 1) + result == [] +} # non-empty list -expect - result = filter([1, 2, 3, 5], Num.is_odd) - result == [1, 3, 5] +expect { + result = filter([1, 2, 3, 5], |n| n % 2 == 1) + result == [1, 3, 5] +} ## ## returns the length of a list ## # empty list -expect - result = length([]) - result == 0 +expect { + result = length([]) + result == 0 +} # non-empty list -expect - result = length([1, 2, 3, 4]) - result == 4 +expect { + result = length([1, 2, 3, 4]) + result == 4 +} ## ## return a list of elements whose values equal the list value transformed by the mapping function ## # empty list -expect - result = map([], |x| x + 1) - result == [] +expect { + result = map([], |x| x + 1) + result == [] +} # non-empty list -expect - result = map([1, 3, 5, 7], |x| x + 1) - result == [2, 4, 6, 8] +expect { + result = map([1, 3, 5, 7], |x| x + 1) + result == [2, 4, 6, 8] +} ## ## folds (reduces) the given list from the left with a function ## # empty list -expect - result = foldl([], 2, |acc, el| el * acc) - result == 2 +expect { + result = foldl([], 2, |acc, el| el * acc) + result == 2 +} # direction independent function applied to non-empty list -expect - result = foldl([1, 2, 3, 4], 5, |acc, el| el + acc) - result == 15 +expect { + result = foldl([1, 2, 3, 4], 5, |acc, el| el + acc) + result == 15 +} # direction dependent function applied to non-empty list -expect - result = foldl([1, 2, 3, 4], 24, |acc, el| el / acc) |> Num.round - result == 64 +expect { + result = foldl([1, 2, 3, 4], 24, |acc, el| el / acc)->round() + result == 64 +} ## ## folds (reduces) the given list from the right with a function ## # empty list -expect - result = foldr([], 2, |acc, el| el * acc) - result == 2 +expect { + result = foldr([], 2, |acc, el| el * acc) + result == 2 +} # direction independent function applied to non-empty list -expect - result = foldr([1, 2, 3, 4], 5, |acc, el| el + acc) - result == 15 +expect { + result = foldr([1, 2, 3, 4], 5, |acc, el| el + acc) + result == 15 +} # direction dependent function applied to non-empty list -expect - result = foldr([1, 2, 3, 4], 24, |acc, el| el / acc) |> Num.round - result == 9 +expect { + result = foldr([1, 2, 3, 4], 24, |acc, el| el / acc)->round() + result == 9 +} ## ## reverse the elements of the list ## # empty list -expect - result = reverse([]) - result == [] +expect { + result = reverse([]) + result == [] +} # non-empty list -expect - result = reverse([1, 3, 5, 7]) - result == [7, 5, 3, 1] +expect { + result = reverse([1, 3, 5, 7]) + result == [7, 5, 3, 1] +} # list of lists is not flattened -expect - result = reverse([[1, 2], [3], [], [4, 5, 6]]) - result == [[4, 5, 6], [], [3], [1, 2]] +expect { + result = reverse([[1, 2], [3], [], [4, 5, 6]]) + result == [[4, 5, 6], [], [3], [1, 2]] +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/luhn/.meta/template.j2 b/exercises/practice/luhn/.meta/template.j2 index 2d95ed83..387605ab 100644 --- a/exercises/practice/luhn/.meta/template.j2 +++ b/exercises/practice/luhn/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["value"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/luhn/luhn-test.roc b/exercises/practice/luhn/luhn-test.roc index 3ed18a32..4a7193c2 100644 --- a/exercises/practice/luhn/luhn-test.roc +++ b/exercises/practice/luhn/luhn-test.roc @@ -1,124 +1,142 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/luhn/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Luhn exposing [valid] # single digit strings can not be valid -expect - result = valid("1") - result == Bool.false +expect { + result = valid("1") + result == Bool.False +} # a single zero is invalid -expect - result = valid("0") - result == Bool.false +expect { + result = valid("0") + result == Bool.False +} # a simple valid SIN that remains valid if reversed -expect - result = valid("059") - result == Bool.true +expect { + result = valid("059") + result == Bool.True +} # a simple valid SIN that becomes invalid if reversed -expect - result = valid("59") - result == Bool.true +expect { + result = valid("59") + result == Bool.True +} # a valid Canadian SIN -expect - result = valid("055 444 285") - result == Bool.true +expect { + result = valid("055 444 285") + result == Bool.True +} # invalid Canadian SIN -expect - result = valid("055 444 286") - result == Bool.false +expect { + result = valid("055 444 286") + result == Bool.False +} # invalid credit card -expect - result = valid("8273 1232 7352 0569") - result == Bool.false +expect { + result = valid("8273 1232 7352 0569") + result == Bool.False +} # invalid long number with an even remainder -expect - result = valid("1 2345 6789 1234 5678 9012") - result == Bool.false +expect { + result = valid("1 2345 6789 1234 5678 9012") + result == Bool.False +} # invalid long number with a remainder divisible by 5 -expect - result = valid("1 2345 6789 1234 5678 9013") - result == Bool.false +expect { + result = valid("1 2345 6789 1234 5678 9013") + result == Bool.False +} # valid number with an even number of digits -expect - result = valid("095 245 88") - result == Bool.true +expect { + result = valid("095 245 88") + result == Bool.True +} # valid number with an odd number of spaces -expect - result = valid("234 567 891 234") - result == Bool.true +expect { + result = valid("234 567 891 234") + result == Bool.True +} # valid strings with a non-digit added at the end become invalid -expect - result = valid("059a") - result == Bool.false +expect { + result = valid("059a") + result == Bool.False +} # valid strings with punctuation included become invalid -expect - result = valid("055-444-285") - result == Bool.false +expect { + result = valid("055-444-285") + result == Bool.False +} # valid strings with symbols included become invalid -expect - result = valid("055# 444$ 285") - result == Bool.false +expect { + result = valid("055# 444$ 285") + result == Bool.False +} # single zero with space is invalid -expect - result = valid(" 0") - result == Bool.false +expect { + result = valid(" 0") + result == Bool.False +} # more than a single zero is valid -expect - result = valid("0000 0") - result == Bool.true +expect { + result = valid("0000 0") + result == Bool.True +} # input digit 9 is correctly converted to output digit 9 -expect - result = valid("091") - result == Bool.true +expect { + result = valid("091") + result == Bool.True +} # very long input is valid -expect - result = valid("9999999999 9999999999 9999999999 9999999999") - result == Bool.true +expect { + result = valid("9999999999 9999999999 9999999999 9999999999") + result == Bool.True +} # valid luhn with an odd number of digits and non zero first digit -expect - result = valid("109") - result == Bool.true +expect { + result = valid("109") + result == Bool.True +} # using ascii value for non-doubled non-digit isn't allowed -expect - result = valid("055b 444 285") - result == Bool.false +expect { + result = valid("055b 444 285") + result == Bool.False +} # using ascii value for doubled non-digit isn't allowed -expect - result = valid(":9") - result == Bool.false +expect { + result = valid(":9") + result == Bool.False +} # non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed -expect - result = valid("59%59") - result == Bool.false +expect { + result = valid("59%59") + result == Bool.False +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/matching-brackets/.meta/template.j2 b/exercises/practice/matching-brackets/.meta/template.j2 index 2748c149..24e2106b 100644 --- a/exercises/practice/matching-brackets/.meta/template.j2 +++ b/exercises/practice/matching-brackets/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect - result = {{ case["input"]["value"] | to_roc }} |> {{ case["property"] | to_snake }} +expect { + result = {{ case["input"]["value"] | to_roc }} -> {{ case["property"] | to_snake }} result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/matching-brackets/matching-brackets-test.roc b/exercises/practice/matching-brackets/matching-brackets-test.roc index d68058fa..07fb20b2 100644 --- a/exercises/practice/matching-brackets/matching-brackets-test.roc +++ b/exercises/practice/matching-brackets/matching-brackets-test.roc @@ -1,114 +1,130 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/matching-brackets/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import MatchingBrackets exposing [is_paired] # paired square brackets -expect - result = "[]" |> is_paired - result == Bool.true +expect { + result = "[]"->is_paired() + result == Bool.True +} # empty string -expect - result = "" |> is_paired - result == Bool.true +expect { + result = ""->is_paired() + result == Bool.True +} # unpaired brackets -expect - result = "[[" |> is_paired - result == Bool.false +expect { + result = "[["->is_paired() + result == Bool.False +} # wrong ordered brackets -expect - result = "}{" |> is_paired - result == Bool.false +expect { + result = "}{"->is_paired() + result == Bool.False +} # wrong closing bracket -expect - result = "{]" |> is_paired - result == Bool.false +expect { + result = "{]"->is_paired() + result == Bool.False +} # paired with whitespace -expect - result = "{ }" |> is_paired - result == Bool.true +expect { + result = "{ }"->is_paired() + result == Bool.True +} # partially paired brackets -expect - result = "{[])" |> is_paired - result == Bool.false +expect { + result = "{[])"->is_paired() + result == Bool.False +} # simple nested brackets -expect - result = "{[]}" |> is_paired - result == Bool.true +expect { + result = "{[]}"->is_paired() + result == Bool.True +} # several paired brackets -expect - result = "{}[]" |> is_paired - result == Bool.true +expect { + result = "{}[]"->is_paired() + result == Bool.True +} # paired and nested brackets -expect - result = "([{}({}[])])" |> is_paired - result == Bool.true +expect { + result = "([{}({}[])])"->is_paired() + result == Bool.True +} # unopened closing brackets -expect - result = "{[)][]}" |> is_paired - result == Bool.false +expect { + result = "{[)][]}"->is_paired() + result == Bool.False +} # unpaired and nested brackets -expect - result = "([{])" |> is_paired - result == Bool.false +expect { + result = "([{])"->is_paired() + result == Bool.False +} # paired and wrong nested brackets -expect - result = "[({]})" |> is_paired - result == Bool.false +expect { + result = "[({]})"->is_paired() + result == Bool.False +} # paired and wrong nested brackets but innermost are correct -expect - result = "[({}])" |> is_paired - result == Bool.false +expect { + result = "[({}])"->is_paired() + result == Bool.False +} # paired and incomplete brackets -expect - result = "{}[" |> is_paired - result == Bool.false +expect { + result = "{}["->is_paired() + result == Bool.False +} # too many closing brackets -expect - result = "[]]" |> is_paired - result == Bool.false +expect { + result = "[]]"->is_paired() + result == Bool.False +} # early unexpected brackets -expect - result = ")()" |> is_paired - result == Bool.false +expect { + result = ")()"->is_paired() + result == Bool.False +} # early mismatched brackets -expect - result = "{)()" |> is_paired - result == Bool.false +expect { + result = "{)()"->is_paired() + result == Bool.False +} # math expression -expect - result = "(((185 + 223.85) * 15) - 543)/2" |> is_paired - result == Bool.true +expect { + result = "(((185 + 223.85) * 15) - 543)/2"->is_paired() + result == Bool.True +} # complex latex expression -expect - result = "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)" |> is_paired - result == Bool.true +expect { + result = "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)"->is_paired() + result == Bool.True +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/matrix/.meta/template.j2 b/exercises/practice/matrix/.meta/template.j2 index aec384ed..c6093f69 100644 --- a/exercises/practice/matrix/.meta/template.j2 +++ b/exercises/practice/matrix/.meta/template.j2 @@ -6,10 +6,11 @@ import {{ exercise | to_pascal }} exposing [row, column] {% for case in cases -%} # {{ case["description"] }} -expect +expect { matrix_str = {{ case["input"]["string"] | to_roc_multiline_string | indent(8) }} - result = matrix_str |> {{ case["property"] | to_snake }}({{ case["input"]["index"] | to_roc }}) + result = matrix_str -> {{ case["property"] | to_snake }}({{ case["input"]["index"] | to_roc }}) result == Ok({{ case["expected"] | to_roc }}) +} {% endfor %} diff --git a/exercises/practice/matrix/matrix-test.roc b/exercises/practice/matrix/matrix-test.roc index 5ff6736f..990fbcbf 100644 --- a/exercises/practice/matrix/matrix-test.roc +++ b/exercises/practice/matrix/matrix-test.roc @@ -1,91 +1,89 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/matrix/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Matrix exposing [row, column] # extract row from one number matrix -expect - matrix_str = "1" - result = matrix_str |> row(1) - result == Ok([1]) +expect { + matrix_str = "1" + result = matrix_str->row(1) + result == Ok([1]) +} # can extract row -expect - matrix_str = - """ - 1 2 - 3 4 - """ - result = matrix_str |> row(2) - result == Ok([3, 4]) +expect { + matrix_str = + \\1 2 + \\3 4 + + result = matrix_str->row(2) + result == Ok([3, 4]) +} # extract row where numbers have different widths -expect - matrix_str = - """ - 1 2 - 10 20 - """ - result = matrix_str |> row(2) - result == Ok([10, 20]) +expect { + matrix_str = + \\1 2 + \\10 20 + + result = matrix_str->row(2) + result == Ok([10, 20]) +} # can extract row from non-square matrix with no corresponding column -expect - matrix_str = - """ - 1 2 3 - 4 5 6 - 7 8 9 - 8 7 6 - """ - result = matrix_str |> row(4) - result == Ok([8, 7, 6]) +expect { + matrix_str = + \\1 2 3 + \\4 5 6 + \\7 8 9 + \\8 7 6 + + result = matrix_str->row(4) + result == Ok([8, 7, 6]) +} # extract column from one number matrix -expect - matrix_str = "1" - result = matrix_str |> column(1) - result == Ok([1]) +expect { + matrix_str = "1" + result = matrix_str->column(1) + result == Ok([1]) +} # can extract column -expect - matrix_str = - """ - 1 2 3 - 4 5 6 - 7 8 9 - """ - result = matrix_str |> column(3) - result == Ok([3, 6, 9]) +expect { + matrix_str = + \\1 2 3 + \\4 5 6 + \\7 8 9 + + result = matrix_str->column(3) + result == Ok([3, 6, 9]) +} # can extract column from non-square matrix with no corresponding row -expect - matrix_str = - """ - 1 2 3 4 - 5 6 7 8 - 9 8 7 6 - """ - result = matrix_str |> column(4) - result == Ok([4, 8, 6]) +expect { + matrix_str = + \\1 2 3 4 + \\5 6 7 8 + \\9 8 7 6 + + result = matrix_str->column(4) + result == Ok([4, 8, 6]) +} # extract column where numbers have different widths -expect - matrix_str = - """ - 89 1903 3 - 18 3 1 - 9 4 800 - """ - result = matrix_str |> column(2) - result == Ok([1903, 3, 4]) +expect { + matrix_str = + \\89 1903 3 + \\18 3 1 + \\9 4 800 + + result = matrix_str->column(2) + result == Ok([1903, 3, 4]) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/meetup/.meta/template.j2 b/exercises/practice/meetup/.meta/template.j2 index 30e4bcb0..22d2f277 100644 --- a/exercises/practice/meetup/.meta/template.j2 +++ b/exercises/practice/meetup/.meta/template.j2 @@ -6,7 +6,7 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({ year: {{ case["input"]["year"] | to_roc }}, month: {{ case["input"]["month"] | to_roc }}, @@ -15,6 +15,7 @@ expect }) expected = Ok({{ case["expected"] | to_roc }}) result == expected +} {% endfor %} diff --git a/exercises/practice/meetup/meetup-test.roc b/exercises/practice/meetup/meetup-test.roc index 373c0878..99689d6d 100644 --- a/exercises/practice/meetup/meetup-test.roc +++ b/exercises/practice/meetup/meetup-test.roc @@ -1,1250 +1,1346 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/meetup/canonical-data.json -# File last updated on 2025-09-15 +# File last updated on 2026-06-13 app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", - isodate: "https://github.com/imclerran/roc-isodate/releases/download/v0.6.2/73w_H-aSJNcWqtXvMG4JQw_HoaApMBLnE92XD4OcVGU.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", + isodate: "https://github.com/imclerran/roc-isodate/releases/download/v0.6.2/73w_H-aSJNcWqtXvMG4JQw_HoaApMBLnE92XD4OcVGU.tar.br", } import pf.Stdout -main! = |_args| - Stdout.line!("") - import Meetup exposing [meetup] # when teenth Monday is the 13th, the first day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 5, - week: Teenth, - day_of_week: Monday, - }, - ) - expected = Ok("2013-05-13") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 5, + week: Teenth, + day_of_week: Monday, + }, + ) + expected = Ok("2013-05-13") + result == expected +} # when teenth Monday is the 19th, the last day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 8, - week: Teenth, - day_of_week: Monday, - }, - ) - expected = Ok("2013-08-19") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 8, + week: Teenth, + day_of_week: Monday, + }, + ) + expected = Ok("2013-08-19") + result == expected +} # when teenth Monday is some day in the middle of the teenth week -expect - result = meetup( - { - year: 2013, - month: 9, - week: Teenth, - day_of_week: Monday, - }, - ) - expected = Ok("2013-09-16") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 9, + week: Teenth, + day_of_week: Monday, + }, + ) + expected = Ok("2013-09-16") + result == expected +} # when teenth Tuesday is the 19th, the last day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 3, - week: Teenth, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-03-19") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: Teenth, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-03-19") + result == expected +} # when teenth Tuesday is some day in the middle of the teenth week -expect - result = meetup( - { - year: 2013, - month: 4, - week: Teenth, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-04-16") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Teenth, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-04-16") + result == expected +} # when teenth Tuesday is the 13th, the first day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 8, - week: Teenth, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-08-13") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 8, + week: Teenth, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-08-13") + result == expected +} # when teenth Wednesday is some day in the middle of the teenth week -expect - result = meetup( - { - year: 2013, - month: 1, - week: Teenth, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-01-16") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 1, + week: Teenth, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-01-16") + result == expected +} # when teenth Wednesday is the 13th, the first day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 2, - week: Teenth, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-02-13") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 2, + week: Teenth, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-02-13") + result == expected +} # when teenth Wednesday is the 19th, the last day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 6, - week: Teenth, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-06-19") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 6, + week: Teenth, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-06-19") + result == expected +} # when teenth Thursday is some day in the middle of the teenth week -expect - result = meetup( - { - year: 2013, - month: 5, - week: Teenth, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-05-16") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 5, + week: Teenth, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-05-16") + result == expected +} # when teenth Thursday is the 13th, the first day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 6, - week: Teenth, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-06-13") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 6, + week: Teenth, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-06-13") + result == expected +} # when teenth Thursday is the 19th, the last day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 9, - week: Teenth, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-09-19") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 9, + week: Teenth, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-09-19") + result == expected +} # when teenth Friday is the 19th, the last day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 4, - week: Teenth, - day_of_week: Friday, - }, - ) - expected = Ok("2013-04-19") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Teenth, + day_of_week: Friday, + }, + ) + expected = Ok("2013-04-19") + result == expected +} # when teenth Friday is some day in the middle of the teenth week -expect - result = meetup( - { - year: 2013, - month: 8, - week: Teenth, - day_of_week: Friday, - }, - ) - expected = Ok("2013-08-16") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 8, + week: Teenth, + day_of_week: Friday, + }, + ) + expected = Ok("2013-08-16") + result == expected +} # when teenth Friday is the 13th, the first day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 9, - week: Teenth, - day_of_week: Friday, - }, - ) - expected = Ok("2013-09-13") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 9, + week: Teenth, + day_of_week: Friday, + }, + ) + expected = Ok("2013-09-13") + result == expected +} # when teenth Saturday is some day in the middle of the teenth week -expect - result = meetup( - { - year: 2013, - month: 2, - week: Teenth, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-02-16") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 2, + week: Teenth, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-02-16") + result == expected +} # when teenth Saturday is the 13th, the first day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 4, - week: Teenth, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-04-13") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Teenth, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-04-13") + result == expected +} # when teenth Saturday is the 19th, the last day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 10, - week: Teenth, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-10-19") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 10, + week: Teenth, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-10-19") + result == expected +} # when teenth Sunday is the 19th, the last day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 5, - week: Teenth, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-05-19") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 5, + week: Teenth, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-05-19") + result == expected +} # when teenth Sunday is some day in the middle of the teenth week -expect - result = meetup( - { - year: 2013, - month: 6, - week: Teenth, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-06-16") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 6, + week: Teenth, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-06-16") + result == expected +} # when teenth Sunday is the 13th, the first day of the teenth week -expect - result = meetup( - { - year: 2013, - month: 10, - week: Teenth, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-10-13") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 10, + week: Teenth, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-10-13") + result == expected +} # when first Monday is some day in the middle of the first week -expect - result = meetup( - { - year: 2013, - month: 3, - week: First, - day_of_week: Monday, - }, - ) - expected = Ok("2013-03-04") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: First, + day_of_week: Monday, + }, + ) + expected = Ok("2013-03-04") + result == expected +} # when first Monday is the 1st, the first day of the first week -expect - result = meetup( - { - year: 2013, - month: 4, - week: First, - day_of_week: Monday, - }, - ) - expected = Ok("2013-04-01") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: First, + day_of_week: Monday, + }, + ) + expected = Ok("2013-04-01") + result == expected +} # when first Tuesday is the 7th, the last day of the first week -expect - result = meetup( - { - year: 2013, - month: 5, - week: First, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-05-07") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 5, + week: First, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-05-07") + result == expected +} # when first Tuesday is some day in the middle of the first week -expect - result = meetup( - { - year: 2013, - month: 6, - week: First, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-06-04") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 6, + week: First, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-06-04") + result == expected +} # when first Wednesday is some day in the middle of the first week -expect - result = meetup( - { - year: 2013, - month: 7, - week: First, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-07-03") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 7, + week: First, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-07-03") + result == expected +} # when first Wednesday is the 7th, the last day of the first week -expect - result = meetup( - { - year: 2013, - month: 8, - week: First, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-08-07") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 8, + week: First, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-08-07") + result == expected +} # when first Thursday is some day in the middle of the first week -expect - result = meetup( - { - year: 2013, - month: 9, - week: First, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-09-05") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 9, + week: First, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-09-05") + result == expected +} # when first Thursday is another day in the middle of the first week -expect - result = meetup( - { - year: 2013, - month: 10, - week: First, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-10-03") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 10, + week: First, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-10-03") + result == expected +} # when first Friday is the 1st, the first day of the first week -expect - result = meetup( - { - year: 2013, - month: 11, - week: First, - day_of_week: Friday, - }, - ) - expected = Ok("2013-11-01") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 11, + week: First, + day_of_week: Friday, + }, + ) + expected = Ok("2013-11-01") + result == expected +} # when first Friday is some day in the middle of the first week -expect - result = meetup( - { - year: 2013, - month: 12, - week: First, - day_of_week: Friday, - }, - ) - expected = Ok("2013-12-06") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 12, + week: First, + day_of_week: Friday, + }, + ) + expected = Ok("2013-12-06") + result == expected +} # when first Saturday is some day in the middle of the first week -expect - result = meetup( - { - year: 2013, - month: 1, - week: First, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-01-05") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 1, + week: First, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-01-05") + result == expected +} # when first Saturday is another day in the middle of the first week -expect - result = meetup( - { - year: 2013, - month: 2, - week: First, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-02-02") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 2, + week: First, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-02-02") + result == expected +} # when first Sunday is some day in the middle of the first week -expect - result = meetup( - { - year: 2013, - month: 3, - week: First, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-03-03") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: First, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-03-03") + result == expected +} # when first Sunday is the 7th, the last day of the first week -expect - result = meetup( - { - year: 2013, - month: 4, - week: First, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-04-07") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: First, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-04-07") + result == expected +} # when second Monday is some day in the middle of the second week -expect - result = meetup( - { - year: 2013, - month: 3, - week: Second, - day_of_week: Monday, - }, - ) - expected = Ok("2013-03-11") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: Second, + day_of_week: Monday, + }, + ) + expected = Ok("2013-03-11") + result == expected +} # when second Monday is the 8th, the first day of the second week -expect - result = meetup( - { - year: 2013, - month: 4, - week: Second, - day_of_week: Monday, - }, - ) - expected = Ok("2013-04-08") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Second, + day_of_week: Monday, + }, + ) + expected = Ok("2013-04-08") + result == expected +} # when second Tuesday is the 14th, the last day of the second week -expect - result = meetup( - { - year: 2013, - month: 5, - week: Second, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-05-14") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 5, + week: Second, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-05-14") + result == expected +} # when second Tuesday is some day in the middle of the second week -expect - result = meetup( - { - year: 2013, - month: 6, - week: Second, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-06-11") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 6, + week: Second, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-06-11") + result == expected +} # when second Wednesday is some day in the middle of the second week -expect - result = meetup( - { - year: 2013, - month: 7, - week: Second, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-07-10") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 7, + week: Second, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-07-10") + result == expected +} # when second Wednesday is the 14th, the last day of the second week -expect - result = meetup( - { - year: 2013, - month: 8, - week: Second, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-08-14") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 8, + week: Second, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-08-14") + result == expected +} # when second Thursday is some day in the middle of the second week -expect - result = meetup( - { - year: 2013, - month: 9, - week: Second, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-09-12") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 9, + week: Second, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-09-12") + result == expected +} # when second Thursday is another day in the middle of the second week -expect - result = meetup( - { - year: 2013, - month: 10, - week: Second, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-10-10") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 10, + week: Second, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-10-10") + result == expected +} # when second Friday is the 8th, the first day of the second week -expect - result = meetup( - { - year: 2013, - month: 11, - week: Second, - day_of_week: Friday, - }, - ) - expected = Ok("2013-11-08") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 11, + week: Second, + day_of_week: Friday, + }, + ) + expected = Ok("2013-11-08") + result == expected +} # when second Friday is some day in the middle of the second week -expect - result = meetup( - { - year: 2013, - month: 12, - week: Second, - day_of_week: Friday, - }, - ) - expected = Ok("2013-12-13") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 12, + week: Second, + day_of_week: Friday, + }, + ) + expected = Ok("2013-12-13") + result == expected +} # when second Saturday is some day in the middle of the second week -expect - result = meetup( - { - year: 2013, - month: 1, - week: Second, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-01-12") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 1, + week: Second, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-01-12") + result == expected +} # when second Saturday is another day in the middle of the second week -expect - result = meetup( - { - year: 2013, - month: 2, - week: Second, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-02-09") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 2, + week: Second, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-02-09") + result == expected +} # when second Sunday is some day in the middle of the second week -expect - result = meetup( - { - year: 2013, - month: 3, - week: Second, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-03-10") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: Second, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-03-10") + result == expected +} # when second Sunday is the 14th, the last day of the second week -expect - result = meetup( - { - year: 2013, - month: 4, - week: Second, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-04-14") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Second, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-04-14") + result == expected +} # when third Monday is some day in the middle of the third week -expect - result = meetup( - { - year: 2013, - month: 3, - week: Third, - day_of_week: Monday, - }, - ) - expected = Ok("2013-03-18") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: Third, + day_of_week: Monday, + }, + ) + expected = Ok("2013-03-18") + result == expected +} # when third Monday is the 15th, the first day of the third week -expect - result = meetup( - { - year: 2013, - month: 4, - week: Third, - day_of_week: Monday, - }, - ) - expected = Ok("2013-04-15") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Third, + day_of_week: Monday, + }, + ) + expected = Ok("2013-04-15") + result == expected +} # when third Tuesday is the 21st, the last day of the third week -expect - result = meetup( - { - year: 2013, - month: 5, - week: Third, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-05-21") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 5, + week: Third, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-05-21") + result == expected +} # when third Tuesday is some day in the middle of the third week -expect - result = meetup( - { - year: 2013, - month: 6, - week: Third, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-06-18") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 6, + week: Third, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-06-18") + result == expected +} # when third Wednesday is some day in the middle of the third week -expect - result = meetup( - { - year: 2013, - month: 7, - week: Third, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-07-17") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 7, + week: Third, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-07-17") + result == expected +} # when third Wednesday is the 21st, the last day of the third week -expect - result = meetup( - { - year: 2013, - month: 8, - week: Third, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-08-21") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 8, + week: Third, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-08-21") + result == expected +} # when third Thursday is some day in the middle of the third week -expect - result = meetup( - { - year: 2013, - month: 9, - week: Third, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-09-19") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 9, + week: Third, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-09-19") + result == expected +} # when third Thursday is another day in the middle of the third week -expect - result = meetup( - { - year: 2013, - month: 10, - week: Third, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-10-17") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 10, + week: Third, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-10-17") + result == expected +} # when third Friday is the 15th, the first day of the third week -expect - result = meetup( - { - year: 2013, - month: 11, - week: Third, - day_of_week: Friday, - }, - ) - expected = Ok("2013-11-15") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 11, + week: Third, + day_of_week: Friday, + }, + ) + expected = Ok("2013-11-15") + result == expected +} # when third Friday is some day in the middle of the third week -expect - result = meetup( - { - year: 2013, - month: 12, - week: Third, - day_of_week: Friday, - }, - ) - expected = Ok("2013-12-20") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 12, + week: Third, + day_of_week: Friday, + }, + ) + expected = Ok("2013-12-20") + result == expected +} # when third Saturday is some day in the middle of the third week -expect - result = meetup( - { - year: 2013, - month: 1, - week: Third, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-01-19") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 1, + week: Third, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-01-19") + result == expected +} # when third Saturday is another day in the middle of the third week -expect - result = meetup( - { - year: 2013, - month: 2, - week: Third, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-02-16") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 2, + week: Third, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-02-16") + result == expected +} # when third Sunday is some day in the middle of the third week -expect - result = meetup( - { - year: 2013, - month: 3, - week: Third, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-03-17") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: Third, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-03-17") + result == expected +} # when third Sunday is the 21st, the last day of the third week -expect - result = meetup( - { - year: 2013, - month: 4, - week: Third, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-04-21") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Third, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-04-21") + result == expected +} # when fourth Monday is some day in the middle of the fourth week -expect - result = meetup( - { - year: 2013, - month: 3, - week: Fourth, - day_of_week: Monday, - }, - ) - expected = Ok("2013-03-25") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: Fourth, + day_of_week: Monday, + }, + ) + expected = Ok("2013-03-25") + result == expected +} # when fourth Monday is the 22nd, the first day of the fourth week -expect - result = meetup( - { - year: 2013, - month: 4, - week: Fourth, - day_of_week: Monday, - }, - ) - expected = Ok("2013-04-22") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Fourth, + day_of_week: Monday, + }, + ) + expected = Ok("2013-04-22") + result == expected +} # when fourth Tuesday is the 28th, the last day of the fourth week -expect - result = meetup( - { - year: 2013, - month: 5, - week: Fourth, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-05-28") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 5, + week: Fourth, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-05-28") + result == expected +} # when fourth Tuesday is some day in the middle of the fourth week -expect - result = meetup( - { - year: 2013, - month: 6, - week: Fourth, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-06-25") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 6, + week: Fourth, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-06-25") + result == expected +} # when fourth Wednesday is some day in the middle of the fourth week -expect - result = meetup( - { - year: 2013, - month: 7, - week: Fourth, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-07-24") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 7, + week: Fourth, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-07-24") + result == expected +} # when fourth Wednesday is the 28th, the last day of the fourth week -expect - result = meetup( - { - year: 2013, - month: 8, - week: Fourth, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-08-28") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 8, + week: Fourth, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-08-28") + result == expected +} # when fourth Thursday is some day in the middle of the fourth week -expect - result = meetup( - { - year: 2013, - month: 9, - week: Fourth, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-09-26") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 9, + week: Fourth, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-09-26") + result == expected +} # when fourth Thursday is another day in the middle of the fourth week -expect - result = meetup( - { - year: 2013, - month: 10, - week: Fourth, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-10-24") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 10, + week: Fourth, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-10-24") + result == expected +} # when fourth Friday is the 22nd, the first day of the fourth week -expect - result = meetup( - { - year: 2013, - month: 11, - week: Fourth, - day_of_week: Friday, - }, - ) - expected = Ok("2013-11-22") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 11, + week: Fourth, + day_of_week: Friday, + }, + ) + expected = Ok("2013-11-22") + result == expected +} # when fourth Friday is some day in the middle of the fourth week -expect - result = meetup( - { - year: 2013, - month: 12, - week: Fourth, - day_of_week: Friday, - }, - ) - expected = Ok("2013-12-27") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 12, + week: Fourth, + day_of_week: Friday, + }, + ) + expected = Ok("2013-12-27") + result == expected +} # when fourth Saturday is some day in the middle of the fourth week -expect - result = meetup( - { - year: 2013, - month: 1, - week: Fourth, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-01-26") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 1, + week: Fourth, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-01-26") + result == expected +} # when fourth Saturday is another day in the middle of the fourth week -expect - result = meetup( - { - year: 2013, - month: 2, - week: Fourth, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-02-23") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 2, + week: Fourth, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-02-23") + result == expected +} # when fourth Sunday is some day in the middle of the fourth week -expect - result = meetup( - { - year: 2013, - month: 3, - week: Fourth, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-03-24") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: Fourth, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-03-24") + result == expected +} # when fourth Sunday is the 28th, the last day of the fourth week -expect - result = meetup( - { - year: 2013, - month: 4, - week: Fourth, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-04-28") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Fourth, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-04-28") + result == expected +} # last Monday in a month with four Mondays -expect - result = meetup( - { - year: 2013, - month: 3, - week: Last, - day_of_week: Monday, - }, - ) - expected = Ok("2013-03-25") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: Last, + day_of_week: Monday, + }, + ) + expected = Ok("2013-03-25") + result == expected +} # last Monday in a month with five Mondays -expect - result = meetup( - { - year: 2013, - month: 4, - week: Last, - day_of_week: Monday, - }, - ) - expected = Ok("2013-04-29") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Last, + day_of_week: Monday, + }, + ) + expected = Ok("2013-04-29") + result == expected +} # last Tuesday in a month with four Tuesdays -expect - result = meetup( - { - year: 2013, - month: 5, - week: Last, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-05-28") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 5, + week: Last, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-05-28") + result == expected +} # last Tuesday in another month with four Tuesdays -expect - result = meetup( - { - year: 2013, - month: 6, - week: Last, - day_of_week: Tuesday, - }, - ) - expected = Ok("2013-06-25") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 6, + week: Last, + day_of_week: Tuesday, + }, + ) + expected = Ok("2013-06-25") + result == expected +} # last Wednesday in a month with five Wednesdays -expect - result = meetup( - { - year: 2013, - month: 7, - week: Last, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-07-31") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 7, + week: Last, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-07-31") + result == expected +} # last Wednesday in a month with four Wednesdays -expect - result = meetup( - { - year: 2013, - month: 8, - week: Last, - day_of_week: Wednesday, - }, - ) - expected = Ok("2013-08-28") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 8, + week: Last, + day_of_week: Wednesday, + }, + ) + expected = Ok("2013-08-28") + result == expected +} # last Thursday in a month with four Thursdays -expect - result = meetup( - { - year: 2013, - month: 9, - week: Last, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-09-26") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 9, + week: Last, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-09-26") + result == expected +} # last Thursday in a month with five Thursdays -expect - result = meetup( - { - year: 2013, - month: 10, - week: Last, - day_of_week: Thursday, - }, - ) - expected = Ok("2013-10-31") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 10, + week: Last, + day_of_week: Thursday, + }, + ) + expected = Ok("2013-10-31") + result == expected +} # last Friday in a month with five Fridays -expect - result = meetup( - { - year: 2013, - month: 11, - week: Last, - day_of_week: Friday, - }, - ) - expected = Ok("2013-11-29") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 11, + week: Last, + day_of_week: Friday, + }, + ) + expected = Ok("2013-11-29") + result == expected +} # last Friday in a month with four Fridays -expect - result = meetup( - { - year: 2013, - month: 12, - week: Last, - day_of_week: Friday, - }, - ) - expected = Ok("2013-12-27") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 12, + week: Last, + day_of_week: Friday, + }, + ) + expected = Ok("2013-12-27") + result == expected +} # last Saturday in a month with four Saturdays -expect - result = meetup( - { - year: 2013, - month: 1, - week: Last, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-01-26") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 1, + week: Last, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-01-26") + result == expected +} # last Saturday in another month with four Saturdays -expect - result = meetup( - { - year: 2013, - month: 2, - week: Last, - day_of_week: Saturday, - }, - ) - expected = Ok("2013-02-23") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 2, + week: Last, + day_of_week: Saturday, + }, + ) + expected = Ok("2013-02-23") + result == expected +} # last Sunday in a month with five Sundays -expect - result = meetup( - { - year: 2013, - month: 3, - week: Last, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-03-31") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 3, + week: Last, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-03-31") + result == expected +} # last Sunday in a month with four Sundays -expect - result = meetup( - { - year: 2013, - month: 4, - week: Last, - day_of_week: Sunday, - }, - ) - expected = Ok("2013-04-28") - result == expected +expect { + result = meetup( + { + year: 2013, + month: 4, + week: Last, + day_of_week: Sunday, + }, + ) + expected = Ok("2013-04-28") + result == expected +} # when last Wednesday in February in a leap year is the 29th -expect - result = meetup( - { - year: 2012, - month: 2, - week: Last, - day_of_week: Wednesday, - }, - ) - expected = Ok("2012-02-29") - result == expected +expect { + result = meetup( + { + year: 2012, + month: 2, + week: Last, + day_of_week: Wednesday, + }, + ) + expected = Ok("2012-02-29") + result == expected +} # last Wednesday in December that is also the last day of the year -expect - result = meetup( - { - year: 2014, - month: 12, - week: Last, - day_of_week: Wednesday, - }, - ) - expected = Ok("2014-12-31") - result == expected +expect { + result = meetup( + { + year: 2014, + month: 12, + week: Last, + day_of_week: Wednesday, + }, + ) + expected = Ok("2014-12-31") + result == expected +} # when last Sunday in February in a non-leap year is not the 29th -expect - result = meetup( - { - year: 2015, - month: 2, - week: Last, - day_of_week: Sunday, - }, - ) - expected = Ok("2015-02-22") - result == expected +expect { + result = meetup( + { + year: 2015, + month: 2, + week: Last, + day_of_week: Sunday, + }, + ) + expected = Ok("2015-02-22") + result == expected +} # when first Friday is the 7th, the last day of the first week -expect - result = meetup( - { - year: 2012, - month: 12, - week: First, - day_of_week: Friday, - }, - ) - expected = Ok("2012-12-07") - result == expected +expect { + result = meetup( + { + year: 2012, + month: 12, + week: First, + day_of_week: Friday, + }, + ) + expected = Ok("2012-12-07") + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/micro-blog/.meta/template.j2 b/exercises/practice/micro-blog/.meta/template.j2 index 1473238d..4c6ab6a5 100644 --- a/exercises/practice/micro-blog/.meta/template.j2 +++ b/exercises/practice/micro-blog/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["phrase"] | to_roc }}) result == Ok({{ case["expected"] | to_roc }}) +} {% endfor %} diff --git a/exercises/practice/micro-blog/micro-blog-test.roc b/exercises/practice/micro-blog/micro-blog-test.roc index 04dbf0dd..c23c11e0 100644 --- a/exercises/practice/micro-blog/micro-blog-test.roc +++ b/exercises/practice/micro-blog/micro-blog-test.roc @@ -1,75 +1,88 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/micro-blog/canonical-data.json -# File last updated on 2025-09-15 +# File last updated on 2026-06-13 app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", - unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", + unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br", } import pf.Stdout -main! = |_args| - Stdout.line!("") - import MicroBlog exposing [truncate] # English language short -expect - result = truncate("Hi") - result == Ok("Hi") +expect { + result = truncate("Hi") + result == Ok("Hi") +} # English language long -expect - result = truncate("Hello there") - result == Ok("Hello") +expect { + result = truncate("Hello there") + result == Ok("Hello") +} # German language short (broth) -expect - result = truncate("brühe") - result == Ok("brühe") +expect { + result = truncate("brühe") + result == Ok("brühe") +} # German language long (bear carpet → beards) -expect - result = truncate("Bärteppich") - result == Ok("Bärte") +expect { + result = truncate("Bärteppich") + result == Ok("Bärte") +} # Bulgarian language short (good) -expect - result = truncate("Добър") - result == Ok("Добър") +expect { + result = truncate("Добър") + result == Ok("Добър") +} # Greek language short (health) -expect - result = truncate("υγειά") - result == Ok("υγειά") +expect { + result = truncate("υγειά") + result == Ok("υγειά") +} # Maths short -expect - result = truncate("a=πr²") - result == Ok("a=πr²") +expect { + result = truncate("a=πr²") + result == Ok("a=πr²") +} # Maths long -expect - result = truncate("∅⊊ℕ⊊ℤ⊊ℚ⊊ℝ⊊ℂ") - result == Ok("∅⊊ℕ⊊ℤ") +expect { + result = truncate("∅⊊ℕ⊊ℤ⊊ℚ⊊ℝ⊊ℂ") + result == Ok("∅⊊ℕ⊊ℤ") +} # English and emoji short -expect - result = truncate("Fly 🛫") - result == Ok("Fly 🛫") +expect { + result = truncate("Fly 🛫") + result == Ok("Fly 🛫") +} # Emoji short -expect - result = truncate("💇") - result == Ok("💇") +expect { + result = truncate("💇") + result == Ok("💇") +} # Emoji long -expect - result = truncate("❄🌡🤧🤒🏥🕰😀") - result == Ok("❄🌡🤧🤒🏥") +expect { + result = truncate("❄🌡🤧🤒🏥🕰😀") + result == Ok("❄🌡🤧🤒🏥") +} # Royal Flush? -expect - result = truncate("🃎🂸🃅🃋🃍🃁🃊") - result == Ok("🃎🂸🃅🃋🃍") +expect { + result = truncate("🃎🂸🃅🃋🃍🃁🃊") + result == Ok("🃎🂸🃅🃋🃍") +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/minesweeper/.meta/template.j2 b/exercises/practice/minesweeper/.meta/template.j2 index 3f7155c9..26848a7b 100644 --- a/exercises/practice/minesweeper/.meta/template.j2 +++ b/exercises/practice/minesweeper/.meta/template.j2 @@ -6,11 +6,12 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect - minefield = {{ case["input"]["minefield"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }} |> Str.replace_each("·", " ") +expect { + minefield = {{ case["input"]["minefield"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") result = {{ case["property"] | to_snake }}(minefield) - expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }} |> Str.replace_each("·", " ") + expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") result == expected +} {% endfor %} diff --git a/exercises/practice/minesweeper/minesweeper-test.roc b/exercises/practice/minesweeper/minesweeper-test.roc index 80a7e710..f29f4d4c 100644 --- a/exercises/practice/minesweeper/minesweeper-test.roc +++ b/exercises/practice/minesweeper/minesweeper-test.roc @@ -1,212 +1,236 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/minesweeper/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Minesweeper exposing [annotate] # no rows -expect - minefield = "" |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = "" |> Str.replace_each("·", " ") - result == expected +expect { + minefield = "".replace_each("·", " ") + result = annotate(minefield) + expected = "".replace_each("·", " ") + result == expected +} # no columns -expect - minefield = "" |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = "" |> Str.replace_each("·", " ") - result == expected +expect { + minefield = "".replace_each("·", " ") + result = annotate(minefield) + expected = "".replace_each("·", " ") + result == expected +} # no mines -expect - minefield = - """ - ··· - ··· - ··· - """ - |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = - """ - ··· - ··· - ··· - """ - |> Str.replace_each("·", " ") - result == expected +expect { + minefield = + \\··· + \\··· + \\··· + .replace_each( + "·", + " ", + ) + result = annotate(minefield) + expected = + \\··· + \\··· + \\··· + .replace_each( + "·", + " ", + ) + result == expected +} # minefield with only mines -expect - minefield = - """ - *** - *** - *** - """ - |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = - """ - *** - *** - *** - """ - |> Str.replace_each("·", " ") - result == expected +expect { + minefield = + \\*** + \\*** + \\*** + .replace_each( + "·", + " ", + ) + result = annotate(minefield) + expected = + \\*** + \\*** + \\*** + .replace_each( + "·", + " ", + ) + result == expected +} # mine surrounded by spaces -expect - minefield = - """ - ··· - ·*· - ··· - """ - |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = - """ - 111 - 1*1 - 111 - """ - |> Str.replace_each("·", " ") - result == expected +expect { + minefield = + \\··· + \\·*· + \\··· + .replace_each( + "·", + " ", + ) + result = annotate(minefield) + expected = + \\111 + \\1*1 + \\111 + .replace_each( + "·", + " ", + ) + result == expected +} # space surrounded by mines -expect - minefield = - """ - *** - *·* - *** - """ - |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = - """ - *** - *8* - *** - """ - |> Str.replace_each("·", " ") - result == expected +expect { + minefield = + \\*** + \\*·* + \\*** + .replace_each( + "·", + " ", + ) + result = annotate(minefield) + expected = + \\*** + \\*8* + \\*** + .replace_each( + "·", + " ", + ) + result == expected +} # horizontal line -expect - minefield = "·*·*·" |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = "1*2*1" |> Str.replace_each("·", " ") - result == expected +expect { + minefield = "·*·*·".replace_each("·", " ") + result = annotate(minefield) + expected = "1*2*1".replace_each("·", " ") + result == expected +} # horizontal line, mines at edges -expect - minefield = "*···*" |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = "*1·1*" |> Str.replace_each("·", " ") - result == expected +expect { + minefield = "*···*".replace_each("·", " ") + result = annotate(minefield) + expected = "*1·1*".replace_each("·", " ") + result == expected +} # vertical line -expect - minefield = - """ - · - * - · - * - · - """ - |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = - """ - 1 - * - 2 - * - 1 - """ - |> Str.replace_each("·", " ") - result == expected +expect { + minefield = + \\· + \\* + \\· + \\* + \\· + .replace_each( + "·", + " ", + ) + result = annotate(minefield) + expected = + \\1 + \\* + \\2 + \\* + \\1 + .replace_each( + "·", + " ", + ) + result == expected +} # vertical line, mines at edges -expect - minefield = - """ - * - · - · - · - * - """ - |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = - """ - * - 1 - · - 1 - * - """ - |> Str.replace_each("·", " ") - result == expected +expect { + minefield = + \\* + \\· + \\· + \\· + \\* + .replace_each( + "·", + " ", + ) + result = annotate(minefield) + expected = + \\* + \\1 + \\· + \\1 + \\* + .replace_each( + "·", + " ", + ) + result == expected +} # cross -expect - minefield = - """ - ··*·· - ··*·· - ***** - ··*·· - ··*·· - """ - |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = - """ - ·2*2· - 25*52 - ***** - 25*52 - ·2*2· - """ - |> Str.replace_each("·", " ") - result == expected +expect { + minefield = + \\··*·· + \\··*·· + \\***** + \\··*·· + \\··*·· + .replace_each( + "·", + " ", + ) + result = annotate(minefield) + expected = + \\·2*2· + \\25*52 + \\***** + \\25*52 + \\·2*2· + .replace_each( + "·", + " ", + ) + result == expected +} # large minefield -expect - minefield = - """ - ·*··*· - ··*··· - ····*· - ···*·* - ·*··*· - ······ - """ - |> Str.replace_each("·", " ") - result = annotate(minefield) - expected = - """ - 1*22*1 - 12*322 - ·123*2 - 112*4* - 1*22*2 - 111111 - """ - |> Str.replace_each("·", " ") - result == expected +expect { + minefield = + \\·*··*· + \\··*··· + \\····*· + \\···*·* + \\·*··*· + \\······ + .replace_each( + "·", + " ", + ) + result = annotate(minefield) + expected = + \\1*22*1 + \\12*322 + \\·123*2 + \\112*4* + \\1*22*2 + \\111111 + .replace_each( + "·", + " ", + ) + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/nth-prime/.meta/template.j2 b/exercises/practice/nth-prime/.meta/template.j2 index 1934f692..a90aa4ec 100644 --- a/exercises/practice/nth-prime/.meta/template.j2 +++ b/exercises/practice/nth-prime/.meta/template.j2 @@ -6,13 +6,14 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["number"] }}) {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} result == Ok({{ case["expected"] }}) {%- endif %} +} {% endfor %} diff --git a/exercises/practice/nth-prime/nth-prime-test.roc b/exercises/practice/nth-prime/nth-prime-test.roc index 1d6c324c..6b93ec43 100644 --- a/exercises/practice/nth-prime/nth-prime-test.roc +++ b/exercises/practice/nth-prime/nth-prime-test.roc @@ -1,39 +1,40 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/nth-prime/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import NthPrime exposing [prime] # first prime -expect - result = prime(1) - result == Ok(2) +expect { + result = prime(1) + result == Ok(2) +} # second prime -expect - result = prime(2) - result == Ok(3) +expect { + result = prime(2) + result == Ok(3) +} # sixth prime -expect - result = prime(6) - result == Ok(13) +expect { + result = prime(6) + result == Ok(13) +} # big prime -expect - result = prime(10001) - result == Ok(104743) +expect { + result = prime(10001) + result == Ok(104743) +} # there is no zeroth prime -expect - result = prime(0) - result |> Result.is_err +expect { + result = prime(0) + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/nucleotide-count/.meta/template.j2 b/exercises/practice/nucleotide-count/.meta/template.j2 index faad29b1..4cbccb9f 100644 --- a/exercises/practice/nucleotide-count/.meta/template.j2 +++ b/exercises/practice/nucleotide-count/.meta/template.j2 @@ -6,13 +6,14 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["strand"] | to_roc }}) {%- if case["expected"]["error"] %} - Result.is_err(result) + result.is_err() {%- else %} result == Ok({{ case["expected"] | to_roc }}) {%- endif %} +} {% endfor %} diff --git a/exercises/practice/nucleotide-count/nucleotide-count-test.roc b/exercises/practice/nucleotide-count/nucleotide-count-test.roc index fcb82444..f9ab1aee 100644 --- a/exercises/practice/nucleotide-count/nucleotide-count-test.roc +++ b/exercises/practice/nucleotide-count/nucleotide-count-test.roc @@ -1,39 +1,40 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/nucleotide-count/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import NucleotideCount exposing [nucleotide_counts] # empty strand -expect - result = nucleotide_counts("") - result == Ok({ a: 0, c: 0, g: 0, t: 0 }) +expect { + result = nucleotide_counts("") + result == Ok({ a: 0, c: 0, g: 0, t: 0 }) +} # can count one nucleotide in single-character input -expect - result = nucleotide_counts("G") - result == Ok({ a: 0, c: 0, g: 1, t: 0 }) +expect { + result = nucleotide_counts("G") + result == Ok({ a: 0, c: 0, g: 1, t: 0 }) +} # strand with repeated nucleotide -expect - result = nucleotide_counts("GGGGGGG") - result == Ok({ a: 0, c: 0, g: 7, t: 0 }) +expect { + result = nucleotide_counts("GGGGGGG") + result == Ok({ a: 0, c: 0, g: 7, t: 0 }) +} # strand with multiple nucleotides -expect - result = nucleotide_counts("AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC") - result == Ok({ a: 20, c: 12, g: 17, t: 21 }) +expect { + result = nucleotide_counts("AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC") + result == Ok({ a: 20, c: 12, g: 17, t: 21 }) +} # strand with invalid nucleotides -expect - result = nucleotide_counts("AGXXACT") - Result.is_err(result) +expect { + result = nucleotide_counts("AGXXACT") + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/ocr-numbers/.meta/template.j2 b/exercises/practice/ocr-numbers/.meta/template.j2 index 620860e5..b2419365 100644 --- a/exercises/practice/ocr-numbers/.meta/template.j2 +++ b/exercises/practice/ocr-numbers/.meta/template.j2 @@ -6,15 +6,16 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { grid = {{ case["input"]["rows"] | to_roc_multiline_string | indent(8) }} result = {{ case["property"] | to_snake }}(grid) {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} expected = Ok({{ case["expected"] | to_roc }}) result == expected {%- endif %} +} {% endfor %} diff --git a/exercises/practice/ocr-numbers/ocr-numbers-test.roc b/exercises/practice/ocr-numbers/ocr-numbers-test.roc index 0151351b..7b1ef617 100644 --- a/exercises/practice/ocr-numbers/ocr-numbers-test.roc +++ b/exercises/practice/ocr-numbers/ocr-numbers-test.roc @@ -1,240 +1,236 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/ocr-numbers/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import OcrNumbers exposing [convert] # Recognizes 0 -expect - grid = - """ - _ - | | - |_| - - """ - result = convert(grid) - expected = Ok("0") - result == expected +expect { + grid = + \\ _ + \\| | + \\|_| + \\ + + result = convert(grid) + expected = Ok("0") + result == expected +} # Recognizes 1 -expect - grid = - """ - - | - | - - """ - result = convert(grid) - expected = Ok("1") - result == expected +expect { + grid = + \\ + \\ | + \\ | + \\ + + result = convert(grid) + expected = Ok("1") + result == expected +} # Unreadable but correctly sized inputs return ? -expect - grid = - """ - - _ - | - - """ - result = convert(grid) - expected = Ok("?") - result == expected +expect { + grid = + \\ + \\ _ + \\ | + \\ + + result = convert(grid) + expected = Ok("?") + result == expected +} # Input with a number of lines that is not a multiple of four raises an error -expect - grid = - """ - _ - | | - - """ - result = convert(grid) - result |> Result.is_err +expect { + grid = + \\ _ + \\| | + \\ + + result = convert(grid) + result.is_err() +} # Input with a number of columns that is not a multiple of three raises an error -expect - grid = - """ - - | - | - - """ - result = convert(grid) - result |> Result.is_err +expect { + grid = + \\ + \\ | + \\ | + \\ + + result = convert(grid) + result.is_err() +} # Recognizes 110101100 -expect - grid = - """ - _ _ _ _ - | || | || | | || || | - | ||_| ||_| | ||_||_| - - """ - result = convert(grid) - expected = Ok("110101100") - result == expected +expect { + grid = + \\ _ _ _ _ + \\ | || | || | | || || | + \\ | ||_| ||_| | ||_||_| + \\ + + result = convert(grid) + expected = Ok("110101100") + result == expected +} # Garbled numbers in a string are replaced with ? -expect - grid = - """ - _ _ _ - | || | || | || || | - | | _| ||_| | ||_||_| - - """ - result = convert(grid) - expected = Ok("11?10?1?0") - result == expected +expect { + grid = + \\ _ _ _ + \\ | || | || | || || | + \\ | | _| ||_| | ||_||_| + \\ + + result = convert(grid) + expected = Ok("11?10?1?0") + result == expected +} # Recognizes 2 -expect - grid = - """ - _ - _| - |_ - - """ - result = convert(grid) - expected = Ok("2") - result == expected +expect { + grid = + \\ _ + \\ _| + \\|_ + \\ + + result = convert(grid) + expected = Ok("2") + result == expected +} # Recognizes 3 -expect - grid = - """ - _ - _| - _| - - """ - result = convert(grid) - expected = Ok("3") - result == expected +expect { + grid = + \\ _ + \\ _| + \\ _| + \\ + + result = convert(grid) + expected = Ok("3") + result == expected +} # Recognizes 4 -expect - grid = - """ - - |_| - | - - """ - result = convert(grid) - expected = Ok("4") - result == expected +expect { + grid = + \\ + \\|_| + \\ | + \\ + + result = convert(grid) + expected = Ok("4") + result == expected +} # Recognizes 5 -expect - grid = - """ - _ - |_ - _| - - """ - result = convert(grid) - expected = Ok("5") - result == expected +expect { + grid = + \\ _ + \\|_ + \\ _| + \\ + + result = convert(grid) + expected = Ok("5") + result == expected +} # Recognizes 6 -expect - grid = - """ - _ - |_ - |_| - - """ - result = convert(grid) - expected = Ok("6") - result == expected +expect { + grid = + \\ _ + \\|_ + \\|_| + \\ + + result = convert(grid) + expected = Ok("6") + result == expected +} # Recognizes 7 -expect - grid = - """ - _ - | - | - - """ - result = convert(grid) - expected = Ok("7") - result == expected +expect { + grid = + \\ _ + \\ | + \\ | + \\ + + result = convert(grid) + expected = Ok("7") + result == expected +} # Recognizes 8 -expect - grid = - """ - _ - |_| - |_| - - """ - result = convert(grid) - expected = Ok("8") - result == expected +expect { + grid = + \\ _ + \\|_| + \\|_| + \\ + + result = convert(grid) + expected = Ok("8") + result == expected +} # Recognizes 9 -expect - grid = - """ - _ - |_| - _| - - """ - result = convert(grid) - expected = Ok("9") - result == expected +expect { + grid = + \\ _ + \\|_| + \\ _| + \\ + + result = convert(grid) + expected = Ok("9") + result == expected +} # Recognizes string of decimal numbers -expect - grid = - """ - _ _ _ _ _ _ _ _ - | _| _||_||_ |_ ||_||_|| | - ||_ _| | _||_| ||_| _||_| - - """ - result = convert(grid) - expected = Ok("1234567890") - result == expected +expect { + grid = + \\ _ _ _ _ _ _ _ _ + \\ | _| _||_||_ |_ ||_||_|| | + \\ ||_ _| | _||_| ||_| _||_| + \\ + + result = convert(grid) + expected = Ok("1234567890") + result == expected +} # Numbers separated by empty lines are recognized. Lines are joined by commas. -expect - grid = - """ - _ _ - | _| _| - ||_ _| - - _ _ - |_||_ |_ - | _||_| - - _ _ _ - ||_||_| - ||_| _| - - """ - result = convert(grid) - expected = Ok("123,456,789") - result == expected +expect { + grid = + \\ _ _ + \\ | _| _| + \\ ||_ _| + \\ + \\ _ _ + \\|_||_ |_ + \\ | _||_| + \\ + \\ _ _ _ + \\ ||_||_| + \\ ||_| _| + \\ + + result = convert(grid) + expected = Ok("123,456,789") + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/octal/octal-test.roc b/exercises/practice/octal/octal-test.roc index 2127ca2c..2c22c787 100644 --- a/exercises/practice/octal/octal-test.roc +++ b/exercises/practice/octal/octal-test.roc @@ -1,221 +1,261 @@ -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Octal exposing [parse] # Parse "0" -expect - result = parse("0") - result == Ok(0) +expect { + result = parse("0") + result == Ok(0) +} # Parse "1" -expect - result = parse("1") - result == Ok(1) +expect { + result = parse("1") + result == Ok(1) +} # Parse "2" -expect - result = parse("2") - result == Ok(2) +expect { + result = parse("2") + result == Ok(2) +} # Parse "3" -expect - result = parse("3") - result == Ok(3) +expect { + result = parse("3") + result == Ok(3) +} # Parse "4" -expect - result = parse("4") - result == Ok(4) +expect { + result = parse("4") + result == Ok(4) +} # Parse "5" -expect - result = parse("5") - result == Ok(5) +expect { + result = parse("5") + result == Ok(5) +} # Parse "6" -expect - result = parse("6") - result == Ok(6) +expect { + result = parse("6") + result == Ok(6) +} # Parse "7" -expect - result = parse("7") - result == Ok(7) +expect { + result = parse("7") + result == Ok(7) +} # Parse "10" -expect - result = parse("10") - result == Ok(8) +expect { + result = parse("10") + result == Ok(8) +} # Parse "11" -expect - result = parse("11") - result == Ok(9) +expect { + result = parse("11") + result == Ok(9) +} # Parse "12" -expect - result = parse("12") - result == Ok(10) +expect { + result = parse("12") + result == Ok(10) +} # Parse "13" -expect - result = parse("13") - result == Ok(11) +expect { + result = parse("13") + result == Ok(11) +} # Parse "14" -expect - result = parse("14") - result == Ok(12) +expect { + result = parse("14") + result == Ok(12) +} # Parse "15" -expect - result = parse("15") - result == Ok(13) +expect { + result = parse("15") + result == Ok(13) +} # Parse "16" -expect - result = parse("16") - result == Ok(14) +expect { + result = parse("16") + result == Ok(14) +} # Parse "17" -expect - result = parse("17") - result == Ok(15) +expect { + result = parse("17") + result == Ok(15) +} # Parse "20" -expect - result = parse("20") - result == Ok(16) +expect { + result = parse("20") + result == Ok(16) +} # Parse "21" -expect - result = parse("21") - result == Ok(17) +expect { + result = parse("21") + result == Ok(17) +} # Parse "22" -expect - result = parse("22") - result == Ok(18) +expect { + result = parse("22") + result == Ok(18) +} # Parse "77" -expect - result = parse("77") - result == Ok(63) +expect { + result = parse("77") + result == Ok(63) +} # Parse "100" -expect - result = parse("100") - result == Ok(64) +expect { + result = parse("100") + result == Ok(64) +} # Parse "1234567654321" -expect - result = parse("1234567654321") - result == Ok(89755965649) +expect { + result = parse("1234567654321") + result == Ok(89755965649) +} # Parse "1777777777777777777777", the largest U64 value -expect - result = parse("1777777777777777777777") - result == Ok(18446744073709551615) +expect { + result = parse("1777777777777777777777") + result == Ok(18446744073709551615) +} # Ignore leading zeros in "00000" -expect - result = parse("00000") - result == Ok(0) +expect { + result = parse("00000") + result == Ok(0) +} # Ignore leading zeros in "00001" -expect - result = parse("00001") - result == Ok(1) +expect { + result = parse("00001") + result == Ok(1) +} # Ignore leading zeros in "00007" -expect - result = parse("00007") - result == Ok(7) +expect { + result = parse("00007") + result == Ok(7) +} # Ignore leading zeros in "000010" -expect - result = parse("000010") - result == Ok(8) +expect { + result = parse("000010") + result == Ok(8) +} # Ignore leading zeros in "000077" -expect - result = parse("000077") - result == Ok(63) +expect { + result = parse("000077") + result == Ok(63) +} # Ignore leading zeros in "000070000" -expect - result = parse("000070000") - result == Ok(28672) +expect { + result = parse("000070000") + result == Ok(28672) +} # Ignore leading zeros even before the largest U64 value -expect - result = parse("00001777777777777777777777") - result == Ok(18446744073709551615) +expect { + result = parse("00001777777777777777777777") + result == Ok(18446744073709551615) +} # Empty strings are invalid -expect - result = parse("") - result |> Result.is_err +expect { + result = parse("") + result.is_err() +} # A string with only spaces is invalid -expect - result = parse(" ") - result |> Result.is_err +expect { + result = parse(" ") + result.is_err() +} # Leading spaces are invalid -expect - result = parse(" 1234567") - result |> Result.is_err +expect { + result = parse(" 1234567") + result.is_err() +} # Trailing spaces are invalid -expect - result = parse("1234567 ") - result |> Result.is_err +expect { + result = parse("1234567 ") + result.is_err() +} # Spaces anywhere are invalid -expect - result = parse("123 4567") - result |> Result.is_err +expect { + result = parse("123 4567") + result.is_err() +} # Invalid character in "1*234" -expect - result = parse("1*234") - result |> Result.is_err +expect { + result = parse("1*234") + result.is_err() +} # Invalid character in "12abc" -expect - result = parse("12abc") - result |> Result.is_err +expect { + result = parse("12abc") + result.is_err() +} # Invalid character in "12/34" -expect - result = parse("12/34") - result |> Result.is_err +expect { + result = parse("12/34") + result.is_err() +} # Invalid character in "1234\n" -expect - result = parse("1234\n") - result |> Result.is_err +expect { + result = parse("1234\n") + result.is_err() +} # Invalid character in "-1234" -expect - result = parse("-1234") - result |> Result.is_err +expect { + result = parse("-1234") + result.is_err() +} # Invalid character in "+1234" -expect - result = parse("+1234") - result |> Result.is_err +expect { + result = parse("+1234") + result.is_err() +} # For numbers that don't fit in an U64, `parse` should return an error # instead of crashing -expect - result = parse("2000000000000000000000") - result |> Result.is_err +expect { + result = parse("2000000000000000000000") + result.is_err() +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/palindrome-products/.meta/template.j2 b/exercises/practice/palindrome-products/.meta/template.j2 index 7f769da4..fe14ce8d 100644 --- a/exercises/practice/palindrome-products/.meta/template.j2 +++ b/exercises/practice/palindrome-products/.meta/template.j2 @@ -4,18 +4,21 @@ import {{ exercise | to_pascal }} exposing [smallest, largest] -is_eq = |result, expected| - when (result, expected) is - (Ok({value, factors}), Ok({value: expected_value, factors: expected_factors})) -> - value == expected_value && factors == expected_factors - _ -> Bool.false +is_eq = |result, expected| { + match (result, expected) { + (Ok({value, factors}), Ok({value: expected_value, factors: expected_factors})) => { + value == expected_value and factors == expected_factors + } + _ => Bool.False + } +} {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"] | to_roc }}) {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} expected = Ok({ value: {% if case["expected"]["value"] is none %}0{% else %}{{ case["expected"]["value"] }}{% endif %}, @@ -24,8 +27,9 @@ expect ({{ pair[0] }}, {{ pair[1] }}),{% endfor %} ]) }) - result |> is_eq(expected) + result->is_eq(expected) {%- endif %} +} {% endfor %} diff --git a/exercises/practice/palindrome-products/palindrome-products-test.roc b/exercises/practice/palindrome-products/palindrome-products-test.roc index 3af510f5..d21cc8ca 100644 --- a/exercises/practice/palindrome-products/palindrome-products-test.roc +++ b/exercises/practice/palindrome-products/palindrome-products-test.roc @@ -1,195 +1,204 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/palindrome-products/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import PalindromeProducts exposing [smallest, largest] -is_eq = |result, expected| - when (result, expected) is - (Ok({ value, factors }), Ok({ value: expected_value, factors: expected_factors })) -> - value == expected_value and factors == expected_factors - - _ -> Bool.false +is_eq = |result, expected| { + match (result, expected) { + (Ok({ value, factors }), Ok({ value: expected_value, factors: expected_factors })) => { + value == expected_value and factors == expected_factors + } + _ => Bool.False + } +} # find the smallest palindrome from single digit factors -expect - result = smallest({ min: 1, max: 9 }) - expected = Ok( - { - value: 1, - factors: Set.from_list( - [ - (1, 1), - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = smallest({ min: 1, max: 9 }) + expected = Ok( + { + value: 1, + factors: Set.from_list( + [ + (1, 1), + ], + ), + }, + ) + result->is_eq(expected) +} # find the largest palindrome from single digit factors -expect - result = largest({ min: 1, max: 9 }) - expected = Ok( - { - value: 9, - factors: Set.from_list( - [ - (1, 9), - (3, 3), - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = largest({ min: 1, max: 9 }) + expected = Ok( + { + value: 9, + factors: Set.from_list( + [ + (1, 9), + (3, 3), + ], + ), + }, + ) + result->is_eq(expected) +} # find the smallest palindrome from double digit factors -expect - result = smallest({ min: 10, max: 99 }) - expected = Ok( - { - value: 121, - factors: Set.from_list( - [ - (11, 11), - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = smallest({ min: 10, max: 99 }) + expected = Ok( + { + value: 121, + factors: Set.from_list( + [ + (11, 11), + ], + ), + }, + ) + result->is_eq(expected) +} # find the largest palindrome from double digit factors -expect - result = largest({ min: 10, max: 99 }) - expected = Ok( - { - value: 9009, - factors: Set.from_list( - [ - (91, 99), - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = largest({ min: 10, max: 99 }) + expected = Ok( + { + value: 9009, + factors: Set.from_list( + [ + (91, 99), + ], + ), + }, + ) + result->is_eq(expected) +} # find the smallest palindrome from triple digit factors -expect - result = smallest({ min: 100, max: 999 }) - expected = Ok( - { - value: 10201, - factors: Set.from_list( - [ - (101, 101), - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = smallest({ min: 100, max: 999 }) + expected = Ok( + { + value: 10201, + factors: Set.from_list( + [ + (101, 101), + ], + ), + }, + ) + result->is_eq(expected) +} # find the largest palindrome from triple digit factors -expect - result = largest({ min: 100, max: 999 }) - expected = Ok( - { - value: 906609, - factors: Set.from_list( - [ - (913, 993), - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = largest({ min: 100, max: 999 }) + expected = Ok( + { + value: 906609, + factors: Set.from_list( + [ + (913, 993), + ], + ), + }, + ) + result->is_eq(expected) +} # find the smallest palindrome from four digit factors -expect - result = smallest({ min: 1000, max: 9999 }) - expected = Ok( - { - value: 1002001, - factors: Set.from_list( - [ - (1001, 1001), - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = smallest({ min: 1000, max: 9999 }) + expected = Ok( + { + value: 1002001, + factors: Set.from_list( + [ + (1001, 1001), + ], + ), + }, + ) + result->is_eq(expected) +} # find the largest palindrome from four digit factors -expect - result = largest({ min: 1000, max: 9999 }) - expected = Ok( - { - value: 99000099, - factors: Set.from_list( - [ - (9901, 9999), - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = largest({ min: 1000, max: 9999 }) + expected = Ok( + { + value: 99000099, + factors: Set.from_list( + [ + (9901, 9999), + ], + ), + }, + ) + result->is_eq(expected) +} # empty result for smallest if no palindrome in the range -expect - result = smallest({ min: 1002, max: 1003 }) - expected = Ok( - { - value: 0, - factors: Set.from_list( - [ - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = smallest({ min: 1002, max: 1003 }) + expected = Ok( + { + value: 0, + factors: Set.from_list( + [], + ), + }, + ) + result->is_eq(expected) +} # empty result for largest if no palindrome in the range -expect - result = largest({ min: 15, max: 15 }) - expected = Ok( - { - value: 0, - factors: Set.from_list( - [ - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = largest({ min: 15, max: 15 }) + expected = Ok( + { + value: 0, + factors: Set.from_list( + [], + ), + }, + ) + result->is_eq(expected) +} # error result for smallest if min is more than max -expect - result = smallest({ min: 10000, max: 1 }) - result |> Result.is_err +expect { + result = smallest({ min: 10000, max: 1 }) + result.is_err() +} # error result for largest if min is more than max -expect - result = largest({ min: 2, max: 1 }) - result |> Result.is_err +expect { + result = largest({ min: 2, max: 1 }) + result.is_err() +} # smallest product does not use the smallest factor -expect - result = smallest({ min: 3215, max: 4000 }) - expected = Ok( - { - value: 10988901, - factors: Set.from_list( - [ - (3297, 3333), - ], - ), - }, - ) - result |> is_eq(expected) +expect { + result = smallest({ min: 3215, max: 4000 }) + expected = Ok( + { + value: 10988901, + factors: Set.from_list( + [ + (3297, 3333), + ], + ), + }, + ) + result->is_eq(expected) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/pangram/.meta/template.j2 b/exercises/practice/pangram/.meta/template.j2 index f70258e1..b4be700c 100644 --- a/exercises/practice/pangram/.meta/template.j2 +++ b/exercises/practice/pangram/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [is_pangram] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["sentence"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/pangram/pangram-test.roc b/exercises/practice/pangram/pangram-test.roc index 1348a315..c6c855f7 100644 --- a/exercises/practice/pangram/pangram-test.roc +++ b/exercises/practice/pangram/pangram-test.roc @@ -1,64 +1,70 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pangram/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Pangram exposing [is_pangram] # empty sentence -expect - result = is_pangram("") - result == Bool.false +expect { + result = is_pangram("") + result == Bool.False +} # perfect lower case -expect - result = is_pangram("abcdefghijklmnopqrstuvwxyz") - result == Bool.true +expect { + result = is_pangram("abcdefghijklmnopqrstuvwxyz") + result == Bool.True +} # only lower case -expect - result = is_pangram("the quick brown fox jumps over the lazy dog") - result == Bool.true +expect { + result = is_pangram("the quick brown fox jumps over the lazy dog") + result == Bool.True +} # missing the letter 'x' -expect - result = is_pangram("a quick movement of the enemy will jeopardize five gunboats") - result == Bool.false +expect { + result = is_pangram("a quick movement of the enemy will jeopardize five gunboats") + result == Bool.False +} # missing the letter 'h' -expect - result = is_pangram("five boxing wizards jump quickly at it") - result == Bool.false +expect { + result = is_pangram("five boxing wizards jump quickly at it") + result == Bool.False +} # with underscores -expect - result = is_pangram("the_quick_brown_fox_jumps_over_the_lazy_dog") - result == Bool.true +expect { + result = is_pangram("the_quick_brown_fox_jumps_over_the_lazy_dog") + result == Bool.True +} # with numbers -expect - result = is_pangram("the 1 quick brown fox jumps over the 2 lazy dogs") - result == Bool.true +expect { + result = is_pangram("the 1 quick brown fox jumps over the 2 lazy dogs") + result == Bool.True +} # missing letters replaced by numbers -expect - result = is_pangram("7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog") - result == Bool.false +expect { + result = is_pangram("7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog") + result == Bool.False +} # mixed case and punctuation -expect - result = is_pangram("\"Five quacking Zephyrs jolt my wax bed.\"") - result == Bool.true +expect { + result = is_pangram("\"Five quacking Zephyrs jolt my wax bed.\"") + result == Bool.True +} # a-m and A-M are 26 different characters but not a pangram -expect - result = is_pangram("abcdefghijklm ABCDEFGHIJKLM") - result == Bool.false +expect { + result = is_pangram("abcdefghijklm ABCDEFGHIJKLM") + result == Bool.False +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/pascals-triangle/.meta/template.j2 b/exercises/practice/pascals-triangle/.meta/template.j2 index 71d4abfe..ecf09c1e 100644 --- a/exercises/practice/pascals-triangle/.meta/template.j2 +++ b/exercises/practice/pascals-triangle/.meta/template.j2 @@ -6,12 +6,13 @@ import {{ exercise | to_pascal }} exposing [pascals_triangle] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = pascals_triangle({{ case["input"]["count"] | to_roc }}) expected = [{%- for row in case["expected"] %} {{ row | to_roc }}, {%- endfor %}] result == expected +} {% endfor %} diff --git a/exercises/practice/pascals-triangle/pascals-triangle-test.roc b/exercises/practice/pascals-triangle/pascals-triangle-test.roc index d46b38f2..a882a64d 100644 --- a/exercises/practice/pascals-triangle/pascals-triangle-test.roc +++ b/exercises/practice/pascals-triangle/pascals-triangle-test.roc @@ -1,100 +1,104 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pascals-triangle/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import PascalsTriangle exposing [pascals_triangle] # zero rows -expect - result = pascals_triangle(0) - expected = [] - result == expected +expect { + result = pascals_triangle(0) + expected = [] + result == expected +} # single row -expect - result = pascals_triangle(1) - expected = [ - [1], - ] - result == expected +expect { + result = pascals_triangle(1) + expected = [ + [1], + ] + result == expected +} # two rows -expect - result = pascals_triangle(2) - expected = [ - [1], - [1, 1], - ] - result == expected +expect { + result = pascals_triangle(2) + expected = [ + [1], + [1, 1], + ] + result == expected +} # three rows -expect - result = pascals_triangle(3) - expected = [ - [1], - [1, 1], - [1, 2, 1], - ] - result == expected +expect { + result = pascals_triangle(3) + expected = [ + [1], + [1, 1], + [1, 2, 1], + ] + result == expected +} # four rows -expect - result = pascals_triangle(4) - expected = [ - [1], - [1, 1], - [1, 2, 1], - [1, 3, 3, 1], - ] - result == expected +expect { + result = pascals_triangle(4) + expected = [ + [1], + [1, 1], + [1, 2, 1], + [1, 3, 3, 1], + ] + result == expected +} # five rows -expect - result = pascals_triangle(5) - expected = [ - [1], - [1, 1], - [1, 2, 1], - [1, 3, 3, 1], - [1, 4, 6, 4, 1], - ] - result == expected +expect { + result = pascals_triangle(5) + expected = [ + [1], + [1, 1], + [1, 2, 1], + [1, 3, 3, 1], + [1, 4, 6, 4, 1], + ] + result == expected +} # six rows -expect - result = pascals_triangle(6) - expected = [ - [1], - [1, 1], - [1, 2, 1], - [1, 3, 3, 1], - [1, 4, 6, 4, 1], - [1, 5, 10, 10, 5, 1], - ] - result == expected +expect { + result = pascals_triangle(6) + expected = [ + [1], + [1, 1], + [1, 2, 1], + [1, 3, 3, 1], + [1, 4, 6, 4, 1], + [1, 5, 10, 10, 5, 1], + ] + result == expected +} # ten rows -expect - result = pascals_triangle(10) - expected = [ - [1], - [1, 1], - [1, 2, 1], - [1, 3, 3, 1], - [1, 4, 6, 4, 1], - [1, 5, 10, 10, 5, 1], - [1, 6, 15, 20, 15, 6, 1], - [1, 7, 21, 35, 35, 21, 7, 1], - [1, 8, 28, 56, 70, 56, 28, 8, 1], - [1, 9, 36, 84, 126, 126, 84, 36, 9, 1], - ] - result == expected +expect { + result = pascals_triangle(10) + expected = [ + [1], + [1, 1], + [1, 2, 1], + [1, 3, 3, 1], + [1, 4, 6, 4, 1], + [1, 5, 10, 10, 5, 1], + [1, 6, 15, 20, 15, 6, 1], + [1, 7, 21, 35, 35, 21, 7, 1], + [1, 8, 28, 56, 70, 56, 28, 8, 1], + [1, 9, 36, 84, 126, 126, 84, 36, 9, 1], + ] + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/perfect-numbers/.meta/template.j2 b/exercises/practice/perfect-numbers/.meta/template.j2 index 93f14ecd..d822bce0 100644 --- a/exercises/practice/perfect-numbers/.meta/template.j2 +++ b/exercises/practice/perfect-numbers/.meta/template.j2 @@ -11,13 +11,14 @@ import {{ exercise | to_pascal }} exposing [classify] {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["number"] }}) {%- if case["expected"]["error"] %} - Result.is_err(result) + result.is_err() {%- else %} result == Ok({{ case["expected"] | to_pascal }}) {%- endif %} +} {% endfor %} {% endfor %} diff --git a/exercises/practice/perfect-numbers/perfect-numbers-test.roc b/exercises/practice/perfect-numbers/perfect-numbers-test.roc index cd821008..d5b308f5 100644 --- a/exercises/practice/perfect-numbers/perfect-numbers-test.roc +++ b/exercises/practice/perfect-numbers/perfect-numbers-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/perfect-numbers/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import PerfectNumbers exposing [classify] @@ -17,74 +9,90 @@ import PerfectNumbers exposing [classify] ## # Smallest perfect number is classified correctly -expect - result = classify(6) - result == Ok(Perfect) +expect { + result = classify(6) + result == Ok(Perfect) +} # Medium perfect number is classified correctly -expect - result = classify(28) - result == Ok(Perfect) +expect { + result = classify(28) + result == Ok(Perfect) +} # Large perfect number is classified correctly -expect - result = classify(33550336) - result == Ok(Perfect) +expect { + result = classify(33550336) + result == Ok(Perfect) +} ## ## Abundant numbers ## # Smallest abundant number is classified correctly -expect - result = classify(12) - result == Ok(Abundant) +expect { + result = classify(12) + result == Ok(Abundant) +} # Medium abundant number is classified correctly -expect - result = classify(30) - result == Ok(Abundant) +expect { + result = classify(30) + result == Ok(Abundant) +} # Large abundant number is classified correctly -expect - result = classify(33550335) - result == Ok(Abundant) +expect { + result = classify(33550335) + result == Ok(Abundant) +} ## ## Deficient numbers ## # Smallest prime deficient number is classified correctly -expect - result = classify(2) - result == Ok(Deficient) +expect { + result = classify(2) + result == Ok(Deficient) +} # Smallest non-prime deficient number is classified correctly -expect - result = classify(4) - result == Ok(Deficient) +expect { + result = classify(4) + result == Ok(Deficient) +} # Medium deficient number is classified correctly -expect - result = classify(32) - result == Ok(Deficient) +expect { + result = classify(32) + result == Ok(Deficient) +} # Large deficient number is classified correctly -expect - result = classify(33550337) - result == Ok(Deficient) +expect { + result = classify(33550337) + result == Ok(Deficient) +} # Edge case (no factors other than itself) is classified correctly -expect - result = classify(1) - result == Ok(Deficient) +expect { + result = classify(1) + result == Ok(Deficient) +} ## ## Invalid inputs ## # Zero is rejected (as it is not a positive integer) -expect - result = classify(0) - Result.is_err(result) +expect { + result = classify(0) + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/phone-number/.meta/template.j2 b/exercises/practice/phone-number/.meta/template.j2 index db676f1b..dec60079 100644 --- a/exercises/practice/phone-number/.meta/template.j2 +++ b/exercises/practice/phone-number/.meta/template.j2 @@ -6,14 +6,15 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["phrase"] | to_roc }}) {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} expected = Ok({{ case["expected"] | to_roc }}) result == expected {%- endif %} +} {% endfor %} diff --git a/exercises/practice/phone-number/phone-number-test.roc b/exercises/practice/phone-number/phone-number-test.roc index f66146da..7514ab4f 100644 --- a/exercises/practice/phone-number/phone-number-test.roc +++ b/exercises/practice/phone-number/phone-number-test.roc @@ -1,109 +1,123 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/phone-number/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import PhoneNumber exposing [clean] # cleans the number -expect - result = clean("(223) 456-7890") - expected = Ok("2234567890") - result == expected +expect { + result = clean("(223) 456-7890") + expected = Ok("2234567890") + result == expected +} # cleans numbers with dots -expect - result = clean("223.456.7890") - expected = Ok("2234567890") - result == expected +expect { + result = clean("223.456.7890") + expected = Ok("2234567890") + result == expected +} # cleans numbers with multiple spaces -expect - result = clean("223 456 7890 ") - expected = Ok("2234567890") - result == expected +expect { + result = clean("223 456 7890 ") + expected = Ok("2234567890") + result == expected +} # invalid when 9 digits -expect - result = clean("123456789") - result |> Result.is_err +expect { + result = clean("123456789") + result.is_err() +} # invalid when 11 digits does not start with a 1 -expect - result = clean("22234567890") - result |> Result.is_err +expect { + result = clean("22234567890") + result.is_err() +} # valid when 11 digits and starting with 1 -expect - result = clean("12234567890") - expected = Ok("2234567890") - result == expected +expect { + result = clean("12234567890") + expected = Ok("2234567890") + result == expected +} # valid when 11 digits and starting with 1 even with punctuation -expect - result = clean("+1 (223) 456-7890") - expected = Ok("2234567890") - result == expected +expect { + result = clean("+1 (223) 456-7890") + expected = Ok("2234567890") + result == expected +} # invalid when more than 11 digits -expect - result = clean("321234567890") - result |> Result.is_err +expect { + result = clean("321234567890") + result.is_err() +} # invalid with letters -expect - result = clean("523-abc-7890") - result |> Result.is_err +expect { + result = clean("523-abc-7890") + result.is_err() +} # invalid with punctuations -expect - result = clean("523-@:!-7890") - result |> Result.is_err +expect { + result = clean("523-@:!-7890") + result.is_err() +} # invalid if area code starts with 0 -expect - result = clean("(023) 456-7890") - result |> Result.is_err +expect { + result = clean("(023) 456-7890") + result.is_err() +} # invalid if area code starts with 1 -expect - result = clean("(123) 456-7890") - result |> Result.is_err +expect { + result = clean("(123) 456-7890") + result.is_err() +} # invalid if exchange code starts with 0 -expect - result = clean("(223) 056-7890") - result |> Result.is_err +expect { + result = clean("(223) 056-7890") + result.is_err() +} # invalid if exchange code starts with 1 -expect - result = clean("(223) 156-7890") - result |> Result.is_err +expect { + result = clean("(223) 156-7890") + result.is_err() +} # invalid if area code starts with 0 on valid 11-digit number -expect - result = clean("1 (023) 456-7890") - result |> Result.is_err +expect { + result = clean("1 (023) 456-7890") + result.is_err() +} # invalid if area code starts with 1 on valid 11-digit number -expect - result = clean("1 (123) 456-7890") - result |> Result.is_err +expect { + result = clean("1 (123) 456-7890") + result.is_err() +} # invalid if exchange code starts with 0 on valid 11-digit number -expect - result = clean("1 (223) 056-7890") - result |> Result.is_err +expect { + result = clean("1 (223) 056-7890") + result.is_err() +} # invalid if exchange code starts with 1 on valid 11-digit number -expect - result = clean("1 (223) 156-7890") - result |> Result.is_err +expect { + result = clean("1 (223) 156-7890") + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/pig-latin/.meta/template.j2 b/exercises/practice/pig-latin/.meta/template.j2 index 1c20e627..ab6d4470 100644 --- a/exercises/practice/pig-latin/.meta/template.j2 +++ b/exercises/practice/pig-latin/.meta/template.j2 @@ -11,9 +11,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["cases"][0]["property"] {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["phrase"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} {% endfor %} diff --git a/exercises/practice/pig-latin/pig-latin-test.roc b/exercises/practice/pig-latin/pig-latin-test.roc index a2483268..07b15110 100644 --- a/exercises/practice/pig-latin/pig-latin-test.roc +++ b/exercises/practice/pig-latin/pig-latin-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pig-latin/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import PigLatin exposing [translate] @@ -17,137 +9,164 @@ import PigLatin exposing [translate] ## # word beginning with a -expect - result = translate("apple") - result == "appleay" +expect { + result = translate("apple") + result == "appleay" +} # word beginning with e -expect - result = translate("ear") - result == "earay" +expect { + result = translate("ear") + result == "earay" +} # word beginning with i -expect - result = translate("igloo") - result == "iglooay" +expect { + result = translate("igloo") + result == "iglooay" +} # word beginning with o -expect - result = translate("object") - result == "objectay" +expect { + result = translate("object") + result == "objectay" +} # word beginning with u -expect - result = translate("under") - result == "underay" +expect { + result = translate("under") + result == "underay" +} # word beginning with a vowel and followed by a qu -expect - result = translate("equal") - result == "equalay" +expect { + result = translate("equal") + result == "equalay" +} ## ## first letter and ay are moved to the end of words that start with consonants ## # word beginning with p -expect - result = translate("pig") - result == "igpay" +expect { + result = translate("pig") + result == "igpay" +} # word beginning with k -expect - result = translate("koala") - result == "oalakay" +expect { + result = translate("koala") + result == "oalakay" +} # word beginning with x -expect - result = translate("xenon") - result == "enonxay" +expect { + result = translate("xenon") + result == "enonxay" +} # word beginning with q without a following u -expect - result = translate("qat") - result == "atqay" +expect { + result = translate("qat") + result == "atqay" +} # word beginning with consonant and vowel containing qu -expect - result = translate("liquid") - result == "iquidlay" +expect { + result = translate("liquid") + result == "iquidlay" +} ## ## some letter clusters are treated like a single consonant ## # word beginning with ch -expect - result = translate("chair") - result == "airchay" +expect { + result = translate("chair") + result == "airchay" +} # word beginning with qu -expect - result = translate("queen") - result == "eenquay" +expect { + result = translate("queen") + result == "eenquay" +} # word beginning with qu and a preceding consonant -expect - result = translate("square") - result == "aresquay" +expect { + result = translate("square") + result == "aresquay" +} # word beginning with th -expect - result = translate("therapy") - result == "erapythay" +expect { + result = translate("therapy") + result == "erapythay" +} # word beginning with thr -expect - result = translate("thrush") - result == "ushthray" +expect { + result = translate("thrush") + result == "ushthray" +} # word beginning with sch -expect - result = translate("school") - result == "oolschay" +expect { + result = translate("school") + result == "oolschay" +} ## ## some letter clusters are treated like a single vowel ## # word beginning with yt -expect - result = translate("yttria") - result == "yttriaay" +expect { + result = translate("yttria") + result == "yttriaay" +} # word beginning with xr -expect - result = translate("xray") - result == "xrayay" +expect { + result = translate("xray") + result == "xrayay" +} ## ## position of y in a word determines if it is a consonant or a vowel ## # y is treated like a consonant at the beginning of a word -expect - result = translate("yellow") - result == "ellowyay" +expect { + result = translate("yellow") + result == "ellowyay" +} # y is treated like a vowel at the end of a consonant cluster -expect - result = translate("rhythm") - result == "ythmrhay" +expect { + result = translate("rhythm") + result == "ythmrhay" +} # y as second letter in two letter word -expect - result = translate("my") - result == "ymay" +expect { + result = translate("my") + result == "ymay" +} ## ## phrases are translated ## # a whole phrase -expect - result = translate("quick fast run") - result == "ickquay astfay unray" +expect { + result = translate("quick fast run") + result == "ickquay astfay unray" +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/poker/.meta/template.j2 b/exercises/practice/poker/.meta/template.j2 index 5b795e32..3ab96f53 100644 --- a/exercises/practice/poker/.meta/template.j2 +++ b/exercises/practice/poker/.meta/template.j2 @@ -6,10 +6,11 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { hands = {{ case["input"]["hands"] | to_roc }} result = {{ case["property"] | to_snake }}(hands) result == Ok({{ case["expected"] | to_roc }}) +} {% endfor %} diff --git a/exercises/practice/poker/poker-test.roc b/exercises/practice/poker/poker-test.roc index aae1b2c7..8c8bb88b 100644 --- a/exercises/practice/poker/poker-test.roc +++ b/exercises/practice/poker/poker-test.roc @@ -1,236 +1,269 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/poker/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Poker exposing [best_hands] # single hand always wins -expect - hands = ["4S 5S 7H 8D JC"] - result = best_hands(hands) - result == Ok(["4S 5S 7H 8D JC"]) +expect { + hands = ["4S 5S 7H 8D JC"] + result = best_hands(hands) + result == Ok(["4S 5S 7H 8D JC"]) +} # highest card out of all hands wins -expect - hands = ["4D 5S 6S 8D 3C", "2S 4C 7S 9H 10H", "3S 4S 5D 6H JH"] - result = best_hands(hands) - result == Ok(["3S 4S 5D 6H JH"]) +expect { + hands = ["4D 5S 6S 8D 3C", "2S 4C 7S 9H 10H", "3S 4S 5D 6H JH"] + result = best_hands(hands) + result == Ok(["3S 4S 5D 6H JH"]) +} # a tie has multiple winners -expect - hands = ["4D 5S 6S 8D 3C", "2S 4C 7S 9H 10H", "3S 4S 5D 6H JH", "3H 4H 5C 6C JD"] - result = best_hands(hands) - result == Ok(["3S 4S 5D 6H JH", "3H 4H 5C 6C JD"]) +expect { + hands = ["4D 5S 6S 8D 3C", "2S 4C 7S 9H 10H", "3S 4S 5D 6H JH", "3H 4H 5C 6C JD"] + result = best_hands(hands) + result == Ok(["3S 4S 5D 6H JH", "3H 4H 5C 6C JD"]) +} # multiple hands with the same high cards, tie compares next highest ranked, down to last card -expect - hands = ["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"] - result = best_hands(hands) - result == Ok(["3S 5H 6S 8D 7H"]) +expect { + hands = ["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"] + result = best_hands(hands) + result == Ok(["3S 5H 6S 8D 7H"]) +} # winning high card hand also has the lowest card -expect - hands = ["2S 5H 6S 8D 7H", "3S 4D 6D 8C 7S"] - result = best_hands(hands) - result == Ok(["2S 5H 6S 8D 7H"]) +expect { + hands = ["2S 5H 6S 8D 7H", "3S 4D 6D 8C 7S"] + result = best_hands(hands) + result == Ok(["2S 5H 6S 8D 7H"]) +} # one pair beats high card -expect - hands = ["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"] - result = best_hands(hands) - result == Ok(["2S 4H 6S 4D JH"]) +expect { + hands = ["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"] + result = best_hands(hands) + result == Ok(["2S 4H 6S 4D JH"]) +} # highest pair wins -expect - hands = ["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"] - result = best_hands(hands) - result == Ok(["2S 4H 6C 4D JD"]) +expect { + hands = ["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"] + result = best_hands(hands) + result == Ok(["2S 4H 6C 4D JD"]) +} # both hands have the same pair, high card wins -expect - hands = ["4H 4S AH JC 3D", "4C 4D AS 5D 6C"] - result = best_hands(hands) - result == Ok(["4H 4S AH JC 3D"]) +expect { + hands = ["4H 4S AH JC 3D", "4C 4D AS 5D 6C"] + result = best_hands(hands) + result == Ok(["4H 4S AH JC 3D"]) +} # two pairs beats one pair -expect - hands = ["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"] - result = best_hands(hands) - result == Ok(["4S 5H 4C 8C 5C"]) +expect { + hands = ["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"] + result = best_hands(hands) + result == Ok(["4S 5H 4C 8C 5C"]) +} # both hands have two pairs, highest ranked pair wins -expect - hands = ["2S 8H 2D 8D 3H", "4S 5H 4C 8S 5D"] - result = best_hands(hands) - result == Ok(["2S 8H 2D 8D 3H"]) +expect { + hands = ["2S 8H 2D 8D 3H", "4S 5H 4C 8S 5D"] + result = best_hands(hands) + result == Ok(["2S 8H 2D 8D 3H"]) +} # both hands have two pairs, with the same highest ranked pair, tie goes to low pair -expect - hands = ["2S QS 2C QD JH", "JD QH JS 8D QC"] - result = best_hands(hands) - result == Ok(["JD QH JS 8D QC"]) +expect { + hands = ["2S QS 2C QD JH", "JD QH JS 8D QC"] + result = best_hands(hands) + result == Ok(["JD QH JS 8D QC"]) +} # both hands have two identically ranked pairs, tie goes to remaining card (kicker) -expect - hands = ["JD QH JS 8D QC", "JS QS JC 2D QD"] - result = best_hands(hands) - result == Ok(["JD QH JS 8D QC"]) +expect { + hands = ["JD QH JS 8D QC", "JS QS JC 2D QD"] + result = best_hands(hands) + result == Ok(["JD QH JS 8D QC"]) +} # both hands have two pairs that add to the same value, win goes to highest pair -expect - hands = ["6S 6H 3S 3H AS", "7H 7S 2H 2S AC"] - result = best_hands(hands) - result == Ok(["7H 7S 2H 2S AC"]) +expect { + hands = ["6S 6H 3S 3H AS", "7H 7S 2H 2S AC"] + result = best_hands(hands) + result == Ok(["7H 7S 2H 2S AC"]) +} # two pairs first ranked by largest pair -expect - hands = ["5C 2S 5S 4H 4C", "6S 2S 6H 7C 2C"] - result = best_hands(hands) - result == Ok(["6S 2S 6H 7C 2C"]) +expect { + hands = ["5C 2S 5S 4H 4C", "6S 2S 6H 7C 2C"] + result = best_hands(hands) + result == Ok(["6S 2S 6H 7C 2C"]) +} # three of a kind beats two pair -expect - hands = ["2S 8H 2H 8D JH", "4S 5H 4C 8S 4H"] - result = best_hands(hands) - result == Ok(["4S 5H 4C 8S 4H"]) +expect { + hands = ["2S 8H 2H 8D JH", "4S 5H 4C 8S 4H"] + result = best_hands(hands) + result == Ok(["4S 5H 4C 8S 4H"]) +} # both hands have three of a kind, tie goes to highest ranked triplet -expect - hands = ["2S 2H 2C 8D JH", "4S AH AS 8C AD"] - result = best_hands(hands) - result == Ok(["4S AH AS 8C AD"]) +expect { + hands = ["2S 2H 2C 8D JH", "4S AH AS 8C AD"] + result = best_hands(hands) + result == Ok(["4S AH AS 8C AD"]) +} # with multiple decks, two players can have same three of a kind, ties go to highest remaining cards -expect - hands = ["5S AH AS 7C AD", "4S AH AS 8C AD"] - result = best_hands(hands) - result == Ok(["4S AH AS 8C AD"]) +expect { + hands = ["5S AH AS 7C AD", "4S AH AS 8C AD"] + result = best_hands(hands) + result == Ok(["4S AH AS 8C AD"]) +} # a straight beats three of a kind -expect - hands = ["4S 5H 4C 8D 4H", "3S 4D 2S 6D 5C"] - result = best_hands(hands) - result == Ok(["3S 4D 2S 6D 5C"]) +expect { + hands = ["4S 5H 4C 8D 4H", "3S 4D 2S 6D 5C"] + result = best_hands(hands) + result == Ok(["3S 4D 2S 6D 5C"]) +} # aces can end a straight (10 J Q K A) -expect - hands = ["4S 5H 4C 8D 4H", "10D JH QS KD AC"] - result = best_hands(hands) - result == Ok(["10D JH QS KD AC"]) +expect { + hands = ["4S 5H 4C 8D 4H", "10D JH QS KD AC"] + result = best_hands(hands) + result == Ok(["10D JH QS KD AC"]) +} # aces can start a straight (A 2 3 4 5) -expect - hands = ["4S 5H 4C 8D 4H", "4D AH 3S 2D 5C"] - result = best_hands(hands) - result == Ok(["4D AH 3S 2D 5C"]) +expect { + hands = ["4S 5H 4C 8D 4H", "4D AH 3S 2D 5C"] + result = best_hands(hands) + result == Ok(["4D AH 3S 2D 5C"]) +} # aces cannot be in the middle of a straight (Q K A 2 3) -expect - hands = ["2C 3D 7H 5H 2S", "QS KH AC 2D 3S"] - result = best_hands(hands) - result == Ok(["2C 3D 7H 5H 2S"]) +expect { + hands = ["2C 3D 7H 5H 2S", "QS KH AC 2D 3S"] + result = best_hands(hands) + result == Ok(["2C 3D 7H 5H 2S"]) +} # both hands with a straight, tie goes to highest ranked card -expect - hands = ["4S 6C 7S 8D 5H", "5S 7H 8S 9D 6H"] - result = best_hands(hands) - result == Ok(["5S 7H 8S 9D 6H"]) +expect { + hands = ["4S 6C 7S 8D 5H", "5S 7H 8S 9D 6H"] + result = best_hands(hands) + result == Ok(["5S 7H 8S 9D 6H"]) +} # even though an ace is usually high, a 5-high straight is the lowest-scoring straight -expect - hands = ["2H 3C 4D 5D 6H", "4S AH 3S 2D 5H"] - result = best_hands(hands) - result == Ok(["2H 3C 4D 5D 6H"]) +expect { + hands = ["2H 3C 4D 5D 6H", "4S AH 3S 2D 5H"] + result = best_hands(hands) + result == Ok(["2H 3C 4D 5D 6H"]) +} # flush beats a straight -expect - hands = ["4C 6H 7D 8D 5H", "2S 4S 5S 6S 7S"] - result = best_hands(hands) - result == Ok(["2S 4S 5S 6S 7S"]) +expect { + hands = ["4C 6H 7D 8D 5H", "2S 4S 5S 6S 7S"] + result = best_hands(hands) + result == Ok(["2S 4S 5S 6S 7S"]) +} # both hands have a flush, tie goes to high card, down to the last one if necessary -expect - hands = ["2H 7H 8H 9H 6H", "3S 5S 6S 7S 8S"] - result = best_hands(hands) - result == Ok(["2H 7H 8H 9H 6H"]) +expect { + hands = ["2H 7H 8H 9H 6H", "3S 5S 6S 7S 8S"] + result = best_hands(hands) + result == Ok(["2H 7H 8H 9H 6H"]) +} # full house beats a flush -expect - hands = ["3H 6H 7H 8H 5H", "4S 5H 4C 5D 4H"] - result = best_hands(hands) - result == Ok(["4S 5H 4C 5D 4H"]) +expect { + hands = ["3H 6H 7H 8H 5H", "4S 5H 4C 5D 4H"] + result = best_hands(hands) + result == Ok(["4S 5H 4C 5D 4H"]) +} # both hands have a full house, tie goes to highest-ranked triplet -expect - hands = ["4H 4S 4D 9S 9D", "5H 5S 5D 8S 8D"] - result = best_hands(hands) - result == Ok(["5H 5S 5D 8S 8D"]) +expect { + hands = ["4H 4S 4D 9S 9D", "5H 5S 5D 8S 8D"] + result = best_hands(hands) + result == Ok(["5H 5S 5D 8S 8D"]) +} # with multiple decks, both hands have a full house with the same triplet, tie goes to the pair -expect - hands = ["5H 5S 5D 9S 9D", "5H 5S 5D 8S 8D"] - result = best_hands(hands) - result == Ok(["5H 5S 5D 9S 9D"]) +expect { + hands = ["5H 5S 5D 9S 9D", "5H 5S 5D 8S 8D"] + result = best_hands(hands) + result == Ok(["5H 5S 5D 9S 9D"]) +} # four of a kind beats a full house -expect - hands = ["4S 5H 4D 5D 4H", "3S 3H 2S 3D 3C"] - result = best_hands(hands) - result == Ok(["3S 3H 2S 3D 3C"]) +expect { + hands = ["4S 5H 4D 5D 4H", "3S 3H 2S 3D 3C"] + result = best_hands(hands) + result == Ok(["3S 3H 2S 3D 3C"]) +} # both hands have four of a kind, tie goes to high quad -expect - hands = ["2S 2H 2C 8D 2D", "4S 5H 5S 5D 5C"] - result = best_hands(hands) - result == Ok(["4S 5H 5S 5D 5C"]) +expect { + hands = ["2S 2H 2C 8D 2D", "4S 5H 5S 5D 5C"] + result = best_hands(hands) + result == Ok(["4S 5H 5S 5D 5C"]) +} # with multiple decks, both hands with identical four of a kind, tie determined by kicker -expect - hands = ["3S 3H 2S 3D 3C", "3S 3H 4S 3D 3C"] - result = best_hands(hands) - result == Ok(["3S 3H 4S 3D 3C"]) +expect { + hands = ["3S 3H 2S 3D 3C", "3S 3H 4S 3D 3C"] + result = best_hands(hands) + result == Ok(["3S 3H 4S 3D 3C"]) +} # straight flush beats four of a kind -expect - hands = ["4S 5H 5S 5D 5C", "7S 8S 9S 6S 10S"] - result = best_hands(hands) - result == Ok(["7S 8S 9S 6S 10S"]) +expect { + hands = ["4S 5H 5S 5D 5C", "7S 8S 9S 6S 10S"] + result = best_hands(hands) + result == Ok(["7S 8S 9S 6S 10S"]) +} # aces can end a straight flush (10 J Q K A) -expect - hands = ["KC AH AS AD AC", "10C JC QC KC AC"] - result = best_hands(hands) - result == Ok(["10C JC QC KC AC"]) +expect { + hands = ["KC AH AS AD AC", "10C JC QC KC AC"] + result = best_hands(hands) + result == Ok(["10C JC QC KC AC"]) +} # aces can start a straight flush (A 2 3 4 5) -expect - hands = ["KS AH AS AD AC", "4H AH 3H 2H 5H"] - result = best_hands(hands) - result == Ok(["4H AH 3H 2H 5H"]) +expect { + hands = ["KS AH AS AD AC", "4H AH 3H 2H 5H"] + result = best_hands(hands) + result == Ok(["4H AH 3H 2H 5H"]) +} # aces cannot be in the middle of a straight flush (Q K A 2 3) -expect - hands = ["2C AC QC 10C KC", "QH KH AH 2H 3H"] - result = best_hands(hands) - result == Ok(["2C AC QC 10C KC"]) +expect { + hands = ["2C AC QC 10C KC", "QH KH AH 2H 3H"] + result = best_hands(hands) + result == Ok(["2C AC QC 10C KC"]) +} # both hands have a straight flush, tie goes to highest-ranked card -expect - hands = ["4H 6H 7H 8H 5H", "5S 7S 8S 9S 6S"] - result = best_hands(hands) - result == Ok(["5S 7S 8S 9S 6S"]) +expect { + hands = ["4H 6H 7H 8H 5H", "5S 7S 8S 9S 6S"] + result = best_hands(hands) + result == Ok(["5S 7S 8S 9S 6S"]) +} # even though an ace is usually high, a 5-high straight flush is the lowest-scoring straight flush -expect - hands = ["2H 3H 4H 5H 6H", "4D AD 3D 2D 5D"] - result = best_hands(hands) - result == Ok(["2H 3H 4H 5H 6H"]) +expect { + hands = ["2H 3H 4H 5H 6H", "4D AD 3D 2D 5D"] + result = best_hands(hands) + result == Ok(["2H 3H 4H 5H 6H"]) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/prime-factors/.meta/template.j2 b/exercises/practice/prime-factors/.meta/template.j2 index d3828ccd..08c00775 100644 --- a/exercises/practice/prime-factors/.meta/template.j2 +++ b/exercises/practice/prime-factors/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ exercise | to_snake }}] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ exercise | to_snake }}({{ case["input"]["value"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/prime-factors/prime-factors-test.roc b/exercises/practice/prime-factors/prime-factors-test.roc index 6febf001..07192ae1 100644 --- a/exercises/practice/prime-factors/prime-factors-test.roc +++ b/exercises/practice/prime-factors/prime-factors-test.roc @@ -1,74 +1,82 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/prime-factors/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import PrimeFactors exposing [prime_factors] # no factors -expect - result = prime_factors(1) - result == [] +expect { + result = prime_factors(1) + result == [] +} # prime number -expect - result = prime_factors(2) - result == [2] +expect { + result = prime_factors(2) + result == [2] +} # another prime number -expect - result = prime_factors(3) - result == [3] +expect { + result = prime_factors(3) + result == [3] +} # square of a prime -expect - result = prime_factors(9) - result == [3, 3] +expect { + result = prime_factors(9) + result == [3, 3] +} # product of first prime -expect - result = prime_factors(4) - result == [2, 2] +expect { + result = prime_factors(4) + result == [2, 2] +} # cube of a prime -expect - result = prime_factors(8) - result == [2, 2, 2] +expect { + result = prime_factors(8) + result == [2, 2, 2] +} # product of second prime -expect - result = prime_factors(27) - result == [3, 3, 3] +expect { + result = prime_factors(27) + result == [3, 3, 3] +} # product of third prime -expect - result = prime_factors(625) - result == [5, 5, 5, 5] +expect { + result = prime_factors(625) + result == [5, 5, 5, 5] +} # product of first and second prime -expect - result = prime_factors(6) - result == [2, 3] +expect { + result = prime_factors(6) + result == [2, 3] +} # product of primes and non-primes -expect - result = prime_factors(12) - result == [2, 2, 3] +expect { + result = prime_factors(12) + result == [2, 2, 3] +} # product of primes -expect - result = prime_factors(901255) - result == [5, 17, 23, 461] +expect { + result = prime_factors(901255) + result == [5, 17, 23, 461] +} # factors include a large prime -expect - result = prime_factors(93819012551) - result == [11, 9539, 894119] +expect { + result = prime_factors(93819012551) + result == [11, 9539, 894119] +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/protein-translation/.meta/template.j2 b/exercises/practice/protein-translation/.meta/template.j2 index 3f03f6b7..3eb89911 100644 --- a/exercises/practice/protein-translation/.meta/template.j2 +++ b/exercises/practice/protein-translation/.meta/template.j2 @@ -6,14 +6,15 @@ import {{ exercise | to_pascal }} exposing [to_protein] {% for case in cases -%} # {{ case["description"] }} -expect +expect { rna = {{ case["input"]["strand"] | to_roc }} - result = rna |> to_protein + result = rna -> to_protein() {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} result == Ok([{%- for aminoAcid in case["expected"] -%}{{ aminoAcid | to_pascal }}, {%- endfor %}]) {%- endif %} +} {% endfor %} diff --git a/exercises/practice/protein-translation/protein-translation-test.roc b/exercises/practice/protein-translation/protein-translation-test.roc index a6b589ce..68a57447 100644 --- a/exercises/practice/protein-translation/protein-translation-test.roc +++ b/exercises/practice/protein-translation/protein-translation-test.roc @@ -1,194 +1,321 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/protein-translation/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import ProteinTranslation exposing [to_protein] # Empty RNA sequence results in no proteins -expect - rna = "" - result = rna |> to_protein - result == Ok([]) +expect { + rna = "" + result = rna->to_protein() + result == Ok([]) +} # Methionine RNA sequence -expect - rna = "AUG" - result = rna |> to_protein - result == Ok([Methionine]) +expect { + rna = "AUG" + result = rna->to_protein() + result == Ok( + [ + Methionine, + ], + ) +} # Phenylalanine RNA sequence 1 -expect - rna = "UUU" - result = rna |> to_protein - result == Ok([Phenylalanine]) +expect { + rna = "UUU" + result = rna->to_protein() + result == Ok( + [ + Phenylalanine, + ], + ) +} # Phenylalanine RNA sequence 2 -expect - rna = "UUC" - result = rna |> to_protein - result == Ok([Phenylalanine]) +expect { + rna = "UUC" + result = rna->to_protein() + result == Ok( + [ + Phenylalanine, + ], + ) +} # Leucine RNA sequence 1 -expect - rna = "UUA" - result = rna |> to_protein - result == Ok([Leucine]) +expect { + rna = "UUA" + result = rna->to_protein() + result == Ok( + [ + Leucine, + ], + ) +} # Leucine RNA sequence 2 -expect - rna = "UUG" - result = rna |> to_protein - result == Ok([Leucine]) +expect { + rna = "UUG" + result = rna->to_protein() + result == Ok( + [ + Leucine, + ], + ) +} # Serine RNA sequence 1 -expect - rna = "UCU" - result = rna |> to_protein - result == Ok([Serine]) +expect { + rna = "UCU" + result = rna->to_protein() + result == Ok( + [ + Serine, + ], + ) +} # Serine RNA sequence 2 -expect - rna = "UCC" - result = rna |> to_protein - result == Ok([Serine]) +expect { + rna = "UCC" + result = rna->to_protein() + result == Ok( + [ + Serine, + ], + ) +} # Serine RNA sequence 3 -expect - rna = "UCA" - result = rna |> to_protein - result == Ok([Serine]) +expect { + rna = "UCA" + result = rna->to_protein() + result == Ok( + [ + Serine, + ], + ) +} # Serine RNA sequence 4 -expect - rna = "UCG" - result = rna |> to_protein - result == Ok([Serine]) +expect { + rna = "UCG" + result = rna->to_protein() + result == Ok( + [ + Serine, + ], + ) +} # Tyrosine RNA sequence 1 -expect - rna = "UAU" - result = rna |> to_protein - result == Ok([Tyrosine]) +expect { + rna = "UAU" + result = rna->to_protein() + result == Ok( + [ + Tyrosine, + ], + ) +} # Tyrosine RNA sequence 2 -expect - rna = "UAC" - result = rna |> to_protein - result == Ok([Tyrosine]) +expect { + rna = "UAC" + result = rna->to_protein() + result == Ok( + [ + Tyrosine, + ], + ) +} # Cysteine RNA sequence 1 -expect - rna = "UGU" - result = rna |> to_protein - result == Ok([Cysteine]) +expect { + rna = "UGU" + result = rna->to_protein() + result == Ok( + [ + Cysteine, + ], + ) +} # Cysteine RNA sequence 2 -expect - rna = "UGC" - result = rna |> to_protein - result == Ok([Cysteine]) +expect { + rna = "UGC" + result = rna->to_protein() + result == Ok( + [ + Cysteine, + ], + ) +} # Tryptophan RNA sequence -expect - rna = "UGG" - result = rna |> to_protein - result == Ok([Tryptophan]) +expect { + rna = "UGG" + result = rna->to_protein() + result == Ok( + [ + Tryptophan, + ], + ) +} # STOP codon RNA sequence 1 -expect - rna = "UAA" - result = rna |> to_protein - result == Ok([]) +expect { + rna = "UAA" + result = rna->to_protein() + result == Ok([]) +} # STOP codon RNA sequence 2 -expect - rna = "UAG" - result = rna |> to_protein - result == Ok([]) +expect { + rna = "UAG" + result = rna->to_protein() + result == Ok([]) +} # STOP codon RNA sequence 3 -expect - rna = "UGA" - result = rna |> to_protein - result == Ok([]) +expect { + rna = "UGA" + result = rna->to_protein() + result == Ok([]) +} # Sequence of two protein codons translates into proteins -expect - rna = "UUUUUU" - result = rna |> to_protein - result == Ok([Phenylalanine, Phenylalanine]) +expect { + rna = "UUUUUU" + result = rna->to_protein() + result == Ok( + [ + Phenylalanine, + Phenylalanine, + ], + ) +} # Sequence of two different protein codons translates into proteins -expect - rna = "UUAUUG" - result = rna |> to_protein - result == Ok([Leucine, Leucine]) +expect { + rna = "UUAUUG" + result = rna->to_protein() + result == Ok( + [ + Leucine, + Leucine, + ], + ) +} # Translate RNA strand into correct protein list -expect - rna = "AUGUUUUGG" - result = rna |> to_protein - result == Ok([Methionine, Phenylalanine, Tryptophan]) +expect { + rna = "AUGUUUUGG" + result = rna->to_protein() + result == Ok( + [ + Methionine, + Phenylalanine, + Tryptophan, + ], + ) +} # Translation stops if STOP codon at beginning of sequence -expect - rna = "UAGUGG" - result = rna |> to_protein - result == Ok([]) +expect { + rna = "UAGUGG" + result = rna->to_protein() + result == Ok([]) +} # Translation stops if STOP codon at end of two-codon sequence -expect - rna = "UGGUAG" - result = rna |> to_protein - result == Ok([Tryptophan]) +expect { + rna = "UGGUAG" + result = rna->to_protein() + result == Ok( + [ + Tryptophan, + ], + ) +} # Translation stops if STOP codon at end of three-codon sequence -expect - rna = "AUGUUUUAA" - result = rna |> to_protein - result == Ok([Methionine, Phenylalanine]) +expect { + rna = "AUGUUUUAA" + result = rna->to_protein() + result == Ok( + [ + Methionine, + Phenylalanine, + ], + ) +} # Translation stops if STOP codon in middle of three-codon sequence -expect - rna = "UGGUAGUGG" - result = rna |> to_protein - result == Ok([Tryptophan]) +expect { + rna = "UGGUAGUGG" + result = rna->to_protein() + result == Ok( + [ + Tryptophan, + ], + ) +} # Translation stops if STOP codon in middle of six-codon sequence -expect - rna = "UGGUGUUAUUAAUGGUUU" - result = rna |> to_protein - result == Ok([Tryptophan, Cysteine, Tyrosine]) +expect { + rna = "UGGUGUUAUUAAUGGUUU" + result = rna->to_protein() + result == Ok( + [ + Tryptophan, + Cysteine, + Tyrosine, + ], + ) +} # Sequence of two non-STOP codons does not translate to a STOP codon -expect - rna = "AUGAUG" - result = rna |> to_protein - result == Ok([Methionine, Methionine]) +expect { + rna = "AUGAUG" + result = rna->to_protein() + result == Ok( + [ + Methionine, + Methionine, + ], + ) +} # Unknown amino acids, not part of a codon, can't translate -expect - rna = "XYZ" - result = rna |> to_protein - result |> Result.is_err +expect { + rna = "XYZ" + result = rna->to_protein() + result.is_err() +} # Incomplete RNA sequence can't translate -expect - rna = "AUGU" - result = rna |> to_protein - result |> Result.is_err +expect { + rna = "AUGU" + result = rna->to_protein() + result.is_err() +} # Incomplete RNA sequence can translate if valid until a STOP codon -expect - rna = "UUCUUCUAAUGGU" - result = rna |> to_protein - result == Ok([Phenylalanine, Phenylalanine]) +expect { + rna = "UUCUUCUAAUGGU" + result = rna->to_protein() + result == Ok( + [ + Phenylalanine, + Phenylalanine, + ], + ) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/proverb/.meta/template.j2 b/exercises/practice/proverb/.meta/template.j2 index c3520633..3875a281 100644 --- a/exercises/practice/proverb/.meta/template.j2 +++ b/exercises/practice/proverb/.meta/template.j2 @@ -6,10 +6,11 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["strings"] | to_roc }}) expected = {{ case["expected"] | to_roc_multiline_string | indent(8) }} result == expected +} {% endfor %} diff --git a/exercises/practice/proverb/proverb-test.roc b/exercises/practice/proverb/proverb-test.roc index 0d39d912..fd28843b 100644 --- a/exercises/practice/proverb/proverb-test.roc +++ b/exercises/practice/proverb/proverb-test.roc @@ -1,74 +1,72 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/proverb/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Proverb exposing [recite] # zero pieces -expect - result = recite([]) - expected = "" - result == expected +expect { + result = recite([]) + expected = "" + result == expected +} # one piece -expect - result = recite(["nail"]) - expected = "And all for the want of a nail." - result == expected +expect { + result = recite(["nail"]) + expected = "And all for the want of a nail." + result == expected +} # two pieces -expect - result = recite(["nail", "shoe"]) - expected = - """ - For want of a nail the shoe was lost. - And all for the want of a nail. - """ - result == expected +expect { + result = recite(["nail", "shoe"]) + expected = + \\For want of a nail the shoe was lost. + \\And all for the want of a nail. + + result == expected +} # three pieces -expect - result = recite(["nail", "shoe", "horse"]) - expected = - """ - For want of a nail the shoe was lost. - For want of a shoe the horse was lost. - And all for the want of a nail. - """ - result == expected +expect { + result = recite(["nail", "shoe", "horse"]) + expected = + \\For want of a nail the shoe was lost. + \\For want of a shoe the horse was lost. + \\And all for the want of a nail. + + result == expected +} # full proverb -expect - result = recite(["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]) - expected = - """ - For want of a nail the shoe was lost. - For want of a shoe the horse was lost. - For want of a horse the rider was lost. - For want of a rider the message was lost. - For want of a message the battle was lost. - For want of a battle the kingdom was lost. - And all for the want of a nail. - """ - result == expected +expect { + result = recite(["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]) + expected = + \\For want of a nail the shoe was lost. + \\For want of a shoe the horse was lost. + \\For want of a horse the rider was lost. + \\For want of a rider the message was lost. + \\For want of a message the battle was lost. + \\For want of a battle the kingdom was lost. + \\And all for the want of a nail. + + result == expected +} # four pieces modernized -expect - result = recite(["pin", "gun", "soldier", "battle"]) - expected = - """ - For want of a pin the gun was lost. - For want of a gun the soldier was lost. - For want of a soldier the battle was lost. - And all for the want of a pin. - """ - result == expected +expect { + result = recite(["pin", "gun", "soldier", "battle"]) + expected = + \\For want of a pin the gun was lost. + \\For want of a gun the soldier was lost. + \\For want of a soldier the battle was lost. + \\And all for the want of a pin. + result == expected +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/pythagorean-triplet/.meta/template.j2 b/exercises/practice/pythagorean-triplet/.meta/template.j2 index b447f763..40413365 100644 --- a/exercises/practice/pythagorean-triplet/.meta/template.j2 +++ b/exercises/practice/pythagorean-triplet/.meta/template.j2 @@ -6,12 +6,13 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["n"] }}) expected = Set.from_list([{%- for triplet in case["expected"] %} ({{ triplet[0] }}, {{ triplet[1] }}, {{ triplet[2] }}), {%- endfor %}]) result == expected +} {% endfor %} diff --git a/exercises/practice/pythagorean-triplet/pythagorean-triplet-test.roc b/exercises/practice/pythagorean-triplet/pythagorean-triplet-test.roc index 44ae295b..32c4547e 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean-triplet-test.roc +++ b/exercises/practice/pythagorean-triplet/pythagorean-triplet-test.roc @@ -1,92 +1,95 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pythagorean-triplet/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import PythagoreanTriplet exposing [triplets_with_sum] # triplets whose sum is 12 -expect - result = triplets_with_sum(12) - expected = Set.from_list( - [ - (3, 4, 5), - ], - ) - result == expected +expect { + result = triplets_with_sum(12) + expected = Set.from_list( + [ + (3, 4, 5), + ], + ) + result == expected +} # triplets whose sum is 108 -expect - result = triplets_with_sum(108) - expected = Set.from_list( - [ - (27, 36, 45), - ], - ) - result == expected +expect { + result = triplets_with_sum(108) + expected = Set.from_list( + [ + (27, 36, 45), + ], + ) + result == expected +} # triplets whose sum is 1000 -expect - result = triplets_with_sum(1000) - expected = Set.from_list( - [ - (200, 375, 425), - ], - ) - result == expected +expect { + result = triplets_with_sum(1000) + expected = Set.from_list( + [ + (200, 375, 425), + ], + ) + result == expected +} # no matching triplets for 1001 -expect - result = triplets_with_sum(1001) - expected = Set.from_list([]) - result == expected +expect { + result = triplets_with_sum(1001) + expected = Set.from_list([]) + result == expected +} # returns all matching triplets -expect - result = triplets_with_sum(90) - expected = Set.from_list( - [ - (9, 40, 41), - (15, 36, 39), - ], - ) - result == expected +expect { + result = triplets_with_sum(90) + expected = Set.from_list( + [ + (9, 40, 41), + (15, 36, 39), + ], + ) + result == expected +} # several matching triplets -expect - result = triplets_with_sum(840) - expected = Set.from_list( - [ - (40, 399, 401), - (56, 390, 394), - (105, 360, 375), - (120, 350, 370), - (140, 336, 364), - (168, 315, 357), - (210, 280, 350), - (240, 252, 348), - ], - ) - result == expected +expect { + result = triplets_with_sum(840) + expected = Set.from_list( + [ + (40, 399, 401), + (56, 390, 394), + (105, 360, 375), + (120, 350, 370), + (140, 336, 364), + (168, 315, 357), + (210, 280, 350), + (240, 252, 348), + ], + ) + result == expected +} # triplets for large number -expect - result = triplets_with_sum(30000) - expected = Set.from_list( - [ - (1200, 14375, 14425), - (1875, 14000, 14125), - (5000, 12000, 13000), - (6000, 11250, 12750), - (7500, 10000, 12500), - ], - ) - result == expected +expect { + result = triplets_with_sum(30000) + expected = Set.from_list( + [ + (1200, 14375, 14425), + (1875, 14000, 14125), + (5000, 12000, 13000), + (6000, 11250, 12750), + (7500, 10000, 12500), + ], + ) + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/queen-attack/.meta/template.j2 b/exercises/practice/queen-attack/.meta/template.j2 index 780b0deb..c1562b53 100644 --- a/exercises/practice/queen-attack/.meta/template.j2 +++ b/exercises/practice/queen-attack/.meta/template.j2 @@ -13,29 +13,35 @@ import {{ exercise | to_pascal }} exposing [create, rank, file, queen_can_attack # {{ case["description"] }} {%- if case["property"] == "create" %} {%- if case["expected"] == 0 %} -expect +expect { maybe_square = create("{{ plugins.to_square(case["input"]["queen"]) }}") - result = maybe_square |> Result.try( |square| Ok(rank square) ) + result = maybe_square -> Result.try( |square| Ok(rank square) ) result == Ok({{ plugins.to_rank(case["input"]["queen"]["position"]["row"]) }}) +} -expect +expect { maybe_square = create("{{ plugins.to_square(case["input"]["queen"]) }}") - result = maybe_square |> Result.try( |square| Ok(file square) ) + result = maybe_square -> Result.try( |square| Ok(file square) ) result == Ok('{{ plugins.to_file(case["input"]["queen"]["position"]["column"]) }}') +} {%- else %} -expect +expect { result = create("{{ plugins.to_square(case["input"]["queen"]) }}") - result |> Result.is_err + result.is_err() +} {%- endif %} {%- elif case["property"] == "canAttack" %} -expect +expect { maybe_square1 = create("{{ plugins.to_square(case["input"]["white_queen"]) }}") maybe_square2 = create("{{ plugins.to_square(case["input"]["black_queen"]) }}") - result = when (maybe_square1, maybe_square2) is - (Ok(square1), Ok(square2)) -> - square1 |> queen_can_attack(square2) - _ -> crash "Unreachable: {{ plugins.to_square(case["input"]["white_queen"]) }} and {{ plugins.to_square(case["input"]["black_queen"]) }} are both valid squares" + result = match (maybe_square1, maybe_square2) { + (Ok(square1), Ok(square2)) => { + square1 -> queen_can_attack(square2) + } + _ => { crash "Unreachable: {{ plugins.to_square(case["input"]["white_queen"]) }} and {{ plugins.to_square(case["input"]["black_queen"]) }} are both valid squares" } + } result == {{ case["expected"] | to_roc }} +} {%- endif %} {% endfor %} diff --git a/exercises/practice/queen-attack/queen-attack-test.roc b/exercises/practice/queen-attack/queen-attack-test.roc index 383e4983..d0861b61 100644 --- a/exercises/practice/queen-attack/queen-attack-test.roc +++ b/exercises/practice/queen-attack/queen-attack-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/queen-attack/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import QueenAttack exposing [create, rank, file, queen_can_attack] @@ -17,123 +9,155 @@ import QueenAttack exposing [create, rank, file, queen_can_attack] ## # queen with a valid position -expect - maybe_square = create("C6") - result = maybe_square |> Result.try(|square| Ok(rank square)) - result == Ok(6) +expect { + maybe_square = create("C6") + result = maybe_square->Result.try(|square| Ok(rank, square)) + result == Ok(6) +} -expect - maybe_square = create("C6") - result = maybe_square |> Result.try(|square| Ok(file square)) - result == Ok('C') +expect { + maybe_square = create("C6") + result = maybe_square->Result.try(|square| Ok(file, square)) + result == Ok('C') +} # queen must have row on board -expect - result = create("E0") - result |> Result.is_err +expect { + result = create("E0") + result.is_err() +} # queen must have column on board -expect - result = create("I4") - result |> Result.is_err +expect { + result = create("I4") + result.is_err() +} ## ## Test the ability of one queen to attack another ## # cannot attack -expect - maybe_square1 = create("E6") - maybe_square2 = create("G2") - result = - when (maybe_square1, maybe_square2) is - (Ok(square1), Ok(square2)) -> - square1 |> queen_can_attack(square2) - - _ -> crash "Unreachable: E6 and G2 are both valid squares" - result == Bool.false +expect { + maybe_square1 = create("E6") + maybe_square2 = create("G2") + result = match (maybe_square1, maybe_square2) { + (Ok(square1), Ok(square2)) => { + square1->queen_can_attack(square2) + } + _ => { + crash "Unreachable: E6 and G2 are both valid squares" + } + } + result == Bool.False +} # can attack on same row -expect - maybe_square1 = create("E6") - maybe_square2 = create("G6") - result = - when (maybe_square1, maybe_square2) is - (Ok(square1), Ok(square2)) -> - square1 |> queen_can_attack(square2) - - _ -> crash "Unreachable: E6 and G6 are both valid squares" - result == Bool.true +expect { + maybe_square1 = create("E6") + maybe_square2 = create("G6") + result = match (maybe_square1, maybe_square2) { + (Ok(square1), Ok(square2)) => { + square1->queen_can_attack(square2) + } + _ => { + crash "Unreachable: E6 and G6 are both valid squares" + } + } + result == Bool.True +} # can attack on same column -expect - maybe_square1 = create("F4") - maybe_square2 = create("F6") - result = - when (maybe_square1, maybe_square2) is - (Ok(square1), Ok(square2)) -> - square1 |> queen_can_attack(square2) - - _ -> crash "Unreachable: F4 and F6 are both valid squares" - result == Bool.true +expect { + maybe_square1 = create("F4") + maybe_square2 = create("F6") + result = match (maybe_square1, maybe_square2) { + (Ok(square1), Ok(square2)) => { + square1->queen_can_attack(square2) + } + _ => { + crash "Unreachable: F4 and F6 are both valid squares" + } + } + result == Bool.True +} # can attack on first diagonal -expect - maybe_square1 = create("C6") - maybe_square2 = create("E8") - result = - when (maybe_square1, maybe_square2) is - (Ok(square1), Ok(square2)) -> - square1 |> queen_can_attack(square2) - - _ -> crash "Unreachable: C6 and E8 are both valid squares" - result == Bool.true +expect { + maybe_square1 = create("C6") + maybe_square2 = create("E8") + result = match (maybe_square1, maybe_square2) { + (Ok(square1), Ok(square2)) => { + square1->queen_can_attack(square2) + } + _ => { + crash "Unreachable: C6 and E8 are both valid squares" + } + } + result == Bool.True +} # can attack on second diagonal -expect - maybe_square1 = create("C6") - maybe_square2 = create("B5") - result = - when (maybe_square1, maybe_square2) is - (Ok(square1), Ok(square2)) -> - square1 |> queen_can_attack(square2) - - _ -> crash "Unreachable: C6 and B5 are both valid squares" - result == Bool.true +expect { + maybe_square1 = create("C6") + maybe_square2 = create("B5") + result = match (maybe_square1, maybe_square2) { + (Ok(square1), Ok(square2)) => { + square1->queen_can_attack(square2) + } + _ => { + crash "Unreachable: C6 and B5 are both valid squares" + } + } + result == Bool.True +} # can attack on third diagonal -expect - maybe_square1 = create("C6") - maybe_square2 = create("B7") - result = - when (maybe_square1, maybe_square2) is - (Ok(square1), Ok(square2)) -> - square1 |> queen_can_attack(square2) - - _ -> crash "Unreachable: C6 and B7 are both valid squares" - result == Bool.true +expect { + maybe_square1 = create("C6") + maybe_square2 = create("B7") + result = match (maybe_square1, maybe_square2) { + (Ok(square1), Ok(square2)) => { + square1->queen_can_attack(square2) + } + _ => { + crash "Unreachable: C6 and B7 are both valid squares" + } + } + result == Bool.True +} # can attack on fourth diagonal -expect - maybe_square1 = create("H7") - maybe_square2 = create("G8") - result = - when (maybe_square1, maybe_square2) is - (Ok(square1), Ok(square2)) -> - square1 |> queen_can_attack(square2) - - _ -> crash "Unreachable: H7 and G8 are both valid squares" - result == Bool.true +expect { + maybe_square1 = create("H7") + maybe_square2 = create("G8") + result = match (maybe_square1, maybe_square2) { + (Ok(square1), Ok(square2)) => { + square1->queen_can_attack(square2) + } + _ => { + crash "Unreachable: H7 and G8 are both valid squares" + } + } + result == Bool.True +} # cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal -expect - maybe_square1 = create("B4") - maybe_square2 = create("F6") - result = - when (maybe_square1, maybe_square2) is - (Ok(square1), Ok(square2)) -> - square1 |> queen_can_attack(square2) - - _ -> crash "Unreachable: B4 and F6 are both valid squares" - result == Bool.false +expect { + maybe_square1 = create("B4") + maybe_square2 = create("F6") + result = match (maybe_square1, maybe_square2) { + (Ok(square1), Ok(square2)) => { + square1->queen_can_attack(square2) + } + _ => { + crash "Unreachable: B4 and F6 are both valid squares" + } + } + result == Bool.False +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/rail-fence-cipher/.meta/template.j2 b/exercises/practice/rail-fence-cipher/.meta/template.j2 index d8ed6612..8a3d715f 100644 --- a/exercises/practice/rail-fence-cipher/.meta/template.j2 +++ b/exercises/practice/rail-fence-cipher/.meta/template.j2 @@ -11,11 +11,12 @@ import {{ exercise | to_pascal }} exposing [encode, decode] {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect +expect { message = {{ case["input"]["msg"] | to_roc }} - result = message |> {{ case["property"] | to_snake }}({{ case["input"]["rails"] | to_roc }}) + result = message -> {{ case["property"] | to_snake }}({{ case["input"]["rails"] | to_roc }}) expected = Ok({{ case["expected"] | to_roc }}) result == expected +} {% endfor %} {% endfor %} diff --git a/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc b/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc index f200ba3e..2cf5a132 100644 --- a/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc +++ b/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rail-fence-cipher/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import RailFenceCipher exposing [encode, decode] @@ -17,48 +9,58 @@ import RailFenceCipher exposing [encode, decode] ## # encode with two rails -expect - message = "XOXOXOXOXOXOXOXOXO" - result = message |> encode(2) - expected = Ok("XXXXXXXXXOOOOOOOOO") - result == expected +expect { + message = "XOXOXOXOXOXOXOXOXO" + result = message->encode(2) + expected = Ok("XXXXXXXXXOOOOOOOOO") + result == expected +} # encode with three rails -expect - message = "WEAREDISCOVEREDFLEEATONCE" - result = message |> encode(3) - expected = Ok("WECRLTEERDSOEEFEAOCAIVDEN") - result == expected +expect { + message = "WEAREDISCOVEREDFLEEATONCE" + result = message->encode(3) + expected = Ok("WECRLTEERDSOEEFEAOCAIVDEN") + result == expected +} # encode with ending in the middle -expect - message = "EXERCISES" - result = message |> encode(4) - expected = Ok("ESXIEECSR") - result == expected +expect { + message = "EXERCISES" + result = message->encode(4) + expected = Ok("ESXIEECSR") + result == expected +} ## ## decode ## # decode with three rails -expect - message = "TEITELHDVLSNHDTISEIIEA" - result = message |> decode(3) - expected = Ok("THEDEVILISINTHEDETAILS") - result == expected +expect { + message = "TEITELHDVLSNHDTISEIIEA" + result = message->decode(3) + expected = Ok("THEDEVILISINTHEDETAILS") + result == expected +} # decode with five rails -expect - message = "EIEXMSMESAORIWSCE" - result = message |> decode(5) - expected = Ok("EXERCISMISAWESOME") - result == expected +expect { + message = "EIEXMSMESAORIWSCE" + result = message->decode(5) + expected = Ok("EXERCISMISAWESOME") + result == expected +} # decode with six rails -expect - message = "133714114238148966225439541018335470986172518171757571896261" - result = message |> decode(6) - expected = Ok("112358132134558914423337761098715972584418167651094617711286") - result == expected +expect { + message = "133714114238148966225439541018335470986172518171757571896261" + result = message->decode(6) + expected = Ok("112358132134558914423337761098715972584418167651094617711286") + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/raindrops/.meta/template.j2 b/exercises/practice/raindrops/.meta/template.j2 index 95233d7a..326c5375 100644 --- a/exercises/practice/raindrops/.meta/template.j2 +++ b/exercises/practice/raindrops/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [convert] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["number"] }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/raindrops/raindrops-test.roc b/exercises/practice/raindrops/raindrops-test.roc index bc7cc9c9..512d41a5 100644 --- a/exercises/practice/raindrops/raindrops-test.roc +++ b/exercises/practice/raindrops/raindrops-test.roc @@ -1,104 +1,118 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/raindrops/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Raindrops exposing [convert] # the sound for 1 is 1 -expect - result = convert(1) - result == "1" +expect { + result = convert(1) + result == "1" +} # the sound for 3 is Pling -expect - result = convert(3) - result == "Pling" +expect { + result = convert(3) + result == "Pling" +} # the sound for 5 is Plang -expect - result = convert(5) - result == "Plang" +expect { + result = convert(5) + result == "Plang" +} # the sound for 7 is Plong -expect - result = convert(7) - result == "Plong" +expect { + result = convert(7) + result == "Plong" +} # the sound for 6 is Pling as it has a factor 3 -expect - result = convert(6) - result == "Pling" +expect { + result = convert(6) + result == "Pling" +} # 2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base -expect - result = convert(8) - result == "8" +expect { + result = convert(8) + result == "8" +} # the sound for 9 is Pling as it has a factor 3 -expect - result = convert(9) - result == "Pling" +expect { + result = convert(9) + result == "Pling" +} # the sound for 10 is Plang as it has a factor 5 -expect - result = convert(10) - result == "Plang" +expect { + result = convert(10) + result == "Plang" +} # the sound for 14 is Plong as it has a factor of 7 -expect - result = convert(14) - result == "Plong" +expect { + result = convert(14) + result == "Plong" +} # the sound for 15 is PlingPlang as it has factors 3 and 5 -expect - result = convert(15) - result == "PlingPlang" +expect { + result = convert(15) + result == "PlingPlang" +} # the sound for 21 is PlingPlong as it has factors 3 and 7 -expect - result = convert(21) - result == "PlingPlong" +expect { + result = convert(21) + result == "PlingPlong" +} # the sound for 25 is Plang as it has a factor 5 -expect - result = convert(25) - result == "Plang" +expect { + result = convert(25) + result == "Plang" +} # the sound for 27 is Pling as it has a factor 3 -expect - result = convert(27) - result == "Pling" +expect { + result = convert(27) + result == "Pling" +} # the sound for 35 is PlangPlong as it has factors 5 and 7 -expect - result = convert(35) - result == "PlangPlong" +expect { + result = convert(35) + result == "PlangPlong" +} # the sound for 49 is Plong as it has a factor 7 -expect - result = convert(49) - result == "Plong" +expect { + result = convert(49) + result == "Plong" +} # the sound for 52 is 52 -expect - result = convert(52) - result == "52" +expect { + result = convert(52) + result == "52" +} # the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7 -expect - result = convert(105) - result == "PlingPlangPlong" +expect { + result = convert(105) + result == "PlingPlangPlong" +} # the sound for 3125 is Plang as it has a factor 5 -expect - result = convert(3125) - result == "Plang" +expect { + result = convert(3125) + result == "Plang" +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/rational-numbers/.meta/template.j2 b/exercises/practice/rational-numbers/.meta/template.j2 index 25007435..df932170 100644 --- a/exercises/practice/rational-numbers/.meta/template.j2 +++ b/exercises/practice/rational-numbers/.meta/template.j2 @@ -4,6 +4,10 @@ import {{ exercise | to_pascal }} exposing [add, sub, mul, div, abs, exp, exp_real, reduce] +is_approx_eq = |f1, f2| { + (f1 * 1e9 + 0.5).to_u64_wrap() == (f2 * 1e9 + 0.5).to_u64_wrap() +} + {% set property_map = { "abs": "abs", "add": "add", @@ -18,16 +22,16 @@ import {{ exercise | to_pascal }} exposing [add, sub, mul, div, abs, exp, exp_re {% macro test_case(case) -%} # {{ case["description"] }} -expect +expect { {%- if "r1" in case["input"] %} - result = {{ plugins.to_roc_rational(case["input"]["r1"]) }} |> {{ property_map[case["property"]] | to_snake }}(({{ plugins.to_roc_rational(case["input"]["r2"]) }})) + result = {{ plugins.to_roc_rational(case["input"]["r1"]) }}.{{ property_map[case["property"]] | to_snake }}(({{ plugins.to_roc_rational(case["input"]["r2"]) }})) {%- elif "r" in case["input"] %} {%- if "x" in case["input"] %} - result = {{ case["input"]["x"] | to_roc }} |> {{ property_map[case["property"]] | to_snake }}(({{ plugins.to_roc_rational(case["input"]["r"]) }})) + result = {{ case["input"]["x"] | to_roc }} -> {{ property_map[case["property"]] | to_snake }}(({{ plugins.to_roc_rational(case["input"]["r"]) }})) {%- elif "n" in case["input"] %} - result = {{ plugins.to_roc_rational(case["input"]["r"]) }} |> {{ property_map[case["property"]] | to_snake }}({{ case["input"]["n"] | to_roc }}) + result = {{ plugins.to_roc_rational(case["input"]["r"]) }}.{{ property_map[case["property"]] | to_snake }}({{ case["input"]["n"] | to_roc }}) {%- else %} - result = {{ plugins.to_roc_rational(case["input"]["r"]) }} |> {{ property_map[case["property"]] | to_snake }} + result = {{ plugins.to_roc_rational(case["input"]["r"]) }}.{{ property_map[case["property"]] | to_snake }} {%- endif %} {%- else %} crash "This test case is not implemented yet." @@ -35,8 +39,9 @@ expect {%- if case["expected"] is iterable %} result == {{ plugins.to_roc_rational(case["expected"]) }} {%- else %} - result |> Num.is_approx_eq({{ case["expected"] | to_roc }}, {}) + result -> is_approx_eq({{ case["expected"] | to_roc }}) {%- endif %} +} {% endmacro %} diff --git a/exercises/practice/rational-numbers/rational-numbers-test.roc b/exercises/practice/rational-numbers/rational-numbers-test.roc index d3d9c7a9..57bffcb2 100644 --- a/exercises/practice/rational-numbers/rational-numbers-test.roc +++ b/exercises/practice/rational-numbers/rational-numbers-test.roc @@ -1,249 +1,292 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rational-numbers/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import RationalNumbers exposing [add, sub, mul, div, abs, exp, exp_real, reduce] +is_approx_eq = |f1, f2| { + (f1 * 1e9 + 0.5).to_u64_wrap() == (f2 * 1e9 + 0.5).to_u64_wrap() +} + ## ## Arithmetic ## # Add two positive rational numbers -expect - result = Rational(1, 2) |> add(Rational(2, 3)) - result == Rational(7, 6) +expect { + result = Rational(1, 2).add((Rational(2, 3))) + result == Rational(7, 6) +} # Add a positive rational number and a negative rational number -expect - result = Rational(1, 2) |> add(Rational(-2, 3)) - result == Rational(-1, 6) +expect { + result = Rational(1, 2).add((Rational(-2, 3))) + result == Rational(-1, 6) +} # Add two negative rational numbers -expect - result = Rational(-1, 2) |> add(Rational(-2, 3)) - result == Rational(-7, 6) +expect { + result = Rational(-1, 2).add((Rational(-2, 3))) + result == Rational(-7, 6) +} # Add a rational number to its additive inverse -expect - result = Rational(1, 2) |> add(Rational(-1, 2)) - result == Rational(0, 1) +expect { + result = Rational(1, 2).add((Rational(-1, 2))) + result == Rational(0, 1) +} # Subtract two positive rational numbers -expect - result = Rational(1, 2) |> sub(Rational(2, 3)) - result == Rational(-1, 6) +expect { + result = Rational(1, 2).sub((Rational(2, 3))) + result == Rational(-1, 6) +} # Subtract a positive rational number and a negative rational number -expect - result = Rational(1, 2) |> sub(Rational(-2, 3)) - result == Rational(7, 6) +expect { + result = Rational(1, 2).sub((Rational(-2, 3))) + result == Rational(7, 6) +} # Subtract two negative rational numbers -expect - result = Rational(-1, 2) |> sub(Rational(-2, 3)) - result == Rational(1, 6) +expect { + result = Rational(-1, 2).sub((Rational(-2, 3))) + result == Rational(1, 6) +} # Subtract a rational number from itself -expect - result = Rational(1, 2) |> sub(Rational(1, 2)) - result == Rational(0, 1) +expect { + result = Rational(1, 2).sub((Rational(1, 2))) + result == Rational(0, 1) +} # Multiply two positive rational numbers -expect - result = Rational(1, 2) |> mul(Rational(2, 3)) - result == Rational(1, 3) +expect { + result = Rational(1, 2).mul((Rational(2, 3))) + result == Rational(1, 3) +} # Multiply a negative rational number by a positive rational number -expect - result = Rational(-1, 2) |> mul(Rational(2, 3)) - result == Rational(-1, 3) +expect { + result = Rational(-1, 2).mul((Rational(2, 3))) + result == Rational(-1, 3) +} # Multiply two negative rational numbers -expect - result = Rational(-1, 2) |> mul(Rational(-2, 3)) - result == Rational(1, 3) +expect { + result = Rational(-1, 2).mul((Rational(-2, 3))) + result == Rational(1, 3) +} # Multiply a rational number by its reciprocal -expect - result = Rational(1, 2) |> mul(Rational(2, 1)) - result == Rational(1, 1) +expect { + result = Rational(1, 2).mul((Rational(2, 1))) + result == Rational(1, 1) +} # Multiply a rational number by 1 -expect - result = Rational(1, 2) |> mul(Rational(1, 1)) - result == Rational(1, 2) +expect { + result = Rational(1, 2).mul((Rational(1, 1))) + result == Rational(1, 2) +} # Multiply a rational number by 0 -expect - result = Rational(1, 2) |> mul(Rational(0, 1)) - result == Rational(0, 1) +expect { + result = Rational(1, 2).mul((Rational(0, 1))) + result == Rational(0, 1) +} # Divide two positive rational numbers -expect - result = Rational(1, 2) |> div(Rational(2, 3)) - result == Rational(3, 4) +expect { + result = Rational(1, 2).div((Rational(2, 3))) + result == Rational(3, 4) +} # Divide a positive rational number by a negative rational number -expect - result = Rational(1, 2) |> div(Rational(-2, 3)) - result == Rational(-3, 4) +expect { + result = Rational(1, 2).div((Rational(-2, 3))) + result == Rational(-3, 4) +} # Divide two negative rational numbers -expect - result = Rational(-1, 2) |> div(Rational(-2, 3)) - result == Rational(3, 4) +expect { + result = Rational(-1, 2).div((Rational(-2, 3))) + result == Rational(3, 4) +} # Divide a rational number by 1 -expect - result = Rational(1, 2) |> div(Rational(1, 1)) - result == Rational(1, 2) +expect { + result = Rational(1, 2).div((Rational(1, 1))) + result == Rational(1, 2) +} ## ## Absolute value ## # Absolute value of a positive rational number -expect - result = Rational(1, 2) |> abs - result == Rational(1, 2) +expect { + result = Rational(1, 2).abs + result == Rational(1, 2) +} # Absolute value of a positive rational number with negative numerator and denominator -expect - result = Rational(-1, -2) |> abs - result == Rational(1, 2) +expect { + result = Rational(-1, -2).abs + result == Rational(1, 2) +} # Absolute value of a negative rational number -expect - result = Rational(-1, 2) |> abs - result == Rational(1, 2) +expect { + result = Rational(-1, 2).abs + result == Rational(1, 2) +} # Absolute value of a negative rational number with negative denominator -expect - result = Rational(1, -2) |> abs - result == Rational(1, 2) +expect { + result = Rational(1, -2).abs + result == Rational(1, 2) +} # Absolute value of zero -expect - result = Rational(0, 1) |> abs - result == Rational(0, 1) +expect { + result = Rational(0, 1).abs + result == Rational(0, 1) +} # Absolute value of a rational number is reduced to lowest terms -expect - result = Rational(2, 4) |> abs - result == Rational(1, 2) +expect { + result = Rational(2, 4).abs + result == Rational(1, 2) +} ## ## Exponentiation of a rational number ## # Raise a positive rational number to a positive integer power -expect - result = Rational(1, 2) |> exp(3) - result == Rational(1, 8) +expect { + result = Rational(1, 2).exp(3) + result == Rational(1, 8) +} # Raise a negative rational number to a positive integer power -expect - result = Rational(-1, 2) |> exp(3) - result == Rational(-1, 8) +expect { + result = Rational(-1, 2).exp(3) + result == Rational(-1, 8) +} # Raise a positive rational number to a negative integer power -expect - result = Rational(3, 5) |> exp(-2) - result == Rational(25, 9) +expect { + result = Rational(3, 5).exp(-2) + result == Rational(25, 9) +} # Raise a negative rational number to an even negative integer power -expect - result = Rational(-3, 5) |> exp(-2) - result == Rational(25, 9) +expect { + result = Rational(-3, 5).exp(-2) + result == Rational(25, 9) +} # Raise a negative rational number to an odd negative integer power -expect - result = Rational(-3, 5) |> exp(-3) - result == Rational(-125, 27) +expect { + result = Rational(-3, 5).exp(-3) + result == Rational(-125, 27) +} # Raise zero to an integer power -expect - result = Rational(0, 1) |> exp(5) - result == Rational(0, 1) +expect { + result = Rational(0, 1).exp(5) + result == Rational(0, 1) +} # Raise one to an integer power -expect - result = Rational(1, 1) |> exp(4) - result == Rational(1, 1) +expect { + result = Rational(1, 1).exp(4) + result == Rational(1, 1) +} # Raise a positive rational number to the power of zero -expect - result = Rational(1, 2) |> exp(0) - result == Rational(1, 1) +expect { + result = Rational(1, 2).exp(0) + result == Rational(1, 1) +} # Raise a negative rational number to the power of zero -expect - result = Rational(-1, 2) |> exp(0) - result == Rational(1, 1) +expect { + result = Rational(-1, 2).exp(0) + result == Rational(1, 1) +} ## ## Exponentiation of a real number to a rational number ## # Raise a real number to a positive rational number -expect - result = 8 |> exp_real(Rational(4, 3)) - result |> Num.is_approx_eq(16.0f64, {}) +expect { + result = 8->exp_real((Rational(4, 3))) + result->is_approx_eq(16.0.F64) +} # Raise a real number to a negative rational number -expect - result = 9 |> exp_real(Rational(-1, 2)) - result |> Num.is_approx_eq(0.3333333333333333f64, {}) +expect { + result = 9->exp_real((Rational(-1, 2))) + result->is_approx_eq(0.3333333333333333.F64) +} # Raise a real number to a zero rational number -expect - result = 2 |> exp_real(Rational(0, 1)) - result |> Num.is_approx_eq(1.0f64, {}) +expect { + result = 2->exp_real((Rational(0, 1))) + result->is_approx_eq(1.0.F64) +} ## ## Reduction to lowest terms ## # Reduce a positive rational number to lowest terms -expect - result = Rational(2, 4) |> reduce - result == Rational(1, 2) +expect { + result = Rational(2, 4).reduce + result == Rational(1, 2) +} # Reduce places the minus sign on the numerator -expect - result = Rational(3, -4) |> reduce - result == Rational(-3, 4) +expect { + result = Rational(3, -4).reduce + result == Rational(-3, 4) +} # Reduce a negative rational number to lowest terms -expect - result = Rational(-4, 6) |> reduce - result == Rational(-2, 3) +expect { + result = Rational(-4, 6).reduce + result == Rational(-2, 3) +} # Reduce a rational number with a negative denominator to lowest terms -expect - result = Rational(3, -9) |> reduce - result == Rational(-1, 3) +expect { + result = Rational(3, -9).reduce + result == Rational(-1, 3) +} # Reduce zero to lowest terms -expect - result = Rational(0, 6) |> reduce - result == Rational(0, 1) +expect { + result = Rational(0, 6).reduce + result == Rational(0, 1) +} # Reduce an integer to lowest terms -expect - result = Rational(-14, 7) |> reduce - result == Rational(-2, 1) +expect { + result = Rational(-14, 7).reduce + result == Rational(-2, 1) +} # Reduce one to lowest terms -expect - result = Rational(13, 13) |> reduce - result == Rational(1, 1) +expect { + result = Rational(13, 13).reduce + result == Rational(1, 1) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/rectangles/.meta/template.j2 b/exercises/practice/rectangles/.meta/template.j2 index 62712bac..b0918e38 100644 --- a/exercises/practice/rectangles/.meta/template.j2 +++ b/exercises/practice/rectangles/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["strings"] | to_roc_multiline_string | indent(8) }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/rectangles/rectangles-test.roc b/exercises/practice/rectangles/rectangles-test.roc index 1279b25d..bfec6746 100644 --- a/exercises/practice/rectangles/rectangles-test.roc +++ b/exercises/practice/rectangles/rectangles-test.roc @@ -1,167 +1,166 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rectangles/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Rectangles exposing [rectangles] # no rows -expect - result = rectangles("") - result == 0 +expect { + result = rectangles("") + result == 0 +} # no columns -expect - result = rectangles("") - result == 0 +expect { + result = rectangles("") + result == 0 +} # no rectangles -expect - result = rectangles(" ") - result == 0 +expect { + result = rectangles(" ") + result == 0 +} # one rectangle -expect - result = rectangles( - """ - +-+ - | | - +-+ - """, - ) - result == 1 +expect { + result = rectangles( + \\+-+ + \\| | + \\+-+ + , + ) + result == 1 +} # two rectangles without shared parts -expect - result = rectangles( - """ - +-+ - | | - +-+-+ - | | - +-+ - """, - ) - result == 2 +expect { + result = rectangles( + \\ +-+ + \\ | | + \\+-+-+ + \\| | + \\+-+ + , + ) + result == 2 +} # five rectangles with shared parts -expect - result = rectangles( - """ - +-+ - | | - +-+-+ - | | | - +-+-+ - """, - ) - result == 5 +expect { + result = rectangles( + \\ +-+ + \\ | | + \\+-+-+ + \\| | | + \\+-+-+ + , + ) + result == 5 +} # rectangle of height 1 is counted -expect - result = rectangles( - """ - +--+ - +--+ - """, - ) - result == 1 +expect { + result = rectangles( + \\+--+ + \\+--+ + , + ) + result == 1 +} # rectangle of width 1 is counted -expect - result = rectangles( - """ - ++ - || - ++ - """, - ) - result == 1 +expect { + result = rectangles( + \\++ + \\|| + \\++ + , + ) + result == 1 +} # 1x1 square is counted -expect - result = rectangles( - """ - ++ - ++ - """, - ) - result == 1 +expect { + result = rectangles( + \\++ + \\++ + , + ) + result == 1 +} # only complete rectangles are counted -expect - result = rectangles( - """ - +-+ - | - +-+-+ - | | - - +-+-+ - """, - ) - result == 1 +expect { + result = rectangles( + \\ +-+ + \\ | + \\+-+-+ + \\| | - + \\+-+-+ + , + ) + result == 1 +} # rectangles can be of different sizes -expect - result = rectangles( - """ - +------+----+ - | | | - +---+--+ | - | | | - +---+-------+ - """, - ) - result == 3 +expect { + result = rectangles( + \\+------+----+ + \\| | | + \\+---+--+ | + \\| | | + \\+---+-------+ + , + ) + result == 3 +} # corner is required for a rectangle to be complete -expect - result = rectangles( - """ - +------+----+ - | | | - +------+ | - | | | - +---+-------+ - """, - ) - result == 2 +expect { + result = rectangles( + \\+------+----+ + \\| | | + \\+------+ | + \\| | | + \\+---+-------+ + , + ) + result == 2 +} # large input with many rectangles -expect - result = rectangles( - """ - +---+--+----+ - | +--+----+ - +---+--+ | - | +--+----+ - +---+--+--+-+ - +---+--+--+-+ - +------+ | | - +-+ - """, - ) - result == 60 +expect { + result = rectangles( + \\+---+--+----+ + \\| +--+----+ + \\+---+--+ | + \\| +--+----+ + \\+---+--+--+-+ + \\+---+--+--+-+ + \\+------+ | | + \\ +-+ + , + ) + result == 60 +} # rectangles must have four sides -expect - result = rectangles( - """ - +-+ +-+ - | | | | - +-+-+-+ - | | - +-+-+-+ - | | | | - +-+ +-+ - """, - ) - result == 5 +expect { + result = rectangles( + \\+-+ +-+ + \\| | | | + \\+-+-+-+ + \\ | | + \\+-+-+-+ + \\| | | | + \\+-+ +-+ + , + ) + result == 5 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/resistor-color-duo/.meta/template.j2 b/exercises/practice/resistor-color-duo/.meta/template.j2 index 52e62a4b..b6333171 100644 --- a/exercises/practice/resistor-color-duo/.meta/template.j2 +++ b/exercises/practice/resistor-color-duo/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [value] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["colors"][0] | to_pascal }}, {{ case["input"]["colors"][1] | to_pascal }}) result == {{ case["expected"] }} +} {% endfor %} diff --git a/exercises/practice/resistor-color-duo/resistor-color-duo-test.roc b/exercises/practice/resistor-color-duo/resistor-color-duo-test.roc index 131175f0..9fd40bb7 100644 --- a/exercises/practice/resistor-color-duo/resistor-color-duo-test.roc +++ b/exercises/practice/resistor-color-duo/resistor-color-duo-test.roc @@ -1,44 +1,46 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-duo/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import ResistorColorDuo exposing [value] # Brown and black -expect - result = value(Brown, Black) - result == 10 +expect { + result = value(Brown, Black) + result == 10 +} # Blue and grey -expect - result = value(Blue, Grey) - result == 68 +expect { + result = value(Blue, Grey) + result == 68 +} # Yellow and violet -expect - result = value(Yellow, Violet) - result == 47 +expect { + result = value(Yellow, Violet) + result == 47 +} # White and red -expect - result = value(White, Red) - result == 92 +expect { + result = value(White, Red) + result == 92 +} # Orange and orange -expect - result = value(Orange, Orange) - result == 33 +expect { + result = value(Orange, Orange) + result == 33 +} # Black and brown, one-digit -expect - result = value(Black, Brown) - result == 1 +expect { + result = value(Black, Brown) + result == 1 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/resistor-color/.meta/template.j2 b/exercises/practice/resistor-color/.meta/template.j2 index 6df5e065..ece97f25 100644 --- a/exercises/practice/resistor-color/.meta/template.j2 +++ b/exercises/practice/resistor-color/.meta/template.j2 @@ -9,22 +9,23 @@ import {{ exercise | to_pascal }} exposing [color_code, colors] ## {{ case["description"] }} ## {% if case["property"] == "colors" %} -expect - result = {{ case["property"] | to_snake }} - result == [ +expect { + {{ case["property"] | to_snake }} == [ {%- for color in case["expected"] %} {{ color | to_roc }}, {%- endfor %} ] +} {% else %} {% for subcase in case["cases"] -%} # {{ subcase["description"] }} -expect +expect { result = {{ subcase["property"] | to_snake }}({{ subcase["input"]["color"] | to_roc }}) result == Ok({{ subcase["expected"] }}) +} {% endfor %} {%- endif -%} -{%- endfor -%} +{%- endfor %} {{ macros.footer() }} diff --git a/exercises/practice/resistor-color/resistor-color-test.roc b/exercises/practice/resistor-color/resistor-color-test.roc index b1a35811..29780682 100644 --- a/exercises/practice/resistor-color/resistor-color-test.roc +++ b/exercises/practice/resistor-color/resistor-color-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import ResistorColor exposing [color_code, colors] @@ -17,36 +9,43 @@ import ResistorColor exposing [color_code, colors] ## # Black -expect - result = color_code("black") - result == Ok(0) +expect { + result = color_code("black") + result == Ok(0) +} # White -expect - result = color_code("white") - result == Ok(9) +expect { + result = color_code("white") + result == Ok(9) +} # Orange -expect - result = color_code("orange") - result == Ok(3) +expect { + result = color_code("orange") + result == Ok(3) +} ## ## Colors ## -expect - result = colors - result - == [ - "black", - "brown", - "red", - "orange", - "yellow", - "green", - "blue", - "violet", - "grey", - "white", - ] +expect { + colors == [ + "black", + "brown", + "red", + "orange", + "yellow", + "green", + "blue", + "violet", + "grey", + "white", + ] +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/rest-api/.meta/template.j2 b/exercises/practice/rest-api/.meta/template.j2 index bd23100b..255952af 100644 --- a/exercises/practice/rest-api/.meta/template.j2 +++ b/exercises/practice/rest-api/.meta/template.j2 @@ -4,17 +4,19 @@ import {{ exercise | to_pascal }} exposing [get, post] -standardize_result = |result| +standardize_result = |result| { result - |> Result.try( - |string| + -> Result.try( + |string| { string - |> Str.replace_each(".0,", ",") - |> Str.replace_each(".0}", "}") - |> Str.to_utf8 - |> List.drop_if |c| [' ', '\t', '\n'] |> List.contains(c) - |> Str.from_utf8 + .replace_each(".0,", ",") + .replace_each(".0}", "}") + .to_utf8() + .drop_if(|c| { [' ', '\t', '\n'].contains(c) }) + ->Str.from_utf8() + } ) +} {% for supercase in cases %} ## @@ -23,7 +25,7 @@ standardize_result = |result| {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect +expect { database = { users: [ {%- for user in case["input"]["database"]["users"] %} @@ -44,14 +46,15 @@ expect {%- endfor %} ] } - result = database |> {{ case["property"] | to_snake }}({ + result = database -> {{ case["property"] | to_snake }}({ url: {{ case["input"]["url"] | to_roc }}, {%- if case["input"].get("payload", {}) != {} %} payload: {{ case["input"]["payload"] | tojson | to_roc }} {%- endif %} - }) |> standardize_result + }) -> standardize_result() expected = Ok({{ case["expected"] | tojson | replace(".0,", ",") | replace(".0}", "}") | replace(" ", "") | to_roc }}) result == expected +} {% endfor %} {% endfor %} diff --git a/exercises/practice/rest-api/rest-api-test.roc b/exercises/practice/rest-api/rest-api-test.roc index 37149394..b9148767 100644 --- a/exercises/practice/rest-api/rest-api-test.roc +++ b/exercises/practice/rest-api/rest-api-test.roc @@ -1,332 +1,325 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rest-api/canonical-data.json -# File last updated on 2025-09-15 +# File last updated on 2026-06-13 app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", - json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.13.0/RqendgZw5e1RsQa3kFhgtnMP8efWoqGRsAvubx4-zus.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", + json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.13.0/RqendgZw5e1RsQa3kFhgtnMP8efWoqGRsAvubx4-zus.tar.br", } import pf.Stdout -main! = |_args| - Stdout.line!("") - import RestApi exposing [get, post] -standardize_result = |result| - result - |> Result.try( - |string| - string - |> Str.replace_each(".0,", ",") - |> Str.replace_each(".0}", "}") - |> Str.to_utf8 - |> List.drop_if |c| [' ', '\t', '\n'] |> List.contains(c) - |> Str.from_utf8, - ) +standardize_result = |result| { + result + ->Result.try( + |string| { + string + .replace_each( + ".0,", + ",", + ) + .replace_each( + ".0}", + "}", + ) + .to_utf8() + .drop_if( + |c| { + [' ', '\t', '\n'].contains(c) + }, + ) + ->Str.from_utf8() + }, + ) +} ## ## user management ## # no users -expect - database = { - users: [ - ], - } - result = - database - |> get( - { - url: "/users", - }, - ) - |> standardize_result - expected = Ok("{\"users\":[]}") - result == expected +expect { + database = { + users: [], + } + result = database->get( + { + url: "/users", + }, + )->standardize_result() + expected = Ok("{\"users\":[]}") + result == expected +} # add user -expect - database = { - users: [ - ], - } - result = - database - |> post( - { - url: "/add", - payload: "{\"user\": \"Adam\"}", - }, - ) - |> standardize_result - expected = Ok("{\"balance\":0,\"name\":\"Adam\",\"owed_by\":{},\"owes\":{}}") - result == expected +expect { + database = { + users: [], + } + result = database->post( + { + url: "/add", + payload: "{\"user\": \"Adam\"}", + }, + )->standardize_result() + expected = Ok("{\"balance\":0,\"name\":\"Adam\",\"owed_by\":{},\"owes\":{}}") + result == expected +} # get single user -expect - database = { - users: [ - { - name: "Adam", - owes: Dict.from_list([]), - owed_by: Dict.from_list([]), - balance: 0.0, - }, - { - name: "Bob", - owes: Dict.from_list([]), - owed_by: Dict.from_list([]), - balance: 0.0, - }, - ], - } - result = - database - |> get( - { - url: "/users", - payload: "{\"users\": [\"Bob\"]}", - }, - ) - |> standardize_result - expected = Ok("{\"users\":[{\"balance\":0,\"name\":\"Bob\",\"owed_by\":{},\"owes\":{}}]}") - result == expected +expect { + database = { + users: [ + { + name: "Adam", + owes: Dict.from_list([]), + owed_by: Dict.from_list([]), + balance: 0.0, + }, + { + name: "Bob", + owes: Dict.from_list([]), + owed_by: Dict.from_list([]), + balance: 0.0, + }, + ], + } + result = database->get( + { + url: "/users", + payload: "{\"users\": [\"Bob\"]}", + }, + )->standardize_result() + expected = Ok("{\"users\":[{\"balance\":0,\"name\":\"Bob\",\"owed_by\":{},\"owes\":{}}]}") + result == expected +} ## ## iou ## # both users have 0 balance -expect - database = { - users: [ - { - name: "Adam", - owes: Dict.from_list([]), - owed_by: Dict.from_list([]), - balance: 0.0, - }, - { - name: "Bob", - owes: Dict.from_list([]), - owed_by: Dict.from_list([]), - balance: 0.0, - }, - ], - } - result = - database - |> post( - { - url: "/iou", - payload: "{\"amount\": 3.0, \"borrower\": \"Bob\", \"lender\": \"Adam\"}", - }, - ) - |> standardize_result - expected = Ok("{\"users\":[{\"balance\":3,\"name\":\"Adam\",\"owed_by\":{\"Bob\":3},\"owes\":{}},{\"balance\":-3,\"name\":\"Bob\",\"owed_by\":{},\"owes\":{\"Adam\":3}}]}") - result == expected +expect { + database = { + users: [ + { + name: "Adam", + owes: Dict.from_list([]), + owed_by: Dict.from_list([]), + balance: 0.0, + }, + { + name: "Bob", + owes: Dict.from_list([]), + owed_by: Dict.from_list([]), + balance: 0.0, + }, + ], + } + result = database->post( + { + url: "/iou", + payload: "{\"amount\": 3.0, \"borrower\": \"Bob\", \"lender\": \"Adam\"}", + }, + )->standardize_result() + expected = Ok("{\"users\":[{\"balance\":3,\"name\":\"Adam\",\"owed_by\":{\"Bob\":3},\"owes\":{}},{\"balance\":-3,\"name\":\"Bob\",\"owed_by\":{},\"owes\":{\"Adam\":3}}]}") + result == expected +} # borrower has negative balance -expect - database = { - users: [ - { - name: "Adam", - owes: Dict.from_list([]), - owed_by: Dict.from_list([]), - balance: 0.0, - }, - { - name: "Bob", - owes: Dict.from_list( - [ - ("Chuck", 3.0), - ], - ), - owed_by: Dict.from_list([]), - balance: -3.0, - }, - { - name: "Chuck", - owes: Dict.from_list([]), - owed_by: Dict.from_list( - [ - ("Bob", 3.0), - ], - ), - balance: 3.0, - }, - ], - } - result = - database - |> post( - { - url: "/iou", - payload: "{\"amount\": 3.0, \"borrower\": \"Bob\", \"lender\": \"Adam\"}", - }, - ) - |> standardize_result - expected = Ok("{\"users\":[{\"balance\":3,\"name\":\"Adam\",\"owed_by\":{\"Bob\":3},\"owes\":{}},{\"balance\":-6,\"name\":\"Bob\",\"owed_by\":{},\"owes\":{\"Adam\":3,\"Chuck\":3}}]}") - result == expected +expect { + database = { + users: [ + { + name: "Adam", + owes: Dict.from_list([]), + owed_by: Dict.from_list([]), + balance: 0.0, + }, + { + name: "Bob", + owes: Dict.from_list( + [ + ("Chuck", 3.0), + ], + ), + owed_by: Dict.from_list([]), + balance: -3.0, + }, + { + name: "Chuck", + owes: Dict.from_list([]), + owed_by: Dict.from_list( + [ + ("Bob", 3.0), + ], + ), + balance: 3.0, + }, + ], + } + result = database->post( + { + url: "/iou", + payload: "{\"amount\": 3.0, \"borrower\": \"Bob\", \"lender\": \"Adam\"}", + }, + )->standardize_result() + expected = Ok("{\"users\":[{\"balance\":3,\"name\":\"Adam\",\"owed_by\":{\"Bob\":3},\"owes\":{}},{\"balance\":-6,\"name\":\"Bob\",\"owed_by\":{},\"owes\":{\"Adam\":3,\"Chuck\":3}}]}") + result == expected +} # lender has negative balance -expect - database = { - users: [ - { - name: "Adam", - owes: Dict.from_list([]), - owed_by: Dict.from_list([]), - balance: 0.0, - }, - { - name: "Bob", - owes: Dict.from_list( - [ - ("Chuck", 3.0), - ], - ), - owed_by: Dict.from_list([]), - balance: -3.0, - }, - { - name: "Chuck", - owes: Dict.from_list([]), - owed_by: Dict.from_list( - [ - ("Bob", 3.0), - ], - ), - balance: 3.0, - }, - ], - } - result = - database - |> post( - { - url: "/iou", - payload: "{\"amount\": 3.0, \"borrower\": \"Adam\", \"lender\": \"Bob\"}", - }, - ) - |> standardize_result - expected = Ok("{\"users\":[{\"balance\":-3,\"name\":\"Adam\",\"owed_by\":{},\"owes\":{\"Bob\":3}},{\"balance\":0,\"name\":\"Bob\",\"owed_by\":{\"Adam\":3},\"owes\":{\"Chuck\":3}}]}") - result == expected +expect { + database = { + users: [ + { + name: "Adam", + owes: Dict.from_list([]), + owed_by: Dict.from_list([]), + balance: 0.0, + }, + { + name: "Bob", + owes: Dict.from_list( + [ + ("Chuck", 3.0), + ], + ), + owed_by: Dict.from_list([]), + balance: -3.0, + }, + { + name: "Chuck", + owes: Dict.from_list([]), + owed_by: Dict.from_list( + [ + ("Bob", 3.0), + ], + ), + balance: 3.0, + }, + ], + } + result = database->post( + { + url: "/iou", + payload: "{\"amount\": 3.0, \"borrower\": \"Adam\", \"lender\": \"Bob\"}", + }, + )->standardize_result() + expected = Ok("{\"users\":[{\"balance\":-3,\"name\":\"Adam\",\"owed_by\":{},\"owes\":{\"Bob\":3}},{\"balance\":0,\"name\":\"Bob\",\"owed_by\":{\"Adam\":3},\"owes\":{\"Chuck\":3}}]}") + result == expected +} # lender owes borrower -expect - database = { - users: [ - { - name: "Adam", - owes: Dict.from_list( - [ - ("Bob", 3.0), - ], - ), - owed_by: Dict.from_list([]), - balance: -3.0, - }, - { - name: "Bob", - owes: Dict.from_list([]), - owed_by: Dict.from_list( - [ - ("Adam", 3.0), - ], - ), - balance: 3.0, - }, - ], - } - result = - database - |> post( - { - url: "/iou", - payload: "{\"amount\": 2.0, \"borrower\": \"Bob\", \"lender\": \"Adam\"}", - }, - ) - |> standardize_result - expected = Ok("{\"users\":[{\"balance\":-1,\"name\":\"Adam\",\"owed_by\":{},\"owes\":{\"Bob\":1}},{\"balance\":1,\"name\":\"Bob\",\"owed_by\":{\"Adam\":1},\"owes\":{}}]}") - result == expected +expect { + database = { + users: [ + { + name: "Adam", + owes: Dict.from_list( + [ + ("Bob", 3.0), + ], + ), + owed_by: Dict.from_list([]), + balance: -3.0, + }, + { + name: "Bob", + owes: Dict.from_list([]), + owed_by: Dict.from_list( + [ + ("Adam", 3.0), + ], + ), + balance: 3.0, + }, + ], + } + result = database->post( + { + url: "/iou", + payload: "{\"amount\": 2.0, \"borrower\": \"Bob\", \"lender\": \"Adam\"}", + }, + )->standardize_result() + expected = Ok("{\"users\":[{\"balance\":-1,\"name\":\"Adam\",\"owed_by\":{},\"owes\":{\"Bob\":1}},{\"balance\":1,\"name\":\"Bob\",\"owed_by\":{\"Adam\":1},\"owes\":{}}]}") + result == expected +} # lender owes borrower less than new loan -expect - database = { - users: [ - { - name: "Adam", - owes: Dict.from_list( - [ - ("Bob", 3.0), - ], - ), - owed_by: Dict.from_list([]), - balance: -3.0, - }, - { - name: "Bob", - owes: Dict.from_list([]), - owed_by: Dict.from_list( - [ - ("Adam", 3.0), - ], - ), - balance: 3.0, - }, - ], - } - result = - database - |> post( - { - url: "/iou", - payload: "{\"amount\": 4.0, \"borrower\": \"Bob\", \"lender\": \"Adam\"}", - }, - ) - |> standardize_result - expected = Ok("{\"users\":[{\"balance\":1,\"name\":\"Adam\",\"owed_by\":{\"Bob\":1},\"owes\":{}},{\"balance\":-1,\"name\":\"Bob\",\"owed_by\":{},\"owes\":{\"Adam\":1}}]}") - result == expected +expect { + database = { + users: [ + { + name: "Adam", + owes: Dict.from_list( + [ + ("Bob", 3.0), + ], + ), + owed_by: Dict.from_list([]), + balance: -3.0, + }, + { + name: "Bob", + owes: Dict.from_list([]), + owed_by: Dict.from_list( + [ + ("Adam", 3.0), + ], + ), + balance: 3.0, + }, + ], + } + result = database->post( + { + url: "/iou", + payload: "{\"amount\": 4.0, \"borrower\": \"Bob\", \"lender\": \"Adam\"}", + }, + )->standardize_result() + expected = Ok("{\"users\":[{\"balance\":1,\"name\":\"Adam\",\"owed_by\":{\"Bob\":1},\"owes\":{}},{\"balance\":-1,\"name\":\"Bob\",\"owed_by\":{},\"owes\":{\"Adam\":1}}]}") + result == expected +} # lender owes borrower same as new loan -expect - database = { - users: [ - { - name: "Adam", - owes: Dict.from_list( - [ - ("Bob", 3.0), - ], - ), - owed_by: Dict.from_list([]), - balance: -3.0, - }, - { - name: "Bob", - owes: Dict.from_list([]), - owed_by: Dict.from_list( - [ - ("Adam", 3.0), - ], - ), - balance: 3.0, - }, - ], - } - result = - database - |> post( - { - url: "/iou", - payload: "{\"amount\": 3.0, \"borrower\": \"Bob\", \"lender\": \"Adam\"}", - }, - ) - |> standardize_result - expected = Ok("{\"users\":[{\"balance\":0,\"name\":\"Adam\",\"owed_by\":{},\"owes\":{}},{\"balance\":0,\"name\":\"Bob\",\"owed_by\":{},\"owes\":{}}]}") - result == expected +expect { + database = { + users: [ + { + name: "Adam", + owes: Dict.from_list( + [ + ("Bob", 3.0), + ], + ), + owed_by: Dict.from_list([]), + balance: -3.0, + }, + { + name: "Bob", + owes: Dict.from_list([]), + owed_by: Dict.from_list( + [ + ("Adam", 3.0), + ], + ), + balance: 3.0, + }, + ], + } + result = database->post( + { + url: "/iou", + payload: "{\"amount\": 3.0, \"borrower\": \"Bob\", \"lender\": \"Adam\"}", + }, + )->standardize_result() + expected = Ok("{\"users\":[{\"balance\":0,\"name\":\"Adam\",\"owed_by\":{},\"owes\":{}},{\"balance\":0,\"name\":\"Bob\",\"owed_by\":{},\"owes\":{}}]}") + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/reverse-string/.meta/template.j2 b/exercises/practice/reverse-string/.meta/template.j2 index f0c664e9..8e877ad1 100644 --- a/exercises/practice/reverse-string/.meta/template.j2 +++ b/exercises/practice/reverse-string/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [reverse] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["value"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/reverse-string/reverse-string-test.roc b/exercises/practice/reverse-string/reverse-string-test.roc index ca31f40f..de65424f 100644 --- a/exercises/practice/reverse-string/reverse-string-test.roc +++ b/exercises/practice/reverse-string/reverse-string-test.roc @@ -1,60 +1,70 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/reverse-string/canonical-data.json -# File last updated on 2025-09-15 +# File last updated on 2026-06-13 app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", - unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", + unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br", } import pf.Stdout -main! = |_args| - Stdout.line!("") - import ReverseString exposing [reverse] # an empty string -expect - result = reverse("") - result == "" +expect { + result = reverse("") + result == "" +} # a word -expect - result = reverse("robot") - result == "tobor" +expect { + result = reverse("robot") + result == "tobor" +} # a capitalized word -expect - result = reverse("Ramen") - result == "nemaR" +expect { + result = reverse("Ramen") + result == "nemaR" +} # a sentence with punctuation -expect - result = reverse("I'm hungry!") - result == "!yrgnuh m'I" +expect { + result = reverse("I'm hungry!") + result == "!yrgnuh m'I" +} # a palindrome -expect - result = reverse("racecar") - result == "racecar" +expect { + result = reverse("racecar") + result == "racecar" +} # an even-sized word -expect - result = reverse("drawer") - result == "reward" +expect { + result = reverse("drawer") + result == "reward" +} # wide characters -expect - result = reverse("子猫") - result == "猫子" +expect { + result = reverse("子猫") + result == "猫子" +} # grapheme cluster with pre-combined form -expect - result = reverse("Würstchenstand") - result == "dnatsnehctsrüW" +expect { + result = reverse("Würstchenstand") + result == "dnatsnehctsrüW" +} # grapheme clusters -expect - result = reverse("ผู้เขียนโปรแกรม") - result == "มรกแรปโนยขีเผู้" +expect { + result = reverse("ผู้เขียนโปรแกรม") + result == "มรกแรปโนยขีเผู้" +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/rna-transcription/.meta/template.j2 b/exercises/practice/rna-transcription/.meta/template.j2 index c0d44d79..9b76bf72 100644 --- a/exercises/practice/rna-transcription/.meta/template.j2 +++ b/exercises/practice/rna-transcription/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [to_rna] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["dna"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/rna-transcription/rna-transcription-test.roc b/exercises/practice/rna-transcription/rna-transcription-test.roc index 3af4af80..a5e1b6e4 100644 --- a/exercises/practice/rna-transcription/rna-transcription-test.roc +++ b/exercises/practice/rna-transcription/rna-transcription-test.roc @@ -1,44 +1,46 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rna-transcription/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import RnaTranscription exposing [to_rna] # Empty RNA sequence -expect - result = to_rna("") - result == "" +expect { + result = to_rna("") + result == "" +} # RNA complement of cytosine is guanine -expect - result = to_rna("C") - result == "G" +expect { + result = to_rna("C") + result == "G" +} # RNA complement of guanine is cytosine -expect - result = to_rna("G") - result == "C" +expect { + result = to_rna("G") + result == "C" +} # RNA complement of thymine is adenine -expect - result = to_rna("T") - result == "A" +expect { + result = to_rna("T") + result == "A" +} # RNA complement of adenine is uracil -expect - result = to_rna("A") - result == "U" +expect { + result = to_rna("A") + result == "U" +} # RNA complement -expect - result = to_rna("ACGTGGTCTTAA") - result == "UGCACCAGAAUU" +expect { + result = to_rna("ACGTGGTCTTAA") + result == "UGCACCAGAAUU" +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/robot-simulator/.meta/template.j2 b/exercises/practice/robot-simulator/.meta/template.j2 index e71520b3..c2e32625 100644 --- a/exercises/practice/robot-simulator/.meta/template.j2 +++ b/exercises/practice/robot-simulator/.meta/template.j2 @@ -11,14 +11,15 @@ import {{ exercise | to_pascal }} exposing [create, move] {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect +expect { {%- if case["input"]["instructions"] %} robot = create({{ plugins.to_robot(case["input"], with_defaults=True) }}) - result = robot |> move({{ case["input"]["instructions"] | to_roc }}) + result = robot.move({{ case["input"]["instructions"] | to_roc }}) {%- else %} result = create({{ plugins.to_robot(case["input"], with_defaults=True) }}) {%- endif %} result == {{ plugins.to_robot(case["expected"], with_defaults=False) }} +} {% endfor %} {% endfor %} diff --git a/exercises/practice/robot-simulator/robot-simulator-test.roc b/exercises/practice/robot-simulator/robot-simulator-test.roc index bfc39ed5..5f95f555 100644 --- a/exercises/practice/robot-simulator/robot-simulator-test.roc +++ b/exercises/practice/robot-simulator/robot-simulator-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/robot-simulator/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import RobotSimulator exposing [create, move] @@ -17,124 +9,146 @@ import RobotSimulator exposing [create, move] ## # at origin facing north -expect - result = create({}) - result == { x: 0, y: 0, direction: North } +expect { + result = create({}) + result == { x: 0, y: 0, direction: North } +} # at negative position facing south -expect - result = create({ x: -1, y: -1, direction: South }) - result == { x: -1, y: -1, direction: South } +expect { + result = create({ x: -1, y: -1, direction: South }) + result == { x: -1, y: -1, direction: South } +} ## ## Rotating clockwise ## # changes north to east -expect - robot = create({}) - result = robot |> move("R") - result == { x: 0, y: 0, direction: East } +expect { + robot = create({}) + result = robot.move("R") + result == { x: 0, y: 0, direction: East } +} # changes east to south -expect - robot = create({ direction: East }) - result = robot |> move("R") - result == { x: 0, y: 0, direction: South } +expect { + robot = create({ direction: East }) + result = robot.move("R") + result == { x: 0, y: 0, direction: South } +} # changes south to west -expect - robot = create({ direction: South }) - result = robot |> move("R") - result == { x: 0, y: 0, direction: West } +expect { + robot = create({ direction: South }) + result = robot.move("R") + result == { x: 0, y: 0, direction: West } +} # changes west to north -expect - robot = create({ direction: West }) - result = robot |> move("R") - result == { x: 0, y: 0, direction: North } +expect { + robot = create({ direction: West }) + result = robot.move("R") + result == { x: 0, y: 0, direction: North } +} ## ## Rotating counter-clockwise ## # changes north to west -expect - robot = create({}) - result = robot |> move("L") - result == { x: 0, y: 0, direction: West } +expect { + robot = create({}) + result = robot.move("L") + result == { x: 0, y: 0, direction: West } +} # changes west to south -expect - robot = create({ direction: West }) - result = robot |> move("L") - result == { x: 0, y: 0, direction: South } +expect { + robot = create({ direction: West }) + result = robot.move("L") + result == { x: 0, y: 0, direction: South } +} # changes south to east -expect - robot = create({ direction: South }) - result = robot |> move("L") - result == { x: 0, y: 0, direction: East } +expect { + robot = create({ direction: South }) + result = robot.move("L") + result == { x: 0, y: 0, direction: East } +} # changes east to north -expect - robot = create({ direction: East }) - result = robot |> move("L") - result == { x: 0, y: 0, direction: North } +expect { + robot = create({ direction: East }) + result = robot.move("L") + result == { x: 0, y: 0, direction: North } +} ## ## Moving forward one ## # facing north increments Y -expect - robot = create({}) - result = robot |> move("A") - result == { x: 0, y: 1, direction: North } +expect { + robot = create({}) + result = robot.move("A") + result == { x: 0, y: 1, direction: North } +} # facing south decrements Y -expect - robot = create({ direction: South }) - result = robot |> move("A") - result == { x: 0, y: -1, direction: South } +expect { + robot = create({ direction: South }) + result = robot.move("A") + result == { x: 0, y: -1, direction: South } +} # facing east increments X -expect - robot = create({ direction: East }) - result = robot |> move("A") - result == { x: 1, y: 0, direction: East } +expect { + robot = create({ direction: East }) + result = robot.move("A") + result == { x: 1, y: 0, direction: East } +} # facing west decrements X -expect - robot = create({ direction: West }) - result = robot |> move("A") - result == { x: -1, y: 0, direction: West } +expect { + robot = create({ direction: West }) + result = robot.move("A") + result == { x: -1, y: 0, direction: West } +} ## ## Follow series of instructions ## # moving east and north from README -expect - robot = create({ x: 7, y: 3 }) - result = robot |> move("RAALAL") - result == { x: 9, y: 4, direction: West } +expect { + robot = create({ x: 7, y: 3 }) + result = robot.move("RAALAL") + result == { x: 9, y: 4, direction: West } +} # moving west and north -expect - robot = create({}) - result = robot |> move("LAAARALA") - result == { x: -4, y: 1, direction: West } +expect { + robot = create({}) + result = robot.move("LAAARALA") + result == { x: -4, y: 1, direction: West } +} # moving west and south -expect - robot = create({ x: 2, y: -7, direction: East }) - result = robot |> move("RRAAAAALA") - result == { x: -3, y: -8, direction: South } +expect { + robot = create({ x: 2, y: -7, direction: East }) + result = robot.move("RRAAAAALA") + result == { x: -3, y: -8, direction: South } +} # moving east and north -expect - robot = create({ x: 8, y: 4, direction: South }) - result = robot |> move("LAAARRRALLLL") - result == { x: 11, y: 5, direction: North } +expect { + robot = create({ x: 8, y: 4, direction: South }) + result = robot.move("LAAARRRALLLL") + result == { x: 11, y: 5, direction: North } +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/roman-numerals/.meta/template.j2 b/exercises/practice/roman-numerals/.meta/template.j2 index f02a5f26..84470893 100644 --- a/exercises/practice/roman-numerals/.meta/template.j2 +++ b/exercises/practice/roman-numerals/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["number"] | to_roc }}) result == Ok({{ case["expected"] | to_roc }}) +} {% endfor %} diff --git a/exercises/practice/roman-numerals/roman-numerals-test.roc b/exercises/practice/roman-numerals/roman-numerals-test.roc index 7df552a3..c4535740 100644 --- a/exercises/practice/roman-numerals/roman-numerals-test.roc +++ b/exercises/practice/roman-numerals/roman-numerals-test.roc @@ -1,149 +1,172 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/roman-numerals/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import RomanNumerals exposing [roman] # 1 is I -expect - result = roman(1) - result == Ok("I") +expect { + result = roman(1) + result == Ok("I") +} # 2 is II -expect - result = roman(2) - result == Ok("II") +expect { + result = roman(2) + result == Ok("II") +} # 3 is III -expect - result = roman(3) - result == Ok("III") +expect { + result = roman(3) + result == Ok("III") +} # 4 is IV -expect - result = roman(4) - result == Ok("IV") +expect { + result = roman(4) + result == Ok("IV") +} # 5 is V -expect - result = roman(5) - result == Ok("V") +expect { + result = roman(5) + result == Ok("V") +} # 6 is VI -expect - result = roman(6) - result == Ok("VI") +expect { + result = roman(6) + result == Ok("VI") +} # 9 is IX -expect - result = roman(9) - result == Ok("IX") +expect { + result = roman(9) + result == Ok("IX") +} # 16 is XVI -expect - result = roman(16) - result == Ok("XVI") +expect { + result = roman(16) + result == Ok("XVI") +} # 27 is XXVII -expect - result = roman(27) - result == Ok("XXVII") +expect { + result = roman(27) + result == Ok("XXVII") +} # 48 is XLVIII -expect - result = roman(48) - result == Ok("XLVIII") +expect { + result = roman(48) + result == Ok("XLVIII") +} # 49 is XLIX -expect - result = roman(49) - result == Ok("XLIX") +expect { + result = roman(49) + result == Ok("XLIX") +} # 59 is LIX -expect - result = roman(59) - result == Ok("LIX") +expect { + result = roman(59) + result == Ok("LIX") +} # 66 is LXVI -expect - result = roman(66) - result == Ok("LXVI") +expect { + result = roman(66) + result == Ok("LXVI") +} # 93 is XCIII -expect - result = roman(93) - result == Ok("XCIII") +expect { + result = roman(93) + result == Ok("XCIII") +} # 141 is CXLI -expect - result = roman(141) - result == Ok("CXLI") +expect { + result = roman(141) + result == Ok("CXLI") +} # 163 is CLXIII -expect - result = roman(163) - result == Ok("CLXIII") +expect { + result = roman(163) + result == Ok("CLXIII") +} # 166 is CLXVI -expect - result = roman(166) - result == Ok("CLXVI") +expect { + result = roman(166) + result == Ok("CLXVI") +} # 402 is CDII -expect - result = roman(402) - result == Ok("CDII") +expect { + result = roman(402) + result == Ok("CDII") +} # 575 is DLXXV -expect - result = roman(575) - result == Ok("DLXXV") +expect { + result = roman(575) + result == Ok("DLXXV") +} # 666 is DCLXVI -expect - result = roman(666) - result == Ok("DCLXVI") +expect { + result = roman(666) + result == Ok("DCLXVI") +} # 911 is CMXI -expect - result = roman(911) - result == Ok("CMXI") +expect { + result = roman(911) + result == Ok("CMXI") +} # 1024 is MXXIV -expect - result = roman(1024) - result == Ok("MXXIV") +expect { + result = roman(1024) + result == Ok("MXXIV") +} # 1666 is MDCLXVI -expect - result = roman(1666) - result == Ok("MDCLXVI") +expect { + result = roman(1666) + result == Ok("MDCLXVI") +} # 3000 is MMM -expect - result = roman(3000) - result == Ok("MMM") +expect { + result = roman(3000) + result == Ok("MMM") +} # 3001 is MMMI -expect - result = roman(3001) - result == Ok("MMMI") +expect { + result = roman(3001) + result == Ok("MMMI") +} # 3888 is MMMDCCCLXXXVIII -expect - result = roman(3888) - result == Ok("MMMDCCCLXXXVIII") +expect { + result = roman(3888) + result == Ok("MMMDCCCLXXXVIII") +} # 3999 is MMMCMXCIX -expect - result = roman(3999) - result == Ok("MMMCMXCIX") +expect { + result = roman(3999) + result == Ok("MMMCMXCIX") +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/rotational-cipher/.meta/template.j2 b/exercises/practice/rotational-cipher/.meta/template.j2 index adefef0e..519c882d 100644 --- a/exercises/practice/rotational-cipher/.meta/template.j2 +++ b/exercises/practice/rotational-cipher/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [rotate] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["text"] | to_roc }}, {{ case["input"]["shiftKey"] }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/rotational-cipher/rotational-cipher-test.roc b/exercises/practice/rotational-cipher/rotational-cipher-test.roc index 73dce2b0..55909d7b 100644 --- a/exercises/practice/rotational-cipher/rotational-cipher-test.roc +++ b/exercises/practice/rotational-cipher/rotational-cipher-test.roc @@ -1,64 +1,70 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rotational-cipher/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import RotationalCipher exposing [rotate] # rotate a by 0, same output as input -expect - result = rotate("a", 0) - result == "a" +expect { + result = rotate("a", 0) + result == "a" +} # rotate a by 1 -expect - result = rotate("a", 1) - result == "b" +expect { + result = rotate("a", 1) + result == "b" +} # rotate a by 26, same output as input -expect - result = rotate("a", 26) - result == "a" +expect { + result = rotate("a", 26) + result == "a" +} # rotate m by 13 -expect - result = rotate("m", 13) - result == "z" +expect { + result = rotate("m", 13) + result == "z" +} # rotate n by 13 with wrap around alphabet -expect - result = rotate("n", 13) - result == "a" +expect { + result = rotate("n", 13) + result == "a" +} # rotate capital letters -expect - result = rotate("OMG", 5) - result == "TRL" +expect { + result = rotate("OMG", 5) + result == "TRL" +} # rotate spaces -expect - result = rotate("O M G", 5) - result == "T R L" +expect { + result = rotate("O M G", 5) + result == "T R L" +} # rotate numbers -expect - result = rotate("Testing 1 2 3 testing", 4) - result == "Xiwxmrk 1 2 3 xiwxmrk" +expect { + result = rotate("Testing 1 2 3 testing", 4) + result == "Xiwxmrk 1 2 3 xiwxmrk" +} # rotate punctuation -expect - result = rotate("Let's eat, Grandma!", 21) - result == "Gzo'n zvo, Bmviyhv!" +expect { + result = rotate("Let's eat, Grandma!", 21) + result == "Gzo'n zvo, Bmviyhv!" +} # rotate all letters -expect - result = rotate("The quick brown fox jumps over the lazy dog.", 13) - result == "Gur dhvpx oebja sbk whzcf bire gur ynml qbt." +expect { + result = rotate("The quick brown fox jumps over the lazy dog.", 13) + result == "Gur dhvpx oebja sbk whzcf bire gur ynml qbt." +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/run-length-encoding/.meta/template.j2 b/exercises/practice/run-length-encoding/.meta/template.j2 index 10a2af2d..7e6bed06 100644 --- a/exercises/practice/run-length-encoding/.meta/template.j2 +++ b/exercises/practice/run-length-encoding/.meta/template.j2 @@ -11,16 +11,17 @@ import {{ exercise | to_pascal }} exposing [encode, decode] {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect +expect { string = {{ case["input"]["string"] | to_roc }} {%- if case["property"] == "consistency" %} - result = string |> encode |> Result.try(decode) + result = string -> encode() -> Result.try(decode) result == Ok(string) -{%- else %} - result = string |> {{ case["property"] | to_snake }} + {%- else %} + result = string -> {{ case["property"] | to_snake }} expected = {{ case["expected"] | to_roc }} result == Ok(expected) -{%- endif %} + {%- endif %} +} {% endfor %} {% endfor %} diff --git a/exercises/practice/run-length-encoding/run-length-encoding-test.roc b/exercises/practice/run-length-encoding/run-length-encoding-test.roc index 1b23ddbc..00308330 100644 --- a/exercises/practice/run-length-encoding/run-length-encoding-test.roc +++ b/exercises/practice/run-length-encoding/run-length-encoding-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/run-length-encoding/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import RunLengthEncoding exposing [encode, decode] @@ -17,100 +9,117 @@ import RunLengthEncoding exposing [encode, decode] ## # empty string -expect - string = "" - result = string |> encode - expected = "" - result == Ok(expected) +expect { + string = "" + result = string->encode() + expected = "" + result == Ok(expected) +} # single characters only are encoded without count -expect - string = "XYZ" - result = string |> encode - expected = "XYZ" - result == Ok(expected) +expect { + string = "XYZ" + result = string->encode() + expected = "XYZ" + result == Ok(expected) +} # string with no single characters -expect - string = "AABBBCCCC" - result = string |> encode - expected = "2A3B4C" - result == Ok(expected) +expect { + string = "AABBBCCCC" + result = string->encode() + expected = "2A3B4C" + result == Ok(expected) +} # single characters mixed with repeated characters -expect - string = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" - result = string |> encode - expected = "12WB12W3B24WB" - result == Ok(expected) +expect { + string = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" + result = string->encode() + expected = "12WB12W3B24WB" + result == Ok(expected) +} # multiple whitespace mixed in string -expect - string = " hsqq qww " - result = string |> encode - expected = "2 hs2q q2w2 " - result == Ok(expected) +expect { + string = " hsqq qww " + result = string->encode() + expected = "2 hs2q q2w2 " + result == Ok(expected) +} # lowercase characters -expect - string = "aabbbcccc" - result = string |> encode - expected = "2a3b4c" - result == Ok(expected) +expect { + string = "aabbbcccc" + result = string->encode() + expected = "2a3b4c" + result == Ok(expected) +} ## ## run-length decode a string ## # empty string -expect - string = "" - result = string |> decode - expected = "" - result == Ok(expected) +expect { + string = "" + result = string->decode() + expected = "" + result == Ok(expected) +} # single characters only -expect - string = "XYZ" - result = string |> decode - expected = "XYZ" - result == Ok(expected) +expect { + string = "XYZ" + result = string->decode() + expected = "XYZ" + result == Ok(expected) +} # string with no single characters -expect - string = "2A3B4C" - result = string |> decode - expected = "AABBBCCCC" - result == Ok(expected) +expect { + string = "2A3B4C" + result = string->decode() + expected = "AABBBCCCC" + result == Ok(expected) +} # single characters with repeated characters -expect - string = "12WB12W3B24WB" - result = string |> decode - expected = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" - result == Ok(expected) +expect { + string = "12WB12W3B24WB" + result = string->decode() + expected = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" + result == Ok(expected) +} # multiple whitespace mixed in string -expect - string = "2 hs2q q2w2 " - result = string |> decode - expected = " hsqq qww " - result == Ok(expected) +expect { + string = "2 hs2q q2w2 " + result = string->decode() + expected = " hsqq qww " + result == Ok(expected) +} # lowercase string -expect - string = "2a3b4c" - result = string |> decode - expected = "aabbbcccc" - result == Ok(expected) +expect { + string = "2a3b4c" + result = string->decode() + expected = "aabbbcccc" + result == Ok(expected) +} ## ## encode and then decode ## # encode followed by decode gives original string -expect - string = "zzz ZZ zZ" - result = string |> encode |> Result.try(decode) - result == Ok(string) +expect { + string = "zzz ZZ zZ" + result = string->encode()->Result.try(decode) + result == Ok(string) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/saddle-points/.meta/template.j2 b/exercises/practice/saddle-points/.meta/template.j2 index 1cb43abd..f9cc4d5a 100644 --- a/exercises/practice/saddle-points/.meta/template.j2 +++ b/exercises/practice/saddle-points/.meta/template.j2 @@ -6,19 +6,20 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { tree_heights = [ {%- for row in case["input"]["matrix"] %} {{ row | to_roc }}, {%- endfor %} ] - result = tree_heights |> {{ case["property"] | to_snake }} + result = tree_heights -> {{ case["property"] | to_snake }} expected = Set.from_list([ {%- for tree in case["expected"] %} {{ tree | to_roc }}, {%- endfor %} ]) result == expected +} {% endfor %} diff --git a/exercises/practice/saddle-points/saddle-points-test.roc b/exercises/practice/saddle-points/saddle-points-test.roc index a3cee084..886f7dd9 100644 --- a/exercises/practice/saddle-points/saddle-points-test.roc +++ b/exercises/practice/saddle-points/saddle-points-test.roc @@ -1,150 +1,153 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/saddle-points/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import SaddlePoints exposing [saddle_points] # Can identify single saddle point -expect - tree_heights = [ - [9, 8, 7], - [5, 3, 2], - [6, 6, 7], - ] - result = tree_heights |> saddle_points - expected = Set.from_list( - [ - { row: 2, column: 1 }, - ], - ) - result == expected +expect { + tree_heights = [ + [9, 8, 7], + [5, 3, 2], + [6, 6, 7], + ] + result = tree_heights->saddle_points() + expected = Set.from_list( + [ + { row: 2, column: 1 }, + ], + ) + result == expected +} # Can identify that empty matrix has no saddle points -expect - tree_heights = [ - [], - ] - result = tree_heights |> saddle_points - expected = Set.from_list( - [ - ], - ) - result == expected +expect { + tree_heights = [ + [], + ] + result = tree_heights->saddle_points() + expected = Set.from_list( + [], + ) + result == expected +} # Can identify lack of saddle points when there are none -expect - tree_heights = [ - [1, 2, 3], - [3, 1, 2], - [2, 3, 1], - ] - result = tree_heights |> saddle_points - expected = Set.from_list( - [ - ], - ) - result == expected +expect { + tree_heights = [ + [1, 2, 3], + [3, 1, 2], + [2, 3, 1], + ] + result = tree_heights->saddle_points() + expected = Set.from_list( + [], + ) + result == expected +} # Can identify multiple saddle points in a column -expect - tree_heights = [ - [4, 5, 4], - [3, 5, 5], - [1, 5, 4], - ] - result = tree_heights |> saddle_points - expected = Set.from_list( - [ - { row: 1, column: 2 }, - { row: 2, column: 2 }, - { row: 3, column: 2 }, - ], - ) - result == expected +expect { + tree_heights = [ + [4, 5, 4], + [3, 5, 5], + [1, 5, 4], + ] + result = tree_heights->saddle_points() + expected = Set.from_list( + [ + { row: 1, column: 2 }, + { row: 2, column: 2 }, + { row: 3, column: 2 }, + ], + ) + result == expected +} # Can identify multiple saddle points in a row -expect - tree_heights = [ - [6, 7, 8], - [5, 5, 5], - [7, 5, 6], - ] - result = tree_heights |> saddle_points - expected = Set.from_list( - [ - { row: 2, column: 1 }, - { row: 2, column: 2 }, - { row: 2, column: 3 }, - ], - ) - result == expected +expect { + tree_heights = [ + [6, 7, 8], + [5, 5, 5], + [7, 5, 6], + ] + result = tree_heights->saddle_points() + expected = Set.from_list( + [ + { row: 2, column: 1 }, + { row: 2, column: 2 }, + { row: 2, column: 3 }, + ], + ) + result == expected +} # Can identify saddle point in bottom right corner -expect - tree_heights = [ - [8, 7, 9], - [6, 7, 6], - [3, 2, 5], - ] - result = tree_heights |> saddle_points - expected = Set.from_list( - [ - { row: 3, column: 3 }, - ], - ) - result == expected +expect { + tree_heights = [ + [8, 7, 9], + [6, 7, 6], + [3, 2, 5], + ] + result = tree_heights->saddle_points() + expected = Set.from_list( + [ + { row: 3, column: 3 }, + ], + ) + result == expected +} # Can identify saddle points in a non square matrix -expect - tree_heights = [ - [3, 1, 3], - [3, 2, 4], - ] - result = tree_heights |> saddle_points - expected = Set.from_list( - [ - { row: 1, column: 3 }, - { row: 1, column: 1 }, - ], - ) - result == expected +expect { + tree_heights = [ + [3, 1, 3], + [3, 2, 4], + ] + result = tree_heights->saddle_points() + expected = Set.from_list( + [ + { row: 1, column: 3 }, + { row: 1, column: 1 }, + ], + ) + result == expected +} # Can identify that saddle points in a single column matrix are those with the minimum value -expect - tree_heights = [ - [2], - [1], - [4], - [1], - ] - result = tree_heights |> saddle_points - expected = Set.from_list( - [ - { row: 2, column: 1 }, - { row: 4, column: 1 }, - ], - ) - result == expected +expect { + tree_heights = [ + [2], + [1], + [4], + [1], + ] + result = tree_heights->saddle_points() + expected = Set.from_list( + [ + { row: 2, column: 1 }, + { row: 4, column: 1 }, + ], + ) + result == expected +} # Can identify that saddle points in a single row matrix are those with the maximum value -expect - tree_heights = [ - [2, 5, 3, 5], - ] - result = tree_heights |> saddle_points - expected = Set.from_list( - [ - { row: 1, column: 2 }, - { row: 1, column: 4 }, - ], - ) - result == expected +expect { + tree_heights = [ + [2, 5, 3, 5], + ] + result = tree_heights->saddle_points() + expected = Set.from_list( + [ + { row: 1, column: 2 }, + { row: 1, column: 4 }, + ], + ) + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/say/.meta/template.j2 b/exercises/practice/say/.meta/template.j2 index 3a825ceb..959b3aba 100644 --- a/exercises/practice/say/.meta/template.j2 +++ b/exercises/practice/say/.meta/template.j2 @@ -6,13 +6,14 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["number"] | to_roc }}) {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} result == Ok({{ case["expected"] | to_roc }}) {% endif %} +} {% endfor %} diff --git a/exercises/practice/say/say-test.roc b/exercises/practice/say/say-test.roc index 4bbaa23e..49e1303a 100644 --- a/exercises/practice/say/say-test.roc +++ b/exercises/practice/say/say-test.roc @@ -1,104 +1,135 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/say/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Say exposing [say] # zero -expect - result = say(0) - result == Ok("zero") +expect { + result = say(0) + result == Ok("zero") + +} # one -expect - result = say(1) - result == Ok("one") +expect { + result = say(1) + result == Ok("one") + +} # fourteen -expect - result = say(14) - result == Ok("fourteen") +expect { + result = say(14) + result == Ok("fourteen") + +} # twenty -expect - result = say(20) - result == Ok("twenty") +expect { + result = say(20) + result == Ok("twenty") + +} # twenty-two -expect - result = say(22) - result == Ok("twenty-two") +expect { + result = say(22) + result == Ok("twenty-two") + +} # thirty -expect - result = say(30) - result == Ok("thirty") +expect { + result = say(30) + result == Ok("thirty") + +} # ninety-nine -expect - result = say(99) - result == Ok("ninety-nine") +expect { + result = say(99) + result == Ok("ninety-nine") + +} # one hundred -expect - result = say(100) - result == Ok("one hundred") +expect { + result = say(100) + result == Ok("one hundred") + +} # one hundred twenty-three -expect - result = say(123) - result == Ok("one hundred twenty-three") +expect { + result = say(123) + result == Ok("one hundred twenty-three") + +} # two hundred -expect - result = say(200) - result == Ok("two hundred") +expect { + result = say(200) + result == Ok("two hundred") + +} # nine hundred ninety-nine -expect - result = say(999) - result == Ok("nine hundred ninety-nine") +expect { + result = say(999) + result == Ok("nine hundred ninety-nine") + +} # one thousand -expect - result = say(1000) - result == Ok("one thousand") +expect { + result = say(1000) + result == Ok("one thousand") + +} # one thousand two hundred thirty-four -expect - result = say(1234) - result == Ok("one thousand two hundred thirty-four") +expect { + result = say(1234) + result == Ok("one thousand two hundred thirty-four") + +} # one million -expect - result = say(1000000) - result == Ok("one million") +expect { + result = say(1000000) + result == Ok("one million") + +} # one million two thousand three hundred forty-five -expect - result = say(1002345) - result == Ok("one million two thousand three hundred forty-five") +expect { + result = say(1002345) + result == Ok("one million two thousand three hundred forty-five") + +} # one billion -expect - result = say(1000000000) - result == Ok("one billion") +expect { + result = say(1000000000) + result == Ok("one billion") + +} # a big number -expect - result = say(987654321123) - result == Ok("nine hundred eighty-seven billion six hundred fifty-four million three hundred twenty-one thousand one hundred twenty-three") +expect { + result = say(987654321123) + result == Ok("nine hundred eighty-seven billion six hundred fifty-four million three hundred twenty-one thousand one hundred twenty-three") + +} # numbers above 999,999,999,999 are out of range -expect - result = say(1000000000000) - result |> Result.is_err +expect { + result = say(1000000000000) + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/scrabble-score/.meta/template.j2 b/exercises/practice/scrabble-score/.meta/template.j2 index 1aeb76f2..6cb66b9a 100644 --- a/exercises/practice/scrabble-score/.meta/template.j2 +++ b/exercises/practice/scrabble-score/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["word"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/scrabble-score/scrabble-score-test.roc b/exercises/practice/scrabble-score/scrabble-score-test.roc index 5d9611ba..6354ce3e 100644 --- a/exercises/practice/scrabble-score/scrabble-score-test.roc +++ b/exercises/practice/scrabble-score/scrabble-score-test.roc @@ -1,69 +1,76 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/scrabble-score/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import ScrabbleScore exposing [score] # lowercase letter -expect - result = score("a") - result == 1 +expect { + result = score("a") + result == 1 +} # uppercase letter -expect - result = score("A") - result == 1 +expect { + result = score("A") + result == 1 +} # valuable letter -expect - result = score("f") - result == 4 +expect { + result = score("f") + result == 4 +} # short word -expect - result = score("at") - result == 2 +expect { + result = score("at") + result == 2 +} # short, valuable word -expect - result = score("zoo") - result == 12 +expect { + result = score("zoo") + result == 12 +} # medium word -expect - result = score("street") - result == 6 +expect { + result = score("street") + result == 6 +} # medium, valuable word -expect - result = score("quirky") - result == 22 +expect { + result = score("quirky") + result == 22 +} # long, mixed-case word -expect - result = score("OxyphenButazone") - result == 41 +expect { + result = score("OxyphenButazone") + result == 41 +} # english-like word -expect - result = score("pinata") - result == 8 +expect { + result = score("pinata") + result == 8 +} # empty input -expect - result = score("") - result == 0 +expect { + result = score("") + result == 0 +} # entire alphabet available -expect - result = score("abcdefghijklmnopqrstuvwxyz") - result == 87 +expect { + result = score("abcdefghijklmnopqrstuvwxyz") + result == 87 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/secret-handshake/.meta/template.j2 b/exercises/practice/secret-handshake/.meta/template.j2 index b75580df..dbae90c8 100644 --- a/exercises/practice/secret-handshake/.meta/template.j2 +++ b/exercises/practice/secret-handshake/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["number"] }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/secret-handshake/secret-handshake-test.roc b/exercises/practice/secret-handshake/secret-handshake-test.roc index 830756fe..83cf9079 100644 --- a/exercises/practice/secret-handshake/secret-handshake-test.roc +++ b/exercises/practice/secret-handshake/secret-handshake-test.roc @@ -1,69 +1,76 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/secret-handshake/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import SecretHandshake exposing [commands] # wink for 1 -expect - result = commands(1) - result == ["wink"] +expect { + result = commands(1) + result == ["wink"] +} # double blink for 10 -expect - result = commands(2) - result == ["double blink"] +expect { + result = commands(2) + result == ["double blink"] +} # close your eyes for 100 -expect - result = commands(4) - result == ["close your eyes"] +expect { + result = commands(4) + result == ["close your eyes"] +} # jump for 1000 -expect - result = commands(8) - result == ["jump"] +expect { + result = commands(8) + result == ["jump"] +} # combine two actions -expect - result = commands(3) - result == ["wink", "double blink"] +expect { + result = commands(3) + result == ["wink", "double blink"] +} # reverse two actions -expect - result = commands(19) - result == ["double blink", "wink"] +expect { + result = commands(19) + result == ["double blink", "wink"] +} # reversing one action gives the same action -expect - result = commands(24) - result == ["jump"] +expect { + result = commands(24) + result == ["jump"] +} # reversing no actions still gives no actions -expect - result = commands(16) - result == [] +expect { + result = commands(16) + result == [] +} # all possible actions -expect - result = commands(15) - result == ["wink", "double blink", "close your eyes", "jump"] +expect { + result = commands(15) + result == ["wink", "double blink", "close your eyes", "jump"] +} # reverse all possible actions -expect - result = commands(31) - result == ["jump", "close your eyes", "double blink", "wink"] +expect { + result = commands(31) + result == ["jump", "close your eyes", "double blink", "wink"] +} # do nothing for zero -expect - result = commands(0) - result == [] +expect { + result = commands(0) + result == [] +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/series/.meta/template.j2 b/exercises/practice/series/.meta/template.j2 index aeb61d0d..8260e386 100644 --- a/exercises/practice/series/.meta/template.j2 +++ b/exercises/practice/series/.meta/template.j2 @@ -7,13 +7,14 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} {%- if case["expected"]["error"] %} – just return an empty list{%- endif %} -expect - result = {{ case["input"]["series"] | to_roc }} |> {{ case["property"] | to_snake }}({{ case["input"]["sliceLength"] | to_roc }}) +expect { + result = {{ case["input"]["series"] | to_roc }} -> {{ case["property"] | to_snake }}({{ case["input"]["sliceLength"] | to_roc }}) {%- if case["expected"]["error"] %} result == [] {%- else %} result == {{ case["expected"] | to_roc }} {%- endif %} +} {% endfor %} diff --git a/exercises/practice/series/series-test.roc b/exercises/practice/series/series-test.roc index 3e510bba..fb240bab 100644 --- a/exercises/practice/series/series-test.roc +++ b/exercises/practice/series/series-test.roc @@ -1,64 +1,70 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/series/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Series exposing [slices] # slices of one from one -expect - result = "1" |> slices(1) - result == ["1"] +expect { + result = "1"->slices(1) + result == ["1"] +} # slices of one from two -expect - result = "12" |> slices(1) - result == ["1", "2"] +expect { + result = "12"->slices(1) + result == ["1", "2"] +} # slices of two -expect - result = "35" |> slices(2) - result == ["35"] +expect { + result = "35"->slices(2) + result == ["35"] +} # slices of two overlap -expect - result = "9142" |> slices(2) - result == ["91", "14", "42"] +expect { + result = "9142"->slices(2) + result == ["91", "14", "42"] +} # slices can include duplicates -expect - result = "777777" |> slices(3) - result == ["777", "777", "777", "777"] +expect { + result = "777777"->slices(3) + result == ["777", "777", "777", "777"] +} # slices of a long series -expect - result = "918493904243" |> slices(5) - result == ["91849", "18493", "84939", "49390", "93904", "39042", "90424", "04243"] +expect { + result = "918493904243"->slices(5) + result == ["91849", "18493", "84939", "49390", "93904", "39042", "90424", "04243"] +} # slice length is too large – just return an empty list -expect - result = "12345" |> slices(6) - result == [] +expect { + result = "12345"->slices(6) + result == [] +} # slice length is way too large – just return an empty list -expect - result = "12345" |> slices(42) - result == [] +expect { + result = "12345"->slices(42) + result == [] +} # slice length cannot be zero – just return an empty list -expect - result = "12345" |> slices(0) - result == [] +expect { + result = "12345"->slices(0) + result == [] +} # empty series is invalid – just return an empty list -expect - result = "" |> slices(1) - result == [] +expect { + result = ""->slices(1) + result == [] +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/sgf-parsing/.meta/template.j2 b/exercises/practice/sgf-parsing/.meta/template.j2 index c45199d3..1807c550 100644 --- a/exercises/practice/sgf-parsing/.meta/template.j2 +++ b/exercises/practice/sgf-parsing/.meta/template.j2 @@ -20,16 +20,16 @@ GameNode({ {% for case in cases -%} # {{ case["description"] }} -expect +expect { sgf = {{ case["input"]["encoded"] | to_roc }} result = {{ case["property"] | to_snake }}(sgf) {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} expected = {{ to_node(case["expected"]) | indent(4) }} result == Ok(expected) {%- endif %} - +} {% endfor %} diff --git a/exercises/practice/sgf-parsing/sgf-parsing-test.roc b/exercises/practice/sgf-parsing/sgf-parsing-test.roc index 8d141d8b..00edea7c 100644 --- a/exercises/practice/sgf-parsing/sgf-parsing-test.roc +++ b/exercises/practice/sgf-parsing/sgf-parsing-test.roc @@ -1,405 +1,435 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sgf-parsing/canonical-data.json -# File last updated on 2025-09-15 +# File last updated on 2026-06-13 app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", - parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.10.0/6eZYaXkrakq9fJ4oUc0VfdxU1Fap2iTuAN18q9OgQss.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", + parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.10.0/6eZYaXkrakq9fJ4oUc0VfdxU1Fap2iTuAN18q9OgQss.tar.br", } import pf.Stdout -main! = |_args| - Stdout.line!("") - import SgfParsing exposing [parse] # empty input -expect - sgf = "" - result = parse(sgf) - result |> Result.is_err +expect { + sgf = "" + result = parse(sgf) + result.is_err() +} # tree with no nodes -expect - sgf = "()" - result = parse(sgf) - result |> Result.is_err +expect { + sgf = "()" + result = parse(sgf) + result.is_err() +} # node without tree -expect - sgf = ";" - result = parse(sgf) - result |> Result.is_err +expect { + sgf = ";" + result = parse(sgf) + result.is_err() +} # node without properties -expect - sgf = "(;)" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list([]), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;)" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list([]), + children: [], + }, + ) + result == Ok(expected) +} # single node tree -expect - sgf = "(;A[B])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["B"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[B])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["B"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} # multiple properties -expect - sgf = "(;A[b]C[d])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["b"]), - ("C", ["d"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[b]C[d])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["b"]), + ("C", ["d"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} # properties without delimiter -expect - sgf = "(;A)" - result = parse(sgf) - result |> Result.is_err +expect { + sgf = "(;A)" + result = parse(sgf) + result.is_err() +} # all lowercase property -expect - sgf = "(;a[b])" - result = parse(sgf) - result |> Result.is_err +expect { + sgf = "(;a[b])" + result = parse(sgf) + result.is_err() +} # upper and lowercase property -expect - sgf = "(;Aa[b])" - result = parse(sgf) - result |> Result.is_err +expect { + sgf = "(;Aa[b])" + result = parse(sgf) + result.is_err() +} # two nodes -expect - sgf = "(;A[B];B[C])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["B"]), - ], - ), - children: [ - GameNode( - { - properties: Dict.from_list( - [ - ("B", ["C"]), - ], - ), - children: [], - }, - ), - ], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[B];B[C])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["B"]), + ], + ), + children: [ + + GameNode( + { + properties: Dict.from_list( + [ + ("B", ["C"]), + ], + ), + children: [], + }, + ), + ], + }, + ) + result == Ok(expected) +} # two child trees -expect - sgf = "(;A[B](;B[C])(;C[D]))" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["B"]), - ], - ), - children: [ - GameNode( - { - properties: Dict.from_list( - [ - ("B", ["C"]), - ], - ), - children: [], - }, - ), - GameNode( - { - properties: Dict.from_list( - [ - ("C", ["D"]), - ], - ), - children: [], - }, - ), - ], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[B](;B[C])(;C[D]))" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["B"]), + ], + ), + children: [ + + GameNode( + { + properties: Dict.from_list( + [ + ("B", ["C"]), + ], + ), + children: [], + }, + ), + + GameNode( + { + properties: Dict.from_list( + [ + ("C", ["D"]), + ], + ), + children: [], + }, + ), + ], + }, + ) + result == Ok(expected) +} # multiple property values -expect - sgf = "(;A[b][c][d])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["b", "c", "d"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[b][c][d])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["b", "c", "d"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} # within property values, whitespace characters such as tab are converted to spaces -expect - sgf = "(;A[hello\t\tworld])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["hello world"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[hello\t\tworld])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["hello world"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} # within property values, newlines remain as newlines -expect - sgf = "(;A[hello\n\nworld])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["hello\n\nworld"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[hello\n\nworld])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["hello\n\nworld"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} # escaped closing bracket within property value becomes just a closing bracket -expect - sgf = "(;A[\\]])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["]"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[\\]])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["]"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} # escaped backslash in property value becomes just a backslash -expect - sgf = "(;A[\\\\])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["\\"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[\\\\])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["\\"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} # opening bracket within property value doesn't need to be escaped -expect - sgf = "(;A[x[y\\]z][foo]B[bar];C[baz])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["x[y]z", "foo"]), - ("B", ["bar"]), - ], - ), - children: [ - GameNode( - { - properties: Dict.from_list( - [ - ("C", ["baz"]), - ], - ), - children: [], - }, - ), - ], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[x[y\\]z][foo]B[bar];C[baz])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["x[y]z", "foo"]), + ("B", ["bar"]), + ], + ), + children: [ + + GameNode( + { + properties: Dict.from_list( + [ + ("C", ["baz"]), + ], + ), + children: [], + }, + ), + ], + }, + ) + result == Ok(expected) +} # semicolon in property value doesn't need to be escaped -expect - sgf = "(;A[a;b][foo]B[bar];C[baz])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["a;b", "foo"]), - ("B", ["bar"]), - ], - ), - children: [ - GameNode( - { - properties: Dict.from_list( - [ - ("C", ["baz"]), - ], - ), - children: [], - }, - ), - ], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[a;b][foo]B[bar];C[baz])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["a;b", "foo"]), + ("B", ["bar"]), + ], + ), + children: [ + + GameNode( + { + properties: Dict.from_list( + [ + ("C", ["baz"]), + ], + ), + children: [], + }, + ), + ], + }, + ) + result == Ok(expected) +} # parentheses in property value don't need to be escaped -expect - sgf = "(;A[x(y)z][foo]B[bar];C[baz])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["x(y)z", "foo"]), - ("B", ["bar"]), - ], - ), - children: [ - GameNode( - { - properties: Dict.from_list( - [ - ("C", ["baz"]), - ], - ), - children: [], - }, - ), - ], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[x(y)z][foo]B[bar];C[baz])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["x(y)z", "foo"]), + ("B", ["bar"]), + ], + ), + children: [ + + GameNode( + { + properties: Dict.from_list( + [ + ("C", ["baz"]), + ], + ), + children: [], + }, + ), + ], + }, + ) + result == Ok(expected) +} # escaped tab in property value is converted to space -expect - sgf = "(;A[hello\\\tworld])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["hello world"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[hello\\\tworld])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["hello world"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} # escaped newline in property value is converted to nothing at all -expect - sgf = "(;A[hello\\\nworld])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["helloworld"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[hello\\\nworld])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["helloworld"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} # escaped t and n in property value are just letters, not whitespace -expect - sgf = "(;A[\\t = t and \\n = n])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["t = t and n = n"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[\\t = t and \\n = n])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["t = t and n = n"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} # mixing various kinds of whitespace and escaped characters in property value -expect - sgf = "(;A[\\]b\nc\\\nd\t\te\\\\ \\\n\\]])" - result = parse(sgf) - expected = - GameNode( - { - properties: Dict.from_list( - [ - ("A", ["]b\ncd e\\ ]"]), - ], - ), - children: [], - }, - ) - result == Ok(expected) +expect { + sgf = "(;A[\\]b\nc\\\nd\t\te\\\\ \\\n\\]])" + result = parse(sgf) + expected = + GameNode( + { + properties: Dict.from_list( + [ + ("A", ["]b\ncd e\\ ]"]), + ], + ), + children: [], + }, + ) + result == Ok(expected) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/sieve/.meta/template.j2 b/exercises/practice/sieve/.meta/template.j2 index ba6db06d..8189c1a0 100644 --- a/exercises/practice/sieve/.meta/template.j2 +++ b/exercises/practice/sieve/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["limit"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/sieve/sieve-test.roc b/exercises/practice/sieve/sieve-test.roc index c1746d79..edeab996 100644 --- a/exercises/practice/sieve/sieve-test.roc +++ b/exercises/practice/sieve/sieve-test.roc @@ -1,39 +1,40 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sieve/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Sieve exposing [primes] # no primes under two -expect - result = primes(1) - result == [] +expect { + result = primes(1) + result == [] +} # find first prime -expect - result = primes(2) - result == [2] +expect { + result = primes(2) + result == [2] +} # find primes up to 10 -expect - result = primes(10) - result == [2, 3, 5, 7] +expect { + result = primes(10) + result == [2, 3, 5, 7] +} # limit is prime -expect - result = primes(13) - result == [2, 3, 5, 7, 11, 13] +expect { + result = primes(13) + result == [2, 3, 5, 7, 11, 13] +} # find primes up to 1000 -expect - result = primes(1000) - result == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997] +expect { + result = primes(1000) + result == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997] +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/simple-linked-list/simple-linked-list-test.roc b/exercises/practice/simple-linked-list/simple-linked-list-test.roc index 4eac0970..9d8c4a29 100644 --- a/exercises/practice/simple-linked-list/simple-linked-list-test.roc +++ b/exercises/practice/simple-linked-list/simple-linked-list-test.roc @@ -1,78 +1,89 @@ # File last updated on 2025-1-4 app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", } import pf.Stdout main! = |_args| - Stdout.line!("") + Stdout.line!("") import SimpleLinkedList exposing [from_list, to_list, push, pop, reverse, len] # can create an empty linked list -expect - result = [] |> from_list |> to_list - expected = [] - result == expected +expect { + result = []->from_list()->to_list() + expected = [] + result == expected +} # can create a linked list with a single element -expect - result = [123] |> from_list |> to_list - expected = [123] - result == expected +expect { + result = [123]->from_list()->to_list() + expected = [123] + result == expected +} # can create a linked list with multiple elements -expect - result = [123, 456, 789] |> from_list |> to_list - expected = [123, 456, 789] - result == expected +expect { + result = [123, 456, 789]->from_list()->to_list() + expected = [123, 456, 789] + result == expected +} # can push items to a linked list -expect - result = [123] |> from_list |> push(456) |> push(789) |> to_list - expected = [123, 456, 789] - result == expected +expect { + result = [123]->from_list()->push(456)->push(789)->to_list() + expected = [123, 456, 789] + result == expected +} # can pop an item from a linked list -expect - pop_result = [123, 456, 789] |> from_list |> pop - result = pop_result |> Result.try(|popped| Ok(popped.value)) - expected = Ok(789) - result == expected +expect { + pop_result = [123, 456, 789]->from_list()->pop() + result = pop_result->Result.try(|popped| Ok(popped.value)) + expected = Ok(789) + result == expected +} # the last element should be gone after pop -expect - pop_result = [123, 456, 789] |> from_list |> pop - result = pop_result |> Result.try(|popped| Ok((popped.linked_list |> to_list))) - expected = Ok([123, 456]) - result == expected +expect { + pop_result = [123, 456, 789]->from_list()->pop() + result = pop_result->Result.try(|popped| Ok((popped.linked_list->to_list()))) + expected = Ok([123, 456]) + result == expected +} # cannot pop an empty linked list -expect - result = [] |> from_list |> pop - result |> Result.is_err +expect { + result = []->from_list()->pop() + result.is_err() +} # can reverse a linked list -expect - result = [123, 456, 789] |> from_list |> reverse |> to_list - expected = [789, 456, 123] - result == expected +expect { + result = [123, 456, 789]->from_list()->reverse()->to_list() + expected = [789, 456, 123] + result == expected +} # can reverse an empty linked list and it's still empty -expect - result = [] |> from_list |> reverse |> to_list - expected = [] - result == expected +expect { + result = []->from_list()->reverse()->to_list() + expected = [] + result == expected +} # can get the length of a linked list -expect - result = [123, 456, 789] |> from_list |> len - expected = 3 - result == expected +expect { + result = [123, 456, 789]->from_list()->len() + expected = 3 + result == expected +} # can get the length of an empty linked list -expect - result = [] |> from_list |> len - expected = 0 - result == expected +expect { + result = []->from_list()->len() + expected = 0 + result == expected +} diff --git a/exercises/practice/space-age/.meta/template.j2 b/exercises/practice/space-age/.meta/template.j2 index 1a810b91..f9b8704c 100644 --- a/exercises/practice/space-age/.meta/template.j2 +++ b/exercises/practice/space-age/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["planet"] | to_pascal }}, {{ case["input"]["seconds"] }}) Num.is_approx_eq(result, {{ case["expected"] }}, { atol: 0.01 }) +} {% endfor %} diff --git a/exercises/practice/space-age/space-age-test.roc b/exercises/practice/space-age/space-age-test.roc index 09b01663..45416b57 100644 --- a/exercises/practice/space-age/space-age-test.roc +++ b/exercises/practice/space-age/space-age-test.roc @@ -1,54 +1,58 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/space-age/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import SpaceAge exposing [age] # age on Earth -expect - result = age(Earth, 1000000000) - Num.is_approx_eq(result, 31.69, { atol: 0.01 }) +expect { + result = age(Earth, 1000000000) + Num.is_approx_eq(result, 31.69, { atol: 0.01 }) +} # age on Mercury -expect - result = age(Mercury, 2134835688) - Num.is_approx_eq(result, 280.88, { atol: 0.01 }) +expect { + result = age(Mercury, 2134835688) + Num.is_approx_eq(result, 280.88, { atol: 0.01 }) +} # age on Venus -expect - result = age(Venus, 189839836) - Num.is_approx_eq(result, 9.78, { atol: 0.01 }) +expect { + result = age(Venus, 189839836) + Num.is_approx_eq(result, 9.78, { atol: 0.01 }) +} # age on Mars -expect - result = age(Mars, 2129871239) - Num.is_approx_eq(result, 35.88, { atol: 0.01 }) +expect { + result = age(Mars, 2129871239) + Num.is_approx_eq(result, 35.88, { atol: 0.01 }) +} # age on Jupiter -expect - result = age(Jupiter, 901876382) - Num.is_approx_eq(result, 2.41, { atol: 0.01 }) +expect { + result = age(Jupiter, 901876382) + Num.is_approx_eq(result, 2.41, { atol: 0.01 }) +} # age on Saturn -expect - result = age(Saturn, 2000000000) - Num.is_approx_eq(result, 2.15, { atol: 0.01 }) +expect { + result = age(Saturn, 2000000000) + Num.is_approx_eq(result, 2.15, { atol: 0.01 }) +} # age on Uranus -expect - result = age(Uranus, 1210123456) - Num.is_approx_eq(result, 0.46, { atol: 0.01 }) +expect { + result = age(Uranus, 1210123456) + Num.is_approx_eq(result, 0.46, { atol: 0.01 }) +} # age on Neptune -expect - result = age(Neptune, 1821023456) - Num.is_approx_eq(result, 0.35, { atol: 0.01 }) +expect { + result = age(Neptune, 1821023456) + Num.is_approx_eq(result, 0.35, { atol: 0.01 }) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/spiral-matrix/.meta/template.j2 b/exercises/practice/spiral-matrix/.meta/template.j2 index be3fe425..0a917df8 100644 --- a/exercises/practice/spiral-matrix/.meta/template.j2 +++ b/exercises/practice/spiral-matrix/.meta/template.j2 @@ -6,7 +6,7 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["size"] }}) {%- if case["expected"] == [] %} result == [] @@ -18,6 +18,7 @@ expect ] result == expected {%- endif %} +} {% endfor %} diff --git a/exercises/practice/spiral-matrix/spiral-matrix-test.roc b/exercises/practice/spiral-matrix/spiral-matrix-test.roc index 319e124d..9b750aca 100644 --- a/exercises/practice/spiral-matrix/spiral-matrix-test.roc +++ b/exercises/practice/spiral-matrix/spiral-matrix-test.roc @@ -1,69 +1,71 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/spiral-matrix/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import SpiralMatrix exposing [spiral_matrix] # empty spiral -expect - result = spiral_matrix(0) - result == [] +expect { + result = spiral_matrix(0) + result == [] +} # trivial spiral -expect - result = spiral_matrix(1) - expected = [ - [1], - ] - result == expected +expect { + result = spiral_matrix(1) + expected = [ + [1], + ] + result == expected +} # spiral of size 2 -expect - result = spiral_matrix(2) - expected = [ - [1, 2], - [4, 3], - ] - result == expected +expect { + result = spiral_matrix(2) + expected = [ + [1, 2], + [4, 3], + ] + result == expected +} # spiral of size 3 -expect - result = spiral_matrix(3) - expected = [ - [1, 2, 3], - [8, 9, 4], - [7, 6, 5], - ] - result == expected +expect { + result = spiral_matrix(3) + expected = [ + [1, 2, 3], + [8, 9, 4], + [7, 6, 5], + ] + result == expected +} # spiral of size 4 -expect - result = spiral_matrix(4) - expected = [ - [1, 2, 3, 4], - [12, 13, 14, 5], - [11, 16, 15, 6], - [10, 9, 8, 7], - ] - result == expected +expect { + result = spiral_matrix(4) + expected = [ + [1, 2, 3, 4], + [12, 13, 14, 5], + [11, 16, 15, 6], + [10, 9, 8, 7], + ] + result == expected +} # spiral of size 5 -expect - result = spiral_matrix(5) - expected = [ - [1, 2, 3, 4, 5], - [16, 17, 18, 19, 6], - [15, 24, 25, 20, 7], - [14, 23, 22, 21, 8], - [13, 12, 11, 10, 9], - ] - result == expected +expect { + result = spiral_matrix(5) + expected = [ + [1, 2, 3, 4, 5], + [16, 17, 18, 19, 6], + [15, 24, 25, 20, 7], + [14, 23, 22, 21, 8], + [13, 12, 11, 10, 9], + ] + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/square-root/.meta/template.j2 b/exercises/practice/square-root/.meta/template.j2 index 9a336343..fa85fcf3 100644 --- a/exercises/practice/square-root/.meta/template.j2 +++ b/exercises/practice/square-root/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["radicand"] | to_roc }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/square-root/square-root-test.roc b/exercises/practice/square-root/square-root-test.roc index 8f57f30e..c39903d0 100644 --- a/exercises/practice/square-root/square-root-test.roc +++ b/exercises/practice/square-root/square-root-test.roc @@ -1,44 +1,46 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/square-root/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import SquareRoot exposing [square_root] # root of 1 -expect - result = square_root(1) - result == 1 +expect { + result = square_root(1) + result == 1 +} # root of 4 -expect - result = square_root(4) - result == 2 +expect { + result = square_root(4) + result == 2 +} # root of 25 -expect - result = square_root(25) - result == 5 +expect { + result = square_root(25) + result == 5 +} # root of 81 -expect - result = square_root(81) - result == 9 +expect { + result = square_root(81) + result == 9 +} # root of 196 -expect - result = square_root(196) - result == 14 +expect { + result = square_root(196) + result == 14 +} # root of 65025 -expect - result = square_root(65025) - result == 255 +expect { + result = square_root(65025) + result == 255 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/strain/.meta/template.j2 b/exercises/practice/strain/.meta/template.j2 index 9a798221..515b1223 100644 --- a/exercises/practice/strain/.meta/template.j2 +++ b/exercises/practice/strain/.meta/template.j2 @@ -5,22 +5,23 @@ import {{ exercise | to_pascal }} exposing [keep, discard] {% set function_map = { - "fn(x) -> contains(x, 5)": "|x| x |> List.contains(5)", - "fn(x) -> false": "\\_ -> Bool.false", - "fn(x) -> starts_with(x, 'z')": "|x| x |> Str.starts_with(\"z\")", - "fn(x) -> true": "\\_ -> Bool.true", - "fn(x) -> x % 2 == 0": "\\x -> x % 2 == 0", - "fn(x) -> x % 2 == 1": "\\x -> x % 2 == 1", + "fn(x) -> contains(x, 5)": "|x| x.contains(5)", + "fn(x) -> false": "|_| Bool.False", + "fn(x) -> starts_with(x, 'z')": "|x| x.starts_with(\"z\")", + "fn(x) -> true": "|_| Bool.True", + "fn(x) -> x % 2 == 0": "|x| x % 2 == 0", + "fn(x) -> x % 2 == 1": "|x| x % 2 == 1", } %} {% for case in cases -%} # {{ case["description"] }} -expect +expect { list = {{ case["input"]["list"] | to_roc }} - result = list |> {{ case["property"] | to_snake }}({{ function_map[case["input"]["predicate"]] }}) + result = list -> {{ case["property"] | to_snake }}({{ function_map[case["input"]["predicate"]] }}) expected = {{ case["expected"] | to_roc }} result == expected +} {% endfor %} diff --git a/exercises/practice/strain/strain-test.roc b/exercises/practice/strain/strain-test.roc index ed49e927..fdb79d35 100644 --- a/exercises/practice/strain/strain-test.roc +++ b/exercises/practice/strain/strain-test.roc @@ -1,112 +1,122 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/strain/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Strain exposing [keep, discard] # keep on empty list returns empty list -expect - list = [] - result = list |> keep(|_| Bool.true) - expected = [] - result == expected +expect { + list = [] + result = list->keep(|_| Bool.True) + expected = [] + result == expected +} # keeps everything -expect - list = [1, 3, 5] - result = list |> keep(|_| Bool.true) - expected = [1, 3, 5] - result == expected +expect { + list = [1, 3, 5] + result = list->keep(|_| Bool.True) + expected = [1, 3, 5] + result == expected +} # keeps nothing -expect - list = [1, 3, 5] - result = list |> keep(|_| Bool.false) - expected = [] - result == expected +expect { + list = [1, 3, 5] + result = list->keep(|_| Bool.False) + expected = [] + result == expected +} # keeps first and last -expect - list = [1, 2, 3] - result = list |> keep(|x| x % 2 == 1) - expected = [1, 3] - result == expected +expect { + list = [1, 2, 3] + result = list->keep(|x| x % 2 == 1) + expected = [1, 3] + result == expected +} # keeps neither first nor last -expect - list = [1, 2, 3] - result = list |> keep(|x| x % 2 == 0) - expected = [2] - result == expected +expect { + list = [1, 2, 3] + result = list->keep(|x| x % 2 == 0) + expected = [2] + result == expected +} # keeps strings -expect - list = ["apple", "zebra", "banana", "zombies", "cherimoya", "zealot"] - result = list |> keep(|x| x |> Str.starts_with("z")) - expected = ["zebra", "zombies", "zealot"] - result == expected +expect { + list = ["apple", "zebra", "banana", "zombies", "cherimoya", "zealot"] + result = list->keep(|x| x.starts_with("z")) + expected = ["zebra", "zombies", "zealot"] + result == expected +} # keeps lists -expect - list = [[1, 2, 3], [5, 5, 5], [5, 1, 2], [2, 1, 2], [1, 5, 2], [2, 2, 1], [1, 2, 5]] - result = list |> keep(|x| x |> List.contains(5)) - expected = [[5, 5, 5], [5, 1, 2], [1, 5, 2], [1, 2, 5]] - result == expected +expect { + list = [[1, 2, 3], [5, 5, 5], [5, 1, 2], [2, 1, 2], [1, 5, 2], [2, 2, 1], [1, 2, 5]] + result = list->keep(|x| x.contains(5)) + expected = [[5, 5, 5], [5, 1, 2], [1, 5, 2], [1, 2, 5]] + result == expected +} # discard on empty list returns empty list -expect - list = [] - result = list |> discard(|_| Bool.true) - expected = [] - result == expected +expect { + list = [] + result = list->discard(|_| Bool.True) + expected = [] + result == expected +} # discards everything -expect - list = [1, 3, 5] - result = list |> discard(|_| Bool.true) - expected = [] - result == expected +expect { + list = [1, 3, 5] + result = list->discard(|_| Bool.True) + expected = [] + result == expected +} # discards nothing -expect - list = [1, 3, 5] - result = list |> discard(|_| Bool.false) - expected = [1, 3, 5] - result == expected +expect { + list = [1, 3, 5] + result = list->discard(|_| Bool.False) + expected = [1, 3, 5] + result == expected +} # discards first and last -expect - list = [1, 2, 3] - result = list |> discard(|x| x % 2 == 1) - expected = [2] - result == expected +expect { + list = [1, 2, 3] + result = list->discard(|x| x % 2 == 1) + expected = [2] + result == expected +} # discards neither first nor last -expect - list = [1, 2, 3] - result = list |> discard(|x| x % 2 == 0) - expected = [1, 3] - result == expected +expect { + list = [1, 2, 3] + result = list->discard(|x| x % 2 == 0) + expected = [1, 3] + result == expected +} # discards strings -expect - list = ["apple", "zebra", "banana", "zombies", "cherimoya", "zealot"] - result = list |> discard(|x| x |> Str.starts_with("z")) - expected = ["apple", "banana", "cherimoya"] - result == expected +expect { + list = ["apple", "zebra", "banana", "zombies", "cherimoya", "zealot"] + result = list->discard(|x| x.starts_with("z")) + expected = ["apple", "banana", "cherimoya"] + result == expected +} # discards lists -expect - list = [[1, 2, 3], [5, 5, 5], [5, 1, 2], [2, 1, 2], [1, 5, 2], [2, 2, 1], [1, 2, 5]] - result = list |> discard(|x| x |> List.contains(5)) - expected = [[1, 2, 3], [2, 1, 2], [2, 2, 1]] - result == expected +expect { + list = [[1, 2, 3], [5, 5, 5], [5, 1, 2], [2, 1, 2], [1, 5, 2], [2, 2, 1], [1, 2, 5]] + result = list->discard(|x| x.contains(5)) + expected = [[1, 2, 3], [2, 1, 2], [2, 2, 1]] + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/sublist/.meta/template.j2 b/exercises/practice/sublist/.meta/template.j2 index 15eb919a..59a2327a 100644 --- a/exercises/practice/sublist/.meta/template.j2 +++ b/exercises/practice/sublist/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect - result = {{ case["input"]["listOne"] | to_roc }} |> {{ case["property"] | to_snake }}({{ case["input"]["listTwo"] | to_roc }}) +expect { + result = {{ case["input"]["listOne"] | to_roc }} -> {{ case["property"] | to_snake }}({{ case["input"]["listTwo"] | to_roc }}) result == {{ case["expected"] | to_pascal }} +} {% endfor %} diff --git a/exercises/practice/sublist/sublist-test.roc b/exercises/practice/sublist/sublist-test.roc index 36ba4198..bc393ec6 100644 --- a/exercises/practice/sublist/sublist-test.roc +++ b/exercises/practice/sublist/sublist-test.roc @@ -1,104 +1,118 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sublist/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Sublist exposing [sublist] # empty lists -expect - result = [] |> sublist([]) - result == Equal +expect { + result = []->sublist([]) + result == Equal +} # empty list within non empty list -expect - result = [] |> sublist([1, 2, 3]) - result == Sublist +expect { + result = []->sublist([1, 2, 3]) + result == Sublist +} # non empty list contains empty list -expect - result = [1, 2, 3] |> sublist([]) - result == Superlist +expect { + result = [1, 2, 3]->sublist([]) + result == Superlist +} # list equals itself -expect - result = [1, 2, 3] |> sublist([1, 2, 3]) - result == Equal +expect { + result = [1, 2, 3]->sublist([1, 2, 3]) + result == Equal +} # different lists -expect - result = [1, 2, 3] |> sublist([2, 3, 4]) - result == Unequal +expect { + result = [1, 2, 3]->sublist([2, 3, 4]) + result == Unequal +} # false start -expect - result = [1, 2, 5] |> sublist([0, 1, 2, 3, 1, 2, 5, 6]) - result == Sublist +expect { + result = [1, 2, 5]->sublist([0, 1, 2, 3, 1, 2, 5, 6]) + result == Sublist +} # consecutive -expect - result = [1, 1, 2] |> sublist([0, 1, 1, 1, 2, 1, 2]) - result == Sublist +expect { + result = [1, 1, 2]->sublist([0, 1, 1, 1, 2, 1, 2]) + result == Sublist +} # sublist at start -expect - result = [0, 1, 2] |> sublist([0, 1, 2, 3, 4, 5]) - result == Sublist +expect { + result = [0, 1, 2]->sublist([0, 1, 2, 3, 4, 5]) + result == Sublist +} # sublist in middle -expect - result = [2, 3, 4] |> sublist([0, 1, 2, 3, 4, 5]) - result == Sublist +expect { + result = [2, 3, 4]->sublist([0, 1, 2, 3, 4, 5]) + result == Sublist +} # sublist at end -expect - result = [3, 4, 5] |> sublist([0, 1, 2, 3, 4, 5]) - result == Sublist +expect { + result = [3, 4, 5]->sublist([0, 1, 2, 3, 4, 5]) + result == Sublist +} # at start of superlist -expect - result = [0, 1, 2, 3, 4, 5] |> sublist([0, 1, 2]) - result == Superlist +expect { + result = [0, 1, 2, 3, 4, 5]->sublist([0, 1, 2]) + result == Superlist +} # in middle of superlist -expect - result = [0, 1, 2, 3, 4, 5] |> sublist([2, 3]) - result == Superlist +expect { + result = [0, 1, 2, 3, 4, 5]->sublist([2, 3]) + result == Superlist +} # at end of superlist -expect - result = [0, 1, 2, 3, 4, 5] |> sublist([3, 4, 5]) - result == Superlist +expect { + result = [0, 1, 2, 3, 4, 5]->sublist([3, 4, 5]) + result == Superlist +} # first list missing element from second list -expect - result = [1, 3] |> sublist([1, 2, 3]) - result == Unequal +expect { + result = [1, 3]->sublist([1, 2, 3]) + result == Unequal +} # second list missing element from first list -expect - result = [1, 2, 3] |> sublist([1, 3]) - result == Unequal +expect { + result = [1, 2, 3]->sublist([1, 3]) + result == Unequal +} # first list missing additional digits from second list -expect - result = [1, 2] |> sublist([1, 22]) - result == Unequal +expect { + result = [1, 2]->sublist([1, 22]) + result == Unequal +} # order matters to a list -expect - result = [1, 2, 3] |> sublist([3, 2, 1]) - result == Unequal +expect { + result = [1, 2, 3]->sublist([3, 2, 1]) + result == Unequal +} # same digits but different numbers -expect - result = [1, 0, 1] |> sublist([10, 1]) - result == Unequal +expect { + result = [1, 0, 1]->sublist([10, 1]) + result == Unequal +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/sum-of-multiples/.meta/template.j2 b/exercises/practice/sum-of-multiples/.meta/template.j2 index 299d1ad9..ddf38306 100644 --- a/exercises/practice/sum-of-multiples/.meta/template.j2 +++ b/exercises/practice/sum-of-multiples/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ exercise | to_snake }}] {% for case in cases -%} # {{ case["description"] }} -expect - result = {{ case["input"]["factors"] | to_roc }} |> {{ exercise | to_snake }}({{ case["input"]["limit"] }}) +expect { + result = {{ case["input"]["factors"] | to_roc }} -> {{ exercise | to_snake }}({{ case["input"]["limit"] }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/sum-of-multiples/sum-of-multiples-test.roc b/exercises/practice/sum-of-multiples/sum-of-multiples-test.roc index 30bb1bba..d07104c6 100644 --- a/exercises/practice/sum-of-multiples/sum-of-multiples-test.roc +++ b/exercises/practice/sum-of-multiples/sum-of-multiples-test.roc @@ -1,94 +1,106 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sum-of-multiples/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import SumOfMultiples exposing [sum_of_multiples] # no multiples within limit -expect - result = [3, 5] |> sum_of_multiples(1) - result == 0 +expect { + result = [3, 5]->sum_of_multiples(1) + result == 0 +} # one factor has multiples within limit -expect - result = [3, 5] |> sum_of_multiples(4) - result == 3 +expect { + result = [3, 5]->sum_of_multiples(4) + result == 3 +} # more than one multiple within limit -expect - result = [3] |> sum_of_multiples(7) - result == 9 +expect { + result = [3]->sum_of_multiples(7) + result == 9 +} # more than one factor with multiples within limit -expect - result = [3, 5] |> sum_of_multiples(10) - result == 23 +expect { + result = [3, 5]->sum_of_multiples(10) + result == 23 +} # each multiple is only counted once -expect - result = [3, 5] |> sum_of_multiples(100) - result == 2318 +expect { + result = [3, 5]->sum_of_multiples(100) + result == 2318 +} # a much larger limit -expect - result = [3, 5] |> sum_of_multiples(1000) - result == 233168 +expect { + result = [3, 5]->sum_of_multiples(1000) + result == 233168 +} # three factors -expect - result = [7, 13, 17] |> sum_of_multiples(20) - result == 51 +expect { + result = [7, 13, 17]->sum_of_multiples(20) + result == 51 +} # factors not relatively prime -expect - result = [4, 6] |> sum_of_multiples(15) - result == 30 +expect { + result = [4, 6]->sum_of_multiples(15) + result == 30 +} # some pairs of factors relatively prime and some not -expect - result = [5, 6, 8] |> sum_of_multiples(150) - result == 4419 +expect { + result = [5, 6, 8]->sum_of_multiples(150) + result == 4419 +} # one factor is a multiple of another -expect - result = [5, 25] |> sum_of_multiples(51) - result == 275 +expect { + result = [5, 25]->sum_of_multiples(51) + result == 275 +} # much larger factors -expect - result = [43, 47] |> sum_of_multiples(10000) - result == 2203160 +expect { + result = [43, 47]->sum_of_multiples(10000) + result == 2203160 +} # all numbers are multiples of 1 -expect - result = [1] |> sum_of_multiples(100) - result == 4950 +expect { + result = [1]->sum_of_multiples(100) + result == 4950 +} # no factors means an empty sum -expect - result = [] |> sum_of_multiples(10000) - result == 0 +expect { + result = []->sum_of_multiples(10000) + result == 0 +} # the only multiple of 0 is 0 -expect - result = [0] |> sum_of_multiples(1) - result == 0 +expect { + result = [0]->sum_of_multiples(1) + result == 0 +} # the factor 0 does not affect the sum of multiples of other factors -expect - result = [3, 0] |> sum_of_multiples(4) - result == 3 +expect { + result = [3, 0]->sum_of_multiples(4) + result == 3 +} # solutions using include-exclude must extend to cardinality greater than 3 -expect - result = [2, 3, 5, 7, 11] |> sum_of_multiples(10000) - result == 39614537 +expect { + result = [2, 3, 5, 7, 11]->sum_of_multiples(10000) + result == 39614537 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/tournament/.meta/template.j2 b/exercises/practice/tournament/.meta/template.j2 index 261f24c9..1df6f05f 100644 --- a/exercises/practice/tournament/.meta/template.j2 +++ b/exercises/practice/tournament/.meta/template.j2 @@ -6,11 +6,12 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { table = {{ case["input"]["rows"] | to_roc_multiline_string | indent(8) }} result = {{ case["property"] | to_snake }}(table) expected = Ok({{ case["expected"] | to_roc_multiline_string | indent(8) }}) result == expected +} {% endfor %} diff --git a/exercises/practice/tournament/tournament-test.roc b/exercises/practice/tournament/tournament-test.roc index 29af8863..35c91299 100644 --- a/exercises/practice/tournament/tournament-test.roc +++ b/exercises/practice/tournament/tournament-test.roc @@ -1,213 +1,203 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/tournament/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Tournament exposing [tally] # just the header if no input -expect - table = "" - result = tally(table) - expected = Ok("Team | MP | W | D | L | P") - result == expected +expect { + table = "" + result = tally(table) + expected = Ok("Team | MP | W | D | L | P") + result == expected +} # a win is three points, a loss is zero points -expect - table = "Allegoric Alaskans;Blithering Badgers;win" - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Allegoric Alaskans | 1 | 1 | 0 | 0 | 3 - Blithering Badgers | 1 | 0 | 0 | 1 | 0 - """, - ) - result == expected +expect { + table = "Allegoric Alaskans;Blithering Badgers;win" + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Allegoric Alaskans | 1 | 1 | 0 | 0 | 3 + \\Blithering Badgers | 1 | 0 | 0 | 1 | 0 + , + ) + result == expected +} # a win can also be expressed as a loss -expect - table = "Blithering Badgers;Allegoric Alaskans;loss" - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Allegoric Alaskans | 1 | 1 | 0 | 0 | 3 - Blithering Badgers | 1 | 0 | 0 | 1 | 0 - """, - ) - result == expected +expect { + table = "Blithering Badgers;Allegoric Alaskans;loss" + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Allegoric Alaskans | 1 | 1 | 0 | 0 | 3 + \\Blithering Badgers | 1 | 0 | 0 | 1 | 0 + , + ) + result == expected +} # a different team can win -expect - table = "Blithering Badgers;Allegoric Alaskans;win" - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Blithering Badgers | 1 | 1 | 0 | 0 | 3 - Allegoric Alaskans | 1 | 0 | 0 | 1 | 0 - """, - ) - result == expected +expect { + table = "Blithering Badgers;Allegoric Alaskans;win" + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Blithering Badgers | 1 | 1 | 0 | 0 | 3 + \\Allegoric Alaskans | 1 | 0 | 0 | 1 | 0 + , + ) + result == expected +} # a draw is one point each -expect - table = "Allegoric Alaskans;Blithering Badgers;draw" - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Allegoric Alaskans | 1 | 0 | 1 | 0 | 1 - Blithering Badgers | 1 | 0 | 1 | 0 | 1 - """, - ) - result == expected +expect { + table = "Allegoric Alaskans;Blithering Badgers;draw" + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Allegoric Alaskans | 1 | 0 | 1 | 0 | 1 + \\Blithering Badgers | 1 | 0 | 1 | 0 | 1 + , + ) + result == expected +} # There can be more than one match -expect - table = - """ - Allegoric Alaskans;Blithering Badgers;win - Allegoric Alaskans;Blithering Badgers;win - """ - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Allegoric Alaskans | 2 | 2 | 0 | 0 | 6 - Blithering Badgers | 2 | 0 | 0 | 2 | 0 - """, - ) - result == expected +expect { + table = + \\Allegoric Alaskans;Blithering Badgers;win + \\Allegoric Alaskans;Blithering Badgers;win + + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Allegoric Alaskans | 2 | 2 | 0 | 0 | 6 + \\Blithering Badgers | 2 | 0 | 0 | 2 | 0 + , + ) + result == expected +} # There can be more than one winner -expect - table = - """ - Allegoric Alaskans;Blithering Badgers;loss - Allegoric Alaskans;Blithering Badgers;win - """ - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Allegoric Alaskans | 2 | 1 | 0 | 1 | 3 - Blithering Badgers | 2 | 1 | 0 | 1 | 3 - """, - ) - result == expected +expect { + table = + \\Allegoric Alaskans;Blithering Badgers;loss + \\Allegoric Alaskans;Blithering Badgers;win + + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Allegoric Alaskans | 2 | 1 | 0 | 1 | 3 + \\Blithering Badgers | 2 | 1 | 0 | 1 | 3 + , + ) + result == expected +} # There can be more than two teams -expect - table = - """ - Allegoric Alaskans;Blithering Badgers;win - Blithering Badgers;Courageous Californians;win - Courageous Californians;Allegoric Alaskans;loss - """ - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Allegoric Alaskans | 2 | 2 | 0 | 0 | 6 - Blithering Badgers | 2 | 1 | 0 | 1 | 3 - Courageous Californians | 2 | 0 | 0 | 2 | 0 - """, - ) - result == expected +expect { + table = + \\Allegoric Alaskans;Blithering Badgers;win + \\Blithering Badgers;Courageous Californians;win + \\Courageous Californians;Allegoric Alaskans;loss + + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Allegoric Alaskans | 2 | 2 | 0 | 0 | 6 + \\Blithering Badgers | 2 | 1 | 0 | 1 | 3 + \\Courageous Californians | 2 | 0 | 0 | 2 | 0 + , + ) + result == expected +} # typical input -expect - table = - """ - Allegoric Alaskans;Blithering Badgers;win - Devastating Donkeys;Courageous Californians;draw - Devastating Donkeys;Allegoric Alaskans;win - Courageous Californians;Blithering Badgers;loss - Blithering Badgers;Devastating Donkeys;loss - Allegoric Alaskans;Courageous Californians;win - """ - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Devastating Donkeys | 3 | 2 | 1 | 0 | 7 - Allegoric Alaskans | 3 | 2 | 0 | 1 | 6 - Blithering Badgers | 3 | 1 | 0 | 2 | 3 - Courageous Californians | 3 | 0 | 1 | 2 | 1 - """, - ) - result == expected +expect { + table = + \\Allegoric Alaskans;Blithering Badgers;win + \\Devastating Donkeys;Courageous Californians;draw + \\Devastating Donkeys;Allegoric Alaskans;win + \\Courageous Californians;Blithering Badgers;loss + \\Blithering Badgers;Devastating Donkeys;loss + \\Allegoric Alaskans;Courageous Californians;win + + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Devastating Donkeys | 3 | 2 | 1 | 0 | 7 + \\Allegoric Alaskans | 3 | 2 | 0 | 1 | 6 + \\Blithering Badgers | 3 | 1 | 0 | 2 | 3 + \\Courageous Californians | 3 | 0 | 1 | 2 | 1 + , + ) + result == expected +} # incomplete competition (not all pairs have played) -expect - table = - """ - Allegoric Alaskans;Blithering Badgers;loss - Devastating Donkeys;Allegoric Alaskans;loss - Courageous Californians;Blithering Badgers;draw - Allegoric Alaskans;Courageous Californians;win - """ - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Allegoric Alaskans | 3 | 2 | 0 | 1 | 6 - Blithering Badgers | 2 | 1 | 1 | 0 | 4 - Courageous Californians | 2 | 0 | 1 | 1 | 1 - Devastating Donkeys | 1 | 0 | 0 | 1 | 0 - """, - ) - result == expected +expect { + table = + \\Allegoric Alaskans;Blithering Badgers;loss + \\Devastating Donkeys;Allegoric Alaskans;loss + \\Courageous Californians;Blithering Badgers;draw + \\Allegoric Alaskans;Courageous Californians;win + + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Allegoric Alaskans | 3 | 2 | 0 | 1 | 6 + \\Blithering Badgers | 2 | 1 | 1 | 0 | 4 + \\Courageous Californians | 2 | 0 | 1 | 1 | 1 + \\Devastating Donkeys | 1 | 0 | 0 | 1 | 0 + , + ) + result == expected +} # ties broken alphabetically -expect - table = - """ - Courageous Californians;Devastating Donkeys;win - Allegoric Alaskans;Blithering Badgers;win - Devastating Donkeys;Allegoric Alaskans;loss - Courageous Californians;Blithering Badgers;win - Blithering Badgers;Devastating Donkeys;draw - Allegoric Alaskans;Courageous Californians;draw - """ - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Allegoric Alaskans | 3 | 2 | 1 | 0 | 7 - Courageous Californians | 3 | 2 | 1 | 0 | 7 - Blithering Badgers | 3 | 0 | 1 | 2 | 1 - Devastating Donkeys | 3 | 0 | 1 | 2 | 1 - """, - ) - result == expected +expect { + table = + \\Courageous Californians;Devastating Donkeys;win + \\Allegoric Alaskans;Blithering Badgers;win + \\Devastating Donkeys;Allegoric Alaskans;loss + \\Courageous Californians;Blithering Badgers;win + \\Blithering Badgers;Devastating Donkeys;draw + \\Allegoric Alaskans;Courageous Californians;draw + + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Allegoric Alaskans | 3 | 2 | 1 | 0 | 7 + \\Courageous Californians | 3 | 2 | 1 | 0 | 7 + \\Blithering Badgers | 3 | 0 | 1 | 2 | 1 + \\Devastating Donkeys | 3 | 0 | 1 | 2 | 1 + , + ) + result == expected +} # ensure points sorted numerically -expect - table = - """ - Devastating Donkeys;Blithering Badgers;win - Devastating Donkeys;Blithering Badgers;win - Devastating Donkeys;Blithering Badgers;win - Devastating Donkeys;Blithering Badgers;win - Blithering Badgers;Devastating Donkeys;win - """ - result = tally(table) - expected = Ok( - """ - Team | MP | W | D | L | P - Devastating Donkeys | 5 | 4 | 0 | 1 | 12 - Blithering Badgers | 5 | 1 | 0 | 4 | 3 - """, - ) - result == expected +expect { + table = + \\Devastating Donkeys;Blithering Badgers;win + \\Devastating Donkeys;Blithering Badgers;win + \\Devastating Donkeys;Blithering Badgers;win + \\Devastating Donkeys;Blithering Badgers;win + \\Blithering Badgers;Devastating Donkeys;win + + result = tally(table) + expected = Ok( + \\Team | MP | W | D | L | P + \\Devastating Donkeys | 5 | 4 | 0 | 1 | 12 + \\Blithering Badgers | 5 | 1 | 0 | 4 | 3 + , + ) + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/transpose/.meta/template.j2 b/exercises/practice/transpose/.meta/template.j2 index d524fdc1..bd920a92 100644 --- a/exercises/practice/transpose/.meta/template.j2 +++ b/exercises/practice/transpose/.meta/template.j2 @@ -6,11 +6,12 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect - input = {{ case["input"]["lines"] | to_roc_multiline_string | replace(" ", "□") | indent(8) }} |> Str.replace_each("□", " ") - result = {{ case["property"] | to_snake }}(input) |> Str.replace_each(" ", "□") +expect { + input = {{ case["input"]["lines"] | to_roc_multiline_string | replace(" ", "□") | indent(8) }}.replace_each("□", " ") + result = {{ case["property"] | to_snake }}input.replace_each(" ", "□") expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "□") | indent(8) }} result == expected +} {% endfor %} diff --git a/exercises/practice/transpose/transpose-test.roc b/exercises/practice/transpose/transpose-test.roc index 5d7b52dc..12b813f6 100644 --- a/exercises/practice/transpose/transpose-test.roc +++ b/exercises/practice/transpose/transpose-test.roc @@ -1,269 +1,276 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/transpose/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Transpose exposing [transpose] # empty string -expect - input = "" |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = "" - result == expected +expect { + input = "".replace_each("□", " ") + result = transposeinput.replace_each(" ", "□") + expected = "" + result == expected +} # two characters in a row -expect - input = "A1" |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = - """ - A - 1 - """ - result == expected +expect { + input = "A1".replace_each("□", " ") + result = transposeinput.replace_each(" ", "□") + expected = + \\A + \\1 + + result == expected +} # two characters in a column -expect - input = - """ - A - 1 - """ - |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = "A1" - result == expected +expect { + input = + \\A + \\1 + .replace_each( + "□", + " ", + ) + result = transposeinput.replace_each(" ", "□") + expected = "A1" + result == expected +} # simple -expect - input = - """ - ABC - 123 - """ - |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = - """ - A1 - B2 - C3 - """ - result == expected +expect { + input = + \\ABC + \\123 + .replace_each( + "□", + " ", + ) + result = transposeinput.replace_each(" ", "□") + expected = + \\A1 + \\B2 + \\C3 + + result == expected +} # single line -expect - input = "Single□line." |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = - """ - S - i - n - g - l - e - □ - l - i - n - e - . - """ - result == expected +expect { + input = "Single□line.".replace_each("□", " ") + result = transposeinput.replace_each(" ", "□") + expected = + \\S + \\i + \\n + \\g + \\l + \\e + \\□ + \\l + \\i + \\n + \\e + \\. + + result == expected +} # first line longer than second line -expect - input = - """ - The□fourth□line. - The□fifth□line. - """ - |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = - """ - TT - hh - ee - □□ - ff - oi - uf - rt - th - h□ - □l - li - in - ne - e. - . - """ - result == expected +expect { + input = + \\The□fourth□line. + \\The□fifth□line. + .replace_each( + "□", + " ", + ) + result = transposeinput.replace_each(" ", "□") + expected = + \\TT + \\hh + \\ee + \\□□ + \\ff + \\oi + \\uf + \\rt + \\th + \\h□ + \\□l + \\li + \\in + \\ne + \\e. + \\. + + result == expected +} # second line longer than first line -expect - input = - """ - The□first□line. - The□second□line. - """ - |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = - """ - TT - hh - ee - □□ - fs - ie - rc - so - tn - □d - l□ - il - ni - en - .e - □. - """ - result == expected +expect { + input = + \\The□first□line. + \\The□second□line. + .replace_each( + "□", + " ", + ) + result = transposeinput.replace_each(" ", "□") + expected = + \\TT + \\hh + \\ee + \\□□ + \\fs + \\ie + \\rc + \\so + \\tn + \\□d + \\l□ + \\il + \\ni + \\en + \\.e + \\□. + + result == expected +} # mixed line length -expect - input = - """ - The□longest□line. - A□long□line. - A□longer□line. - A□line. - """ - |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = - """ - TAAA - h□□□ - elll - □ooi - lnnn - ogge - n□e. - glr - ei□ - snl - tei - □.n - l□e - i□. - n - e - . - """ - result == expected +expect { + input = + \\The□longest□line. + \\A□long□line. + \\A□longer□line. + \\A□line. + .replace_each( + "□", + " ", + ) + result = transposeinput.replace_each(" ", "□") + expected = + \\TAAA + \\h□□□ + \\elll + \\□ooi + \\lnnn + \\ogge + \\n□e. + \\glr + \\ei□ + \\snl + \\tei + \\□.n + \\l□e + \\i□. + \\n + \\e + \\. + + result == expected +} # square -expect - input = - """ - HEART - EMBER - ABUSE - RESIN - TREND - """ - |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = - """ - HEART - EMBER - ABUSE - RESIN - TREND - """ - result == expected +expect { + input = + \\HEART + \\EMBER + \\ABUSE + \\RESIN + \\TREND + .replace_each( + "□", + " ", + ) + result = transposeinput.replace_each(" ", "□") + expected = + \\HEART + \\EMBER + \\ABUSE + \\RESIN + \\TREND + + result == expected +} # rectangle -expect - input = - """ - FRACTURE - OUTLINED - BLOOMING - SEPTETTE - """ - |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = - """ - FOBS - RULE - ATOP - CLOT - TIME - UNIT - RENT - EDGE - """ - result == expected +expect { + input = + \\FRACTURE + \\OUTLINED + \\BLOOMING + \\SEPTETTE + .replace_each( + "□", + " ", + ) + result = transposeinput.replace_each(" ", "□") + expected = + \\FOBS + \\RULE + \\ATOP + \\CLOT + \\TIME + \\UNIT + \\RENT + \\EDGE + + result == expected +} # triangle -expect - input = - """ - T - EE - AAA - SSSS - EEEEE - RRRRRR - """ - |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = - """ - TEASER - □EASER - □□ASER - □□□SER - □□□□ER - □□□□□R - """ - result == expected +expect { + input = + \\T + \\EE + \\AAA + \\SSSS + \\EEEEE + \\RRRRRR + .replace_each( + "□", + " ", + ) + result = transposeinput.replace_each(" ", "□") + expected = + \\TEASER + \\□EASER + \\□□ASER + \\□□□SER + \\□□□□ER + \\□□□□□R + + result == expected +} # jagged triangle -expect - input = - """ - 11 - 2 - 3333 - 444 - 555555 - 66666 - """ - |> Str.replace_each("□", " ") - result = transpose(input) |> Str.replace_each(" ", "□") - expected = - """ - 123456 - 1□3456 - □□3456 - □□3□56 - □□□□56 - □□□□5 - """ - result == expected +expect { + input = + \\11 + \\2 + \\3333 + \\444 + \\555555 + \\66666 + .replace_each( + "□", + " ", + ) + result = transposeinput.replace_each(" ", "□") + expected = + \\123456 + \\1□3456 + \\□□3456 + \\□□3□56 + \\□□□□56 + \\□□□□5 + result == expected +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/triangle/.meta/template.j2 b/exercises/practice/triangle/.meta/template.j2 index 39d7277c..c46324ca 100644 --- a/exercises/practice/triangle/.meta/template.j2 +++ b/exercises/practice/triangle/.meta/template.j2 @@ -11,9 +11,10 @@ import {{ exercise | to_pascal }} exposing [is_equilateral, is_isosceles, is_sca {% for case in supercase["cases"] -%} # {{ case["description"] }} -expect +expect { result = is_{{ case["property"] }}({{ case["input"]["sides"] | to_roc_tuple }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} {% endfor %} diff --git a/exercises/practice/triangle/triangle-test.roc b/exercises/practice/triangle/triangle-test.roc index 90faf9c3..f5f8a9a9 100644 --- a/exercises/practice/triangle/triangle-test.roc +++ b/exercises/practice/triangle/triangle-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Triangle exposing [is_equilateral, is_isosceles, is_scalene] @@ -17,90 +9,110 @@ import Triangle exposing [is_equilateral, is_isosceles, is_scalene] ## # all sides are equal -expect - result = is_equilateral((2, 2, 2)) - result == Bool.true +expect { + result = is_equilateral((2, 2, 2)) + result == Bool.True +} # any side is unequal -expect - result = is_equilateral((2, 3, 2)) - result == Bool.false +expect { + result = is_equilateral((2, 3, 2)) + result == Bool.False +} # no sides are equal -expect - result = is_equilateral((5, 4, 6)) - result == Bool.false +expect { + result = is_equilateral((5, 4, 6)) + result == Bool.False +} # sides may be floats -expect - result = is_equilateral((0.5f64, 0.5f64, 0.5f64)) - result == Bool.true +expect { + result = is_equilateral((0.5.F64, 0.5.F64, 0.5.F64)) + result == Bool.True +} ## ## isosceles triangle ## # last two sides are equal -expect - result = is_isosceles((3, 4, 4)) - result == Bool.true +expect { + result = is_isosceles((3, 4, 4)) + result == Bool.True +} # first two sides are equal -expect - result = is_isosceles((4, 4, 3)) - result == Bool.true +expect { + result = is_isosceles((4, 4, 3)) + result == Bool.True +} # first and last sides are equal -expect - result = is_isosceles((4, 3, 4)) - result == Bool.true +expect { + result = is_isosceles((4, 3, 4)) + result == Bool.True +} # equilateral triangles are also isosceles -expect - result = is_isosceles((4, 4, 4)) - result == Bool.true +expect { + result = is_isosceles((4, 4, 4)) + result == Bool.True +} # no sides are equal -expect - result = is_isosceles((2, 3, 4)) - result == Bool.false +expect { + result = is_isosceles((2, 3, 4)) + result == Bool.False +} # sides may be floats -expect - result = is_isosceles((0.5f64, 0.4f64, 0.5f64)) - result == Bool.true +expect { + result = is_isosceles((0.5.F64, 0.4.F64, 0.5.F64)) + result == Bool.True +} ## ## scalene triangle ## # no sides are equal -expect - result = is_scalene((5, 4, 6)) - result == Bool.true +expect { + result = is_scalene((5, 4, 6)) + result == Bool.True +} # all sides are equal -expect - result = is_scalene((4, 4, 4)) - result == Bool.false +expect { + result = is_scalene((4, 4, 4)) + result == Bool.False +} # first and second sides are equal -expect - result = is_scalene((4, 4, 3)) - result == Bool.false +expect { + result = is_scalene((4, 4, 3)) + result == Bool.False +} # first and third sides are equal -expect - result = is_scalene((3, 4, 3)) - result == Bool.false +expect { + result = is_scalene((3, 4, 3)) + result == Bool.False +} # second and third sides are equal -expect - result = is_scalene((4, 3, 3)) - result == Bool.false +expect { + result = is_scalene((4, 3, 3)) + result == Bool.False +} # sides may be floats -expect - result = is_scalene((0.5f64, 0.4f64, 0.6f64)) - result == Bool.true +expect { + result = is_scalene((0.5.F64, 0.4.F64, 0.6.F64)) + result == Bool.True +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/two-bucket/.meta/template.j2 b/exercises/practice/two-bucket/.meta/template.j2 index 1251fe41..9db17cc4 100644 --- a/exercises/practice/two-bucket/.meta/template.j2 +++ b/exercises/practice/two-bucket/.meta/template.j2 @@ -6,7 +6,7 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({ bucket_one: {{ case["input"]["bucketOne"] }}, bucket_two: {{ case["input"]["bucketTwo"] }}, @@ -14,7 +14,7 @@ expect start_bucket: {{ case["input"]["startBucket"] | to_pascal }}, }) {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} expected = Ok({ moves: {{ case["expected"]["moves"] }}, @@ -23,6 +23,7 @@ expect }) result == expected {%- endif %} +} {% endfor %} diff --git a/exercises/practice/two-bucket/two-bucket-test.roc b/exercises/practice/two-bucket/two-bucket-test.roc index 89a6d978..e65433df 100644 --- a/exercises/practice/two-bucket/two-bucket-test.roc +++ b/exercises/practice/two-bucket/two-bucket-test.roc @@ -1,209 +1,216 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/two-bucket/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import TwoBucket exposing [measure] # Measure using bucket one of size 3 and bucket two of size 5 - start with bucket one -expect - result = measure( - { - bucket_one: 3, - bucket_two: 5, - goal: 1, - start_bucket: One, - }, - ) - expected = Ok( - { - moves: 4, - goal_bucket: One, - other_bucket: 5, - }, - ) - result == expected +expect { + result = measure( + { + bucket_one: 3, + bucket_two: 5, + goal: 1, + start_bucket: One, + }, + ) + expected = Ok( + { + moves: 4, + goal_bucket: One, + other_bucket: 5, + }, + ) + result == expected +} # Measure using bucket one of size 3 and bucket two of size 5 - start with bucket two -expect - result = measure( - { - bucket_one: 3, - bucket_two: 5, - goal: 1, - start_bucket: Two, - }, - ) - expected = Ok( - { - moves: 8, - goal_bucket: Two, - other_bucket: 3, - }, - ) - result == expected +expect { + result = measure( + { + bucket_one: 3, + bucket_two: 5, + goal: 1, + start_bucket: Two, + }, + ) + expected = Ok( + { + moves: 8, + goal_bucket: Two, + other_bucket: 3, + }, + ) + result == expected +} # Measure using bucket one of size 7 and bucket two of size 11 - start with bucket one -expect - result = measure( - { - bucket_one: 7, - bucket_two: 11, - goal: 2, - start_bucket: One, - }, - ) - expected = Ok( - { - moves: 14, - goal_bucket: One, - other_bucket: 11, - }, - ) - result == expected +expect { + result = measure( + { + bucket_one: 7, + bucket_two: 11, + goal: 2, + start_bucket: One, + }, + ) + expected = Ok( + { + moves: 14, + goal_bucket: One, + other_bucket: 11, + }, + ) + result == expected +} # Measure using bucket one of size 7 and bucket two of size 11 - start with bucket two -expect - result = measure( - { - bucket_one: 7, - bucket_two: 11, - goal: 2, - start_bucket: Two, - }, - ) - expected = Ok( - { - moves: 18, - goal_bucket: Two, - other_bucket: 7, - }, - ) - result == expected +expect { + result = measure( + { + bucket_one: 7, + bucket_two: 11, + goal: 2, + start_bucket: Two, + }, + ) + expected = Ok( + { + moves: 18, + goal_bucket: Two, + other_bucket: 7, + }, + ) + result == expected +} # Measure one step using bucket one of size 1 and bucket two of size 3 - start with bucket two -expect - result = measure( - { - bucket_one: 1, - bucket_two: 3, - goal: 3, - start_bucket: Two, - }, - ) - expected = Ok( - { - moves: 1, - goal_bucket: Two, - other_bucket: 0, - }, - ) - result == expected +expect { + result = measure( + { + bucket_one: 1, + bucket_two: 3, + goal: 3, + start_bucket: Two, + }, + ) + expected = Ok( + { + moves: 1, + goal_bucket: Two, + other_bucket: 0, + }, + ) + result == expected +} # Measure using bucket one of size 2 and bucket two of size 3 - start with bucket one and end with bucket two -expect - result = measure( - { - bucket_one: 2, - bucket_two: 3, - goal: 3, - start_bucket: One, - }, - ) - expected = Ok( - { - moves: 2, - goal_bucket: Two, - other_bucket: 2, - }, - ) - result == expected +expect { + result = measure( + { + bucket_one: 2, + bucket_two: 3, + goal: 3, + start_bucket: One, + }, + ) + expected = Ok( + { + moves: 2, + goal_bucket: Two, + other_bucket: 2, + }, + ) + result == expected +} # Measure using bucket one much bigger than bucket two -expect - result = measure( - { - bucket_one: 5, - bucket_two: 1, - goal: 2, - start_bucket: One, - }, - ) - expected = Ok( - { - moves: 6, - goal_bucket: One, - other_bucket: 1, - }, - ) - result == expected +expect { + result = measure( + { + bucket_one: 5, + bucket_two: 1, + goal: 2, + start_bucket: One, + }, + ) + expected = Ok( + { + moves: 6, + goal_bucket: One, + other_bucket: 1, + }, + ) + result == expected +} # Measure using bucket one much smaller than bucket two -expect - result = measure( - { - bucket_one: 3, - bucket_two: 15, - goal: 9, - start_bucket: One, - }, - ) - expected = Ok( - { - moves: 6, - goal_bucket: Two, - other_bucket: 0, - }, - ) - result == expected +expect { + result = measure( + { + bucket_one: 3, + bucket_two: 15, + goal: 9, + start_bucket: One, + }, + ) + expected = Ok( + { + moves: 6, + goal_bucket: Two, + other_bucket: 0, + }, + ) + result == expected +} # Not possible to reach the goal -expect - result = measure( - { - bucket_one: 6, - bucket_two: 15, - goal: 5, - start_bucket: One, - }, - ) - result |> Result.is_err +expect { + result = measure( + { + bucket_one: 6, + bucket_two: 15, + goal: 5, + start_bucket: One, + }, + ) + result.is_err() +} # With the same buckets but a different goal, then it is possible -expect - result = measure( - { - bucket_one: 6, - bucket_two: 15, - goal: 9, - start_bucket: One, - }, - ) - expected = Ok( - { - moves: 10, - goal_bucket: Two, - other_bucket: 0, - }, - ) - result == expected +expect { + result = measure( + { + bucket_one: 6, + bucket_two: 15, + goal: 9, + start_bucket: One, + }, + ) + expected = Ok( + { + moves: 10, + goal_bucket: Two, + other_bucket: 0, + }, + ) + result == expected +} # Goal larger than both buckets is impossible -expect - result = measure( - { - bucket_one: 5, - bucket_two: 7, - goal: 8, - start_bucket: One, - }, - ) - result |> Result.is_err +expect { + result = measure( + { + bucket_one: 5, + bucket_two: 7, + goal: 8, + start_bucket: One, + }, + ) + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/two-fer/.meta/template.j2 b/exercises/practice/two-fer/.meta/template.j2 index c770bac0..102b569f 100644 --- a/exercises/practice/two-fer/.meta/template.j2 +++ b/exercises/practice/two-fer/.meta/template.j2 @@ -6,13 +6,14 @@ import {{ exercise | to_pascal }} exposing [{{ exercise | to_snake }}] {% for case in cases -%} # {{ case["description"] }} -expect +expect { {%- if case["input"]["name"] == None %} result = {{ case["property"] | to_snake }}(Anonymous) {%- else %} result = {{ case["property"] | to_snake }}((Name({{ case["input"]["name"] | to_roc }}))) {%- endif %} result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/two-fer/two-fer-test.roc b/exercises/practice/two-fer/two-fer-test.roc index 7ab95b07..064a2771 100644 --- a/exercises/practice/two-fer/two-fer-test.roc +++ b/exercises/practice/two-fer/two-fer-test.roc @@ -1,29 +1,28 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/two-fer/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import TwoFer exposing [two_fer] # no name given -expect - result = two_fer(Anonymous) - result == "One for you, one for me." +expect { + result = two_fer(Anonymous) + result == "One for you, one for me." +} # a name given -expect - result = two_fer(Name("Alice")) - result == "One for Alice, one for me." +expect { + result = two_fer((Name("Alice"))) + result == "One for Alice, one for me." +} # another name given -expect - result = two_fer(Name("Bob")) - result == "One for Bob, one for me." +expect { + result = two_fer((Name("Bob"))) + result == "One for Bob, one for me." +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/variable-length-quantity/.meta/template.j2 b/exercises/practice/variable-length-quantity/.meta/template.j2 index 6f2122ca..209ac5be 100644 --- a/exercises/practice/variable-length-quantity/.meta/template.j2 +++ b/exercises/practice/variable-length-quantity/.meta/template.j2 @@ -12,23 +12,26 @@ import {{ exercise | to_pascal }} exposing [encode, decode] {% for case in supercase["cases"] -%} # {{ case["description"] }} {%- if case["property"] == "encode" %} -expect +expect { integers = {{ case["input"]["integers"] | to_roc }} result = encode(integers) expected = {{ case["expected"] | to_roc }} result == expected +} {%- else %} -expect +expect { bytes = {{ case["input"]["integers"] | to_roc }} result = decode bytes {%- if case["expected"]["error"] %} - result |> Result.is_err + result.is_err() {%- else %} expected = Ok({{ case["expected"] | to_roc }}) result == expected {%- endif %} +} {%- endif %} + {% endfor %} {% endfor %} diff --git a/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc b/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc index e2316abd..ca2fbbe7 100644 --- a/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc +++ b/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc @@ -1,14 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/variable-length-quantity/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import VariableLengthQuantity exposing [encode, decode] @@ -17,221 +9,264 @@ import VariableLengthQuantity exposing [encode, decode] ## # zero -expect - integers = [0] - result = encode(integers) - expected = [0] - result == expected +expect { + integers = [0] + result = encode(integers) + expected = [0] + result == expected +} # arbitrary single byte -expect - integers = [64] - result = encode(integers) - expected = [64] - result == expected +expect { + integers = [64] + result = encode(integers) + expected = [64] + result == expected +} # asymmetric single byte -expect - integers = [83] - result = encode(integers) - expected = [83] - result == expected +expect { + integers = [83] + result = encode(integers) + expected = [83] + result == expected +} # largest single byte -expect - integers = [127] - result = encode(integers) - expected = [127] - result == expected +expect { + integers = [127] + result = encode(integers) + expected = [127] + result == expected +} # smallest double byte -expect - integers = [128] - result = encode(integers) - expected = [129, 0] - result == expected +expect { + integers = [128] + result = encode(integers) + expected = [129, 0] + result == expected +} # arbitrary double byte -expect - integers = [8192] - result = encode(integers) - expected = [192, 0] - result == expected +expect { + integers = [8192] + result = encode(integers) + expected = [192, 0] + result == expected +} # asymmetric double byte -expect - integers = [173] - result = encode(integers) - expected = [129, 45] - result == expected +expect { + integers = [173] + result = encode(integers) + expected = [129, 45] + result == expected +} # largest double byte -expect - integers = [16383] - result = encode(integers) - expected = [255, 127] - result == expected +expect { + integers = [16383] + result = encode(integers) + expected = [255, 127] + result == expected +} # smallest triple byte -expect - integers = [16384] - result = encode(integers) - expected = [129, 128, 0] - result == expected +expect { + integers = [16384] + result = encode(integers) + expected = [129, 128, 0] + result == expected +} # arbitrary triple byte -expect - integers = [1048576] - result = encode(integers) - expected = [192, 128, 0] - result == expected +expect { + integers = [1048576] + result = encode(integers) + expected = [192, 128, 0] + result == expected +} # asymmetric triple byte -expect - integers = [120220] - result = encode(integers) - expected = [135, 171, 28] - result == expected +expect { + integers = [120220] + result = encode(integers) + expected = [135, 171, 28] + result == expected +} # largest triple byte -expect - integers = [2097151] - result = encode(integers) - expected = [255, 255, 127] - result == expected +expect { + integers = [2097151] + result = encode(integers) + expected = [255, 255, 127] + result == expected +} # smallest quadruple byte -expect - integers = [2097152] - result = encode(integers) - expected = [129, 128, 128, 0] - result == expected +expect { + integers = [2097152] + result = encode(integers) + expected = [129, 128, 128, 0] + result == expected +} # arbitrary quadruple byte -expect - integers = [134217728] - result = encode(integers) - expected = [192, 128, 128, 0] - result == expected +expect { + integers = [134217728] + result = encode(integers) + expected = [192, 128, 128, 0] + result == expected +} # asymmetric quadruple byte -expect - integers = [3503876] - result = encode(integers) - expected = [129, 213, 238, 4] - result == expected +expect { + integers = [3503876] + result = encode(integers) + expected = [129, 213, 238, 4] + result == expected +} # largest quadruple byte -expect - integers = [268435455] - result = encode(integers) - expected = [255, 255, 255, 127] - result == expected +expect { + integers = [268435455] + result = encode(integers) + expected = [255, 255, 255, 127] + result == expected +} # smallest quintuple byte -expect - integers = [268435456] - result = encode(integers) - expected = [129, 128, 128, 128, 0] - result == expected +expect { + integers = [268435456] + result = encode(integers) + expected = [129, 128, 128, 128, 0] + result == expected +} # arbitrary quintuple byte -expect - integers = [4278190080] - result = encode(integers) - expected = [143, 248, 128, 128, 0] - result == expected +expect { + integers = [4278190080] + result = encode(integers) + expected = [143, 248, 128, 128, 0] + result == expected +} # asymmetric quintuple byte -expect - integers = [2254790917] - result = encode(integers) - expected = [136, 179, 149, 194, 5] - result == expected +expect { + integers = [2254790917] + result = encode(integers) + expected = [136, 179, 149, 194, 5] + result == expected +} # maximum 32-bit integer input -expect - integers = [4294967295] - result = encode(integers) - expected = [143, 255, 255, 255, 127] - result == expected +expect { + integers = [4294967295] + result = encode(integers) + expected = [143, 255, 255, 255, 127] + result == expected +} # two single-byte values -expect - integers = [64, 127] - result = encode(integers) - expected = [64, 127] - result == expected +expect { + integers = [64, 127] + result = encode(integers) + expected = [64, 127] + result == expected +} # two multi-byte values -expect - integers = [16384, 1193046] - result = encode(integers) - expected = [129, 128, 0, 200, 232, 86] - result == expected +expect { + integers = [16384, 1193046] + result = encode(integers) + expected = [129, 128, 0, 200, 232, 86] + result == expected +} # many multi-byte values -expect - integers = [8192, 1193046, 268435455, 0, 16383, 16384] - result = encode(integers) - expected = [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0] - result == expected +expect { + integers = [8192, 1193046, 268435455, 0, 16383, 16384] + result = encode(integers) + expected = [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0] + result == expected +} ## ## Decode a series of bytes, producing a series of integers. ## # one byte -expect - bytes = [127] - result = decode bytes - expected = Ok([127]) - result == expected +expect { + bytes = [127] + result = decode + bytes + expected = Ok([127]) + result == expected +} # two bytes -expect - bytes = [192, 0] - result = decode bytes - expected = Ok([8192]) - result == expected +expect { + bytes = [192, 0] + result = decode + bytes + expected = Ok([8192]) + result == expected +} # three bytes -expect - bytes = [255, 255, 127] - result = decode bytes - expected = Ok([2097151]) - result == expected +expect { + bytes = [255, 255, 127] + result = decode + bytes + expected = Ok([2097151]) + result == expected +} # four bytes -expect - bytes = [129, 128, 128, 0] - result = decode bytes - expected = Ok([2097152]) - result == expected +expect { + bytes = [129, 128, 128, 0] + result = decode + bytes + expected = Ok([2097152]) + result == expected +} # maximum 32-bit integer -expect - bytes = [143, 255, 255, 255, 127] - result = decode bytes - expected = Ok([4294967295]) - result == expected +expect { + bytes = [143, 255, 255, 255, 127] + result = decode + bytes + expected = Ok([4294967295]) + result == expected +} # incomplete sequence causes error -expect - bytes = [255] - result = decode bytes - result |> Result.is_err +expect { + bytes = [255] + result = decode + bytes + result.is_err() +} # incomplete sequence causes error, even if value is zero -expect - bytes = [128] - result = decode bytes - result |> Result.is_err +expect { + bytes = [128] + result = decode + bytes + result.is_err() +} # multiple values -expect - bytes = [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0] - result = decode bytes - expected = Ok([8192, 1193046, 268435455, 0, 16383, 16384]) - result == expected +expect { + bytes = [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0] + result = decode + bytes + expected = Ok([8192, 1193046, 268435455, 0, 16383, 16384]) + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/word-count/.meta/template.j2 b/exercises/practice/word-count/.meta/template.j2 index 8975a757..e128f38d 100644 --- a/exercises/practice/word-count/.meta/template.j2 +++ b/exercises/practice/word-count/.meta/template.j2 @@ -6,7 +6,7 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["sentence"] | to_roc }}) expected = Dict.from_list([ {%- for word, count in case["expected"].items() %} @@ -14,6 +14,7 @@ expect {%- endfor %} ]) result == expected +} {% endfor %} diff --git a/exercises/practice/word-count/word-count-test.roc b/exercises/practice/word-count/word-count-test.roc index 8eab5385..6f9faff1 100644 --- a/exercises/practice/word-count/word-count-test.roc +++ b/exercises/practice/word-count/word-count-test.roc @@ -1,194 +1,204 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/word-count/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import WordCount exposing [count_words] # count one word -expect - result = count_words("word") - expected = Dict.from_list( - [ - ("word", 1), - ], - ) - result == expected +expect { + result = count_words("word") + expected = Dict.from_list( + [ + ("word", 1), + ], + ) + result == expected +} # count one of each word -expect - result = count_words("one of each") - expected = Dict.from_list( - [ - ("one", 1), - ("of", 1), - ("each", 1), - ], - ) - result == expected +expect { + result = count_words("one of each") + expected = Dict.from_list( + [ + ("one", 1), + ("of", 1), + ("each", 1), + ], + ) + result == expected +} # multiple occurrences of a word -expect - result = count_words("one fish two fish red fish blue fish") - expected = Dict.from_list( - [ - ("one", 1), - ("fish", 4), - ("two", 1), - ("red", 1), - ("blue", 1), - ], - ) - result == expected +expect { + result = count_words("one fish two fish red fish blue fish") + expected = Dict.from_list( + [ + ("one", 1), + ("fish", 4), + ("two", 1), + ("red", 1), + ("blue", 1), + ], + ) + result == expected +} # handles cramped lists -expect - result = count_words("one,two,three") - expected = Dict.from_list( - [ - ("one", 1), - ("two", 1), - ("three", 1), - ], - ) - result == expected +expect { + result = count_words("one,two,three") + expected = Dict.from_list( + [ + ("one", 1), + ("two", 1), + ("three", 1), + ], + ) + result == expected +} # handles expanded lists -expect - result = count_words("one,\ntwo,\nthree") - expected = Dict.from_list( - [ - ("one", 1), - ("two", 1), - ("three", 1), - ], - ) - result == expected +expect { + result = count_words("one,\ntwo,\nthree") + expected = Dict.from_list( + [ + ("one", 1), + ("two", 1), + ("three", 1), + ], + ) + result == expected +} # ignore punctuation -expect - result = count_words("car: carpet as java: javascript!!&@$%^&") - expected = Dict.from_list( - [ - ("car", 1), - ("carpet", 1), - ("as", 1), - ("java", 1), - ("javascript", 1), - ], - ) - result == expected +expect { + result = count_words("car: carpet as java: javascript!!&@$%^&") + expected = Dict.from_list( + [ + ("car", 1), + ("carpet", 1), + ("as", 1), + ("java", 1), + ("javascript", 1), + ], + ) + result == expected +} # include numbers -expect - result = count_words("testing, 1, 2 testing") - expected = Dict.from_list( - [ - ("testing", 2), - ("1", 1), - ("2", 1), - ], - ) - result == expected +expect { + result = count_words("testing, 1, 2 testing") + expected = Dict.from_list( + [ + ("testing", 2), + ("1", 1), + ("2", 1), + ], + ) + result == expected +} # normalize case -expect - result = count_words("go Go GO Stop stop") - expected = Dict.from_list( - [ - ("go", 3), - ("stop", 2), - ], - ) - result == expected +expect { + result = count_words("go Go GO Stop stop") + expected = Dict.from_list( + [ + ("go", 3), + ("stop", 2), + ], + ) + result == expected +} # with apostrophes -expect - result = count_words("'First: don't laugh. Then: don't cry. You're getting it.'") - expected = Dict.from_list( - [ - ("first", 1), - ("don't", 2), - ("laugh", 1), - ("then", 1), - ("cry", 1), - ("you're", 1), - ("getting", 1), - ("it", 1), - ], - ) - result == expected +expect { + result = count_words("'First: don't laugh. Then: don't cry. You're getting it.'") + expected = Dict.from_list( + [ + ("first", 1), + ("don't", 2), + ("laugh", 1), + ("then", 1), + ("cry", 1), + ("you're", 1), + ("getting", 1), + ("it", 1), + ], + ) + result == expected +} # with quotations -expect - result = count_words("Joe can't tell between 'large' and large.") - expected = Dict.from_list( - [ - ("joe", 1), - ("can't", 1), - ("tell", 1), - ("between", 1), - ("large", 2), - ("and", 1), - ], - ) - result == expected +expect { + result = count_words("Joe can't tell between 'large' and large.") + expected = Dict.from_list( + [ + ("joe", 1), + ("can't", 1), + ("tell", 1), + ("between", 1), + ("large", 2), + ("and", 1), + ], + ) + result == expected +} # substrings from the beginning -expect - result = count_words("Joe can't tell between app, apple and a.") - expected = Dict.from_list( - [ - ("joe", 1), - ("can't", 1), - ("tell", 1), - ("between", 1), - ("app", 1), - ("apple", 1), - ("and", 1), - ("a", 1), - ], - ) - result == expected +expect { + result = count_words("Joe can't tell between app, apple and a.") + expected = Dict.from_list( + [ + ("joe", 1), + ("can't", 1), + ("tell", 1), + ("between", 1), + ("app", 1), + ("apple", 1), + ("and", 1), + ("a", 1), + ], + ) + result == expected +} # multiple spaces not detected as a word -expect - result = count_words(" multiple whitespaces") - expected = Dict.from_list( - [ - ("multiple", 1), - ("whitespaces", 1), - ], - ) - result == expected +expect { + result = count_words(" multiple whitespaces") + expected = Dict.from_list( + [ + ("multiple", 1), + ("whitespaces", 1), + ], + ) + result == expected +} # alternating word separators not detected as a word -expect - result = count_words(",\n,one,\n ,two \n 'three'") - expected = Dict.from_list( - [ - ("one", 1), - ("two", 1), - ("three", 1), - ], - ) - result == expected +expect { + result = count_words(",\n,one,\n ,two \n 'three'") + expected = Dict.from_list( + [ + ("one", 1), + ("two", 1), + ("three", 1), + ], + ) + result == expected +} # quotation for word with apostrophe -expect - result = count_words("can, can't, 'can't'") - expected = Dict.from_list( - [ - ("can", 1), - ("can't", 2), - ], - ) - result == expected +expect { + result = count_words("can, can't, 'can't'") + expected = Dict.from_list( + [ + ("can", 1), + ("can't", 2), + ], + ) + result == expected +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/word-search/.meta/template.j2 b/exercises/practice/word-search/.meta/template.j2 index 25d4c5c0..79301224 100644 --- a/exercises/practice/word-search/.meta/template.j2 +++ b/exercises/practice/word-search/.meta/template.j2 @@ -6,10 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect +expect { grid = {{ case["input"]["grid"] | to_roc_multiline_string | indent(8) }} words_to_search_for = {{ case["input"]["wordsToSearchFor"] | to_roc }} - result = grid |> {{ case["property"] | to_snake }}(words_to_search_for) + result = grid -> {{ case["property"] | to_snake }}(words_to_search_for) expected = Dict.from_list([ {%- for word, result in case["expected"].items() %} {%- if result is none %} @@ -20,6 +20,7 @@ expect {%- endfor %} ]) result == expected +} {% endfor %} diff --git a/exercises/practice/word-search/word-search-test.roc b/exercises/practice/word-search/word-search-test.roc index 614cb6f8..dbc66923 100644 --- a/exercises/practice/word-search/word-search-test.roc +++ b/exercises/practice/word-search/word-search-test.roc @@ -1,512 +1,502 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/word-search/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import WordSearch exposing [search] # Should accept an initial game grid and a target search word -expect - grid = "jefblpepre" - words_to_search_for = ["clojure"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - # "clojure" is not in the grid - ], - ) - result == expected +expect { + grid = "jefblpepre" + words_to_search_for = ["clojure"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [], + ) + result == expected +} # Should locate one word written left to right -expect - grid = "clojurermt" - words_to_search_for = ["clojure"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 1 }, end: { column: 7, row: 1 } }), - ], - ) - result == expected +expect { + grid = "clojurermt" + words_to_search_for = ["clojure"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 1 }, end: { column: 7, row: 1 } }), + ], + ) + result == expected +} # Should locate the same word written left to right in a different position -expect - grid = "mtclojurer" - words_to_search_for = ["clojure"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 3, row: 1 }, end: { column: 9, row: 1 } }), - ], - ) - result == expected +expect { + grid = "mtclojurer" + words_to_search_for = ["clojure"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 3, row: 1 }, end: { column: 9, row: 1 } }), + ], + ) + result == expected +} # Should locate a different left to right word -expect - grid = "coffeelplx" - words_to_search_for = ["coffee"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("coffee", { start: { column: 1, row: 1 }, end: { column: 6, row: 1 } }), - ], - ) - result == expected +expect { + grid = "coffeelplx" + words_to_search_for = ["coffee"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("coffee", { start: { column: 1, row: 1 }, end: { column: 6, row: 1 } }), + ], + ) + result == expected +} # Should locate that different left to right word in a different position -expect - grid = "xcoffeezlp" - words_to_search_for = ["coffee"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("coffee", { start: { column: 2, row: 1 }, end: { column: 7, row: 1 } }), - ], - ) - result == expected +expect { + grid = "xcoffeezlp" + words_to_search_for = ["coffee"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("coffee", { start: { column: 2, row: 1 }, end: { column: 7, row: 1 } }), + ], + ) + result == expected +} # Should locate a left to right word in two line grid -expect - grid = - """ - jefblpepre - tclojurerm - """ - words_to_search_for = ["clojure"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 2, row: 2 }, end: { column: 8, row: 2 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\tclojurerm + + words_to_search_for = ["clojure"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 2, row: 2 }, end: { column: 8, row: 2 } }), + ], + ) + result == expected +} # Should locate a left to right word in three line grid -expect - grid = - """ - camdcimgtc - jefblpepre - clojurermt - """ - words_to_search_for = ["clojure"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 3 }, end: { column: 7, row: 3 } }), - ], - ) - result == expected +expect { + grid = + \\camdcimgtc + \\jefblpepre + \\clojurermt + + words_to_search_for = ["clojure"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 3 }, end: { column: 7, row: 3 } }), + ], + ) + result == expected +} # Should locate a left to right word in ten line grid -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - screeaumgr - alxhpburyi - jalaycalmp - clojurermt - """ - words_to_search_for = ["clojure"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\screeaumgr + \\alxhpburyi + \\jalaycalmp + \\clojurermt + + words_to_search_for = ["clojure"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), + ], + ) + result == expected +} # Should locate that left to right word in a different position in a ten line grid -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - screeaumgr - alxhpburyi - clojurermt - jalaycalmp - """ - words_to_search_for = ["clojure"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 9 }, end: { column: 7, row: 9 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\screeaumgr + \\alxhpburyi + \\clojurermt + \\jalaycalmp + + words_to_search_for = ["clojure"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 9 }, end: { column: 7, row: 9 } }), + ], + ) + result == expected +} # Should locate a different left to right word in a ten line grid -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - fortranftw - alxhpburyi - clojurermt - jalaycalmp - """ - words_to_search_for = ["fortran"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("fortran", { start: { column: 1, row: 7 }, end: { column: 7, row: 7 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\fortranftw + \\alxhpburyi + \\clojurermt + \\jalaycalmp + + words_to_search_for = ["fortran"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("fortran", { start: { column: 1, row: 7 }, end: { column: 7, row: 7 } }), + ], + ) + result == expected +} # Should locate multiple words -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - fortranftw - alxhpburyi - jalaycalmp - clojurermt - """ - words_to_search_for = ["fortran", "clojure"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), - ("fortran", { start: { column: 1, row: 7 }, end: { column: 7, row: 7 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\fortranftw + \\alxhpburyi + \\jalaycalmp + \\clojurermt + + words_to_search_for = ["fortran", "clojure"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), + ("fortran", { start: { column: 1, row: 7 }, end: { column: 7, row: 7 } }), + ], + ) + result == expected +} # Should locate a single word written right to left -expect - grid = "rixilelhrs" - words_to_search_for = ["elixir"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("elixir", { start: { column: 6, row: 1 }, end: { column: 1, row: 1 } }), - ], - ) - result == expected +expect { + grid = "rixilelhrs" + words_to_search_for = ["elixir"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("elixir", { start: { column: 6, row: 1 }, end: { column: 1, row: 1 } }), + ], + ) + result == expected +} # Should locate multiple words written in different horizontal directions -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - screeaumgr - alxhpburyi - jalaycalmp - clojurermt - """ - words_to_search_for = ["elixir", "clojure"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), - ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\screeaumgr + \\alxhpburyi + \\jalaycalmp + \\clojurermt + + words_to_search_for = ["elixir", "clojure"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), + ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), + ], + ) + result == expected +} # Should locate words written top to bottom -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - screeaumgr - alxhpburyi - jalaycalmp - clojurermt - """ - words_to_search_for = ["clojure", "elixir", "ecmascript"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), - ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), - ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\screeaumgr + \\alxhpburyi + \\jalaycalmp + \\clojurermt + + words_to_search_for = ["clojure", "elixir", "ecmascript"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), + ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), + ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), + ], + ) + result == expected +} # Should locate words written bottom to top -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - screeaumgr - alxhpburyi - jalaycalmp - clojurermt - """ - words_to_search_for = ["clojure", "elixir", "ecmascript", "rust"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), - ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), - ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), - ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\screeaumgr + \\alxhpburyi + \\jalaycalmp + \\clojurermt + + words_to_search_for = ["clojure", "elixir", "ecmascript", "rust"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), + ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), + ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), + ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), + ], + ) + result == expected +} # Should locate words written top left to bottom right -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - screeaumgr - alxhpburyi - jalaycalmp - clojurermt - """ - words_to_search_for = ["clojure", "elixir", "ecmascript", "rust", "java"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), - ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), - ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), - ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), - ("java", { start: { column: 1, row: 1 }, end: { column: 4, row: 4 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\screeaumgr + \\alxhpburyi + \\jalaycalmp + \\clojurermt + + words_to_search_for = ["clojure", "elixir", "ecmascript", "rust", "java"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), + ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), + ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), + ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), + ("java", { start: { column: 1, row: 1 }, end: { column: 4, row: 4 } }), + ], + ) + result == expected +} # Should locate words written bottom right to top left -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - screeaumgr - alxhpburyi - jalaycalmp - clojurermt - """ - words_to_search_for = ["clojure", "elixir", "ecmascript", "rust", "java", "lua"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), - ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), - ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), - ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), - ("java", { start: { column: 1, row: 1 }, end: { column: 4, row: 4 } }), - ("lua", { start: { column: 8, row: 9 }, end: { column: 6, row: 7 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\screeaumgr + \\alxhpburyi + \\jalaycalmp + \\clojurermt + + words_to_search_for = ["clojure", "elixir", "ecmascript", "rust", "java", "lua"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), + ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), + ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), + ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), + ("java", { start: { column: 1, row: 1 }, end: { column: 4, row: 4 } }), + ("lua", { start: { column: 8, row: 9 }, end: { column: 6, row: 7 } }), + ], + ) + result == expected +} # Should locate words written bottom left to top right -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - screeaumgr - alxhpburyi - jalaycalmp - clojurermt - """ - words_to_search_for = ["clojure", "elixir", "ecmascript", "rust", "java", "lua", "lisp"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), - ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), - ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), - ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), - ("java", { start: { column: 1, row: 1 }, end: { column: 4, row: 4 } }), - ("lua", { start: { column: 8, row: 9 }, end: { column: 6, row: 7 } }), - ("lisp", { start: { column: 3, row: 6 }, end: { column: 6, row: 3 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\screeaumgr + \\alxhpburyi + \\jalaycalmp + \\clojurermt + + words_to_search_for = ["clojure", "elixir", "ecmascript", "rust", "java", "lua", "lisp"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), + ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), + ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), + ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), + ("java", { start: { column: 1, row: 1 }, end: { column: 4, row: 4 } }), + ("lua", { start: { column: 8, row: 9 }, end: { column: 6, row: 7 } }), + ("lisp", { start: { column: 3, row: 6 }, end: { column: 6, row: 3 } }), + ], + ) + result == expected +} # Should locate words written top right to bottom left -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - screeaumgr - alxhpburyi - jalaycalmp - clojurermt - """ - words_to_search_for = ["clojure", "elixir", "ecmascript", "rust", "java", "lua", "lisp", "ruby"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), - ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), - ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), - ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), - ("java", { start: { column: 1, row: 1 }, end: { column: 4, row: 4 } }), - ("lua", { start: { column: 8, row: 9 }, end: { column: 6, row: 7 } }), - ("lisp", { start: { column: 3, row: 6 }, end: { column: 6, row: 3 } }), - ("ruby", { start: { column: 8, row: 6 }, end: { column: 5, row: 9 } }), - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\screeaumgr + \\alxhpburyi + \\jalaycalmp + \\clojurermt + + words_to_search_for = ["clojure", "elixir", "ecmascript", "rust", "java", "lua", "lisp", "ruby"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), + ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), + ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), + ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), + ("java", { start: { column: 1, row: 1 }, end: { column: 4, row: 4 } }), + ("lua", { start: { column: 8, row: 9 }, end: { column: 6, row: 7 } }), + ("lisp", { start: { column: 3, row: 6 }, end: { column: 6, row: 3 } }), + ("ruby", { start: { column: 8, row: 6 }, end: { column: 5, row: 9 } }), + ], + ) + result == expected +} # Should fail to locate a word that is not in the puzzle -expect - grid = - """ - jefblpepre - camdcimgtc - oivokprjsm - pbwasqroua - rixilelhrs - wolcqlirpc - screeaumgr - alxhpburyi - jalaycalmp - clojurermt - """ - words_to_search_for = ["clojure", "elixir", "ecmascript", "rust", "java", "lua", "lisp", "ruby", "haskell"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), - ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), - ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), - ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), - ("java", { start: { column: 1, row: 1 }, end: { column: 4, row: 4 } }), - ("lua", { start: { column: 8, row: 9 }, end: { column: 6, row: 7 } }), - ("lisp", { start: { column: 3, row: 6 }, end: { column: 6, row: 3 } }), - ("ruby", { start: { column: 8, row: 6 }, end: { column: 5, row: 9 } }), - # "haskell" is not in the grid - ], - ) - result == expected +expect { + grid = + \\jefblpepre + \\camdcimgtc + \\oivokprjsm + \\pbwasqroua + \\rixilelhrs + \\wolcqlirpc + \\screeaumgr + \\alxhpburyi + \\jalaycalmp + \\clojurermt + + words_to_search_for = ["clojure", "elixir", "ecmascript", "rust", "java", "lua", "lisp", "ruby", "haskell"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [ + ("clojure", { start: { column: 1, row: 10 }, end: { column: 7, row: 10 } }), + ("elixir", { start: { column: 6, row: 5 }, end: { column: 1, row: 5 } }), + ("ecmascript", { start: { column: 10, row: 1 }, end: { column: 10, row: 10 } }), + ("rust", { start: { column: 9, row: 5 }, end: { column: 9, row: 2 } }), + ("java", { start: { column: 1, row: 1 }, end: { column: 4, row: 4 } }), + ("lua", { start: { column: 8, row: 9 }, end: { column: 6, row: 7 } }), + ("lisp", { start: { column: 3, row: 6 }, end: { column: 6, row: 3 } }), + ("ruby", { start: { column: 8, row: 6 }, end: { column: 5, row: 9 } }), + # "haskell" is not in the grid + ], + ) + result == expected +} # Should fail to locate words that are not on horizontal, vertical, or diagonal lines -expect - grid = - """ - abc - def - """ - words_to_search_for = ["aef", "ced", "abf", "cbd"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - # "aef" is not in the grid - # "ced" is not in the grid - # "abf" is not in the grid - # "cbd" is not in the grid - ], - ) - result == expected +expect { + grid = + \\abc + \\def + + words_to_search_for = ["aef", "ced", "abf", "cbd"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [], + ) + result == expected +} # Should not concatenate different lines to find a horizontal word -expect - grid = - """ - abceli - xirdfg - """ - words_to_search_for = ["elixir"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - # "elixir" is not in the grid - ], - ) - result == expected +expect { + grid = + \\abceli + \\xirdfg + + words_to_search_for = ["elixir"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [], + ) + result == expected +} # Should not wrap around horizontally to find a word -expect - grid = "silabcdefp" - words_to_search_for = ["lisp"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - # "lisp" is not in the grid - ], - ) - result == expected +expect { + grid = "silabcdefp" + words_to_search_for = ["lisp"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [], + ) + result == expected +} # Should not wrap around vertically to find a word -expect - grid = - """ - s - u - r - a - b - c - t - """ - words_to_search_for = ["rust"] - result = grid |> search(words_to_search_for) - expected = Dict.from_list( - [ - # "rust" is not in the grid - ], - ) - result == expected +expect { + grid = + \\s + \\u + \\r + \\a + \\b + \\c + \\t + words_to_search_for = ["rust"] + result = grid->search(words_to_search_for) + expected = Dict.from_list( + [], + ) + result == expected +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/wordy/.meta/template.j2 b/exercises/practice/wordy/.meta/template.j2 index 3037f73c..90876432 100644 --- a/exercises/practice/wordy/.meta/template.j2 +++ b/exercises/practice/wordy/.meta/template.j2 @@ -6,13 +6,14 @@ import {{ exercise | to_pascal }} exposing [answer] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }}({{ case["input"]["question"] | to_roc }}) {%- if case["expected"]["error"] %} - Result.is_err(result) + result.is_err() {%- else %} result == Ok({{ case["expected"] }}) {%- endif %} +} {% endfor %} diff --git a/exercises/practice/wordy/wordy-test.roc b/exercises/practice/wordy/wordy-test.roc index a3f523a3..40f6ad0b 100644 --- a/exercises/practice/wordy/wordy-test.roc +++ b/exercises/practice/wordy/wordy-test.roc @@ -1,149 +1,172 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/wordy/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Wordy exposing [answer] # just a number -expect - result = answer("What is 5?") - result == Ok(5) +expect { + result = answer("What is 5?") + result == Ok(5) +} # just a zero -expect - result = answer("What is 0?") - result == Ok(0) +expect { + result = answer("What is 0?") + result == Ok(0) +} # just a negative number -expect - result = answer("What is -123?") - result == Ok(-123) +expect { + result = answer("What is -123?") + result == Ok(-123) +} # addition -expect - result = answer("What is 1 plus 1?") - result == Ok(2) +expect { + result = answer("What is 1 plus 1?") + result == Ok(2) +} # addition with a left hand zero -expect - result = answer("What is 0 plus 2?") - result == Ok(2) +expect { + result = answer("What is 0 plus 2?") + result == Ok(2) +} # addition with a right hand zero -expect - result = answer("What is 3 plus 0?") - result == Ok(3) +expect { + result = answer("What is 3 plus 0?") + result == Ok(3) +} # more addition -expect - result = answer("What is 53 plus 2?") - result == Ok(55) +expect { + result = answer("What is 53 plus 2?") + result == Ok(55) +} # addition with negative numbers -expect - result = answer("What is -1 plus -10?") - result == Ok(-11) +expect { + result = answer("What is -1 plus -10?") + result == Ok(-11) +} # large addition -expect - result = answer("What is 123 plus 45678?") - result == Ok(45801) +expect { + result = answer("What is 123 plus 45678?") + result == Ok(45801) +} # subtraction -expect - result = answer("What is 4 minus -12?") - result == Ok(16) +expect { + result = answer("What is 4 minus -12?") + result == Ok(16) +} # multiplication -expect - result = answer("What is -3 multiplied by 25?") - result == Ok(-75) +expect { + result = answer("What is -3 multiplied by 25?") + result == Ok(-75) +} # division -expect - result = answer("What is 33 divided by -3?") - result == Ok(-11) +expect { + result = answer("What is 33 divided by -3?") + result == Ok(-11) +} # multiple additions -expect - result = answer("What is 1 plus 1 plus 1?") - result == Ok(3) +expect { + result = answer("What is 1 plus 1 plus 1?") + result == Ok(3) +} # addition and subtraction -expect - result = answer("What is 1 plus 5 minus -2?") - result == Ok(8) +expect { + result = answer("What is 1 plus 5 minus -2?") + result == Ok(8) +} # multiple subtraction -expect - result = answer("What is 20 minus 4 minus 13?") - result == Ok(3) +expect { + result = answer("What is 20 minus 4 minus 13?") + result == Ok(3) +} # subtraction then addition -expect - result = answer("What is 17 minus 6 plus 3?") - result == Ok(14) +expect { + result = answer("What is 17 minus 6 plus 3?") + result == Ok(14) +} # multiple multiplication -expect - result = answer("What is 2 multiplied by -2 multiplied by 3?") - result == Ok(-12) +expect { + result = answer("What is 2 multiplied by -2 multiplied by 3?") + result == Ok(-12) +} # addition and multiplication -expect - result = answer("What is -3 plus 7 multiplied by -2?") - result == Ok(-8) +expect { + result = answer("What is -3 plus 7 multiplied by -2?") + result == Ok(-8) +} # multiple division -expect - result = answer("What is -12 divided by 2 divided by -3?") - result == Ok(2) +expect { + result = answer("What is -12 divided by 2 divided by -3?") + result == Ok(2) +} # unknown operation -expect - result = answer("What is 52 cubed?") - Result.is_err(result) +expect { + result = answer("What is 52 cubed?") + result.is_err() +} # Non math question -expect - result = answer("Who is the President of the United States?") - Result.is_err(result) +expect { + result = answer("Who is the President of the United States?") + result.is_err() +} # reject problem missing an operand -expect - result = answer("What is 1 plus?") - Result.is_err(result) +expect { + result = answer("What is 1 plus?") + result.is_err() +} # reject problem with no operands or operators -expect - result = answer("What is?") - Result.is_err(result) +expect { + result = answer("What is?") + result.is_err() +} # reject two operations in a row -expect - result = answer("What is 1 plus plus 2?") - Result.is_err(result) +expect { + result = answer("What is 1 plus plus 2?") + result.is_err() +} # reject two numbers in a row -expect - result = answer("What is 1 plus 2 1?") - Result.is_err(result) +expect { + result = answer("What is 1 plus 2 1?") + result.is_err() +} # reject postfix notation -expect - result = answer("What is 1 2 plus?") - Result.is_err(result) +expect { + result = answer("What is 1 2 plus?") + result.is_err() +} # reject prefix notation -expect - result = answer("What is plus 1 2?") - Result.is_err(result) +expect { + result = answer("What is plus 1 2?") + result.is_err() +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/yacht/.meta/template.j2 b/exercises/practice/yacht/.meta/template.j2 index d4a1665c..af483896 100644 --- a/exercises/practice/yacht/.meta/template.j2 +++ b/exercises/practice/yacht/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} -expect - result = {{ case["input"]["dice"] | to_roc }} |> {{ case["property"] | to_snake }}({{ case["input"]["category"] | to_pascal }}) +expect { + result = {{ case["input"]["dice"] | to_roc }} -> {{ case["property"] | to_snake }}({{ case["input"]["category"] | to_pascal }}) result == {{ case["expected"] | to_roc }} +} {% endfor %} diff --git a/exercises/practice/yacht/yacht-test.roc b/exercises/practice/yacht/yacht-test.roc index 12764068..66f2f4c0 100644 --- a/exercises/practice/yacht/yacht-test.roc +++ b/exercises/practice/yacht/yacht-test.roc @@ -1,159 +1,184 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/yacht/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import Yacht exposing [score] # Yacht -expect - result = [5, 5, 5, 5, 5] |> score(Yacht) - result == 50 +expect { + result = [5, 5, 5, 5, 5]->score(Yacht) + result == 50 +} # Not Yacht -expect - result = [1, 3, 3, 2, 5] |> score(Yacht) - result == 0 +expect { + result = [1, 3, 3, 2, 5]->score(Yacht) + result == 0 +} # Ones -expect - result = [1, 1, 1, 3, 5] |> score(Ones) - result == 3 +expect { + result = [1, 1, 1, 3, 5]->score(Ones) + result == 3 +} # Ones, out of order -expect - result = [3, 1, 1, 5, 1] |> score(Ones) - result == 3 +expect { + result = [3, 1, 1, 5, 1]->score(Ones) + result == 3 +} # No ones -expect - result = [4, 3, 6, 5, 5] |> score(Ones) - result == 0 +expect { + result = [4, 3, 6, 5, 5]->score(Ones) + result == 0 +} # Twos -expect - result = [2, 3, 4, 5, 6] |> score(Twos) - result == 2 +expect { + result = [2, 3, 4, 5, 6]->score(Twos) + result == 2 +} # Fours -expect - result = [1, 4, 1, 4, 1] |> score(Fours) - result == 8 +expect { + result = [1, 4, 1, 4, 1]->score(Fours) + result == 8 +} # Yacht counted as threes -expect - result = [3, 3, 3, 3, 3] |> score(Threes) - result == 15 +expect { + result = [3, 3, 3, 3, 3]->score(Threes) + result == 15 +} # Yacht of 3s counted as fives -expect - result = [3, 3, 3, 3, 3] |> score(Fives) - result == 0 +expect { + result = [3, 3, 3, 3, 3]->score(Fives) + result == 0 +} # Fives -expect - result = [1, 5, 3, 5, 3] |> score(Fives) - result == 10 +expect { + result = [1, 5, 3, 5, 3]->score(Fives) + result == 10 +} # Sixes -expect - result = [2, 3, 4, 5, 6] |> score(Sixes) - result == 6 +expect { + result = [2, 3, 4, 5, 6]->score(Sixes) + result == 6 +} # Full house two small, three big -expect - result = [2, 2, 4, 4, 4] |> score(FullHouse) - result == 16 +expect { + result = [2, 2, 4, 4, 4]->score(FullHouse) + result == 16 +} # Full house three small, two big -expect - result = [5, 3, 3, 5, 3] |> score(FullHouse) - result == 19 +expect { + result = [5, 3, 3, 5, 3]->score(FullHouse) + result == 19 +} # Two pair is not a full house -expect - result = [2, 2, 4, 4, 5] |> score(FullHouse) - result == 0 +expect { + result = [2, 2, 4, 4, 5]->score(FullHouse) + result == 0 +} # Four of a kind is not a full house -expect - result = [1, 4, 4, 4, 4] |> score(FullHouse) - result == 0 +expect { + result = [1, 4, 4, 4, 4]->score(FullHouse) + result == 0 +} # Yacht is not a full house -expect - result = [2, 2, 2, 2, 2] |> score(FullHouse) - result == 0 +expect { + result = [2, 2, 2, 2, 2]->score(FullHouse) + result == 0 +} # Four of a Kind -expect - result = [6, 6, 4, 6, 6] |> score(FourOfAKind) - result == 24 +expect { + result = [6, 6, 4, 6, 6]->score(FourOfAKind) + result == 24 +} # Yacht can be scored as Four of a Kind -expect - result = [3, 3, 3, 3, 3] |> score(FourOfAKind) - result == 12 +expect { + result = [3, 3, 3, 3, 3]->score(FourOfAKind) + result == 12 +} # Full house is not Four of a Kind -expect - result = [3, 3, 3, 5, 5] |> score(FourOfAKind) - result == 0 +expect { + result = [3, 3, 3, 5, 5]->score(FourOfAKind) + result == 0 +} # Little Straight -expect - result = [3, 5, 4, 1, 2] |> score(LittleStraight) - result == 30 +expect { + result = [3, 5, 4, 1, 2]->score(LittleStraight) + result == 30 +} # Little Straight as Big Straight -expect - result = [1, 2, 3, 4, 5] |> score(BigStraight) - result == 0 +expect { + result = [1, 2, 3, 4, 5]->score(BigStraight) + result == 0 +} # Four in order but not a little straight -expect - result = [1, 1, 2, 3, 4] |> score(LittleStraight) - result == 0 +expect { + result = [1, 1, 2, 3, 4]->score(LittleStraight) + result == 0 +} # No pairs but not a little straight -expect - result = [1, 2, 3, 4, 6] |> score(LittleStraight) - result == 0 +expect { + result = [1, 2, 3, 4, 6]->score(LittleStraight) + result == 0 +} # Minimum is 1, maximum is 5, but not a little straight -expect - result = [1, 1, 3, 4, 5] |> score(LittleStraight) - result == 0 +expect { + result = [1, 1, 3, 4, 5]->score(LittleStraight) + result == 0 +} # Big Straight -expect - result = [4, 6, 2, 5, 3] |> score(BigStraight) - result == 30 +expect { + result = [4, 6, 2, 5, 3]->score(BigStraight) + result == 30 +} # Big Straight as little straight -expect - result = [6, 5, 4, 3, 2] |> score(LittleStraight) - result == 0 +expect { + result = [6, 5, 4, 3, 2]->score(LittleStraight) + result == 0 +} # No pairs but not a big straight -expect - result = [6, 5, 4, 3, 1] |> score(BigStraight) - result == 0 +expect { + result = [6, 5, 4, 3, 1]->score(BigStraight) + result == 0 +} # Choice -expect - result = [3, 3, 5, 6, 6] |> score(Choice) - result == 23 +expect { + result = [3, 3, 5, 6, 6]->score(Choice) + result == 23 +} # Yacht as choice -expect - result = [2, 2, 2, 2, 2] |> score(Choice) - result == 10 +expect { + result = [2, 2, 2, 2, 2]->score(Choice) + result == 10 +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} diff --git a/exercises/practice/zebra-puzzle/.meta/template.j2 b/exercises/practice/zebra-puzzle/.meta/template.j2 index 968597b5..096ab687 100644 --- a/exercises/practice/zebra-puzzle/.meta/template.j2 +++ b/exercises/practice/zebra-puzzle/.meta/template.j2 @@ -6,9 +6,10 @@ import {{ exercise | to_pascal }} exposing [owns_zebra, drinks_water] {% for case in cases -%} # {{ case["description"] }} -expect +expect { result = {{ case["property"] | to_snake }} result == Ok({{ case["expected"] }}) +} {% endfor %} diff --git a/exercises/practice/zebra-puzzle/zebra-puzzle-test.roc b/exercises/practice/zebra-puzzle/zebra-puzzle-test.roc index a8dd3c80..bfcf9b27 100644 --- a/exercises/practice/zebra-puzzle/zebra-puzzle-test.roc +++ b/exercises/practice/zebra-puzzle/zebra-puzzle-test.roc @@ -1,24 +1,22 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/zebra-puzzle/canonical-data.json -# File last updated on 2025-09-15 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") +# File last updated on 2026-06-13 import ZebraPuzzle exposing [owns_zebra, drinks_water] # resident who drinks water -expect - result = drinks_water - result == Ok(Norwegian) +expect { + result = drinks_water + result == Ok(Norwegian) +} # resident who owns zebra -expect - result = owns_zebra - result == Ok(Japanese) +expect { + result = owns_zebra + result == Ok(Japanese) +} +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) +} From d2181f02ef5c65b3975cbd5fdff2af7738c2b56f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 15:00:39 +1200 Subject: [PATCH 023/162] Fix to_roc_multiline_string Jinja filter, and skip non-dir files in exercises/practice --- bin/generate_tests.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/generate_tests.py b/bin/generate_tests.py index 0f8617c9..65835acc 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -157,9 +157,9 @@ def to_roc_multiline_string(lines: Union[str, List[str]]) -> str: elif len(lines) == 1: return to_roc_string(lines[0]) else: - return "\n".join( + return "\n" + "\n".join( [r'\\' + escape_roc_string_content(line) for line in lines] - ).replace("${", r"\${") + ).replace("${", r"\${") + "\n" def to_roc_tuple(values: Any): @@ -568,6 +568,8 @@ def generate( env.tests["error_case"] = error_case result = True for exercise in sorted(Path("exercises/practice").glob(exercise_glob)): + if not exercise.is_dir(): + continue if not generate_exercise(env, spec_path, exercise, check): result = False if stop_on_failure: From e493437c921c48ad7d3709dbcbc97007b8c89d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 15:32:29 +1200 Subject: [PATCH 024/162] Update exercise to new compiler: binary-search --- .../practice/binary-search/.meta/Example.roc | 39 +++++++++++-------- .../practice/binary-search/BinarySearch.roc | 11 +++--- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/exercises/practice/binary-search/.meta/Example.roc b/exercises/practice/binary-search/.meta/Example.roc index 12921133..ebf378da 100644 --- a/exercises/practice/binary-search/.meta/Example.roc +++ b/exercises/practice/binary-search/.meta/Example.roc @@ -1,18 +1,23 @@ -module [find] +BinarySearch :: {}.{ + find : List(U64), U64 -> Try(U64, [ValueWasNotFound(List(U64), U64)]) + find = |array, value| { + binary_search = |min_index, max_index| { + middle_index = (min_index + max_index) // 2 + middle_value = array.get(middle_index)? + if middle_value == value { + Ok(middle_index) + } else if min_index == max_index { + Err(ValueWasNotFound(array, value)) + } else if middle_value < value { + Ok(binary_search((middle_index + 1), max_index)?) + } else { + Ok(binary_search(min_index, (middle_index - 1))?) + } + } -find : List U64, U64 -> Result U64 [ValueWasNotFound (List U64) U64] -find = |array, value| - binary_search = |min_index, max_index| - middle_index = (min_index + max_index) // 2 - middle_value = List.get(array, middle_index)? - if middle_value == value then - Ok(middle_index) - else if min_index == max_index then - Err(ValueWasNotFound(array, value)) - else if middle_value < value then - Ok(binary_search((middle_index + 1), max_index)?) - else - Ok(binary_search(min_index, (middle_index - 1))?) - - binary_search(0, List.len(array)) - |> Result.on_err(|_| Err(ValueWasNotFound(array, value))) + binary_search(0, array.len()) + .map_err( + |_| ValueWasNotFound(array, value), + ) + } +} diff --git a/exercises/practice/binary-search/BinarySearch.roc b/exercises/practice/binary-search/BinarySearch.roc index e090506e..a9ca6600 100644 --- a/exercises/practice/binary-search/BinarySearch.roc +++ b/exercises/practice/binary-search/BinarySearch.roc @@ -1,5 +1,6 @@ -module [find] - -find : List U64, U64 -> Result U64 _ -find = |array, value| - crash("Please implement the 'find' function") +BinarySearch :: {}.{ + find : List(U64), U64 -> Try(U64, _) + find = |array, value| { + crash "Please implement the 'find' function" + } +} From a4de3832bba7b5d84a02e97acba9dcc43f3ae2eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 15:36:16 +1200 Subject: [PATCH 025/162] Change Bool.true/false to Bool.True/False --- exercises/practice/bowling/.meta/Example.roc | 8 ++++---- exercises/practice/connect/.meta/Example.roc | 4 ++-- exercises/practice/grep/.meta/Example.roc | 20 +++++++++---------- .../practice/isbn-verifier/.meta/Example.roc | 2 +- exercises/practice/luhn/.meta/Example.roc | 2 +- .../matching-brackets/.meta/Example.roc | 4 ++-- .../practice/pig-latin/.meta/Example.roc | 8 ++++---- .../practice/two-bucket/.meta/Example.roc | 2 +- .../practice/word-count/.meta/Example.roc | 12 +++++------ .../practice/zebra-puzzle/.meta/Example.roc | 8 ++++---- 10 files changed, 35 insertions(+), 35 deletions(-) diff --git a/exercises/practice/bowling/.meta/Example.roc b/exercises/practice/bowling/.meta/Example.roc index 88157f3d..353a8ef8 100644 --- a/exercises/practice/bowling/.meta/Example.roc +++ b/exercises/practice/bowling/.meta/Example.roc @@ -34,10 +34,10 @@ check_max_10_pins = |last_frame, pins| is_over : Game -> Bool is_over = |@Game({ frames })| when frames is - _ if List.len(frames) < 10 -> Bool.false - [.., Ball1(_)] | [.., Spare(_, _)] | [.., Strike] | [.., StrikeFill1(_)] -> Bool.false - [.., Ball2(_, _)] | [.., SpareFill(_)] | [.., StrikeFill2(_, _)] -> Bool.true - _ -> Bool.false + _ if List.len(frames) < 10 -> Bool.False + [.., Ball1(_)] | [.., Spare(_, _)] | [.., Strike] | [.., StrikeFill1(_)] -> Bool.False + [.., Ball2(_, _)] | [.., SpareFill(_)] | [.., StrikeFill2(_, _)] -> Bool.True + _ -> Bool.False roll : Game, U64 -> Result Game [MoreThan10Pins, GameOver] roll = |@Game({ frames }), pins| diff --git a/exercises/practice/connect/.meta/Example.roc b/exercises/practice/connect/.meta/Example.roc index 815be141..66ef840c 100644 --- a/exercises/practice/connect/.meta/Example.roc +++ b/exercises/practice/connect/.meta/Example.roc @@ -75,13 +75,13 @@ has_north_south_path = |board, stone| has_path_to_south : List Position, Set Position -> Bool has_path_to_south = |to_visit, visited| when to_visit is - [] -> Bool.false + [] -> Bool.False [position, .. as rest] -> is_player_stone = board |> get_cell(position) == Ok(stone) if is_player_stone and !(visited |> Set.contains(position)) then { x, y } = position if y + 1 == List.len(board) then - Bool.true # we've reached the South! + Bool.True # we've reached the South! else neighbors = [(-1, 0), (1, 0), (0, -1), (1, -1), (-1, 1), (0, 1)] diff --git a/exercises/practice/grep/.meta/Example.roc b/exercises/practice/grep/.meta/Example.roc index 51a3a958..17ae6d71 100644 --- a/exercises/practice/grep/.meta/Example.roc +++ b/exercises/practice/grep/.meta/Example.roc @@ -84,22 +84,22 @@ Config : { parse_flags : List Str -> Result Config _ parse_flags = |flags| default_config = { - display_line_numbers: Bool.false, - display_file_names: Bool.false, - ignore_case: Bool.false, - match_full_lines: Bool.false, - invert_results: Bool.false, + display_line_numbers: Bool.False, + display_file_names: Bool.False, + ignore_case: Bool.False, + match_full_lines: Bool.False, + invert_results: Bool.False, } List.walk_try( flags, default_config, |config, flag| when flag is - "-l" -> Ok({ config & display_file_names: Bool.true }) - "-n" -> Ok({ config & display_line_numbers: Bool.true }) - "-i" -> Ok({ config & ignore_case: Bool.true }) - "-x" -> Ok({ config & match_full_lines: Bool.true }) - "-v" -> Ok({ config & invert_results: Bool.true }) + "-l" -> Ok({ config & display_file_names: Bool.True }) + "-n" -> Ok({ config & display_line_numbers: Bool.True }) + "-i" -> Ok({ config & ignore_case: Bool.True }) + "-x" -> Ok({ config & match_full_lines: Bool.True }) + "-v" -> Ok({ config & invert_results: Bool.True }) _ -> Err(UnknownFlag(flag)), ) diff --git a/exercises/practice/isbn-verifier/.meta/Example.roc b/exercises/practice/isbn-verifier/.meta/Example.roc index 802b842f..7be61400 100644 --- a/exercises/practice/isbn-verifier/.meta/Example.roc +++ b/exercises/practice/isbn-verifier/.meta/Example.roc @@ -18,7 +18,7 @@ is_valid = |isbn| |> Str.to_utf8 |> List.drop_if(|char| char == '-') if List.len(chars) != 10 then - Bool.false + Bool.False else values = chars diff --git a/exercises/practice/luhn/.meta/Example.roc b/exercises/practice/luhn/.meta/Example.roc index 707029c7..7624f9df 100644 --- a/exercises/practice/luhn/.meta/Example.roc +++ b/exercises/practice/luhn/.meta/Example.roc @@ -13,7 +13,7 @@ valid = |number| |> List.sum |> Num.is_multiple_of(10) - _ -> Bool.false + _ -> Bool.False to_digits : Str -> Result (List U16) [IllegalCharacter] to_digits = |number| diff --git a/exercises/practice/matching-brackets/.meta/Example.roc b/exercises/practice/matching-brackets/.meta/Example.roc index de43b274..e83b532a 100644 --- a/exercises/practice/matching-brackets/.meta/Example.roc +++ b/exercises/practice/matching-brackets/.meta/Example.roc @@ -13,12 +13,12 @@ is_paired = |string| help((open_brackets |> List.append(next_char)), rest_chars) else if is_close(next_char) then when open_brackets is - [] -> Bool.false # missing opening bracket + [] -> Bool.False # missing opening bracket [.. as previous_opens, last_open] -> if is_match((last_open, next_char)) then help(previous_opens, rest_chars) else - Bool.false # mismatching brackets + Bool.False # mismatching brackets else help(open_brackets, rest_chars) diff --git a/exercises/practice/pig-latin/.meta/Example.roc b/exercises/practice/pig-latin/.meta/Example.roc index 83010251..19e47a17 100644 --- a/exercises/practice/pig-latin/.meta/Example.roc +++ b/exercises/practice/pig-latin/.meta/Example.roc @@ -5,10 +5,10 @@ is_vowel = |char| rule1_applies = |chars| when chars is - [c, ..] if is_vowel(c) -> Bool.true - ['x', 'r', ..] -> Bool.true - ['y', 't', ..] -> Bool.true - _ -> Bool.false + [c, ..] if is_vowel(c) -> Bool.True + ['x', 'r', ..] -> Bool.True + ['y', 't', ..] -> Bool.True + _ -> Bool.False pig_latin_swap = |chars| if rule1_applies(chars) then diff --git a/exercises/practice/two-bucket/.meta/Example.roc b/exercises/practice/two-bucket/.meta/Example.roc index fec53c7f..fef0db87 100644 --- a/exercises/practice/two-bucket/.meta/Example.roc +++ b/exercises/practice/two-bucket/.meta/Example.roc @@ -1,7 +1,7 @@ module [measure] ## Breadth-First Search finds the shortest path from the `start` node to any successful -## node (i.e., such that `success node == Bool.true`). The `neighbors` function must +## node (i.e., such that `success node == Bool.True`). The `neighbors` function must ## return the list of direct neighbors of a given node. bfs = |{ start, neighbors, success }| help = |to_visit, visited, from| diff --git a/exercises/practice/word-count/.meta/Example.roc b/exercises/practice/word-count/.meta/Example.roc index b66f1581..67691ff6 100644 --- a/exercises/practice/word-count/.meta/Example.roc +++ b/exercises/practice/word-count/.meta/Example.roc @@ -6,26 +6,26 @@ count_words = |sentence| |> Str.to_utf8 |> List.append(' ') # to ensure the last word is added |> List.walk( - { words: [], word: [], contraction_started: Bool.false }, + { words: [], word: [], contraction_started: Bool.False }, |state, char| { words, word, contraction_started } = state when char is c if c >= 'A' and c <= 'Z' -> - { words, word: word |> List.append((c - 'A' + 'a')), contraction_started: Bool.false } + { words, word: word |> List.append((c - 'A' + 'a')), contraction_started: Bool.False } c if c >= 'a' and c <= 'z' or c >= '0' and c <= '9' -> - { words, word: word |> List.append(c), contraction_started: Bool.false } + { words, word: word |> List.append(c), contraction_started: Bool.False } c -> if List.is_empty(word) then state else if c != '\'' or contraction_started then if contraction_started then - { words: words |> List.append((word |> List.drop_last(1))), word: [], contraction_started: Bool.false } + { words: words |> List.append((word |> List.drop_last(1))), word: [], contraction_started: Bool.False } else - { words: words |> List.append(word), word: [], contraction_started: Bool.false } + { words: words |> List.append(word), word: [], contraction_started: Bool.False } else - { words, word: word |> List.append(c), contraction_started: Bool.true }, + { words, word: word |> List.append(c), contraction_started: Bool.True }, ) |> .words |> List.drop_if(List.is_empty) diff --git a/exercises/practice/zebra-puzzle/.meta/Example.roc b/exercises/practice/zebra-puzzle/.meta/Example.roc index 0e78ee67..8acf41c8 100644 --- a/exercises/practice/zebra-puzzle/.meta/Example.roc +++ b/exercises/practice/zebra-puzzle/.meta/Example.roc @@ -129,7 +129,7 @@ rule6 = |houses| ivory_house = houses |> List.find_first_index(|house| house.color == 3) when (green_house, ivory_house) is (Ok(green_index), Ok(ivory_index)) -> green_index == ivory_index + 1 - _ -> Bool.true + _ -> Bool.True # The snail owner likes to go dancing. rule7 : List House -> Bool @@ -168,7 +168,7 @@ rule11 = |houses| fox_house = houses |> List.find_first_index(|house| house.animal == 3) when (reader_house, fox_house) is (Ok(reader_index), Ok(fox_index)) -> (reader_index == fox_index + 1) or (fox_index == reader_index + 1) - _ -> Bool.true + _ -> Bool.True # The painter's house is next to the house with the horse. rule12 : List House -> Bool @@ -177,7 +177,7 @@ rule12 = |houses| horse_house = houses |> List.find_first_index(|house| house.animal == 4) when (painter_house, horse_house) is (Ok(painter_index), Ok(horse_index)) -> (painter_index == horse_index + 1) or (horse_index == painter_index + 1) - _ -> Bool.true + _ -> Bool.True # The List person -> Bool rule13 : List House -> Bool @@ -196,7 +196,7 @@ rule15 = |houses| blue_house = houses |> List.find_first_index(|house| house.color == 5) when (norwegian_house, blue_house) is (Ok(norwegian_index), Ok(blue_index)) -> (norwegian_index == blue_index + 1) or (blue_index == norwegian_index + 1) - _ -> Bool.true + _ -> Bool.True rule16 : List House -> Bool rule16 = |houses| From af94237030f4710b8ca3edd58d11164f369ce51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 15:36:46 +1200 Subject: [PATCH 026/162] Replace Num.log(2), Num.pi, Num.e with float values --- exercises/practice/complex-numbers/.meta/plugins.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/practice/complex-numbers/.meta/plugins.py b/exercises/practice/complex-numbers/.meta/plugins.py index 5459402e..dfefe221 100644 --- a/exercises/practice/complex-numbers/.meta/plugins.py +++ b/exercises/practice/complex-numbers/.meta/plugins.py @@ -1,9 +1,9 @@ float_map = { - "e": "Num.e", - "ln(2)": "Num.log(2f64)", - "ln(2)/2": "Num.log(2f64) / 2", - "pi": "Num.pi", - "pi/4": "Num.pi / 4", + "e": "2.718281828459045.F64", + "ln(2)": "0.6931471805599453.F64", + "ln(2)/2": "0.6931471805599453.F64 / 2", + "pi": "3.141592653589793.F64", + "pi/4": "3.141592653589793.F64 / 4", } From 0f199c3527ac238daf8a5b75e6969b1d058061bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 15:55:25 +1200 Subject: [PATCH 027/162] Upgrade modules to the new type module format (first step) --- .../binary-search-tree/.meta/Example.roc | 25 +-- .../binary-search-tree/BinarySearchTree.roc | 17 +- exercises/practice/bob/.meta/Example.roc | 37 ++--- exercises/practice/bob/Bob.roc | 10 +- exercises/practice/bowling/.meta/Example.roc | 149 +++++++++--------- exercises/practice/bowling/Bowling.roc | 27 ++-- exercises/practice/change/.meta/Example.roc | 56 +++---- exercises/practice/change/Change.roc | 10 +- exercises/practice/clock/.meta/Example.roc | 47 +++--- exercises/practice/clock/Clock.roc | 29 ++-- .../collatz-conjecture/.meta/Example.roc | 24 +-- .../collatz-conjecture/CollatzConjecture.roc | 10 +- .../complex-numbers/.meta/Example.roc | 87 +++++----- .../complex-numbers/ComplexNumbers.roc | 59 +++---- exercises/practice/connect/.meta/Example.roc | 24 +-- exercises/practice/connect/Connect.roc | 10 +- .../practice/crypto-square/.meta/Example.roc | 62 ++++---- .../practice/crypto-square/CryptoSquare.roc | 10 +- .../practice/custom-set/.meta/Example.roc | 124 +++++++-------- exercises/practice/custom-set/CustomSet.roc | 103 ++++++------ exercises/practice/darts/.meta/Example.roc | 27 ++-- exercises/practice/darts/Darts.roc | 10 +- exercises/practice/diamond/.meta/Example.roc | 27 ++-- exercises/practice/diamond/Diamond.roc | 10 +- .../difference-of-squares/.meta/Example.roc | 31 ++-- .../DifferenceOfSquares.roc | 22 +-- exercises/practice/dominoes/.meta/Example.roc | 79 ++++++---- exercises/practice/dominoes/Dominoes.roc | 11 +- .../practice/eliuds-eggs/.meta/Example.roc | 22 +-- exercises/practice/eliuds-eggs/EliudsEggs.roc | 10 +- .../practice/error-handling/.meta/Example.roc | 83 +++++----- .../practice/error-handling/ErrorHandling.roc | 35 ++-- exercises/practice/etl/.meta/Example.roc | 23 +-- exercises/practice/etl/Etl.roc | 10 +- .../practice/flatten-array/.meta/Example.roc | 17 +- .../practice/flatten-array/FlattenArray.roc | 11 +- .../practice/flower-field/.meta/Example.roc | 61 +++---- .../practice/flower-field/FlowerField.roc | 10 +- .../practice/food-chain/.meta/Example.roc | 12 +- exercises/practice/food-chain/FoodChain.roc | 10 +- exercises/practice/forth/.meta/Example.roc | 23 +-- exercises/practice/forth/Forth.roc | 11 +- .../practice/gigasecond/.meta/Example.roc | 15 +- exercises/practice/gigasecond/Gigasecond.roc | 10 +- .../practice/go-counting/.meta/Example.roc | 81 +++++----- exercises/practice/go-counting/GoCounting.roc | 19 +-- exercises/practice/grains/.meta/Example.roc | 22 +-- exercises/practice/grains/Grains.roc | 16 +- exercises/practice/grep/.meta/Example.roc | 65 ++++---- exercises/practice/grep/Grep.roc | 11 +- exercises/practice/hamming/.meta/Example.roc | 34 ++-- exercises/practice/hamming/Hamming.roc | 10 +- .../practice/hello-world/.meta/Example.roc | 8 +- exercises/practice/hello-world/HelloWorld.roc | 8 +- .../practice/hexadecimal/.meta/Example.roc | 37 ++--- .../practice/hexadecimal/Hexadecimal.roc | 10 +- .../practice/high-scores/.meta/Example.roc | 19 +-- exercises/practice/high-scores/HighScores.roc | 23 +-- exercises/practice/house/.meta/Example.roc | 15 +- exercises/practice/house/House.roc | 10 +- .../practice/isbn-verifier/.meta/Example.roc | 33 ++-- .../practice/isbn-verifier/IsbnVerifier.roc | 10 +- exercises/practice/isogram/.meta/Example.roc | 20 +-- exercises/practice/isogram/Isogram.roc | 10 +- .../killer-sudoku-helper/.meta/Example.roc | 49 +++--- .../KillerSudokuHelper.roc | 11 +- .../kindergarten-garden/.meta/Example.roc | 35 ++-- .../KindergartenGarden.roc | 11 +- exercises/practice/knapsack/.meta/Example.roc | 27 ++-- exercises/practice/knapsack/Knapsack.roc | 11 +- .../largest-series-product/.meta/Example.roc | 50 +++--- .../LargestSeriesProduct.roc | 10 +- exercises/practice/leap/.meta/Example.roc | 10 +- exercises/practice/leap/Leap.roc | 10 +- exercises/practice/list-ops/.meta/Example.roc | 126 +++++++-------- exercises/practice/list-ops/ListOps.roc | 66 ++++---- exercises/practice/luhn/.meta/Example.roc | 30 ++-- exercises/practice/luhn/Luhn.roc | 10 +- .../matching-brackets/.meta/Example.roc | 48 +++--- .../matching-brackets/MatchingBrackets.roc | 10 +- exercises/practice/matrix/.meta/Example.roc | 37 ++--- exercises/practice/matrix/Matrix.roc | 16 +- exercises/practice/meetup/.meta/Example.roc | 51 +++--- exercises/practice/meetup/Meetup.roc | 19 +-- .../practice/micro-blog/.meta/Example.roc | 19 +-- exercises/practice/micro-blog/MicroBlog.roc | 11 +- .../practice/minesweeper/.meta/Example.roc | 61 +++---- .../practice/minesweeper/Minesweeper.roc | 10 +- .../practice/nth-prime/.meta/Example.roc | 56 +++---- exercises/practice/nth-prime/NthPrime.roc | 10 +- .../nucleotide-count/.meta/Example.roc | 30 ++-- .../nucleotide-count/NucleotideCount.roc | 10 +- .../practice/ocr-numbers/.meta/Example.roc | 39 ++--- exercises/practice/ocr-numbers/OcrNumbers.roc | 10 +- exercises/practice/octal/.meta/Example.roc | 37 ++--- exercises/practice/octal/Octal.roc | 10 +- .../palindrome-products/.meta/Example.roc | 92 +++++------ .../PalindromeProducts.roc | 16 +- exercises/practice/pangram/.meta/Example.roc | 21 +-- exercises/practice/pangram/Pangram.roc | 10 +- .../pascals-triangle/.meta/Example.roc | 20 +-- .../pascals-triangle/PascalsTriangle.roc | 10 +- .../perfect-numbers/.meta/Example.roc | 23 +-- .../perfect-numbers/PerfectNumbers.roc | 10 +- .../practice/phone-number/.meta/Example.roc | 34 ++-- .../practice/phone-number/PhoneNumber.roc | 10 +- .../practice/pig-latin/.meta/Example.roc | 17 +- exercises/practice/pig-latin/PigLatin.roc | 10 +- exercises/practice/poker/.meta/Example.roc | 29 ++-- exercises/practice/poker/Poker.roc | 10 +- .../practice/prime-factors/.meta/Example.roc | 30 ++-- .../practice/prime-factors/PrimeFactors.roc | 10 +- .../protein-translation/.meta/Example.roc | 27 ++-- .../ProteinTranslation.roc | 11 +- exercises/practice/proverb/.meta/Example.roc | 32 ++-- exercises/practice/proverb/Proverb.roc | 10 +- .../pythagorean-triplet/.meta/Example.roc | 43 ++--- .../PythagoreanTriplet.roc | 11 +- .../practice/queen-attack/.meta/Example.roc | 49 +++--- .../practice/queen-attack/QueenAttack.roc | 29 ++-- .../rail-fence-cipher/.meta/Example.roc | 19 +-- .../rail-fence-cipher/RailFenceCipher.roc | 16 +- .../practice/raindrops/.meta/Example.roc | 24 +-- exercises/practice/raindrops/Raindrops.roc | 10 +- .../rational-numbers/.meta/Example.roc | 110 ++++++------- .../rational-numbers/RationalNumbers.roc | 54 +++---- .../practice/rectangles/.meta/Example.roc | 61 +++---- exercises/practice/rectangles/Rectangles.roc | 10 +- .../resistor-color-duo/.meta/Example.roc | 11 +- .../resistor-color-duo/ResistorColorDuo.roc | 11 +- .../practice/resistor-color/.meta/Example.roc | 20 +-- .../practice/resistor-color/ResistorColor.roc | 16 +- exercises/practice/rest-api/.meta/Example.roc | 45 +++--- exercises/practice/rest-api/RestApi.roc | 19 +-- .../practice/reverse-string/.meta/Example.roc | 47 +++--- .../practice/reverse-string/ReverseString.roc | 16 +- .../rna-transcription/.meta/Example.roc | 27 ++-- .../rna-transcription/RnaTranscription.roc | 10 +- .../robot-simulator/.meta/Example.roc | 69 ++++---- .../robot-simulator/RobotSimulator.roc | 19 +-- .../practice/roman-numerals/.meta/Example.roc | 34 ++-- .../practice/roman-numerals/RomanNumerals.roc | 10 +- .../rotational-cipher/.meta/Example.roc | 19 +-- .../rotational-cipher/RotationalCipher.roc | 10 +- .../run-length-encoding/.meta/Example.roc | 94 +++++------ .../run-length-encoding/RunLengthEncoding.roc | 16 +- .../practice/saddle-points/.meta/Example.roc | 83 +++++----- .../practice/saddle-points/SaddlePoints.roc | 11 +- exercises/practice/say/.meta/Example.roc | 73 ++++----- exercises/practice/say/Say.roc | 10 +- .../practice/scrabble-score/.meta/Example.roc | 16 +- .../practice/scrabble-score/ScrabbleScore.roc | 10 +- .../secret-handshake/.meta/Example.roc | 28 ++-- .../secret-handshake/SecretHandshake.roc | 10 +- exercises/practice/series/.meta/Example.roc | 40 ++--- exercises/practice/series/Series.roc | 10 +- .../practice/sgf-parsing/.meta/Example.roc | 11 +- exercises/practice/sgf-parsing/SgfParsing.roc | 11 +- exercises/practice/sieve/.meta/Example.roc | 40 ++--- exercises/practice/sieve/Sieve.roc | 10 +- .../simple-linked-list/.meta/Example.roc | 61 +++---- .../simple-linked-list/SimpleLinkedList.roc | 51 +++--- .../practice/space-age/.meta/Example.roc | 17 +- exercises/practice/space-age/SpaceAge.roc | 11 +- .../practice/spiral-matrix/.meta/Example.roc | 58 +++---- .../practice/spiral-matrix/SpiralMatrix.roc | 10 +- .../practice/square-root/.meta/Example.roc | 34 ++-- exercises/practice/square-root/SquareRoot.roc | 10 +- exercises/practice/strain/.meta/Example.roc | 48 +++--- exercises/practice/strain/Strain.roc | 16 +- exercises/practice/sublist/.meta/Example.roc | 52 +++--- exercises/practice/sublist/Sublist.roc | 10 +- .../sum-of-multiples/.meta/Example.roc | 26 +-- .../sum-of-multiples/SumOfMultiples.roc | 10 +- .../practice/tournament/.meta/Example.roc | 55 +++---- exercises/practice/tournament/Tournament.roc | 10 +- .../practice/transpose/.meta/Example.roc | 44 +++--- exercises/practice/transpose/Transpose.roc | 10 +- exercises/practice/triangle/.meta/Example.roc | 27 ++-- exercises/practice/triangle/Triangle.roc | 22 +-- .../practice/two-bucket/.meta/Example.roc | 117 +++++++------- exercises/practice/two-bucket/TwoBucket.roc | 16 +- exercises/practice/two-fer/.meta/Example.roc | 14 +- exercises/practice/two-fer/TwoFer.roc | 10 +- .../.meta/Example.roc | 48 +++--- .../VariableLengthQuantity.roc | 16 +- .../practice/word-count/.meta/Example.roc | 88 +++++------ exercises/practice/word-count/WordCount.roc | 10 +- .../practice/word-search/.meta/Example.roc | 101 ++++++------ exercises/practice/word-search/WordSearch.roc | 11 +- exercises/practice/wordy/.meta/Example.roc | 43 ++--- exercises/practice/wordy/Wordy.roc | 10 +- exercises/practice/yacht/.meta/Example.roc | 41 ++--- exercises/practice/yacht/Yacht.roc | 11 +- .../practice/zebra-puzzle/.meta/Example.roc | 19 +-- .../practice/zebra-puzzle/ZebraPuzzle.roc | 17 +- 196 files changed, 2928 insertions(+), 2851 deletions(-) diff --git a/exercises/practice/binary-search-tree/.meta/Example.roc b/exercises/practice/binary-search-tree/.meta/Example.roc index 26a0cafb..072c28a9 100644 --- a/exercises/practice/binary-search-tree/.meta/Example.roc +++ b/exercises/practice/binary-search-tree/.meta/Example.roc @@ -1,10 +1,18 @@ -module [from_list, to_list] +BinarySearchTree :: {}.{ + from_list : List U64 -> BinaryTree + from_list = |data| + data |> List.walk(Nil, insert) + + to_list : BinaryTree -> List U64 + to_list = |tree| + when tree is + Nil -> [] + Node(node) -> + node.left |> to_list |> List.append(node.value) |> List.concat((node.right |> to_list)) +} -BinaryTree : [Nil, Node { value : U64, left : BinaryTree, right : BinaryTree }] -from_list : List U64 -> BinaryTree -from_list = |data| - data |> List.walk(Nil, insert) +BinaryTree : [Nil, Node { value : U64, left : BinaryTree, right : BinaryTree }] insert : BinaryTree, U64 -> BinaryTree insert = |tree, value| @@ -15,10 +23,3 @@ insert = |tree, value| Node({ node & left: (node.left |> insert(value)) }) else Node({ node & right: (node.right |> insert(value)) }) - -to_list : BinaryTree -> List U64 -to_list = |tree| - when tree is - Nil -> [] - Node(node) -> - node.left |> to_list |> List.append(node.value) |> List.concat((node.right |> to_list)) diff --git a/exercises/practice/binary-search-tree/BinarySearchTree.roc b/exercises/practice/binary-search-tree/BinarySearchTree.roc index 8d8ef7f5..511a6298 100644 --- a/exercises/practice/binary-search-tree/BinarySearchTree.roc +++ b/exercises/practice/binary-search-tree/BinarySearchTree.roc @@ -1,11 +1,12 @@ -module [from_list, to_list] +BinarySearchTree :: {}.{ + from_list : List U64 -> BinaryTree + from_list = |data| + crash("Please implement the 'from_list' function") -BinaryTree : [Nil, Node { value : U64, left : BinaryTree, right : BinaryTree }] + to_list : BinaryTree -> List U64 + to_list = |tree| + crash("Please implement the 'to_list' function") +} -from_list : List U64 -> BinaryTree -from_list = |data| - crash("Please implement the 'from_list' function") -to_list : BinaryTree -> List U64 -to_list = |tree| - crash("Please implement the 'to_list' function") +BinaryTree : [Nil, Node { value : U64, left : BinaryTree, right : BinaryTree }] diff --git a/exercises/practice/bob/.meta/Example.roc b/exercises/practice/bob/.meta/Example.roc index 74d8346e..f832c490 100644 --- a/exercises/practice/bob/.meta/Example.roc +++ b/exercises/practice/bob/.meta/Example.roc @@ -1,4 +1,22 @@ -module [response] +Bob :: {}.{ + response : Str -> Str + response = |hey_bob| + trimmed = Str.trim(hey_bob) + if trimmed == "" then + "Fine. Be that way!" + else + is_q = is_question(trimmed) + is_y = is_yelling(trimmed) + if is_q and is_y then + "Calm down, I know what I'm doing!" + else if is_q then + "Sure." + else if is_y then + "Whoa, chill out!" + else + "Whatever." +} + is_question = |hey_bob| Str.ends_with(hey_bob, "?") @@ -10,20 +28,3 @@ is_yelling = |hey_bob| c >= 'A' and c <= 'Z' chars = Str.to_utf8(hey_bob) (chars |> List.any(is_upper)) and !(chars |> List.any(is_lower)) - -response : Str -> Str -response = |hey_bob| - trimmed = Str.trim(hey_bob) - if trimmed == "" then - "Fine. Be that way!" - else - is_q = is_question(trimmed) - is_y = is_yelling(trimmed) - if is_q and is_y then - "Calm down, I know what I'm doing!" - else if is_q then - "Sure." - else if is_y then - "Whoa, chill out!" - else - "Whatever." diff --git a/exercises/practice/bob/Bob.roc b/exercises/practice/bob/Bob.roc index 5c08722a..79c1184f 100644 --- a/exercises/practice/bob/Bob.roc +++ b/exercises/practice/bob/Bob.roc @@ -1,5 +1,5 @@ -module [response] - -response : Str -> Str -response = |hey_bob| - crash("Please implement the 'response' function") +Bob :: {}.{ + response : Str -> Str + response = |hey_bob| + crash("Please implement the 'response' function") +} diff --git a/exercises/practice/bowling/.meta/Example.roc b/exercises/practice/bowling/.meta/Example.roc index 353a8ef8..85228ab2 100644 --- a/exercises/practice/bowling/.meta/Example.roc +++ b/exercises/practice/bowling/.meta/Example.roc @@ -1,4 +1,78 @@ -module [Game, create, roll, score] +Bowling :: {}.{ + create : { previous_rolls ?? List U64 } -> Result Game [MoreThan10Pins, GameOver] + create = |{ previous_rolls ?? [] }| + List.walk_try( + previous_rolls, + @Game({ frames: [] }), + |game, pins| + roll(game, pins), + ) + + roll : Game, U64 -> Result Game [MoreThan10Pins, GameOver] + roll = |@Game({ frames }), pins| + if @Game({ frames }) |> is_over then + Err(GameOver) + else + last_frame = frames |> List.last |> Result.with_default(Ball2(0, 0)) + check_max_10_pins(last_frame, pins)? + updated_frames = + when last_frame is + Ball1(pins1) -> + frames + |> List.drop_last(1) + |> List.append( + ( + if pins1 + pins == 10 then + Spare(pins1, pins) + else + Ball2(pins1, pins) + ), + ) + + StrikeFill1(pins1) -> + frames |> List.drop_last(1) |> List.append(StrikeFill2(pins1, pins)) + + Ball2(_, _) | Spare(_, _) | Strike if List.len(frames) < 10 -> + if pins == 10 then + frames |> List.append(Strike) + else + frames |> List.append(Ball1(pins)) + + Spare(_, _) -> + frames |> List.append(SpareFill(pins)) + + Strike -> + frames |> List.append(StrikeFill1(pins)) + + Ball2(_, _) | SpareFill(_) | StrikeFill2(_, _) -> + crash("Impossible, an unfinished game cannot have these in the last frame after the 10th frame") + @Game({ frames: updated_frames }) |> Ok + + score : Game -> Result U64 [GameIsNotOver] + score = |@Game({ frames })| + if @Game({ frames }) |> is_over then + frames + |> map_triplets( + |frame1, frame2, frame3| + when frame1 is + Ball2(pins1, pins2) -> pins1 + pins2 + Spare(pins1, pins2) -> pins1 + pins2 + first_pins(frame2) + Strike -> + when frame2 is + Strike -> 10 + 10 + first_pins(frame3) + _ -> 10 + total_pins(frame2) + + SpareFill(_) -> 0 # already counted in the Spare + StrikeFill2(_, _) -> 0 # already counter in the Strike + Ball1(_) | StrikeFill1(_) -> + crash("Impossible, unfinished frames should not exist in a finished game"), + ) + |> List.sum + |> Ok + else + Err(GameIsNotOver) +} + Frame : [ Ball1 U64, # unfinished frame @@ -12,15 +86,6 @@ Frame : [ Game := { frames : List Frame } -create : { previous_rolls ?? List U64 } -> Result Game [MoreThan10Pins, GameOver] -create = |{ previous_rolls ?? [] }| - List.walk_try( - previous_rolls, - @Game({ frames: [] }), - |game, pins| - roll(game, pins), - ) - check_max_10_pins : Frame, U64 -> Result {} [MoreThan10Pins, GameOver] check_max_10_pins = |last_frame, pins| when last_frame is @@ -39,46 +104,6 @@ is_over = |@Game({ frames })| [.., Ball2(_, _)] | [.., SpareFill(_)] | [.., StrikeFill2(_, _)] -> Bool.True _ -> Bool.False -roll : Game, U64 -> Result Game [MoreThan10Pins, GameOver] -roll = |@Game({ frames }), pins| - if @Game({ frames }) |> is_over then - Err(GameOver) - else - last_frame = frames |> List.last |> Result.with_default(Ball2(0, 0)) - check_max_10_pins(last_frame, pins)? - updated_frames = - when last_frame is - Ball1(pins1) -> - frames - |> List.drop_last(1) - |> List.append( - ( - if pins1 + pins == 10 then - Spare(pins1, pins) - else - Ball2(pins1, pins) - ), - ) - - StrikeFill1(pins1) -> - frames |> List.drop_last(1) |> List.append(StrikeFill2(pins1, pins)) - - Ball2(_, _) | Spare(_, _) | Strike if List.len(frames) < 10 -> - if pins == 10 then - frames |> List.append(Strike) - else - frames |> List.append(Ball1(pins)) - - Spare(_, _) -> - frames |> List.append(SpareFill(pins)) - - Strike -> - frames |> List.append(StrikeFill1(pins)) - - Ball2(_, _) | SpareFill(_) | StrikeFill2(_, _) -> - crash("Impossible, an unfinished game cannot have these in the last frame after the 10th frame") - @Game({ frames: updated_frames }) |> Ok - map_triplets : List Frame, (Frame, Frame, Frame -> U64) -> List U64 map_triplets = |list, score_func| get_or_0 = |index| list |> List.get(index) |> Result.with_default(Ball2(0, 0)) @@ -100,27 +125,3 @@ total_pins = |frame| Ball2(pins1, pins2) | Spare(pins1, pins2) | StrikeFill2(pins1, pins2) -> pins1 + pins2 Ball1(pins) | SpareFill(pins) | StrikeFill1(pins) -> pins Strike -> 10 - -score : Game -> Result U64 [GameIsNotOver] -score = |@Game({ frames })| - if @Game({ frames }) |> is_over then - frames - |> map_triplets( - |frame1, frame2, frame3| - when frame1 is - Ball2(pins1, pins2) -> pins1 + pins2 - Spare(pins1, pins2) -> pins1 + pins2 + first_pins(frame2) - Strike -> - when frame2 is - Strike -> 10 + 10 + first_pins(frame3) - _ -> 10 + total_pins(frame2) - - SpareFill(_) -> 0 # already counted in the Spare - StrikeFill2(_, _) -> 0 # already counter in the Strike - Ball1(_) | StrikeFill1(_) -> - crash("Impossible, unfinished frames should not exist in a finished game"), - ) - |> List.sum - |> Ok - else - Err(GameIsNotOver) diff --git a/exercises/practice/bowling/Bowling.roc b/exercises/practice/bowling/Bowling.roc index a6a49b09..3dc0ee45 100644 --- a/exercises/practice/bowling/Bowling.roc +++ b/exercises/practice/bowling/Bowling.roc @@ -1,4 +1,17 @@ -module [Game, create, roll, score] +Bowling :: {}.{ + create : { previous_rolls ?? List U64 } -> Result Game _ + create = |{ previous_rolls ?? [] }| + crash("Please implement the 'create' function") + + roll : Game, U64 -> Result Game _ + roll = |game, pins| + crash("Please implement the 'roll' function") + + score : Game -> Result U64 _ + score = |finished_game| + crash("Please implement the 'score' function") +} + Game := { # TODO: change this opaque type however you need @@ -7,15 +20,3 @@ Game := { todo3 : U64, # etc. } - -create : { previous_rolls ?? List U64 } -> Result Game _ -create = |{ previous_rolls ?? [] }| - crash("Please implement the 'create' function") - -roll : Game, U64 -> Result Game _ -roll = |game, pins| - crash("Please implement the 'roll' function") - -score : Game -> Result U64 _ -score = |finished_game| - crash("Please implement the 'score' function") diff --git a/exercises/practice/change/.meta/Example.roc b/exercises/practice/change/.meta/Example.roc index dd32c0dd..e0c933af 100644 --- a/exercises/practice/change/.meta/Example.roc +++ b/exercises/practice/change/.meta/Example.roc @@ -1,30 +1,30 @@ -module [find_fewest_coins] +Change :: {}.{ + find_fewest_coins : List U64, U64 -> Result (List U64) [NotFound] + find_fewest_coins = |coins, target| + help = |sorted_coins, sub_target, max_length| + if sub_target == 0 then + Ok([]) + else if max_length == 0 then + Err(NotFound) + else + when sorted_coins is + [] -> Err(NotFound) + [largest_coin, .. as other_coins] -> + if largest_coin == sub_target then + Ok([largest_coin]) + else if largest_coin < sub_target then + when help(sorted_coins, (sub_target - largest_coin), (max_length - 1)) is + Ok(other_coins_with) -> + coins_with = other_coins_with |> List.append(largest_coin) + when help(other_coins, sub_target, (List.len(coins_with) - 1)) is + Ok(coins_without) -> Ok(coins_without) + Err(NotFound) -> Ok(coins_with) -find_fewest_coins : List U64, U64 -> Result (List U64) [NotFound] -find_fewest_coins = |coins, target| - help = |sorted_coins, sub_target, max_length| - if sub_target == 0 then - Ok([]) - else if max_length == 0 then - Err(NotFound) - else - when sorted_coins is - [] -> Err(NotFound) - [largest_coin, .. as other_coins] -> - if largest_coin == sub_target then - Ok([largest_coin]) - else if largest_coin < sub_target then - when help(sorted_coins, (sub_target - largest_coin), (max_length - 1)) is - Ok(other_coins_with) -> - coins_with = other_coins_with |> List.append(largest_coin) - when help(other_coins, sub_target, (List.len(coins_with) - 1)) is - Ok(coins_without) -> Ok(coins_without) - Err(NotFound) -> Ok(coins_with) + Err(NotFound) -> help(other_coins, sub_target, max_length) + else + help(other_coins, sub_target, max_length) - Err(NotFound) -> help(other_coins, sub_target, max_length) - else - help(other_coins, sub_target, max_length) - - help((coins |> List.sort_desc), target, Num.max_u64)? - |> List.sort_asc - |> Ok + help((coins |> List.sort_desc), target, Num.max_u64)? + |> List.sort_asc + |> Ok +} diff --git a/exercises/practice/change/Change.roc b/exercises/practice/change/Change.roc index f1ac7871..d8480373 100644 --- a/exercises/practice/change/Change.roc +++ b/exercises/practice/change/Change.roc @@ -1,5 +1,5 @@ -module [find_fewest_coins] - -find_fewest_coins : List U64, U64 -> Result (List U64) _ -find_fewest_coins = |coins, target| - crash("Please implement the 'find_fewest_coins' function") +Change :: {}.{ + find_fewest_coins : List U64, U64 -> Result (List U64) _ + find_fewest_coins = |coins, target| + crash("Please implement the 'find_fewest_coins' function") +} diff --git a/exercises/practice/clock/.meta/Example.roc b/exercises/practice/clock/.meta/Example.roc index 1bbbd56e..47c7bd1b 100644 --- a/exercises/practice/clock/.meta/Example.roc +++ b/exercises/practice/clock/.meta/Example.roc @@ -1,29 +1,30 @@ -module [create, to_str, add, subtract] +Clock :: {}.{ + create : { hours ?? I64, minutes ?? I64 }* -> Clock + create = |{ hours ?? 0, minutes ?? 0 }| + hours24 = (hours % 24 + minutes // 60) % 24 + minutes60 = minutes % 60 + total_minutes = ((hours24 * 60 + minutes60) % minutes_per_day + minutes_per_day) % minutes_per_day + hh = total_minutes // 60 |> Num.to_u8 + mm = total_minutes % 60 |> Num.to_u8 + { hour: hh, minute: mm } -Clock : { hour : U8, minute : U8 } + to_str : Clock -> Str + to_str = |{ hour, minute }| + zero_padded = |num| "${if num < 10 then "0" else ""}${num |> Num.to_str}" + "${zero_padded(hour)}:${zero_padded(minute)}" -minutes_per_day = 24 * 60 + add : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock + add = |{ hour, minute }, { hours ?? 0, minutes ?? 0 }| + total_hours = Num.to_i64(hour) + (hours % 24 + minutes // 60) + total_minutes = Num.to_i64(minute) + minutes % 60 + create({ hours: total_hours, minutes: total_minutes }) -create : { hours ?? I64, minutes ?? I64 }* -> Clock -create = |{ hours ?? 0, minutes ?? 0 }| - hours24 = (hours % 24 + minutes // 60) % 24 - minutes60 = minutes % 60 - total_minutes = ((hours24 * 60 + minutes60) % minutes_per_day + minutes_per_day) % minutes_per_day - hh = total_minutes // 60 |> Num.to_u8 - mm = total_minutes % 60 |> Num.to_u8 - { hour: hh, minute: mm } + subtract : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock + subtract = |clock, { hours ?? 0, minutes ?? 0 }| + clock |> add({ hours: -(hours % 24 + minutes // 60), minutes: -(minutes % 60) }) +} -to_str : Clock -> Str -to_str = |{ hour, minute }| - zero_padded = |num| "${if num < 10 then "0" else ""}${num |> Num.to_str}" - "${zero_padded(hour)}:${zero_padded(minute)}" -add : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock -add = |{ hour, minute }, { hours ?? 0, minutes ?? 0 }| - total_hours = Num.to_i64(hour) + (hours % 24 + minutes // 60) - total_minutes = Num.to_i64(minute) + minutes % 60 - create({ hours: total_hours, minutes: total_minutes }) +Clock : { hour : U8, minute : U8 } -subtract : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock -subtract = |clock, { hours ?? 0, minutes ?? 0 }| - clock |> add({ hours: -(hours % 24 + minutes // 60), minutes: -(minutes % 60) }) +minutes_per_day = 24 * 60 diff --git a/exercises/practice/clock/Clock.roc b/exercises/practice/clock/Clock.roc index e08c7e53..200e2ba4 100644 --- a/exercises/practice/clock/Clock.roc +++ b/exercises/practice/clock/Clock.roc @@ -1,20 +1,21 @@ -module [create, to_str, add, subtract] +Clock :: {}.{ + create : { hours ?? I64, minutes ?? I64 }* -> Clock + create = |{ hours ?? 0, minutes ?? 0 }| + crash("Please implement the 'create' function") -Clock : { hour : U8, minute : U8 } + to_str : Clock -> Str + to_str = |{ hour, minute }| + crash("Please implement the 'to_str' function") -create : { hours ?? I64, minutes ?? I64 }* -> Clock -create = |{ hours ?? 0, minutes ?? 0 }| - crash("Please implement the 'create' function") + add : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock + add = |{ hour, minute }, { hours ?? 0, minutes ?? 0 }| + crash("Please implement the 'add' function") -to_str : Clock -> Str -to_str = |{ hour, minute }| - crash("Please implement the 'to_str' function") + subtract : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock + subtract = |clock, { hours ?? 0, minutes ?? 0 }| + crash("Please implement the 'subtract' function") -add : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock -add = |{ hour, minute }, { hours ?? 0, minutes ?? 0 }| - crash("Please implement the 'add' function") +} -subtract : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock -subtract = |clock, { hours ?? 0, minutes ?? 0 }| - crash("Please implement the 'subtract' function") +Clock : { hour : U8, minute : U8 } diff --git a/exercises/practice/collatz-conjecture/.meta/Example.roc b/exercises/practice/collatz-conjecture/.meta/Example.roc index 907382c2..8bad7313 100644 --- a/exercises/practice/collatz-conjecture/.meta/Example.roc +++ b/exercises/practice/collatz-conjecture/.meta/Example.roc @@ -1,12 +1,12 @@ -module [steps] - -steps : U64 -> Result U64 [NumberArgWasZero] -steps = |number| - if number <= 0 then - Err(NumberArgWasZero) - else if number == 1 then - Ok(0) - else if Num.is_even(number) then - Ok(steps(number // 2)? + 1) - else - Ok(steps(3 * number + 1)? + 1) +CollatzConjecture :: {}.{ + steps : U64 -> Result U64 [NumberArgWasZero] + steps = |number| + if number <= 0 then + Err(NumberArgWasZero) + else if number == 1 then + Ok(0) + else if Num.is_even(number) then + Ok(steps(number // 2)? + 1) + else + Ok(steps(3 * number + 1)? + 1) +} diff --git a/exercises/practice/collatz-conjecture/CollatzConjecture.roc b/exercises/practice/collatz-conjecture/CollatzConjecture.roc index f639c340..30942a7a 100644 --- a/exercises/practice/collatz-conjecture/CollatzConjecture.roc +++ b/exercises/practice/collatz-conjecture/CollatzConjecture.roc @@ -1,5 +1,5 @@ -module [steps] - -steps : U64 -> Result U64 _ -steps = |number| - crash("Please implement the 'steps' function") +CollatzConjecture :: {}.{ + steps : U64 -> Result U64 _ + steps = |number| + crash("Please implement the 'steps' function") +} diff --git a/exercises/practice/complex-numbers/.meta/Example.roc b/exercises/practice/complex-numbers/.meta/Example.roc index a0e872f4..c21e296c 100644 --- a/exercises/practice/complex-numbers/.meta/Example.roc +++ b/exercises/practice/complex-numbers/.meta/Example.roc @@ -1,53 +1,54 @@ -module [real, imaginary, add, sub, mul, div, conjugate, abs, exp] +ComplexNumbers :: {}.{ + real : Complex -> F64 + real = |z| z.re -Complex : { re : F64, im : F64 } - -real : Complex -> F64 -real = |z| z.re - -imaginary : Complex -> F64 -imaginary = |z| z.im + imaginary : Complex -> F64 + imaginary = |z| z.im -add : Complex, Complex -> Complex -add = |{ re: a, im: b }, { re: c, im: d }| { - re: a + c, - im: b + d, -} + add : Complex, Complex -> Complex + add = |{ re: a, im: b }, { re: c, im: d }| { + re: a + c, + im: b + d, + } -sub : Complex, Complex -> Complex -sub = |{ re: a, im: b }, { re: c, im: d }| { - re: a - c, - im: b - d, -} + sub : Complex, Complex -> Complex + sub = |{ re: a, im: b }, { re: c, im: d }| { + re: a - c, + im: b - d, + } -mul : Complex, Complex -> Complex -mul = |{ re: a, im: b }, { re: c, im: d }| { - re: a * c - b * d, - im: a * d + b * c, -} + mul : Complex, Complex -> Complex + mul = |{ re: a, im: b }, { re: c, im: d }| { + re: a * c - b * d, + im: a * d + b * c, + } -div : Complex, Complex -> Complex -div = |{ re: a, im: b }, { re: c, im: d }| - denominator = c * c + d * d - { - re: (a * c + b * d) / denominator, - im: (b * c - a * d) / denominator, + div : Complex, Complex -> Complex + div = |{ re: a, im: b }, { re: c, im: d }| + denominator = c * c + d * d + { + re: (a * c + b * d) / denominator, + im: (b * c - a * d) / denominator, + } + + conjugate : Complex -> Complex + conjugate = |z| { + re: z.re, + im: -z.im, } -conjugate : Complex -> Complex -conjugate = |z| { - re: z.re, - im: -z.im, + abs : Complex -> F64 + abs = |{ re: a, im: b }| + a * a + b * b |> Num.sqrt + + exp : Complex -> Complex + exp = |z| + factor = Num.e |> Num.pow(z.re) + { + re: factor * Num.cos(z.im), + im: factor * Num.sin(z.im), + } } -abs : Complex -> F64 -abs = |{ re: a, im: b }| - a * a + b * b |> Num.sqrt -exp : Complex -> Complex -exp = |z| - factor = Num.e |> Num.pow(z.re) - { - re: factor * Num.cos(z.im), - im: factor * Num.sin(z.im), - } +Complex : { re : F64, im : F64 } diff --git a/exercises/practice/complex-numbers/ComplexNumbers.roc b/exercises/practice/complex-numbers/ComplexNumbers.roc index 207ef99c..2298d15e 100644 --- a/exercises/practice/complex-numbers/ComplexNumbers.roc +++ b/exercises/practice/complex-numbers/ComplexNumbers.roc @@ -1,39 +1,40 @@ -module [real, imaginary, add, sub, mul, div, conjugate, abs, exp] +ComplexNumbers :: {}.{ + real : Complex -> F64 + real = |z| + crash("Please implement the 'real' function") -Complex : { re : F64, im : F64 } + imaginary : Complex -> F64 + imaginary = |z| + crash("Please implement the 'imaginary' function") -real : Complex -> F64 -real = |z| - crash("Please implement the 'real' function") + add : Complex, Complex -> Complex + add = |z1, z2| + crash("Please implement the 'add' function") -imaginary : Complex -> F64 -imaginary = |z| - crash("Please implement the 'imaginary' function") + sub : Complex, Complex -> Complex + sub = |z1, z2| + crash("Please implement the 'sub' function") -add : Complex, Complex -> Complex -add = |z1, z2| - crash("Please implement the 'add' function") + mul : Complex, Complex -> Complex + mul = |z1, z2| + crash("Please implement the 'mul' function") -sub : Complex, Complex -> Complex -sub = |z1, z2| - crash("Please implement the 'sub' function") + div : Complex, Complex -> Complex + div = |z1, z2| + crash("Please implement the 'div' function") -mul : Complex, Complex -> Complex -mul = |z1, z2| - crash("Please implement the 'mul' function") + conjugate : Complex -> Complex + conjugate = |z| + crash("Please implement the 'conjugate' function") -div : Complex, Complex -> Complex -div = |z1, z2| - crash("Please implement the 'div' function") + abs : Complex -> F64 + abs = |z| + crash("Please implement the 'abs' function") -conjugate : Complex -> Complex -conjugate = |z| - crash("Please implement the 'conjugate' function") + exp : Complex -> Complex + exp = |z| + crash("Please implement the 'exp' function") +} -abs : Complex -> F64 -abs = |z| - crash("Please implement the 'abs' function") -exp : Complex -> Complex -exp = |z| - crash("Please implement the 'exp' function") +Complex : { re : F64, im : F64 } diff --git a/exercises/practice/connect/.meta/Example.roc b/exercises/practice/connect/.meta/Example.roc index 66ef840c..a4effad8 100644 --- a/exercises/practice/connect/.meta/Example.roc +++ b/exercises/practice/connect/.meta/Example.roc @@ -1,4 +1,15 @@ -module [winner] +Connect :: {}.{ + winner : Str -> Result [PlayerO, PlayerX] [NotFinished, InvalidCharacter U8, InvalidBoardShape] + winner = |board_str| + board = parse(board_str)? + validate(board)? + if board |> has_north_south_path(StoneO) then + Ok(PlayerO) + else if board |> transpose |> has_north_south_path(StoneX) then + Ok(PlayerX) + else + Err(NotFinished) +} Cell : [StoneO, StoneX, Empty] Board : List (List Cell) @@ -34,17 +45,6 @@ validate = |board| else Ok({}) -winner : Str -> Result [PlayerO, PlayerX] [NotFinished, InvalidCharacter U8, InvalidBoardShape] -winner = |board_str| - board = parse(board_str)? - validate(board)? - if board |> has_north_south_path(StoneO) then - Ok(PlayerO) - else if board |> transpose |> has_north_south_path(StoneX) then - Ok(PlayerX) - else - Err(NotFinished) - transpose : Board -> Board transpose = |board| width = board |> first_row |> List.len diff --git a/exercises/practice/connect/Connect.roc b/exercises/practice/connect/Connect.roc index 1f485b8a..48c43e5b 100644 --- a/exercises/practice/connect/Connect.roc +++ b/exercises/practice/connect/Connect.roc @@ -1,5 +1,5 @@ -module [winner] - -winner : Str -> Result [PlayerO, PlayerX] _ -winner = |board_str| - crash("Please implement the 'winner' function") +Connect :: {}.{ + winner : Str -> Result [PlayerO, PlayerX] _ + winner = |board_str| + crash("Please implement the 'winner' function") +} diff --git a/exercises/practice/crypto-square/.meta/Example.roc b/exercises/practice/crypto-square/.meta/Example.roc index 9364e643..96861d93 100644 --- a/exercises/practice/crypto-square/.meta/Example.roc +++ b/exercises/practice/crypto-square/.meta/Example.roc @@ -1,33 +1,33 @@ -module [ciphertext] +CryptoSquare :: {}.{ + ciphertext : Str -> Result Str _ + ciphertext = |text| + chars = + text + |> Str.to_utf8 + |> List.join_map( + |char| + if (char >= 'a' and char <= 'z') or (char >= '0' and char <= '9') then + [char] + else if char >= 'A' and char <= 'Z' then + [char - 'A' + 'a'] + else + [], + ) -ciphertext : Str -> Result Str _ -ciphertext = |text| - chars = - text - |> Str.to_utf8 - |> List.join_map( - |char| - if (char >= 'a' and char <= 'z') or (char >= '0' and char <= '9') then - [char] - else if char >= 'A' and char <= 'Z' then - [char - 'A' + 'a'] - else - [], - ) - - length = List.len(chars) - width = length |> Num.to_f64 |> Num.sqrt |> Num.ceiling |> Num.to_u64 - rows = chars |> List.chunks_of(width) + length = List.len(chars) + width = length |> Num.to_f64 |> Num.sqrt |> Num.ceiling |> Num.to_u64 + rows = chars |> List.chunks_of(width) - List.range({ start: At(0), end: Before(width) }) - |> List.map( - |column| - rows - |> List.map( - |row| - row |> List.get(column) |> Result.with_default(' '), - ), - ) - |> List.intersperse([' ']) - |> List.join - |> Str.from_utf8 + List.range({ start: At(0), end: Before(width) }) + |> List.map( + |column| + rows + |> List.map( + |row| + row |> List.get(column) |> Result.with_default(' '), + ), + ) + |> List.intersperse([' ']) + |> List.join + |> Str.from_utf8 +} diff --git a/exercises/practice/crypto-square/CryptoSquare.roc b/exercises/practice/crypto-square/CryptoSquare.roc index 0074be06..e7c28202 100644 --- a/exercises/practice/crypto-square/CryptoSquare.roc +++ b/exercises/practice/crypto-square/CryptoSquare.roc @@ -1,5 +1,5 @@ -module [ciphertext] - -ciphertext : Str -> Result Str _ -ciphertext = |text| - crash("Please implement the 'ciphertext' function") +CryptoSquare :: {}.{ + ciphertext : Str -> Result Str _ + ciphertext = |text| + crash("Please implement the 'ciphertext' function") +} diff --git a/exercises/practice/custom-set/.meta/Example.roc b/exercises/practice/custom-set/.meta/Example.roc index 4e7132f9..e9336c65 100644 --- a/exercises/practice/custom-set/.meta/Example.roc +++ b/exercises/practice/custom-set/.meta/Example.roc @@ -1,81 +1,69 @@ -module [ - contains, - difference, - from_list, - insert, - intersection, - is_disjoint_with, - is_empty, - is_eq, - is_subset_of, - to_list, - to_list, - union, -] +CustomSet :: {}.{ + contains : CustomSet, Element -> Bool + contains = |@CustomSet({ items }), element| + items |> List.contains(element) -Element : U64 - -CustomSet := { items : List Element } implements [Eq] - -contains : CustomSet, Element -> Bool -contains = |@CustomSet({ items }), element| - items |> List.contains(element) + difference : CustomSet, CustomSet -> CustomSet + difference = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| + items = items1 |> List.drop_if(|item| items2 |> List.contains(item)) + @CustomSet({ items }) -difference : CustomSet, CustomSet -> CustomSet -difference = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| - items = items1 |> List.drop_if(|item| items2 |> List.contains(item)) - @CustomSet({ items }) + from_list : List Element -> CustomSet + from_list = |list| + when list |> List.sort_asc is + [] -> @CustomSet({ items: [] }) + [first, .. as rest] -> + items = + rest + |> List.walk( + { items: [first], previous: first }, + |state, item| + if item == state.previous then + state + else + { items: state.items |> List.append(item), previous: item }, + ) + |> .items + @CustomSet({ items }) -from_list : List Element -> CustomSet -from_list = |list| - when list |> List.sort_asc is - [] -> @CustomSet({ items: [] }) - [first, .. as rest] -> - items = - rest - |> List.walk( - { items: [first], previous: first }, - |state, item| - if item == state.previous then - state - else - { items: state.items |> List.append(item), previous: item }, - ) - |> .items + insert : CustomSet, Element -> CustomSet + insert = |@CustomSet({ items }), element| + if items |> List.contains(element) then @CustomSet({ items }) + else + @CustomSet({ items: items |> List.append(element) }) -insert : CustomSet, Element -> CustomSet -insert = |@CustomSet({ items }), element| - if items |> List.contains(element) then + intersection : CustomSet, CustomSet -> CustomSet + intersection = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| + items = items1 |> List.keep_if(|item| items2 |> List.contains(item)) @CustomSet({ items }) - else - @CustomSet({ items: items |> List.append(element) }) -intersection : CustomSet, CustomSet -> CustomSet -intersection = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| - items = items1 |> List.keep_if(|item| items2 |> List.contains(item)) - @CustomSet({ items }) + is_disjoint_with : CustomSet, CustomSet -> Bool + is_disjoint_with = |set1, set2| + set1 |> intersection(set2) |> is_empty -is_disjoint_with : CustomSet, CustomSet -> Bool -is_disjoint_with = |set1, set2| - set1 |> intersection(set2) |> is_empty + is_empty : CustomSet -> Bool + is_empty = |@CustomSet({ items })| + items |> List.is_empty -is_empty : CustomSet -> Bool -is_empty = |@CustomSet({ items })| - items |> List.is_empty + is_eq : CustomSet, CustomSet -> Bool + is_eq = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| + items1 |> List.sort_asc == items2 |> List.sort_asc -is_eq : CustomSet, CustomSet -> Bool -is_eq = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| - items1 |> List.sort_asc == items2 |> List.sort_asc + is_subset_of : CustomSet, CustomSet -> Bool + is_subset_of = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| + items1 |> List.all(|item| items2 |> List.contains(item)) -is_subset_of : CustomSet, CustomSet -> Bool -is_subset_of = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| - items1 |> List.all(|item| items2 |> List.contains(item)) + to_list : CustomSet -> List Element + to_list = |@CustomSet({ items })| + items -to_list : CustomSet -> List Element -to_list = |@CustomSet({ items })| - items + union : CustomSet, CustomSet -> CustomSet + union = |set1, set2| + set1 |> to_list |> List.concat((set2 |> to_list)) |> from_list +} -union : CustomSet, CustomSet -> CustomSet -union = |set1, set2| - set1 |> to_list |> List.concat((set2 |> to_list)) |> from_list + +Element : U64 + +CustomSet := { items : List Element } implements [Eq] diff --git a/exercises/practice/custom-set/CustomSet.roc b/exercises/practice/custom-set/CustomSet.roc index 8311e872..b6e3067c 100644 --- a/exercises/practice/custom-set/CustomSet.roc +++ b/exercises/practice/custom-set/CustomSet.roc @@ -1,16 +1,49 @@ -module [ - contains, - difference, - from_list, - insert, - intersection, - is_disjoint_with, - is_empty, - is_eq, - is_subset_of, - to_list, - union, -] +CustomSet :: {}.{ + contains : CustomSet, Element -> Bool + contains = |set, element| + crash("Please implement the 'contains' function") + + difference : CustomSet, CustomSet -> CustomSet + difference = |set1, set2| + crash("Please implement the 'difference' function") + + from_list : List Element -> CustomSet + from_list = |list| + crash("Please implement the 'from_list' function") + + insert : CustomSet, Element -> CustomSet + insert = |set, element| + crash("Please implement the 'insert' function") + + intersection : CustomSet, CustomSet -> CustomSet + intersection = |set1, set2| + crash("Please implement the 'intersection' function") + + is_disjoint_with : CustomSet, CustomSet -> Bool + is_disjoint_with = |set1, set2| + crash("Please implement the 'is_disjoint_with' function") + + is_empty : CustomSet -> Bool + is_empty = |set| + crash("Please implement the 'is_empty' function") + + is_eq : CustomSet, CustomSet -> Bool + is_eq = |set1, set2| + crash("Please implement the 'is_eq' function") + + is_subset_of : CustomSet, CustomSet -> Bool + is_subset_of = |set1, set2| + crash("Please implement the 'is_subset_of' function") + + to_list : CustomSet -> List Element + to_list = |set| + crash("Please implement the 'to_list' function") + + union : CustomSet, CustomSet -> CustomSet + union = |set1, set2| + crash("Please implement the 'union' function") +} + Element : U64 @@ -22,47 +55,3 @@ CustomSet := { # etc. } implements [Eq] - -contains : CustomSet, Element -> Bool -contains = |set, element| - crash("Please implement the 'contains' function") - -difference : CustomSet, CustomSet -> CustomSet -difference = |set1, set2| - crash("Please implement the 'difference' function") - -from_list : List Element -> CustomSet -from_list = |list| - crash("Please implement the 'from_list' function") - -insert : CustomSet, Element -> CustomSet -insert = |set, element| - crash("Please implement the 'insert' function") - -intersection : CustomSet, CustomSet -> CustomSet -intersection = |set1, set2| - crash("Please implement the 'intersection' function") - -is_disjoint_with : CustomSet, CustomSet -> Bool -is_disjoint_with = |set1, set2| - crash("Please implement the 'is_disjoint_with' function") - -is_empty : CustomSet -> Bool -is_empty = |set| - crash("Please implement the 'is_empty' function") - -is_eq : CustomSet, CustomSet -> Bool -is_eq = |set1, set2| - crash("Please implement the 'is_eq' function") - -is_subset_of : CustomSet, CustomSet -> Bool -is_subset_of = |set1, set2| - crash("Please implement the 'is_subset_of' function") - -to_list : CustomSet -> List Element -to_list = |set| - crash("Please implement the 'to_list' function") - -union : CustomSet, CustomSet -> CustomSet -union = |set1, set2| - crash("Please implement the 'union' function") diff --git a/exercises/practice/darts/.meta/Example.roc b/exercises/practice/darts/.meta/Example.roc index 2b9853ed..45dd7067 100644 --- a/exercises/practice/darts/.meta/Example.roc +++ b/exercises/practice/darts/.meta/Example.roc @@ -1,16 +1,17 @@ -module [score] +Darts :: {}.{ + score : F64, F64 -> U64 + score = |x, y| + d2 = distance_squared(x, y) + if d2 > 100 then + 0 + else if d2 > 25 then + 1 + else if d2 > 1 then + 5 + else + 10 +} + distance_squared = |x, y| x * x + y * y - -score : F64, F64 -> U64 -score = |x, y| - d2 = distance_squared(x, y) - if d2 > 100 then - 0 - else if d2 > 25 then - 1 - else if d2 > 1 then - 5 - else - 10 diff --git a/exercises/practice/darts/Darts.roc b/exercises/practice/darts/Darts.roc index 5e23f36a..cd65a6ba 100644 --- a/exercises/practice/darts/Darts.roc +++ b/exercises/practice/darts/Darts.roc @@ -1,6 +1,6 @@ -module [score] - -score : F64, F64 -> U64 -score = |x, y| - crash("Please implement the 'score' function") +Darts :: {}.{ + score : F64, F64 -> U64 + score = |x, y| + crash("Please implement the 'score' function") +} diff --git a/exercises/practice/diamond/.meta/Example.roc b/exercises/practice/diamond/.meta/Example.roc index 3ca3e219..116ec672 100644 --- a/exercises/practice/diamond/.meta/Example.roc +++ b/exercises/practice/diamond/.meta/Example.roc @@ -1,4 +1,17 @@ -module [diamond] +Diamond :: {}.{ + diamond : U8 -> Str + diamond = |letter| + letter_index = letter - 'A' |> Num.to_i8 + List.range({ start: At(-letter_index), end: At(letter_index) }) + |> List.map( + |row_index| + List.range({ start: At(-letter_index), end: At(letter_index) }) + |> List.map(|col_index| get_char(row_index, col_index, letter_index)) + |> unwrap_from_utf8, + ) + |> Str.join_with("\n") +} + get_char : I8, I8, I8 -> U8 get_char = |row_index, col_index, letter_index| @@ -12,15 +25,3 @@ unwrap_from_utf8 = |chars| when chars |> Str.from_utf8 is Ok(result) -> result Err(_) -> crash("Str.from_utf8 should never fail here") - -diamond : U8 -> Str -diamond = |letter| - letter_index = letter - 'A' |> Num.to_i8 - List.range({ start: At(-letter_index), end: At(letter_index) }) - |> List.map( - |row_index| - List.range({ start: At(-letter_index), end: At(letter_index) }) - |> List.map(|col_index| get_char(row_index, col_index, letter_index)) - |> unwrap_from_utf8, - ) - |> Str.join_with("\n") diff --git a/exercises/practice/diamond/Diamond.roc b/exercises/practice/diamond/Diamond.roc index 46f3b524..36caae19 100644 --- a/exercises/practice/diamond/Diamond.roc +++ b/exercises/practice/diamond/Diamond.roc @@ -1,5 +1,5 @@ -module [diamond] - -diamond : U8 -> Str -diamond = |letter| - crash("Please implement the 'diamond' function") +Diamond :: {}.{ + diamond : U8 -> Str + diamond = |letter| + crash("Please implement the 'diamond' function") +} diff --git a/exercises/practice/difference-of-squares/.meta/Example.roc b/exercises/practice/difference-of-squares/.meta/Example.roc index b4528d80..ab40c6f6 100644 --- a/exercises/practice/difference-of-squares/.meta/Example.roc +++ b/exercises/practice/difference-of-squares/.meta/Example.roc @@ -1,19 +1,20 @@ -module [square_of_sum, sum_of_squares, difference_of_squares] +DifferenceOfSquares :: {}.{ + square_of_sum : U64 -> U64 + square_of_sum = |number| + s = sum(number) + s * s -sum : U64 -> U64 -sum = |number| - number * (number + 1) // 2 + sum_of_squares : U64 -> U64 + sum_of_squares = |number| + s = sum(number) + s * (2 * number + 1) // 3 -square_of_sum : U64 -> U64 -square_of_sum = |number| - s = sum(number) - s * s + difference_of_squares : U64 -> U64 + difference_of_squares = |number| + (square_of_sum(number)) - (sum_of_squares(number)) +} -sum_of_squares : U64 -> U64 -sum_of_squares = |number| - s = sum(number) - s * (2 * number + 1) // 3 -difference_of_squares : U64 -> U64 -difference_of_squares = |number| - (square_of_sum(number)) - (sum_of_squares(number)) +sum : U64 -> U64 +sum = |number| + number * (number + 1) // 2 diff --git a/exercises/practice/difference-of-squares/DifferenceOfSquares.roc b/exercises/practice/difference-of-squares/DifferenceOfSquares.roc index 132797b8..cac8d17e 100644 --- a/exercises/practice/difference-of-squares/DifferenceOfSquares.roc +++ b/exercises/practice/difference-of-squares/DifferenceOfSquares.roc @@ -1,13 +1,13 @@ -module [square_of_sum, sum_of_squares, difference_of_squares] +DifferenceOfSquares :: {}.{ + square_of_sum : U64 -> U64 + square_of_sum = |number| + crash("Please implement the 'square_of_sum' function") -square_of_sum : U64 -> U64 -square_of_sum = |number| - crash("Please implement the 'square_of_sum' function") + sum_of_squares : U64 -> U64 + sum_of_squares = |number| + crash("Please implement the 'sum_of_squares' function") -sum_of_squares : U64 -> U64 -sum_of_squares = |number| - crash("Please implement the 'sum_of_squares' function") - -difference_of_squares : U64 -> U64 -difference_of_squares = |number| - crash("Please implement the 'difference_of_squares' function") + difference_of_squares : U64 -> U64 + difference_of_squares = |number| + crash("Please implement the 'difference_of_squares' function") +} diff --git a/exercises/practice/dominoes/.meta/Example.roc b/exercises/practice/dominoes/.meta/Example.roc index fdcbf632..a5674335 100644 --- a/exercises/practice/dominoes/.meta/Example.roc +++ b/exercises/practice/dominoes/.meta/Example.roc @@ -1,36 +1,51 @@ -module [find_chain] +Dominoes :: {}.{ +} -Domino : (U8, U8) -find_chain : List Domino -> Result (List Domino) [NoChainExists] -find_chain = |dominoes| - find_chain_helper = |used, available| - when available is - [] -> - when used is - [] -> Ok([]) - [single] -> - if single.0 == single.1 then Ok(used) else Err(NoChainExists) +Dominoes :: {}.{ + Domino := (U8, U8) - [first, .., last] -> if first.0 == last.1 then Ok(used) else Err(NoChainExists) + find_chain : List(Domino) -> Try(List(Domino), [NoChainExists]) + find_chain = |dominoes| { + find_chain_helper = |used, available| { + match available { + [] => { + match used { + [] => Ok([]) + [single] => if single.0 == single.1 Ok(used) else Err(NoChainExists) + [first, .., last] => if first.0 == last.1 Ok(used) else Err(NoChainExists) + } + } - [first_available, .. as rest_available] -> - when used is - [] -> find_chain_helper([first_available], rest_available) - [.., last_used] -> - available - |> List.walk_with_index_until( - Err(NoChainExists), - |_, domino, index| - maybe_chain = - if last_used.1 == domino.0 then - find_chain_helper((used |> List.append(domino)), (available |> List.drop_at(index))) - else if last_used.1 == domino.1 then - find_chain_helper((used |> List.append((domino.1, domino.0))), (available |> List.drop_at(index))) - else - Err(NoChainExists) - when maybe_chain is - Ok(chain) -> Break(Ok(chain)) - Err(NoChainExists) -> Continue(Err(NoChainExists)), - ) - find_chain_helper([], dominoes) + [first_available, .. as rest_available] => { + match used { + [] => find_chain_helper([first_available], rest_available) + [.., last_used] => { + available + .fold_with_index_until( + Err(NoChainExists), + |_, domino, index| { + maybe_chain = { + if last_used.1 == domino.0 { + find_chain_helper((used |> List.append(domino)), (available |> List.drop_at(index))) + } else if last_used.1 == domino.1 { + find_chain_helper((used |> List.append((domino.1, domino.0))), (available |> List.drop_at(index))) + } else { + Err(NoChainExists) + } + } + match maybe_chain { + Ok(chain) => Break(Ok(chain)) + Err(NoChainExists) => Continue(Err(NoChainExists)) + } + } + ) + } + } + } + } + } + + find_chain_helper([], dominoes) + } +} diff --git a/exercises/practice/dominoes/Dominoes.roc b/exercises/practice/dominoes/Dominoes.roc index 36652837..635e16dc 100644 --- a/exercises/practice/dominoes/Dominoes.roc +++ b/exercises/practice/dominoes/Dominoes.roc @@ -1,7 +1,8 @@ -module [find_chain] +Dominoes :: {}.{ + find_chain : List Domino -> Result (List Domino) _ + find_chain = |dominoes| + crash("Please implement the 'find_chain' function") +} -Domino : (U8, U8) -find_chain : List Domino -> Result (List Domino) _ -find_chain = |dominoes| - crash("Please implement the 'find_chain' function") +Domino : (U8, U8) diff --git a/exercises/practice/eliuds-eggs/.meta/Example.roc b/exercises/practice/eliuds-eggs/.meta/Example.roc index af844f63..0b7177f3 100644 --- a/exercises/practice/eliuds-eggs/.meta/Example.roc +++ b/exercises/practice/eliuds-eggs/.meta/Example.roc @@ -1,12 +1,12 @@ -module [egg_count] +EliudsEggs :: {}.{ + egg_count : U64 -> U64 + egg_count = |number| + help = |count, remaining| + if remaining == 0 then + count + else + digit = Num.rem(remaining, 2) + help((count + digit), (remaining // 2)) -egg_count : U64 -> U64 -egg_count = |number| - help = |count, remaining| - if remaining == 0 then - count - else - digit = Num.rem(remaining, 2) - help((count + digit), (remaining // 2)) - - help(0, number) + help(0, number) +} diff --git a/exercises/practice/eliuds-eggs/EliudsEggs.roc b/exercises/practice/eliuds-eggs/EliudsEggs.roc index 6a4477b2..d0fd3ae4 100644 --- a/exercises/practice/eliuds-eggs/EliudsEggs.roc +++ b/exercises/practice/eliuds-eggs/EliudsEggs.roc @@ -1,5 +1,5 @@ -module [egg_count] - -egg_count : U64 -> U64 -egg_count = |number| - crash("Please implement the 'egg_count' function") +EliudsEggs :: {}.{ + egg_count : U64 -> U64 + egg_count = |number| + crash("Please implement the 'egg_count' function") +} diff --git a/exercises/practice/error-handling/.meta/Example.roc b/exercises/practice/error-handling/.meta/Example.roc index 1a7c077b..2ccf8b2f 100644 --- a/exercises/practice/error-handling/.meta/Example.roc +++ b/exercises/practice/error-handling/.meta/Example.roc @@ -1,4 +1,45 @@ -module [get_user, parse_user_id, get_page, error_message] +ErrorHandling :: {}.{ + get_user : UserId -> Result User [UserNotFound UserId] + get_user = |user_id| + users |> Dict.get(user_id) |> Result.map_err(|KeyNotFound| UserNotFound(user_id)) + + parse_user_id : Str -> Result UserId [InvalidUserId Str] + parse_user_id = |path| + user_id_str = path |> Str.replace_first("/users/", "") + user_id_str |> Str.to_u64 |> Result.map_err(|InvalidNumStr| InvalidUserId(user_id_str)) + + get_page : Str -> Result Str [InsecureConnection Str, InvalidDomain Str, InvalidUserId Str, UserNotFound UserId, PageNotFound Str] + get_page = |url| + when parse_path(url)? is + "/" -> Ok("Home page") + "/users/" -> Ok("Users page") + user_path if user_path |> Str.starts_with("/users/") -> + user_id = parse_user_id(user_path)? + user = get_user(user_id)? + Ok("${user.name}'s page") + + unknown_path -> Err(PageNotFound(unknown_path)) + + error_message : [InsecureConnection Str, InvalidDomain Str, InvalidUserId Str, UserNotFound UserId, PageNotFound Str], [English, French] -> Str + error_message = |err, language| + when language is + English -> + when err is + InsecureConnection(url) -> "Insecure connection (non HTTPS): ${url}" + InvalidDomain(url) -> "Invalid domain name: ${url}" + InvalidUserId(user_id_str) -> "User ID is not a positive integer: ${user_id_str}" + UserNotFound(user_id) -> "User #${user_id |> Num.to_str} was not found" + PageNotFound(path) -> "Page not found: ${path}" + + French -> + when err is + InsecureConnection(url) -> "Connexion non sécurisée (non HTTPS): ${url}" + InvalidDomain(url) -> "Ce nom de domaine est incorrect: ${url}" + InvalidUserId(user_id_str) -> "Cet identifiant utilisateur devrait être un entier positif: ${user_id_str}" + UserNotFound(user_id) -> "L'utilisateur #${user_id |> Num.to_str} est inconnu" + PageNotFound(path) -> "Cette page est inconnue: ${path}" +} + User : { name : Str } UserId : U64 @@ -13,15 +54,6 @@ users = ], ) -get_user : UserId -> Result User [UserNotFound UserId] -get_user = |user_id| - users |> Dict.get(user_id) |> Result.map_err(|KeyNotFound| UserNotFound(user_id)) - -parse_user_id : Str -> Result UserId [InvalidUserId Str] -parse_user_id = |path| - user_id_str = path |> Str.replace_first("/users/", "") - user_id_str |> Str.to_u64 |> Result.map_err(|InvalidNumStr| InvalidUserId(user_id_str)) - parse_path : Str -> Result Str [InvalidDomain Str, InsecureConnection Str] parse_path = |url| prefix = "https://example.com" @@ -31,34 +63,3 @@ parse_path = |url| Err(InvalidDomain(url)) else Err(InsecureConnection(url)) - -get_page : Str -> Result Str [InsecureConnection Str, InvalidDomain Str, InvalidUserId Str, UserNotFound UserId, PageNotFound Str] -get_page = |url| - when parse_path(url)? is - "/" -> Ok("Home page") - "/users/" -> Ok("Users page") - user_path if user_path |> Str.starts_with("/users/") -> - user_id = parse_user_id(user_path)? - user = get_user(user_id)? - Ok("${user.name}'s page") - - unknown_path -> Err(PageNotFound(unknown_path)) - -error_message : [InsecureConnection Str, InvalidDomain Str, InvalidUserId Str, UserNotFound UserId, PageNotFound Str], [English, French] -> Str -error_message = |err, language| - when language is - English -> - when err is - InsecureConnection(url) -> "Insecure connection (non HTTPS): ${url}" - InvalidDomain(url) -> "Invalid domain name: ${url}" - InvalidUserId(user_id_str) -> "User ID is not a positive integer: ${user_id_str}" - UserNotFound(user_id) -> "User #${user_id |> Num.to_str} was not found" - PageNotFound(path) -> "Page not found: ${path}" - - French -> - when err is - InsecureConnection(url) -> "Connexion non sécurisée (non HTTPS): ${url}" - InvalidDomain(url) -> "Ce nom de domaine est incorrect: ${url}" - InvalidUserId(user_id_str) -> "Cet identifiant utilisateur devrait être un entier positif: ${user_id_str}" - UserNotFound(user_id) -> "L'utilisateur #${user_id |> Num.to_str} est inconnu" - PageNotFound(path) -> "Cette page est inconnue: ${path}" diff --git a/exercises/practice/error-handling/ErrorHandling.roc b/exercises/practice/error-handling/ErrorHandling.roc index a9d54371..f3f20be6 100644 --- a/exercises/practice/error-handling/ErrorHandling.roc +++ b/exercises/practice/error-handling/ErrorHandling.roc @@ -1,4 +1,21 @@ -module [get_user, parse_user_id, get_page, error_message] +ErrorHandling :: {}.{ + get_user : UserId -> Result User [UserNotFound Str] + get_user = |user_id| + crash("Please implement the 'get_user' function") + + parse_user_id : Str -> Result UserId [InvalidUserId Str] + parse_user_id = |path| + crash("Please implement the 'parse_user_id' function") + + get_page : Str -> Result Str _ + get_page = |url| + crash("Please implement the 'get_page' function") + + error_message : _, [English] -> Str + error_message = |err, language| + crash("Please implement the 'error_message' function") +} + User : { name : Str } UserId : U64 @@ -12,19 +29,3 @@ users = (789, { name: "Charlie" }), ], ) - -get_user : UserId -> Result User [UserNotFound Str] -get_user = |user_id| - crash("Please implement the 'get_user' function") - -parse_user_id : Str -> Result UserId [InvalidUserId Str] -parse_user_id = |path| - crash("Please implement the 'parse_user_id' function") - -get_page : Str -> Result Str _ -get_page = |url| - crash("Please implement the 'get_page' function") - -error_message : _, [English] -> Str -error_message = |err, language| - crash("Please implement the 'error_message' function") diff --git a/exercises/practice/etl/.meta/Example.roc b/exercises/practice/etl/.meta/Example.roc index 4d38720e..0c36ef41 100644 --- a/exercises/practice/etl/.meta/Example.roc +++ b/exercises/practice/etl/.meta/Example.roc @@ -1,4 +1,15 @@ -module [transform] +Etl :: {}.{ + transform : Dict U64 (List U8) -> Dict U8 U64 + transform = |legacy| + legacy + |> Dict.join_map( + |score, letters| + letters + |> List.map(|c| (to_lower(c), score)) + |> Dict.from_list, + ) +} + to_lower : U8 -> U8 to_lower = |char| @@ -6,13 +17,3 @@ to_lower = |char| char - 'A' + 'a' else char - -transform : Dict U64 (List U8) -> Dict U8 U64 -transform = |legacy| - legacy - |> Dict.join_map( - |score, letters| - letters - |> List.map(|c| (to_lower(c), score)) - |> Dict.from_list, - ) diff --git a/exercises/practice/etl/Etl.roc b/exercises/practice/etl/Etl.roc index e692b2a1..38a2b45e 100644 --- a/exercises/practice/etl/Etl.roc +++ b/exercises/practice/etl/Etl.roc @@ -1,5 +1,5 @@ -module [transform] - -transform : Dict U64 (List U8) -> Dict U8 U64 -transform = |legacy| - crash("Please implement the 'transform' function") +Etl :: {}.{ + transform : Dict U64 (List U8) -> Dict U8 U64 + transform = |legacy| + crash("Please implement the 'transform' function") +} diff --git a/exercises/practice/flatten-array/.meta/Example.roc b/exercises/practice/flatten-array/.meta/Example.roc index b394fbb1..4207fe08 100644 --- a/exercises/practice/flatten-array/.meta/Example.roc +++ b/exercises/practice/flatten-array/.meta/Example.roc @@ -1,10 +1,11 @@ -module [flatten] +FlattenArray :: {}.{ + flatten : NestedValue -> List I64 + flatten = |array| + when array is + NestedArray(list) -> list |> List.join_map(flatten) + Value(value) -> [value] + Null -> [] +} -NestedValue : [Value I64, Null, NestedArray (List NestedValue)] -flatten : NestedValue -> List I64 -flatten = |array| - when array is - NestedArray(list) -> list |> List.join_map(flatten) - Value(value) -> [value] - Null -> [] +NestedValue : [Value I64, Null, NestedArray (List NestedValue)] diff --git a/exercises/practice/flatten-array/FlattenArray.roc b/exercises/practice/flatten-array/FlattenArray.roc index a3e3d37e..20a0154c 100644 --- a/exercises/practice/flatten-array/FlattenArray.roc +++ b/exercises/practice/flatten-array/FlattenArray.roc @@ -1,7 +1,8 @@ -module [flatten] +FlattenArray :: {}.{ + flatten : NestedValue -> List I64 + flatten = |array| + crash("Please implement the 'flatten' function") +} -NestedValue : [Value I64, Null, NestedArray (List NestedValue)] -flatten : NestedValue -> List I64 -flatten = |array| - crash("Please implement the 'flatten' function") +NestedValue : [Value I64, Null, NestedArray (List NestedValue)] diff --git a/exercises/practice/flower-field/.meta/Example.roc b/exercises/practice/flower-field/.meta/Example.roc index 34120bcc..9887b5f1 100644 --- a/exercises/practice/flower-field/.meta/Example.roc +++ b/exercises/practice/flower-field/.meta/Example.roc @@ -1,4 +1,34 @@ -module [annotate] +FlowerField :: {}.{ + annotate : Str -> Str + annotate = |garden| + rows = garden |> Str.to_utf8 |> List.split_on('\n') + annotated = + rows + |> List.map_with_index( + |row, y| + row + |> List.map_with_index( + |cell, x| + if cell == '*' then + '*' + else + when count_neighbors(rows, x, y) is + 0 -> ' ' + n -> '0' + n, + ) + |> Str.from_utf8, + ) + + annotated + |> List.map( + |maybe_row| + when maybe_row is + Ok(row) -> row + Err(_) -> crash("Unreachable"), + ) # fromUtf8 cannot fail in the code above + |> Str.join_with("\n") +} + is_flower : List (List U8), I64, I64 -> Result Bool [OutOfBounds] is_flower = |rows, nx, ny| @@ -25,32 +55,3 @@ count_neighbors = |rows, x, y| |> List.sum, ) |> List.sum - -annotate : Str -> Str -annotate = |garden| - rows = garden |> Str.to_utf8 |> List.split_on('\n') - annotated = - rows - |> List.map_with_index( - |row, y| - row - |> List.map_with_index( - |cell, x| - if cell == '*' then - '*' - else - when count_neighbors(rows, x, y) is - 0 -> ' ' - n -> '0' + n, - ) - |> Str.from_utf8, - ) - - annotated - |> List.map( - |maybe_row| - when maybe_row is - Ok(row) -> row - Err(_) -> crash("Unreachable"), - ) # fromUtf8 cannot fail in the code above - |> Str.join_with("\n") diff --git a/exercises/practice/flower-field/FlowerField.roc b/exercises/practice/flower-field/FlowerField.roc index 5e7b8931..5b88aad9 100644 --- a/exercises/practice/flower-field/FlowerField.roc +++ b/exercises/practice/flower-field/FlowerField.roc @@ -1,5 +1,5 @@ -module [annotate] - -annotate : Str -> Str -annotate = |garden| - crash("Please implement the 'annotate' function") +FlowerField :: {}.{ + annotate : Str -> Str + annotate = |garden| + crash("Please implement the 'annotate' function") +} diff --git a/exercises/practice/food-chain/.meta/Example.roc b/exercises/practice/food-chain/.meta/Example.roc index d12dc249..42a768cd 100644 --- a/exercises/practice/food-chain/.meta/Example.roc +++ b/exercises/practice/food-chain/.meta/Example.roc @@ -1,9 +1,9 @@ -module [recite] - -recite : U64, U64 -> Str -recite = |start_verse, end_verse| - List.sublist(verse_list, { start: start_verse - 1, len: end_verse - (start_verse - 1) }) - |> Str.join_with("\n\n") +FoodChain :: {}.{ + recite : U64, U64 -> Str + recite = |start_verse, end_verse| + List.sublist(verse_list, { start: start_verse - 1, len: end_verse - (start_verse - 1) }) + |> Str.join_with("\n\n") +} verse_list : List Str verse_list = diff --git a/exercises/practice/food-chain/FoodChain.roc b/exercises/practice/food-chain/FoodChain.roc index 90ae8aa3..516a6257 100644 --- a/exercises/practice/food-chain/FoodChain.roc +++ b/exercises/practice/food-chain/FoodChain.roc @@ -1,5 +1,5 @@ -module [recite] - -recite : U64, U64 -> Str -recite = |start_verse, end_verse| - crash("Please implement the 'recite' function") +FoodChain :: {}.{ + recite : U64, U64 -> Str + recite = |start_verse, end_verse| + crash("Please implement the 'recite' function") +} diff --git a/exercises/practice/forth/.meta/Example.roc b/exercises/practice/forth/.meta/Example.roc index 83521dc3..d5c6ce94 100644 --- a/exercises/practice/forth/.meta/Example.roc +++ b/exercises/practice/forth/.meta/Example.roc @@ -1,4 +1,15 @@ -module [evaluate] +Forth :: {}.{ + # Evaluation + evaluate : Str -> Result Stack Str + evaluate = |program| + result = |_| + lower = to_lower(program) + operations = parse(lower)? + interpret(operations) + + Result.map_err(result({}), handle_error) +} + # Types Defs : Dict Str (List Str) @@ -15,16 +26,6 @@ Op : [ Number I16, ] -# Evaluation -evaluate : Str -> Result Stack Str -evaluate = |program| - result = |_| - lower = to_lower(program) - operations = parse(lower)? - interpret(operations) - - Result.map_err(result({}), handle_error) - interpret : List Op -> Result Stack _ interpret = |program| help = |ops, stack| diff --git a/exercises/practice/forth/Forth.roc b/exercises/practice/forth/Forth.roc index 9fa226b7..8407a83c 100644 --- a/exercises/practice/forth/Forth.roc +++ b/exercises/practice/forth/Forth.roc @@ -1,7 +1,8 @@ -module [evaluate] +Forth :: {}.{ + evaluate : Str -> Result Stack _ + evaluate = |program| + crash("Please implement the 'evaluate' function") +} -Stack : List I16 -evaluate : Str -> Result Stack _ -evaluate = |program| - crash("Please implement the 'evaluate' function") +Stack : List I16 diff --git a/exercises/practice/gigasecond/.meta/Example.roc b/exercises/practice/gigasecond/.meta/Example.roc index 57aedcdc..50e2e2ef 100644 --- a/exercises/practice/gigasecond/.meta/Example.roc +++ b/exercises/practice/gigasecond/.meta/Example.roc @@ -1,6 +1,13 @@ -module [add] + import isodate.DateTime +Gigasecond :: {}.{ + add : Str -> Str + add = |moment| + when future_datetime(moment) is + Ok(string) -> string + Err(_) -> "Unexpected error" +} future_datetime : Str -> Result Str [InvalidDateTimeFormat] future_datetime = |moment| @@ -15,9 +22,3 @@ future_datetime = |moment| |> DateTime.to_iso_str ), ) - -add : Str -> Str -add = |moment| - when future_datetime(moment) is - Ok(string) -> string - Err(_) -> "Unexpected error" diff --git a/exercises/practice/gigasecond/Gigasecond.roc b/exercises/practice/gigasecond/Gigasecond.roc index 997a32c3..c300b224 100644 --- a/exercises/practice/gigasecond/Gigasecond.roc +++ b/exercises/practice/gigasecond/Gigasecond.roc @@ -1,5 +1,5 @@ -module [add] - -add : Str -> Str -add = |moment| - crash("Please implement the 'add' function") +Gigasecond :: {}.{ + add : Str -> Str + add = |moment| + crash("Please implement the 'add' function") +} diff --git a/exercises/practice/go-counting/.meta/Example.roc b/exercises/practice/go-counting/.meta/Example.roc index c99d9ecf..7d1b0261 100644 --- a/exercises/practice/go-counting/.meta/Example.roc +++ b/exercises/practice/go-counting/.meta/Example.roc @@ -1,4 +1,44 @@ -module [territory, territories] +GoCounting :: {}.{ + territory : Str, Intersection -> Result Territory [OutOfBounds, BoardWasEmpty, BoardWasNotRectangular, InvalidChar U8] + territory = |board_str, intersection| + board = parse(board_str)? + if intersection.x >= board.width or intersection.y >= board.height then + Err(OutOfBounds) + else + Ok(search_territory(board, intersection)) + + territories : Str -> Result Territories [BoardWasEmpty, BoardWasNotRectangular, InvalidChar U8] + territories = |board_str| + board = parse(board_str)? + board.rows + |> List.map_with_index( + |row, y| + row + |> List.map_with_index( + |stone, x| + if stone == None then + [{ x, y }] + else + [], + ) + |> List.join, + ) + |> List.join + |> List.walk( + { black: Set.empty({}), white: Set.empty({}), none: Set.empty({}) }, + |state, intersection| + if state.black |> Set.contains(intersection) or state.white |> Set.contains(intersection) or state.none |> Set.contains(intersection) then + state + else + new_territory = search_territory(board, intersection) + when new_territory.owner is + Black -> { black: state.black |> Set.union(new_territory.territory), white: state.white, none: state.none } + White -> { black: state.black, white: state.white |> Set.union(new_territory.territory), none: state.none } + None -> { black: state.black, white: state.white, none: state.none |> Set.union(new_territory.territory) }, + ) + |> Ok +} + Intersection : { x : U64, y : U64 } @@ -54,14 +94,6 @@ get_stone : Board, Intersection -> Stone get_stone = |board, { x, y }| board.rows |> List.get(y) |> Result.with_default([]) |> List.get(x) |> Result.with_default(None) -territory : Str, Intersection -> Result Territory [OutOfBounds, BoardWasEmpty, BoardWasNotRectangular, InvalidChar U8] -territory = |board_str, intersection| - board = parse(board_str)? - if intersection.x >= board.width or intersection.y >= board.height then - Err(OutOfBounds) - else - Ok(search_territory(board, intersection)) - search_territory : Board, Intersection -> Territory search_territory = |board, intersection| help = |to_visit, visited, surrounding_stones| @@ -104,34 +136,3 @@ search_territory = |board, intersection| else None { owner, territory: search_result.visited } - -territories : Str -> Result Territories [BoardWasEmpty, BoardWasNotRectangular, InvalidChar U8] -territories = |board_str| - board = parse(board_str)? - board.rows - |> List.map_with_index( - |row, y| - row - |> List.map_with_index( - |stone, x| - if stone == None then - [{ x, y }] - else - [], - ) - |> List.join, - ) - |> List.join - |> List.walk( - { black: Set.empty({}), white: Set.empty({}), none: Set.empty({}) }, - |state, intersection| - if state.black |> Set.contains(intersection) or state.white |> Set.contains(intersection) or state.none |> Set.contains(intersection) then - state - else - new_territory = search_territory(board, intersection) - when new_territory.owner is - Black -> { black: state.black |> Set.union(new_territory.territory), white: state.white, none: state.none } - White -> { black: state.black, white: state.white |> Set.union(new_territory.territory), none: state.none } - None -> { black: state.black, white: state.white, none: state.none |> Set.union(new_territory.territory) }, - ) - |> Ok diff --git a/exercises/practice/go-counting/GoCounting.roc b/exercises/practice/go-counting/GoCounting.roc index 7a715ee2..ce35f9b7 100644 --- a/exercises/practice/go-counting/GoCounting.roc +++ b/exercises/practice/go-counting/GoCounting.roc @@ -1,4 +1,13 @@ -module [territory, territories] +GoCounting :: {}.{ + territory : Str, Intersection -> Result Territory _ + territory = |board_str, { x, y }| + crash("Please implement the 'territory' function") + + territories : Str -> Result Territories _ + territories = |board_str| + crash("Please implement the 'territories' function") +} + Intersection : { x : U64, y : U64 } @@ -14,11 +23,3 @@ Territories : { white : Set Intersection, none : Set Intersection, } - -territory : Str, Intersection -> Result Territory _ -territory = |board_str, { x, y }| - crash("Please implement the 'territory' function") - -territories : Str -> Result Territories _ -territories = |board_str| - crash("Please implement the 'territories' function") diff --git a/exercises/practice/grains/.meta/Example.roc b/exercises/practice/grains/.meta/Example.roc index 6d069e0e..38de665a 100644 --- a/exercises/practice/grains/.meta/Example.roc +++ b/exercises/practice/grains/.meta/Example.roc @@ -1,12 +1,12 @@ -module [grains_on_square, total_grains] +Grains :: {}.{ + grains_on_square : U8 -> Result U64 [SquareArgWasNotBetween1And64 U8] + grains_on_square = |square| + if square > 0 and square <= 64 then + 2u64 |> Num.pow_int((Num.to_u64(square) - 1)) |> Ok + else + Err(SquareArgWasNotBetween1And64(square)) -grains_on_square : U8 -> Result U64 [SquareArgWasNotBetween1And64 U8] -grains_on_square = |square| - if square > 0 and square <= 64 then - 2u64 |> Num.pow_int((Num.to_u64(square) - 1)) |> Ok - else - Err(SquareArgWasNotBetween1And64(square)) - -total_grains : U64 -total_grains = - Num.max_u64 + total_grains : U64 + total_grains = + Num.max_u64 +} diff --git a/exercises/practice/grains/Grains.roc b/exercises/practice/grains/Grains.roc index 0ef72beb..8f5b3675 100644 --- a/exercises/practice/grains/Grains.roc +++ b/exercises/practice/grains/Grains.roc @@ -1,9 +1,9 @@ -module [grains_on_square, total_grains] +Grains :: {}.{ + grains_on_square : U8 -> Result U64 _ + grains_on_square = |square| + crash("Please implement the 'grains_on_square' function") -grains_on_square : U8 -> Result U64 _ -grains_on_square = |square| - crash("Please implement the 'grains_on_square' function") - -total_grains : U64 -total_grains = - crash("Please implement the 'total_grains' function") + total_grains : U64 + total_grains = + crash("Please implement the 'total_grains' function") +} diff --git a/exercises/practice/grep/.meta/Example.roc b/exercises/practice/grep/.meta/Example.roc index 17ae6d71..52da23d0 100644 --- a/exercises/practice/grep/.meta/Example.roc +++ b/exercises/practice/grep/.meta/Example.roc @@ -1,39 +1,40 @@ -module [grep] + import "iliad.txt" as iliad : Str import "midsummer-night.txt" as midsummer_night : Str import "paradise-lost.txt" as paradise_lost : Str - -grep : Str, List Str, List Str -> Result Str _ -grep = |pattern, flags, file_names| - config = parse_flags(flags)? - files = collect_files(file_names)? - display_file_names = List.len(files) > 1 - List.join_map( - files, - |file| - when find_matches(config, pattern, file.text) is - [] -> [] - _ if config.display_file_names -> [file.name] - matches -> - List.map( - matches, - |{ line, index }| - line_number = - if config.display_line_numbers then - "${index + 1 |> Num.to_str}:" - else - "" - file_name = - if display_file_names then - "${file.name}:" - else - "" - "${file_name}${line_number}${line}", - ), - ) - |> Str.join_with("\n") - |> Ok +Grep :: {}.{ + grep : Str, List Str, List Str -> Result Str _ + grep = |pattern, flags, file_names| + config = parse_flags(flags)? + files = collect_files(file_names)? + display_file_names = List.len(files) > 1 + List.join_map( + files, + |file| + when find_matches(config, pattern, file.text) is + [] -> [] + _ if config.display_file_names -> [file.name] + matches -> + List.map( + matches, + |{ line, index }| + line_number = + if config.display_line_numbers then + "${index + 1 |> Num.to_str}:" + else + "" + file_name = + if display_file_names then + "${file.name}:" + else + "" + "${file_name}${line_number}${line}", + ), + ) + |> Str.join_with("\n") + |> Ok +} find_matches : Config, Str, Str -> List { line : Str, index : U64 } find_matches = |config, pattern, text| diff --git a/exercises/practice/grep/Grep.roc b/exercises/practice/grep/Grep.roc index 1cfe39f9..0bcc4f1e 100644 --- a/exercises/practice/grep/Grep.roc +++ b/exercises/practice/grep/Grep.roc @@ -1,9 +1,10 @@ -module [grep] + import "iliad.txt" as iliad : Str import "midsummer-night.txt" as midsummer_night : Str import "paradise-lost.txt" as paradise_lost : Str - -grep : Str, List Str, List Str -> Result Str _ -grep = |pattern, flags, files| - crash("Please implement the 'grep' function") +Grep :: {}.{ + grep : Str, List Str, List Str -> Result Str _ + grep = |pattern, flags, files| + crash("Please implement the 'grep' function") +} diff --git a/exercises/practice/hamming/.meta/Example.roc b/exercises/practice/hamming/.meta/Example.roc index eb3f83e0..2907f96e 100644 --- a/exercises/practice/hamming/.meta/Example.roc +++ b/exercises/practice/hamming/.meta/Example.roc @@ -1,17 +1,17 @@ -module [distance] - -distance : Str, Str -> Result (Num *) [StrandArgsWereNotOfEqualLength Str Str] -distance = |strand1, strand2| - nucleotides1 = strand1 |> Str.to_utf8 - nucleotides2 = strand2 |> Str.to_utf8 - if List.len(nucleotides1) == List.len(nucleotides2) then - List.map2( - nucleotides1, - nucleotides2, - |n1, n2| - if n1 == n2 then 0 else 1, - ) - |> List.sum - |> Ok - else - Err(StrandArgsWereNotOfEqualLength(strand1, strand2)) +Hamming :: {}.{ + distance : Str, Str -> Result (Num *) [StrandArgsWereNotOfEqualLength Str Str] + distance = |strand1, strand2| + nucleotides1 = strand1 |> Str.to_utf8 + nucleotides2 = strand2 |> Str.to_utf8 + if List.len(nucleotides1) == List.len(nucleotides2) then + List.map2( + nucleotides1, + nucleotides2, + |n1, n2| + if n1 == n2 then 0 else 1, + ) + |> List.sum + |> Ok + else + Err(StrandArgsWereNotOfEqualLength(strand1, strand2)) +} diff --git a/exercises/practice/hamming/Hamming.roc b/exercises/practice/hamming/Hamming.roc index 55b5e20e..4b535aab 100644 --- a/exercises/practice/hamming/Hamming.roc +++ b/exercises/practice/hamming/Hamming.roc @@ -1,5 +1,5 @@ -module [distance] - -distance : Str, Str -> Result (Num *) _ -distance = |strand1, strand2| - crash("Please implement the 'distance' function") +Hamming :: {}.{ + distance : Str, Str -> Result (Num *) _ + distance = |strand1, strand2| + crash("Please implement the 'distance' function") +} diff --git a/exercises/practice/hello-world/.meta/Example.roc b/exercises/practice/hello-world/.meta/Example.roc index 9804f555..15c687c3 100644 --- a/exercises/practice/hello-world/.meta/Example.roc +++ b/exercises/practice/hello-world/.meta/Example.roc @@ -1,4 +1,4 @@ -module [hello] - -hello : Str -hello = "Hello, World!" +HelloWorld :: {}.{ + hello : Str + hello = "Hello, World!" +} diff --git a/exercises/practice/hello-world/HelloWorld.roc b/exercises/practice/hello-world/HelloWorld.roc index 1aa1043d..2bf61ca2 100644 --- a/exercises/practice/hello-world/HelloWorld.roc +++ b/exercises/practice/hello-world/HelloWorld.roc @@ -1,4 +1,4 @@ -module [hello] - -hello : Str -hello = "Goodbye, Mars!" +HelloWorld :: {}.{ + hello : Str + hello = "Goodbye, Mars!" +} diff --git a/exercises/practice/hexadecimal/.meta/Example.roc b/exercises/practice/hexadecimal/.meta/Example.roc index 529fe293..b5f60815 100644 --- a/exercises/practice/hexadecimal/.meta/Example.roc +++ b/exercises/practice/hexadecimal/.meta/Example.roc @@ -1,4 +1,22 @@ -module [parse] +Hexadecimal :: {}.{ + parse : Str -> Result U64 _ + parse = |string| + if string == "" then + Err(InvalidNumStr) + else + string + |> Str.to_utf8 + |> List.walk_try( + 0, + |number, char| + nibble = parse_nibble(char)? + if number > 0xfffffffffffffff then + Err(InvalidNumStr) + else + number |> Num.shift_left_by(4) |> Num.add(nibble) |> Ok, + ) +} + parse_nibble = |char| if char >= '0' and char <= '9' then @@ -9,20 +27,3 @@ parse_nibble = |char| Ok((char - 'a' + 10 |> Num.to_u64)) else Err(InvalidNumStr) - -parse : Str -> Result U64 _ -parse = |string| - if string == "" then - Err(InvalidNumStr) - else - string - |> Str.to_utf8 - |> List.walk_try( - 0, - |number, char| - nibble = parse_nibble(char)? - if number > 0xfffffffffffffff then - Err(InvalidNumStr) - else - number |> Num.shift_left_by(4) |> Num.add(nibble) |> Ok, - ) diff --git a/exercises/practice/hexadecimal/Hexadecimal.roc b/exercises/practice/hexadecimal/Hexadecimal.roc index e286f262..5c170b72 100644 --- a/exercises/practice/hexadecimal/Hexadecimal.roc +++ b/exercises/practice/hexadecimal/Hexadecimal.roc @@ -1,5 +1,5 @@ -module [parse] - -parse : Str -> Result U64 _ -parse = |string| - crash("Please implement the 'parse' function") +Hexadecimal :: {}.{ + parse : Str -> Result U64 _ + parse = |string| + crash("Please implement the 'parse' function") +} diff --git a/exercises/practice/high-scores/.meta/Example.roc b/exercises/practice/high-scores/.meta/Example.roc index cb59ab58..a9d898d9 100644 --- a/exercises/practice/high-scores/.meta/Example.roc +++ b/exercises/practice/high-scores/.meta/Example.roc @@ -1,13 +1,14 @@ -module [latest, personal_best, personal_top_three] +HighScores :: {}.{ + latest : List Score -> Result Score [ListWasEmpty] + latest = List.last -Score : U64 + personal_best : List Score -> Result Score [ListWasEmpty] + personal_best = List.max -latest : List Score -> Result Score [ListWasEmpty] -latest = List.last + personal_top_three : List Score -> List Score + personal_top_three = |scores| + scores |> List.sort_desc |> List.take_first(3) +} -personal_best : List Score -> Result Score [ListWasEmpty] -personal_best = List.max -personal_top_three : List Score -> List Score -personal_top_three = |scores| - scores |> List.sort_desc |> List.take_first(3) +Score : U64 diff --git a/exercises/practice/high-scores/HighScores.roc b/exercises/practice/high-scores/HighScores.roc index f3b4b2d4..a404b113 100644 --- a/exercises/practice/high-scores/HighScores.roc +++ b/exercises/practice/high-scores/HighScores.roc @@ -1,15 +1,16 @@ -module [latest, personal_best, personal_top_three] +HighScores :: {}.{ + latest : List Score -> Result Score _ + latest = |scores| + crash("Please implement the 'latest' function") -Score : U64 + personal_best : List Score -> Result Score _ + personal_best = |scores| + crash("Please implement the 'personal_best' function") -latest : List Score -> Result Score _ -latest = |scores| - crash("Please implement the 'latest' function") + personal_top_three : List Score -> List Score + personal_top_three = |scores| + crash("Please implement the 'personal_top_three' function") +} -personal_best : List Score -> Result Score _ -personal_best = |scores| - crash("Please implement the 'personal_best' function") -personal_top_three : List Score -> List Score -personal_top_three = |scores| - crash("Please implement the 'personal_top_three' function") +Score : U64 diff --git a/exercises/practice/house/.meta/Example.roc b/exercises/practice/house/.meta/Example.roc index 11ec29e3..4a65237d 100644 --- a/exercises/practice/house/.meta/Example.roc +++ b/exercises/practice/house/.meta/Example.roc @@ -1,4 +1,11 @@ -module [recite] +House :: {}.{ + recite : U64, U64 -> Str + recite = |start_verse, end_verse| + List.range({ start: At(start_verse), end: At(end_verse) }) + |> List.map(verse) + |> Str.join_with("\n") +} + segments = [ "house that Jack built.", @@ -22,9 +29,3 @@ verse = |index| |> List.reverse |> Str.join_with(" the ") "This is the ${blablabla}" - -recite : U64, U64 -> Str -recite = |start_verse, end_verse| - List.range({ start: At(start_verse), end: At(end_verse) }) - |> List.map(verse) - |> Str.join_with("\n") diff --git a/exercises/practice/house/House.roc b/exercises/practice/house/House.roc index 90ae8aa3..47cc0c75 100644 --- a/exercises/practice/house/House.roc +++ b/exercises/practice/house/House.roc @@ -1,5 +1,5 @@ -module [recite] - -recite : U64, U64 -> Str -recite = |start_verse, end_verse| - crash("Please implement the 'recite' function") +House :: {}.{ + recite : U64, U64 -> Str + recite = |start_verse, end_verse| + crash("Please implement the 'recite' function") +} diff --git a/exercises/practice/isbn-verifier/.meta/Example.roc b/exercises/practice/isbn-verifier/.meta/Example.roc index 7be61400..9290d95f 100644 --- a/exercises/practice/isbn-verifier/.meta/Example.roc +++ b/exercises/practice/isbn-verifier/.meta/Example.roc @@ -1,4 +1,20 @@ -module [is_valid] +IsbnVerifier :: {}.{ + is_valid : Str -> Bool + is_valid = |isbn| + chars = + isbn + |> Str.to_utf8 + |> List.drop_if(|char| char == '-') + if List.len(chars) != 10 then + Bool.False + else + values = + chars + |> List.map_with_index(char_value) + |> List.keep_oks(|v| v) + List.len(values) == 10 and (List.sum(values)) % 11 == 0 +} + char_value = |char, index| if char == 'X' then @@ -10,18 +26,3 @@ char_value = |char, index| (10 - index) * (Num.int_cast((char - '0'))) |> Ok else Err(InvalidIsbnBadChar) - -is_valid : Str -> Bool -is_valid = |isbn| - chars = - isbn - |> Str.to_utf8 - |> List.drop_if(|char| char == '-') - if List.len(chars) != 10 then - Bool.False - else - values = - chars - |> List.map_with_index(char_value) - |> List.keep_oks(|v| v) - List.len(values) == 10 and (List.sum(values)) % 11 == 0 diff --git a/exercises/practice/isbn-verifier/IsbnVerifier.roc b/exercises/practice/isbn-verifier/IsbnVerifier.roc index 16c47251..d6cd0517 100644 --- a/exercises/practice/isbn-verifier/IsbnVerifier.roc +++ b/exercises/practice/isbn-verifier/IsbnVerifier.roc @@ -1,5 +1,5 @@ -module [is_valid] - -is_valid : Str -> Bool -is_valid = |isbn| - crash("Please implement the 'is_valid' function") +IsbnVerifier :: {}.{ + is_valid : Str -> Bool + is_valid = |isbn| + crash("Please implement the 'is_valid' function") +} diff --git a/exercises/practice/isogram/.meta/Example.roc b/exercises/practice/isogram/.meta/Example.roc index e8858127..7d1157ff 100644 --- a/exercises/practice/isogram/.meta/Example.roc +++ b/exercises/practice/isogram/.meta/Example.roc @@ -1,12 +1,12 @@ -module [is_isogram] +Isogram :: {}.{ + is_isogram : Str -> Bool + is_isogram = |phrase| + chars = + phrase + |> Str.to_utf8 + |> List.drop_if(|c| c == ' ' or c == '-') + |> List.map(|c| if c >= 'a' and c <= 'z' then c + 'A' - 'a' else c) # to uppercase -is_isogram : Str -> Bool -is_isogram = |phrase| - chars = - phrase - |> Str.to_utf8 - |> List.drop_if(|c| c == ' ' or c == '-') - |> List.map(|c| if c >= 'a' and c <= 'z' then c + 'A' - 'a' else c) # to uppercase - - (List.len(chars)) == Set.len(Set.from_list(chars)) + (List.len(chars)) == Set.len(Set.from_list(chars)) +} diff --git a/exercises/practice/isogram/Isogram.roc b/exercises/practice/isogram/Isogram.roc index fad2b384..b829ac51 100644 --- a/exercises/practice/isogram/Isogram.roc +++ b/exercises/practice/isogram/Isogram.roc @@ -1,5 +1,5 @@ -module [is_isogram] - -is_isogram : Str -> Bool -is_isogram = |phrase| - crash("Please implement the 'is_isogram' function") +Isogram :: {}.{ + is_isogram : Str -> Bool + is_isogram = |phrase| + crash("Please implement the 'is_isogram' function") +} diff --git a/exercises/practice/killer-sudoku-helper/.meta/Example.roc b/exercises/practice/killer-sudoku-helper/.meta/Example.roc index 305cb429..25e623e8 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/Example.roc +++ b/exercises/practice/killer-sudoku-helper/.meta/Example.roc @@ -1,26 +1,27 @@ -module [combinations] +KillerSudokuHelper :: {}.{ + combinations : { sum : U8, size : U8, exclude ?? List U8 } -> List Combination + combinations = |{ sum, size, exclude ?? [] }| + help = |target, digits| + if target == 0 then + [[]] + else + when digits is + [] -> [] + [single] -> if single == target then [[single]] else [] + [first, .. as rest] -> + if first > target then + [] + else + help((target - first), rest) + |> List.map(|combi| combi |> List.append(first)) + |> List.concat(help(target, rest)) + available_digits = + [1, 2, 3, 4, 5, 6, 7, 8, 9] + |> List.drop_if(|digit| exclude |> List.contains(digit)) + help(sum, available_digits) + |> List.keep_if(|combi| List.len(combi) == size |> Num.to_u64) + |> List.map(List.reverse) +} -Combination : List U8 -combinations : { sum : U8, size : U8, exclude ?? List U8 } -> List Combination -combinations = |{ sum, size, exclude ?? [] }| - help = |target, digits| - if target == 0 then - [[]] - else - when digits is - [] -> [] - [single] -> if single == target then [[single]] else [] - [first, .. as rest] -> - if first > target then - [] - else - help((target - first), rest) - |> List.map(|combi| combi |> List.append(first)) - |> List.concat(help(target, rest)) - available_digits = - [1, 2, 3, 4, 5, 6, 7, 8, 9] - |> List.drop_if(|digit| exclude |> List.contains(digit)) - help(sum, available_digits) - |> List.keep_if(|combi| List.len(combi) == size |> Num.to_u64) - |> List.map(List.reverse) +Combination : List U8 diff --git a/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.roc b/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.roc index 107376d0..92e9e118 100644 --- a/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.roc +++ b/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.roc @@ -1,7 +1,8 @@ -module [combinations] +KillerSudokuHelper :: {}.{ + combinations : { sum : U8, size : U8, exclude ?? List U8 } -> List Combination + combinations = |{ sum, size, exclude ?? [] }| + crash("Please implement the 'combinations' function") +} -Combination : List U8 -combinations : { sum : U8, size : U8, exclude ?? List U8 } -> List Combination -combinations = |{ sum, size, exclude ?? [] }| - crash("Please implement the 'combinations' function") +Combination : List U8 diff --git a/exercises/practice/kindergarten-garden/.meta/Example.roc b/exercises/practice/kindergarten-garden/.meta/Example.roc index 4a2d6e4b..43c9c6a4 100644 --- a/exercises/practice/kindergarten-garden/.meta/Example.roc +++ b/exercises/practice/kindergarten-garden/.meta/Example.roc @@ -1,4 +1,21 @@ -module [plants] +KindergartenGarden :: {}.{ + plants : Str, Student -> Result (List Plant) _ + plants = |diagram, student| + start_index = 2 * student_index(student) + grid = diagram |> Str.to_utf8 |> List.split_on('\n') + [(0, 0), (0, 1), (1, 0), (1, 1)] + |> List.map_try( + |(row, column)| + plant = grid |> List.get(row)? |> List.get(start_index + column)? + when plant is + 'G' -> Ok(Grass) + 'C' -> Ok(Clover) + 'R' -> Ok(Radishes) + 'V' -> Ok(Violets) + _ -> Err(UnknownPlant(plant)), + ) +} + Student : [Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, Larry] Plant : [Grass, Clover, Radishes, Violets] @@ -18,19 +35,3 @@ student_index = |student| Joseph -> 9 Kincaid -> 10 Larry -> 11 - -plants : Str, Student -> Result (List Plant) _ -plants = |diagram, student| - start_index = 2 * student_index(student) - grid = diagram |> Str.to_utf8 |> List.split_on('\n') - [(0, 0), (0, 1), (1, 0), (1, 1)] - |> List.map_try( - |(row, column)| - plant = grid |> List.get(row)? |> List.get(start_index + column)? - when plant is - 'G' -> Ok(Grass) - 'C' -> Ok(Clover) - 'R' -> Ok(Radishes) - 'V' -> Ok(Violets) - _ -> Err(UnknownPlant(plant)), - ) diff --git a/exercises/practice/kindergarten-garden/KindergartenGarden.roc b/exercises/practice/kindergarten-garden/KindergartenGarden.roc index 0fe4c6bc..a86914f7 100644 --- a/exercises/practice/kindergarten-garden/KindergartenGarden.roc +++ b/exercises/practice/kindergarten-garden/KindergartenGarden.roc @@ -1,8 +1,9 @@ -module [plants] +KindergartenGarden :: {}.{ + plants : Str, Student -> Result (List Plant) _ + plants = |diagram, student| + crash("Please implement the 'plants' function") +} + Student : [Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, Larry] Plant : [Grass, Clover, Radishes, Violets] - -plants : Str, Student -> Result (List Plant) _ -plants = |diagram, student| - crash("Please implement the 'plants' function") diff --git a/exercises/practice/knapsack/.meta/Example.roc b/exercises/practice/knapsack/.meta/Example.roc index d981c8dc..f5c0508e 100644 --- a/exercises/practice/knapsack/.meta/Example.roc +++ b/exercises/practice/knapsack/.meta/Example.roc @@ -1,15 +1,16 @@ -module [maximum_value] +Knapsack :: {}.{ + maximum_value : { items : List Item, maximum_weight : U64 } -> U64 + maximum_value = |{ items, maximum_weight }| + when items is + [] -> 0 + [item, .. as rest] -> + max_value_without_item = maximum_value({ items: rest, maximum_weight }) + if item.weight > maximum_weight then + max_value_without_item + else + max_value_with_item = item.value + maximum_value({ items: rest, maximum_weight: maximum_weight - item.weight }) + Num.max(max_value_without_item, max_value_with_item) +} -Item : { weight : U64, value : U64 } -maximum_value : { items : List Item, maximum_weight : U64 } -> U64 -maximum_value = |{ items, maximum_weight }| - when items is - [] -> 0 - [item, .. as rest] -> - max_value_without_item = maximum_value({ items: rest, maximum_weight }) - if item.weight > maximum_weight then - max_value_without_item - else - max_value_with_item = item.value + maximum_value({ items: rest, maximum_weight: maximum_weight - item.weight }) - Num.max(max_value_without_item, max_value_with_item) +Item : { weight : U64, value : U64 } diff --git a/exercises/practice/knapsack/Knapsack.roc b/exercises/practice/knapsack/Knapsack.roc index 07aa9504..c9d9bb09 100644 --- a/exercises/practice/knapsack/Knapsack.roc +++ b/exercises/practice/knapsack/Knapsack.roc @@ -1,7 +1,8 @@ -module [maximum_value] +Knapsack :: {}.{ + maximum_value : { items : List Item, maximum_weight : U64 } -> U64 + maximum_value = |{ items, maximum_weight }| + crash("Please implement the 'maximum_value' function") +} -Item : { weight : U64, value : U64 } -maximum_value : { items : List Item, maximum_weight : U64 } -> U64 -maximum_value = |{ items, maximum_weight }| - crash("Please implement the 'maximum_value' function") +Item : { weight : U64, value : U64 } diff --git a/exercises/practice/largest-series-product/.meta/Example.roc b/exercises/practice/largest-series-product/.meta/Example.roc index 2930e049..c3ff36eb 100644 --- a/exercises/practice/largest-series-product/.meta/Example.roc +++ b/exercises/practice/largest-series-product/.meta/Example.roc @@ -1,27 +1,27 @@ -module [largest_product] - -largest_product : Str, U64 -> Result U64 [SpanWasTooLarge, InvalidDigit] -largest_product = |digits, span| - if span == 0 then - Ok(1) - else - chars = digits |> Str.to_utf8 - if List.len(chars) < span then - Err(SpanWasTooLarge) - else if chars |> List.any(|char| char < '0' or char > '9') then - Err(InvalidDigit) +LargestSeriesProduct :: {}.{ + largest_product : Str, U64 -> Result U64 [SpanWasTooLarge, InvalidDigit] + largest_product = |digits, span| + if span == 0 then + Ok(1) else - List.range({ start: At(0), end: At((List.len(chars) - span)) }) - |> List.map( - |start_index| - chars - |> List.sublist({ start: start_index, len: span }) - |> List.walk( - 1, - |product, char| - product * (char - '0' |> Num.to_u64), - ), - ) - |> List.max - |> Result.on_err(|ListWasEmpty| crash("Unreachable: the list cannot be empty here")) + chars = digits |> Str.to_utf8 + if List.len(chars) < span then + Err(SpanWasTooLarge) + else if chars |> List.any(|char| char < '0' or char > '9') then + Err(InvalidDigit) + else + List.range({ start: At(0), end: At((List.len(chars) - span)) }) + |> List.map( + |start_index| + chars + |> List.sublist({ start: start_index, len: span }) + |> List.walk( + 1, + |product, char| + product * (char - '0' |> Num.to_u64), + ), + ) + |> List.max + |> Result.on_err(|ListWasEmpty| crash("Unreachable: the list cannot be empty here")) +} diff --git a/exercises/practice/largest-series-product/LargestSeriesProduct.roc b/exercises/practice/largest-series-product/LargestSeriesProduct.roc index a2721951..b24faa85 100644 --- a/exercises/practice/largest-series-product/LargestSeriesProduct.roc +++ b/exercises/practice/largest-series-product/LargestSeriesProduct.roc @@ -1,5 +1,5 @@ -module [largest_product] - -largest_product : Str, U64 -> Result U64 _ -largest_product = |digits, span| - crash("Please implement the 'largest_product' function") +LargestSeriesProduct :: {}.{ + largest_product : Str, U64 -> Result U64 _ + largest_product = |digits, span| + crash("Please implement the 'largest_product' function") +} diff --git a/exercises/practice/leap/.meta/Example.roc b/exercises/practice/leap/.meta/Example.roc index e12fb53f..fc3b71bf 100644 --- a/exercises/practice/leap/.meta/Example.roc +++ b/exercises/practice/leap/.meta/Example.roc @@ -1,5 +1,5 @@ -module [is_leap_year] - -is_leap_year : I64 -> Bool -is_leap_year = |year| - (year % 4 == 0) and (year % 400 == 0 or year % 100 != 0) +Leap :: {}.{ + is_leap_year : I64 -> Bool + is_leap_year = |year| + (year % 4 == 0) and (year % 400 == 0 or year % 100 != 0) +} diff --git a/exercises/practice/leap/Leap.roc b/exercises/practice/leap/Leap.roc index 00789433..062fb970 100644 --- a/exercises/practice/leap/Leap.roc +++ b/exercises/practice/leap/Leap.roc @@ -1,5 +1,5 @@ -module [is_leap_year] - -is_leap_year : I64 -> Bool -is_leap_year = |year| - crash("Please implement the 'is_leap_year' function") +Leap :: {}.{ + is_leap_year : I64 -> Bool + is_leap_year = |year| + crash("Please implement the 'is_leap_year' function") +} diff --git a/exercises/practice/list-ops/.meta/Example.roc b/exercises/practice/list-ops/.meta/Example.roc index 723cd03f..82a1dbc9 100644 --- a/exercises/practice/list-ops/.meta/Example.roc +++ b/exercises/practice/list-ops/.meta/Example.roc @@ -1,71 +1,71 @@ -module [append, concat, filter, length, map, foldl, foldr, reverse] +ListOps :: {}.{ + append : List a, List a -> List a + append = |list1, list2| + # Cheating: list1 |> List.concat list2 + when list2 is + [] -> list1 + [first, .. as rest] -> list1 |> List.append(first) |> append(rest) -append : List a, List a -> List a -append = |list1, list2| - # Cheating: list1 |> List.concat list2 - when list2 is - [] -> list1 - [first, .. as rest] -> list1 |> List.append(first) |> append(rest) + concat : List (List a) -> List a + concat = |lists| + # Cheating: list |> List.join + when lists is + [] -> [] + [one] -> one + [.. as rest, one, two] -> rest |> List.append(append(one, two)) |> concat -concat : List (List a) -> List a -concat = |lists| - # Cheating: list |> List.join - when lists is - [] -> [] - [one] -> one - [.. as rest, one, two] -> rest |> List.append(append(one, two)) |> concat + filter : List a, (a -> Bool) -> List a + filter = |list, function| + # Cheating: list |> List.keep_if function + loop = |l, acc| + when l is + [] -> acc + [first, .. as rest] -> + if function(first) then + rest |> loop(List.append(acc, first)) + else + rest |> loop(acc) -filter : List a, (a -> Bool) -> List a -filter = |list, function| - # Cheating: list |> List.keep_if function - loop = |l, acc| - when l is - [] -> acc - [first, .. as rest] -> - if function(first) then - rest |> loop(List.append(acc, first)) - else - rest |> loop(acc) + loop(list, []) - loop(list, []) + length : List a -> U64 + length = |list| + # Cheating: list |> List.len + loop = |l, acc| + when l is + [] -> acc + [_, .. as rest] -> rest |> loop((acc + 1)) + loop(list, 0) -length : List a -> U64 -length = |list| - # Cheating: list |> List.len - loop = |l, acc| - when l is - [] -> acc - [_, .. as rest] -> rest |> loop((acc + 1)) - loop(list, 0) + map : List a, (a -> b) -> List b + map = |list, function| + # Cheating: list |> List.map function + loop = |l, acc| + when l is + [] -> acc + [first, .. as rest] -> rest |> loop(List.append(acc, function(first))) + loop(list, []) -map : List a, (a -> b) -> List b -map = |list, function| - # Cheating: list |> List.map function - loop = |l, acc| - when l is - [] -> acc - [first, .. as rest] -> rest |> loop(List.append(acc, function(first))) - loop(list, []) + foldl : List a, b, (b, a -> b) -> b + foldl = |list, initial, function| + # Cheating: list |> List.walk initial function + when list is + [] -> initial + [first, .. as rest] -> rest |> foldl(function(initial, first), function) -foldl : List a, b, (b, a -> b) -> b -foldl = |list, initial, function| - # Cheating: list |> List.walk initial function - when list is - [] -> initial - [first, .. as rest] -> rest |> foldl(function(initial, first), function) + foldr : List a, b, (b, a -> b) -> b + foldr = |list, initial, function| + # Cheating: list |> List.walk_backwards initial function + when list is + [] -> initial + [.. as rest, last] -> rest |> foldr(function(initial, last), function) -foldr : List a, b, (b, a -> b) -> b -foldr = |list, initial, function| - # Cheating: list |> List.walk_backwards initial function - when list is - [] -> initial - [.. as rest, last] -> rest |> foldr(function(initial, last), function) - -reverse : List a -> List a -reverse = |list| - # Cheating: list |> List.reverse - loop = |l, acc| - when l is - [] -> acc - [.. as rest, last] -> rest |> loop(List.append(acc, last)) - loop(list, []) + reverse : List a -> List a + reverse = |list| + # Cheating: list |> List.reverse + loop = |l, acc| + when l is + [] -> acc + [.. as rest, last] -> rest |> loop(List.append(acc, last)) + loop(list, []) +} diff --git a/exercises/practice/list-ops/ListOps.roc b/exercises/practice/list-ops/ListOps.roc index 3e5762a8..85acec2f 100644 --- a/exercises/practice/list-ops/ListOps.roc +++ b/exercises/practice/list-ops/ListOps.roc @@ -1,33 +1,33 @@ -module [append, concat, filter, length, map, foldl, foldr, reverse] - -append : List a, List a -> List a -append = |list1, list2| - crash("Please implement the 'append' function") - -concat : List (List a) -> List a -concat = |lists| - crash("Please implement the 'concat' function") - -filter : List a, (a -> Bool) -> List a -filter = |list, function| - crash("Please implement the 'filter' function") - -length : List a -> U64 -length = |list| - crash("Please implement the 'length' function") - -map : List a, (a -> b) -> List b -map = |list, function| - crash("Please implement the 'map' function") - -foldl : List a, b, (b, a -> b) -> b -foldl = |list, initial, function| - crash("Please implement the 'foldl' function") - -foldr : List a, b, (b, a -> b) -> b -foldr = |list, initial, function| - crash("Please implement the 'foldr' function") - -reverse : List a -> List a -reverse = |list| - crash("Please implement the 'reverse' function") +ListOps :: {}.{ + append : List a, List a -> List a + append = |list1, list2| + crash("Please implement the 'append' function") + + concat : List (List a) -> List a + concat = |lists| + crash("Please implement the 'concat' function") + + filter : List a, (a -> Bool) -> List a + filter = |list, function| + crash("Please implement the 'filter' function") + + length : List a -> U64 + length = |list| + crash("Please implement the 'length' function") + + map : List a, (a -> b) -> List b + map = |list, function| + crash("Please implement the 'map' function") + + foldl : List a, b, (b, a -> b) -> b + foldl = |list, initial, function| + crash("Please implement the 'foldl' function") + + foldr : List a, b, (b, a -> b) -> b + foldr = |list, initial, function| + crash("Please implement the 'foldr' function") + + reverse : List a -> List a + reverse = |list| + crash("Please implement the 'reverse' function") +} diff --git a/exercises/practice/luhn/.meta/Example.roc b/exercises/practice/luhn/.meta/Example.roc index 7624f9df..822383f0 100644 --- a/exercises/practice/luhn/.meta/Example.roc +++ b/exercises/practice/luhn/.meta/Example.roc @@ -1,19 +1,19 @@ -module [valid] +Luhn :: {}.{ + valid : Str -> Bool + valid = |number| + when to_digits(number) is + Ok(digits) if List.len(digits) > 1 -> + map_every_other_backwards( + digits, + |digit| + product = digit * 2 + if product < 10 then product else product - 9, + ) + |> List.sum + |> Num.is_multiple_of(10) -valid : Str -> Bool -valid = |number| - when to_digits(number) is - Ok(digits) if List.len(digits) > 1 -> - map_every_other_backwards( - digits, - |digit| - product = digit * 2 - if product < 10 then product else product - 9, - ) - |> List.sum - |> Num.is_multiple_of(10) - - _ -> Bool.False + _ -> Bool.False +} to_digits : Str -> Result (List U16) [IllegalCharacter] to_digits = |number| diff --git a/exercises/practice/luhn/Luhn.roc b/exercises/practice/luhn/Luhn.roc index 90952c5b..25f7a343 100644 --- a/exercises/practice/luhn/Luhn.roc +++ b/exercises/practice/luhn/Luhn.roc @@ -1,5 +1,5 @@ -module [valid] - -valid : Str -> Bool -valid = |digits| - crash("Please implement the 'valid' function") +Luhn :: {}.{ + valid : Str -> Bool + valid = |digits| + crash("Please implement the 'valid' function") +} diff --git a/exercises/practice/matching-brackets/.meta/Example.roc b/exercises/practice/matching-brackets/.meta/Example.roc index e83b532a..3f670fc5 100644 --- a/exercises/practice/matching-brackets/.meta/Example.roc +++ b/exercises/practice/matching-brackets/.meta/Example.roc @@ -1,25 +1,25 @@ -module [is_paired] +MatchingBrackets :: {}.{ + is_paired : Str -> Bool + is_paired = |string| + is_open = |c| c == '[' or c == '(' or c == '{' + is_close = |c| c == ']' or c == ')' or c == '}' + is_match = |pair| pair == ('[', ']') or pair == ('(', ')') or pair == ('{', '}') + help = |open_brackets, remaining_chars| + when remaining_chars is + [] -> List.is_empty(open_brackets) # ok or missing closing bracket + [next_char, .. as rest_chars] -> + if is_open(next_char) then + help((open_brackets |> List.append(next_char)), rest_chars) + else if is_close(next_char) then + when open_brackets is + [] -> Bool.False # missing opening bracket + [.. as previous_opens, last_open] -> + if is_match((last_open, next_char)) then + help(previous_opens, rest_chars) + else + Bool.False # mismatching brackets + else + help(open_brackets, rest_chars) -is_paired : Str -> Bool -is_paired = |string| - is_open = |c| c == '[' or c == '(' or c == '{' - is_close = |c| c == ']' or c == ')' or c == '}' - is_match = |pair| pair == ('[', ']') or pair == ('(', ')') or pair == ('{', '}') - help = |open_brackets, remaining_chars| - when remaining_chars is - [] -> List.is_empty(open_brackets) # ok or missing closing bracket - [next_char, .. as rest_chars] -> - if is_open(next_char) then - help((open_brackets |> List.append(next_char)), rest_chars) - else if is_close(next_char) then - when open_brackets is - [] -> Bool.False # missing opening bracket - [.. as previous_opens, last_open] -> - if is_match((last_open, next_char)) then - help(previous_opens, rest_chars) - else - Bool.False # mismatching brackets - else - help(open_brackets, rest_chars) - - help([], (string |> Str.to_utf8)) + help([], (string |> Str.to_utf8)) +} diff --git a/exercises/practice/matching-brackets/MatchingBrackets.roc b/exercises/practice/matching-brackets/MatchingBrackets.roc index 0ea9898f..2ef0c547 100644 --- a/exercises/practice/matching-brackets/MatchingBrackets.roc +++ b/exercises/practice/matching-brackets/MatchingBrackets.roc @@ -1,5 +1,5 @@ -module [is_paired] - -is_paired : Str -> Bool -is_paired = |string| - crash("Please implement the 'is_paired' function") +MatchingBrackets :: {}.{ + is_paired : Str -> Bool + is_paired = |string| + crash("Please implement the 'is_paired' function") +} diff --git a/exercises/practice/matrix/.meta/Example.roc b/exercises/practice/matrix/.meta/Example.roc index 72692ef5..5414e8f7 100644 --- a/exercises/practice/matrix/.meta/Example.roc +++ b/exercises/practice/matrix/.meta/Example.roc @@ -1,4 +1,22 @@ -module [row, column] +Matrix :: {}.{ + column : Str, U64 -> Result (List I64) [InvalidNumStr, OutOfBounds] + column = |matrix_str, index| + if index == 0 then + Err(OutOfBounds) + else + matrix = parse_matrix(matrix_str)? + matrix |> List.map_try(|r| r |> List.get((index - 1))) + + row : Str, U64 -> Result (List I64) [InvalidNumStr, OutOfBounds] + row = |matrix_str, index| + if index == 0 then + Err(OutOfBounds) + else + matrix = parse_matrix(matrix_str)? + result = matrix |> List.get(index - 1)? + Ok(result) +} + parse_row : Str -> Result (List I64) [InvalidNumStr] parse_row = |row_str| @@ -14,20 +32,3 @@ parse_matrix = |matrix_str| matrix_str |> Str.split_on("\n") |> List.map_try(parse_row) - -column : Str, U64 -> Result (List I64) [InvalidNumStr, OutOfBounds] -column = |matrix_str, index| - if index == 0 then - Err(OutOfBounds) - else - matrix = parse_matrix(matrix_str)? - matrix |> List.map_try(|r| r |> List.get((index - 1))) - -row : Str, U64 -> Result (List I64) [InvalidNumStr, OutOfBounds] -row = |matrix_str, index| - if index == 0 then - Err(OutOfBounds) - else - matrix = parse_matrix(matrix_str)? - result = matrix |> List.get(index - 1)? - Ok(result) diff --git a/exercises/practice/matrix/Matrix.roc b/exercises/practice/matrix/Matrix.roc index c37de1b0..b77057f5 100644 --- a/exercises/practice/matrix/Matrix.roc +++ b/exercises/practice/matrix/Matrix.roc @@ -1,9 +1,9 @@ -module [row, column] +Matrix :: {}.{ + column : Str, U64 -> Result (List I64) _ + column = |matrix_str, index| + crash("Please implement the 'column' function") -column : Str, U64 -> Result (List I64) _ -column = |matrix_str, index| - crash("Please implement the 'column' function") - -row : Str, U64 -> Result (List I64) _ -row = |matrix_str, index| - crash("Please implement the 'row' function") + row : Str, U64 -> Result (List I64) _ + row = |matrix_str, index| + crash("Please implement the 'row' function") +} diff --git a/exercises/practice/meetup/.meta/Example.roc b/exercises/practice/meetup/.meta/Example.roc index 382010f6..f7cdabae 100644 --- a/exercises/practice/meetup/.meta/Example.roc +++ b/exercises/practice/meetup/.meta/Example.roc @@ -1,34 +1,35 @@ -module [meetup] + import isodate.Date +Meetup :: {}.{ + meetup : { year : I64, month : U8, week : Week, day_of_week : DayOfWeek } -> Result Str [InvalidMonth, InvalidYear] + meetup = |{ year, month, week, day_of_week }| + if month == 0 or month > 12 then + Err(InvalidMonth) + else + first_day = Date.from_ymd(year, month, 1) + first_weekday = Date.weekday(first_day.year, first_day.month, first_day.day_of_month) + first_time = (7 + day_of_week_number(day_of_week) - first_weekday) % 7 + 1 + day_of_month = + when week is + First -> first_time + Second -> first_time + 7 + Third -> first_time + 14 + Fourth -> first_time + 21 + Last -> + if first_time + 28 > Date.days_in_month(year, month) then + first_time + 21 + else + first_time + 28 + + Teenth -> + if first_time == 6 then 13 else first_time + 14 + Ok("${year |> pad_number(4)}-${month |> pad_number(2)}-${day_of_month |> pad_number(2)}") +} Week : [First, Second, Third, Fourth, Last, Teenth] DayOfWeek : [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] -meetup : { year : I64, month : U8, week : Week, day_of_week : DayOfWeek } -> Result Str [InvalidMonth, InvalidYear] -meetup = |{ year, month, week, day_of_week }| - if month == 0 or month > 12 then - Err(InvalidMonth) - else - first_day = Date.from_ymd(year, month, 1) - first_weekday = Date.weekday(first_day.year, first_day.month, first_day.day_of_month) - first_time = (7 + day_of_week_number(day_of_week) - first_weekday) % 7 + 1 - day_of_month = - when week is - First -> first_time - Second -> first_time + 7 - Third -> first_time + 14 - Fourth -> first_time + 21 - Last -> - if first_time + 28 > Date.days_in_month(year, month) then - first_time + 21 - else - first_time + 28 - - Teenth -> - if first_time == 6 then 13 else first_time + 14 - Ok("${year |> pad_number(4)}-${month |> pad_number(2)}-${day_of_month |> pad_number(2)}") - pad_number : Num *, U64 -> Str pad_number = |number, pad| number_str = number |> Num.to_str diff --git a/exercises/practice/meetup/Meetup.roc b/exercises/practice/meetup/Meetup.roc index 4132bf1b..4430344a 100644 --- a/exercises/practice/meetup/Meetup.roc +++ b/exercises/practice/meetup/Meetup.roc @@ -1,12 +1,13 @@ -module [meetup] +Meetup :: {}.{ + meetup : { year : I64, month : U8, week : Week, day_of_week : DayOfWeek } -> Result Str _ + meetup = |{ year, month, week, day_of_week }| + crash("Please implement the 'meetup' function") -Week : [First, Second, Third, Fourth, Last, Teenth] -DayOfWeek : [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] + # HINT: we have added the `roc-isodate` package to the app's header in + # meetup-test.roc, so you can use it here if you need to. + # For example, you could import isodate.Date, just sayin'. +} -meetup : { year : I64, month : U8, week : Week, day_of_week : DayOfWeek } -> Result Str _ -meetup = |{ year, month, week, day_of_week }| - crash("Please implement the 'meetup' function") -# HINT: we have added the `roc-isodate` package to the app's header in -# meetup-test.roc, so you can use it here if you need to. -# For example, you could import isodate.Date, just sayin'. +Week : [First, Second, Third, Fourth, Last, Teenth] +DayOfWeek : [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] diff --git a/exercises/practice/micro-blog/.meta/Example.roc b/exercises/practice/micro-blog/.meta/Example.roc index 8c5d2260..e9be92f8 100644 --- a/exercises/practice/micro-blog/.meta/Example.roc +++ b/exercises/practice/micro-blog/.meta/Example.roc @@ -1,6 +1,15 @@ -module [truncate] + import unicode.Grapheme +MicroBlog :: {}.{ + truncate : Str -> Result Str GraphemeErrors + truncate = |input| + input + |> Grapheme.split? + |> List.take_first(5) + |> Str.join_with("") + |> Ok +} GraphemeErrors : [ CodepointTooLarge, @@ -10,11 +19,3 @@ GraphemeErrors : [ ListWasEmpty, OverlongEncoding, ] - -truncate : Str -> Result Str GraphemeErrors -truncate = |input| - input - |> Grapheme.split? - |> List.take_first(5) - |> Str.join_with("") - |> Ok diff --git a/exercises/practice/micro-blog/MicroBlog.roc b/exercises/practice/micro-blog/MicroBlog.roc index c3f0d9c3..4309a21b 100644 --- a/exercises/practice/micro-blog/MicroBlog.roc +++ b/exercises/practice/micro-blog/MicroBlog.roc @@ -1,7 +1,8 @@ -module [truncate] -import unicode.Grapheme -truncate : Str -> Result Str _ -truncate = |input| - crash("Please implement the 'truncate' function") +import unicode.Grapheme +MicroBlog :: {}.{ + truncate : Str -> Result Str _ + truncate = |input| + crash("Please implement the 'truncate' function") +} diff --git a/exercises/practice/minesweeper/.meta/Example.roc b/exercises/practice/minesweeper/.meta/Example.roc index 15c2b2ad..abca0126 100644 --- a/exercises/practice/minesweeper/.meta/Example.roc +++ b/exercises/practice/minesweeper/.meta/Example.roc @@ -1,4 +1,34 @@ -module [annotate] +Minesweeper :: {}.{ + annotate : Str -> Str + annotate = |minefield| + rows = minefield |> Str.to_utf8 |> List.split_on('\n') + annotated = + rows + |> List.map_with_index( + |row, y| + row + |> List.map_with_index( + |cell, x| + if cell == '*' then + '*' + else + when count_neighbors(rows, x, y) is + 0 -> ' ' + n -> '0' + n, + ) + |> Str.from_utf8, + ) + + annotated + |> List.map( + |maybe_row| + when maybe_row is + Ok(row) -> row + Err(_) -> crash("Unreachable"), + ) # fromUtf8 cannot fail in the code above + |> Str.join_with("\n") +} + is_bomb : List (List U8), I64, I64 -> Result Bool [OutOfBounds] is_bomb = |rows, nx, ny| @@ -25,32 +55,3 @@ count_neighbors = |rows, x, y| |> List.sum, ) |> List.sum - -annotate : Str -> Str -annotate = |minefield| - rows = minefield |> Str.to_utf8 |> List.split_on('\n') - annotated = - rows - |> List.map_with_index( - |row, y| - row - |> List.map_with_index( - |cell, x| - if cell == '*' then - '*' - else - when count_neighbors(rows, x, y) is - 0 -> ' ' - n -> '0' + n, - ) - |> Str.from_utf8, - ) - - annotated - |> List.map( - |maybe_row| - when maybe_row is - Ok(row) -> row - Err(_) -> crash("Unreachable"), - ) # fromUtf8 cannot fail in the code above - |> Str.join_with("\n") diff --git a/exercises/practice/minesweeper/Minesweeper.roc b/exercises/practice/minesweeper/Minesweeper.roc index d965e113..cc5963d1 100644 --- a/exercises/practice/minesweeper/Minesweeper.roc +++ b/exercises/practice/minesweeper/Minesweeper.roc @@ -1,5 +1,5 @@ -module [annotate] - -annotate : Str -> Str -annotate = |minefield| - crash("Please implement the 'annotate' function") +Minesweeper :: {}.{ + annotate : Str -> Str + annotate = |minefield| + crash("Please implement the 'annotate' function") +} diff --git a/exercises/practice/nth-prime/.meta/Example.roc b/exercises/practice/nth-prime/.meta/Example.roc index 60dd0e3a..81c32578 100644 --- a/exercises/practice/nth-prime/.meta/Example.roc +++ b/exercises/practice/nth-prime/.meta/Example.roc @@ -1,28 +1,28 @@ -module [prime] - -prime : U64 -> Result U64 [NoPrime0] -prime = |number| - if number == 0 then - Err(NoPrime0) - else if number == 1 then - Ok(2) - else if number == 2 then - Ok(3) - else - find_prime = |primes, index| - if List.len(primes) == number then - primes - else - next_index = index + 2 - new_primes = - if primes |> List.any(|p| next_index % p == 0) then - primes - else - primes |> List.append(next_index) - find_prime(new_primes, next_index) - find_prime([2, 3, 5], 5) - |> List.last - |> Result.map_err( - |_| - crash("Unreachable: list cannot be empty"), - ) +NthPrime :: {}.{ + prime : U64 -> Result U64 [NoPrime0] + prime = |number| + if number == 0 then + Err(NoPrime0) + else if number == 1 then + Ok(2) + else if number == 2 then + Ok(3) + else + find_prime = |primes, index| + if List.len(primes) == number then + primes + else + next_index = index + 2 + new_primes = + if primes |> List.any(|p| next_index % p == 0) then + primes + else + primes |> List.append(next_index) + find_prime(new_primes, next_index) + find_prime([2, 3, 5], 5) + |> List.last + |> Result.map_err( + |_| + crash("Unreachable: list cannot be empty"), + ) +} diff --git a/exercises/practice/nth-prime/NthPrime.roc b/exercises/practice/nth-prime/NthPrime.roc index 51865d64..0b3eee81 100644 --- a/exercises/practice/nth-prime/NthPrime.roc +++ b/exercises/practice/nth-prime/NthPrime.roc @@ -1,5 +1,5 @@ -module [prime] - -prime : U64 -> Result U64 _ -prime = |number| - crash("Please implement the 'prime' function") +NthPrime :: {}.{ + prime : U64 -> Result U64 _ + prime = |number| + crash("Please implement the 'prime' function") +} diff --git a/exercises/practice/nucleotide-count/.meta/Example.roc b/exercises/practice/nucleotide-count/.meta/Example.roc index ca48c467..796af15e 100644 --- a/exercises/practice/nucleotide-count/.meta/Example.roc +++ b/exercises/practice/nucleotide-count/.meta/Example.roc @@ -1,15 +1,15 @@ -module [nucleotide_counts] - -nucleotide_counts : Str -> Result { a : U64, c : U64, g : U64, t : U64 } _ -nucleotide_counts = |input| - Str.to_utf8(input) - |> List.walk_try( - { a: 0, c: 0, g: 0, t: 0 }, - |state, elem| - when elem is - 'A' -> Ok({ state & a: state.a + 1 }) - 'C' -> Ok({ state & c: state.c + 1 }) - 'G' -> Ok({ state & g: state.g + 1 }) - 'T' -> Ok({ state & t: state.t + 1 }) - _ -> Err(InvalidNucleotide(elem)), - ) +NucleotideCount :: {}.{ + nucleotide_counts : Str -> Result { a : U64, c : U64, g : U64, t : U64 } _ + nucleotide_counts = |input| + Str.to_utf8(input) + |> List.walk_try( + { a: 0, c: 0, g: 0, t: 0 }, + |state, elem| + when elem is + 'A' -> Ok({ state & a: state.a + 1 }) + 'C' -> Ok({ state & c: state.c + 1 }) + 'G' -> Ok({ state & g: state.g + 1 }) + 'T' -> Ok({ state & t: state.t + 1 }) + _ -> Err(InvalidNucleotide(elem)), + ) +} diff --git a/exercises/practice/nucleotide-count/NucleotideCount.roc b/exercises/practice/nucleotide-count/NucleotideCount.roc index 4a3fc337..2df98b43 100644 --- a/exercises/practice/nucleotide-count/NucleotideCount.roc +++ b/exercises/practice/nucleotide-count/NucleotideCount.roc @@ -1,5 +1,5 @@ -module [nucleotide_counts] - -nucleotide_counts : Str -> Result { a : U64, c : U64, g : U64, t : U64 } _ -nucleotide_counts = |input| - crash("Please implement the 'nucleotide_counts' function") +NucleotideCount :: {}.{ + nucleotide_counts : Str -> Result { a : U64, c : U64, g : U64, t : U64 } _ + nucleotide_counts = |input| + crash("Please implement the 'nucleotide_counts' function") +} diff --git a/exercises/practice/ocr-numbers/.meta/Example.roc b/exercises/practice/ocr-numbers/.meta/Example.roc index e87e926f..98b1f54a 100644 --- a/exercises/practice/ocr-numbers/.meta/Example.roc +++ b/exercises/practice/ocr-numbers/.meta/Example.roc @@ -1,24 +1,25 @@ -module [convert] +OcrNumbers :: {}.{ + convert : Str -> Result Str BadGridSize + convert = |grid| + if grid == "" then + Ok("") + else + grid_chars = grid |> Str.to_utf8 |> List.split_on('\n') + size = check_size(grid_chars)? + grid_chars + |> List.chunks_of(4) # split vertically into groups of 4 rows + |> List.map( + |row_group| + get_digit_grids(row_group, size.width) + |> List.map(identify_digit) + |> Str.join_with(""), + ) + |> Str.join_with(",") + |> Ok +} -BadGridSize : [HeightWasNotAMultipleOf4, WidthWasNotAMultipleOf3, GridShapeWasNotRectangular] -convert : Str -> Result Str BadGridSize -convert = |grid| - if grid == "" then - Ok("") - else - grid_chars = grid |> Str.to_utf8 |> List.split_on('\n') - size = check_size(grid_chars)? - grid_chars - |> List.chunks_of(4) # split vertically into groups of 4 rows - |> List.map( - |row_group| - get_digit_grids(row_group, size.width) - |> List.map(identify_digit) - |> Str.join_with(""), - ) - |> Str.join_with(",") - |> Ok +BadGridSize : [HeightWasNotAMultipleOf4, WidthWasNotAMultipleOf3, GridShapeWasNotRectangular] check_size : List (List U8) -> Result { height : U64, width : U64 } BadGridSize check_size = |grid_chars| diff --git a/exercises/practice/ocr-numbers/OcrNumbers.roc b/exercises/practice/ocr-numbers/OcrNumbers.roc index dcabe3b7..bf4cdeff 100644 --- a/exercises/practice/ocr-numbers/OcrNumbers.roc +++ b/exercises/practice/ocr-numbers/OcrNumbers.roc @@ -1,5 +1,5 @@ -module [convert] - -convert : Str -> Result Str _ -convert = |grid| - crash("Please implement the 'convert' function") +OcrNumbers :: {}.{ + convert : Str -> Result Str _ + convert = |grid| + crash("Please implement the 'convert' function") +} diff --git a/exercises/practice/octal/.meta/Example.roc b/exercises/practice/octal/.meta/Example.roc index df228124..305f81dd 100644 --- a/exercises/practice/octal/.meta/Example.roc +++ b/exercises/practice/octal/.meta/Example.roc @@ -1,24 +1,25 @@ -module [parse] +Octal :: {}.{ + parse : Str -> Result U64 _ + parse = |string| + if string == "" then + Err(InvalidNumStr) + else + string + |> Str.to_utf8 + |> List.walk_try( + 0, + |number, char| + octal_digit = parse_octal_digit(char)? + if number > 0x1fffffffffffffff then + Err(InvalidNumStr) + else + number |> Num.shift_left_by(3) |> Num.add(octal_digit) |> Ok, + ) +} + parse_octal_digit = |char| if char >= '0' and char <= '7' then Ok((char - '0' |> Num.to_u64)) else Err(InvalidNumStr) - -parse : Str -> Result U64 _ -parse = |string| - if string == "" then - Err(InvalidNumStr) - else - string - |> Str.to_utf8 - |> List.walk_try( - 0, - |number, char| - octal_digit = parse_octal_digit(char)? - if number > 0x1fffffffffffffff then - Err(InvalidNumStr) - else - number |> Num.shift_left_by(3) |> Num.add(octal_digit) |> Ok, - ) diff --git a/exercises/practice/octal/Octal.roc b/exercises/practice/octal/Octal.roc index e286f262..60f80a72 100644 --- a/exercises/practice/octal/Octal.roc +++ b/exercises/practice/octal/Octal.roc @@ -1,5 +1,5 @@ -module [parse] - -parse : Str -> Result U64 _ -parse = |string| - crash("Please implement the 'parse' function") +Octal :: {}.{ + parse : Str -> Result U64 _ + parse = |string| + crash("Please implement the 'parse' function") +} diff --git a/exercises/practice/palindrome-products/.meta/Example.roc b/exercises/practice/palindrome-products/.meta/Example.roc index 640e84f4..0378b902 100644 --- a/exercises/practice/palindrome-products/.meta/Example.roc +++ b/exercises/practice/palindrome-products/.meta/Example.roc @@ -1,58 +1,58 @@ -module [smallest, largest] - -smallest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } [MinWasLargerThanMax] -smallest = |{ min, max }| - if min > max then - Err(MinWasLargerThanMax) - else - help = |factor1, factor2, best_value, factors| - if factor1 > max then - final_value = if factors == [] then 0 else best_value - Ok({ value: final_value, factors: Set.from_list(factors) }) - else if factor2 == max + 1 then - help((factor1 + 1), (factor1 + 1), best_value, factors) - else - value = factor1 * factor2 - if value > best_value then +PalindromeProducts :: {}.{ + smallest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } [MinWasLargerThanMax] + smallest = |{ min, max }| + if min > max then + Err(MinWasLargerThanMax) + else + help = |factor1, factor2, best_value, factors| + if factor1 > max then + final_value = if factors == [] then 0 else best_value + Ok({ value: final_value, factors: Set.from_list(factors) }) + else if factor2 == max + 1 then help((factor1 + 1), (factor1 + 1), best_value, factors) else - next_factor2 = factor2 + 1 - if value == best_value then - new_factors = factors |> List.append((factor1, factor2)) - help(factor1, next_factor2, best_value, new_factors) - else if value |> is_palindrome then - help(factor1, next_factor2, value, [(factor1, factor2)]) + value = factor1 * factor2 + if value > best_value then + help((factor1 + 1), (factor1 + 1), best_value, factors) else - help(factor1, next_factor2, best_value, factors) + next_factor2 = factor2 + 1 + if value == best_value then + new_factors = factors |> List.append((factor1, factor2)) + help(factor1, next_factor2, best_value, new_factors) + else if value |> is_palindrome then + help(factor1, next_factor2, value, [(factor1, factor2)]) + else + help(factor1, next_factor2, best_value, factors) - help(min, min, Num.max_u64, []) + help(min, min, Num.max_u64, []) -largest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } [MinWasLargerThanMax] -largest = |{ min, max }| - if min > max then - Err(MinWasLargerThanMax) - else - help = |factor1, factor2, best_value, factors| - if factor1 < min then - final_value = if factors == [] then 0 else best_value - Ok({ value: final_value, factors: Set.from_list(factors) }) - else if factor2 < factor1 then - help((factor1 - 1), max, best_value, factors) - else - value = factor1 * factor2 - if value < best_value then + largest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } [MinWasLargerThanMax] + largest = |{ min, max }| + if min > max then + Err(MinWasLargerThanMax) + else + help = |factor1, factor2, best_value, factors| + if factor1 < min then + final_value = if factors == [] then 0 else best_value + Ok({ value: final_value, factors: Set.from_list(factors) }) + else if factor2 < factor1 then help((factor1 - 1), max, best_value, factors) else - next_factor2 = factor2 - 1 - if value == best_value then - new_factors = factors |> List.append((factor1, factor2)) - help(factor1, next_factor2, best_value, new_factors) - else if value |> is_palindrome then - help(factor1, next_factor2, value, [(factor1, factor2)]) + value = factor1 * factor2 + if value < best_value then + help((factor1 - 1), max, best_value, factors) else - help(factor1, next_factor2, best_value, factors) + next_factor2 = factor2 - 1 + if value == best_value then + new_factors = factors |> List.append((factor1, factor2)) + help(factor1, next_factor2, best_value, new_factors) + else if value |> is_palindrome then + help(factor1, next_factor2, value, [(factor1, factor2)]) + else + help(factor1, next_factor2, best_value, factors) - help(max, max, 0, []) + help(max, max, 0, []) +} is_palindrome : U64 -> Bool is_palindrome = |number| diff --git a/exercises/practice/palindrome-products/PalindromeProducts.roc b/exercises/practice/palindrome-products/PalindromeProducts.roc index ccdf41ed..b006634f 100644 --- a/exercises/practice/palindrome-products/PalindromeProducts.roc +++ b/exercises/practice/palindrome-products/PalindromeProducts.roc @@ -1,9 +1,9 @@ -module [smallest, largest] +PalindromeProducts :: {}.{ + smallest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } _ + smallest = |{ min, max }| + crash("Please implement the 'smallest' function") -smallest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } _ -smallest = |{ min, max }| - crash("Please implement the 'smallest' function") - -largest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } _ -largest = |{ min, max }| - crash("Please implement the 'largest' function") + largest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } _ + largest = |{ min, max }| + crash("Please implement the 'largest' function") +} diff --git a/exercises/practice/pangram/.meta/Example.roc b/exercises/practice/pangram/.meta/Example.roc index 671c9d9f..e65f02cc 100644 --- a/exercises/practice/pangram/.meta/Example.roc +++ b/exercises/practice/pangram/.meta/Example.roc @@ -1,13 +1,14 @@ -module [is_pangram] +Pangram :: {}.{ + is_pangram : Str -> Bool + is_pangram = |sentence| + sentence + |> Str.to_utf8 + |> Set.from_list + |> Set.map(|c| if c >= 'a' and c <= 'z' then c + 'A' - 'a' else c) # to uppercase + |> Set.keep_if(|c| c >= 'A' and c <= 'Z') + |> Bool.is_eq(alphabet) +} + alphabet = List.range({ start: At('A'), end: At('Z') }) |> Set.from_list - -is_pangram : Str -> Bool -is_pangram = |sentence| - sentence - |> Str.to_utf8 - |> Set.from_list - |> Set.map(|c| if c >= 'a' and c <= 'z' then c + 'A' - 'a' else c) # to uppercase - |> Set.keep_if(|c| c >= 'A' and c <= 'Z') - |> Bool.is_eq(alphabet) diff --git a/exercises/practice/pangram/Pangram.roc b/exercises/practice/pangram/Pangram.roc index 004f9c57..9dcdfb72 100644 --- a/exercises/practice/pangram/Pangram.roc +++ b/exercises/practice/pangram/Pangram.roc @@ -1,5 +1,5 @@ -module [is_pangram] - -is_pangram : Str -> Bool -is_pangram = |sentence| - crash("Please implement the 'is_pangram' function") +Pangram :: {}.{ + is_pangram : Str -> Bool + is_pangram = |sentence| + crash("Please implement the 'is_pangram' function") +} diff --git a/exercises/practice/pascals-triangle/.meta/Example.roc b/exercises/practice/pascals-triangle/.meta/Example.roc index bc219678..fcaf23ae 100644 --- a/exercises/practice/pascals-triangle/.meta/Example.roc +++ b/exercises/practice/pascals-triangle/.meta/Example.roc @@ -1,13 +1,13 @@ -module [pascals_triangle] - -pascals_triangle : U64 -> List (List U64) -pascals_triangle = |count| - List.range({ start: At(0), end: Before(count) }) - |> List.map( - |row| - List.range({ start: At(0), end: At(row) }) - |> List.map(|column| binomial_coefficient(row, column)), - ) +PascalsTriangle :: {}.{ + pascals_triangle : U64 -> List (List U64) + pascals_triangle = |count| + List.range({ start: At(0), end: Before(count) }) + |> List.map( + |row| + List.range({ start: At(0), end: At(row) }) + |> List.map(|column| binomial_coefficient(row, column)), + ) +} binomial_coefficient : U64, U64 -> U64 binomial_coefficient = |n, k| diff --git a/exercises/practice/pascals-triangle/PascalsTriangle.roc b/exercises/practice/pascals-triangle/PascalsTriangle.roc index 07593063..41c0c3cb 100644 --- a/exercises/practice/pascals-triangle/PascalsTriangle.roc +++ b/exercises/practice/pascals-triangle/PascalsTriangle.roc @@ -1,5 +1,5 @@ -module [pascals_triangle] - -pascals_triangle : U64 -> List (List U64) -pascals_triangle = |count| - crash("Please implement the 'pascals_triangle' function") +PascalsTriangle :: {}.{ + pascals_triangle : U64 -> List (List U64) + pascals_triangle = |count| + crash("Please implement the 'pascals_triangle' function") +} diff --git a/exercises/practice/perfect-numbers/.meta/Example.roc b/exercises/practice/perfect-numbers/.meta/Example.roc index 09ba58da..6da750bc 100644 --- a/exercises/practice/perfect-numbers/.meta/Example.roc +++ b/exercises/practice/perfect-numbers/.meta/Example.roc @@ -1,4 +1,15 @@ -module [classify] +PerfectNumbers :: {}.{ + classify : U64 -> Result [Abundant, Deficient, Perfect] [NumberArgIsZero] + classify = |number| + sum = aliquot_sum(number)? + if sum == number then + Ok(Perfect) + else if sum > number then + Ok(Abundant) + else + Ok(Deficient) +} + aliquot_sum : U64 -> Result U64 [NumberArgIsZero] aliquot_sum = |number| @@ -15,13 +26,3 @@ aliquot_sum = |number| |> List.sum ), ) - -classify : U64 -> Result [Abundant, Deficient, Perfect] [NumberArgIsZero] -classify = |number| - sum = aliquot_sum(number)? - if sum == number then - Ok(Perfect) - else if sum > number then - Ok(Abundant) - else - Ok(Deficient) diff --git a/exercises/practice/perfect-numbers/PerfectNumbers.roc b/exercises/practice/perfect-numbers/PerfectNumbers.roc index 508d38f0..ee9c1f84 100644 --- a/exercises/practice/perfect-numbers/PerfectNumbers.roc +++ b/exercises/practice/perfect-numbers/PerfectNumbers.roc @@ -1,5 +1,5 @@ -module [classify] - -classify : U64 -> Result [Abundant, Deficient, Perfect] _ -classify = |number| - crash("Please implement the 'classify' function") +PerfectNumbers :: {}.{ + classify : U64 -> Result [Abundant, Deficient, Perfect] _ + classify = |number| + crash("Please implement the 'classify' function") +} diff --git a/exercises/practice/phone-number/.meta/Example.roc b/exercises/practice/phone-number/.meta/Example.roc index c923336d..25f06b2e 100644 --- a/exercises/practice/phone-number/.meta/Example.roc +++ b/exercises/practice/phone-number/.meta/Example.roc @@ -1,21 +1,21 @@ -module [clean] +PhoneNumber :: {}.{ + clean : Str -> Result Str [InvalidNumber] + clean = |phone_number| + digits = + phone_number + |> Str.to_utf8 + |> List.keep_if(|c| c >= '0' and c <= '9') -clean : Str -> Result Str [InvalidNumber] -clean = |phone_number| - digits = - phone_number - |> Str.to_utf8 - |> List.keep_if(|c| c >= '0' and c <= '9') - - num_digits = List.len(digits) - if num_digits == 10 then - digits |> check_number - else if num_digits == 11 then - when digits is - ['1', .. as rest] -> rest |> check_number - _ -> Err(InvalidNumber) # only country code 1 is supported - else - Err(InvalidNumber) + num_digits = List.len(digits) + if num_digits == 10 then + digits |> check_number + else if num_digits == 11 then + when digits is + ['1', .. as rest] -> rest |> check_number + _ -> Err(InvalidNumber) # only country code 1 is supported + else + Err(InvalidNumber) +} check_number : List U8 -> Result Str [InvalidNumber] check_number = |digits| diff --git a/exercises/practice/phone-number/PhoneNumber.roc b/exercises/practice/phone-number/PhoneNumber.roc index 7f05b445..564a6e0a 100644 --- a/exercises/practice/phone-number/PhoneNumber.roc +++ b/exercises/practice/phone-number/PhoneNumber.roc @@ -1,5 +1,5 @@ -module [clean] - -clean : Str -> Result Str _ -clean = |phone_number| - crash("Please implement the 'clean' function") +PhoneNumber :: {}.{ + clean : Str -> Result Str _ + clean = |phone_number| + crash("Please implement the 'clean' function") +} diff --git a/exercises/practice/pig-latin/.meta/Example.roc b/exercises/practice/pig-latin/.meta/Example.roc index 19e47a17..0d0a1850 100644 --- a/exercises/practice/pig-latin/.meta/Example.roc +++ b/exercises/practice/pig-latin/.meta/Example.roc @@ -1,4 +1,12 @@ -module [translate] +PigLatin :: {}.{ + translate : Str -> Str + translate = |phrase| + phrase + |> Str.split_on(" ") + |> List.map(translate_word) + |> Str.join_with(" ") +} + is_vowel = |char| ['a', 'e', 'i', 'o', 'u'] |> List.contains(char) @@ -39,10 +47,3 @@ translate_word = |word| when maybe_result is Ok(result) -> result Err(_) -> crash("Unreachable") - -translate : Str -> Str -translate = |phrase| - phrase - |> Str.split_on(" ") - |> List.map(translate_word) - |> Str.join_with(" ") diff --git a/exercises/practice/pig-latin/PigLatin.roc b/exercises/practice/pig-latin/PigLatin.roc index 1ad8dbfd..3cb52aaa 100644 --- a/exercises/practice/pig-latin/PigLatin.roc +++ b/exercises/practice/pig-latin/PigLatin.roc @@ -1,5 +1,5 @@ -module [translate] - -translate : Str -> Str -translate = |phrase| - crash("Please implement the 'translate' function") +PigLatin :: {}.{ + translate : Str -> Str + translate = |phrase| + crash("Please implement the 'translate' function") +} diff --git a/exercises/practice/poker/.meta/Example.roc b/exercises/practice/poker/.meta/Example.roc index 96503495..dbde3b88 100644 --- a/exercises/practice/poker/.meta/Example.roc +++ b/exercises/practice/poker/.meta/Example.roc @@ -1,4 +1,17 @@ -module [best_hands] +Poker :: {}.{ + best_hands : List Str -> Result (List Str) HandParsingError + best_hands = |hands| + parsed_hands = hands |> List.map_try(parse_hand)? + ranks = parsed_hands |> List.map(get_rank) + top_rank = ranks |> List.max |> Result.with_default(0) + List.map2(hands, ranks, |hand, rank| { hand, rank }) + |> List.join_map( + |{ hand, rank }| + if rank == top_rank then [hand] else [], + ) + |> Ok +} + Value : U8 Suit : [Spades, Hearts, Diamonds, Clubs] @@ -6,18 +19,6 @@ Card : { value : Value, suit : Suit } Hand : List Card HandParsingError : [InvalidNumberOfCards U64, CardWasEmpty, InvalidCardValue (List U8), InvalidCardSuit U8] -best_hands : List Str -> Result (List Str) HandParsingError -best_hands = |hands| - parsed_hands = hands |> List.map_try(parse_hand)? - ranks = parsed_hands |> List.map(get_rank) - top_rank = ranks |> List.max |> Result.with_default(0) - List.map2(hands, ranks, |hand, rank| { hand, rank }) - |> List.join_map( - |{ hand, rank }| - if rank == top_rank then [hand] else [], - ) - |> Ok - parse_hand : Str -> Result Hand HandParsingError parse_hand = |hand_str| cards = hand_str |> Str.split_on(" ") @@ -67,7 +68,7 @@ get_rank = |hand| is_flush = (hand |> List.map(.suit) |> Set.from_list |> Set.len) == 1 value_groups = - # Example: [4, 4, 4, 7, 7] -> [{size: 3, value: 4}, {size: 2, value: 7}] + # Poker: [4, 4, 4, 7, 7] -> [{size: 3, value: 4}, {size: 2, value: 7}] card_values |> List.walk( List.repeat(0, 13), diff --git a/exercises/practice/poker/Poker.roc b/exercises/practice/poker/Poker.roc index d3445344..817f225a 100644 --- a/exercises/practice/poker/Poker.roc +++ b/exercises/practice/poker/Poker.roc @@ -1,5 +1,5 @@ -module [best_hands] - -best_hands : List Str -> Result (List Str) _ -best_hands = |hands| - crash("Please implement the 'best_hands' function") +Poker :: {}.{ + best_hands : List Str -> Result (List Str) _ + best_hands = |hands| + crash("Please implement the 'best_hands' function") +} diff --git a/exercises/practice/prime-factors/.meta/Example.roc b/exercises/practice/prime-factors/.meta/Example.roc index 9b2fce2b..d3839034 100644 --- a/exercises/practice/prime-factors/.meta/Example.roc +++ b/exercises/practice/prime-factors/.meta/Example.roc @@ -1,16 +1,16 @@ -module [prime_factors] +PrimeFactors :: {}.{ + prime_factors : U64 -> List U64 + prime_factors = |value| + find_prime_factors = |factors, n, p| + if n < 2 then + factors + else if n |> Num.is_multiple_of(p) then + find_prime_factors(List.append(factors, p), (n // p), p) + else if p * p < n then + next_p = if p == 2 then 3 else p + 2 + find_prime_factors(factors, n, next_p) + else + List.append(factors, n) -prime_factors : U64 -> List U64 -prime_factors = |value| - find_prime_factors = |factors, n, p| - if n < 2 then - factors - else if n |> Num.is_multiple_of(p) then - find_prime_factors(List.append(factors, p), (n // p), p) - else if p * p < n then - next_p = if p == 2 then 3 else p + 2 - find_prime_factors(factors, n, next_p) - else - List.append(factors, n) - - find_prime_factors([], value, 2) + find_prime_factors([], value, 2) +} diff --git a/exercises/practice/prime-factors/PrimeFactors.roc b/exercises/practice/prime-factors/PrimeFactors.roc index f8df4a49..513d9e8c 100644 --- a/exercises/practice/prime-factors/PrimeFactors.roc +++ b/exercises/practice/prime-factors/PrimeFactors.roc @@ -1,5 +1,5 @@ -module [prime_factors] - -prime_factors : U64 -> List U64 -prime_factors = |value| - crash("Please implement the 'prime_factors' function") +PrimeFactors :: {}.{ + prime_factors : U64 -> List U64 + prime_factors = |value| + crash("Please implement the 'prime_factors' function") +} diff --git a/exercises/practice/protein-translation/.meta/Example.roc b/exercises/practice/protein-translation/.meta/Example.roc index 836b06af..524a68c5 100644 --- a/exercises/practice/protein-translation/.meta/Example.roc +++ b/exercises/practice/protein-translation/.meta/Example.roc @@ -1,4 +1,17 @@ -module [to_protein] +ProteinTranslation :: {}.{ + to_protein : Str -> Result Protein [InvalidCodon Codon] + to_protein = |rna| + help = |protein, codons| + when codons is + [] -> Ok(protein) + [codon, .. as rest] -> + when codon |> to_instruction is + Ok(Append(amino_acid)) -> protein |> List.append(amino_acid) |> help(rest) + Ok(Stop) -> Ok(protein) + Err(err) -> Err(err) + help([], (rna |> Str.to_utf8 |> List.chunks_of(3))) +} + Codon : List U8 AminoAcid : [Cysteine, Leucine, Methionine, Phenylalanine, Serine, Tryptophan, Tyrosine] @@ -25,15 +38,3 @@ to_instruction = |codon| ['U', 'A', 'G'] -> Ok(Stop) ['U', 'G', 'A'] -> Ok(Stop) _ -> Err(InvalidCodon(codon)) - -to_protein : Str -> Result Protein [InvalidCodon Codon] -to_protein = |rna| - help = |protein, codons| - when codons is - [] -> Ok(protein) - [codon, .. as rest] -> - when codon |> to_instruction is - Ok(Append(amino_acid)) -> protein |> List.append(amino_acid) |> help(rest) - Ok(Stop) -> Ok(protein) - Err(err) -> Err(err) - help([], (rna |> Str.to_utf8 |> List.chunks_of(3))) diff --git a/exercises/practice/protein-translation/ProteinTranslation.roc b/exercises/practice/protein-translation/ProteinTranslation.roc index 8a61979f..a3c666f9 100644 --- a/exercises/practice/protein-translation/ProteinTranslation.roc +++ b/exercises/practice/protein-translation/ProteinTranslation.roc @@ -1,8 +1,9 @@ -module [to_protein] +ProteinTranslation :: {}.{ + to_protein : Str -> Result Protein _ + to_protein = |rna| + crash("Please implement the 'to_protein' function") +} + AminoAcid : [Cysteine, Leucine, Methionine, Phenylalanine, Serine, Tryptophan, Tyrosine] Protein : List AminoAcid - -to_protein : Str -> Result Protein _ -to_protein = |rna| - crash("Please implement the 'to_protein' function") diff --git a/exercises/practice/proverb/.meta/Example.roc b/exercises/practice/proverb/.meta/Example.roc index 55079566..697d6a1f 100644 --- a/exercises/practice/proverb/.meta/Example.roc +++ b/exercises/practice/proverb/.meta/Example.roc @@ -1,16 +1,16 @@ -module [recite] - -recite : List Str -> Str -recite = |strings| - when strings is - [] -> "" - [firtst_thing, .. as rest] -> - rest - |> List.walk( - (firtst_thing, []), - |(thing1, lines), thing2| - (thing2, lines |> List.append("For want of a ${thing1} the ${thing2} was lost.")), - ) - |> .1 - |> List.append("And all for the want of a ${firtst_thing}.") - |> Str.join_with("\n") +Proverb :: {}.{ + recite : List Str -> Str + recite = |strings| + when strings is + [] -> "" + [firtst_thing, .. as rest] -> + rest + |> List.walk( + (firtst_thing, []), + |(thing1, lines), thing2| + (thing2, lines |> List.append("For want of a ${thing1} the ${thing2} was lost.")), + ) + |> .1 + |> List.append("And all for the want of a ${firtst_thing}.") + |> Str.join_with("\n") +} diff --git a/exercises/practice/proverb/Proverb.roc b/exercises/practice/proverb/Proverb.roc index faa3131f..092b4cb8 100644 --- a/exercises/practice/proverb/Proverb.roc +++ b/exercises/practice/proverb/Proverb.roc @@ -1,5 +1,5 @@ -module [recite] - -recite : List Str -> Str -recite = |strings| - crash("Please implement the 'recite' function") +Proverb :: {}.{ + recite : List Str -> Str + recite = |strings| + crash("Please implement the 'recite' function") +} diff --git a/exercises/practice/pythagorean-triplet/.meta/Example.roc b/exercises/practice/pythagorean-triplet/.meta/Example.roc index e0466f4e..2bd4ea2c 100644 --- a/exercises/practice/pythagorean-triplet/.meta/Example.roc +++ b/exercises/practice/pythagorean-triplet/.meta/Example.roc @@ -1,23 +1,24 @@ -module [triplets_with_sum] +PythagoreanTriplet :: {}.{ + triplets_with_sum : U64 -> Set Triplet + triplets_with_sum = |sum| + help = |triplets, a, b| + if a + b + b + 1 > sum then + # c would have to be too small (≤ b) + if 3 * (a + 1) > sum then + # we can't increment a so we're done + triplets + else + help(triplets, (a + 1), (a + 2)) # increment a + else + c = sum - a - b + new_triplets = + if a * a + b * b == c * c then + triplets |> List.append((a, b, c)) # success! + else + triplets + help(new_triplets, a, (b + 1)) # increment b + help([], 1, 2) |> Set.from_list +} -Triplet : (U64, U64, U64) -triplets_with_sum : U64 -> Set Triplet -triplets_with_sum = |sum| - help = |triplets, a, b| - if a + b + b + 1 > sum then - # c would have to be too small (≤ b) - if 3 * (a + 1) > sum then - # we can't increment a so we're done - triplets - else - help(triplets, (a + 1), (a + 2)) # increment a - else - c = sum - a - b - new_triplets = - if a * a + b * b == c * c then - triplets |> List.append((a, b, c)) # success! - else - triplets - help(new_triplets, a, (b + 1)) # increment b - help([], 1, 2) |> Set.from_list +Triplet : (U64, U64, U64) diff --git a/exercises/practice/pythagorean-triplet/PythagoreanTriplet.roc b/exercises/practice/pythagorean-triplet/PythagoreanTriplet.roc index d5a6cfdb..6d341dac 100644 --- a/exercises/practice/pythagorean-triplet/PythagoreanTriplet.roc +++ b/exercises/practice/pythagorean-triplet/PythagoreanTriplet.roc @@ -1,7 +1,8 @@ -module [triplets_with_sum] +PythagoreanTriplet :: {}.{ + triplets_with_sum : U64 -> Set Triplet + triplets_with_sum = |sum| + crash("Please implement the 'triplets_with_sum' function") +} -Triplet : (U64, U64, U64) -triplets_with_sum : U64 -> Set Triplet -triplets_with_sum = |sum| - crash("Please implement the 'triplets_with_sum' function") +Triplet : (U64, U64, U64) diff --git a/exercises/practice/queen-attack/.meta/Example.roc b/exercises/practice/queen-attack/.meta/Example.roc index eb779b49..70a1733c 100644 --- a/exercises/practice/queen-attack/.meta/Example.roc +++ b/exercises/practice/queen-attack/.meta/Example.roc @@ -1,29 +1,30 @@ -module [create, rank, file, queen_can_attack] +QueenAttack :: {}.{ + rank : Square -> U8 + rank = |@Square({ row, column: _ })| 8 - row -Square := { row : U8, column : U8 } - -rank : Square -> U8 -rank = |@Square({ row, column: _ })| 8 - row - -file : Square -> U8 -file = |@Square({ row: _, column })| column + 'A' + file : Square -> U8 + file = |@Square({ row: _, column })| column + 'A' -create : Str -> Result Square [InvalidSquare] -create = |square_str| - chars = square_str |> Str.to_utf8 - if List.len(chars) != 2 then - Err(InvalidSquare) - else - file_char = chars |> List.get(0) |> Result.map_err(|OutOfBounds| InvalidSquare)? - rank_char = chars |> List.get(1) |> Result.map_err(|OutOfBounds| InvalidSquare)? - if file_char < 'A' or file_char > 'H' or rank_char < '1' or rank_char > '8' then + create : Str -> Result Square [InvalidSquare] + create = |square_str| + chars = square_str |> Str.to_utf8 + if List.len(chars) != 2 then Err(InvalidSquare) else - Ok(@Square({ row: 7 - (rank_char - '1'), column: file_char - 'A' })) + file_char = chars |> List.get(0) |> Result.map_err(|OutOfBounds| InvalidSquare)? + rank_char = chars |> List.get(1) |> Result.map_err(|OutOfBounds| InvalidSquare)? + if file_char < 'A' or file_char > 'H' or rank_char < '1' or rank_char > '8' then + Err(InvalidSquare) + else + Ok(@Square({ row: 7 - (rank_char - '1'), column: file_char - 'A' })) -queen_can_attack : Square, Square -> Bool -queen_can_attack = |@Square({ row: r1, column: c1 }), @Square({ row: r2, column: c2 })| - abs_diff = |u, v| if u < v then v - u else u - v - row_diff = abs_diff(r1, r2) - column_diff = abs_diff(c1, c2) - row_diff == 0 or column_diff == 0 or row_diff == column_diff + queen_can_attack : Square, Square -> Bool + queen_can_attack = |@Square({ row: r1, column: c1 }), @Square({ row: r2, column: c2 })| + abs_diff = |u, v| if u < v then v - u else u - v + row_diff = abs_diff(r1, r2) + column_diff = abs_diff(c1, c2) + row_diff == 0 or column_diff == 0 or row_diff == column_diff +} + + +Square := { row : U8, column : U8 } diff --git a/exercises/practice/queen-attack/QueenAttack.roc b/exercises/practice/queen-attack/QueenAttack.roc index 861512dc..17b30ad7 100644 --- a/exercises/practice/queen-attack/QueenAttack.roc +++ b/exercises/practice/queen-attack/QueenAttack.roc @@ -1,19 +1,20 @@ -module [create, rank, file, queen_can_attack] +QueenAttack :: {}.{ + rank : Square -> U8 + rank = |@Square({ row, column })| + crash("Please implement the 'rank' function") -Square := { row : U8, column : U8 } + file : Square -> U8 + file = |@Square({ row, column })| + crash("Please implement the 'file' function") -rank : Square -> U8 -rank = |@Square({ row, column })| - crash("Please implement the 'rank' function") + create : Str -> Result Square _ + create = |square_str| + crash("Please implement the 'create' function") -file : Square -> U8 -file = |@Square({ row, column })| - crash("Please implement the 'file' function") + queen_can_attack : Square, Square -> Bool + queen_can_attack = |square1, square2| + crash("Please implement the 'queen_can_attack' function") +} -create : Str -> Result Square _ -create = |square_str| - crash("Please implement the 'create' function") -queen_can_attack : Square, Square -> Bool -queen_can_attack = |square1, square2| - crash("Please implement the 'queen_can_attack' function") +Square := { row : U8, column : U8 } diff --git a/exercises/practice/rail-fence-cipher/.meta/Example.roc b/exercises/practice/rail-fence-cipher/.meta/Example.roc index bece776e..064dd354 100644 --- a/exercises/practice/rail-fence-cipher/.meta/Example.roc +++ b/exercises/practice/rail-fence-cipher/.meta/Example.roc @@ -1,4 +1,13 @@ -module [encode, decode] +RailFenceCipher :: {}.{ + encode : Str, U64 -> Result Str [ZeroRails, BadUtf8 _] + encode = |message, rails| + message |> reorder_with(encoded_indices, rails) + + decode : Str, U64 -> Result Str [ZeroRails, BadUtf8 _] + decode = |encrypted, rails| + encrypted |> reorder_with(decoded_indices, rails) +} + encoded_indices : U64, U64 -> List U64 encoded_indices = |len, rails| @@ -30,14 +39,6 @@ decoded_indices = |len, rails| ) |> List.map(.decoded) -encode : Str, U64 -> Result Str [ZeroRails, BadUtf8 _] -encode = |message, rails| - message |> reorder_with(encoded_indices, rails) - -decode : Str, U64 -> Result Str [ZeroRails, BadUtf8 _] -decode = |encrypted, rails| - encrypted |> reorder_with(decoded_indices, rails) - reorder_with : Str, (U64, U64 -> List U64), U64 -> Result Str [ZeroRails, BadUtf8 _] reorder_with = |message, get_indices, rails| if rails == 0 then diff --git a/exercises/practice/rail-fence-cipher/RailFenceCipher.roc b/exercises/practice/rail-fence-cipher/RailFenceCipher.roc index 7801c11e..95c64373 100644 --- a/exercises/practice/rail-fence-cipher/RailFenceCipher.roc +++ b/exercises/practice/rail-fence-cipher/RailFenceCipher.roc @@ -1,9 +1,9 @@ -module [encode, decode] +RailFenceCipher :: {}.{ + encode : Str, U64 -> Result Str _ + encode = |message, rails| + crash("Please implement the 'encode' function") -encode : Str, U64 -> Result Str _ -encode = |message, rails| - crash("Please implement the 'encode' function") - -decode : Str, U64 -> Result Str _ -decode = |encrypted, rails| - crash("Please implement the 'decode' function") + decode : Str, U64 -> Result Str _ + decode = |encrypted, rails| + crash("Please implement the 'decode' function") +} diff --git a/exercises/practice/raindrops/.meta/Example.roc b/exercises/practice/raindrops/.meta/Example.roc index fc9c3d0f..e51fc819 100644 --- a/exercises/practice/raindrops/.meta/Example.roc +++ b/exercises/practice/raindrops/.meta/Example.roc @@ -1,12 +1,12 @@ -module [convert] - -convert : U64 -> Str -convert = |number| - pling = if number % 3 == 0 then "Pling" else "" - plang = if number % 5 == 0 then "Plang" else "" - plong = if number % 7 == 0 then "Plong" else "" - result = "${pling}${plang}${plong}" - if result == "" then - Num.to_str(number) - else - result +Raindrops :: {}.{ + convert : U64 -> Str + convert = |number| + pling = if number % 3 == 0 then "Pling" else "" + plang = if number % 5 == 0 then "Plang" else "" + plong = if number % 7 == 0 then "Plong" else "" + result = "${pling}${plang}${plong}" + if result == "" then + Num.to_str(number) + else + result +} diff --git a/exercises/practice/raindrops/Raindrops.roc b/exercises/practice/raindrops/Raindrops.roc index fc9884ba..9f8570ce 100644 --- a/exercises/practice/raindrops/Raindrops.roc +++ b/exercises/practice/raindrops/Raindrops.roc @@ -1,5 +1,5 @@ -module [convert] - -convert : U64 -> Str -convert = |number| - crash("Please implement the 'convert' function") +Raindrops :: {}.{ + convert : U64 -> Str + convert = |number| + crash("Please implement the 'convert' function") +} diff --git a/exercises/practice/rational-numbers/.meta/Example.roc b/exercises/practice/rational-numbers/.meta/Example.roc index 070f10cd..cc0dc097 100644 --- a/exercises/practice/rational-numbers/.meta/Example.roc +++ b/exercises/practice/rational-numbers/.meta/Example.roc @@ -1,55 +1,55 @@ -module [add, sub, mul, div, abs, exp, exp_real, reduce] - -add : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] -add = |r1, r2| - Rational(a1, b1) = r1 - Rational(a2, b2) = r2 - Rational((a1 * b2 + a2 * b1), (b1 * b2)) |> reduce - -sub : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] -sub = |r1, r2| - Rational(a1, b1) = r1 - Rational(a2, b2) = r2 - Rational((a1 * b2 - a2 * b1), (b1 * b2)) |> reduce - -mul : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] -mul = |r1, r2| - Rational(a1, b1) = r1 - Rational(a2, b2) = r2 - Rational((a1 * a2), (b1 * b2)) |> reduce - -div : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] -div = |r1, r2| - Rational(a1, b1) = r1 - Rational(a2, b2) = r2 - Rational((a1 * b2), (a2 * b1)) |> reduce - -abs : [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] -abs = |r| - Rational(a, b) = r - Rational(Num.abs(a), Num.abs(b)) |> reduce - -exp : [Rational (Int a) (Int a)], Int a -> [Rational (Int a) (Int a)] -exp = |r, n| - Rational(a, b) = r - when n is - 0 -> Rational(1, 1) - pos if pos > 0 -> Rational((a |> Num.pow_int(pos)), (b |> Num.pow_int(pos))) |> reduce - neg -> - m = Num.abs(neg) - Rational((b |> Num.pow_int(m)), (a |> Num.pow_int(m))) |> reduce - -exp_real : Frac a, [Rational (Int b) (Int b)] -> Frac a -exp_real = |x, r| - Rational(a, b) = r - x |> Num.pow((Num.to_frac(a) / Num.to_frac(b))) - -reduce : [Rational (Int b) (Int b)] -> [Rational (Int b) (Int b)] -reduce = |r| - Rational(a, b) = r - gcd = |m, n| if n == 0 then m else gcd(n, (m % n)) - sign = |n| if n < 0 then -1 else 1 - abs_a = Num.abs(a) - abs_b = Num.abs(b) - d = gcd(abs_a, abs_b) - Rational((sign(a) * sign(b) * abs_a // d), (abs_b // d)) +RationalNumbers :: {}.{ + add : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] + add = |r1, r2| + Rational(a1, b1) = r1 + Rational(a2, b2) = r2 + Rational((a1 * b2 + a2 * b1), (b1 * b2)) |> reduce + + sub : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] + sub = |r1, r2| + Rational(a1, b1) = r1 + Rational(a2, b2) = r2 + Rational((a1 * b2 - a2 * b1), (b1 * b2)) |> reduce + + mul : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] + mul = |r1, r2| + Rational(a1, b1) = r1 + Rational(a2, b2) = r2 + Rational((a1 * a2), (b1 * b2)) |> reduce + + div : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] + div = |r1, r2| + Rational(a1, b1) = r1 + Rational(a2, b2) = r2 + Rational((a1 * b2), (a2 * b1)) |> reduce + + abs : [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] + abs = |r| + Rational(a, b) = r + Rational(Num.abs(a), Num.abs(b)) |> reduce + + exp : [Rational (Int a) (Int a)], Int a -> [Rational (Int a) (Int a)] + exp = |r, n| + Rational(a, b) = r + when n is + 0 -> Rational(1, 1) + pos if pos > 0 -> Rational((a |> Num.pow_int(pos)), (b |> Num.pow_int(pos))) |> reduce + neg -> + m = Num.abs(neg) + Rational((b |> Num.pow_int(m)), (a |> Num.pow_int(m))) |> reduce + + exp_real : Frac a, [Rational (Int b) (Int b)] -> Frac a + exp_real = |x, r| + Rational(a, b) = r + x |> Num.pow((Num.to_frac(a) / Num.to_frac(b))) + + reduce : [Rational (Int b) (Int b)] -> [Rational (Int b) (Int b)] + reduce = |r| + Rational(a, b) = r + gcd = |m, n| if n == 0 then m else gcd(n, (m % n)) + sign = |n| if n < 0 then -1 else 1 + abs_a = Num.abs(a) + abs_b = Num.abs(b) + d = gcd(abs_a, abs_b) + Rational((sign(a) * sign(b) * abs_a // d), (abs_b // d)) +} diff --git a/exercises/practice/rational-numbers/RationalNumbers.roc b/exercises/practice/rational-numbers/RationalNumbers.roc index 5d6012ef..890a913f 100644 --- a/exercises/practice/rational-numbers/RationalNumbers.roc +++ b/exercises/practice/rational-numbers/RationalNumbers.roc @@ -1,35 +1,35 @@ -module [add, sub, mul, div, abs, exp, exp_real, reduce] +RationalNumbers :: {}.{ + add : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] + add = |r1, r2| + crash("Please implement the 'add' function") -add : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] -add = |r1, r2| - crash("Please implement the 'add' function") + sub : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] + sub = |r1, r2| + crash("Please implement the 'sub' function") -sub : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] -sub = |r1, r2| - crash("Please implement the 'sub' function") + mul : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] + mul = |r1, r2| + crash("Please implement the 'mul' function") -mul : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] -mul = |r1, r2| - crash("Please implement the 'mul' function") + div : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] + div = |r1, r2| + crash("Please implement the 'div' function") -div : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] -div = |r1, r2| - crash("Please implement the 'div' function") + abs : [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] + abs = |r| + crash("Please implement the 'abs' function") -abs : [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] -abs = |r| - crash("Please implement the 'abs' function") + exp : [Rational (Int a) (Int a)], Int a -> [Rational (Int a) (Int a)] + exp = |r, n| + crash("Please implement the 'exp' function") -exp : [Rational (Int a) (Int a)], Int a -> [Rational (Int a) (Int a)] -exp = |r, n| - crash("Please implement the 'exp' function") + exp_real : Frac a, [Rational (Int b) (Int b)] -> Frac a + exp_real = |x, r| + crash("Please implement the 'exp_real' function") -exp_real : Frac a, [Rational (Int b) (Int b)] -> Frac a -exp_real = |x, r| - crash("Please implement the 'exp_real' function") - -## Reduce a rational number to its lowest terms, e.g., 6 / 8 --> 3 / 4 -reduce : [Rational (Int b) (Int b)] -> [Rational (Int b) (Int b)] -reduce = |r| - crash("Please implement the 'reduce' function") + ## Reduce a rational number to its lowest terms, e.g., 6 / 8 --> 3 / 4 + reduce : [Rational (Int b) (Int b)] -> [Rational (Int b) (Int b)] + reduce = |r| + crash("Please implement the 'reduce' function") +} diff --git a/exercises/practice/rectangles/.meta/Example.roc b/exercises/practice/rectangles/.meta/Example.roc index dfe2189d..8b2a6be1 100644 --- a/exercises/practice/rectangles/.meta/Example.roc +++ b/exercises/practice/rectangles/.meta/Example.roc @@ -1,4 +1,34 @@ -module [rectangles] +Rectangles :: {}.{ + rectangles : Str -> U64 + rectangles = |diagram| + grid = + diagram + |> Str.split_on("\n") + |> List.map(Str.to_utf8) + height = grid |> List.len + grid + |> List.map_with_index( + |row, y1| # number of rectangles with top on this row + row + |> List.map_with_index( + |_char, x1| # number with top-left on this column + List.range({ start: After(y1), end: Before(height) }) + |> List.map( + |y2| # number of rectangles with bottom on this row + List.range({ start: After(x1), end: Before(List.len(row)) }) + |> List.map( + |x2| # number with bottom-right on this column + if is_rectangle({ grid, x1, y1, x2, y2 }) then 1 else 0, + ) + |> List.sum, + ) + |> List.sum, + ) + |> List.sum, + ) + |> List.sum +} + ## Is there a rectangle between the top left (x1, y1) corner and the bottom ## right (x2, y2) corner. @@ -26,32 +56,3 @@ is_rectangle = |{ grid, x1, y1, x2, y2 }| and ([y1, y2] |> List.all(has_horizontal_border)) and ([x1, x2] |> List.all(has_vertical_border)) ) - -rectangles : Str -> U64 -rectangles = |diagram| - grid = - diagram - |> Str.split_on("\n") - |> List.map(Str.to_utf8) - height = grid |> List.len - grid - |> List.map_with_index( - |row, y1| # number of rectangles with top on this row - row - |> List.map_with_index( - |_char, x1| # number with top-left on this column - List.range({ start: After(y1), end: Before(height) }) - |> List.map( - |y2| # number of rectangles with bottom on this row - List.range({ start: After(x1), end: Before(List.len(row)) }) - |> List.map( - |x2| # number with bottom-right on this column - if is_rectangle({ grid, x1, y1, x2, y2 }) then 1 else 0, - ) - |> List.sum, - ) - |> List.sum, - ) - |> List.sum, - ) - |> List.sum diff --git a/exercises/practice/rectangles/Rectangles.roc b/exercises/practice/rectangles/Rectangles.roc index 415700ec..dab1d29e 100644 --- a/exercises/practice/rectangles/Rectangles.roc +++ b/exercises/practice/rectangles/Rectangles.roc @@ -1,5 +1,5 @@ -module [rectangles] - -rectangles : Str -> U64 -rectangles = |diagram| - crash("Please implement the 'rectangles' function") +Rectangles :: {}.{ + rectangles : Str -> U64 + rectangles = |diagram| + crash("Please implement the 'rectangles' function") +} diff --git a/exercises/practice/resistor-color-duo/.meta/Example.roc b/exercises/practice/resistor-color-duo/.meta/Example.roc index ef94d0ff..3849debd 100644 --- a/exercises/practice/resistor-color-duo/.meta/Example.roc +++ b/exercises/practice/resistor-color-duo/.meta/Example.roc @@ -1,4 +1,9 @@ -module [value] +ResistorColorDuo :: {}.{ + value : Color, Color -> U8 + value = |first, second| + 10 * get_code(first) + get_code(second) +} + Color : [ Black, @@ -13,10 +18,6 @@ Color : [ White, ] -value : Color, Color -> U8 -value = |first, second| - 10 * get_code(first) + get_code(second) - get_code : Color -> U8 get_code = |color| when color is diff --git a/exercises/practice/resistor-color-duo/ResistorColorDuo.roc b/exercises/practice/resistor-color-duo/ResistorColorDuo.roc index 389ec2d1..b0a41f7f 100644 --- a/exercises/practice/resistor-color-duo/ResistorColorDuo.roc +++ b/exercises/practice/resistor-color-duo/ResistorColorDuo.roc @@ -1,4 +1,9 @@ -module [value] +ResistorColorDuo :: {}.{ + value : Color, Color -> U8 + value = |first, second| + crash("Please implement the 'value' function") +} + Color : [ Black, @@ -12,7 +17,3 @@ Color : [ Grey, White, ] - -value : Color, Color -> U8 -value = |first, second| - crash("Please implement the 'value' function") diff --git a/exercises/practice/resistor-color/.meta/Example.roc b/exercises/practice/resistor-color/.meta/Example.roc index e964ae08..81a52595 100644 --- a/exercises/practice/resistor-color/.meta/Example.roc +++ b/exercises/practice/resistor-color/.meta/Example.roc @@ -1,12 +1,15 @@ -module [color_code, color_code_from_dict, colors] +ResistorColor :: {}.{ + colors : List Str + colors = + ["black", "brown", "red", "orange", "yellow", "green", "blue", "violet", "grey", "white"] -colors : List Str -colors = - ["black", "brown", "red", "orange", "yellow", "green", "blue", "violet", "grey", "white"] + color_code : Str -> Result U64 [NotFound] + color_code = |color| + colors |> List.find_first_index(|elem| elem == color) -color_code : Str -> Result U64 [NotFound] -color_code = |color| - colors |> List.find_first_index(|elem| elem == color) + color_code_from_dict = |color| + colors_dict |> Dict.get(color) +} # Since the list of colors is quite small, representing it as a List is simple # and efficient. However, if there were many more colors, it would make sense @@ -15,6 +18,3 @@ colors_dict = colors |> List.map_with_index(|color, index| (color, index)) |> Dict.from_list - -color_code_from_dict = |color| - colors_dict |> Dict.get(color) diff --git a/exercises/practice/resistor-color/ResistorColor.roc b/exercises/practice/resistor-color/ResistorColor.roc index 48130358..e8c04815 100644 --- a/exercises/practice/resistor-color/ResistorColor.roc +++ b/exercises/practice/resistor-color/ResistorColor.roc @@ -1,9 +1,9 @@ -module [color_code, colors] +ResistorColor :: {}.{ + color_code : Str -> Result U64 _ + color_code = |color| + crash("Please implement the 'color_code' function") -color_code : Str -> Result U64 _ -color_code = |color| - crash("Please implement the 'color_code' function") - -colors : List Str -colors = - crash("Please implement the 'colors' function") + colors : List Str + colors = + crash("Please implement the 'colors' function") +} diff --git a/exercises/practice/rest-api/.meta/Example.roc b/exercises/practice/rest-api/.meta/Example.roc index 46309eef..51f9bc00 100644 --- a/exercises/practice/rest-api/.meta/Example.roc +++ b/exercises/practice/rest-api/.meta/Example.roc @@ -1,6 +1,28 @@ -module [get, post] + import json.Json +RestApi :: {}.{ + get : Database, { url : Str, payload ?? Str } -> Result Str [Http404 Str, Http422 Str] + get = |database, { url, payload ?? "" }| + when url is + "/users" -> + database + |> get_users(payload) + |> Result.map_err(|InvalidJson| Http422(payload)) + + bad_url -> Err(Http404(bad_url)) + + post : Database, { url : Str, payload ?? Str } -> Result Str [Http404 Str, Http422 Str] + post = |database, { url, payload ?? "" }| + handle_error = |err| + when err is + InvalidJson -> Http422(payload) + NotFound -> Http404(payload) + when url is + "/add" -> database |> add_user(payload) |> Result.map_err(handle_error) + "/iou" -> database |> add_loan(payload) |> Result.map_err(handle_error) + bad_url -> Err(Http404(bad_url)) +} User : { name : Str, @@ -13,16 +35,6 @@ Database : { users : List User } Loan : { lender : Str, borrower : Str, amount : F64 } -get : Database, { url : Str, payload ?? Str } -> Result Str [Http404 Str, Http422 Str] -get = |database, { url, payload ?? "" }| - when url is - "/users" -> - database - |> get_users(payload) - |> Result.map_err(|InvalidJson| Http422(payload)) - - bad_url -> Err(Http404(bad_url)) - compare_strings : Str, Str -> [LT, EQ, GT] compare_strings = |string1, string2| b1 = string1 |> Str.to_utf8 @@ -119,17 +131,6 @@ parse_json_loan = |payload| maybe_loan = Decode.from_bytes_partial(bytes, Json.utf8) maybe_loan.result |> Result.map_err(|_| InvalidJson) -post : Database, { url : Str, payload ?? Str } -> Result Str [Http404 Str, Http422 Str] -post = |database, { url, payload ?? "" }| - handle_error = |err| - when err is - InvalidJson -> Http422(payload) - NotFound -> Http404(payload) - when url is - "/add" -> database |> add_user(payload) |> Result.map_err(handle_error) - "/iou" -> database |> add_loan(payload) |> Result.map_err(handle_error) - bad_url -> Err(Http404(bad_url)) - add_user : Database, Str -> Result Str [InvalidJson] add_user = |_database, payload| user_payload = parse_json_user(payload)? diff --git a/exercises/practice/rest-api/RestApi.roc b/exercises/practice/rest-api/RestApi.roc index 6480a401..ef6bfae4 100644 --- a/exercises/practice/rest-api/RestApi.roc +++ b/exercises/practice/rest-api/RestApi.roc @@ -1,6 +1,15 @@ -module [get, post] + import json.Json +RestApi :: {}.{ + get : Database, { url : Str, payload ?? Str } -> Result Str _ + get = |database, { url, payload ?? "" }| + crash("Please implement the 'get' function") + + post : Database, { url : Str, payload ?? Str } -> Result Str _ + post = |database, { url, payload ?? "" }| + crash("Please implement the 'post' function") +} User : { name : Str, @@ -10,11 +19,3 @@ User : { } Database : { users : List User } - -get : Database, { url : Str, payload ?? Str } -> Result Str _ -get = |database, { url, payload ?? "" }| - crash("Please implement the 'get' function") - -post : Database, { url : Str, payload ?? Str } -> Result Str _ -post = |database, { url, payload ?? "" }| - crash("Please implement the 'post' function") diff --git a/exercises/practice/reverse-string/.meta/Example.roc b/exercises/practice/reverse-string/.meta/Example.roc index 067dc413..b0304354 100644 --- a/exercises/practice/reverse-string/.meta/Example.roc +++ b/exercises/practice/reverse-string/.meta/Example.roc @@ -1,26 +1,27 @@ -module [reverse, reverse_ascii] -import unicode.Grapheme -## This function reverses the input string, e.g., "café" -> "éfac". -## This implementation uses the `unicode` package from github.com/roc-lang/unicode -## to split the input string into separate "Extended Grapheme Clusters". This is a -## fancy Unicode name for characters that human beings recognize as such: each EGC -## may actually be composed of several Unicode codepoints, e.g., the character -## é is recognized as a single character, but it may actually be represented as two -## Unicode codepoints (e + ´). -## To use the `unicode` package, its URL must be added to the app's header. -## Luckily, we've added it for you in reverse-string-test.roc. Take a look! -reverse : Str -> Str -reverse = |string| - when Grapheme.split(string) is - Ok(graphemes) -> graphemes |> List.reverse |> Str.join_with("") - Err(_) -> "Unexpected error: could not split the string into graphemes" +import unicode.Grapheme +ReverseString :: {}.{ + ## This function reverses the input string, e.g., "café" -> "éfac". + ## This implementation uses the `unicode` package from github.com/roc-lang/unicode + ## to split the input string into separate "Extended Grapheme Clusters". This is a + ## fancy Unicode name for characters that human beings recognize as such: each EGC + ## may actually be composed of several Unicode codepoints, e.g., the character + ## é is recognized as a single character, but it may actually be represented as two + ## Unicode codepoints (e + ´). + ## To use the `unicode` package, its URL must be added to the app's header. + ## Luckily, we've added it for you in reverse-string-test.roc. Take a look! + reverse : Str -> Str + reverse = |string| + when Grapheme.split(string) is + Ok(graphemes) -> graphemes |> List.reverse |> Str.join_with("") + Err(_) -> "Unexpected error: could not split the string into graphemes" -## This function reverses the input string, e.g., "hello" -> "olleh". It is -## faster and simpler than the implementation above, plus it does not require an -## external package, but it is only guaranteed to work on ASCII strings. -reverse_ascii = |string| - when string |> Str.to_utf8 |> List.reverse |> Str.from_utf8 is - Ok(reversed) -> reversed - Err(_) -> "This implementation online works on ASCII strings" + ## This function reverses the input string, e.g., "hello" -> "olleh". It is + ## faster and simpler than the implementation above, plus it does not require an + ## external package, but it is only guaranteed to work on ASCII strings. + reverse_ascii = |string| + when string |> Str.to_utf8 |> List.reverse |> Str.from_utf8 is + Ok(reversed) -> reversed + Err(_) -> "This implementation online works on ASCII strings" +} diff --git a/exercises/practice/reverse-string/ReverseString.roc b/exercises/practice/reverse-string/ReverseString.roc index 4a453977..2fb6385b 100644 --- a/exercises/practice/reverse-string/ReverseString.roc +++ b/exercises/practice/reverse-string/ReverseString.roc @@ -1,9 +1,9 @@ -module [reverse] +ReverseString :: {}.{ + reverse : Str -> Str + reverse = |string| + crash("Please implement the 'reverse' function") -reverse : Str -> Str -reverse = |string| - crash("Please implement the 'reverse' function") - -# HINT: we have added the `unicode` package to the app's header in -# reverse-string-test.roc, so you can use it here if you need to. -# For example, you could use unicode.Grapheme, just sayin'. + # HINT: we have added the `unicode` package to the app's header in + # reverse-string-test.roc, so you can use it here if you need to. + # For example, you could use unicode.Grapheme, just sayin'. +} diff --git a/exercises/practice/rna-transcription/.meta/Example.roc b/exercises/practice/rna-transcription/.meta/Example.roc index 9fb81478..4a15c767 100644 --- a/exercises/practice/rna-transcription/.meta/Example.roc +++ b/exercises/practice/rna-transcription/.meta/Example.roc @@ -1,4 +1,17 @@ -module [to_rna] +RnaTranscription :: {}.{ + to_rna : Str -> Str + to_rna = |dna| + maybe_rna = + dna + |> Str.to_utf8 + |> List.map(complement) + |> Str.from_utf8 + + when maybe_rna is + Ok(rna) -> rna + Err(_) -> crash("Unreachable code: toUt8 -> fromUtf8 will always be Ok") +} + complement = |nucleotide| when nucleotide is @@ -7,15 +20,3 @@ complement = |nucleotide| 'T' -> 'A' 'A' -> 'U' c -> c # invalid nucleotides are ignored - -to_rna : Str -> Str -to_rna = |dna| - maybe_rna = - dna - |> Str.to_utf8 - |> List.map(complement) - |> Str.from_utf8 - - when maybe_rna is - Ok(rna) -> rna - Err(_) -> crash("Unreachable code: toUt8 -> fromUtf8 will always be Ok") diff --git a/exercises/practice/rna-transcription/RnaTranscription.roc b/exercises/practice/rna-transcription/RnaTranscription.roc index 1c7f505c..9474d045 100644 --- a/exercises/practice/rna-transcription/RnaTranscription.roc +++ b/exercises/practice/rna-transcription/RnaTranscription.roc @@ -1,5 +1,5 @@ -module [to_rna] - -to_rna : Str -> Str -to_rna = |dna| - crash("Please implement the 'to_rna' function") +RnaTranscription :: {}.{ + to_rna : Str -> Str + to_rna = |dna| + crash("Please implement the 'to_rna' function") +} diff --git a/exercises/practice/robot-simulator/.meta/Example.roc b/exercises/practice/robot-simulator/.meta/Example.roc index beec2f8f..19abec11 100644 --- a/exercises/practice/robot-simulator/.meta/Example.roc +++ b/exercises/practice/robot-simulator/.meta/Example.roc @@ -1,41 +1,42 @@ -module [create, move] +RobotSimulator :: {}.{ + create : { x ?? I64, y ?? I64, direction ?? Direction } -> Robot + create = |{ x ?? 0, y ?? 0, direction ?? North }| + { x, y, direction } -Direction : [North, East, South, West] -Robot : { x : I64, y : I64, direction : Direction } + move : Robot, Str -> Robot + move = |robot, instructions| + instructions + |> Str.to_utf8 + |> List.walk( + robot, + |{ x, y, direction }, command| + when command is + 'R' -> + when direction is + North -> { x, y, direction: East } + East -> { x, y, direction: South } + South -> { x, y, direction: West } + West -> { x, y, direction: North } -create : { x ?? I64, y ?? I64, direction ?? Direction } -> Robot -create = |{ x ?? 0, y ?? 0, direction ?? North }| - { x, y, direction } + 'L' -> + when direction is + North -> { x, y, direction: West } + East -> { x, y, direction: North } + South -> { x, y, direction: East } + West -> { x, y, direction: South } -move : Robot, Str -> Robot -move = |robot, instructions| - instructions - |> Str.to_utf8 - |> List.walk( - robot, - |{ x, y, direction }, command| - when command is - 'R' -> - when direction is - North -> { x, y, direction: East } - East -> { x, y, direction: South } - South -> { x, y, direction: West } - West -> { x, y, direction: North } + 'A' -> + when direction is + North -> { x, y: y + 1, direction } + East -> { x: x + 1, y, direction } + South -> { x, y: y - 1, direction } + West -> { x: x - 1, y, direction } - 'L' -> - when direction is - North -> { x, y, direction: West } - East -> { x, y, direction: North } - South -> { x, y, direction: East } - West -> { x, y, direction: South } + _ -> { x, y, direction }, + ) # invalid instructions are ignored - 'A' -> - when direction is - North -> { x, y: y + 1, direction } - East -> { x: x + 1, y, direction } - South -> { x, y: y - 1, direction } - West -> { x: x - 1, y, direction } +} - _ -> { x, y, direction }, - ) # invalid instructions are ignored +Direction : [North, East, South, West] +Robot : { x : I64, y : I64, direction : Direction } diff --git a/exercises/practice/robot-simulator/RobotSimulator.roc b/exercises/practice/robot-simulator/RobotSimulator.roc index 24759203..408c83e7 100644 --- a/exercises/practice/robot-simulator/RobotSimulator.roc +++ b/exercises/practice/robot-simulator/RobotSimulator.roc @@ -1,12 +1,13 @@ -module [create, move] +RobotSimulator :: {}.{ + create : { x ?? I64, y ?? I64, direction ?? Direction } -> Robot + create = |{ x ?? 0, y ?? 0, direction ?? North }| + crash("Please implement the 'create' function") -Direction : [North, East, South, West] -Robot : { x : I64, y : I64, direction : Direction } + move : Robot, Str -> Robot + move = |robot, instructions| + crash("Please implement the 'move' function") +} -create : { x ?? I64, y ?? I64, direction ?? Direction } -> Robot -create = |{ x ?? 0, y ?? 0, direction ?? North }| - crash("Please implement the 'create' function") -move : Robot, Str -> Robot -move = |robot, instructions| - crash("Please implement the 'move' function") +Direction : [North, East, South, West] +Robot : { x : I64, y : I64, direction : Direction } diff --git a/exercises/practice/roman-numerals/.meta/Example.roc b/exercises/practice/roman-numerals/.meta/Example.roc index d7096008..35debd3f 100644 --- a/exercises/practice/roman-numerals/.meta/Example.roc +++ b/exercises/practice/roman-numerals/.meta/Example.roc @@ -1,17 +1,17 @@ -module [roman] - -roman : U64 -> Result Str [InvalidNumber U64] -roman = |number| - if number == 0 or number > 3999 then - Err(InvalidNumber(number)) - else - convert = |digit, x1, x5, x10| - when digit is - 4 -> "${x1}${x5}" - 9 -> "${x1}${x10}" - n -> if n <= 3 then Str.repeat(x1, n) else "${x5}${Str.repeat(x1, (n - 5))}" - thousands = convert((number // 1000), "M", "?", "?") - hundreds = convert((number % 1000 // 100), "C", "D", "M") - tens = convert((number % 100 // 10), "X", "L", "C") - units = convert((number % 10), "I", "V", "X") - Ok("${thousands}${hundreds}${tens}${units}") +RomanNumerals :: {}.{ + roman : U64 -> Result Str [InvalidNumber U64] + roman = |number| + if number == 0 or number > 3999 then + Err(InvalidNumber(number)) + else + convert = |digit, x1, x5, x10| + when digit is + 4 -> "${x1}${x5}" + 9 -> "${x1}${x10}" + n -> if n <= 3 then Str.repeat(x1, n) else "${x5}${Str.repeat(x1, (n - 5))}" + thousands = convert((number // 1000), "M", "?", "?") + hundreds = convert((number % 1000 // 100), "C", "D", "M") + tens = convert((number % 100 // 10), "X", "L", "C") + units = convert((number % 10), "I", "V", "X") + Ok("${thousands}${hundreds}${tens}${units}") +} diff --git a/exercises/practice/roman-numerals/RomanNumerals.roc b/exercises/practice/roman-numerals/RomanNumerals.roc index 4aba0410..4583bea1 100644 --- a/exercises/practice/roman-numerals/RomanNumerals.roc +++ b/exercises/practice/roman-numerals/RomanNumerals.roc @@ -1,5 +1,5 @@ -module [roman] - -roman : U64 -> Result Str _ -roman = |number| - crash("Please implement the 'roman' function") +RomanNumerals :: {}.{ + roman : U64 -> Result Str _ + roman = |number| + crash("Please implement the 'roman' function") +} diff --git a/exercises/practice/rotational-cipher/.meta/Example.roc b/exercises/practice/rotational-cipher/.meta/Example.roc index 509a9d72..b337b040 100644 --- a/exercises/practice/rotational-cipher/.meta/Example.roc +++ b/exercises/practice/rotational-cipher/.meta/Example.roc @@ -1,4 +1,13 @@ -module [rotate] +RotationalCipher :: {}.{ + rotate : Str, U8 -> Str + rotate = |text, shift_key| + text + |> Str.to_utf8 + |> List.map(|c| shift_char(c, shift_key)) + |> Str.from_utf8 + |> Result.with_default("Unreachable") +} + shift_char = |c, shift_key| if c >= 'a' and c <= 'z' then @@ -7,11 +16,3 @@ shift_char = |c, shift_key| (c - 'A' + shift_key) % 26 + 'A' else c - -rotate : Str, U8 -> Str -rotate = |text, shift_key| - text - |> Str.to_utf8 - |> List.map(|c| shift_char(c, shift_key)) - |> Str.from_utf8 - |> Result.with_default("Unreachable") diff --git a/exercises/practice/rotational-cipher/RotationalCipher.roc b/exercises/practice/rotational-cipher/RotationalCipher.roc index fec5b98f..b03b7df9 100644 --- a/exercises/practice/rotational-cipher/RotationalCipher.roc +++ b/exercises/practice/rotational-cipher/RotationalCipher.roc @@ -1,5 +1,5 @@ -module [rotate] - -rotate : Str, U8 -> Str -rotate = |text, shift_key| - crash("Please implement the 'rotate' function") +RotationalCipher :: {}.{ + rotate : Str, U8 -> Str + rotate = |text, shift_key| + crash("Please implement the 'rotate' function") +} diff --git a/exercises/practice/run-length-encoding/.meta/Example.roc b/exercises/practice/run-length-encoding/.meta/Example.roc index 736cb6ff..baa9afec 100644 --- a/exercises/practice/run-length-encoding/.meta/Example.roc +++ b/exercises/practice/run-length-encoding/.meta/Example.roc @@ -1,50 +1,50 @@ -module [encode, decode] - -encode : Str -> Result Str [BadUtf8 _] -encode = |string| - append_count_and_letter = |state| - if state.count == 0 then - [] - else if state.count == 1 then - state.chars |> List.append(state.last_char) - else - digits = state.count |> Num.to_str |> Str.to_utf8 - state.chars |> List.concat(digits) |> List.append(state.last_char) - - string - |> Str.to_utf8 - |> List.walk( - { chars: [], last_char: 0, count: 0 }, - |state, char| +RunLengthEncoding :: {}.{ + encode : Str -> Result Str [BadUtf8 _] + encode = |string| + append_count_and_letter = |state| if state.count == 0 then - { chars: [], last_char: char, count: 1 } - else if state.last_char == char then - { state & count: state.count + 1 } + [] + else if state.count == 1 then + state.chars |> List.append(state.last_char) else - chars = append_count_and_letter(state) - { chars, last_char: char, count: 1 }, - ) - |> append_count_and_letter - |> Str.from_utf8 + digits = state.count |> Num.to_str |> Str.to_utf8 + state.chars |> List.concat(digits) |> List.append(state.last_char) -decode : Str -> Result Str [BadUtf8 _, InvalidNumStr] -decode = |string| - string - |> Str.to_utf8 - |> List.walk_try( - { chars: [], digits: [] }, - |state, char| - if char >= '0' and char <= '9' then - digits = state.digits |> List.append(char) - Ok({ state & digits }) - else if state.digits == [] then - chars = state.chars |> List.append(char) - Ok({ state & chars }) - else - count_str = Str.from_utf8(state.digits)? - count = Str.to_u64(count_str)? - chars = state.chars |> List.concat(List.repeat(char, count)) - Ok({ chars, digits: [] }), - )? - |> .chars - |> Str.from_utf8 + string + |> Str.to_utf8 + |> List.walk( + { chars: [], last_char: 0, count: 0 }, + |state, char| + if state.count == 0 then + { chars: [], last_char: char, count: 1 } + else if state.last_char == char then + { state & count: state.count + 1 } + else + chars = append_count_and_letter(state) + { chars, last_char: char, count: 1 }, + ) + |> append_count_and_letter + |> Str.from_utf8 + + decode : Str -> Result Str [BadUtf8 _, InvalidNumStr] + decode = |string| + string + |> Str.to_utf8 + |> List.walk_try( + { chars: [], digits: [] }, + |state, char| + if char >= '0' and char <= '9' then + digits = state.digits |> List.append(char) + Ok({ state & digits }) + else if state.digits == [] then + chars = state.chars |> List.append(char) + Ok({ state & chars }) + else + count_str = Str.from_utf8(state.digits)? + count = Str.to_u64(count_str)? + chars = state.chars |> List.concat(List.repeat(char, count)) + Ok({ chars, digits: [] }), + )? + |> .chars + |> Str.from_utf8 +} diff --git a/exercises/practice/run-length-encoding/RunLengthEncoding.roc b/exercises/practice/run-length-encoding/RunLengthEncoding.roc index 3a239a66..a791b2e0 100644 --- a/exercises/practice/run-length-encoding/RunLengthEncoding.roc +++ b/exercises/practice/run-length-encoding/RunLengthEncoding.roc @@ -1,9 +1,9 @@ -module [encode, decode] +RunLengthEncoding :: {}.{ + encode : Str -> Result Str _ + encode = |string| + crash("Please implement the 'encode' function") -encode : Str -> Result Str _ -encode = |string| - crash("Please implement the 'encode' function") - -decode : Str -> Result Str _ -decode = |string| - crash("Please implement the 'decode' function") + decode : Str -> Result Str _ + decode = |string| + crash("Please implement the 'decode' function") +} diff --git a/exercises/practice/saddle-points/.meta/Example.roc b/exercises/practice/saddle-points/.meta/Example.roc index 7beb8c81..e4c96f7a 100644 --- a/exercises/practice/saddle-points/.meta/Example.roc +++ b/exercises/practice/saddle-points/.meta/Example.roc @@ -1,46 +1,47 @@ -module [saddle_points] +SaddlePoints :: {}.{ + saddle_points : Forest -> Set Position + saddle_points = |tree_heights| + tallest_trees_east_west = + tree_heights + |> List.map_with_index( + |row, row_index| + max_in_row = row |> List.max |> Result.with_default(0) + row + |> List.map_with_index( + |height, column_index| + if height == max_in_row then [{ row: row_index + 1, column: column_index + 1 }] else [], + ) + |> List.join, + ) + |> List.join + |> Set.from_list -Forest : List (List U8) -Position : { row : U64, column : U64 } + num_columns = tree_heights |> List.map(List.len) |> List.max |> Result.with_default(0) + smallest_trees_north_south = + List.range({ start: At(0), end: Before(num_columns) }) + |> List.map( + |column_index| + column = + tree_heights + |> List.map_with_index( + |row, row_index| + row + |> List.get(column_index)? + |> |height| Ok({ height, row_index }), + ) + |> List.keep_oks(|id| id) -saddle_points : Forest -> Set Position -saddle_points = |tree_heights| - tallest_trees_east_west = - tree_heights - |> List.map_with_index( - |row, row_index| - max_in_row = row |> List.max |> Result.with_default(0) - row - |> List.map_with_index( - |height, column_index| - if height == max_in_row then [{ row: row_index + 1, column: column_index + 1 }] else [], - ) - |> List.join, - ) - |> List.join - |> Set.from_list + min_in_column = column |> List.map(.height) |> List.min |> Result.with_default(0) + column + |> List.keep_if(|{ height }| height == min_in_column) + |> List.map(|{ row_index }| { row: row_index + 1, column: column_index + 1 }), + ) + |> List.join_map(|id| id) + |> Set.from_list - num_columns = tree_heights |> List.map(List.len) |> List.max |> Result.with_default(0) - smallest_trees_north_south = - List.range({ start: At(0), end: Before(num_columns) }) - |> List.map( - |column_index| - column = - tree_heights - |> List.map_with_index( - |row, row_index| - row - |> List.get(column_index)? - |> |height| Ok({ height, row_index }), - ) - |> List.keep_oks(|id| id) + tallest_trees_east_west |> Set.intersection(smallest_trees_north_south) +} - min_in_column = column |> List.map(.height) |> List.min |> Result.with_default(0) - column - |> List.keep_if(|{ height }| height == min_in_column) - |> List.map(|{ row_index }| { row: row_index + 1, column: column_index + 1 }), - ) - |> List.join_map(|id| id) - |> Set.from_list - tallest_trees_east_west |> Set.intersection(smallest_trees_north_south) +Forest : List (List U8) +Position : { row : U64, column : U64 } diff --git a/exercises/practice/saddle-points/SaddlePoints.roc b/exercises/practice/saddle-points/SaddlePoints.roc index d534dae6..d4043cd6 100644 --- a/exercises/practice/saddle-points/SaddlePoints.roc +++ b/exercises/practice/saddle-points/SaddlePoints.roc @@ -1,8 +1,9 @@ -module [saddle_points] +SaddlePoints :: {}.{ + saddle_points : Forest -> Set Position + saddle_points = |tree_heights| + crash("Please implement the 'saddle_points' function") +} + Forest : List (List U8) Position : { row : U64, column : U64 } - -saddle_points : Forest -> Set Position -saddle_points = |tree_heights| - crash("Please implement the 'saddle_points' function") diff --git a/exercises/practice/say/.meta/Example.roc b/exercises/practice/say/.meta/Example.roc index 5e304842..82ac9063 100644 --- a/exercises/practice/say/.meta/Example.roc +++ b/exercises/practice/say/.meta/Example.roc @@ -1,4 +1,40 @@ -module [say] +Say :: {}.{ + say : U64 -> Result Str [OutOfBounds] + say = |number| + if number < 20 then + zero_to_nineteen |> List.get(number)? |> Ok + else if number < 100 then + tens_word = tens_after_ten |> List.get(number // 10 - 2)? + digit = number % 10 + if digit > 0 then + digit_word = say(digit)? + Ok("${tens_word}-${digit_word}") + else + Ok(tens_word) + else if number < 1_000_000_000_000 then + [ + (1_000_000_000_000, 1_000_000_000, "billion"), + (1_000_000_000, 1_000_000, "million"), + (1_000_000, 1000, "thousand"), + (1000, 100, "hundred"), + (100, 1, ""), + ] + |> List.keep_oks( + |(modulo, divisor, name)| + how_many = (number % modulo) // divisor + if how_many == 0 then + Err(NothingToSay) + else + say_how_many = say(how_many)? + Ok("${say_how_many} ${name}"), + ) + |> Str.join_with(" ") + |> Str.trim_end + |> Ok + else + Err(OutOfBounds) +} + zero_to_nineteen = [ "zero", @@ -33,38 +69,3 @@ tens_after_ten = [ "eighty", "ninety", ] - -say : U64 -> Result Str [OutOfBounds] -say = |number| - if number < 20 then - zero_to_nineteen |> List.get(number)? |> Ok - else if number < 100 then - tens_word = tens_after_ten |> List.get(number // 10 - 2)? - digit = number % 10 - if digit > 0 then - digit_word = say(digit)? - Ok("${tens_word}-${digit_word}") - else - Ok(tens_word) - else if number < 1_000_000_000_000 then - [ - (1_000_000_000_000, 1_000_000_000, "billion"), - (1_000_000_000, 1_000_000, "million"), - (1_000_000, 1000, "thousand"), - (1000, 100, "hundred"), - (100, 1, ""), - ] - |> List.keep_oks( - |(modulo, divisor, name)| - how_many = (number % modulo) // divisor - if how_many == 0 then - Err(NothingToSay) - else - say_how_many = say(how_many)? - Ok("${say_how_many} ${name}"), - ) - |> Str.join_with(" ") - |> Str.trim_end - |> Ok - else - Err(OutOfBounds) diff --git a/exercises/practice/say/Say.roc b/exercises/practice/say/Say.roc index 1995e45d..95a3f96f 100644 --- a/exercises/practice/say/Say.roc +++ b/exercises/practice/say/Say.roc @@ -1,5 +1,5 @@ -module [say] - -say : U64 -> Result Str [OutOfBounds] -say = |number| - crash("Please implement the 'say' function") +Say :: {}.{ + say : U64 -> Result Str [OutOfBounds] + say = |number| + crash("Please implement the 'say' function") +} diff --git a/exercises/practice/scrabble-score/.meta/Example.roc b/exercises/practice/scrabble-score/.meta/Example.roc index 61d87411..5f861abf 100644 --- a/exercises/practice/scrabble-score/.meta/Example.roc +++ b/exercises/practice/scrabble-score/.meta/Example.roc @@ -1,11 +1,11 @@ -module [score] - -score : Str -> U64 -score = |word| - word - |> Str.to_utf8 - |> List.map(letter_value) - |> List.sum +ScrabbleScore :: {}.{ + score : Str -> U64 + score = |word| + word + |> Str.to_utf8 + |> List.map(letter_value) + |> List.sum +} to_upper : U8 -> U8 to_upper = |letter| diff --git a/exercises/practice/scrabble-score/ScrabbleScore.roc b/exercises/practice/scrabble-score/ScrabbleScore.roc index 1e26390c..efe42887 100644 --- a/exercises/practice/scrabble-score/ScrabbleScore.roc +++ b/exercises/practice/scrabble-score/ScrabbleScore.roc @@ -1,5 +1,5 @@ -module [score] - -score : Str -> U64 -score = |word| - crash("Please implement the 'score' function") +ScrabbleScore :: {}.{ + score : Str -> U64 + score = |word| + crash("Please implement the 'score' function") +} diff --git a/exercises/practice/secret-handshake/.meta/Example.roc b/exercises/practice/secret-handshake/.meta/Example.roc index 49f26c13..322602df 100644 --- a/exercises/practice/secret-handshake/.meta/Example.roc +++ b/exercises/practice/secret-handshake/.meta/Example.roc @@ -1,15 +1,15 @@ -module [commands] +SecretHandshake :: {}.{ + commands : U64 -> List Str + commands = |number| + actions = + [(1, "wink"), (2, "double blink"), (4, "close your eyes"), (8, "jump")] + |> List.join_map( + |(mask, action)| + if Num.bitwise_and(number, mask) == 0 then [] else [action], + ) -commands : U64 -> List Str -commands = |number| - actions = - [(1, "wink"), (2, "double blink"), (4, "close your eyes"), (8, "jump")] - |> List.join_map( - |(mask, action)| - if Num.bitwise_and(number, mask) == 0 then [] else [action], - ) - - if Num.bitwise_and(number, 16) == 0 then - actions - else - List.reverse(actions) + if Num.bitwise_and(number, 16) == 0 then + actions + else + List.reverse(actions) +} diff --git a/exercises/practice/secret-handshake/SecretHandshake.roc b/exercises/practice/secret-handshake/SecretHandshake.roc index ef974948..4d838185 100644 --- a/exercises/practice/secret-handshake/SecretHandshake.roc +++ b/exercises/practice/secret-handshake/SecretHandshake.roc @@ -1,5 +1,5 @@ -module [commands] - -commands : U64 -> List Str -commands = |number| - crash("Please implement the 'commands' function") +SecretHandshake :: {}.{ + commands : U64 -> List Str + commands = |number| + crash("Please implement the 'commands' function") +} diff --git a/exercises/practice/series/.meta/Example.roc b/exercises/practice/series/.meta/Example.roc index 4ed73020..cedc9311 100644 --- a/exercises/practice/series/.meta/Example.roc +++ b/exercises/practice/series/.meta/Example.roc @@ -1,20 +1,20 @@ -module [slices] - -slices : Str, U64 -> List Str -slices = |string, slice_length| - chars = string |> Str.to_utf8 - len = chars |> List.len - if len == 0 or slice_length == 0 or slice_length > len then - [] - else - maybe_list = - List.range({ start: At(0), end: At((len - slice_length)) }) - |> List.map_try( - |start_index| - chars - |> List.sublist({ start: start_index, len: slice_length }) - |> Str.from_utf8, - ) - when maybe_list is - Ok(list) -> list - Err(BadUtf8(_)) -> crash("Only ASCII strings are supported") +Series :: {}.{ + slices : Str, U64 -> List Str + slices = |string, slice_length| + chars = string |> Str.to_utf8 + len = chars |> List.len + if len == 0 or slice_length == 0 or slice_length > len then + [] + else + maybe_list = + List.range({ start: At(0), end: At((len - slice_length)) }) + |> List.map_try( + |start_index| + chars + |> List.sublist({ start: start_index, len: slice_length }) + |> Str.from_utf8, + ) + when maybe_list is + Ok(list) -> list + Err(BadUtf8(_)) -> crash("Only ASCII strings are supported") +} diff --git a/exercises/practice/series/Series.roc b/exercises/practice/series/Series.roc index 4fb408be..63c1084c 100644 --- a/exercises/practice/series/Series.roc +++ b/exercises/practice/series/Series.roc @@ -1,5 +1,5 @@ -module [slices] - -slices : Str, U64 -> List Str -slices = |string, slice_length| - crash("Please implement the 'slices' function") +Series :: {}.{ + slices : Str, U64 -> List Str + slices = |string, slice_length| + crash("Please implement the 'slices' function") +} diff --git a/exercises/practice/sgf-parsing/.meta/Example.roc b/exercises/practice/sgf-parsing/.meta/Example.roc index 08980461..c3c1eda1 100644 --- a/exercises/practice/sgf-parsing/.meta/Example.roc +++ b/exercises/practice/sgf-parsing/.meta/Example.roc @@ -1,7 +1,12 @@ -module [parse] + import parser.Parser as P import parser.String as S +SgfParsing :: {}.{ + parse : Str -> Result GameTree [ParsingFailure Str, ParsingIncomplete Str] + parse = |sgf| + S.parse_str(game_tree, sgf) +} # --- SGF GRAMMAR --- # Source: https://homepages.cwi.nl/~aeb/go/misc/sgfnotes.html @@ -110,7 +115,3 @@ value_type = uc_letter : P.Parser (List U8) U8 uc_letter = S.codeunit_satisfies(|b| b >= 'A' and b <= 'Z') - -parse : Str -> Result GameTree [ParsingFailure Str, ParsingIncomplete Str] -parse = |sgf| - S.parse_str(game_tree, sgf) diff --git a/exercises/practice/sgf-parsing/SgfParsing.roc b/exercises/practice/sgf-parsing/SgfParsing.roc index 584f64cc..a24becbd 100644 --- a/exercises/practice/sgf-parsing/SgfParsing.roc +++ b/exercises/practice/sgf-parsing/SgfParsing.roc @@ -1,4 +1,9 @@ -module [parse] +SgfParsing :: {}.{ + parse : Str -> Result GameTree _ + parse = |sgf| + crash("Please implement the 'parse' function") +} + # HINT: we have added the `roc-parser` package to the app's header in # sgf-parsing-test.roc. You can use it if you want, particularly the @@ -13,7 +18,3 @@ NodeProperties : Dict Str (List Str) # the Roc compiler does not yet understand that an empty List can end the # recursion. GameTree : [Empty, GameNode { properties : NodeProperties, children : List GameTree }] - -parse : Str -> Result GameTree _ -parse = |sgf| - crash("Please implement the 'parse' function") diff --git a/exercises/practice/sieve/.meta/Example.roc b/exercises/practice/sieve/.meta/Example.roc index 34253bb2..cb6e5818 100644 --- a/exercises/practice/sieve/.meta/Example.roc +++ b/exercises/practice/sieve/.meta/Example.roc @@ -1,20 +1,20 @@ -module [primes] - -primes : U64 -> List U64 -primes = |limit| - if limit < 2 then - [] - else - help_sieve = |candidates, found_primes| - when candidates is - [] -> found_primes - [0, .. as rest] -> rest |> help_sieve(found_primes) - [prime, .. as rest] -> - List.range({ start: After(prime), end: At(limit), step: prime }) - |> List.walk( - rest, - |filtered_candidates, multiple_of_prime| - filtered_candidates |> List.replace((multiple_of_prime - prime - 1), 0) |> .list, - ) - |> help_sieve((found_primes |> List.append(prime))) - help_sieve(List.range({ start: At(2), end: At(limit) }), []) +Sieve :: {}.{ + primes : U64 -> List U64 + primes = |limit| + if limit < 2 then + [] + else + help_sieve = |candidates, found_primes| + when candidates is + [] -> found_primes + [0, .. as rest] -> rest |> help_sieve(found_primes) + [prime, .. as rest] -> + List.range({ start: After(prime), end: At(limit), step: prime }) + |> List.walk( + rest, + |filtered_candidates, multiple_of_prime| + filtered_candidates |> List.replace((multiple_of_prime - prime - 1), 0) |> .list, + ) + |> help_sieve((found_primes |> List.append(prime))) + help_sieve(List.range({ start: At(2), end: At(limit) }), []) +} diff --git a/exercises/practice/sieve/Sieve.roc b/exercises/practice/sieve/Sieve.roc index 1485e34b..474b3f14 100644 --- a/exercises/practice/sieve/Sieve.roc +++ b/exercises/practice/sieve/Sieve.roc @@ -1,5 +1,5 @@ -module [primes] - -primes : U64 -> List U64 -primes = |limit| - crash("Please implement the 'primes' function") +Sieve :: {}.{ + primes : U64 -> List U64 + primes = |limit| + crash("Please implement the 'primes' function") +} diff --git a/exercises/practice/simple-linked-list/.meta/Example.roc b/exercises/practice/simple-linked-list/.meta/Example.roc index 4551fd71..9be6c4d5 100644 --- a/exercises/practice/simple-linked-list/.meta/Example.roc +++ b/exercises/practice/simple-linked-list/.meta/Example.roc @@ -1,37 +1,38 @@ -module [from_list, to_list, push, pop, reverse, len] +SimpleLinkedList :: {}.{ + from_list : List U64 -> SimpleLinkedList + from_list = |list| + list |> List.walk(Nil, push) -SimpleLinkedList : [Nil, Cons U64 SimpleLinkedList] + to_list : SimpleLinkedList -> List U64 + to_list = |linked_list| + when linked_list is + Nil -> [] + Cons(head, tail) -> tail |> to_list |> List.append(head) -from_list : List U64 -> SimpleLinkedList -from_list = |list| - list |> List.walk(Nil, push) + push : SimpleLinkedList, U64 -> SimpleLinkedList + push = |linked_list, item| + Cons(item, linked_list) -to_list : SimpleLinkedList -> List U64 -to_list = |linked_list| - when linked_list is - Nil -> [] - Cons(head, tail) -> tail |> to_list |> List.append(head) + pop : SimpleLinkedList -> Result { value : U64, linked_list : SimpleLinkedList } [LinkedListWasEmpty] + pop = |linked_list| + when linked_list is + Nil -> Err(LinkedListWasEmpty) + Cons(head, tail) -> Ok({ value: head, linked_list: tail }) -push : SimpleLinkedList, U64 -> SimpleLinkedList -push = |linked_list, item| - Cons(item, linked_list) + reverse : SimpleLinkedList -> SimpleLinkedList + reverse = |linked_list| + help = |result, rest| + when rest is + Nil -> result + Cons(head, tail) -> help((result |> push(head)), tail) + help(Nil, linked_list) -pop : SimpleLinkedList -> Result { value : U64, linked_list : SimpleLinkedList } [LinkedListWasEmpty] -pop = |linked_list| - when linked_list is - Nil -> Err(LinkedListWasEmpty) - Cons(head, tail) -> Ok({ value: head, linked_list: tail }) + len : SimpleLinkedList -> U64 + len = |linked_list| + when linked_list is + Nil -> 0 + Cons(_, tail) -> 1 + len(tail) +} -reverse : SimpleLinkedList -> SimpleLinkedList -reverse = |linked_list| - help = |result, rest| - when rest is - Nil -> result - Cons(head, tail) -> help((result |> push(head)), tail) - help(Nil, linked_list) -len : SimpleLinkedList -> U64 -len = |linked_list| - when linked_list is - Nil -> 0 - Cons(_, tail) -> 1 + len(tail) +SimpleLinkedList : [Nil, Cons U64 SimpleLinkedList] diff --git a/exercises/practice/simple-linked-list/SimpleLinkedList.roc b/exercises/practice/simple-linked-list/SimpleLinkedList.roc index 8e7201ce..16afb388 100644 --- a/exercises/practice/simple-linked-list/SimpleLinkedList.roc +++ b/exercises/practice/simple-linked-list/SimpleLinkedList.roc @@ -1,4 +1,29 @@ -module [from_list, to_list, push, pop, reverse, len] +SimpleLinkedList :: {}.{ + from_list : List U64 -> SimpleLinkedList + from_list = |list| + crash("Please implement the 'from_list' function") + + to_list : SimpleLinkedList -> List U64 + to_list = |linked_list| + crash("Please implement the 'to_list' function") + + push : SimpleLinkedList, U64 -> SimpleLinkedList + push = |linked_list, item| + crash("Please implement the 'push' function") + + pop : SimpleLinkedList -> Result { value : U64, linked_list : SimpleLinkedList } _ + pop = |linked_list| + crash("Please implement the 'pop' function") + + reverse : SimpleLinkedList -> SimpleLinkedList + reverse = |linked_list| + crash("Please implement the 'reverse' function") + + len : SimpleLinkedList -> U64 + len = |linked_list| + crash("Please implement the 'len' function") +} + SimpleLinkedList : { # TODO: change this type however you need @@ -7,27 +32,3 @@ SimpleLinkedList : { todo3 : U64, # etc. } - -from_list : List U64 -> SimpleLinkedList -from_list = |list| - crash("Please implement the 'from_list' function") - -to_list : SimpleLinkedList -> List U64 -to_list = |linked_list| - crash("Please implement the 'to_list' function") - -push : SimpleLinkedList, U64 -> SimpleLinkedList -push = |linked_list, item| - crash("Please implement the 'push' function") - -pop : SimpleLinkedList -> Result { value : U64, linked_list : SimpleLinkedList } _ -pop = |linked_list| - crash("Please implement the 'pop' function") - -reverse : SimpleLinkedList -> SimpleLinkedList -reverse = |linked_list| - crash("Please implement the 'reverse' function") - -len : SimpleLinkedList -> U64 -len = |linked_list| - crash("Please implement the 'len' function") diff --git a/exercises/practice/space-age/.meta/Example.roc b/exercises/practice/space-age/.meta/Example.roc index 71ede8fc..23e4adc5 100644 --- a/exercises/practice/space-age/.meta/Example.roc +++ b/exercises/practice/space-age/.meta/Example.roc @@ -1,4 +1,12 @@ -module [age] +SpaceAge :: {}.{ + age : Planet, Dec -> Dec + age = |planet, seconds| + period_in_earth_years = orbital_period_in_earth_years(planet) + period_in_seconds = period_in_earth_years * 365.25 * 24 * 60 * 60 + planet_years = seconds / period_in_seconds + round(planet_years) +} + Planet : [ Mercury, @@ -11,13 +19,6 @@ Planet : [ Neptune, ] -age : Planet, Dec -> Dec -age = |planet, seconds| - period_in_earth_years = orbital_period_in_earth_years(planet) - period_in_seconds = period_in_earth_years * 365.25 * 24 * 60 * 60 - planet_years = seconds / period_in_seconds - round(planet_years) - round : Dec -> Dec round = |value| pow = 10.0 |> Num.pow(2) diff --git a/exercises/practice/space-age/SpaceAge.roc b/exercises/practice/space-age/SpaceAge.roc index 616135d8..ebb812c7 100644 --- a/exercises/practice/space-age/SpaceAge.roc +++ b/exercises/practice/space-age/SpaceAge.roc @@ -1,4 +1,9 @@ -module [age] +SpaceAge :: {}.{ + age : Planet, Dec -> Dec + age = |planet, seconds| + crash("Please implement the 'age' function") +} + Planet : [ Mercury, @@ -10,7 +15,3 @@ Planet : [ Uranus, Neptune, ] - -age : Planet, Dec -> Dec -age = |planet, seconds| - crash("Please implement the 'age' function") diff --git a/exercises/practice/spiral-matrix/.meta/Example.roc b/exercises/practice/spiral-matrix/.meta/Example.roc index 94923e23..f3b6917b 100644 --- a/exercises/practice/spiral-matrix/.meta/Example.roc +++ b/exercises/practice/spiral-matrix/.meta/Example.roc @@ -1,31 +1,31 @@ -module [spiral_matrix] +SpiralMatrix :: {}.{ + spiral_matrix : U64 -> List (List U64) + spiral_matrix = |size| + zero_matrix = List.repeat(List.repeat(0, size), size) + List.range({ start: At(1), end: At((size * size)) }) + |> List.walk( + { x: -1, y: 0, dx: 1, dy: 0, matrix: zero_matrix }, + |state, index| + get_value = |x_i64, y_i64| + row = state.matrix |> List.get(Num.to_u64(y_i64)) |> Result.with_default([]) + row |> List.get(Num.to_u64(x_i64)) -spiral_matrix : U64 -> List (List U64) -spiral_matrix = |size| - zero_matrix = List.repeat(List.repeat(0, size), size) - List.range({ start: At(1), end: At((size * size)) }) - |> List.walk( - { x: -1, y: 0, dx: 1, dy: 0, matrix: zero_matrix }, - |state, index| - get_value = |x_i64, y_i64| - row = state.matrix |> List.get(Num.to_u64(y_i64)) |> Result.with_default([]) - row |> List.get(Num.to_u64(x_i64)) + (dx, dy) = + (nx, ny) = (state.x + state.dx, state.y + state.dy) + if nx < 0 or ny < 0 or get_value(nx, ny) != Ok(0) then + (-state.dy, state.dx) # outside or non-zero value => turn right + else + (state.dx, state.dy) # or else continue in the same direction - (dx, dy) = - (nx, ny) = (state.x + state.dx, state.y + state.dy) - if nx < 0 or ny < 0 or get_value(nx, ny) != Ok(0) then - (-state.dy, state.dx) # outside or non-zero value => turn right - else - (state.dx, state.dy) # or else continue in the same direction - - (x, y) = (state.x + dx, state.y + dy) - matrix = - state.matrix - |> List.update( - (y |> Num.to_u64), - |row| - row |> List.update((x |> Num.to_u64), |_| index), - ) - { x, y, dx, dy, matrix }, - ) - |> .matrix + (x, y) = (state.x + dx, state.y + dy) + matrix = + state.matrix + |> List.update( + (y |> Num.to_u64), + |row| + row |> List.update((x |> Num.to_u64), |_| index), + ) + { x, y, dx, dy, matrix }, + ) + |> .matrix +} diff --git a/exercises/practice/spiral-matrix/SpiralMatrix.roc b/exercises/practice/spiral-matrix/SpiralMatrix.roc index 8e313a2f..32280bb8 100644 --- a/exercises/practice/spiral-matrix/SpiralMatrix.roc +++ b/exercises/practice/spiral-matrix/SpiralMatrix.roc @@ -1,5 +1,5 @@ -module [spiral_matrix] - -spiral_matrix : U64 -> List (List U64) -spiral_matrix = |size| - crash("Please implement the 'spiral_matrix' function") +SpiralMatrix :: {}.{ + spiral_matrix : U64 -> List (List U64) + spiral_matrix = |size| + crash("Please implement the 'spiral_matrix' function") +} diff --git a/exercises/practice/square-root/.meta/Example.roc b/exercises/practice/square-root/.meta/Example.roc index 1f559797..bc5878d6 100644 --- a/exercises/practice/square-root/.meta/Example.roc +++ b/exercises/practice/square-root/.meta/Example.roc @@ -1,19 +1,19 @@ -module [square_root, square_rootTheSimpleWay] +SquareRoot :: {}.{ + square_root : U64 -> U64 + square_root = |radicand| + binary_search = |min, max| + val = (min + max) // 2 + square = val * val + if square == radicand or min >= max then + val + else if square > radicand then + binary_search(min, (val - 1)) + else + binary_search((val + 1), max) -square_root : U64 -> U64 -square_root = |radicand| - binary_search = |min, max| - val = (min + max) // 2 - square = val * val - if square == radicand or min >= max then - val - else if square > radicand then - binary_search(min, (val - 1)) - else - binary_search((val + 1), max) + binary_search(0, radicand) - binary_search(0, radicand) - -square_rootTheSimpleWay = |radicand| - # This works too... but it's cheating, right? - radicand |> Num.to_f64 |> Num.sqrt |> Num.round + square_rootTheSimpleWay = |radicand| + # This works too... but it's cheating, right? + radicand |> Num.to_f64 |> Num.sqrt |> Num.round +} diff --git a/exercises/practice/square-root/SquareRoot.roc b/exercises/practice/square-root/SquareRoot.roc index b8070acf..16d71a8d 100644 --- a/exercises/practice/square-root/SquareRoot.roc +++ b/exercises/practice/square-root/SquareRoot.roc @@ -1,5 +1,5 @@ -module [square_root] - -square_root : U64 -> U64 -square_root = |radicand| - crash("Please implement the 'square_root' function") +SquareRoot :: {}.{ + square_root : U64 -> U64 + square_root = |radicand| + crash("Please implement the 'square_root' function") +} diff --git a/exercises/practice/strain/.meta/Example.roc b/exercises/practice/strain/.meta/Example.roc index 825bb268..77da32df 100644 --- a/exercises/practice/strain/.meta/Example.roc +++ b/exercises/practice/strain/.meta/Example.roc @@ -1,27 +1,27 @@ -module [keep, discard] +Strain :: {}.{ + keep : List a, (a -> Bool) -> List a + keep = |list, predicate| + loop = |sub_list, kept_items| + when sub_list is + [] -> kept_items + [first, .. as rest] -> + if predicate(first) then + rest |> loop(List.append(kept_items, first)) + else + rest |> loop(kept_items) -keep : List a, (a -> Bool) -> List a -keep = |list, predicate| - loop = |sub_list, kept_items| - when sub_list is - [] -> kept_items - [first, .. as rest] -> - if predicate(first) then - rest |> loop(List.append(kept_items, first)) - else - rest |> loop(kept_items) + loop(list, []) - loop(list, []) + discard : List a, (a -> Bool) -> List a + discard = |list, predicate| + loop = |sub_list, non_discarded_items| + when sub_list is + [] -> non_discarded_items + [first, .. as rest] -> + if predicate(first) then + rest |> loop(non_discarded_items) + else + rest |> loop(List.append(non_discarded_items, first)) -discard : List a, (a -> Bool) -> List a -discard = |list, predicate| - loop = |sub_list, non_discarded_items| - when sub_list is - [] -> non_discarded_items - [first, .. as rest] -> - if predicate(first) then - rest |> loop(non_discarded_items) - else - rest |> loop(List.append(non_discarded_items, first)) - - loop(list, []) + loop(list, []) +} diff --git a/exercises/practice/strain/Strain.roc b/exercises/practice/strain/Strain.roc index d55941da..b32a95c4 100644 --- a/exercises/practice/strain/Strain.roc +++ b/exercises/practice/strain/Strain.roc @@ -1,9 +1,9 @@ -module [keep, discard] +Strain :: {}.{ + keep : List a, (a -> Bool) -> List a + keep = |list, predicate| + crash("Please implement the 'keep' function") -keep : List a, (a -> Bool) -> List a -keep = |list, predicate| - crash("Please implement the 'keep' function") - -discard : List a, (a -> Bool) -> List a -discard = |list, predicate| - crash("Please implement the 'discard' function") + discard : List a, (a -> Bool) -> List a + discard = |list, predicate| + crash("Please implement the 'discard' function") +} diff --git a/exercises/practice/sublist/.meta/Example.roc b/exercises/practice/sublist/.meta/Example.roc index 3ba0ea99..de9f8a2e 100644 --- a/exercises/practice/sublist/.meta/Example.roc +++ b/exercises/practice/sublist/.meta/Example.roc @@ -1,29 +1,29 @@ -module [sublist] +Sublist :: {}.{ + sublist : List U8, List U8 -> [Equal, Sublist, Superlist, Unequal] + sublist = |list1, list2| + when List.len(list1) |> Num.compare(List.len(list2)) is + GT -> + when list2 |> sublist(list1) is + Sublist -> Superlist + Unequal -> Unequal + Superlist -> crash("Unreachable: list 2 is shorter than list 1") + Equal -> crash("Unreachable: list 1 and 2 don't have the same length") -sublist : List U8, List U8 -> [Equal, Sublist, Superlist, Unequal] -sublist = |list1, list2| - when List.len(list1) |> Num.compare(List.len(list2)) is - GT -> - when list2 |> sublist(list1) is - Sublist -> Superlist - Unequal -> Unequal - Superlist -> crash("Unreachable: list 2 is shorter than list 1") - Equal -> crash("Unreachable: list 1 and 2 don't have the same length") + EQ -> + if list1 == list2 then Equal else Unequal - EQ -> - if list1 == list2 then Equal else Unequal + LT -> + length_diff = List.len(list2) - List.len(list1) + maybe_equal_index = + List.range({ start: At(0), end: At(length_diff) }) + |> List.find_first( + |start| + list2 + |> List.sublist({ start, len: List.len(list1) }) + |> Bool.is_eq(list1), + ) - LT -> - length_diff = List.len(list2) - List.len(list1) - maybe_equal_index = - List.range({ start: At(0), end: At(length_diff) }) - |> List.find_first( - |start| - list2 - |> List.sublist({ start, len: List.len(list1) }) - |> Bool.is_eq(list1), - ) - - when maybe_equal_index is - Ok(_) -> Sublist - Err(NotFound) -> Unequal + when maybe_equal_index is + Ok(_) -> Sublist + Err(NotFound) -> Unequal +} diff --git a/exercises/practice/sublist/Sublist.roc b/exercises/practice/sublist/Sublist.roc index 98a1af62..3edbb4d9 100644 --- a/exercises/practice/sublist/Sublist.roc +++ b/exercises/practice/sublist/Sublist.roc @@ -1,5 +1,5 @@ -module [sublist] - -sublist : List U8, List U8 -> [Equal, Sublist, Superlist, Unequal] -sublist = |list1, list2| - crash("Please implement the 'sublist' function") +Sublist :: {}.{ + sublist : List U8, List U8 -> [Equal, Sublist, Superlist, Unequal] + sublist = |list1, list2| + crash("Please implement the 'sublist' function") +} diff --git a/exercises/practice/sum-of-multiples/.meta/Example.roc b/exercises/practice/sum-of-multiples/.meta/Example.roc index d3fd8200..1a82c4d5 100644 --- a/exercises/practice/sum-of-multiples/.meta/Example.roc +++ b/exercises/practice/sum-of-multiples/.meta/Example.roc @@ -1,13 +1,13 @@ -module [sum_of_multiples] - -sum_of_multiples : List U64, U64 -> U64 -sum_of_multiples = |factors, limit| - factors - |> List.keep_if(|factor| factor > 0) - |> List.join_map( - |factor| - List.range({ start: At(factor), end: Before(limit), step: factor }), - ) - |> Set.from_list - |> Set.to_list - |> List.sum +SumOfMultiples :: {}.{ + sum_of_multiples : List U64, U64 -> U64 + sum_of_multiples = |factors, limit| + factors + |> List.keep_if(|factor| factor > 0) + |> List.join_map( + |factor| + List.range({ start: At(factor), end: Before(limit), step: factor }), + ) + |> Set.from_list + |> Set.to_list + |> List.sum +} diff --git a/exercises/practice/sum-of-multiples/SumOfMultiples.roc b/exercises/practice/sum-of-multiples/SumOfMultiples.roc index fca4b8c1..01ec1b3f 100644 --- a/exercises/practice/sum-of-multiples/SumOfMultiples.roc +++ b/exercises/practice/sum-of-multiples/SumOfMultiples.roc @@ -1,5 +1,5 @@ -module [sum_of_multiples] - -sum_of_multiples : List U64, U64 -> U64 -sum_of_multiples = |factors, limit| - crash("Please implement the 'sum_of_multiples' function") +SumOfMultiples :: {}.{ + sum_of_multiples : List U64, U64 -> U64 + sum_of_multiples = |factors, limit| + crash("Please implement the 'sum_of_multiples' function") +} diff --git a/exercises/practice/tournament/.meta/Example.roc b/exercises/practice/tournament/.meta/Example.roc index 618df903..a29e9c81 100644 --- a/exercises/practice/tournament/.meta/Example.roc +++ b/exercises/practice/tournament/.meta/Example.roc @@ -1,33 +1,34 @@ -module [tally] +Tournament :: {}.{ + tally : Str -> Result Str [InvalidRow Str, InvalidResult Str] + tally = |table| + if table == "" then + Ok(header) + else + table + |> Str.split_on("\n") + |> List.map_try( + |row| + when row |> Str.split_on(";") is + [team1, team2, result_str] -> + result = result_str |> parse_result? + Ok((team1, team2, result)) -MatchResult : [Win, Loss, Draw] -TeamTally : { mp : U64, w : U64, d : U64, l : U64, p : U64 } + _ -> Err(InvalidRow(row)), + )? + |> List.walk( + Dict.empty({}), + |tally_dict, (team1, team2, result)| + tally_dict + |> update_tally_dict(team1, result) + |> update_tally_dict(team2, opposite_result(result)), + ) + |> tally_dict_to_table + |> Ok +} -tally : Str -> Result Str [InvalidRow Str, InvalidResult Str] -tally = |table| - if table == "" then - Ok(header) - else - table - |> Str.split_on("\n") - |> List.map_try( - |row| - when row |> Str.split_on(";") is - [team1, team2, result_str] -> - result = result_str |> parse_result? - Ok((team1, team2, result)) - _ -> Err(InvalidRow(row)), - )? - |> List.walk( - Dict.empty({}), - |tally_dict, (team1, team2, result)| - tally_dict - |> update_tally_dict(team1, result) - |> update_tally_dict(team2, opposite_result(result)), - ) - |> tally_dict_to_table - |> Ok +MatchResult : [Win, Loss, Draw] +TeamTally : { mp : U64, w : U64, d : U64, l : U64, p : U64 } parse_result : Str -> Result MatchResult [InvalidResult Str] parse_result = |result_str| diff --git a/exercises/practice/tournament/Tournament.roc b/exercises/practice/tournament/Tournament.roc index 2dffe164..c5ee0d22 100644 --- a/exercises/practice/tournament/Tournament.roc +++ b/exercises/practice/tournament/Tournament.roc @@ -1,5 +1,5 @@ -module [tally] - -tally : Str -> Result Str _ -tally = |table| - crash("Please implement the 'tally' function") +Tournament :: {}.{ + tally : Str -> Result Str _ + tally = |table| + crash("Please implement the 'tally' function") +} diff --git a/exercises/practice/transpose/.meta/Example.roc b/exercises/practice/transpose/.meta/Example.roc index b3077876..f57e9976 100644 --- a/exercises/practice/transpose/.meta/Example.roc +++ b/exercises/practice/transpose/.meta/Example.roc @@ -1,22 +1,22 @@ -module [transpose] - -## Transpose the input string. Input string must be ASCII. -transpose : Str -> Str -transpose = |string| - chars = string |> Str.to_utf8 |> List.split_on('\n') - get_char = |row, col| - chars |> List.get(row)? |> List.get(col) - max_width = chars |> List.map(List.len) |> List.max |> Result.with_default(0) - List.range({ start: At(0), end: Before(max_width) }) - |> List.map( - |col| - max_row = - chars - |> List.find_last_index(|row_chars| List.len(row_chars) > col) - |> Result.with_default(0) - List.range({ start: At(0), end: At(max_row) }) - |> List.map(|row| get_char(row, col) |> Result.with_default(' ')) - |> Str.from_utf8 - |> Result.with_default(""), - ) # unreachable because string is ASCII - |> Str.join_with("\n") +Transpose :: {}.{ + ## Transpose the input string. Input string must be ASCII. + transpose : Str -> Str + transpose = |string| + chars = string |> Str.to_utf8 |> List.split_on('\n') + get_char = |row, col| + chars |> List.get(row)? |> List.get(col) + max_width = chars |> List.map(List.len) |> List.max |> Result.with_default(0) + List.range({ start: At(0), end: Before(max_width) }) + |> List.map( + |col| + max_row = + chars + |> List.find_last_index(|row_chars| List.len(row_chars) > col) + |> Result.with_default(0) + List.range({ start: At(0), end: At(max_row) }) + |> List.map(|row| get_char(row, col) |> Result.with_default(' ')) + |> Str.from_utf8 + |> Result.with_default(""), + ) # unreachable because string is ASCII + |> Str.join_with("\n") +} diff --git a/exercises/practice/transpose/Transpose.roc b/exercises/practice/transpose/Transpose.roc index f2a7822f..0e9b8076 100644 --- a/exercises/practice/transpose/Transpose.roc +++ b/exercises/practice/transpose/Transpose.roc @@ -1,5 +1,5 @@ -module [transpose] - -transpose : Str -> Str -transpose = |string| - crash("Please implement the 'transpose' function") +Transpose :: {}.{ + transpose : Str -> Str + transpose = |string| + crash("Please implement the 'transpose' function") +} diff --git a/exercises/practice/triangle/.meta/Example.roc b/exercises/practice/triangle/.meta/Example.roc index 4c063425..2416b447 100644 --- a/exercises/practice/triangle/.meta/Example.roc +++ b/exercises/practice/triangle/.meta/Example.roc @@ -1,19 +1,20 @@ -module [is_equilateral, is_isosceles, is_scalene] +Triangle :: {}.{ + is_equilateral : (F64, F64, F64) -> Bool + is_equilateral = |(a, b, c)| + is_valid_triangle((a, b, c)) and approx_eq(a, b) and approx_eq(b, c) + + is_isosceles : (F64, F64, F64) -> Bool + is_isosceles = |(a, b, c)| + is_valid_triangle((a, b, c)) and (approx_eq(a, b) or approx_eq(b, c) or approx_eq(a, c)) + + is_scalene : (F64, F64, F64) -> Bool + is_scalene = |(a, b, c)| + is_valid_triangle((a, b, c)) and !(is_isosceles((a, b, c))) +} + approx_eq = |x, y| Num.is_approx_eq(x, y, {}) is_valid_triangle = |(a, b, c)| a > 0 and b > 0 and c > 0 and a + b >= c and a + c >= b and b + c >= a - -is_equilateral : (F64, F64, F64) -> Bool -is_equilateral = |(a, b, c)| - is_valid_triangle((a, b, c)) and approx_eq(a, b) and approx_eq(b, c) - -is_isosceles : (F64, F64, F64) -> Bool -is_isosceles = |(a, b, c)| - is_valid_triangle((a, b, c)) and (approx_eq(a, b) or approx_eq(b, c) or approx_eq(a, c)) - -is_scalene : (F64, F64, F64) -> Bool -is_scalene = |(a, b, c)| - is_valid_triangle((a, b, c)) and !(is_isosceles((a, b, c))) diff --git a/exercises/practice/triangle/Triangle.roc b/exercises/practice/triangle/Triangle.roc index 41ebf7b2..247fa127 100644 --- a/exercises/practice/triangle/Triangle.roc +++ b/exercises/practice/triangle/Triangle.roc @@ -1,13 +1,13 @@ -module [is_equilateral, is_isosceles, is_scalene] +Triangle :: {}.{ + is_equilateral : (F64, F64, F64) -> Bool + is_equilateral = |(a, b, c)| + crash("Please implement the 'is_equilateral' function") -is_equilateral : (F64, F64, F64) -> Bool -is_equilateral = |(a, b, c)| - crash("Please implement the 'is_equilateral' function") + is_isosceles : (F64, F64, F64) -> Bool + is_isosceles = |(a, b, c)| + crash("Please implement the 'is_isosceles' function") -is_isosceles : (F64, F64, F64) -> Bool -is_isosceles = |(a, b, c)| - crash("Please implement the 'is_isosceles' function") - -is_scalene : (F64, F64, F64) -> Bool -is_scalene = |(a, b, c)| - crash("Please implement the 'is_scalene' function") + is_scalene : (F64, F64, F64) -> Bool + is_scalene = |(a, b, c)| + crash("Please implement the 'is_scalene' function") +} diff --git a/exercises/practice/two-bucket/.meta/Example.roc b/exercises/practice/two-bucket/.meta/Example.roc index fef0db87..e0819200 100644 --- a/exercises/practice/two-bucket/.meta/Example.roc +++ b/exercises/practice/two-bucket/.meta/Example.roc @@ -1,4 +1,62 @@ -module [measure] +TwoBucket :: {}.{ + measure : + { bucket_one : U64, bucket_two : U64, goal : U64, start_bucket : [One, Two] } + -> + Result { moves : U64, goal_bucket : [One, Two], other_bucket : U64 } [NoSolutionExists] + measure = |{ bucket_one, bucket_two, goal, start_bucket }| + if goal == 0 then + Ok({ moves: 0, goal_bucket: One, other_bucket: 0 }) + else + start = + when start_bucket is + One -> { volume_one: bucket_one, volume_two: 0 } + Two -> { volume_one: 0, volume_two: bucket_two } + + neighbors = |{ volume_one, volume_two }| + volume_one_to_two = Num.min(volume_one, (bucket_two - volume_two)) + volume_two_to_one = Num.min(volume_two, (bucket_one - volume_one)) + [ + { volume_one: 0, volume_two }, # empty bucket one + { volume_one, volume_two: 0 }, # empty bucket two + { volume_one: bucket_one, volume_two }, # fill bucket one + { volume_one, volume_two: bucket_two }, # fill bucket two + { + # pour bucket one into bucket two + volume_one: volume_one - volume_one_to_two, + volume_two: volume_two + volume_one_to_two, + }, + { + # pour bucket two into bucket one + volume_one: volume_one + volume_two_to_one, + volume_two: volume_two - volume_two_to_one, + }, + ] + |> List.drop_if( + |{ volume_one: v1, volume_two: v2 }| + (v1 == volume_one and v2 == volume_two) # no change + or + # forbidden move: cannot end up with the starting bucket empty and + # the other bucket full + when start_bucket is + One -> v1 == 0 and v2 == bucket_two + Two -> v1 == bucket_one and v2 == 0, + ) + + success = |{ volume_one, volume_two }| volume_one == goal or volume_two == goal + + when bfs({ start, neighbors, success }) is + Ok([.. as rest, last]) -> + Ok( + { + moves: List.len(rest) + 1, + goal_bucket: if last.volume_one == goal then One else Two, + other_bucket: if last.volume_one == goal then last.volume_two else last.volume_one, + }, + ) + + _ -> Err(NoSolutionExists) +} + ## Breadth-First Search finds the shortest path from the `start` node to any successful ## node (i.e., such that `success node == Bool.True`). The `neighbors` function must @@ -29,60 +87,3 @@ bfs = |{ start, neighbors, success }| updated_to_visit = rest_to_visit |> List.concat(neighbor_nodes) help(updated_to_visit, updated_visited, updated_from) help([start], Set.empty({}), Dict.empty({})) - -measure : - { bucket_one : U64, bucket_two : U64, goal : U64, start_bucket : [One, Two] } - -> - Result { moves : U64, goal_bucket : [One, Two], other_bucket : U64 } [NoSolutionExists] -measure = |{ bucket_one, bucket_two, goal, start_bucket }| - if goal == 0 then - Ok({ moves: 0, goal_bucket: One, other_bucket: 0 }) - else - start = - when start_bucket is - One -> { volume_one: bucket_one, volume_two: 0 } - Two -> { volume_one: 0, volume_two: bucket_two } - - neighbors = |{ volume_one, volume_two }| - volume_one_to_two = Num.min(volume_one, (bucket_two - volume_two)) - volume_two_to_one = Num.min(volume_two, (bucket_one - volume_one)) - [ - { volume_one: 0, volume_two }, # empty bucket one - { volume_one, volume_two: 0 }, # empty bucket two - { volume_one: bucket_one, volume_two }, # fill bucket one - { volume_one, volume_two: bucket_two }, # fill bucket two - { - # pour bucket one into bucket two - volume_one: volume_one - volume_one_to_two, - volume_two: volume_two + volume_one_to_two, - }, - { - # pour bucket two into bucket one - volume_one: volume_one + volume_two_to_one, - volume_two: volume_two - volume_two_to_one, - }, - ] - |> List.drop_if( - |{ volume_one: v1, volume_two: v2 }| - (v1 == volume_one and v2 == volume_two) # no change - or - # forbidden move: cannot end up with the starting bucket empty and - # the other bucket full - when start_bucket is - One -> v1 == 0 and v2 == bucket_two - Two -> v1 == bucket_one and v2 == 0, - ) - - success = |{ volume_one, volume_two }| volume_one == goal or volume_two == goal - - when bfs({ start, neighbors, success }) is - Ok([.. as rest, last]) -> - Ok( - { - moves: List.len(rest) + 1, - goal_bucket: if last.volume_one == goal then One else Two, - other_bucket: if last.volume_one == goal then last.volume_two else last.volume_one, - }, - ) - - _ -> Err(NoSolutionExists) diff --git a/exercises/practice/two-bucket/TwoBucket.roc b/exercises/practice/two-bucket/TwoBucket.roc index 5e467b1d..edb80dd0 100644 --- a/exercises/practice/two-bucket/TwoBucket.roc +++ b/exercises/practice/two-bucket/TwoBucket.roc @@ -1,8 +1,8 @@ -module [measure] - -measure : - { bucket_one : U64, bucket_two : U64, goal : U64, start_bucket : [One, Two] } - -> - Result { moves : U64, goal_bucket : [One, Two], other_bucket : U64 } _ -measure = |{ bucket_one, bucket_two, goal, start_bucket }| - crash("Please implement the 'measure' function") +TwoBucket :: {}.{ + measure : + { bucket_one : U64, bucket_two : U64, goal : U64, start_bucket : [One, Two] } + -> + Result { moves : U64, goal_bucket : [One, Two], other_bucket : U64 } _ + measure = |{ bucket_one, bucket_two, goal, start_bucket }| + crash("Please implement the 'measure' function") +} diff --git a/exercises/practice/two-fer/.meta/Example.roc b/exercises/practice/two-fer/.meta/Example.roc index a5559fe6..d4183584 100644 --- a/exercises/practice/two-fer/.meta/Example.roc +++ b/exercises/practice/two-fer/.meta/Example.roc @@ -1,7 +1,7 @@ -module [two_fer] - -two_fer : [Name Str, Anonymous] -> Str -two_fer = |name| - when name is - Anonymous -> "One for you, one for me." - Name(n) -> "One for ${n}, one for me." +TwoFer :: {}.{ + two_fer : [Name Str, Anonymous] -> Str + two_fer = |name| + when name is + Anonymous -> "One for you, one for me." + Name(n) -> "One for ${n}, one for me." +} diff --git a/exercises/practice/two-fer/TwoFer.roc b/exercises/practice/two-fer/TwoFer.roc index 2ba99aec..ea2e431d 100644 --- a/exercises/practice/two-fer/TwoFer.roc +++ b/exercises/practice/two-fer/TwoFer.roc @@ -1,5 +1,5 @@ -module [two_fer] - -two_fer : [Name Str, Anonymous] -> Str -two_fer = |name| - crash("Please implement the 'two_fer' function") +TwoFer :: {}.{ + two_fer : [Name Str, Anonymous] -> Str + two_fer = |name| + crash("Please implement the 'two_fer' function") +} diff --git a/exercises/practice/variable-length-quantity/.meta/Example.roc b/exercises/practice/variable-length-quantity/.meta/Example.roc index 7f88477a..01345ac5 100644 --- a/exercises/practice/variable-length-quantity/.meta/Example.roc +++ b/exercises/practice/variable-length-quantity/.meta/Example.roc @@ -1,8 +1,28 @@ -module [encode, decode] +VariableLengthQuantity :: {}.{ + encode : List U32 -> List U8 + encode = |integers| + integers |> List.join_map(encode_integer) -encode : List U32 -> List U8 -encode = |integers| - integers |> List.join_map(encode_integer) + decode : List U8 -> Result (List U32) [IncompleteSequence] + decode = |bytes| + when bytes is + [] -> Err(IncompleteSequence) + [.., last] if last >= 128 -> Err(IncompleteSequence) + _ -> + bytes + |> List.walk( + { integers: [], integer: 0 }, + |state, byte| + last_7_bits = byte % 128 + integer = state.integer * 128 + Num.to_u32(last_7_bits) + if byte >= 128 then + { state & integer } + else + { integers: state.integers |> List.append(integer), integer: 0 }, + ) + |> .integers + |> Ok +} encode_integer : U32 -> List U8 encode_integer = |integer| @@ -20,23 +40,3 @@ encode_integer = |integer| help((bytes |> List.append(byte)), next_n) if integer == 0 then [0] else help([], integer) |> List.reverse - -decode : List U8 -> Result (List U32) [IncompleteSequence] -decode = |bytes| - when bytes is - [] -> Err(IncompleteSequence) - [.., last] if last >= 128 -> Err(IncompleteSequence) - _ -> - bytes - |> List.walk( - { integers: [], integer: 0 }, - |state, byte| - last_7_bits = byte % 128 - integer = state.integer * 128 + Num.to_u32(last_7_bits) - if byte >= 128 then - { state & integer } - else - { integers: state.integers |> List.append(integer), integer: 0 }, - ) - |> .integers - |> Ok diff --git a/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc b/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc index 7b49852b..767a4d24 100644 --- a/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc +++ b/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc @@ -1,9 +1,9 @@ -module [encode, decode] +VariableLengthQuantity :: {}.{ + encode : List U32 -> List U8 + encode = |integers| + crash("Please implement the 'encode' function") -encode : List U32 -> List U8 -encode = |integers| - crash("Please implement the 'encode' function") - -decode : List U8 -> Result (List U32) _ -decode = |bytes| - crash("Please implement the 'decode' function") + decode : List U8 -> Result (List U32) _ + decode = |bytes| + crash("Please implement the 'decode' function") +} diff --git a/exercises/practice/word-count/.meta/Example.roc b/exercises/practice/word-count/.meta/Example.roc index 67691ff6..4c489d07 100644 --- a/exercises/practice/word-count/.meta/Example.roc +++ b/exercises/practice/word-count/.meta/Example.roc @@ -1,47 +1,47 @@ -module [count_words] +WordCount :: {}.{ + count_words : Str -> Dict Str U64 + count_words = |sentence| + sentence + |> Str.to_utf8 + |> List.append(' ') # to ensure the last word is added + |> List.walk( + { words: [], word: [], contraction_started: Bool.False }, + |state, char| + { words, word, contraction_started } = state + when char is + c if c >= 'A' and c <= 'Z' -> + { words, word: word |> List.append((c - 'A' + 'a')), contraction_started: Bool.False } -count_words : Str -> Dict Str U64 -count_words = |sentence| - sentence - |> Str.to_utf8 - |> List.append(' ') # to ensure the last word is added - |> List.walk( - { words: [], word: [], contraction_started: Bool.False }, - |state, char| - { words, word, contraction_started } = state - when char is - c if c >= 'A' and c <= 'Z' -> - { words, word: word |> List.append((c - 'A' + 'a')), contraction_started: Bool.False } + c if c >= 'a' and c <= 'z' or c >= '0' and c <= '9' -> + { words, word: word |> List.append(c), contraction_started: Bool.False } - c if c >= 'a' and c <= 'z' or c >= '0' and c <= '9' -> - { words, word: word |> List.append(c), contraction_started: Bool.False } - - c -> - if List.is_empty(word) then - state - else if c != '\'' or contraction_started then - if contraction_started then - { words: words |> List.append((word |> List.drop_last(1))), word: [], contraction_started: Bool.False } + c -> + if List.is_empty(word) then + state + else if c != '\'' or contraction_started then + if contraction_started then + { words: words |> List.append((word |> List.drop_last(1))), word: [], contraction_started: Bool.False } + else + { words: words |> List.append(word), word: [], contraction_started: Bool.False } else - { words: words |> List.append(word), word: [], contraction_started: Bool.False } - else - { words, word: word |> List.append(c), contraction_started: Bool.True }, - ) - |> .words - |> List.drop_if(List.is_empty) - |> List.walk( - Dict.empty({}), - |result, chars| - word = - when chars |> Str.from_utf8 is - Ok(parsed_word) -> parsed_word - Err(BadUtf8(_)) -> crash("Unreachable: we only use ASCII characters") - result - |> Dict.update( - word, - |maybe_count| - when maybe_count is - Ok(count) -> Ok((count + 1)) - Err(Missing) -> Ok(1), - ), - ) + { words, word: word |> List.append(c), contraction_started: Bool.True }, + ) + |> .words + |> List.drop_if(List.is_empty) + |> List.walk( + Dict.empty({}), + |result, chars| + word = + when chars |> Str.from_utf8 is + Ok(parsed_word) -> parsed_word + Err(BadUtf8(_)) -> crash("Unreachable: we only use ASCII characters") + result + |> Dict.update( + word, + |maybe_count| + when maybe_count is + Ok(count) -> Ok((count + 1)) + Err(Missing) -> Ok(1), + ), + ) +} diff --git a/exercises/practice/word-count/WordCount.roc b/exercises/practice/word-count/WordCount.roc index b8aa1ea4..ef308756 100644 --- a/exercises/practice/word-count/WordCount.roc +++ b/exercises/practice/word-count/WordCount.roc @@ -1,5 +1,5 @@ -module [count_words] - -count_words : Str -> Dict Str U64 -count_words = |sentence| - crash("Please implement the 'count_words' function") +WordCount :: {}.{ + count_words : Str -> Dict Str U64 + count_words = |sentence| + crash("Please implement the 'count_words' function") +} diff --git a/exercises/practice/word-search/.meta/Example.roc b/exercises/practice/word-search/.meta/Example.roc index 54014a4b..d913dd80 100644 --- a/exercises/practice/word-search/.meta/Example.roc +++ b/exercises/practice/word-search/.meta/Example.roc @@ -1,4 +1,54 @@ -module [search] +WordSearch :: {}.{ + search : Str, List Str -> Dict Str WordLocation + search = |grid, words_to_search_for| + { rows, width } = grid |> Str.to_utf8 |> List.split_on('\n') |> pad_right + height = List.len(rows) + height_i64 = height |> Num.to_i64 + width_i64 = width |> Num.to_i64 + max_length = Num.max(width, height) |> Num.to_i64 + # for all possible starting positions: + List.range({ start: At(0), end: Before(width) }) + |> List.join_map( + |column_index| + List.range({ start: At(0), end: Before(height) }) + |> List.join_map( + |row_index| + # for all possible directions: + [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] + |> List.join_map( + |(dir_column, dir_row)| + start = { column: column_index + 1, row: row_index + 1 } + # for all possible lengths: + List.range({ start: At(0), end: Before(max_length) }) # for all possible words starting at the given position and + # going in the given direction, take note of the start and end + # positions + |> List.walk_until( + { found_words: [], chars: [] }, + |state, index| + end_column_index = Num.to_i64(column_index) + dir_column * index + end_row_index = Num.to_i64(row_index) + dir_row * index + if end_column_index < 0 or end_column_index >= width_i64 or end_row_index < 0 or end_row_index >= height_i64 then + Break(state) + else + end_column_index_u64 = end_column_index |> Num.to_u64 + end_row_index_u64 = end_row_index |> Num.to_u64 + char = rows |> get_char(end_column_index_u64, end_row_index_u64) + new_chars = state.chars |> List.append(char) + end = { column: end_column_index_u64 + 1, row: end_row_index_u64 + 1 } + maybe_word = words_to_search_for |> List.find_first(|word| word |> Str.to_utf8 == new_chars) + new_found_words = + when maybe_word is + Ok(word) -> state.found_words |> List.append((word, { start, end })) + Err(NotFound) -> state.found_words + Continue({ found_words: new_found_words, chars: new_chars }), + ) + |> .found_words, + ), + ), + ) + |> Dict.from_list +} + Position : { column : U64, row : U64 } WordLocation : { start : Position, end : Position } @@ -20,52 +70,3 @@ get_char = |grid, column_index, row_index| |> Result.with_default([]) |> List.get(column_index) |> Result.with_default(' ') - -search : Str, List Str -> Dict Str WordLocation -search = |grid, words_to_search_for| - { rows, width } = grid |> Str.to_utf8 |> List.split_on('\n') |> pad_right - height = List.len(rows) - height_i64 = height |> Num.to_i64 - width_i64 = width |> Num.to_i64 - max_length = Num.max(width, height) |> Num.to_i64 - # for all possible starting positions: - List.range({ start: At(0), end: Before(width) }) - |> List.join_map( - |column_index| - List.range({ start: At(0), end: Before(height) }) - |> List.join_map( - |row_index| - # for all possible directions: - [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] - |> List.join_map( - |(dir_column, dir_row)| - start = { column: column_index + 1, row: row_index + 1 } - # for all possible lengths: - List.range({ start: At(0), end: Before(max_length) }) # for all possible words starting at the given position and - # going in the given direction, take note of the start and end - # positions - |> List.walk_until( - { found_words: [], chars: [] }, - |state, index| - end_column_index = Num.to_i64(column_index) + dir_column * index - end_row_index = Num.to_i64(row_index) + dir_row * index - if end_column_index < 0 or end_column_index >= width_i64 or end_row_index < 0 or end_row_index >= height_i64 then - Break(state) - else - end_column_index_u64 = end_column_index |> Num.to_u64 - end_row_index_u64 = end_row_index |> Num.to_u64 - char = rows |> get_char(end_column_index_u64, end_row_index_u64) - new_chars = state.chars |> List.append(char) - end = { column: end_column_index_u64 + 1, row: end_row_index_u64 + 1 } - maybe_word = words_to_search_for |> List.find_first(|word| word |> Str.to_utf8 == new_chars) - new_found_words = - when maybe_word is - Ok(word) -> state.found_words |> List.append((word, { start, end })) - Err(NotFound) -> state.found_words - Continue({ found_words: new_found_words, chars: new_chars }), - ) - |> .found_words, - ), - ), - ) - |> Dict.from_list diff --git a/exercises/practice/word-search/WordSearch.roc b/exercises/practice/word-search/WordSearch.roc index 0d1f45af..6afb2e9a 100644 --- a/exercises/practice/word-search/WordSearch.roc +++ b/exercises/practice/word-search/WordSearch.roc @@ -1,8 +1,9 @@ -module [search] +WordSearch :: {}.{ + search : Str, List Str -> Dict Str WordLocation + search = |grid, words_to_search_for| + crash("Please implement the 'search' function") +} + Position : { column : U64, row : U64 } WordLocation : { start : Position, end : Position } - -search : Str, List Str -> Dict Str WordLocation -search = |grid, words_to_search_for| - crash("Please implement the 'search' function") diff --git a/exercises/practice/wordy/.meta/Example.roc b/exercises/practice/wordy/.meta/Example.roc index b7c06d0f..f832a08c 100644 --- a/exercises/practice/wordy/.meta/Example.roc +++ b/exercises/practice/wordy/.meta/Example.roc @@ -1,4 +1,25 @@ -module [answer] +Wordy :: {}.{ + answer : Str -> Result I64 [QuestionArgHadAnUnknownOperation Str, QuestionArgHadASyntaxError Str] + answer = |question| + words = question |> Str.replace_each("?", " ?") |> Str.split_on(" ") + when words is + ["What", "is", number_string, .. as operations, "?"] -> + maybe_start_number = Str.to_i64(number_string) + when maybe_start_number is + Ok(start_number) -> + when evaluate_expression(start_number, operations) is + Err(OperationsArgHadAnInvalidOperation(_)) -> Err(QuestionArgHadAnUnknownOperation(question)) + Err(OperationsArgHadASyntaxError(_)) -> Err(QuestionArgHadASyntaxError(question)) + Err(InvalidNumStr) -> Err(QuestionArgHadASyntaxError(question)) + Ok(result) -> Ok(result) + + Err(InvalidNumStr) -> Err(QuestionArgHadASyntaxError(question)) + + [_, "is", _, .., "?"] -> Err(QuestionArgHadAnUnknownOperation(question)) + [_, "are", .., "?"] -> Err(QuestionArgHadAnUnknownOperation(question)) + _ -> Err(QuestionArgHadASyntaxError(question)) +} + evaluate_expression = |accumulator, operations| when operations is @@ -17,23 +38,3 @@ evaluate_expression = |accumulator, operations| ["cubed"] -> Err(OperationsArgHadAnInvalidOperation(operations)) _ -> Err(OperationsArgHadASyntaxError(operations)) - -answer : Str -> Result I64 [QuestionArgHadAnUnknownOperation Str, QuestionArgHadASyntaxError Str] -answer = |question| - words = question |> Str.replace_each("?", " ?") |> Str.split_on(" ") - when words is - ["What", "is", number_string, .. as operations, "?"] -> - maybe_start_number = Str.to_i64(number_string) - when maybe_start_number is - Ok(start_number) -> - when evaluate_expression(start_number, operations) is - Err(OperationsArgHadAnInvalidOperation(_)) -> Err(QuestionArgHadAnUnknownOperation(question)) - Err(OperationsArgHadASyntaxError(_)) -> Err(QuestionArgHadASyntaxError(question)) - Err(InvalidNumStr) -> Err(QuestionArgHadASyntaxError(question)) - Ok(result) -> Ok(result) - - Err(InvalidNumStr) -> Err(QuestionArgHadASyntaxError(question)) - - [_, "is", _, .., "?"] -> Err(QuestionArgHadAnUnknownOperation(question)) - [_, "are", .., "?"] -> Err(QuestionArgHadAnUnknownOperation(question)) - _ -> Err(QuestionArgHadASyntaxError(question)) diff --git a/exercises/practice/wordy/Wordy.roc b/exercises/practice/wordy/Wordy.roc index 471c091a..a2ecd985 100644 --- a/exercises/practice/wordy/Wordy.roc +++ b/exercises/practice/wordy/Wordy.roc @@ -1,5 +1,5 @@ -module [answer] - -answer : Str -> Result I64 _ -answer = |question| - crash("Please implement the 'answer' function") +Wordy :: {}.{ + answer : Str -> Result I64 _ + answer = |question| + crash("Please implement the 'answer' function") +} diff --git a/exercises/practice/yacht/.meta/Example.roc b/exercises/practice/yacht/.meta/Example.roc index 45da41b1..d63b5cc3 100644 --- a/exercises/practice/yacht/.meta/Example.roc +++ b/exercises/practice/yacht/.meta/Example.roc @@ -1,4 +1,24 @@ -module [score] +Yacht :: {}.{ + score : List U8, Category -> U8 + score = |dice, category| + if dice |> List.len != 5 or dice |> List.any(|die| die < 1 or die > 6) then + 0 + else + when category is + Ones -> dice |> score_ones_to_sixes(1) + Twos -> dice |> score_ones_to_sixes(2) + Threes -> dice |> score_ones_to_sixes(3) + Fours -> dice |> score_ones_to_sixes(4) + Fives -> dice |> score_ones_to_sixes(5) + Sixes -> dice |> score_ones_to_sixes(6) + FullHouse -> dice |> score_full_house + FourOfAKind -> dice |> score_four_of_a_kind + LittleStraight -> dice |> score_straight([1, 2, 3, 4, 5]) + BigStraight -> dice |> score_straight([2, 3, 4, 5, 6]) + Choice -> dice |> List.sum + Yacht -> if dice |> Set.from_list |> Set.len == 1 then 50 else 0 +} + Category : [Ones, Twos, Threes, Fours, Fives, Sixes, FullHouse, FourOfAKind, LittleStraight, BigStraight, Choice, Yacht] @@ -35,22 +55,3 @@ score_four_of_a_kind = |dice| score_straight : List U8, List U8 -> U8 score_straight = |dice, target| if dice |> List.sort_asc == target then 30 else 0 - -score : List U8, Category -> U8 -score = |dice, category| - if dice |> List.len != 5 or dice |> List.any(|die| die < 1 or die > 6) then - 0 - else - when category is - Ones -> dice |> score_ones_to_sixes(1) - Twos -> dice |> score_ones_to_sixes(2) - Threes -> dice |> score_ones_to_sixes(3) - Fours -> dice |> score_ones_to_sixes(4) - Fives -> dice |> score_ones_to_sixes(5) - Sixes -> dice |> score_ones_to_sixes(6) - FullHouse -> dice |> score_full_house - FourOfAKind -> dice |> score_four_of_a_kind - LittleStraight -> dice |> score_straight([1, 2, 3, 4, 5]) - BigStraight -> dice |> score_straight([2, 3, 4, 5, 6]) - Choice -> dice |> List.sum - Yacht -> if dice |> Set.from_list |> Set.len == 1 then 50 else 0 diff --git a/exercises/practice/yacht/Yacht.roc b/exercises/practice/yacht/Yacht.roc index d38ff702..86711a97 100644 --- a/exercises/practice/yacht/Yacht.roc +++ b/exercises/practice/yacht/Yacht.roc @@ -1,7 +1,8 @@ -module [score] +Yacht :: {}.{ + score : List U8, Category -> U8 + score = |dice, category| + crash("Please implement the 'score' function") +} -Category : [Ones, Twos, Threes, Fours, Fives, Sixes, FullHouse, FourOfAKind, LittleStraight, BigStraight, Choice, Yacht] -score : List U8, Category -> U8 -score = |dice, category| - crash("Please implement the 'score' function") +Category : [Ones, Twos, Threes, Fours, Fives, Sixes, FullHouse, FourOfAKind, LittleStraight, BigStraight, Choice, Yacht] diff --git a/exercises/practice/zebra-puzzle/.meta/Example.roc b/exercises/practice/zebra-puzzle/.meta/Example.roc index 8acf41c8..99a4fc38 100644 --- a/exercises/practice/zebra-puzzle/.meta/Example.roc +++ b/exercises/practice/zebra-puzzle/.meta/Example.roc @@ -1,4 +1,13 @@ -module [owns_zebra, drinks_water] +ZebraPuzzle :: {}.{ + owns_zebra : Result Person [NotFound] + owns_zebra = + owner_of(|house| house.animal == 5) + + drinks_water : Result Person [NotFound] + drinks_water = + owner_of(|house| house.drink == 5) +} + Person : [Englishman, Spaniard, Ukrainian, Norwegian, Japanese] @@ -10,14 +19,6 @@ House : { person : U8, # 0 = Undefined, 1 = Englishman, 2 = Spaniard, 3 = Ukrainian, 4 = Norwegian, 5 = Japanese } -owns_zebra : Result Person [NotFound] -owns_zebra = - owner_of(|house| house.animal == 5) - -drinks_water : Result Person [NotFound] -drinks_water = - owner_of(|house| house.drink == 5) - owner_of : (House -> Bool) -> Result Person [NotFound] owner_of = |condition| when solution is diff --git a/exercises/practice/zebra-puzzle/ZebraPuzzle.roc b/exercises/practice/zebra-puzzle/ZebraPuzzle.roc index 6be56aa5..3b466861 100644 --- a/exercises/practice/zebra-puzzle/ZebraPuzzle.roc +++ b/exercises/practice/zebra-puzzle/ZebraPuzzle.roc @@ -1,11 +1,12 @@ -module [owns_zebra, drinks_water] +ZebraPuzzle :: {}.{ + owns_zebra : Result Person _ + owns_zebra = + crash("Please implement the 'owns_zebra' function") -Person : [Englishman, Spaniard, Ukrainian, Norwegian, Japanese] + drinks_water : Result Person _ + drinks_water = + crash("Please implement the 'drinks_water' function") +} -owns_zebra : Result Person _ -owns_zebra = - crash("Please implement the 'owns_zebra' function") -drinks_water : Result Person _ -drinks_water = - crash("Please implement the 'drinks_water' function") +Person : [Englishman, Spaniard, Ukrainian, Norwegian, Japanese] From 8300bdaada4826505f3e7ccb0ea96b261011487b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 17:53:53 +1200 Subject: [PATCH 028/162] Update exercise to new compiler: bob --- exercises/practice/bob/.meta/Example.roc | 58 +++++++++++++----------- exercises/practice/bob/Bob.roc | 7 +-- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/exercises/practice/bob/.meta/Example.roc b/exercises/practice/bob/.meta/Example.roc index f832c490..274c7773 100644 --- a/exercises/practice/bob/.meta/Example.roc +++ b/exercises/practice/bob/.meta/Example.roc @@ -1,30 +1,36 @@ Bob :: {}.{ - response : Str -> Str - response = |hey_bob| - trimmed = Str.trim(hey_bob) - if trimmed == "" then - "Fine. Be that way!" - else - is_q = is_question(trimmed) - is_y = is_yelling(trimmed) - if is_q and is_y then - "Calm down, I know what I'm doing!" - else if is_q then - "Sure." - else if is_y then - "Whoa, chill out!" - else - "Whatever." + response : Str -> Str + response = |hey_bob| { + trimmed = hey_bob.trim() + if trimmed == "" { + "Fine. Be that way!" + } else { + is_q = is_question(trimmed) + is_y = is_yelling(trimmed) + if is_q and is_y { + "Calm down, I know what I'm doing!" + } else if is_q { + "Sure." + } else if is_y { + "Whoa, chill out!" + } else { + "Whatever." + } + } + } } +is_question = |hey_bob| { + hey_bob.ends_with("?") +} -is_question = |hey_bob| - Str.ends_with(hey_bob, "?") - -is_yelling = |hey_bob| - is_lower = |c| - c >= 'a' and c <= 'z' - is_upper = |c| - c >= 'A' and c <= 'Z' - chars = Str.to_utf8(hey_bob) - (chars |> List.any(is_upper)) and !(chars |> List.any(is_lower)) +is_yelling = |hey_bob| { + is_lower = |c| { + c >= 'a' and c <= 'z' + } + is_upper = |c| { + c >= 'A' and c <= 'Z' + } + chars = hey_bob.to_utf8() + (chars.any(is_upper)) and !(chars.any(is_lower)) +} diff --git a/exercises/practice/bob/Bob.roc b/exercises/practice/bob/Bob.roc index 79c1184f..2ea44ca1 100644 --- a/exercises/practice/bob/Bob.roc +++ b/exercises/practice/bob/Bob.roc @@ -1,5 +1,6 @@ Bob :: {}.{ - response : Str -> Str - response = |hey_bob| - crash("Please implement the 'response' function") + response : Str -> Str + response = |hey_bob| { + crash "Please implement the 'response' function" + } } From 6043ce53d29941e04e2e7710c6aa253592aa8e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 19:33:58 +1200 Subject: [PATCH 029/162] Update exercise to new compiler: bowling --- exercises/practice/bowling/.meta/Example.roc | 300 +++++++++++-------- exercises/practice/bowling/.meta/template.j2 | 26 +- exercises/practice/bowling/Bowling.roc | 38 +-- exercises/practice/bowling/bowling-test.roc | 257 ++++++++-------- 4 files changed, 339 insertions(+), 282 deletions(-) diff --git a/exercises/practice/bowling/.meta/Example.roc b/exercises/practice/bowling/.meta/Example.roc index 85228ab2..6330552e 100644 --- a/exercises/practice/bowling/.meta/Example.roc +++ b/exercises/practice/bowling/.meta/Example.roc @@ -1,127 +1,193 @@ -Bowling :: {}.{ - create : { previous_rolls ?? List U64 } -> Result Game [MoreThan10Pins, GameOver] - create = |{ previous_rolls ?? [] }| - List.walk_try( - previous_rolls, - @Game({ frames: [] }), - |game, pins| - roll(game, pins), - ) +Bowling :: { frames : List(Frame) }.{ + new : Bowling + new = { frames: [] } + + roll : Bowling, U64 -> Try(Bowling, [MoreThan10Pins, GameOver]) + roll = |game, pins| { + if game.is_over() { + Err(GameOver) + } else { + { frames } = game + last_frame = frames.last() ?? Ball2(0, 0) + check_max_10_pins(last_frame, pins)? + updated_frames = { + match last_frame { + Ball1(pins1) => { + frames + .drop_last( + 1, + ) + .append( + if pins1 + pins == 10 { + Spare(pins1, pins) + } else { + Ball2(pins1, pins) + }, + ) + } + + StrikeFill1(pins1) => { + frames.drop_last(1).append(StrikeFill2(pins1, pins)) + } + + Ball2(_, _) | Spare(_, _) | Strike if frames.len() < 10 => { + if pins == 10 { + frames.append(Strike) + } else { + frames.append(Ball1(pins)) + } + } + + Spare(_, _) => { + frames.append(SpareFill(pins)) + } + + Strike => { + frames.append(StrikeFill1(pins)) + } + + Ball2(_, _) | SpareFill(_) | StrikeFill2(_, _) => { + crash "Impossible, an unfinished game cannot have these in the last frame after the 10th frame" + } + } + } + Ok({ frames: updated_frames }) + } + } + + score : Bowling -> Try(U64, [GameIsNotOver]) + score = |game| { + if game.is_over() { + { frames } = game + points = { + frames->map_triplets( + |frame1, frame2, frame3| { + match frame1 { + Ball2(pins1, pins2) => { + pins1 + pins2 + } + Spare(pins1, pins2) => { + pins1 + pins2 + first_pins(frame2) + } + Strike => { + match frame2 { + Strike => { + 10 + 10 + first_pins(frame3) + } + _ => { + 10 + total_pins(frame2) + } + } + } + SpareFill(_) => 0 # already counted in the Spare + StrikeFill2(_, _) => 0 # already counter in the Strike + Ball1(_) | StrikeFill1(_) => { + crash "Impossible, unfinished frames should not exist in a finished game" + } + } + }, + ) + } + Ok(points.sum()) + } + else { + Err(GameIsNotOver) + } + } + + is_over : Bowling -> Bool + is_over = |game| { + { frames } = game + match frames { + _ if frames.len() < 10 => Bool.False + [.., Ball1(_)] | [.., Spare(_, _)] | [.., Strike] | [.., StrikeFill1(_)] => Bool.False + [.., Ball2(_, _)] | [.., SpareFill(_)] | [.., StrikeFill2(_, _)] => Bool.True + _ => Bool.False + } + } - roll : Game, U64 -> Result Game [MoreThan10Pins, GameOver] - roll = |@Game({ frames }), pins| - if @Game({ frames }) |> is_over then - Err(GameOver) - else - last_frame = frames |> List.last |> Result.with_default(Ball2(0, 0)) - check_max_10_pins(last_frame, pins)? - updated_frames = - when last_frame is - Ball1(pins1) -> - frames - |> List.drop_last(1) - |> List.append( - ( - if pins1 + pins == 10 then - Spare(pins1, pins) - else - Ball2(pins1, pins) - ), - ) - - StrikeFill1(pins1) -> - frames |> List.drop_last(1) |> List.append(StrikeFill2(pins1, pins)) - - Ball2(_, _) | Spare(_, _) | Strike if List.len(frames) < 10 -> - if pins == 10 then - frames |> List.append(Strike) - else - frames |> List.append(Ball1(pins)) - - Spare(_, _) -> - frames |> List.append(SpareFill(pins)) - - Strike -> - frames |> List.append(StrikeFill1(pins)) - - Ball2(_, _) | SpareFill(_) | StrikeFill2(_, _) -> - crash("Impossible, an unfinished game cannot have these in the last frame after the 10th frame") - @Game({ frames: updated_frames }) |> Ok - - score : Game -> Result U64 [GameIsNotOver] - score = |@Game({ frames })| - if @Game({ frames }) |> is_over then - frames - |> map_triplets( - |frame1, frame2, frame3| - when frame1 is - Ball2(pins1, pins2) -> pins1 + pins2 - Spare(pins1, pins2) -> pins1 + pins2 + first_pins(frame2) - Strike -> - when frame2 is - Strike -> 10 + 10 + first_pins(frame3) - _ -> 10 + total_pins(frame2) - - SpareFill(_) -> 0 # already counted in the Spare - StrikeFill2(_, _) -> 0 # already counter in the Strike - Ball1(_) | StrikeFill1(_) -> - crash("Impossible, unfinished frames should not exist in a finished game"), - ) - |> List.sum - |> Ok - else - Err(GameIsNotOver) } - -Frame : [ - Ball1 U64, # unfinished frame - Ball2 U64 U64, - Spare U64 U64, - Strike, - SpareFill U64, - StrikeFill1 U64, # unfinished frame - StrikeFill2 U64 U64, +Frame := [ + Ball1(U64), # unfinished frame + Ball2(U64, U64), + Spare(U64, U64), + Strike, + SpareFill(U64), + StrikeFill1(U64), # unfinished frame + StrikeFill2(U64, U64), ] -Game := { frames : List Frame } - -check_max_10_pins : Frame, U64 -> Result {} [MoreThan10Pins, GameOver] -check_max_10_pins = |last_frame, pins| - when last_frame is - Ball1(pins1) -> if pins1 + pins > 10 then Err(MoreThan10Pins) else Ok({}) - StrikeFill1(pins1) -> - if pins > 10 or (pins1 < 10 and pins1 + pins > 10) then Err(MoreThan10Pins) else Ok({}) - - Ball2(_, _) | Spare(_, _) | Strike -> if pins > 10 then Err(MoreThan10Pins) else Ok({}) - SpareFill(_) | StrikeFill2(_, _) -> Err(GameOver) - -is_over : Game -> Bool -is_over = |@Game({ frames })| - when frames is - _ if List.len(frames) < 10 -> Bool.False - [.., Ball1(_)] | [.., Spare(_, _)] | [.., Strike] | [.., StrikeFill1(_)] -> Bool.False - [.., Ball2(_, _)] | [.., SpareFill(_)] | [.., StrikeFill2(_, _)] -> Bool.True - _ -> Bool.False +check_max_10_pins : Frame, U64 -> Try({}, [MoreThan10Pins, GameOver]) +check_max_10_pins = |last_frame, pins| { + match last_frame { + Ball1(pins1) => if pins1 + pins > 10 { + Err(MoreThan10Pins) + } else { + Ok({}) + } + StrikeFill1(pins1) => { + if pins > 10 or (pins1 < 10 and pins1 + pins > 10) { + Err(MoreThan10Pins) + } else { + Ok({}) + } + } + Ball2(_, _) | Spare(_, _) | Strike => if pins > 10 { + Err(MoreThan10Pins) + } else { + Ok({}) + } + SpareFill(_) | StrikeFill2(_, _) => Err(GameOver) + } +} -map_triplets : List Frame, (Frame, Frame, Frame -> U64) -> List U64 -map_triplets = |list, score_func| - get_or_0 = |index| list |> List.get(index) |> Result.with_default(Ball2(0, 0)) - List.range({ start: At(0), end: Before(List.len(list)) }) - |> List.map( - |index| - score_func(get_or_0(index), get_or_0((index + 1)), get_or_0((index + 2))), - ) +map_triplets : List(Frame), (Frame, Frame, Frame -> U64) -> List(U64) +map_triplets = |list, score_func| { + get_or_0 = |index| { + list.get(index) ?? Ball2(0, 0) + } + 0.to(list.len()).map( + |index| { + score_func(get_or_0(index), get_or_0((index + 1)), get_or_0((index + 2))) + }, + )->List.from_iter() +} first_pins : Frame -> U64 -first_pins = |frame| - when frame is - Ball1(pins) | Ball2(pins, _) | Spare(pins, _) | SpareFill(pins) | StrikeFill1(pins) | StrikeFill2(pins, _) -> pins - Strike -> 10 +first_pins = |frame| { + match frame { + Ball1(pins) | Ball2(pins, _) | Spare(pins, _) | SpareFill(pins) | StrikeFill1(pins) | StrikeFill2(pins, _) => pins + Strike => 10 + } +} total_pins : Frame -> U64 -total_pins = |frame| - when frame is - Ball2(pins1, pins2) | Spare(pins1, pins2) | StrikeFill2(pins1, pins2) -> pins1 + pins2 - Ball1(pins) | SpareFill(pins) | StrikeFill1(pins) -> pins - Strike -> 10 +total_pins = |frame| { + match frame { + Ball2(pins1, pins2) | Spare(pins1, pins2) | StrikeFill2(pins1, pins2) => pins1 + pins2 + Ball1(pins) | SpareFill(pins) | StrikeFill1(pins) => pins + Strike => 10 + } +} + +# The following function should soon be available in Roc's builtins +fold_try : List(a), b, (b, a -> Try(b, err)) -> Try(b, err) +fold_try = |list, init, func| { + list.fold_until( + Ok(init), + |state, item| { + match state { + Ok(internal_state) => { + match func(internal_state, item) { + Ok(new_state) => Continue(Ok(new_state)) + Err(final_err) => Break(Err(final_err)) + } + } + Err(_) => { + crash "Unreachable" + } + } + }, + ) +} diff --git a/exercises/practice/bowling/.meta/template.j2 b/exercises/practice/bowling/.meta/template.j2 index c6a91368..6844fbec 100644 --- a/exercises/practice/bowling/.meta/template.j2 +++ b/exercises/practice/bowling/.meta/template.j2 @@ -2,23 +2,22 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [Game, create, roll, score] +import {{ exercise | to_pascal }} -replay_game : List(U64) -> Try(Game, _) -replay_game = |rolls| { - new_game = create({})? - rolls - -> walk_until( +game_with_previous_rolls : List(U64) -> Try(Bowling, _) +game_with_previous_rolls = |rolls| { + new_game = {{ exercise | to_pascal }}.new + rolls.fold_until( Ok(new_game), |state, pins| { match state { Ok(game) => { - match game -> roll(pins) { + match game.roll(pins) { Ok(updated_game) => Continue(Ok(updated_game)) Err(err) => Break(Err(err)) } } - Err(_) => { crash "Impossible, we don't start or Continue with an Err" } + Err(_) => { crash "Impossible, we don't start or continue with an Err" } } } ) @@ -28,13 +27,12 @@ replay_game = |rolls| { {% for case in cases -%} # {{ case["description"] }} expect { - maybe_game = create({ previous_rolls : {{ case["input"]["previousRolls"] | to_roc }} }) + rolls = {{ case["input"]["previousRolls"] | to_roc }} + game = game_with_previous_rolls(rolls)? {%- if case["property"] == "score" %} - result = maybe_game -> Result.try(|game| score(game)) + result = game.score() {%- else %} - result = maybe_game -> Result.try(|game| { - game -> {{ case["property"] | to_snake }}({{ case["input"]["roll"] }}) - }) + result = game.{{ case["property"] | to_snake }}({{ case["input"]["roll"] }}) {%- endif %} {%- if case["expected"]["error"] %} result.is_err() @@ -47,7 +45,7 @@ expect { # should be able to replay this finished game from the start expect { rolls = {{ case["input"]["previousRolls"] | to_roc }} - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } diff --git a/exercises/practice/bowling/Bowling.roc b/exercises/practice/bowling/Bowling.roc index 3dc0ee45..8a75ff57 100644 --- a/exercises/practice/bowling/Bowling.roc +++ b/exercises/practice/bowling/Bowling.roc @@ -1,22 +1,22 @@ -Bowling :: {}.{ - create : { previous_rolls ?? List U64 } -> Result Game _ - create = |{ previous_rolls ?? [] }| - crash("Please implement the 'create' function") +Bowling :: { + # TODO: change this opaque type however you need + todo1 : U64, + todo2 : U64, + todo3 : U64, + # etc. +}.{ + new : Bowling + new = { + crash "Please implement the 'new' constant" + } - roll : Game, U64 -> Result Game _ - roll = |game, pins| - crash("Please implement the 'roll' function") + roll : Bowling, U64 -> Try(Bowling, _) + roll = |game, pins| { + crash "Please implement the 'roll' function" + } - score : Game -> Result U64 _ - score = |finished_game| - crash("Please implement the 'score' function") -} - - -Game := { - # TODO: change this opaque type however you need - todo : U64, - todo2 : U64, - todo3 : U64, - # etc. + score : Bowling -> Try(U64, _) + score = |game| { + crash "Please implement the 'score' function" + } } diff --git a/exercises/practice/bowling/bowling-test.roc b/exercises/practice/bowling/bowling-test.roc index b6f345a0..7a1744d9 100644 --- a/exercises/practice/bowling/bowling-test.roc +++ b/exercises/practice/bowling/bowling-test.roc @@ -2,385 +2,378 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/bowling/canonical-data.json # File last updated on 2026-06-13 -import Bowling exposing [Game, create, roll, score] - -replay_game : List(U64) -> Try(Game, _) -replay_game = |rolls| { - new_game = create({})? - rolls - ->walk_until( - Ok(new_game), - |state, pins| { - match state { - Ok(game) => { - match game->roll(pins) { - Ok(updated_game) => Continue(Ok(updated_game)) - Err(err) => Break(Err(err)) - } - } - Err(_) => { - crash "Impossible, we don't start or Continue with an Err" +import Bowling + +game_with_previous_rolls : List(U64) -> Try(Bowling, _) +game_with_previous_rolls = |rolls| { + new_game = Bowling.new + rolls.fold_until( + Ok(new_game), + |state, pins| { + match state { + Ok(game) => { + match game.roll(pins) { + Ok(updated_game) => Continue(Ok(updated_game)) + Err(err) => Break(Err(err)) } } - }, - ) + Err(_) => { + crash "Impossible, we don't start or continue with an Err" + } + } + }, + ) } # should be able to score a game with all zeros expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(0) } # should be able to replay this finished game from the start expect { rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # should be able to score a game with no strikes or spares expect { - maybe_game = create({ previous_rolls: [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(90) } # should be able to replay this finished game from the start expect { rolls = [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # a spare followed by zeros is worth ten points expect { - maybe_game = create({ previous_rolls: [6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(10) } # should be able to replay this finished game from the start expect { rolls = [6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # points scored in the roll after a spare are counted twice expect { - maybe_game = create({ previous_rolls: [6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(16) } # should be able to replay this finished game from the start expect { rolls = [6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # consecutive spares each get a one roll bonus expect { - maybe_game = create({ previous_rolls: [5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(31) } # should be able to replay this finished game from the start expect { rolls = [5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # a spare in the last frame gets a one roll bonus that is counted once expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(17) } # should be able to replay this finished game from the start expect { rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # a strike earns ten points in a frame with a single roll expect { - maybe_game = create({ previous_rolls: [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(10) } # should be able to replay this finished game from the start expect { rolls = [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # points scored in the two rolls after a strike are counted twice as a bonus expect { - maybe_game = create({ previous_rolls: [10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(26) } # should be able to replay this finished game from the start expect { rolls = [10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # consecutive strikes each get the two roll bonus expect { - maybe_game = create({ previous_rolls: [10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(81) } # should be able to replay this finished game from the start expect { rolls = [10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # a strike in the last frame gets a two roll bonus that is counted once expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(18) } # should be able to replay this finished game from the start expect { rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # rolling a spare with the two roll bonus does not get a bonus roll expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(20) } # should be able to replay this finished game from the start expect { rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # strikes with the two roll bonus do not get bonus rolls expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(30) } # should be able to replay this finished game from the start expect { rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # last two strikes followed by only last bonus with non strike points expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(31) } # should be able to replay this finished game from the start expect { rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # a strike with the one roll bonus after a spare in the last frame does not get a bonus expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(20) } # should be able to replay this finished game from the start expect { rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # all strikes is a perfect game expect { - maybe_game = create({ previous_rolls: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(300) } # should be able to replay this finished game from the start expect { rolls = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # a roll cannot score more than 10 points expect { - maybe_game = create({ previous_rolls: [] }) - result = maybe_game->Result.try( - |game| { - game->roll(11) - }, - ) + rolls = [] + game = game_with_previous_rolls(rolls)? + result = game.roll(11) result.is_err() } # two rolls in a frame cannot score more than 10 points expect { - maybe_game = create({ previous_rolls: [5] }) - result = maybe_game->Result.try( - |game| { - game->roll(6) - }, - ) + rolls = [5] + game = game_with_previous_rolls(rolls)? + result = game.roll(6) result.is_err() } # bonus roll after a strike in the last frame cannot score more than 10 points expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] }) - result = maybe_game->Result.try( - |game| { - game->roll(11) - }, - ) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] + game = game_with_previous_rolls(rolls)? + result = game.roll(11) result.is_err() } # two bonus rolls after a strike in the last frame cannot score more than 10 points expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 5] }) - result = maybe_game->Result.try( - |game| { - game->roll(6) - }, - ) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 5] + game = game_with_previous_rolls(rolls)? + result = game.roll(6) result.is_err() } # two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 6] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 6] + game = game_with_previous_rolls(rolls)? + result = game.score() result == Ok(26) } # should be able to replay this finished game from the start expect { rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 6] - result = replay_game(rolls) + result = game_with_previous_rolls(rolls) result.is_ok() } # the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 6] }) - result = maybe_game->Result.try( - |game| { - game->roll(10) - }, - ) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 6] + game = game_with_previous_rolls(rolls)? + result = game.roll(10) result.is_err() } # second bonus roll after a strike in the last frame cannot score more than 10 points expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10] }) - result = maybe_game->Result.try( - |game| { - game->roll(11) - }, - ) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10] + game = game_with_previous_rolls(rolls)? + result = game.roll(11) result.is_err() } # an unstarted game cannot be scored expect { - maybe_game = create({ previous_rolls: [] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [] + game = game_with_previous_rolls(rolls)? + result = game.score() result.is_err() } # an incomplete game cannot be scored expect { - maybe_game = create({ previous_rolls: [0, 0] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0] + game = game_with_previous_rolls(rolls)? + result = game.score() result.is_err() } # cannot roll if game already has ten frames expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) - result = maybe_game->Result.try( - |game| { - game->roll(0) - }, - ) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + game = game_with_previous_rolls(rolls)? + result = game.roll(0) result.is_err() } # bonus rolls for a strike in the last frame must be rolled before score can be calculated expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10] + game = game_with_previous_rolls(rolls)? + result = game.score() result.is_err() } # both bonus rolls for a strike in the last frame must be rolled before score can be calculated expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10] + game = game_with_previous_rolls(rolls)? + result = game.score() result.is_err() } # bonus roll for a spare in the last frame must be rolled before score can be calculated expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3] }) - result = maybe_game->Result.try(|game| score(game)) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3] + game = game_with_previous_rolls(rolls)? + result = game.score() result.is_err() } # cannot roll after bonus roll for spare expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 2] }) - result = maybe_game->Result.try( - |game| { - game->roll(2) - }, - ) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 2] + game = game_with_previous_rolls(rolls)? + result = game.roll(2) result.is_err() } # cannot roll after bonus rolls for strike expect { - maybe_game = create({ previous_rolls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 3, 2] }) - result = maybe_game->Result.try( - |game| { - game->roll(2) - }, - ) + rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 3, 2] + game = game_with_previous_rolls(rolls)? + result = game.roll(2) result.is_err() } From 4e89774da49395eb33aec5d43a1873cb7ca7220e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 19:50:21 +1200 Subject: [PATCH 030/162] Update exercise to new compiler: change --- exercises/practice/change/.meta/Example.roc | 87 +++++++++++++++------ exercises/practice/change/Change.roc | 7 +- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/exercises/practice/change/.meta/Example.roc b/exercises/practice/change/.meta/Example.roc index e0c933af..f1632b37 100644 --- a/exercises/practice/change/.meta/Example.roc +++ b/exercises/practice/change/.meta/Example.roc @@ -1,30 +1,65 @@ Change :: {}.{ - find_fewest_coins : List U64, U64 -> Result (List U64) [NotFound] - find_fewest_coins = |coins, target| - help = |sorted_coins, sub_target, max_length| - if sub_target == 0 then - Ok([]) - else if max_length == 0 then - Err(NotFound) - else - when sorted_coins is - [] -> Err(NotFound) - [largest_coin, .. as other_coins] -> - if largest_coin == sub_target then - Ok([largest_coin]) - else if largest_coin < sub_target then - when help(sorted_coins, (sub_target - largest_coin), (max_length - 1)) is - Ok(other_coins_with) -> - coins_with = other_coins_with |> List.append(largest_coin) - when help(other_coins, sub_target, (List.len(coins_with) - 1)) is - Ok(coins_without) -> Ok(coins_without) - Err(NotFound) -> Ok(coins_with) + find_fewest_coins : List(U64), U64 -> Try(List(U64), [NotFound]) + find_fewest_coins = |coins, target| { + help = |sorted_coins, sub_target, max_length| { + if sub_target == 0 { + Ok([]) + } else if max_length == 0 { + Err(NotFound) + } else { + match sorted_coins { + [] => Err(NotFound) + [largest_coin, .. as other_coins] => { + if largest_coin == sub_target { + Ok([largest_coin]) + } else if largest_coin < sub_target { + match help(sorted_coins, (sub_target - largest_coin), (max_length - 1)) { + Ok(other_coins_with) => { + coins_with = other_coins_with.append(largest_coin) + match help(other_coins, sub_target, (coins_with.len() - 1)) { + Ok(coins_without) => Ok(coins_without) + Err(NotFound) => Ok(coins_with) + } + } + Err(NotFound) => help(other_coins, sub_target, max_length) + } + } else { + help(other_coins, sub_target, max_length) + } + } + } + } + } - Err(NotFound) -> help(other_coins, sub_target, max_length) - else - help(other_coins, sub_target, max_length) + help(coins->sort_desc(), target, max_u64)? + } + ->sort_asc() + ->Ok() +} + +# The following functions should soon be available in Roc's builtins +sort_asc = |list| { + list.sort_with( + |a, b| if a < b { + LT + } else if a > b { + GT + } else { + EQ + }, + ) +} - help((coins |> List.sort_desc), target, Num.max_u64)? - |> List.sort_asc - |> Ok +sort_desc = |list| { + list.sort_with( + |a, b| if a < b { + GT + } else if a > b { + LT + } else { + EQ + }, + ) } + +max_u64 = 18_446_744_073_709_551_615 diff --git a/exercises/practice/change/Change.roc b/exercises/practice/change/Change.roc index d8480373..dfaf4dca 100644 --- a/exercises/practice/change/Change.roc +++ b/exercises/practice/change/Change.roc @@ -1,5 +1,6 @@ Change :: {}.{ - find_fewest_coins : List U64, U64 -> Result (List U64) _ - find_fewest_coins = |coins, target| - crash("Please implement the 'find_fewest_coins' function") + find_fewest_coins : List(U64), U64 -> Try(List(U64), _) + find_fewest_coins = |coins, target| { + crash "Please implement the 'find_fewest_coins' function" + } } From 99d212ab7498c14e1248d78b03a14553f316cbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 21:39:49 +1200 Subject: [PATCH 031/162] Update exercise to new compiler: clock --- exercises/practice/clock/.meta/Example.roc | 56 +++--- exercises/practice/clock/.meta/plugins.py | 17 +- exercises/practice/clock/.meta/template.j2 | 10 +- exercises/practice/clock/Clock.roc | 34 ++-- exercises/practice/clock/clock-test.roc | 188 ++++++++++----------- 5 files changed, 150 insertions(+), 155 deletions(-) diff --git a/exercises/practice/clock/.meta/Example.roc b/exercises/practice/clock/.meta/Example.roc index 47c7bd1b..e16c50ce 100644 --- a/exercises/practice/clock/.meta/Example.roc +++ b/exercises/practice/clock/.meta/Example.roc @@ -1,30 +1,34 @@ -Clock :: {}.{ - create : { hours ?? I64, minutes ?? I64 }* -> Clock - create = |{ hours ?? 0, minutes ?? 0 }| - hours24 = (hours % 24 + minutes // 60) % 24 - minutes60 = minutes % 60 - total_minutes = ((hours24 * 60 + minutes60) % minutes_per_day + minutes_per_day) % minutes_per_day - hh = total_minutes // 60 |> Num.to_u8 - mm = total_minutes % 60 |> Num.to_u8 - { hour: hh, minute: mm } +Clock :: { hour : U8, minute : U8 }.{ + create : { hour : I64, minute : I64 } -> Clock + create = |{ hour, minute }| { + hour24 = (hour % 24 + minute // 60) % 24 + minute60 = minute % 60 + minutes_per_day = 24 * 60 + total_minute = ((hour24 * 60 + minute60) % minutes_per_day + minutes_per_day) % minutes_per_day + hh = (total_minute // 60).to_u8_try() ?? { + crash "Unreachable" + } + mm = (total_minute % 60).to_u8_try() ?? { + crash "Unreachable" + } + { hour: hh, minute: mm } + } - to_str : Clock -> Str - to_str = |{ hour, minute }| - zero_padded = |num| "${if num < 10 then "0" else ""}${num |> Num.to_str}" - "${zero_padded(hour)}:${zero_padded(minute)}" + to_str : Clock -> Str + to_str = |{ hour, minute }| { + zero_padded = |num| "${if num < 10 "0" else ""}${num.to_str()}" + "${zero_padded(hour)}:${zero_padded(minute)}" + } - add : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock - add = |{ hour, minute }, { hours ?? 0, minutes ?? 0 }| - total_hours = Num.to_i64(hour) + (hours % 24 + minutes // 60) - total_minutes = Num.to_i64(minute) + minutes % 60 - create({ hours: total_hours, minutes: total_minutes }) + add : Clock, { hour : I64, minute : I64 } -> Clock + add = |clock, { hour, minute }| { + total_hour = clock.hour.to_i64() + (hour % 24 + minute // 60) + total_minute = clock.minute.to_i64() + minute % 60 + create({ hour: total_hour, minute: total_minute }) + } - subtract : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock - subtract = |clock, { hours ?? 0, minutes ?? 0 }| - clock |> add({ hours: -(hours % 24 + minutes // 60), minutes: -(minutes % 60) }) + subtract : Clock, { hour : I64, minute : I64 } -> Clock + subtract = |clock, { hour, minute }| { + clock.add({ hour: -(hour % 24 + minute // 60), minute: -(minute % 60) }) + } } - - -Clock : { hour : U8, minute : U8 } - -minutes_per_day = 24 * 60 diff --git a/exercises/practice/clock/.meta/plugins.py b/exercises/practice/clock/.meta/plugins.py index 6be898e6..ff8f8f05 100644 --- a/exercises/practice/clock/.meta/plugins.py +++ b/exercises/practice/clock/.meta/plugins.py @@ -1,13 +1,4 @@ -def to_hours_minutes_record(input): - hours = input.get("hour", 0) - minutes = input.get("minute", 0) - fields = [] - if hours != 0: - fields.append(f"hours: {hours}") - if minutes != 0: - fields.append(f"minutes: {minutes}") - if fields: - content = ", ".join(fields) - return "{ " + content + " }" - else: - return "{}" +def to_hour_minute_record(input): + hour = input.get("hour", 0) + minute = input.get("minute", 0) + return f'{{hour: {hour}, minute: {minute}}}' \ No newline at end of file diff --git a/exercises/practice/clock/.meta/template.j2 b/exercises/practice/clock/.meta/template.j2 index 130a425b..90b02c56 100644 --- a/exercises/practice/clock/.meta/template.j2 +++ b/exercises/practice/clock/.meta/template.j2 @@ -9,22 +9,22 @@ import {{ exercise | to_pascal }} exposing [create, add, subtract, to_str] {%- if case["property"] == "create" %} expect { - clock = create({{ plugins.to_hours_minutes_record(case["input"]) }}) + clock = create({{ plugins.to_hour_minute_record(case["input"]) }}) result = clock.to_str() expected = {{ case["expected"] | to_roc }} result == expected } {%- elif case["property"] in ["add", "subtract"] %} expect { - clock = create({{ plugins.to_hours_minutes_record(case["input"]) }}) - result = clock.{{ case["property"] | to_snake }}({ minutes: {{ case["input"]["value"] }} }).to_str() + clock = create({{ plugins.to_hour_minute_record(case["input"]) }}) + result = clock.{{ case["property"] | to_snake }}({ hour: 0, minute: {{ case["input"]["value"] }} }).to_str() expected = {{ case["expected"] | to_roc }} result == expected } {%- elif case["property"] == "equal" %} expect { - clock1 = create({{ plugins.to_hours_minutes_record(case["input"]["clock1"]) }}) - clock2 = create({{ plugins.to_hours_minutes_record(case["input"]["clock2"]) }}) + clock1 = create({{ plugins.to_hour_minute_record(case["input"]["clock1"]) }}) + clock2 = create({{ plugins.to_hour_minute_record(case["input"]["clock2"]) }}) clock1 {%- if case["expected"] %} == {%- else %} != {% endif %} clock2 } diff --git a/exercises/practice/clock/Clock.roc b/exercises/practice/clock/Clock.roc index 200e2ba4..83255724 100644 --- a/exercises/practice/clock/Clock.roc +++ b/exercises/practice/clock/Clock.roc @@ -1,21 +1,21 @@ -Clock :: {}.{ - create : { hours ?? I64, minutes ?? I64 }* -> Clock - create = |{ hours ?? 0, minutes ?? 0 }| - crash("Please implement the 'create' function") +Clock :: { hour : U8, minute : U8 }.{ + create : { hour : I64, minute : I64 } -> Clock + create = |{ hour, minute }| { + crash "Please implement the 'create' function" + } - to_str : Clock -> Str - to_str = |{ hour, minute }| - crash("Please implement the 'to_str' function") + to_str : Clock -> Str + to_str = |clock| { + crash "Please implement the 'to_str' function" + } - add : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock - add = |{ hour, minute }, { hours ?? 0, minutes ?? 0 }| - crash("Please implement the 'add' function") - - subtract : Clock, { hours ?? I64, minutes ?? I64 }* -> Clock - subtract = |clock, { hours ?? 0, minutes ?? 0 }| - crash("Please implement the 'subtract' function") + add : Clock, { hour : I64, minute : I64 } -> Clock + add = |clock, { hour, minute }| { + crash "Please implement the 'add' function" + } + subtract : Clock, { hour : I64, minute : I64 } -> Clock + subtract = |clock, { hour, minute }| { + crash "Please implement the 'subtract' function" + } } - - -Clock : { hour : U8, minute : U8 } diff --git a/exercises/practice/clock/clock-test.roc b/exercises/practice/clock/clock-test.roc index 558f9af4..08eab107 100644 --- a/exercises/practice/clock/clock-test.roc +++ b/exercises/practice/clock/clock-test.roc @@ -10,7 +10,7 @@ import Clock exposing [create, add, subtract, to_str] # on the hour expect { - clock = create({ hours: 8 }) + clock = create({ hour: 8, minute: 0 }) result = clock.to_str() expected = "08:00" result == expected @@ -18,7 +18,7 @@ expect { # past the hour expect { - clock = create({ hours: 11, minutes: 9 }) + clock = create({ hour: 11, minute: 9 }) result = clock.to_str() expected = "11:09" result == expected @@ -26,7 +26,7 @@ expect { # midnight is zero hours expect { - clock = create({ hours: 24 }) + clock = create({ hour: 24, minute: 0 }) result = clock.to_str() expected = "00:00" result == expected @@ -34,7 +34,7 @@ expect { # hour rolls over expect { - clock = create({ hours: 25 }) + clock = create({ hour: 25, minute: 0 }) result = clock.to_str() expected = "01:00" result == expected @@ -42,7 +42,7 @@ expect { # hour rolls over continuously expect { - clock = create({ hours: 100 }) + clock = create({ hour: 100, minute: 0 }) result = clock.to_str() expected = "04:00" result == expected @@ -50,7 +50,7 @@ expect { # sixty minutes is next hour expect { - clock = create({ hours: 1, minutes: 60 }) + clock = create({ hour: 1, minute: 60 }) result = clock.to_str() expected = "02:00" result == expected @@ -58,7 +58,7 @@ expect { # minutes roll over expect { - clock = create({ minutes: 160 }) + clock = create({ hour: 0, minute: 160 }) result = clock.to_str() expected = "02:40" result == expected @@ -66,7 +66,7 @@ expect { # minutes roll over continuously expect { - clock = create({ minutes: 1723 }) + clock = create({ hour: 0, minute: 1723 }) result = clock.to_str() expected = "04:43" result == expected @@ -74,7 +74,7 @@ expect { # hour and minutes roll over expect { - clock = create({ hours: 25, minutes: 160 }) + clock = create({ hour: 25, minute: 160 }) result = clock.to_str() expected = "03:40" result == expected @@ -82,7 +82,7 @@ expect { # hour and minutes roll over continuously expect { - clock = create({ hours: 201, minutes: 3001 }) + clock = create({ hour: 201, minute: 3001 }) result = clock.to_str() expected = "11:01" result == expected @@ -90,7 +90,7 @@ expect { # hour and minutes roll over to exactly midnight expect { - clock = create({ hours: 72, minutes: 8640 }) + clock = create({ hour: 72, minute: 8640 }) result = clock.to_str() expected = "00:00" result == expected @@ -98,7 +98,7 @@ expect { # negative hour expect { - clock = create({ hours: -1, minutes: 15 }) + clock = create({ hour: -1, minute: 15 }) result = clock.to_str() expected = "23:15" result == expected @@ -106,7 +106,7 @@ expect { # negative hour rolls over expect { - clock = create({ hours: -25 }) + clock = create({ hour: -25, minute: 0 }) result = clock.to_str() expected = "23:00" result == expected @@ -114,7 +114,7 @@ expect { # negative hour rolls over continuously expect { - clock = create({ hours: -91 }) + clock = create({ hour: -91, minute: 0 }) result = clock.to_str() expected = "05:00" result == expected @@ -122,7 +122,7 @@ expect { # negative minutes expect { - clock = create({ hours: 1, minutes: -40 }) + clock = create({ hour: 1, minute: -40 }) result = clock.to_str() expected = "00:20" result == expected @@ -130,7 +130,7 @@ expect { # negative minutes roll over expect { - clock = create({ hours: 1, minutes: -160 }) + clock = create({ hour: 1, minute: -160 }) result = clock.to_str() expected = "22:20" result == expected @@ -138,7 +138,7 @@ expect { # negative minutes roll over continuously expect { - clock = create({ hours: 1, minutes: -4820 }) + clock = create({ hour: 1, minute: -4820 }) result = clock.to_str() expected = "16:40" result == expected @@ -146,7 +146,7 @@ expect { # negative sixty minutes is previous hour expect { - clock = create({ hours: 2, minutes: -60 }) + clock = create({ hour: 2, minute: -60 }) result = clock.to_str() expected = "01:00" result == expected @@ -154,7 +154,7 @@ expect { # negative hour and minutes both roll over expect { - clock = create({ hours: -25, minutes: -160 }) + clock = create({ hour: -25, minute: -160 }) result = clock.to_str() expected = "20:20" result == expected @@ -162,7 +162,7 @@ expect { # negative hour and minutes both roll over continuously expect { - clock = create({ hours: -121, minutes: -5810 }) + clock = create({ hour: -121, minute: -5810 }) result = clock.to_str() expected = "22:10" result == expected @@ -174,64 +174,64 @@ expect { # add minutes expect { - clock = create({ hours: 10 }) - result = clock.add({ minutes: 3 }).to_str() + clock = create({ hour: 10, minute: 0 }) + result = clock.add({ hour: 0, minute: 3 }).to_str() expected = "10:03" result == expected } # add no minutes expect { - clock = create({ hours: 6, minutes: 41 }) - result = clock.add({ minutes: 0 }).to_str() + clock = create({ hour: 6, minute: 41 }) + result = clock.add({ hour: 0, minute: 0 }).to_str() expected = "06:41" result == expected } # add to next hour expect { - clock = create({ minutes: 45 }) - result = clock.add({ minutes: 40 }).to_str() + clock = create({ hour: 0, minute: 45 }) + result = clock.add({ hour: 0, minute: 40 }).to_str() expected = "01:25" result == expected } # add more than one hour expect { - clock = create({ hours: 10 }) - result = clock.add({ minutes: 61 }).to_str() + clock = create({ hour: 10, minute: 0 }) + result = clock.add({ hour: 0, minute: 61 }).to_str() expected = "11:01" result == expected } # add more than two hours with carry expect { - clock = create({ minutes: 45 }) - result = clock.add({ minutes: 160 }).to_str() + clock = create({ hour: 0, minute: 45 }) + result = clock.add({ hour: 0, minute: 160 }).to_str() expected = "03:25" result == expected } # add across midnight expect { - clock = create({ hours: 23, minutes: 59 }) - result = clock.add({ minutes: 2 }).to_str() + clock = create({ hour: 23, minute: 59 }) + result = clock.add({ hour: 0, minute: 2 }).to_str() expected = "00:01" result == expected } # add more than one day (1500 min = 25 hrs) expect { - clock = create({ hours: 5, minutes: 32 }) - result = clock.add({ minutes: 1500 }).to_str() + clock = create({ hour: 5, minute: 32 }) + result = clock.add({ hour: 0, minute: 1500 }).to_str() expected = "06:32" result == expected } # add more than two days expect { - clock = create({ hours: 1, minutes: 1 }) - result = clock.add({ minutes: 3500 }).to_str() + clock = create({ hour: 1, minute: 1 }) + result = clock.add({ hour: 0, minute: 3500 }).to_str() expected = "11:21" result == expected } @@ -242,64 +242,64 @@ expect { # subtract minutes expect { - clock = create({ hours: 10, minutes: 3 }) - result = clock.subtract({ minutes: 3 }).to_str() + clock = create({ hour: 10, minute: 3 }) + result = clock.subtract({ hour: 0, minute: 3 }).to_str() expected = "10:00" result == expected } # subtract to previous hour expect { - clock = create({ hours: 10, minutes: 3 }) - result = clock.subtract({ minutes: 30 }).to_str() + clock = create({ hour: 10, minute: 3 }) + result = clock.subtract({ hour: 0, minute: 30 }).to_str() expected = "09:33" result == expected } # subtract more than an hour expect { - clock = create({ hours: 10, minutes: 3 }) - result = clock.subtract({ minutes: 70 }).to_str() + clock = create({ hour: 10, minute: 3 }) + result = clock.subtract({ hour: 0, minute: 70 }).to_str() expected = "08:53" result == expected } # subtract across midnight expect { - clock = create({ minutes: 3 }) - result = clock.subtract({ minutes: 4 }).to_str() + clock = create({ hour: 0, minute: 3 }) + result = clock.subtract({ hour: 0, minute: 4 }).to_str() expected = "23:59" result == expected } # subtract more than two hours expect { - clock = create({}) - result = clock.subtract({ minutes: 160 }).to_str() + clock = create({ hour: 0, minute: 0 }) + result = clock.subtract({ hour: 0, minute: 160 }).to_str() expected = "21:20" result == expected } # subtract more than two hours with borrow expect { - clock = create({ hours: 6, minutes: 15 }) - result = clock.subtract({ minutes: 160 }).to_str() + clock = create({ hour: 6, minute: 15 }) + result = clock.subtract({ hour: 0, minute: 160 }).to_str() expected = "03:35" result == expected } # subtract more than one day (1500 min = 25 hrs) expect { - clock = create({ hours: 5, minutes: 32 }) - result = clock.subtract({ minutes: 1500 }).to_str() + clock = create({ hour: 5, minute: 32 }) + result = clock.subtract({ hour: 0, minute: 1500 }).to_str() expected = "04:32" result == expected } # subtract more than two days expect { - clock = create({ hours: 2, minutes: 20 }) - result = clock.subtract({ minutes: 3000 }).to_str() + clock = create({ hour: 2, minute: 20 }) + result = clock.subtract({ hour: 0, minute: 3000 }).to_str() expected = "00:20" result == expected } @@ -310,113 +310,113 @@ expect { # clocks with same time expect { - clock1 = create({ hours: 15, minutes: 37 }) - clock2 = create({ hours: 15, minutes: 37 }) + clock1 = create({ hour: 15, minute: 37 }) + clock2 = create({ hour: 15, minute: 37 }) clock1 == clock2 } # clocks a minute apart expect { - clock1 = create({ hours: 15, minutes: 36 }) - clock2 = create({ hours: 15, minutes: 37 }) + clock1 = create({ hour: 15, minute: 36 }) + clock2 = create({ hour: 15, minute: 37 }) clock1 != clock2 } # clocks an hour apart expect { - clock1 = create({ hours: 14, minutes: 37 }) - clock2 = create({ hours: 15, minutes: 37 }) + clock1 = create({ hour: 14, minute: 37 }) + clock2 = create({ hour: 15, minute: 37 }) clock1 != clock2 } # clocks with hour overflow expect { - clock1 = create({ hours: 10, minutes: 37 }) - clock2 = create({ hours: 34, minutes: 37 }) + clock1 = create({ hour: 10, minute: 37 }) + clock2 = create({ hour: 34, minute: 37 }) clock1 == clock2 } # clocks with hour overflow by several days expect { - clock1 = create({ hours: 3, minutes: 11 }) - clock2 = create({ hours: 99, minutes: 11 }) + clock1 = create({ hour: 3, minute: 11 }) + clock2 = create({ hour: 99, minute: 11 }) clock1 == clock2 } # clocks with negative hour expect { - clock1 = create({ hours: 22, minutes: 40 }) - clock2 = create({ hours: -2, minutes: 40 }) + clock1 = create({ hour: 22, minute: 40 }) + clock2 = create({ hour: -2, minute: 40 }) clock1 == clock2 } # clocks with negative hour that wraps expect { - clock1 = create({ hours: 17, minutes: 3 }) - clock2 = create({ hours: -31, minutes: 3 }) + clock1 = create({ hour: 17, minute: 3 }) + clock2 = create({ hour: -31, minute: 3 }) clock1 == clock2 } # clocks with negative hour that wraps multiple times expect { - clock1 = create({ hours: 13, minutes: 49 }) - clock2 = create({ hours: -83, minutes: 49 }) + clock1 = create({ hour: 13, minute: 49 }) + clock2 = create({ hour: -83, minute: 49 }) clock1 == clock2 } # clocks with minute overflow expect { - clock1 = create({ minutes: 1 }) - clock2 = create({ minutes: 1441 }) + clock1 = create({ hour: 0, minute: 1 }) + clock2 = create({ hour: 0, minute: 1441 }) clock1 == clock2 } # clocks with minute overflow by several days expect { - clock1 = create({ hours: 2, minutes: 2 }) - clock2 = create({ hours: 2, minutes: 4322 }) + clock1 = create({ hour: 2, minute: 2 }) + clock2 = create({ hour: 2, minute: 4322 }) clock1 == clock2 } # clocks with negative minute expect { - clock1 = create({ hours: 2, minutes: 40 }) - clock2 = create({ hours: 3, minutes: -20 }) + clock1 = create({ hour: 2, minute: 40 }) + clock2 = create({ hour: 3, minute: -20 }) clock1 == clock2 } # clocks with negative minute that wraps expect { - clock1 = create({ hours: 4, minutes: 10 }) - clock2 = create({ hours: 5, minutes: -1490 }) + clock1 = create({ hour: 4, minute: 10 }) + clock2 = create({ hour: 5, minute: -1490 }) clock1 == clock2 } # clocks with negative minute that wraps multiple times expect { - clock1 = create({ hours: 6, minutes: 15 }) - clock2 = create({ hours: 6, minutes: -4305 }) + clock1 = create({ hour: 6, minute: 15 }) + clock2 = create({ hour: 6, minute: -4305 }) clock1 == clock2 } # clocks with negative hours and minutes expect { - clock1 = create({ hours: 7, minutes: 32 }) - clock2 = create({ hours: -12, minutes: -268 }) + clock1 = create({ hour: 7, minute: 32 }) + clock2 = create({ hour: -12, minute: -268 }) clock1 == clock2 } # clocks with negative hours and minutes that wrap expect { - clock1 = create({ hours: 18, minutes: 7 }) - clock2 = create({ hours: -54, minutes: -11513 }) + clock1 = create({ hour: 18, minute: 7 }) + clock2 = create({ hour: -54, minute: -11513 }) clock1 == clock2 } # full clock and zeroed clock expect { - clock1 = create({ hours: 24 }) - clock2 = create({}) + clock1 = create({ hour: 24, minute: 0 }) + clock2 = create({ hour: 0, minute: 0 }) clock1 == clock2 } @@ -426,7 +426,7 @@ expect { # Can create a clock with max I64 values expect { - clock = create({ hours: 9223372036854775807, minutes: 9223372036854775807 }) + clock = create({ hour: 9223372036854775807, minute: 9223372036854775807 }) result = clock.to_str() expected = "01:07" result == expected @@ -434,7 +434,7 @@ expect { # Can create a clock with min I64 values expect { - clock = create({ hours: -9223372036854775808, minutes: -9223372036854775808 }) + clock = create({ hour: -9223372036854775808, minute: -9223372036854775808 }) result = clock.to_str() expected = "21:52" result == expected @@ -442,32 +442,32 @@ expect { # Can add max I64 values to a clock expect { - clock = create({ hours: 23, minutes: 59 }) - result = clock.add({ minutes: 9223372036854775807 }).to_str() + clock = create({ hour: 23, minute: 59 }) + result = clock.add({ hour: 0, minute: 9223372036854775807 }).to_str() expected = "18:06" result == expected } # Can add min I64 values to a clock expect { - clock = create({ hours: 23, minutes: 59 }) - result = clock.add({ minutes: -9223372036854775808 }).to_str() + clock = create({ hour: 23, minute: 59 }) + result = clock.add({ hour: 0, minute: -9223372036854775808 }).to_str() expected = "05:51" result == expected } # Can subtract max I64 values from a clock expect { - clock = create({ hours: 23, minutes: 59 }) - result = clock.subtract({ minutes: 9223372036854775807 }).to_str() + clock = create({ hour: 23, minute: 59 }) + result = clock.subtract({ hour: 0, minute: 9223372036854775807 }).to_str() expected = "05:52" result == expected } # Can subtract min I64 values from a clock expect { - clock = create({ hours: 23, minutes: 59 }) - result = clock.subtract({ minutes: -9223372036854775808 }).to_str() + clock = create({ hour: 23, minute: 59 }) + result = clock.subtract({ hour: 0, minute: -9223372036854775808 }).to_str() expected = "18:07" result == expected } From 404cefaf3b3a93b6c37d2892656d719ed9873b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 21:42:05 +1200 Subject: [PATCH 032/162] Update exercise to new compiler: collatz-conjecture --- .../collatz-conjecture/.meta/Example.roc | 22 ++++++++++--------- .../collatz-conjecture/CollatzConjecture.roc | 7 +++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/exercises/practice/collatz-conjecture/.meta/Example.roc b/exercises/practice/collatz-conjecture/.meta/Example.roc index 8bad7313..9251c468 100644 --- a/exercises/practice/collatz-conjecture/.meta/Example.roc +++ b/exercises/practice/collatz-conjecture/.meta/Example.roc @@ -1,12 +1,14 @@ CollatzConjecture :: {}.{ - steps : U64 -> Result U64 [NumberArgWasZero] - steps = |number| - if number <= 0 then - Err(NumberArgWasZero) - else if number == 1 then - Ok(0) - else if Num.is_even(number) then - Ok(steps(number // 2)? + 1) - else - Ok(steps(3 * number + 1)? + 1) + steps : U64 -> Try(U64, [NumberArgWasZero]) + steps = |number| { + if number <= 0 { + Err(NumberArgWasZero) + } else if number == 1 { + Ok(0) + } else if number % 2 == 0 { + Ok(steps(number // 2)? + 1) + } else { + Ok(steps(3 * number + 1)? + 1) + } + } } diff --git a/exercises/practice/collatz-conjecture/CollatzConjecture.roc b/exercises/practice/collatz-conjecture/CollatzConjecture.roc index 30942a7a..7dd2696d 100644 --- a/exercises/practice/collatz-conjecture/CollatzConjecture.roc +++ b/exercises/practice/collatz-conjecture/CollatzConjecture.roc @@ -1,5 +1,6 @@ CollatzConjecture :: {}.{ - steps : U64 -> Result U64 _ - steps = |number| - crash("Please implement the 'steps' function") + steps : U64 -> Try(U64, _) + steps = |number| { + crash "Please implement the 'steps' function" + } } From b652e0d3ce3344cdadbaf032d5d0b4f2a9b42842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 13 Jun 2026 21:54:54 +1200 Subject: [PATCH 033/162] Prefer try+crash rather than wrap --- exercises/practice/affine-cipher/.meta/Example.roc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/exercises/practice/affine-cipher/.meta/Example.roc b/exercises/practice/affine-cipher/.meta/Example.roc index 8086b9cf..66192eeb 100644 --- a/exercises/practice/affine-cipher/.meta/Example.roc +++ b/exercises/practice/affine-cipher/.meta/Example.roc @@ -13,7 +13,11 @@ AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) .map( |index| { encoded_index = (a * index + b) % alphabet_size - 'a' + encoded_index.to_u8_wrap() + 'a' + ( + encoded_index.to_u8_try() ?? { + crash "Unreachable" + }, + ) }, ) ->collect() @@ -40,7 +44,11 @@ AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) ) .map( |pair| { - pair.decoded_index.to_u8_wrap() + 'a' + ( + pair.decoded_index.to_u8_try() ?? { + crash "Unreachable" + }, + ) + 'a' }, ) From 145de5222e96fd0aae9cf25d6f9284dddab3764b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sun, 14 Jun 2026 23:02:57 +1200 Subject: [PATCH 034/162] Update exercise to new compiler: crypto-square --- .../practice/crypto-square/.meta/Example.roc | 105 +++++++++++++----- .../practice/crypto-square/.meta/template.j2 | 2 +- .../practice/crypto-square/CryptoSquare.roc | 7 +- .../crypto-square/crypto-square-test.roc | 18 +-- 4 files changed, 90 insertions(+), 42 deletions(-) diff --git a/exercises/practice/crypto-square/.meta/Example.roc b/exercises/practice/crypto-square/.meta/Example.roc index 96861d93..df545867 100644 --- a/exercises/practice/crypto-square/.meta/Example.roc +++ b/exercises/practice/crypto-square/.meta/Example.roc @@ -1,33 +1,80 @@ CryptoSquare :: {}.{ - ciphertext : Str -> Result Str _ - ciphertext = |text| - chars = - text - |> Str.to_utf8 - |> List.join_map( - |char| - if (char >= 'a' and char <= 'z') or (char >= '0' and char <= '9') then - [char] - else if char >= 'A' and char <= 'Z' then - [char - 'A' + 'a'] - else - [], - ) + ciphertext : Str -> Str + ciphertext = |text| { + chars = { + text + .to_utf8() + ->join_map( + |char| { + if (char >= 'a' and char <= 'z') or (char >= '0' and char <= '9') { + [char] + } else if char >= 'A' and char <= 'Z' { + [char - 'A' + 'a'] + } else { + [] + } + }, + ) + .map( + |c| [c]->Str.from_utf8() ?? { + crash "Unreachable" + }, + ) + } + length = chars.len() + width = length->sqrt_ceiling() # to_f64().sqrt().ceiling().to_u64() + rows = chars->chunks_of(width) - length = List.len(chars) - width = length |> Num.to_f64 |> Num.sqrt |> Num.ceiling |> Num.to_u64 - rows = chars |> List.chunks_of(width) + if width == 0 { + "" + } else { + 0.to(width - 1) + .map( + |column| { + rows.map( + |row| { + row.get(column) ?? " " + }, + )->Str.join_with("") + }, + ) + ->List.from_iter() + ->Str.join_with(" ") + } + } +} + +# The following function should soon be available in Roc's builtins +chunks_of = |iter, size| { + var $state = [] + var $chunk = [] + for item in iter { + $chunk = $chunk.append(item) + if $chunk.len() == size { + $state = $state.append($chunk) + $chunk = [] + } + } + if $chunk.len() > 0 { + $state = $state.append($chunk) + } + $state +} + +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} - List.range({ start: At(0), end: Before(width) }) - |> List.map( - |column| - rows - |> List.map( - |row| - row |> List.get(column) |> Result.with_default(' '), - ), - ) - |> List.intersperse([' ']) - |> List.join - |> Str.from_utf8 +sqrt_ceiling = |n| { + var $i = 0 + while $i * $i < n { + $i = $i + 1 + } + $i } diff --git a/exercises/practice/crypto-square/.meta/template.j2 b/exercises/practice/crypto-square/.meta/template.j2 index 70c76336..a51bd600 100644 --- a/exercises/practice/crypto-square/.meta/template.j2 +++ b/exercises/practice/crypto-square/.meta/template.j2 @@ -9,7 +9,7 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } expect { text = {{ case["input"]["plaintext"] | to_roc }} result = {{ case["property"] | to_snake }}(text) - expected = Ok({{ case["expected"] | to_roc }}) + expected = {{ case["expected"] | to_roc }} result == expected } diff --git a/exercises/practice/crypto-square/CryptoSquare.roc b/exercises/practice/crypto-square/CryptoSquare.roc index e7c28202..e952adc4 100644 --- a/exercises/practice/crypto-square/CryptoSquare.roc +++ b/exercises/practice/crypto-square/CryptoSquare.roc @@ -1,5 +1,6 @@ CryptoSquare :: {}.{ - ciphertext : Str -> Result Str _ - ciphertext = |text| - crash("Please implement the 'ciphertext' function") + ciphertext : Str -> Str + ciphertext = |text| { + crash "Please implement the 'ciphertext' function" + } } diff --git a/exercises/practice/crypto-square/crypto-square-test.roc b/exercises/practice/crypto-square/crypto-square-test.roc index e4366e33..2b72bb1c 100644 --- a/exercises/practice/crypto-square/crypto-square-test.roc +++ b/exercises/practice/crypto-square/crypto-square-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/crypto-square/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-14 import CryptoSquare exposing [ciphertext] @@ -8,7 +8,7 @@ import CryptoSquare exposing [ciphertext] expect { text = "" result = ciphertext(text) - expected = Ok("") + expected = "" result == expected } @@ -16,7 +16,7 @@ expect { expect { text = "... --- ..." result = ciphertext(text) - expected = Ok("") + expected = "" result == expected } @@ -24,7 +24,7 @@ expect { expect { text = "A" result = ciphertext(text) - expected = Ok("a") + expected = "a" result == expected } @@ -32,7 +32,7 @@ expect { expect { text = " b " result = ciphertext(text) - expected = Ok("b") + expected = "b" result == expected } @@ -40,7 +40,7 @@ expect { expect { text = "@1,%!" result = ciphertext(text) - expected = Ok("1") + expected = "1" result == expected } @@ -48,7 +48,7 @@ expect { expect { text = "This is fun!" result = ciphertext(text) - expected = Ok("tsf hiu isn") + expected = "tsf hiu isn" result == expected } @@ -56,7 +56,7 @@ expect { expect { text = "Chill out." result = ciphertext(text) - expected = Ok("clu hlt io ") + expected = "clu hlt io " result == expected } @@ -64,7 +64,7 @@ expect { expect { text = "If man was meant to stay on the ground, god would have given us roots." result = ciphertext(text) - expected = Ok("imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ") + expected = "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau " result == expected } From 66a3bbe2d8e2c94cc6c2ef34a9b1a62b6d3aff55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 10:54:05 +1200 Subject: [PATCH 035/162] Replace .to() with ..= or ..< --- exercises/practice/affine-cipher/.meta/Example.roc | 2 +- exercises/practice/armstrong-numbers/.meta/Example.roc | 2 +- exercises/practice/bowling/.meta/Example.roc | 2 +- exercises/practice/circular-buffer/CircularBuffer.roc | 4 ++-- exercises/practice/crypto-square/.meta/Example.roc | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/exercises/practice/affine-cipher/.meta/Example.roc b/exercises/practice/affine-cipher/.meta/Example.roc index 66192eeb..6b7886fa 100644 --- a/exercises/practice/affine-cipher/.meta/Example.roc +++ b/exercises/practice/affine-cipher/.meta/Example.roc @@ -9,7 +9,7 @@ AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) new = |{ a, b }| { encode_map : List(U8) encode_map = - 0.to(alphabet_size - 1) + (0.. U64 pow_int = |number, pow| { - 1.to(pow).fold( + (1..=pow).fold( 1, |acc, _| { acc * number diff --git a/exercises/practice/bowling/.meta/Example.roc b/exercises/practice/bowling/.meta/Example.roc index 6330552e..8de495c9 100644 --- a/exercises/practice/bowling/.meta/Example.roc +++ b/exercises/practice/bowling/.meta/Example.roc @@ -147,7 +147,7 @@ map_triplets = |list, score_func| { get_or_0 = |index| { list.get(index) ?? Ball2(0, 0) } - 0.to(list.len()).map( + (0.. Try({ updated_buffer : CircularBuffer, value : I64 }, [BufferEmpty]) + read : CircularBuffer -> Try({ updated_buffer : CircularBuffer, value : I64 }, [BufferEmpty, ..]) read = |circular_buffer| { crash "Please implement the 'read' function" } - write : CircularBuffer, I64 -> Try(CircularBuffer, [BufferFull]) + write : CircularBuffer, I64 -> Try(CircularBuffer, [BufferFull, ..]) write = |circular_buffer, value| { crash "Please implement the 'write' function" } diff --git a/exercises/practice/crypto-square/.meta/Example.roc b/exercises/practice/crypto-square/.meta/Example.roc index df545867..4615d4a2 100644 --- a/exercises/practice/crypto-square/.meta/Example.roc +++ b/exercises/practice/crypto-square/.meta/Example.roc @@ -28,7 +28,7 @@ CryptoSquare :: {}.{ if width == 0 { "" } else { - 0.to(width - 1) + (0.. Date: Wed, 17 Jun 2026 10:54:46 +1200 Subject: [PATCH 036/162] Update exercise to new compiler: binary-search-tree --- .../binary-search-tree/.meta/Example.roc | 51 ++++++++++--------- .../binary-search-tree/BinarySearchTree.roc | 19 ++++--- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/exercises/practice/binary-search-tree/.meta/Example.roc b/exercises/practice/binary-search-tree/.meta/Example.roc index 072c28a9..61a8aa70 100644 --- a/exercises/practice/binary-search-tree/.meta/Example.roc +++ b/exercises/practice/binary-search-tree/.meta/Example.roc @@ -1,25 +1,30 @@ -BinarySearchTree :: {}.{ - from_list : List U64 -> BinaryTree - from_list = |data| - data |> List.walk(Nil, insert) - - to_list : BinaryTree -> List U64 - to_list = |tree| - when tree is - Nil -> [] - Node(node) -> - node.left |> to_list |> List.append(node.value) |> List.concat((node.right |> to_list)) -} +BinarySearchTree := [Nil, Node({ value : U64, left : BinarySearchTree, right : BinarySearchTree })].{ + from_list : List(U64) -> BinarySearchTree + from_list = |data| { + data.fold(Nil, insert) + } + to_list : BinarySearchTree -> List(U64) + to_list = |tree| { + match tree { + Nil => [] + Node(node) => { + to_list(node.left).append(node.value).concat(to_list(node.right)) + } + } + } -BinaryTree : [Nil, Node { value : U64, left : BinaryTree, right : BinaryTree }] - -insert : BinaryTree, U64 -> BinaryTree -insert = |tree, value| - when tree is - Nil -> Node({ value, left: Nil, right: Nil }) - Node(node) -> - if value <= node.value then - Node({ node & left: (node.left |> insert(value)) }) - else - Node({ node & right: (node.right |> insert(value)) }) + insert : BinarySearchTree, U64 -> BinarySearchTree + insert = |tree, value| { + match tree { + Nil => Node({ value, left: Nil, right: Nil }) + Node(node) => { + if value <= node.value { + Node({ ..node, left: node.left.insert(value) }) + } else { + Node({ ..node, right: node.right.insert(value) }) + } + } + } + } +} diff --git a/exercises/practice/binary-search-tree/BinarySearchTree.roc b/exercises/practice/binary-search-tree/BinarySearchTree.roc index 511a6298..325c7e48 100644 --- a/exercises/practice/binary-search-tree/BinarySearchTree.roc +++ b/exercises/practice/binary-search-tree/BinarySearchTree.roc @@ -1,12 +1,11 @@ -BinarySearchTree :: {}.{ - from_list : List U64 -> BinaryTree - from_list = |data| - crash("Please implement the 'from_list' function") +BinarySearchTree := [Nil, Node({ value : U64, left : BinarySearchTree, right : BinarySearchTree })].{ + from_list : List(U64) -> BinarySearchTree + from_list = |data| { + crash "Please implement the 'from_list' function" + } - to_list : BinaryTree -> List U64 - to_list = |tree| - crash("Please implement the 'to_list' function") + to_list : BinarySearchTree -> List(U64) + to_list = |tree| { + crash "Please implement the 'to_list' function" + } } - - -BinaryTree : [Nil, Node { value : U64, left : BinaryTree, right : BinaryTree }] From a03bac918d2e78687d33b6dbb2f3af99bf2e2140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 10:55:00 +1200 Subject: [PATCH 037/162] Update exercise to new compiler: collatz-conjecture --- exercises/practice/collatz-conjecture/.meta/Example.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/collatz-conjecture/.meta/Example.roc b/exercises/practice/collatz-conjecture/.meta/Example.roc index 9251c468..b6136ed0 100644 --- a/exercises/practice/collatz-conjecture/.meta/Example.roc +++ b/exercises/practice/collatz-conjecture/.meta/Example.roc @@ -1,5 +1,5 @@ CollatzConjecture :: {}.{ - steps : U64 -> Try(U64, [NumberArgWasZero]) + steps : U64 -> Try(U64, [NumberArgWasZero, ..]) steps = |number| { if number <= 0 { Err(NumberArgWasZero) From 87d1cffb4d44719684f07a2f8da294b5bf6ae4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 11:07:39 +1200 Subject: [PATCH 038/162] Update exercise to new compiler: alphametics (but it triggers a roc bug) --- .../practice/alphametics/.meta/Example.roc | 238 +++++++++++------- .../practice/alphametics/Alphametics.roc | 11 +- 2 files changed, 154 insertions(+), 95 deletions(-) diff --git a/exercises/practice/alphametics/.meta/Example.roc b/exercises/practice/alphametics/.meta/Example.roc index 6751d842..e6f50761 100644 --- a/exercises/practice/alphametics/.meta/Example.roc +++ b/exercises/practice/alphametics/.meta/Example.roc @@ -1,100 +1,158 @@ -module [solve] +Alphametics :: {}.{ + solve : Str -> Try(List((U8, U8)), [InvalidAssignment, ..]) + solve = |problem| { + { addends, sum } = parse(problem)? -solve : Str -> Result (List (U8, U8)) _ -solve = |problem| - { addends, sum } = parse(problem)? + # We can represent the equation as a dictionary of the letters mapped to their coefficients + # when we simplify the equation. For example, we can write AB + A + B == C as 11A + 2B + (-1)C == 0. + # That then becomes this dictionary: `Dict.from_list([('A', 11), ('B', 2), ('C', -1)]) + equation : Dict(U8, I64) + equation = { + addends.fold( + Dict.empty(), + |dict, term| { + dict->insert_term(term, 1) + }, + )->insert_term(sum, -1) + } - # We can represent the equation as a dictionary of the letters mapped to their coefficients - # when we simplify the equation. For example, we can write AB + A + B == C as 11A + 2B + (-1)C == 0. - # That then becomes this dictionary: `Dict.from_list [('A', 11), ('B', 2), ('C', -1)] - equation = - List.walk( - addends, - Dict.empty({}), - |dict, term| - insert_term(dict, term, 1), - ) - |> insert_term(sum, -1) + leading_digits : Set(U8) + leading_digits = + addends.map( + |letters| { + letters.first() ?? 0 + }, + ) + ->Set.from_list() + .insert( + sum.first() ?? 0, + ) - leading_digits = - List.map( - addends, - |letters| - List.first(letters) |> Result.with_default(0), - ) - |> Set.from_list - |> Set.insert((List.first(sum) |> Result.with_default(0))) + find_match : List((U8, U8)), List(U8), Set(U8) -> Try(List((U8, U8)), [InvalidAssignment, ..]) + find_match = |assignments, remaining_vars, remaining_digits| { + match remaining_vars { + [] => { + total_val : I64 + total_val = + assignments.fold( + 0, + |total, (letter, value)| { + (equation.get(letter) ?? 0) * value.to_i64() + total + }, + ) - find_match = |assignments, remaining_vars, remaining_digits| - when remaining_vars is - [] -> - total_val = - List.walk( - assignments, - 0, - |total, (letter, value)| - Dict.get(equation, letter) - |> Result.with_default(0) - |> Num.mul(Num.to_i64(value)) - |> Num.add(total), - ) + if total_val != 0 { + Err(InvalidAssignment) + } else { + Ok(assignments) + } + } + [letter, .. as rest] => { + find_first_ok( + remaining_digits, + |digit| { + if digit == 0 and leading_digits.contains(letter) { + Err(InvalidAssignment) + } else { + # Each digit has to be unique, so once we use a digit we remove it from the pool + find_match(assignments.append((letter, digit)), rest, remaining_digits.remove(digit)) + } + }, + ) + } + } + } - if total_val != 0 then - Err(InvalidAssignment) - else - Ok(assignments) - - [letter, .. as rest] -> - find_first_ok( - remaining_digits, - |digit| - if digit == 0 and Set.contains(leading_digits, letter) then - Err(InvalidAssignment) - else - # Each digit has to be unique, so once we use a digit we remove it from the pool - find_match(List.append(assignments, (letter, digit)), rest, Set.remove(remaining_digits, digit)), - ) - - digits = List.range({ start: At(0), end: At(9) }) |> Set.from_list - find_match([], Dict.keys(equation), digits) + digits = Set.from_list([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + find_match([], equation.keys(), digits) + } +} # Apply a function to each element of a list until the function returns an Ok, then return that value -find_first_ok : Set a, (a -> Result b err) -> Result b [NotFound] -find_first_ok = |set, func| - Set.walk_until( - set, - Err(NotFound), - |state, elem| - when func(elem) is - Err(_) -> Continue(state) - Ok(val) -> Break(Ok(val)), - ) +find_first_ok : Set(a), (a -> Try(b, err)) -> Try(b, [InvalidAssignment, ..]) +find_first_ok = |set, func| { + set.to_list().fold_until( + Err(InvalidAssignment), + |state, elem| { + match func(elem) { + Err(_) => Continue(state) + Ok(val) => Break(Ok(val)) + } + }, + ) +} # Update the equation with the values of a term -insert_term : Dict U8 I64, List U8, I64 -> Dict U8 I64 -insert_term = |equation, letters, polarity| - List.reverse(letters) - |> List.walk_with_index( - equation, - |dict, letter, index| - coeff = - Num.pow_int(10, index) - |> Num.to_i64 - |> Num.mul(polarity) - Dict.update( - dict, - letter, - |val| - when val is - Err(Missing) -> Ok(coeff) - Ok(c) -> Ok((c + coeff)), - ), - ) +insert_term : Dict(U8, I64), List(U8), I64 -> Dict(U8, I64) +insert_term = |equation, letters, polarity| { + letters + ->list_reverse() + .fold_with_index( + equation, + |dict, letter, index| { + coeff = pow_int(10, index) * polarity + dict.update( + letter, + |val| { + match val { + Err(Missing) => Ok(coeff) + Ok(c) => Ok((c + coeff)) + } + }, + ) + }, + ) +} + +parse : Str -> Try({ addends : List(List(U8)), sum : List(U8) }, _) +parse = |problem| { + { before, after } = problem->split_first(" == ")? + addends = + before + .split_on( + " + ", + ) + .map( + |s| { + s.to_utf8() + }, + ) + Ok({ addends, sum: after.to_utf8() }) +} + +# The following function should soon be available in Roc's builtins +split_first : Str, Str -> Try({ before : Str, after : Str }, [InvalidAssignment, ..]) +split_first = |str, sep| { + match str.split_on(sep) { + [] => Err(InvalidAssignment) + [_] => Err(InvalidAssignment) + [before, .. as rest] => Ok({ before, after: rest->Str.join_with(sep) }) + } +} + +list_reverse : List(a) -> List(a) +list_reverse = |list| { + match list { + [] => [] + [first, .. as rest] => list_reverse(rest).append(first) + } +} + +reverse : Str -> Str +reverse = |str| { + str + .to_utf8() + ->list_reverse() + ->Str.from_utf8() + ?? "" +} -parse : Str -> Result { addends : List (List U8), sum : List U8 } _ -parse = |problem| - { before, after } = Str.split_first(problem, " == ")? - addends = - Str.split_on(before, " + ") - |> List.map(Str.to_utf8) - Ok({ addends, sum: Str.to_utf8(after) }) +pow_int : I64, U64 -> I64 +pow_int = |number, pow| { + (1..=pow).fold( + 1, + |acc, _| { + acc * number + }, + ) +} diff --git a/exercises/practice/alphametics/Alphametics.roc b/exercises/practice/alphametics/Alphametics.roc index 4379b251..35fc4fba 100644 --- a/exercises/practice/alphametics/Alphametics.roc +++ b/exercises/practice/alphametics/Alphametics.roc @@ -1,5 +1,6 @@ -module [solve] - -solve : Str -> Result (List (U8, U8)) _ -solve = |problem| - crash("Please implement 'solve'") +Alphametics :: {}.{ + solve : Str -> Try(List((U8, U8)), _) + solve = |problem| { + crash "Please implement the 'solve' function" + } +} From a75f0fbdf2cd2c37e4212bddca4e2c5242b7ee2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 11:08:18 +1200 Subject: [PATCH 039/162] Update exercise to new compiler: complex-numbers (but it triggers a roc bug) --- .../complex-numbers/.meta/Example.roc | 279 ++++++++++++++---- .../complex-numbers/.meta/template.j2 | 22 +- .../complex-numbers/ComplexNumbers.roc | 80 ++--- .../complex-numbers/complex-numbers-test.roc | 62 ++-- 4 files changed, 318 insertions(+), 125 deletions(-) diff --git a/exercises/practice/complex-numbers/.meta/Example.roc b/exercises/practice/complex-numbers/.meta/Example.roc index c21e296c..59ca4da5 100644 --- a/exercises/practice/complex-numbers/.meta/Example.roc +++ b/exercises/practice/complex-numbers/.meta/Example.roc @@ -1,54 +1,233 @@ ComplexNumbers :: {}.{ - real : Complex -> F64 - real = |z| z.re - - imaginary : Complex -> F64 - imaginary = |z| z.im - - add : Complex, Complex -> Complex - add = |{ re: a, im: b }, { re: c, im: d }| { - re: a + c, - im: b + d, - } - - sub : Complex, Complex -> Complex - sub = |{ re: a, im: b }, { re: c, im: d }| { - re: a - c, - im: b - d, - } - - mul : Complex, Complex -> Complex - mul = |{ re: a, im: b }, { re: c, im: d }| { - re: a * c - b * d, - im: a * d + b * c, - } - - div : Complex, Complex -> Complex - div = |{ re: a, im: b }, { re: c, im: d }| - denominator = c * c + d * d - { - re: (a * c + b * d) / denominator, - im: (b * c - a * d) / denominator, - } - - conjugate : Complex -> Complex - conjugate = |z| { - re: z.re, - im: -z.im, - } - - abs : Complex -> F64 - abs = |{ re: a, im: b }| - a * a + b * b |> Num.sqrt - - exp : Complex -> Complex - exp = |z| - factor = Num.e |> Num.pow(z.re) - { - re: factor * Num.cos(z.im), - im: factor * Num.sin(z.im), - } + Complex := { re : F64, im : F64 } + + real : Complex -> F64 + real = |z| z.re + + imaginary : Complex -> F64 + imaginary = |z| z.im + + add : Complex, Complex -> Complex + add = |{ re: a, im: b }, { re: c, im: d }| { + { + re: a + c, + im: b + d, + } + } + + sub : Complex, Complex -> Complex + sub = |{ re: a, im: b }, { re: c, im: d }| { + { + re: a - c, + im: b - d, + } + } + + mul : Complex, Complex -> Complex + mul = |{ re: a, im: b }, { re: c, im: d }| { + { + re: a * c - b * d, + im: a * d + b * c, + } + } + + div : Complex, Complex -> Complex + div = |{ re: a, im: b }, { re: c, im: d }| { + denominator = c * c + d * d + { + re: (a * c + b * d) / denominator, + im: (b * c - a * d) / denominator, + } + } + + conjugate : Complex -> Complex + conjugate = |z| { + { + re: z.re, + im: -z.im, + } + } + + abs : Complex -> F64 + abs = |{ re: a, im: b }| { + sqrt(a * a + b * b) + } + + exp : Complex -> Complex + exp = |z| { + e = 2.718281828459045 + factor = e->pow(z.re) + { + re: factor * cos(z.im), + im: factor * sin(z.im), + } + } } +# The following function should soon be available in Roc's builtins + +# Calculates the natural logarithm of x, ln(x). +ln = |x| { + if x <= 0.0 { + # Natural log is undefined for zero and negative numbers + crash "ln is undefined for zero or negative numbers" + } else { + var $norm_x = x + var $log_offset = 0.0 + + e_val = 2.718281828459045 + inv_e = 0.36787944117144233 + + # Range reduction: keep x between [1/e, e] + while $norm_x > e_val { + $norm_x = $norm_x / e_val + $log_offset = $log_offset + 1.0 + } + while $norm_x < inv_e { + $norm_x = $norm_x * e_val + $log_offset = $log_offset - 1.0 + } + + # Area hyperbolic tangent series + z = ($norm_x - 1.0) / ($norm_x + 1.0) + z2 = z * z + var $z_term = z + var $ln_sum = 0.0 + var $ln_n = 1.0 + + var $i = 0 + while $i < 30 { + $ln_sum = $ln_sum + ($z_term / $ln_n) + $z_term = $z_term * z2 + $ln_n = $ln_n + 2.0 + $i = $i + 1 + } + + (2.0 * $ln_sum) + $log_offset + } +} + +# Calculates e^x using the Taylor series. +exp = |x| { + var $norm_x = x + var $exp_mult = 1.0 + e_val = 2.718281828459045 + + # Range reduction: keep x between [-1.0, 1.0] + while $norm_x > 1.0 { + $norm_x = $norm_x - 1.0 + $exp_mult = $exp_mult * e_val + } + while $norm_x < -1.0 { + $norm_x = $norm_x + 1.0 + $exp_mult = $exp_mult / e_val + } + + # Taylor series + var $exp_term = 1.0 + var $exp_sum = 1.0 + var $exp_n = 1.0 -Complex : { re : F64, im : F64 } + var $j = 0 + while $j < 25 { + $exp_term = $exp_term * $norm_x / $exp_n + $exp_sum = $exp_sum + $exp_term + $exp_n = $exp_n + 1.0 + $j = $j + 1 + } + + $exp_sum * $exp_mult +} + +# Calculates x^p for F64 values using the newly separated functions. +pow = |x, p| { + if x == 0.0 { + if p == 0.0 { + 1.0 + } else { + 0.0 + } + } else if x < 0.0 { + # Fractional powers of negative numbers are undefined in pure real F64 math + 0.0 + } else if p == 0.0 { + 1.0 + } else { + # The core mathematical calculation + exp(p * ln(x)) + } +} + +# cos uses the Taylor series expansion. +# We first normalize `x` to the [-PI, PI] range to ensure rapid, stable convergence. +cos = |x| { + var $norm_x = x + while $norm_x > 3.141592653589793 { + $norm_x = $norm_x - 6.283185307179586 + } + while $norm_x < -3.141592653589793 { + $norm_x = $norm_x + 6.283185307179586 + } + + var $term = 1.0 + var $sum = 1.0 + var $n = 0.0 + + # 20 iterations is more than enough for F64 precision in this range + var $i = 0 + while $i < 20 { + $term = { + -($term) * $norm_x * $norm_x / (($n + 1.0) * ($n + 2.0)) + } + $sum = $sum + $term + $n = $n + 2.0 + $i = $i + 1 + } + + $sum +} + +# sin uses the Taylor series expansion. +sin = |x| { + var $norm_x = x + while $norm_x > 3.141592653589793 { + $norm_x = $norm_x - 6.283185307179586 + } + while $norm_x < -3.141592653589793 { + $norm_x = $norm_x + 6.283185307179586 + } + + var $term = $norm_x + var $sum = $norm_x + var $n = 1.0 + + var $i = 0 + while $i < 20 { + $term = { + -($term) * $norm_x * $norm_x / (($n + 1.0) * ($n + 2.0)) + } + $sum = $sum + $term + $n = $n + 2.0 + $i = $i + 1 + } + + $sum +} + +# sqrt uses the Babylonian method (Newton-Raphson approach). +sqrt = |x| { + if x <= 0.0 { + 0.0 + } else { + var $guess = x / 2.0 + var $i = 0 + + # 25 iterations will aggressively converge for almost all F64 values + while $i < 25 { + $guess = ($guess + (x / $guess)) / 2.0 + $i = $i + 1 + } + + $guess + } +} diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2 index b8e67e3a..0f3c1e69 100644 --- a/exercises/practice/complex-numbers/.meta/template.j2 +++ b/exercises/practice/complex-numbers/.meta/template.j2 @@ -2,15 +2,7 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [real, imaginary, add, sub, mul, div, conjugate, abs, exp] - -is_approx_eq = |x1, x2| { - (x1 * 1e9 + 0.5).to_u64_wrap() == (x2 * 1e9 + 0.5).to_u64_wrap() -} - -complex_is_approx_eq = |z1, z2| { - is_approx_eq(z1.re, z2.re) and is_approx_eq(z1.im, z2.im) -} +import {{ exercise | to_pascal }} exposing [Complex, real, imaginary, add, sub, mul, div, conjugate, abs, exp] {% for supercase in cases %} ### @@ -42,7 +34,7 @@ expect { expect { z1 = {{ plugins.to_complex_number(subcase["input"]["z1"]) }} z2 = {{ plugins.to_complex_number(subcase["input"]["z2"]) }} - result = z1.{{ subcase["property"] }}(z2) + result = {{ subcase["property"] }}(z1, z2) expected = {{ plugins.to_complex_number(subcase["expected"]) }} result -> complex_is_approx_eq(expected) } @@ -53,4 +45,14 @@ expect { {% endfor %} {% endfor %} +is_approx_eq = |x1, x2| { + i1 = (x1 * 1000 + 0.5).to_u64_try() ?? { crash "Unreachable" } + i2 = (x2 * 1000 + 0.5).to_u64_try() ?? { crash "Unreachable" } + i1 == i2 +} + +complex_is_approx_eq = |z1, z2| { + is_approx_eq(z1.re, z2.re) and is_approx_eq(z1.im, z2.im) +} + {{ macros.footer() }} diff --git a/exercises/practice/complex-numbers/ComplexNumbers.roc b/exercises/practice/complex-numbers/ComplexNumbers.roc index 2298d15e..37feaef0 100644 --- a/exercises/practice/complex-numbers/ComplexNumbers.roc +++ b/exercises/practice/complex-numbers/ComplexNumbers.roc @@ -1,40 +1,48 @@ ComplexNumbers :: {}.{ - real : Complex -> F64 - real = |z| - crash("Please implement the 'real' function") - - imaginary : Complex -> F64 - imaginary = |z| - crash("Please implement the 'imaginary' function") - - add : Complex, Complex -> Complex - add = |z1, z2| - crash("Please implement the 'add' function") - - sub : Complex, Complex -> Complex - sub = |z1, z2| - crash("Please implement the 'sub' function") - - mul : Complex, Complex -> Complex - mul = |z1, z2| - crash("Please implement the 'mul' function") - - div : Complex, Complex -> Complex - div = |z1, z2| - crash("Please implement the 'div' function") - - conjugate : Complex -> Complex - conjugate = |z| - crash("Please implement the 'conjugate' function") - - abs : Complex -> F64 - abs = |z| - crash("Please implement the 'abs' function") - - exp : Complex -> Complex - exp = |z| - crash("Please implement the 'exp' function") + real : Complex -> F64 + real = |z| { + crash "Please implement the 'real' function" + } + + imaginary : Complex -> F64 + imaginary = |z| { + crash "Please implement the 'imaginary' function" + } + + add : Complex, Complex -> Complex + add = |z1, z2| { + crash "Please implement the 'add' function" + } + + sub : Complex, Complex -> Complex + sub = |z1, z2| { + crash "Please implement the 'sub' function" + } + + mul : Complex, Complex -> Complex + mul = |z1, z2| { + crash "Please implement the 'mul' function" + } + + div : Complex, Complex -> Complex + div = |z1, z2| { + crash "Please implement the 'div' function" + } + + conjugate : Complex -> Complex + conjugate = |z| { + crash "Please implement the 'conjugate' function" + } + + abs : Complex -> F64 + abs = |z| { + crash "Please implement the 'abs' function" + } + + exp : Complex -> Complex + exp = |z| { + crash "Please implement the 'exp' function" + } } - Complex : { re : F64, im : F64 } diff --git a/exercises/practice/complex-numbers/complex-numbers-test.roc b/exercises/practice/complex-numbers/complex-numbers-test.roc index d37baa78..a7c4216c 100644 --- a/exercises/practice/complex-numbers/complex-numbers-test.roc +++ b/exercises/practice/complex-numbers/complex-numbers-test.roc @@ -2,15 +2,7 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json # File last updated on 2026-06-13 -import ComplexNumbers exposing [real, imaginary, add, sub, mul, div, conjugate, abs, exp] - -is_approx_eq = |x1, x2| { - (x1 * 1e9 + 0.5).to_u64_wrap() == (x2 * 1e9 + 0.5).to_u64_wrap() -} - -complex_is_approx_eq = |z1, z2| { - is_approx_eq(z1.re, z2.re) and is_approx_eq(z1.im, z2.im) -} +import ComplexNumbers exposing [Complex, real, imaginary, add, sub, mul, div, conjugate, abs, exp] ### ### Real part @@ -76,7 +68,7 @@ expect { expect { z1 = { re: 1, im: 0 } z2 = { re: 2, im: 0 } - result = z1.add(z2) + result = add(z1, z2) expected = { re: 3, im: 0 } result->complex_is_approx_eq(expected) } @@ -85,7 +77,7 @@ expect { expect { z1 = { re: 0, im: 1 } z2 = { re: 0, im: 2 } - result = z1.add(z2) + result = add(z1, z2) expected = { re: 0, im: 3 } result->complex_is_approx_eq(expected) } @@ -94,7 +86,7 @@ expect { expect { z1 = { re: 1, im: 2 } z2 = { re: 3, im: 4 } - result = z1.add(z2) + result = add(z1, z2) expected = { re: 4, im: 6 } result->complex_is_approx_eq(expected) } @@ -105,7 +97,7 @@ expect { expect { z1 = { re: 1, im: 0 } z2 = { re: 2, im: 0 } - result = z1.sub(z2) + result = sub(z1, z2) expected = { re: -1, im: 0 } result->complex_is_approx_eq(expected) } @@ -114,7 +106,7 @@ expect { expect { z1 = { re: 0, im: 1 } z2 = { re: 0, im: 2 } - result = z1.sub(z2) + result = sub(z1, z2) expected = { re: 0, im: -1 } result->complex_is_approx_eq(expected) } @@ -123,7 +115,7 @@ expect { expect { z1 = { re: 1, im: 2 } z2 = { re: 3, im: 4 } - result = z1.sub(z2) + result = sub(z1, z2) expected = { re: -2, im: -2 } result->complex_is_approx_eq(expected) } @@ -134,7 +126,7 @@ expect { expect { z1 = { re: 1, im: 0 } z2 = { re: 2, im: 0 } - result = z1.mul(z2) + result = mul(z1, z2) expected = { re: 2, im: 0 } result->complex_is_approx_eq(expected) } @@ -143,7 +135,7 @@ expect { expect { z1 = { re: 0, im: 1 } z2 = { re: 0, im: 2 } - result = z1.mul(z2) + result = mul(z1, z2) expected = { re: -2, im: 0 } result->complex_is_approx_eq(expected) } @@ -152,7 +144,7 @@ expect { expect { z1 = { re: 1, im: 2 } z2 = { re: 3, im: 4 } - result = z1.mul(z2) + result = mul(z1, z2) expected = { re: -5, im: 10 } result->complex_is_approx_eq(expected) } @@ -163,7 +155,7 @@ expect { expect { z1 = { re: 1, im: 0 } z2 = { re: 2, im: 0 } - result = z1.div(z2) + result = div(z1, z2) expected = { re: 0.5, im: 0 } result->complex_is_approx_eq(expected) } @@ -172,7 +164,7 @@ expect { expect { z1 = { re: 0, im: 1 } z2 = { re: 0, im: 2 } - result = z1.div(z2) + result = div(z1, z2) expected = { re: 0.5, im: 0 } result->complex_is_approx_eq(expected) } @@ -181,7 +173,7 @@ expect { expect { z1 = { re: 1, im: 2 } z2 = { re: 3, im: 4 } - result = z1.div(z2) + result = div(z1, z2) expected = { re: 0.44, im: 0.08 } result->complex_is_approx_eq(expected) } @@ -305,7 +297,7 @@ expect { expect { z1 = { re: 1, im: 2 } z2 = { re: 5, im: 0 } - result = z1.add(z2) + result = add(z1, z2) expected = { re: 6, im: 2 } result->complex_is_approx_eq(expected) } @@ -314,7 +306,7 @@ expect { expect { z1 = { re: 5, im: 0 } z2 = { re: 1, im: 2 } - result = z1.add(z2) + result = add(z1, z2) expected = { re: 6, im: 2 } result->complex_is_approx_eq(expected) } @@ -323,7 +315,7 @@ expect { expect { z1 = { re: 5, im: 7 } z2 = { re: 4, im: 0 } - result = z1.sub(z2) + result = sub(z1, z2) expected = { re: 1, im: 7 } result->complex_is_approx_eq(expected) } @@ -332,7 +324,7 @@ expect { expect { z1 = { re: 4, im: 0 } z2 = { re: 5, im: 7 } - result = z1.sub(z2) + result = sub(z1, z2) expected = { re: -1, im: -7 } result->complex_is_approx_eq(expected) } @@ -341,7 +333,7 @@ expect { expect { z1 = { re: 2, im: 5 } z2 = { re: 5, im: 0 } - result = z1.mul(z2) + result = mul(z1, z2) expected = { re: 10, im: 25 } result->complex_is_approx_eq(expected) } @@ -350,7 +342,7 @@ expect { expect { z1 = { re: 5, im: 0 } z2 = { re: 2, im: 5 } - result = z1.mul(z2) + result = mul(z1, z2) expected = { re: 10, im: 25 } result->complex_is_approx_eq(expected) } @@ -359,7 +351,7 @@ expect { expect { z1 = { re: 10, im: 100 } z2 = { re: 10, im: 0 } - result = z1.div(z2) + result = div(z1, z2) expected = { re: 1, im: 10 } result->complex_is_approx_eq(expected) } @@ -368,11 +360,23 @@ expect { expect { z1 = { re: 5, im: 0 } z2 = { re: 1, im: 1 } - result = z1.div(z2) + result = div(z1, z2) expected = { re: 2.5, im: -2.5 } result->complex_is_approx_eq(expected) } +is_approx_eq : F64, F64 -> Bool +is_approx_eq = |x1, x2| { + i1 = (x1 * 1000 + 0.5).to_u64_wrap() + i2 = (x2 * 1000 + 0.5).to_u64_wrap() + i1 == i2 +} + +complex_is_approx_eq : Complex, Complex -> Bool +complex_is_approx_eq = |z1, z2| { + is_approx_eq(z1.re, z2.re) and is_approx_eq(z1.im, z2.im) +} + # This program is only used to run tests with `roc test`, so main! does nothing. main! = |_args| { Ok({}) From 13da63bb3cbfad71c47162a452df2cf0c6f55f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 11:09:55 +1200 Subject: [PATCH 040/162] Update exercise to new compiler: connect (but it triggers a roc bug) --- exercises/practice/connect/.meta/Example.roc | 285 ++++++++++++------- exercises/practice/connect/Connect.roc | 7 +- exercises/practice/connect/connect-test.roc | 2 +- 3 files changed, 191 insertions(+), 103 deletions(-) diff --git a/exercises/practice/connect/.meta/Example.roc b/exercises/practice/connect/.meta/Example.roc index a4effad8..9f4fe58c 100644 --- a/exercises/practice/connect/.meta/Example.roc +++ b/exercises/practice/connect/.meta/Example.roc @@ -1,112 +1,199 @@ Connect :: {}.{ - winner : Str -> Result [PlayerO, PlayerX] [NotFinished, InvalidCharacter U8, InvalidBoardShape] - winner = |board_str| - board = parse(board_str)? - validate(board)? - if board |> has_north_south_path(StoneO) then - Ok(PlayerO) - else if board |> transpose |> has_north_south_path(StoneX) then - Ok(PlayerX) - else - Err(NotFinished) + winner : Str -> Try([PlayerO, PlayerX], [NotFinished, InvalidCharacter(U8), InvalidBoardShape, ..]) + winner = |board_str| { + board = parse(board_str)? + _ = validate(board)? + if board->has_north_south_path(StoneO) { + Ok(PlayerO) + } else if board->transpose()->has_north_south_path(StoneX) { + Ok(PlayerX) + } else { + Err(NotFinished) + } + } } -Cell : [StoneO, StoneX, Empty] -Board : List (List Cell) +Cell := [StoneO, StoneX, Empty] + +Board : List(List(Cell)) + Position : { x : U64, y : U64 } -## Parse a string to a Board -parse : Str -> Result Board [InvalidCharacter U8] -parse = |board_str| - board_str - |> Str.trim - |> Str.to_utf8 - |> List.split_on('\n') - |> List.map_try( - |row| - row - |> List.drop_if(|char| char == ' ') - |> List.map_try( - |char| - when char is - 'O' -> Ok(StoneO) - 'X' -> Ok(StoneX) - '.' -> Ok(Empty) - _ -> Err(InvalidCharacter(char)), - ), - ) +# # Parse a string to a Board +parse : Str -> Try(Board, [InvalidCharacter(U8), ..]) +parse = |board_str| { + board_str + .trim() + .to_utf8() + .split_on( + '\n', + ) + ->map_try( + |row| { + row + .drop_if( + |char| char == ' ', + ) + ->map_try( + |char| { + match char { + 'O' => Ok(StoneO) + 'X' => Ok(StoneX) + '.' => Ok(Empty) + _ => Err(InvalidCharacter(char)) + } + }, + ) + }, + ) +} -## Ensure that the board has a least one cell, and that all rows have the same length -validate : Board -> Result {} [InvalidBoardShape] -validate = |board| - row_lengths = board |> List.map(List.len) |> Set.from_list - if Set.len(row_lengths) != 1 or row_lengths == Set.from_list([0]) then - Err(InvalidBoardShape) - else - Ok({}) +# # Ensure that the board has a least one cell, and that all rows have the same length +validate : Board -> Try({}, [InvalidBoardShape, ..]) +validate = |board| { + row_lengths = board.map(List.len)->Set.from_list() + if row_lengths.len() != 1 or row_lengths == Set.from_list([0]) { + Err(InvalidBoardShape) + } else { + Ok({}) + } +} transpose : Board -> Board -transpose = |board| - width = board |> first_row |> List.len - List.range({ start: At(0), end: Before(width) }) - |> List.map( - |x| - List.range({ start: At(0), end: Before((board |> List.len)) }) - |> List.map( - |y| - when board |> get_cell({ x, y }) is - Ok(cell) -> cell - Err(OutOfBounds) -> crash("Unreachable: all rows have the same length"), - ), - ) +transpose = |board| { + width = (board->first_row()).len() + (0..get_cell({ x, y }) { + Ok(cell) => cell + Err(OutOfBounds) => { + crash "Unreachable: all rows have the same length" + } + } + }, + ) + ->List.from_iter() + }, + ) + ->List.from_iter() +} -first_row : Board -> List Cell -first_row = |board| - when board |> List.first is - Ok(row) -> row - Err(ListWasEmpty) -> crash("Unreachable: the board has at least one cell") +first_row : Board -> List(Cell) +first_row = |board| { + match board.first() { + Ok(row) => row + Err(ListWasEmpty) => { + crash "Unreachable: the board has at least one cell" + } + } +} -get_cell : Board, Position -> Result Cell [OutOfBounds] -get_cell = |board, { x, y }| - board |> List.get(y)? |> List.get(x) +get_cell : Board, Position -> Try(Cell, [OutOfBounds, ..]) +get_cell = |board, { x, y }| { + board.get(y)?.get(x) +} has_north_south_path : Board, Cell -> Bool -has_north_south_path = |board, stone| - has_path_to_south : List Position, Set Position -> Bool - has_path_to_south = |to_visit, visited| - when to_visit is - [] -> Bool.False - [position, .. as rest] -> - is_player_stone = board |> get_cell(position) == Ok(stone) - if is_player_stone and !(visited |> Set.contains(position)) then - { x, y } = position - if y + 1 == List.len(board) then - Bool.True # we've reached the South! - else - neighbors = - [(-1, 0), (1, 0), (0, -1), (1, -1), (-1, 1), (0, 1)] - |> List.join_map( - |(dx, dy)| - nx = (x |> Num.to_i64) + dx - ny = (y |> Num.to_i64) + dy - if nx >= 0 and ny >= 0 then - [{ x: nx |> Num.to_u64, y: ny |> Num.to_u64 }] - else - [], - ) - has_path_to_south((rest |> List.concat(neighbors)), (visited |> Set.insert(position))) - else - has_path_to_south(rest, visited) +has_north_south_path = |board, stone| { + has_path_to_south : List(Position), Set(Position) -> Bool + has_path_to_south = |to_visit, visited| { + match to_visit { + [] => Bool.False + [position, .. as rest] => { + is_player_stone = board->get_cell(position) == Ok(stone) + if is_player_stone and !(visited.contains(position)) { + { x, y } = position + if y + 1 == board.len() { + Bool.True # we've reached the South! + } else { + neighbors = { + [(-1, 0), (1, 0), (0, -1), (1, -1), (-1, 1), (0, 1)] + ->join_map( + |(dx, dy)| { + nx = ( + x.to_i64_try() ?? { + crash "Unreachable" + }, + ) + dx + ny = ( + y.to_i64_try() ?? { + crash "Unreachable" + }, + ) + dy + if nx >= 0 and ny >= 0 { + [ + { + x: nx.to_u64_try() ?? { + crash "Unreachable" + }, + y: ny.to_u64_try() ?? { + crash "Unreachable" + }, + }, + ] + } else { + [] + } + }, + ) + } + has_path_to_south((rest.concat(neighbors)), (visited.insert(position))) + } + } else { + has_path_to_south(rest, visited) + } + } + } + } - north_stones : List Position - north_stones = - board - |> first_row - |> List.map_with_index( - |cell, x| - when cell is - StoneO | StoneX -> if cell == stone then Ok({ x, y: 0 }) else Err(NotPlayerStone) - Empty -> Err(NotPlayerStone), - ) - |> List.keep_oks(|id| id) - has_path_to_south(north_stones, Set.empty({})) + north_stones : List(Position) + north_stones = { + board + ->first_row() + .map_with_index( + |cell, x| { + match cell { + StoneO | StoneX => if cell == stone Ok({ x, y: 0 }) else Err(NotPlayerStone) + Empty => Err(NotPlayerStone) + } + }, + ) + ->keep_oks(|id| id) + } + has_path_to_south(north_stones, Set.empty()) +} + +# The following functions should soon be available in Roc's builtins +keep_oks = |iter, func| { + iter + ->join_map( + |item| { + match func(item) { + Ok(result) => [result] + Err(_) => [] + } + }, + ) +} + +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} + +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/connect/Connect.roc b/exercises/practice/connect/Connect.roc index 48c43e5b..9012fc19 100644 --- a/exercises/practice/connect/Connect.roc +++ b/exercises/practice/connect/Connect.roc @@ -1,5 +1,6 @@ Connect :: {}.{ - winner : Str -> Result [PlayerO, PlayerX] _ - winner = |board_str| - crash("Please implement the 'winner' function") + winner : Str -> Try([PlayerO, PlayerX], _) + winner = |board_str| { + crash "Please implement the 'winner' function" + } } diff --git a/exercises/practice/connect/connect-test.roc b/exercises/practice/connect/connect-test.roc index df50a83c..174206da 100644 --- a/exercises/practice/connect/connect-test.roc +++ b/exercises/practice/connect/connect-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/connect/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-14 import Connect exposing [winner] From 4099031f041890451076dc11e7f24d6fb8e6e221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 11:15:40 +1200 Subject: [PATCH 041/162] Update exercise to new compiler: hello-world --- exercises/practice/hello-world/.meta/Example.roc | 4 ++-- exercises/practice/hello-world/HelloWorld.roc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/hello-world/.meta/Example.roc b/exercises/practice/hello-world/.meta/Example.roc index 15c687c3..ffd4c67c 100644 --- a/exercises/practice/hello-world/.meta/Example.roc +++ b/exercises/practice/hello-world/.meta/Example.roc @@ -1,4 +1,4 @@ HelloWorld :: {}.{ - hello : Str - hello = "Hello, World!" + hello : Str + hello = "Hello, World!" } diff --git a/exercises/practice/hello-world/HelloWorld.roc b/exercises/practice/hello-world/HelloWorld.roc index 2bf61ca2..1c6fe3bf 100644 --- a/exercises/practice/hello-world/HelloWorld.roc +++ b/exercises/practice/hello-world/HelloWorld.roc @@ -1,4 +1,4 @@ HelloWorld :: {}.{ - hello : Str - hello = "Goodbye, Mars!" + hello : Str + hello = "Goodbye, Mars!" } From a902e09b0a3d5f24d147386527f6b35c2f44e6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 11:45:00 +1200 Subject: [PATCH 042/162] Update exercise to new compiler: leap --- exercises/practice/leap/.meta/Example.roc | 6 +++--- exercises/practice/leap/Leap.roc | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/exercises/practice/leap/.meta/Example.roc b/exercises/practice/leap/.meta/Example.roc index fc3b71bf..0a3f48e0 100644 --- a/exercises/practice/leap/.meta/Example.roc +++ b/exercises/practice/leap/.meta/Example.roc @@ -1,5 +1,5 @@ Leap :: {}.{ - is_leap_year : I64 -> Bool - is_leap_year = |year| - (year % 4 == 0) and (year % 400 == 0 or year % 100 != 0) + is_leap_year : I64 -> Bool + is_leap_year = |year| + (year % 4 == 0) and (year % 400 == 0 or year % 100 != 0) } diff --git a/exercises/practice/leap/Leap.roc b/exercises/practice/leap/Leap.roc index 062fb970..eb44b513 100644 --- a/exercises/practice/leap/Leap.roc +++ b/exercises/practice/leap/Leap.roc @@ -1,5 +1,6 @@ Leap :: {}.{ - is_leap_year : I64 -> Bool - is_leap_year = |year| - crash("Please implement the 'is_leap_year' function") + is_leap_year : I64 -> Bool + is_leap_year = |year| { + crash "Please implement the 'is_leap_year' function" + } } From 1b1a27ad26d8edb737f039a9ec2360eec86550da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 11:51:22 +1200 Subject: [PATCH 043/162] Update exercise to new compiler: two-fer --- exercises/practice/two-fer/.meta/Example.roc | 12 +++++++----- exercises/practice/two-fer/TwoFer.roc | 7 ++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/exercises/practice/two-fer/.meta/Example.roc b/exercises/practice/two-fer/.meta/Example.roc index d4183584..65c8ada7 100644 --- a/exercises/practice/two-fer/.meta/Example.roc +++ b/exercises/practice/two-fer/.meta/Example.roc @@ -1,7 +1,9 @@ TwoFer :: {}.{ - two_fer : [Name Str, Anonymous] -> Str - two_fer = |name| - when name is - Anonymous -> "One for you, one for me." - Name(n) -> "One for ${n}, one for me." + two_fer : [Name(Str), Anonymous] -> Str + two_fer = |name| { + match name { + Anonymous => "One for you, one for me." + Name(n) => "One for ${n}, one for me." + } + } } diff --git a/exercises/practice/two-fer/TwoFer.roc b/exercises/practice/two-fer/TwoFer.roc index ea2e431d..7af7d755 100644 --- a/exercises/practice/two-fer/TwoFer.roc +++ b/exercises/practice/two-fer/TwoFer.roc @@ -1,5 +1,6 @@ TwoFer :: {}.{ - two_fer : [Name Str, Anonymous] -> Str - two_fer = |name| - crash("Please implement the 'two_fer' function") + two_fer : [Name(Str), Anonymous] -> Str + two_fer = |name| { + crash "Please implement the 'two_fer' function" + } } From 8a1cbabfb3f59eb3e70d07849e8b17cb0af55a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 12:04:04 +1200 Subject: [PATCH 044/162] Update exercise to new compiler: flatten-array --- .../practice/flatten-array/.meta/Example.roc | 28 +++++++++++++------ .../practice/flatten-array/FlattenArray.roc | 21 ++++++++++---- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/exercises/practice/flatten-array/.meta/Example.roc b/exercises/practice/flatten-array/.meta/Example.roc index 4207fe08..df6e1c6b 100644 --- a/exercises/practice/flatten-array/.meta/Example.roc +++ b/exercises/practice/flatten-array/.meta/Example.roc @@ -1,11 +1,23 @@ FlattenArray :: {}.{ - flatten : NestedValue -> List I64 - flatten = |array| - when array is - NestedArray(list) -> list |> List.join_map(flatten) - Value(value) -> [value] - Null -> [] -} + NestedValue := [Value(I64), Null, NestedArray(List(NestedValue))] + flatten : NestedValue -> List(I64) + flatten = |array| { + match array { + NestedArray(list) => list->join_map(flatten) + Value(value) => [value] + Null => [] + } + } +} -NestedValue : [Value I64, Null, NestedArray (List NestedValue)] +# The following function should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} diff --git a/exercises/practice/flatten-array/FlattenArray.roc b/exercises/practice/flatten-array/FlattenArray.roc index 20a0154c..b3ee8acd 100644 --- a/exercises/practice/flatten-array/FlattenArray.roc +++ b/exercises/practice/flatten-array/FlattenArray.roc @@ -1,8 +1,19 @@ FlattenArray :: {}.{ - flatten : NestedValue -> List I64 - flatten = |array| - crash("Please implement the 'flatten' function") -} + NestedValue := [Value(I64), Null, NestedArray(List(NestedValue))] + flatten : NestedValue -> List(I64) + flatten = |array| { + crash ("Please implement the 'flatten' function") + } +} -NestedValue : [Value I64, Null, NestedArray (List NestedValue)] +# The following function should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} From 4793883232ee882351179e015f86c1c05609cf45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 12:15:13 +1200 Subject: [PATCH 045/162] Update exercise to new compiler: eliuds-eggs --- .../practice/eliuds-eggs/.meta/Example.roc | 21 +++++++++++-------- exercises/practice/eliuds-eggs/EliudsEggs.roc | 7 ++++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/exercises/practice/eliuds-eggs/.meta/Example.roc b/exercises/practice/eliuds-eggs/.meta/Example.roc index 0b7177f3..349cc6b1 100644 --- a/exercises/practice/eliuds-eggs/.meta/Example.roc +++ b/exercises/practice/eliuds-eggs/.meta/Example.roc @@ -1,12 +1,15 @@ EliudsEggs :: {}.{ - egg_count : U64 -> U64 - egg_count = |number| - help = |count, remaining| - if remaining == 0 then - count - else - digit = Num.rem(remaining, 2) - help((count + digit), (remaining // 2)) + egg_count : U64 -> U64 + egg_count = |number| { + help = |count, remaining| { + if remaining == 0 { + count + } else { + digit = remaining % 2 + help((count + digit), (remaining // 2)) + } + } - help(0, number) + help(0, number) + } } diff --git a/exercises/practice/eliuds-eggs/EliudsEggs.roc b/exercises/practice/eliuds-eggs/EliudsEggs.roc index d0fd3ae4..58e9ac6b 100644 --- a/exercises/practice/eliuds-eggs/EliudsEggs.roc +++ b/exercises/practice/eliuds-eggs/EliudsEggs.roc @@ -1,5 +1,6 @@ EliudsEggs :: {}.{ - egg_count : U64 -> U64 - egg_count = |number| - crash("Please implement the 'egg_count' function") + egg_count : U64 -> U64 + egg_count = |number| { + crash ("Please implement the 'egg_count' function") + } } From 425d91ddc7f6324168c319b571051da5bdb15c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 12:33:26 +1200 Subject: [PATCH 046/162] Update exercise to new compiler: grains --- exercises/practice/grains/.meta/Example.roc | 32 +++++++++++++++------ exercises/practice/grains/Grains.roc | 14 +++++---- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/exercises/practice/grains/.meta/Example.roc b/exercises/practice/grains/.meta/Example.roc index 38de665a..bbac6222 100644 --- a/exercises/practice/grains/.meta/Example.roc +++ b/exercises/practice/grains/.meta/Example.roc @@ -1,12 +1,26 @@ Grains :: {}.{ - grains_on_square : U8 -> Result U64 [SquareArgWasNotBetween1And64 U8] - grains_on_square = |square| - if square > 0 and square <= 64 then - 2u64 |> Num.pow_int((Num.to_u64(square) - 1)) |> Ok - else - Err(SquareArgWasNotBetween1And64(square)) + grains_on_square : U8 -> Try(U64, [SquareArgWasNotBetween1And64(U8)]) + grains_on_square = |square| { + if square > 0 and square <= 64 { + Ok(2->pow_int(square.to_u64() - 1)) + } else { + Err(SquareArgWasNotBetween1And64(square)) + } + } - total_grains : U64 - total_grains = - Num.max_u64 + total_grains : U64 + total_grains = max_u64 } + +# This function should soon be available in Roc's builtins +pow_int : U64, U64 -> U64 +pow_int = |number, pow| { + (1..=pow).fold( + 1, + |acc, _| { + acc * number + }, + ) +} + +max_u64 = 18_446_744_073_709_551_615 diff --git a/exercises/practice/grains/Grains.roc b/exercises/practice/grains/Grains.roc index 8f5b3675..ddfe4310 100644 --- a/exercises/practice/grains/Grains.roc +++ b/exercises/practice/grains/Grains.roc @@ -1,9 +1,11 @@ Grains :: {}.{ - grains_on_square : U8 -> Result U64 _ - grains_on_square = |square| - crash("Please implement the 'grains_on_square' function") + grains_on_square : U8 -> Try(U64, _) + grains_on_square = |square| { + crash ("Please implement the 'grains_on_square' function") + } - total_grains : U64 - total_grains = - crash("Please implement the 'total_grains' function") + total_grains : U64 + total_grains = { + crash ("Please implement the 'total_grains' function") + } } From e2ad1b9196063bf07013b490d13e07932e432dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 14:16:14 +1200 Subject: [PATCH 047/162] Update exercise to new compiler: isogram --- exercises/practice/isogram/.meta/Example.roc | 28 +++++++++++++------- exercises/practice/isogram/Isogram.roc | 7 ++--- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/exercises/practice/isogram/.meta/Example.roc b/exercises/practice/isogram/.meta/Example.roc index 7d1157ff..437ea04d 100644 --- a/exercises/practice/isogram/.meta/Example.roc +++ b/exercises/practice/isogram/.meta/Example.roc @@ -1,12 +1,22 @@ Isogram :: {}.{ - is_isogram : Str -> Bool - is_isogram = |phrase| - chars = - phrase - |> Str.to_utf8 - |> List.drop_if(|c| c == ' ' or c == '-') - |> List.map(|c| if c >= 'a' and c <= 'z' then c + 'A' - 'a' else c) # to uppercase - - (List.len(chars)) == Set.len(Set.from_list(chars)) + is_isogram : Str -> Bool + is_isogram = |phrase| { + chars = + phrase + .to_utf8() + .drop_if( + |c| c == ' ' or c == '-', + ) + .map( + |c| { + if c >= 'a' and c <= 'z' { + c + 'A' - 'a' + } else { + c + } + }, + ) # to uppercase + chars.len() == chars->Set.from_list().len() + } } diff --git a/exercises/practice/isogram/Isogram.roc b/exercises/practice/isogram/Isogram.roc index b829ac51..f6072808 100644 --- a/exercises/practice/isogram/Isogram.roc +++ b/exercises/practice/isogram/Isogram.roc @@ -1,5 +1,6 @@ Isogram :: {}.{ - is_isogram : Str -> Bool - is_isogram = |phrase| - crash("Please implement the 'is_isogram' function") + is_isogram : Str -> Bool + is_isogram = |phrase| { + crash ("Please implement the 'is_isogram' function") + } } From 7d8ddbee0206b9c2c1383cb8bba6e49264cc4c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 14:17:58 +1200 Subject: [PATCH 048/162] Update exercise to new compiler: raindrops --- .../practice/raindrops/.meta/Example.roc | 34 +++++++++++++------ exercises/practice/raindrops/Raindrops.roc | 7 ++-- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/exercises/practice/raindrops/.meta/Example.roc b/exercises/practice/raindrops/.meta/Example.roc index e51fc819..927f627d 100644 --- a/exercises/practice/raindrops/.meta/Example.roc +++ b/exercises/practice/raindrops/.meta/Example.roc @@ -1,12 +1,26 @@ Raindrops :: {}.{ - convert : U64 -> Str - convert = |number| - pling = if number % 3 == 0 then "Pling" else "" - plang = if number % 5 == 0 then "Plang" else "" - plong = if number % 7 == 0 then "Plong" else "" - result = "${pling}${plang}${plong}" - if result == "" then - Num.to_str(number) - else - result + convert : U64 -> Str + convert = |number| { + pling = if number % 3 == 0 { + "Pling" + } else { + "" + } + plang = if number % 5 == 0 { + "Plang" + } else { + "" + } + plong = if number % 7 == 0 { + "Plong" + } else { + "" + } + result = "${pling}${plang}${plong}" + if result == "" { + U64.to_str(number) + } else { + result + } + } } diff --git a/exercises/practice/raindrops/Raindrops.roc b/exercises/practice/raindrops/Raindrops.roc index 9f8570ce..fa28f772 100644 --- a/exercises/practice/raindrops/Raindrops.roc +++ b/exercises/practice/raindrops/Raindrops.roc @@ -1,5 +1,6 @@ Raindrops :: {}.{ - convert : U64 -> Str - convert = |number| - crash("Please implement the 'convert' function") + convert : U64 -> Str + convert = |number| { + crash ("Please implement the 'convert' function") + } } From 3ced8b766b73a160286a2d7109c31d51a7b06059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 14:59:13 +1200 Subject: [PATCH 049/162] Update exercise to new compiler: sum-of-multiples --- .../sum-of-multiples/.meta/Example.roc | 33 ++++++++++++------- .../sum-of-multiples/SumOfMultiples.roc | 7 ++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/exercises/practice/sum-of-multiples/.meta/Example.roc b/exercises/practice/sum-of-multiples/.meta/Example.roc index 1a82c4d5..4f9bfc2d 100644 --- a/exercises/practice/sum-of-multiples/.meta/Example.roc +++ b/exercises/practice/sum-of-multiples/.meta/Example.roc @@ -1,13 +1,24 @@ SumOfMultiples :: {}.{ - sum_of_multiples : List U64, U64 -> U64 - sum_of_multiples = |factors, limit| - factors - |> List.keep_if(|factor| factor > 0) - |> List.join_map( - |factor| - List.range({ start: At(factor), end: Before(limit), step: factor }), - ) - |> Set.from_list - |> Set.to_list - |> List.sum + sum_of_multiples : List(U64), U64 -> U64 + sum_of_multiples = |factors, limit| { + factors + .keep_if( + |factor| factor > 0, + ) + ->join_map(|factor| (factor..Set.from_list() + .to_list() + .sum() + } +} + +# The following function should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state } diff --git a/exercises/practice/sum-of-multiples/SumOfMultiples.roc b/exercises/practice/sum-of-multiples/SumOfMultiples.roc index 01ec1b3f..57741519 100644 --- a/exercises/practice/sum-of-multiples/SumOfMultiples.roc +++ b/exercises/practice/sum-of-multiples/SumOfMultiples.roc @@ -1,5 +1,6 @@ SumOfMultiples :: {}.{ - sum_of_multiples : List U64, U64 -> U64 - sum_of_multiples = |factors, limit| - crash("Please implement the 'sum_of_multiples' function") + sum_of_multiples : List(U64), U64 -> U64 + sum_of_multiples = |factors, limit| { + crash "Please implement the 'sum_of_multiples' function" + } } From 82962a6c85c561c3a3d8a3984e15fe1e565560fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 23:53:00 +1200 Subject: [PATCH 050/162] Update exercise to new compiler: high-scores --- .../practice/high-scores/.meta/Example.roc | 33 ++++++++++++++----- exercises/practice/high-scores/HighScores.roc | 24 +++++++------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/exercises/practice/high-scores/.meta/Example.roc b/exercises/practice/high-scores/.meta/Example.roc index a9d898d9..1024f0c2 100644 --- a/exercises/practice/high-scores/.meta/Example.roc +++ b/exercises/practice/high-scores/.meta/Example.roc @@ -1,14 +1,29 @@ HighScores :: {}.{ - latest : List Score -> Result Score [ListWasEmpty] - latest = List.last + latest : List(U64) -> Try(U64, [ListWasEmpty]) + latest = |scores| { + List.last(scores) + } - personal_best : List Score -> Result Score [ListWasEmpty] - personal_best = List.max + personal_best : List(U64) -> Try(U64, [ListWasEmpty]) + personal_best = |scores| { + List.max(scores) + } - personal_top_three : List Score -> List Score - personal_top_three = |scores| - scores |> List.sort_desc |> List.take_first(3) + personal_top_three : List(U64) -> List(U64) + personal_top_three = |scores| { + scores->sort_desc().take_first(3) + } } - -Score : U64 +# The following functions should soon be available in Roc's builtins +sort_desc = |list| { + list.sort_with( + |a, b| if a < b { + GT + } else if a > b { + LT + } else { + EQ + }, + ) +} diff --git a/exercises/practice/high-scores/HighScores.roc b/exercises/practice/high-scores/HighScores.roc index a404b113..e6753001 100644 --- a/exercises/practice/high-scores/HighScores.roc +++ b/exercises/practice/high-scores/HighScores.roc @@ -1,16 +1,16 @@ HighScores :: {}.{ - latest : List Score -> Result Score _ - latest = |scores| - crash("Please implement the 'latest' function") + latest : List(U64) -> Try(U64, _) + latest = |scores| { + crash "Please implement the 'latest' function" + } - personal_best : List Score -> Result Score _ - personal_best = |scores| - crash("Please implement the 'personal_best' function") + personal_best : List(U64) -> Try(U64, _) + personal_best = |scores| { + crash "Please implement the 'personal_best' function" + } - personal_top_three : List Score -> List Score - personal_top_three = |scores| - crash("Please implement the 'personal_top_three' function") + personal_top_three : List(U64) -> List(U64) + personal_top_three = |scores| { + crash "Please implement the 'personal_top_three' function" + } } - - -Score : U64 From cdcbb76c3e1f094e509bbc9b77413cad2f9f64a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Wed, 17 Jun 2026 23:55:55 +1200 Subject: [PATCH 051/162] Update exercise to new compiler: pangram --- exercises/practice/pangram/.meta/Example.roc | 34 +++++++++++++------- exercises/practice/pangram/Pangram.roc | 7 ++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/exercises/practice/pangram/.meta/Example.roc b/exercises/practice/pangram/.meta/Example.roc index e65f02cc..17ac6623 100644 --- a/exercises/practice/pangram/.meta/Example.roc +++ b/exercises/practice/pangram/.meta/Example.roc @@ -1,14 +1,26 @@ Pangram :: {}.{ - is_pangram : Str -> Bool - is_pangram = |sentence| - sentence - |> Str.to_utf8 - |> Set.from_list - |> Set.map(|c| if c >= 'a' and c <= 'z' then c + 'A' - 'a' else c) # to uppercase - |> Set.keep_if(|c| c >= 'A' and c <= 'Z') - |> Bool.is_eq(alphabet) + is_pangram : Str -> Bool + is_pangram = |sentence| { + sentence + .to_utf8() + ->Set.from_list() + .map( + |c| if c >= 'a' and c <= 'z' { + c + 'A' - 'a' + } else { + c + }, + ) + .keep_if( + |c| c >= 'A' and c <= 'Z', + ) + == alphabet + } } - -alphabet = - List.range({ start: At('A'), end: At('Z') }) |> Set.from_list +alphabet = + ('A'..='Z') + .fold( + Set.empty(), + |set, c| set.insert(c), + ) diff --git a/exercises/practice/pangram/Pangram.roc b/exercises/practice/pangram/Pangram.roc index 9dcdfb72..457d609c 100644 --- a/exercises/practice/pangram/Pangram.roc +++ b/exercises/practice/pangram/Pangram.roc @@ -1,5 +1,6 @@ Pangram :: {}.{ - is_pangram : Str -> Bool - is_pangram = |sentence| - crash("Please implement the 'is_pangram' function") + is_pangram : Str -> Bool + is_pangram = |sentence| { + crash "Please implement the 'is_pangram' function" + } } From 679727bd6fa69d2714033b1a181233a7d0317ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 00:16:42 +1200 Subject: [PATCH 052/162] Update exercise to new compiler: nucleotide-count --- .../nucleotide-count/.meta/Example.roc | 35 ++++++++++++------- .../nucleotide-count/NucleotideCount.roc | 7 ++-- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/exercises/practice/nucleotide-count/.meta/Example.roc b/exercises/practice/nucleotide-count/.meta/Example.roc index 796af15e..07a5c335 100644 --- a/exercises/practice/nucleotide-count/.meta/Example.roc +++ b/exercises/practice/nucleotide-count/.meta/Example.roc @@ -1,15 +1,24 @@ NucleotideCount :: {}.{ - nucleotide_counts : Str -> Result { a : U64, c : U64, g : U64, t : U64 } _ - nucleotide_counts = |input| - Str.to_utf8(input) - |> List.walk_try( - { a: 0, c: 0, g: 0, t: 0 }, - |state, elem| - when elem is - 'A' -> Ok({ state & a: state.a + 1 }) - 'C' -> Ok({ state & c: state.c + 1 }) - 'G' -> Ok({ state & g: state.g + 1 }) - 'T' -> Ok({ state & t: state.t + 1 }) - _ -> Err(InvalidNucleotide(elem)), - ) + nucleotide_counts : Str -> Try({ a : U64, c : U64, g : U64, t : U64 }, [InvalidNucleotide(U8)]) + nucleotide_counts = |input| { + input + .to_utf8() + .fold_until( + Ok({ a: 0, c: 0, g: 0, t: 0 }), + |state_res, elem| { + match state_res { + Ok(state) => { + match elem { + 'A' => Continue(Ok({ ..state, a: state.a + 1 })) + 'C' => Continue(Ok({ ..state, c: state.c + 1 })) + 'G' => Continue(Ok({ ..state, g: state.g + 1 })) + 'T' => Continue(Ok({ ..state, t: state.t + 1 })) + _ => Break(Err(InvalidNucleotide(elem))) + } + } + Err(err) => Break(Err(err)) + } + }, + ) + } } diff --git a/exercises/practice/nucleotide-count/NucleotideCount.roc b/exercises/practice/nucleotide-count/NucleotideCount.roc index 2df98b43..2b1b0f90 100644 --- a/exercises/practice/nucleotide-count/NucleotideCount.roc +++ b/exercises/practice/nucleotide-count/NucleotideCount.roc @@ -1,5 +1,6 @@ NucleotideCount :: {}.{ - nucleotide_counts : Str -> Result { a : U64, c : U64, g : U64, t : U64 } _ - nucleotide_counts = |input| - crash("Please implement the 'nucleotide_counts' function") + nucleotide_counts : Str -> Try({ a : U64, c : U64, g : U64, t : U64 }, [InvalidNucleotide(U8)]) + nucleotide_counts = |input| { + crash "Please implement the 'nucleotide_counts' function" + } } From e05a931775c12bf975d0e460420263e35cfe087f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 00:16:42 +1200 Subject: [PATCH 053/162] Update exercise to new compiler: secret-handshake --- .../secret-handshake/.meta/Example.roc | 50 ++++++++++++++----- .../secret-handshake/SecretHandshake.roc | 7 +-- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/exercises/practice/secret-handshake/.meta/Example.roc b/exercises/practice/secret-handshake/.meta/Example.roc index 322602df..dd0a26f8 100644 --- a/exercises/practice/secret-handshake/.meta/Example.roc +++ b/exercises/practice/secret-handshake/.meta/Example.roc @@ -1,15 +1,41 @@ SecretHandshake :: {}.{ - commands : U64 -> List Str - commands = |number| - actions = - [(1, "wink"), (2, "double blink"), (4, "close your eyes"), (8, "jump")] - |> List.join_map( - |(mask, action)| - if Num.bitwise_and(number, mask) == 0 then [] else [action], - ) + commands : U64 -> List(Str) + commands = |number| { + actions = + [(1, "wink"), (2, "double blink"), (4, "close your eyes"), (8, "jump")] + ->join_map( + |(mask, action)| { + if U64.bitwise_and(number, mask) == 0 { + [] + } else { + [action] + } + }, + ) - if Num.bitwise_and(number, 16) == 0 then - actions - else - List.reverse(actions) + if U64.bitwise_and(number, 16) == 0 { + actions + } else { + actions->reverse() + } + } +} + +# The following function should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} + +reverse : List(a) -> List(a) +reverse = |list| { + match list { + [] => [] + [first, .. as rest] => reverse(rest).append(first) + } } diff --git a/exercises/practice/secret-handshake/SecretHandshake.roc b/exercises/practice/secret-handshake/SecretHandshake.roc index 4d838185..9b3cc9e9 100644 --- a/exercises/practice/secret-handshake/SecretHandshake.roc +++ b/exercises/practice/secret-handshake/SecretHandshake.roc @@ -1,5 +1,6 @@ SecretHandshake :: {}.{ - commands : U64 -> List Str - commands = |number| - crash("Please implement the 'commands' function") + commands : U64 -> List(Str) + commands = |number| { + crash "Please implement the 'commands' function" + } } From 0dd8650f9cf739d2b7a2f7d51714c6159bdb4a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 00:16:42 +1200 Subject: [PATCH 054/162] Update exercise to new compiler: knapsack --- exercises/practice/knapsack/.meta/Example.roc | 29 ++++++++++--------- exercises/practice/knapsack/Knapsack.roc | 10 +++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/exercises/practice/knapsack/.meta/Example.roc b/exercises/practice/knapsack/.meta/Example.roc index f5c0508e..eb4e7642 100644 --- a/exercises/practice/knapsack/.meta/Example.roc +++ b/exercises/practice/knapsack/.meta/Example.roc @@ -1,16 +1,19 @@ Knapsack :: {}.{ - maximum_value : { items : List Item, maximum_weight : U64 } -> U64 - maximum_value = |{ items, maximum_weight }| - when items is - [] -> 0 - [item, .. as rest] -> - max_value_without_item = maximum_value({ items: rest, maximum_weight }) - if item.weight > maximum_weight then - max_value_without_item - else - max_value_with_item = item.value + maximum_value({ items: rest, maximum_weight: maximum_weight - item.weight }) - Num.max(max_value_without_item, max_value_with_item) + maximum_value : { items : List(Item), maximum_weight : U64 } -> U64 + maximum_value = |{ items, maximum_weight }| { + match items { + [] => 0 + [item, .. as rest] => { + max_value_without_item = maximum_value({ items: rest, maximum_weight }) + if item.weight > maximum_weight { + max_value_without_item + } else { + max_value_with_item = item.value + maximum_value({ items: rest, maximum_weight: maximum_weight - item.weight }) + U64.max(max_value_without_item, max_value_with_item) + } + } + } + } } - -Item : { weight : U64, value : U64 } +Item := { weight : U64, value : U64 } diff --git a/exercises/practice/knapsack/Knapsack.roc b/exercises/practice/knapsack/Knapsack.roc index c9d9bb09..c7b1d929 100644 --- a/exercises/practice/knapsack/Knapsack.roc +++ b/exercises/practice/knapsack/Knapsack.roc @@ -1,8 +1,8 @@ Knapsack :: {}.{ - maximum_value : { items : List Item, maximum_weight : U64 } -> U64 - maximum_value = |{ items, maximum_weight }| - crash("Please implement the 'maximum_value' function") + maximum_value : { items : List(Item), maximum_weight : U64 } -> U64 + maximum_value = |{ items, maximum_weight }| { + crash "Please implement the 'maximum_value' function" + } } - -Item : { weight : U64, value : U64 } +Item := { weight : U64, value : U64 } From 0b14ca7909b017741fc475475610d25bfe877666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 00:16:42 +1200 Subject: [PATCH 055/162] Update exercise to new compiler: prime-factors --- .../practice/prime-factors/.meta/Example.roc | 35 ++++++++++++------- .../practice/prime-factors/PrimeFactors.roc | 7 ++-- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/exercises/practice/prime-factors/.meta/Example.roc b/exercises/practice/prime-factors/.meta/Example.roc index d3839034..1a67c9ad 100644 --- a/exercises/practice/prime-factors/.meta/Example.roc +++ b/exercises/practice/prime-factors/.meta/Example.roc @@ -1,16 +1,25 @@ PrimeFactors :: {}.{ - prime_factors : U64 -> List U64 - prime_factors = |value| - find_prime_factors = |factors, n, p| - if n < 2 then - factors - else if n |> Num.is_multiple_of(p) then - find_prime_factors(List.append(factors, p), (n // p), p) - else if p * p < n then - next_p = if p == 2 then 3 else p + 2 - find_prime_factors(factors, n, next_p) - else - List.append(factors, n) + prime_factors : U64 -> List(U64) + prime_factors = |value| { + find_prime_factors = |factors, n, p| { + if n < 2 { + factors + } else if n->is_multiple_of(p) { + find_prime_factors(factors.append(p), (n // p), p) + } else if p * p < n { + next_p = if p == 2 { + 3 + } else { + p + 2 + } + find_prime_factors(factors, n, next_p) + } else { + factors.append(n) + } + } - find_prime_factors([], value, 2) + find_prime_factors([], value, 2) + } } + +is_multiple_of = |n, p| n % p == 0 diff --git a/exercises/practice/prime-factors/PrimeFactors.roc b/exercises/practice/prime-factors/PrimeFactors.roc index 513d9e8c..9b9dcc9a 100644 --- a/exercises/practice/prime-factors/PrimeFactors.roc +++ b/exercises/practice/prime-factors/PrimeFactors.roc @@ -1,5 +1,6 @@ PrimeFactors :: {}.{ - prime_factors : U64 -> List U64 - prime_factors = |value| - crash("Please implement the 'prime_factors' function") + prime_factors : U64 -> List(U64) + prime_factors = |value| { + crash "Please implement the 'prime_factors' function" + } } From fbe4c1b88b49b9208e33d939898b2cdf84684798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 00:16:43 +1200 Subject: [PATCH 056/162] Update exercise to new compiler: proverb --- exercises/practice/proverb/.meta/Example.roc | 35 ++++++++++++-------- exercises/practice/proverb/Proverb.roc | 7 ++-- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/exercises/practice/proverb/.meta/Example.roc b/exercises/practice/proverb/.meta/Example.roc index 697d6a1f..b7716851 100644 --- a/exercises/practice/proverb/.meta/Example.roc +++ b/exercises/practice/proverb/.meta/Example.roc @@ -1,16 +1,23 @@ Proverb :: {}.{ - recite : List Str -> Str - recite = |strings| - when strings is - [] -> "" - [firtst_thing, .. as rest] -> - rest - |> List.walk( - (firtst_thing, []), - |(thing1, lines), thing2| - (thing2, lines |> List.append("For want of a ${thing1} the ${thing2} was lost.")), - ) - |> .1 - |> List.append("And all for the want of a ${firtst_thing}.") - |> Str.join_with("\n") + recite : List(Str) -> Str + recite = |strings| { + match strings { + [] => "" + [first_thing, .. as rest] => { + (_, lines) = + rest.fold( + (first_thing, []), + |(thing1, acc_lines), thing2| { + (thing2, acc_lines.append("For want of a ${thing1} the ${thing2} was lost.")) + }, + ) + + lines + .append( + "And all for the want of a ${first_thing}.", + ) + ->Str.join_with("\n") + } + } + } } diff --git a/exercises/practice/proverb/Proverb.roc b/exercises/practice/proverb/Proverb.roc index 092b4cb8..9d6f03f8 100644 --- a/exercises/practice/proverb/Proverb.roc +++ b/exercises/practice/proverb/Proverb.roc @@ -1,5 +1,6 @@ Proverb :: {}.{ - recite : List Str -> Str - recite = |strings| - crash("Please implement the 'recite' function") + recite : List(Str) -> Str + recite = |strings| { + crash "Please implement the 'recite' function" + } } From 1921324aae67de2050731c4aa19d58710175779b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 00:16:43 +1200 Subject: [PATCH 057/162] Update exercise to new compiler: darts --- exercises/practice/darts/.meta/Example.roc | 30 ++++++++++++---------- exercises/practice/darts/Darts.roc | 8 +++--- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/exercises/practice/darts/.meta/Example.roc b/exercises/practice/darts/.meta/Example.roc index 45dd7067..2b85ab80 100644 --- a/exercises/practice/darts/.meta/Example.roc +++ b/exercises/practice/darts/.meta/Example.roc @@ -1,17 +1,19 @@ Darts :: {}.{ - score : F64, F64 -> U64 - score = |x, y| - d2 = distance_squared(x, y) - if d2 > 100 then - 0 - else if d2 > 25 then - 1 - else if d2 > 1 then - 5 - else - 10 + score : F64, F64 -> U64 + score = |x, y| { + d2 = distance_squared(x, y) + if d2 > 100 { + 0 + } else if d2 > 25 { + 1 + } else if d2 > 1 { + 5 + } else { + 10 + } + } } - -distance_squared = |x, y| - x * x + y * y +distance_squared = |x, y| { + x * x + y * y +} diff --git a/exercises/practice/darts/Darts.roc b/exercises/practice/darts/Darts.roc index cd65a6ba..e0b70b91 100644 --- a/exercises/practice/darts/Darts.roc +++ b/exercises/practice/darts/Darts.roc @@ -1,6 +1,6 @@ Darts :: {}.{ - score : F64, F64 -> U64 - score = |x, y| - crash("Please implement the 'score' function") - + score : F64, F64 -> U64 + score = |x, y| { + crash "Please implement the 'score' function" + } } From 0c52c15c3500c0dbea2760fd10a6c8a9a5f1d54e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 00:16:43 +1200 Subject: [PATCH 058/162] Update exercise to new compiler: hamming --- exercises/practice/hamming/.meta/Example.roc | 38 ++++++++++++-------- exercises/practice/hamming/Hamming.roc | 7 ++-- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/exercises/practice/hamming/.meta/Example.roc b/exercises/practice/hamming/.meta/Example.roc index 2907f96e..cac1ed75 100644 --- a/exercises/practice/hamming/.meta/Example.roc +++ b/exercises/practice/hamming/.meta/Example.roc @@ -1,17 +1,25 @@ Hamming :: {}.{ - distance : Str, Str -> Result (Num *) [StrandArgsWereNotOfEqualLength Str Str] - distance = |strand1, strand2| - nucleotides1 = strand1 |> Str.to_utf8 - nucleotides2 = strand2 |> Str.to_utf8 - if List.len(nucleotides1) == List.len(nucleotides2) then - List.map2( - nucleotides1, - nucleotides2, - |n1, n2| - if n1 == n2 then 0 else 1, - ) - |> List.sum - |> Ok - else - Err(StrandArgsWereNotOfEqualLength(strand1, strand2)) + distance : Str, Str -> Try(U64, [StrandArgsWereNotOfEqualLength(Str, Str)]) + distance = |strand1, strand2| { + nucleotides1 = strand1.to_utf8() + nucleotides2 = strand2.to_utf8() + if nucleotides1.len() == nucleotides2.len() { + Ok( + List.map2( + nucleotides1, + nucleotides2, + |n1, n2| { + if n1 == n2 { + 0 + } else { + 1 + } + }, + ) + .sum(), + ) + } else { + Err(StrandArgsWereNotOfEqualLength(strand1, strand2)) + } + } } diff --git a/exercises/practice/hamming/Hamming.roc b/exercises/practice/hamming/Hamming.roc index 4b535aab..1756334b 100644 --- a/exercises/practice/hamming/Hamming.roc +++ b/exercises/practice/hamming/Hamming.roc @@ -1,5 +1,6 @@ Hamming :: {}.{ - distance : Str, Str -> Result (Num *) _ - distance = |strand1, strand2| - crash("Please implement the 'distance' function") + distance : Str, Str -> Try(U64, [StrandArgsWereNotOfEqualLength(Str, Str)]) + distance = |strand1, strand2| { + crash "Please implement the 'distance' function" + } } From 030c8bec7a0819c8bb4e883680a5b870c4ab9101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 00:16:43 +1200 Subject: [PATCH 059/162] Update exercise to new compiler: roman-numerals --- .../practice/roman-numerals/.meta/Example.roc | 40 ++++++++++++------- .../practice/roman-numerals/RomanNumerals.roc | 7 ++-- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/exercises/practice/roman-numerals/.meta/Example.roc b/exercises/practice/roman-numerals/.meta/Example.roc index 35debd3f..d716ef78 100644 --- a/exercises/practice/roman-numerals/.meta/Example.roc +++ b/exercises/practice/roman-numerals/.meta/Example.roc @@ -1,17 +1,27 @@ RomanNumerals :: {}.{ - roman : U64 -> Result Str [InvalidNumber U64] - roman = |number| - if number == 0 or number > 3999 then - Err(InvalidNumber(number)) - else - convert = |digit, x1, x5, x10| - when digit is - 4 -> "${x1}${x5}" - 9 -> "${x1}${x10}" - n -> if n <= 3 then Str.repeat(x1, n) else "${x5}${Str.repeat(x1, (n - 5))}" - thousands = convert((number // 1000), "M", "?", "?") - hundreds = convert((number % 1000 // 100), "C", "D", "M") - tens = convert((number % 100 // 10), "X", "L", "C") - units = convert((number % 10), "I", "V", "X") - Ok("${thousands}${hundreds}${tens}${units}") + roman : U64 -> Try(Str, [InvalidNumber(U64)]) + roman = |number| { + if number == 0 or number > 3999 { + Err(InvalidNumber(number)) + } else { + convert = |digit, x1, x5, x10| { + match digit { + 4 => "${x1}${x5}" + 9 => "${x1}${x10}" + n => { + if n <= 3 { + x1.repeat(n) + } else { + "${x5}${x1.repeat(n - 5)}" + } + } + } + } + thousands = convert(number // 1000, "M", "?", "?") + hundreds = convert((number % 1000) // 100, "C", "D", "M") + tens = convert((number % 100) // 10, "X", "L", "C") + units = convert(number % 10, "I", "V", "X") + Ok("${thousands}${hundreds}${tens}${units}") + } + } } diff --git a/exercises/practice/roman-numerals/RomanNumerals.roc b/exercises/practice/roman-numerals/RomanNumerals.roc index 4583bea1..63a10483 100644 --- a/exercises/practice/roman-numerals/RomanNumerals.roc +++ b/exercises/practice/roman-numerals/RomanNumerals.roc @@ -1,5 +1,6 @@ RomanNumerals :: {}.{ - roman : U64 -> Result Str _ - roman = |number| - crash("Please implement the 'roman' function") + roman : U64 -> Try(Str, [InvalidNumber(U64)]) + roman = |number| { + crash "Please implement the 'roman' function" + } } From 57cdd178583f690d1b09fb60eb710a99f031b90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 00:16:43 +1200 Subject: [PATCH 060/162] Update exercise to new compiler: rotational-cipher --- .../rotational-cipher/.meta/Example.roc | 34 +++++++++++-------- .../rotational-cipher/RotationalCipher.roc | 7 ++-- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/exercises/practice/rotational-cipher/.meta/Example.roc b/exercises/practice/rotational-cipher/.meta/Example.roc index b337b040..9f564ebc 100644 --- a/exercises/practice/rotational-cipher/.meta/Example.roc +++ b/exercises/practice/rotational-cipher/.meta/Example.roc @@ -1,18 +1,22 @@ RotationalCipher :: {}.{ - rotate : Str, U8 -> Str - rotate = |text, shift_key| - text - |> Str.to_utf8 - |> List.map(|c| shift_char(c, shift_key)) - |> Str.from_utf8 - |> Result.with_default("Unreachable") + rotate : Str, U8 -> Str + rotate = |text, shift_key| { + text + .to_utf8() + .map( + |c| shift_char(c, shift_key), + ) + ->Str.from_utf8() + ?? "Unreachable" + } } - -shift_char = |c, shift_key| - if c >= 'a' and c <= 'z' then - (c - 'a' + shift_key) % 26 + 'a' - else if c >= 'A' and c <= 'Z' then - (c - 'A' + shift_key) % 26 + 'A' - else - c +shift_char = |c, shift_key| { + if c >= 'a' and c <= 'z' { + (c - 'a' + shift_key) % 26 + 'a' + } else if c >= 'A' and c <= 'Z' { + (c - 'A' + shift_key) % 26 + 'A' + } else { + c + } +} diff --git a/exercises/practice/rotational-cipher/RotationalCipher.roc b/exercises/practice/rotational-cipher/RotationalCipher.roc index b03b7df9..27dc6264 100644 --- a/exercises/practice/rotational-cipher/RotationalCipher.roc +++ b/exercises/practice/rotational-cipher/RotationalCipher.roc @@ -1,5 +1,6 @@ RotationalCipher :: {}.{ - rotate : Str, U8 -> Str - rotate = |text, shift_key| - crash("Please implement the 'rotate' function") + rotate : Str, U8 -> Str + rotate = |text, shift_key| { + crash "Please implement the 'rotate' function" + } } From 116b49dffc4111522d146ac4d1e64631639aebf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 00:16:43 +1200 Subject: [PATCH 061/162] Update exercise to new compiler: etl --- exercises/practice/etl/.meta/Example.roc | 35 ++++++++++++++---------- exercises/practice/etl/Etl.roc | 7 +++-- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/exercises/practice/etl/.meta/Example.roc b/exercises/practice/etl/.meta/Example.roc index 0c36ef41..8ac2e399 100644 --- a/exercises/practice/etl/.meta/Example.roc +++ b/exercises/practice/etl/.meta/Example.roc @@ -1,19 +1,24 @@ Etl :: {}.{ - transform : Dict U64 (List U8) -> Dict U8 U64 - transform = |legacy| - legacy - |> Dict.join_map( - |score, letters| - letters - |> List.map(|c| (to_lower(c), score)) - |> Dict.from_list, - ) + transform : Dict(U64, List(U8)) -> Dict(U8, U64) + transform = |legacy| { + legacy + .join_map( + |score, letters| { + letters + .map( + |c| (to_lower(c), score), + ) + ->Dict.from_list() + }, + ) + } } - to_lower : U8 -> U8 -to_lower = |char| - if char >= 'A' and char <= 'Z' then - char - 'A' + 'a' - else - char +to_lower = |char| { + if char >= 'A' and char <= 'Z' { + char - 'A' + 'a' + } else { + char + } +} diff --git a/exercises/practice/etl/Etl.roc b/exercises/practice/etl/Etl.roc index 38a2b45e..927b0c29 100644 --- a/exercises/practice/etl/Etl.roc +++ b/exercises/practice/etl/Etl.roc @@ -1,5 +1,6 @@ Etl :: {}.{ - transform : Dict U64 (List U8) -> Dict U8 U64 - transform = |legacy| - crash("Please implement the 'transform' function") + transform : Dict(U64, List(U8)) -> Dict(U8, U64) + transform = |legacy| { + crash "Please implement the 'transform' function" + } } From 914100f3c82223e10fb31aef0aebb80e15e99f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 09:56:16 +1200 Subject: [PATCH 062/162] Update exercise to new compiler: square-root --- .../square-root/.docs/instructions.append.md | 2 +- .../practice/square-root/.meta/Example.roc | 31 +++++++++---------- exercises/practice/square-root/SquareRoot.roc | 7 +++-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/exercises/practice/square-root/.docs/instructions.append.md b/exercises/practice/square-root/.docs/instructions.append.md index b91263a2..750beed5 100644 --- a/exercises/practice/square-root/.docs/instructions.append.md +++ b/exercises/practice/square-root/.docs/instructions.append.md @@ -2,7 +2,7 @@ ## Implementation -This problem can be solved easily using Roc's built-in `Num` module, including the [`Num.sqrt`][sqrt] function. +This problem can be solved easily using Roc's built-in `sqrt` function. However, we'd like you to consider the challenge of solving this exercise without using built-ins or modules. diff --git a/exercises/practice/square-root/.meta/Example.roc b/exercises/practice/square-root/.meta/Example.roc index bc5878d6..ed0b5c63 100644 --- a/exercises/practice/square-root/.meta/Example.roc +++ b/exercises/practice/square-root/.meta/Example.roc @@ -1,19 +1,18 @@ SquareRoot :: {}.{ - square_root : U64 -> U64 - square_root = |radicand| - binary_search = |min, max| - val = (min + max) // 2 - square = val * val - if square == radicand or min >= max then - val - else if square > radicand then - binary_search(min, (val - 1)) - else - binary_search((val + 1), max) + square_root : U64 -> U64 + square_root = |radicand| { + binary_search = |min, max, target_square| { + val = (min + max) // 2 + square = val * val + if square == target_square or min >= max { + val + } else if square > target_square { + binary_search(min, (val - 1), target_square) + } else { + binary_search((val + 1), max, target_square) + } + } - binary_search(0, radicand) - - square_rootTheSimpleWay = |radicand| - # This works too... but it's cheating, right? - radicand |> Num.to_f64 |> Num.sqrt |> Num.round + binary_search(0, radicand, radicand) + } } diff --git a/exercises/practice/square-root/SquareRoot.roc b/exercises/practice/square-root/SquareRoot.roc index 16d71a8d..3e95499f 100644 --- a/exercises/practice/square-root/SquareRoot.roc +++ b/exercises/practice/square-root/SquareRoot.roc @@ -1,5 +1,6 @@ SquareRoot :: {}.{ - square_root : U64 -> U64 - square_root = |radicand| - crash("Please implement the 'square_root' function") + square_root : U64 -> U64 + square_root = |radicand| { + crash "Please implement the 'square_root' function" + } } From 7431e5d57afb00d00e57bcafa3c222a06f96ef4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 10:08:41 +1200 Subject: [PATCH 063/162] Update exercise to new compiler: difference-of-squares --- .../difference-of-squares/.meta/Example.roc | 31 ++++++++++--------- .../DifferenceOfSquares.roc | 21 +++++++------ 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/exercises/practice/difference-of-squares/.meta/Example.roc b/exercises/practice/difference-of-squares/.meta/Example.roc index ab40c6f6..c878aea0 100644 --- a/exercises/practice/difference-of-squares/.meta/Example.roc +++ b/exercises/practice/difference-of-squares/.meta/Example.roc @@ -1,20 +1,23 @@ DifferenceOfSquares :: {}.{ - square_of_sum : U64 -> U64 - square_of_sum = |number| - s = sum(number) - s * s + square_of_sum : U64 -> U64 + square_of_sum = |number| { + s = sum(number) + s * s + } - sum_of_squares : U64 -> U64 - sum_of_squares = |number| - s = sum(number) - s * (2 * number + 1) // 3 + sum_of_squares : U64 -> U64 + sum_of_squares = |number| { + s = sum(number) + s * (2 * number + 1) // 3 + } - difference_of_squares : U64 -> U64 - difference_of_squares = |number| - (square_of_sum(number)) - (sum_of_squares(number)) + difference_of_squares : U64 -> U64 + difference_of_squares = |number| { + (square_of_sum(number)) - (sum_of_squares(number)) + } } - sum : U64 -> U64 -sum = |number| - number * (number + 1) // 2 +sum = |number| { + number * (number + 1) // 2 +} diff --git a/exercises/practice/difference-of-squares/DifferenceOfSquares.roc b/exercises/practice/difference-of-squares/DifferenceOfSquares.roc index cac8d17e..9cd25d31 100644 --- a/exercises/practice/difference-of-squares/DifferenceOfSquares.roc +++ b/exercises/practice/difference-of-squares/DifferenceOfSquares.roc @@ -1,13 +1,16 @@ DifferenceOfSquares :: {}.{ - square_of_sum : U64 -> U64 - square_of_sum = |number| - crash("Please implement the 'square_of_sum' function") + square_of_sum : U64 -> U64 + square_of_sum = |number| { + crash "Please implement the 'square_of_sum' function" + } - sum_of_squares : U64 -> U64 - sum_of_squares = |number| - crash("Please implement the 'sum_of_squares' function") + sum_of_squares : U64 -> U64 + sum_of_squares = |number| { + crash "Please implement the 'sum_of_squares' function" + } - difference_of_squares : U64 -> U64 - difference_of_squares = |number| - crash("Please implement the 'difference_of_squares' function") + difference_of_squares : U64 -> U64 + difference_of_squares = |number| { + crash "Please implement the 'difference_of_squares' function" + } } From 8b9b733d54a3765023b2a5989d17590c7e381a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 10:11:52 +1200 Subject: [PATCH 064/162] Update exercise to new compiler: resistor-color --- .../practice/resistor-color/.meta/Example.roc | 23 +++++-------------- .../practice/resistor-color/ResistorColor.roc | 14 ++++++----- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/exercises/practice/resistor-color/.meta/Example.roc b/exercises/practice/resistor-color/.meta/Example.roc index 81a52595..2c974e09 100644 --- a/exercises/practice/resistor-color/.meta/Example.roc +++ b/exercises/practice/resistor-color/.meta/Example.roc @@ -1,20 +1,9 @@ ResistorColor :: {}.{ - colors : List Str - colors = - ["black", "brown", "red", "orange", "yellow", "green", "blue", "violet", "grey", "white"] + colors : List(Str) + colors = ["black", "brown", "red", "orange", "yellow", "green", "blue", "violet", "grey", "white"] - color_code : Str -> Result U64 [NotFound] - color_code = |color| - colors |> List.find_first_index(|elem| elem == color) - - color_code_from_dict = |color| - colors_dict |> Dict.get(color) + color_code : Str -> Try(U64, [NotFound]) + color_code = |color| { + colors.find_first_index(|elem| elem == color) + } } - -# Since the list of colors is quite small, representing it as a List is simple -# and efficient. However, if there were many more colors, it would make sense -# to use a Dict instead to avoid traversing the list at each lookup: -colors_dict = - colors - |> List.map_with_index(|color, index| (color, index)) - |> Dict.from_list diff --git a/exercises/practice/resistor-color/ResistorColor.roc b/exercises/practice/resistor-color/ResistorColor.roc index e8c04815..6aecdb76 100644 --- a/exercises/practice/resistor-color/ResistorColor.roc +++ b/exercises/practice/resistor-color/ResistorColor.roc @@ -1,9 +1,11 @@ ResistorColor :: {}.{ - color_code : Str -> Result U64 _ - color_code = |color| - crash("Please implement the 'color_code' function") + color_code : Str -> Try(U64, [NotFound]) + color_code = |color| { + crash "Please implement the 'color_code' function" + } - colors : List Str - colors = - crash("Please implement the 'colors' function") + colors : List(Str) + colors = { + crash "Please implement the 'colors' function" + } } From f0249360dccf4258d4e8b8788843f7dbad23a03c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 10:13:32 +1200 Subject: [PATCH 065/162] Update exercise to new compiler: series --- exercises/practice/series/.meta/Example.roc | 53 ++++++++++++++------- exercises/practice/series/Series.roc | 7 +-- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/exercises/practice/series/.meta/Example.roc b/exercises/practice/series/.meta/Example.roc index cedc9311..c1aa797d 100644 --- a/exercises/practice/series/.meta/Example.roc +++ b/exercises/practice/series/.meta/Example.roc @@ -1,20 +1,37 @@ Series :: {}.{ - slices : Str, U64 -> List Str - slices = |string, slice_length| - chars = string |> Str.to_utf8 - len = chars |> List.len - if len == 0 or slice_length == 0 or slice_length > len then - [] - else - maybe_list = - List.range({ start: At(0), end: At((len - slice_length)) }) - |> List.map_try( - |start_index| - chars - |> List.sublist({ start: start_index, len: slice_length }) - |> Str.from_utf8, - ) - when maybe_list is - Ok(list) -> list - Err(BadUtf8(_)) -> crash("Only ASCII strings are supported") + slices : Str, U64 -> List(Str) + slices = |string, slice_length| { + chars = string.to_utf8() + len = chars.len() + if len == 0 or slice_length == 0 or slice_length > len { + [] + } else { + maybe_list = + (0..=(len - slice_length)) + ->map_try( + |start_index| { + chars + .sublist( + { start: start_index, len: slice_length }, + ) + ->Str.from_utf8() + }, + ) + match maybe_list { + Ok(list) => list + Err(BadUtf8(_)) => { + crash "Only ASCII strings are supported" + } + } + } + } +} + +# The following function should soon be available in Roc's builtins +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) } diff --git a/exercises/practice/series/Series.roc b/exercises/practice/series/Series.roc index 63c1084c..4daac8ee 100644 --- a/exercises/practice/series/Series.roc +++ b/exercises/practice/series/Series.roc @@ -1,5 +1,6 @@ Series :: {}.{ - slices : Str, U64 -> List Str - slices = |string, slice_length| - crash("Please implement the 'slices' function") + slices : Str, U64 -> List(Str) + slices = |string, slice_length| { + crash "Please implement the 'slices' function" + } } From 1526a07da95f6e4bb5bb364d61d7865c47a44c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 10:45:07 +1200 Subject: [PATCH 066/162] Update exercise to new compiler: sieve --- exercises/practice/sieve/.meta/Example.roc | 53 ++++++++++++++-------- exercises/practice/sieve/Sieve.roc | 7 +-- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/exercises/practice/sieve/.meta/Example.roc b/exercises/practice/sieve/.meta/Example.roc index cb6e5818..3e82516c 100644 --- a/exercises/practice/sieve/.meta/Example.roc +++ b/exercises/practice/sieve/.meta/Example.roc @@ -1,20 +1,37 @@ Sieve :: {}.{ - primes : U64 -> List U64 - primes = |limit| - if limit < 2 then - [] - else - help_sieve = |candidates, found_primes| - when candidates is - [] -> found_primes - [0, .. as rest] -> rest |> help_sieve(found_primes) - [prime, .. as rest] -> - List.range({ start: After(prime), end: At(limit), step: prime }) - |> List.walk( - rest, - |filtered_candidates, multiple_of_prime| - filtered_candidates |> List.replace((multiple_of_prime - prime - 1), 0) |> .list, - ) - |> help_sieve((found_primes |> List.append(prime))) - help_sieve(List.range({ start: At(2), end: At(limit) }), []) + primes : U64 -> List(U64) + primes = |limit| { + if limit < 2 { + [] + } else { + help_sieve = |candidates, found_primes, limit2| { + match candidates { + [] => found_primes + [0, .. as rest] => { + help_sieve(rest, found_primes, limit2) + } + [prime, .. as rest] => { + ((prime * 2)..=limit2).step_by(prime) + .fold( + rest, + |filtered_candidates, multiple_of_prime| { + match filtered_candidates.replace(multiple_of_prime - prime - 1, 0) { + Ok(result) => result.list + Err(_) => { + crash "Unreachable" + } + } + }, + ) + ->help_sieve(found_primes.append(prime), limit2) + } + } + } + + initial_candidates = List.from_iter(2..=limit) + help_sieve(initial_candidates, [], limit) + } + } } + +# TODO: update once https://github.com/roc-lang/roc/issues/9690 is fixed diff --git a/exercises/practice/sieve/Sieve.roc b/exercises/practice/sieve/Sieve.roc index 474b3f14..649d96b5 100644 --- a/exercises/practice/sieve/Sieve.roc +++ b/exercises/practice/sieve/Sieve.roc @@ -1,5 +1,6 @@ Sieve :: {}.{ - primes : U64 -> List U64 - primes = |limit| - crash("Please implement the 'primes' function") + primes : U64 -> List(U64) + primes = |limit| { + crash "Please implement the 'primes' function" + } } From 09ce2435442aea4b26df2751b81641602ff9a4cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 10:46:00 +1200 Subject: [PATCH 067/162] Add comment about issue 9690 --- exercises/practice/square-root/.meta/Example.roc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exercises/practice/square-root/.meta/Example.roc b/exercises/practice/square-root/.meta/Example.roc index ed0b5c63..2377d2e4 100644 --- a/exercises/practice/square-root/.meta/Example.roc +++ b/exercises/practice/square-root/.meta/Example.roc @@ -16,3 +16,5 @@ SquareRoot :: {}.{ binary_search(0, radicand, radicand) } } + +# TODO: update once https://github.com/roc-lang/roc/issues/9690 is fixed From 12c5941302f2cff2025017f0df5d2a12b91a0abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 12:30:13 +1200 Subject: [PATCH 068/162] Update exercise to new compiler: triangle --- exercises/practice/triangle/.meta/Example.roc | 40 +++++++++++++------ exercises/practice/triangle/Triangle.roc | 21 +++++----- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/exercises/practice/triangle/.meta/Example.roc b/exercises/practice/triangle/.meta/Example.roc index 2416b447..808ffc00 100644 --- a/exercises/practice/triangle/.meta/Example.roc +++ b/exercises/practice/triangle/.meta/Example.roc @@ -1,20 +1,34 @@ Triangle :: {}.{ - is_equilateral : (F64, F64, F64) -> Bool - is_equilateral = |(a, b, c)| - is_valid_triangle((a, b, c)) and approx_eq(a, b) and approx_eq(b, c) + is_equilateral : (F64, F64, F64) -> Bool + is_equilateral = |(a, b, c)| { + is_valid_triangle((a, b, c)) and approx_eq(a, b) and approx_eq(b, c) + } - is_isosceles : (F64, F64, F64) -> Bool - is_isosceles = |(a, b, c)| - is_valid_triangle((a, b, c)) and (approx_eq(a, b) or approx_eq(b, c) or approx_eq(a, c)) + is_isosceles : (F64, F64, F64) -> Bool + is_isosceles = |(a, b, c)| { + is_valid_triangle((a, b, c)) and (approx_eq(a, b) or approx_eq(b, c) or approx_eq(a, c)) + } - is_scalene : (F64, F64, F64) -> Bool - is_scalene = |(a, b, c)| - is_valid_triangle((a, b, c)) and !(is_isosceles((a, b, c))) + is_scalene : (F64, F64, F64) -> Bool + is_scalene = |(a, b, c)| { + is_valid_triangle((a, b, c)) and !(is_isosceles((a, b, c))) + } } +is_valid_triangle = |(a, b, c)| { + a > 0 and b > 0 and c > 0 and a + b >= c and a + c >= b and b + c >= a +} -approx_eq = |x, y| - Num.is_approx_eq(x, y, {}) +# The following function should soon be available in Roc's builtins +approx_eq : F64, F64 -> Bool +approx_eq = |x, y| { + to_int : F64 -> Try(I64, [OutOfRange]) + to_int = |f| { + (f * 1e6).to_i64_try() + } -is_valid_triangle = |(a, b, c)| - a > 0 and b > 0 and c > 0 and a + b >= c and a + c >= b and b + c >= a + match (to_int(x), to_int(y)) { + (Ok(xi), Ok(yi)) => (xi == yi) + _ => Bool.False + } +} diff --git a/exercises/practice/triangle/Triangle.roc b/exercises/practice/triangle/Triangle.roc index 247fa127..23e7f92a 100644 --- a/exercises/practice/triangle/Triangle.roc +++ b/exercises/practice/triangle/Triangle.roc @@ -1,13 +1,16 @@ Triangle :: {}.{ - is_equilateral : (F64, F64, F64) -> Bool - is_equilateral = |(a, b, c)| - crash("Please implement the 'is_equilateral' function") + is_equilateral : (F64, F64, F64) -> Bool + is_equilateral = |(a, b, c)| { + crash "Please implement the 'is_equilateral' function" + } - is_isosceles : (F64, F64, F64) -> Bool - is_isosceles = |(a, b, c)| - crash("Please implement the 'is_isosceles' function") + is_isosceles : (F64, F64, F64) -> Bool + is_isosceles = |(a, b, c)| { + crash "Please implement the 'is_isosceles' function" + } - is_scalene : (F64, F64, F64) -> Bool - is_scalene = |(a, b, c)| - crash("Please implement the 'is_scalene' function") + is_scalene : (F64, F64, F64) -> Bool + is_scalene = |(a, b, c)| { + crash "Please implement the 'is_scalene' function" + } } From 8d8ed7b5d5277d98d3622a77734d8336d8adb111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 12:31:04 +1200 Subject: [PATCH 069/162] Update exercise to new compiler: rna-transcription --- .../rna-transcription/.meta/Example.roc | 43 +++++++++++-------- .../rna-transcription/RnaTranscription.roc | 7 +-- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/exercises/practice/rna-transcription/.meta/Example.roc b/exercises/practice/rna-transcription/.meta/Example.roc index 4a15c767..7a445246 100644 --- a/exercises/practice/rna-transcription/.meta/Example.roc +++ b/exercises/practice/rna-transcription/.meta/Example.roc @@ -1,22 +1,29 @@ RnaTranscription :: {}.{ - to_rna : Str -> Str - to_rna = |dna| - maybe_rna = - dna - |> Str.to_utf8 - |> List.map(complement) - |> Str.from_utf8 + to_rna : Str -> Str + to_rna = |dna| { + maybe_rna = + dna + .to_utf8() + .map( + complement, + ) + ->Str.from_utf8() - when maybe_rna is - Ok(rna) -> rna - Err(_) -> crash("Unreachable code: toUt8 -> fromUtf8 will always be Ok") + match maybe_rna { + Ok(rna) => rna + Err(_) => { + crash "Unreachable code: toUt8 -> fromUtf8 will always be Ok" + } + } + } } - -complement = |nucleotide| - when nucleotide is - 'G' -> 'C' - 'C' -> 'G' - 'T' -> 'A' - 'A' -> 'U' - c -> c # invalid nucleotides are ignored +complement = |nucleotide| { + match nucleotide { + 'G' => 'C' + 'C' => 'G' + 'T' => 'A' + 'A' => 'U' + c => c + } +} diff --git a/exercises/practice/rna-transcription/RnaTranscription.roc b/exercises/practice/rna-transcription/RnaTranscription.roc index 9474d045..29e06bf3 100644 --- a/exercises/practice/rna-transcription/RnaTranscription.roc +++ b/exercises/practice/rna-transcription/RnaTranscription.roc @@ -1,5 +1,6 @@ RnaTranscription :: {}.{ - to_rna : Str -> Str - to_rna = |dna| - crash("Please implement the 'to_rna' function") + to_rna : Str -> Str + to_rna = |dna| { + crash "Please implement the 'to_rna' function" + } } From df690b7fd58c5ebb152768b2808e431ba34bfdc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 12:46:10 +1200 Subject: [PATCH 070/162] Update exercise to new compiler: transpose --- .../practice/transpose/.meta/Example.roc | 50 +++--- .../practice/transpose/.meta/template.j2 | 6 +- exercises/practice/transpose/Transpose.roc | 7 +- .../practice/transpose/transpose-test.roc | 143 +++++++----------- 4 files changed, 95 insertions(+), 111 deletions(-) diff --git a/exercises/practice/transpose/.meta/Example.roc b/exercises/practice/transpose/.meta/Example.roc index f57e9976..5c231e6a 100644 --- a/exercises/practice/transpose/.meta/Example.roc +++ b/exercises/practice/transpose/.meta/Example.roc @@ -1,22 +1,32 @@ Transpose :: {}.{ - ## Transpose the input string. Input string must be ASCII. - transpose : Str -> Str - transpose = |string| - chars = string |> Str.to_utf8 |> List.split_on('\n') - get_char = |row, col| - chars |> List.get(row)? |> List.get(col) - max_width = chars |> List.map(List.len) |> List.max |> Result.with_default(0) - List.range({ start: At(0), end: Before(max_width) }) - |> List.map( - |col| - max_row = - chars - |> List.find_last_index(|row_chars| List.len(row_chars) > col) - |> Result.with_default(0) - List.range({ start: At(0), end: At(max_row) }) - |> List.map(|row| get_char(row, col) |> Result.with_default(' ')) - |> Str.from_utf8 - |> Result.with_default(""), - ) # unreachable because string is ASCII - |> Str.join_with("\n") + # # Transpose the input string. Input string must be ASCII. + transpose : Str -> Str + transpose = |string| { + chars = string.to_utf8().split_on('\n') + get_char = |row, col| { + chars.get(row)?.get(col) + } + max_width = chars.map(List.len).max() ?? 0 + (0.. col, + ) + ?? 0 + + (0..=max_row) + .map( + |row| get_char(row, col) ?? ' ', + ) + ->List.from_iter() + ->Str.from_utf8() + ?? "" + }, + ) + ->List.from_iter() + ->Str.join_with("\n") + } } diff --git a/exercises/practice/transpose/.meta/template.j2 b/exercises/practice/transpose/.meta/template.j2 index bd920a92..d3b5cf75 100644 --- a/exercises/practice/transpose/.meta/template.j2 +++ b/exercises/practice/transpose/.meta/template.j2 @@ -7,9 +7,9 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} expect { - input = {{ case["input"]["lines"] | to_roc_multiline_string | replace(" ", "□") | indent(8) }}.replace_each("□", " ") - result = {{ case["property"] | to_snake }}input.replace_each(" ", "□") - expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "□") | indent(8) }} + input = {{ case["input"]["lines"] | to_roc_multiline_string | indent(8) }} + result = {{ case["property"] | to_snake }}(input) + expected = {{ case["expected"] | to_roc_multiline_string | indent(8) }} result == expected } diff --git a/exercises/practice/transpose/Transpose.roc b/exercises/practice/transpose/Transpose.roc index 0e9b8076..34b20cf1 100644 --- a/exercises/practice/transpose/Transpose.roc +++ b/exercises/practice/transpose/Transpose.roc @@ -1,5 +1,6 @@ Transpose :: {}.{ - transpose : Str -> Str - transpose = |string| - crash("Please implement the 'transpose' function") + transpose : Str -> Str + transpose = |string| { + crash "Please implement the 'transpose' function" + } } diff --git a/exercises/practice/transpose/transpose-test.roc b/exercises/practice/transpose/transpose-test.roc index 12b813f6..63b135ca 100644 --- a/exercises/practice/transpose/transpose-test.roc +++ b/exercises/practice/transpose/transpose-test.roc @@ -1,21 +1,21 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/transpose/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-18 import Transpose exposing [transpose] # empty string expect { - input = "".replace_each("□", " ") - result = transposeinput.replace_each(" ", "□") + input = "" + result = transpose(input) expected = "" result == expected } # two characters in a row expect { - input = "A1".replace_each("□", " ") - result = transposeinput.replace_each(" ", "□") + input = "A1" + result = transpose(input) expected = \\A \\1 @@ -28,11 +28,8 @@ expect { input = \\A \\1 - .replace_each( - "□", - " ", - ) - result = transposeinput.replace_each(" ", "□") + + result = transpose(input) expected = "A1" result == expected } @@ -42,11 +39,8 @@ expect { input = \\ABC \\123 - .replace_each( - "□", - " ", - ) - result = transposeinput.replace_each(" ", "□") + + result = transpose(input) expected = \\A1 \\B2 @@ -57,8 +51,8 @@ expect { # single line expect { - input = "Single□line.".replace_each("□", " ") - result = transposeinput.replace_each(" ", "□") + input = "Single line." + result = transpose(input) expected = \\S \\i @@ -66,7 +60,7 @@ expect { \\g \\l \\e - \\□ + \\ \\l \\i \\n @@ -79,25 +73,22 @@ expect { # first line longer than second line expect { input = - \\The□fourth□line. - \\The□fifth□line. - .replace_each( - "□", - " ", - ) - result = transposeinput.replace_each(" ", "□") + \\The fourth line. + \\The fifth line. + + result = transpose(input) expected = \\TT \\hh \\ee - \\□□ + \\ \\ff \\oi \\uf \\rt \\th - \\h□ - \\□l + \\h + \\ l \\li \\in \\ne @@ -110,30 +101,27 @@ expect { # second line longer than first line expect { input = - \\The□first□line. - \\The□second□line. - .replace_each( - "□", - " ", - ) - result = transposeinput.replace_each(" ", "□") + \\The first line. + \\The second line. + + result = transpose(input) expected = \\TT \\hh \\ee - \\□□ + \\ \\fs \\ie \\rc \\so \\tn - \\□d - \\l□ + \\ d + \\l \\il \\ni \\en \\.e - \\□. + \\ . result == expected } @@ -141,30 +129,27 @@ expect { # mixed line length expect { input = - \\The□longest□line. - \\A□long□line. - \\A□longer□line. - \\A□line. - .replace_each( - "□", - " ", - ) - result = transposeinput.replace_each(" ", "□") + \\The longest line. + \\A long line. + \\A longer line. + \\A line. + + result = transpose(input) expected = \\TAAA - \\h□□□ + \\h \\elll - \\□ooi + \\ ooi \\lnnn \\ogge - \\n□e. + \\n e. \\glr - \\ei□ + \\ei \\snl \\tei - \\□.n - \\l□e - \\i□. + \\ .n + \\l e + \\i . \\n \\e \\. @@ -180,11 +165,8 @@ expect { \\ABUSE \\RESIN \\TREND - .replace_each( - "□", - " ", - ) - result = transposeinput.replace_each(" ", "□") + + result = transpose(input) expected = \\HEART \\EMBER @@ -202,11 +184,8 @@ expect { \\OUTLINED \\BLOOMING \\SEPTETTE - .replace_each( - "□", - " ", - ) - result = transposeinput.replace_each(" ", "□") + + result = transpose(input) expected = \\FOBS \\RULE @@ -229,18 +208,15 @@ expect { \\SSSS \\EEEEE \\RRRRRR - .replace_each( - "□", - " ", - ) - result = transposeinput.replace_each(" ", "□") + + result = transpose(input) expected = \\TEASER - \\□EASER - \\□□ASER - \\□□□SER - \\□□□□ER - \\□□□□□R + \\ EASER + \\ ASER + \\ SER + \\ ER + \\ R result == expected } @@ -254,18 +230,15 @@ expect { \\444 \\555555 \\66666 - .replace_each( - "□", - " ", - ) - result = transposeinput.replace_each(" ", "□") + + result = transpose(input) expected = \\123456 - \\1□3456 - \\□□3456 - \\□□3□56 - \\□□□□56 - \\□□□□5 + \\1 3456 + \\ 3456 + \\ 3 56 + \\ 56 + \\ 5 result == expected } From 7fd090e286aa79f7df9d14a50ae5b3b6e7db5516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 12:47:39 +1200 Subject: [PATCH 071/162] Update exercise to new compiler: pascals-triangle --- .../pascals-triangle/.meta/Example.roc | 52 ++++++++++++------- .../pascals-triangle/PascalsTriangle.roc | 7 +-- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/exercises/practice/pascals-triangle/.meta/Example.roc b/exercises/practice/pascals-triangle/.meta/Example.roc index fcaf23ae..7a241ca2 100644 --- a/exercises/practice/pascals-triangle/.meta/Example.roc +++ b/exercises/practice/pascals-triangle/.meta/Example.roc @@ -1,23 +1,37 @@ PascalsTriangle :: {}.{ - pascals_triangle : U64 -> List (List U64) - pascals_triangle = |count| - List.range({ start: At(0), end: Before(count) }) - |> List.map( - |row| - List.range({ start: At(0), end: At(row) }) - |> List.map(|column| binomial_coefficient(row, column)), - ) + pascals_triangle : U64 -> List(List(U64)) + pascals_triangle = |count| { + (0..List.from_iter() + }, + ) + ->List.from_iter() + } } binomial_coefficient : U64, U64 -> U64 -binomial_coefficient = |n, k| - if k == 0 or k == n then - 1 - else - numerator = - List.range({ start: At((n + 1 - k)), end: At(n) }) - |> List.walk(1, |product, value| product * value) - denominator = - List.range({ start: At(1), end: At(k) }) - |> List.walk(1, |product, value| product * value) - numerator // denominator +binomial_coefficient = |n, k| { + if k == 0 or k == n { + 1 + } else { + numerator = + ((n + 1 - k)..=n) + .fold( + 1, + |product, value| product * value, + ) + denominator = + (1..=k) + .fold( + 1, + |product, value| product * value, + ) + numerator // denominator + } +} diff --git a/exercises/practice/pascals-triangle/PascalsTriangle.roc b/exercises/practice/pascals-triangle/PascalsTriangle.roc index 41c0c3cb..62e8cc15 100644 --- a/exercises/practice/pascals-triangle/PascalsTriangle.roc +++ b/exercises/practice/pascals-triangle/PascalsTriangle.roc @@ -1,5 +1,6 @@ PascalsTriangle :: {}.{ - pascals_triangle : U64 -> List (List U64) - pascals_triangle = |count| - crash("Please implement the 'pascals_triangle' function") + pascals_triangle : U64 -> List(List(U64)) + pascals_triangle = |count| { + crash "Please implement the 'pascals_triangle' function" + } } From 856081316c2b7cf86e04d4d51ff88b0ccaa7deaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 12:50:42 +1200 Subject: [PATCH 072/162] Update exercise to new compiler: pythagorean-triplet --- .../pythagorean-triplet/.meta/Example.roc | 48 +++++++++++-------- .../PythagoreanTriplet.roc | 12 ++--- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/exercises/practice/pythagorean-triplet/.meta/Example.roc b/exercises/practice/pythagorean-triplet/.meta/Example.roc index 2bd4ea2c..6f9df885 100644 --- a/exercises/practice/pythagorean-triplet/.meta/Example.roc +++ b/exercises/practice/pythagorean-triplet/.meta/Example.roc @@ -1,24 +1,30 @@ PythagoreanTriplet :: {}.{ - triplets_with_sum : U64 -> Set Triplet - triplets_with_sum = |sum| - help = |triplets, a, b| - if a + b + b + 1 > sum then - # c would have to be too small (≤ b) - if 3 * (a + 1) > sum then - # we can't increment a so we're done - triplets - else - help(triplets, (a + 1), (a + 2)) # increment a - else - c = sum - a - b - new_triplets = - if a * a + b * b == c * c then - triplets |> List.append((a, b, c)) # success! - else - triplets - help(new_triplets, a, (b + 1)) # increment b - help([], 1, 2) |> Set.from_list -} + Triplet : (U64, U64, U64) + triplets_with_sum : U64 -> Set(Triplet) + triplets_with_sum = |sum| { + help = |triplets, a, b, sum2| { + if a + b + b + 1 > sum2 { + # c would have to be too small (≤ b) + if 3 * (a + 1) > sum2 { + # we can't increment a so we're done + triplets + } else { + help(triplets, (a + 1), (a + 2), sum2) # increment a + } + } else { + c = sum2 - a - b + new_triplets = + if a * a + b * b == c * c { + triplets.append((a, b, c)) # success! + } else { + triplets + } + help(new_triplets, a, (b + 1), sum2) # increment b + } + } + help([], 1, 2, sum)->Set.from_list() + } +} -Triplet : (U64, U64, U64) +# TODO: update once https://github.com/roc-lang/roc/issues/9690 is fixed diff --git a/exercises/practice/pythagorean-triplet/PythagoreanTriplet.roc b/exercises/practice/pythagorean-triplet/PythagoreanTriplet.roc index 6d341dac..7756ee36 100644 --- a/exercises/practice/pythagorean-triplet/PythagoreanTriplet.roc +++ b/exercises/practice/pythagorean-triplet/PythagoreanTriplet.roc @@ -1,8 +1,8 @@ PythagoreanTriplet :: {}.{ - triplets_with_sum : U64 -> Set Triplet - triplets_with_sum = |sum| - crash("Please implement the 'triplets_with_sum' function") -} - + Triplet : (U64, U64, U64) -Triplet : (U64, U64, U64) + triplets_with_sum : U64 -> Set(Triplet) + triplets_with_sum = |sum| { + crash "Please implement the 'triplets_with_sum' function" + } +} From afb46db36f4dee5d912f7925d97ef6b1d4d0d590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 12:51:39 +1200 Subject: [PATCH 073/162] Update exercise to new compiler: matching-brackets --- .../matching-brackets/.meta/Example.roc | 58 ++++++++++++------- .../matching-brackets/MatchingBrackets.roc | 7 ++- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/exercises/practice/matching-brackets/.meta/Example.roc b/exercises/practice/matching-brackets/.meta/Example.roc index 3f670fc5..70f6dfe1 100644 --- a/exercises/practice/matching-brackets/.meta/Example.roc +++ b/exercises/practice/matching-brackets/.meta/Example.roc @@ -1,25 +1,39 @@ MatchingBrackets :: {}.{ - is_paired : Str -> Bool - is_paired = |string| - is_open = |c| c == '[' or c == '(' or c == '{' - is_close = |c| c == ']' or c == ')' or c == '}' - is_match = |pair| pair == ('[', ']') or pair == ('(', ')') or pair == ('{', '}') - help = |open_brackets, remaining_chars| - when remaining_chars is - [] -> List.is_empty(open_brackets) # ok or missing closing bracket - [next_char, .. as rest_chars] -> - if is_open(next_char) then - help((open_brackets |> List.append(next_char)), rest_chars) - else if is_close(next_char) then - when open_brackets is - [] -> Bool.False # missing opening bracket - [.. as previous_opens, last_open] -> - if is_match((last_open, next_char)) then - help(previous_opens, rest_chars) - else - Bool.False # mismatching brackets - else - help(open_brackets, rest_chars) + is_paired : Str -> Bool + is_paired = |string| { + is_open = |c| { + c == '[' or c == '(' or c == '{' + } + is_close = |c| { + c == ']' or c == ')' or c == '}' + } + is_match = |pair| { + pair == ('[', ']') or pair == ('(', ')') or pair == ('{', '}') + } + help = |open_brackets, remaining_chars| { + match remaining_chars { + [] => open_brackets.is_empty() + [next_char, .. as rest_chars] => { + if is_open(next_char) { + help(open_brackets.append(next_char), rest_chars) + } else if is_close(next_char) { + match open_brackets { + [] => Bool.False + [.. as previous_opens, last_open] => { + if is_match((last_open, next_char)) { + help(previous_opens, rest_chars) + } else { + Bool.False + } + } + } + } else { + help(open_brackets, rest_chars) + } + } + } + } - help([], (string |> Str.to_utf8)) + help([], string.to_utf8()) + } } diff --git a/exercises/practice/matching-brackets/MatchingBrackets.roc b/exercises/practice/matching-brackets/MatchingBrackets.roc index 2ef0c547..70c07a9a 100644 --- a/exercises/practice/matching-brackets/MatchingBrackets.roc +++ b/exercises/practice/matching-brackets/MatchingBrackets.roc @@ -1,5 +1,6 @@ MatchingBrackets :: {}.{ - is_paired : Str -> Bool - is_paired = |string| - crash("Please implement the 'is_paired' function") + is_paired : Str -> Bool + is_paired = |string| { + crash "Please implement the 'is_paired' function" + } } From edc08ba2b01dbbed89888ce4e4e235fa8c6c32f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 12:53:23 +1200 Subject: [PATCH 074/162] Update exercise to new compiler: octal --- exercises/practice/octal/.meta/Example.roc | 60 ++++++++++++++-------- exercises/practice/octal/Octal.roc | 7 +-- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/exercises/practice/octal/.meta/Example.roc b/exercises/practice/octal/.meta/Example.roc index 305f81dd..4c6f7ce6 100644 --- a/exercises/practice/octal/.meta/Example.roc +++ b/exercises/practice/octal/.meta/Example.roc @@ -1,25 +1,43 @@ Octal :: {}.{ - parse : Str -> Result U64 _ - parse = |string| - if string == "" then - Err(InvalidNumStr) - else - string - |> Str.to_utf8 - |> List.walk_try( - 0, - |number, char| - octal_digit = parse_octal_digit(char)? - if number > 0x1fffffffffffffff then - Err(InvalidNumStr) - else - number |> Num.shift_left_by(3) |> Num.add(octal_digit) |> Ok, - ) + parse : Str -> Try(U64, [InvalidNumStr]) + parse = |string| { + if string == "" { + Err(InvalidNumStr) + } else { + digits = string.to_utf8()->map_try(parse_octal_digit)? + digits.fold_until( + Ok(0), + |acc_res, octal_digit| { + match acc_res { + Ok(number) => { + if number > 0x1fffffffffffffff { + Break(Err(InvalidNumStr)) + } else { + Continue(Ok(U64.shift_left_by(number, 3) + octal_digit)) + } + } + Err(err) => Break(Err(err)) + } + }, + ) + } + } } +parse_octal_digit : U8 -> Try(U64, [InvalidNumStr]) +parse_octal_digit = |char| { + if char >= '0' and char <= '7' { + Ok((char - '0').to_u64()) + } else { + Err(InvalidNumStr) + } +} -parse_octal_digit = |char| - if char >= '0' and char <= '7' then - Ok((char - '0' |> Num.to_u64)) - else - Err(InvalidNumStr) +# The following function should soon be available in Roc's builtins +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/octal/Octal.roc b/exercises/practice/octal/Octal.roc index 60f80a72..1dafb992 100644 --- a/exercises/practice/octal/Octal.roc +++ b/exercises/practice/octal/Octal.roc @@ -1,5 +1,6 @@ Octal :: {}.{ - parse : Str -> Result U64 _ - parse = |string| - crash("Please implement the 'parse' function") + parse : Str -> Try(U64, [InvalidNumStr]) + parse = |string| { + crash "Please implement the 'parse' function" + } } From ac6a2b3007bbe0e82741edd7314090536ebf9903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 12:57:10 +1200 Subject: [PATCH 075/162] Update exercise to new compiler: diamond --- exercises/practice/diamond/.meta/Example.roc | 61 +++++--- exercises/practice/diamond/.meta/template.j2 | 2 +- exercises/practice/diamond/Diamond.roc | 7 +- exercises/practice/diamond/diamond-test.roc | 156 +++++++++---------- 4 files changed, 116 insertions(+), 110 deletions(-) diff --git a/exercises/practice/diamond/.meta/Example.roc b/exercises/practice/diamond/.meta/Example.roc index 116ec672..ebf612b3 100644 --- a/exercises/practice/diamond/.meta/Example.roc +++ b/exercises/practice/diamond/.meta/Example.roc @@ -1,27 +1,44 @@ Diamond :: {}.{ - diamond : U8 -> Str - diamond = |letter| - letter_index = letter - 'A' |> Num.to_i8 - List.range({ start: At(-letter_index), end: At(letter_index) }) - |> List.map( - |row_index| - List.range({ start: At(-letter_index), end: At(letter_index) }) - |> List.map(|col_index| get_char(row_index, col_index, letter_index)) - |> unwrap_from_utf8, - ) - |> Str.join_with("\n") + diamond : U8 -> Str + diamond = |letter| { + letter_index = (letter - 'A').to_i8_try() ?? { + crash "Unreachable" + } + ((-letter_index)..=letter_index) + .map( + |row_index| { + ((-letter_index)..=letter_index) + .map( + |col_index| get_char(row_index, col_index, letter_index), + ) + ->List.from_iter() + ->unwrap_from_utf8() + }, + ) + ->List.from_iter() + ->Str.join_with("\n") + } } - get_char : I8, I8, I8 -> U8 -get_char = |row_index, col_index, letter_index| - if Num.abs(row_index) + Num.abs(col_index) == letter_index then - letter_index - Num.abs(row_index) |> Num.to_u8 |> Num.add('A') - else - ' ' +get_char = |row_index, col_index, letter_index| { + if row_index.abs() + col_index.abs() == letter_index { + ( + (letter_index - row_index.abs()).to_u8_try() ?? { + crash "Unreachable" + }, + ) + 'A' + } else { + ' ' + } +} -unwrap_from_utf8 : List U8 -> Str -unwrap_from_utf8 = |chars| - when chars |> Str.from_utf8 is - Ok(result) -> result - Err(_) -> crash("Str.from_utf8 should never fail here") +unwrap_from_utf8 : List(U8) -> Str +unwrap_from_utf8 = |chars| { + match Str.from_utf8(chars) { + Ok(result) => result + Err(_) => { + crash "Str.from_utf8 should never fail here" + } + } +} diff --git a/exercises/practice/diamond/.meta/template.j2 b/exercises/practice/diamond/.meta/template.j2 index a0bbc81e..635b8b55 100644 --- a/exercises/practice/diamond/.meta/template.j2 +++ b/exercises/practice/diamond/.meta/template.j2 @@ -8,7 +8,7 @@ import {{ exercise | to_pascal }} exposing [{{ exercise | to_snake }}] # {{ case["description"] }} expect { result = {{ exercise | to_snake }}('{{ case["input"]["letter"] }}') - expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") + expected = {{ case["expected"] | to_roc_multiline_string | indent(8) }} result == expected } diff --git a/exercises/practice/diamond/Diamond.roc b/exercises/practice/diamond/Diamond.roc index 36caae19..e8931caa 100644 --- a/exercises/practice/diamond/Diamond.roc +++ b/exercises/practice/diamond/Diamond.roc @@ -1,5 +1,6 @@ Diamond :: {}.{ - diamond : U8 -> Str - diamond = |letter| - crash("Please implement the 'diamond' function") + diamond : U8 -> Str + diamond = |letter| { + crash "Please implement the 'diamond' function" + } } diff --git a/exercises/practice/diamond/diamond-test.roc b/exercises/practice/diamond/diamond-test.roc index 8d3b6c37..8eaac858 100644 --- a/exercises/practice/diamond/diamond-test.roc +++ b/exercises/practice/diamond/diamond-test.roc @@ -1,13 +1,13 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/diamond/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-18 import Diamond exposing [diamond] # Degenerate case with a single 'A' row expect { result = diamond('A') - expected = "A".replace_each("·", " ") + expected = "A" result == expected } @@ -15,13 +15,10 @@ expect { expect { result = diamond('B') expected = - \\·A· - \\B·B - \\·A· - .replace_each( - "·", - " ", - ) + \\ A + \\B B + \\ A + result == expected } @@ -29,15 +26,12 @@ expect { expect { result = diamond('C') expected = - \\··A·· - \\·B·B· - \\C···C - \\·B·B· - \\··A·· - .replace_each( - "·", - " ", - ) + \\ A + \\ B B + \\C C + \\ B B + \\ A + result == expected } @@ -45,17 +39,14 @@ expect { expect { result = diamond('D') expected = - \\···A··· - \\··B·B·· - \\·C···C· - \\D·····D - \\·C···C· - \\··B·B·· - \\···A··· - .replace_each( - "·", - " ", - ) + \\ A + \\ B B + \\ C C + \\D D + \\ C C + \\ B B + \\ A + result == expected } @@ -63,61 +54,58 @@ expect { expect { result = diamond('Z') expected = - \\·························A························· - \\························B·B························ - \\·······················C···C······················· - \\······················D·····D······················ - \\·····················E·······E····················· - \\····················F·········F···················· - \\···················G···········G··················· - \\··················H·············H·················· - \\·················I···············I················· - \\················J·················J················ - \\···············K···················K··············· - \\··············L·····················L·············· - \\·············M·······················M············· - \\············N·························N············ - \\···········O···························O··········· - \\··········P·····························P·········· - \\·········Q·······························Q········· - \\········R·································R········ - \\·······S···································S······· - \\······T·····································T······ - \\·····U·······································U····· - \\····V·········································V···· - \\···W···········································W··· - \\··X·············································X·· - \\·Y···············································Y· - \\Z·················································Z - \\·Y···············································Y· - \\··X·············································X·· - \\···W···········································W··· - \\····V·········································V···· - \\·····U·······································U····· - \\······T·····································T······ - \\·······S···································S······· - \\········R·································R········ - \\·········Q·······························Q········· - \\··········P·····························P·········· - \\···········O···························O··········· - \\············N·························N············ - \\·············M·······················M············· - \\··············L·····················L·············· - \\···············K···················K··············· - \\················J·················J················ - \\·················I···············I················· - \\··················H·············H·················· - \\···················G···········G··················· - \\····················F·········F···················· - \\·····················E·······E····················· - \\······················D·····D······················ - \\·······················C···C······················· - \\························B·B························ - \\·························A························· - .replace_each( - "·", - " ", - ) + \\ A + \\ B B + \\ C C + \\ D D + \\ E E + \\ F F + \\ G G + \\ H H + \\ I I + \\ J J + \\ K K + \\ L L + \\ M M + \\ N N + \\ O O + \\ P P + \\ Q Q + \\ R R + \\ S S + \\ T T + \\ U U + \\ V V + \\ W W + \\ X X + \\ Y Y + \\Z Z + \\ Y Y + \\ X X + \\ W W + \\ V V + \\ U U + \\ T T + \\ S S + \\ R R + \\ Q Q + \\ P P + \\ O O + \\ N N + \\ M M + \\ L L + \\ K K + \\ J J + \\ I I + \\ H H + \\ G G + \\ F F + \\ E E + \\ D D + \\ C C + \\ B B + \\ A + result == expected } From 06e7ed3b83b674836ace0b6efe9e7b363a9b1630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 12:58:35 +1200 Subject: [PATCH 076/162] Update exercise to new compiler: perfect-numbers --- .../perfect-numbers/.meta/Example.roc | 55 ++++++++++--------- .../perfect-numbers/PerfectNumbers.roc | 7 ++- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/exercises/practice/perfect-numbers/.meta/Example.roc b/exercises/practice/perfect-numbers/.meta/Example.roc index 6da750bc..45c43f31 100644 --- a/exercises/practice/perfect-numbers/.meta/Example.roc +++ b/exercises/practice/perfect-numbers/.meta/Example.roc @@ -1,28 +1,33 @@ PerfectNumbers :: {}.{ - classify : U64 -> Result [Abundant, Deficient, Perfect] [NumberArgIsZero] - classify = |number| - sum = aliquot_sum(number)? - if sum == number then - Ok(Perfect) - else if sum > number then - Ok(Abundant) - else - Ok(Deficient) + classify : U64 -> Try([Abundant, Deficient, Perfect], [NumberArgIsZero]) + classify = |number| { + sum = aliquot_sum(number)? + if sum == number { + Ok(Perfect) + } else if sum > number { + Ok(Abundant) + } else { + Ok(Deficient) + } + } } - -aliquot_sum : U64 -> Result U64 [NumberArgIsZero] -aliquot_sum = |number| - if number == 0 then - Err(NumberArgIsZero) - else if number == 1 then - Ok(0) # edge case - else - Ok( - ( - # There are more efficient algorithms, but this is the simplest - List.range({ start: At(1), end: At((number // 2)) }) - |> List.keep_if(|d| number % d == 0) - |> List.sum - ), - ) +aliquot_sum : U64 -> Try(U64, [NumberArgIsZero]) +aliquot_sum = |number| { + if number == 0 { + Err(NumberArgIsZero) + } else if number == 1 { + Ok(0) + } else { + (1..=(number // 2)) + .fold( + 0, + |acc, d| if number % d == 0 { + acc + d + } else { + acc + }, + ) + ->Ok() + } +} diff --git a/exercises/practice/perfect-numbers/PerfectNumbers.roc b/exercises/practice/perfect-numbers/PerfectNumbers.roc index ee9c1f84..455378e7 100644 --- a/exercises/practice/perfect-numbers/PerfectNumbers.roc +++ b/exercises/practice/perfect-numbers/PerfectNumbers.roc @@ -1,5 +1,6 @@ PerfectNumbers :: {}.{ - classify : U64 -> Result [Abundant, Deficient, Perfect] _ - classify = |number| - crash("Please implement the 'classify' function") + classify : U64 -> Try([Abundant, Deficient, Perfect], [NumberArgIsZero]) + classify = |number| { + crash "Please implement the 'classify' function" + } } From f762954766303744fa0807d4ae27e7c01a787fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 13:02:29 +1200 Subject: [PATCH 077/162] Update exercise to new compiler: hexadecimal --- .../practice/hexadecimal/.meta/Example.roc | 67 ++++++++++++------- .../practice/hexadecimal/Hexadecimal.roc | 7 +- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/exercises/practice/hexadecimal/.meta/Example.roc b/exercises/practice/hexadecimal/.meta/Example.roc index b5f60815..33f77213 100644 --- a/exercises/practice/hexadecimal/.meta/Example.roc +++ b/exercises/practice/hexadecimal/.meta/Example.roc @@ -1,29 +1,46 @@ Hexadecimal :: {}.{ - parse : Str -> Result U64 _ - parse = |string| - if string == "" then - Err(InvalidNumStr) - else - string - |> Str.to_utf8 - |> List.walk_try( - 0, - |number, char| - nibble = parse_nibble(char)? - if number > 0xfffffffffffffff then - Err(InvalidNumStr) - else - number |> Num.shift_left_by(4) |> Num.add(nibble) |> Ok, - ) + parse : Str -> Try(U64, [InvalidNumStr]) + parse = |string| { + if string == "" { + Err(InvalidNumStr) + } else { + digits = string.to_utf8()->map_try(parse_nibble)? + digits.fold_until( + Ok(0), + |acc_res, nibble| { + match acc_res { + Ok(number) => { + if number > 0xfffffffffffffff { + Break(Err(InvalidNumStr)) + } else { + Continue(Ok(U64.shift_left_by(number, 4) + nibble)) + } + } + Err(err) => Break(Err(err)) + } + }, + ) + } + } } +parse_nibble : U8 -> Try(U64, _) +parse_nibble = |char| { + if char >= '0' and char <= '9' { + Ok((char - '0').to_u64()) + } else if char >= 'A' and char <= 'F' { + Ok((char - 'A' + 10).to_u64()) + } else if char >= 'a' and char <= 'f' { + Ok((char - 'a' + 10).to_u64()) + } else { + Err(InvalidNumStr) + } +} -parse_nibble = |char| - if char >= '0' and char <= '9' then - Ok((char - '0' |> Num.to_u64)) - else if char >= 'A' and char <= 'F' then - Ok((char - 'A' + 10 |> Num.to_u64)) - else if char >= 'a' and char <= 'f' then - Ok((char - 'a' + 10 |> Num.to_u64)) - else - Err(InvalidNumStr) +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/hexadecimal/Hexadecimal.roc b/exercises/practice/hexadecimal/Hexadecimal.roc index 5c170b72..f593b960 100644 --- a/exercises/practice/hexadecimal/Hexadecimal.roc +++ b/exercises/practice/hexadecimal/Hexadecimal.roc @@ -1,5 +1,6 @@ Hexadecimal :: {}.{ - parse : Str -> Result U64 _ - parse = |string| - crash("Please implement the 'parse' function") + parse : Str -> Try(U64, [InvalidNumStr]) + parse = |string| { + crash "Please implement the 'parse' function" + } } From 97d0eb748e54072a0a7d770455003aa84982c094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 13:07:02 +1200 Subject: [PATCH 078/162] Update exercise to new compiler: killer-sudoku-helper --- .../killer-sudoku-helper/.meta/Example.roc | 78 +++++++++++++------ .../killer-sudoku-helper/.meta/template.j2 | 5 +- .../KillerSudokuHelper.roc | 12 +-- .../killer-sudoku-helper-test.roc | 26 +++---- 4 files changed, 74 insertions(+), 47 deletions(-) diff --git a/exercises/practice/killer-sudoku-helper/.meta/Example.roc b/exercises/practice/killer-sudoku-helper/.meta/Example.roc index 25e623e8..4a6dfc13 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/Example.roc +++ b/exercises/practice/killer-sudoku-helper/.meta/Example.roc @@ -1,27 +1,57 @@ KillerSudokuHelper :: {}.{ - combinations : { sum : U8, size : U8, exclude ?? List U8 } -> List Combination - combinations = |{ sum, size, exclude ?? [] }| - help = |target, digits| - if target == 0 then - [[]] - else - when digits is - [] -> [] - [single] -> if single == target then [[single]] else [] - [first, .. as rest] -> - if first > target then - [] - else - help((target - first), rest) - |> List.map(|combi| combi |> List.append(first)) - |> List.concat(help(target, rest)) - available_digits = - [1, 2, 3, 4, 5, 6, 7, 8, 9] - |> List.drop_if(|digit| exclude |> List.contains(digit)) - help(sum, available_digits) - |> List.keep_if(|combi| List.len(combi) == size |> Num.to_u64) - |> List.map(List.reverse) -} + Combination : List(U8) + combinations : { sum : U8, size : U8, exclude : List(U8) } -> List(Combination) + combinations = |{ sum, size, exclude }| { + help = |target, digits| { + if target == 0 { + [[]] + } else { + match digits { + [] => [] + [single] => { + if single == target { + [[single]] + } else { + [] + } + } + [first, .. as rest] => { + if first > target { + [] + } else { + help((target - first), rest) + .map( + |combi| combi.append(first), + ) + .concat( + help(target, rest), + ) + } + } + } + } + } + available_digits = + [1, 2, 3, 4, 5, 6, 7, 8, 9] + .drop_if( + |digit| exclude.contains(digit), + ) + help(sum, available_digits) + .keep_if( + |combi| combi.len() == size.to_u64(), + ) + .map( + reverse, + ) + } +} -Combination : List U8 +# List.reverse should soon be available in Roc's builtins +reverse : List(a) -> List(a) +reverse = |list| { + match list { + [] => [] + [first, .. as rest] => reverse(rest).append(first) + } +} diff --git a/exercises/practice/killer-sudoku-helper/.meta/template.j2 b/exercises/practice/killer-sudoku-helper/.meta/template.j2 index 162e94fb..068045d8 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/template.j2 +++ b/exercises/practice/killer-sudoku-helper/.meta/template.j2 @@ -5,10 +5,7 @@ import {{ exercise | to_pascal }} exposing [combinations] {% macro to_cage(cage) -%} -{ sum: {{ cage["sum"] }}, size: {{ cage["size"] }} -{%- if cage["exclude"] != [] -%} -, exclude: {{ cage["exclude"] | to_roc }} -{%- endif %} } +{ sum: {{ cage["sum"] }}, size: {{ cage["size"] }}, exclude: {{ cage["exclude"] | to_roc }} } {%- endmacro %} {% for supercase in cases %} diff --git a/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.roc b/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.roc index 92e9e118..be7f7c12 100644 --- a/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.roc +++ b/exercises/practice/killer-sudoku-helper/KillerSudokuHelper.roc @@ -1,8 +1,8 @@ KillerSudokuHelper :: {}.{ - combinations : { sum : U8, size : U8, exclude ?? List U8 } -> List Combination - combinations = |{ sum, size, exclude ?? [] }| - crash("Please implement the 'combinations' function") -} - + Combination : List(U8) -Combination : List U8 + combinations : { sum : U8, size : U8, exclude : List(U8) } -> List(Combination) + combinations = |{ sum, size, exclude }| { + crash "Please implement the 'combinations' function" + } +} diff --git a/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc index 8fcb76d4..c07be02b 100644 --- a/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc +++ b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc @@ -1,79 +1,79 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/killer-sudoku-helper/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-18 import KillerSudokuHelper exposing [combinations] ## Trivial 1-digit cages # 1 expect { - result = combinations({ sum: 1, size: 1 }) + result = combinations({ sum: 1, size: 1, exclude: [] }) result == [[1]] } # 2 expect { - result = combinations({ sum: 2, size: 1 }) + result = combinations({ sum: 2, size: 1, exclude: [] }) result == [[2]] } # 3 expect { - result = combinations({ sum: 3, size: 1 }) + result = combinations({ sum: 3, size: 1, exclude: [] }) result == [[3]] } # 4 expect { - result = combinations({ sum: 4, size: 1 }) + result = combinations({ sum: 4, size: 1, exclude: [] }) result == [[4]] } # 5 expect { - result = combinations({ sum: 5, size: 1 }) + result = combinations({ sum: 5, size: 1, exclude: [] }) result == [[5]] } # 6 expect { - result = combinations({ sum: 6, size: 1 }) + result = combinations({ sum: 6, size: 1, exclude: [] }) result == [[6]] } # 7 expect { - result = combinations({ sum: 7, size: 1 }) + result = combinations({ sum: 7, size: 1, exclude: [] }) result == [[7]] } # 8 expect { - result = combinations({ sum: 8, size: 1 }) + result = combinations({ sum: 8, size: 1, exclude: [] }) result == [[8]] } # 9 expect { - result = combinations({ sum: 9, size: 1 }) + result = combinations({ sum: 9, size: 1, exclude: [] }) result == [[9]] } ## Cage with sum 45 contains all digits 1:9 expect { - result = combinations({ sum: 45, size: 9 }) + result = combinations({ sum: 45, size: 9, exclude: [] }) result == [[1, 2, 3, 4, 5, 6, 7, 8, 9]] } ## Cage with only 1 possible combination expect { - result = combinations({ sum: 7, size: 3 }) + result = combinations({ sum: 7, size: 3, exclude: [] }) result == [[1, 2, 4]] } ## Cage with several combinations expect { - result = combinations({ sum: 10, size: 2 }) + result = combinations({ sum: 10, size: 2, exclude: [] }) result == [[1, 9], [2, 8], [3, 7], [4, 6]] } From c939b05fa43b48015a257fefd4fb6aaabb1e0ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 13:16:39 +1200 Subject: [PATCH 079/162] Update exercise to new compiler: largest-series-product --- .../largest-series-product/.meta/Example.roc | 61 +++++++++++-------- .../LargestSeriesProduct.roc | 7 ++- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/exercises/practice/largest-series-product/.meta/Example.roc b/exercises/practice/largest-series-product/.meta/Example.roc index c3ff36eb..7e115041 100644 --- a/exercises/practice/largest-series-product/.meta/Example.roc +++ b/exercises/practice/largest-series-product/.meta/Example.roc @@ -1,27 +1,38 @@ LargestSeriesProduct :: {}.{ - largest_product : Str, U64 -> Result U64 [SpanWasTooLarge, InvalidDigit] - largest_product = |digits, span| - if span == 0 then - Ok(1) - else - chars = digits |> Str.to_utf8 - if List.len(chars) < span then - Err(SpanWasTooLarge) - else if chars |> List.any(|char| char < '0' or char > '9') then - Err(InvalidDigit) - else - List.range({ start: At(0), end: At((List.len(chars) - span)) }) - |> List.map( - |start_index| - chars - |> List.sublist({ start: start_index, len: span }) - |> List.walk( - 1, - |product, char| - product * (char - '0' |> Num.to_u64), - ), - ) - |> List.max - |> Result.on_err(|ListWasEmpty| crash("Unreachable: the list cannot be empty here")) - + largest_product : Str, U64 -> Try(U64, [SpanWasTooLarge, InvalidDigit]) + largest_product = |digits, span| { + if span == 0 { + Ok(1) + } else { + chars = digits.to_utf8() + if chars.len() < span { + Err(SpanWasTooLarge) + } else if chars.any(|char| char < '0' or char > '9') { + Err(InvalidDigit) + } else { + (0..=(chars.len() - span)) + .map( + |start_index| { + chars + .sublist( + { start: start_index, len: span }, + ) + .fold( + 1, + |product, char| { + product * (char - '0').to_u64() + }, + ) + }, + ) + ->List.from_iter() + .max() # TODO: replace with Iter.max when available + .map_err( + |_| { + crash "Unreachable: the list cannot be empty here" + }, + ) + } + } + } } diff --git a/exercises/practice/largest-series-product/LargestSeriesProduct.roc b/exercises/practice/largest-series-product/LargestSeriesProduct.roc index b24faa85..313c7a4b 100644 --- a/exercises/practice/largest-series-product/LargestSeriesProduct.roc +++ b/exercises/practice/largest-series-product/LargestSeriesProduct.roc @@ -1,5 +1,6 @@ LargestSeriesProduct :: {}.{ - largest_product : Str, U64 -> Result U64 _ - largest_product = |digits, span| - crash("Please implement the 'largest_product' function") + largest_product : Str, U64 -> Try(U64, [SpanWasTooLarge, InvalidDigit]) + largest_product = |digits, span| { + crash "Please implement the 'largest_product' function" + } } From a51f403d31c0c2f794b9e066fad6ec8ec8587570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 14:27:03 +1200 Subject: [PATCH 080/162] Fix error in sum-of-multiples --- exercises/practice/sum-of-multiples/.meta/Example.roc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/exercises/practice/sum-of-multiples/.meta/Example.roc b/exercises/practice/sum-of-multiples/.meta/Example.roc index 4f9bfc2d..286982a8 100644 --- a/exercises/practice/sum-of-multiples/.meta/Example.roc +++ b/exercises/practice/sum-of-multiples/.meta/Example.roc @@ -2,10 +2,8 @@ SumOfMultiples :: {}.{ sum_of_multiples : List(U64), U64 -> U64 sum_of_multiples = |factors, limit| { factors - .keep_if( - |factor| factor > 0, - ) - ->join_map(|factor| (factor.. 0) + ->join_map(|factor| (factor..List.from_iter()) ->Set.from_list() .to_list() .sum() From 389e5afd0730fedd5d4307851bb1a686aaded422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 14:30:17 +1200 Subject: [PATCH 081/162] Update exercise to new compiler: strain --- exercises/practice/strain/.meta/Example.roc | 56 +++++++++++++-------- exercises/practice/strain/Strain.roc | 14 +++--- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/exercises/practice/strain/.meta/Example.roc b/exercises/practice/strain/.meta/Example.roc index 77da32df..c4a4ceb8 100644 --- a/exercises/practice/strain/.meta/Example.roc +++ b/exercises/practice/strain/.meta/Example.roc @@ -1,27 +1,39 @@ Strain :: {}.{ - keep : List a, (a -> Bool) -> List a - keep = |list, predicate| - loop = |sub_list, kept_items| - when sub_list is - [] -> kept_items - [first, .. as rest] -> - if predicate(first) then - rest |> loop(List.append(kept_items, first)) - else - rest |> loop(kept_items) + keep : List(a), (a -> Bool) -> List(a) + keep = |list, predicate| { + loop = |sub_list, kept_items, predicate2| { + match sub_list { + [] => kept_items + [first, .. as rest] => { + if predicate2(first) { + rest->loop(kept_items.append(first), predicate2) + } else { + rest->loop(kept_items, predicate2) + } + } + } + } - loop(list, []) + loop(list, [], predicate) + } - discard : List a, (a -> Bool) -> List a - discard = |list, predicate| - loop = |sub_list, non_discarded_items| - when sub_list is - [] -> non_discarded_items - [first, .. as rest] -> - if predicate(first) then - rest |> loop(non_discarded_items) - else - rest |> loop(List.append(non_discarded_items, first)) + discard : List(a), (a -> Bool) -> List(a) + discard = |list, predicate| { + loop = |sub_list, non_discarded_items, predicate2| { + match sub_list { + [] => non_discarded_items + [first, .. as rest] => { + if predicate2(first) { + rest->loop(non_discarded_items, predicate2) + } else { + rest->loop(non_discarded_items.append(first), predicate2) + } + } + } + } - loop(list, []) + loop(list, [], predicate) + } } + +# TODO: remove predicate2 once https://github.com/roc-lang/roc/issues/9690 is fixed diff --git a/exercises/practice/strain/Strain.roc b/exercises/practice/strain/Strain.roc index b32a95c4..197fb440 100644 --- a/exercises/practice/strain/Strain.roc +++ b/exercises/practice/strain/Strain.roc @@ -1,9 +1,11 @@ Strain :: {}.{ - keep : List a, (a -> Bool) -> List a - keep = |list, predicate| - crash("Please implement the 'keep' function") + keep : List(a), (a -> Bool) -> List(a) + keep = |list, predicate| { + crash "Please implement the 'keep' function" + } - discard : List a, (a -> Bool) -> List a - discard = |list, predicate| - crash("Please implement the 'discard' function") + discard : List(a), (a -> Bool) -> List(a) + discard = |list, predicate| { + crash "Please implement the 'discard' function" + } } From de76368207f61b4388269953db9715cc5a0907b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 14:52:01 +1200 Subject: [PATCH 082/162] Update exercise to new compiler: isbn-verifier --- .../practice/isbn-verifier/.meta/Example.roc | 81 +++++++++++++------ .../practice/isbn-verifier/IsbnVerifier.roc | 7 +- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/exercises/practice/isbn-verifier/.meta/Example.roc b/exercises/practice/isbn-verifier/.meta/Example.roc index 9290d95f..f3484493 100644 --- a/exercises/practice/isbn-verifier/.meta/Example.roc +++ b/exercises/practice/isbn-verifier/.meta/Example.roc @@ -1,28 +1,61 @@ IsbnVerifier :: {}.{ - is_valid : Str -> Bool - is_valid = |isbn| - chars = - isbn - |> Str.to_utf8 - |> List.drop_if(|char| char == '-') - if List.len(chars) != 10 then - Bool.False - else - values = - chars - |> List.map_with_index(char_value) - |> List.keep_oks(|v| v) - List.len(values) == 10 and (List.sum(values)) % 11 == 0 + is_valid : Str -> Bool + is_valid = |isbn| { + chars = + isbn + .to_utf8() + .drop_if( + |char| char == '-', + ) + if chars.len() != 10 { + Bool.False + } else { + values : List(U64) + values = + chars + .map_with_index( + char_value, + ) + ->keep_oks(|v| v) + values.len() == 10 and (values.sum()) % 11 == 0 + } + } } +char_value : U8, U64 -> Try(U64, _) +char_value = |char, index| { + if char == 'X' { + if index == 9 { + Ok(10) + } else { + Err(InvalidIsbnBadX) + } + } else if char >= '0' and char <= '9' { + Ok((10 - index) * (char - '0').to_u64()) + } else { + Err(InvalidIsbnBadChar) + } +} + +# The following functions should soon be available in Roc's builtins +keep_oks = |iter, func| { + iter + ->join_map( + |item| { + match func(item) { + Ok(result) => [result] + Err(_) => [] + } + }, + ) +} -char_value = |char, index| - if char == 'X' then - if index == 9 then - Ok(10) - else - Err(InvalidIsbnBadX) - else if char >= '0' and char <= '9' then - (10 - index) * (Num.int_cast((char - '0'))) |> Ok - else - Err(InvalidIsbnBadChar) +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} diff --git a/exercises/practice/isbn-verifier/IsbnVerifier.roc b/exercises/practice/isbn-verifier/IsbnVerifier.roc index d6cd0517..12954087 100644 --- a/exercises/practice/isbn-verifier/IsbnVerifier.roc +++ b/exercises/practice/isbn-verifier/IsbnVerifier.roc @@ -1,5 +1,6 @@ IsbnVerifier :: {}.{ - is_valid : Str -> Bool - is_valid = |isbn| - crash("Please implement the 'is_valid' function") + is_valid : Str -> Bool + is_valid = |isbn| { + crash "Please implement the 'is_valid' function" + } } From 011580658543edfead49ed5ee7fdb7b6fa7dd0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 14:58:24 +1200 Subject: [PATCH 083/162] Update exercise to new compiler: nth-prime --- .../practice/nth-prime/.meta/Example.roc | 61 +++++++++++-------- exercises/practice/nth-prime/NthPrime.roc | 7 ++- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/exercises/practice/nth-prime/.meta/Example.roc b/exercises/practice/nth-prime/.meta/Example.roc index 81c32578..f8d94b5f 100644 --- a/exercises/practice/nth-prime/.meta/Example.roc +++ b/exercises/practice/nth-prime/.meta/Example.roc @@ -1,28 +1,37 @@ NthPrime :: {}.{ - prime : U64 -> Result U64 [NoPrime0] - prime = |number| - if number == 0 then - Err(NoPrime0) - else if number == 1 then - Ok(2) - else if number == 2 then - Ok(3) - else - find_prime = |primes, index| - if List.len(primes) == number then - primes - else - next_index = index + 2 - new_primes = - if primes |> List.any(|p| next_index % p == 0) then - primes - else - primes |> List.append(next_index) - find_prime(new_primes, next_index) - find_prime([2, 3, 5], 5) - |> List.last - |> Result.map_err( - |_| - crash("Unreachable: list cannot be empty"), - ) + prime : U64 -> Try(U64, [NoPrime0]) + prime = |number| { + if number == 0 { + Err(NoPrime0) + } else if number == 1 { + Ok(2) + } else if number == 2 { + Ok(3) + } else { + find_prime = |primes, index, number2| { + if primes.len() == number2 { + primes + } else { + next_index = index + 2 + new_primes = { + if primes.any(|p| next_index % p == 0) { + primes + } else { + primes.append(next_index) + } + } + find_prime(new_primes, next_index, number2) + } + } + find_prime([2, 3, 5], 5, number) + .last() + .map_err( + |_| { + crash "Unreachable: list cannot be empty" + }, + ) + } + } } + +# TODO: remove number2 once https://github.com/roc-lang/roc/issues/9690 is fixed diff --git a/exercises/practice/nth-prime/NthPrime.roc b/exercises/practice/nth-prime/NthPrime.roc index 0b3eee81..7673807b 100644 --- a/exercises/practice/nth-prime/NthPrime.roc +++ b/exercises/practice/nth-prime/NthPrime.roc @@ -1,5 +1,6 @@ NthPrime :: {}.{ - prime : U64 -> Result U64 _ - prime = |number| - crash("Please implement the 'prime' function") + prime : U64 -> Try(U64, _) + prime = |number| { + crash "Please implement the 'prime' function" + } } From 2d6c4fb877f7f1f05019d15a120486a4730c6bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 15:09:36 +1200 Subject: [PATCH 084/162] Update exercise to new compiler: phone-number --- .../practice/phone-number/.meta/Example.roc | 55 ++++++++++--------- .../practice/phone-number/PhoneNumber.roc | 7 ++- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/exercises/practice/phone-number/.meta/Example.roc b/exercises/practice/phone-number/.meta/Example.roc index 25f06b2e..8cb4d9a5 100644 --- a/exercises/practice/phone-number/.meta/Example.roc +++ b/exercises/practice/phone-number/.meta/Example.roc @@ -1,29 +1,34 @@ PhoneNumber :: {}.{ - clean : Str -> Result Str [InvalidNumber] - clean = |phone_number| - digits = - phone_number - |> Str.to_utf8 - |> List.keep_if(|c| c >= '0' and c <= '9') + clean : Str -> Try(Str, [InvalidNumber]) + clean = |phone_number| { + digits = + phone_number + .to_utf8() + .keep_if(|c| c >= '0' and c <= '9') - num_digits = List.len(digits) - if num_digits == 10 then - digits |> check_number - else if num_digits == 11 then - when digits is - ['1', .. as rest] -> rest |> check_number - _ -> Err(InvalidNumber) # only country code 1 is supported - else - Err(InvalidNumber) + num_digits = digits.len() + if num_digits == 10 { + check_number(digits) + } else if num_digits == 11 { + match digits { + ['1', .. as rest] => check_number(rest) + _ => Err(InvalidNumber) + } + } else { + Err(InvalidNumber) + } + } } -check_number : List U8 -> Result Str [InvalidNumber] -check_number = |digits| - when digits is - [a1, _, _, e1, ..] if a1 == '0' or a1 == '1' or e1 == '0' or e1 == '1' -> - Err(InvalidNumber) # area code and exchange must not start with 0 or 1 - - _ -> - when digits |> Str.from_utf8 is - Ok(number) -> Ok(number) - Err(BadUtf8(_)) -> crash("Unreachable: a string of digits is valid UTF8") +check_number : List(U8) -> Try(Str, [InvalidNumber]) +check_number = |digits| { + match digits { + [a1, _, _, e1, ..] if a1 == '0' or a1 == '1' or e1 == '0' or e1 == '1' => Err(InvalidNumber) + _ => match Str.from_utf8(digits) { + Ok(number) => Ok(number) + Err(BadUtf8(_)) => { + crash "Unreachable: a string of digits is valid UTF8" + } + } + } +} diff --git a/exercises/practice/phone-number/PhoneNumber.roc b/exercises/practice/phone-number/PhoneNumber.roc index 564a6e0a..8d9f86f2 100644 --- a/exercises/practice/phone-number/PhoneNumber.roc +++ b/exercises/practice/phone-number/PhoneNumber.roc @@ -1,5 +1,6 @@ PhoneNumber :: {}.{ - clean : Str -> Result Str _ - clean = |phone_number| - crash("Please implement the 'clean' function") + clean : Str -> Try(Str, _) + clean = |phone_number| { + crash "Please implement the 'clean' function" + } } From 5501ca6193d54dd1198a677c9dcd5e62a684b51a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 15:11:56 +1200 Subject: [PATCH 085/162] Update exercise to new compiler: sublist --- exercises/practice/sublist/.meta/Example.roc | 76 +++++++++++++------- exercises/practice/sublist/Sublist.roc | 7 +- 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/exercises/practice/sublist/.meta/Example.roc b/exercises/practice/sublist/.meta/Example.roc index de9f8a2e..e4db77d4 100644 --- a/exercises/practice/sublist/.meta/Example.roc +++ b/exercises/practice/sublist/.meta/Example.roc @@ -1,29 +1,57 @@ Sublist :: {}.{ - sublist : List U8, List U8 -> [Equal, Sublist, Superlist, Unequal] - sublist = |list1, list2| - when List.len(list1) |> Num.compare(List.len(list2)) is - GT -> - when list2 |> sublist(list1) is - Sublist -> Superlist - Unequal -> Unequal - Superlist -> crash("Unreachable: list 2 is shorter than list 1") - Equal -> crash("Unreachable: list 1 and 2 don't have the same length") + sublist : List(U8), List(U8) -> [Equal, Sublist, Superlist, Unequal] + sublist = |list1, list2| { + match list1.len()->compare(list2.len()) { + GT => { + match sublist(list2, list1) { + Sublist => Superlist + Unequal => Unequal + Superlist => { + crash "Unreachable: list 2 is shorter than list 1" + } + Equal => { + crash "Unreachable: list 1 and 2 don't have the same length" + } + } + } - EQ -> - if list1 == list2 then Equal else Unequal + EQ => { + if list1 == list2 { + Equal + } else { + Unequal + } + } - LT -> - length_diff = List.len(list2) - List.len(list1) - maybe_equal_index = - List.range({ start: At(0), end: At(length_diff) }) - |> List.find_first( - |start| - list2 - |> List.sublist({ start, len: List.len(list1) }) - |> Bool.is_eq(list1), - ) + LT => { + length_diff = list2.len() - list1.len() + maybe_equal_index = + (0..=length_diff) + .fold([], |acc, x| acc.append(x)) + .find_first( + |start| { + list2 + .sublist({ start, len: list1.len() }) + == list1 + }, + ) - when maybe_equal_index is - Ok(_) -> Sublist - Err(NotFound) -> Unequal + match maybe_equal_index { + Ok(_) => Sublist + Err(NotFound) => Unequal + } + } + } + } +} + +# The following function should soon be available in Roc's builtins +compare = |a, b| { + if a < b { + LT + } else if a > b { + GT + } else { + EQ + } } diff --git a/exercises/practice/sublist/Sublist.roc b/exercises/practice/sublist/Sublist.roc index 3edbb4d9..658084f1 100644 --- a/exercises/practice/sublist/Sublist.roc +++ b/exercises/practice/sublist/Sublist.roc @@ -1,5 +1,6 @@ Sublist :: {}.{ - sublist : List U8, List U8 -> [Equal, Sublist, Superlist, Unequal] - sublist = |list1, list2| - crash("Please implement the 'sublist' function") + sublist : List(U8), List(U8) -> [Equal, Sublist, Superlist, Unequal] + sublist = |list1, list2| { + crash "Please implement the 'sublist' function" + } } From 56958c1ae457c1a7777d8e77ba398438d1cbcc39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 17:09:39 +1200 Subject: [PATCH 086/162] Update exercise to new compiler: queen-attack --- .../queen-attack/.docs/instructions.append.md | 6 +- .../practice/queen-attack/.meta/Example.roc | 59 +++++---- .../practice/queen-attack/.meta/plugins.py | 2 +- .../practice/queen-attack/.meta/template.j2 | 26 ++-- .../practice/queen-attack/QueenAttack.roc | 39 +++--- .../queen-attack/queen-attack-test.roc | 125 +++++------------- 6 files changed, 108 insertions(+), 149 deletions(-) diff --git a/exercises/practice/queen-attack/.docs/instructions.append.md b/exercises/practice/queen-attack/.docs/instructions.append.md index 39447833..dea983c8 100644 --- a/exercises/practice/queen-attack/.docs/instructions.append.md +++ b/exercises/practice/queen-attack/.docs/instructions.append.md @@ -6,14 +6,14 @@ In Chess, rows are called "ranks", and columns are called "files". Ranks range from 1 to 8, while files range from A to H. In this exercise, `Square` is an opaque type which represents a square on a chessboard. -It uses 0-indexed `row` & `column` fields internally, and it guarantees that they are always valid (i.e., from 0 to 7). +It uses 0-indexed `row` and `column` fields internally, and it guarantees that they are always valid (i.e., from 0 to 7). The `create` function takes a `Str` as input, representing a valid square on the chessboard using the official notation, such as `"C5"`. The `create` function must parse this `Str`, verify that it represents a valid square, and if so it must return a `Square` value containing the appropriate `row` and `column` fields. Rank 1 corresponds to row 0, and rank 8 corresponds to row 7. -For example, `create "C5"` must return `@Square { row : 2, column : 3 }`. +For example, `create("C5")` must return a square with row 4 and column 2. -The `QueenAttack` module also exposes handy `rank` & `file` functions. +The `Square` type also exposes handy `rank` and `file` methods. In the example above, `rank` must return the integer `5`, and `file` must return the character `'C'`. Lastly, the `queen_can_attack` function takes two different `Square` values and checks whether or not queens placed on these squares can attack each other. diff --git a/exercises/practice/queen-attack/.meta/Example.roc b/exercises/practice/queen-attack/.meta/Example.roc index 70a1733c..e0aef4c8 100644 --- a/exercises/practice/queen-attack/.meta/Example.roc +++ b/exercises/practice/queen-attack/.meta/Example.roc @@ -1,30 +1,39 @@ QueenAttack :: {}.{ - rank : Square -> U8 - rank = |@Square({ row, column: _ })| 8 - row + Square :: { row : U8, column : U8 }.{ + rank : Square -> U8 + rank = |{ row }| row + 1 - file : Square -> U8 - file = |@Square({ row: _, column })| column + 'A' + file : Square -> U8 + file = |{ column }| column + 'A' - create : Str -> Result Square [InvalidSquare] - create = |square_str| - chars = square_str |> Str.to_utf8 - if List.len(chars) != 2 then - Err(InvalidSquare) - else - file_char = chars |> List.get(0) |> Result.map_err(|OutOfBounds| InvalidSquare)? - rank_char = chars |> List.get(1) |> Result.map_err(|OutOfBounds| InvalidSquare)? - if file_char < 'A' or file_char > 'H' or rank_char < '1' or rank_char > '8' then - Err(InvalidSquare) - else - Ok(@Square({ row: 7 - (rank_char - '1'), column: file_char - 'A' })) + create : Str -> Try(Square, [InvalidSquare]) + create = |square_str| { + chars = square_str.to_utf8() + if chars.len() != 2 { + Err(InvalidSquare) + } else { + file_char = chars.get(0).map_err(|OutOfBounds| InvalidSquare)? + rank_char = chars.get(1).map_err(|OutOfBounds| InvalidSquare)? + if file_char < 'A' or file_char > 'H' or rank_char < '1' or rank_char > '8' { + Err(InvalidSquare) + } else { + Ok({ row: rank_char - '1', column: file_char - 'A' }) + } + } + } - queen_can_attack : Square, Square -> Bool - queen_can_attack = |@Square({ row: r1, column: c1 }), @Square({ row: r2, column: c2 })| - abs_diff = |u, v| if u < v then v - u else u - v - row_diff = abs_diff(r1, r2) - column_diff = abs_diff(c1, c2) - row_diff == 0 or column_diff == 0 or row_diff == column_diff + queen_can_attack : Square, Square -> Bool + queen_can_attack = |{ row: r1, column: c1 }, { row: r2, column: c2 }| { + abs_diff = |u, v| { + if u < v { + v - u + } else { + u - v + } + } + rank_diff = abs_diff(r1, r2) + file_diff = abs_diff(c1, c2) + rank_diff == 0 or file_diff == 0 or rank_diff == file_diff + } + } } - - -Square := { row : U8, column : U8 } diff --git a/exercises/practice/queen-attack/.meta/plugins.py b/exercises/practice/queen-attack/.meta/plugins.py index 11b94b37..f31d9c62 100644 --- a/exercises/practice/queen-attack/.meta/plugins.py +++ b/exercises/practice/queen-attack/.meta/plugins.py @@ -5,7 +5,7 @@ def to_square(queen): def to_rank(row): - return 8 - row + return row + 1 def to_file(column): diff --git a/exercises/practice/queen-attack/.meta/template.j2 b/exercises/practice/queen-attack/.meta/template.j2 index c1562b53..ea9d3a78 100644 --- a/exercises/practice/queen-attack/.meta/template.j2 +++ b/exercises/practice/queen-attack/.meta/template.j2 @@ -2,7 +2,8 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [create, rank, file, queen_can_attack] +import {{ exercise | to_pascal }} +create = {{ exercise | to_pascal }}.Square.create {% for supercase in cases %} ## @@ -14,15 +15,15 @@ import {{ exercise | to_pascal }} exposing [create, rank, file, queen_can_attack {%- if case["property"] == "create" %} {%- if case["expected"] == 0 %} expect { - maybe_square = create("{{ plugins.to_square(case["input"]["queen"]) }}") - result = maybe_square -> Result.try( |square| Ok(rank square) ) - result == Ok({{ plugins.to_rank(case["input"]["queen"]["position"]["row"]) }}) + square = create("{{ plugins.to_square(case["input"]["queen"]) }}")? + result = square.rank() + result == {{ plugins.to_rank(case["input"]["queen"]["position"]["row"]) }} } expect { - maybe_square = create("{{ plugins.to_square(case["input"]["queen"]) }}") - result = maybe_square -> Result.try( |square| Ok(file square) ) - result == Ok('{{ plugins.to_file(case["input"]["queen"]["position"]["column"]) }}') + square = create("{{ plugins.to_square(case["input"]["queen"]) }}")? + result = square.file() + result == '{{ plugins.to_file(case["input"]["queen"]["position"]["column"]) }}' } {%- else %} expect { @@ -32,14 +33,9 @@ expect { {%- endif %} {%- elif case["property"] == "canAttack" %} expect { - maybe_square1 = create("{{ plugins.to_square(case["input"]["white_queen"]) }}") - maybe_square2 = create("{{ plugins.to_square(case["input"]["black_queen"]) }}") - result = match (maybe_square1, maybe_square2) { - (Ok(square1), Ok(square2)) => { - square1 -> queen_can_attack(square2) - } - _ => { crash "Unreachable: {{ plugins.to_square(case["input"]["white_queen"]) }} and {{ plugins.to_square(case["input"]["black_queen"]) }} are both valid squares" } - } + square1 = create("{{ plugins.to_square(case["input"]["white_queen"]) }}")? + square2 = create("{{ plugins.to_square(case["input"]["black_queen"]) }}")? + result = square1.queen_can_attack(square2) result == {{ case["expected"] | to_roc }} } {%- endif %} diff --git a/exercises/practice/queen-attack/QueenAttack.roc b/exercises/practice/queen-attack/QueenAttack.roc index 17b30ad7..dab5a494 100644 --- a/exercises/practice/queen-attack/QueenAttack.roc +++ b/exercises/practice/queen-attack/QueenAttack.roc @@ -1,20 +1,29 @@ QueenAttack :: {}.{ - rank : Square -> U8 - rank = |@Square({ row, column })| - crash("Please implement the 'rank' function") + Square :: { + # TODO: change this opaque type however you need + todo1 : U64, + todo2 : U64, + todo3 : U64, + # etc. + }.{ + create : Str -> Try(Square, _) + create = |square_str| { + crash "Please implement the 'create' function" + } - file : Square -> U8 - file = |@Square({ row, column })| - crash("Please implement the 'file' function") + rank : Square -> U8 + rank = |square| { + crash "Please implement the 'rank' function" + } - create : Str -> Result Square _ - create = |square_str| - crash("Please implement the 'create' function") + file : Square -> U8 + file = |square| { + crash "Please implement the 'file' function" + } - queen_can_attack : Square, Square -> Bool - queen_can_attack = |square1, square2| - crash("Please implement the 'queen_can_attack' function") + queen_can_attack : Square, Square -> Bool + queen_can_attack = |square1, square2| { + crash "Please implement the 'queen_can_attack' function" + } + } } - - -Square := { row : U8, column : U8 } diff --git a/exercises/practice/queen-attack/queen-attack-test.roc b/exercises/practice/queen-attack/queen-attack-test.roc index d0861b61..d67eb9f6 100644 --- a/exercises/practice/queen-attack/queen-attack-test.roc +++ b/exercises/practice/queen-attack/queen-attack-test.roc @@ -1,8 +1,9 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/queen-attack/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-18 -import QueenAttack exposing [create, rank, file, queen_can_attack] +import QueenAttack +create = QueenAttack.Square.create ## ## Test creation of Queens with valid and invalid positions @@ -10,26 +11,26 @@ import QueenAttack exposing [create, rank, file, queen_can_attack] # queen with a valid position expect { - maybe_square = create("C6") - result = maybe_square->Result.try(|square| Ok(rank, square)) - result == Ok(6) + square = create("C3")? + result = square.rank() + result == 3 } expect { - maybe_square = create("C6") - result = maybe_square->Result.try(|square| Ok(file, square)) - result == Ok('C') + square = create("C3")? + result = square.file() + result == 'C' } # queen must have row on board expect { - result = create("E0") + result = create("E9") result.is_err() } # queen must have column on board expect { - result = create("I4") + result = create("I5") result.is_err() } @@ -39,121 +40,65 @@ expect { # cannot attack expect { - maybe_square1 = create("E6") - maybe_square2 = create("G2") - result = match (maybe_square1, maybe_square2) { - (Ok(square1), Ok(square2)) => { - square1->queen_can_attack(square2) - } - _ => { - crash "Unreachable: E6 and G2 are both valid squares" - } - } + square1 = create("E3")? + square2 = create("G7")? + result = square1.queen_can_attack(square2) result == Bool.False } # can attack on same row expect { - maybe_square1 = create("E6") - maybe_square2 = create("G6") - result = match (maybe_square1, maybe_square2) { - (Ok(square1), Ok(square2)) => { - square1->queen_can_attack(square2) - } - _ => { - crash "Unreachable: E6 and G6 are both valid squares" - } - } + square1 = create("E3")? + square2 = create("G3")? + result = square1.queen_can_attack(square2) result == Bool.True } # can attack on same column expect { - maybe_square1 = create("F4") - maybe_square2 = create("F6") - result = match (maybe_square1, maybe_square2) { - (Ok(square1), Ok(square2)) => { - square1->queen_can_attack(square2) - } - _ => { - crash "Unreachable: F4 and F6 are both valid squares" - } - } + square1 = create("F5")? + square2 = create("F3")? + result = square1.queen_can_attack(square2) result == Bool.True } # can attack on first diagonal expect { - maybe_square1 = create("C6") - maybe_square2 = create("E8") - result = match (maybe_square1, maybe_square2) { - (Ok(square1), Ok(square2)) => { - square1->queen_can_attack(square2) - } - _ => { - crash "Unreachable: C6 and E8 are both valid squares" - } - } + square1 = create("C3")? + square2 = create("E1")? + result = square1.queen_can_attack(square2) result == Bool.True } # can attack on second diagonal expect { - maybe_square1 = create("C6") - maybe_square2 = create("B5") - result = match (maybe_square1, maybe_square2) { - (Ok(square1), Ok(square2)) => { - square1->queen_can_attack(square2) - } - _ => { - crash "Unreachable: C6 and B5 are both valid squares" - } - } + square1 = create("C3")? + square2 = create("B4")? + result = square1.queen_can_attack(square2) result == Bool.True } # can attack on third diagonal expect { - maybe_square1 = create("C6") - maybe_square2 = create("B7") - result = match (maybe_square1, maybe_square2) { - (Ok(square1), Ok(square2)) => { - square1->queen_can_attack(square2) - } - _ => { - crash "Unreachable: C6 and B7 are both valid squares" - } - } + square1 = create("C3")? + square2 = create("B2")? + result = square1.queen_can_attack(square2) result == Bool.True } # can attack on fourth diagonal expect { - maybe_square1 = create("H7") - maybe_square2 = create("G8") - result = match (maybe_square1, maybe_square2) { - (Ok(square1), Ok(square2)) => { - square1->queen_can_attack(square2) - } - _ => { - crash "Unreachable: H7 and G8 are both valid squares" - } - } + square1 = create("H2")? + square2 = create("G1")? + result = square1.queen_can_attack(square2) result == Bool.True } # cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal expect { - maybe_square1 = create("B4") - maybe_square2 = create("F6") - result = match (maybe_square1, maybe_square2) { - (Ok(square1), Ok(square2)) => { - square1->queen_can_attack(square2) - } - _ => { - crash "Unreachable: B4 and F6 are both valid squares" - } - } + square1 = create("B5")? + square2 = create("F3")? + result = square1.queen_can_attack(square2) result == Bool.False } From 736251c7fcd87ba3b475e7f5d0f31e9af16c0e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 19:43:52 +1200 Subject: [PATCH 087/162] Update exercise to new compiler: house --- exercises/practice/house/.meta/Example.roc | 61 +++++++++++++--------- exercises/practice/house/House.roc | 7 +-- 2 files changed, 40 insertions(+), 28 deletions(-) diff --git a/exercises/practice/house/.meta/Example.roc b/exercises/practice/house/.meta/Example.roc index 4a65237d..c69cde14 100644 --- a/exercises/practice/house/.meta/Example.roc +++ b/exercises/practice/house/.meta/Example.roc @@ -1,31 +1,42 @@ House :: {}.{ - recite : U64, U64 -> Str - recite = |start_verse, end_verse| - List.range({ start: At(start_verse), end: At(end_verse) }) - |> List.map(verse) - |> Str.join_with("\n") + recite : U64, U64 -> Str + recite = |start_verse, end_verse| { + (start_verse..=end_verse) + .map(verse) + ->List.from_iter() + ->Str.join_with("\n") + } } - segments = [ - "house that Jack built.", - "malt that lay in", - "rat that ate", - "cat that killed", - "dog that worried", - "cow with the crumpled horn that tossed", - "maiden all forlorn that milked", - "man all tattered and torn that kissed", - "priest all shaven and shorn that married", - "rooster that crowed in the morn that woke", - "farmer sowing his corn that kept", - "horse and the hound and the horn that belonged to", + "house that Jack built.", + "malt that lay in", + "rat that ate", + "cat that killed", + "dog that worried", + "cow with the crumpled horn that tossed", + "maiden all forlorn that milked", + "man all tattered and torn that kissed", + "priest all shaven and shorn that married", + "rooster that crowed in the morn that woke", + "farmer sowing his corn that kept", + "horse and the hound and the horn that belonged to", ] -verse = |index| - blablabla = - segments - |> List.take_first(index) - |> List.reverse - |> Str.join_with(" the ") - "This is the ${blablabla}" +verse = |index| { + blablabla = + segments + .take_first(index) + ->reverse() + ->Str.join_with(" the ") + "This is the ${blablabla}" +} + +# List.reverse should soon be available in Roc's builtins +reverse : List(a) -> List(a) +reverse = |list| { + match list { + [] => [] + [first, .. as rest] => reverse(rest).append(first) + } +} diff --git a/exercises/practice/house/House.roc b/exercises/practice/house/House.roc index 47cc0c75..2005c037 100644 --- a/exercises/practice/house/House.roc +++ b/exercises/practice/house/House.roc @@ -1,5 +1,6 @@ House :: {}.{ - recite : U64, U64 -> Str - recite = |start_verse, end_verse| - crash("Please implement the 'recite' function") + recite : U64, U64 -> Str + recite = |start_verse, end_verse| { + crash "Please implement the 'recite' function" + } } From 997b69729c632d03880506528b177502240a42ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 19:52:53 +1200 Subject: [PATCH 088/162] Update exercise to new compiler: spiral-matrix --- .../practice/spiral-matrix/.meta/Example.roc | 72 ++++++++++++------- .../practice/spiral-matrix/SpiralMatrix.roc | 7 +- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/exercises/practice/spiral-matrix/.meta/Example.roc b/exercises/practice/spiral-matrix/.meta/Example.roc index f3b6917b..57088cfa 100644 --- a/exercises/practice/spiral-matrix/.meta/Example.roc +++ b/exercises/practice/spiral-matrix/.meta/Example.roc @@ -1,31 +1,49 @@ SpiralMatrix :: {}.{ - spiral_matrix : U64 -> List (List U64) - spiral_matrix = |size| - zero_matrix = List.repeat(List.repeat(0, size), size) - List.range({ start: At(1), end: At((size * size)) }) - |> List.walk( - { x: -1, y: 0, dx: 1, dy: 0, matrix: zero_matrix }, - |state, index| - get_value = |x_i64, y_i64| - row = state.matrix |> List.get(Num.to_u64(y_i64)) |> Result.with_default([]) - row |> List.get(Num.to_u64(x_i64)) + spiral_matrix : U64 -> List(List(U64)) + spiral_matrix = |size| { + zero_matrix = List.repeat(List.repeat(0, size), size) + (1..=(size * size)) + .fold( + { x: -1, y: 0, dx: 1, dy: 0, matrix: zero_matrix }, + |state, index| { + get_value : I64, I64 -> Try(U64, _) + get_value = |x_i64, y_i64| { + row = state.matrix.get(y_i64.to_u64_try()?)? + row.get(x_i64.to_u64_try()?) + } - (dx, dy) = - (nx, ny) = (state.x + state.dx, state.y + state.dy) - if nx < 0 or ny < 0 or get_value(nx, ny) != Ok(0) then - (-state.dy, state.dx) # outside or non-zero value => turn right - else - (state.dx, state.dy) # or else continue in the same direction + (dx, dy) = { + (nx, ny) = (state.x + state.dx, state.y + state.dy) + if nx < 0 or ny < 0 or get_value(nx, ny) != Ok(0) { + (-state.dy, state.dx) + } else { + (state.dx, state.dy) + } + } - (x, y) = (state.x + dx, state.y + dy) - matrix = - state.matrix - |> List.update( - (y |> Num.to_u64), - |row| - row |> List.update((x |> Num.to_u64), |_| index), - ) - { x, y, dx, dy, matrix }, - ) - |> .matrix + (x, y) = (state.x + dx, state.y + dy) + matrix = + state.matrix + .update( + y.to_u64_try() ?? { + crash "Unreachable" + }, + |row| { + row.update( + x.to_u64_try() ?? { + crash "Unreachable" + }, + |_| index, + ) ?? { + crash "Unreachable" + } + }, + ) ?? { + crash "Unreachable" + } + { x, y, dx, dy, matrix } + }, + ) + .matrix + } } diff --git a/exercises/practice/spiral-matrix/SpiralMatrix.roc b/exercises/practice/spiral-matrix/SpiralMatrix.roc index 32280bb8..bf2ba92d 100644 --- a/exercises/practice/spiral-matrix/SpiralMatrix.roc +++ b/exercises/practice/spiral-matrix/SpiralMatrix.roc @@ -1,5 +1,6 @@ SpiralMatrix :: {}.{ - spiral_matrix : U64 -> List (List U64) - spiral_matrix = |size| - crash("Please implement the 'spiral_matrix' function") + spiral_matrix : U64 -> List(List(U64)) + spiral_matrix = |size| { + crash "Please implement the 'spiral_matrix' function" + } } From 55a58a578181db2102e00826aedefba1d885e74b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 20:21:23 +1200 Subject: [PATCH 089/162] Update exercise to new compiler: resistor-color-duo --- .../resistor-color-duo/.meta/Example.roc | 60 ++++++++++--------- .../resistor-color-duo/ResistorColorDuo.roc | 34 +++++------ 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/exercises/practice/resistor-color-duo/.meta/Example.roc b/exercises/practice/resistor-color-duo/.meta/Example.roc index 3849debd..832c8ca7 100644 --- a/exercises/practice/resistor-color-duo/.meta/Example.roc +++ b/exercises/practice/resistor-color-duo/.meta/Example.roc @@ -1,33 +1,35 @@ ResistorColorDuo :: {}.{ - value : Color, Color -> U8 - value = |first, second| - 10 * get_code(first) + get_code(second) -} - + Color := [ + Black, + Brown, + Red, + Orange, + Yellow, + Green, + Blue, + Violet, + Grey, + White, + ] -Color : [ - Black, - Brown, - Red, - Orange, - Yellow, - Green, - Blue, - Violet, - Grey, - White, -] + value : Color, Color -> U8 + value = |first, second| { + 10 * get_code(first) + get_code(second) + } +} get_code : Color -> U8 -get_code = |color| - when color is - Black -> 0 - Brown -> 1 - Red -> 2 - Orange -> 3 - Yellow -> 4 - Green -> 5 - Blue -> 6 - Violet -> 7 - Grey -> 8 - White -> 9 +get_code = |color| { + match color { + Black => 0 + Brown => 1 + Red => 2 + Orange => 3 + Yellow => 4 + Green => 5 + Blue => 6 + Violet => 7 + Grey => 8 + White => 9 + } +} diff --git a/exercises/practice/resistor-color-duo/ResistorColorDuo.roc b/exercises/practice/resistor-color-duo/ResistorColorDuo.roc index b0a41f7f..7f842b26 100644 --- a/exercises/practice/resistor-color-duo/ResistorColorDuo.roc +++ b/exercises/practice/resistor-color-duo/ResistorColorDuo.roc @@ -1,19 +1,19 @@ ResistorColorDuo :: {}.{ - value : Color, Color -> U8 - value = |first, second| - crash("Please implement the 'value' function") -} - + Color := [ + Black, + Brown, + Red, + Orange, + Yellow, + Green, + Blue, + Violet, + Grey, + White, + ] -Color : [ - Black, - Brown, - Red, - Orange, - Yellow, - Green, - Blue, - Violet, - Grey, - White, -] + value : Color, Color -> U8 + value = |first, second| { + crash "Please implement the 'value' function" + } +} From 1aec3af1fb537c9292a5c03e31316e47f8033b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 20:37:35 +1200 Subject: [PATCH 090/162] Update exercise to new compiler: scrabble-score --- .../practice/scrabble-score/.meta/Example.roc | 61 +++++++++++-------- .../practice/scrabble-score/ScrabbleScore.roc | 7 ++- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/exercises/practice/scrabble-score/.meta/Example.roc b/exercises/practice/scrabble-score/.meta/Example.roc index 5f861abf..8eb93905 100644 --- a/exercises/practice/scrabble-score/.meta/Example.roc +++ b/exercises/practice/scrabble-score/.meta/Example.roc @@ -1,33 +1,40 @@ ScrabbleScore :: {}.{ - score : Str -> U64 - score = |word| - word - |> Str.to_utf8 - |> List.map(letter_value) - |> List.sum + score : Str -> U64 + score = |word| { + word + .to_utf8() + .map(letter_value) + .sum() + } } to_upper : U8 -> U8 -to_upper = |letter| - if letter >= 'a' and letter <= 'z' then - letter - 'a' + 'A' - else - letter +to_upper = |letter| { + if letter >= 'a' and letter <= 'z' { + letter - 'a' + 'A' + } else { + letter + } +} letter_value : U8 -> U64 -letter_value = |letter| - [ - ("AEIOULNRST", 1), - ("DG", 2), - ("BCMP", 3), - ("FHVWY", 4), - ("K", 5), - ("JX", 8), - ("QZ", 10), - ] - |> List.find_first( - |(letters, _)| - letters |> Str.to_utf8 |> List.contains(to_upper(letter)), - ) - |> Result.with_default(("", 0)) # ignore invalid characters - |> .1 +letter_value = |letter| { + (_, val) = + [ + ("AEIOULNRST", 1), + ("DG", 2), + ("BCMP", 3), + ("FHVWY", 4), + ("K", 5), + ("JX", 8), + ("QZ", 10), + ] + .find_first( + |(letters, _)| { + letters + ->Str.to_utf8() + .contains(to_upper(letter)) + }, + ) ?? ("", 0) + val +} diff --git a/exercises/practice/scrabble-score/ScrabbleScore.roc b/exercises/practice/scrabble-score/ScrabbleScore.roc index efe42887..20535c94 100644 --- a/exercises/practice/scrabble-score/ScrabbleScore.roc +++ b/exercises/practice/scrabble-score/ScrabbleScore.roc @@ -1,5 +1,6 @@ ScrabbleScore :: {}.{ - score : Str -> U64 - score = |word| - crash("Please implement the 'score' function") + score : Str -> U64 + score = |word| { + crash "Please implement the 'score' function" + } } From 5707d545cbdcb561f56a4842d7321191e1f66451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 20:44:05 +1200 Subject: [PATCH 091/162] Update exercise to new compiler: matrix --- exercises/practice/matrix/.meta/Example.roc | 70 ++++++++++++--------- exercises/practice/matrix/Matrix.roc | 14 +++-- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/exercises/practice/matrix/.meta/Example.roc b/exercises/practice/matrix/.meta/Example.roc index 5414e8f7..016e46b6 100644 --- a/exercises/practice/matrix/.meta/Example.roc +++ b/exercises/practice/matrix/.meta/Example.roc @@ -1,34 +1,48 @@ Matrix :: {}.{ - column : Str, U64 -> Result (List I64) [InvalidNumStr, OutOfBounds] - column = |matrix_str, index| - if index == 0 then - Err(OutOfBounds) - else - matrix = parse_matrix(matrix_str)? - matrix |> List.map_try(|r| r |> List.get((index - 1))) + column : Str, U64 -> Try(List(I64), [BadNumStr, OutOfBounds, ..]) + column = |matrix_str, index| { + if index == 0 { + Err(OutOfBounds) + } else { + matrix = parse_matrix(matrix_str)? + matrix->map_try(|r| r.get(index - 1)) + } + } - row : Str, U64 -> Result (List I64) [InvalidNumStr, OutOfBounds] - row = |matrix_str, index| - if index == 0 then - Err(OutOfBounds) - else - matrix = parse_matrix(matrix_str)? - result = matrix |> List.get(index - 1)? - Ok(result) + row : Str, U64 -> Try(List(I64), [BadNumStr, OutOfBounds, ..]) + row = |matrix_str, index| { + if index == 0 { + Err(OutOfBounds) + } else { + matrix = parse_matrix(matrix_str)? + result = matrix.get(index - 1)? + Ok(result) + } + } } +parse_row : Str -> Try(List(I64), [BadNumStr, ..]) +parse_row = |row_str| { + row_str + .trim() + .split_on(" ") + .map(Str.trim) + .drop_if(Str.is_empty) + ->map_try(I64.from_str) +} -parse_row : Str -> Result (List I64) [InvalidNumStr] -parse_row = |row_str| - row_str - |> Str.trim - |> Str.split_on(" ") - |> List.map(Str.trim) - |> List.drop_if(Str.is_empty) - |> List.map_try(Str.to_i64) +parse_matrix : Str -> Try(List(List(I64)), [BadNumStr, ..]) +parse_matrix = |matrix_str| { + matrix_str + .split_on("\n") + ->map_try(parse_row) +} -parse_matrix : Str -> Result (List (List I64)) [InvalidNumStr] -parse_matrix = |matrix_str| - matrix_str - |> Str.split_on("\n") - |> List.map_try(parse_row) +# The following functions should soon be available in Roc's builtins +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/matrix/Matrix.roc b/exercises/practice/matrix/Matrix.roc index b77057f5..b69652f3 100644 --- a/exercises/practice/matrix/Matrix.roc +++ b/exercises/practice/matrix/Matrix.roc @@ -1,9 +1,11 @@ Matrix :: {}.{ - column : Str, U64 -> Result (List I64) _ - column = |matrix_str, index| - crash("Please implement the 'column' function") + column : Str, U64 -> Try(List(I64), _) + column = |matrix_str, index| { + crash "Please implement the 'column' function" + } - row : Str, U64 -> Result (List I64) _ - row = |matrix_str, index| - crash("Please implement the 'row' function") + row : Str, U64 -> Try(List(I64), _) + row = |matrix_str, index| { + crash "Please implement the 'row' function" + } } From 78a315ff3f988a386b46d41a523dab2e3af84136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 20:58:40 +1200 Subject: [PATCH 092/162] Update exercise to new compiler: space-age --- .../practice/space-age/.meta/Example.roc | 70 +++++++++++-------- .../practice/space-age/.meta/template.j2 | 16 ++++- exercises/practice/space-age/SpaceAge.roc | 30 ++++---- .../practice/space-age/space-age-test.roc | 32 ++++++--- 4 files changed, 92 insertions(+), 56 deletions(-) diff --git a/exercises/practice/space-age/.meta/Example.roc b/exercises/practice/space-age/.meta/Example.roc index 23e4adc5..d18846bc 100644 --- a/exercises/practice/space-age/.meta/Example.roc +++ b/exercises/practice/space-age/.meta/Example.roc @@ -1,36 +1,44 @@ SpaceAge :: {}.{ - age : Planet, Dec -> Dec - age = |planet, seconds| - period_in_earth_years = orbital_period_in_earth_years(planet) - period_in_seconds = period_in_earth_years * 365.25 * 24 * 60 * 60 - planet_years = seconds / period_in_seconds - round(planet_years) -} + Planet := [ + Mercury, + Venus, + Earth, + Mars, + Jupiter, + Saturn, + Uranus, + Neptune, + ] + age : Planet, Dec -> Dec + age = |planet, seconds| { + period_in_earth_years = orbital_period_in_earth_years(planet) + period_in_seconds = period_in_earth_years * 365.25 * 24 * 60 * 60 + planet_years = seconds / period_in_seconds + round(planet_years) + } +} -Planet : [ - Mercury, - Venus, - Earth, - Mars, - Jupiter, - Saturn, - Uranus, - Neptune, -] +orbital_period_in_earth_years = |planet| { + match planet { + Mercury => 0.2408467 + Venus => 0.61519726 + Earth => 1.0 + Mars => 1.8808158 + Jupiter => 11.862615 + Saturn => 29.447498 + Uranus => 84.016846 + Neptune => 164.79132 + } +} +# The following function will soon be available in Roc's builtins round : Dec -> Dec -round = |value| - pow = 10.0 |> Num.pow(2) - value * pow |> Num.round |> Num.to_frac |> Num.div(pow) - -orbital_period_in_earth_years = |planet| - when planet is - Mercury -> 0.2408467 - Venus -> 0.61519726 - Earth -> 1.0 - Mars -> 1.8808158 - Jupiter -> 11.862615 - Saturn -> 29.447498 - Uranus -> 84.016846 - Neptune -> 164.79132 +round = |value| { + pow = 100.0 + ( + (value * pow + 0.5).to_u64_try() ?? { + crash "Unreachable" + }, + ).to_dec() / pow +} diff --git a/exercises/practice/space-age/.meta/template.j2 b/exercises/practice/space-age/.meta/template.j2 index f9b8704c..bc601a8b 100644 --- a/exercises/practice/space-age/.meta/template.j2 +++ b/exercises/practice/space-age/.meta/template.j2 @@ -8,9 +8,23 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } # {{ case["description"] }} expect { result = {{ case["property"] | to_snake }}({{ case["input"]["planet"] | to_pascal }}, {{ case["input"]["seconds"] }}) - Num.is_approx_eq(result, {{ case["expected"] }}, { atol: 0.01 }) + is_approx_eq(result, {{ case["expected"] }}, { atol: 0.01 }) } {% endfor %} +# The following function should soon be available in Roc's builtins +is_approx_eq : Dec, Dec, { atol: Dec } -> Bool +is_approx_eq = |x, y, {atol}| { + to_int : Dec -> Try(I64, [OutOfRange]) + to_int = |f| { + (f / atol + 0.5).to_i64_try() + } + + match (to_int(x), to_int(y)) { + (Ok(xi), Ok(yi)) => (xi == yi) + _ => Bool.False + } +} + {{ macros.footer() }} diff --git a/exercises/practice/space-age/SpaceAge.roc b/exercises/practice/space-age/SpaceAge.roc index ebb812c7..e3197f66 100644 --- a/exercises/practice/space-age/SpaceAge.roc +++ b/exercises/practice/space-age/SpaceAge.roc @@ -1,17 +1,17 @@ SpaceAge :: {}.{ - age : Planet, Dec -> Dec - age = |planet, seconds| - crash("Please implement the 'age' function") -} - + Planet := [ + Mercury, + Venus, + Earth, + Mars, + Jupiter, + Saturn, + Uranus, + Neptune, + ] -Planet : [ - Mercury, - Venus, - Earth, - Mars, - Jupiter, - Saturn, - Uranus, - Neptune, -] + age : Planet, Dec -> Dec + age = |planet, seconds| { + crash "Please implement the 'age' function" + } +} diff --git a/exercises/practice/space-age/space-age-test.roc b/exercises/practice/space-age/space-age-test.roc index 45416b57..e002e050 100644 --- a/exercises/practice/space-age/space-age-test.roc +++ b/exercises/practice/space-age/space-age-test.roc @@ -1,55 +1,69 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/space-age/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-18 import SpaceAge exposing [age] # age on Earth expect { result = age(Earth, 1000000000) - Num.is_approx_eq(result, 31.69, { atol: 0.01 }) + is_approx_eq(result, 31.69, { atol: 0.01 }) } # age on Mercury expect { result = age(Mercury, 2134835688) - Num.is_approx_eq(result, 280.88, { atol: 0.01 }) + is_approx_eq(result, 280.88, { atol: 0.01 }) } # age on Venus expect { result = age(Venus, 189839836) - Num.is_approx_eq(result, 9.78, { atol: 0.01 }) + is_approx_eq(result, 9.78, { atol: 0.01 }) } # age on Mars expect { result = age(Mars, 2129871239) - Num.is_approx_eq(result, 35.88, { atol: 0.01 }) + is_approx_eq(result, 35.88, { atol: 0.01 }) } # age on Jupiter expect { result = age(Jupiter, 901876382) - Num.is_approx_eq(result, 2.41, { atol: 0.01 }) + is_approx_eq(result, 2.41, { atol: 0.01 }) } # age on Saturn expect { result = age(Saturn, 2000000000) - Num.is_approx_eq(result, 2.15, { atol: 0.01 }) + is_approx_eq(result, 2.15, { atol: 0.01 }) } # age on Uranus expect { result = age(Uranus, 1210123456) - Num.is_approx_eq(result, 0.46, { atol: 0.01 }) + is_approx_eq(result, 0.46, { atol: 0.01 }) } # age on Neptune expect { result = age(Neptune, 1821023456) - Num.is_approx_eq(result, 0.35, { atol: 0.01 }) + is_approx_eq(result, 0.35, { atol: 0.01 }) +} + +# The following function should soon be available in Roc's builtins +is_approx_eq : Dec, Dec, { atol : Dec } -> Bool +is_approx_eq = |x, y, { atol }| { + to_int : Dec -> Try(I64, [OutOfRange]) + to_int = |f| { + (f / atol + 0.5).to_i64_try() + } + + match (to_int(x), to_int(y)) { + (Ok(xi), Ok(yi)) => (xi == yi) + _ => Bool.False + } } # This program is only used to run tests with `roc test`, so main! does nothing. From 5af9b48f637aa616209d9293f3fc0451fff84db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 21:07:25 +1200 Subject: [PATCH 093/162] Update exercise to new compiler: kindergarten-garden --- .../kindergarten-garden/.meta/Example.roc | 78 +++++++++++-------- .../kindergarten-garden/.meta/template.j2 | 2 +- .../KindergartenGarden.roc | 14 ++-- .../kindergarten-garden-test.roc | 36 ++++----- 4 files changed, 71 insertions(+), 59 deletions(-) diff --git a/exercises/practice/kindergarten-garden/.meta/Example.roc b/exercises/practice/kindergarten-garden/.meta/Example.roc index 43c9c6a4..a8d5ce32 100644 --- a/exercises/practice/kindergarten-garden/.meta/Example.roc +++ b/exercises/practice/kindergarten-garden/.meta/Example.roc @@ -1,37 +1,49 @@ KindergartenGarden :: {}.{ - plants : Str, Student -> Result (List Plant) _ - plants = |diagram, student| - start_index = 2 * student_index(student) - grid = diagram |> Str.to_utf8 |> List.split_on('\n') - [(0, 0), (0, 1), (1, 0), (1, 1)] - |> List.map_try( - |(row, column)| - plant = grid |> List.get(row)? |> List.get(start_index + column)? - when plant is - 'G' -> Ok(Grass) - 'C' -> Ok(Clover) - 'R' -> Ok(Radishes) - 'V' -> Ok(Violets) - _ -> Err(UnknownPlant(plant)), - ) -} - + Student := [Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, Larry] + Plant := [Grass, Clover, Radishes, Violets] -Student : [Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, Larry] -Plant : [Grass, Clover, Radishes, Violets] + plants : Str, Student -> Try(List(Plant), [UnknownPlant(U8), OutOfBounds]) + plants = |diagram, student| { + start_index = 2 * student_index(student) + grid = diagram.to_utf8().split_on('\n') + [(0, 0), (0, 1), (1, 0), (1, 1)] + ->map_try( + |(row, column)| { + plant = grid.get(row)?.get(start_index + column)? + match plant { + 'G' => Ok(Grass) + 'C' => Ok(Clover) + 'R' => Ok(Radishes) + 'V' => Ok(Violets) + _ => Err(UnknownPlant(plant)) + } + }, + ) + } +} student_index : Student -> U64 -student_index = |student| - when student is - Alice -> 0 - Bob -> 1 - Charlie -> 2 - David -> 3 - Eve -> 4 - Fred -> 5 - Ginny -> 6 - Harriet -> 7 - Ileana -> 8 - Joseph -> 9 - Kincaid -> 10 - Larry -> 11 +student_index = |student| { + match student { + Alice => 0 + Bob => 1 + Charlie => 2 + David => 3 + Eve => 4 + Fred => 5 + Ginny => 6 + Harriet => 7 + Ileana => 8 + Joseph => 9 + Kincaid => 10 + Larry => 11 + } +} + +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/kindergarten-garden/.meta/template.j2 b/exercises/practice/kindergarten-garden/.meta/template.j2 index 12c8201f..eb1bbad8 100644 --- a/exercises/practice/kindergarten-garden/.meta/template.j2 +++ b/exercises/practice/kindergarten-garden/.meta/template.j2 @@ -21,7 +21,7 @@ import {{ exercise | to_pascal }} exposing [plants] # {{ subcase["description"] }} expect { diagram = {{ subcase["input"]["diagram"] | to_roc_multiline_string | indent(8) }} - result = diagram.{{ subcase["property"] | to_snake }}({{ subcase["input"]["student"] | to_pascal }}) + result = diagram->{{ subcase["property"] | to_snake }}({{ subcase["input"]["student"] | to_pascal }}) result == Ok([{% for plant in subcase["expected"] %}{{ plant | to_pascal }}, {% endfor %}]) } diff --git a/exercises/practice/kindergarten-garden/KindergartenGarden.roc b/exercises/practice/kindergarten-garden/KindergartenGarden.roc index a86914f7..a0b2deab 100644 --- a/exercises/practice/kindergarten-garden/KindergartenGarden.roc +++ b/exercises/practice/kindergarten-garden/KindergartenGarden.roc @@ -1,9 +1,9 @@ KindergartenGarden :: {}.{ - plants : Str, Student -> Result (List Plant) _ - plants = |diagram, student| - crash("Please implement the 'plants' function") -} - + Student := [Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, Larry] + Plant := [Grass, Clover, Radishes, Violets] -Student : [Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, Larry] -Plant : [Grass, Clover, Radishes, Violets] + plants : Str, Student -> Try(List(Plant), _) + plants = |diagram, student| { + crash "Please implement the 'plants' function" + } +} diff --git a/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc b/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc index a43e02d4..5410eefc 100644 --- a/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc +++ b/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/kindergarten-garden/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-18 import KindergartenGarden exposing [plants] @@ -14,7 +14,7 @@ expect { \\RC \\GG - result = diagram.plants(Alice) + result = diagram->plants(Alice) result == Ok( [ Radishes, @@ -31,7 +31,7 @@ expect { \\VC \\RC - result = diagram.plants(Alice) + result = diagram->plants(Alice) result == Ok( [ Violets, @@ -48,7 +48,7 @@ expect { \\VVCG \\VVRC - result = diagram.plants(Bob) + result = diagram->plants(Bob) result == Ok( [ Clover, @@ -67,7 +67,7 @@ expect { \\VVCCGG \\VVCCGG - result = diagram.plants(Bob) + result = diagram->plants(Bob) result == Ok( [ Clover, @@ -84,7 +84,7 @@ expect { \\VVCCGG \\VVCCGG - result = diagram.plants(Charlie) + result = diagram->plants(Charlie) result == Ok( [ Grass, @@ -105,7 +105,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Alice) + result = diagram->plants(Alice) result == Ok( [ Violets, @@ -122,7 +122,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Bob) + result = diagram->plants(Bob) result == Ok( [ Clover, @@ -139,7 +139,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Charlie) + result = diagram->plants(Charlie) result == Ok( [ Violets, @@ -156,7 +156,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(David) + result = diagram->plants(David) result == Ok( [ Radishes, @@ -173,7 +173,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Eve) + result = diagram->plants(Eve) result == Ok( [ Clover, @@ -190,7 +190,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Fred) + result = diagram->plants(Fred) result == Ok( [ Grass, @@ -207,7 +207,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Ginny) + result = diagram->plants(Ginny) result == Ok( [ Clover, @@ -224,7 +224,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Harriet) + result = diagram->plants(Harriet) result == Ok( [ Violets, @@ -241,7 +241,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Ileana) + result = diagram->plants(Ileana) result == Ok( [ Grass, @@ -258,7 +258,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Joseph) + result = diagram->plants(Joseph) result == Ok( [ Violets, @@ -275,7 +275,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Kincaid) + result = diagram->plants(Kincaid) result == Ok( [ Grass, @@ -292,7 +292,7 @@ expect { \\VRCGVVRVCGGCCGVRGCVCGCGV \\VRCCCGCRRGVCGCRVVCVGCGCV - result = diagram.plants(Larry) + result = diagram->plants(Larry) result == Ok( [ Grass, From 38f7f86cb26d319c4d9357a8360e8dca0d253e0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 21:48:22 +1200 Subject: [PATCH 094/162] Update exercise to new compiler: protein-translation --- .../protein-translation/.meta/Example.roc | 101 +++++++++++------- .../ProteinTranslation.roc | 15 +-- 2 files changed, 73 insertions(+), 43 deletions(-) diff --git a/exercises/practice/protein-translation/.meta/Example.roc b/exercises/practice/protein-translation/.meta/Example.roc index 524a68c5..0277291c 100644 --- a/exercises/practice/protein-translation/.meta/Example.roc +++ b/exercises/practice/protein-translation/.meta/Example.roc @@ -1,40 +1,69 @@ ProteinTranslation :: {}.{ - to_protein : Str -> Result Protein [InvalidCodon Codon] - to_protein = |rna| - help = |protein, codons| - when codons is - [] -> Ok(protein) - [codon, .. as rest] -> - when codon |> to_instruction is - Ok(Append(amino_acid)) -> protein |> List.append(amino_acid) |> help(rest) - Ok(Stop) -> Ok(protein) - Err(err) -> Err(err) - help([], (rna |> Str.to_utf8 |> List.chunks_of(3))) -} + Codon : List(U8) + AminoAcid : [Cysteine, Leucine, Methionine, Phenylalanine, Serine, Tryptophan, Tyrosine] + Protein : List(AminoAcid) + to_protein : Str -> Try(Protein, [InvalidCodon(Codon)]) + to_protein = |rna| { + help : Protein, List(Codon) -> Try(Protein, [InvalidCodon(Codon)]) + help = |protein, codons| { + match codons { + [] => Ok(protein) + [codon, .. as rest] => { + match to_instruction(codon)? { + Append(amino_acid) => { + protein.append(amino_acid)->help(rest) + } + Stop => Ok(protein) + } + } + } + } + help([], rna.to_utf8()->chunks_of(3)) + } +} -Codon : List U8 -AminoAcid : [Cysteine, Leucine, Methionine, Phenylalanine, Serine, Tryptophan, Tyrosine] -Protein : List AminoAcid +to_instruction : Codon -> Try([Append(AminoAcid), Stop], [InvalidCodon(Codon)]) +to_instruction = |codon| { + # See https://github.com/roc-lang/roc/issues/9700 + codon_str = codon->Str.from_utf8() ?? { + crash "Unreachable" + } + match codon_str { + "AUG" => Ok(Append(Methionine)) + "UUU" => Ok(Append(Phenylalanine)) + "UUC" => Ok(Append(Phenylalanine)) + "UUA" => Ok(Append(Leucine)) + "UUG" => Ok(Append(Leucine)) + "UCU" => Ok(Append(Serine)) + "UCC" => Ok(Append(Serine)) + "UCA" => Ok(Append(Serine)) + "UCG" => Ok(Append(Serine)) + "UAU" => Ok(Append(Tyrosine)) + "UAC" => Ok(Append(Tyrosine)) + "UGU" => Ok(Append(Cysteine)) + "UGC" => Ok(Append(Cysteine)) + "UGG" => Ok(Append(Tryptophan)) + "UAA" => Ok(Stop) + "UAG" => Ok(Stop) + "UGA" => Ok(Stop) + _ => Err(InvalidCodon(codon)) + } +} -to_instruction : Codon -> Result [Append AminoAcid, Stop] [InvalidCodon Codon] -to_instruction = |codon| - when codon is - ['A', 'U', 'G'] -> Ok(Append(Methionine)) - ['U', 'U', 'U'] -> Ok(Append(Phenylalanine)) - ['U', 'U', 'C'] -> Ok(Append(Phenylalanine)) - ['U', 'U', 'A'] -> Ok(Append(Leucine)) - ['U', 'U', 'G'] -> Ok(Append(Leucine)) - ['U', 'C', 'U'] -> Ok(Append(Serine)) - ['U', 'C', 'C'] -> Ok(Append(Serine)) - ['U', 'C', 'A'] -> Ok(Append(Serine)) - ['U', 'C', 'G'] -> Ok(Append(Serine)) - ['U', 'A', 'U'] -> Ok(Append(Tyrosine)) - ['U', 'A', 'C'] -> Ok(Append(Tyrosine)) - ['U', 'G', 'U'] -> Ok(Append(Cysteine)) - ['U', 'G', 'C'] -> Ok(Append(Cysteine)) - ['U', 'G', 'G'] -> Ok(Append(Tryptophan)) - ['U', 'A', 'A'] -> Ok(Stop) - ['U', 'A', 'G'] -> Ok(Stop) - ['U', 'G', 'A'] -> Ok(Stop) - _ -> Err(InvalidCodon(codon)) +# The following functions should soon be available in Roc's builtins +chunks_of = |iter, size| { + var $state = [] + var $chunk = [] + for item in iter { + $chunk = $chunk.append(item) + if $chunk.len() == size { + $state = $state.append($chunk) + $chunk = [] + } + } + if $chunk.len() > 0 { + $state = $state.append($chunk) + } + $state +} diff --git a/exercises/practice/protein-translation/ProteinTranslation.roc b/exercises/practice/protein-translation/ProteinTranslation.roc index a3c666f9..392cf5bf 100644 --- a/exercises/practice/protein-translation/ProteinTranslation.roc +++ b/exercises/practice/protein-translation/ProteinTranslation.roc @@ -1,9 +1,10 @@ ProteinTranslation :: {}.{ - to_protein : Str -> Result Protein _ - to_protein = |rna| - crash("Please implement the 'to_protein' function") -} - + Codon : List(U8) + AminoAcid : [Cysteine, Leucine, Methionine, Phenylalanine, Serine, Tryptophan, Tyrosine] + Protein : List(AminoAcid) -AminoAcid : [Cysteine, Leucine, Methionine, Phenylalanine, Serine, Tryptophan, Tyrosine] -Protein : List AminoAcid + to_protein : Str -> Try(Protein, _) + to_protein = |rna| { + crash "Please implement the 'to_protein' function" + } +} From 90db856e5a0585979fbd62d68b80312407677b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 22:09:07 +1200 Subject: [PATCH 095/162] Update exercise to new compiler: wordy --- exercises/practice/wordy/.meta/Example.roc | 79 ++++++++++++---------- exercises/practice/wordy/Wordy.roc | 7 +- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/exercises/practice/wordy/.meta/Example.roc b/exercises/practice/wordy/.meta/Example.roc index f832a08c..27193928 100644 --- a/exercises/practice/wordy/.meta/Example.roc +++ b/exercises/practice/wordy/.meta/Example.roc @@ -1,40 +1,45 @@ Wordy :: {}.{ - answer : Str -> Result I64 [QuestionArgHadAnUnknownOperation Str, QuestionArgHadASyntaxError Str] - answer = |question| - words = question |> Str.replace_each("?", " ?") |> Str.split_on(" ") - when words is - ["What", "is", number_string, .. as operations, "?"] -> - maybe_start_number = Str.to_i64(number_string) - when maybe_start_number is - Ok(start_number) -> - when evaluate_expression(start_number, operations) is - Err(OperationsArgHadAnInvalidOperation(_)) -> Err(QuestionArgHadAnUnknownOperation(question)) - Err(OperationsArgHadASyntaxError(_)) -> Err(QuestionArgHadASyntaxError(question)) - Err(InvalidNumStr) -> Err(QuestionArgHadASyntaxError(question)) - Ok(result) -> Ok(result) - - Err(InvalidNumStr) -> Err(QuestionArgHadASyntaxError(question)) - - [_, "is", _, .., "?"] -> Err(QuestionArgHadAnUnknownOperation(question)) - [_, "are", .., "?"] -> Err(QuestionArgHadAnUnknownOperation(question)) - _ -> Err(QuestionArgHadASyntaxError(question)) + answer : Str -> Try(I64, [QuestionArgHadAnUnknownOperation(Str), QuestionArgHadASyntaxError(Str)]) + answer = |question| { + words = question.drop_suffix("?").split_on(" ") + match words { + ["What", "is", number_string, .. as operations] => { + maybe_start_number = I64.from_str(number_string) + match maybe_start_number { + Ok(start_number) => { + match evaluate_expression(start_number, operations) { + Err(OperationsArgHadAnInvalidOperation(_)) => Err(QuestionArgHadAnUnknownOperation(question)) + Err(OperationsArgHadASyntaxError(_)) => Err(QuestionArgHadASyntaxError(question)) + Err(BadNumStr) => Err(QuestionArgHadASyntaxError(question)) + Ok(result) => Ok(result) + } + } + Err(BadNumStr) => Err(QuestionArgHadASyntaxError(question)) + } + } + [_, "is", _, ..] => Err(QuestionArgHadAnUnknownOperation(question)) + [_, "are", ..] => Err(QuestionArgHadAnUnknownOperation(question)) + _ => Err(QuestionArgHadASyntaxError(question)) + } + } } - -evaluate_expression = |accumulator, operations| - when operations is - [] -> Ok(accumulator) - ["plus", number_string, .. as rest] -> - evaluate_expression((accumulator + Str.to_i64(number_string)?), rest) - - ["minus", number_string, .. as rest] -> - evaluate_expression(accumulator - Str.to_i64(number_string)?, rest) - - ["multiplied", "by", number_string, .. as rest] -> - evaluate_expression(accumulator * Str.to_i64(number_string)?, rest) - - ["divided", "by", number_string, .. as rest] -> - evaluate_expression(accumulator // Str.to_i64(number_string)?, rest) - - ["cubed"] -> Err(OperationsArgHadAnInvalidOperation(operations)) - _ -> Err(OperationsArgHadASyntaxError(operations)) +evaluate_expression = |accumulator, operations| { + match operations { + [] => Ok(accumulator) + ["plus", number_string, .. as rest] => { + evaluate_expression((accumulator + I64.from_str(number_string)?), rest) + } + ["minus", number_string, .. as rest] => { + evaluate_expression((accumulator - I64.from_str(number_string)?), rest) + } + ["multiplied", "by", number_string, .. as rest] => { + evaluate_expression((accumulator * I64.from_str(number_string)?), rest) + } + ["divided", "by", number_string, .. as rest] => { + evaluate_expression((accumulator // I64.from_str(number_string)?), rest) + } + ["cubed"] => Err(OperationsArgHadAnInvalidOperation(operations)) + _ => Err(OperationsArgHadASyntaxError(operations)) + } +} diff --git a/exercises/practice/wordy/Wordy.roc b/exercises/practice/wordy/Wordy.roc index a2ecd985..64593e4d 100644 --- a/exercises/practice/wordy/Wordy.roc +++ b/exercises/practice/wordy/Wordy.roc @@ -1,5 +1,6 @@ Wordy :: {}.{ - answer : Str -> Result I64 _ - answer = |question| - crash("Please implement the 'answer' function") + answer : Str -> Try(I64, _) + answer = |question| { + crash "Please implement the 'answer' function" + } } From 3b34f63c8e53b8e311e7c89606b9996b434cb58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 22:23:15 +1200 Subject: [PATCH 096/162] Update exercise to new compiler: robot-simulator --- .../robot-simulator/.meta/Example.roc | 75 +++++++++-------- .../practice/robot-simulator/.meta/plugins.py | 12 +-- .../robot-simulator/.meta/template.j2 | 12 +-- .../practice/robot-simulator/.meta/tests.toml | 2 + .../robot-simulator/RobotSimulator.roc | 16 ++-- .../robot-simulator/robot-simulator-test.roc | 84 ++++++++----------- 6 files changed, 87 insertions(+), 114 deletions(-) diff --git a/exercises/practice/robot-simulator/.meta/Example.roc b/exercises/practice/robot-simulator/.meta/Example.roc index 19abec11..570529b1 100644 --- a/exercises/practice/robot-simulator/.meta/Example.roc +++ b/exercises/practice/robot-simulator/.meta/Example.roc @@ -1,42 +1,45 @@ RobotSimulator :: {}.{ - create : { x ?? I64, y ?? I64, direction ?? Direction } -> Robot - create = |{ x ?? 0, y ?? 0, direction ?? North }| - { x, y, direction } + Direction : [North, East, South, West] + Robot : { x : I64, y : I64, direction : Direction } - move : Robot, Str -> Robot - move = |robot, instructions| - instructions - |> Str.to_utf8 - |> List.walk( - robot, - |{ x, y, direction }, command| - when command is - 'R' -> - when direction is - North -> { x, y, direction: East } - East -> { x, y, direction: South } - South -> { x, y, direction: West } - West -> { x, y, direction: North } + move : Robot, Str -> Robot + move = |robot, instructions| { + instructions + .to_utf8() + .fold( + robot, + |{ x, y, direction }, command| { + match command { + 'R' => { + match direction { + North => { x, y, direction: East } + East => { x, y, direction: South } + South => { x, y, direction: West } + West => { x, y, direction: North } + } + } - 'L' -> - when direction is - North -> { x, y, direction: West } - East -> { x, y, direction: North } - South -> { x, y, direction: East } - West -> { x, y, direction: South } + 'L' => { + match direction { + North => { x, y, direction: West } + East => { x, y, direction: North } + South => { x, y, direction: East } + West => { x, y, direction: South } + } + } - 'A' -> - when direction is - North -> { x, y: y + 1, direction } - East -> { x: x + 1, y, direction } - South -> { x, y: y - 1, direction } - West -> { x: x - 1, y, direction } - - _ -> { x, y, direction }, - ) # invalid instructions are ignored + 'A' => { + match direction { + North => { x, y: y + 1, direction } + East => { x: x + 1, y, direction } + South => { x, y: y - 1, direction } + West => { x: x - 1, y, direction } + } + } + _ => { x, y, direction } + } + }, + ) + } } - - -Direction : [North, East, South, West] -Robot : { x : I64, y : I64, direction : Direction } diff --git a/exercises/practice/robot-simulator/.meta/plugins.py b/exercises/practice/robot-simulator/.meta/plugins.py index afb5b468..2615ad25 100644 --- a/exercises/practice/robot-simulator/.meta/plugins.py +++ b/exercises/practice/robot-simulator/.meta/plugins.py @@ -1,13 +1,5 @@ -def to_robot(robot, with_defaults): +def to_robot(robot): x = robot["position"]["x"] y = robot["position"]["y"] direction = robot["direction"] - fields = [] - if x != 0 or not with_defaults: - fields.append(f"x : {x}") - if y != 0 or not with_defaults: - fields.append(f"y : {y}") - if direction != "north" or not with_defaults: - fields.append(f"direction : {direction.capitalize()}") - content = ", ".join(fields) - return f"{{{content}}}" + return f"{{x : {x}, y : {y}, direction : {direction.capitalize()}}}" diff --git a/exercises/practice/robot-simulator/.meta/template.j2 b/exercises/practice/robot-simulator/.meta/template.j2 index c2e32625..2de4ca27 100644 --- a/exercises/practice/robot-simulator/.meta/template.j2 +++ b/exercises/practice/robot-simulator/.meta/template.j2 @@ -2,7 +2,7 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [create, move] +import {{ exercise | to_pascal }} exposing [move] {% for supercase in cases %} ## @@ -12,13 +12,9 @@ import {{ exercise | to_pascal }} exposing [create, move] {% for case in supercase["cases"] -%} # {{ case["description"] }} expect { - {%- if case["input"]["instructions"] %} - robot = create({{ plugins.to_robot(case["input"], with_defaults=True) }}) - result = robot.move({{ case["input"]["instructions"] | to_roc }}) - {%- else %} - result = create({{ plugins.to_robot(case["input"], with_defaults=True) }}) - {%- endif %} - result == {{ plugins.to_robot(case["expected"], with_defaults=False) }} + robot = {{ plugins.to_robot(case["input"]) }} + result = robot->move({{ case["input"]["instructions"] | to_roc }}) + result == {{ plugins.to_robot(case["expected"]) }} } {% endfor %} diff --git a/exercises/practice/robot-simulator/.meta/tests.toml b/exercises/practice/robot-simulator/.meta/tests.toml index 16da03d4..f2e52b19 100644 --- a/exercises/practice/robot-simulator/.meta/tests.toml +++ b/exercises/practice/robot-simulator/.meta/tests.toml @@ -11,9 +11,11 @@ [c557c16d-26c1-4e06-827c-f6602cd0785c] description = "Create robot -> at origin facing north" +include = false [bf0dffce-f11c-4cdb-8a5e-2c89d8a5a67d] description = "Create robot -> at negative position facing south" +include = false [8cbd0086-6392-4680-b9b9-73cf491e67e5] description = "Rotating clockwise -> changes north to east" diff --git a/exercises/practice/robot-simulator/RobotSimulator.roc b/exercises/practice/robot-simulator/RobotSimulator.roc index 408c83e7..0933626b 100644 --- a/exercises/practice/robot-simulator/RobotSimulator.roc +++ b/exercises/practice/robot-simulator/RobotSimulator.roc @@ -1,13 +1,9 @@ RobotSimulator :: {}.{ - create : { x ?? I64, y ?? I64, direction ?? Direction } -> Robot - create = |{ x ?? 0, y ?? 0, direction ?? North }| - crash("Please implement the 'create' function") + Direction : [North, East, South, West] + Robot : { x : I64, y : I64, direction : Direction } - move : Robot, Str -> Robot - move = |robot, instructions| - crash("Please implement the 'move' function") + move : Robot, Str -> Robot + move = |robot, instructions| { + crash "Please implement the 'move' function" + } } - - -Direction : [North, East, South, West] -Robot : { x : I64, y : I64, direction : Direction } diff --git a/exercises/practice/robot-simulator/robot-simulator-test.roc b/exercises/practice/robot-simulator/robot-simulator-test.roc index 5f95f555..e41e9337 100644 --- a/exercises/practice/robot-simulator/robot-simulator-test.roc +++ b/exercises/practice/robot-simulator/robot-simulator-test.roc @@ -1,24 +1,8 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/robot-simulator/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-18 -import RobotSimulator exposing [create, move] - -## -## Create robot -## - -# at origin facing north -expect { - result = create({}) - result == { x: 0, y: 0, direction: North } -} - -# at negative position facing south -expect { - result = create({ x: -1, y: -1, direction: South }) - result == { x: -1, y: -1, direction: South } -} +import RobotSimulator exposing [move] ## ## Rotating clockwise @@ -26,29 +10,29 @@ expect { # changes north to east expect { - robot = create({}) - result = robot.move("R") + robot = { x: 0, y: 0, direction: North } + result = robot->move("R") result == { x: 0, y: 0, direction: East } } # changes east to south expect { - robot = create({ direction: East }) - result = robot.move("R") + robot = { x: 0, y: 0, direction: East } + result = robot->move("R") result == { x: 0, y: 0, direction: South } } # changes south to west expect { - robot = create({ direction: South }) - result = robot.move("R") + robot = { x: 0, y: 0, direction: South } + result = robot->move("R") result == { x: 0, y: 0, direction: West } } # changes west to north expect { - robot = create({ direction: West }) - result = robot.move("R") + robot = { x: 0, y: 0, direction: West } + result = robot->move("R") result == { x: 0, y: 0, direction: North } } @@ -58,29 +42,29 @@ expect { # changes north to west expect { - robot = create({}) - result = robot.move("L") + robot = { x: 0, y: 0, direction: North } + result = robot->move("L") result == { x: 0, y: 0, direction: West } } # changes west to south expect { - robot = create({ direction: West }) - result = robot.move("L") + robot = { x: 0, y: 0, direction: West } + result = robot->move("L") result == { x: 0, y: 0, direction: South } } # changes south to east expect { - robot = create({ direction: South }) - result = robot.move("L") + robot = { x: 0, y: 0, direction: South } + result = robot->move("L") result == { x: 0, y: 0, direction: East } } # changes east to north expect { - robot = create({ direction: East }) - result = robot.move("L") + robot = { x: 0, y: 0, direction: East } + result = robot->move("L") result == { x: 0, y: 0, direction: North } } @@ -90,29 +74,29 @@ expect { # facing north increments Y expect { - robot = create({}) - result = robot.move("A") + robot = { x: 0, y: 0, direction: North } + result = robot->move("A") result == { x: 0, y: 1, direction: North } } # facing south decrements Y expect { - robot = create({ direction: South }) - result = robot.move("A") + robot = { x: 0, y: 0, direction: South } + result = robot->move("A") result == { x: 0, y: -1, direction: South } } # facing east increments X expect { - robot = create({ direction: East }) - result = robot.move("A") + robot = { x: 0, y: 0, direction: East } + result = robot->move("A") result == { x: 1, y: 0, direction: East } } # facing west decrements X expect { - robot = create({ direction: West }) - result = robot.move("A") + robot = { x: 0, y: 0, direction: West } + result = robot->move("A") result == { x: -1, y: 0, direction: West } } @@ -122,29 +106,29 @@ expect { # moving east and north from README expect { - robot = create({ x: 7, y: 3 }) - result = robot.move("RAALAL") + robot = { x: 7, y: 3, direction: North } + result = robot->move("RAALAL") result == { x: 9, y: 4, direction: West } } # moving west and north expect { - robot = create({}) - result = robot.move("LAAARALA") + robot = { x: 0, y: 0, direction: North } + result = robot->move("LAAARALA") result == { x: -4, y: 1, direction: West } } # moving west and south expect { - robot = create({ x: 2, y: -7, direction: East }) - result = robot.move("RRAAAAALA") + robot = { x: 2, y: -7, direction: East } + result = robot->move("RRAAAAALA") result == { x: -3, y: -8, direction: South } } # moving east and north expect { - robot = create({ x: 8, y: 4, direction: South }) - result = robot.move("LAAARRRALLLL") + robot = { x: 8, y: 4, direction: South } + result = robot->move("LAAARRRALLLL") result == { x: 11, y: 5, direction: North } } From f4ecb4c95d4d82164e2fdb26dcd91819ab4946f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 18 Jun 2026 22:49:35 +1200 Subject: [PATCH 097/162] Update exercise to new compiler: variable-length-quantity --- .../.meta/Example.roc | 105 ++++++++++++------ .../.meta/template.j2 | 2 +- .../VariableLengthQuantity.roc | 14 ++- .../variable-length-quantity-test.roc | 26 ++--- 4 files changed, 86 insertions(+), 61 deletions(-) diff --git a/exercises/practice/variable-length-quantity/.meta/Example.roc b/exercises/practice/variable-length-quantity/.meta/Example.roc index 01345ac5..73f59d6d 100644 --- a/exercises/practice/variable-length-quantity/.meta/Example.roc +++ b/exercises/practice/variable-length-quantity/.meta/Example.roc @@ -1,42 +1,73 @@ VariableLengthQuantity :: {}.{ - encode : List U32 -> List U8 - encode = |integers| - integers |> List.join_map(encode_integer) + encode : List(U32) -> List(U8) + encode = |integers| { + integers->join_map(encode_integer) + } - decode : List U8 -> Result (List U32) [IncompleteSequence] - decode = |bytes| - when bytes is - [] -> Err(IncompleteSequence) - [.., last] if last >= 128 -> Err(IncompleteSequence) - _ -> - bytes - |> List.walk( - { integers: [], integer: 0 }, - |state, byte| - last_7_bits = byte % 128 - integer = state.integer * 128 + Num.to_u32(last_7_bits) - if byte >= 128 then - { state & integer } - else - { integers: state.integers |> List.append(integer), integer: 0 }, - ) - |> .integers - |> Ok + decode : List(U8) -> Try(List(U32), [IncompleteSequence]) + decode = |bytes| { + match bytes { + [] => Err(IncompleteSequence) + [.., last] if last >= 128 => Err(IncompleteSequence) + _ => { + res = bytes.fold( + { integers: [], integer: 0 }, + |state, byte| { + last_7_bits = byte % 128 + integer = state.integer * 128 + last_7_bits.to_u32() + if byte >= 128 { + { ..state, integer } + } else { + { integers: state.integers.append(integer), integer: 0 } + } + }, + ) + Ok(res.integers) + } + } + } } -encode_integer : U32 -> List U8 -encode_integer = |integer| - help = |bytes, n| - if n == 0 then - bytes - else - next_n = n // 128 # same as n |> Num.shift_right_zf_by 7 - last_7_bits = n % 128 |> Num.to_u8 # same as n |> Num.bitwise_and 0b1111111 |> Num.to_u8 - byte = - if bytes == [] then - last_7_bits - else - last_7_bits + 128 # same as last7Bits |> Num.bitwise_or 0b10000000 - help((bytes |> List.append(byte)), next_n) +encode_integer : U32 -> List(U8) +encode_integer = |integer| { + help : List(U8), U32 -> List(U8) + help = |bytes, n| { + if n == 0 { + bytes + } else { + next_n = n // 128 + last_7_bits = (n % 128).to_u8_try() ?? { + crash "Unreachable" + } + byte = match bytes { + [] => last_7_bits + _ => last_7_bits + 128 + } + help(bytes.append(byte), next_n) + } + } + if integer == 0 { + [0] + } else { + help([], integer)->reverse() + } +} + +# The following functions should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} - if integer == 0 then [0] else help([], integer) |> List.reverse +reverse : List(a) -> List(a) +reverse = |list| { + match list { + [] => [] + [first, .. as rest] => reverse(rest).append(first) + } +} diff --git a/exercises/practice/variable-length-quantity/.meta/template.j2 b/exercises/practice/variable-length-quantity/.meta/template.j2 index 209ac5be..610258f9 100644 --- a/exercises/practice/variable-length-quantity/.meta/template.j2 +++ b/exercises/practice/variable-length-quantity/.meta/template.j2 @@ -21,7 +21,7 @@ expect { {%- else %} expect { bytes = {{ case["input"]["integers"] | to_roc }} - result = decode bytes + result = decode(bytes) {%- if case["expected"]["error"] %} result.is_err() {%- else %} diff --git a/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc b/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc index 767a4d24..b9aa9c8f 100644 --- a/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc +++ b/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc @@ -1,9 +1,11 @@ VariableLengthQuantity :: {}.{ - encode : List U32 -> List U8 - encode = |integers| - crash("Please implement the 'encode' function") + encode : List(U32) -> List(U8) + encode = |integers| { + crash "Please implement the 'encode' function" + } - decode : List U8 -> Result (List U32) _ - decode = |bytes| - crash("Please implement the 'decode' function") + decode : List(U8) -> Try(List(U32), _) + decode = |bytes| { + crash "Please implement the 'decode' function" + } } diff --git a/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc b/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc index ca2fbbe7..02f28f47 100644 --- a/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc +++ b/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/variable-length-quantity/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-18 import VariableLengthQuantity exposing [encode, decode] @@ -199,8 +199,7 @@ expect { # one byte expect { bytes = [127] - result = decode - bytes + result = decode(bytes) expected = Ok([127]) result == expected } @@ -208,8 +207,7 @@ expect { # two bytes expect { bytes = [192, 0] - result = decode - bytes + result = decode(bytes) expected = Ok([8192]) result == expected } @@ -217,8 +215,7 @@ expect { # three bytes expect { bytes = [255, 255, 127] - result = decode - bytes + result = decode(bytes) expected = Ok([2097151]) result == expected } @@ -226,8 +223,7 @@ expect { # four bytes expect { bytes = [129, 128, 128, 0] - result = decode - bytes + result = decode(bytes) expected = Ok([2097152]) result == expected } @@ -235,8 +231,7 @@ expect { # maximum 32-bit integer expect { bytes = [143, 255, 255, 255, 127] - result = decode - bytes + result = decode(bytes) expected = Ok([4294967295]) result == expected } @@ -244,24 +239,21 @@ expect { # incomplete sequence causes error expect { bytes = [255] - result = decode - bytes + result = decode(bytes) result.is_err() } # incomplete sequence causes error, even if value is zero expect { bytes = [128] - result = decode - bytes + result = decode(bytes) result.is_err() } # multiple values expect { bytes = [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0] - result = decode - bytes + result = decode(bytes) expected = Ok([8192, 1193046, 268435455, 0, 16383, 16384]) result == expected } From 1c80b59ae18d75fbb3ea8997220e995c475007eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 19 Jun 2026 14:13:01 +1200 Subject: [PATCH 098/162] Remove specific errors from affine-cipher exercise stub --- exercises/practice/affine-cipher/AffineCipher.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/affine-cipher/AffineCipher.roc b/exercises/practice/affine-cipher/AffineCipher.roc index 7521ddcc..eb06fe9b 100644 --- a/exercises/practice/affine-cipher/AffineCipher.roc +++ b/exercises/practice/affine-cipher/AffineCipher.roc @@ -5,7 +5,7 @@ AffineCipher :: { a : U64, b : U64 }.{ group_length : U64 group_length = 5 - new : { a : U64, b : U64 } -> Try(AffineCipher, [InvalidKey]) + new : { a : U64, b : U64 } -> Try(AffineCipher, _) new = |key| { crash "Please implement the 'new' method" } From 8fd010fedd3eeaeb8d0010f911f97964fb9f073e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 19 Jun 2026 14:14:57 +1200 Subject: [PATCH 099/162] Update exercise to new compiler: luhn --- exercises/practice/luhn/.meta/Example.roc | 99 ++++++++++++++--------- exercises/practice/luhn/Luhn.roc | 7 +- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/exercises/practice/luhn/.meta/Example.roc b/exercises/practice/luhn/.meta/Example.roc index 822383f0..fb5c8f5d 100644 --- a/exercises/practice/luhn/.meta/Example.roc +++ b/exercises/practice/luhn/.meta/Example.roc @@ -1,45 +1,66 @@ Luhn :: {}.{ - valid : Str -> Bool - valid = |number| - when to_digits(number) is - Ok(digits) if List.len(digits) > 1 -> - map_every_other_backwards( - digits, - |digit| - product = digit * 2 - if product < 10 then product else product - 9, - ) - |> List.sum - |> Num.is_multiple_of(10) - - _ -> Bool.False + valid : Str -> Bool + valid = |number| { + match to_digits(number) { + Ok(digits) if digits.len() > 1 => { + sum = map_every_other_backwards( + digits, + |digit| { + product = digit * 2 + if product < 10 { + product + } else { + product - 9 + } + }, + ).sum() + sum % 10 == 0 + } + _ => Bool.False + } + } } -to_digits : Str -> Result (List U16) [IllegalCharacter] -to_digits = |number| - help = |input, digits| - when input is - [] -> Ok(digits) - [byte, .. as rest] if byte == ' ' -> help(rest, digits) - [byte, .. as rest] if '0' <= byte and byte <= '9' -> - # convert to U16 to prevent an overflow when summing up the digits - digit = byte - '0' |> Num.to_u16 - help(rest, List.append(digits, digit)) - - _ -> Err(IllegalCharacter) - help(Str.to_utf8(number), []) +to_digits : Str -> Try(List(U16), [IllegalCharacter]) +to_digits = |number| { + help : List(U8), List(U16) -> Try(List(U16), [IllegalCharacter]) + help = |input, digits| { + match input { + [] => Ok(digits) + [byte, .. as rest] if byte == ' ' => help(rest, digits) + [byte, .. as rest] if '0' <= byte and byte <= '9' => { + digit = (byte - '0').to_u16() + help(rest, digits.append(digit)) + } + _ => Err(IllegalCharacter) + } + } + help(number.to_utf8(), []) +} -map_every_other_backwards : List a, (a -> a) -> List a -map_every_other_backwards = |list, func| - help = |state, input| - when input is - [.. as rest, x, y] -> - List.append(state, y) - |> List.append(func(x)) - |> help(rest) +map_every_other_backwards : List(a), (a -> a) -> List(a) +map_every_other_backwards = |list, func| { + help = |state, input| { + match input { + [.. as rest, x, y] => { + state + .append(y) + .append(func(x)) + ->help(rest) + } - [x] -> List.append(state, x) - [] -> state + [x] => state.append(x) + [] => state + } + } + help([], list)->reverse() +} - help([], list) - |> List.reverse +# List.reverse should soon be available in Roc's builtins +reverse : List(a) -> List(a) +reverse = |list| { + match list { + [] => [] + [first, .. as rest] => reverse(rest).append(first) + } +} diff --git a/exercises/practice/luhn/Luhn.roc b/exercises/practice/luhn/Luhn.roc index 25f7a343..c90cb499 100644 --- a/exercises/practice/luhn/Luhn.roc +++ b/exercises/practice/luhn/Luhn.roc @@ -1,5 +1,6 @@ Luhn :: {}.{ - valid : Str -> Bool - valid = |digits| - crash("Please implement the 'valid' function") + valid : Str -> Bool + valid = |digits| { + crash "Please implement the 'valid' function" + } } From 642a5e83cd45049e97a753c124ea41e84284cf77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 19 Jun 2026 14:52:38 +1200 Subject: [PATCH 100/162] Update exercise to new compiler: saddle-points --- .../practice/saddle-points/.meta/Example.roc | 115 ++++++++++++------ .../practice/saddle-points/SaddlePoints.roc | 14 +-- 2 files changed, 82 insertions(+), 47 deletions(-) diff --git a/exercises/practice/saddle-points/.meta/Example.roc b/exercises/practice/saddle-points/.meta/Example.roc index e4c96f7a..99f88bd0 100644 --- a/exercises/practice/saddle-points/.meta/Example.roc +++ b/exercises/practice/saddle-points/.meta/Example.roc @@ -1,47 +1,82 @@ SaddlePoints :: {}.{ - saddle_points : Forest -> Set Position - saddle_points = |tree_heights| - tallest_trees_east_west = - tree_heights - |> List.map_with_index( - |row, row_index| - max_in_row = row |> List.max |> Result.with_default(0) - row - |> List.map_with_index( - |height, column_index| - if height == max_in_row then [{ row: row_index + 1, column: column_index + 1 }] else [], - ) - |> List.join, - ) - |> List.join - |> Set.from_list + Forest : List(List(U8)) + Position : { row : U64, column : U64 } - num_columns = tree_heights |> List.map(List.len) |> List.max |> Result.with_default(0) - smallest_trees_north_south = - List.range({ start: At(0), end: Before(num_columns) }) - |> List.map( - |column_index| - column = - tree_heights - |> List.map_with_index( - |row, row_index| - row - |> List.get(column_index)? - |> |height| Ok({ height, row_index }), - ) - |> List.keep_oks(|id| id) + saddle_points : Forest -> Set(Position) + saddle_points = |tree_heights| { + tallest_trees_east_west : Set(Position) + tallest_trees_east_west = { + tree_heights + .map_with_index( + |row, row_index| { + max_in_row : U8 + max_in_row = row.max() ?? 0.U8 + row.map_with_index( + |height, column_index| { + if height == max_in_row + [{ row: row_index + 1, column: column_index + 1 }] + else + [] + }, + ) + ->join_map(|id| id) # TODO: replace with .join() when available + }, + ) + ->join_map(|id2| id2) # TODO: replace with .join() when available + ->Set.from_list() + } - min_in_column = column |> List.map(.height) |> List.min |> Result.with_default(0) - column - |> List.keep_if(|{ height }| height == min_in_column) - |> List.map(|{ row_index }| { row: row_index + 1, column: column_index + 1 }), - ) - |> List.join_map(|id| id) - |> Set.from_list + num_columns : U64 + num_columns = tree_heights.map(List.len).max() ?? 0 - tallest_trees_east_west |> Set.intersection(smallest_trees_north_south) + smallest_trees_north_south : Set(Position) + smallest_trees_north_south = + (0..join_map( + |column_index| { + column : List({ height : U8, row_index : U64 }) + column = + tree_heights + .map_with_index( + |row, row_index| { + height = row.get(column_index)? + Ok({ height, row_index }) + }, + ) + ->keep_oks(|id| id) + + min_in_column : U8 + min_in_column = column.map(|c| c.height).min() ?? 0.U8 + column + .keep_if(|{ height, row_index: _ }| height == min_in_column) + .map(|{ height: _, row_index }| { row: row_index + 1, column: column_index + 1 }) + }, + ) + ->Set.from_list() + + tallest_trees_east_west.intersection(smallest_trees_north_south) + } } +# The following functions should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} -Forest : List (List U8) -Position : { row : U64, column : U64 } +keep_oks = |iter, func| { + iter + ->join_map( + |item| { + match func(item) { + Ok(result) => [result] + Err(_) => [] + } + }, + ) +} diff --git a/exercises/practice/saddle-points/SaddlePoints.roc b/exercises/practice/saddle-points/SaddlePoints.roc index d4043cd6..db8a0d4b 100644 --- a/exercises/practice/saddle-points/SaddlePoints.roc +++ b/exercises/practice/saddle-points/SaddlePoints.roc @@ -1,9 +1,9 @@ SaddlePoints :: {}.{ - saddle_points : Forest -> Set Position - saddle_points = |tree_heights| - crash("Please implement the 'saddle_points' function") -} - + Forest : List(List(U8)) + Position : { row : U64, column : U64 } -Forest : List (List U8) -Position : { row : U64, column : U64 } + saddle_points : Forest -> Set(Position) + saddle_points = |tree_heights| { + crash "Please implement the 'saddle_points' function" + } +} From cb33966bcd171371d4a551b90029a36d1dd957da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 19 Jun 2026 15:05:13 +1200 Subject: [PATCH 101/162] Update exercise to new compiler: word-count --- .../practice/word-count/.meta/Example.roc | 97 ++++++++++--------- exercises/practice/word-count/WordCount.roc | 7 +- 2 files changed, 57 insertions(+), 47 deletions(-) diff --git a/exercises/practice/word-count/.meta/Example.roc b/exercises/practice/word-count/.meta/Example.roc index 4c489d07..7ced94c5 100644 --- a/exercises/practice/word-count/.meta/Example.roc +++ b/exercises/practice/word-count/.meta/Example.roc @@ -1,47 +1,56 @@ WordCount :: {}.{ - count_words : Str -> Dict Str U64 - count_words = |sentence| - sentence - |> Str.to_utf8 - |> List.append(' ') # to ensure the last word is added - |> List.walk( - { words: [], word: [], contraction_started: Bool.False }, - |state, char| - { words, word, contraction_started } = state - when char is - c if c >= 'A' and c <= 'Z' -> - { words, word: word |> List.append((c - 'A' + 'a')), contraction_started: Bool.False } + count_words : Str -> Dict(Str, U64) + count_words = |sentence| { + all_words = + sentence + .to_utf8() + .append(' ') + .fold( + { words: [], word: [], contraction_started: Bool.False }, + |state, char| { + { words, word, contraction_started } = state + if char >= 'A' and char <= 'Z' { + { words, word: word.append(char - 'A' + 'a'), contraction_started: Bool.False } + } else if (char >= 'a' and char <= 'z') or (char >= '0' and char <= '9') { + { words, word: word.append(char), contraction_started: Bool.False } + } else { + if word.is_empty() { + state + } else if char != '\'' or contraction_started { + if contraction_started { + { words: words.append(word.drop_last(1)), word: [], contraction_started: Bool.False } + } else { + { words: words.append(word), word: [], contraction_started: Bool.False } + } + } else { + { words, word: word.append(char), contraction_started: Bool.True } + } + } + }, + ) + .words + .drop_if(List.is_empty) - c if c >= 'a' and c <= 'z' or c >= '0' and c <= '9' -> - { words, word: word |> List.append(c), contraction_started: Bool.False } - - c -> - if List.is_empty(word) then - state - else if c != '\'' or contraction_started then - if contraction_started then - { words: words |> List.append((word |> List.drop_last(1))), word: [], contraction_started: Bool.False } - else - { words: words |> List.append(word), word: [], contraction_started: Bool.False } - else - { words, word: word |> List.append(c), contraction_started: Bool.True }, - ) - |> .words - |> List.drop_if(List.is_empty) - |> List.walk( - Dict.empty({}), - |result, chars| - word = - when chars |> Str.from_utf8 is - Ok(parsed_word) -> parsed_word - Err(BadUtf8(_)) -> crash("Unreachable: we only use ASCII characters") - result - |> Dict.update( - word, - |maybe_count| - when maybe_count is - Ok(count) -> Ok((count + 1)) - Err(Missing) -> Ok(1), - ), - ) + all_words.fold( + Dict.empty(), + |result, chars| { + word = + match Str.from_utf8(chars) { + Ok(parsed_word) => parsed_word + Err(BadUtf8(_)) => { + crash "Unreachable: we only use ASCII characters" + } + } + result.update( + word, + |maybe_count| { + match maybe_count { + Ok(count) => Ok(count + 1) + Err(Missing) => Ok(1) + } + }, + ) + }, + ) + } } diff --git a/exercises/practice/word-count/WordCount.roc b/exercises/practice/word-count/WordCount.roc index ef308756..299d206d 100644 --- a/exercises/practice/word-count/WordCount.roc +++ b/exercises/practice/word-count/WordCount.roc @@ -1,5 +1,6 @@ WordCount :: {}.{ - count_words : Str -> Dict Str U64 - count_words = |sentence| - crash("Please implement the 'count_words' function") + count_words : Str -> Dict(Str, U64) + count_words = |sentence| { + crash "Please implement the 'count_words' function" + } } From 9f4ebf2900cc3d4dc7265b9bbdc07900f9612a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 19 Jun 2026 15:06:00 +1200 Subject: [PATCH 102/162] Update exercise to new compiler: pig-latin --- .../practice/pig-latin/.meta/Example.roc | 95 +++++++++++-------- exercises/practice/pig-latin/PigLatin.roc | 7 +- 2 files changed, 57 insertions(+), 45 deletions(-) diff --git a/exercises/practice/pig-latin/.meta/Example.roc b/exercises/practice/pig-latin/.meta/Example.roc index 0d0a1850..e2e69d69 100644 --- a/exercises/practice/pig-latin/.meta/Example.roc +++ b/exercises/practice/pig-latin/.meta/Example.roc @@ -1,49 +1,60 @@ PigLatin :: {}.{ - translate : Str -> Str - translate = |phrase| - phrase - |> Str.split_on(" ") - |> List.map(translate_word) - |> Str.join_with(" ") + translate : Str -> Str + translate = |phrase| { + phrase + .split_on(" ") + .map(translate_word) + ->Str.join_with(" ") + } } +is_vowel = |char| { + ['a', 'e', 'i', 'o', 'u'].contains(char) +} -is_vowel = |char| - ['a', 'e', 'i', 'o', 'u'] |> List.contains(char) - -rule1_applies = |chars| - when chars is - [c, ..] if is_vowel(c) -> Bool.True - ['x', 'r', ..] -> Bool.True - ['y', 't', ..] -> Bool.True - _ -> Bool.False +rule1_applies = |chars| { + match chars { + [c, ..] if is_vowel(c) => Bool.True + ['x', 'r', ..] => Bool.True + ['y', 't', ..] => Bool.True + _ => Bool.False + } +} -pig_latin_swap = |chars| - if rule1_applies(chars) then - chars - else - (_, split_index) = - chars - |> List.walk_until( - (0, 0), - |(previous_char, index), char| - when (previous_char, char) is - ('q', 'u') -> Break((0, index + 1)) # rule 3 - (_, 'y') if index > 0 -> Break((0, index)) # rule 4 - (_, c) if is_vowel(c) -> Break((0, index)) # rule 2 - _ -> Continue((char, index + 1)), - ) - { before, others } = chars |> List.split_at(split_index) - others |> List.concat(before) +pig_latin_swap = |chars| { + if rule1_applies(chars) { + chars + } else { + (_, split_index) = + chars + .fold_until( + (0, 0), + |(previous_char, index), char| { + match (previous_char, char) { + ('q', 'u') => Break((0, index + 1)) + (_, 'y') if index > 0 => Break((0, index)) + (_, c) if is_vowel(c) => Break((0, index)) + _ => Continue((char, index + 1)) + } + }, + ) + { before, others } = chars.split_at(split_index) + others.concat(before) + } +} translate_word : Str -> Str -translate_word = |word| - maybe_result = - word - |> Str.to_utf8 - |> pig_latin_swap - |> List.concat(['a', 'y']) - |> Str.from_utf8 - when maybe_result is - Ok(result) -> result - Err(_) -> crash("Unreachable") +translate_word = |word| { + maybe_result = + word + .to_utf8() + ->pig_latin_swap() + .concat(['a', 'y']) + ->Str.from_utf8() + match maybe_result { + Ok(result) => result + Err(_) => { + crash "Unreachable" + } + } +} diff --git a/exercises/practice/pig-latin/PigLatin.roc b/exercises/practice/pig-latin/PigLatin.roc index 3cb52aaa..18b3cb15 100644 --- a/exercises/practice/pig-latin/PigLatin.roc +++ b/exercises/practice/pig-latin/PigLatin.roc @@ -1,5 +1,6 @@ PigLatin :: {}.{ - translate : Str -> Str - translate = |phrase| - crash("Please implement the 'translate' function") + translate : Str -> Str + translate = |phrase| { + crash "Please implement the 'translate' function" + } } From e35f87ab2ffb6be0d0a64a71f137302fa407bfa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 19 Jun 2026 15:53:40 +1200 Subject: [PATCH 103/162] Update exercise to new compiler: run-length-encoding --- .../run-length-encoding/.meta/Example.roc | 122 +++++++++++------- .../run-length-encoding/.meta/template.j2 | 3 +- .../run-length-encoding/RunLengthEncoding.roc | 14 +- .../run-length-encoding-test.roc | 5 +- 4 files changed, 89 insertions(+), 55 deletions(-) diff --git a/exercises/practice/run-length-encoding/.meta/Example.roc b/exercises/practice/run-length-encoding/.meta/Example.roc index baa9afec..a97b1629 100644 --- a/exercises/practice/run-length-encoding/.meta/Example.roc +++ b/exercises/practice/run-length-encoding/.meta/Example.roc @@ -1,50 +1,80 @@ RunLengthEncoding :: {}.{ - encode : Str -> Result Str [BadUtf8 _] - encode = |string| - append_count_and_letter = |state| - if state.count == 0 then - [] - else if state.count == 1 then - state.chars |> List.append(state.last_char) - else - digits = state.count |> Num.to_str |> Str.to_utf8 - state.chars |> List.concat(digits) |> List.append(state.last_char) + encode : Str -> Try(Str, [BadUtf8(_), ..]) + encode = |string| { + append_count_and_letter = |state| { + match state.count { + 0 => [] + 1 => state.chars.append(state.last_char) + _ => { + digits = state.count.to_str().to_utf8() + state.chars.concat(digits).append(state.last_char) + } + } + } - string - |> Str.to_utf8 - |> List.walk( - { chars: [], last_char: 0, count: 0 }, - |state, char| - if state.count == 0 then - { chars: [], last_char: char, count: 1 } - else if state.last_char == char then - { state & count: state.count + 1 } - else - chars = append_count_and_letter(state) - { chars, last_char: char, count: 1 }, - ) - |> append_count_and_letter - |> Str.from_utf8 + string + .to_utf8() + .fold( + { chars: [], last_char: 0.U8, count: 0.U64 }, + |state, char| { + if state.count == 0 { + { chars: [], last_char: char, count: 1 } + } else if state.last_char == char { + { ..state, count: state.count + 1 } + } else { + chars = append_count_and_letter(state) + { chars, last_char: char, count: 1 } + } + }, + ) + ->append_count_and_letter() + ->Str.from_utf8() + } - decode : Str -> Result Str [BadUtf8 _, InvalidNumStr] - decode = |string| - string - |> Str.to_utf8 - |> List.walk_try( - { chars: [], digits: [] }, - |state, char| - if char >= '0' and char <= '9' then - digits = state.digits |> List.append(char) - Ok({ state & digits }) - else if state.digits == [] then - chars = state.chars |> List.append(char) - Ok({ state & chars }) - else - count_str = Str.from_utf8(state.digits)? - count = Str.to_u64(count_str)? - chars = state.chars |> List.concat(List.repeat(char, count)) - Ok({ chars, digits: [] }), - )? - |> .chars - |> Str.from_utf8 + decode : Str -> Try(Str, [BadUtf8(_), BadNumStr, ..]) + decode = |string| { + state_to_str = |state| state.chars->Str.from_utf8() + string + .to_utf8() + ->fold_try( + { chars: [], digits: [] }, + |state, char| { + if char >= '0' and char <= '9' { + digits = state.digits.append(char) + Ok({ ..state, digits }) + } else if state.digits == [] { + chars = state.chars.append(char) + Ok({ ..state, chars }) + } else { + count_str = state.digits->Str.from_utf8()? + count = count_str->U64.from_str()? + chars = state.chars.concat(char->List.repeat(count)) + Ok({ chars, digits: [] }) + } + }, + )? + ->state_to_str() + + } +} + +# The following function should soon be available in Roc's builtins +fold_try : List(a), b, (b, a -> Try(b, err)) -> Try(b, err) +fold_try = |list, init, func| { + list.fold_until( + Ok(init), + |state, item| { + match state { + Ok(internal_state) => { + match func(internal_state, item) { + Ok(new_state) => Continue(Ok(new_state)) + Err(final_err) => Break(Err(final_err)) + } + } + Err(_) => { + crash "Unreachable" + } + } + }, + ) } diff --git a/exercises/practice/run-length-encoding/.meta/template.j2 b/exercises/practice/run-length-encoding/.meta/template.j2 index 7e6bed06..a6afb823 100644 --- a/exercises/practice/run-length-encoding/.meta/template.j2 +++ b/exercises/practice/run-length-encoding/.meta/template.j2 @@ -14,7 +14,8 @@ import {{ exercise | to_pascal }} exposing [encode, decode] expect { string = {{ case["input"]["string"] | to_roc }} {%- if case["property"] == "consistency" %} - result = string -> encode() -> Result.try(decode) + encoded = string -> encode()? + result = encoded -> decode() result == Ok(string) {%- else %} result = string -> {{ case["property"] | to_snake }} diff --git a/exercises/practice/run-length-encoding/RunLengthEncoding.roc b/exercises/practice/run-length-encoding/RunLengthEncoding.roc index a791b2e0..8bf6372a 100644 --- a/exercises/practice/run-length-encoding/RunLengthEncoding.roc +++ b/exercises/practice/run-length-encoding/RunLengthEncoding.roc @@ -1,9 +1,11 @@ RunLengthEncoding :: {}.{ - encode : Str -> Result Str _ - encode = |string| - crash("Please implement the 'encode' function") + encode : Str -> Try(Str, _) + encode = |string| { + crash "Please implement the 'encode' function" + } - decode : Str -> Result Str _ - decode = |string| - crash("Please implement the 'decode' function") + decode : Str -> Try(Str, _) + decode = |string| { + crash "Please implement the 'decode' function" + } } diff --git a/exercises/practice/run-length-encoding/run-length-encoding-test.roc b/exercises/practice/run-length-encoding/run-length-encoding-test.roc index 00308330..9baeab93 100644 --- a/exercises/practice/run-length-encoding/run-length-encoding-test.roc +++ b/exercises/practice/run-length-encoding/run-length-encoding-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/run-length-encoding/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-19 import RunLengthEncoding exposing [encode, decode] @@ -115,7 +115,8 @@ expect { # encode followed by decode gives original string expect { string = "zzz ZZ zZ" - result = string->encode()->Result.try(decode) + encoded = string->encode()? + result = encoded->decode() result == Ok(string) } From eb118ba5bafed5647b7a7d616c903eb42f7c037f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 19 Jun 2026 22:49:30 +1200 Subject: [PATCH 104/162] Update exercise to new compiler: dominoes --- exercises/practice/dominoes/.meta/Example.roc | 86 ++--- exercises/practice/dominoes/.meta/template.j2 | 93 +++-- exercises/practice/dominoes/Dominoes.roc | 12 +- exercises/practice/dominoes/dominoes-test.roc | 346 ++++++++---------- 4 files changed, 237 insertions(+), 300 deletions(-) diff --git a/exercises/practice/dominoes/.meta/Example.roc b/exercises/practice/dominoes/.meta/Example.roc index a5674335..1322a33a 100644 --- a/exercises/practice/dominoes/.meta/Example.roc +++ b/exercises/practice/dominoes/.meta/Example.roc @@ -1,51 +1,43 @@ Dominoes :: {}.{ -} - - -Dominoes :: {}.{ - Domino := (U8, U8) - - find_chain : List(Domino) -> Try(List(Domino), [NoChainExists]) - find_chain = |dominoes| { - find_chain_helper = |used, available| { - match available { - [] => { - match used { - [] => Ok([]) - [single] => if single.0 == single.1 Ok(used) else Err(NoChainExists) - [first, .., last] => if first.0 == last.1 Ok(used) else Err(NoChainExists) - } - } + Domino : (U8, U8) - [first_available, .. as rest_available] => { - match used { - [] => find_chain_helper([first_available], rest_available) - [.., last_used] => { - available - .fold_with_index_until( - Err(NoChainExists), - |_, domino, index| { - maybe_chain = { - if last_used.1 == domino.0 { - find_chain_helper((used |> List.append(domino)), (available |> List.drop_at(index))) - } else if last_used.1 == domino.1 { - find_chain_helper((used |> List.append((domino.1, domino.0))), (available |> List.drop_at(index))) - } else { - Err(NoChainExists) - } - } - match maybe_chain { - Ok(chain) => Break(Ok(chain)) - Err(NoChainExists) => Continue(Err(NoChainExists)) - } - } - ) - } - } - } - } - } + find_chain : List(Domino) -> Try(List(Domino), [NoChainExists]) + find_chain = |dominoes| { + find_chain_helper = |used, available| { + match available { + [] => match used { + [] => Ok([]) + [(single_left, single_right)] => + if single_left == single_right Ok(used) else Err(NoChainExists) + [(first_left, _), .., (_, last_right)] => + if first_left == last_right Ok(used) else Err(NoChainExists) + } + [first_available, .. as rest_available] => match used { + [] => find_chain_helper([first_available], rest_available) + [.., (_, last_used_right)] => { + available + .fold_with_index_until( + Err(NoChainExists), + |_, (domino_left, domino_right), index| { + maybe_chain = + if last_used_right == domino_left { + find_chain_helper(used.append((domino_left, domino_right)), available.drop_at(index)) + } else if last_used_right == domino_right { + find_chain_helper(used.append((domino_right, domino_left)), available.drop_at(index)) + } else { + Err(NoChainExists) + } + match maybe_chain { + Ok(chain) => Break(Ok(chain)) + Err(NoChainExists) => Continue(Err(NoChainExists)) + } + }, + ) + } + } + } + } - find_chain_helper([], dominoes) - } + find_chain_helper([], dominoes) + } } diff --git a/exercises/practice/dominoes/.meta/template.j2 b/exercises/practice/dominoes/.meta/template.j2 index fbee614b..9df4df99 100644 --- a/exercises/practice/dominoes/.meta/template.j2 +++ b/exercises/practice/dominoes/.meta/template.j2 @@ -12,58 +12,13 @@ import {{ exercise | to_pascal }} exposing [Domino, find_chain] ] {%- endmacro %} -## Rotate each domino if needed to ensure that the small side is on the left -canonicalize : List(Domino) -> List(Domino) -canonicalize = |dominoes| { - dominoes - .map(|domino| { - if domino.0 > domino.1 (domino.1, domino.0) else domino - }) -} - -## Ensure that the given result is Ok and is a valid chain for the -## given list of dominoes -is_valid_chain_for : Try(List(Domino), _), List(Domino) -> Bool -is_valid_chain_for = |maybe_chain, dominoes| { - match maybe_chain { - Err(_) => Bool.False - Ok(chain) => { - if Set.from_list(canonicalize(chain)) == Set.from_list(canonicalize(dominoes)) { - match chain { - [] => Bool.True - [.., last] => { - chain - .fold_until( - Ok(last), - |state, domino| { - match state { - Err(InvalidChain) => { crash "Unreachable" } - Ok(previous) => { - if previous.1 == domino.0 { - Continue(Ok(domino)) - } else { - Break(Err(InvalidChain)) - } - } - } - } - ) - .is_ok() - } - } - } else { - Bool.False - } - } - } -} - {% for case in cases -%} # {{ case["description"] }} expect { - result = Domino.find_chain({{ to_list_of_pairs(case["input"]["dominoes"]) }}) + dominoes = {{ to_list_of_pairs(case["input"]["dominoes"]) }} + result = find_chain(dominoes) {%- if case["expected"] %} - result -> is_valid_chain_for({{ to_list_of_pairs(case["input"]["dominoes"]) }}) + result -> is_valid_chain_for(dominoes) {%- else %} result.is_err() {%- endif %} @@ -71,4 +26,46 @@ expect { {% endfor %} +# # Compare two dominoes in lexicographical order, for example (3, 1) > (2, 5) +compare_dominoes : (U8, U8), (U8, U8) -> [LT, GT, EQ] +compare_dominoes = |a, b| { + if a.0 < b.0 { LT } else if a.0 > b.0 { GT } else + if a.1 < b.1 { LT } else if a.1 > b.1 { GT } else { EQ } +} + +# # Rotate each domino if needed to ensure that the small side is on the left +# # then sort the dominoes in lexicographical order +canonicalize : List((U8, U8)) -> List((U8, U8)) +canonicalize = |dominoes| { + maybe_flip : (U8, U8) -> (U8, U8) + maybe_flip = |domino| { + if domino.0 > domino.1 (domino.1, domino.0) else domino + } + dominoes.map(maybe_flip).sort_with(compare_dominoes) +} + +# # Checks whether the list of dominoes forms a valid chain +is_valid_chain = |dominoes| { + match dominoes { + [] => Bool.True + [(first_left, first_right), .. as rest] => { + var $previous = first_right + for (next_left, next_right) in rest { + if $previous != next_left { return Bool.False } + $previous = next_right + } + $previous == first_left + } + } +} + +# # Check whether the given chain (if Ok) is valid and corresponds to the given +# # list of dominoes +is_valid_chain_for = |maybe_chain, dominoes| { + match maybe_chain { + Ok(chain) => (canonicalize(chain) == canonicalize(dominoes)) and is_valid_chain(chain) + Err(_) => Bool.False + } +} + {{ macros.footer() }} diff --git a/exercises/practice/dominoes/Dominoes.roc b/exercises/practice/dominoes/Dominoes.roc index 635e16dc..9f18eab2 100644 --- a/exercises/practice/dominoes/Dominoes.roc +++ b/exercises/practice/dominoes/Dominoes.roc @@ -1,8 +1,8 @@ Dominoes :: {}.{ - find_chain : List Domino -> Result (List Domino) _ - find_chain = |dominoes| - crash("Please implement the 'find_chain' function") -} - + Domino : (U8, U8) -Domino : (U8, U8) + find_chain : List(Domino) -> Try(List(Domino), _) + find_chain = |dominoes| { + crash "Please implement the 'find_chain' function" + } +} diff --git a/exercises/practice/dominoes/dominoes-test.roc b/exercises/practice/dominoes/dominoes-test.roc index a3543c99..3d5caad5 100644 --- a/exercises/practice/dominoes/dominoes-test.roc +++ b/exercises/practice/dominoes/dominoes-test.roc @@ -1,265 +1,213 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/dominoes/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-19 import Dominoes exposing [Domino, find_chain] -## Rotate each domino if needed to ensure that the small side is on the left -canonicalize : List(Domino) -> List(Domino) -canonicalize = |dominoes| { - dominoes - .map( - |domino| { - if domino.0 > domino.1 (domino.1, domino.0) else domino - }, - ) -} - -## Ensure that the given result is Ok and is a valid chain for the -## given list of dominoes -is_valid_chain_for : Try(List(Domino), _), List(Domino) -> Bool -is_valid_chain_for = |maybe_chain, dominoes| { - match maybe_chain { - Err(_) => Bool.False - Ok(chain) => { - if Set.from_list(canonicalize(chain)) == Set.from_list(canonicalize(dominoes)) { - match chain { - [] => Bool.True - [.., last] => { - chain - .fold_until( - Ok(last), - |state, domino| { - match state { - Err(InvalidChain) => { - crash "Unreachable" - } - Ok(previous) => { - if previous.1 == domino.0 { - Continue(Ok(domino)) - } else { - Break(Err(InvalidChain)) - } - } - } - }, - ) - .is_ok() - } - } - } else { - Bool.False - } - } - } -} - # empty input = empty output expect { - result = Domino.find_chain([]) - result->is_valid_chain_for([]) + dominoes = [] + result = find_chain(dominoes) + result->is_valid_chain_for(dominoes) } # singleton input = singleton output expect { - result = Domino.find_chain( - [ - (1, 1), - ], - ) - result->is_valid_chain_for( - [ - (1, 1), - ], - ) + dominoes = [ + (1, 1), + ] + result = find_chain(dominoes) + result->is_valid_chain_for(dominoes) } # singleton that can't be chained expect { - result = Domino.find_chain( - [ - (1, 2), - ], - ) + dominoes = [ + (1, 2), + ] + result = find_chain(dominoes) result.is_err() } # three elements expect { - result = Domino.find_chain( - [ - (1, 2), - (3, 1), - (2, 3), - ], - ) - result->is_valid_chain_for( - [ - (1, 2), - (3, 1), - (2, 3), - ], - ) + dominoes = [ + (1, 2), + (3, 1), + (2, 3), + ] + result = find_chain(dominoes) + result->is_valid_chain_for(dominoes) } # can reverse dominoes expect { - result = Domino.find_chain( - [ - (1, 2), - (1, 3), - (2, 3), - ], - ) - result->is_valid_chain_for( - [ - (1, 2), - (1, 3), - (2, 3), - ], - ) + dominoes = [ + (1, 2), + (1, 3), + (2, 3), + ] + result = find_chain(dominoes) + result->is_valid_chain_for(dominoes) } # can't be chained expect { - result = Domino.find_chain( - [ - (1, 2), - (4, 1), - (2, 3), - ], - ) + dominoes = [ + (1, 2), + (4, 1), + (2, 3), + ] + result = find_chain(dominoes) result.is_err() } # disconnected - simple expect { - result = Domino.find_chain( - [ - (1, 1), - (2, 2), - ], - ) + dominoes = [ + (1, 1), + (2, 2), + ] + result = find_chain(dominoes) result.is_err() } # disconnected - double loop expect { - result = Domino.find_chain( - [ - (1, 2), - (2, 1), - (3, 4), - (4, 3), - ], - ) + dominoes = [ + (1, 2), + (2, 1), + (3, 4), + (4, 3), + ] + result = find_chain(dominoes) result.is_err() } # disconnected - single isolated expect { - result = Domino.find_chain( - [ - (1, 2), - (2, 3), - (3, 1), - (4, 4), - ], - ) + dominoes = [ + (1, 2), + (2, 3), + (3, 1), + (4, 4), + ] + result = find_chain(dominoes) result.is_err() } # need backtrack expect { - result = Domino.find_chain( - [ - (1, 2), - (2, 3), - (3, 1), - (2, 4), - (2, 4), - ], - ) - result->is_valid_chain_for( - [ - (1, 2), - (2, 3), - (3, 1), - (2, 4), - (2, 4), - ], - ) + dominoes = [ + (1, 2), + (2, 3), + (3, 1), + (2, 4), + (2, 4), + ] + result = find_chain(dominoes) + result->is_valid_chain_for(dominoes) } # separate loops expect { - result = Domino.find_chain( - [ - (1, 2), - (2, 3), - (3, 1), - (1, 1), - (2, 2), - (3, 3), - ], - ) - result->is_valid_chain_for( - [ - (1, 2), - (2, 3), - (3, 1), - (1, 1), - (2, 2), - (3, 3), - ], - ) + dominoes = [ + (1, 2), + (2, 3), + (3, 1), + (1, 1), + (2, 2), + (3, 3), + ] + result = find_chain(dominoes) + result->is_valid_chain_for(dominoes) } # nine elements expect { - result = Domino.find_chain( - [ - (1, 2), - (5, 3), - (3, 1), - (1, 2), - (2, 4), - (1, 6), - (2, 3), - (3, 4), - (5, 6), - ], - ) - result->is_valid_chain_for( - [ - (1, 2), - (5, 3), - (3, 1), - (1, 2), - (2, 4), - (1, 6), - (2, 3), - (3, 4), - (5, 6), - ], - ) + dominoes = [ + (1, 2), + (5, 3), + (3, 1), + (1, 2), + (2, 4), + (1, 6), + (2, 3), + (3, 4), + (5, 6), + ] + result = find_chain(dominoes) + result->is_valid_chain_for(dominoes) } # separate three-domino loops expect { - result = Domino.find_chain( - [ - (1, 2), - (2, 3), - (3, 1), - (4, 5), - (5, 6), - (6, 4), - ], - ) + dominoes = [ + (1, 2), + (2, 3), + (3, 1), + (4, 5), + (5, 6), + (6, 4), + ] + result = find_chain(dominoes) result.is_err() } +# # Compare two dominoes in lexicographical order, for example (3, 1) > (2, 5) +compare_dominoes : (U8, U8), (U8, U8) -> [LT, GT, EQ] +compare_dominoes = |a, b| { + if a.0 < b.0 { + LT + } else if a.0 > b.0 { + GT + } else + if a.1 < b.1 { + LT + } else if a.1 > b.1 { + GT + } else { + EQ + } +} + +# # Rotate each domino if needed to ensure that the small side is on the left +# # then sort the dominoes in lexicographical order +canonicalize : List((U8, U8)) -> List((U8, U8)) +canonicalize = |dominoes| { + maybe_flip : (U8, U8) -> (U8, U8) + maybe_flip = |domino| { + if domino.0 > domino.1 (domino.1, domino.0) else domino + } + dominoes.map(maybe_flip).sort_with(compare_dominoes) +} + +# # Checks whether the list of dominoes forms a valid chain +is_valid_chain = |dominoes| { + match dominoes { + [] => Bool.True + [(first_left, first_right), .. as rest] => { + var $previous = first_right + for (next_left, next_right) in rest { + if $previous != next_left { + return Bool.False + } + $previous = next_right + } + $previous == first_left + } + } +} + +# # Check whether the given chain (if Ok) is valid and corresponds to the given +# # list of dominoes +is_valid_chain_for = |maybe_chain, dominoes| { + match maybe_chain { + Ok(chain) => (canonicalize(chain) == canonicalize(dominoes)) and is_valid_chain(chain) + Err(_) => Bool.False + } +} + # This program is only used to run tests with `roc test`, so main! does nothing. main! = |_args| { Ok({}) From 3fb6b9404122ed25e612eb3ddea4449700dfa8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 19 Jun 2026 23:12:03 +1200 Subject: [PATCH 105/162] Update exercise to new compiler: rail-fence-cipher --- .../rail-fence-cipher/.meta/Example.roc | 129 +++++++++++------- .../rail-fence-cipher/RailFenceCipher.roc | 14 +- 2 files changed, 89 insertions(+), 54 deletions(-) diff --git a/exercises/practice/rail-fence-cipher/.meta/Example.roc b/exercises/practice/rail-fence-cipher/.meta/Example.roc index 064dd354..e55b8ecd 100644 --- a/exercises/practice/rail-fence-cipher/.meta/Example.roc +++ b/exercises/practice/rail-fence-cipher/.meta/Example.roc @@ -1,55 +1,88 @@ RailFenceCipher :: {}.{ - encode : Str, U64 -> Result Str [ZeroRails, BadUtf8 _] - encode = |message, rails| - message |> reorder_with(encoded_indices, rails) + encode : Str, U64 -> Try(Str, [ZeroRails, BadUtf8(_), ..]) + encode = |message, rails| { + reorder_with(message, encoded_indices, rails) + } - decode : Str, U64 -> Result Str [ZeroRails, BadUtf8 _] - decode = |encrypted, rails| - encrypted |> reorder_with(decoded_indices, rails) + decode : Str, U64 -> Try(Str, [ZeroRails, BadUtf8(_), ..]) + decode = |encrypted, rails| { + reorder_with(encrypted, decoded_indices, rails) + } } +encoded_indices : U64, U64 -> List(U64) +encoded_indices = |len, rails| { + indices = (0..List.from_iter() + (0..join_map( + |rail| { + period = 2 * (rails - 1) + indices->join_map( + |index| { + to_rail = index % period + if to_rail == rail or to_rail == period - rail { + [index] + } else { + [] + } + }, + ) + }, + ) +} + +decoded_indices : U64, U64 -> List(U64) +decoded_indices = |len, rails| { + encoded_indices(len, rails) + .map_with_index(|encoded, decoded| { encoded, decoded }) + .sort_with( + |{ encoded: encoded1, decoded: _ }, { encoded: encoded2, decoded: _ }| { + if encoded1 < encoded2 { + LT + } else if encoded1 > encoded2 { + GT + } else { + EQ + } + }, + ) + .map(|r| r.decoded) +} -encoded_indices : U64, U64 -> List U64 -encoded_indices = |len, rails| - indices = List.range({ start: At(0), end: Before(len) }) - List.range({ start: At(0), end: Before(rails) }) - |> List.map( - |rail| - period = 2 * (rails - 1) - indices - |> List.map_with_index( - |index, original_index| - to_rail = original_index % period - if to_rail == rail or to_rail == period - rail then - [index] - else - [], - ) - |> List.join, - ) - |> List.join +reorder_with : Str, (U64, U64 -> List(U64)), U64 -> Try(Str, [ZeroRails, BadUtf8(_), ..]) +reorder_with = |message, get_indices, rails| { + if rails == 0 { + Err(ZeroRails) + } else if rails == 1 { + Ok(message) + } else { + chars = message.to_utf8() + indices = get_indices(chars.len(), rails) + result = indices->map_try(|index| chars.get(index)) + match result { + Ok(encrypted_chars) => Str.from_utf8(encrypted_chars) + Err(OutOfBounds) => { + crash "Unreachable: indices cannot be out of bounds here" + } + } + } +} -decoded_indices : U64, U64 -> List U64 -decoded_indices = |len, rails| - encoded_indices(len, rails) - |> List.map_with_index(|encoded, decoded| { encoded, decoded }) - |> List.sort_with( - |{ encoded: encoded1 }, { encoded: encoded2 }| - Num.compare(encoded1, encoded2), - ) - |> List.map(.decoded) +# The following functions should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} -reorder_with : Str, (U64, U64 -> List U64), U64 -> Result Str [ZeroRails, BadUtf8 _] -reorder_with = |message, get_indices, rails| - if rails == 0 then - Err(ZeroRails) - else if rails == 1 then - Ok(message) - else - chars = message |> Str.to_utf8 - result = - get_indices(List.len(chars), rails) - |> List.map_try(|index| chars |> List.get(index)) - when result is - Ok(encrypted_chars) -> encrypted_chars |> Str.from_utf8 - Err(OutOfBounds) -> crash("Unreachable: indices cannot be out of bounds here") +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/rail-fence-cipher/RailFenceCipher.roc b/exercises/practice/rail-fence-cipher/RailFenceCipher.roc index 95c64373..c5625610 100644 --- a/exercises/practice/rail-fence-cipher/RailFenceCipher.roc +++ b/exercises/practice/rail-fence-cipher/RailFenceCipher.roc @@ -1,9 +1,11 @@ RailFenceCipher :: {}.{ - encode : Str, U64 -> Result Str _ - encode = |message, rails| - crash("Please implement the 'encode' function") + encode : Str, U64 -> Try(Str, _) + encode = |message, rails| { + crash "Please implement the 'encode' function" + } - decode : Str, U64 -> Result Str _ - decode = |encrypted, rails| - crash("Please implement the 'decode' function") + decode : Str, U64 -> Try(Str, _) + decode = |encrypted, rails| { + crash "Please implement the 'decode' function" + } } From 4bcb588b860344579ddaf6e9be043b5669d0db7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 14:18:33 +1200 Subject: [PATCH 106/162] Update exercise to new compiler: flower-field --- .../practice/flower-field/.meta/Example.roc | 125 ++++++++------ .../practice/flower-field/.meta/template.j2 | 4 +- .../practice/flower-field/FlowerField.roc | 7 +- .../flower-field/flower-field-test.roc | 158 ++++++------------ 4 files changed, 134 insertions(+), 160 deletions(-) diff --git a/exercises/practice/flower-field/.meta/Example.roc b/exercises/practice/flower-field/.meta/Example.roc index 9887b5f1..d7d69258 100644 --- a/exercises/practice/flower-field/.meta/Example.roc +++ b/exercises/practice/flower-field/.meta/Example.roc @@ -1,57 +1,78 @@ FlowerField :: {}.{ - annotate : Str -> Str - annotate = |garden| - rows = garden |> Str.to_utf8 |> List.split_on('\n') - annotated = - rows - |> List.map_with_index( - |row, y| - row - |> List.map_with_index( - |cell, x| - if cell == '*' then - '*' - else - when count_neighbors(rows, x, y) is - 0 -> ' ' - n -> '0' + n, - ) - |> Str.from_utf8, - ) + annotate : Str -> Str + annotate = |garden| { + rows = garden.to_utf8().split_on('\n') + annotated = + rows.map_with_index( + |row, y| { + row.map_with_index( + |cell, x| { + if cell == '*' { + '*' + } else { + match count_neighbors(rows, x, y) { + 0 => ' ' + n => '0' + n + } + } + }, + ) + ->Str.from_utf8() + }, + ) - annotated - |> List.map( - |maybe_row| - when maybe_row is - Ok(row) -> row - Err(_) -> crash("Unreachable"), - ) # fromUtf8 cannot fail in the code above - |> Str.join_with("\n") + annotated.map( + |maybe_row| { + match maybe_row { + Ok(row) => row + Err(_) => { + crash "Unreachable" + } + } + }, + ) + ->Str.join_with("\n") + } } +count_neighbors : List(List(U8)), U64, U64 -> U8 +count_neighbors = |rows, x, y| { + [-1, 0, 1].map( + |dy| { + [-1, 0, 1].map( + |dx| { + nx = dx + ( + x.to_i64_try() ?? { + crash "Unreachable" + }, + ) + ny = dy + ( + y.to_i64_try() ?? { + crash "Unreachable" + }, + ) + if is_flower(rows, nx, ny) 1 else 0 + }, + ) + .sum() + }, + ) + .sum() +} -is_flower : List (List U8), I64, I64 -> Result Bool [OutOfBounds] -is_flower = |rows, nx, ny| - x = Num.to_u64_checked(nx)? - y = Num.to_u64_checked(ny)? - rows - |> List.get(y)? - |> List.get(x)? - |> Bool.is_eq('*') - |> Ok - -count_neighbors : List (List U8), U64, U64 -> Num * -count_neighbors = |rows, x, y| - [-1, 0, 1] - |> List.map( - |dy| - [-1, 0, 1] - |> List.map( - |dx| - when is_flower(rows, (Num.to_i64(x) + dx), (Num.to_i64(y) + dy)) is - Ok(flower) -> if flower then 1 else 0 - Err(OutOfBounds) -> 0, - ) - |> List.sum, - ) - |> List.sum +is_flower : List(List(U8)), I64, I64 -> Bool +is_flower = |rows, nx, ny| { + if nx < 0 or ny < 0 { + Bool.False + } else { + x = nx.to_u64_try() ?? { + crash "Unreachable" + } + y = ny.to_u64_try() ?? { + crash "Unreachable" + } + row = rows.get(y) ?? [] + cell = row.get(x) ?? ' ' + cell == '*' + } +} diff --git a/exercises/practice/flower-field/.meta/template.j2 b/exercises/practice/flower-field/.meta/template.j2 index 9371ed57..908494cb 100644 --- a/exercises/practice/flower-field/.meta/template.j2 +++ b/exercises/practice/flower-field/.meta/template.j2 @@ -7,9 +7,9 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} expect { - garden = {{ case["input"]["garden"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") + garden = {{ case["input"]["garden"] | to_roc_multiline_string | indent(8) }} result = {{ case["property"] | to_snake }}(garden) - expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") + expected = {{ case["expected"] | to_roc_multiline_string | indent(8) }} result == expected } diff --git a/exercises/practice/flower-field/FlowerField.roc b/exercises/practice/flower-field/FlowerField.roc index 5b88aad9..232ed236 100644 --- a/exercises/practice/flower-field/FlowerField.roc +++ b/exercises/practice/flower-field/FlowerField.roc @@ -1,5 +1,6 @@ FlowerField :: {}.{ - annotate : Str -> Str - annotate = |garden| - crash("Please implement the 'annotate' function") + annotate : Str -> Str + annotate = |garden| { + crash "Please implement the 'annotate' function" + } } diff --git a/exercises/practice/flower-field/flower-field-test.roc b/exercises/practice/flower-field/flower-field-test.roc index 4591b85c..28644463 100644 --- a/exercises/practice/flower-field/flower-field-test.roc +++ b/exercises/practice/flower-field/flower-field-test.roc @@ -1,44 +1,38 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/flower-field/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-20 import FlowerField exposing [annotate] # no rows expect { - garden = "".replace_each("·", " ") + garden = "" result = annotate(garden) - expected = "".replace_each("·", " ") + expected = "" result == expected } # no columns expect { - garden = "".replace_each("·", " ") + garden = "" result = annotate(garden) - expected = "".replace_each("·", " ") + expected = "" result == expected } # no flowers expect { garden = - \\··· - \\··· - \\··· - .replace_each( - "·", - " ", - ) + \\ + \\ + \\ + result = annotate(garden) expected = - \\··· - \\··· - \\··· - .replace_each( - "·", - " ", - ) + \\ + \\ + \\ + result == expected } @@ -48,41 +42,29 @@ expect { \\*** \\*** \\*** - .replace_each( - "·", - " ", - ) + result = annotate(garden) expected = \\*** \\*** \\*** - .replace_each( - "·", - " ", - ) + result == expected } # flower surrounded by spaces expect { garden = - \\··· - \\·*· - \\··· - .replace_each( - "·", - " ", - ) + \\ + \\ * + \\ + result = annotate(garden) expected = \\111 \\1*1 \\111 - .replace_each( - "·", - " ", - ) + result == expected } @@ -90,52 +72,43 @@ expect { expect { garden = \\*** - \\*·* + \\* * \\*** - .replace_each( - "·", - " ", - ) + result = annotate(garden) expected = \\*** \\*8* \\*** - .replace_each( - "·", - " ", - ) + result == expected } # horizontal line expect { - garden = "·*·*·".replace_each("·", " ") + garden = " * * " result = annotate(garden) - expected = "1*2*1".replace_each("·", " ") + expected = "1*2*1" result == expected } # horizontal line, flowers at edges expect { - garden = "*···*".replace_each("·", " ") + garden = "* *" result = annotate(garden) - expected = "*1·1*".replace_each("·", " ") + expected = "*1 1*" result == expected } # vertical line expect { garden = - \\· + \\ \\* - \\· + \\ \\* - \\· - .replace_each( - "·", - " ", - ) + \\ + result = annotate(garden) expected = \\1 @@ -143,10 +116,7 @@ expect { \\2 \\* \\1 - .replace_each( - "·", - " ", - ) + result == expected } @@ -154,79 +124,61 @@ expect { expect { garden = \\* - \\· - \\· - \\· + \\ + \\ + \\ \\* - .replace_each( - "·", - " ", - ) + result = annotate(garden) expected = \\* \\1 - \\· + \\ \\1 \\* - .replace_each( - "·", - " ", - ) + result == expected } # cross expect { garden = - \\··*·· - \\··*·· + \\ * + \\ * \\***** - \\··*·· - \\··*·· - .replace_each( - "·", - " ", - ) + \\ * + \\ * + result = annotate(garden) expected = - \\·2*2· + \\ 2*2 \\25*52 \\***** \\25*52 - \\·2*2· - .replace_each( - "·", - " ", - ) + \\ 2*2 + result == expected } # large garden expect { garden = - \\·*··*· - \\··*··· - \\····*· - \\···*·* - \\·*··*· - \\······ - .replace_each( - "·", - " ", - ) + \\ * * + \\ * + \\ * + \\ * * + \\ * * + \\ + result = annotate(garden) expected = \\1*22*1 \\12*322 - \\·123*2 + \\ 123*2 \\112*4* \\1*22*2 \\111111 - .replace_each( - "·", - " ", - ) + result == expected } From 03a039e70ced13f4ffb2ff91584b4d6d141ec19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 14:21:29 +1200 Subject: [PATCH 107/162] Update exercise to new compiler: minesweeper --- .../practice/minesweeper/.meta/Example.roc | 121 ++++++++------ .../practice/minesweeper/.meta/template.j2 | 4 +- .../practice/minesweeper/Minesweeper.roc | 7 +- .../practice/minesweeper/minesweeper-test.roc | 158 ++++++------------ 4 files changed, 130 insertions(+), 160 deletions(-) diff --git a/exercises/practice/minesweeper/.meta/Example.roc b/exercises/practice/minesweeper/.meta/Example.roc index abca0126..1a9693a9 100644 --- a/exercises/practice/minesweeper/.meta/Example.roc +++ b/exercises/practice/minesweeper/.meta/Example.roc @@ -1,57 +1,74 @@ Minesweeper :: {}.{ - annotate : Str -> Str - annotate = |minefield| - rows = minefield |> Str.to_utf8 |> List.split_on('\n') - annotated = - rows - |> List.map_with_index( - |row, y| - row - |> List.map_with_index( - |cell, x| - if cell == '*' then - '*' - else - when count_neighbors(rows, x, y) is - 0 -> ' ' - n -> '0' + n, - ) - |> Str.from_utf8, - ) + annotate : Str -> Str + annotate = |minefield| { + rows = minefield.to_utf8().split_on('\n') + annotated = + rows.map_with_index( + |row, y| { + row.map_with_index( + |cell, x| { + if cell == '*' { + '*' + } else { + match count_neighbors(rows, x, y) { + 0 => ' ' + n => '0' + n + } + } + }, + ) + ->Str.from_utf8() + }, + ) - annotated - |> List.map( - |maybe_row| - when maybe_row is - Ok(row) -> row - Err(_) -> crash("Unreachable"), - ) # fromUtf8 cannot fail in the code above - |> Str.join_with("\n") + annotated + .map( + |maybe_row| { + match maybe_row { + Ok(row) => row + Err(_) => { + crash "Unreachable" + } + } + }, + ) + ->Str.join_with("\n") + } } +is_bomb : List(List(U8)), I64, I64 -> Try(Bool, [OutOfBounds]) +is_bomb = |rows, nx, ny| { + if nx < 0 or ny < 0 { + Err(OutOfBounds) + } else { + x = nx.to_u64_try() ?? 0 + y = ny.to_u64_try() ?? 0 + row = rows.get(y)? + cell = row.get(x)? + Ok(cell == '*') + } +} -is_bomb : List (List U8), I64, I64 -> Result Bool [OutOfBounds] -is_bomb = |rows, nx, ny| - x = Num.to_u64_checked(nx)? - y = Num.to_u64_checked(ny)? - rows - |> List.get(y)? - |> List.get(x)? - |> Bool.is_eq('*') - |> Ok - -count_neighbors : List (List U8), U64, U64 -> Num * -count_neighbors = |rows, x, y| - [-1, 0, 1] - |> List.map( - |dy| - [-1, 0, 1] - |> List.map( - |dx| - when is_bomb(rows, (Num.to_i64(x) + dx), (Num.to_i64(y) + dy)) is - Ok(bomb) -> if bomb then 1 else 0 - Err(OutOfBounds) -> 0, - ) - |> List.sum, - ) - |> List.sum +count_neighbors : List(List(U8)), U64, U64 -> U8 +count_neighbors = |rows, x, y| { + [-1, 0, 1] + .map( + |dy| { + [-1, 0, 1] + .map( + |dx| { + match is_bomb(rows, ((x.to_i64_try() ?? 0) + dx), ((y.to_i64_try() ?? 0) + dy)) { + Ok(bomb) => if bomb { + 1 + } else { + 0 + } + Err(OutOfBounds) => 0 + } + }, + ) + .sum() + }, + ) + .sum() +} diff --git a/exercises/practice/minesweeper/.meta/template.j2 b/exercises/practice/minesweeper/.meta/template.j2 index 26848a7b..9c8e1820 100644 --- a/exercises/practice/minesweeper/.meta/template.j2 +++ b/exercises/practice/minesweeper/.meta/template.j2 @@ -7,9 +7,9 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} expect { - minefield = {{ case["input"]["minefield"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") + minefield = {{ case["input"]["minefield"] | to_roc_multiline_string | indent(8) }} result = {{ case["property"] | to_snake }}(minefield) - expected = {{ case["expected"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") + expected = {{ case["expected"] | to_roc_multiline_string | indent(8) }} result == expected } diff --git a/exercises/practice/minesweeper/Minesweeper.roc b/exercises/practice/minesweeper/Minesweeper.roc index cc5963d1..bec7ed30 100644 --- a/exercises/practice/minesweeper/Minesweeper.roc +++ b/exercises/practice/minesweeper/Minesweeper.roc @@ -1,5 +1,6 @@ Minesweeper :: {}.{ - annotate : Str -> Str - annotate = |minefield| - crash("Please implement the 'annotate' function") + annotate : Str -> Str + annotate = |minefield| { + crash "Please implement the 'annotate' function" + } } diff --git a/exercises/practice/minesweeper/minesweeper-test.roc b/exercises/practice/minesweeper/minesweeper-test.roc index f29f4d4c..3e531d32 100644 --- a/exercises/practice/minesweeper/minesweeper-test.roc +++ b/exercises/practice/minesweeper/minesweeper-test.roc @@ -1,44 +1,38 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/minesweeper/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-20 import Minesweeper exposing [annotate] # no rows expect { - minefield = "".replace_each("·", " ") + minefield = "" result = annotate(minefield) - expected = "".replace_each("·", " ") + expected = "" result == expected } # no columns expect { - minefield = "".replace_each("·", " ") + minefield = "" result = annotate(minefield) - expected = "".replace_each("·", " ") + expected = "" result == expected } # no mines expect { minefield = - \\··· - \\··· - \\··· - .replace_each( - "·", - " ", - ) + \\ + \\ + \\ + result = annotate(minefield) expected = - \\··· - \\··· - \\··· - .replace_each( - "·", - " ", - ) + \\ + \\ + \\ + result == expected } @@ -48,41 +42,29 @@ expect { \\*** \\*** \\*** - .replace_each( - "·", - " ", - ) + result = annotate(minefield) expected = \\*** \\*** \\*** - .replace_each( - "·", - " ", - ) + result == expected } # mine surrounded by spaces expect { minefield = - \\··· - \\·*· - \\··· - .replace_each( - "·", - " ", - ) + \\ + \\ * + \\ + result = annotate(minefield) expected = \\111 \\1*1 \\111 - .replace_each( - "·", - " ", - ) + result == expected } @@ -90,52 +72,43 @@ expect { expect { minefield = \\*** - \\*·* + \\* * \\*** - .replace_each( - "·", - " ", - ) + result = annotate(minefield) expected = \\*** \\*8* \\*** - .replace_each( - "·", - " ", - ) + result == expected } # horizontal line expect { - minefield = "·*·*·".replace_each("·", " ") + minefield = " * * " result = annotate(minefield) - expected = "1*2*1".replace_each("·", " ") + expected = "1*2*1" result == expected } # horizontal line, mines at edges expect { - minefield = "*···*".replace_each("·", " ") + minefield = "* *" result = annotate(minefield) - expected = "*1·1*".replace_each("·", " ") + expected = "*1 1*" result == expected } # vertical line expect { minefield = - \\· + \\ \\* - \\· + \\ \\* - \\· - .replace_each( - "·", - " ", - ) + \\ + result = annotate(minefield) expected = \\1 @@ -143,10 +116,7 @@ expect { \\2 \\* \\1 - .replace_each( - "·", - " ", - ) + result == expected } @@ -154,79 +124,61 @@ expect { expect { minefield = \\* - \\· - \\· - \\· + \\ + \\ + \\ \\* - .replace_each( - "·", - " ", - ) + result = annotate(minefield) expected = \\* \\1 - \\· + \\ \\1 \\* - .replace_each( - "·", - " ", - ) + result == expected } # cross expect { minefield = - \\··*·· - \\··*·· + \\ * + \\ * \\***** - \\··*·· - \\··*·· - .replace_each( - "·", - " ", - ) + \\ * + \\ * + result = annotate(minefield) expected = - \\·2*2· + \\ 2*2 \\25*52 \\***** \\25*52 - \\·2*2· - .replace_each( - "·", - " ", - ) + \\ 2*2 + result == expected } # large minefield expect { minefield = - \\·*··*· - \\··*··· - \\····*· - \\···*·* - \\·*··*· - \\······ - .replace_each( - "·", - " ", - ) + \\ * * + \\ * + \\ * + \\ * * + \\ * * + \\ + result = annotate(minefield) expected = \\1*22*1 \\12*322 - \\·123*2 + \\ 123*2 \\112*4* \\1*22*2 \\111111 - .replace_each( - "·", - " ", - ) + result == expected } From b866a79423a66887d127e6fe7e888880b456730d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 14:23:04 +1200 Subject: [PATCH 108/162] Update exercise to new compiler: yacht --- exercises/practice/yacht/.meta/Example.roc | 134 +++++++++++++-------- exercises/practice/yacht/Yacht.roc | 12 +- 2 files changed, 91 insertions(+), 55 deletions(-) diff --git a/exercises/practice/yacht/.meta/Example.roc b/exercises/practice/yacht/.meta/Example.roc index d63b5cc3..f4775eea 100644 --- a/exercises/practice/yacht/.meta/Example.roc +++ b/exercises/practice/yacht/.meta/Example.roc @@ -1,57 +1,93 @@ Yacht :: {}.{ - score : List U8, Category -> U8 - score = |dice, category| - if dice |> List.len != 5 or dice |> List.any(|die| die < 1 or die > 6) then - 0 - else - when category is - Ones -> dice |> score_ones_to_sixes(1) - Twos -> dice |> score_ones_to_sixes(2) - Threes -> dice |> score_ones_to_sixes(3) - Fours -> dice |> score_ones_to_sixes(4) - Fives -> dice |> score_ones_to_sixes(5) - Sixes -> dice |> score_ones_to_sixes(6) - FullHouse -> dice |> score_full_house - FourOfAKind -> dice |> score_four_of_a_kind - LittleStraight -> dice |> score_straight([1, 2, 3, 4, 5]) - BigStraight -> dice |> score_straight([2, 3, 4, 5, 6]) - Choice -> dice |> List.sum - Yacht -> if dice |> Set.from_list |> Set.len == 1 then 50 else 0 -} + Category := [Ones, Twos, Threes, Fours, Fives, Sixes, FullHouse, FourOfAKind, LittleStraight, BigStraight, Choice, Yacht] + score : List(U8), Category -> U8 + score = |dice, category| { + if dice.len() != 5 or dice.any(|die| die < 1 or die > 6) { + 0 + } else { + match category { + Ones => score_ones_to_sixes(dice, 1) + Twos => score_ones_to_sixes(dice, 2) + Threes => score_ones_to_sixes(dice, 3) + Fours => score_ones_to_sixes(dice, 4) + Fives => score_ones_to_sixes(dice, 5) + Sixes => score_ones_to_sixes(dice, 6) + FullHouse => score_full_house(dice) + FourOfAKind => score_four_of_a_kind(dice) + LittleStraight => score_straight(dice, [1, 2, 3, 4, 5]) + BigStraight => score_straight(dice, [2, 3, 4, 5, 6]) + Choice => dice.sum() + Yacht => { + if Set.from_list(dice).len() == 1 { + 50 + } else { + 0 + } + } + } + } + } +} -Category : [Ones, Twos, Threes, Fours, Fives, Sixes, FullHouse, FourOfAKind, LittleStraight, BigStraight, Choice, Yacht] +score_ones_to_sixes : List(U8), U8 -> U8 +score_ones_to_sixes = |dice, value| { + dice.keep_if(|die| die == value).sum() +} -score_ones_to_sixes : List U8, U8 -> U8 -score_ones_to_sixes = |dice, value| - dice |> List.keep_if(|die| die == value) |> List.sum +value_counts : List(U8) -> List(U8) +value_counts = |dice| { + dice.fold( + [0, 0, 0, 0, 0, 0], + |counts, die| { + counts.update(U8.to_u64(die - 1), |x| x + 1) ?? counts + }, + ) +} -value_counts : List U8 -> List U8 -value_counts = |dice| - dice - |> List.walk( - [0, 0, 0, 0, 0, 0], - |counts, die| - counts |> List.update((die - 1 |> Num.to_u64), |x| x + 1), - ) +score_full_house : List(U8) -> U8 +score_full_house = |dice| { + if Set.from_list(value_counts(dice)) == Set.from_list([0, 2, 3]) { + dice.sum() + } else { + 0 + } +} -score_full_house : List U8 -> U8 -score_full_house = |dice| - if dice |> value_counts |> Set.from_list == Set.from_list([0, 2, 3]) then - dice |> List.sum - else - 0 +score_four_of_a_kind : List(U8) -> U8 +score_four_of_a_kind = |dice| { + value_counts(dice) + .fold_with_index_until( + 0, + |_, count, index| { + if count < 4 { + Continue(0) + } else { + Break(((index + 1).to_u8_try() ?? 0) * 4) + } + }, + ) +} -score_four_of_a_kind : List U8 -> U8 -score_four_of_a_kind = |dice| - dice - |> value_counts - |> List.walk_with_index_until( - 0, - |_, count, index| - if count < 4 then Continue(0) else (index + 1 |> Num.to_u8) * 4 |> Break, - ) +score_straight : List(U8), List(U8) -> U8 +score_straight = |dice, target| { + if sort_asc(dice) == target { + 30 + } else { + 0 + } +} -score_straight : List U8, List U8 -> U8 -score_straight = |dice, target| - if dice |> List.sort_asc == target then 30 else 0 +sort_asc = |list| { + list.sort_with( + |a, b| { + if a < b { + LT + } else if a > b { + GT + } else { + EQ + } + }, + ) +} diff --git a/exercises/practice/yacht/Yacht.roc b/exercises/practice/yacht/Yacht.roc index 86711a97..e142bccb 100644 --- a/exercises/practice/yacht/Yacht.roc +++ b/exercises/practice/yacht/Yacht.roc @@ -1,8 +1,8 @@ Yacht :: {}.{ - score : List U8, Category -> U8 - score = |dice, category| - crash("Please implement the 'score' function") -} - + Category := [Ones, Twos, Threes, Fours, Fives, Sixes, FullHouse, FourOfAKind, LittleStraight, BigStraight, Choice, Yacht] -Category : [Ones, Twos, Threes, Fours, Fives, Sixes, FullHouse, FourOfAKind, LittleStraight, BigStraight, Choice, Yacht] + score : List(U8), Category -> U8 + score = |dice, category| { + crash "Please implement the 'score' function" + } +} From 5234778d8b4b3f4fb37987f8cf3181e2a2a3d596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 14:23:47 +1200 Subject: [PATCH 109/162] Update exercise to new compiler: rectangles --- .../practice/rectangles/.meta/Example.roc | 121 ++++++++++-------- exercises/practice/rectangles/Rectangles.roc | 7 +- 2 files changed, 73 insertions(+), 55 deletions(-) diff --git a/exercises/practice/rectangles/.meta/Example.roc b/exercises/practice/rectangles/.meta/Example.roc index 8b2a6be1..62f38a76 100644 --- a/exercises/practice/rectangles/.meta/Example.roc +++ b/exercises/practice/rectangles/.meta/Example.roc @@ -1,58 +1,75 @@ Rectangles :: {}.{ - rectangles : Str -> U64 - rectangles = |diagram| - grid = - diagram - |> Str.split_on("\n") - |> List.map(Str.to_utf8) - height = grid |> List.len - grid - |> List.map_with_index( - |row, y1| # number of rectangles with top on this row - row - |> List.map_with_index( - |_char, x1| # number with top-left on this column - List.range({ start: After(y1), end: Before(height) }) - |> List.map( - |y2| # number of rectangles with bottom on this row - List.range({ start: After(x1), end: Before(List.len(row)) }) - |> List.map( - |x2| # number with bottom-right on this column - if is_rectangle({ grid, x1, y1, x2, y2 }) then 1 else 0, - ) - |> List.sum, - ) - |> List.sum, - ) - |> List.sum, - ) - |> List.sum + rectangles : Str -> U64 + rectangles = |diagram| { + grid = + diagram + .split_on("\n") + .map(|s| s.to_utf8()) + height = grid.len() + grid + .map_with_index( + |row, y1| { + row + .map_with_index( + |_char, x1| { + y2s = ((y1 + 1).. Bool +is_rectangle = |{ grid, x1, y1, x2, y2 }| { + get_cell = |pos| { + (x, y) = pos + grid.get(y)?.get(x) + } -## Is there a rectangle between the top left (x1, y1) corner and the bottom -## right (x2, y2) corner. -is_rectangle : { grid : List (List U8), x1 : U64, y1 : U64, x2 : U64, y2 : U64 } -> Bool -is_rectangle = |{ grid, x1, y1, x2, y2 }| - get_cell = - |(x, y)| - grid - |> List.get(y)? - |> List.get(x) + is_corner = |pos| { + get_cell(pos) == Ok('+') + } + is_horizontal = |pos| { + is_corner(pos) or get_cell(pos) == Ok('-') + } + is_vertical = |pos| { + is_corner(pos) or get_cell(pos) == Ok('|') + } - is_corner = |pos| get_cell(pos) == Ok('+') - is_horizontal = |pos| is_corner(pos) or get_cell(pos) == Ok('-') - is_vertical = |pos| is_corner(pos) or get_cell(pos) == Ok('|') + has_horizontal_border = |y| { + xs = (x1..=x2).fold([], |acc, x| acc.append(x)) + xs.all(|x| is_horizontal((x, y))) + } - has_horizontal_border = |y| - List.range({ start: At(x1), end: At(x2) }) - |> List.all(|x| is_horizontal((x, y))) - - has_vertical_border = |x| - List.range({ start: At(y1), end: At(y2) }) - |> List.all(|y| is_vertical((x, y))) - ( - ([(x1, y1), (x2, y1), (x1, y2), (x2, y2)] |> List.all(is_corner)) - and ([y1, y2] |> List.all(has_horizontal_border)) - and ([x1, x2] |> List.all(has_vertical_border)) - ) + has_vertical_border = |x| { + ys = (y1..=y2).fold([], |acc, y| acc.append(y)) + ys.all(|y| is_vertical((x, y))) + } + + ( + [(x1, y1), (x2, y1), (x1, y2), (x2, y2)].all(is_corner) + and [y1, y2].all(has_horizontal_border) + and [x1, x2].all(has_vertical_border), + ) +} diff --git a/exercises/practice/rectangles/Rectangles.roc b/exercises/practice/rectangles/Rectangles.roc index dab1d29e..16633cb8 100644 --- a/exercises/practice/rectangles/Rectangles.roc +++ b/exercises/practice/rectangles/Rectangles.roc @@ -1,5 +1,6 @@ Rectangles :: {}.{ - rectangles : Str -> U64 - rectangles = |diagram| - crash("Please implement the 'rectangles' function") + rectangles : Str -> U64 + rectangles = |diagram| { + crash "Please implement the 'rectangles' function" + } } From 4f41d8dfe94562aabf7788dd2241dba540718908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 14:25:37 +1200 Subject: [PATCH 110/162] Update exercise to new compiler: palindrome-products --- .../palindrome-products/.meta/Example.roc | 138 +++++++++++------- .../PalindromeProducts.roc | 14 +- 2 files changed, 93 insertions(+), 59 deletions(-) diff --git a/exercises/practice/palindrome-products/.meta/Example.roc b/exercises/practice/palindrome-products/.meta/Example.roc index 0378b902..ab2c4577 100644 --- a/exercises/practice/palindrome-products/.meta/Example.roc +++ b/exercises/practice/palindrome-products/.meta/Example.roc @@ -1,60 +1,92 @@ PalindromeProducts :: {}.{ - smallest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } [MinWasLargerThanMax] - smallest = |{ min, max }| - if min > max then - Err(MinWasLargerThanMax) - else - help = |factor1, factor2, best_value, factors| - if factor1 > max then - final_value = if factors == [] then 0 else best_value - Ok({ value: final_value, factors: Set.from_list(factors) }) - else if factor2 == max + 1 then - help((factor1 + 1), (factor1 + 1), best_value, factors) - else - value = factor1 * factor2 - if value > best_value then - help((factor1 + 1), (factor1 + 1), best_value, factors) - else - next_factor2 = factor2 + 1 - if value == best_value then - new_factors = factors |> List.append((factor1, factor2)) - help(factor1, next_factor2, best_value, new_factors) - else if value |> is_palindrome then - help(factor1, next_factor2, value, [(factor1, factor2)]) - else - help(factor1, next_factor2, best_value, factors) + smallest : { min : U64, max : U64 } -> Try({ value : U64, factors : Set((U64, U64)) }, [MinWasLargerThanMax, ..]) + smallest = |{ min, max }| { + if min > max { + Err(MinWasLargerThanMax) + } else { + help = |factor1, factor2, best_value, factors| { + if factor1 > max { + final_value = if factors == [] { + 0 + } else { + best_value + } + Ok({ value: final_value, factors: Set.from_list(factors) }) + } else if factor2 == max + 1 { + help((factor1 + 1), (factor1 + 1), best_value, factors) + } else { + value = factor1 * factor2 + if value > best_value { + help((factor1 + 1), (factor1 + 1), best_value, factors) + } else { + next_factor2 = factor2 + 1 + if value == best_value { + new_factors = factors.append((factor1, factor2)) + help(factor1, next_factor2, best_value, new_factors) + } else if is_palindrome(value) { + help(factor1, next_factor2, value, [(factor1, factor2)]) + } else { + help(factor1, next_factor2, best_value, factors) + } + } + } + } - help(min, min, Num.max_u64, []) + help(min, min, max_u64, []) + } + } - largest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } [MinWasLargerThanMax] - largest = |{ min, max }| - if min > max then - Err(MinWasLargerThanMax) - else - help = |factor1, factor2, best_value, factors| - if factor1 < min then - final_value = if factors == [] then 0 else best_value - Ok({ value: final_value, factors: Set.from_list(factors) }) - else if factor2 < factor1 then - help((factor1 - 1), max, best_value, factors) - else - value = factor1 * factor2 - if value < best_value then - help((factor1 - 1), max, best_value, factors) - else - next_factor2 = factor2 - 1 - if value == best_value then - new_factors = factors |> List.append((factor1, factor2)) - help(factor1, next_factor2, best_value, new_factors) - else if value |> is_palindrome then - help(factor1, next_factor2, value, [(factor1, factor2)]) - else - help(factor1, next_factor2, best_value, factors) + largest : { min : U64, max : U64 } -> Try({ value : U64, factors : Set((U64, U64)) }, [MinWasLargerThanMax, ..]) + largest = |{ min, max }| { + if min > max { + Err(MinWasLargerThanMax) + } else { + help = |factor1, factor2, best_value, factors| { + if factor1 < min { + final_value = if factors == [] { + 0 + } else { + best_value + } + Ok({ value: final_value, factors: Set.from_list(factors) }) + } else if factor2 < factor1 { + help((factor1 - 1), max, best_value, factors) + } else { + value = factor1 * factor2 + if value < best_value { + help((factor1 - 1), max, best_value, factors) + } else { + next_factor2 = factor2 - 1 + if value == best_value { + new_factors = factors.append((factor1, factor2)) + help(factor1, next_factor2, best_value, new_factors) + } else if is_palindrome(value) { + help(factor1, next_factor2, value, [(factor1, factor2)]) + } else { + help(factor1, next_factor2, best_value, factors) + } + } + } + } - help(max, max, 0, []) + help(max, max, 0, []) + } + } } is_palindrome : U64 -> Bool -is_palindrome = |number| - digits = number |> Num.to_str |> Str.to_utf8 - digits == digits |> List.reverse +is_palindrome = |number| { + digits = number.to_str().to_utf8() + digits == reverse(digits) +} + +# The following will soon be available in Roc's builtins +max_u64 = 18_446_744_073_709_551_615 + +reverse : List(a) -> List(a) +reverse = |list| { + match list { + [] => [] + [first, .. as rest] => reverse(rest).append(first) + } +} diff --git a/exercises/practice/palindrome-products/PalindromeProducts.roc b/exercises/practice/palindrome-products/PalindromeProducts.roc index b006634f..1f8e3be2 100644 --- a/exercises/practice/palindrome-products/PalindromeProducts.roc +++ b/exercises/practice/palindrome-products/PalindromeProducts.roc @@ -1,9 +1,11 @@ PalindromeProducts :: {}.{ - smallest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } _ - smallest = |{ min, max }| - crash("Please implement the 'smallest' function") + smallest : { min : U64, max : U64 } -> Try({ value : U64, factors : Set((U64, U64)) }, _) + smallest = |{ min, max }| { + crash "Please implement the 'smallest' function" + } - largest : { min : U64, max : U64 } -> Result { value : U64, factors : Set (U64, U64) } _ - largest = |{ min, max }| - crash("Please implement the 'largest' function") + largest : { min : U64, max : U64 } -> Try({ value : U64, factors : Set((U64, U64)) }, _) + largest = |{ min, max }| { + crash "Please implement the 'largest' function" + } } From 66256e81cddeacf3337536d1c915ff33b5f2c225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 14:26:24 +1200 Subject: [PATCH 111/162] Update exercise to new compiler: food-chain --- .../practice/food-chain/.meta/Example.roc | 88 ++++++++----------- exercises/practice/food-chain/FoodChain.roc | 7 +- 2 files changed, 41 insertions(+), 54 deletions(-) diff --git a/exercises/practice/food-chain/.meta/Example.roc b/exercises/practice/food-chain/.meta/Example.roc index 42a768cd..ae0568f9 100644 --- a/exercises/practice/food-chain/.meta/Example.roc +++ b/exercises/practice/food-chain/.meta/Example.roc @@ -1,63 +1,49 @@ FoodChain :: {}.{ - recite : U64, U64 -> Str - recite = |start_verse, end_verse| - List.sublist(verse_list, { start: start_verse - 1, len: end_verse - (start_verse - 1) }) - |> Str.join_with("\n\n") + recite : U64, U64 -> Str + recite = |start_verse, end_verse| { + verse_list + .sublist({ start: start_verse - 1, len: end_verse - (start_verse - 1) }) + ->Str.join_with("\n\n") + } } -verse_list : List Str -verse_list = - initial_state = { verses: [], verse_body: "I don't know why she swallowed the fly. Perhaps she'll die.", previous_animal: "fly" } - result = - List.walk( - animals, - initial_state, - |{ verses, verse_body, previous_animal }, animal| - description = Dict.get(animal_descriptions, previous_animal) |> Result.with_default("") - new_verse_body = - """ - She swallowed the ${animal.name} to catch the ${previous_animal}${description}. - ${verse_body} - """ - verse = - """ - I know an old lady who swallowed a ${animal.name}. - ${animal.exclamation} - ${new_verse_body} - """ - { - verses: List.append(verses, verse), - verse_body: new_verse_body, - previous_animal: animal.name, - }, - ) - List.join([[first_verse], result.verses, [last_verse]]) +verse_list : List(Str) +verse_list = { + initial_state = { verses: [], verse_body: "I don't know why she swallowed the fly. Perhaps she'll die.", previous_animal: "fly" } + result = + animals.fold( + initial_state, + |{ verses, verse_body, previous_animal }, animal| { + description = animal_descriptions.get(previous_animal) ?? "" + new_verse_body = "She swallowed the ${animal.name} to catch the ${previous_animal}${description}.\n${verse_body}" + verse = "I know an old lady who swallowed a ${animal.name}.\n${animal.exclamation}\n${new_verse_body}" + { + verses: verses.append(verse), + verse_body: new_verse_body, + previous_animal: animal.name, + } + }, + ) + [first_verse].concat(result.verses).concat([last_verse]) +} # I would prefer to use an if expression here, but I ran into a compiler bug doing that -animal_descriptions : Dict Str Str -animal_descriptions = - Dict.single("spider", " that wriggled and jiggled and tickled inside her") +animal_descriptions : Dict(Str, Str) +animal_descriptions = + Dict.single("spider", " that wriggled and jiggled and tickled inside her") -animals : List { name : Str, exclamation : Str } +animals : List({ name : Str, exclamation : Str }) animals = [ - { name: "spider", exclamation: "It wriggled and jiggled and tickled inside her." }, - { name: "bird", exclamation: "How absurd to swallow a bird!" }, - { name: "cat", exclamation: "Imagine that, to swallow a cat!" }, - { name: "dog", exclamation: "What a hog, to swallow a dog!" }, - { name: "goat", exclamation: "Just opened her throat and swallowed a goat!" }, - { name: "cow", exclamation: "I don't know how she swallowed a cow!" }, + { name: "spider", exclamation: "It wriggled and jiggled and tickled inside her." }, + { name: "bird", exclamation: "How absurd to swallow a bird!" }, + { name: "cat", exclamation: "Imagine that, to swallow a cat!" }, + { name: "dog", exclamation: "What a hog, to swallow a dog!" }, + { name: "goat", exclamation: "Just opened her throat and swallowed a goat!" }, + { name: "cow", exclamation: "I don't know how she swallowed a cow!" }, ] first_verse : Str -first_verse = - """ - I know an old lady who swallowed a fly. - I don't know why she swallowed the fly. Perhaps she'll die. - """ +first_verse = "I know an old lady who swallowed a fly.\nI don't know why she swallowed the fly. Perhaps she'll die." last_verse : Str -last_verse = - """ - I know an old lady who swallowed a horse. - She's dead, of course! - """ +last_verse = "I know an old lady who swallowed a horse.\nShe's dead, of course!" diff --git a/exercises/practice/food-chain/FoodChain.roc b/exercises/practice/food-chain/FoodChain.roc index 516a6257..1c1a93ae 100644 --- a/exercises/practice/food-chain/FoodChain.roc +++ b/exercises/practice/food-chain/FoodChain.roc @@ -1,5 +1,6 @@ FoodChain :: {}.{ - recite : U64, U64 -> Str - recite = |start_verse, end_verse| - crash("Please implement the 'recite' function") + recite : U64, U64 -> Str + recite = |start_verse, end_verse| { + crash "Please implement the 'recite' function" + } } From b84f37d652f9e10d581c450549fb07c96608db20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 17:05:31 +1200 Subject: [PATCH 112/162] Update exercise to new compiler: error-handling --- .../.docs/instructions.append.md | 28 ++-- .../practice/error-handling/.meta/Example.roc | 140 +++++++++++------- .../practice/error-handling/ErrorHandling.roc | 60 ++++---- exercises/practice/error-handling/Temp.roc | 97 ++++++++++++ .../error-handling/error-handling-test.roc | 97 ++++-------- 5 files changed, 254 insertions(+), 168 deletions(-) create mode 100644 exercises/practice/error-handling/Temp.roc diff --git a/exercises/practice/error-handling/.docs/instructions.append.md b/exercises/practice/error-handling/.docs/instructions.append.md index b5625706..12e3e849 100644 --- a/exercises/practice/error-handling/.docs/instructions.append.md +++ b/exercises/practice/error-handling/.docs/instructions.append.md @@ -6,9 +6,9 @@ You are building a tiny web server that queries an even tinier user database. So - the connection may be insecure: the URL must start with `"https://"` - the domain name may be invalid: in this exercise, it should be `"example.com"` -- the page may not be found: only `"https://example.com/"`, `"https://example.com/users/"` and `"https://example.com/users/"` are allowed -- the `userId` may not be a positive integer -- no user may exist in the database with this `userId` +- the page may not be found: only `"https://example.com/"`, `"https://example.com/users/"` and `"https://example.com/users/"` are allowed +- the `user_id` may not be a positive integer +- no user may exist in the database with this `user_id` When things go wrong, it's important to give the end user a nice and helpful error message so that they can solve the issue. For this, you need to ensure that your code propagates informative errors from the point where the error is detected to the point where the error message is produced. @@ -19,21 +19,21 @@ Luckily, Roc allows you to carry payload (i.e., data) inside your `Err` tag. It' - in many cases your error handling code may need to handle some errors differently than others. However, if the errors only carry string payloads, your error handling code will have to parse that string to know what problem occurred: this is inefficient and it can easily break if the error message is ever tweaked. - if your website is multilingual, you will need to translate the error message to the user's language. It's going to be much easier to do that if the error payload is machine-friendly data rather than an English string. -So in this exercise, your errors will instead carry a meaningful tag along with its own helpful payload. For example, if the user is not found, the error will look like `Err (UserNotFound 42)`. +So in this exercise, your errors will instead carry a meaningful tag along with its own helpful payload. For example, if the user is not found, the error will look like `Err(UserNotFound(42))`. Okay, let's get started! Here's what you need to do: -1. Implement `getUser` to return the requested user from the `users` "database" (it's actually just a `Dict`). Make sure the function returns `Err (UserNotFound userId)` in case the user is not found, instead of `Err KeyNotFound`. -2. Implement `parseUserId` to convert the URL's path (such as `"/users/123"`) to a positive integer user ID (`123`). In case of error, return `Err (InvalidUserId userIdStr)`. +1. Implement `get_user` to return the requested user from the `users` "database" (it's actually just a `Dict`). Make sure the function returns `Err(UserNotFound(user_id))` in case the user is not found, instead of `Err(KeyNotFound)`. +2. Implement `parse_user_id` to convert the URL's path (such as `"/users/123"`) to a positive integer user ID (`123`). In case of error, return `Err(InvalidUserId(user_id_str))`. 3. Implement `getPage`: - - If the URL is `"https://example.com/"`, return `Ok "Home page"` - - If the URL is `"https://example.com/users/"`, return `Ok "Users page"` - - If the URL is `"https://example.com/users/"`, parse the user ID, load the user with that ID, and return `Ok "'s page"` - - If the URL prefix is not `"https://"`, return `Err (InsecureConnection url)` - - If the URL domain name is not `"example.com"`, return `Err (InvalidDomain url)` - - If the path is not `/` or `/users/` or `/users/`, return `Err (PageNotFound path)` - - If the user ID is not a positive integer, return `Err (InvalidUserId userIdStr)` - - If the user does not exist, return `Err (UserNotFound userId)` + - If the URL is `"https://example.com/"`, return `Ok("Home page")` + - If the URL is `"https://example.com/users/"`, return `Ok("Users page")` + - If the URL is `"https://example.com/users/"`, parse the user ID, load the user with that ID, and return `Ok("'s page")` + - If the URL prefix is not `"https://"`, return `Err(InsecureConnection(url))` + - If the URL domain name is not `"example.com"`, return `Err(InvalidDomain(url))` + - If the path is not `/` or `/users/` or `/users/`, return `Err(PageNotFound(path))` + - If the user ID is not a positive integer, return `Err(InvalidUserId(user_id_str))` + - If the user does not exist, return `Err(UserNotFound(user_id))` 4. Implement `errorMessage` to convert the previous errors to translated error messages. The function should at least handle English, but you are encouraged to try handling another language as well. The English error messages should like this: - `"Insecure connection (non HTTPS): http://example.com/users/789"` diff --git a/exercises/practice/error-handling/.meta/Example.roc b/exercises/practice/error-handling/.meta/Example.roc index 2ccf8b2f..301a2c84 100644 --- a/exercises/practice/error-handling/.meta/Example.roc +++ b/exercises/practice/error-handling/.meta/Example.roc @@ -1,65 +1,91 @@ ErrorHandling :: {}.{ - get_user : UserId -> Result User [UserNotFound UserId] - get_user = |user_id| - users |> Dict.get(user_id) |> Result.map_err(|KeyNotFound| UserNotFound(user_id)) + User : { name : Str } + UserId : U64 + users : Dict(UserId, User) + users = Dict.from_list( + [ + (123, { name: "Alice" }), + (456, { name: "Bob" }), + (789, { name: "Charlie" }), + ], + ) - parse_user_id : Str -> Result UserId [InvalidUserId Str] - parse_user_id = |path| - user_id_str = path |> Str.replace_first("/users/", "") - user_id_str |> Str.to_u64 |> Result.map_err(|InvalidNumStr| InvalidUserId(user_id_str)) + # # Returns the user with the given user_id, or UserNotFound(user_id) + get_user : UserId -> Try(User, [UserNotFound(UserId), ..]) + get_user = |user_id| { + users.get(user_id).map_err(|_| UserNotFound(user_id)) + } - get_page : Str -> Result Str [InsecureConnection Str, InvalidDomain Str, InvalidUserId Str, UserNotFound UserId, PageNotFound Str] - get_page = |url| - when parse_path(url)? is - "/" -> Ok("Home page") - "/users/" -> Ok("Users page") - user_path if user_path |> Str.starts_with("/users/") -> - user_id = parse_user_id(user_path)? - user = get_user(user_id)? - Ok("${user.name}'s page") + # # Parses a string formatted as "/users/" and returns the user_id + # # or InvalidUserId(user_id_str) is the part of the path cannot + # # be parsed to a U64 + parse_user_id : Str -> Try(UserId, [InvalidUserId(Str), ..]) + parse_user_id = |path| { + user_id_str = path.drop_prefix("/users/") + user_id_str->U64.from_str().map_err(|_| InvalidUserId(user_id_str)) + } - unknown_path -> Err(PageNotFound(unknown_path)) + # # Takes a URL and returns the page content, or the appropriate error + get_page : Str -> Try(Str, [InsecureConnection(Str), InvalidDomain(Str), InvalidUserId(Str), UserNotFound(UserId), PageNotFound(Str)]) + get_page = |url| { + match parse_path(url) { + Ok(path) => { + match path { + "/" => Ok("Home page") + "/users/" => Ok("Users page") + user_path if user_path.starts_with("/users/") => { + user = user_path->parse_user_id()?->get_user()? + Ok("${user.name}'s page") + } + unknown_path => Err(PageNotFound(unknown_path)) + } + } + Err(InsecureConnection(url_err)) => Err(InsecureConnection(url_err)) + Err(InvalidDomain(url_err)) => Err(InvalidDomain(url_err)) + } + } - error_message : [InsecureConnection Str, InvalidDomain Str, InvalidUserId Str, UserNotFound UserId, PageNotFound Str], [English, French] -> Str - error_message = |err, language| - when language is - English -> - when err is - InsecureConnection(url) -> "Insecure connection (non HTTPS): ${url}" - InvalidDomain(url) -> "Invalid domain name: ${url}" - InvalidUserId(user_id_str) -> "User ID is not a positive integer: ${user_id_str}" - UserNotFound(user_id) -> "User #${user_id |> Num.to_str} was not found" - PageNotFound(path) -> "Page not found: ${path}" + # # Takes an error and returns the corresponding error message in the + # # given language + error_message : [InsecureConnection(Str), InvalidDomain(Str), InvalidUserId(Str), UserNotFound(UserId), PageNotFound(Str)], [English, French] -> Str + error_message = |err, language| { + match language { + English => { + match err { + InsecureConnection(url) => "Insecure connection (non HTTPS): ${url}" + InvalidDomain(url) => "Invalid domain name: ${url}" + InvalidUserId(user_id_str) => "User ID is not a positive integer: ${user_id_str}" + UserNotFound(user_id) => "User #${user_id.to_str()} was not found" + PageNotFound(path) => "Page not found: ${path}" + } + } - French -> - when err is - InsecureConnection(url) -> "Connexion non sécurisée (non HTTPS): ${url}" - InvalidDomain(url) -> "Ce nom de domaine est incorrect: ${url}" - InvalidUserId(user_id_str) -> "Cet identifiant utilisateur devrait être un entier positif: ${user_id_str}" - UserNotFound(user_id) -> "L'utilisateur #${user_id |> Num.to_str} est inconnu" - PageNotFound(path) -> "Cette page est inconnue: ${path}" + French => { + match err { + InsecureConnection(url) => "Connexion non sécurisée (non HTTPS): ${url}" + InvalidDomain(url) => "Ce nom de domaine est incorrect: ${url}" + InvalidUserId(user_id_str) => "Cet identifiant utilisateur devrait être un entier positif: ${user_id_str}" + UserNotFound(user_id) => "L'utilisateur #${user_id.to_str()} est inconnu" + PageNotFound(path) => "Cette page est inconnue: ${path}" + } + } + } + } } - -User : { name : Str } -UserId : U64 - -users : Dict UserId User -users = - Dict.from_list( - [ - (123, { name: "Alice" }), - (456, { name: "Bob" }), - (789, { name: "Charlie" }), - ], - ) - -parse_path : Str -> Result Str [InvalidDomain Str, InsecureConnection Str] -parse_path = |url| - prefix = "https://example.com" - if url |> Str.starts_with(prefix) then - url |> Str.replace_first(prefix, "") |> Ok - else if url |> Str.starts_with("https://") then - Err(InvalidDomain(url)) - else - Err(InsecureConnection(url)) +# # Parses a URL formatted as https://example.com/ and returns "/" +# # or the appropriate error if the URL is malformed or http instead of https +parse_path : Str -> Try(Str, [InvalidDomain(Str), InsecureConnection(Str), ..]) +parse_path = |url| { + prefix = "https://example.com" + if url.starts_with(prefix) { + path = url.drop_prefix(prefix) + if path == "" { + Ok("/") + } else if path.starts_with("/") Ok(path) else Err(InvalidDomain(url)) + } else if url.starts_with("https://") { + Err(InvalidDomain(url)) + } else { + Err(InsecureConnection(url)) + } +} diff --git a/exercises/practice/error-handling/ErrorHandling.roc b/exercises/practice/error-handling/ErrorHandling.roc index f3f20be6..d1758e5b 100644 --- a/exercises/practice/error-handling/ErrorHandling.roc +++ b/exercises/practice/error-handling/ErrorHandling.roc @@ -1,31 +1,39 @@ ErrorHandling :: {}.{ - get_user : UserId -> Result User [UserNotFound Str] - get_user = |user_id| - crash("Please implement the 'get_user' function") + User : { name : Str } + UserId : U64 + users : Dict(UserId, User) + users = Dict.from_list( + [ + (123, { name: "Alice" }), + (456, { name: "Bob" }), + (789, { name: "Charlie" }), + ], + ) - parse_user_id : Str -> Result UserId [InvalidUserId Str] - parse_user_id = |path| - crash("Please implement the 'parse_user_id' function") + # # Returns the user with the given user_id, or UserNotFound(user_id) + get_user : UserId -> Try(User, _) + get_user = |user_id| { + crash "Please implement the 'get_user' function" + } - get_page : Str -> Result Str _ - get_page = |url| - crash("Please implement the 'get_page' function") - - error_message : _, [English] -> Str - error_message = |err, language| - crash("Please implement the 'error_message' function") -} + # # Parses a string formatted as "/users/" and returns the user_id + # # or InvalidUserId(user_id_str) is the part of the path cannot + # # be parsed to a U64 + parse_user_id : Str -> Try(UserId, _) + parse_user_id = |path| { + crash "Please implement the 'parse_user_id' function" + } + # # Takes a URL and returns the page content, or the appropriate error + get_page : Str -> Try(Str, _) + get_page = |url| { + crash "Please implement the 'get_page' function" + } -User : { name : Str } -UserId : U64 - -users : Dict UserId User -users = - Dict.from_list( - [ - (123, { name: "Alice" }), - (456, { name: "Bob" }), - (789, { name: "Charlie" }), - ], - ) + # # Takes an error and returns the corresponding error message in the + # # given language + error_message : [InsecureConnection(Str), InvalidDomain(Str), InvalidUserId(Str), UserNotFound(UserId), PageNotFound(Str)], [English, French] -> Str + error_message = |err, language| { + crash "Please implement the 'error_message' function" + } +} diff --git a/exercises/practice/error-handling/Temp.roc b/exercises/practice/error-handling/Temp.roc new file mode 100644 index 00000000..f3141d17 --- /dev/null +++ b/exercises/practice/error-handling/Temp.roc @@ -0,0 +1,97 @@ +Temp :: {}.{ + get_user : UserId -> Try(User, [UserNotFound(UserId)]) + get_user = |user_id| { + match users.get(user_id) { + Ok(user) => Ok(user) + Err(KeyNotFound) => Err(UserNotFound(user_id)) + } + } + + parse_user_id : Str -> Try(UserId, [InvalidUserId(Str)]) + parse_user_id = |path| { + user_id_str = Str.replace_each(path, "/users/", "") + match U64.from_str(user_id_str) { + Ok(id) => Ok(id) + Err(BadNumStr) => Err(InvalidUserId(user_id_str)) + } + } + + get_page : Str -> Try(Str, [InsecureConnection(Str), InvalidDomain(Str), InvalidUserId(Str), UserNotFound(UserId), PageNotFound(Str)]) + get_page = |url| { + match parse_path(url) { + Ok(path) => { + match path { + "/" => Ok("Home page") + "/users/" => Ok("Users page") + user_path if user_path.starts_with("/users/") => { + match parse_user_id(user_path) { + Ok(user_id) => { + match get_user(user_id) { + Ok(user) => Ok("${user.name}'s page") + Err(UserNotFound(uid)) => Err(UserNotFound(uid)) + } + } + Err(InvalidUserId(uid_str)) => Err(InvalidUserId(uid_str)) + } + } + + unknown_path => Err(PageNotFound(unknown_path)) + } + } + + Err(InsecureConnection(url_err)) => Err(InsecureConnection(url_err)) + Err(InvalidDomain(url_err)) => Err(InvalidDomain(url_err)) + } + } + + error_message : [InsecureConnection(Str), InvalidDomain(Str), InvalidUserId(Str), UserNotFound(UserId), PageNotFound(Str)], [English, French] -> Str + error_message = |err, language| { + match language { + English => { + match err { + InsecureConnection(url) => "Insecure connection (non HTTPS): ${url}" + InvalidDomain(url) => "Invalid domain name: ${url}" + InvalidUserId(user_id_str) => "User ID is not a positive integer: ${user_id_str}" + UserNotFound(user_id) => "User #${user_id.to_str()} was not found" + PageNotFound(path) => "Page not found: ${path}" + } + } + + French => { + match err { + InsecureConnection(url) => "Connexion non sécurisée (non HTTPS): ${url}" + InvalidDomain(url) => "Ce nom de domaine est incorrect: ${url}" + InvalidUserId(user_id_str) => "Cet identifiant utilisateur devrait être un entier positif: ${user_id_str}" + UserNotFound(user_id) => "L'utilisateur #${user_id.to_str()} est inconnu" + PageNotFound(path) => "Cette page est inconnue: ${path}" + } + } + } + } +} + +User : { name : Str } + +UserId : U64 + +users : Dict(UserId, User) +users = + Dict.from_list( + [ + (123, { name: "Alice" }), + (456, { name: "Bob" }), + (789, { name: "Charlie" }), + ], + ) + +parse_path : Str -> Try(Str, [InvalidDomain(Str), InsecureConnection(Str)]) +parse_path = |url| { + prefix = "https://example.com" + if url.starts_with(prefix) { + Ok(Str.replace_each(url, prefix, "")) + } else if url.starts_with("https://") { + Err(InvalidDomain(url)) + } else { + Err(InsecureConnection(url)) + } +} diff --git a/exercises/practice/error-handling/error-handling-test.roc b/exercises/practice/error-handling/error-handling-test.roc index ffe9217a..d9d7e613 100644 --- a/exercises/practice/error-handling/error-handling-test.roc +++ b/exercises/practice/error-handling/error-handling-test.roc @@ -3,7 +3,7 @@ import ErrorHandling exposing [get_user, parse_user_id, get_page, error_message] ## -## get_user should return Ok or Err UserNotFound +## get_user should return Ok() or Err(UserNotFound()) ## # get_user 123 should return Alice @@ -31,49 +31,50 @@ expect { } ## -## parse_user_id should parse a string to a positive integer and return -## Ok if successful, or Err InvalidUserId otherwise +## parse_user_id should parse a string formatted as "/users/" to a +## positive integer, and return Ok() if successful, or +## Err(InvalidUserId()) otherwise ## -# Parsing a valid userId should return Ok +# Parsing a valid user ID should return Ok() expect { - result = parse_user_id("123") + result = parse_user_id("/users/123") result == Ok(123) } # Parsing an empty string should fail expect { - result = parse_user_id("") + result = parse_user_id("/users/") result == Err(InvalidUserId("")) } # Parsing a negative number should fail expect { - result = parse_user_id("-123") + result = parse_user_id("/users/-123") result == Err(InvalidUserId("-123")) } # Parsing a fractional number should fail expect { - result = parse_user_id("123.456") + result = parse_user_id("/users/123.456") result == Err(InvalidUserId("123.456")) } # Parsing a number in scientific format should fail expect { - result = parse_user_id("1e03") + result = parse_user_id("/users/1e03") result == Err(InvalidUserId("1e03")) } # Parsing a string containing letters should fail expect { - result = parse_user_id("abc") + result = parse_user_id("/users/abc") result == Err(InvalidUserId("abc")) } -# Parsing a string containing a valid userId followed by junk should fail +# Parsing a string containing a valid user_id followed by junk should fail expect { - result = parse_user_id("123 abc") + result = parse_user_id("/users/123 abc") result == Err(InvalidUserId("123 abc")) } @@ -130,7 +131,7 @@ expect { result == Err(PageNotFound("/oops")) } -# Error: invalid userId +# Error: invalid user_id expect { result = get_page("https://example.com/users/abc") result == Err(InvalidUserId("abc")) @@ -143,92 +144,46 @@ expect { } ## -## Handle errors and return a clear message to the user, in the user's language +## Handle errors and return a clear message to the user, in the user's language. ## Your implementation must at least handle English, but you can handle other ## languages if you want ## -# No error for root URL: just return the Ok result -expect { - page_result = get_page("https://example.com/") - result = page_result? - |err| err->error_message(English) - result == Ok("Home page") -} - -# No error for users URL: just return the Ok result -expect { - page_result = get_page("https://example.com/users/") - result = page_result? - |err| err->error_message(English) - result == Ok("Users page") -} - -# No error for specific user URL: just return the Ok result -expect { - page_result = get_page("https://example.com/users/123") - result = page_result? - |err| err->error_message(English) - result == Ok("Alice's page") -} - -# No error for specific user URL: just return the Ok result -expect { - page_result = get_page("https://example.com/users/456") - result = page_result? - |err| err->error_message(English) - result == Ok("Bob's page") -} - -# No error for specific user URL: just return the Ok result -expect { - page_result = get_page("https://example.com/users/789") - result = page_result? - |err| err->error_message(English) - result == Ok("Charlie's page") -} - # Error: insecure connection # Note: instead of displaying an error message, the server could automatically - # redirect the user to the HTTPS URL. This is an example of a recoverable error # which would be easy to handle because the error payload is machine-friendly expect { - page_result = get_page("http://example.com/users/789") - result = page_result? - |err| err->error_message(English) + page = get_page("http://example.com/users/789") + result = page.map_err(|e| e->error_message(English)) result == Err("Insecure connection (non HTTPS): http://example.com/users/789") } # Error: invalid domain name expect { - page_result = get_page("https://google.com/wrong") - result = page_result? - |err| err->error_message(English) + page = get_page("https://google.com/wrong") + result = page.map_err(|e| e->error_message(English)) result == Err("Invalid domain name: https://google.com/wrong") } # Error: page not found expect { - page_result = get_page("https://example.com/oops") - result = page_result? - |err| err->error_message(English) + page = get_page("https://example.com/oops") + result = page.map_err(|e| e->error_message(English)) result == Err("Page not found: /oops") } -# Error: invalid userId +# Error: invalid user_id expect { - page_result = get_page("https://example.com/users/abc") - result = page_result? - |err| err->error_message(English) + page = get_page("https://example.com/users/abc") + result = page.map_err(|e| e->error_message(English)) result == Err("User ID is not a positive integer: abc") } # Error: user not found expect { - page_result = get_page("https://example.com/users/42") - result = page_result? - |err| err->error_message(English) + page = get_page("https://example.com/users/42") + result = page.map_err(|e| e->error_message(English)) result == Err("User #42 was not found") } From 6ca60fcd84c9c0488f4609984102e9177ff24bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 17:46:12 +1200 Subject: [PATCH 113/162] Update exercise to new compiler: custom-set --- .../practice/custom-set/.meta/Example.roc | 132 +++++----- .../practice/custom-set/.meta/template.j2 | 44 ++-- exercises/practice/custom-set/CustomSet.roc | 113 +++++---- .../practice/custom-set/custom-set-test.roc | 234 +++++++++--------- 4 files changed, 275 insertions(+), 248 deletions(-) diff --git a/exercises/practice/custom-set/.meta/Example.roc b/exercises/practice/custom-set/.meta/Example.roc index e9336c65..55f657bb 100644 --- a/exercises/practice/custom-set/.meta/Example.roc +++ b/exercises/practice/custom-set/.meta/Example.roc @@ -1,69 +1,87 @@ -CustomSet :: {}.{ - contains : CustomSet, Element -> Bool - contains = |@CustomSet({ items }), element| - items |> List.contains(element) +CustomSet :: { items : List(U64) }.{ + Item : U64 - difference : CustomSet, CustomSet -> CustomSet - difference = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| - items = items1 |> List.drop_if(|item| items2 |> List.contains(item)) - @CustomSet({ items }) + contains : CustomSet, Item -> Bool + contains = |{ items }, item| items.contains(item) - from_list : List Element -> CustomSet - from_list = |list| - when list |> List.sort_asc is - [] -> @CustomSet({ items: [] }) - [first, .. as rest] -> - items = - rest - |> List.walk( - { items: [first], previous: first }, - |state, item| - if item == state.previous then - state - else - { items: state.items |> List.append(item), previous: item }, - ) - |> .items - @CustomSet({ items }) + difference : CustomSet, CustomSet -> CustomSet + difference = |{ items: items1 }, { items: items2 }| { + { items: items1.drop_if(|item| items2.contains(item)) } + } - insert : CustomSet, Element -> CustomSet - insert = |@CustomSet({ items }), element| - if items |> List.contains(element) then - @CustomSet({ items }) - else - @CustomSet({ items: items |> List.append(element) }) + from_list : List(Item) -> CustomSet + from_list = |list| { + match sort_asc(list) { + [] => { items: [] } + [first, .. as rest] => { + state_res = rest.fold( + { items: [first], previous: first }, + |state, item| { + if item == state.previous { + state + } else { + { items: state.items.append(item), previous: item } + } + }, + ) + { items: state_res.items } + } + } + } - intersection : CustomSet, CustomSet -> CustomSet - intersection = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| - items = items1 |> List.keep_if(|item| items2 |> List.contains(item)) - @CustomSet({ items }) + insert : CustomSet, Item -> CustomSet + insert = |{ items }, item| { + if items.contains(item) { + { items: items } + } else { + { items: items.append(item) } + } + } - is_disjoint_with : CustomSet, CustomSet -> Bool - is_disjoint_with = |set1, set2| - set1 |> intersection(set2) |> is_empty + intersection : CustomSet, CustomSet -> CustomSet + intersection = |{ items: items1 }, { items: items2 }| { + { items: items1.keep_if(|item| items2.contains(item)) } + } - is_empty : CustomSet -> Bool - is_empty = |@CustomSet({ items })| - items |> List.is_empty + is_disjoint_with : CustomSet, CustomSet -> Bool + is_disjoint_with = |set1, set2| { + intersection(set1, set2).is_empty() + } - is_eq : CustomSet, CustomSet -> Bool - is_eq = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| - items1 |> List.sort_asc == items2 |> List.sort_asc + is_empty : CustomSet -> Bool + is_empty = |{ items }| { + items.is_empty() + } - is_subset_of : CustomSet, CustomSet -> Bool - is_subset_of = |@CustomSet({ items: items1 }), @CustomSet({ items: items2 })| - items1 |> List.all(|item| items2 |> List.contains(item)) + is_eq : CustomSet, CustomSet -> Bool + is_eq = |{ items: items1 }, { items: items2 }| { + sort_asc(items1) == sort_asc(items2) + } - to_list : CustomSet -> List Element - to_list = |@CustomSet({ items })| - items - - union : CustomSet, CustomSet -> CustomSet - union = |set1, set2| - set1 |> to_list |> List.concat((set2 |> to_list)) |> from_list -} + is_subset_of : CustomSet, CustomSet -> Bool + is_subset_of = |{ items: items1 }, { items: items2 }| { + items1.all(|item| items2.contains(item)) + } + to_list : CustomSet -> List(Item) + to_list = |{ items }| items -Element : U64 + union : CustomSet, CustomSet -> CustomSet + union = |set1, set2| { + set1.to_list().concat(set2.to_list())->from_list() + } +} -CustomSet := { items : List Element } implements [Eq] +sort_asc = |list| { + list.sort_with( + |a, b| { + if a < b { + LT + } else if a > b { + GT + } else { + EQ + } + }, + ) +} diff --git a/exercises/practice/custom-set/.meta/template.j2 b/exercises/practice/custom-set/.meta/template.j2 index ad127cfe..e549defe 100644 --- a/exercises/practice/custom-set/.meta/template.j2 +++ b/exercises/practice/custom-set/.meta/template.j2 @@ -2,20 +2,7 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [ - contains, - difference, - from_list, - insert, - intersection, - is_disjoint_with, - is_empty, - is_eq, - is_subset_of, - to_list, - union, -] - +import {{ exercise | to_pascal }} {% set property_map = { "add": "insert", @@ -36,16 +23,15 @@ set property_map = { {% set property = property_map.get(case["property"], case["property"]) %} expect { {%- if "set" in case["input"] %} - set = from_list({{ case["input"]["set"] | to_roc }}) - result = set.{{ property | to_snake }}() - {%- if "element" in case["input"] %}({{(case["input"]["element"])}}){%- endif %} + set = {{ exercise | to_pascal }}.from_list({{ case["input"]["set"] | to_roc }}) + result = set.{{ property | to_snake }}({{(case["input"]["element"])}}) {%- else %} - set1 = from_list({{ case["input"]["set1"] | to_roc }}) - set2 = from_list({{ case["input"]["set2"] | to_roc }}) + set1 = {{ exercise | to_pascal }}.from_list({{ case["input"]["set1"] | to_roc }}) + set2 = {{ exercise | to_pascal }}.from_list({{ case["input"]["set2"] | to_roc }}) result = set1.{{ property | to_snake }}(set2) {%- endif %} {%- if case["expected"] is iterable %} - expected = {{ case["expected"] | to_roc }} -> from_list() + expected = {{ case["expected"] | to_roc }} -> {{ exercise | to_pascal }}.from_list() result.is_eq(expected) {%- else %} expected = {{ case["expected"] | to_roc }} @@ -63,12 +49,26 @@ expect { {% for case in additional_cases -%} # {{ case["description"] }} expect { - set = from_list({{ case["input"]["set"] | to_roc }}) - result = set.to_list().sort_asc() + set = {{ exercise | to_pascal }}.from_list({{ case["input"]["set"] | to_roc }}) + result = set.to_list()->sort_asc() expected = {{ case["expected"] | to_roc }} result == expected } {% endfor %} +sort_asc = |list| { + list.sort_with( + |a, b| { + if a < b { + LT + } else if a > b { + GT + } else { + EQ + } + }, + ) +} + {{ macros.footer() }} diff --git a/exercises/practice/custom-set/CustomSet.roc b/exercises/practice/custom-set/CustomSet.roc index b6e3067c..6ae7c53f 100644 --- a/exercises/practice/custom-set/CustomSet.roc +++ b/exercises/practice/custom-set/CustomSet.roc @@ -1,57 +1,64 @@ -CustomSet :: {}.{ - contains : CustomSet, Element -> Bool - contains = |set, element| - crash("Please implement the 'contains' function") - - difference : CustomSet, CustomSet -> CustomSet - difference = |set1, set2| - crash("Please implement the 'difference' function") - - from_list : List Element -> CustomSet - from_list = |list| - crash("Please implement the 'from_list' function") - - insert : CustomSet, Element -> CustomSet - insert = |set, element| - crash("Please implement the 'insert' function") - - intersection : CustomSet, CustomSet -> CustomSet - intersection = |set1, set2| - crash("Please implement the 'intersection' function") - - is_disjoint_with : CustomSet, CustomSet -> Bool - is_disjoint_with = |set1, set2| - crash("Please implement the 'is_disjoint_with' function") - - is_empty : CustomSet -> Bool - is_empty = |set| - crash("Please implement the 'is_empty' function") - - is_eq : CustomSet, CustomSet -> Bool - is_eq = |set1, set2| - crash("Please implement the 'is_eq' function") - - is_subset_of : CustomSet, CustomSet -> Bool - is_subset_of = |set1, set2| - crash("Please implement the 'is_subset_of' function") - - to_list : CustomSet -> List Element - to_list = |set| - crash("Please implement the 'to_list' function") - - union : CustomSet, CustomSet -> CustomSet - union = |set1, set2| - crash("Please implement the 'union' function") -} +CustomSet :: { + # TODO: change this opaque type however you need + todo1 : U64, + todo2 : U64, + todo3 : U64, + # etc. +}.{ + Item : U64 + + contains : CustomSet, Item -> Bool + contains = |custom_set, item| { + crash "Please implement the 'contains' function" + } + + difference : CustomSet, CustomSet -> CustomSet + difference = |custom_set1, custom_set2| { + crash "Please implement the 'difference' function" + } + + from_list : List(Item) -> CustomSet + from_list = |list| { + crash "Please implement the 'from_list' function" + } + + insert : CustomSet, Item -> CustomSet + insert = |custom_set, item| { + crash "Please implement the 'insert' function" + } + + intersection : CustomSet, CustomSet -> CustomSet + intersection = |custom_set1, custom_set2| { + crash "Please implement the 'intersection' function" + } + + is_disjoint_with : CustomSet, CustomSet -> Bool + is_disjoint_with = |set1, set2| { + crash "Please implement the 'is_disjoint_with' function" + } + + is_empty : CustomSet -> Bool + is_empty = |custom_set| { + crash "Please implement the 'is_empty' function" + } + + is_eq : CustomSet, CustomSet -> Bool + is_eq = |custom_set1, custom_set2| { + crash "Please implement the 'is_eq' function" + } + is_subset_of : CustomSet, CustomSet -> Bool + is_subset_of = |custom_set1, custom_set2| { + crash "Please implement the 'is_subset_of' function" + } -Element : U64 + to_list : CustomSet -> List(Item) + to_list = |custom_set| { + crash "Please implement the 'to_list' function" + } -CustomSet := { - # TODO: change this opaque type however you need - todo : U64, - todo2 : U64, - todo3 : U64, - # etc. + union : CustomSet, CustomSet -> CustomSet + union = |set1, set2| { + crash "Please implement the 'union' function" + } } - implements [Eq] diff --git a/exercises/practice/custom-set/custom-set-test.roc b/exercises/practice/custom-set/custom-set-test.roc index 95ec53cf..f4bd2b11 100644 --- a/exercises/practice/custom-set/custom-set-test.roc +++ b/exercises/practice/custom-set/custom-set-test.roc @@ -1,20 +1,8 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json -# File last updated on 2026-06-13 - -import CustomSet exposing [ - contains, - difference, - from_list, - insert, - intersection, - is_disjoint_with, - is_empty, - is_eq, - is_subset_of, - to_list, - union, -] +# File last updated on 2026-06-20 + +import CustomSet ## ## Returns true if the set contains no elements @@ -23,7 +11,7 @@ import CustomSet exposing [ # sets with no elements are empty expect { - set = from_list([]) + set = CustomSet.from_list([]) result = set.is_empty() expected = Bool.True result == expected @@ -32,7 +20,7 @@ expect { # sets with elements are not empty expect { - set = from_list([1]) + set = CustomSet.from_list([1]) result = set.is_empty() expected = Bool.False result == expected @@ -45,8 +33,8 @@ expect { # nothing is contained in an empty set expect { - set = from_list([]) - result = set.contains()(1) + set = CustomSet.from_list([]) + result = set.contains(1) expected = Bool.False result == expected } @@ -54,8 +42,8 @@ expect { # when the element is in the set expect { - set = from_list([1, 2, 3]) - result = set.contains()(1) + set = CustomSet.from_list([1, 2, 3]) + result = set.contains(1) expected = Bool.True result == expected } @@ -63,8 +51,8 @@ expect { # when the element is not in the set expect { - set = from_list([1, 2, 3]) - result = set.contains()(4) + set = CustomSet.from_list([1, 2, 3]) + result = set.contains(4) expected = Bool.False result == expected } @@ -76,8 +64,8 @@ expect { # empty set is a subset of another empty set expect { - set1 = from_list([]) - set2 = from_list([]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([]) result = set1.is_subset_of(set2) expected = Bool.True result == expected @@ -86,8 +74,8 @@ expect { # empty set is a subset of non-empty set expect { - set1 = from_list([]) - set2 = from_list([1]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([1]) result = set1.is_subset_of(set2) expected = Bool.True result == expected @@ -96,8 +84,8 @@ expect { # non-empty set is not a subset of empty set expect { - set1 = from_list([1]) - set2 = from_list([]) + set1 = CustomSet.from_list([1]) + set2 = CustomSet.from_list([]) result = set1.is_subset_of(set2) expected = Bool.False result == expected @@ -106,8 +94,8 @@ expect { # set is a subset of set with exact same elements expect { - set1 = from_list([1, 2, 3]) - set2 = from_list([1, 2, 3]) + set1 = CustomSet.from_list([1, 2, 3]) + set2 = CustomSet.from_list([1, 2, 3]) result = set1.is_subset_of(set2) expected = Bool.True result == expected @@ -116,8 +104,8 @@ expect { # set is a subset of larger set with same elements expect { - set1 = from_list([1, 2, 3]) - set2 = from_list([4, 1, 2, 3]) + set1 = CustomSet.from_list([1, 2, 3]) + set2 = CustomSet.from_list([4, 1, 2, 3]) result = set1.is_subset_of(set2) expected = Bool.True result == expected @@ -126,8 +114,8 @@ expect { # set is not a subset of set that does not contain its elements expect { - set1 = from_list([1, 2, 3]) - set2 = from_list([4, 1, 3]) + set1 = CustomSet.from_list([1, 2, 3]) + set2 = CustomSet.from_list([4, 1, 3]) result = set1.is_subset_of(set2) expected = Bool.False result == expected @@ -140,8 +128,8 @@ expect { # the empty set is disjoint with itself expect { - set1 = from_list([]) - set2 = from_list([]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([]) result = set1.is_disjoint_with(set2) expected = Bool.True result == expected @@ -150,8 +138,8 @@ expect { # empty set is disjoint with non-empty set expect { - set1 = from_list([]) - set2 = from_list([1]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([1]) result = set1.is_disjoint_with(set2) expected = Bool.True result == expected @@ -160,8 +148,8 @@ expect { # non-empty set is disjoint with empty set expect { - set1 = from_list([1]) - set2 = from_list([]) + set1 = CustomSet.from_list([1]) + set2 = CustomSet.from_list([]) result = set1.is_disjoint_with(set2) expected = Bool.True result == expected @@ -170,8 +158,8 @@ expect { # sets are not disjoint if they share an element expect { - set1 = from_list([1, 2]) - set2 = from_list([2, 3]) + set1 = CustomSet.from_list([1, 2]) + set2 = CustomSet.from_list([2, 3]) result = set1.is_disjoint_with(set2) expected = Bool.False result == expected @@ -180,8 +168,8 @@ expect { # sets are disjoint if they share no elements expect { - set1 = from_list([1, 2]) - set2 = from_list([3, 4]) + set1 = CustomSet.from_list([1, 2]) + set2 = CustomSet.from_list([3, 4]) result = set1.is_disjoint_with(set2) expected = Bool.True result == expected @@ -194,8 +182,8 @@ expect { # empty sets are equal expect { - set1 = from_list([]) - set2 = from_list([]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([]) result = set1.is_eq(set2) expected = Bool.True result == expected @@ -204,8 +192,8 @@ expect { # empty set is not equal to non-empty set expect { - set1 = from_list([]) - set2 = from_list([1, 2, 3]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([1, 2, 3]) result = set1.is_eq(set2) expected = Bool.False result == expected @@ -214,8 +202,8 @@ expect { # non-empty set is not equal to empty set expect { - set1 = from_list([1, 2, 3]) - set2 = from_list([]) + set1 = CustomSet.from_list([1, 2, 3]) + set2 = CustomSet.from_list([]) result = set1.is_eq(set2) expected = Bool.False result == expected @@ -224,8 +212,8 @@ expect { # sets with the same elements are equal expect { - set1 = from_list([1, 2]) - set2 = from_list([2, 1]) + set1 = CustomSet.from_list([1, 2]) + set2 = CustomSet.from_list([2, 1]) result = set1.is_eq(set2) expected = Bool.True result == expected @@ -234,8 +222,8 @@ expect { # sets with different elements are not equal expect { - set1 = from_list([1, 2, 3]) - set2 = from_list([1, 2, 4]) + set1 = CustomSet.from_list([1, 2, 3]) + set2 = CustomSet.from_list([1, 2, 4]) result = set1.is_eq(set2) expected = Bool.False result == expected @@ -244,8 +232,8 @@ expect { # set is not equal to larger set with same elements expect { - set1 = from_list([1, 2, 3]) - set2 = from_list([1, 2, 3, 4]) + set1 = CustomSet.from_list([1, 2, 3]) + set2 = CustomSet.from_list([1, 2, 3, 4]) result = set1.is_eq(set2) expected = Bool.False result == expected @@ -254,8 +242,8 @@ expect { # set is equal to a set constructed from an array with duplicates expect { - set1 = from_list([1]) - set2 = from_list([1, 1]) + set1 = CustomSet.from_list([1]) + set2 = CustomSet.from_list([1, 1]) result = set1.is_eq(set2) expected = Bool.True result == expected @@ -268,27 +256,27 @@ expect { # add to empty set expect { - set = from_list([]) - result = set.insert()(3) - expected = [3]->from_list() + set = CustomSet.from_list([]) + result = set.insert(3) + expected = [3]->CustomSet.from_list() result.is_eq(expected) } # add to non-empty set expect { - set = from_list([1, 2, 4]) - result = set.insert()(3) - expected = [1, 2, 3, 4]->from_list() + set = CustomSet.from_list([1, 2, 4]) + result = set.insert(3) + expected = [1, 2, 3, 4]->CustomSet.from_list() result.is_eq(expected) } # adding an existing element does not change the set expect { - set = from_list([1, 2, 3]) - result = set.insert()(3) - expected = [1, 2, 3]->from_list() + set = CustomSet.from_list([1, 2, 3]) + result = set.insert(3) + expected = [1, 2, 3]->CustomSet.from_list() result.is_eq(expected) } @@ -299,50 +287,50 @@ expect { # intersection of two empty sets is an empty set expect { - set1 = from_list([]) - set2 = from_list([]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([]) result = set1.intersection(set2) - expected = []->from_list() + expected = []->CustomSet.from_list() result.is_eq(expected) } # intersection of an empty set and non-empty set is an empty set expect { - set1 = from_list([]) - set2 = from_list([3, 2, 5]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([3, 2, 5]) result = set1.intersection(set2) - expected = []->from_list() + expected = []->CustomSet.from_list() result.is_eq(expected) } # intersection of a non-empty set and an empty set is an empty set expect { - set1 = from_list([1, 2, 3, 4]) - set2 = from_list([]) + set1 = CustomSet.from_list([1, 2, 3, 4]) + set2 = CustomSet.from_list([]) result = set1.intersection(set2) - expected = []->from_list() + expected = []->CustomSet.from_list() result.is_eq(expected) } # intersection of two sets with no shared elements is an empty set expect { - set1 = from_list([1, 2, 3]) - set2 = from_list([4, 5, 6]) + set1 = CustomSet.from_list([1, 2, 3]) + set2 = CustomSet.from_list([4, 5, 6]) result = set1.intersection(set2) - expected = []->from_list() + expected = []->CustomSet.from_list() result.is_eq(expected) } # intersection of two sets with shared elements is a set of the shared elements expect { - set1 = from_list([1, 2, 3, 4]) - set2 = from_list([3, 2, 5]) + set1 = CustomSet.from_list([1, 2, 3, 4]) + set2 = CustomSet.from_list([3, 2, 5]) result = set1.intersection(set2) - expected = [2, 3]->from_list() + expected = [2, 3]->CustomSet.from_list() result.is_eq(expected) } @@ -353,50 +341,50 @@ expect { # difference of two empty sets is an empty set expect { - set1 = from_list([]) - set2 = from_list([]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([]) result = set1.difference(set2) - expected = []->from_list() + expected = []->CustomSet.from_list() result.is_eq(expected) } # difference of empty set and non-empty set is an empty set expect { - set1 = from_list([]) - set2 = from_list([3, 2, 5]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([3, 2, 5]) result = set1.difference(set2) - expected = []->from_list() + expected = []->CustomSet.from_list() result.is_eq(expected) } # difference of a non-empty set and an empty set is the non-empty set expect { - set1 = from_list([1, 2, 3, 4]) - set2 = from_list([]) + set1 = CustomSet.from_list([1, 2, 3, 4]) + set2 = CustomSet.from_list([]) result = set1.difference(set2) - expected = [1, 2, 3, 4]->from_list() + expected = [1, 2, 3, 4]->CustomSet.from_list() result.is_eq(expected) } # difference of two non-empty sets is a set of elements that are only in the first set expect { - set1 = from_list([3, 2, 1]) - set2 = from_list([2, 4]) + set1 = CustomSet.from_list([3, 2, 1]) + set2 = CustomSet.from_list([2, 4]) result = set1.difference(set2) - expected = [1, 3]->from_list() + expected = [1, 3]->CustomSet.from_list() result.is_eq(expected) } # difference removes all duplicates in the first set expect { - set1 = from_list([1, 1]) - set2 = from_list([1]) + set1 = CustomSet.from_list([1, 1]) + set2 = CustomSet.from_list([1]) result = set1.difference(set2) - expected = []->from_list() + expected = []->CustomSet.from_list() result.is_eq(expected) } @@ -407,40 +395,40 @@ expect { # union of empty sets is an empty set expect { - set1 = from_list([]) - set2 = from_list([]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([]) result = set1.union(set2) - expected = []->from_list() + expected = []->CustomSet.from_list() result.is_eq(expected) } # union of an empty set and non-empty set is the non-empty set expect { - set1 = from_list([]) - set2 = from_list([2]) + set1 = CustomSet.from_list([]) + set2 = CustomSet.from_list([2]) result = set1.union(set2) - expected = [2]->from_list() + expected = [2]->CustomSet.from_list() result.is_eq(expected) } # union of a non-empty set and empty set is the non-empty set expect { - set1 = from_list([1, 3]) - set2 = from_list([]) + set1 = CustomSet.from_list([1, 3]) + set2 = CustomSet.from_list([]) result = set1.union(set2) - expected = [1, 3]->from_list() + expected = [1, 3]->CustomSet.from_list() result.is_eq(expected) } # union of non-empty sets contains all unique elements expect { - set1 = from_list([1, 3]) - set2 = from_list([2, 3]) + set1 = CustomSet.from_list([1, 3]) + set2 = CustomSet.from_list([2, 3]) result = set1.union(set2) - expected = [3, 2, 1]->from_list() + expected = [3, 2, 1]->CustomSet.from_list() result.is_eq(expected) } @@ -450,28 +438,42 @@ expect { # an empty set has an empty list of items expect { - set = from_list([]) - result = set.to_list().sort_asc() + set = CustomSet.from_list([]) + result = set.to_list()->sort_asc() expected = [] result == expected } # a set can provide the list of its items expect { - set = from_list([1, 2, 3, 4]) - result = set.to_list().sort_asc() + set = CustomSet.from_list([1, 2, 3, 4]) + result = set.to_list()->sort_asc() expected = [1, 2, 3, 4] result == expected } # duplicate items must be removed expect { - set = from_list([1, 2, 2, 3, 3, 3, 4, 4, 4, 4]) - result = set.to_list().sort_asc() + set = CustomSet.from_list([1, 2, 2, 3, 3, 3, 4, 4, 4, 4]) + result = set.to_list()->sort_asc() expected = [1, 2, 3, 4] result == expected } +sort_asc = |list| { + list.sort_with( + |a, b| { + if a < b { + LT + } else if a > b { + GT + } else { + EQ + } + }, + ) +} + # This program is only used to run tests with `roc test`, so main! does nothing. main! = |_args| { Ok({}) From d7074ff9e26581a86732442633fa68f00fef9680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 18:25:43 +1200 Subject: [PATCH 114/162] Update exercise to new compiler: list-ops --- .../list-ops/.docs/instructions.append.md | 35 +++-- exercises/practice/list-ops/.meta/Example.roc | 135 ++++++++++-------- exercises/practice/list-ops/.meta/template.j2 | 32 +++-- exercises/practice/list-ops/ListOps.roc | 66 +++++---- exercises/practice/list-ops/list-ops-test.roc | 59 ++++---- 5 files changed, 201 insertions(+), 126 deletions(-) diff --git a/exercises/practice/list-ops/.docs/instructions.append.md b/exercises/practice/list-ops/.docs/instructions.append.md index 56d270a1..b2bbf008 100644 --- a/exercises/practice/list-ops/.docs/instructions.append.md +++ b/exercises/practice/list-ops/.docs/instructions.append.md @@ -3,7 +3,7 @@ ## Wait, It's Impossible! Implementing these list operations without using _any_ built-in function is virtually impossible in Roc, because you need a way to append or prepend elements to a list. -In other languages you might use operators such as `:` in Haskell or `+=` in Python, but in Roc you have to use the [`List` functions](https://www.roc-lang.org/builtins/List). +In other languages you might use operators such as `:` in Haskell or `+=` in Python, but in Roc you have to use the [`List` functions](https://www.roc-lang.org/builtins/main/#Builtin.List). So for this exercise you're allowed to use `List.append` (but avoid using any other built-in function). @@ -13,18 +13,37 @@ In Roc however, a `List` is an array (a contiguous chunk of bytes). Arrays have different properties than linked lists like the ability to efficiently access elements by index and append new elements. Because of this, in Roc we use `List.append` often and rarely use `List.prepend`. -Hint: try using: +## Naming + +Roc uses slightly different names than the ones listed in the general instructions: + +* `concat` instead of `append` +- `join` instead of `concatenate` +- `len` instead of `length` +- `fold` instead of `foldl` +- `fold_rev` instead of `foldr` + +However, the following function names are standard in Roc: +- `filter` +- `map` +- `reverse` + +## Hint + +Try using: ```roc -when list is - [] -> ???? - [first, .. as rest] -> ??? +match list { + [] => ???? + [first, .. as rest] => ???? +} ``` or ```roc -when list is - [] -> ??? - [.. as rest, last] -> ??? +match list { + [] -> ???? + [.. as rest, last] => ???? +} ``` diff --git a/exercises/practice/list-ops/.meta/Example.roc b/exercises/practice/list-ops/.meta/Example.roc index 82a1dbc9..04b71af8 100644 --- a/exercises/practice/list-ops/.meta/Example.roc +++ b/exercises/practice/list-ops/.meta/Example.roc @@ -1,71 +1,84 @@ ListOps :: {}.{ - append : List a, List a -> List a - append = |list1, list2| - # Cheating: list1 |> List.concat list2 - when list2 is - [] -> list1 - [first, .. as rest] -> list1 |> List.append(first) |> append(rest) + concat : List(a), List(a) -> List(a) + concat = |list1, list2| { + match list2 { + [] => list1 + [first, .. as rest] => concat(list1.append(first), rest) + } + } - concat : List (List a) -> List a - concat = |lists| - # Cheating: list |> List.join - when lists is - [] -> [] - [one] -> one - [.. as rest, one, two] -> rest |> List.append(append(one, two)) |> concat + join : List(List(a)) -> List(a) + join = |lists| { + match lists { + [] => [] + [first, .. as rest] => first->concat(join(rest)) + } + } - filter : List a, (a -> Bool) -> List a - filter = |list, function| - # Cheating: list |> List.keep_if function - loop = |l, acc| - when l is - [] -> acc - [first, .. as rest] -> - if function(first) then - rest |> loop(List.append(acc, first)) - else - rest |> loop(acc) + filter : List(a), (a -> Bool) -> List(a) + filter = |list, function| { + loop = |l, acc| { + match l { + [] => acc + [first, .. as rest] => { + if function(first) { + loop(rest, acc.append(first)) + } else { + loop(rest, acc) + } + } + } + } - loop(list, []) + loop(list, []) + } - length : List a -> U64 - length = |list| - # Cheating: list |> List.len - loop = |l, acc| - when l is - [] -> acc - [_, .. as rest] -> rest |> loop((acc + 1)) - loop(list, 0) + len : List(a) -> U64 + len = |list| { + loop = |l, acc| { + match l { + [] => acc + [_, .. as rest] => loop(rest, (acc + 1)) + } + } + loop(list, 0) + } - map : List a, (a -> b) -> List b - map = |list, function| - # Cheating: list |> List.map function - loop = |l, acc| - when l is - [] -> acc - [first, .. as rest] -> rest |> loop(List.append(acc, function(first))) - loop(list, []) + map : List(a), (a -> b) -> List(b) + map = |list, function| { + loop = |l, acc| { + match l { + [] => acc + [first, .. as rest] => loop(rest, acc.append(function(first))) + } + } + loop(list, []) + } - foldl : List a, b, (b, a -> b) -> b - foldl = |list, initial, function| - # Cheating: list |> List.walk initial function - when list is - [] -> initial - [first, .. as rest] -> rest |> foldl(function(initial, first), function) + fold : List(a), b, (b, a -> b) -> b + fold = |list, initial, function| { + match list { + [] => initial + [first, .. as rest] => fold(rest, function(initial, first), function) + } + } - foldr : List a, b, (b, a -> b) -> b - foldr = |list, initial, function| - # Cheating: list |> List.walk_backwards initial function - when list is - [] -> initial - [.. as rest, last] -> rest |> foldr(function(initial, last), function) + fold_rev : List(a), b, (b, a -> b) -> b + fold_rev = |list, initial, function| { + match list { + [] => initial + [.. as rest, last] => fold_rev(rest, function(initial, last), function) + } + } - reverse : List a -> List a - reverse = |list| - # Cheating: list |> List.reverse - loop = |l, acc| - when l is - [] -> acc - [.. as rest, last] -> rest |> loop(List.append(acc, last)) - loop(list, []) + reverse : List(a) -> List(a) + reverse = |list| { + loop = |l, acc| { + match l { + [] => acc + [.. as rest, last] => loop(rest, acc.append(last)) + } + } + loop(list, []) + } } diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2 index 8e968dfe..897bb299 100644 --- a/exercises/practice/list-ops/.meta/template.j2 +++ b/exercises/practice/list-ops/.meta/template.j2 @@ -2,7 +2,7 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [append, concat, filter, length, map, foldl, foldr, reverse] +import {{ exercise | to_pascal }} exposing [concat, join, filter, len, map, fold, fold_rev, reverse] {% set function_map = { "(acc, el) -> el * acc": "|acc, el| el * acc", @@ -21,25 +21,28 @@ import {{ exercise | to_pascal }} exposing [append, concat, filter, length, map, # {{ case["description"] }} expect { {%- if case["property"] == "append" %} - result = {{ case["property"] | to_snake }}({{ case["input"]["list1"] | to_roc }}, {{ case["input"]["list2"] | to_roc }}) + result = {{ case["input"]["list1"] | to_roc }}->concat({{ case["input"]["list2"] | to_roc }}) result == {{ case["expected"] | to_roc }} {%- elif case["property"] == "concat" %} - result = {{ case["property"] | to_snake }}({{ case["input"]["lists"] | to_roc }}) + result = {{ case["input"]["lists"] | to_roc }}->join() result == {{ case["expected"] | to_roc }} {%- elif case["property"] == "filter" %} - result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}, {{ function_map[case["input"]["function"]] }}) + result = {{ case["input"]["list"] | to_roc }}->filter({{ function_map[case["input"]["function"]] }}) result == {{ case["expected"] | to_roc }} {%- elif case["property"] == "length" %} - result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}) + result = {{ case["input"]["list"] | to_roc }}->len() result == {{ case["expected"] }} {%- elif case["property"] == "map" %} - result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}, {{ function_map[case["input"]["function"]] }}) + result = {{ case["input"]["list"] | to_roc }}->map({{ function_map[case["input"]["function"]] }}) result == {{ case["expected"] }} -{%- elif case["property"] in ("foldl", "foldr") %} - result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}, {{ case["input"]["initial"] }}, {{ function_map[case["input"]["function"]] }}){% if "/" in function_map[case["input"]["function"]] %} -> round(){% endif %} +{%- elif case["property"] == "foldl" %} + result = {{ case["input"]["list"] | to_roc }}->fold({{ case["input"]["initial"] }}, {{ function_map[case["input"]["function"]] }}){% if "/" in function_map[case["input"]["function"]] %} -> round(){% endif %} + result == {{ case["expected"] | to_roc }} +{%- elif case["property"] == "foldr" %} + result = {{ case["input"]["list"] | to_roc }}->fold_rev({{ case["input"]["initial"] }}, {{ function_map[case["input"]["function"]] }}){% if "/" in function_map[case["input"]["function"]] %} -> round(){% endif %} result == {{ case["expected"] | to_roc }} {%- elif case["property"] == "reverse" %} - result = {{ case["property"] | to_snake }}({{ case["input"]["list"] | to_roc }}) + result = {{ case["input"]["list"] | to_roc }}->reverse() result == {{ case["expected"] | to_roc }} {%- else %} Bool.True # This test case is not yet implemented @@ -49,4 +52,15 @@ expect { {% endfor -%} {% endfor -%} +# The following function will soon be available in Roc's builtins +round : Dec -> Dec +round = |value| { + pow = 1000.0 + ( + (value * pow + 0.5).to_u64_try() ?? { + crash "Unreachable" + } + ).to_dec() / pow +} + {{ macros.footer() }} diff --git a/exercises/practice/list-ops/ListOps.roc b/exercises/practice/list-ops/ListOps.roc index 85acec2f..1a1411eb 100644 --- a/exercises/practice/list-ops/ListOps.roc +++ b/exercises/practice/list-ops/ListOps.roc @@ -1,33 +1,51 @@ ListOps :: {}.{ - append : List a, List a -> List a - append = |list1, list2| - crash("Please implement the 'append' function") + # # Note: this function is named "append" in the exercise instructions + # # but Roc prefers to name this "concat" + concat : List(a), List(a) -> List(a) + concat = |list1, list2| { + crash "Please implement the 'concat' function" + } - concat : List (List a) -> List a - concat = |lists| - crash("Please implement the 'concat' function") + # # Note: this function is named "concatenate" in the exercise instructions + # # but Roc prefers to name this "join" + join : List(List(a)) -> List(a) + join = |lists| { + crash "Please implement the 'join' function" + } - filter : List a, (a -> Bool) -> List a - filter = |list, function| - crash("Please implement the 'filter' function") + filter : List(a), (a -> Bool) -> List(a) + filter = |list, function| { + crash "Please implement the 'filter' function" + } - length : List a -> U64 - length = |list| - crash("Please implement the 'length' function") + # # Note: this function is named "length" in the exercise instructions + # # but Roc prefers to name this "len" + len : List(a) -> U64 + len = |list| { + crash "Please implement the 'len' function" + } - map : List a, (a -> b) -> List b - map = |list, function| - crash("Please implement the 'map' function") + map : List(a), (a -> b) -> List(b) + map = |list, function| { + crash "Please implement the 'map' function" + } - foldl : List a, b, (b, a -> b) -> b - foldl = |list, initial, function| - crash("Please implement the 'foldl' function") + # # Note: this function is named "foldl" in the exercise instructions + # # but Roc prefers to name this "fold" + fold : List(a), b, (b, a -> b) -> b + fold = |list, initial, function| { + crash "Please implement the 'fold' function" + } - foldr : List a, b, (b, a -> b) -> b - foldr = |list, initial, function| - crash("Please implement the 'foldr' function") + # # Note: this function is named "foldr" in the exercise instructions + # # but Roc prefers to name this "fold_rev" + fold_rev : List(a), b, (b, a -> b) -> b + fold_rev = |list, initial, function| { + crash "Please implement the 'fold_rev' function" + } - reverse : List a -> List a - reverse = |list| - crash("Please implement the 'reverse' function") + reverse : List(a) -> List(a) + reverse = |list| { + crash "Please implement the 'reverse' function" + } } diff --git a/exercises/practice/list-ops/list-ops-test.roc b/exercises/practice/list-ops/list-ops-test.roc index ab35154e..0f8f3775 100644 --- a/exercises/practice/list-ops/list-ops-test.roc +++ b/exercises/practice/list-ops/list-ops-test.roc @@ -1,8 +1,8 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/list-ops/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-20 -import ListOps exposing [append, concat, filter, length, map, foldl, foldr, reverse] +import ListOps exposing [concat, join, filter, len, map, fold, fold_rev, reverse] ## ## append entries to a list and return the new list @@ -10,25 +10,25 @@ import ListOps exposing [append, concat, filter, length, map, foldl, foldr, reve # empty lists expect { - result = append([], []) + result = []->concat([]) result == [] } # list to empty list expect { - result = append([], [1, 2, 3, 4]) + result = []->concat([1, 2, 3, 4]) result == [1, 2, 3, 4] } # empty list to list expect { - result = append([1, 2, 3, 4], []) + result = [1, 2, 3, 4]->concat([]) result == [1, 2, 3, 4] } # non-empty lists expect { - result = append([1, 2], [2, 3, 4, 5]) + result = [1, 2]->concat([2, 3, 4, 5]) result == [1, 2, 2, 3, 4, 5] } @@ -38,19 +38,19 @@ expect { # empty list expect { - result = concat([]) + result = []->join() result == [] } # list of lists expect { - result = concat([[1, 2], [3], [], [4, 5, 6]]) + result = [[1, 2], [3], [], [4, 5, 6]]->join() result == [1, 2, 3, 4, 5, 6] } # list of nested lists expect { - result = concat([[[1], [2]], [[3]], [[]], [[4, 5, 6]]]) + result = [[[1], [2]], [[3]], [[]], [[4, 5, 6]]]->join() result == [[1], [2], [3], [], [4, 5, 6]] } @@ -60,13 +60,13 @@ expect { # empty list expect { - result = filter([], |n| n % 2 == 1) + result = []->filter(|n| n % 2 == 1) result == [] } # non-empty list expect { - result = filter([1, 2, 3, 5], |n| n % 2 == 1) + result = [1, 2, 3, 5]->filter(|n| n % 2 == 1) result == [1, 3, 5] } @@ -76,13 +76,13 @@ expect { # empty list expect { - result = length([]) + result = []->len() result == 0 } # non-empty list expect { - result = length([1, 2, 3, 4]) + result = [1, 2, 3, 4]->len() result == 4 } @@ -92,13 +92,13 @@ expect { # empty list expect { - result = map([], |x| x + 1) + result = []->map(|x| x + 1) result == [] } # non-empty list expect { - result = map([1, 3, 5, 7], |x| x + 1) + result = [1, 3, 5, 7]->map(|x| x + 1) result == [2, 4, 6, 8] } @@ -108,19 +108,19 @@ expect { # empty list expect { - result = foldl([], 2, |acc, el| el * acc) + result = []->fold(2, |acc, el| el * acc) result == 2 } # direction independent function applied to non-empty list expect { - result = foldl([1, 2, 3, 4], 5, |acc, el| el + acc) + result = [1, 2, 3, 4]->fold(5, |acc, el| el + acc) result == 15 } # direction dependent function applied to non-empty list expect { - result = foldl([1, 2, 3, 4], 24, |acc, el| el / acc)->round() + result = [1, 2, 3, 4]->fold(24, |acc, el| el / acc)->round() result == 64 } @@ -130,19 +130,19 @@ expect { # empty list expect { - result = foldr([], 2, |acc, el| el * acc) + result = []->fold_rev(2, |acc, el| el * acc) result == 2 } # direction independent function applied to non-empty list expect { - result = foldr([1, 2, 3, 4], 5, |acc, el| el + acc) + result = [1, 2, 3, 4]->fold_rev(5, |acc, el| el + acc) result == 15 } # direction dependent function applied to non-empty list expect { - result = foldr([1, 2, 3, 4], 24, |acc, el| el / acc)->round() + result = [1, 2, 3, 4]->fold_rev(24, |acc, el| el / acc)->round() result == 9 } @@ -152,22 +152,33 @@ expect { # empty list expect { - result = reverse([]) + result = []->reverse() result == [] } # non-empty list expect { - result = reverse([1, 3, 5, 7]) + result = [1, 3, 5, 7]->reverse() result == [7, 5, 3, 1] } # list of lists is not flattened expect { - result = reverse([[1, 2], [3], [], [4, 5, 6]]) + result = [[1, 2], [3], [], [4, 5, 6]]->reverse() result == [[4, 5, 6], [], [3], [1, 2]] } +# The following function will soon be available in Roc's builtins +round : Dec -> Dec +round = |value| { + pow = 1000.0 + ( + (value * pow + 0.5).to_u64_try() ?? { + crash "Unreachable" + }, + ).to_dec() / pow +} + # This program is only used to run tests with `roc test`, so main! does nothing. main! = |_args| { Ok({}) From f713c3b1e63b87a0be9d8a9c1cba585f7c63d04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 21:55:22 +1200 Subject: [PATCH 115/162] Remove Temp.roc --- exercises/practice/error-handling/Temp.roc | 97 ---------------------- 1 file changed, 97 deletions(-) delete mode 100644 exercises/practice/error-handling/Temp.roc diff --git a/exercises/practice/error-handling/Temp.roc b/exercises/practice/error-handling/Temp.roc deleted file mode 100644 index f3141d17..00000000 --- a/exercises/practice/error-handling/Temp.roc +++ /dev/null @@ -1,97 +0,0 @@ -Temp :: {}.{ - get_user : UserId -> Try(User, [UserNotFound(UserId)]) - get_user = |user_id| { - match users.get(user_id) { - Ok(user) => Ok(user) - Err(KeyNotFound) => Err(UserNotFound(user_id)) - } - } - - parse_user_id : Str -> Try(UserId, [InvalidUserId(Str)]) - parse_user_id = |path| { - user_id_str = Str.replace_each(path, "/users/", "") - match U64.from_str(user_id_str) { - Ok(id) => Ok(id) - Err(BadNumStr) => Err(InvalidUserId(user_id_str)) - } - } - - get_page : Str -> Try(Str, [InsecureConnection(Str), InvalidDomain(Str), InvalidUserId(Str), UserNotFound(UserId), PageNotFound(Str)]) - get_page = |url| { - match parse_path(url) { - Ok(path) => { - match path { - "/" => Ok("Home page") - "/users/" => Ok("Users page") - user_path if user_path.starts_with("/users/") => { - match parse_user_id(user_path) { - Ok(user_id) => { - match get_user(user_id) { - Ok(user) => Ok("${user.name}'s page") - Err(UserNotFound(uid)) => Err(UserNotFound(uid)) - } - } - Err(InvalidUserId(uid_str)) => Err(InvalidUserId(uid_str)) - } - } - - unknown_path => Err(PageNotFound(unknown_path)) - } - } - - Err(InsecureConnection(url_err)) => Err(InsecureConnection(url_err)) - Err(InvalidDomain(url_err)) => Err(InvalidDomain(url_err)) - } - } - - error_message : [InsecureConnection(Str), InvalidDomain(Str), InvalidUserId(Str), UserNotFound(UserId), PageNotFound(Str)], [English, French] -> Str - error_message = |err, language| { - match language { - English => { - match err { - InsecureConnection(url) => "Insecure connection (non HTTPS): ${url}" - InvalidDomain(url) => "Invalid domain name: ${url}" - InvalidUserId(user_id_str) => "User ID is not a positive integer: ${user_id_str}" - UserNotFound(user_id) => "User #${user_id.to_str()} was not found" - PageNotFound(path) => "Page not found: ${path}" - } - } - - French => { - match err { - InsecureConnection(url) => "Connexion non sécurisée (non HTTPS): ${url}" - InvalidDomain(url) => "Ce nom de domaine est incorrect: ${url}" - InvalidUserId(user_id_str) => "Cet identifiant utilisateur devrait être un entier positif: ${user_id_str}" - UserNotFound(user_id) => "L'utilisateur #${user_id.to_str()} est inconnu" - PageNotFound(path) => "Cette page est inconnue: ${path}" - } - } - } - } -} - -User : { name : Str } - -UserId : U64 - -users : Dict(UserId, User) -users = - Dict.from_list( - [ - (123, { name: "Alice" }), - (456, { name: "Bob" }), - (789, { name: "Charlie" }), - ], - ) - -parse_path : Str -> Try(Str, [InvalidDomain(Str), InsecureConnection(Str)]) -parse_path = |url| { - prefix = "https://example.com" - if url.starts_with(prefix) { - Ok(Str.replace_each(url, prefix, "")) - } else if url.starts_with("https://") { - Err(InvalidDomain(url)) - } else { - Err(InsecureConnection(url)) - } -} From c48ca5eb77fb3250ff7106217425d554ad09957d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 22:41:49 +1200 Subject: [PATCH 116/162] Upgrade to latest roc version and fix a few minor issues --- exercises/practice/acronym/.meta/Example.roc | 2 +- .../practice/affine-cipher/.meta/Example.roc | 2 +- .../practice/affine-cipher/AffineCipher.roc | 65 +------------------ exercises/practice/connect/Connect.roc | 2 +- .../practice/hexadecimal/Hexadecimal.roc | 2 +- exercises/practice/knapsack/.meta/Example.roc | 3 +- exercises/practice/knapsack/Knapsack.roc | 3 +- .../LargestSeriesProduct.roc | 2 +- exercises/practice/octal/Octal.roc | 2 +- .../perfect-numbers/PerfectNumbers.roc | 2 +- .../practice/queen-attack/.meta/Example.roc | 4 +- .../square-root/.docs/instructions.append.md | 4 +- 12 files changed, 17 insertions(+), 76 deletions(-) diff --git a/exercises/practice/acronym/.meta/Example.roc b/exercises/practice/acronym/.meta/Example.roc index 4880325b..0cf187c9 100644 --- a/exercises/practice/acronym/.meta/Example.roc +++ b/exercises/practice/acronym/.meta/Example.roc @@ -3,7 +3,7 @@ Acronym :: {}.{ abbreviate = |text| { bytes = text.to_utf8() - { acronym } = bytes.fold( + { acronym, ready_for_letter: _ } = bytes.fold( { acronym: [], ready_for_letter: Bool.True }, |state, byte| { if state.ready_for_letter and is_letter(byte) { diff --git a/exercises/practice/affine-cipher/.meta/Example.roc b/exercises/practice/affine-cipher/.meta/Example.roc index 6b7886fa..3000f7c9 100644 --- a/exercises/practice/affine-cipher/.meta/Example.roc +++ b/exercises/practice/affine-cipher/.meta/Example.roc @@ -32,7 +32,7 @@ AffineCipher :: { a : U64, b : U64, encode_map : List(U8), decode_map : List(U8) |encoded, decoded_index| { encoded, decoded_index }, ) .sort_with( - |{ encoded: encoded1 }, { encoded: encoded2 }| { + |{ encoded: encoded1, decoded_index: _ }, { encoded: encoded2, decoded_index: _ }| { if encoded1 < encoded2 { LT } else if encoded1 > encoded2 { diff --git a/exercises/practice/affine-cipher/AffineCipher.roc b/exercises/practice/affine-cipher/AffineCipher.roc index eb06fe9b..6c5c6aea 100644 --- a/exercises/practice/affine-cipher/AffineCipher.roc +++ b/exercises/practice/affine-cipher/AffineCipher.roc @@ -10,74 +10,13 @@ AffineCipher :: { a : U64, b : U64 }.{ crash "Please implement the 'new' method" } - encode : Str, AffineCipher -> Try(Str, [BadUtf8(_)]) + encode : Str, AffineCipher -> Try(Str, _) encode = |affine_cipher, phrase| { crash "Please implement the 'encode' method" } - decode : Str, AffineCipher -> Try(Str, [BadUtf8(_)]) + decode : Str, AffineCipher -> Try(Str, _) decode = |affine_cipher, phrase| { crash "Please implement the 'decode' method" } } - -# The following functions should soon be available in Roc's builtins -chunks_of = |iter, size| { - var $state = [] - var $chunk = [] - for item in iter { - $chunk = $chunk.append(item) - if $chunk.len() == size { - $state = $state.append($chunk) - $chunk = [] - } - } - if $chunk.len() > 0 { - $state = $state.append($chunk) - } - $state -} - -collect = |iter| { - var $state = [] - for item in iter { - $state = $state.append(item) - } - $state -} - -intersperse = |list, sep| { - match list { - [] => [] - [_] => list - [first, .. as rest] => [first, sep].concat(intersperse(rest, sep)) - } -} - -join = |iter| { - var $state = [] - for sublist in iter { - for item in sublist { - $state = $state.append(item) - } - } - $state -} - -join_map = |iter, func| { - var $state = [] - for item in iter { - for subitem in func(item) { - $state = $state.append(subitem) - } - } - $state -} - -map_try = |iter, func| { - var $state = [] - for item in iter { - $state = $state.append(func(item)?) - } - Ok($state) -} diff --git a/exercises/practice/connect/Connect.roc b/exercises/practice/connect/Connect.roc index 9012fc19..15248f78 100644 --- a/exercises/practice/connect/Connect.roc +++ b/exercises/practice/connect/Connect.roc @@ -1,5 +1,5 @@ Connect :: {}.{ - winner : Str -> Try([PlayerO, PlayerX], _) + winner : Str -> Try([PlayerO, PlayerX], [NotFinished, ..]) winner = |board_str| { crash "Please implement the 'winner' function" } diff --git a/exercises/practice/hexadecimal/Hexadecimal.roc b/exercises/practice/hexadecimal/Hexadecimal.roc index f593b960..57958603 100644 --- a/exercises/practice/hexadecimal/Hexadecimal.roc +++ b/exercises/practice/hexadecimal/Hexadecimal.roc @@ -1,5 +1,5 @@ Hexadecimal :: {}.{ - parse : Str -> Try(U64, [InvalidNumStr]) + parse : Str -> Try(U64, _) parse = |string| { crash "Please implement the 'parse' function" } diff --git a/exercises/practice/knapsack/.meta/Example.roc b/exercises/practice/knapsack/.meta/Example.roc index eb4e7642..e8abb191 100644 --- a/exercises/practice/knapsack/.meta/Example.roc +++ b/exercises/practice/knapsack/.meta/Example.roc @@ -1,4 +1,6 @@ Knapsack :: {}.{ + Item : { weight : U64, value : U64 } + maximum_value : { items : List(Item), maximum_weight : U64 } -> U64 maximum_value = |{ items, maximum_weight }| { match items { @@ -16,4 +18,3 @@ Knapsack :: {}.{ } } -Item := { weight : U64, value : U64 } diff --git a/exercises/practice/knapsack/Knapsack.roc b/exercises/practice/knapsack/Knapsack.roc index c7b1d929..640bfd33 100644 --- a/exercises/practice/knapsack/Knapsack.roc +++ b/exercises/practice/knapsack/Knapsack.roc @@ -1,8 +1,9 @@ Knapsack :: {}.{ + Item : { weight : U64, value : U64 } + maximum_value : { items : List(Item), maximum_weight : U64 } -> U64 maximum_value = |{ items, maximum_weight }| { crash "Please implement the 'maximum_value' function" } } -Item := { weight : U64, value : U64 } diff --git a/exercises/practice/largest-series-product/LargestSeriesProduct.roc b/exercises/practice/largest-series-product/LargestSeriesProduct.roc index 313c7a4b..a77afb7e 100644 --- a/exercises/practice/largest-series-product/LargestSeriesProduct.roc +++ b/exercises/practice/largest-series-product/LargestSeriesProduct.roc @@ -1,5 +1,5 @@ LargestSeriesProduct :: {}.{ - largest_product : Str, U64 -> Try(U64, [SpanWasTooLarge, InvalidDigit]) + largest_product : Str, U64 -> Try(U64, _) largest_product = |digits, span| { crash "Please implement the 'largest_product' function" } diff --git a/exercises/practice/octal/Octal.roc b/exercises/practice/octal/Octal.roc index 1dafb992..b6e2eeb7 100644 --- a/exercises/practice/octal/Octal.roc +++ b/exercises/practice/octal/Octal.roc @@ -1,5 +1,5 @@ Octal :: {}.{ - parse : Str -> Try(U64, [InvalidNumStr]) + parse : Str -> Try(U64, _) parse = |string| { crash "Please implement the 'parse' function" } diff --git a/exercises/practice/perfect-numbers/PerfectNumbers.roc b/exercises/practice/perfect-numbers/PerfectNumbers.roc index 455378e7..eaee1196 100644 --- a/exercises/practice/perfect-numbers/PerfectNumbers.roc +++ b/exercises/practice/perfect-numbers/PerfectNumbers.roc @@ -1,5 +1,5 @@ PerfectNumbers :: {}.{ - classify : U64 -> Try([Abundant, Deficient, Perfect], [NumberArgIsZero]) + classify : U64 -> Try([Abundant, Deficient, Perfect], _) classify = |number| { crash "Please implement the 'classify' function" } diff --git a/exercises/practice/queen-attack/.meta/Example.roc b/exercises/practice/queen-attack/.meta/Example.roc index e0aef4c8..6dac58d0 100644 --- a/exercises/practice/queen-attack/.meta/Example.roc +++ b/exercises/practice/queen-attack/.meta/Example.roc @@ -1,10 +1,10 @@ QueenAttack :: {}.{ Square :: { row : U8, column : U8 }.{ rank : Square -> U8 - rank = |{ row }| row + 1 + rank = |{ row, column: _ }| row + 1 file : Square -> U8 - file = |{ column }| column + 'A' + file = |{ row: _, column }| column + 'A' create : Str -> Try(Square, [InvalidSquare]) create = |square_str| { diff --git a/exercises/practice/square-root/.docs/instructions.append.md b/exercises/practice/square-root/.docs/instructions.append.md index 750beed5..7008cfcd 100644 --- a/exercises/practice/square-root/.docs/instructions.append.md +++ b/exercises/practice/square-root/.docs/instructions.append.md @@ -2,11 +2,11 @@ ## Implementation -This problem can be solved easily using Roc's built-in `sqrt` function. +This problem can be solved easily using [Roc's built-in `sqrt` function](sqrt). However, we'd like you to consider the challenge of solving this exercise without using built-ins or modules. While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of having only [natural numbers][natural-number] (positive integers) as solutions. -[sqrt]: https://www.roc-lang.org/builtins/Num#sqrt +[sqrt]: https://www.roc-lang.org/builtins/main/#Builtin.Num.F64.sqrt [natural-number]: https://en.wikipedia.org/wiki/Natural_number From 61c2095124c459233272f00dc7dc5a5979c7f484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 23:14:21 +1200 Subject: [PATCH 117/162] Update exercise to new compiler: say --- exercises/practice/say/.meta/Example.roc | 163 ++++++++++++++--------- exercises/practice/say/Say.roc | 7 +- exercises/practice/say/say-test.roc | 2 +- 3 files changed, 105 insertions(+), 67 deletions(-) diff --git a/exercises/practice/say/.meta/Example.roc b/exercises/practice/say/.meta/Example.roc index 82ac9063..cb5923c8 100644 --- a/exercises/practice/say/.meta/Example.roc +++ b/exercises/practice/say/.meta/Example.roc @@ -1,71 +1,108 @@ Say :: {}.{ - say : U64 -> Result Str [OutOfBounds] - say = |number| - if number < 20 then - zero_to_nineteen |> List.get(number)? |> Ok - else if number < 100 then - tens_word = tens_after_ten |> List.get(number // 10 - 2)? - digit = number % 10 - if digit > 0 then - digit_word = say(digit)? - Ok("${tens_word}-${digit_word}") - else - Ok(tens_word) - else if number < 1_000_000_000_000 then - [ - (1_000_000_000_000, 1_000_000_000, "billion"), - (1_000_000_000, 1_000_000, "million"), - (1_000_000, 1000, "thousand"), - (1000, 100, "hundred"), - (100, 1, ""), - ] - |> List.keep_oks( - |(modulo, divisor, name)| - how_many = (number % modulo) // divisor - if how_many == 0 then - Err(NothingToSay) - else - say_how_many = say(how_many)? - Ok("${say_how_many} ${name}"), - ) - |> Str.join_with(" ") - |> Str.trim_end - |> Ok - else - Err(OutOfBounds) + say : U64 -> Try(Str, [OutOfBounds]) + say = |number| { + if number < 20 { + Ok(zero_to_nineteen.get(number)?) + } else if number < 100 { + tens_word = tens_after_ten.get(number // 10 - 2)? + digit = number % 10 + if digit > 0 { + digit_word = say(digit)? + Ok("${tens_word}-${digit_word}") + } else { + Ok(tens_word) + } + } else if number < 1_000_000_000_000 { + words = + [ + (1_000_000_000_000, 1_000_000_000, "billion"), + (1_000_000_000, 1_000_000, "million"), + (1_000_000, 1000, "thousand"), + (1000, 100, "hundred"), + (100, 1, ""), + ] + ->keep_oks( + |triple| { + (modulo, divisor, name) = triple + how_many = (number % modulo) // divisor + if how_many == 0 { + Err(NothingToSay) + } else { + say_how_many = say(how_many).map_err(|_| NothingToSay)? + Ok("${say_how_many} ${name}") + } + }, + ) + ->Str.join_with(" ") + .trim_end() + Ok(words) + } else { + Err(OutOfBounds) + } + } } - zero_to_nineteen = [ - "zero", - "one", - "two", - "three", - "four", - "five", - "six", - "seven", - "eight", - "nine", - "ten", - "eleven", - "twelve", - "thirteen", - "fourteen", - "fifteen", - "sixteen", - "seventeen", - "eighteen", - "nineteen", + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine", + "ten", + "eleven", + "twelve", + "thirteen", + "fourteen", + "fifteen", + "sixteen", + "seventeen", + "eighteen", + "nineteen", ] tens_after_ten = [ - "twenty", - "thirty", - "forty", - "fifty", - "sixty", - "seventy", - "eighty", - "ninety", + "twenty", + "thirty", + "forty", + "fifty", + "sixty", + "seventy", + "eighty", + "ninety", ] + +# The following functions should soon be available in Roc's builtins +keep_oks = |iter, func| { + iter + ->join_map( + |item| { + match func(item) { + Ok(result) => [result] + Err(_) => [] + } + }, + ) +} + +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} + +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/say/Say.roc b/exercises/practice/say/Say.roc index 95a3f96f..b4dfcd80 100644 --- a/exercises/practice/say/Say.roc +++ b/exercises/practice/say/Say.roc @@ -1,5 +1,6 @@ Say :: {}.{ - say : U64 -> Result Str [OutOfBounds] - say = |number| - crash("Please implement the 'say' function") + say : U64 -> Try(Str, _) + say = |number| { + crash "Please implement the 'say' function" + } } diff --git a/exercises/practice/say/say-test.roc b/exercises/practice/say/say-test.roc index 49e1303a..9c1dfaad 100644 --- a/exercises/practice/say/say-test.roc +++ b/exercises/practice/say/say-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/say/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-20 import Say exposing [say] From b2c8284af547ff89cc4ad562a71b35a70104c51d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 23:21:06 +1200 Subject: [PATCH 118/162] Update exercise to new compiler: word-search --- .../practice/word-search/.meta/Example.roc | 160 ++++++++++-------- exercises/practice/word-search/WordSearch.roc | 14 +- .../practice/word-search/word-search-test.roc | 2 +- 3 files changed, 102 insertions(+), 74 deletions(-) diff --git a/exercises/practice/word-search/.meta/Example.roc b/exercises/practice/word-search/.meta/Example.roc index d913dd80..cd6778d1 100644 --- a/exercises/practice/word-search/.meta/Example.roc +++ b/exercises/practice/word-search/.meta/Example.roc @@ -1,72 +1,100 @@ WordSearch :: {}.{ - search : Str, List Str -> Dict Str WordLocation - search = |grid, words_to_search_for| - { rows, width } = grid |> Str.to_utf8 |> List.split_on('\n') |> pad_right - height = List.len(rows) - height_i64 = height |> Num.to_i64 - width_i64 = width |> Num.to_i64 - max_length = Num.max(width, height) |> Num.to_i64 - # for all possible starting positions: - List.range({ start: At(0), end: Before(width) }) - |> List.join_map( - |column_index| - List.range({ start: At(0), end: Before(height) }) - |> List.join_map( - |row_index| - # for all possible directions: - [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] - |> List.join_map( - |(dir_column, dir_row)| - start = { column: column_index + 1, row: row_index + 1 } - # for all possible lengths: - List.range({ start: At(0), end: Before(max_length) }) # for all possible words starting at the given position and - # going in the given direction, take note of the start and end - # positions - |> List.walk_until( - { found_words: [], chars: [] }, - |state, index| - end_column_index = Num.to_i64(column_index) + dir_column * index - end_row_index = Num.to_i64(row_index) + dir_row * index - if end_column_index < 0 or end_column_index >= width_i64 or end_row_index < 0 or end_row_index >= height_i64 then - Break(state) - else - end_column_index_u64 = end_column_index |> Num.to_u64 - end_row_index_u64 = end_row_index |> Num.to_u64 - char = rows |> get_char(end_column_index_u64, end_row_index_u64) - new_chars = state.chars |> List.append(char) - end = { column: end_column_index_u64 + 1, row: end_row_index_u64 + 1 } - maybe_word = words_to_search_for |> List.find_first(|word| word |> Str.to_utf8 == new_chars) - new_found_words = - when maybe_word is - Ok(word) -> state.found_words |> List.append((word, { start, end })) - Err(NotFound) -> state.found_words - Continue({ found_words: new_found_words, chars: new_chars }), - ) - |> .found_words, - ), - ), - ) - |> Dict.from_list -} + Position : { column : U64, row : U64 } + WordLocation : { start : Position, end : Position } + search : Str, List(Str) -> Dict(Str, WordLocation) + search = |grid, words_to_search_for| { + { rows, width } = pad_right(grid.to_utf8().split_on('\n')) + height = rows.len() + height_i64 = height.to_i64_try() ?? 0 + width_i64 = width.to_i64_try() ?? 0 + max_length = ( + if width > height { + width + } else { + height + }, + ).to_i64_try() ?? 0 -Position : { column : U64, row : U64 } -WordLocation : { start : Position, end : Position } + # for all possible starting positions: + (0..join_map( + |column_index| { + (0..join_map( + |row_index| { + # for all possible directions: + [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] + ->join_map( + |dir| { + (dir_column, dir_row) = dir + start = { column: column_index + 1, row: row_index + 1 } + # for all possible lengths: + (0..List.from_iter() + .fold_until( + { found_words: [], chars: [] }, + |state, index| { + end_column_index = (column_index.to_i64_try() ?? 0) + dir_column * index + end_row_index = (row_index.to_i64_try() ?? 0) + dir_row * index + if end_column_index < 0 or end_column_index >= width_i64 or end_row_index < 0 or end_row_index >= height_i64 { + Break(state) + } else { + end_column_index_u64 = end_column_index.to_u64_try() ?? 0 + end_row_index_u64 = end_row_index.to_u64_try() ?? 0 + char = get_char(rows, end_column_index_u64, end_row_index_u64) + new_chars = state.chars.append(char) + end = { column: end_column_index_u64 + 1, row: end_row_index_u64 + 1 } + maybe_word = words_to_search_for.find_first(|word| word.to_utf8() == new_chars) + new_found_words = + match maybe_word { + Ok(word) => state.found_words.append((word, { start, end })) + Err(NotFound) => state.found_words + } + Continue({ found_words: new_found_words, chars: new_chars }) + } + }, + ) + .found_words + }, + ) + }, + ) + }, + ) + ->Dict.from_list() + } +} ## Pad each row with spaces to ensure all rows have the same width -pad_right : List (List U8) -> { rows : List (List U8), width : U64 } -pad_right = |grid| - width = grid |> List.map(List.len) |> List.max |> Result.with_default(0) - pad_spaces = |chars| List.repeat(' ', (width - List.len(chars))) - { - rows: grid |> List.map(|chars| chars |> List.concat(pad_spaces(chars))), - width, - } +pad_right : List(List(U8)) -> { rows : List(List(U8)), width : U64 } +pad_right = |grid| { + width = grid.map(List.len).fold( + 0, + |max_val, len| if len > max_val { + len + } else { + max_val + }, + ) + pad_spaces = |chars| List.repeat(' ', (width - chars.len())) + { + rows: grid.map(|chars| chars.concat(pad_spaces(chars))), + width, + } +} -get_char : List (List U8), U64, U64 -> U8 -get_char = |grid, column_index, row_index| - grid - |> List.get(row_index) - |> Result.with_default([]) - |> List.get(column_index) - |> Result.with_default(' ') +get_char : List(List(U8)), U64, U64 -> U8 +get_char = |grid, column_index, row_index| { + row = grid.get(row_index) ?? [] + row.get(column_index) ?? ' ' +} + +# The following function should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} diff --git a/exercises/practice/word-search/WordSearch.roc b/exercises/practice/word-search/WordSearch.roc index 6afb2e9a..70e70143 100644 --- a/exercises/practice/word-search/WordSearch.roc +++ b/exercises/practice/word-search/WordSearch.roc @@ -1,9 +1,9 @@ WordSearch :: {}.{ - search : Str, List Str -> Dict Str WordLocation - search = |grid, words_to_search_for| - crash("Please implement the 'search' function") -} - + Position : { column : U64, row : U64 } + WordLocation : { start : Position, end : Position } -Position : { column : U64, row : U64 } -WordLocation : { start : Position, end : Position } + search : Str, List(Str) -> Dict(Str, WordLocation) + search = |grid, words_to_search_for| { + crash "Please implement the 'search' function" + } +} diff --git a/exercises/practice/word-search/word-search-test.roc b/exercises/practice/word-search/word-search-test.roc index dbc66923..74cc8f95 100644 --- a/exercises/practice/word-search/word-search-test.roc +++ b/exercises/practice/word-search/word-search-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/word-search/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-20 import WordSearch exposing [search] From d173ab58f971c7bda3a2d12c2c0bc22ff8c9c07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sat, 20 Jun 2026 23:23:39 +1200 Subject: [PATCH 119/162] Update exercise to new compiler: ocr-numbers --- .../practice/ocr-numbers/.meta/Example.roc | 172 +++++++++++------- exercises/practice/ocr-numbers/OcrNumbers.roc | 7 +- 2 files changed, 106 insertions(+), 73 deletions(-) diff --git a/exercises/practice/ocr-numbers/.meta/Example.roc b/exercises/practice/ocr-numbers/.meta/Example.roc index 98b1f54a..7b51a707 100644 --- a/exercises/practice/ocr-numbers/.meta/Example.roc +++ b/exercises/practice/ocr-numbers/.meta/Example.roc @@ -1,78 +1,110 @@ OcrNumbers :: {}.{ - convert : Str -> Result Str BadGridSize - convert = |grid| - if grid == "" then - Ok("") - else - grid_chars = grid |> Str.to_utf8 |> List.split_on('\n') - size = check_size(grid_chars)? - grid_chars - |> List.chunks_of(4) # split vertically into groups of 4 rows - |> List.map( - |row_group| - get_digit_grids(row_group, size.width) - |> List.map(identify_digit) - |> Str.join_with(""), - ) - |> Str.join_with(",") - |> Ok + convert : Str -> Try(Str, BadGridSize) + convert = |grid| { + if grid == "" { + Ok("") + } else { + grid_chars = grid.to_utf8().split_on('\n') + size = check_size(grid_chars)? + digits_str = + grid_chars + ->chunks_of(4) + .map( + |row_group| { + get_digit_grids(row_group, size.width) + .map(identify_digit) + ->Str.join_with("") + }, + ) + ->Str.join_with(",") + Ok(digits_str) + } + } } - BadGridSize : [HeightWasNotAMultipleOf4, WidthWasNotAMultipleOf3, GridShapeWasNotRectangular] -check_size : List (List U8) -> Result { height : U64, width : U64 } BadGridSize -check_size = |grid_chars| - height = List.len(grid_chars) - if height % 4 != 0 then - Err(HeightWasNotAMultipleOf4) - else - width = grid_chars |> List.map(List.len) |> List.max |> Result.with_default(0) - if width % 3 != 0 then - Err(WidthWasNotAMultipleOf3) - else - is_rectangular = grid_chars |> List.all(|row| List.len(row) == width) - if is_rectangular then - Ok({ height, width }) - else - Err(GridShapeWasNotRectangular) +check_size : List(List(U8)) -> Try({ height : U64, width : U64 }, BadGridSize) +check_size = |grid_chars| { + height = grid_chars.len() + if height % 4 != 0 { + Err(HeightWasNotAMultipleOf4) + } else { + width = grid_chars.map(List.len).fold( + 0, + |max_val, len| if len > max_val { + len + } else { + max_val + }, + ) + if width % 3 != 0 { + Err(WidthWasNotAMultipleOf3) + } else { + is_rectangular = grid_chars.all(|row| row.len() == width) + if is_rectangular { + Ok({ height, width }) + } else { + Err(GridShapeWasNotRectangular) + } + } + } +} ## Given four rows from the full grid, return the 3x4 grid for each digit -get_digit_grids : List (List U8), U64 -> List (List (List U8)) -get_digit_grids = |row_group, full_grid_width| - chunked_rows = - row_group - |> List.map( - |row| - row |> List.chunks_of(3), - ) - num_horizontal_chunks = full_grid_width // 3 - List.range({ start: At(0), end: Before(num_horizontal_chunks) }) - |> List.map( - |chunk_index| - chunked_rows - |> List.map( - |chunked_row| - when chunked_row |> List.get(chunk_index) is - Ok(chunk) -> chunk - Err(OutOfBounds) -> crash("Unreachable: we checked the grid size"), - ), - ) +get_digit_grids : List(List(U8)), U64 -> List(List(List(U8))) +get_digit_grids = |row_group, full_grid_width| { + chunked_rows = row_group.map(|row| row->chunks_of(3)) + num_horizontal_chunks = full_grid_width // 3 + (0.. chunk + Err(OutOfBounds) => { + crash "Unreachable: we checked the grid size" + } + } + }, + ) + }, + ) + ->List.from_iter() +} -identify_digit : List (List U8) -> Str -identify_digit = |digit_grid| - # _ _ _ _ _ _ _ _ - # | | | _| _||_||_ |_ ||_||_| - # |_| ||_ _| | _||_| ||_| _| - when digit_grid is - [[' ', '_', ' '], ['|', ' ', '|'], ['|', '_', '|'], [' ', ' ', ' ']] -> "0" - [[' ', ' ', ' '], [' ', ' ', '|'], [' ', ' ', '|'], [' ', ' ', ' ']] -> "1" - [[' ', '_', ' '], [' ', '_', '|'], ['|', '_', ' '], [' ', ' ', ' ']] -> "2" - [[' ', '_', ' '], [' ', '_', '|'], [' ', '_', '|'], [' ', ' ', ' ']] -> "3" - [[' ', ' ', ' '], ['|', '_', '|'], [' ', ' ', '|'], [' ', ' ', ' ']] -> "4" - [[' ', '_', ' '], ['|', '_', ' '], [' ', '_', '|'], [' ', ' ', ' ']] -> "5" - [[' ', '_', ' '], ['|', '_', ' '], ['|', '_', '|'], [' ', ' ', ' ']] -> "6" - [[' ', '_', ' '], [' ', ' ', '|'], [' ', ' ', '|'], [' ', ' ', ' ']] -> "7" - [[' ', '_', ' '], ['|', '_', '|'], ['|', '_', '|'], [' ', ' ', ' ']] -> "8" - [[' ', '_', ' '], ['|', '_', '|'], [' ', '_', '|'], [' ', ' ', ' ']] -> "9" - _ -> "?" +identify_digit : List(List(U8)) -> Str +identify_digit = |digit_grid| { + match digit_grid { + [[' ', '_', ' '], ['|', ' ', '|'], ['|', '_', '|'], [' ', ' ', ' ']] => "0" + [[' ', ' ', ' '], [' ', ' ', '|'], [' ', ' ', '|'], [' ', ' ', ' ']] => "1" + [[' ', '_', ' '], [' ', '_', '|'], ['|', '_', ' '], [' ', ' ', ' ']] => "2" + [[' ', '_', ' '], [' ', '_', '|'], [' ', '_', '|'], [' ', ' ', ' ']] => "3" + [[' ', ' ', ' '], ['|', '_', '|'], [' ', ' ', '|'], [' ', ' ', ' ']] => "4" + [[' ', '_', ' '], ['|', '_', ' '], [' ', '_', '|'], [' ', ' ', ' ']] => "5" + [[' ', '_', ' '], ['|', '_', ' '], ['|', '_', '|'], [' ', ' ', ' ']] => "6" + [[' ', '_', ' '], [' ', ' ', '|'], [' ', ' ', '|'], [' ', ' ', ' ']] => "7" + [[' ', '_', ' '], ['|', '_', '|'], ['|', '_', '|'], [' ', ' ', ' ']] => "8" + [[' ', '_', ' '], ['|', '_', '|'], [' ', '_', '|'], [' ', ' ', ' ']] => "9" + _ => "?" + } +} + +# The following functions should soon be available in Roc's builtins +chunks_of = |iter, size| { + var $state = [] + var $chunk = [] + for item in iter { + $chunk = $chunk.append(item) + if $chunk.len() == size { + $state = $state.append($chunk) + $chunk = [] + } + } + if $chunk.len() > 0 { + $state = $state.append($chunk) + } + $state +} diff --git a/exercises/practice/ocr-numbers/OcrNumbers.roc b/exercises/practice/ocr-numbers/OcrNumbers.roc index bf4cdeff..0e5de8f9 100644 --- a/exercises/practice/ocr-numbers/OcrNumbers.roc +++ b/exercises/practice/ocr-numbers/OcrNumbers.roc @@ -1,5 +1,6 @@ OcrNumbers :: {}.{ - convert : Str -> Result Str _ - convert = |grid| - crash("Please implement the 'convert' function") + convert : Str -> Try(Str, _) + convert = |grid| { + crash "Please implement the 'convert' function" + } } From 1daeb1a330881b29bf3aa9574f94ea3df2a13f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sun, 21 Jun 2026 12:17:47 +1200 Subject: [PATCH 120/162] Update exercise to new compiler: two-bucket --- .../practice/two-bucket/.meta/Example.roc | 204 +++++++++++------- exercises/practice/two-bucket/TwoBucket.roc | 11 +- 2 files changed, 128 insertions(+), 87 deletions(-) diff --git a/exercises/practice/two-bucket/.meta/Example.roc b/exercises/practice/two-bucket/.meta/Example.roc index e0819200..f905bc58 100644 --- a/exercises/practice/two-bucket/.meta/Example.roc +++ b/exercises/practice/two-bucket/.meta/Example.roc @@ -1,89 +1,131 @@ TwoBucket :: {}.{ - measure : - { bucket_one : U64, bucket_two : U64, goal : U64, start_bucket : [One, Two] } - -> - Result { moves : U64, goal_bucket : [One, Two], other_bucket : U64 } [NoSolutionExists] - measure = |{ bucket_one, bucket_two, goal, start_bucket }| - if goal == 0 then - Ok({ moves: 0, goal_bucket: One, other_bucket: 0 }) - else - start = - when start_bucket is - One -> { volume_one: bucket_one, volume_two: 0 } - Two -> { volume_one: 0, volume_two: bucket_two } + measure : + { bucket_one : U64, bucket_two : U64, goal : U64, start_bucket : [One, Two] } -> Try({ moves : U64, goal_bucket : [One, Two], other_bucket : U64 }, [NoSolutionExists]) + measure = |{ bucket_one, bucket_two, goal, start_bucket }| { + if goal == 0 { + Ok({ moves: 0, goal_bucket: One, other_bucket: 0 }) + } else { + start = + match start_bucket { + One => { volume_one: bucket_one, volume_two: 0 } + Two => { volume_one: 0, volume_two: bucket_two } + } - neighbors = |{ volume_one, volume_two }| - volume_one_to_two = Num.min(volume_one, (bucket_two - volume_two)) - volume_two_to_one = Num.min(volume_two, (bucket_one - volume_one)) - [ - { volume_one: 0, volume_two }, # empty bucket one - { volume_one, volume_two: 0 }, # empty bucket two - { volume_one: bucket_one, volume_two }, # fill bucket one - { volume_one, volume_two: bucket_two }, # fill bucket two - { - # pour bucket one into bucket two - volume_one: volume_one - volume_one_to_two, - volume_two: volume_two + volume_one_to_two, - }, - { - # pour bucket two into bucket one - volume_one: volume_one + volume_two_to_one, - volume_two: volume_two - volume_two_to_one, - }, - ] - |> List.drop_if( - |{ volume_one: v1, volume_two: v2 }| - (v1 == volume_one and v2 == volume_two) # no change - or - # forbidden move: cannot end up with the starting bucket empty and - # the other bucket full - when start_bucket is - One -> v1 == 0 and v2 == bucket_two - Two -> v1 == bucket_one and v2 == 0, - ) + neighbors = |{ volume_one, volume_two }| { + volume_one_to_two = if volume_one < (bucket_two - volume_two) { + volume_one + } else { + bucket_two - volume_two + } + volume_two_to_one = if volume_two < (bucket_one - volume_one) { + volume_two + } else { + bucket_one - volume_one + } + [ + { volume_one: 0, volume_two }, # empty bucket one + { volume_one, volume_two: 0 }, # empty bucket two + { volume_one: bucket_one, volume_two }, # fill bucket one + { volume_one, volume_two: bucket_two }, # fill bucket two + { + # pour bucket one into bucket two + volume_one: volume_one - volume_one_to_two, + volume_two: volume_two + volume_one_to_two, + }, + { + # pour bucket two into bucket one + volume_one: volume_one + volume_two_to_one, + volume_two: volume_two - volume_two_to_one, + }, + ] + .drop_if( + |{ volume_one: v1, volume_two: v2 }| { + (v1 == volume_one and v2 == volume_two) # no change + or + # forbidden move: cannot end up with the starting bucket empty and + # the other bucket full + match start_bucket { + One => v1 == 0 and v2 == bucket_two + Two => v1 == bucket_one and v2 == 0 + } + }, + ) + } - success = |{ volume_one, volume_two }| volume_one == goal or volume_two == goal + success = |{ volume_one, volume_two }| volume_one == goal or volume_two == goal - when bfs({ start, neighbors, success }) is - Ok([.. as rest, last]) -> - Ok( - { - moves: List.len(rest) + 1, - goal_bucket: if last.volume_one == goal then One else Two, - other_bucket: if last.volume_one == goal then last.volume_two else last.volume_one, - }, - ) + match bfs({ start, neighbors, success }) { + Ok(path) => { + match path { + [.. as rest, last] => { + Ok( + { + moves: rest.len() + 1, + goal_bucket: if last.volume_one == goal { + One + } else { + Two + }, + other_bucket: if last.volume_one == goal { + last.volume_two + } else { + last.volume_one + }, + }, + ) + } + _ => Err(NoSolutionExists) + } + } - _ -> Err(NoSolutionExists) + _ => Err(NoSolutionExists) + } + } + } } +bfs = |{ start, neighbors, success }| { + help = |to_visit, visited, from| { + match to_visit { + [] => Err(NoPathExists) + [node, .. as rest_to_visit] => { + if visited.contains(node) { + help(rest_to_visit, visited, from) + } else if success(node) { + path_back_to_start = |path, step| { + updated_path = path.append(step) + match from.get(to_str(step)) { + Ok(previous) => path_back_to_start(updated_path, previous) + Err(KeyNotFound) => updated_path + } + } + Ok(reverse(path_back_to_start([], node))) + } else { + neighbor_nodes = neighbors(node) + updated_from = + neighbor_nodes + .drop_if(|neighbor| visited.contains(neighbor)) + .fold(from, |acc_from, neighbor| acc_from.insert(to_str(neighbor), node)) + updated_visited = visited.insert(node) + updated_to_visit = rest_to_visit.concat(neighbor_nodes) + help(updated_to_visit, updated_visited, updated_from) + } + } + } + } + help([start], Set.empty(), Dict.empty()) +} + +# TODO: remove to_str once records have a to_hash method +to_str = |{ volume_one, volume_two }| { + volume_one.to_str().concat(volume_two.to_str()) +} -## Breadth-First Search finds the shortest path from the `start` node to any successful -## node (i.e., such that `success node == Bool.True`). The `neighbors` function must -## return the list of direct neighbors of a given node. -bfs = |{ start, neighbors, success }| - help = |to_visit, visited, from| - when to_visit is - [] -> Err(NoPathExists) - [node, .. as rest_to_visit] -> - if visited |> Set.contains(node) then - help(rest_to_visit, visited, from) - else if success(node) then - path_back_to_start = |path, step| - updated_path = path |> List.append(step) - when from |> Dict.get(step) is - Ok(previous) -> path_back_to_start(updated_path, previous) - Err(KeyNotFound) -> updated_path - path_back_to_start([], node) |> List.reverse |> Ok - else - neighbor_nodes = neighbors(node) - new_from = - neighbor_nodes - |> List.drop_if(|neighbor| visited |> Set.contains(neighbor)) - |> List.map(|neighbor| (neighbor, node)) - |> Dict.from_list - updated_from = from |> Dict.insert_all(new_from) - updated_visited = visited |> Set.insert(node) - updated_to_visit = rest_to_visit |> List.concat(neighbor_nodes) - help(updated_to_visit, updated_visited, updated_from) - help([start], Set.empty({}), Dict.empty({})) +# The following function should soon be available in Roc's builtins +reverse : List(a) -> List(a) +reverse = |list| { + match list { + [] => [] + [first, .. as rest] => reverse(rest).append(first) + } +} diff --git a/exercises/practice/two-bucket/TwoBucket.roc b/exercises/practice/two-bucket/TwoBucket.roc index edb80dd0..e62d222a 100644 --- a/exercises/practice/two-bucket/TwoBucket.roc +++ b/exercises/practice/two-bucket/TwoBucket.roc @@ -1,8 +1,7 @@ TwoBucket :: {}.{ - measure : - { bucket_one : U64, bucket_two : U64, goal : U64, start_bucket : [One, Two] } - -> - Result { moves : U64, goal_bucket : [One, Two], other_bucket : U64 } _ - measure = |{ bucket_one, bucket_two, goal, start_bucket }| - crash("Please implement the 'measure' function") + measure : + { bucket_one : U64, bucket_two : U64, goal : U64, start_bucket : [One, Two] } -> Try({ moves : U64, goal_bucket : [One, Two], other_bucket : U64 }, _) + measure = |{ bucket_one, bucket_two, goal, start_bucket }| { + crash "Please implement the 'measure' function" + } } From a40b753bfefcfeaca5993ef02f4a350d5943293e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 00:22:39 +1200 Subject: [PATCH 121/162] Update exercise to new compiler: simple-linked-list --- .../simple-linked-list/.meta/Example.roc | 76 +++-- .../simple-linked-list/.meta/template.j2 | 81 +++++ .../simple-linked-list/.meta/tests.toml | 103 ++++++ .../simple-linked-list-test.roc | 318 +++++++++++++++--- .../simple-linked-list-test2.roc | 89 +++++ 5 files changed, 584 insertions(+), 83 deletions(-) create mode 100644 exercises/practice/simple-linked-list/.meta/template.j2 create mode 100644 exercises/practice/simple-linked-list/.meta/tests.toml create mode 100644 exercises/practice/simple-linked-list/simple-linked-list-test2.roc diff --git a/exercises/practice/simple-linked-list/.meta/Example.roc b/exercises/practice/simple-linked-list/.meta/Example.roc index 9be6c4d5..c6f90af9 100644 --- a/exercises/practice/simple-linked-list/.meta/Example.roc +++ b/exercises/practice/simple-linked-list/.meta/Example.roc @@ -1,38 +1,50 @@ -SimpleLinkedList :: {}.{ - from_list : List U64 -> SimpleLinkedList - from_list = |list| - list |> List.walk(Nil, push) +SimpleLinkedList :: [Nil, Cons(U64, SimpleLinkedList)].{ + from_list : List(U64) -> SimpleLinkedList + from_list = |list| list.fold(Nil, push) - to_list : SimpleLinkedList -> List U64 - to_list = |linked_list| - when linked_list is - Nil -> [] - Cons(head, tail) -> tail |> to_list |> List.append(head) + to_list : SimpleLinkedList -> List(U64) + to_list = |linked_list| { + match linked_list { + Nil => [] + Cons(head, tail) => tail.to_list().append(head) + } + } - push : SimpleLinkedList, U64 -> SimpleLinkedList - push = |linked_list, item| - Cons(item, linked_list) + push : SimpleLinkedList, U64 -> SimpleLinkedList + push = |linked_list, item| Cons(item, linked_list) - pop : SimpleLinkedList -> Result { value : U64, linked_list : SimpleLinkedList } [LinkedListWasEmpty] - pop = |linked_list| - when linked_list is - Nil -> Err(LinkedListWasEmpty) - Cons(head, tail) -> Ok({ value: head, linked_list: tail }) + pop : SimpleLinkedList -> Try({ value : U64, updated_list : SimpleLinkedList }, [LinkedListWasEmpty]) + pop = |linked_list| { + match linked_list { + Nil => Err(LinkedListWasEmpty) + Cons(head, tail) => Ok({ value: head, updated_list: tail }) + } + } - reverse : SimpleLinkedList -> SimpleLinkedList - reverse = |linked_list| - help = |result, rest| - when rest is - Nil -> result - Cons(head, tail) -> help((result |> push(head)), tail) - help(Nil, linked_list) - - len : SimpleLinkedList -> U64 - len = |linked_list| - when linked_list is - Nil -> 0 - Cons(_, tail) -> 1 + len(tail) -} + peek : SimpleLinkedList -> Try(U64, [LinkedListWasEmpty]) + peek = |linked_list| { + match linked_list { + Nil => Err(LinkedListWasEmpty) + Cons(head, _) => Ok(head) + } + } + reverse : SimpleLinkedList -> SimpleLinkedList + reverse = |linked_list| { + help = |result, rest| { + match rest { + Nil => result + Cons(head, tail) => help((result->push(head)), tail) + } + } + help(Nil, linked_list) + } -SimpleLinkedList : [Nil, Cons U64 SimpleLinkedList] + len : SimpleLinkedList -> U64 + len = |linked_list| { + match linked_list { + Nil => 0 + Cons(_, tail) => 1 + tail.len() + } + } +} diff --git a/exercises/practice/simple-linked-list/.meta/template.j2 b/exercises/practice/simple-linked-list/.meta/template.j2 new file mode 100644 index 00000000..aeb54851 --- /dev/null +++ b/exercises/practice/simple-linked-list/.meta/template.j2 @@ -0,0 +1,81 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} +{{ macros.header() }} + +import {{ exercise | to_pascal }} + +{% for supercase in cases if supercase["description"] != "toList LIFO" %} +## +## {{ supercase["description"] }} +## + +{% for case in supercase["cases"] -%} +# {{ case["description"] }} +expect { + result = + SimpleLinkedList.from_list({{ case["input"]["initialValues"] | to_roc }}) + {%- for op in case["input"]["operations"] %} + {%- if op["operation"] == "push" %} + .push({{ op["value"] }}) + {%- elif op["operation"] == "pop" %} + .pop() + {%- if not op["expected"] is mapping %}? + {%- if loop.last %} + .value + {%- else %} + ->expect_value({{ op["expected"] | to_roc }})? + {%- endif %} + {%- endif %} + {%- elif op["operation"] == "peek" %} + {%- if loop.last %} + .peek(){%- if not op["expected"] is mapping %}?{%- endif %} + {%- else %} + ->expect_peek({{ op["expected"] | to_roc }})? + {%- endif %} + {%- elif op["operation"] == "count" %} + {%- if loop.last %} + .len() + {%- else %} + ->expect_len({{ op["expected"] | to_roc }})? + {%- endif %} + {%- elif op["operation"] == "toList" %} + .to_list() + {%- else %} + .{{ op["operation"] }}({{ op["value"] }}) + {%- endif %} + + {%- if loop.last %} + {%- if op["expected"] is mapping %} + + result.is_err() + {%- else %} + + result == {{ op["expected"] | to_roc }} + {%- endif %} + {%- endif %} + {%- endfor %} +} + +{% endfor %} +{% endfor %} + +expect_value : { updated_list: SimpleLinkedList, value: U64 }, U64 -> Try(SimpleLinkedList, [UnexpectedValue(U64)]) +expect_value = |{ updated_list, value }, expected_value| { + if value == expected_value Ok(updated_list) else Err(UnexpectedValue(value)) +} + +expect_len : SimpleLinkedList, U64 -> Try(SimpleLinkedList, [UnexpectedLen(U64)]) +expect_len = |list, expected_len| { + actual_len = list.len() + if actual_len == expected_len Ok(list) else Err(UnexpectedLen(actual_len)) +} + +expect_peek : SimpleLinkedList, U64 -> Try(SimpleLinkedList, [UnexpectedPeek(U64), LinkedListWasEmpty]) +expect_peek = |list, expected_peek| { + match list.peek() { + Ok(actual_peek) => if actual_peek == expected_peek Ok(list) else Err(UnexpectedPeek(actual_peek)) + Err(LinkedListWasEmpty) => Err(LinkedListWasEmpty) + } +} + +{{ macros.footer() }} diff --git a/exercises/practice/simple-linked-list/.meta/tests.toml b/exercises/practice/simple-linked-list/.meta/tests.toml new file mode 100644 index 00000000..736247f2 --- /dev/null +++ b/exercises/practice/simple-linked-list/.meta/tests.toml @@ -0,0 +1,103 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[962d998c-c203-41e2-8fbd-85a7b98b79b9] +description = "count -> Empty list has length of zero" + +[9760262e-d7e4-4639-9840-87e2e2fbb115] +description = "count -> Singleton list has length of one" + +[d9955c90-637c-441b-b41d-8cfb48e924a8] +description = "count -> Non-empty list has correct length" + +[0c3966db-58f9-4632-b94c-8ea13e54c2c8] +description = "pop -> Pop from empty list is an error" + +[a4f9d2e1-7425-49ef-9ee8-6c0cb3407cf0] +description = "pop -> Can pop from singleton list" + +[6dcbb2c9-d98a-47bc-a010-9c19703d3ea2] +description = "pop -> Can pop from non-empty list" + +[e83aade9-f030-4096-aaf0-f9dc6491e6cf] +description = "pop -> Can pop multiple items" + +[5c46bcf2-c0a9-4654-ae17-f3192436fcf1] +description = "pop -> Pop updates the count" + +[70d747a1-2e84-4ebc-bc3f-dcbee6a05f6b] +description = "push -> Can push to an empty list" +include = false + +[f3197f0a-1fea-45a5-939f-4a5ea60387ec] +description = "push -> Can push to an empty list" +reimplements = "70d747a1-2e84-4ebc-bc3f-dcbee6a05f6b" +include = false + +[391e332e-1f91-4033-b1e0-0e0c17812fa7] +description = "push -> Can push to a non-empty list" +include = false + +[ed4b0e01-3bbd-4895-af25-152b5914b3da] +description = "push -> Push updates count" + +[41666790-b932-4e5a-b323-e848a83d12d5] +description = "push -> Push and pop" + +[930a4a5c-76f6-47ec-9be3-4e70993173a1] +description = "peek -> Peek on empty list is an error" + +[43255a50-d919-4e81-afce-e4a271eaedbd] +description = "peek -> Can peek on singleton list" + +[48353020-e25d-4621-a854-e35fb1e15fa7] +description = "peek -> Can peek on non-empty list" + +[96fcead9-a713-46c2-8005-3f246c873851] +description = "peek -> Peek does not change the count" + +[7576ed05-7ff7-4b84-8efb-d34d62c110f5] +description = "peek -> Can peek after a pop and push" + +[b97d00b6-2fab-435d-ae74-3233dcc13698] +description = "toList LIFO -> Empty linked list to list is empty" + +[eedeb95f-b5cf-431d-8ad6-5854ba6b251c] +description = "toList LIFO -> To list with multiple values" + +[838678de-eaf3-4c14-b34e-7e35b6d851e8] +description = "toList LIFO -> To list after a pop" + +[03fc83a5-48a8-470b-a2d2-a286c5e8365f] +description = "toList FIFO -> Empty linked list to list is empty" + +[1282484e-a58c-426a-972e-90746bda61fc] +description = "toList FIFO -> To list with multiple values" + +[05ca3109-1249-4c0c-a567-a3b2f8352a7c] +description = "toList FIFO -> To list after a pop" + +[5e6c1a3d-e34b-46d3-be59-3f132a820ed5] +description = "reverse -> Reversed empty list has same values" + +[93c87ed3-862a-474f-820b-ba3fd6b6daf6] +description = "reverse -> Reversed singleton list is same list" + +[92851ebe-9f52-4406-b92e-0718c441a2ab] +description = "reverse -> Reversed non-empty list is reversed" +include = false + +[1210eeda-b23f-4790-930c-7ac6d0c8e723] +description = "reverse -> Reversed non-empty list is reversed" +reimplements = "92851ebe-9f52-4406-b92e-0718c441a2ab" + +[9b53af96-7494-4cfa-9b77-b7366fed5c4c] +description = "reverse -> Double reverse" diff --git a/exercises/practice/simple-linked-list/simple-linked-list-test.roc b/exercises/practice/simple-linked-list/simple-linked-list-test.roc index 9d8c4a29..a03a2b65 100644 --- a/exercises/practice/simple-linked-list/simple-linked-list-test.roc +++ b/exercises/practice/simple-linked-list/simple-linked-list-test.roc @@ -1,89 +1,305 @@ -# File last updated on 2025-1-4 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/simple-linked-list/canonical-data.json +# File last updated on 2026-06-21 + +import SimpleLinkedList + +## +## count +## + +# Empty list has length of zero +expect { + result = + SimpleLinkedList.from_list([]) + .len() + + result == 0 } -import pf.Stdout +# Singleton list has length of one +expect { + result = + SimpleLinkedList.from_list([1]) + .len() -main! = |_args| - Stdout.line!("") + result == 1 +} -import SimpleLinkedList exposing [from_list, to_list, push, pop, reverse, len] +# Non-empty list has correct length +expect { + result = + SimpleLinkedList.from_list([1, 2, 3]) + .len() -# can create an empty linked list + result == 3 +} + +## +## pop +## + +# Pop from empty list is an error +expect { + result = + SimpleLinkedList.from_list([]) + .pop() + + result.is_err() +} + +# Can pop from singleton list expect { - result = []->from_list()->to_list() - expected = [] - result == expected + result = + SimpleLinkedList.from_list([1]) + .pop()? + .value + + result == 1 } -# can create a linked list with a single element +# Can pop from non-empty list expect { - result = [123]->from_list()->to_list() - expected = [123] - result == expected + result = + SimpleLinkedList.from_list([1, 2]) + .pop()? + .value + + result == 2 } -# can create a linked list with multiple elements +# Can pop multiple items expect { - result = [123, 456, 789]->from_list()->to_list() - expected = [123, 456, 789] - result == expected + result = + SimpleLinkedList.from_list([1, 2]) + .pop()? + ->expect_value(2)? + .pop()? + .value + + result == 1 } -# can push items to a linked list +# Pop updates the count expect { - result = [123]->from_list()->push(456)->push(789)->to_list() - expected = [123, 456, 789] - result == expected + result1 = + SimpleLinkedList.from_list([1, 2]) + ->expect_len(2)? + .pop()? + ->expect_value(2)? + result2 = # TODO: remove this workaround when the roc bug is fixed. + result1 + ->expect_len(1)? + .pop()? + ->expect_value(1)? + .len() + + result2 == 0 } -# can pop an item from a linked list +## +## push +## + +# Push updates count expect { - pop_result = [123, 456, 789]->from_list()->pop() - result = pop_result->Result.try(|popped| Ok(popped.value)) - expected = Ok(789) - result == expected + result = + SimpleLinkedList.from_list([1, 2]) + .push(3) + .len() + + result == 3 } -# the last element should be gone after pop +# Push and pop expect { - pop_result = [123, 456, 789]->from_list()->pop() - result = pop_result->Result.try(|popped| Ok((popped.linked_list->to_list()))) - expected = Ok([123, 456]) - result == expected + result = + SimpleLinkedList.from_list([]) + .push(1) + .push(2) + .pop()? + ->expect_value(2)? + .push(3) + ->expect_len(2)? + .pop()? + ->expect_value(3)? + .pop()? + ->expect_value(1)? + .len() + + result == 0 } -# cannot pop an empty linked list +## +## peek +## + +# Peek on empty list is an error expect { - result = []->from_list()->pop() + result = + SimpleLinkedList.from_list([]) + .peek() + result.is_err() } -# can reverse a linked list +# Can peek on singleton list expect { - result = [123, 456, 789]->from_list()->reverse()->to_list() - expected = [789, 456, 123] - result == expected + result = + SimpleLinkedList.from_list([1]) + .peek()? + + result == 1 } -# can reverse an empty linked list and it's still empty +# Can peek on non-empty list expect { - result = []->from_list()->reverse()->to_list() - expected = [] - result == expected + result = + SimpleLinkedList.from_list([1, 2]) + .peek()? + + result == 2 +} + +# Peek does not change the count +expect { + result = + SimpleLinkedList.from_list([1, 2]) + ->expect_peek(2)? + .len() + + result == 2 } -# can get the length of a linked list +# Can peek after a pop and push expect { - result = [123, 456, 789]->from_list()->len() - expected = 3 - result == expected + result = + SimpleLinkedList.from_list([]) + .push(1) + .push(2) + ->expect_peek(2)? + .pop()? + ->expect_value(2)? + ->expect_peek(1)? + .push(3) + .peek()? + + result == 3 } -# can get the length of an empty linked list +## +## toList FIFO +## + +# Empty linked list to list is empty expect { - result = []->from_list()->len() - expected = 0 - result == expected + result = + SimpleLinkedList.from_list([]) + .to_list() + + result == [] +} + +# To list with multiple values +expect { + result = + SimpleLinkedList.from_list([1, 2, 3]) + .to_list() + + result == [1, 2, 3] +} + +# To list after a pop +expect { + result = + SimpleLinkedList.from_list([]) + .push(1) + .push(2) + .push(3) + .pop()? + ->expect_value(3)? + .push(4) + .to_list() + + result == [1, 2, 4] +} + +## +## reverse +## + +# Reversed empty list has same values +expect { + result = + SimpleLinkedList.from_list([]) + .reverse() + .to_list() + + result == [] +} + +# Reversed singleton list is same list +expect { + result = + SimpleLinkedList.from_list([1]) + .reverse() + .to_list() + + result == [1] +} + +# Reversed non-empty list is reversed +expect { + result = + SimpleLinkedList.from_list([1, 2, 3]) + .reverse() + ->expect_len(3)? + .pop()? + ->expect_value(1)? + .pop()? + ->expect_value(2)? + .pop()? + .value + + result == 3 +} + +# Double reverse +expect { + result = + SimpleLinkedList.from_list([1, 2, 3]) + .reverse() + .reverse() + .pop()? + ->expect_value(3)? + .pop()? + ->expect_value(2)? + .pop()? + .value + + result == 1 +} + +expect_value : { updated_list : SimpleLinkedList, value : U64 }, U64 -> Try(SimpleLinkedList, [UnexpectedValue(U64)]) +expect_value = |{ updated_list, value }, expected_value| { + if value == expected_value Ok(updated_list) else Err(UnexpectedValue(value)) +} + +expect_len : SimpleLinkedList, U64 -> Try(SimpleLinkedList, [UnexpectedLen(U64)]) +expect_len = |list, expected_len| { + actual_len = list.len() + if actual_len == expected_len Ok(list) else Err(UnexpectedLen(actual_len)) +} + +expect_peek : SimpleLinkedList, U64 -> Try(SimpleLinkedList, [UnexpectedPeek(U64), LinkedListWasEmpty]) +expect_peek = |list, expected_peek| { + match list.peek() { + Ok(actual_peek) => if actual_peek == expected_peek Ok(list) else Err(UnexpectedPeek(actual_peek)) + Err(LinkedListWasEmpty) => Err(LinkedListWasEmpty) + } +} + +# This program is only used to run tests with `roc test`, so main! does nothing. +main! = |_args| { + Ok({}) } diff --git a/exercises/practice/simple-linked-list/simple-linked-list-test2.roc b/exercises/practice/simple-linked-list/simple-linked-list-test2.roc new file mode 100644 index 00000000..9d8c4a29 --- /dev/null +++ b/exercises/practice/simple-linked-list/simple-linked-list-test2.roc @@ -0,0 +1,89 @@ +# File last updated on 2025-1-4 +app [main!] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", +} + +import pf.Stdout + +main! = |_args| + Stdout.line!("") + +import SimpleLinkedList exposing [from_list, to_list, push, pop, reverse, len] + +# can create an empty linked list +expect { + result = []->from_list()->to_list() + expected = [] + result == expected +} + +# can create a linked list with a single element +expect { + result = [123]->from_list()->to_list() + expected = [123] + result == expected +} + +# can create a linked list with multiple elements +expect { + result = [123, 456, 789]->from_list()->to_list() + expected = [123, 456, 789] + result == expected +} + +# can push items to a linked list +expect { + result = [123]->from_list()->push(456)->push(789)->to_list() + expected = [123, 456, 789] + result == expected +} + +# can pop an item from a linked list +expect { + pop_result = [123, 456, 789]->from_list()->pop() + result = pop_result->Result.try(|popped| Ok(popped.value)) + expected = Ok(789) + result == expected +} + +# the last element should be gone after pop +expect { + pop_result = [123, 456, 789]->from_list()->pop() + result = pop_result->Result.try(|popped| Ok((popped.linked_list->to_list()))) + expected = Ok([123, 456]) + result == expected +} + +# cannot pop an empty linked list +expect { + result = []->from_list()->pop() + result.is_err() +} + +# can reverse a linked list +expect { + result = [123, 456, 789]->from_list()->reverse()->to_list() + expected = [789, 456, 123] + result == expected +} + +# can reverse an empty linked list and it's still empty +expect { + result = []->from_list()->reverse()->to_list() + expected = [] + result == expected +} + +# can get the length of a linked list +expect { + result = [123, 456, 789]->from_list()->len() + expected = 3 + result == expected +} + +# can get the length of an empty linked list +expect { + result = []->from_list()->len() + expected = 0 + result == expected +} From bde2f9c5c55bda89f775be015b1a0e012b73759c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 00:26:20 +1200 Subject: [PATCH 122/162] Update the exercise stub for simple-linked-list --- .../simple-linked-list/SimpleLinkedList.roc | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/exercises/practice/simple-linked-list/SimpleLinkedList.roc b/exercises/practice/simple-linked-list/SimpleLinkedList.roc index 16afb388..bd440b02 100644 --- a/exercises/practice/simple-linked-list/SimpleLinkedList.roc +++ b/exercises/practice/simple-linked-list/SimpleLinkedList.roc @@ -1,34 +1,36 @@ -SimpleLinkedList :: {}.{ - from_list : List U64 -> SimpleLinkedList - from_list = |list| - crash("Please implement the 'from_list' function") +SimpleLinkedList :: [Nil, Cons(U64, SimpleLinkedList)].{ + from_list : List(U64) -> SimpleLinkedList + from_list = |list| { + crash "Please implement the 'from_list' function" + } - to_list : SimpleLinkedList -> List U64 - to_list = |linked_list| - crash("Please implement the 'to_list' function") + to_list : SimpleLinkedList -> List(U64) + to_list = |linked_list| { + crash "Please implement the 'to_list' function" + } - push : SimpleLinkedList, U64 -> SimpleLinkedList - push = |linked_list, item| - crash("Please implement the 'push' function") + push : SimpleLinkedList, U64 -> SimpleLinkedList + push = |linked_list, item| { + crash "Please implement the 'push' function" + } - pop : SimpleLinkedList -> Result { value : U64, linked_list : SimpleLinkedList } _ - pop = |linked_list| - crash("Please implement the 'pop' function") + pop : SimpleLinkedList -> Try({ value : U64, updated_list : SimpleLinkedList }, [LinkedListWasEmpty]) + pop = |linked_list| { + crash "Please implement the 'pop' function" + } - reverse : SimpleLinkedList -> SimpleLinkedList - reverse = |linked_list| - crash("Please implement the 'reverse' function") - - len : SimpleLinkedList -> U64 - len = |linked_list| - crash("Please implement the 'len' function") -} + peek : SimpleLinkedList -> Try(U64, [LinkedListWasEmpty]) + peek = |linked_list| { + crash "Please implement the 'peek' function" + } + reverse : SimpleLinkedList -> SimpleLinkedList + reverse = |linked_list| { + crash "Please implement the 'reverse' function" + } -SimpleLinkedList : { - # TODO: change this type however you need - todo : U64, - todo2 : U64, - todo3 : U64, - # etc. + len : SimpleLinkedList -> U64 + len = |linked_list| { + crash "Please implement the 'len' function" + } } From 0e7604f8dc5512a74a37b4a75480a4a0a17d00ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 00:41:04 +1200 Subject: [PATCH 123/162] Remove old test file for simple-linked-list --- .../simple-linked-list-test2.roc | 89 ------------------- 1 file changed, 89 deletions(-) delete mode 100644 exercises/practice/simple-linked-list/simple-linked-list-test2.roc diff --git a/exercises/practice/simple-linked-list/simple-linked-list-test2.roc b/exercises/practice/simple-linked-list/simple-linked-list-test2.roc deleted file mode 100644 index 9d8c4a29..00000000 --- a/exercises/practice/simple-linked-list/simple-linked-list-test2.roc +++ /dev/null @@ -1,89 +0,0 @@ -# File last updated on 2025-1-4 -app [main!] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br", -} - -import pf.Stdout - -main! = |_args| - Stdout.line!("") - -import SimpleLinkedList exposing [from_list, to_list, push, pop, reverse, len] - -# can create an empty linked list -expect { - result = []->from_list()->to_list() - expected = [] - result == expected -} - -# can create a linked list with a single element -expect { - result = [123]->from_list()->to_list() - expected = [123] - result == expected -} - -# can create a linked list with multiple elements -expect { - result = [123, 456, 789]->from_list()->to_list() - expected = [123, 456, 789] - result == expected -} - -# can push items to a linked list -expect { - result = [123]->from_list()->push(456)->push(789)->to_list() - expected = [123, 456, 789] - result == expected -} - -# can pop an item from a linked list -expect { - pop_result = [123, 456, 789]->from_list()->pop() - result = pop_result->Result.try(|popped| Ok(popped.value)) - expected = Ok(789) - result == expected -} - -# the last element should be gone after pop -expect { - pop_result = [123, 456, 789]->from_list()->pop() - result = pop_result->Result.try(|popped| Ok((popped.linked_list->to_list()))) - expected = Ok([123, 456]) - result == expected -} - -# cannot pop an empty linked list -expect { - result = []->from_list()->pop() - result.is_err() -} - -# can reverse a linked list -expect { - result = [123, 456, 789]->from_list()->reverse()->to_list() - expected = [789, 456, 123] - result == expected -} - -# can reverse an empty linked list and it's still empty -expect { - result = []->from_list()->reverse()->to_list() - expected = [] - result == expected -} - -# can get the length of a linked list -expect { - result = [123, 456, 789]->from_list()->len() - expected = 3 - result == expected -} - -# can get the length of an empty linked list -expect { - result = []->from_list()->len() - expected = 0 - result == expected -} From 443744ad412da8e271db30f9c919bcf6d3258473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 08:52:14 +1200 Subject: [PATCH 124/162] List.rev is now available --- .../practice/accumulate/.meta/template.j2 | 11 +----- .../practice/accumulate/accumulate-test.roc | 13 +------ .../practice/alphametics/.meta/Example.roc | 39 ++++++++----------- .../practice/alphametics/alphametics-test.roc | 2 +- exercises/practice/house/.meta/Example.roc | 11 +----- .../killer-sudoku-helper/.meta/Example.roc | 13 +------ exercises/practice/luhn/.meta/Example.roc | 11 +----- .../palindrome-products/.meta/Example.roc | 15 +------ .../secret-handshake/.meta/Example.roc | 10 +---- .../practice/two-bucket/.meta/Example.roc | 11 +----- .../.meta/Example.roc | 10 +---- 11 files changed, 28 insertions(+), 118 deletions(-) diff --git a/exercises/practice/accumulate/.meta/template.j2 b/exercises/practice/accumulate/.meta/template.j2 index 47015ff6..e26e848e 100644 --- a/exercises/practice/accumulate/.meta/template.j2 +++ b/exercises/practice/accumulate/.meta/template.j2 @@ -39,18 +39,9 @@ str_reverse : Str -> Str str_reverse = |str| { str .to_utf8() - ->reverse() + .rev() ->Str.from_utf8() ?? "" } -# List.reverse should soon be available in Roc's builtins -reverse : List(a) -> List(a) -reverse = |list| { - match list { - [] => [] - [first, .. as rest] => reverse(rest).append(first) - } -} - {{ macros.footer() }} \ No newline at end of file diff --git a/exercises/practice/accumulate/accumulate-test.roc b/exercises/practice/accumulate/accumulate-test.roc index 6baf595a..83637502 100644 --- a/exercises/practice/accumulate/accumulate-test.roc +++ b/exercises/practice/accumulate/accumulate-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/accumulate/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 import Accumulate exposing [accumulate] @@ -72,20 +72,11 @@ str_reverse : Str -> Str str_reverse = |str| { str .to_utf8() - ->reverse() + .rev() ->Str.from_utf8() ?? "" } -# List.reverse should soon be available in Roc's builtins -reverse : List(a) -> List(a) -reverse = |list| { - match list { - [] => [] - [first, .. as rest] => reverse(rest).append(first) - } -} - # This program is only used to run tests with `roc test`, so main! does nothing. main! = |_args| { Ok({}) diff --git a/exercises/practice/alphametics/.meta/Example.roc b/exercises/practice/alphametics/.meta/Example.roc index e6f50761..3bd97a3e 100644 --- a/exercises/practice/alphametics/.meta/Example.roc +++ b/exercises/practice/alphametics/.meta/Example.roc @@ -17,16 +17,17 @@ Alphametics :: {}.{ } leading_digits : Set(U8) - leading_digits = + leading_digits = { addends.map( |letters| { letters.first() ?? 0 }, ) - ->Set.from_list() - .insert( - sum.first() ?? 0, - ) + ->Set.from_list() + .insert( + sum.first() ?? 0, + ) + } find_match : List((U8, U8)), List(U8), Set(U8) -> Try(List((U8, U8)), [InvalidAssignment, ..]) find_match = |assignments, remaining_vars, remaining_digits| { @@ -86,7 +87,7 @@ find_first_ok = |set, func| { insert_term : Dict(U8, I64), List(U8), I64 -> Dict(U8, I64) insert_term = |equation, letters, polarity| { letters - ->list_reverse() + .rev() .fold_with_index( equation, |dict, letter, index| { @@ -120,6 +121,15 @@ parse = |problem| { Ok({ addends, sum: after.to_utf8() }) } +reverse : Str -> Str +reverse = |str| { + str + .to_utf8() + .rev() + ->Str.from_utf8() + ?? "" +} + # The following function should soon be available in Roc's builtins split_first : Str, Str -> Try({ before : Str, after : Str }, [InvalidAssignment, ..]) split_first = |str, sep| { @@ -130,23 +140,6 @@ split_first = |str, sep| { } } -list_reverse : List(a) -> List(a) -list_reverse = |list| { - match list { - [] => [] - [first, .. as rest] => list_reverse(rest).append(first) - } -} - -reverse : Str -> Str -reverse = |str| { - str - .to_utf8() - ->list_reverse() - ->Str.from_utf8() - ?? "" -} - pow_int : I64, U64 -> I64 pow_int = |number, pow| { (1..=pow).fold( diff --git a/exercises/practice/alphametics/alphametics-test.roc b/exercises/practice/alphametics/alphametics-test.roc index dcd059f5..7dd3cbcc 100644 --- a/exercises/practice/alphametics/alphametics-test.roc +++ b/exercises/practice/alphametics/alphametics-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/alphametics/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 import Alphametics exposing [solve] diff --git a/exercises/practice/house/.meta/Example.roc b/exercises/practice/house/.meta/Example.roc index c69cde14..bce1cfc0 100644 --- a/exercises/practice/house/.meta/Example.roc +++ b/exercises/practice/house/.meta/Example.roc @@ -27,16 +27,7 @@ verse = |index| { blablabla = segments .take_first(index) - ->reverse() + .rev() ->Str.join_with(" the ") "This is the ${blablabla}" } - -# List.reverse should soon be available in Roc's builtins -reverse : List(a) -> List(a) -reverse = |list| { - match list { - [] => [] - [first, .. as rest] => reverse(rest).append(first) - } -} diff --git a/exercises/practice/killer-sudoku-helper/.meta/Example.roc b/exercises/practice/killer-sudoku-helper/.meta/Example.roc index 4a6dfc13..82465b4e 100644 --- a/exercises/practice/killer-sudoku-helper/.meta/Example.roc +++ b/exercises/practice/killer-sudoku-helper/.meta/Example.roc @@ -41,17 +41,6 @@ KillerSudokuHelper :: {}.{ .keep_if( |combi| combi.len() == size.to_u64(), ) - .map( - reverse, - ) - } -} - -# List.reverse should soon be available in Roc's builtins -reverse : List(a) -> List(a) -reverse = |list| { - match list { - [] => [] - [first, .. as rest] => reverse(rest).append(first) + .map(List.rev) } } diff --git a/exercises/practice/luhn/.meta/Example.roc b/exercises/practice/luhn/.meta/Example.roc index fb5c8f5d..9f6edfd3 100644 --- a/exercises/practice/luhn/.meta/Example.roc +++ b/exercises/practice/luhn/.meta/Example.roc @@ -53,14 +53,5 @@ map_every_other_backwards = |list, func| { [] => state } } - help([], list)->reverse() -} - -# List.reverse should soon be available in Roc's builtins -reverse : List(a) -> List(a) -reverse = |list| { - match list { - [] => [] - [first, .. as rest] => reverse(rest).append(first) - } + help([], list).rev() } diff --git a/exercises/practice/palindrome-products/.meta/Example.roc b/exercises/practice/palindrome-products/.meta/Example.roc index ab2c4577..e1819863 100644 --- a/exercises/practice/palindrome-products/.meta/Example.roc +++ b/exercises/practice/palindrome-products/.meta/Example.roc @@ -32,7 +32,7 @@ PalindromeProducts :: {}.{ } } - help(min, min, max_u64, []) + help(min, min, U64.highest, []) } } @@ -77,16 +77,5 @@ PalindromeProducts :: {}.{ is_palindrome : U64 -> Bool is_palindrome = |number| { digits = number.to_str().to_utf8() - digits == reverse(digits) -} - -# The following will soon be available in Roc's builtins -max_u64 = 18_446_744_073_709_551_615 - -reverse : List(a) -> List(a) -reverse = |list| { - match list { - [] => [] - [first, .. as rest] => reverse(rest).append(first) - } + digits == digits.rev() } diff --git a/exercises/practice/secret-handshake/.meta/Example.roc b/exercises/practice/secret-handshake/.meta/Example.roc index dd0a26f8..a081b395 100644 --- a/exercises/practice/secret-handshake/.meta/Example.roc +++ b/exercises/practice/secret-handshake/.meta/Example.roc @@ -16,7 +16,7 @@ SecretHandshake :: {}.{ if U64.bitwise_and(number, 16) == 0 { actions } else { - actions->reverse() + actions.rev() } } } @@ -31,11 +31,3 @@ join_map = |iter, func| { } $state } - -reverse : List(a) -> List(a) -reverse = |list| { - match list { - [] => [] - [first, .. as rest] => reverse(rest).append(first) - } -} diff --git a/exercises/practice/two-bucket/.meta/Example.roc b/exercises/practice/two-bucket/.meta/Example.roc index f905bc58..779e7ff5 100644 --- a/exercises/practice/two-bucket/.meta/Example.roc +++ b/exercises/practice/two-bucket/.meta/Example.roc @@ -99,7 +99,7 @@ bfs = |{ start, neighbors, success }| { Err(KeyNotFound) => updated_path } } - Ok(reverse(path_back_to_start([], node))) + Ok(path_back_to_start([], node).rev()) } else { neighbor_nodes = neighbors(node) updated_from = @@ -120,12 +120,3 @@ bfs = |{ start, neighbors, success }| { to_str = |{ volume_one, volume_two }| { volume_one.to_str().concat(volume_two.to_str()) } - -# The following function should soon be available in Roc's builtins -reverse : List(a) -> List(a) -reverse = |list| { - match list { - [] => [] - [first, .. as rest] => reverse(rest).append(first) - } -} diff --git a/exercises/practice/variable-length-quantity/.meta/Example.roc b/exercises/practice/variable-length-quantity/.meta/Example.roc index 73f59d6d..78f60c0e 100644 --- a/exercises/practice/variable-length-quantity/.meta/Example.roc +++ b/exercises/practice/variable-length-quantity/.meta/Example.roc @@ -49,7 +49,7 @@ encode_integer = |integer| { if integer == 0 { [0] } else { - help([], integer)->reverse() + help([], integer).rev() } } @@ -63,11 +63,3 @@ join_map = |iter, func| { } $state } - -reverse : List(a) -> List(a) -reverse = |list| { - match list { - [] => [] - [first, .. as rest] => reverse(rest).append(first) - } -} From e3b5f1b870dcafd822233bc1a091dbf069f45b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 08:56:03 +1200 Subject: [PATCH 125/162] Avoid using ?? when it can silently fail --- .../practice/alphametics/.meta/template.j2 | 4 +-- .../practice/alphametics/alphametics-test.roc | 32 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/exercises/practice/alphametics/.meta/template.j2 b/exercises/practice/alphametics/.meta/template.j2 index b9613a9d..d35ad15b 100644 --- a/exercises/practice/alphametics/.meta/template.j2 +++ b/exercises/practice/alphametics/.meta/template.j2 @@ -8,8 +8,8 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } # {{ case["description"] }} expect { {%- if case["expected"] %} - result = {{ case["property"] | to_snake }}({{ case["input"]["puzzle"] | to_roc }}) - Set.from_list(result ?? []) == Set.from_list([ + result = {{ case["property"] | to_snake }}({{ case["input"]["puzzle"] | to_roc }})? + Set.from_list(result) == Set.from_list([ {%- for letter, value in case["expected"].items() %} ('{{ letter }}', {{ value }}), {%- endfor %} diff --git a/exercises/practice/alphametics/alphametics-test.roc b/exercises/practice/alphametics/alphametics-test.roc index 7dd3cbcc..8c6b7f14 100644 --- a/exercises/practice/alphametics/alphametics-test.roc +++ b/exercises/practice/alphametics/alphametics-test.roc @@ -6,8 +6,8 @@ import Alphametics exposing [solve] # puzzle with three letters expect { - result = solve("I + BB == ILL") - Set.from_list(result ?? []) == Set.from_list( + result = solve("I + BB == ILL")? + Set.from_list(result) == Set.from_list( [ ('I', 1), ('B', 9), @@ -30,8 +30,8 @@ expect { # puzzle with two digits final carry expect { - result = solve("A + A + A + A + A + A + A + A + A + A + A + B == BCC") - Set.from_list(result ?? []) == Set.from_list( + result = solve("A + A + A + A + A + A + A + A + A + A + A + B == BCC")? + Set.from_list(result) == Set.from_list( [ ('A', 9), ('B', 1), @@ -42,8 +42,8 @@ expect { # puzzle with four letters expect { - result = solve("AS + A == MOM") - Set.from_list(result ?? []) == Set.from_list( + result = solve("AS + A == MOM")? + Set.from_list(result) == Set.from_list( [ ('A', 9), ('S', 2), @@ -55,8 +55,8 @@ expect { # puzzle with six letters expect { - result = solve("NO + NO + TOO == LATE") - Set.from_list(result ?? []) == Set.from_list( + result = solve("NO + NO + TOO == LATE")? + Set.from_list(result) == Set.from_list( [ ('N', 7), ('O', 4), @@ -70,8 +70,8 @@ expect { # puzzle with seven letters expect { - result = solve("HE + SEES + THE == LIGHT") - Set.from_list(result ?? []) == Set.from_list( + result = solve("HE + SEES + THE == LIGHT")? + Set.from_list(result) == Set.from_list( [ ('E', 4), ('G', 2), @@ -86,8 +86,8 @@ expect { # puzzle with eight letters expect { - result = solve("SEND + MORE == MONEY") - Set.from_list(result ?? []) == Set.from_list( + result = solve("SEND + MORE == MONEY")? + Set.from_list(result) == Set.from_list( [ ('S', 9), ('E', 5), @@ -103,8 +103,8 @@ expect { # puzzle with ten letters expect { - result = solve("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE") - Set.from_list(result ?? []) == Set.from_list( + result = solve("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE")? + Set.from_list(result) == Set.from_list( [ ('A', 5), ('D', 3), @@ -122,8 +122,8 @@ expect { # puzzle with ten letters and 199 addends expect { - result = solve("THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES") - Set.from_list(result ?? []) == Set.from_list( + result = solve("THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES")? + Set.from_list(result) == Set.from_list( [ ('A', 1), ('E', 0), From 77fcf8726030d6fd5f697fd6186adb1390edf855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 10:18:37 +1200 Subject: [PATCH 126/162] Replace external library URL with TODO --- exercises/practice/anagram/anagram-test.roc | 6 +++--- .../practice/gigasecond/gigasecond-test.roc | 6 +++--- exercises/practice/meetup/meetup-test.roc | 6 +++--- .../practice/micro-blog/micro-blog-test.roc | 6 +++--- exercises/practice/rest-api/rest-api-test.roc | 16 +++++----------- .../reverse-string/reverse-string-test.roc | 6 +++--- .../practice/sgf-parsing/sgf-parsing-test.roc | 6 +++--- 7 files changed, 23 insertions(+), 29 deletions(-) diff --git a/exercises/practice/anagram/anagram-test.roc b/exercises/practice/anagram/anagram-test.roc index 06873c45..73a3d991 100644 --- a/exercises/practice/anagram/anagram-test.roc +++ b/exercises/practice/anagram/anagram-test.roc @@ -1,9 +1,9 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", - unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + unicode: "https://github.com/roc-lang/unicode/...", # TODO: update when a zig-compatible release is available } import pf.Stdout diff --git a/exercises/practice/gigasecond/gigasecond-test.roc b/exercises/practice/gigasecond/gigasecond-test.roc index 8bd74088..4ce28978 100644 --- a/exercises/practice/gigasecond/gigasecond-test.roc +++ b/exercises/practice/gigasecond/gigasecond-test.roc @@ -1,9 +1,9 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/gigasecond/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", - isodate: "https://github.com/imclerran/roc-isodate/releases/download/v0.6.2/73w_H-aSJNcWqtXvMG4JQw_HoaApMBLnE92XD4OcVGU.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + isodate: "https://github.com/imclerran/roc-isodate/...", # TODO: update when a zig-compatible release is available } import pf.Stdout diff --git a/exercises/practice/meetup/meetup-test.roc b/exercises/practice/meetup/meetup-test.roc index 99689d6d..8c091cce 100644 --- a/exercises/practice/meetup/meetup-test.roc +++ b/exercises/practice/meetup/meetup-test.roc @@ -1,9 +1,9 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/meetup/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", - isodate: "https://github.com/imclerran/roc-isodate/releases/download/v0.6.2/73w_H-aSJNcWqtXvMG4JQw_HoaApMBLnE92XD4OcVGU.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + isodate: "https://github.com/imclerran/roc-isodate/...", # TODO: update when a zig-compatible release is available } import pf.Stdout diff --git a/exercises/practice/micro-blog/micro-blog-test.roc b/exercises/practice/micro-blog/micro-blog-test.roc index c23c11e0..3172b662 100644 --- a/exercises/practice/micro-blog/micro-blog-test.roc +++ b/exercises/practice/micro-blog/micro-blog-test.roc @@ -1,9 +1,9 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/micro-blog/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", - unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + unicode: "https://github.com/roc-lang/unicode/...", # TODO: update when a zig-compatible release is available } import pf.Stdout diff --git a/exercises/practice/rest-api/rest-api-test.roc b/exercises/practice/rest-api/rest-api-test.roc index b9148767..826c4454 100644 --- a/exercises/practice/rest-api/rest-api-test.roc +++ b/exercises/practice/rest-api/rest-api-test.roc @@ -1,9 +1,9 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rest-api/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", - json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.13.0/RqendgZw5e1RsQa3kFhgtnMP8efWoqGRsAvubx4-zus.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + json: "https://github.com/lukewilliamboswell/roc-json/...", # TODO: update when a zig-compatible release is available } import pf.Stdout @@ -15,14 +15,8 @@ standardize_result = |result| { ->Result.try( |string| { string - .replace_each( - ".0,", - ",", - ) - .replace_each( - ".0}", - "}", - ) + .replace_each(".0,", ",") + .replace_each(".0}", "}") .to_utf8() .drop_if( |c| { diff --git a/exercises/practice/reverse-string/reverse-string-test.roc b/exercises/practice/reverse-string/reverse-string-test.roc index de65424f..8ace6964 100644 --- a/exercises/practice/reverse-string/reverse-string-test.roc +++ b/exercises/practice/reverse-string/reverse-string-test.roc @@ -1,9 +1,9 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/reverse-string/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", - unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + unicode: "https://github.com/roc-lang/unicode/...", # TODO: update when a zig-compatible release is available } import pf.Stdout diff --git a/exercises/practice/sgf-parsing/sgf-parsing-test.roc b/exercises/practice/sgf-parsing/sgf-parsing-test.roc index 00edea7c..2835836b 100644 --- a/exercises/practice/sgf-parsing/sgf-parsing-test.roc +++ b/exercises/practice/sgf-parsing/sgf-parsing-test.roc @@ -1,9 +1,9 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sgf-parsing/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst", - parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.10.0/6eZYaXkrakq9fJ4oUc0VfdxU1Fap2iTuAN18q9OgQss.tar.br", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + parser: "https://github.com/lukewilliamboswell/roc-parser/...", # TODO: update when a zig-compatible release is available } import pf.Stdout From 6ec49aa727f5d60e6205b596db92a0085c2dec70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 10:21:21 +1200 Subject: [PATCH 127/162] Simplify the game_with_previous_rolls function by using vars --- exercises/practice/bowling/.meta/template.j2 | 29 ++++++------------ exercises/practice/bowling/bowling-test.roc | 32 ++++++-------------- 2 files changed, 19 insertions(+), 42 deletions(-) diff --git a/exercises/practice/bowling/.meta/template.j2 b/exercises/practice/bowling/.meta/template.j2 index 6844fbec..89d5fd93 100644 --- a/exercises/practice/bowling/.meta/template.j2 +++ b/exercises/practice/bowling/.meta/template.j2 @@ -4,26 +4,6 @@ import {{ exercise | to_pascal }} -game_with_previous_rolls : List(U64) -> Try(Bowling, _) -game_with_previous_rolls = |rolls| { - new_game = {{ exercise | to_pascal }}.new - rolls.fold_until( - Ok(new_game), - |state, pins| { - match state { - Ok(game) => { - match game.roll(pins) { - Ok(updated_game) => Continue(Ok(updated_game)) - Err(err) => Break(Err(err)) - } - } - Err(_) => { crash "Impossible, we don't start or continue with an Err" } - } - } - ) -} - - {% for case in cases -%} # {{ case["description"] }} expect { @@ -54,4 +34,13 @@ expect { {% endfor %} +game_with_previous_rolls : List(U64) -> Try(Bowling, _) +game_with_previous_rolls = |rolls| { + var $game = Bowling.new + for pins in rolls { + $game = $game.roll(pins)? + } + Ok($game) +} + {{ macros.footer() }} diff --git a/exercises/practice/bowling/bowling-test.roc b/exercises/practice/bowling/bowling-test.roc index 7a1744d9..2733c827 100644 --- a/exercises/practice/bowling/bowling-test.roc +++ b/exercises/practice/bowling/bowling-test.roc @@ -1,30 +1,9 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bowling/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 import Bowling -game_with_previous_rolls : List(U64) -> Try(Bowling, _) -game_with_previous_rolls = |rolls| { - new_game = Bowling.new - rolls.fold_until( - Ok(new_game), - |state, pins| { - match state { - Ok(game) => { - match game.roll(pins) { - Ok(updated_game) => Continue(Ok(updated_game)) - Err(err) => Break(Err(err)) - } - } - Err(_) => { - crash "Impossible, we don't start or continue with an Err" - } - } - }, - ) -} - # should be able to score a game with all zeros expect { rolls = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] @@ -377,6 +356,15 @@ expect { result.is_err() } +game_with_previous_rolls : List(U64) -> Try(Bowling, _) +game_with_previous_rolls = |rolls| { + var $game = Bowling.new + for pins in rolls { + $game = $game.roll(pins)? + } + Ok($game) +} + # This program is only used to run tests with `roc test`, so main! does nothing. main! = |_args| { Ok({}) From 8f4f3a85fff75e288c400ab7adb29d4eb0c72628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 10:33:53 +1200 Subject: [PATCH 128/162] Replace [1,2,3]->find(2) with find([1,2,3], 2) to reduce surprise for newcomers --- .../practice/binary-search/.meta/template.j2 | 2 +- .../binary-search/binary-search-test.roc | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/exercises/practice/binary-search/.meta/template.j2 b/exercises/practice/binary-search/.meta/template.j2 index 63a128e7..5ddc957c 100644 --- a/exercises/practice/binary-search/.meta/template.j2 +++ b/exercises/practice/binary-search/.meta/template.j2 @@ -7,7 +7,7 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } {% for case in cases -%} # {{ case["description"] }} expect { - result = {{ case["input"]["array"] | to_roc }} -> {{ case["property"] | to_snake }}({{ case["input"]["value"] }}) + result = {{ case["property"] | to_snake }}({{ case["input"]["array"] | to_roc }}, {{ case["input"]["value"] }}) {%- if case["expected"]["error"] %} result.is_err() {%- else %} diff --git a/exercises/practice/binary-search/binary-search-test.roc b/exercises/practice/binary-search/binary-search-test.roc index 3a5231f1..72c54377 100644 --- a/exercises/practice/binary-search/binary-search-test.roc +++ b/exercises/practice/binary-search/binary-search-test.roc @@ -1,72 +1,72 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 import BinarySearch exposing [find] # finds a value in an array with one element expect { - result = [6]->find(6) + result = find([6], 6) result == Ok(0) } # finds a value in the middle of an array expect { - result = [1, 3, 4, 6, 8, 9, 11]->find(6) + result = find([1, 3, 4, 6, 8, 9, 11], 6) result == Ok(3) } # finds a value at the beginning of an array expect { - result = [1, 3, 4, 6, 8, 9, 11]->find(1) + result = find([1, 3, 4, 6, 8, 9, 11], 1) result == Ok(0) } # finds a value at the end of an array expect { - result = [1, 3, 4, 6, 8, 9, 11]->find(11) + result = find([1, 3, 4, 6, 8, 9, 11], 11) result == Ok(6) } # finds a value in an array of odd length expect { - result = [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634]->find(144) + result = find([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634], 144) result == Ok(9) } # finds a value in an array of even length expect { - result = [1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]->find(21) + result = find([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377], 21) result == Ok(5) } # identifies that a value is not included in the array expect { - result = [1, 3, 4, 6, 8, 9, 11]->find(7) + result = find([1, 3, 4, 6, 8, 9, 11], 7) result.is_err() } # a value smaller than the array's smallest value is not found expect { - result = [1, 3, 4, 6, 8, 9, 11]->find(0) + result = find([1, 3, 4, 6, 8, 9, 11], 0) result.is_err() } # a value larger than the array's largest value is not found expect { - result = [1, 3, 4, 6, 8, 9, 11]->find(13) + result = find([1, 3, 4, 6, 8, 9, 11], 13) result.is_err() } # nothing is found in an empty array expect { - result = []->find(1) + result = find([], 1) result.is_err() } # nothing is found when the left and right bounds cross expect { - result = [1, 2]->find(0) + result = find([1, 2], 0) result.is_err() } From 4e687a0f9fac6cedbc265ab6c9f91836c5231995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 10:35:37 +1200 Subject: [PATCH 129/162] Replace coins->find_fewest_coins(1) with find_fewest_coins(coins, 1) to reduce newcomer surprise --- exercises/practice/change/.meta/template.j2 | 2 +- exercises/practice/change/change-test.roc | 26 ++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/exercises/practice/change/.meta/template.j2 b/exercises/practice/change/.meta/template.j2 index 54a272af..fef56d1c 100644 --- a/exercises/practice/change/.meta/template.j2 +++ b/exercises/practice/change/.meta/template.j2 @@ -8,7 +8,7 @@ import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_snake } # {{ case["description"] }} expect { coins = {{ case["input"]["coins"] | to_roc }} - result = coins -> {{ case["property"] | to_snake }}({{ case["input"]["target"] }}) + result = {{ case["property"] | to_snake }}(coins, {{ case["input"]["target"] }}) {%- if case["expected"]["error"] %} result.is_err() {%- else %} diff --git a/exercises/practice/change/change-test.roc b/exercises/practice/change/change-test.roc index 0bbdea25..535d321e 100644 --- a/exercises/practice/change/change-test.roc +++ b/exercises/practice/change/change-test.roc @@ -1,90 +1,90 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 import Change exposing [find_fewest_coins] # change for 1 cent expect { coins = [1, 5, 10, 25] - result = coins->find_fewest_coins(1) + result = find_fewest_coins(coins, 1) result == Ok([1]) } # single coin change expect { coins = [1, 5, 10, 25, 100] - result = coins->find_fewest_coins(25) + result = find_fewest_coins(coins, 25) result == Ok([25]) } # multiple coin change expect { coins = [1, 5, 10, 25, 100] - result = coins->find_fewest_coins(15) + result = find_fewest_coins(coins, 15) result == Ok([5, 10]) } # change with Lilliputian Coins expect { coins = [1, 4, 15, 20, 50] - result = coins->find_fewest_coins(23) + result = find_fewest_coins(coins, 23) result == Ok([4, 4, 15]) } # change with Lower Elbonia Coins expect { coins = [1, 5, 10, 21, 25] - result = coins->find_fewest_coins(63) + result = find_fewest_coins(coins, 63) result == Ok([21, 21, 21]) } # large target values expect { coins = [1, 2, 5, 10, 20, 50, 100] - result = coins->find_fewest_coins(999) + result = find_fewest_coins(coins, 999) result == Ok([2, 2, 5, 20, 20, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100]) } # possible change without unit coins available expect { coins = [2, 5, 10, 20, 50] - result = coins->find_fewest_coins(21) + result = find_fewest_coins(coins, 21) result == Ok([2, 2, 2, 5, 10]) } # another possible change without unit coins available expect { coins = [4, 5] - result = coins->find_fewest_coins(27) + result = find_fewest_coins(coins, 27) result == Ok([4, 4, 4, 5, 5, 5]) } # a greedy approach is not optimal expect { coins = [1, 10, 11] - result = coins->find_fewest_coins(20) + result = find_fewest_coins(coins, 20) result == Ok([10, 10]) } # no coins make 0 change expect { coins = [1, 5, 10, 21, 25] - result = coins->find_fewest_coins(0) + result = find_fewest_coins(coins, 0) result == Ok([]) } # error testing for change smaller than the smallest of coins expect { coins = [5, 10] - result = coins->find_fewest_coins(3) + result = find_fewest_coins(coins, 3) result.is_err() } # error if no combination can add up to target expect { coins = [5, 10] - result = coins->find_fewest_coins(94) + result = find_fewest_coins(coins, 94) result.is_err() } From 91adcde2b668f9d44ed5f3d210752b85fcb17d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 10:46:05 +1200 Subject: [PATCH 130/162] Update to cli platform 0.8, and replace external libraries with TODO comments --- config/generator_macros.j2 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/generator_macros.j2 b/config/generator_macros.j2 index c0c1fa5c..f0eb95f1 100644 --- a/config/generator_macros.j2 +++ b/config/generator_macros.j2 @@ -19,16 +19,16 @@ {%- if imports -%} app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.7/DuRUyJh31Gt41YArMcVcvybLa2bCWboccWQ7Zq1KZPZ6.tar.zst" + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst" {%- for name in imports -%}, {% if name == "unicode" -%} - unicode: "https://github.com/roc-lang/unicode/releases/download/0.3.0/9KKFsA4CdOz0JIOL7iBSI_2jGIXQ6TsFBXgd086idpY.tar.br" + unicode: "https://github.com/roc-lang/unicode/..." # TODO: update when a zig-compatible release is available {%- elif name == "isodate" -%} - isodate: "https://github.com/imclerran/roc-isodate/releases/download/v0.6.2/73w_H-aSJNcWqtXvMG4JQw_HoaApMBLnE92XD4OcVGU.tar.br" + isodate: "https://github.com/imclerran/roc-isodate/..." # TODO: update when a zig-compatible release is available {%- elif name == "json" -%} - json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.13.0/RqendgZw5e1RsQa3kFhgtnMP8efWoqGRsAvubx4-zus.tar.br" + json: "https://github.com/lukewilliamboswell/roc-json/..." # TODO: update when a zig-compatible release is available {%- elif name == "parser" -%} - parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.10.0/6eZYaXkrakq9fJ4oUc0VfdxU1Fap2iTuAN18q9OgQss.tar.br" + parser: "https://github.com/lukewilliamboswell/roc-parser/..." # TODO: update when a zig-compatible release is available {%- endif -%} {%- endfor %} } From c3ab56a1b904bd6730f610fc817965c749c62e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 11:22:56 +1200 Subject: [PATCH 131/162] Fix complex-numbers --- .../complex-numbers/.meta/Example.roc | 68 +++++++++---------- .../complex-numbers/.meta/template.j2 | 4 +- .../complex-numbers/ComplexNumbers.roc | 3 +- .../complex-numbers/complex-numbers-test.roc | 12 ++-- 4 files changed, 42 insertions(+), 45 deletions(-) diff --git a/exercises/practice/complex-numbers/.meta/Example.roc b/exercises/practice/complex-numbers/.meta/Example.roc index 59ca4da5..f499c670 100644 --- a/exercises/practice/complex-numbers/.meta/Example.roc +++ b/exercises/practice/complex-numbers/.meta/Example.roc @@ -55,7 +55,6 @@ ComplexNumbers :: {}.{ exp : Complex -> Complex exp = |z| { - e = 2.718281828459045 factor = e->pow(z.re) { re: factor * cos(z.im), @@ -66,7 +65,11 @@ ComplexNumbers :: {}.{ # The following function should soon be available in Roc's builtins +e = 2.718281828459045.F64 +pi = 3.141592653589793.F64 + # Calculates the natural logarithm of x, ln(x). +ln : F64 -> F64 ln = |x| { if x <= 0.0 { # Natural log is undefined for zero and negative numbers @@ -75,16 +78,13 @@ ln = |x| { var $norm_x = x var $log_offset = 0.0 - e_val = 2.718281828459045 - inv_e = 0.36787944117144233 - # Range reduction: keep x between [1/e, e] - while $norm_x > e_val { - $norm_x = $norm_x / e_val + while $norm_x > e { + $norm_x = $norm_x / e $log_offset = $log_offset + 1.0 } - while $norm_x < inv_e { - $norm_x = $norm_x * e_val + while $norm_x < 1 / e { + $norm_x = $norm_x * e $log_offset = $log_offset - 1.0 } @@ -95,12 +95,10 @@ ln = |x| { var $ln_sum = 0.0 var $ln_n = 1.0 - var $i = 0 - while $i < 30 { + for _ in 0..<30.U8 { $ln_sum = $ln_sum + ($z_term / $ln_n) $z_term = $z_term * z2 $ln_n = $ln_n + 2.0 - $i = $i + 1 } (2.0 * $ln_sum) + $log_offset @@ -108,19 +106,19 @@ ln = |x| { } # Calculates e^x using the Taylor series. +exp : F64 -> F64 exp = |x| { var $norm_x = x var $exp_mult = 1.0 - e_val = 2.718281828459045 # Range reduction: keep x between [-1.0, 1.0] while $norm_x > 1.0 { $norm_x = $norm_x - 1.0 - $exp_mult = $exp_mult * e_val + $exp_mult = $exp_mult * e } while $norm_x < -1.0 { $norm_x = $norm_x + 1.0 - $exp_mult = $exp_mult / e_val + $exp_mult = $exp_mult / e } # Taylor series @@ -128,18 +126,17 @@ exp = |x| { var $exp_sum = 1.0 var $exp_n = 1.0 - var $j = 0 - while $j < 25 { + for _ in 0..<25.U8 { $exp_term = $exp_term * $norm_x / $exp_n $exp_sum = $exp_sum + $exp_term $exp_n = $exp_n + 1.0 - $j = $j + 1 } $exp_sum * $exp_mult } -# Calculates x^p for F64 values using the newly separated functions. +# Calculates x^p for 64-bit values using the newly separated functions. +pow : F64, F64 -> F64 pow = |x, p| { if x == 0.0 { if p == 0.0 { @@ -148,8 +145,8 @@ pow = |x, p| { 0.0 } } else if x < 0.0 { - # Fractional powers of negative numbers are undefined in pure real F64 math - 0.0 + # Fractional powers of negative numbers are undefined in pure real 64-bit math + crash "Raising a negative number to a fractional power is undefined" } else if p == 0.0 { 1.0 } else { @@ -160,13 +157,14 @@ pow = |x, p| { # cos uses the Taylor series expansion. # We first normalize `x` to the [-PI, PI] range to ensure rapid, stable convergence. +cos : F64 -> F64 cos = |x| { var $norm_x = x - while $norm_x > 3.141592653589793 { - $norm_x = $norm_x - 6.283185307179586 + while $norm_x > pi { + $norm_x = $norm_x - 2 * pi } - while $norm_x < -3.141592653589793 { - $norm_x = $norm_x + 6.283185307179586 + while $norm_x < -pi { + $norm_x = $norm_x + 2 * pi } var $term = 1.0 @@ -174,58 +172,54 @@ cos = |x| { var $n = 0.0 # 20 iterations is more than enough for F64 precision in this range - var $i = 0 - while $i < 20 { + for _ in 0..<20.U8 { $term = { -($term) * $norm_x * $norm_x / (($n + 1.0) * ($n + 2.0)) } $sum = $sum + $term $n = $n + 2.0 - $i = $i + 1 } $sum } # sin uses the Taylor series expansion. +sin : F64 -> F64 sin = |x| { var $norm_x = x - while $norm_x > 3.141592653589793 { - $norm_x = $norm_x - 6.283185307179586 + while $norm_x > pi { + $norm_x = $norm_x - 2 * pi } - while $norm_x < -3.141592653589793 { - $norm_x = $norm_x + 6.283185307179586 + while $norm_x < -pi { + $norm_x = $norm_x + 2 * pi } var $term = $norm_x var $sum = $norm_x var $n = 1.0 - var $i = 0 - while $i < 20 { + for _ in 0..<20.U8 { $term = { -($term) * $norm_x * $norm_x / (($n + 1.0) * ($n + 2.0)) } $sum = $sum + $term $n = $n + 2.0 - $i = $i + 1 } $sum } # sqrt uses the Babylonian method (Newton-Raphson approach). +sqrt : F64 -> F64 sqrt = |x| { if x <= 0.0 { 0.0 } else { var $guess = x / 2.0 - var $i = 0 # 25 iterations will aggressively converge for almost all F64 values - while $i < 25 { + for _ in 0..<25.U8 { $guess = ($guess + (x / $guess)) / 2.0 - $i = $i + 1 } $guess diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2 index 0f3c1e69..82e53ab4 100644 --- a/exercises/practice/complex-numbers/.meta/template.j2 +++ b/exercises/practice/complex-numbers/.meta/template.j2 @@ -46,8 +46,8 @@ expect { {% endfor %} is_approx_eq = |x1, x2| { - i1 = (x1 * 1000 + 0.5).to_u64_try() ?? { crash "Unreachable" } - i2 = (x2 * 1000 + 0.5).to_u64_try() ?? { crash "Unreachable" } + i1 = (x1 * 1000 + 0.5).to_i64_try() ?? { crash "Unreachable" } + i2 = (x2 * 1000 + 0.5).to_i64_try() ?? { crash "Unreachable" } i1 == i2 } diff --git a/exercises/practice/complex-numbers/ComplexNumbers.roc b/exercises/practice/complex-numbers/ComplexNumbers.roc index 37feaef0..34c5e706 100644 --- a/exercises/practice/complex-numbers/ComplexNumbers.roc +++ b/exercises/practice/complex-numbers/ComplexNumbers.roc @@ -1,4 +1,6 @@ ComplexNumbers :: {}.{ + Complex : { re : F64, im : F64 } + real : Complex -> F64 real = |z| { crash "Please implement the 'real' function" @@ -45,4 +47,3 @@ ComplexNumbers :: {}.{ } } -Complex : { re : F64, im : F64 } diff --git a/exercises/practice/complex-numbers/complex-numbers-test.roc b/exercises/practice/complex-numbers/complex-numbers-test.roc index a7c4216c..1bbbd102 100644 --- a/exercises/practice/complex-numbers/complex-numbers-test.roc +++ b/exercises/practice/complex-numbers/complex-numbers-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 import ComplexNumbers exposing [Complex, real, imaginary, add, sub, mul, div, conjugate, abs, exp] @@ -365,14 +365,16 @@ expect { result->complex_is_approx_eq(expected) } -is_approx_eq : F64, F64 -> Bool is_approx_eq = |x1, x2| { - i1 = (x1 * 1000 + 0.5).to_u64_wrap() - i2 = (x2 * 1000 + 0.5).to_u64_wrap() + i1 = (x1 * 1000 + 0.5).to_i64_try() ?? { + crash "Unreachable" + } + i2 = (x2 * 1000 + 0.5).to_i64_try() ?? { + crash "Unreachable" + } i1 == i2 } -complex_is_approx_eq : Complex, Complex -> Bool complex_is_approx_eq = |z1, z2| { is_approx_eq(z1.re, z2.re) and is_approx_eq(z1.im, z2.im) } From 78a2417dbb827c29b1bc4f63efbb74e610a6d08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 12:34:38 +1200 Subject: [PATCH 132/162] Work around issue #9742 --- exercises/practice/strain/.meta/Example.roc | 29 +++++++-------------- exercises/practice/strain/strain-test.roc | 2 +- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/exercises/practice/strain/.meta/Example.roc b/exercises/practice/strain/.meta/Example.roc index c4a4ceb8..1a933a18 100644 --- a/exercises/practice/strain/.meta/Example.roc +++ b/exercises/practice/strain/.meta/Example.roc @@ -1,39 +1,30 @@ Strain :: {}.{ keep : List(a), (a -> Bool) -> List(a) keep = |list, predicate| { - loop = |sub_list, kept_items, predicate2| { - match sub_list { - [] => kept_items - [first, .. as rest] => { - if predicate2(first) { - rest->loop(kept_items.append(first), predicate2) - } else { - rest->loop(kept_items, predicate2) - } - } + var $result = [] + for item in list { + if predicate(item) { + $result = $result.append(item) } } - - loop(list, [], predicate) + $result } discard : List(a), (a -> Bool) -> List(a) discard = |list, predicate| { - loop = |sub_list, non_discarded_items, predicate2| { + loop = |sub_list, non_discarded_items| { match sub_list { [] => non_discarded_items [first, .. as rest] => { - if predicate2(first) { - rest->loop(non_discarded_items, predicate2) + if predicate(first) { + rest->loop(non_discarded_items) } else { - rest->loop(non_discarded_items.append(first), predicate2) + rest->loop(non_discarded_items.append(first)) } } } } - loop(list, [], predicate) + loop(list, []) } } - -# TODO: remove predicate2 once https://github.com/roc-lang/roc/issues/9690 is fixed diff --git a/exercises/practice/strain/strain-test.roc b/exercises/practice/strain/strain-test.roc index fdb79d35..876191ba 100644 --- a/exercises/practice/strain/strain-test.roc +++ b/exercises/practice/strain/strain-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/strain/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 import Strain exposing [keep, discard] From 0c313cb542ca738bbebc5ed35bfd892476079e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 15:29:34 +1200 Subject: [PATCH 133/162] Simplify nth-prime solution --- exercises/practice/nth-prime/.meta/Example.roc | 12 ++++-------- exercises/practice/nth-prime/nth-prime-test.roc | 4 ++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/exercises/practice/nth-prime/.meta/Example.roc b/exercises/practice/nth-prime/.meta/Example.roc index f8d94b5f..096a0e5c 100644 --- a/exercises/practice/nth-prime/.meta/Example.roc +++ b/exercises/practice/nth-prime/.meta/Example.roc @@ -5,11 +5,9 @@ NthPrime :: {}.{ Err(NoPrime0) } else if number == 1 { Ok(2) - } else if number == 2 { - Ok(3) } else { - find_prime = |primes, index, number2| { - if primes.len() == number2 { + find_prime = |primes, index| { + if primes.len() == number { primes } else { next_index = index + 2 @@ -20,10 +18,10 @@ NthPrime :: {}.{ primes.append(next_index) } } - find_prime(new_primes, next_index, number2) + find_prime(new_primes, next_index) } } - find_prime([2, 3, 5], 5, number) + find_prime([2, 3], 3) .last() .map_err( |_| { @@ -33,5 +31,3 @@ NthPrime :: {}.{ } } } - -# TODO: remove number2 once https://github.com/roc-lang/roc/issues/9690 is fixed diff --git a/exercises/practice/nth-prime/nth-prime-test.roc b/exercises/practice/nth-prime/nth-prime-test.roc index 6b93ec43..dff8432d 100644 --- a/exercises/practice/nth-prime/nth-prime-test.roc +++ b/exercises/practice/nth-prime/nth-prime-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/nth-prime/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import NthPrime exposing [prime] @@ -16,7 +16,7 @@ expect { result == Ok(3) } -# sixth prime +# sixth primes expect { result = prime(6) result == Ok(13) From ef7a068feb3b01d5d92773b43b91b88ecef37432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 15:34:28 +1200 Subject: [PATCH 134/162] Update exercise to new compiler: forth --- exercises/practice/forth/.meta/Example.roc | 515 ++++++++++++--------- exercises/practice/forth/.meta/template.j2 | 4 +- exercises/practice/forth/Forth.roc | 12 +- exercises/practice/forth/forth-test.roc | 192 ++------ 4 files changed, 339 insertions(+), 384 deletions(-) diff --git a/exercises/practice/forth/.meta/Example.roc b/exercises/practice/forth/.meta/Example.roc index d5c6ce94..988a1aeb 100644 --- a/exercises/practice/forth/.meta/Example.roc +++ b/exercises/practice/forth/.meta/Example.roc @@ -1,241 +1,302 @@ Forth :: {}.{ - # Evaluation - evaluate : Str -> Result Stack Str - evaluate = |program| - result = |_| - lower = to_lower(program) - operations = parse(lower)? - interpret(operations) - - Result.map_err(result({}), handle_error) + Stack : List(I16) + + evaluate : Str -> Try(Stack, Str) + evaluate = |program| { + lower = to_lower(program) + match parse(lower) { + Ok(operations) => { + match interpret(operations) { + Ok(stack) => Ok(stack) + Err(err) => Err(handle_error(err)) + } + } + Err(err) => Err(handle_error(err)) + } + } } +Defs : Dict(Str, List(Str)) -# Types -Defs : Dict Str (List Str) -Stack : List I16 Op : [ - Dup, - Drop, - Swap, - Over, - Add, - Subtract, - Multiply, - Divide, - Number I16, + Dup, + Drop, + Swap, + Over, + Add, + Subtract, + Multiply, + Divide, + Number(I16), ] -interpret : List Op -> Result Stack _ -interpret = |program| - help = |ops, stack| - when ops is - [] -> Ok(stack) - [op, .. as rest] -> - when step(stack, op) is - Ok(new_stack) -> help(rest, new_stack) - Err(error) -> EvaluationError({ error, stack, op, ops }) |> Err - help(program, []) - -step : Stack, Op -> Result Stack _ -step = |stack, op| - when op is - Number(x) -> - List.append(stack, x) |> Ok - - Dup -> - when stack is - [.., x] -> - List.append(stack, x) |> Ok - - _ -> Err(Arity(1)) - - Drop -> - when stack is - [.. as rest, _] -> Ok(rest) - _ -> Err(Arity(1)) - - Swap -> - when stack is - [.. as rest, x, y] -> - List.append(rest, y) - |> List.append(x) - |> Ok - - _ -> Err(Arity(2)) - - Over -> - when stack is - [.. as rest, x, y] -> - List.concat(rest, [x, y, x]) |> Ok - - _ -> Err(Arity(2)) - - Add -> - when stack is - [.. as rest, x, y] -> - List.append(rest, (x + y)) |> Ok - - _ -> Err(Arity(2)) - - Subtract -> - when stack is - [.. as rest, x, y] -> - List.append(rest, (x - y)) |> Ok - - _ -> Err(Arity(2)) - - Multiply -> - when stack is - [.. as rest, x, y] -> - List.append(rest, (x * y)) |> Ok - - _ -> Err(Arity(2)) - - Divide -> - when stack is - [.. as rest, x, y] -> - quotient = Num.div_trunc_checked(x, y)? - List.append(rest, quotient) |> Ok - - _ -> Err(Arity(2)) - -# Parsing -parse : Str -> Result (List Op) _ -parse = |str| - when Str.split_on(Str.trim(str), "\n") is - [.. as def_lines, program] -> - defs = parse_defs(def_lines)? - - Str.split_on(program, " ") - |> flatten_defs(defs) - |> List.map_try(to_op) - - [] -> Ok([]) # We'll let the empty program return the empty list - -parse_defs : List Str -> Result Defs _ -parse_defs = |lines| - List.walk_try( - lines, - Dict.empty({}), - |defs, line| - when Str.split_on(line, " ") is - [":", name, .. as tokens, ";"] -> - ops = parse_def(tokens, defs)? - Dict.insert(defs, name, ops) |> Ok - - _ -> Err(UnableToParseDef(line)), - ) - -parse_def : List Str, Defs -> Result (List Str) _ -parse_def = |tokens, defs| - List.walk_try( - tokens, - [], - |ops, token| - when Dict.get(defs, token) is - Ok(body) -> List.concat(ops, body) |> Ok - _ if is_builtin(token) -> List.append(ops, token) |> Ok - _ -> Err(UnknownName(token)), - ) +interpret : List(Op) -> Try(Stack, _) +interpret = |program| { + help = |ops, stack| { + match ops { + [] => Ok(stack) + [op, .. as rest] => { + match step(stack, op) { + Ok(new_stack) => help(rest, new_stack) + Err(error) => Err(EvaluationError({ error, stack, op, ops })) + } + } + } + } + help(program, []) +} + +step : Stack, Op -> Try(Stack, _) +step = |stack, op| { + match op { + Number(x) => Ok(stack.append(x)) + Dup => { + match stack { + [.., x] => Ok(stack.append(x)) + _ => Err(Arity(1)) + } + } + + Drop => { + match stack { + [.. as rest, _] => Ok(rest) + _ => Err(Arity(1)) + } + } + + Swap => { + match stack { + [.. as rest, x, y] => Ok(rest.append(y).append(x)) + _ => Err(Arity(2)) + } + } + + Over => { + match stack { + [.. as rest, x, y] => Ok(rest.concat([x, y, x])) + _ => Err(Arity(2)) + } + } + + Add => { + match stack { + [.. as rest, x, y] => Ok(rest.append(x + y)) + _ => Err(Arity(2)) + } + } + + Subtract => { + match stack { + [.. as rest, x, y] => Ok(rest.append(x - y)) + _ => Err(Arity(2)) + } + } + + Multiply => { + match stack { + [.. as rest, x, y] => Ok(rest.append(x * y)) + _ => Err(Arity(2)) + } + } + + Divide => { + match stack { + [.. as rest, x, y] => { + if y == 0 { + Err(DivByZero) + } else { + Ok(rest.append(x // y)) + } + } + _ => Err(Arity(2)) + } + } + } +} + +parse : Str -> Try(List(Op), _) +parse = |str| { + match Str.split_on(Str.trim(str), "\n") { + [.. as def_lines, program] => { + defs = parse_defs(def_lines)? + program.split_on(" ") + ->flatten_defs(defs) + ->map_try(to_op) + } + [] => Ok([]) + } +} + +parse_defs : List(Str) -> Try(Defs, _) +parse_defs = |lines| { + List.fold_until( + lines, + Ok(Dict.empty()), + |acc_res, line| { + match acc_res { + Ok(defs) => { + match line.split_on(" ") { + [":", name, .. as tokens, ";"] => { + match parse_def(tokens, defs) { + Ok(ops) => Continue(Ok(defs.insert(name, ops))) + Err(err) => Break(Err(err)) + } + } + _ => Break(Err(UnableToParseDef(line))) + } + } + Err(err) => Break(Err(err)) + } + }, + ) +} + +parse_def : List(Str), Defs -> Try(List(Str), _) +parse_def = |tokens, defs| { + List.fold_until( + tokens, + Ok([]), + |acc_res, token| { + match acc_res { + Ok(ops) => { + match defs.get(token) { + Ok(body) => Continue(Ok(ops.concat(body))) + Err(KeyNotFound) => { + if is_builtin(token) { + Continue(Ok(ops.append(token))) + } else { + Break(Err(UnknownName(token))) + } + } + } + } + Err(err) => Break(Err(err)) + } + }, + ) +} is_builtin : Str -> Bool -is_builtin = |token| - builtins = ["dup", "drop", "swap", "over", "+", "-", "*", "/"] - (builtins |> List.contains(token)) or (Result.is_ok(Str.to_i16(token))) - -flatten_defs : List Str, Defs -> List Str -flatten_defs = |tokens, defs| - List.join_map( - tokens, - |token| - when Dict.get(defs, token) is - Ok(body) -> body - _ -> [token], - ) - -to_op : Str -> Result Op _ -to_op = |str| - when str is - "dup" -> Ok(Dup) - "drop" -> Ok(Drop) - "swap" -> Ok(Swap) - "over" -> Ok(Over) - "+" -> Ok(Add) - "-" -> Ok(Subtract) - "*" -> Ok(Multiply) - "/" -> Ok(Divide) - _ -> - when Str.to_i16(str) is - Ok(num) -> Ok(Number(num)) - Err(_) -> Err(UnknownName(str)) - -# Display +is_builtin = |token| { + builtins = ["dup", "drop", "swap", "over", "+", "-", "*", "/"] + builtins.contains(token) or ( + match I16.from_str(token) { + Ok(_) => Bool.True + Err(_) => Bool.False + }, + ) +} + +flatten_defs : List(Str), Defs -> List(Str) +flatten_defs = |tokens, defs| { + tokens->join_map( + |token| { + match defs.get(token) { + Ok(body) => body + _ => [token] + } + }, + ) +} + +to_op : Str -> Try(Op, _) +to_op = |str| { + match str { + "dup" => Ok(Dup) + "drop" => Ok(Drop) + "swap" => Ok(Swap) + "over" => Ok(Over) + "+" => Ok(Add) + "-" => Ok(Subtract) + "*" => Ok(Multiply) + "/" => Ok(Divide) + _ => { + match I16.from_str(str) { + Ok(num) => Ok(Number(num)) + Err(_) => Err(UnknownName(str)) + } + } + } +} + handle_error : _ -> Str -handle_error = |err| - when err is - UnknownName(key) -> "Hmm, I don't know any operations called '${key}'. Maybe there's a typo?" - UnableToParseDef(line) -> - """ - This is supposed to be a definition, but I'm not sure how to parse it: - ${line} - """ - - EvaluationError({ error, stack, op, ops }) -> - when error is - Arity(1) -> - """ - Oops! '${op_to_str(op)}' expected 1 argument, but the stack was empty. - ${show_execution(stack, ops)} - """ - - Arity(n) -> - """ - Oops! '${op_to_str(op)}' expected ${Num.to_str(n)} arguments, but there weren't enough on the stack. - ${show_execution(stack, ops)} - """ - - DivByZero -> - """ - Sorry, division by zero is not allowed. - ${show_execution(stack, ops)} - """ - -show_execution : Stack, List Op -> Str -show_execution = |stack, ops| - stack_str = - List.map(stack, Num.to_str) - |> Str.join_with(" ") - ops_str = - List.map(ops, op_to_str) - |> Str.join_with(" ") - "${stack_str} | ${ops_str}" +handle_error = |err| { + match err { + UnknownName(key) => "Hmm, I don't know any operations called '${key}'. Maybe there's a typo?" + UnableToParseDef(line) => "This is supposed to be a definition, but I'm not sure how to parse it:\n${line}" + EvaluationError({ error, stack, op, ops }) => { + match error { + Arity(1) => "Oops! '${op_to_str(op)}' expected 1 argument, but the stack was empty.\n${show_execution(stack, ops)}" + Arity(n) => "Oops! '${op_to_str(op)}' expected ${n.to_str()} arguments, but there weren't enough on the stack.\n${show_execution(stack, ops)}" + DivByZero => "Sorry, division by zero is not allowed.\n${show_execution(stack, ops)}" + } + } + } +} + +show_execution : Stack, List(Op) -> Str +show_execution = |stack, ops| { + stack_str = + stack.map(|n| n.to_str()) + ->Str.join_with(" ") + ops_str = + ops.map(op_to_str) + ->Str.join_with(" ") + "${stack_str} | ${ops_str}" +} op_to_str : Op -> Str -op_to_str = |op| - when op is - Dup -> "dup" - Drop -> "drop" - Swap -> "swap" - Over -> "over" - Add -> "+" - Subtract -> "-" - Multiply -> "*" - Divide -> "/" - Number(num) -> Num.to_str(num) +op_to_str = |op| { + match op { + Dup => "dup" + Drop => "drop" + Swap => "swap" + Over => "over" + Add => "+" + Subtract => "-" + Multiply => "*" + Divide => "/" + Number(num) => num.to_str() + } +} to_lower : Str -> Str -to_lower = |str| - result = - Str.to_utf8(str) - |> List.map( - |byte| - if 'A' <= byte and byte <= 'Z' then - byte - 'A' + 'a' - else - byte, - ) - |> Str.from_utf8 - when result is - Ok(s) -> s - _ -> crash("There was an unexpected error converting back to Str") +to_lower = |str| { + result = + str.to_utf8() + .map( + |byte| { + if 'A' <= byte and byte <= 'Z' { + byte - 'A' + 'a' + } else { + byte + } + }, + ) + ->Str.from_utf8() + match result { + Ok(s) => s + _ => { + crash "There was an unexpected error converting back to Str" + } + } +} + +# The following function should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} + +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/forth/.meta/template.j2 b/exercises/practice/forth/.meta/template.j2 index 297fa482..2e12e1cb 100644 --- a/exercises/practice/forth/.meta/template.j2 +++ b/exercises/practice/forth/.meta/template.j2 @@ -8,9 +8,7 @@ import Forth exposing [evaluate] {% for innerCase in case["cases"] %} # {{ case["description"] }}: {{ innerCase["description"] }} expect { - result = evaluate( - {{ innerCase["input"]["instructions"] | join('\n') | to_roc_multiline_string | indent(8) }} - ) + result = evaluate({{ innerCase["input"]["instructions"] | join('\n') | to_roc_multiline_string | indent(8) }}) {%- if innerCase["expected"]["error"] %} result.is_err() {%- else %} diff --git a/exercises/practice/forth/Forth.roc b/exercises/practice/forth/Forth.roc index 8407a83c..6fa1d10c 100644 --- a/exercises/practice/forth/Forth.roc +++ b/exercises/practice/forth/Forth.roc @@ -1,8 +1,8 @@ Forth :: {}.{ - evaluate : Str -> Result Stack _ - evaluate = |program| - crash("Please implement the 'evaluate' function") -} - + Stack : List(I16) -Stack : List I16 + evaluate : Str -> Try(Stack, _) + evaluate = |program| { + crash "Please implement the 'evaluate' function" + } +} diff --git a/exercises/practice/forth/forth-test.roc b/exercises/practice/forth/forth-test.roc index a730a541..7011ddea 100644 --- a/exercises/practice/forth/forth-test.roc +++ b/exercises/practice/forth/forth-test.roc @@ -1,321 +1,243 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/forth/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Forth exposing [evaluate] # parsing and numbers: numbers just get pushed onto the stack expect { - result = evaluate( - "1 2 3 4 5", - ) + result = evaluate("1 2 3 4 5") result == Ok([1, 2, 3, 4, 5]) } # parsing and numbers: pushes negative numbers onto the stack expect { - result = evaluate( - "-1 -2 -3 -4 -5", - ) + result = evaluate("-1 -2 -3 -4 -5") result == Ok([-1, -2, -3, -4, -5]) } # addition: can add two numbers expect { - result = evaluate( - "1 2 +", - ) + result = evaluate("1 2 +") result == Ok([3]) } # addition: errors if there is nothing on the stack expect { - result = evaluate( - "+", - ) + result = evaluate("+") result.is_err() } # addition: errors if there is only one value on the stack expect { - result = evaluate( - "1 +", - ) + result = evaluate("1 +") result.is_err() } # addition: more than two values on the stack expect { - result = evaluate( - "1 2 3 +", - ) + result = evaluate("1 2 3 +") result == Ok([1, 5]) } # subtraction: can subtract two numbers expect { - result = evaluate( - "3 4 -", - ) + result = evaluate("3 4 -") result == Ok([-1]) } # subtraction: errors if there is nothing on the stack expect { - result = evaluate( - "-", - ) + result = evaluate("-") result.is_err() } # subtraction: errors if there is only one value on the stack expect { - result = evaluate( - "1 -", - ) + result = evaluate("1 -") result.is_err() } # subtraction: more than two values on the stack expect { - result = evaluate( - "1 12 3 -", - ) + result = evaluate("1 12 3 -") result == Ok([1, 9]) } # multiplication: can multiply two numbers expect { - result = evaluate( - "2 4 *", - ) + result = evaluate("2 4 *") result == Ok([8]) } # multiplication: errors if there is nothing on the stack expect { - result = evaluate( - "*", - ) + result = evaluate("*") result.is_err() } # multiplication: errors if there is only one value on the stack expect { - result = evaluate( - "1 *", - ) + result = evaluate("1 *") result.is_err() } # multiplication: more than two values on the stack expect { - result = evaluate( - "1 2 3 *", - ) + result = evaluate("1 2 3 *") result == Ok([1, 6]) } # division: can divide two numbers expect { - result = evaluate( - "12 3 /", - ) + result = evaluate("12 3 /") result == Ok([4]) } # division: performs integer division expect { - result = evaluate( - "8 3 /", - ) + result = evaluate("8 3 /") result == Ok([2]) } # division: errors if dividing by zero expect { - result = evaluate( - "4 0 /", - ) + result = evaluate("4 0 /") result.is_err() } # division: errors if there is nothing on the stack expect { - result = evaluate( - "/", - ) + result = evaluate("/") result.is_err() } # division: errors if there is only one value on the stack expect { - result = evaluate( - "1 /", - ) + result = evaluate("1 /") result.is_err() } # division: more than two values on the stack expect { - result = evaluate( - "1 12 3 /", - ) + result = evaluate("1 12 3 /") result == Ok([1, 4]) } # combined arithmetic: addition and subtraction expect { - result = evaluate( - "1 2 + 4 -", - ) + result = evaluate("1 2 + 4 -") result == Ok([-1]) } # combined arithmetic: multiplication and division expect { - result = evaluate( - "2 4 * 3 /", - ) + result = evaluate("2 4 * 3 /") result == Ok([2]) } # combined arithmetic: multiplication and addition expect { - result = evaluate( - "1 3 4 * +", - ) + result = evaluate("1 3 4 * +") result == Ok([13]) } # combined arithmetic: addition and multiplication expect { - result = evaluate( - "1 3 4 + *", - ) + result = evaluate("1 3 4 + *") result == Ok([7]) } # dup: copies a value on the stack expect { - result = evaluate( - "1 dup", - ) + result = evaluate("1 dup") result == Ok([1, 1]) } # dup: copies the top value on the stack expect { - result = evaluate( - "1 2 dup", - ) + result = evaluate("1 2 dup") result == Ok([1, 2, 2]) } # dup: errors if there is nothing on the stack expect { - result = evaluate( - "dup", - ) + result = evaluate("dup") result.is_err() } # drop: removes the top value on the stack if it is the only one expect { - result = evaluate( - "1 drop", - ) + result = evaluate("1 drop") result == Ok([]) } # drop: removes the top value on the stack if it is not the only one expect { - result = evaluate( - "1 2 drop", - ) + result = evaluate("1 2 drop") result == Ok([1]) } # drop: errors if there is nothing on the stack expect { - result = evaluate( - "drop", - ) + result = evaluate("drop") result.is_err() } # swap: swaps the top two values on the stack if they are the only ones expect { - result = evaluate( - "1 2 swap", - ) + result = evaluate("1 2 swap") result == Ok([2, 1]) } # swap: swaps the top two values on the stack if they are not the only ones expect { - result = evaluate( - "1 2 3 swap", - ) + result = evaluate("1 2 3 swap") result == Ok([1, 3, 2]) } # swap: errors if there is nothing on the stack expect { - result = evaluate( - "swap", - ) + result = evaluate("swap") result.is_err() } # swap: errors if there is only one value on the stack expect { - result = evaluate( - "1 swap", - ) + result = evaluate("1 swap") result.is_err() } # over: copies the second element if there are only two expect { - result = evaluate( - "1 2 over", - ) + result = evaluate("1 2 over") result == Ok([1, 2, 1]) } # over: copies the second element if there are more than two expect { - result = evaluate( - "1 2 3 over", - ) + result = evaluate("1 2 3 over") result == Ok([1, 2, 3, 2]) } # over: errors if there is nothing on the stack expect { - result = evaluate( - "over", - ) + result = evaluate("over") result.is_err() } # over: errors if there is only one value on the stack expect { - result = evaluate( - "1 over", - ) + result = evaluate("1 over") result.is_err() } # user-defined words: can consist of built-in words expect { result = evaluate( - \\: dup-twice dup dup ; \\1 dup-twice , - ) result == Ok([1, 1, 1]) } @@ -323,11 +245,9 @@ expect { # user-defined words: execute in the right order expect { result = evaluate( - \\: countup 1 2 3 ; \\countup , - ) result == Ok([1, 2, 3]) } @@ -335,12 +255,10 @@ expect { # user-defined words: can override other user-defined words expect { result = evaluate( - \\: foo dup ; \\: foo dup dup ; \\1 foo , - ) result == Ok([1, 1, 1]) } @@ -348,11 +266,9 @@ expect { # user-defined words: can override built-in words expect { result = evaluate( - \\: swap dup ; \\1 swap , - ) result == Ok([1, 1]) } @@ -360,11 +276,9 @@ expect { # user-defined words: can override built-in operators expect { result = evaluate( - \\: + * ; \\3 4 + , - ) result == Ok([12]) } @@ -372,13 +286,11 @@ expect { # user-defined words: can use different words with the same name expect { result = evaluate( - \\: foo 5 ; \\: bar foo ; \\: foo 6 ; \\bar foo , - ) result == Ok([5, 6]) } @@ -386,64 +298,50 @@ expect { # user-defined words: can define word that uses word with the same name expect { result = evaluate( - \\: foo 10 ; \\: foo foo 1 + ; \\foo , - ) result == Ok([11]) } # user-defined words: errors if executing a non-existent word expect { - result = evaluate( - "foo", - ) + result = evaluate("foo") result.is_err() } # case-insensitivity: DUP is case-insensitive expect { - result = evaluate( - "1 DUP Dup dup", - ) + result = evaluate("1 DUP Dup dup") result == Ok([1, 1, 1, 1]) } # case-insensitivity: DROP is case-insensitive expect { - result = evaluate( - "1 2 3 4 DROP Drop drop", - ) + result = evaluate("1 2 3 4 DROP Drop drop") result == Ok([1]) } # case-insensitivity: SWAP is case-insensitive expect { - result = evaluate( - "1 2 SWAP 3 Swap 4 swap", - ) + result = evaluate("1 2 SWAP 3 Swap 4 swap") result == Ok([2, 3, 4, 1]) } # case-insensitivity: OVER is case-insensitive expect { - result = evaluate( - "1 2 OVER Over over", - ) + result = evaluate("1 2 OVER Over over") result == Ok([1, 2, 1, 2, 1]) } # case-insensitivity: user-defined words are case-insensitive expect { result = evaluate( - \\: foo dup ; \\1 FOO Foo foo , - ) result == Ok([1, 1, 1, 1]) } @@ -451,11 +349,9 @@ expect { # case-insensitivity: definitions are case-insensitive expect { result = evaluate( - \\: SWAP DUP Dup dup ; \\1 swap , - ) result == Ok([1, 1, 1, 1]) } From 3c0fce909e296906da2baa8c6d102f292fb8dcfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:12:16 +1200 Subject: [PATCH 135/162] Update exercise to new compiler: go-counting --- .../practice/go-counting/.meta/Example.roc | 291 +++++++++++------- .../practice/go-counting/.meta/template.j2 | 40 +-- exercises/practice/go-counting/GoCounting.roc | 37 +-- .../practice/go-counting/go-counting-test.roc | 153 ++++----- 4 files changed, 280 insertions(+), 241 deletions(-) diff --git a/exercises/practice/go-counting/.meta/Example.roc b/exercises/practice/go-counting/.meta/Example.roc index 7d1b0261..8d7cfa93 100644 --- a/exercises/practice/go-counting/.meta/Example.roc +++ b/exercises/practice/go-counting/.meta/Example.roc @@ -1,138 +1,197 @@ GoCounting :: {}.{ - territory : Str, Intersection -> Result Territory [OutOfBounds, BoardWasEmpty, BoardWasNotRectangular, InvalidChar U8] - territory = |board_str, intersection| - board = parse(board_str)? - if intersection.x >= board.width or intersection.y >= board.height then - Err(OutOfBounds) - else - Ok(search_territory(board, intersection)) + territory : Str, Intersection -> Try(Territory, [OutOfBounds, BoardWasEmpty, BoardWasNotRectangular, InvalidChar(U8), ..]) + territory = |board_str, intersection| { + board = parse(board_str)? + if intersection.x >= board.width or intersection.y >= board.height { + Err(OutOfBounds) + } else { + Ok(search_territory(board, intersection)) + } + } - territories : Str -> Result Territories [BoardWasEmpty, BoardWasNotRectangular, InvalidChar U8] - territories = |board_str| - board = parse(board_str)? - board.rows - |> List.map_with_index( - |row, y| - row - |> List.map_with_index( - |stone, x| - if stone == None then - [{ x, y }] - else - [], - ) - |> List.join, - ) - |> List.join - |> List.walk( - { black: Set.empty({}), white: Set.empty({}), none: Set.empty({}) }, - |state, intersection| - if state.black |> Set.contains(intersection) or state.white |> Set.contains(intersection) or state.none |> Set.contains(intersection) then - state - else - new_territory = search_territory(board, intersection) - when new_territory.owner is - Black -> { black: state.black |> Set.union(new_territory.territory), white: state.white, none: state.none } - White -> { black: state.black, white: state.white |> Set.union(new_territory.territory), none: state.none } - None -> { black: state.black, white: state.white, none: state.none |> Set.union(new_territory.territory) }, - ) - |> Ok + territories : Str -> Try(Territories, [BoardWasEmpty, BoardWasNotRectangular, InvalidChar(U8), ..]) + territories = |board_str| { + board = parse(board_str)? + empty_intersections = { + board.rows + .map_with_index( + |row, y| { + row.map_with_index( + |stone, x| { + if stone == None [{ x, y }] else [] + }, + ) + ->join() + }, + ) + ->join() + } + empty_intersections.fold( + { black: Set.empty(), white: Set.empty(), none: Set.empty() }, + |state, intersection| { + if state.black.contains(intersection) or state.white.contains(intersection) or state.none.contains(intersection) { + state + } else { + new_territory = search_territory(board, intersection) + match new_territory.owner { + Black => { ..state, black: state.black.union(new_territory.territory) } + White => { ..state, white: state.white.union(new_territory.territory) } + None => { ..state, none: state.none.union(new_territory.territory) } + } + } + }, + ) + ->Ok() + } } - Intersection : { x : U64, y : U64 } Stone : [White, Black, None] Territory : { - owner : Stone, - territory : Set Intersection, + owner : Stone, + territory : Set(Intersection), } Territories : { - black : Set Intersection, - white : Set Intersection, - none : Set Intersection, + black : Set(Intersection), + white : Set(Intersection), + none : Set(Intersection), } Board : { - rows : List (List Stone), - width : U64, - height : U64, + rows : List(List(Stone)), + width : U64, + height : U64, } -parse : Str -> Result Board [BoardWasEmpty, BoardWasNotRectangular, InvalidChar U8] -parse = |board_str| - if board_str == "" then - Err(BoardWasEmpty) - else - rows = - board_str - |> Str.to_utf8 - |> List.split_on('\n') - |> List.map_try( - |row| - row - |> List.map_try( - |char| - when char is - 'B' -> Ok(Black) - 'W' -> Ok(White) - ' ' -> Ok(None) - _ -> Err(InvalidChar(char)), - ), - )? - row_widths = rows |> List.map(List.len) - width = row_widths |> List.max |> Result.with_default(0) - if row_widths |> List.any(|w| w != width) then - Err(BoardWasNotRectangular) - else - height = List.len(rows) - Ok({ rows, width, height }) +parse : Str -> Try(Board, [BoardWasEmpty, BoardWasNotRectangular, InvalidChar(U8), ..]) +parse = |board_str| { + if board_str == "" { + Err(BoardWasEmpty) + } else { + rows = + board_str + .to_utf8() + .split_on('\n') + ->map_try( + |row| { + row->map_try( + |char| { + match char { + 'B' => Ok(Black) + 'W' => Ok(White) + ' ' => Ok(None) + _ => Err(InvalidChar(char)) + } + }, + ) + }, + )? + row_widths = rows.map(List.len) + width = row_widths.max() ?? 0 + if row_widths.any(|w| w != width) { + Err(BoardWasNotRectangular) + } else { + height = rows.len() + Ok({ rows, width, height }) + } + } +} get_stone : Board, Intersection -> Stone -get_stone = |board, { x, y }| - board.rows |> List.get(y) |> Result.with_default([]) |> List.get(x) |> Result.with_default(None) +get_stone = |board, { x, y }| { + (board.rows.get(y) ?? []).get(x) ?? None +} search_territory : Board, Intersection -> Territory -search_territory = |board, intersection| - help = |to_visit, visited, surrounding_stones| - when to_visit is - [] -> { visited, surrounding_stones } - [visiting, .. as rest_to_visit] -> - if visited |> Set.contains(visiting) then - help(rest_to_visit, visited, surrounding_stones) - else - stone = board |> get_stone(visiting) - when stone is - Black | White -> - new_surrounding_stones = surrounding_stones |> Set.insert(stone) - help(rest_to_visit, visited, new_surrounding_stones) +search_territory = |board, intersection| { + help = |to_visit, visited, surrounding_stones| { + match to_visit { + [] => { visited, surrounding_stones } + [visiting, .. as rest_to_visit] => { + if visited.contains(visiting) { + help(rest_to_visit, visited, surrounding_stones) + } else { + stone = get_stone(board, visiting) + match stone { + Black | White => { + new_surrounding_stones = surrounding_stones.insert(stone) + help(rest_to_visit, visited, new_surrounding_stones) + } + + None => { + neighbors = + [ + { + x: ( + if visiting.x > 0 { + visiting.x - 1 + } else 0, + ), + y: visiting.y, + }, + { x: visiting.x + 1, y: visiting.y }, + { + x: visiting.x, + y: ( + if visiting.y > 0 { + visiting.y - 1 + } else 0, + ), + }, + { x: visiting.x, y: visiting.y + 1 }, + ] + .drop_if( + |neighbor| { + neighbor.x >= board.width or neighbor.y >= board.height or neighbor == visiting + }, + ) + new_to_visit = rest_to_visit.concat(neighbors) + new_visited = visited.insert(visiting) + help(new_to_visit, new_visited, surrounding_stones) + } + } + } + } + } + } + search_result = help([intersection], Set.empty(), Set.empty()) + if search_result.visited.is_empty() { + { owner: None, territory: Set.empty() } + } else { + owner = + if search_result.surrounding_stones == Set.from_list([Black]) { + Black + } else if search_result.surrounding_stones == Set.from_list([White]) { + White + } else { + None + } + { owner, territory: search_result.visited } + } +} - None -> - neighbors = - [ - { x: visiting.x |> Num.sub_saturated(1), y: visiting.y }, - { x: visiting.x + 1, y: visiting.y }, - { x: visiting.x, y: visiting.y |> Num.sub_saturated(1) }, - { x: visiting.x, y: visiting.y + 1 }, - ] - |> List.drop_if( - |neighbor| - neighbor.x >= board.width or neighbor.y >= board.height or neighbor == visiting, - ) - new_to_visit = rest_to_visit |> List.concat(neighbors) - new_visited = visited |> Set.insert(visiting) - help(new_to_visit, new_visited, surrounding_stones) - search_result = help([intersection], Set.empty({}), Set.empty({})) - if search_result.visited |> Set.is_empty then - { owner: None, territory: Set.empty({}) } - else - owner = - if search_result.surrounding_stones == Set.single(Black) then - Black - else if search_result.surrounding_stones == Set.single(White) then - White - else - None - { owner, territory: search_result.visited } +# The following function should soon be available in Roc's builtins +join = |iter| { + var $state = [] + for sublist in iter { + for item in sublist { + $state = $state.append(item) + } + } + $state +} + +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} + +sub_saturated = |a, b| { + a.sub_checked(b) ?? 0 +} diff --git a/exercises/practice/go-counting/.meta/template.j2 b/exercises/practice/go-counting/.meta/template.j2 index bdf36331..7cf6d364 100644 --- a/exercises/practice/go-counting/.meta/template.j2 +++ b/exercises/practice/go-counting/.meta/template.j2 @@ -4,7 +4,7 @@ {% macro to_territory(territory) %} {%- if territory == [] %} -Set.empty({}) +Set.empty() {%- else %} Set.from_list([ {%- for intersection in territory %} @@ -16,28 +16,10 @@ Set.from_list([ import {{ exercise | to_pascal }} exposing [territory, territories] -## The following two comparison functions are temporary workarounds for Roc issue #7144: -## comparing tags or records containing sets sometimes returns the wrong result -## depending on the internal order of the set data, so we have to unwrap the sets -## in order to compare them properly. -compare_territory = |maybe_result, maybe_expected| { - match (maybe_result, maybe_expected) { - (Ok(result), Ok(expected)) => result.owner == expected.owner and result.territory == expected.territory - _ => Bool.False - } -} - -compare_territories = |maybe_result, maybe_expected| { - match (maybe_result, maybe_expected) { - (Ok(result), Ok(expected)) => result.black == expected.black and result.white == expected.white and result.none == expected.none - _ => Bool.False - } -} - {% for case in cases -%} # {{ case["description"] }} expect { - board = {{ case["input"]["board"] | to_roc_multiline_string | replace(" ", "·") | indent(8) }}.replace_each("·", " ") + board = {{ case["input"]["board"] | to_roc_multiline_string | indent(8) }} result = board -> {{ case["property"] | to_snake }} {%- if case["property"] == "territory" %}({ x : {{ case["input"]["x"] }}, y : {{ case["input"]["y"] }} }){% endif %} {%- if case["expected"]["error"] %} @@ -60,4 +42,22 @@ expect { {% endfor %} +## The following two comparison functions are temporary workarounds for Roc issue #7144: +## comparing tags or records containing sets sometimes returns the wrong result +## depending on the internal order of the set data, so we have to unwrap the sets +## in order to compare them properly. +compare_territory = |maybe_result, maybe_expected| { + match (maybe_result, maybe_expected) { + (Ok(result), Ok(expected)) => result.owner == expected.owner and result.territory == expected.territory + _ => Bool.False + } +} + +compare_territories = |maybe_result, maybe_expected| { + match (maybe_result, maybe_expected) { + (Ok(result), Ok(expected)) => result.black == expected.black and result.white == expected.white and result.none == expected.none + _ => Bool.False + } +} + {{ macros.footer() }} diff --git a/exercises/practice/go-counting/GoCounting.roc b/exercises/practice/go-counting/GoCounting.roc index ce35f9b7..06e64e39 100644 --- a/exercises/practice/go-counting/GoCounting.roc +++ b/exercises/practice/go-counting/GoCounting.roc @@ -1,25 +1,26 @@ GoCounting :: {}.{ - territory : Str, Intersection -> Result Territory _ - territory = |board_str, { x, y }| - crash("Please implement the 'territory' function") - - territories : Str -> Result Territories _ - territories = |board_str| - crash("Please implement the 'territories' function") -} + Intersection : { x : U64, y : U64 } + Stone : [White, Black, None] -Intersection : { x : U64, y : U64 } + Territory : { + owner : Stone, + territory : Set(Intersection), + } -Stone : [White, Black, None] + Territories : { + black : Set(Intersection), + white : Set(Intersection), + none : Set(Intersection), + } -Territory : { - owner : Stone, - territory : Set Intersection, -} + territory : Str, Intersection -> Try(Territory, _) + territory = |board_str, { x, y }| { + crash "Please implement the 'territory' function" + } -Territories : { - black : Set Intersection, - white : Set Intersection, - none : Set Intersection, + territories : Str -> Try(Territories, _) + territories = |board_str| { + crash "Please implement the 'territories' function" + } } diff --git a/exercises/practice/go-counting/go-counting-test.roc b/exercises/practice/go-counting/go-counting-test.roc index 92697e30..1f7ee871 100644 --- a/exercises/practice/go-counting/go-counting-test.roc +++ b/exercises/practice/go-counting/go-counting-test.roc @@ -1,39 +1,18 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/go-counting/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import GoCounting exposing [territory, territories] -## The following two comparison functions are temporary workarounds for Roc issue #7144: -## comparing tags or records containing sets sometimes returns the wrong result -## depending on the internal order of the set data, so we have to unwrap the sets -## in order to compare them properly. -compare_territory = |maybe_result, maybe_expected| { - match (maybe_result, maybe_expected) { - (Ok(result), Ok(expected)) => result.owner == expected.owner and result.territory == expected.territory - _ => Bool.False - } -} - -compare_territories = |maybe_result, maybe_expected| { - match (maybe_result, maybe_expected) { - (Ok(result), Ok(expected)) => result.black == expected.black and result.white == expected.white and result.none == expected.none - _ => Bool.False - } -} - # Black corner territory on 5x5 board expect { board = - \\··B·· - \\·B·B· - \\B·W·B - \\·W·W· - \\··W·· - .replace_each( - "·", - " ", - ) + \\ B + \\ B B + \\B W B + \\ W W + \\ W + result = board->territory({ x: 0, y: 1 }) expected = Ok( { @@ -53,15 +32,12 @@ expect { # White center territory on 5x5 board expect { board = - \\··B·· - \\·B·B· - \\B·W·B - \\·W·W· - \\··W·· - .replace_each( - "·", - " ", - ) + \\ B + \\ B B + \\B W B + \\ W W + \\ W + result = board->territory({ x: 2, y: 3 }) expected = Ok( { @@ -79,15 +55,12 @@ expect { # Open corner territory on 5x5 board expect { board = - \\··B·· - \\·B·B· - \\B·W·B - \\·W·W· - \\··W·· - .replace_each( - "·", - " ", - ) + \\ B + \\ B B + \\B W B + \\ W W + \\ W + result = board->territory({ x: 1, y: 4 }) expected = Ok( { @@ -107,20 +80,17 @@ expect { # A stone and not a territory on 5x5 board expect { board = - \\··B·· - \\·B·B· - \\B·W·B - \\·W·W· - \\··W·· - .replace_each( - "·", - " ", - ) + \\ B + \\ B B + \\B W B + \\ W W + \\ W + result = board->territory({ x: 1, y: 1 }) expected = Ok( { owner: None, - territory: Set.empty({}), + territory: Set.empty(), }, ) result->compare_territory(expected) @@ -129,15 +99,12 @@ expect { # Invalid because X is too high for 5x5 board expect { board = - \\··B·· - \\·B·B· - \\B·W·B - \\·W·W· - \\··W·· - .replace_each( - "·", - " ", - ) + \\ B + \\ B B + \\B W B + \\ W W + \\ W + result = board->territory({ x: 5, y: 1 }) result.is_err() } @@ -145,27 +112,24 @@ expect { # Invalid because Y is too high for 5x5 board expect { board = - \\··B·· - \\·B·B· - \\B·W·B - \\·W·W· - \\··W·· - .replace_each( - "·", - " ", - ) + \\ B + \\ B B + \\B W B + \\ W W + \\ W + result = board->territory({ x: 1, y: 5 }) result.is_err() } # One territory is the whole board expect { - board = "·".replace_each("·", " ") + board = " " result = board->territories() expected = Ok( { - black: Set.empty({}), - white: Set.empty({}), + black: Set.empty(), + white: Set.empty(), none: Set.from_list( [ { x: 0, y: 0 }, @@ -179,12 +143,9 @@ expect { # Two territory rectangular board expect { board = - \\·BW· - \\·BW· - .replace_each( - "·", - " ", - ) + \\ BW + \\ BW + result = board->territories() expected = Ok( { @@ -200,7 +161,7 @@ expect { { x: 3, y: 1 }, ], ), - none: Set.empty({}), + none: Set.empty(), }, ) result->compare_territories(expected) @@ -208,7 +169,7 @@ expect { # Two region rectangular board expect { - board = "·B·".replace_each("·", " ") + board = " B " result = board->territories() expected = Ok( { @@ -218,13 +179,31 @@ expect { { x: 2, y: 0 }, ], ), - white: Set.empty({}), - none: Set.empty({}), + white: Set.empty(), + none: Set.empty(), }, ) result->compare_territories(expected) } +## The following two comparison functions are temporary workarounds for Roc issue #7144: +## comparing tags or records containing sets sometimes returns the wrong result +## depending on the internal order of the set data, so we have to unwrap the sets +## in order to compare them properly. +compare_territory = |maybe_result, maybe_expected| { + match (maybe_result, maybe_expected) { + (Ok(result), Ok(expected)) => result.owner == expected.owner and result.territory == expected.territory + _ => Bool.False + } +} + +compare_territories = |maybe_result, maybe_expected| { + match (maybe_result, maybe_expected) { + (Ok(result), Ok(expected)) => result.black == expected.black and result.white == expected.white and result.none == expected.none + _ => Bool.False + } +} + # This program is only used to run tests with `roc test`, so main! does nothing. main! = |_args| { Ok({}) From 993b377d79cce82bece19b56e43aa7dedeef4da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:23:12 +1200 Subject: [PATCH 136/162] Clean up circular-buffer test formatting --- .../circular-buffer/circular-buffer-test.roc | 118 +++++------------- 1 file changed, 30 insertions(+), 88 deletions(-) diff --git a/exercises/practice/circular-buffer/circular-buffer-test.roc b/exercises/practice/circular-buffer/circular-buffer-test.roc index c4cbf21a..926730a8 100644 --- a/exercises/practice/circular-buffer/circular-buffer-test.roc +++ b/exercises/practice/circular-buffer/circular-buffer-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/circular-buffer/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import CircularBuffer @@ -20,9 +20,7 @@ expect { # can read an item just written expect { _result = CircularBuffer.create({ capacity: 1 }) - .write( - 1, - )? + .write(1)? .read()? ->expect_value(1) @@ -32,9 +30,7 @@ expect { # each item may only be read once expect { result = CircularBuffer.create({ capacity: 1 }) - .write( - 1, - )? + .write(1)? .read()? ->expect_value(1) .read() @@ -45,12 +41,8 @@ expect { # items are read in the order they are written expect { _result = CircularBuffer.create({ capacity: 2 }) - .write( - 1, - )? - .write( - 2, - )? + .write(1)? + .write(2)? .read()? ->expect_value(1) .read()? @@ -62,12 +54,8 @@ expect { # full buffer can't be written to expect { result = CircularBuffer.create({ capacity: 1 }) - .write( - 1, - )? - .write( - 2, - ) + .write(1)? + .write(2) result == Err(BufferFull) } @@ -75,14 +63,10 @@ expect { # a read frees up capacity for another write expect { _result = CircularBuffer.create({ capacity: 1 }) - .write( - 1, - )? + .write(1)? .read()? ->expect_value(1) - .write( - 2, - )? + .write(2)? .read()? ->expect_value(2) @@ -92,17 +76,11 @@ expect { # read position is maintained even across multiple writes expect { _result = CircularBuffer.create({ capacity: 3 }) - .write( - 1, - )? - .write( - 2, - )? + .write(1)? + .write(2)? .read()? ->expect_value(1) - .write( - 3, - )? + .write(3)? .read()? ->expect_value(2) .read()? @@ -114,9 +92,7 @@ expect { # items cleared out of buffer can't be read expect { result = CircularBuffer.create({ capacity: 1 }) - .write( - 1, - )? + .write(1)? .clear() .read() @@ -126,13 +102,9 @@ expect { # clear frees up capacity for another write expect { _result = CircularBuffer.create({ capacity: 1 }) - .write( - 1, - )? + .write(1)? .clear() - .write( - 2, - )? + .write(2)? .read()? ->expect_value(2) @@ -143,9 +115,7 @@ expect { expect { _result = CircularBuffer.create({ capacity: 1 }) .clear() - .write( - 1, - )? + .write(1)? .read()? ->expect_value(1) @@ -155,12 +125,8 @@ expect { # overwrite acts like write on non-full buffer expect { _result = CircularBuffer.create({ capacity: 2 }) - .write( - 1, - )? - .overwrite( - 2, - ) + .write(1)? + .overwrite(2) .read()? ->expect_value(1) .read()? @@ -172,15 +138,9 @@ expect { # overwrite replaces the oldest item on full buffer expect { _result = CircularBuffer.create({ capacity: 2 }) - .write( - 1, - )? - .write( - 2, - )? - .overwrite( - 3, - ) + .write(1)? + .write(2)? + .overwrite(3) .read()? ->expect_value(2) .read()? @@ -192,23 +152,13 @@ expect { # overwrite replaces the oldest item remaining in buffer following a read expect { _result = CircularBuffer.create({ capacity: 3 }) - .write( - 1, - )? - .write( - 2, - )? - .write( - 3, - )? + .write(1)? + .write(2)? + .write(3)? .read()? ->expect_value(1) - .write( - 4, - )? - .overwrite( - 5, - ) + .write(4)? + .overwrite(5) .read()? ->expect_value(3) .read()? @@ -223,18 +173,10 @@ expect { expect { result = CircularBuffer.create({ capacity: 2 }) .clear() - .write( - 1, - )? - .write( - 2, - )? - .overwrite( - 3, - ) - .overwrite( - 4, - ) + .write(1)? + .write(2)? + .overwrite(3) + .overwrite(4) .read()? ->expect_value(3) .read()? From 6a6f3367c9443ba3ed98b2580657e9b25d68c3d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:23:52 +1200 Subject: [PATCH 137/162] Add new tests in 'connect' exercise --- exercises/practice/connect/connect-test.roc | 26 ++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/exercises/practice/connect/connect-test.roc b/exercises/practice/connect/connect-test.roc index 174206da..0bc75a0f 100644 --- a/exercises/practice/connect/connect-test.roc +++ b/exercises/practice/connect/connect-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/connect/canonical-data.json -# File last updated on 2026-06-14 +# File last updated on 2026-06-22 import Connect exposing [winner] @@ -82,6 +82,30 @@ expect { result == Ok(PlayerX) } +# X wins with left-hand dead end fork +expect { + board = + \\. . X . + \\ X X . . + \\ . X X X + \\ O O O O + + result = board->winner() + result == Ok(PlayerX) +} + +# X wins with right-hand dead end fork +expect { + board = + \\. . X X + \\ X X . . + \\ . X X . + \\ O O O O + + result = board->winner() + result == Ok(PlayerX) +} + # O wins crossing from top to bottom expect { board = From cf39c757bc9e8c17afeb0c5c062dae6161907c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:24:39 +1200 Subject: [PATCH 138/162] Update flower-field tests (adds a new test) --- exercises/practice/flower-field/flower-field-test.roc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/exercises/practice/flower-field/flower-field-test.roc b/exercises/practice/flower-field/flower-field-test.roc index 28644463..e6b7a910 100644 --- a/exercises/practice/flower-field/flower-field-test.roc +++ b/exercises/practice/flower-field/flower-field-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/flower-field/canonical-data.json -# File last updated on 2026-06-20 +# File last updated on 2026-06-22 import FlowerField exposing [annotate] @@ -182,6 +182,14 @@ expect { result == expected } +# multiple adjacent flowers +expect { + garden = " ** " + result = annotate(garden) + expected = "1**1" + result == expected +} + # This program is only used to run tests with `roc test`, so main! does nothing. main! = |_args| { Ok({}) From f9be238e60be42e9753461245c7064fb0c653ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:25:22 +1200 Subject: [PATCH 139/162] Update isbn-verifier tests (adds two tests) --- .../practice/isbn-verifier/isbn-verifier-test.roc | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/exercises/practice/isbn-verifier/isbn-verifier-test.roc b/exercises/practice/isbn-verifier/isbn-verifier-test.roc index ad757d02..a6dff87a 100644 --- a/exercises/practice/isbn-verifier/isbn-verifier-test.roc +++ b/exercises/practice/isbn-verifier/isbn-verifier-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/isbn-verifier/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import IsbnVerifier exposing [is_valid] @@ -46,6 +46,18 @@ expect { result == Bool.False } +# only one check digit is allowed +expect { + result = is_valid("3-598-21508-96") + result == Bool.False +} + +# X is not substituted by the value 10 +expect { + result = is_valid("3-598-2X507-5") + result == Bool.False +} + # valid isbn without separating dashes expect { result = is_valid("3598215088") From 7d749f0e6055e8b3d61d8cd7d32c7e40515c772e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:26:13 +1200 Subject: [PATCH 140/162] Update perfect-numbers tests (adds one test) --- .../practice/perfect-numbers/perfect-numbers-test.roc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/exercises/practice/perfect-numbers/perfect-numbers-test.roc b/exercises/practice/perfect-numbers/perfect-numbers-test.roc index d5b308f5..c6060e48 100644 --- a/exercises/practice/perfect-numbers/perfect-numbers-test.roc +++ b/exercises/practice/perfect-numbers/perfect-numbers-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/perfect-numbers/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import PerfectNumbers exposing [classify] @@ -48,6 +48,12 @@ expect { result == Ok(Abundant) } +# Perfect square abundant number is classified correctly +expect { + result = classify(196) + result == Ok(Abundant) +} + ## ## Deficient numbers ## From 78818dc45a60471c33f443b10a8d5de4c0bb9b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:33:21 +1200 Subject: [PATCH 141/162] Regenerated tests --- exercises/practice/accumulate/accumulate-test.roc | 2 +- exercises/practice/acronym/acronym-test.roc | 2 +- exercises/practice/affine-cipher/affine-cipher-test.roc | 2 +- exercises/practice/all-your-base/all-your-base-test.roc | 2 +- exercises/practice/allergies/allergies-test.roc | 2 +- exercises/practice/alphametics/alphametics-test.roc | 2 +- exercises/practice/armstrong-numbers/armstrong-numbers-test.roc | 2 +- exercises/practice/atbash-cipher/atbash-cipher-test.roc | 2 +- .../practice/binary-search-tree/binary-search-tree-test.roc | 2 +- exercises/practice/binary-search/binary-search-test.roc | 2 +- exercises/practice/binary/binary-test.roc | 2 +- exercises/practice/bob/bob-test.roc | 2 +- exercises/practice/bowling/bowling-test.roc | 2 +- exercises/practice/change/change-test.roc | 2 +- exercises/practice/clock/clock-test.roc | 2 +- .../practice/collatz-conjecture/collatz-conjecture-test.roc | 2 +- exercises/practice/complex-numbers/complex-numbers-test.roc | 2 +- exercises/practice/crypto-square/crypto-square-test.roc | 2 +- exercises/practice/custom-set/custom-set-test.roc | 2 +- exercises/practice/darts/darts-test.roc | 2 +- exercises/practice/diamond/diamond-test.roc | 2 +- .../difference-of-squares/difference-of-squares-test.roc | 2 +- exercises/practice/dominoes/dominoes-test.roc | 2 +- exercises/practice/eliuds-eggs/eliuds-eggs-test.roc | 2 +- exercises/practice/etl/etl-test.roc | 2 +- exercises/practice/flatten-array/flatten-array-test.roc | 2 +- exercises/practice/food-chain/food-chain-test.roc | 2 +- exercises/practice/grains/grains-test.roc | 2 +- exercises/practice/grep/grep-test.roc | 2 +- exercises/practice/hamming/hamming-test.roc | 2 +- exercises/practice/hello-world/hello-world-test.roc | 2 +- exercises/practice/high-scores/high-scores-test.roc | 2 +- exercises/practice/house/house-test.roc | 2 +- exercises/practice/isogram/isogram-test.roc | 2 +- .../practice/killer-sudoku-helper/killer-sudoku-helper-test.roc | 2 +- .../practice/kindergarten-garden/kindergarten-garden-test.roc | 2 +- exercises/practice/knapsack/knapsack-test.roc | 2 +- .../largest-series-product/largest-series-product-test.roc | 2 +- exercises/practice/leap/leap-test.roc | 2 +- exercises/practice/list-ops/list-ops-test.roc | 2 +- exercises/practice/luhn/luhn-test.roc | 2 +- exercises/practice/matching-brackets/matching-brackets-test.roc | 2 +- exercises/practice/matrix/matrix-test.roc | 2 +- exercises/practice/minesweeper/minesweeper-test.roc | 2 +- exercises/practice/nucleotide-count/nucleotide-count-test.roc | 2 +- exercises/practice/ocr-numbers/ocr-numbers-test.roc | 2 +- .../practice/palindrome-products/palindrome-products-test.roc | 2 +- exercises/practice/pangram/pangram-test.roc | 2 +- exercises/practice/pascals-triangle/pascals-triangle-test.roc | 2 +- exercises/practice/phone-number/phone-number-test.roc | 2 +- exercises/practice/pig-latin/pig-latin-test.roc | 2 +- exercises/practice/prime-factors/prime-factors-test.roc | 2 +- exercises/practice/proverb/proverb-test.roc | 2 +- .../practice/pythagorean-triplet/pythagorean-triplet-test.roc | 2 +- exercises/practice/queen-attack/queen-attack-test.roc | 2 +- exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc | 2 +- exercises/practice/raindrops/raindrops-test.roc | 2 +- exercises/practice/rectangles/rectangles-test.roc | 2 +- .../practice/resistor-color-duo/resistor-color-duo-test.roc | 2 +- exercises/practice/resistor-color/resistor-color-test.roc | 2 +- exercises/practice/rna-transcription/rna-transcription-test.roc | 2 +- exercises/practice/robot-simulator/robot-simulator-test.roc | 2 +- exercises/practice/roman-numerals/roman-numerals-test.roc | 2 +- exercises/practice/rotational-cipher/rotational-cipher-test.roc | 2 +- .../practice/run-length-encoding/run-length-encoding-test.roc | 2 +- exercises/practice/saddle-points/saddle-points-test.roc | 2 +- exercises/practice/say/say-test.roc | 2 +- exercises/practice/scrabble-score/scrabble-score-test.roc | 2 +- exercises/practice/secret-handshake/secret-handshake-test.roc | 2 +- exercises/practice/series/series-test.roc | 2 +- exercises/practice/sieve/sieve-test.roc | 2 +- exercises/practice/space-age/space-age-test.roc | 2 +- exercises/practice/spiral-matrix/spiral-matrix-test.roc | 2 +- exercises/practice/square-root/square-root-test.roc | 2 +- exercises/practice/strain/strain-test.roc | 2 +- exercises/practice/sublist/sublist-test.roc | 2 +- exercises/practice/sum-of-multiples/sum-of-multiples-test.roc | 2 +- exercises/practice/transpose/transpose-test.roc | 2 +- exercises/practice/triangle/triangle-test.roc | 2 +- exercises/practice/two-bucket/two-bucket-test.roc | 2 +- exercises/practice/two-fer/two-fer-test.roc | 2 +- .../variable-length-quantity/variable-length-quantity-test.roc | 2 +- exercises/practice/word-count/word-count-test.roc | 2 +- exercises/practice/word-search/word-search-test.roc | 2 +- exercises/practice/wordy/wordy-test.roc | 2 +- exercises/practice/yacht/yacht-test.roc | 2 +- 86 files changed, 86 insertions(+), 86 deletions(-) diff --git a/exercises/practice/accumulate/accumulate-test.roc b/exercises/practice/accumulate/accumulate-test.roc index 83637502..915fc8fc 100644 --- a/exercises/practice/accumulate/accumulate-test.roc +++ b/exercises/practice/accumulate/accumulate-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/accumulate/canonical-data.json -# File last updated on 2026-06-21 +# File last updated on 2026-06-22 import Accumulate exposing [accumulate] diff --git a/exercises/practice/acronym/acronym-test.roc b/exercises/practice/acronym/acronym-test.roc index 78fb3962..66b60b56 100644 --- a/exercises/practice/acronym/acronym-test.roc +++ b/exercises/practice/acronym/acronym-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/acronym/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Acronym exposing [abbreviate] diff --git a/exercises/practice/affine-cipher/affine-cipher-test.roc b/exercises/practice/affine-cipher/affine-cipher-test.roc index feee6eb0..0a6d57f3 100644 --- a/exercises/practice/affine-cipher/affine-cipher-test.roc +++ b/exercises/practice/affine-cipher/affine-cipher-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/affine-cipher/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import AffineCipher diff --git a/exercises/practice/all-your-base/all-your-base-test.roc b/exercises/practice/all-your-base/all-your-base-test.roc index e7524f8c..622dff25 100644 --- a/exercises/practice/all-your-base/all-your-base-test.roc +++ b/exercises/practice/all-your-base/all-your-base-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/all-your-base/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import AllYourBase exposing [rebase] diff --git a/exercises/practice/allergies/allergies-test.roc b/exercises/practice/allergies/allergies-test.roc index b176d198..580241ec 100644 --- a/exercises/practice/allergies/allergies-test.roc +++ b/exercises/practice/allergies/allergies-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/allergies/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Allergies exposing [allergic_to, set] diff --git a/exercises/practice/alphametics/alphametics-test.roc b/exercises/practice/alphametics/alphametics-test.roc index 8c6b7f14..46f54a77 100644 --- a/exercises/practice/alphametics/alphametics-test.roc +++ b/exercises/practice/alphametics/alphametics-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/alphametics/canonical-data.json -# File last updated on 2026-06-21 +# File last updated on 2026-06-22 import Alphametics exposing [solve] diff --git a/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc b/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc index 5f8cd885..ec2d62df 100644 --- a/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc +++ b/exercises/practice/armstrong-numbers/armstrong-numbers-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/armstrong-numbers/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import ArmstrongNumbers exposing [is_armstrong_number] diff --git a/exercises/practice/atbash-cipher/atbash-cipher-test.roc b/exercises/practice/atbash-cipher/atbash-cipher-test.roc index e28ddab6..03b819e8 100644 --- a/exercises/practice/atbash-cipher/atbash-cipher-test.roc +++ b/exercises/practice/atbash-cipher/atbash-cipher-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/atbash-cipher/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import AtbashCipher exposing [encode, decode] diff --git a/exercises/practice/binary-search-tree/binary-search-tree-test.roc b/exercises/practice/binary-search-tree/binary-search-tree-test.roc index ba93ff83..bf144964 100644 --- a/exercises/practice/binary-search-tree/binary-search-tree-test.roc +++ b/exercises/practice/binary-search-tree/binary-search-tree-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search-tree/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import BinarySearchTree exposing [from_list, to_list] diff --git a/exercises/practice/binary-search/binary-search-test.roc b/exercises/practice/binary-search/binary-search-test.roc index 72c54377..412a2d34 100644 --- a/exercises/practice/binary-search/binary-search-test.roc +++ b/exercises/practice/binary-search/binary-search-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary-search/canonical-data.json -# File last updated on 2026-06-21 +# File last updated on 2026-06-22 import BinarySearch exposing [find] diff --git a/exercises/practice/binary/binary-test.roc b/exercises/practice/binary/binary-test.roc index cea81d5a..a4090e9d 100644 --- a/exercises/practice/binary/binary-test.roc +++ b/exercises/practice/binary/binary-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/binary/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Binary exposing [decimal] diff --git a/exercises/practice/bob/bob-test.roc b/exercises/practice/bob/bob-test.roc index 6c366073..a59f214e 100644 --- a/exercises/practice/bob/bob-test.roc +++ b/exercises/practice/bob/bob-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bob/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Bob exposing [response] diff --git a/exercises/practice/bowling/bowling-test.roc b/exercises/practice/bowling/bowling-test.roc index 2733c827..5fc170d1 100644 --- a/exercises/practice/bowling/bowling-test.roc +++ b/exercises/practice/bowling/bowling-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bowling/canonical-data.json -# File last updated on 2026-06-21 +# File last updated on 2026-06-22 import Bowling diff --git a/exercises/practice/change/change-test.roc b/exercises/practice/change/change-test.roc index 535d321e..b7d6bfab 100644 --- a/exercises/practice/change/change-test.roc +++ b/exercises/practice/change/change-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json -# File last updated on 2026-06-21 +# File last updated on 2026-06-22 import Change exposing [find_fewest_coins] diff --git a/exercises/practice/clock/clock-test.roc b/exercises/practice/clock/clock-test.roc index 08eab107..2fa44427 100644 --- a/exercises/practice/clock/clock-test.roc +++ b/exercises/practice/clock/clock-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/clock/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Clock exposing [create, add, subtract, to_str] diff --git a/exercises/practice/collatz-conjecture/collatz-conjecture-test.roc b/exercises/practice/collatz-conjecture/collatz-conjecture-test.roc index 157ccb1f..b696f35d 100644 --- a/exercises/practice/collatz-conjecture/collatz-conjecture-test.roc +++ b/exercises/practice/collatz-conjecture/collatz-conjecture-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/collatz-conjecture/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import CollatzConjecture exposing [steps] diff --git a/exercises/practice/complex-numbers/complex-numbers-test.roc b/exercises/practice/complex-numbers/complex-numbers-test.roc index 1bbbd102..8e501ab7 100644 --- a/exercises/practice/complex-numbers/complex-numbers-test.roc +++ b/exercises/practice/complex-numbers/complex-numbers-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json -# File last updated on 2026-06-21 +# File last updated on 2026-06-22 import ComplexNumbers exposing [Complex, real, imaginary, add, sub, mul, div, conjugate, abs, exp] diff --git a/exercises/practice/crypto-square/crypto-square-test.roc b/exercises/practice/crypto-square/crypto-square-test.roc index 2b72bb1c..abde0ee0 100644 --- a/exercises/practice/crypto-square/crypto-square-test.roc +++ b/exercises/practice/crypto-square/crypto-square-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/crypto-square/canonical-data.json -# File last updated on 2026-06-14 +# File last updated on 2026-06-22 import CryptoSquare exposing [ciphertext] diff --git a/exercises/practice/custom-set/custom-set-test.roc b/exercises/practice/custom-set/custom-set-test.roc index f4bd2b11..076d7613 100644 --- a/exercises/practice/custom-set/custom-set-test.roc +++ b/exercises/practice/custom-set/custom-set-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json -# File last updated on 2026-06-20 +# File last updated on 2026-06-22 import CustomSet diff --git a/exercises/practice/darts/darts-test.roc b/exercises/practice/darts/darts-test.roc index a0659bc0..7e6a4557 100644 --- a/exercises/practice/darts/darts-test.roc +++ b/exercises/practice/darts/darts-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/darts/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Darts exposing [score] diff --git a/exercises/practice/diamond/diamond-test.roc b/exercises/practice/diamond/diamond-test.roc index 8eaac858..8ad5ccb4 100644 --- a/exercises/practice/diamond/diamond-test.roc +++ b/exercises/practice/diamond/diamond-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/diamond/canonical-data.json -# File last updated on 2026-06-18 +# File last updated on 2026-06-22 import Diamond exposing [diamond] diff --git a/exercises/practice/difference-of-squares/difference-of-squares-test.roc b/exercises/practice/difference-of-squares/difference-of-squares-test.roc index 4681fa2b..7ee9a23c 100644 --- a/exercises/practice/difference-of-squares/difference-of-squares-test.roc +++ b/exercises/practice/difference-of-squares/difference-of-squares-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/difference-of-squares/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import DifferenceOfSquares exposing [square_of_sum, sum_of_squares, difference_of_squares] diff --git a/exercises/practice/dominoes/dominoes-test.roc b/exercises/practice/dominoes/dominoes-test.roc index 3d5caad5..4401433e 100644 --- a/exercises/practice/dominoes/dominoes-test.roc +++ b/exercises/practice/dominoes/dominoes-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/dominoes/canonical-data.json -# File last updated on 2026-06-19 +# File last updated on 2026-06-22 import Dominoes exposing [Domino, find_chain] diff --git a/exercises/practice/eliuds-eggs/eliuds-eggs-test.roc b/exercises/practice/eliuds-eggs/eliuds-eggs-test.roc index 447ddb26..a6e0660b 100644 --- a/exercises/practice/eliuds-eggs/eliuds-eggs-test.roc +++ b/exercises/practice/eliuds-eggs/eliuds-eggs-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/eliuds-eggs/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import EliudsEggs exposing [egg_count] diff --git a/exercises/practice/etl/etl-test.roc b/exercises/practice/etl/etl-test.roc index f1d3ff2f..44f09180 100644 --- a/exercises/practice/etl/etl-test.roc +++ b/exercises/practice/etl/etl-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/etl/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Etl exposing [transform] diff --git a/exercises/practice/flatten-array/flatten-array-test.roc b/exercises/practice/flatten-array/flatten-array-test.roc index 88364893..b9d1fad9 100644 --- a/exercises/practice/flatten-array/flatten-array-test.roc +++ b/exercises/practice/flatten-array/flatten-array-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/flatten-array/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import FlattenArray exposing [flatten] diff --git a/exercises/practice/food-chain/food-chain-test.roc b/exercises/practice/food-chain/food-chain-test.roc index 354c730f..0ed657f6 100644 --- a/exercises/practice/food-chain/food-chain-test.roc +++ b/exercises/practice/food-chain/food-chain-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/food-chain/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import FoodChain exposing [recite] diff --git a/exercises/practice/grains/grains-test.roc b/exercises/practice/grains/grains-test.roc index cb7419e7..9c4f95e2 100644 --- a/exercises/practice/grains/grains-test.roc +++ b/exercises/practice/grains/grains-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grains/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Grains exposing [grains_on_square, total_grains] diff --git a/exercises/practice/grep/grep-test.roc b/exercises/practice/grep/grep-test.roc index c4896a5e..57f91018 100644 --- a/exercises/practice/grep/grep-test.roc +++ b/exercises/practice/grep/grep-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/grep/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-21 import Grep exposing [grep] diff --git a/exercises/practice/hamming/hamming-test.roc b/exercises/practice/hamming/hamming-test.roc index 5ffc80b2..6e1d81d7 100644 --- a/exercises/practice/hamming/hamming-test.roc +++ b/exercises/practice/hamming/hamming-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/hamming/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Hamming exposing [distance] diff --git a/exercises/practice/hello-world/hello-world-test.roc b/exercises/practice/hello-world/hello-world-test.roc index 28d66ce9..c7eb72a4 100644 --- a/exercises/practice/hello-world/hello-world-test.roc +++ b/exercises/practice/hello-world/hello-world-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/hello-world/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import HelloWorld exposing [hello] diff --git a/exercises/practice/high-scores/high-scores-test.roc b/exercises/practice/high-scores/high-scores-test.roc index 55ce86b4..c5e21867 100644 --- a/exercises/practice/high-scores/high-scores-test.roc +++ b/exercises/practice/high-scores/high-scores-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/high-scores/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import HighScores exposing [latest, personal_best, personal_top_three] diff --git a/exercises/practice/house/house-test.roc b/exercises/practice/house/house-test.roc index 03225f74..364edc22 100644 --- a/exercises/practice/house/house-test.roc +++ b/exercises/practice/house/house-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/house/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import House exposing [recite] diff --git a/exercises/practice/isogram/isogram-test.roc b/exercises/practice/isogram/isogram-test.roc index 5753510b..7c1710ed 100644 --- a/exercises/practice/isogram/isogram-test.roc +++ b/exercises/practice/isogram/isogram-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/isogram/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Isogram exposing [is_isogram] diff --git a/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc index c07be02b..100c4b65 100644 --- a/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc +++ b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/killer-sudoku-helper/canonical-data.json -# File last updated on 2026-06-18 +# File last updated on 2026-06-22 import KillerSudokuHelper exposing [combinations] diff --git a/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc b/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc index 5410eefc..d190ae54 100644 --- a/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc +++ b/exercises/practice/kindergarten-garden/kindergarten-garden-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/kindergarten-garden/canonical-data.json -# File last updated on 2026-06-18 +# File last updated on 2026-06-22 import KindergartenGarden exposing [plants] diff --git a/exercises/practice/knapsack/knapsack-test.roc b/exercises/practice/knapsack/knapsack-test.roc index e19011cb..1c7ae31a 100644 --- a/exercises/practice/knapsack/knapsack-test.roc +++ b/exercises/practice/knapsack/knapsack-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/knapsack/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Knapsack exposing [maximum_value] diff --git a/exercises/practice/largest-series-product/largest-series-product-test.roc b/exercises/practice/largest-series-product/largest-series-product-test.roc index 6cd6c929..d77b948d 100644 --- a/exercises/practice/largest-series-product/largest-series-product-test.roc +++ b/exercises/practice/largest-series-product/largest-series-product-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/largest-series-product/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import LargestSeriesProduct exposing [largest_product] diff --git a/exercises/practice/leap/leap-test.roc b/exercises/practice/leap/leap-test.roc index 5a79aaeb..67a5c22e 100644 --- a/exercises/practice/leap/leap-test.roc +++ b/exercises/practice/leap/leap-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/leap/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Leap exposing [is_leap_year] diff --git a/exercises/practice/list-ops/list-ops-test.roc b/exercises/practice/list-ops/list-ops-test.roc index 0f8f3775..1b835464 100644 --- a/exercises/practice/list-ops/list-ops-test.roc +++ b/exercises/practice/list-ops/list-ops-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/list-ops/canonical-data.json -# File last updated on 2026-06-20 +# File last updated on 2026-06-22 import ListOps exposing [concat, join, filter, len, map, fold, fold_rev, reverse] diff --git a/exercises/practice/luhn/luhn-test.roc b/exercises/practice/luhn/luhn-test.roc index 4a7193c2..10a0f0b2 100644 --- a/exercises/practice/luhn/luhn-test.roc +++ b/exercises/practice/luhn/luhn-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/luhn/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Luhn exposing [valid] diff --git a/exercises/practice/matching-brackets/matching-brackets-test.roc b/exercises/practice/matching-brackets/matching-brackets-test.roc index 07fb20b2..e8c237da 100644 --- a/exercises/practice/matching-brackets/matching-brackets-test.roc +++ b/exercises/practice/matching-brackets/matching-brackets-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/matching-brackets/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import MatchingBrackets exposing [is_paired] diff --git a/exercises/practice/matrix/matrix-test.roc b/exercises/practice/matrix/matrix-test.roc index 990fbcbf..c0d56e04 100644 --- a/exercises/practice/matrix/matrix-test.roc +++ b/exercises/practice/matrix/matrix-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/matrix/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Matrix exposing [row, column] diff --git a/exercises/practice/minesweeper/minesweeper-test.roc b/exercises/practice/minesweeper/minesweeper-test.roc index 3e531d32..412ba338 100644 --- a/exercises/practice/minesweeper/minesweeper-test.roc +++ b/exercises/practice/minesweeper/minesweeper-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/minesweeper/canonical-data.json -# File last updated on 2026-06-20 +# File last updated on 2026-06-22 import Minesweeper exposing [annotate] diff --git a/exercises/practice/nucleotide-count/nucleotide-count-test.roc b/exercises/practice/nucleotide-count/nucleotide-count-test.roc index f9ab1aee..f38fa125 100644 --- a/exercises/practice/nucleotide-count/nucleotide-count-test.roc +++ b/exercises/practice/nucleotide-count/nucleotide-count-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/nucleotide-count/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import NucleotideCount exposing [nucleotide_counts] diff --git a/exercises/practice/ocr-numbers/ocr-numbers-test.roc b/exercises/practice/ocr-numbers/ocr-numbers-test.roc index 7b1ef617..29941e49 100644 --- a/exercises/practice/ocr-numbers/ocr-numbers-test.roc +++ b/exercises/practice/ocr-numbers/ocr-numbers-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/ocr-numbers/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import OcrNumbers exposing [convert] diff --git a/exercises/practice/palindrome-products/palindrome-products-test.roc b/exercises/practice/palindrome-products/palindrome-products-test.roc index d21cc8ca..0b54925d 100644 --- a/exercises/practice/palindrome-products/palindrome-products-test.roc +++ b/exercises/practice/palindrome-products/palindrome-products-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/palindrome-products/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import PalindromeProducts exposing [smallest, largest] diff --git a/exercises/practice/pangram/pangram-test.roc b/exercises/practice/pangram/pangram-test.roc index c6c855f7..d61ce420 100644 --- a/exercises/practice/pangram/pangram-test.roc +++ b/exercises/practice/pangram/pangram-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pangram/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Pangram exposing [is_pangram] diff --git a/exercises/practice/pascals-triangle/pascals-triangle-test.roc b/exercises/practice/pascals-triangle/pascals-triangle-test.roc index a882a64d..795e34d0 100644 --- a/exercises/practice/pascals-triangle/pascals-triangle-test.roc +++ b/exercises/practice/pascals-triangle/pascals-triangle-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pascals-triangle/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import PascalsTriangle exposing [pascals_triangle] diff --git a/exercises/practice/phone-number/phone-number-test.roc b/exercises/practice/phone-number/phone-number-test.roc index 7514ab4f..6853fd6e 100644 --- a/exercises/practice/phone-number/phone-number-test.roc +++ b/exercises/practice/phone-number/phone-number-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/phone-number/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import PhoneNumber exposing [clean] diff --git a/exercises/practice/pig-latin/pig-latin-test.roc b/exercises/practice/pig-latin/pig-latin-test.roc index 07b15110..ed46c2f0 100644 --- a/exercises/practice/pig-latin/pig-latin-test.roc +++ b/exercises/practice/pig-latin/pig-latin-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pig-latin/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import PigLatin exposing [translate] diff --git a/exercises/practice/prime-factors/prime-factors-test.roc b/exercises/practice/prime-factors/prime-factors-test.roc index 07192ae1..457d1699 100644 --- a/exercises/practice/prime-factors/prime-factors-test.roc +++ b/exercises/practice/prime-factors/prime-factors-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/prime-factors/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import PrimeFactors exposing [prime_factors] diff --git a/exercises/practice/proverb/proverb-test.roc b/exercises/practice/proverb/proverb-test.roc index fd28843b..3c203b86 100644 --- a/exercises/practice/proverb/proverb-test.roc +++ b/exercises/practice/proverb/proverb-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/proverb/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Proverb exposing [recite] diff --git a/exercises/practice/pythagorean-triplet/pythagorean-triplet-test.roc b/exercises/practice/pythagorean-triplet/pythagorean-triplet-test.roc index 32c4547e..0a68c527 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean-triplet-test.roc +++ b/exercises/practice/pythagorean-triplet/pythagorean-triplet-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pythagorean-triplet/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import PythagoreanTriplet exposing [triplets_with_sum] diff --git a/exercises/practice/queen-attack/queen-attack-test.roc b/exercises/practice/queen-attack/queen-attack-test.roc index d67eb9f6..891ab0ae 100644 --- a/exercises/practice/queen-attack/queen-attack-test.roc +++ b/exercises/practice/queen-attack/queen-attack-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/queen-attack/canonical-data.json -# File last updated on 2026-06-18 +# File last updated on 2026-06-22 import QueenAttack create = QueenAttack.Square.create diff --git a/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc b/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc index 2cf5a132..4e952fb8 100644 --- a/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc +++ b/exercises/practice/rail-fence-cipher/rail-fence-cipher-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rail-fence-cipher/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import RailFenceCipher exposing [encode, decode] diff --git a/exercises/practice/raindrops/raindrops-test.roc b/exercises/practice/raindrops/raindrops-test.roc index 512d41a5..c2c73d32 100644 --- a/exercises/practice/raindrops/raindrops-test.roc +++ b/exercises/practice/raindrops/raindrops-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/raindrops/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Raindrops exposing [convert] diff --git a/exercises/practice/rectangles/rectangles-test.roc b/exercises/practice/rectangles/rectangles-test.roc index bfec6746..cf493bdb 100644 --- a/exercises/practice/rectangles/rectangles-test.roc +++ b/exercises/practice/rectangles/rectangles-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rectangles/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Rectangles exposing [rectangles] diff --git a/exercises/practice/resistor-color-duo/resistor-color-duo-test.roc b/exercises/practice/resistor-color-duo/resistor-color-duo-test.roc index 9fd40bb7..be93d99c 100644 --- a/exercises/practice/resistor-color-duo/resistor-color-duo-test.roc +++ b/exercises/practice/resistor-color-duo/resistor-color-duo-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color-duo/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import ResistorColorDuo exposing [value] diff --git a/exercises/practice/resistor-color/resistor-color-test.roc b/exercises/practice/resistor-color/resistor-color-test.roc index 29780682..f43d1945 100644 --- a/exercises/practice/resistor-color/resistor-color-test.roc +++ b/exercises/practice/resistor-color/resistor-color-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/resistor-color/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import ResistorColor exposing [color_code, colors] diff --git a/exercises/practice/rna-transcription/rna-transcription-test.roc b/exercises/practice/rna-transcription/rna-transcription-test.roc index a5e1b6e4..7b1b106e 100644 --- a/exercises/practice/rna-transcription/rna-transcription-test.roc +++ b/exercises/practice/rna-transcription/rna-transcription-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rna-transcription/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import RnaTranscription exposing [to_rna] diff --git a/exercises/practice/robot-simulator/robot-simulator-test.roc b/exercises/practice/robot-simulator/robot-simulator-test.roc index e41e9337..1ac53bc8 100644 --- a/exercises/practice/robot-simulator/robot-simulator-test.roc +++ b/exercises/practice/robot-simulator/robot-simulator-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/robot-simulator/canonical-data.json -# File last updated on 2026-06-18 +# File last updated on 2026-06-22 import RobotSimulator exposing [move] diff --git a/exercises/practice/roman-numerals/roman-numerals-test.roc b/exercises/practice/roman-numerals/roman-numerals-test.roc index c4535740..d68ccf4c 100644 --- a/exercises/practice/roman-numerals/roman-numerals-test.roc +++ b/exercises/practice/roman-numerals/roman-numerals-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/roman-numerals/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import RomanNumerals exposing [roman] diff --git a/exercises/practice/rotational-cipher/rotational-cipher-test.roc b/exercises/practice/rotational-cipher/rotational-cipher-test.roc index 55909d7b..f2942e87 100644 --- a/exercises/practice/rotational-cipher/rotational-cipher-test.roc +++ b/exercises/practice/rotational-cipher/rotational-cipher-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rotational-cipher/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import RotationalCipher exposing [rotate] diff --git a/exercises/practice/run-length-encoding/run-length-encoding-test.roc b/exercises/practice/run-length-encoding/run-length-encoding-test.roc index 9baeab93..7ff16856 100644 --- a/exercises/practice/run-length-encoding/run-length-encoding-test.roc +++ b/exercises/practice/run-length-encoding/run-length-encoding-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/run-length-encoding/canonical-data.json -# File last updated on 2026-06-19 +# File last updated on 2026-06-22 import RunLengthEncoding exposing [encode, decode] diff --git a/exercises/practice/saddle-points/saddle-points-test.roc b/exercises/practice/saddle-points/saddle-points-test.roc index 886f7dd9..f3e7287f 100644 --- a/exercises/practice/saddle-points/saddle-points-test.roc +++ b/exercises/practice/saddle-points/saddle-points-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/saddle-points/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import SaddlePoints exposing [saddle_points] diff --git a/exercises/practice/say/say-test.roc b/exercises/practice/say/say-test.roc index 9c1dfaad..22f38c2b 100644 --- a/exercises/practice/say/say-test.roc +++ b/exercises/practice/say/say-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/say/canonical-data.json -# File last updated on 2026-06-20 +# File last updated on 2026-06-22 import Say exposing [say] diff --git a/exercises/practice/scrabble-score/scrabble-score-test.roc b/exercises/practice/scrabble-score/scrabble-score-test.roc index 6354ce3e..29a4016e 100644 --- a/exercises/practice/scrabble-score/scrabble-score-test.roc +++ b/exercises/practice/scrabble-score/scrabble-score-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/scrabble-score/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import ScrabbleScore exposing [score] diff --git a/exercises/practice/secret-handshake/secret-handshake-test.roc b/exercises/practice/secret-handshake/secret-handshake-test.roc index 83cf9079..9b5472a9 100644 --- a/exercises/practice/secret-handshake/secret-handshake-test.roc +++ b/exercises/practice/secret-handshake/secret-handshake-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/secret-handshake/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import SecretHandshake exposing [commands] diff --git a/exercises/practice/series/series-test.roc b/exercises/practice/series/series-test.roc index fb240bab..7bc99e7d 100644 --- a/exercises/practice/series/series-test.roc +++ b/exercises/practice/series/series-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/series/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Series exposing [slices] diff --git a/exercises/practice/sieve/sieve-test.roc b/exercises/practice/sieve/sieve-test.roc index edeab996..7f8e2e0f 100644 --- a/exercises/practice/sieve/sieve-test.roc +++ b/exercises/practice/sieve/sieve-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sieve/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Sieve exposing [primes] diff --git a/exercises/practice/space-age/space-age-test.roc b/exercises/practice/space-age/space-age-test.roc index e002e050..39cb5d08 100644 --- a/exercises/practice/space-age/space-age-test.roc +++ b/exercises/practice/space-age/space-age-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/space-age/canonical-data.json -# File last updated on 2026-06-18 +# File last updated on 2026-06-22 import SpaceAge exposing [age] diff --git a/exercises/practice/spiral-matrix/spiral-matrix-test.roc b/exercises/practice/spiral-matrix/spiral-matrix-test.roc index 9b750aca..fed93d09 100644 --- a/exercises/practice/spiral-matrix/spiral-matrix-test.roc +++ b/exercises/practice/spiral-matrix/spiral-matrix-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/spiral-matrix/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import SpiralMatrix exposing [spiral_matrix] diff --git a/exercises/practice/square-root/square-root-test.roc b/exercises/practice/square-root/square-root-test.roc index c39903d0..09776269 100644 --- a/exercises/practice/square-root/square-root-test.roc +++ b/exercises/practice/square-root/square-root-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/square-root/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import SquareRoot exposing [square_root] diff --git a/exercises/practice/strain/strain-test.roc b/exercises/practice/strain/strain-test.roc index 876191ba..d8683514 100644 --- a/exercises/practice/strain/strain-test.roc +++ b/exercises/practice/strain/strain-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/strain/canonical-data.json -# File last updated on 2026-06-21 +# File last updated on 2026-06-22 import Strain exposing [keep, discard] diff --git a/exercises/practice/sublist/sublist-test.roc b/exercises/practice/sublist/sublist-test.roc index bc393ec6..15ef6c1f 100644 --- a/exercises/practice/sublist/sublist-test.roc +++ b/exercises/practice/sublist/sublist-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sublist/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Sublist exposing [sublist] diff --git a/exercises/practice/sum-of-multiples/sum-of-multiples-test.roc b/exercises/practice/sum-of-multiples/sum-of-multiples-test.roc index d07104c6..3f607c52 100644 --- a/exercises/practice/sum-of-multiples/sum-of-multiples-test.roc +++ b/exercises/practice/sum-of-multiples/sum-of-multiples-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/sum-of-multiples/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import SumOfMultiples exposing [sum_of_multiples] diff --git a/exercises/practice/transpose/transpose-test.roc b/exercises/practice/transpose/transpose-test.roc index 63b135ca..a98cc6da 100644 --- a/exercises/practice/transpose/transpose-test.roc +++ b/exercises/practice/transpose/transpose-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/transpose/canonical-data.json -# File last updated on 2026-06-18 +# File last updated on 2026-06-22 import Transpose exposing [transpose] diff --git a/exercises/practice/triangle/triangle-test.roc b/exercises/practice/triangle/triangle-test.roc index f5f8a9a9..480cb34d 100644 --- a/exercises/practice/triangle/triangle-test.roc +++ b/exercises/practice/triangle/triangle-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/triangle/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Triangle exposing [is_equilateral, is_isosceles, is_scalene] diff --git a/exercises/practice/two-bucket/two-bucket-test.roc b/exercises/practice/two-bucket/two-bucket-test.roc index e65433df..02bd5acd 100644 --- a/exercises/practice/two-bucket/two-bucket-test.roc +++ b/exercises/practice/two-bucket/two-bucket-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/two-bucket/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import TwoBucket exposing [measure] diff --git a/exercises/practice/two-fer/two-fer-test.roc b/exercises/practice/two-fer/two-fer-test.roc index 064a2771..682bb039 100644 --- a/exercises/practice/two-fer/two-fer-test.roc +++ b/exercises/practice/two-fer/two-fer-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/two-fer/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import TwoFer exposing [two_fer] diff --git a/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc b/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc index 02f28f47..8c82bc05 100644 --- a/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc +++ b/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/variable-length-quantity/canonical-data.json -# File last updated on 2026-06-18 +# File last updated on 2026-06-22 import VariableLengthQuantity exposing [encode, decode] diff --git a/exercises/practice/word-count/word-count-test.roc b/exercises/practice/word-count/word-count-test.roc index 6f9faff1..625beb8b 100644 --- a/exercises/practice/word-count/word-count-test.roc +++ b/exercises/practice/word-count/word-count-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/word-count/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import WordCount exposing [count_words] diff --git a/exercises/practice/word-search/word-search-test.roc b/exercises/practice/word-search/word-search-test.roc index 74cc8f95..2994f1af 100644 --- a/exercises/practice/word-search/word-search-test.roc +++ b/exercises/practice/word-search/word-search-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/word-search/canonical-data.json -# File last updated on 2026-06-20 +# File last updated on 2026-06-22 import WordSearch exposing [search] diff --git a/exercises/practice/wordy/wordy-test.roc b/exercises/practice/wordy/wordy-test.roc index 40f6ad0b..7100ebc3 100644 --- a/exercises/practice/wordy/wordy-test.roc +++ b/exercises/practice/wordy/wordy-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/wordy/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Wordy exposing [answer] diff --git a/exercises/practice/yacht/yacht-test.roc b/exercises/practice/yacht/yacht-test.roc index 66f2f4c0..71cfbe6b 100644 --- a/exercises/practice/yacht/yacht-test.roc +++ b/exercises/practice/yacht/yacht-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/yacht/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import Yacht exposing [score] From 2c25cc8610dcb57d5d463dadf439198361935bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:34:56 +1200 Subject: [PATCH 142/162] Update tests.toml for connect, flower-field, isbn-verifier, perfect-numbers --- exercises/practice/connect/.meta/tests.toml | 6 ++++++ exercises/practice/flower-field/.meta/tests.toml | 3 +++ exercises/practice/isbn-verifier/.meta/tests.toml | 6 ++++++ exercises/practice/perfect-numbers/.meta/tests.toml | 3 +++ 4 files changed, 18 insertions(+) diff --git a/exercises/practice/connect/.meta/tests.toml b/exercises/practice/connect/.meta/tests.toml index 6ada8773..951b87e5 100644 --- a/exercises/practice/connect/.meta/tests.toml +++ b/exercises/practice/connect/.meta/tests.toml @@ -30,6 +30,12 @@ description = "nobody wins crossing adjacent angles" [cd61c143-92f6-4a8d-84d9-cb2b359e226b] description = "X wins crossing from left to right" +[495e33ed-30a9-4012-b46e-d7c4d5fe13c3] +description = "X wins with left-hand dead end fork" + +[ab167ab0-4a98-4d0f-a1c0-e1cddddc3d58] +description = "X wins with right-hand dead end fork" + [73d1eda6-16ab-4460-9904-b5f5dd401d0b] description = "O wins crossing from top to bottom" diff --git a/exercises/practice/flower-field/.meta/tests.toml b/exercises/practice/flower-field/.meta/tests.toml index c2b24fda..965ba8fd 100644 --- a/exercises/practice/flower-field/.meta/tests.toml +++ b/exercises/practice/flower-field/.meta/tests.toml @@ -44,3 +44,6 @@ description = "cross" [dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8] description = "large garden" + +[6e4ac13a-3e43-4728-a2e3-3551d4b1a996] +description = "multiple adjacent flowers" diff --git a/exercises/practice/isbn-verifier/.meta/tests.toml b/exercises/practice/isbn-verifier/.meta/tests.toml index 6d5a8459..17e18d47 100644 --- a/exercises/practice/isbn-verifier/.meta/tests.toml +++ b/exercises/practice/isbn-verifier/.meta/tests.toml @@ -30,6 +30,12 @@ description = "invalid character in isbn is not treated as zero" [28025280-2c39-4092-9719-f3234b89c627] description = "X is only valid as a check digit" +[8005b57f-f194-44ee-88d2-a77ac4142591] +description = "only one check digit is allowed" + +[fdb14c99-4cf8-43c5-b06d-eb1638eff343] +description = "X is not substituted by the value 10" + [f6294e61-7e79-46b3-977b-f48789a4945b] description = "valid isbn without separating dashes" diff --git a/exercises/practice/perfect-numbers/.meta/tests.toml b/exercises/practice/perfect-numbers/.meta/tests.toml index 1dbd4976..96656996 100644 --- a/exercises/practice/perfect-numbers/.meta/tests.toml +++ b/exercises/practice/perfect-numbers/.meta/tests.toml @@ -27,6 +27,9 @@ description = "Abundant numbers -> Medium abundant number is classified correctl [ec7792e6-8786-449c-b005-ce6dd89a772b] description = "Abundant numbers -> Large abundant number is classified correctly" +[05f15b93-849c-45e9-9c7d-1ea131ef7d10] +description = "Abundant numbers -> Perfect square abundant number is classified correctly" + [e610fdc7-2b6e-43c3-a51c-b70fb37413ba] description = "Deficient numbers -> Smallest prime deficient number is classified correctly" From 62d49ca82ccd772b21b9080587cd0903beeeddc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:35:26 +1200 Subject: [PATCH 143/162] Fix type in markdown formatting in list-ops/.docs/instructions.append.md --- exercises/practice/list-ops/.docs/instructions.append.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/list-ops/.docs/instructions.append.md b/exercises/practice/list-ops/.docs/instructions.append.md index b2bbf008..55dec024 100644 --- a/exercises/practice/list-ops/.docs/instructions.append.md +++ b/exercises/practice/list-ops/.docs/instructions.append.md @@ -17,7 +17,7 @@ Because of this, in Roc we use `List.append` often and rarely use `List.prepend` Roc uses slightly different names than the ones listed in the general instructions: -* `concat` instead of `append` +- `concat` instead of `append` - `join` instead of `concatenate` - `len` instead of `length` - `fold` instead of `foldl` From b9ac45b5786b29dbb2a4a44640a51840e96001d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:37:59 +1200 Subject: [PATCH 144/162] Clarify TODO in simple-linked-list-test.roc regarding issue 9743 --- .../practice/simple-linked-list/simple-linked-list-test.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/simple-linked-list/simple-linked-list-test.roc b/exercises/practice/simple-linked-list/simple-linked-list-test.roc index a03a2b65..15e1001e 100644 --- a/exercises/practice/simple-linked-list/simple-linked-list-test.roc +++ b/exercises/practice/simple-linked-list/simple-linked-list-test.roc @@ -87,7 +87,7 @@ expect { ->expect_len(2)? .pop()? ->expect_value(2)? - result2 = # TODO: remove this workaround when the roc bug is fixed. + result2 = # TODO: remove this workaround when issue #9743 is fixed result1 ->expect_len(1)? .pop()? From 5270b94c21990a40a140f26120c216cdeacec36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:42:21 +1200 Subject: [PATCH 145/162] Update exercise to new compiler: grep --- exercises/practice/grep/.meta/Example.roc | 244 +++++++++++++--------- exercises/practice/grep/Grep.roc | 10 +- 2 files changed, 147 insertions(+), 107 deletions(-) diff --git a/exercises/practice/grep/.meta/Example.roc b/exercises/practice/grep/.meta/Example.roc index 52da23d0..53522706 100644 --- a/exercises/practice/grep/.meta/Example.roc +++ b/exercises/practice/grep/.meta/Example.roc @@ -1,117 +1,157 @@ - - import "iliad.txt" as iliad : Str import "midsummer-night.txt" as midsummer_night : Str import "paradise-lost.txt" as paradise_lost : Str + Grep :: {}.{ - grep : Str, List Str, List Str -> Result Str _ - grep = |pattern, flags, file_names| - config = parse_flags(flags)? - files = collect_files(file_names)? - display_file_names = List.len(files) > 1 - List.join_map( - files, - |file| - when find_matches(config, pattern, file.text) is - [] -> [] - _ if config.display_file_names -> [file.name] - matches -> - List.map( - matches, - |{ line, index }| - line_number = - if config.display_line_numbers then - "${index + 1 |> Num.to_str}:" - else - "" - file_name = - if display_file_names then - "${file.name}:" - else - "" - "${file_name}${line_number}${line}", - ), - ) - |> Str.join_with("\n") - |> Ok + grep : Str, List(Str), List(Str) -> Try(Str, _) + grep = |pattern, flags, file_names| { + config = parse_flags(flags)? + files = collect_files(file_names)? + display_file_names = files.len() > 1 + files + ->join_map( + |file| { + match find_matches(config, pattern, file.text) { + [] => [] + _ if config.display_file_names => [file.name] + matches => { + matches.map( + |match_info| { + { line, index } = match_info + line_number = + if config.display_line_numbers { + "${(index + 1).to_str()}:" + } else { + "" + } + file_name = + if display_file_names { + "${file.name}:" + } else { + "" + } + "${file_name}${line_number}${line}" + }, + ) + } + } + }, + ) + ->Str.join_with("\n") + ->Ok() + } } -find_matches : Config, Str, Str -> List { line : Str, index : U64 } -find_matches = |config, pattern, text| - Str.split_on(text, "\n") - |> List.map_with_index( - |line, index| - { line, index }, - ) - |> List.keep_if( - |{ line }| - (line_to_match, pattern_to_match) = - if config.ignore_case then - (to_lower(line), to_lower(pattern)) - else - (line, pattern) +find_matches : Config, Str, Str -> List({ line : Str, index : U64 }) +find_matches = |config, pattern, text| { + text.split_on("\n") + .map_with_index(|line, index| { line, index }) + .keep_if( + |match_info| { + { line, index: _ } = match_info + (line_to_match, pattern_to_match) = + if config.ignore_case { + (to_lower(line), to_lower(pattern)) + } else { + (line, pattern) + } - matches = - if config.match_full_lines then - line_to_match == pattern_to_match - else - Str.contains(line_to_match, pattern_to_match) + matches = + if config.match_full_lines { + line_to_match == pattern_to_match + } else { + line_to_match.contains(pattern_to_match) + } - # Using != is equivalent to xor which inverts `matches` - config.invert_results != matches, - ) + # Using != is equivalent to xor which inverts `matches` + config.invert_results != matches + }, + ) +} to_lower : Str -> Str -to_lower = |str| - Str.to_utf8(str) - |> List.map( - |byte| - if 'A' <= byte and byte <= 'Z' then - byte - 'A' + 'a' - else - byte, - ) - |> Str.from_utf8 - |> Result.with_default("") +to_lower = |str| { + str.to_utf8() + .map( + |byte| { + if 'A' <= byte and byte <= 'Z' { + byte - 'A' + 'a' + } else { + byte + } + }, + ) + ->Str.from_utf8() + ?? "" +} Config : { - display_line_numbers : Bool, - display_file_names : Bool, - ignore_case : Bool, - match_full_lines : Bool, - invert_results : Bool, + display_line_numbers : Bool, + display_file_names : Bool, + ignore_case : Bool, + match_full_lines : Bool, + invert_results : Bool, } -parse_flags : List Str -> Result Config _ -parse_flags = |flags| - default_config = { - display_line_numbers: Bool.False, - display_file_names: Bool.False, - ignore_case: Bool.False, - match_full_lines: Bool.False, - invert_results: Bool.False, - } - List.walk_try( - flags, - default_config, - |config, flag| - when flag is - "-l" -> Ok({ config & display_file_names: Bool.True }) - "-n" -> Ok({ config & display_line_numbers: Bool.True }) - "-i" -> Ok({ config & ignore_case: Bool.True }) - "-x" -> Ok({ config & match_full_lines: Bool.True }) - "-v" -> Ok({ config & invert_results: Bool.True }) - _ -> Err(UnknownFlag(flag)), - ) +parse_flags : List(Str) -> Try(Config, _) +parse_flags = |flags| { + default_config = { + display_line_numbers: Bool.False, + display_file_names: Bool.False, + ignore_case: Bool.False, + match_full_lines: Bool.False, + invert_results: Bool.False, + } + List.fold_until( + flags, + Ok(default_config), + |config_res, flag| { + match config_res { + Ok(config) => { + match flag { + "-l" => Continue(Ok({ ..config, display_file_names: Bool.True })) + "-n" => Continue(Ok({ ..config, display_line_numbers: Bool.True })) + "-i" => Continue(Ok({ ..config, ignore_case: Bool.True })) + "-x" => Continue(Ok({ ..config, match_full_lines: Bool.True })) + "-v" => Continue(Ok({ ..config, invert_results: Bool.True })) + _ => Break(Err(UnknownFlag(flag))) + } + } + Err(err) => Break(Err(err)) + } + }, + ) +} -collect_files : List Str -> Result (List { name : Str, text : Str }) _ -collect_files = |names| - List.map_try( - names, - |name| - when name is - "midsummer-night.txt" -> Ok({ name: "midsummer-night.txt", text: midsummer_night }) - "iliad.txt" -> Ok({ name: "iliad.txt", text: iliad }) - "paradise-lost.txt" -> Ok({ name: "paradise-lost.txt", text: paradise_lost }) - _ -> Err(FileNotFound(name)), - ) +collect_files : List(Str) -> Try(List({ name : Str, text : Str }), _) +collect_files = |names| { + names->map_try( + |name| { + match name { + "midsummer-night.txt" => Ok({ name: "midsummer-night.txt", text: midsummer_night }) + "iliad.txt" => Ok({ name: "iliad.txt", text: iliad }) + "paradise-lost.txt" => Ok({ name: "paradise-lost.txt", text: paradise_lost }) + _ => Err(FileNotFound(name)) + } + }, + ) +} + +# The following functions should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} + +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/grep/Grep.roc b/exercises/practice/grep/Grep.roc index 0bcc4f1e..aa298bad 100644 --- a/exercises/practice/grep/Grep.roc +++ b/exercises/practice/grep/Grep.roc @@ -1,10 +1,10 @@ - - import "iliad.txt" as iliad : Str import "midsummer-night.txt" as midsummer_night : Str import "paradise-lost.txt" as paradise_lost : Str + Grep :: {}.{ - grep : Str, List Str, List Str -> Result Str _ - grep = |pattern, flags, files| - crash("Please implement the 'grep' function") + grep : Str, List(Str), List(Str) -> Try(Str, _) + grep = |pattern, flags, files| { + crash "Please implement the 'grep' function" + } } From 83dd83852cab37657b0f1716ba6e425dc34626b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 17:56:55 +1200 Subject: [PATCH 146/162] Update exercise to new compiler: poker --- exercises/practice/poker/.meta/Example.roc | 318 ++++++++++++++------- exercises/practice/poker/Poker.roc | 7 +- 2 files changed, 214 insertions(+), 111 deletions(-) diff --git a/exercises/practice/poker/.meta/Example.roc b/exercises/practice/poker/.meta/Example.roc index dbde3b88..ceb9420f 100644 --- a/exercises/practice/poker/.meta/Example.roc +++ b/exercises/practice/poker/.meta/Example.roc @@ -1,116 +1,218 @@ Poker :: {}.{ - best_hands : List Str -> Result (List Str) HandParsingError - best_hands = |hands| - parsed_hands = hands |> List.map_try(parse_hand)? - ranks = parsed_hands |> List.map(get_rank) - top_rank = ranks |> List.max |> Result.with_default(0) - List.map2(hands, ranks, |hand, rank| { hand, rank }) - |> List.join_map( - |{ hand, rank }| - if rank == top_rank then [hand] else [], - ) - |> Ok -} + HandParsingError : [InvalidNumberOfCards(U64), CardWasEmpty, InvalidCardValue(List(U8)), InvalidCardSuit(U8)] + best_hands : List(Str) -> Try(List(Str), HandParsingError) + best_hands = |hands| { + parsed_hands = hands->map_try(parse_hand)? + ranks = parsed_hands.map(get_rank) + top_rank = ranks.fold( + 0, + |max_val, val| if val > max_val { + val + } else { + max_val + }, + ) + List.map2(hands, ranks, |hand, rank| { hand, rank }) + ->join_map( + |res| { + { hand, rank } = res + if rank == top_rank { + [hand] + } else { + [] + } + }, + ) + ->Ok() + } +} Value : U8 + Suit : [Spades, Hearts, Diamonds, Clubs] + Card : { value : Value, suit : Suit } -Hand : List Card -HandParsingError : [InvalidNumberOfCards U64, CardWasEmpty, InvalidCardValue (List U8), InvalidCardSuit U8] - -parse_hand : Str -> Result Hand HandParsingError -parse_hand = |hand_str| - cards = hand_str |> Str.split_on(" ") - num_cards = List.len(cards) - if num_cards != 5 then - Err(InvalidNumberOfCards(num_cards)) - else - cards |> List.map_try(parse_card) - -parse_card : Str -> Result Card [CardWasEmpty, InvalidCardValue (List U8), InvalidCardSuit U8] -parse_card = |card_str| - when card_str |> Str.to_utf8 is - [] -> Err(CardWasEmpty) - [.. as value_chars, suit_char] -> - value = parse_value(value_chars)? - suit = parse_suit(suit_char)? - Ok({ value, suit }) - -parse_value : List U8 -> Result Value [InvalidCardValue (List U8)] -parse_value = |chars| - when chars is - [val] if val >= '2' and val <= '9' -> Ok((val - '0')) - ['1', '0'] -> Ok(10) - ['J'] -> Ok(11) - ['Q'] -> Ok(12) - ['K'] -> Ok(13) - ['A'] -> Ok(14) - _ -> Err(InvalidCardValue(chars)) - -parse_suit : U8 -> Result Suit [InvalidCardSuit U8] -parse_suit = |char| - when char is - 'S' -> Ok(Spades) - 'H' -> Ok(Hearts) - 'D' -> Ok(Diamonds) - 'C' -> Ok(Clubs) - _ -> Err(InvalidCardSuit(char)) + +Hand : List(Card) + +parse_hand : Str -> Try(Hand, HandParsingError) +parse_hand = |hand_str| { + cards = hand_str.split_on(" ") + num_cards = cards.len() + if num_cards != 5 { + Err(InvalidNumberOfCards(num_cards)) + } else { + cards->map_try(parse_card) + } +} + +parse_card : Str -> Try(Card, [CardWasEmpty, InvalidCardValue(List(U8)), InvalidCardSuit(U8), ..]) +parse_card = |card_str| { + match card_str.to_utf8() { + [] => Err(CardWasEmpty) + [.. as value_chars, suit_char] => { + value = parse_value(value_chars)? + suit = parse_suit(suit_char)? + Ok({ value, suit }) + } + } +} + +parse_value : List(U8) -> Try(Value, [InvalidCardValue(List(U8)), ..]) +parse_value = |chars| { + match chars { + [val] if val >= '2' and val <= '9' => Ok((val - '0')) + ['1', '0'] => Ok(10) + ['J'] => Ok(11) + ['Q'] => Ok(12) + ['K'] => Ok(13) + ['A'] => Ok(14) + _ => Err(InvalidCardValue(chars)) + } +} + +parse_suit : U8 -> Try(Suit, [InvalidCardSuit(U8), ..]) +parse_suit = |char| { + match char { + 'S' => Ok(Spades) + 'H' => Ok(Hearts) + 'D' => Ok(Diamonds) + 'C' => Ok(Clubs) + _ => Err(InvalidCardSuit(char)) + } +} get_rank : Hand -> U64 -get_rank = |hand| - card_values = hand |> List.map(.value) |> List.map(Num.to_u64) |> List.sort_asc - is_consecutive = - List.map2(card_values, (card_values |> List.take_last(4)), |card1, card2| (card1, card2)) - |> List.all(|(card1, card2)| card2 - card1 == 1) - is_special_straight = card_values == [2, 3, 4, 5, 14] # straight starting with Ace - is_straight = is_consecutive or is_special_straight - is_flush = (hand |> List.map(.suit) |> Set.from_list |> Set.len) == 1 - - value_groups = - # Poker: [4, 4, 4, 7, 7] -> [{size: 3, value: 4}, {size: 2, value: 7}] - card_values - |> List.walk( - List.repeat(0, 13), - |counters, card_value| - counters |> List.update((card_value - 2), |group_size| group_size + 1), - ) - |> List.map_with_index(|counter, value| counter * 13 + value) - |> List.sort_desc - |> List.map(|group_rank| { size: group_rank // 13, value: group_rank % 13 + 2 }) - |> List.drop_if(|{ size }| size == 0) - - group_sizes = value_groups |> List.map(.size) - - category = - if is_flush and is_straight then - 8 # Straight flush - else if group_sizes == [4, 1] then - 7 # Four of a kind - else if group_sizes == [3, 2] then - 6 # Full house - else if is_flush then - 5 # Flush - else if is_straight then - 4 # Straight - else if group_sizes == [3, 1, 1] then - 3 # Three of a kind - else if group_sizes == [2, 2, 1] then - 2 # Two pairs - else if group_sizes == [2, 1, 1, 1] then - 1 # One pair - else - 0 # High card - - rank_within_category = - if is_special_straight then - 0 # the straight starting with an Ace is the smallest straight - else - value_groups - |> List.walk( - 0, - |rank, { value }| - rank * 13 + value - 2, - ) - - category * Num.pow_int(13, 5) + rank_within_category +get_rank = |hand| { + card_values = sort_asc(hand.map(|c| U8.to_u64(c.value))) + is_consecutive = + List.map2(card_values, take_last(card_values, 4), |card1, card2| (card1, card2)) + .all( + |pair| { + (card1, card2) = pair + card2 - card1 == 1 + }, + ) + is_special_straight = card_values == [2, 3, 4, 5, 14] # straight starting with Ace + is_straight = is_consecutive or is_special_straight + is_flush = (hand.map(|c| c.suit)->Set.from_list().len()) == 1 + + value_groups = + card_values + .fold( + List.repeat(0, 13), + |counters, card_value| { + counters.update((card_value - 2), |group_size| group_size + 1) ?? counters + }, + ) + .map_with_index(|counter, value| counter * 13 + value) + ->sort_desc() + .map(|group_rank| { size: group_rank // 13, value: group_rank % 13 + 2 }) + .drop_if( + |group| { + { size, value: _ } = group + size == 0 + }, + ) + + group_sizes = value_groups.map(|g| g.size) + + category = + if is_flush and is_straight { + 8 # Straight flush + } else if group_sizes == [4, 1] { + 7 # Four of a kind + } else if group_sizes == [3, 2] { + 6 # Full house + } else if is_flush { + 5 # Flush + } else if is_straight { + 4 # Straight + } else if group_sizes == [3, 1, 1] { + 3 # Three of a kind + } else if group_sizes == [2, 2, 1] { + 2 # Two pairs + } else if group_sizes == [2, 1, 1, 1] { + 1 # One pair + } else { + 0 # High card + } + + rank_within_category = + if is_special_straight { + 0 # the straight starting with an Ace is the smallest straight + } else { + value_groups + .fold( + 0, + |rank, group| { + { value, size: _ } = group + rank * 13 + value - 2 + }, + ) + } + + category * pow_int(13, 5) + rank_within_category +} + +pow_int : U64, U64 -> U64 +pow_int = |number, pow| { + (1..=pow).fold( + 1, + |acc, _| { + acc * number + }, + ) +} + +sort_asc = |list| { + list.sort_with( + |a, b| { + if a < b { + LT + } else if a > b { + GT + } else { + EQ + } + }, + ) +} + +sort_desc = |list| { + list.sort_with( + |a, b| { + if a > b { + LT + } else if a < b { + GT + } else { + EQ + } + }, + ) +} + +take_last = |list, n| { + list.sublist({ start: list.len() - n, len: n }) +} + +# The following functions should soon be available in Roc's builtins +join_map = |iter, func| { + var $state = [] + for item in iter { + for subitem in func(item) { + $state = $state.append(subitem) + } + } + $state +} + +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/poker/Poker.roc b/exercises/practice/poker/Poker.roc index 817f225a..8359fdc0 100644 --- a/exercises/practice/poker/Poker.roc +++ b/exercises/practice/poker/Poker.roc @@ -1,5 +1,6 @@ Poker :: {}.{ - best_hands : List Str -> Result (List Str) _ - best_hands = |hands| - crash("Please implement the 'best_hands' function") + best_hands : List(Str) -> Try(List(Str), _) + best_hands = |hands| { + crash "Please implement the 'best_hands' function" + } } From b2eaaa1311841b1af6677b98790661f0593bc9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 20:03:47 +1200 Subject: [PATCH 147/162] Update exercise to new compiler: tournament --- .../practice/tournament/.meta/Example.roc | 264 +++++++++++------- exercises/practice/tournament/Tournament.roc | 7 +- 2 files changed, 171 insertions(+), 100 deletions(-) diff --git a/exercises/practice/tournament/.meta/Example.roc b/exercises/practice/tournament/.meta/Example.roc index a29e9c81..a6fa76c4 100644 --- a/exercises/practice/tournament/.meta/Example.roc +++ b/exercises/practice/tournament/.meta/Example.roc @@ -1,118 +1,188 @@ Tournament :: {}.{ - tally : Str -> Result Str [InvalidRow Str, InvalidResult Str] - tally = |table| - if table == "" then - Ok(header) - else - table - |> Str.split_on("\n") - |> List.map_try( - |row| - when row |> Str.split_on(";") is - [team1, team2, result_str] -> - result = result_str |> parse_result? - Ok((team1, team2, result)) - - _ -> Err(InvalidRow(row)), - )? - |> List.walk( - Dict.empty({}), - |tally_dict, (team1, team2, result)| - tally_dict - |> update_tally_dict(team1, result) - |> update_tally_dict(team2, opposite_result(result)), - ) - |> tally_dict_to_table - |> Ok + tally : Str -> Try(Str, [InvalidRow(Str), InvalidResult(Str)]) + tally = |table| { + if table == "" { + Ok(header) + } else { + rows = table.split_on("\n") + parsed_rows = + rows->map_try( + |row| { + match row.split_on(";") { + [team1, team2, result_str] => { + result = parse_result(result_str)? + Ok((team1, team2, result)) + } + _ => Err(InvalidRow(row)) + } + }, + )? + tally_dict = { + parsed_rows.fold( + Dict.empty(), + |tally_dict_acc, triple| { + (team1, team2, result) = triple + tally_dict_acc + ->update_tally_dict(team1, result) + ->update_tally_dict(team2, opposite_result(result)) + }, + ) + } + Ok(tally_dict_to_table(tally_dict)) + } + } } - MatchResult : [Win, Loss, Draw] + TeamTally : { mp : U64, w : U64, d : U64, l : U64, p : U64 } -parse_result : Str -> Result MatchResult [InvalidResult Str] -parse_result = |result_str| - when result_str is - "win" -> Ok(Win) - "loss" -> Ok(Loss) - "draw" -> Ok(Draw) - _ -> Err(InvalidResult(result_str)) +parse_result : Str -> Try(MatchResult, [InvalidResult(Str), ..]) +parse_result = |result_str| { + match result_str { + "win" => Ok(Win) + "loss" => Ok(Loss) + "draw" => Ok(Draw) + _ => Err(InvalidResult(result_str)) + } +} opposite_result : MatchResult -> MatchResult -opposite_result = |result| - when result is - Win -> Loss - Loss -> Win - Draw -> Draw +opposite_result = |result| { + match result { + Win => Loss + Loss => Win + Draw => Draw + } +} -update_tally_dict : Dict Str TeamTally, Str, MatchResult -> Dict Str TeamTally -update_tally_dict = |tally_dict, team, result| - tally_dict - |> Dict.update( - team, - |maybe_team_tally| - when maybe_team_tally is - Ok(team_tally) -> Ok((team_tally |> update_team_tally(result))) - Err(Missing) -> Ok(({ mp: 0, w: 0, d: 0, l: 0, p: 0 } |> update_team_tally(result))), - ) +update_tally_dict : Dict(Str, TeamTally), Str, MatchResult -> Dict(Str, TeamTally) +update_tally_dict = |tally_dict, team, result| { + tally_dict + .update( + team, + |maybe_team_tally| { + match maybe_team_tally { + Ok(team_tally) => Ok(update_team_tally(team_tally, result)) + Err(Missing) => Ok(update_team_tally({ mp: 0, w: 0, d: 0, l: 0, p: 0 }, result)) + } + }, + ) +} update_team_tally : TeamTally, MatchResult -> TeamTally -update_team_tally = |team_tally, result| - when result is - Win -> { team_tally & mp: team_tally.mp + 1, w: team_tally.w + 1, p: team_tally.p + 3 } - Draw -> { team_tally & mp: team_tally.mp + 1, d: team_tally.d + 1, p: team_tally.p + 1 } - Loss -> { team_tally & mp: team_tally.mp + 1, l: team_tally.l + 1 } +update_team_tally = |team_tally, result| { + match result { + Win => { ..team_tally, mp: team_tally.mp + 1, w: team_tally.w + 1, p: team_tally.p + 3 } + Draw => { ..team_tally, mp: team_tally.mp + 1, d: team_tally.d + 1, p: team_tally.p + 1 } + Loss => { ..team_tally, mp: team_tally.mp + 1, l: team_tally.l + 1 } + } +} -tally_dict_to_table : Dict Str TeamTally -> Str -tally_dict_to_table = |tally_dict| - table_content = - tally_dict - |> Dict.to_list - |> List.sort_with( - |(team1, team_tally1), (team2, team_tally2)| - when Num.compare(team_tally1.p, team_tally2.p) is - GT -> LT - LT -> GT - EQ -> compare_strings(team1, team2), - ) - |> List.map( - |(team, team_tally)| - tally_columns = - [.mp, .w, .d, .l, .p] - |> List.map(|field| team_tally |> field |> align_right) - |> Str.join_with(" | ") - "${team |> pad_right(30)} | ${tally_columns}", - ) - |> Str.join_with("\n") - "${header}\n${table_content}" +tally_dict_to_table : Dict(Str, TeamTally) -> Str +tally_dict_to_table = |tally_dict| { + table_content = + tally_dict + .to_list() + .sort_with( + |pair1, pair2| { + (team1, team_tally1) = pair1 + (team2, team_tally2) = pair2 + p1 = team_tally1.p + p2 = team_tally2.p + if p1 < p2 { + GT + } else if p1 > p2 { + LT + } else { + compare_strings(team1, team2) + } + }, + ) + .map( + |pair| { + (team, team_tally) = pair + tally_columns = + [team_tally.mp, team_tally.w, team_tally.d, team_tally.l, team_tally.p] + .map(align_right) + ->Str.join_with(" | ") + "${pad_right(team, 30)} | ${tally_columns}" + }, + ) + ->Str.join_with("\n") + "${header}\n${table_content}" +} header : Str header = "Team | MP | W | D | L | P" compare_strings : Str, Str -> [LT, EQ, GT] -compare_strings = |string1, string2| - b1 = string1 |> Str.to_utf8 - b2 = string2 |> Str.to_utf8 - result = - List.map2(b1, b2, |c1, c2| Num.compare(c1, c2)) - |> List.walk_try( - Ok(EQ), - |_state, cmp| - when cmp is - EQ -> Ok(EQ) - res -> Err(res), - ) - when result is - Ok(_cmp) -> Num.compare(List.len(b1), List.len(b2)) - Err(res) -> res +compare_strings = |string1, string2| { + b1 = string1.to_utf8() + b2 = string2.to_utf8() + cmp_result = + List.map2( + b1, + b2, + |c1, c2| if c1 < c2 { + LT + } else if c1 > c2 { + GT + } else { + EQ + }, + ) + .fold_until( + EQ, + |_, cmp| { + match cmp { + EQ => Continue(EQ) + res => Break(res) + } + }, + ) + match cmp_result { + EQ => { + len1 = List.len(b1) + len2 = List.len(b2) + if len1 < len2 { + LT + } else if len1 > len2 { + GT + } else { + EQ + } + } + res => res + } +} pad_right : Str, U64 -> Str -pad_right = |string, width| - chars = string |> Str.to_utf8 - length = chars |> List.len - spaces = if length < width then List.repeat(" ", (width - length)) |> Str.join_with("") else "" - "${string}${spaces}" +pad_right = |string, width| { + chars = string.to_utf8() + length = chars.len() + spaces = if length < width { + List.repeat(" ", (width - length))->Str.join_with("") + } else { + "" + } + "${string}${spaces}" +} align_right : U64 -> Str -align_right = |number| - if number < 10 then " ${number |> Num.to_str}" else "${number |> Num.to_str}" +align_right = |number| { + if number < 10 { + " ${number.to_str()}" + } else { + "${number.to_str()}" + } +} + +# The following functions should soon be available in Roc's builtins +map_try = |iter, func| { + var $state = [] + for item in iter { + $state = $state.append(func(item)?) + } + Ok($state) +} diff --git a/exercises/practice/tournament/Tournament.roc b/exercises/practice/tournament/Tournament.roc index c5ee0d22..1642ac43 100644 --- a/exercises/practice/tournament/Tournament.roc +++ b/exercises/practice/tournament/Tournament.roc @@ -1,5 +1,6 @@ Tournament :: {}.{ - tally : Str -> Result Str _ - tally = |table| - crash("Please implement the 'tally' function") + tally : Str -> Try(Str, _) + tally = |table| { + crash "Please implement the 'tally' function" + } } From ab237ccded0954892464f6529012454d8973e162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 21:53:37 +1200 Subject: [PATCH 148/162] Update exercise to new compiler: zebra-puzzle --- .../practice/zebra-puzzle/.meta/Example.roc | 394 ++++++++++-------- .../practice/zebra-puzzle/ZebraPuzzle.roc | 19 +- 2 files changed, 224 insertions(+), 189 deletions(-) diff --git a/exercises/practice/zebra-puzzle/.meta/Example.roc b/exercises/practice/zebra-puzzle/.meta/Example.roc index 99a4fc38..731f5dee 100644 --- a/exercises/practice/zebra-puzzle/.meta/Example.roc +++ b/exercises/practice/zebra-puzzle/.meta/Example.roc @@ -1,212 +1,246 @@ ZebraPuzzle :: {}.{ - owns_zebra : Result Person [NotFound] - owns_zebra = - owner_of(|house| house.animal == 5) + Person : [Englishman, Spaniard, Ukrainian, Norwegian, Japanese] - drinks_water : Result Person [NotFound] - drinks_water = - owner_of(|house| house.drink == 5) + owns_zebra : Try(Person, [NotFound]) + owns_zebra = owner_of(|house| house.animal == 5) + + drinks_water : Try(Person, [NotFound]) + drinks_water = owner_of(|house| house.drink == 5) } +House : { + activity : U8, # 0 = Undefined, 1 = Dancing, 2 = Painting, 3 = Reading, 4 = Football, 5 = Chess + animal : U8, # 0 = Undefined, 1 = Dog, 2 = Snail, 3 = Fox, 4 = Horse, 5 = Zebra + color : U8, # 0 = Undefined, 1 = Red, 2 = Green, 3 = Ivory, 4 = Yellow, 5 = Blue + drink : U8, # 0 = Undefined, 1 = Coffee, 2 = Tea, 3 = Milk, 4 = OrangeJuice, 5 = Water + person : U8, # 0 = Undefined, 1 = Englishman, 2 = Spaniard, 3 = Ukrainian, 4 = Norwegian, 5 = Japanese +} -Person : [Englishman, Spaniard, Ukrainian, Norwegian, Japanese] +owner_of : (House -> Bool) -> Try(Person, [NotFound]) +owner_of = |condition| { + houses = solution? + house = houses.find_first(condition)? + match house.person { + 1 => Ok(Englishman) + 2 => Ok(Spaniard) + 3 => Ok(Ukrainian) + 4 => Ok(Norwegian) + 5 => Ok(Japanese) + _ => Err(NotFound) + } +} -House : { - activity : U8, # 0 = Undefined, 1 = Dancing, 2 = Painting, 3 = Reading, 4 = Football, 5 = Chess - animal : U8, # 0 = Undefined, 1 = Dog, 2 = Snail, 3 = Fox, 4 = Horse, 5 = Zebra - color : U8, # 0 = Undefined, 1 = Red, 2 = Green, 3 = Ivory, 4 = Yellow, 5 = Blue - drink : U8, # 0 = Undefined, 1 = Coffee, 2 = Tea, 3 = Milk, 4 = OrangeJuice, 5 = Water - person : U8, # 0 = Undefined, 1 = Englishman, 2 = Spaniard, 3 = Ukrainian, 4 = Norwegian, 5 = Japanese -} - -owner_of : (House -> Bool) -> Result Person [NotFound] -owner_of = |condition| - when solution is - Ok(houses) -> - when houses |> List.find_first(condition)? |> .person is - 1 -> Ok(Englishman) - 2 -> Ok(Spaniard) - 3 -> Ok(Ukrainian) - 4 -> Ok(Norwegian) - 5 -> Ok(Japanese) - _ -> Err(NotFound) - - Err(NotFound) -> Err(NotFound) - -solution : Result (List House) [NotFound] -solution = - num_houses = 5 # rule 1 - find_first_solution = |houses, house, field_index| - [1, 2, 3, 4, 5] - |> List.walk_until( - Err(NotFound), - |state, value| - updated_house = house |> set_field(field_index, value) - updated_houses = houses |> List.append(updated_house) - if updated_houses |> is_valid then - if List.len(updated_houses) == num_houses and field_index == 4 then - Break(Ok(updated_houses)) - else - (next_houses, next_house, next_field_index) = - if field_index == 4 then - (updated_houses, init_house, 0) - else - (houses, updated_house, field_index + 1) - - when find_first_solution(next_houses, next_house, next_field_index) is - Ok(found) -> Break(Ok(found)) - Err(NotFound) -> Continue(state) - else - Continue(state), - ) - init_house = { activity: 0, animal: 0, color: 0, drink: 0, person: 0 } - find_first_solution([], init_house, 0) +solution : Try(List(House), [NotFound]) +solution = { + num_houses = 5 # rule 1 + init_house = { activity: 0, animal: 0, color: 0, drink: 0, person: 0 } + find_first_solution = |houses, house, field_index| { + [1, 2, 3, 4, 5] + .fold_until( + Err(NotFound), + |state, value| { + updated_house = house->set_field(field_index, value) + updated_houses = houses.append(updated_house) + if updated_houses->is_valid() { + if updated_houses.len() == num_houses and field_index == 4 { + Break(Ok(updated_houses)) + } else { + (next_houses, next_house, next_field_index) = + if field_index == 4 { + (updated_houses, init_house, 0) + } else { + (houses, updated_house, field_index + 1) + } + + match find_first_solution(next_houses, next_house, next_field_index) { + Ok(found) => Break(Ok(found)) + Err(NotFound) => Continue(state) + } + } + } else { + Continue(state) + } + }, + ) + } + find_first_solution([], init_house, 0) +} set_field : House, U8, U8 -> House -set_field = |house, field_index, value| - when field_index is - 0 -> { house & activity: value } - 1 -> { house & animal: value } - 2 -> { house & color: value } - 3 -> { house & drink: value } - 4 -> { house & person: value } - _ -> crash("field_index should always be between 0 and 4") - -is_valid : List House -> Bool -is_valid = |houses| - [ - rule2, - rule3, - rule4, - rule5, - rule6, - rule7, - rule8, - rule9, - rule10, - rule11, - rule12, - rule13, - rule14, - rule15, - rule16, - ] - |> List.all(|rule| rule(houses)) - -same_house : List House, (House -> U8, U8), (House -> U8, U8) -> Bool -same_house = |houses, (field1, value1), (field2, value2)| - houses - |> List.all( - |house| - f1 = field1(house) - f2 = field2(house) - (f1 == 0 or f2 == 0) or (f1 == value1 and f2 == value2) or (f1 != value1 and f2 != value2), - ) +set_field = |house, field_index, value| { + match field_index { + 0 => { ..house, activity: value } + 1 => { ..house, animal: value } + 2 => { ..house, color: value } + 3 => { ..house, drink: value } + 4 => { ..house, person: value } + _ => { + crash "field_index should always be between 0 and 4" + } + } +} + +is_valid : List(House) -> Bool +is_valid = |houses| { + [ + rule2, + rule3, + rule4, + rule5, + rule6, + rule7, + rule8, + rule9, + rule10, + rule11, + rule12, + rule13, + rule14, + rule15, + rule16, + ] + .all(|rule| rule(houses)) +} + +same_house : List(House), ((House -> U8), U8), ((House -> U8), U8) -> Bool +same_house = |houses, (field1, value1), (field2, value2)| { + houses + .all( + |house| { + f1 = field1(house) + f2 = field2(house) + (f1 == 0 or f2 == 0) or (f1 == value1 and f2 == value2) or (f1 != value1 and f2 != value2) + }, + ) +} # The Englishman lives in the red house. -rule2 : List House -> Bool -rule2 = |houses| - houses |> same_house((.person, 1), (.color, 1)) +rule2 : List(House) -> Bool +rule2 = |houses| { + houses->same_house((|h| h.person, 1), (|h| h.color, 1)) +} # The Spaniard owns the dog. -rule3 : List House -> Bool -rule3 = |houses| - houses |> same_house((.person, 2), (.animal, 1)) +rule3 : List(House) -> Bool +rule3 = |houses| { + same_house(houses, (|h| h.person, 2), (|h| h.animal, 1)) +} # The person in the green house drinks coffee. -rule4 : List House -> Bool -rule4 = |houses| - houses |> same_house((.color, 2), (.drink, 1)) +rule4 : List(House) -> Bool +rule4 = |houses| { + same_house(houses, (|h| h.color, 2), (|h| h.drink, 1)) +} # The Ukrainian drinks tea. -rule5 : List House -> Bool -rule5 = |houses| - houses |> same_house((.person, 3), (.drink, 2)) +rule5 : List(House) -> Bool +rule5 = |houses| { + same_house(houses, (|h| h.person, 3), (|h| h.drink, 2)) +} # The green house is immediately to the right of the ivory house. -rule6 : List House -> Bool -rule6 = |houses| - green_house = houses |> List.find_first_index(|house| house.color == 2) - ivory_house = houses |> List.find_first_index(|house| house.color == 3) - when (green_house, ivory_house) is - (Ok(green_index), Ok(ivory_index)) -> green_index == ivory_index + 1 - _ -> Bool.True +rule6 : List(House) -> Bool +rule6 = |houses| { + green_house = houses.find_first_index(|house| house.color == 2) + ivory_house = houses.find_first_index(|house| house.color == 3) + match (green_house, ivory_house) { + (Ok(green_index), Ok(ivory_index)) => green_index == ivory_index + 1 + _ => Bool.True + } +} # The snail owner likes to go dancing. -rule7 : List House -> Bool -rule7 = |houses| - houses |> same_house((.animal, 2), (.activity, 1)) +rule7 : List(House) -> Bool +rule7 = |houses| { + same_house(houses, (|h| h.animal, 2), (|h| h.activity, 1)) +} # The person in the yellow house is a painter. -rule8 : List House -> Bool -rule8 = |houses| - houses |> same_house((.color, 4), (.activity, 2)) +rule8 : List(House) -> Bool +rule8 = |houses| { + same_house(houses, (|h| h.color, 4), (|h| h.activity, 2)) +} # The person in the middle house drinks milk. -rule9 : List House -> Bool -rule9 = |houses| - houses - |> List.map_with_index(|house, index| (house, index)) - |> List.all( - |(house, index)| - (house.drink == 0) or (index == 2 and house.drink == 3) or (index != 2 and house.drink != 3), - ) +rule9 : List(House) -> Bool +rule9 = |houses| { + houses + .map_with_index(|house, index| (house, index)) + .all( + |(house, index)| { + (house.drink == 0) or (index == 2 and house.drink == 3) or (index != 2 and house.drink != 3) + }, + ) +} # The Norwegian lives in the first house. -rule10 : List House -> Bool -rule10 = |houses| - houses - |> List.map_with_index(|house, index| (house, index)) - |> List.all( - |(house, index)| - (house.person == 0) or (index == 0 and house.person == 4) or (index != 0 and house.person != 4), - ) +rule10 : List(House) -> Bool +rule10 = |houses| { + houses + .map_with_index(|house, index| (house, index)) + .all( + |(house, index)| { + (house.person == 0) or (index == 0 and house.person == 4) or (index != 0 and house.person != 4) + }, + ) +} # The person who enjoys reading lives in the house next to the person with the fox. -rule11 : List House -> Bool -rule11 = |houses| - reader_house = houses |> List.find_first_index(|house| house.activity == 3) - fox_house = houses |> List.find_first_index(|house| house.animal == 3) - when (reader_house, fox_house) is - (Ok(reader_index), Ok(fox_index)) -> (reader_index == fox_index + 1) or (fox_index == reader_index + 1) - _ -> Bool.True +rule11 : List(House) -> Bool +rule11 = |houses| { + reader_house = houses.find_first_index(|house| house.activity == 3) + fox_house = houses.find_first_index(|house| house.animal == 3) + match (reader_house, fox_house) { + (Ok(reader_index), Ok(fox_index)) => (reader_index == fox_index + 1) or (fox_index == reader_index + 1) + _ => Bool.True + } +} # The painter's house is next to the house with the horse. -rule12 : List House -> Bool -rule12 = |houses| - painter_house = houses |> List.find_first_index(|house| house.activity == 2) - horse_house = houses |> List.find_first_index(|house| house.animal == 4) - when (painter_house, horse_house) is - (Ok(painter_index), Ok(horse_index)) -> (painter_index == horse_index + 1) or (horse_index == painter_index + 1) - _ -> Bool.True - -# The List person -> Bool -rule13 : List House -> Bool -rule13 = |houses| - houses |> same_house((.activity, 4), (.drink, 4)) - -# The List Japanese -> Bool -rule14 : List House -> Bool -rule14 = |houses| - houses |> same_house((.person, 5), (.activity, 5)) +rule12 : List(House) -> Bool +rule12 = |houses| { + painter_house = houses.find_first_index(|house| house.activity == 2) + horse_house = houses.find_first_index(|house| house.animal == 4) + match (painter_house, horse_house) { + (Ok(painter_index), Ok(horse_index)) => (painter_index == horse_index + 1) or (horse_index == painter_index + 1) + _ => Bool.True + } +} + +# The football person drinks orange juice. +rule13 : List(House) -> Bool +rule13 = |houses| { + same_house(houses, (|h| h.activity, 4), (|h| h.drink, 4)) +} + +# The Japanese plays chess. +rule14 : List(House) -> Bool +rule14 = |houses| { + same_house(houses, (|h| h.person, 5), (|h| h.activity, 5)) +} # The Norwegian lives next to the blue house. -rule15 : List House -> Bool -rule15 = |houses| - norwegian_house = houses |> List.find_first_index(|house| house.person == 4) - blue_house = houses |> List.find_first_index(|house| house.color == 5) - when (norwegian_house, blue_house) is - (Ok(norwegian_index), Ok(blue_index)) -> (norwegian_index == blue_index + 1) or (blue_index == norwegian_index + 1) - _ -> Bool.True - -rule16 : List House -> Bool -rule16 = |houses| - [.activity, .animal, .color, .drink, .person] - |> List.all( - |field| - values = - houses - |> List.map(field) - |> List.keep_if(|value| value != 0) - List.len(values) == Set.len(Set.from_list(values)), - ) +rule15 : List(House) -> Bool +rule15 = |houses| { + norwegian_house = houses.find_first_index(|house| house.person == 4) + blue_house = houses.find_first_index(|house| house.color == 5) + match (norwegian_house, blue_house) { + (Ok(norwegian_index), Ok(blue_index)) => (norwegian_index == blue_index + 1) or (blue_index == norwegian_index + 1) + _ => Bool.True + } +} + +# There must be no duplicates (e.g., two green houses) +rule16 : List(House) -> Bool +rule16 = |houses| { + [|h| h.activity, |h| h.animal, |h| h.color, |h| h.drink, |h| h.person] + .all( + |field| { + values = + houses + .map(field) + .keep_if(|value| value != 0) + values.len() == Set.from_list(values).len() + }, + ) +} diff --git a/exercises/practice/zebra-puzzle/ZebraPuzzle.roc b/exercises/practice/zebra-puzzle/ZebraPuzzle.roc index 3b466861..f1b4645e 100644 --- a/exercises/practice/zebra-puzzle/ZebraPuzzle.roc +++ b/exercises/practice/zebra-puzzle/ZebraPuzzle.roc @@ -1,12 +1,13 @@ ZebraPuzzle :: {}.{ - owns_zebra : Result Person _ - owns_zebra = - crash("Please implement the 'owns_zebra' function") - - drinks_water : Result Person _ - drinks_water = - crash("Please implement the 'drinks_water' function") -} + Person : [Englishman, Spaniard, Ukrainian, Norwegian, Japanese] + owns_zebra : Try(Person, _) + owns_zebra = { + crash "Please implement the 'owns_zebra' function" + } -Person : [Englishman, Spaniard, Ukrainian, Norwegian, Japanese] + drinks_water : Try(Person, _) + drinks_water = { + crash "Please implement the 'drinks_water' function" + } +} From e4e03cc8d58f8abfb90a1853956607d169f8ffc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 22:29:17 +1200 Subject: [PATCH 149/162] Update exercise to new compiler: rational-numbers --- .../rational-numbers/.meta/Example.roc | 218 ++++++++++++---- .../rational-numbers/.meta/plugins.py | 2 +- .../rational-numbers/.meta/template.j2 | 19 +- .../rational-numbers/RationalNumbers.roc | 75 +++--- .../rational-numbers-test.roc | 243 ++++++++++++------ 5 files changed, 379 insertions(+), 178 deletions(-) diff --git a/exercises/practice/rational-numbers/.meta/Example.roc b/exercises/practice/rational-numbers/.meta/Example.roc index cc0dc097..fe8153d3 100644 --- a/exercises/practice/rational-numbers/.meta/Example.roc +++ b/exercises/practice/rational-numbers/.meta/Example.roc @@ -1,55 +1,167 @@ RationalNumbers :: {}.{ - add : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] - add = |r1, r2| - Rational(a1, b1) = r1 - Rational(a2, b2) = r2 - Rational((a1 * b2 + a2 * b1), (b1 * b2)) |> reduce - - sub : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] - sub = |r1, r2| - Rational(a1, b1) = r1 - Rational(a2, b2) = r2 - Rational((a1 * b2 - a2 * b1), (b1 * b2)) |> reduce - - mul : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] - mul = |r1, r2| - Rational(a1, b1) = r1 - Rational(a2, b2) = r2 - Rational((a1 * a2), (b1 * b2)) |> reduce - - div : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] - div = |r1, r2| - Rational(a1, b1) = r1 - Rational(a2, b2) = r2 - Rational((a1 * b2), (a2 * b1)) |> reduce - - abs : [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] - abs = |r| - Rational(a, b) = r - Rational(Num.abs(a), Num.abs(b)) |> reduce - - exp : [Rational (Int a) (Int a)], Int a -> [Rational (Int a) (Int a)] - exp = |r, n| - Rational(a, b) = r - when n is - 0 -> Rational(1, 1) - pos if pos > 0 -> Rational((a |> Num.pow_int(pos)), (b |> Num.pow_int(pos))) |> reduce - neg -> - m = Num.abs(neg) - Rational((b |> Num.pow_int(m)), (a |> Num.pow_int(m))) |> reduce - - exp_real : Frac a, [Rational (Int b) (Int b)] -> Frac a - exp_real = |x, r| - Rational(a, b) = r - x |> Num.pow((Num.to_frac(a) / Num.to_frac(b))) - - reduce : [Rational (Int b) (Int b)] -> [Rational (Int b) (Int b)] - reduce = |r| - Rational(a, b) = r - gcd = |m, n| if n == 0 then m else gcd(n, (m % n)) - sign = |n| if n < 0 then -1 else 1 - abs_a = Num.abs(a) - abs_b = Num.abs(b) - d = gcd(abs_a, abs_b) - Rational((sign(a) * sign(b) * abs_a // d), (abs_b // d)) + Rational : { num : I64, den : I64 } + + add : Rational, Rational -> Rational + add = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { + { num: num1 * den2 + num2 * den1, den: den1 * den2 }->reduce() + } + + sub : Rational, Rational -> Rational + sub = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { + { num: num1 * den2 - num2 * den1, den: den1 * den2 }->reduce() + } + + mul : Rational, Rational -> Rational + mul = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { + { num: num1 * num2, den: den1 * den2 }->reduce() + } + + div : Rational, Rational -> Rational + div = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { + { num: num1 * den2, den: num2 * den1 }->reduce() + } + + abs : Rational -> Rational + abs = |{ num, den }| { + { num: num.abs(), den: den.abs() }->reduce() + } + + exp : Rational, I64 -> Rational + exp = |{ num, den }, n| { + match n { + 0 => { num: 1, den: 1 } + pos if pos > 0 => { num: pow_int(num, pos), den: pow_int(den, pos) }->reduce() + neg => { + m = neg.abs() + { num: pow_int(den, m), den: pow_int(num, m) }->reduce() + } + } + } + + exp_real : F64, Rational -> F64 + exp_real = |x, { num, den }| { + f : F64 + f = num.to_f64() / den.to_f64() + pow_f64(x, f) + } + + # # Reduce a rational number to its lowest terms, e.g., 6 / 8 --> 3 / 4 + reduce : Rational -> Rational + reduce = |{ num, den }| { + gcd = |m, n| if n == 0 { + m + } else { + gcd(n, (m % n)) + } + sign = |n| if n < 0 { + -1 + } else { + 1 + } + abs_num = num.abs() + abs_den = den.abs() + d = gcd(abs_num, abs_den) + { num: sign(num) * sign(den) * abs_num // d, den: abs_den // d } + } +} + +pow_int : I64, I64 -> I64 +pow_int = |number, pow| { + (1..=pow).fold( + 1, + |acc, _| { + acc * number + }, + ) +} + +# The following functions should soon be available in Roc's builtins + +e = 2.718281828459045.F64 + +# Calculates the natural logarithm of x, ln(x). +ln_f64 : F64 -> F64 +ln_f64 = |x| { + if x <= 0.0 { + # Natural log is undefined for zero and negative numbers + crash "ln is undefined for zero or negative numbers" + } else { + var $norm_x = x + var $log_offset = 0.0 + + # Range reduction: keep x between [1/e, e] + while $norm_x > e { + $norm_x = $norm_x / e + $log_offset = $log_offset + 1.0 + } + while $norm_x < 1 / e { + $norm_x = $norm_x * e + $log_offset = $log_offset - 1.0 + } + + # Area hyperbolic tangent series + z = ($norm_x - 1.0) / ($norm_x + 1.0) + z2 = z * z + var $z_term = z + var $ln_sum = 0.0 + var $ln_n = 1.0 + + for _ in 0..<30.U8 { + $ln_sum = $ln_sum + ($z_term / $ln_n) + $z_term = $z_term * z2 + $ln_n = $ln_n + 2.0 + } + + (2.0 * $ln_sum) + $log_offset + } +} + +# Calculates e^x using the Taylor series. +exp_f64 : F64 -> F64 +exp_f64 = |x| { + var $norm_x = x + var $exp_mult = 1.0 + + # Range reduction: keep x between [-1.0, 1.0] + while $norm_x > 1.0 { + $norm_x = $norm_x - 1.0 + $exp_mult = $exp_mult * e + } + while $norm_x < -1.0 { + $norm_x = $norm_x + 1.0 + $exp_mult = $exp_mult / e + } + + # Taylor series + var $exp_term = 1.0 + var $exp_sum = 1.0 + var $exp_n = 1.0 + + for _ in 0..<25.U8 { + $exp_term = $exp_term * $norm_x / $exp_n + $exp_sum = $exp_sum + $exp_term + $exp_n = $exp_n + 1.0 + } + + $exp_sum * $exp_mult +} + +# Calculates x^p for 64-bit values using the newly separated functions. +pow_f64 : F64, F64 -> F64 +pow_f64 = |x, p| { + if x == 0.0 { + if p == 0.0 { + 1.0 + } else { + 0.0 + } + } else if x < 0.0 { + # Fractional powers of negative numbers are undefined in pure real 64-bit math + crash "Raising a negative number to a fractional power is undefined" + } else if p == 0.0 { + 1.0 + } else { + # The core mathematical calculation + exp_f64(p * ln_f64(x)) + } } diff --git a/exercises/practice/rational-numbers/.meta/plugins.py b/exercises/practice/rational-numbers/.meta/plugins.py index 26d36cee..187776b4 100644 --- a/exercises/practice/rational-numbers/.meta/plugins.py +++ b/exercises/practice/rational-numbers/.meta/plugins.py @@ -1,2 +1,2 @@ def to_roc_rational(r): - return f"Rational({r[0]}, {r[1]})" + return f"{{num: {r[0]}, den: {r[1]}}}" diff --git a/exercises/practice/rational-numbers/.meta/template.j2 b/exercises/practice/rational-numbers/.meta/template.j2 index df932170..a48e7e7c 100644 --- a/exercises/practice/rational-numbers/.meta/template.j2 +++ b/exercises/practice/rational-numbers/.meta/template.j2 @@ -2,7 +2,7 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [add, sub, mul, div, abs, exp, exp_real, reduce] +import {{ exercise | to_pascal }} exposing [Rational, add, sub, mul, div, abs, exp, exp_real, reduce] is_approx_eq = |f1, f2| { (f1 * 1e9 + 0.5).to_u64_wrap() == (f2 * 1e9 + 0.5).to_u64_wrap() @@ -24,14 +24,21 @@ is_approx_eq = |f1, f2| { # {{ case["description"] }} expect { {%- if "r1" in case["input"] %} - result = {{ plugins.to_roc_rational(case["input"]["r1"]) }}.{{ property_map[case["property"]] | to_snake }}(({{ plugins.to_roc_rational(case["input"]["r2"]) }})) + r1 = {{ plugins.to_roc_rational(case["input"]["r1"]) }} + r2 = {{ plugins.to_roc_rational(case["input"]["r2"]) }} + result = r1->{{ property_map[case["property"]] | to_snake }}(r2) {%- elif "r" in case["input"] %} {%- if "x" in case["input"] %} - result = {{ case["input"]["x"] | to_roc }} -> {{ property_map[case["property"]] | to_snake }}(({{ plugins.to_roc_rational(case["input"]["r"]) }})) + r = {{ plugins.to_roc_rational(case["input"]["r"]) }} + x = {{ case["input"]["x"] | to_roc }} + result = x->{{ property_map[case["property"]] | to_snake }}(r) {%- elif "n" in case["input"] %} - result = {{ plugins.to_roc_rational(case["input"]["r"]) }}.{{ property_map[case["property"]] | to_snake }}({{ case["input"]["n"] | to_roc }}) + r = {{ plugins.to_roc_rational(case["input"]["r"]) }} + n = {{ case["input"]["n"] | to_roc }} + result = r->{{ property_map[case["property"]] | to_snake }}(n) {%- else %} - result = {{ plugins.to_roc_rational(case["input"]["r"]) }}.{{ property_map[case["property"]] | to_snake }} + r = {{ plugins.to_roc_rational(case["input"]["r"]) }} + result = r->{{ property_map[case["property"]] | to_snake }}() {%- endif %} {%- else %} crash "This test case is not implemented yet." @@ -39,7 +46,7 @@ expect { {%- if case["expected"] is iterable %} result == {{ plugins.to_roc_rational(case["expected"]) }} {%- else %} - result -> is_approx_eq({{ case["expected"] | to_roc }}) + result->is_approx_eq({{ case["expected"] | to_roc }}) {%- endif %} } diff --git a/exercises/practice/rational-numbers/RationalNumbers.roc b/exercises/practice/rational-numbers/RationalNumbers.roc index 890a913f..78c20962 100644 --- a/exercises/practice/rational-numbers/RationalNumbers.roc +++ b/exercises/practice/rational-numbers/RationalNumbers.roc @@ -1,35 +1,44 @@ RationalNumbers :: {}.{ - add : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] - add = |r1, r2| - crash("Please implement the 'add' function") - - sub : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] - sub = |r1, r2| - crash("Please implement the 'sub' function") - - mul : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] - mul = |r1, r2| - crash("Please implement the 'mul' function") - - div : [Rational (Int a) (Int a)], [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] - div = |r1, r2| - crash("Please implement the 'div' function") - - abs : [Rational (Int a) (Int a)] -> [Rational (Int a) (Int a)] - abs = |r| - crash("Please implement the 'abs' function") - - exp : [Rational (Int a) (Int a)], Int a -> [Rational (Int a) (Int a)] - exp = |r, n| - crash("Please implement the 'exp' function") - - exp_real : Frac a, [Rational (Int b) (Int b)] -> Frac a - exp_real = |x, r| - crash("Please implement the 'exp_real' function") - - ## Reduce a rational number to its lowest terms, e.g., 6 / 8 --> 3 / 4 - reduce : [Rational (Int b) (Int b)] -> [Rational (Int b) (Int b)] - reduce = |r| - crash("Please implement the 'reduce' function") - + Rational : { num : I64, den : I64 } + + add : Rational, Rational -> Rational + add = |r1, r2| { + crash "Please implement the 'add' function" + } + + sub : Rational, Rational -> Rational + sub = |r1, r2| { + crash "Please implement the 'sub' function" + } + + mul : Rational, Rational -> Rational + mul = |r1, r2| { + crash "Please implement the 'mul' function" + } + + div : Rational, Rational -> Rational + div = |r1, r2| { + crash "Please implement the 'div' function" + } + + abs : Rational -> Rational + abs = |r| { + crash "Please implement the 'abs' function" + } + + exp : Rational, I64 -> Rational + exp = |r, n| { + crash "Please implement the 'exp' function" + } + + exp_real : F64, Rational -> F64 + exp_real = |x, r| { + crash "Please implement the 'exp_real' function" + } + + # # Reduce a rational number to its lowest terms, e.g., 6 / 8 --> 3 / 4 + reduce : Rational -> Rational + reduce = |r| { + crash "Please implement the 'reduce' function" + } } diff --git a/exercises/practice/rational-numbers/rational-numbers-test.roc b/exercises/practice/rational-numbers/rational-numbers-test.roc index 57bffcb2..9f45c51c 100644 --- a/exercises/practice/rational-numbers/rational-numbers-test.roc +++ b/exercises/practice/rational-numbers/rational-numbers-test.roc @@ -1,8 +1,8 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rational-numbers/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 -import RationalNumbers exposing [add, sub, mul, div, abs, exp, exp_real, reduce] +import RationalNumbers exposing [Rational, add, sub, mul, div, abs, exp, exp_real, reduce] is_approx_eq = |f1, f2| { (f1 * 1e9 + 0.5).to_u64_wrap() == (f2 * 1e9 + 0.5).to_u64_wrap() @@ -14,110 +14,146 @@ is_approx_eq = |f1, f2| { # Add two positive rational numbers expect { - result = Rational(1, 2).add((Rational(2, 3))) - result == Rational(7, 6) + r1 = { num: 1, den: 2 } + r2 = { num: 2, den: 3 } + result = r1->add(r2) + result == { num: 7, den: 6 } } # Add a positive rational number and a negative rational number expect { - result = Rational(1, 2).add((Rational(-2, 3))) - result == Rational(-1, 6) + r1 = { num: 1, den: 2 } + r2 = { num: -2, den: 3 } + result = r1->add(r2) + result == { num: -1, den: 6 } } # Add two negative rational numbers expect { - result = Rational(-1, 2).add((Rational(-2, 3))) - result == Rational(-7, 6) + r1 = { num: -1, den: 2 } + r2 = { num: -2, den: 3 } + result = r1->add(r2) + result == { num: -7, den: 6 } } # Add a rational number to its additive inverse expect { - result = Rational(1, 2).add((Rational(-1, 2))) - result == Rational(0, 1) + r1 = { num: 1, den: 2 } + r2 = { num: -1, den: 2 } + result = r1->add(r2) + result == { num: 0, den: 1 } } # Subtract two positive rational numbers expect { - result = Rational(1, 2).sub((Rational(2, 3))) - result == Rational(-1, 6) + r1 = { num: 1, den: 2 } + r2 = { num: 2, den: 3 } + result = r1->sub(r2) + result == { num: -1, den: 6 } } # Subtract a positive rational number and a negative rational number expect { - result = Rational(1, 2).sub((Rational(-2, 3))) - result == Rational(7, 6) + r1 = { num: 1, den: 2 } + r2 = { num: -2, den: 3 } + result = r1->sub(r2) + result == { num: 7, den: 6 } } # Subtract two negative rational numbers expect { - result = Rational(-1, 2).sub((Rational(-2, 3))) - result == Rational(1, 6) + r1 = { num: -1, den: 2 } + r2 = { num: -2, den: 3 } + result = r1->sub(r2) + result == { num: 1, den: 6 } } # Subtract a rational number from itself expect { - result = Rational(1, 2).sub((Rational(1, 2))) - result == Rational(0, 1) + r1 = { num: 1, den: 2 } + r2 = { num: 1, den: 2 } + result = r1->sub(r2) + result == { num: 0, den: 1 } } # Multiply two positive rational numbers expect { - result = Rational(1, 2).mul((Rational(2, 3))) - result == Rational(1, 3) + r1 = { num: 1, den: 2 } + r2 = { num: 2, den: 3 } + result = r1->mul(r2) + result == { num: 1, den: 3 } } # Multiply a negative rational number by a positive rational number expect { - result = Rational(-1, 2).mul((Rational(2, 3))) - result == Rational(-1, 3) + r1 = { num: -1, den: 2 } + r2 = { num: 2, den: 3 } + result = r1->mul(r2) + result == { num: -1, den: 3 } } # Multiply two negative rational numbers expect { - result = Rational(-1, 2).mul((Rational(-2, 3))) - result == Rational(1, 3) + r1 = { num: -1, den: 2 } + r2 = { num: -2, den: 3 } + result = r1->mul(r2) + result == { num: 1, den: 3 } } # Multiply a rational number by its reciprocal expect { - result = Rational(1, 2).mul((Rational(2, 1))) - result == Rational(1, 1) + r1 = { num: 1, den: 2 } + r2 = { num: 2, den: 1 } + result = r1->mul(r2) + result == { num: 1, den: 1 } } # Multiply a rational number by 1 expect { - result = Rational(1, 2).mul((Rational(1, 1))) - result == Rational(1, 2) + r1 = { num: 1, den: 2 } + r2 = { num: 1, den: 1 } + result = r1->mul(r2) + result == { num: 1, den: 2 } } # Multiply a rational number by 0 expect { - result = Rational(1, 2).mul((Rational(0, 1))) - result == Rational(0, 1) + r1 = { num: 1, den: 2 } + r2 = { num: 0, den: 1 } + result = r1->mul(r2) + result == { num: 0, den: 1 } } # Divide two positive rational numbers expect { - result = Rational(1, 2).div((Rational(2, 3))) - result == Rational(3, 4) + r1 = { num: 1, den: 2 } + r2 = { num: 2, den: 3 } + result = r1->div(r2) + result == { num: 3, den: 4 } } # Divide a positive rational number by a negative rational number expect { - result = Rational(1, 2).div((Rational(-2, 3))) - result == Rational(-3, 4) + r1 = { num: 1, den: 2 } + r2 = { num: -2, den: 3 } + result = r1->div(r2) + result == { num: -3, den: 4 } } # Divide two negative rational numbers expect { - result = Rational(-1, 2).div((Rational(-2, 3))) - result == Rational(3, 4) + r1 = { num: -1, den: 2 } + r2 = { num: -2, den: 3 } + result = r1->div(r2) + result == { num: 3, den: 4 } } # Divide a rational number by 1 expect { - result = Rational(1, 2).div((Rational(1, 1))) - result == Rational(1, 2) + r1 = { num: 1, den: 2 } + r2 = { num: 1, den: 1 } + result = r1->div(r2) + result == { num: 1, den: 2 } } ## @@ -126,38 +162,44 @@ expect { # Absolute value of a positive rational number expect { - result = Rational(1, 2).abs - result == Rational(1, 2) + r = { num: 1, den: 2 } + result = r->abs() + result == { num: 1, den: 2 } } # Absolute value of a positive rational number with negative numerator and denominator expect { - result = Rational(-1, -2).abs - result == Rational(1, 2) + r = { num: -1, den: -2 } + result = r->abs() + result == { num: 1, den: 2 } } # Absolute value of a negative rational number expect { - result = Rational(-1, 2).abs - result == Rational(1, 2) + r = { num: -1, den: 2 } + result = r->abs() + result == { num: 1, den: 2 } } # Absolute value of a negative rational number with negative denominator expect { - result = Rational(1, -2).abs - result == Rational(1, 2) + r = { num: 1, den: -2 } + result = r->abs() + result == { num: 1, den: 2 } } # Absolute value of zero expect { - result = Rational(0, 1).abs - result == Rational(0, 1) + r = { num: 0, den: 1 } + result = r->abs() + result == { num: 0, den: 1 } } # Absolute value of a rational number is reduced to lowest terms expect { - result = Rational(2, 4).abs - result == Rational(1, 2) + r = { num: 2, den: 4 } + result = r->abs() + result == { num: 1, den: 2 } } ## @@ -166,56 +208,74 @@ expect { # Raise a positive rational number to a positive integer power expect { - result = Rational(1, 2).exp(3) - result == Rational(1, 8) + r = { num: 1, den: 2 } + n = 3 + result = r->exp(n) + result == { num: 1, den: 8 } } # Raise a negative rational number to a positive integer power expect { - result = Rational(-1, 2).exp(3) - result == Rational(-1, 8) + r = { num: -1, den: 2 } + n = 3 + result = r->exp(n) + result == { num: -1, den: 8 } } # Raise a positive rational number to a negative integer power expect { - result = Rational(3, 5).exp(-2) - result == Rational(25, 9) + r = { num: 3, den: 5 } + n = -2 + result = r->exp(n) + result == { num: 25, den: 9 } } # Raise a negative rational number to an even negative integer power expect { - result = Rational(-3, 5).exp(-2) - result == Rational(25, 9) + r = { num: -3, den: 5 } + n = -2 + result = r->exp(n) + result == { num: 25, den: 9 } } # Raise a negative rational number to an odd negative integer power expect { - result = Rational(-3, 5).exp(-3) - result == Rational(-125, 27) + r = { num: -3, den: 5 } + n = -3 + result = r->exp(n) + result == { num: -125, den: 27 } } # Raise zero to an integer power expect { - result = Rational(0, 1).exp(5) - result == Rational(0, 1) + r = { num: 0, den: 1 } + n = 5 + result = r->exp(n) + result == { num: 0, den: 1 } } # Raise one to an integer power expect { - result = Rational(1, 1).exp(4) - result == Rational(1, 1) + r = { num: 1, den: 1 } + n = 4 + result = r->exp(n) + result == { num: 1, den: 1 } } # Raise a positive rational number to the power of zero expect { - result = Rational(1, 2).exp(0) - result == Rational(1, 1) + r = { num: 1, den: 2 } + n = 0 + result = r->exp(n) + result == { num: 1, den: 1 } } # Raise a negative rational number to the power of zero expect { - result = Rational(-1, 2).exp(0) - result == Rational(1, 1) + r = { num: -1, den: 2 } + n = 0 + result = r->exp(n) + result == { num: 1, den: 1 } } ## @@ -224,19 +284,25 @@ expect { # Raise a real number to a positive rational number expect { - result = 8->exp_real((Rational(4, 3))) + r = { num: 4, den: 3 } + x = 8 + result = x->exp_real(r) result->is_approx_eq(16.0.F64) } # Raise a real number to a negative rational number expect { - result = 9->exp_real((Rational(-1, 2))) + r = { num: -1, den: 2 } + x = 9 + result = x->exp_real(r) result->is_approx_eq(0.3333333333333333.F64) } # Raise a real number to a zero rational number expect { - result = 2->exp_real((Rational(0, 1))) + r = { num: 0, den: 1 } + x = 2 + result = x->exp_real(r) result->is_approx_eq(1.0.F64) } @@ -246,44 +312,51 @@ expect { # Reduce a positive rational number to lowest terms expect { - result = Rational(2, 4).reduce - result == Rational(1, 2) + r = { num: 2, den: 4 } + result = r->reduce() + result == { num: 1, den: 2 } } # Reduce places the minus sign on the numerator expect { - result = Rational(3, -4).reduce - result == Rational(-3, 4) + r = { num: 3, den: -4 } + result = r->reduce() + result == { num: -3, den: 4 } } # Reduce a negative rational number to lowest terms expect { - result = Rational(-4, 6).reduce - result == Rational(-2, 3) + r = { num: -4, den: 6 } + result = r->reduce() + result == { num: -2, den: 3 } } # Reduce a rational number with a negative denominator to lowest terms expect { - result = Rational(3, -9).reduce - result == Rational(-1, 3) + r = { num: 3, den: -9 } + result = r->reduce() + result == { num: -1, den: 3 } } # Reduce zero to lowest terms expect { - result = Rational(0, 6).reduce - result == Rational(0, 1) + r = { num: 0, den: 6 } + result = r->reduce() + result == { num: 0, den: 1 } } # Reduce an integer to lowest terms expect { - result = Rational(-14, 7).reduce - result == Rational(-2, 1) + r = { num: -14, den: 7 } + result = r->reduce() + result == { num: -2, den: 1 } } # Reduce one to lowest terms expect { - result = Rational(13, 13).reduce - result == Rational(1, 1) + r = { num: 13, den: 13 } + result = r->reduce() + result == { num: 1, den: 1 } } # This program is only used to run tests with `roc test`, so main! does nothing. From af46d92de66be8d9b09691703edf077b10d7c989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Mon, 22 Jun 2026 23:38:56 +1200 Subject: [PATCH 150/162] Update protein-translation since issue 9700 was resolved --- .../protein-translation/.meta/Example.roc | 44 ++++++++----------- .../protein-translation-test.roc | 27 +++++++----- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/exercises/practice/protein-translation/.meta/Example.roc b/exercises/practice/protein-translation/.meta/Example.roc index 0277291c..260b5e8b 100644 --- a/exercises/practice/protein-translation/.meta/Example.roc +++ b/exercises/practice/protein-translation/.meta/Example.roc @@ -11,9 +11,7 @@ ProteinTranslation :: {}.{ [] => Ok(protein) [codon, .. as rest] => { match to_instruction(codon)? { - Append(amino_acid) => { - protein.append(amino_acid)->help(rest) - } + Append(amino_acid) => protein.append(amino_acid)->help(rest) Stop => Ok(protein) } } @@ -25,28 +23,24 @@ ProteinTranslation :: {}.{ to_instruction : Codon -> Try([Append(AminoAcid), Stop], [InvalidCodon(Codon)]) to_instruction = |codon| { - # See https://github.com/roc-lang/roc/issues/9700 - codon_str = codon->Str.from_utf8() ?? { - crash "Unreachable" - } - match codon_str { - "AUG" => Ok(Append(Methionine)) - "UUU" => Ok(Append(Phenylalanine)) - "UUC" => Ok(Append(Phenylalanine)) - "UUA" => Ok(Append(Leucine)) - "UUG" => Ok(Append(Leucine)) - "UCU" => Ok(Append(Serine)) - "UCC" => Ok(Append(Serine)) - "UCA" => Ok(Append(Serine)) - "UCG" => Ok(Append(Serine)) - "UAU" => Ok(Append(Tyrosine)) - "UAC" => Ok(Append(Tyrosine)) - "UGU" => Ok(Append(Cysteine)) - "UGC" => Ok(Append(Cysteine)) - "UGG" => Ok(Append(Tryptophan)) - "UAA" => Ok(Stop) - "UAG" => Ok(Stop) - "UGA" => Ok(Stop) + match codon { + ['A', 'U', 'G'] => Ok(Append(Methionine)) + ['U', 'U', 'U'] => Ok(Append(Phenylalanine)) + ['U', 'U', 'C'] => Ok(Append(Phenylalanine)) + ['U', 'U', 'A'] => Ok(Append(Leucine)) + ['U', 'U', 'G'] => Ok(Append(Leucine)) + ['U', 'C', 'U'] => Ok(Append(Serine)) + ['U', 'C', 'C'] => Ok(Append(Serine)) + ['U', 'C', 'A'] => Ok(Append(Serine)) + ['U', 'C', 'G'] => Ok(Append(Serine)) + ['U', 'A', 'U'] => Ok(Append(Tyrosine)) + ['U', 'A', 'C'] => Ok(Append(Tyrosine)) + ['U', 'G', 'U'] => Ok(Append(Cysteine)) + ['U', 'G', 'C'] => Ok(Append(Cysteine)) + ['U', 'G', 'G'] => Ok(Append(Tryptophan)) + ['U', 'A', 'A'] => Ok(Stop) + ['U', 'A', 'G'] => Ok(Stop) + ['U', 'G', 'A'] => Ok(Stop) _ => Err(InvalidCodon(codon)) } } diff --git a/exercises/practice/protein-translation/protein-translation-test.roc b/exercises/practice/protein-translation/protein-translation-test.roc index 68a57447..58515057 100644 --- a/exercises/practice/protein-translation/protein-translation-test.roc +++ b/exercises/practice/protein-translation/protein-translation-test.roc @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/protein-translation/canonical-data.json -# File last updated on 2026-06-13 +# File last updated on 2026-06-22 import ProteinTranslation exposing [to_protein] @@ -303,17 +303,20 @@ expect { result.is_err() } -# Incomplete RNA sequence can translate if valid until a STOP codon -expect { - rna = "UUCUUCUAAUGGU" - result = rna->to_protein() - result == Ok( - [ - Phenylalanine, - Phenylalanine, - ], - ) -} +# The following test is commented out for now because it causes a segfault +# See https://github.com/roc-lang/roc/issues/9753 + +## Incomplete RNA sequence can translate if valid until a STOP codon +#expect { +# rna = "UUCUUCUAAUGGU" +# result = rna->to_protein() +# result == Ok( +# [ +# Phenylalanine, +# Phenylalanine, +# ], +# ) +#} # This program is only used to run tests with `roc test`, so main! does nothing. main! = |_args| { From 5aafb87c567a752b68c715aa86cea8a1e25b4e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 00:26:49 +1200 Subject: [PATCH 151/162] Upgrade to roc-platform-template-zig version 0.9 --- config/generator_macros.j2 | 2 +- exercises/practice/anagram/anagram-test.roc | 2 +- exercises/practice/gigasecond/gigasecond-test.roc | 2 +- exercises/practice/meetup/meetup-test.roc | 2 +- exercises/practice/micro-blog/micro-blog-test.roc | 2 +- exercises/practice/rest-api/rest-api-test.roc | 2 +- exercises/practice/reverse-string/reverse-string-test.roc | 2 +- exercises/practice/sgf-parsing/sgf-parsing-test.roc | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config/generator_macros.j2 b/config/generator_macros.j2 index f0eb95f1..08570e1d 100644 --- a/config/generator_macros.j2 +++ b/config/generator_macros.j2 @@ -19,7 +19,7 @@ {%- if imports -%} app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst" + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.9/8GdFEvQYS3TeAZxKvTzCLVdQiomweGtXcdZkXNDEeABq.tar.zst" {%- for name in imports -%}, {% if name == "unicode" -%} unicode: "https://github.com/roc-lang/unicode/..." # TODO: update when a zig-compatible release is available diff --git a/exercises/practice/anagram/anagram-test.roc b/exercises/practice/anagram/anagram-test.roc index 73a3d991..9d9638f1 100644 --- a/exercises/practice/anagram/anagram-test.roc +++ b/exercises/practice/anagram/anagram-test.roc @@ -2,7 +2,7 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json # File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.9/8GdFEvQYS3TeAZxKvTzCLVdQiomweGtXcdZkXNDEeABq.tar.zst", unicode: "https://github.com/roc-lang/unicode/...", # TODO: update when a zig-compatible release is available } diff --git a/exercises/practice/gigasecond/gigasecond-test.roc b/exercises/practice/gigasecond/gigasecond-test.roc index 4ce28978..9da08629 100644 --- a/exercises/practice/gigasecond/gigasecond-test.roc +++ b/exercises/practice/gigasecond/gigasecond-test.roc @@ -2,7 +2,7 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/gigasecond/canonical-data.json # File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.9/8GdFEvQYS3TeAZxKvTzCLVdQiomweGtXcdZkXNDEeABq.tar.zst", isodate: "https://github.com/imclerran/roc-isodate/...", # TODO: update when a zig-compatible release is available } diff --git a/exercises/practice/meetup/meetup-test.roc b/exercises/practice/meetup/meetup-test.roc index 8c091cce..d7039b46 100644 --- a/exercises/practice/meetup/meetup-test.roc +++ b/exercises/practice/meetup/meetup-test.roc @@ -2,7 +2,7 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/meetup/canonical-data.json # File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.9/8GdFEvQYS3TeAZxKvTzCLVdQiomweGtXcdZkXNDEeABq.tar.zst", isodate: "https://github.com/imclerran/roc-isodate/...", # TODO: update when a zig-compatible release is available } diff --git a/exercises/practice/micro-blog/micro-blog-test.roc b/exercises/practice/micro-blog/micro-blog-test.roc index 3172b662..cb0515a0 100644 --- a/exercises/practice/micro-blog/micro-blog-test.roc +++ b/exercises/practice/micro-blog/micro-blog-test.roc @@ -2,7 +2,7 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/micro-blog/canonical-data.json # File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.9/8GdFEvQYS3TeAZxKvTzCLVdQiomweGtXcdZkXNDEeABq.tar.zst", unicode: "https://github.com/roc-lang/unicode/...", # TODO: update when a zig-compatible release is available } diff --git a/exercises/practice/rest-api/rest-api-test.roc b/exercises/practice/rest-api/rest-api-test.roc index 826c4454..0ab09fea 100644 --- a/exercises/practice/rest-api/rest-api-test.roc +++ b/exercises/practice/rest-api/rest-api-test.roc @@ -2,7 +2,7 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/rest-api/canonical-data.json # File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.9/8GdFEvQYS3TeAZxKvTzCLVdQiomweGtXcdZkXNDEeABq.tar.zst", json: "https://github.com/lukewilliamboswell/roc-json/...", # TODO: update when a zig-compatible release is available } diff --git a/exercises/practice/reverse-string/reverse-string-test.roc b/exercises/practice/reverse-string/reverse-string-test.roc index 8ace6964..0884b070 100644 --- a/exercises/practice/reverse-string/reverse-string-test.roc +++ b/exercises/practice/reverse-string/reverse-string-test.roc @@ -2,7 +2,7 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/reverse-string/canonical-data.json # File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.9/8GdFEvQYS3TeAZxKvTzCLVdQiomweGtXcdZkXNDEeABq.tar.zst", unicode: "https://github.com/roc-lang/unicode/...", # TODO: update when a zig-compatible release is available } diff --git a/exercises/practice/sgf-parsing/sgf-parsing-test.roc b/exercises/practice/sgf-parsing/sgf-parsing-test.roc index 2835836b..1647d6e5 100644 --- a/exercises/practice/sgf-parsing/sgf-parsing-test.roc +++ b/exercises/practice/sgf-parsing/sgf-parsing-test.roc @@ -2,7 +2,7 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/sgf-parsing/canonical-data.json # File last updated on 2026-06-21 app [main!] { - pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.8/8qf28cxTaxwA16Xe3VBR7YSP2KLVUqDHiPpFYgyikEa1.tar.zst", + pf: platform "https://github.com/lukewilliamboswell/roc-platform-template-zig/releases/download/0.9/8GdFEvQYS3TeAZxKvTzCLVdQiomweGtXcdZkXNDEeABq.tar.zst", parser: "https://github.com/lukewilliamboswell/roc-parser/...", # TODO: update when a zig-compatible release is available } From bf3cbb8bf4fdba4a0bb2609929fdf96087aaaabe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 00:27:30 +1200 Subject: [PATCH 152/162] Add TODO regarding issue 9753 --- .../practice/protein-translation/protein-translation-test.roc | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/protein-translation/protein-translation-test.roc b/exercises/practice/protein-translation/protein-translation-test.roc index 58515057..815504b6 100644 --- a/exercises/practice/protein-translation/protein-translation-test.roc +++ b/exercises/practice/protein-translation/protein-translation-test.roc @@ -305,6 +305,7 @@ expect { # The following test is commented out for now because it causes a segfault # See https://github.com/roc-lang/roc/issues/9753 +# TODO: uncomment once the issue is resolved ## Incomplete RNA sequence can translate if valid until a STOP codon #expect { From b3cd07b839fccd4494c40eefae5ddfe2d4622a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 12:27:23 +1200 Subject: [PATCH 153/162] Fix Err `Err "..."` to `Err("...")` --- exercises/practice/error-handling/.docs/instructions.append.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/error-handling/.docs/instructions.append.md b/exercises/practice/error-handling/.docs/instructions.append.md index 12e3e849..33994666 100644 --- a/exercises/practice/error-handling/.docs/instructions.append.md +++ b/exercises/practice/error-handling/.docs/instructions.append.md @@ -12,7 +12,7 @@ You are building a tiny web server that queries an even tinier user database. So When things go wrong, it's important to give the end user a nice and helpful error message so that they can solve the issue. For this, you need to ensure that your code propagates informative errors from the point where the error is detected to the point where the error message is produced. -Luckily, Roc allows you to carry payload (i.e., data) inside your `Err` tag. It's tempting to carry an error message directly (e.g., `Err "User #42 was not found"`), and this may be fine in some simple cases, but this has several limitations: +Luckily, Roc allows you to carry payload (i.e., data) inside your `Err` tag. It's tempting to carry an error message directly (e.g., `Err("User #42 was not found")`), and this may be fine in some simple cases, but this has several limitations: - you might not have enough context inside the function that detects the error to produce a sufficiently helpful error message. - For example, the `Str.to_u64` function can be used to parse all sorts of integers: days, seconds, user IDs, and more. If the error message just says `Could not convert string "0.5" to a positive integer`, the user may not have enough context to solve the issue. From 402e1953744741fb95dc4b24735ab83b3fe096a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 12:29:59 +1200 Subject: [PATCH 154/162] Migrate errorMessage to error_message --- exercises/practice/error-handling/.docs/instructions.append.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/error-handling/.docs/instructions.append.md b/exercises/practice/error-handling/.docs/instructions.append.md index 33994666..e4519f4f 100644 --- a/exercises/practice/error-handling/.docs/instructions.append.md +++ b/exercises/practice/error-handling/.docs/instructions.append.md @@ -34,7 +34,7 @@ Okay, let's get started! Here's what you need to do: - If the path is not `/` or `/users/` or `/users/`, return `Err(PageNotFound(path))` - If the user ID is not a positive integer, return `Err(InvalidUserId(user_id_str))` - If the user does not exist, return `Err(UserNotFound(user_id))` -4. Implement `errorMessage` to convert the previous errors to translated error messages. The function should at least handle English, but you are encouraged to try handling another language as well. The English error messages should like this: +4. Implement `error_essage` to convert the previous errors to translated error messages. The function should at least handle English, but you are encouraged to try handling another language as well. The English error messages should like this: - `"Insecure connection (non HTTPS): http://example.com/users/789"` - `"Invalid domain name: https://google.com/wrong"` From b8789d73df5b5884bdcb9213dd8d015d47dc4c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 15:21:47 +1200 Subject: [PATCH 155/162] Replace ComplexNumbers.Complex with simply Complex, and replace method names with Roc standards such as plus, times, or div_by --- .../complex-numbers/.meta/Example.roc | 55 ++-- .../complex-numbers/.meta/config.json | 2 +- .../practice/complex-numbers/.meta/plugins.py | 2 +- .../complex-numbers/.meta/template.j2 | 20 +- .../practice/complex-numbers/Complex.roc | 37 +++ .../complex-numbers/ComplexNumbers.roc | 49 ---- .../complex-numbers/complex-numbers-test.roc | 258 +++++++++--------- 7 files changed, 209 insertions(+), 214 deletions(-) create mode 100644 exercises/practice/complex-numbers/Complex.roc delete mode 100644 exercises/practice/complex-numbers/ComplexNumbers.roc diff --git a/exercises/practice/complex-numbers/.meta/Example.roc b/exercises/practice/complex-numbers/.meta/Example.roc index f499c670..7c70e463 100644 --- a/exercises/practice/complex-numbers/.meta/Example.roc +++ b/exercises/practice/complex-numbers/.meta/Example.roc @@ -1,64 +1,59 @@ -ComplexNumbers :: {}.{ - Complex := { re : F64, im : F64 } +Complex := { real : F64, imaginary : F64 }.{ + complex : F64, F64 -> Complex + complex = |real, imaginary| { { real, imaginary } } - real : Complex -> F64 - real = |z| z.re - - imaginary : Complex -> F64 - imaginary = |z| z.im - - add : Complex, Complex -> Complex - add = |{ re: a, im: b }, { re: c, im: d }| { + plus : Complex, Complex -> Complex + plus = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { { - re: a + c, - im: b + d, + real: a + c, + imaginary: b + d, } } - sub : Complex, Complex -> Complex - sub = |{ re: a, im: b }, { re: c, im: d }| { + minus : Complex, Complex -> Complex + minus = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { { - re: a - c, - im: b - d, + real: a - c, + imaginary: b - d, } } - mul : Complex, Complex -> Complex - mul = |{ re: a, im: b }, { re: c, im: d }| { + times : Complex, Complex -> Complex + times = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { { - re: a * c - b * d, - im: a * d + b * c, + real: a * c - b * d, + imaginary: a * d + b * c, } } - div : Complex, Complex -> Complex - div = |{ re: a, im: b }, { re: c, im: d }| { + div_by : Complex, Complex -> Complex + div_by = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { denominator = c * c + d * d { - re: (a * c + b * d) / denominator, - im: (b * c - a * d) / denominator, + real: (a * c + b * d) / denominator, + imaginary: (b * c - a * d) / denominator, } } conjugate : Complex -> Complex conjugate = |z| { { - re: z.re, - im: -z.im, + real: z.real, + imaginary: -z.imaginary, } } abs : Complex -> F64 - abs = |{ re: a, im: b }| { + abs = |{ real: a, imaginary: b }| { sqrt(a * a + b * b) } exp : Complex -> Complex exp = |z| { - factor = e->pow(z.re) + factor = e->pow(z.real) { - re: factor * cos(z.im), - im: factor * sin(z.im), + real: factor * cos(z.imaginary), + imaginary: factor * sin(z.imaginary), } } } diff --git a/exercises/practice/complex-numbers/.meta/config.json b/exercises/practice/complex-numbers/.meta/config.json index 4270326b..5a17e1f9 100644 --- a/exercises/practice/complex-numbers/.meta/config.json +++ b/exercises/practice/complex-numbers/.meta/config.json @@ -4,7 +4,7 @@ ], "files": { "solution": [ - "ComplexNumbers.roc" + "Complex.roc" ], "test": [ "complex-numbers-test.roc" diff --git a/exercises/practice/complex-numbers/.meta/plugins.py b/exercises/practice/complex-numbers/.meta/plugins.py index dfefe221..522e2820 100644 --- a/exercises/practice/complex-numbers/.meta/plugins.py +++ b/exercises/practice/complex-numbers/.meta/plugins.py @@ -22,4 +22,4 @@ def to_complex_number(value): else: re = to_roc(value) im = 0 - return f"{{ re: {re}, im: {im} }}" + return f"complex({re}, {im})" diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2 index 82e53ab4..5f6315b0 100644 --- a/exercises/practice/complex-numbers/.meta/template.j2 +++ b/exercises/practice/complex-numbers/.meta/template.j2 @@ -1,8 +1,20 @@ {%- import "generator_macros.j2" as macros with context -%} {{ macros.canonical_ref() }} {{ macros.header() }} +{% set equations = { + "real": "z.real", + "imaginary": "z.imaginary", + "conjugate": "z.conjugate()", + "abs": "z.abs()", + "exp": "z.exp()", + "add": "z1 + z2", + "sub": "z1 - z2", + "mul": "z1 * z2", + "div": "z1 / z2" +} -%} -import {{ exercise | to_pascal }} exposing [Complex, real, imaginary, add, sub, mul, div, conjugate, abs, exp] + +import Complex exposing [complex] {% for supercase in cases %} ### @@ -22,7 +34,7 @@ import {{ exercise | to_pascal }} exposing [Complex, real, imaginary, add, sub, {%- if subcase["input"]["z"] %} expect { z = {{ plugins.to_complex_number(subcase["input"]["z"]) }} - result = {{ subcase["property"] }}(z) + result = {{ equations[subcase["property"]] }} {%- if subcase["expected"] is iterable and subcase["expected"] is not string %} expected = {{ plugins.to_complex_number(subcase["expected"]) }} result -> complex_is_approx_eq(expected) @@ -34,7 +46,7 @@ expect { expect { z1 = {{ plugins.to_complex_number(subcase["input"]["z1"]) }} z2 = {{ plugins.to_complex_number(subcase["input"]["z2"]) }} - result = {{ subcase["property"] }}(z1, z2) + result = {{ equations[subcase["property"]] }} expected = {{ plugins.to_complex_number(subcase["expected"]) }} result -> complex_is_approx_eq(expected) } @@ -52,7 +64,7 @@ is_approx_eq = |x1, x2| { } complex_is_approx_eq = |z1, z2| { - is_approx_eq(z1.re, z2.re) and is_approx_eq(z1.im, z2.im) + is_approx_eq(z1.real, z2.real) and is_approx_eq(z1.imaginary, z2.imaginary) } {{ macros.footer() }} diff --git a/exercises/practice/complex-numbers/Complex.roc b/exercises/practice/complex-numbers/Complex.roc new file mode 100644 index 00000000..48545674 --- /dev/null +++ b/exercises/practice/complex-numbers/Complex.roc @@ -0,0 +1,37 @@ +Complex := { real: F64, imaginary: F64 }.{ + plus : Complex, Complex -> Complex + plus = |z1, z2| { + crash "Please implement the 'plus' function" + } + + minus : Complex, Complex -> Complex + minus = |z1, z2| { + crash "Please implement the 'minus' function" + } + + times : Complex, Complex -> Complex + times = |z1, z2| { + crash "Please implement the 'times' function" + } + + div_by : Complex, Complex -> Complex + div_by = |z1, z2| { + crash "Please implement the 'div_by' function" + } + + conjugate : Complex -> Complex + conjugate = |z| { + crash "Please implement the 'conjugate' function" + } + + abs : Complex -> F64 + abs = |z| { + crash "Please implement the 'abs' function" + } + + exp : Complex -> Complex + exp = |z| { + crash "Please implement the 'exp' function" + } +} + diff --git a/exercises/practice/complex-numbers/ComplexNumbers.roc b/exercises/practice/complex-numbers/ComplexNumbers.roc deleted file mode 100644 index 34c5e706..00000000 --- a/exercises/practice/complex-numbers/ComplexNumbers.roc +++ /dev/null @@ -1,49 +0,0 @@ -ComplexNumbers :: {}.{ - Complex : { re : F64, im : F64 } - - real : Complex -> F64 - real = |z| { - crash "Please implement the 'real' function" - } - - imaginary : Complex -> F64 - imaginary = |z| { - crash "Please implement the 'imaginary' function" - } - - add : Complex, Complex -> Complex - add = |z1, z2| { - crash "Please implement the 'add' function" - } - - sub : Complex, Complex -> Complex - sub = |z1, z2| { - crash "Please implement the 'sub' function" - } - - mul : Complex, Complex -> Complex - mul = |z1, z2| { - crash "Please implement the 'mul' function" - } - - div : Complex, Complex -> Complex - div = |z1, z2| { - crash "Please implement the 'div' function" - } - - conjugate : Complex -> Complex - conjugate = |z| { - crash "Please implement the 'conjugate' function" - } - - abs : Complex -> F64 - abs = |z| { - crash "Please implement the 'abs' function" - } - - exp : Complex -> Complex - exp = |z| { - crash "Please implement the 'exp' function" - } -} - diff --git a/exercises/practice/complex-numbers/complex-numbers-test.roc b/exercises/practice/complex-numbers/complex-numbers-test.roc index 8e501ab7..cc955740 100644 --- a/exercises/practice/complex-numbers/complex-numbers-test.roc +++ b/exercises/practice/complex-numbers/complex-numbers-test.roc @@ -1,8 +1,8 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json -# File last updated on 2026-06-22 +# File last updated on 2026-06-23 -import ComplexNumbers exposing [Complex, real, imaginary, add, sub, mul, div, conjugate, abs, exp] +import Complex exposing [complex] ### ### Real part @@ -10,22 +10,22 @@ import ComplexNumbers exposing [Complex, real, imaginary, add, sub, mul, div, co # Real part of a purely real number expect { - z = { re: 1, im: 0 } - result = real(z) + z = complex(1, 0) + result = z.real result->is_approx_eq(1) } # Real part of a purely imaginary number expect { - z = { re: 0, im: 1 } - result = real(z) + z = complex(0, 1) + result = z.real result->is_approx_eq(0) } # Real part of a number with real and imaginary part expect { - z = { re: 1, im: 2 } - result = real(z) + z = complex(1, 2) + result = z.real result->is_approx_eq(1) } @@ -35,22 +35,22 @@ expect { # Imaginary part of a purely real number expect { - z = { re: 1, im: 0 } - result = imaginary(z) + z = complex(1, 0) + result = z.imaginary result->is_approx_eq(0) } # Imaginary part of a purely imaginary number expect { - z = { re: 0, im: 1 } - result = imaginary(z) + z = complex(0, 1) + result = z.imaginary result->is_approx_eq(1) } # Imaginary part of a number with real and imaginary part expect { - z = { re: 1, im: 2 } - result = imaginary(z) + z = complex(1, 2) + result = z.imaginary result->is_approx_eq(2) } @@ -66,28 +66,28 @@ expect { # Add purely real numbers expect { - z1 = { re: 1, im: 0 } - z2 = { re: 2, im: 0 } - result = add(z1, z2) - expected = { re: 3, im: 0 } + z1 = complex(1, 0) + z2 = complex(2, 0) + result = z1 + z2 + expected = complex(3, 0) result->complex_is_approx_eq(expected) } # Add purely imaginary numbers expect { - z1 = { re: 0, im: 1 } - z2 = { re: 0, im: 2 } - result = add(z1, z2) - expected = { re: 0, im: 3 } + z1 = complex(0, 1) + z2 = complex(0, 2) + result = z1 + z2 + expected = complex(0, 3) result->complex_is_approx_eq(expected) } # Add numbers with real and imaginary part expect { - z1 = { re: 1, im: 2 } - z2 = { re: 3, im: 4 } - result = add(z1, z2) - expected = { re: 4, im: 6 } + z1 = complex(1, 2) + z2 = complex(3, 4) + result = z1 + z2 + expected = complex(4, 6) result->complex_is_approx_eq(expected) } @@ -95,28 +95,28 @@ expect { # Subtract purely real numbers expect { - z1 = { re: 1, im: 0 } - z2 = { re: 2, im: 0 } - result = sub(z1, z2) - expected = { re: -1, im: 0 } + z1 = complex(1, 0) + z2 = complex(2, 0) + result = z1 - z2 + expected = complex(-1, 0) result->complex_is_approx_eq(expected) } # Subtract purely imaginary numbers expect { - z1 = { re: 0, im: 1 } - z2 = { re: 0, im: 2 } - result = sub(z1, z2) - expected = { re: 0, im: -1 } + z1 = complex(0, 1) + z2 = complex(0, 2) + result = z1 - z2 + expected = complex(0, -1) result->complex_is_approx_eq(expected) } # Subtract numbers with real and imaginary part expect { - z1 = { re: 1, im: 2 } - z2 = { re: 3, im: 4 } - result = sub(z1, z2) - expected = { re: -2, im: -2 } + z1 = complex(1, 2) + z2 = complex(3, 4) + result = z1 - z2 + expected = complex(-2, -2) result->complex_is_approx_eq(expected) } @@ -124,28 +124,28 @@ expect { # Multiply purely real numbers expect { - z1 = { re: 1, im: 0 } - z2 = { re: 2, im: 0 } - result = mul(z1, z2) - expected = { re: 2, im: 0 } + z1 = complex(1, 0) + z2 = complex(2, 0) + result = z1 * z2 + expected = complex(2, 0) result->complex_is_approx_eq(expected) } # Multiply purely imaginary numbers expect { - z1 = { re: 0, im: 1 } - z2 = { re: 0, im: 2 } - result = mul(z1, z2) - expected = { re: -2, im: 0 } + z1 = complex(0, 1) + z2 = complex(0, 2) + result = z1 * z2 + expected = complex(-2, 0) result->complex_is_approx_eq(expected) } # Multiply numbers with real and imaginary part expect { - z1 = { re: 1, im: 2 } - z2 = { re: 3, im: 4 } - result = mul(z1, z2) - expected = { re: -5, im: 10 } + z1 = complex(1, 2) + z2 = complex(3, 4) + result = z1 * z2 + expected = complex(-5, 10) result->complex_is_approx_eq(expected) } @@ -153,28 +153,28 @@ expect { # Divide purely real numbers expect { - z1 = { re: 1, im: 0 } - z2 = { re: 2, im: 0 } - result = div(z1, z2) - expected = { re: 0.5, im: 0 } + z1 = complex(1, 0) + z2 = complex(2, 0) + result = z1 / z2 + expected = complex(0.5, 0) result->complex_is_approx_eq(expected) } # Divide purely imaginary numbers expect { - z1 = { re: 0, im: 1 } - z2 = { re: 0, im: 2 } - result = div(z1, z2) - expected = { re: 0.5, im: 0 } + z1 = complex(0, 1) + z2 = complex(0, 2) + result = z1 / z2 + expected = complex(0.5, 0) result->complex_is_approx_eq(expected) } # Divide numbers with real and imaginary part expect { - z1 = { re: 1, im: 2 } - z2 = { re: 3, im: 4 } - result = div(z1, z2) - expected = { re: 0.44, im: 0.08 } + z1 = complex(1, 2) + z2 = complex(3, 4) + result = z1 / z2 + expected = complex(0.44, 0.08) result->complex_is_approx_eq(expected) } @@ -184,36 +184,36 @@ expect { # Absolute value of a positive purely real number expect { - z = { re: 5, im: 0 } - result = abs(z) + z = complex(5, 0) + result = z.abs() result->is_approx_eq(5) } # Absolute value of a negative purely real number expect { - z = { re: -5, im: 0 } - result = abs(z) + z = complex(-5, 0) + result = z.abs() result->is_approx_eq(5) } # Absolute value of a purely imaginary number with positive imaginary part expect { - z = { re: 0, im: 5 } - result = abs(z) + z = complex(0, 5) + result = z.abs() result->is_approx_eq(5) } # Absolute value of a purely imaginary number with negative imaginary part expect { - z = { re: 0, im: -5 } - result = abs(z) + z = complex(0, -5) + result = z.abs() result->is_approx_eq(5) } # Absolute value of a number with real and imaginary part expect { - z = { re: 3, im: 4 } - result = abs(z) + z = complex(3, 4) + result = z.abs() result->is_approx_eq(5) } @@ -223,25 +223,25 @@ expect { # Conjugate a purely real number expect { - z = { re: 5, im: 0 } - result = conjugate(z) - expected = { re: 5, im: 0 } + z = complex(5, 0) + result = z.conjugate() + expected = complex(5, 0) result->complex_is_approx_eq(expected) } # Conjugate a purely imaginary number expect { - z = { re: 0, im: 5 } - result = conjugate(z) - expected = { re: 0, im: -5 } + z = complex(0, 5) + result = z.conjugate() + expected = complex(0, -5) result->complex_is_approx_eq(expected) } # Conjugate a number with real and imaginary part expect { - z = { re: 1, im: 1 } - result = conjugate(z) - expected = { re: 1, im: -1 } + z = complex(1, 1) + result = z.conjugate() + expected = complex(1, -1) result->complex_is_approx_eq(expected) } @@ -251,41 +251,41 @@ expect { # Euler's identity/formula expect { - z = { re: 0, im: 3.141592653589793.F64 } - result = exp(z) - expected = { re: -1, im: 0 } + z = complex(0, 3.141592653589793.F64) + result = z.exp() + expected = complex(-1, 0) result->complex_is_approx_eq(expected) } # Exponential of 0 expect { - z = { re: 0, im: 0 } - result = exp(z) - expected = { re: 1, im: 0 } + z = complex(0, 0) + result = z.exp() + expected = complex(1, 0) result->complex_is_approx_eq(expected) } # Exponential of a purely real number expect { - z = { re: 1, im: 0 } - result = exp(z) - expected = { re: 2.718281828459045.F64, im: 0 } + z = complex(1, 0) + result = z.exp() + expected = complex(2.718281828459045.F64, 0) result->complex_is_approx_eq(expected) } # Exponential of a number with real and imaginary part expect { - z = { re: 0.6931471805599453.F64, im: 3.141592653589793.F64 } - result = exp(z) - expected = { re: -2, im: 0 } + z = complex(0.6931471805599453.F64, 3.141592653589793.F64) + result = z.exp() + expected = complex(-2, 0) result->complex_is_approx_eq(expected) } # Exponential resulting in a number with real and imaginary part expect { - z = { re: 0.6931471805599453.F64 / 2, im: 3.141592653589793.F64 / 4 } - result = exp(z) - expected = { re: 1, im: 1 } + z = complex(0.6931471805599453.F64 / 2, 3.141592653589793.F64 / 4) + result = z.exp() + expected = complex(1, 1) result->complex_is_approx_eq(expected) } @@ -295,73 +295,73 @@ expect { # Add real number to complex number expect { - z1 = { re: 1, im: 2 } - z2 = { re: 5, im: 0 } - result = add(z1, z2) - expected = { re: 6, im: 2 } + z1 = complex(1, 2) + z2 = complex(5, 0) + result = z1 + z2 + expected = complex(6, 2) result->complex_is_approx_eq(expected) } # Add complex number to real number expect { - z1 = { re: 5, im: 0 } - z2 = { re: 1, im: 2 } - result = add(z1, z2) - expected = { re: 6, im: 2 } + z1 = complex(5, 0) + z2 = complex(1, 2) + result = z1 + z2 + expected = complex(6, 2) result->complex_is_approx_eq(expected) } # Subtract real number from complex number expect { - z1 = { re: 5, im: 7 } - z2 = { re: 4, im: 0 } - result = sub(z1, z2) - expected = { re: 1, im: 7 } + z1 = complex(5, 7) + z2 = complex(4, 0) + result = z1 - z2 + expected = complex(1, 7) result->complex_is_approx_eq(expected) } # Subtract complex number from real number expect { - z1 = { re: 4, im: 0 } - z2 = { re: 5, im: 7 } - result = sub(z1, z2) - expected = { re: -1, im: -7 } + z1 = complex(4, 0) + z2 = complex(5, 7) + result = z1 - z2 + expected = complex(-1, -7) result->complex_is_approx_eq(expected) } # Multiply complex number by real number expect { - z1 = { re: 2, im: 5 } - z2 = { re: 5, im: 0 } - result = mul(z1, z2) - expected = { re: 10, im: 25 } + z1 = complex(2, 5) + z2 = complex(5, 0) + result = z1 * z2 + expected = complex(10, 25) result->complex_is_approx_eq(expected) } # Multiply real number by complex number expect { - z1 = { re: 5, im: 0 } - z2 = { re: 2, im: 5 } - result = mul(z1, z2) - expected = { re: 10, im: 25 } + z1 = complex(5, 0) + z2 = complex(2, 5) + result = z1 * z2 + expected = complex(10, 25) result->complex_is_approx_eq(expected) } # Divide complex number by real number expect { - z1 = { re: 10, im: 100 } - z2 = { re: 10, im: 0 } - result = div(z1, z2) - expected = { re: 1, im: 10 } + z1 = complex(10, 100) + z2 = complex(10, 0) + result = z1 / z2 + expected = complex(1, 10) result->complex_is_approx_eq(expected) } # Divide real number by complex number expect { - z1 = { re: 5, im: 0 } - z2 = { re: 1, im: 1 } - result = div(z1, z2) - expected = { re: 2.5, im: -2.5 } + z1 = complex(5, 0) + z2 = complex(1, 1) + result = z1 / z2 + expected = complex(2.5, -2.5) result->complex_is_approx_eq(expected) } @@ -376,7 +376,7 @@ is_approx_eq = |x1, x2| { } complex_is_approx_eq = |z1, z2| { - is_approx_eq(z1.re, z2.re) and is_approx_eq(z1.im, z2.im) + is_approx_eq(z1.real, z2.real) and is_approx_eq(z1.imaginary, z2.imaginary) } # This program is only used to run tests with `roc test`, so main! does nothing. From 9eaa62799f4878af1e25f291ae8792ad0015fc84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 15:41:19 +1200 Subject: [PATCH 156/162] Replace `complex` with `new` and add a few comments --- .../complex-numbers/.meta/Example.roc | 8 +- .../practice/complex-numbers/.meta/plugins.py | 2 +- .../complex-numbers/.meta/template.j2 | 2 +- .../practice/complex-numbers/Complex.roc | 9 + .../complex-numbers/complex-numbers-test.roc | 176 +++++++++--------- 5 files changed, 105 insertions(+), 92 deletions(-) diff --git a/exercises/practice/complex-numbers/.meta/Example.roc b/exercises/practice/complex-numbers/.meta/Example.roc index 7c70e463..b2c3e3e3 100644 --- a/exercises/practice/complex-numbers/.meta/Example.roc +++ b/exercises/practice/complex-numbers/.meta/Example.roc @@ -1,7 +1,8 @@ Complex := { real : F64, imaginary : F64 }.{ - complex : F64, F64 -> Complex - complex = |real, imaginary| { { real, imaginary } } + new : F64, F64 -> Complex + new = |real, imaginary| { { real, imaginary } } + # # The user can write plus(z1, z2), z1.plus(z2), or simply z1 + z2 plus : Complex, Complex -> Complex plus = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { { @@ -10,6 +11,7 @@ Complex := { real : F64, imaginary : F64 }.{ } } + # # The user can write minus(z1, z2), z1.minus(z2), or simply z1 - z2 minus : Complex, Complex -> Complex minus = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { { @@ -18,6 +20,7 @@ Complex := { real : F64, imaginary : F64 }.{ } } + # # The user can write times(z1, z2), z1.times(z2), or simply z1 * z2 times : Complex, Complex -> Complex times = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { { @@ -26,6 +29,7 @@ Complex := { real : F64, imaginary : F64 }.{ } } + # # The user can write div_by(z1, z2), z1.div_by(z2), or simply z1 / z2 div_by : Complex, Complex -> Complex div_by = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { denominator = c * c + d * d diff --git a/exercises/practice/complex-numbers/.meta/plugins.py b/exercises/practice/complex-numbers/.meta/plugins.py index 522e2820..960b1210 100644 --- a/exercises/practice/complex-numbers/.meta/plugins.py +++ b/exercises/practice/complex-numbers/.meta/plugins.py @@ -22,4 +22,4 @@ def to_complex_number(value): else: re = to_roc(value) im = 0 - return f"complex({re}, {im})" + return f"Complex.new({re}, {im})" diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2 index 5f6315b0..ea5b180e 100644 --- a/exercises/practice/complex-numbers/.meta/template.j2 +++ b/exercises/practice/complex-numbers/.meta/template.j2 @@ -14,7 +14,7 @@ } -%} -import Complex exposing [complex] +import Complex {% for supercase in cases %} ### diff --git a/exercises/practice/complex-numbers/Complex.roc b/exercises/practice/complex-numbers/Complex.roc index 48545674..7ae1dd4b 100644 --- a/exercises/practice/complex-numbers/Complex.roc +++ b/exercises/practice/complex-numbers/Complex.roc @@ -1,19 +1,28 @@ Complex := { real: F64, imaginary: F64 }.{ + new : F64, F64 -> Complex + new = |real, imaginary| { + crash "Please implement the 'new' function" + } + + # # The user can write plus(z1, z2), z1.plus(z2), or simply z1 + z2 plus : Complex, Complex -> Complex plus = |z1, z2| { crash "Please implement the 'plus' function" } + # # The user can write minus(z1, z2), z1.minus(z2), or simply z1 - z2 minus : Complex, Complex -> Complex minus = |z1, z2| { crash "Please implement the 'minus' function" } + # # The user can write times(z1, z2), z1.times(z2), or simply z1 * z2 times : Complex, Complex -> Complex times = |z1, z2| { crash "Please implement the 'times' function" } + # # The user can write div_by(z1, z2), z1.div_by(z2), or simply z1 / z2 div_by : Complex, Complex -> Complex div_by = |z1, z2| { crash "Please implement the 'div_by' function" diff --git a/exercises/practice/complex-numbers/complex-numbers-test.roc b/exercises/practice/complex-numbers/complex-numbers-test.roc index cc955740..294f8c6a 100644 --- a/exercises/practice/complex-numbers/complex-numbers-test.roc +++ b/exercises/practice/complex-numbers/complex-numbers-test.roc @@ -2,7 +2,7 @@ # https://github.com/exercism/problem-specifications/tree/main/exercises/complex-numbers/canonical-data.json # File last updated on 2026-06-23 -import Complex exposing [complex] +import Complex ### ### Real part @@ -10,21 +10,21 @@ import Complex exposing [complex] # Real part of a purely real number expect { - z = complex(1, 0) + z = Complex.new(1, 0) result = z.real result->is_approx_eq(1) } # Real part of a purely imaginary number expect { - z = complex(0, 1) + z = Complex.new(0, 1) result = z.real result->is_approx_eq(0) } # Real part of a number with real and imaginary part expect { - z = complex(1, 2) + z = Complex.new(1, 2) result = z.real result->is_approx_eq(1) } @@ -35,21 +35,21 @@ expect { # Imaginary part of a purely real number expect { - z = complex(1, 0) + z = Complex.new(1, 0) result = z.imaginary result->is_approx_eq(0) } # Imaginary part of a purely imaginary number expect { - z = complex(0, 1) + z = Complex.new(0, 1) result = z.imaginary result->is_approx_eq(1) } # Imaginary part of a number with real and imaginary part expect { - z = complex(1, 2) + z = Complex.new(1, 2) result = z.imaginary result->is_approx_eq(2) } @@ -66,28 +66,28 @@ expect { # Add purely real numbers expect { - z1 = complex(1, 0) - z2 = complex(2, 0) + z1 = Complex.new(1, 0) + z2 = Complex.new(2, 0) result = z1 + z2 - expected = complex(3, 0) + expected = Complex.new(3, 0) result->complex_is_approx_eq(expected) } # Add purely imaginary numbers expect { - z1 = complex(0, 1) - z2 = complex(0, 2) + z1 = Complex.new(0, 1) + z2 = Complex.new(0, 2) result = z1 + z2 - expected = complex(0, 3) + expected = Complex.new(0, 3) result->complex_is_approx_eq(expected) } # Add numbers with real and imaginary part expect { - z1 = complex(1, 2) - z2 = complex(3, 4) + z1 = Complex.new(1, 2) + z2 = Complex.new(3, 4) result = z1 + z2 - expected = complex(4, 6) + expected = Complex.new(4, 6) result->complex_is_approx_eq(expected) } @@ -95,28 +95,28 @@ expect { # Subtract purely real numbers expect { - z1 = complex(1, 0) - z2 = complex(2, 0) + z1 = Complex.new(1, 0) + z2 = Complex.new(2, 0) result = z1 - z2 - expected = complex(-1, 0) + expected = Complex.new(-1, 0) result->complex_is_approx_eq(expected) } # Subtract purely imaginary numbers expect { - z1 = complex(0, 1) - z2 = complex(0, 2) + z1 = Complex.new(0, 1) + z2 = Complex.new(0, 2) result = z1 - z2 - expected = complex(0, -1) + expected = Complex.new(0, -1) result->complex_is_approx_eq(expected) } # Subtract numbers with real and imaginary part expect { - z1 = complex(1, 2) - z2 = complex(3, 4) + z1 = Complex.new(1, 2) + z2 = Complex.new(3, 4) result = z1 - z2 - expected = complex(-2, -2) + expected = Complex.new(-2, -2) result->complex_is_approx_eq(expected) } @@ -124,28 +124,28 @@ expect { # Multiply purely real numbers expect { - z1 = complex(1, 0) - z2 = complex(2, 0) + z1 = Complex.new(1, 0) + z2 = Complex.new(2, 0) result = z1 * z2 - expected = complex(2, 0) + expected = Complex.new(2, 0) result->complex_is_approx_eq(expected) } # Multiply purely imaginary numbers expect { - z1 = complex(0, 1) - z2 = complex(0, 2) + z1 = Complex.new(0, 1) + z2 = Complex.new(0, 2) result = z1 * z2 - expected = complex(-2, 0) + expected = Complex.new(-2, 0) result->complex_is_approx_eq(expected) } # Multiply numbers with real and imaginary part expect { - z1 = complex(1, 2) - z2 = complex(3, 4) + z1 = Complex.new(1, 2) + z2 = Complex.new(3, 4) result = z1 * z2 - expected = complex(-5, 10) + expected = Complex.new(-5, 10) result->complex_is_approx_eq(expected) } @@ -153,28 +153,28 @@ expect { # Divide purely real numbers expect { - z1 = complex(1, 0) - z2 = complex(2, 0) + z1 = Complex.new(1, 0) + z2 = Complex.new(2, 0) result = z1 / z2 - expected = complex(0.5, 0) + expected = Complex.new(0.5, 0) result->complex_is_approx_eq(expected) } # Divide purely imaginary numbers expect { - z1 = complex(0, 1) - z2 = complex(0, 2) + z1 = Complex.new(0, 1) + z2 = Complex.new(0, 2) result = z1 / z2 - expected = complex(0.5, 0) + expected = Complex.new(0.5, 0) result->complex_is_approx_eq(expected) } # Divide numbers with real and imaginary part expect { - z1 = complex(1, 2) - z2 = complex(3, 4) + z1 = Complex.new(1, 2) + z2 = Complex.new(3, 4) result = z1 / z2 - expected = complex(0.44, 0.08) + expected = Complex.new(0.44, 0.08) result->complex_is_approx_eq(expected) } @@ -184,35 +184,35 @@ expect { # Absolute value of a positive purely real number expect { - z = complex(5, 0) + z = Complex.new(5, 0) result = z.abs() result->is_approx_eq(5) } # Absolute value of a negative purely real number expect { - z = complex(-5, 0) + z = Complex.new(-5, 0) result = z.abs() result->is_approx_eq(5) } # Absolute value of a purely imaginary number with positive imaginary part expect { - z = complex(0, 5) + z = Complex.new(0, 5) result = z.abs() result->is_approx_eq(5) } # Absolute value of a purely imaginary number with negative imaginary part expect { - z = complex(0, -5) + z = Complex.new(0, -5) result = z.abs() result->is_approx_eq(5) } # Absolute value of a number with real and imaginary part expect { - z = complex(3, 4) + z = Complex.new(3, 4) result = z.abs() result->is_approx_eq(5) } @@ -223,25 +223,25 @@ expect { # Conjugate a purely real number expect { - z = complex(5, 0) + z = Complex.new(5, 0) result = z.conjugate() - expected = complex(5, 0) + expected = Complex.new(5, 0) result->complex_is_approx_eq(expected) } # Conjugate a purely imaginary number expect { - z = complex(0, 5) + z = Complex.new(0, 5) result = z.conjugate() - expected = complex(0, -5) + expected = Complex.new(0, -5) result->complex_is_approx_eq(expected) } # Conjugate a number with real and imaginary part expect { - z = complex(1, 1) + z = Complex.new(1, 1) result = z.conjugate() - expected = complex(1, -1) + expected = Complex.new(1, -1) result->complex_is_approx_eq(expected) } @@ -251,41 +251,41 @@ expect { # Euler's identity/formula expect { - z = complex(0, 3.141592653589793.F64) + z = Complex.new(0, 3.141592653589793.F64) result = z.exp() - expected = complex(-1, 0) + expected = Complex.new(-1, 0) result->complex_is_approx_eq(expected) } # Exponential of 0 expect { - z = complex(0, 0) + z = Complex.new(0, 0) result = z.exp() - expected = complex(1, 0) + expected = Complex.new(1, 0) result->complex_is_approx_eq(expected) } # Exponential of a purely real number expect { - z = complex(1, 0) + z = Complex.new(1, 0) result = z.exp() - expected = complex(2.718281828459045.F64, 0) + expected = Complex.new(2.718281828459045.F64, 0) result->complex_is_approx_eq(expected) } # Exponential of a number with real and imaginary part expect { - z = complex(0.6931471805599453.F64, 3.141592653589793.F64) + z = Complex.new(0.6931471805599453.F64, 3.141592653589793.F64) result = z.exp() - expected = complex(-2, 0) + expected = Complex.new(-2, 0) result->complex_is_approx_eq(expected) } # Exponential resulting in a number with real and imaginary part expect { - z = complex(0.6931471805599453.F64 / 2, 3.141592653589793.F64 / 4) + z = Complex.new(0.6931471805599453.F64 / 2, 3.141592653589793.F64 / 4) result = z.exp() - expected = complex(1, 1) + expected = Complex.new(1, 1) result->complex_is_approx_eq(expected) } @@ -295,73 +295,73 @@ expect { # Add real number to complex number expect { - z1 = complex(1, 2) - z2 = complex(5, 0) + z1 = Complex.new(1, 2) + z2 = Complex.new(5, 0) result = z1 + z2 - expected = complex(6, 2) + expected = Complex.new(6, 2) result->complex_is_approx_eq(expected) } # Add complex number to real number expect { - z1 = complex(5, 0) - z2 = complex(1, 2) + z1 = Complex.new(5, 0) + z2 = Complex.new(1, 2) result = z1 + z2 - expected = complex(6, 2) + expected = Complex.new(6, 2) result->complex_is_approx_eq(expected) } # Subtract real number from complex number expect { - z1 = complex(5, 7) - z2 = complex(4, 0) + z1 = Complex.new(5, 7) + z2 = Complex.new(4, 0) result = z1 - z2 - expected = complex(1, 7) + expected = Complex.new(1, 7) result->complex_is_approx_eq(expected) } # Subtract complex number from real number expect { - z1 = complex(4, 0) - z2 = complex(5, 7) + z1 = Complex.new(4, 0) + z2 = Complex.new(5, 7) result = z1 - z2 - expected = complex(-1, -7) + expected = Complex.new(-1, -7) result->complex_is_approx_eq(expected) } # Multiply complex number by real number expect { - z1 = complex(2, 5) - z2 = complex(5, 0) + z1 = Complex.new(2, 5) + z2 = Complex.new(5, 0) result = z1 * z2 - expected = complex(10, 25) + expected = Complex.new(10, 25) result->complex_is_approx_eq(expected) } # Multiply real number by complex number expect { - z1 = complex(5, 0) - z2 = complex(2, 5) + z1 = Complex.new(5, 0) + z2 = Complex.new(2, 5) result = z1 * z2 - expected = complex(10, 25) + expected = Complex.new(10, 25) result->complex_is_approx_eq(expected) } # Divide complex number by real number expect { - z1 = complex(10, 100) - z2 = complex(10, 0) + z1 = Complex.new(10, 100) + z2 = Complex.new(10, 0) result = z1 / z2 - expected = complex(1, 10) + expected = Complex.new(1, 10) result->complex_is_approx_eq(expected) } # Divide real number by complex number expect { - z1 = complex(5, 0) - z2 = complex(1, 1) + z1 = Complex.new(5, 0) + z2 = Complex.new(1, 1) result = z1 / z2 - expected = complex(2.5, -2.5) + expected = Complex.new(2.5, -2.5) result->complex_is_approx_eq(expected) } From eb68522aa826c00f7a5b90c34a9d23e789bf8b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 15:48:10 +1200 Subject: [PATCH 157/162] Fix type and replace ???? with ... --- exercises/practice/list-ops/.docs/instructions.append.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/list-ops/.docs/instructions.append.md b/exercises/practice/list-ops/.docs/instructions.append.md index 55dec024..1cabfb38 100644 --- a/exercises/practice/list-ops/.docs/instructions.append.md +++ b/exercises/practice/list-ops/.docs/instructions.append.md @@ -34,8 +34,8 @@ Try using: ```roc match list { - [] => ???? - [first, .. as rest] => ???? + [] => ... + [first, .. as rest] => ... } ``` @@ -43,7 +43,7 @@ or ```roc match list { - [] -> ???? - [.. as rest, last] => ???? + [] => ... + [.. as rest, last] => ... } ``` From 4886728f36df88a00458520498e513f79b1623e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 15:55:24 +1200 Subject: [PATCH 158/162] Shorten imaginary to imag, and replace new(a,b) with new({real: a, imag: b}) --- .../complex-numbers/.meta/Example.roc | 30 +-- .../practice/complex-numbers/.meta/plugins.py | 2 +- .../complex-numbers/.meta/template.j2 | 4 +- .../practice/complex-numbers/Complex.roc | 6 +- .../complex-numbers/complex-numbers-test.roc | 182 +++++++++--------- 5 files changed, 112 insertions(+), 112 deletions(-) diff --git a/exercises/practice/complex-numbers/.meta/Example.roc b/exercises/practice/complex-numbers/.meta/Example.roc index b2c3e3e3..4644db68 100644 --- a/exercises/practice/complex-numbers/.meta/Example.roc +++ b/exercises/practice/complex-numbers/.meta/Example.roc @@ -1,41 +1,41 @@ -Complex := { real : F64, imaginary : F64 }.{ - new : F64, F64 -> Complex - new = |real, imaginary| { { real, imaginary } } +Complex := { real : F64, imag : F64 }.{ + new : { real: F64, imag: F64 } -> Complex + new = |{ real, imag }| { { real, imag } } # # The user can write plus(z1, z2), z1.plus(z2), or simply z1 + z2 plus : Complex, Complex -> Complex - plus = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { + plus = |{ real: a, imag: b }, { real: c, imag: d }| { { real: a + c, - imaginary: b + d, + imag: b + d, } } # # The user can write minus(z1, z2), z1.minus(z2), or simply z1 - z2 minus : Complex, Complex -> Complex - minus = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { + minus = |{ real: a, imag: b }, { real: c, imag: d }| { { real: a - c, - imaginary: b - d, + imag: b - d, } } # # The user can write times(z1, z2), z1.times(z2), or simply z1 * z2 times : Complex, Complex -> Complex - times = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { + times = |{ real: a, imag: b }, { real: c, imag: d }| { { real: a * c - b * d, - imaginary: a * d + b * c, + imag: a * d + b * c, } } # # The user can write div_by(z1, z2), z1.div_by(z2), or simply z1 / z2 div_by : Complex, Complex -> Complex - div_by = |{ real: a, imaginary: b }, { real: c, imaginary: d }| { + div_by = |{ real: a, imag: b }, { real: c, imag: d }| { denominator = c * c + d * d { real: (a * c + b * d) / denominator, - imaginary: (b * c - a * d) / denominator, + imag: (b * c - a * d) / denominator, } } @@ -43,12 +43,12 @@ Complex := { real : F64, imaginary : F64 }.{ conjugate = |z| { { real: z.real, - imaginary: -z.imaginary, + imag: -z.imag, } } abs : Complex -> F64 - abs = |{ real: a, imaginary: b }| { + abs = |{ real: a, imag: b }| { sqrt(a * a + b * b) } @@ -56,8 +56,8 @@ Complex := { real : F64, imaginary : F64 }.{ exp = |z| { factor = e->pow(z.real) { - real: factor * cos(z.imaginary), - imaginary: factor * sin(z.imaginary), + real: factor * cos(z.imag), + imag: factor * sin(z.imag), } } } diff --git a/exercises/practice/complex-numbers/.meta/plugins.py b/exercises/practice/complex-numbers/.meta/plugins.py index 960b1210..9d4947ee 100644 --- a/exercises/practice/complex-numbers/.meta/plugins.py +++ b/exercises/practice/complex-numbers/.meta/plugins.py @@ -22,4 +22,4 @@ def to_complex_number(value): else: re = to_roc(value) im = 0 - return f"Complex.new({re}, {im})" + return f"Complex.new({{real: {re}, imag: {im}}})" diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2 index ea5b180e..853ab0ce 100644 --- a/exercises/practice/complex-numbers/.meta/template.j2 +++ b/exercises/practice/complex-numbers/.meta/template.j2 @@ -3,7 +3,7 @@ {{ macros.header() }} {% set equations = { "real": "z.real", - "imaginary": "z.imaginary", + "imaginary": "z.imag", "conjugate": "z.conjugate()", "abs": "z.abs()", "exp": "z.exp()", @@ -64,7 +64,7 @@ is_approx_eq = |x1, x2| { } complex_is_approx_eq = |z1, z2| { - is_approx_eq(z1.real, z2.real) and is_approx_eq(z1.imaginary, z2.imaginary) + is_approx_eq(z1.real, z2.real) and is_approx_eq(z1.imag, z2.imag) } {{ macros.footer() }} diff --git a/exercises/practice/complex-numbers/Complex.roc b/exercises/practice/complex-numbers/Complex.roc index 7ae1dd4b..eaed6be3 100644 --- a/exercises/practice/complex-numbers/Complex.roc +++ b/exercises/practice/complex-numbers/Complex.roc @@ -1,6 +1,6 @@ -Complex := { real: F64, imaginary: F64 }.{ - new : F64, F64 -> Complex - new = |real, imaginary| { +Complex := { real: F64, imag: F64 }.{ + new : { real: F64, imag: F64 } -> Complex + new = |{ real, imag }| { crash "Please implement the 'new' function" } diff --git a/exercises/practice/complex-numbers/complex-numbers-test.roc b/exercises/practice/complex-numbers/complex-numbers-test.roc index 294f8c6a..55591ce8 100644 --- a/exercises/practice/complex-numbers/complex-numbers-test.roc +++ b/exercises/practice/complex-numbers/complex-numbers-test.roc @@ -10,21 +10,21 @@ import Complex # Real part of a purely real number expect { - z = Complex.new(1, 0) + z = Complex.new({ real: 1, imag: 0 }) result = z.real result->is_approx_eq(1) } # Real part of a purely imaginary number expect { - z = Complex.new(0, 1) + z = Complex.new({ real: 0, imag: 1 }) result = z.real result->is_approx_eq(0) } # Real part of a number with real and imaginary part expect { - z = Complex.new(1, 2) + z = Complex.new({ real: 1, imag: 2 }) result = z.real result->is_approx_eq(1) } @@ -35,22 +35,22 @@ expect { # Imaginary part of a purely real number expect { - z = Complex.new(1, 0) - result = z.imaginary + z = Complex.new({ real: 1, imag: 0 }) + result = z.imag result->is_approx_eq(0) } # Imaginary part of a purely imaginary number expect { - z = Complex.new(0, 1) - result = z.imaginary + z = Complex.new({ real: 0, imag: 1 }) + result = z.imag result->is_approx_eq(1) } # Imaginary part of a number with real and imaginary part expect { - z = Complex.new(1, 2) - result = z.imaginary + z = Complex.new({ real: 1, imag: 2 }) + result = z.imag result->is_approx_eq(2) } @@ -66,28 +66,28 @@ expect { # Add purely real numbers expect { - z1 = Complex.new(1, 0) - z2 = Complex.new(2, 0) + z1 = Complex.new({ real: 1, imag: 0 }) + z2 = Complex.new({ real: 2, imag: 0 }) result = z1 + z2 - expected = Complex.new(3, 0) + expected = Complex.new({ real: 3, imag: 0 }) result->complex_is_approx_eq(expected) } # Add purely imaginary numbers expect { - z1 = Complex.new(0, 1) - z2 = Complex.new(0, 2) + z1 = Complex.new({ real: 0, imag: 1 }) + z2 = Complex.new({ real: 0, imag: 2 }) result = z1 + z2 - expected = Complex.new(0, 3) + expected = Complex.new({ real: 0, imag: 3 }) result->complex_is_approx_eq(expected) } # Add numbers with real and imaginary part expect { - z1 = Complex.new(1, 2) - z2 = Complex.new(3, 4) + z1 = Complex.new({ real: 1, imag: 2 }) + z2 = Complex.new({ real: 3, imag: 4 }) result = z1 + z2 - expected = Complex.new(4, 6) + expected = Complex.new({ real: 4, imag: 6 }) result->complex_is_approx_eq(expected) } @@ -95,28 +95,28 @@ expect { # Subtract purely real numbers expect { - z1 = Complex.new(1, 0) - z2 = Complex.new(2, 0) + z1 = Complex.new({ real: 1, imag: 0 }) + z2 = Complex.new({ real: 2, imag: 0 }) result = z1 - z2 - expected = Complex.new(-1, 0) + expected = Complex.new({ real: -1, imag: 0 }) result->complex_is_approx_eq(expected) } # Subtract purely imaginary numbers expect { - z1 = Complex.new(0, 1) - z2 = Complex.new(0, 2) + z1 = Complex.new({ real: 0, imag: 1 }) + z2 = Complex.new({ real: 0, imag: 2 }) result = z1 - z2 - expected = Complex.new(0, -1) + expected = Complex.new({ real: 0, imag: -1 }) result->complex_is_approx_eq(expected) } # Subtract numbers with real and imaginary part expect { - z1 = Complex.new(1, 2) - z2 = Complex.new(3, 4) + z1 = Complex.new({ real: 1, imag: 2 }) + z2 = Complex.new({ real: 3, imag: 4 }) result = z1 - z2 - expected = Complex.new(-2, -2) + expected = Complex.new({ real: -2, imag: -2 }) result->complex_is_approx_eq(expected) } @@ -124,28 +124,28 @@ expect { # Multiply purely real numbers expect { - z1 = Complex.new(1, 0) - z2 = Complex.new(2, 0) + z1 = Complex.new({ real: 1, imag: 0 }) + z2 = Complex.new({ real: 2, imag: 0 }) result = z1 * z2 - expected = Complex.new(2, 0) + expected = Complex.new({ real: 2, imag: 0 }) result->complex_is_approx_eq(expected) } # Multiply purely imaginary numbers expect { - z1 = Complex.new(0, 1) - z2 = Complex.new(0, 2) + z1 = Complex.new({ real: 0, imag: 1 }) + z2 = Complex.new({ real: 0, imag: 2 }) result = z1 * z2 - expected = Complex.new(-2, 0) + expected = Complex.new({ real: -2, imag: 0 }) result->complex_is_approx_eq(expected) } # Multiply numbers with real and imaginary part expect { - z1 = Complex.new(1, 2) - z2 = Complex.new(3, 4) + z1 = Complex.new({ real: 1, imag: 2 }) + z2 = Complex.new({ real: 3, imag: 4 }) result = z1 * z2 - expected = Complex.new(-5, 10) + expected = Complex.new({ real: -5, imag: 10 }) result->complex_is_approx_eq(expected) } @@ -153,28 +153,28 @@ expect { # Divide purely real numbers expect { - z1 = Complex.new(1, 0) - z2 = Complex.new(2, 0) + z1 = Complex.new({ real: 1, imag: 0 }) + z2 = Complex.new({ real: 2, imag: 0 }) result = z1 / z2 - expected = Complex.new(0.5, 0) + expected = Complex.new({ real: 0.5, imag: 0 }) result->complex_is_approx_eq(expected) } # Divide purely imaginary numbers expect { - z1 = Complex.new(0, 1) - z2 = Complex.new(0, 2) + z1 = Complex.new({ real: 0, imag: 1 }) + z2 = Complex.new({ real: 0, imag: 2 }) result = z1 / z2 - expected = Complex.new(0.5, 0) + expected = Complex.new({ real: 0.5, imag: 0 }) result->complex_is_approx_eq(expected) } # Divide numbers with real and imaginary part expect { - z1 = Complex.new(1, 2) - z2 = Complex.new(3, 4) + z1 = Complex.new({ real: 1, imag: 2 }) + z2 = Complex.new({ real: 3, imag: 4 }) result = z1 / z2 - expected = Complex.new(0.44, 0.08) + expected = Complex.new({ real: 0.44, imag: 0.08 }) result->complex_is_approx_eq(expected) } @@ -184,35 +184,35 @@ expect { # Absolute value of a positive purely real number expect { - z = Complex.new(5, 0) + z = Complex.new({ real: 5, imag: 0 }) result = z.abs() result->is_approx_eq(5) } # Absolute value of a negative purely real number expect { - z = Complex.new(-5, 0) + z = Complex.new({ real: -5, imag: 0 }) result = z.abs() result->is_approx_eq(5) } # Absolute value of a purely imaginary number with positive imaginary part expect { - z = Complex.new(0, 5) + z = Complex.new({ real: 0, imag: 5 }) result = z.abs() result->is_approx_eq(5) } # Absolute value of a purely imaginary number with negative imaginary part expect { - z = Complex.new(0, -5) + z = Complex.new({ real: 0, imag: -5 }) result = z.abs() result->is_approx_eq(5) } # Absolute value of a number with real and imaginary part expect { - z = Complex.new(3, 4) + z = Complex.new({ real: 3, imag: 4 }) result = z.abs() result->is_approx_eq(5) } @@ -223,25 +223,25 @@ expect { # Conjugate a purely real number expect { - z = Complex.new(5, 0) + z = Complex.new({ real: 5, imag: 0 }) result = z.conjugate() - expected = Complex.new(5, 0) + expected = Complex.new({ real: 5, imag: 0 }) result->complex_is_approx_eq(expected) } # Conjugate a purely imaginary number expect { - z = Complex.new(0, 5) + z = Complex.new({ real: 0, imag: 5 }) result = z.conjugate() - expected = Complex.new(0, -5) + expected = Complex.new({ real: 0, imag: -5 }) result->complex_is_approx_eq(expected) } # Conjugate a number with real and imaginary part expect { - z = Complex.new(1, 1) + z = Complex.new({ real: 1, imag: 1 }) result = z.conjugate() - expected = Complex.new(1, -1) + expected = Complex.new({ real: 1, imag: -1 }) result->complex_is_approx_eq(expected) } @@ -251,41 +251,41 @@ expect { # Euler's identity/formula expect { - z = Complex.new(0, 3.141592653589793.F64) + z = Complex.new({ real: 0, imag: 3.141592653589793.F64 }) result = z.exp() - expected = Complex.new(-1, 0) + expected = Complex.new({ real: -1, imag: 0 }) result->complex_is_approx_eq(expected) } # Exponential of 0 expect { - z = Complex.new(0, 0) + z = Complex.new({ real: 0, imag: 0 }) result = z.exp() - expected = Complex.new(1, 0) + expected = Complex.new({ real: 1, imag: 0 }) result->complex_is_approx_eq(expected) } # Exponential of a purely real number expect { - z = Complex.new(1, 0) + z = Complex.new({ real: 1, imag: 0 }) result = z.exp() - expected = Complex.new(2.718281828459045.F64, 0) + expected = Complex.new({ real: 2.718281828459045.F64, imag: 0 }) result->complex_is_approx_eq(expected) } # Exponential of a number with real and imaginary part expect { - z = Complex.new(0.6931471805599453.F64, 3.141592653589793.F64) + z = Complex.new({ real: 0.6931471805599453.F64, imag: 3.141592653589793.F64 }) result = z.exp() - expected = Complex.new(-2, 0) + expected = Complex.new({ real: -2, imag: 0 }) result->complex_is_approx_eq(expected) } # Exponential resulting in a number with real and imaginary part expect { - z = Complex.new(0.6931471805599453.F64 / 2, 3.141592653589793.F64 / 4) + z = Complex.new({ real: 0.6931471805599453.F64 / 2, imag: 3.141592653589793.F64 / 4 }) result = z.exp() - expected = Complex.new(1, 1) + expected = Complex.new({ real: 1, imag: 1 }) result->complex_is_approx_eq(expected) } @@ -295,73 +295,73 @@ expect { # Add real number to complex number expect { - z1 = Complex.new(1, 2) - z2 = Complex.new(5, 0) + z1 = Complex.new({ real: 1, imag: 2 }) + z2 = Complex.new({ real: 5, imag: 0 }) result = z1 + z2 - expected = Complex.new(6, 2) + expected = Complex.new({ real: 6, imag: 2 }) result->complex_is_approx_eq(expected) } # Add complex number to real number expect { - z1 = Complex.new(5, 0) - z2 = Complex.new(1, 2) + z1 = Complex.new({ real: 5, imag: 0 }) + z2 = Complex.new({ real: 1, imag: 2 }) result = z1 + z2 - expected = Complex.new(6, 2) + expected = Complex.new({ real: 6, imag: 2 }) result->complex_is_approx_eq(expected) } # Subtract real number from complex number expect { - z1 = Complex.new(5, 7) - z2 = Complex.new(4, 0) + z1 = Complex.new({ real: 5, imag: 7 }) + z2 = Complex.new({ real: 4, imag: 0 }) result = z1 - z2 - expected = Complex.new(1, 7) + expected = Complex.new({ real: 1, imag: 7 }) result->complex_is_approx_eq(expected) } # Subtract complex number from real number expect { - z1 = Complex.new(4, 0) - z2 = Complex.new(5, 7) + z1 = Complex.new({ real: 4, imag: 0 }) + z2 = Complex.new({ real: 5, imag: 7 }) result = z1 - z2 - expected = Complex.new(-1, -7) + expected = Complex.new({ real: -1, imag: -7 }) result->complex_is_approx_eq(expected) } # Multiply complex number by real number expect { - z1 = Complex.new(2, 5) - z2 = Complex.new(5, 0) + z1 = Complex.new({ real: 2, imag: 5 }) + z2 = Complex.new({ real: 5, imag: 0 }) result = z1 * z2 - expected = Complex.new(10, 25) + expected = Complex.new({ real: 10, imag: 25 }) result->complex_is_approx_eq(expected) } # Multiply real number by complex number expect { - z1 = Complex.new(5, 0) - z2 = Complex.new(2, 5) + z1 = Complex.new({ real: 5, imag: 0 }) + z2 = Complex.new({ real: 2, imag: 5 }) result = z1 * z2 - expected = Complex.new(10, 25) + expected = Complex.new({ real: 10, imag: 25 }) result->complex_is_approx_eq(expected) } # Divide complex number by real number expect { - z1 = Complex.new(10, 100) - z2 = Complex.new(10, 0) + z1 = Complex.new({ real: 10, imag: 100 }) + z2 = Complex.new({ real: 10, imag: 0 }) result = z1 / z2 - expected = Complex.new(1, 10) + expected = Complex.new({ real: 1, imag: 10 }) result->complex_is_approx_eq(expected) } # Divide real number by complex number expect { - z1 = Complex.new(5, 0) - z2 = Complex.new(1, 1) + z1 = Complex.new({ real: 5, imag: 0 }) + z2 = Complex.new({ real: 1, imag: 1 }) result = z1 / z2 - expected = Complex.new(2.5, -2.5) + expected = Complex.new({ real: 2.5, imag: -2.5 }) result->complex_is_approx_eq(expected) } @@ -376,7 +376,7 @@ is_approx_eq = |x1, x2| { } complex_is_approx_eq = |z1, z2| { - is_approx_eq(z1.real, z2.real) and is_approx_eq(z1.imaginary, z2.imaginary) + is_approx_eq(z1.real, z2.real) and is_approx_eq(z1.imag, z2.imag) } # This program is only used to run tests with `roc test`, so main! does nothing. From 674d081b09e01ae2e8be5c8ad27813e53b140876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 17:09:17 +1200 Subject: [PATCH 159/162] Replace RationalNumbers.Rational with simply Rational, and replace method names with Roc standards such as plus, times, or div_by --- .../rational-numbers/.meta/Example.roc | 33 +- .../rational-numbers/.meta/config.json | 2 +- .../rational-numbers/.meta/plugins.py | 2 +- .../rational-numbers/.meta/template.j2 | 36 +-- .../practice/rational-numbers/Rational.roc | 51 +++ .../rational-numbers/RationalNumbers.roc | 44 --- .../rational-numbers-test.roc | 300 +++++++++--------- 7 files changed, 244 insertions(+), 224 deletions(-) create mode 100644 exercises/practice/rational-numbers/Rational.roc delete mode 100644 exercises/practice/rational-numbers/RationalNumbers.roc diff --git a/exercises/practice/rational-numbers/.meta/Example.roc b/exercises/practice/rational-numbers/.meta/Example.roc index fe8153d3..e1340b6d 100644 --- a/exercises/practice/rational-numbers/.meta/Example.roc +++ b/exercises/practice/rational-numbers/.meta/Example.roc @@ -1,23 +1,30 @@ -RationalNumbers :: {}.{ - Rational : { num : I64, den : I64 } +Rational :: { num : I64, den : I64 }.{ + new : { num : I64, den : I64 } -> Rational + new = |{ num, den }| { + { num, den }->reduce() + } - add : Rational, Rational -> Rational - add = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { + # # The user can write plus(r1, r2), r1.plus(r2), or simply r1 + r2 + plus : Rational, Rational -> Rational + plus = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { { num: num1 * den2 + num2 * den1, den: den1 * den2 }->reduce() } - sub : Rational, Rational -> Rational - sub = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { + # # The user can write minus(r1, r2), r1.minus(r2), or simply r1 - r2 + minus : Rational, Rational -> Rational + minus = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { { num: num1 * den2 - num2 * den1, den: den1 * den2 }->reduce() } - mul : Rational, Rational -> Rational - mul = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { + # # The user can write times(r1, r2), r1.times(r2), or simply r1 * r2 + times : Rational, Rational -> Rational + times = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { { num: num1 * num2, den: den1 * den2 }->reduce() } - div : Rational, Rational -> Rational - div = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { + # # The user can write div_by(r1, r2), r1.div_by(r2), or simply r1 / r2 + div_by : Rational, Rational -> Rational + div_by = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { { num: num1 * den2, den: num2 * den1 }->reduce() } @@ -63,6 +70,12 @@ RationalNumbers :: {}.{ d = gcd(abs_num, abs_den) { num: sign(num) * sign(den) * abs_num // d, den: abs_den // d } } + + # TODO: remove this function once https://github.com/roc-lang/roc/issues/9769 is resolved + is_eq : Rational, Rational -> Bool + is_eq = |{ num: num1, den: den1 }, { num: num2, den: den2 }| { + (num1 == num2) and (den1 == den2) + } } pow_int : I64, I64 -> I64 diff --git a/exercises/practice/rational-numbers/.meta/config.json b/exercises/practice/rational-numbers/.meta/config.json index 26387c00..4fa5b763 100644 --- a/exercises/practice/rational-numbers/.meta/config.json +++ b/exercises/practice/rational-numbers/.meta/config.json @@ -4,7 +4,7 @@ ], "files": { "solution": [ - "RationalNumbers.roc" + "Rational.roc" ], "test": [ "rational-numbers-test.roc" diff --git a/exercises/practice/rational-numbers/.meta/plugins.py b/exercises/practice/rational-numbers/.meta/plugins.py index 187776b4..cde472a2 100644 --- a/exercises/practice/rational-numbers/.meta/plugins.py +++ b/exercises/practice/rational-numbers/.meta/plugins.py @@ -1,2 +1,2 @@ def to_roc_rational(r): - return f"{{num: {r[0]}, den: {r[1]}}}" + return f"Rational.new({{num: {r[0]}, den: {r[1]}}})" diff --git a/exercises/practice/rational-numbers/.meta/template.j2 b/exercises/practice/rational-numbers/.meta/template.j2 index a48e7e7c..34171890 100644 --- a/exercises/practice/rational-numbers/.meta/template.j2 +++ b/exercises/practice/rational-numbers/.meta/template.j2 @@ -2,21 +2,17 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} exposing [Rational, add, sub, mul, div, abs, exp, exp_real, reduce] +import Rational -is_approx_eq = |f1, f2| { - (f1 * 1e9 + 0.5).to_u64_wrap() == (f2 * 1e9 + 0.5).to_u64_wrap() -} - -{% set property_map = { - "abs": "abs", - "add": "add", - "div": "div", - "exprational": "exp", - "expreal": "exp_real", - "mul": "mul", - "reduce": "reduce", - "sub": "sub", +{% set equations = { + "add": "r1 + r2", + "sub": "r1 - r2", + "mul": "r1 * r2", + "div": "r1 / r2", + "abs": "r.abs()", + "reduce": "r.reduce()", + "exprational": "r.exp(n)", + "expreal": "Rational.exp_real(x, r)", } %} @@ -26,19 +22,19 @@ expect { {%- if "r1" in case["input"] %} r1 = {{ plugins.to_roc_rational(case["input"]["r1"]) }} r2 = {{ plugins.to_roc_rational(case["input"]["r2"]) }} - result = r1->{{ property_map[case["property"]] | to_snake }}(r2) + result = {{ equations[case["property"]] }} {%- elif "r" in case["input"] %} {%- if "x" in case["input"] %} r = {{ plugins.to_roc_rational(case["input"]["r"]) }} x = {{ case["input"]["x"] | to_roc }} - result = x->{{ property_map[case["property"]] | to_snake }}(r) + result = {{ equations[case["property"]] }} {%- elif "n" in case["input"] %} r = {{ plugins.to_roc_rational(case["input"]["r"]) }} n = {{ case["input"]["n"] | to_roc }} - result = r->{{ property_map[case["property"]] | to_snake }}(n) + result = {{ equations[case["property"]] }} {%- else %} r = {{ plugins.to_roc_rational(case["input"]["r"]) }} - result = r->{{ property_map[case["property"]] | to_snake }}() + result = {{ equations[case["property"]] }} {%- endif %} {%- else %} crash "This test case is not implemented yet." @@ -68,4 +64,8 @@ expect { {% endfor %} {% endfor %} +is_approx_eq = |f1, f2| { + (f1 * 1e9 + 0.5).to_u64_wrap() == (f2 * 1e9 + 0.5).to_u64_wrap() +} + {{ macros.footer() }} diff --git a/exercises/practice/rational-numbers/Rational.roc b/exercises/practice/rational-numbers/Rational.roc new file mode 100644 index 00000000..512a7dc5 --- /dev/null +++ b/exercises/practice/rational-numbers/Rational.roc @@ -0,0 +1,51 @@ +Rational :: { num : I64, den : I64 }.{ + new : { num : I64, den : I64 } -> Rational + new = |{ num, den }| { + crash "Please implement the 'new' function" + } + + # # The user can write plus(r1, r2), r1.plus(r2), or simply r1 + r2 + plus : Rational, Rational -> Rational + plus = |r1, r2| { + crash "Please implement the 'plus' function" + } + + # # The user can write minus(r1, r2), r1.minus(r2), or simply r1 - r2 + minus : Rational, Rational -> Rational + minus = |r1, r2| { + crash "Please implement the 'minus' function" + } + + # # The user can write times(r1, r2), r1.times(r2), or simply r1 * r2 + times : Rational, Rational -> Rational + times = |r1, r2| { + crash "Please implement the 'times' function" + } + + # # The user can write div_by(r1, r2), r1.div_by(r2), or simply r1 / r2 + div_by : Rational, Rational -> Rational + div_by = |r1, r2| { + crash "Please implement the 'div_by' function" + } + + abs : Rational -> Rational + abs = |r| { + crash "Please implement the 'abs' function" + } + + exp : Rational, I64 -> Rational + exp = |r, n| { + crash "Please implement the 'exp' function" + } + + exp_real : F64, Rational -> F64 + exp_real = |x, r| { + crash "Please implement the 'exp_real' function" + } + + # # Reduce a rational number to its lowest terms, e.g., 6 / 8 --> 3 / 4 + reduce : Rational -> Rational + reduce = |r| { + crash "Please implement the 'reduce' function" + } +} diff --git a/exercises/practice/rational-numbers/RationalNumbers.roc b/exercises/practice/rational-numbers/RationalNumbers.roc deleted file mode 100644 index 78c20962..00000000 --- a/exercises/practice/rational-numbers/RationalNumbers.roc +++ /dev/null @@ -1,44 +0,0 @@ -RationalNumbers :: {}.{ - Rational : { num : I64, den : I64 } - - add : Rational, Rational -> Rational - add = |r1, r2| { - crash "Please implement the 'add' function" - } - - sub : Rational, Rational -> Rational - sub = |r1, r2| { - crash "Please implement the 'sub' function" - } - - mul : Rational, Rational -> Rational - mul = |r1, r2| { - crash "Please implement the 'mul' function" - } - - div : Rational, Rational -> Rational - div = |r1, r2| { - crash "Please implement the 'div' function" - } - - abs : Rational -> Rational - abs = |r| { - crash "Please implement the 'abs' function" - } - - exp : Rational, I64 -> Rational - exp = |r, n| { - crash "Please implement the 'exp' function" - } - - exp_real : F64, Rational -> F64 - exp_real = |x, r| { - crash "Please implement the 'exp_real' function" - } - - # # Reduce a rational number to its lowest terms, e.g., 6 / 8 --> 3 / 4 - reduce : Rational -> Rational - reduce = |r| { - crash "Please implement the 'reduce' function" - } -} diff --git a/exercises/practice/rational-numbers/rational-numbers-test.roc b/exercises/practice/rational-numbers/rational-numbers-test.roc index 9f45c51c..e0b0dead 100644 --- a/exercises/practice/rational-numbers/rational-numbers-test.roc +++ b/exercises/practice/rational-numbers/rational-numbers-test.roc @@ -1,12 +1,8 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/rational-numbers/canonical-data.json -# File last updated on 2026-06-22 +# File last updated on 2026-06-23 -import RationalNumbers exposing [Rational, add, sub, mul, div, abs, exp, exp_real, reduce] - -is_approx_eq = |f1, f2| { - (f1 * 1e9 + 0.5).to_u64_wrap() == (f2 * 1e9 + 0.5).to_u64_wrap() -} +import Rational ## ## Arithmetic @@ -14,146 +10,146 @@ is_approx_eq = |f1, f2| { # Add two positive rational numbers expect { - r1 = { num: 1, den: 2 } - r2 = { num: 2, den: 3 } - result = r1->add(r2) - result == { num: 7, den: 6 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: 2, den: 3 }) + result = r1 + r2 + result == Rational.new({ num: 7, den: 6 }) } # Add a positive rational number and a negative rational number expect { - r1 = { num: 1, den: 2 } - r2 = { num: -2, den: 3 } - result = r1->add(r2) - result == { num: -1, den: 6 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: -2, den: 3 }) + result = r1 + r2 + result == Rational.new({ num: -1, den: 6 }) } # Add two negative rational numbers expect { - r1 = { num: -1, den: 2 } - r2 = { num: -2, den: 3 } - result = r1->add(r2) - result == { num: -7, den: 6 } + r1 = Rational.new({ num: -1, den: 2 }) + r2 = Rational.new({ num: -2, den: 3 }) + result = r1 + r2 + result == Rational.new({ num: -7, den: 6 }) } # Add a rational number to its additive inverse expect { - r1 = { num: 1, den: 2 } - r2 = { num: -1, den: 2 } - result = r1->add(r2) - result == { num: 0, den: 1 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: -1, den: 2 }) + result = r1 + r2 + result == Rational.new({ num: 0, den: 1 }) } # Subtract two positive rational numbers expect { - r1 = { num: 1, den: 2 } - r2 = { num: 2, den: 3 } - result = r1->sub(r2) - result == { num: -1, den: 6 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: 2, den: 3 }) + result = r1 - r2 + result == Rational.new({ num: -1, den: 6 }) } # Subtract a positive rational number and a negative rational number expect { - r1 = { num: 1, den: 2 } - r2 = { num: -2, den: 3 } - result = r1->sub(r2) - result == { num: 7, den: 6 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: -2, den: 3 }) + result = r1 - r2 + result == Rational.new({ num: 7, den: 6 }) } # Subtract two negative rational numbers expect { - r1 = { num: -1, den: 2 } - r2 = { num: -2, den: 3 } - result = r1->sub(r2) - result == { num: 1, den: 6 } + r1 = Rational.new({ num: -1, den: 2 }) + r2 = Rational.new({ num: -2, den: 3 }) + result = r1 - r2 + result == Rational.new({ num: 1, den: 6 }) } # Subtract a rational number from itself expect { - r1 = { num: 1, den: 2 } - r2 = { num: 1, den: 2 } - result = r1->sub(r2) - result == { num: 0, den: 1 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: 1, den: 2 }) + result = r1 - r2 + result == Rational.new({ num: 0, den: 1 }) } # Multiply two positive rational numbers expect { - r1 = { num: 1, den: 2 } - r2 = { num: 2, den: 3 } - result = r1->mul(r2) - result == { num: 1, den: 3 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: 2, den: 3 }) + result = r1 * r2 + result == Rational.new({ num: 1, den: 3 }) } # Multiply a negative rational number by a positive rational number expect { - r1 = { num: -1, den: 2 } - r2 = { num: 2, den: 3 } - result = r1->mul(r2) - result == { num: -1, den: 3 } + r1 = Rational.new({ num: -1, den: 2 }) + r2 = Rational.new({ num: 2, den: 3 }) + result = r1 * r2 + result == Rational.new({ num: -1, den: 3 }) } # Multiply two negative rational numbers expect { - r1 = { num: -1, den: 2 } - r2 = { num: -2, den: 3 } - result = r1->mul(r2) - result == { num: 1, den: 3 } + r1 = Rational.new({ num: -1, den: 2 }) + r2 = Rational.new({ num: -2, den: 3 }) + result = r1 * r2 + result == Rational.new({ num: 1, den: 3 }) } # Multiply a rational number by its reciprocal expect { - r1 = { num: 1, den: 2 } - r2 = { num: 2, den: 1 } - result = r1->mul(r2) - result == { num: 1, den: 1 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: 2, den: 1 }) + result = r1 * r2 + result == Rational.new({ num: 1, den: 1 }) } # Multiply a rational number by 1 expect { - r1 = { num: 1, den: 2 } - r2 = { num: 1, den: 1 } - result = r1->mul(r2) - result == { num: 1, den: 2 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: 1, den: 1 }) + result = r1 * r2 + result == Rational.new({ num: 1, den: 2 }) } # Multiply a rational number by 0 expect { - r1 = { num: 1, den: 2 } - r2 = { num: 0, den: 1 } - result = r1->mul(r2) - result == { num: 0, den: 1 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: 0, den: 1 }) + result = r1 * r2 + result == Rational.new({ num: 0, den: 1 }) } # Divide two positive rational numbers expect { - r1 = { num: 1, den: 2 } - r2 = { num: 2, den: 3 } - result = r1->div(r2) - result == { num: 3, den: 4 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: 2, den: 3 }) + result = r1 / r2 + result == Rational.new({ num: 3, den: 4 }) } # Divide a positive rational number by a negative rational number expect { - r1 = { num: 1, den: 2 } - r2 = { num: -2, den: 3 } - result = r1->div(r2) - result == { num: -3, den: 4 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: -2, den: 3 }) + result = r1 / r2 + result == Rational.new({ num: -3, den: 4 }) } # Divide two negative rational numbers expect { - r1 = { num: -1, den: 2 } - r2 = { num: -2, den: 3 } - result = r1->div(r2) - result == { num: 3, den: 4 } + r1 = Rational.new({ num: -1, den: 2 }) + r2 = Rational.new({ num: -2, den: 3 }) + result = r1 / r2 + result == Rational.new({ num: 3, den: 4 }) } # Divide a rational number by 1 expect { - r1 = { num: 1, den: 2 } - r2 = { num: 1, den: 1 } - result = r1->div(r2) - result == { num: 1, den: 2 } + r1 = Rational.new({ num: 1, den: 2 }) + r2 = Rational.new({ num: 1, den: 1 }) + result = r1 / r2 + result == Rational.new({ num: 1, den: 2 }) } ## @@ -162,44 +158,44 @@ expect { # Absolute value of a positive rational number expect { - r = { num: 1, den: 2 } - result = r->abs() - result == { num: 1, den: 2 } + r = Rational.new({ num: 1, den: 2 }) + result = r.abs() + result == Rational.new({ num: 1, den: 2 }) } # Absolute value of a positive rational number with negative numerator and denominator expect { - r = { num: -1, den: -2 } - result = r->abs() - result == { num: 1, den: 2 } + r = Rational.new({ num: -1, den: -2 }) + result = r.abs() + result == Rational.new({ num: 1, den: 2 }) } # Absolute value of a negative rational number expect { - r = { num: -1, den: 2 } - result = r->abs() - result == { num: 1, den: 2 } + r = Rational.new({ num: -1, den: 2 }) + result = r.abs() + result == Rational.new({ num: 1, den: 2 }) } # Absolute value of a negative rational number with negative denominator expect { - r = { num: 1, den: -2 } - result = r->abs() - result == { num: 1, den: 2 } + r = Rational.new({ num: 1, den: -2 }) + result = r.abs() + result == Rational.new({ num: 1, den: 2 }) } # Absolute value of zero expect { - r = { num: 0, den: 1 } - result = r->abs() - result == { num: 0, den: 1 } + r = Rational.new({ num: 0, den: 1 }) + result = r.abs() + result == Rational.new({ num: 0, den: 1 }) } # Absolute value of a rational number is reduced to lowest terms expect { - r = { num: 2, den: 4 } - result = r->abs() - result == { num: 1, den: 2 } + r = Rational.new({ num: 2, den: 4 }) + result = r.abs() + result == Rational.new({ num: 1, den: 2 }) } ## @@ -208,74 +204,74 @@ expect { # Raise a positive rational number to a positive integer power expect { - r = { num: 1, den: 2 } + r = Rational.new({ num: 1, den: 2 }) n = 3 - result = r->exp(n) - result == { num: 1, den: 8 } + result = r.exp(n) + result == Rational.new({ num: 1, den: 8 }) } # Raise a negative rational number to a positive integer power expect { - r = { num: -1, den: 2 } + r = Rational.new({ num: -1, den: 2 }) n = 3 - result = r->exp(n) - result == { num: -1, den: 8 } + result = r.exp(n) + result == Rational.new({ num: -1, den: 8 }) } # Raise a positive rational number to a negative integer power expect { - r = { num: 3, den: 5 } + r = Rational.new({ num: 3, den: 5 }) n = -2 - result = r->exp(n) - result == { num: 25, den: 9 } + result = r.exp(n) + result == Rational.new({ num: 25, den: 9 }) } # Raise a negative rational number to an even negative integer power expect { - r = { num: -3, den: 5 } + r = Rational.new({ num: -3, den: 5 }) n = -2 - result = r->exp(n) - result == { num: 25, den: 9 } + result = r.exp(n) + result == Rational.new({ num: 25, den: 9 }) } # Raise a negative rational number to an odd negative integer power expect { - r = { num: -3, den: 5 } + r = Rational.new({ num: -3, den: 5 }) n = -3 - result = r->exp(n) - result == { num: -125, den: 27 } + result = r.exp(n) + result == Rational.new({ num: -125, den: 27 }) } # Raise zero to an integer power expect { - r = { num: 0, den: 1 } + r = Rational.new({ num: 0, den: 1 }) n = 5 - result = r->exp(n) - result == { num: 0, den: 1 } + result = r.exp(n) + result == Rational.new({ num: 0, den: 1 }) } # Raise one to an integer power expect { - r = { num: 1, den: 1 } + r = Rational.new({ num: 1, den: 1 }) n = 4 - result = r->exp(n) - result == { num: 1, den: 1 } + result = r.exp(n) + result == Rational.new({ num: 1, den: 1 }) } # Raise a positive rational number to the power of zero expect { - r = { num: 1, den: 2 } + r = Rational.new({ num: 1, den: 2 }) n = 0 - result = r->exp(n) - result == { num: 1, den: 1 } + result = r.exp(n) + result == Rational.new({ num: 1, den: 1 }) } # Raise a negative rational number to the power of zero expect { - r = { num: -1, den: 2 } + r = Rational.new({ num: -1, den: 2 }) n = 0 - result = r->exp(n) - result == { num: 1, den: 1 } + result = r.exp(n) + result == Rational.new({ num: 1, den: 1 }) } ## @@ -284,25 +280,25 @@ expect { # Raise a real number to a positive rational number expect { - r = { num: 4, den: 3 } + r = Rational.new({ num: 4, den: 3 }) x = 8 - result = x->exp_real(r) + result = Rational.exp_real(x, r) result->is_approx_eq(16.0.F64) } # Raise a real number to a negative rational number expect { - r = { num: -1, den: 2 } + r = Rational.new({ num: -1, den: 2 }) x = 9 - result = x->exp_real(r) + result = Rational.exp_real(x, r) result->is_approx_eq(0.3333333333333333.F64) } # Raise a real number to a zero rational number expect { - r = { num: 0, den: 1 } + r = Rational.new({ num: 0, den: 1 }) x = 2 - result = x->exp_real(r) + result = Rational.exp_real(x, r) result->is_approx_eq(1.0.F64) } @@ -312,51 +308,55 @@ expect { # Reduce a positive rational number to lowest terms expect { - r = { num: 2, den: 4 } - result = r->reduce() - result == { num: 1, den: 2 } + r = Rational.new({ num: 2, den: 4 }) + result = r.reduce() + result == Rational.new({ num: 1, den: 2 }) } # Reduce places the minus sign on the numerator expect { - r = { num: 3, den: -4 } - result = r->reduce() - result == { num: -3, den: 4 } + r = Rational.new({ num: 3, den: -4 }) + result = r.reduce() + result == Rational.new({ num: -3, den: 4 }) } # Reduce a negative rational number to lowest terms expect { - r = { num: -4, den: 6 } - result = r->reduce() - result == { num: -2, den: 3 } + r = Rational.new({ num: -4, den: 6 }) + result = r.reduce() + result == Rational.new({ num: -2, den: 3 }) } # Reduce a rational number with a negative denominator to lowest terms expect { - r = { num: 3, den: -9 } - result = r->reduce() - result == { num: -1, den: 3 } + r = Rational.new({ num: 3, den: -9 }) + result = r.reduce() + result == Rational.new({ num: -1, den: 3 }) } # Reduce zero to lowest terms expect { - r = { num: 0, den: 6 } - result = r->reduce() - result == { num: 0, den: 1 } + r = Rational.new({ num: 0, den: 6 }) + result = r.reduce() + result == Rational.new({ num: 0, den: 1 }) } # Reduce an integer to lowest terms expect { - r = { num: -14, den: 7 } - result = r->reduce() - result == { num: -2, den: 1 } + r = Rational.new({ num: -14, den: 7 }) + result = r.reduce() + result == Rational.new({ num: -2, den: 1 }) } # Reduce one to lowest terms expect { - r = { num: 13, den: 13 } - result = r->reduce() - result == { num: 1, den: 1 } + r = Rational.new({ num: 13, den: 13 }) + result = r.reduce() + result == Rational.new({ num: 1, den: 1 }) +} + +is_approx_eq = |f1, f2| { + (f1 * 1e9 + 0.5).to_u64_wrap() == (f2 * 1e9 + 0.5).to_u64_wrap() } # This program is only used to run tests with `roc test`, so main! does nothing. From 66b34a1fba212cfc2dba619f8fb730dafe51888c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 17:14:49 +1200 Subject: [PATCH 160/162] Format roc files in complex-numbers exercise --- exercises/practice/complex-numbers/.meta/Example.roc | 9 ++++++--- exercises/practice/complex-numbers/.meta/template.j2 | 1 - exercises/practice/complex-numbers/Complex.roc | 7 +++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/exercises/practice/complex-numbers/.meta/Example.roc b/exercises/practice/complex-numbers/.meta/Example.roc index 4644db68..d895e7b3 100644 --- a/exercises/practice/complex-numbers/.meta/Example.roc +++ b/exercises/practice/complex-numbers/.meta/Example.roc @@ -1,8 +1,10 @@ Complex := { real : F64, imag : F64 }.{ - new : { real: F64, imag: F64 } -> Complex - new = |{ real, imag }| { { real, imag } } + new : { real : F64, imag : F64 } -> Complex + new = |{ real, imag }| { + { real, imag } + } - # # The user can write plus(z1, z2), z1.plus(z2), or simply z1 + z2 + # # The user can write plus(z1, z2), z1.plus(z2), or simply z1 + z2 plus : Complex, Complex -> Complex plus = |{ real: a, imag: b }, { real: c, imag: d }| { { @@ -65,6 +67,7 @@ Complex := { real : F64, imag : F64 }.{ # The following function should soon be available in Roc's builtins e = 2.718281828459045.F64 + pi = 3.141592653589793.F64 # Calculates the natural logarithm of x, ln(x). diff --git a/exercises/practice/complex-numbers/.meta/template.j2 b/exercises/practice/complex-numbers/.meta/template.j2 index 853ab0ce..01868630 100644 --- a/exercises/practice/complex-numbers/.meta/template.j2 +++ b/exercises/practice/complex-numbers/.meta/template.j2 @@ -13,7 +13,6 @@ "div": "z1 / z2" } -%} - import Complex {% for supercase in cases %} diff --git a/exercises/practice/complex-numbers/Complex.roc b/exercises/practice/complex-numbers/Complex.roc index eaed6be3..6d00162a 100644 --- a/exercises/practice/complex-numbers/Complex.roc +++ b/exercises/practice/complex-numbers/Complex.roc @@ -1,10 +1,10 @@ -Complex := { real: F64, imag: F64 }.{ - new : { real: F64, imag: F64 } -> Complex +Complex := { real : F64, imag : F64 }.{ + new : { real : F64, imag : F64 } -> Complex new = |{ real, imag }| { crash "Please implement the 'new' function" } - # # The user can write plus(z1, z2), z1.plus(z2), or simply z1 + z2 + # # The user can write plus(z1, z2), z1.plus(z2), or simply z1 + z2 plus : Complex, Complex -> Complex plus = |z1, z2| { crash "Please implement the 'plus' function" @@ -43,4 +43,3 @@ Complex := { real: F64, imag: F64 }.{ crash "Please implement the 'exp' function" } } - From e131dc44cf71a775fd33411bcab06ed78475db84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 17:23:41 +1200 Subject: [PATCH 161/162] Replace QueenAttack.Square with ChessSquare for a cleaner API --- .../practice/queen-attack/.meta/Example.roc | 54 +++++++++---------- .../practice/queen-attack/.meta/config.json | 2 +- .../practice/queen-attack/.meta/template.j2 | 13 +++-- .../practice/queen-attack/ChessSquare.roc | 27 ++++++++++ .../practice/queen-attack/QueenAttack.roc | 29 ---------- .../queen-attack/queen-attack-test.roc | 45 ++++++++-------- 6 files changed, 82 insertions(+), 88 deletions(-) create mode 100644 exercises/practice/queen-attack/ChessSquare.roc delete mode 100644 exercises/practice/queen-attack/QueenAttack.roc diff --git a/exercises/practice/queen-attack/.meta/Example.roc b/exercises/practice/queen-attack/.meta/Example.roc index 6dac58d0..79a70585 100644 --- a/exercises/practice/queen-attack/.meta/Example.roc +++ b/exercises/practice/queen-attack/.meta/Example.roc @@ -1,39 +1,37 @@ -QueenAttack :: {}.{ - Square :: { row : U8, column : U8 }.{ - rank : Square -> U8 - rank = |{ row, column: _ }| row + 1 +ChessSquare :: { row : U8, column : U8 }.{ + rank : ChessSquare -> U8 + rank = |{ row, column: _ }| row + 1 - file : Square -> U8 - file = |{ row: _, column }| column + 'A' + file : ChessSquare -> U8 + file = |{ row: _, column }| column + 'A' - create : Str -> Try(Square, [InvalidSquare]) - create = |square_str| { - chars = square_str.to_utf8() - if chars.len() != 2 { + create : Str -> Try(ChessSquare, [InvalidSquare]) + create = |square_str| { + chars = square_str.to_utf8() + if chars.len() != 2 { + Err(InvalidSquare) + } else { + file_char = chars.get(0).map_err(|OutOfBounds| InvalidSquare)? + rank_char = chars.get(1).map_err(|OutOfBounds| InvalidSquare)? + if file_char < 'A' or file_char > 'H' or rank_char < '1' or rank_char > '8' { Err(InvalidSquare) } else { - file_char = chars.get(0).map_err(|OutOfBounds| InvalidSquare)? - rank_char = chars.get(1).map_err(|OutOfBounds| InvalidSquare)? - if file_char < 'A' or file_char > 'H' or rank_char < '1' or rank_char > '8' { - Err(InvalidSquare) - } else { - Ok({ row: rank_char - '1', column: file_char - 'A' }) - } + Ok({ row: rank_char - '1', column: file_char - 'A' }) } } + } - queen_can_attack : Square, Square -> Bool - queen_can_attack = |{ row: r1, column: c1 }, { row: r2, column: c2 }| { - abs_diff = |u, v| { - if u < v { - v - u - } else { - u - v - } + queen_can_attack : ChessSquare, ChessSquare -> Bool + queen_can_attack = |{ row: r1, column: c1 }, { row: r2, column: c2 }| { + abs_diff = |u, v| { + if u < v { + v - u + } else { + u - v } - rank_diff = abs_diff(r1, r2) - file_diff = abs_diff(c1, c2) - rank_diff == 0 or file_diff == 0 or rank_diff == file_diff } + rank_diff = abs_diff(r1, r2) + file_diff = abs_diff(c1, c2) + rank_diff == 0 or file_diff == 0 or rank_diff == file_diff } } diff --git a/exercises/practice/queen-attack/.meta/config.json b/exercises/practice/queen-attack/.meta/config.json index d9da1735..65e0ce42 100644 --- a/exercises/practice/queen-attack/.meta/config.json +++ b/exercises/practice/queen-attack/.meta/config.json @@ -4,7 +4,7 @@ ], "files": { "solution": [ - "QueenAttack.roc" + "ChessSquare.roc" ], "test": [ "queen-attack-test.roc" diff --git a/exercises/practice/queen-attack/.meta/template.j2 b/exercises/practice/queen-attack/.meta/template.j2 index ea9d3a78..f895dde1 100644 --- a/exercises/practice/queen-attack/.meta/template.j2 +++ b/exercises/practice/queen-attack/.meta/template.j2 @@ -2,8 +2,7 @@ {{ macros.canonical_ref() }} {{ macros.header() }} -import {{ exercise | to_pascal }} -create = {{ exercise | to_pascal }}.Square.create +import ChessSquare {% for supercase in cases %} ## @@ -15,26 +14,26 @@ create = {{ exercise | to_pascal }}.Square.create {%- if case["property"] == "create" %} {%- if case["expected"] == 0 %} expect { - square = create("{{ plugins.to_square(case["input"]["queen"]) }}")? + square = ChessSquare.create("{{ plugins.to_square(case["input"]["queen"]) }}")? result = square.rank() result == {{ plugins.to_rank(case["input"]["queen"]["position"]["row"]) }} } expect { - square = create("{{ plugins.to_square(case["input"]["queen"]) }}")? + square = ChessSquare.create("{{ plugins.to_square(case["input"]["queen"]) }}")? result = square.file() result == '{{ plugins.to_file(case["input"]["queen"]["position"]["column"]) }}' } {%- else %} expect { - result = create("{{ plugins.to_square(case["input"]["queen"]) }}") + result = ChessSquare.create("{{ plugins.to_square(case["input"]["queen"]) }}") result.is_err() } {%- endif %} {%- elif case["property"] == "canAttack" %} expect { - square1 = create("{{ plugins.to_square(case["input"]["white_queen"]) }}")? - square2 = create("{{ plugins.to_square(case["input"]["black_queen"]) }}")? + square1 = ChessSquare.create("{{ plugins.to_square(case["input"]["white_queen"]) }}")? + square2 = ChessSquare.create("{{ plugins.to_square(case["input"]["black_queen"]) }}")? result = square1.queen_can_attack(square2) result == {{ case["expected"] | to_roc }} } diff --git a/exercises/practice/queen-attack/ChessSquare.roc b/exercises/practice/queen-attack/ChessSquare.roc new file mode 100644 index 00000000..129a7bac --- /dev/null +++ b/exercises/practice/queen-attack/ChessSquare.roc @@ -0,0 +1,27 @@ +ChessSquare :: { + # TODO: change this opaque type however you need + todo1 : U64, + todo2 : U64, + todo3 : U64, + # etc. +}.{ + create : Str -> Try(ChessSquare, _) + create = |square_str| { + crash "Please implement the 'create' function" + } + + rank : ChessSquare -> U8 + rank = |square| { + crash "Please implement the 'rank' function" + } + + file : ChessSquare -> U8 + file = |square| { + crash "Please implement the 'file' function" + } + + queen_can_attack : ChessSquare, ChessSquare -> Bool + queen_can_attack = |square1, square2| { + crash "Please implement the 'queen_can_attack' function" + } +} diff --git a/exercises/practice/queen-attack/QueenAttack.roc b/exercises/practice/queen-attack/QueenAttack.roc deleted file mode 100644 index dab5a494..00000000 --- a/exercises/practice/queen-attack/QueenAttack.roc +++ /dev/null @@ -1,29 +0,0 @@ -QueenAttack :: {}.{ - Square :: { - # TODO: change this opaque type however you need - todo1 : U64, - todo2 : U64, - todo3 : U64, - # etc. - }.{ - create : Str -> Try(Square, _) - create = |square_str| { - crash "Please implement the 'create' function" - } - - rank : Square -> U8 - rank = |square| { - crash "Please implement the 'rank' function" - } - - file : Square -> U8 - file = |square| { - crash "Please implement the 'file' function" - } - - queen_can_attack : Square, Square -> Bool - queen_can_attack = |square1, square2| { - crash "Please implement the 'queen_can_attack' function" - } - } -} diff --git a/exercises/practice/queen-attack/queen-attack-test.roc b/exercises/practice/queen-attack/queen-attack-test.roc index 891ab0ae..0b56b61f 100644 --- a/exercises/practice/queen-attack/queen-attack-test.roc +++ b/exercises/practice/queen-attack/queen-attack-test.roc @@ -1,9 +1,8 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/queen-attack/canonical-data.json -# File last updated on 2026-06-22 +# File last updated on 2026-06-23 -import QueenAttack -create = QueenAttack.Square.create +import ChessSquare ## ## Test creation of Queens with valid and invalid positions @@ -11,26 +10,26 @@ create = QueenAttack.Square.create # queen with a valid position expect { - square = create("C3")? + square = ChessSquare.create("C3")? result = square.rank() result == 3 } expect { - square = create("C3")? + square = ChessSquare.create("C3")? result = square.file() result == 'C' } # queen must have row on board expect { - result = create("E9") + result = ChessSquare.create("E9") result.is_err() } # queen must have column on board expect { - result = create("I5") + result = ChessSquare.create("I5") result.is_err() } @@ -40,64 +39,64 @@ expect { # cannot attack expect { - square1 = create("E3")? - square2 = create("G7")? + square1 = ChessSquare.create("E3")? + square2 = ChessSquare.create("G7")? result = square1.queen_can_attack(square2) result == Bool.False } # can attack on same row expect { - square1 = create("E3")? - square2 = create("G3")? + square1 = ChessSquare.create("E3")? + square2 = ChessSquare.create("G3")? result = square1.queen_can_attack(square2) result == Bool.True } # can attack on same column expect { - square1 = create("F5")? - square2 = create("F3")? + square1 = ChessSquare.create("F5")? + square2 = ChessSquare.create("F3")? result = square1.queen_can_attack(square2) result == Bool.True } # can attack on first diagonal expect { - square1 = create("C3")? - square2 = create("E1")? + square1 = ChessSquare.create("C3")? + square2 = ChessSquare.create("E1")? result = square1.queen_can_attack(square2) result == Bool.True } # can attack on second diagonal expect { - square1 = create("C3")? - square2 = create("B4")? + square1 = ChessSquare.create("C3")? + square2 = ChessSquare.create("B4")? result = square1.queen_can_attack(square2) result == Bool.True } # can attack on third diagonal expect { - square1 = create("C3")? - square2 = create("B2")? + square1 = ChessSquare.create("C3")? + square2 = ChessSquare.create("B2")? result = square1.queen_can_attack(square2) result == Bool.True } # can attack on fourth diagonal expect { - square1 = create("H2")? - square2 = create("G1")? + square1 = ChessSquare.create("H2")? + square2 = ChessSquare.create("G1")? result = square1.queen_can_attack(square2) result == Bool.True } # cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal expect { - square1 = create("B5")? - square2 = create("F3")? + square1 = ChessSquare.create("B5")? + square2 = ChessSquare.create("F3")? result = square1.queen_can_attack(square2) result == Bool.False } From 1ef0a9895b3dd932c9b34be4b9233277306b6205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Tue, 23 Jun 2026 17:47:28 +1200 Subject: [PATCH 162/162] Remove parentheses for crash --- exercises/practice/eliuds-eggs/EliudsEggs.roc | 2 +- exercises/practice/flatten-array/FlattenArray.roc | 2 +- exercises/practice/gigasecond/Gigasecond.roc | 5 +++-- exercises/practice/grains/Grains.roc | 4 ++-- exercises/practice/isogram/Isogram.roc | 2 +- exercises/practice/meetup/Meetup.roc | 5 +++-- exercises/practice/micro-blog/MicroBlog.roc | 8 +++++--- exercises/practice/raindrops/Raindrops.roc | 2 +- 8 files changed, 17 insertions(+), 13 deletions(-) diff --git a/exercises/practice/eliuds-eggs/EliudsEggs.roc b/exercises/practice/eliuds-eggs/EliudsEggs.roc index 58e9ac6b..969d3ef1 100644 --- a/exercises/practice/eliuds-eggs/EliudsEggs.roc +++ b/exercises/practice/eliuds-eggs/EliudsEggs.roc @@ -1,6 +1,6 @@ EliudsEggs :: {}.{ egg_count : U64 -> U64 egg_count = |number| { - crash ("Please implement the 'egg_count' function") + crash "Please implement the 'egg_count' function" } } diff --git a/exercises/practice/flatten-array/FlattenArray.roc b/exercises/practice/flatten-array/FlattenArray.roc index b3ee8acd..997b050b 100644 --- a/exercises/practice/flatten-array/FlattenArray.roc +++ b/exercises/practice/flatten-array/FlattenArray.roc @@ -3,7 +3,7 @@ FlattenArray :: {}.{ flatten : NestedValue -> List(I64) flatten = |array| { - crash ("Please implement the 'flatten' function") + crash "Please implement the 'flatten' function" } } diff --git a/exercises/practice/gigasecond/Gigasecond.roc b/exercises/practice/gigasecond/Gigasecond.roc index c300b224..f485dc7c 100644 --- a/exercises/practice/gigasecond/Gigasecond.roc +++ b/exercises/practice/gigasecond/Gigasecond.roc @@ -1,5 +1,6 @@ Gigasecond :: {}.{ add : Str -> Str - add = |moment| - crash("Please implement the 'add' function") + add = |moment| { + crash "Please implement the 'add' function" + } } diff --git a/exercises/practice/grains/Grains.roc b/exercises/practice/grains/Grains.roc index ddfe4310..85021dbf 100644 --- a/exercises/practice/grains/Grains.roc +++ b/exercises/practice/grains/Grains.roc @@ -1,11 +1,11 @@ Grains :: {}.{ grains_on_square : U8 -> Try(U64, _) grains_on_square = |square| { - crash ("Please implement the 'grains_on_square' function") + crash "Please implement the 'grains_on_square' function" } total_grains : U64 total_grains = { - crash ("Please implement the 'total_grains' function") + crash "Please implement the 'total_grains' function" } } diff --git a/exercises/practice/isogram/Isogram.roc b/exercises/practice/isogram/Isogram.roc index f6072808..e7f5e86f 100644 --- a/exercises/practice/isogram/Isogram.roc +++ b/exercises/practice/isogram/Isogram.roc @@ -1,6 +1,6 @@ Isogram :: {}.{ is_isogram : Str -> Bool is_isogram = |phrase| { - crash ("Please implement the 'is_isogram' function") + crash "Please implement the 'is_isogram' function" } } diff --git a/exercises/practice/meetup/Meetup.roc b/exercises/practice/meetup/Meetup.roc index 4430344a..1929622c 100644 --- a/exercises/practice/meetup/Meetup.roc +++ b/exercises/practice/meetup/Meetup.roc @@ -1,7 +1,8 @@ Meetup :: {}.{ meetup : { year : I64, month : U8, week : Week, day_of_week : DayOfWeek } -> Result Str _ - meetup = |{ year, month, week, day_of_week }| - crash("Please implement the 'meetup' function") + meetup = |{ year, month, week, day_of_week }| { + crash "Please implement the 'meetup' function" + } # HINT: we have added the `roc-isodate` package to the app's header in # meetup-test.roc, so you can use it here if you need to. diff --git a/exercises/practice/micro-blog/MicroBlog.roc b/exercises/practice/micro-blog/MicroBlog.roc index 4309a21b..96d1c3c8 100644 --- a/exercises/practice/micro-blog/MicroBlog.roc +++ b/exercises/practice/micro-blog/MicroBlog.roc @@ -1,8 +1,10 @@ import unicode.Grapheme + MicroBlog :: {}.{ - truncate : Str -> Result Str _ - truncate = |input| - crash("Please implement the 'truncate' function") + truncate : Str -> Try(Str, _) + truncate = |input| { + crash "Please implement the 'truncate' function" + } } diff --git a/exercises/practice/raindrops/Raindrops.roc b/exercises/practice/raindrops/Raindrops.roc index fa28f772..52e28608 100644 --- a/exercises/practice/raindrops/Raindrops.roc +++ b/exercises/practice/raindrops/Raindrops.roc @@ -1,6 +1,6 @@ Raindrops :: {}.{ convert : U64 -> Str convert = |number| { - crash ("Please implement the 'convert' function") + crash "Please implement the 'convert' function" } }