Stream: show and tell

Topic: autonomous Roc agent


view this post on Zulip Anton (Dec 09 2024 at 16:46):

I've built an autonomous Roc agent that uses claude to write Roc and fix it's own errors and test failures, it's implemented in Roc too!

Here you can see the process of writing and fixing a Roc function to find the longest palindromic substring:

INFO: Prompt:

Add a longest palindromic substring function to this Roc code, add tests using expect.
That function should do the following:
Given a string s, find the longest palindromic substring in s. A palindrome is a string that reads the same backward as forward.

Roc instructions:

Extra instructions:

app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" }

import pf.Stdout

main =
    Stdout.line! "Hello, World!"

expect 5 == 5

INFO: Asking Claude...

INFO: Claude's reply:

Here's the implementation of the longest palindromic substring function with tests:

app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" }

import pf.Stdout

main =
    Stdout.line! "Hello, World!"

longestPalindromicSubstring = \str ->
    if Str.isEmpty str then
        ""
    else
        strLen = Str.countGraphemes str
        maxLen = strLen
        findLongest = \len, start ->
            if len == 0 then
                ""
            else
                checkAllStarts = \currentStart ->
                    if currentStart + len > strLen then
                        findLongest (len - 1) 0
                    else
                        substring = Str.slice str { startAt: currentStart, len }
                        if isPalindrome substring then
                            substring
                        else
                            checkAllStarts (currentStart + 1)

                checkAllStarts start

        findLongest maxLen 0

isPalindrome = \str ->
    reversed = str |> Str.graphemes |> List.reverse |> Str.joinWith ""
    str == reversed

expect longestPalindromicSubstring "babad" == "bab"
expect longestPalindromicSubstring "cbbd" == "bb"
expect longestPalindromicSubstring "a" == "a"
expect longestPalindromicSubstring "" == ""
expect longestPalindromicSubstring "ac" == "a"
expect longestPalindromicSubstring "racecar" == "racecar"
expect isPalindrome "racecar" == Bool.true
expect isPalindrome "hello" == Bool.false

END

"
── NOT EXPOSED in main_claude.roc ──────────────────────────────────────────────

The Str module does not expose countGraphemes:

12│ strLen = Str.countGraphemes str
^^^^^^^^^^^^^^^^^^

Did you mean one of these?

Str.countUtf8Bytes
Str.contains
Str.concat
Str.joinWith

── NOT EXPOSED in main_claude.roc ──────────────────────────────────────────────

The Str module does not expose slice:

22│ substring = Str.slice str { startAt: currentStart, len }
^^^^^^^^^

Did you mean one of these?

Str.splitOn
Str.trim
Str.toDec
Str.concat

── NOT EXPOSED in main_claude.roc ──────────────────────────────────────────────

The Str module does not expose graphemes:

33│ reversed = str |> Str.graphemes |> List.reverse |> Str.joinWith ""
^^^^^^^^^^^^^

Did you mean one of these?

Str.repeat
Str.trim
Str.reserve
Str.capacity

────────────────────────────────────────────────────────────────────────────────

3 errors and 0 warnings found in 21 ms
"

INFO: roc check or roc test failed.

(CmdError (ExitCode -99))
INFO: Prompt:

── NOT EXPOSED in main_claude.roc ──────────────────────────────────────────────

The Str module does not expose countGraphemes:

12│ strLen = Str.countGraphemes str
^^^^^^^^^^^^^^^^^^

Did you mean one of these?

Str.countUtf8Bytes
Str.contains
Str.concat
Str.joinWith

── NOT EXPOSED in main_claude.roc ──────────────────────────────────────────────

The Str module does not expose slice:

22│ substring = Str.slice str { startAt: currentStart, len }
^^^^^^^^^

Did you mean one of these?

Str.splitOn
Str.trim
Str.toDec
Str.concat

── NOT EXPOSED in main_claude.roc ──────────────────────────────────────────────

The Str module does not expose graphemes:

33│ reversed = str |> Str.graphemes |> List.reverse |> Str.joinWith ""
^^^^^^^^^^^^^

Did you mean one of these?

Str.repeat
Str.trim
Str.reserve
Str.capacity

────────────────────────────────────────────────────────────────────────────────

3 errors and 0 warnings found in 21 ms

INFO: Asking Claude...

INFO: Claude's reply:

Analysis: It seems the Str module in Roc has different method names than what I used. Here's how we can fix it:

  1. Instead of countGraphemes, we can use Str.len
  2. Instead of slice, we can use string indexing with Str.fromUtf8 and Str.toUtf8
  3. Instead of graphemes, we can convert the string to UTF-8 bytes and work with those

Here's the corrected code:

app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" }

import pf.Stdout

main =
    Stdout.line! "Hello, World!"

