Stream: beginners

Topic: 99 bottles challenge


view this post on Zulip jan kili (Jul 03 2022 at 04:50):

# "99 Bottles of Beer" in Roc, by Jan Van Bruggen
#
# For https://99-bottles-of-beer.net

app "sing"
    packages { pf: "../roc/examples/hello-world/platform/main.roc" }
    imports []
    provides [main] to pf

main = List.range 0 100
    |> List.reverse
    |> List.map \count ->
        before = Num.toStr count
        after = Num.toStr (count - 1)
        when count is
            0 -> "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall."
            1 -> "1 bottle of beer on the wall, 1 bottle of beer.\nTake one down and pass it around, no more bottles of beer on the wall."
            2 -> "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall."
            _ -> "\(before) bottles of beer on the wall, \(before) bottles of beer.\nTake one down and pass it around, \(after) bottles of beer on the wall."
    |> Str.joinWith "\n\n"
    |> Str.concat "\n"

Any other approaches? :)

view this post on Zulip Richard Feldman (Jul 03 2022 at 12:43):

maybe List.range 100 0 should Just Work here

view this post on Zulip Richard Feldman (Jul 03 2022 at 12:43):

would be a lot more efficient than reversing in place!

view this post on Zulip Qqwy / Marten (Jul 03 2022 at 12:44):

List.range 0 100 |> List.map (\x -> 100 - x) |> ... :sunglasses:

view this post on Zulip Richard Feldman (Jul 03 2022 at 12:44):

you could also do a List.walkBackwards and build up the answer list one List.append at a time

view this post on Zulip Qqwy / Marten (Jul 03 2022 at 12:46):

We should benchmark this against using Task.await (Stdout.line ...)

view this post on Zulip Richard Feldman (Jul 03 2022 at 12:55):

I'd expect the List version to be faster - fewer syscalls, and Stdout is usually buffered (on a line boundary too), so probably fewer array writes too :big_smile:

view this post on Zulip Richard Feldman (Jul 03 2022 at 12:56):

but then again, performance is often surprising in practice!

view this post on Zulip jan kili (Jul 24 2022 at 03:41):

I refactored my script to make it more s c a l a b l e - any ideas for how to gracefully merge before1 and before2?

# "99 Bottles of Beer" in Roc, by Jan Van Bruggen
#
# For https://99-bottles-of-beer.net

app "sing"
    packages { pf: "../roc/examples/hello-world/platform/main.roc" }
    imports []
    provides [main] to pf

main = List.range 0 100
    |> List.reverse
    |> List.map verse
    |> Str.joinWith "\n\n"
    |> Str.concat "\n"

verse = \count ->
    take = "Take one down and pass it around"
    more = "Go to the store and buy some more"
    when count is
        0 -> format "No more bottles" "no more bottles" more "99 bottles"
        1 -> format "1 bottle" "1 bottle" take "No more bottles"
        2 -> format "2 bottles" "2 bottles" take "1 bottle"
        _ ->
            stringify = \n -> n |> Num.toStr |> Str.concat " bottles"
            format (stringify count) (stringify count) take (stringify (count - 1))

format = \before1, before2, action, after ->
    "\(before1) of beer on the wall, \(before2) of beer.\n\(action), \(after) of beer on the wall."

view this post on Zulip jan kili (Jul 24 2022 at 03:42):

(the only reason for the split is No more vs. no more :distraught:)

view this post on Zulip jan kili (Jul 24 2022 at 03:43):

... or a bigger idea for how to do dynamic verse formatting more gracefully in general?

view this post on Zulip Thomas Dwyer (Jul 24 2022 at 07:57):

You could pass a function on how to transform a capitalized string "No more bottles" into the non-capitalized form, which gives the caller a similar level of freedom.
A bigger idea could be defining functions like format for specific uses (like this one) but a record with transformations on a string inside of it. Functions that format strings could then require specific formatting functions and make the rest optional (or enable defaulting, as it's entirely possible to default some of these operations). This reduces the number of arguments and enables modular formatting functions with similar design elements. Just an idea though, no idea how ergonomic that would be

view this post on Zulip Thomas Dwyer (Jul 24 2022 at 08:25):

I imagine something like the following:

format :
    {
        toLowerPhr : Str -> Str,
        toUpperPhr : Str -> Str,
        replace : Map [Action, After] Str -> [Action, After] -> Str ,
        # ^ The idea behind this is to mitigate the action and after
               arguments and instead enable ad hoc replacements
        replacements : Map [Action, After] Str
    }*,
    # ^ This whole record would be a Formatter and all formatting functions would use it.
    Str -> Str
format = \{ toLowerPhr, toUpperPhr, replace, replacements}, str ->
    "\(toUpperPhr str) of beer on the wall, \(toLowerPhr str) of beer.\n\(replace replacements Action), \(replace replacements After) of beer on the wall"

This means you pick and choose which formatting functions you want and throw out the rest. The caller provides the formatters they want and the formatter applies them where necessary. Hope this makes sense, also note I haven't done much Roc coding aside from browsing discussions here so I'm unsure how ergonomic this may or may not be. One thing i don't know how to do is enable picking and choosing of record fields from an established record (Formatter) in the Type declaration so new formatting functions can't add new functions not present in Formatter to the record; it may be difficult to have a la carte Open Records, if that makes sense. (Apologies for the several edits, its way too late at night lol)

view this post on Zulip Qqwy / Marten (Jul 24 2022 at 10:22):

Is the 'No more bottles...' thing required? There seem to be many implementations that just use `0 bottles...'

view this post on Zulip jan kili (Jul 24 2022 at 12:14):

I agree, that website probably would accept 0 bottles, but I embrace this string formatting challenge!

view this post on Zulip jan kili (Jul 24 2022 at 12:19):

@Thomas Dwyer interesting, I want to try that and see how it feels!


Last updated: Jul 06 2025 at 12:14 UTC