longestPalindromicSubstring = \str ->
    if Str.isEmpty str then
        ""
    else
        strLen = Str.len str
        maxLen = strLen
        findLongest = \len, start ->
            if len == 0 then
                ""
            else
                checkAllStarts = \currentStart ->
                    if currentStart + len > strLen then
                        findLongest (len - 1) 0
                    else
                        substring = getSubstring str currentStart len
                        if isPalindrome substring then
                            substring
                        else
                            checkAllStarts (currentStart + 1)

                checkAllStarts start

        findLongest maxLen 0

getSubstring = \str, start, len ->
    bytes = Str.toUtf8 str
    result = List.sublist bytes { start, len }
    when Str.fromUtf8 result is
        Ok s -> s
        Err _ -> ""

isPalindrome = \str ->
    bytes = Str.toUtf8 str
    reversed = List.reverse bytes
    Str.fromUtf8 bytes == Str.fromUtf8 reversed

expect longestPalindromicSubstring "babad" == "bab"
expect longestPalindromicSubstring "cbbd" == "bb"
expect longestPalindromicSubstring "a" == "a"
expect longestPalindromicSubstring "" == ""
expect longestPalindromicSubstring "ac" == "a"
expect longestPalindromicSubstring "racecar" == "racecar"
expect isPalindrome "racecar" == Bool.true
expect isPalindrome "hello" == Bool.false

END

"
── NOT EXPOSED in main_claude.roc ──────────────────────────────────────────────

The Str module does not expose len:

12│ strLen = Str.len str
^^^^^^^

Did you mean one of these?

Str.trim
Str.toDec
Str.toU8
Str.toI8

────────────────────────────────────────────────────────────────────────────────

1 error and 0 warnings found in 18 ms
"

INFO: roc check or roc test failed.

INFO: Asking Claude...

INFO: Claude's reply:

Analysis: It seems we need to use a different way to get the string length. In Roc, we can use Str.toUtf8 to convert the string to bytes and then use List.len to get its length.

app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br" }

import pf.Stdout

main =
    Stdout.line! "Hello, World!"

longestPalindromicSubstring = \str ->
    if Str.isEmpty str then
        ""
    else
        strLen = str |> Str.toUtf8 |> List.len
        maxLen = strLen
        findLongest = \len, start ->
            if len == 0 then
                ""
            else
                checkAllStarts = \currentStart ->
                    if currentStart + len > strLen then
                        findLongest (len - 1) 0
                    else
                        substring = getSubstring str currentStart len
                        if isPalindrome substring then
                            substring
                        else
                            checkAllStarts (currentStart + 1)

                checkAllStarts start

        findLongest maxLen 0

getSubstring = \str, start, len ->
    bytes = Str.toUtf8 str
    result = List.sublist bytes { start, len }
    when Str.fromUtf8 result is
        Ok s -> s
        Err _ -> ""

isPalindrome = \str ->
    bytes = Str.toUtf8 str
    reversed = List.reverse bytes
    Str.fromUtf8 bytes == Str.fromUtf8 reversed

expect longestPalindromicSubstring "babad" == "bab"
expect longestPalindromicSubstring "cbbd" == "bb"
expect longestPalindromicSubstring "a" == "a"
expect longestPalindromicSubstring "" == ""
expect longestPalindromicSubstring "ac" == "a"
expect longestPalindromicSubstring "racecar" == "racecar"
expect isPalindrome "racecar" == Bool.true
expect isPalindrome "hello" == Bool.false

END

"0 errors and 0 warnings found in 22 ms
0 failed and 8 passed in 90 ms.
"

view this post on Zulip Anton (Dec 09 2024 at 16:47):

One thing I thought was cool: it tried to use Str.countGraphemes first, next it tried Str.len and after that it realized it had to use Str.toUtf8 followed by List.len. I did not tell it that Str.toUtf8 existed, it got that from its training data.

view this post on Zulip Anton (Dec 09 2024 at 16:49):

Source code:
roc-agent-claude.roc
prompt-palindrome.txt

Also, sed helper script because regex:
removeColorCodes.sh

view this post on Zulip Anton (Dec 09 2024 at 16:50):

Now I can catch up on advent of code :big_smile:

view this post on Zulip Anton (Dec 09 2024 at 16:53):

By the way; the agent still works if you use the cheaper (and less capable) haiku 3.5 model

view this post on Zulip Anthony Bullard (Dec 09 2024 at 17:05):

This is very cool!

view this post on Zulip Isaac Van Doren (Dec 09 2024 at 17:35):

Very cool!

view this post on Zulip Anton (May 09 2025 at 09:23):

Looks like I can set up zed to do everything that roc agent does :eyes:
https://zed.dev/blog/fastest-ai-code-editor


Last updated: Jul 06 2025 at 12:14 UTC