Stream: beginners

Topic: How to (re)write a library?


view this post on Zulip George (Aug 16 2026 at 14:11):

Hello, I played around with basic-webserver and tried to find out if I can get it connected to a postgres database.
I found this library https://github.com/agu-z/roc-pg/ but it hasn't been touched for over a year, so it doesn't work with the latest roc version. I tried to make it work but I am completely stuck.
First, I looked into rewriting that library with the new syntax but I am lost given the lack of documentation. Then I looked into rewriting it from scratch but I just lack some Roc ecosystem basics. For example, how do I express the fact that this library requires the app using it to have access to Tcp sockets, like in basic-cli?
The only point of reference I have is https://www.roc-lang.org/docs/main/langref/modules/ but it lacks the information or guidance I would need. Does anyone have pointers? Or a library I can use as a reference?

view this post on Zulip George (Aug 16 2026 at 14:11):

Sorry, I meant to post this into #beginners

view this post on Zulip Niclas Ahden (Aug 16 2026 at 16:01):

This could be a good reference: https://github.com/niclas-ahden/roc-pg :)

Here's a list of packages, which can be really helpful! https://github.com/lukewilliamboswell/roc-awesome

view this post on Zulip Niclas Ahden (Aug 16 2026 at 16:05):

how do I express the fact that this library requires the app using it to have access to Tcp sockets, like in basic-cli?

I've been doing the following for my packages: https://github.com/niclas-ahden/roc-pg/blob/main/examples/query.roc#L18

You specify a number of effects that the package needs and the user passes those in. That way you're never tied to a certain platform. Users can use your package as long as they can provide the required effects :thumbs_up:

view this post on Zulip Notification Bot (Aug 17 2026 at 10:35):

This topic was moved here from #contributing > How to (re)write a library? by Anton.

view this post on Zulip George (Aug 19 2026 at 17:41):

@Niclas Ahden
Thanks for the reference! I looked into it and have some thoughts. Your version of roc-pg depends on your own fork of basic-cli because you implemented connection pooling on top of it. I am not sure if that's the best approach for anyone other than you because ideally, people (myself included) only want to depend on official roc-lang platforms.

view this post on Zulip George (Aug 19 2026 at 17:43):

This is a tough nut to crack in my brain. I'd love to be able to write a package and say "this only works with basic-cli" and be able to use the code there, but I get that from an ecosystem perspective this is not desirable because it causes fragmentation.

view this post on Zulip Niclas Ahden (Aug 19 2026 at 17:46):

Indeed, to each their own! :)

I was experimenting with connection pooling and am probably going to use that once I start getting my real apps moved over to the new compiler, so that's why it's there. I'm probably going to maintain my own fork of basic-cli for the foreseeable future, because I'll want things in there which _shouldn't_ go in the official one (it's supposed to be "basic", and I'm moving a bit beyond that).

view this post on Zulip George (Aug 19 2026 at 17:46):

But I am also not sure if passing all effects to the library is the way to go. When I imagine a backend with many different libraries (for database, Redis, SDKs for third-party services, etc.) it seems pretty annoying to have to wire all the code/effects from Tcp into them each time.

view this post on Zulip George (Aug 19 2026 at 17:51):

I wonder if it would make sense to externalize Tcp (or something similar that represents a bi-directional communication channel) into a separate package similar to how roc-lang/http does so for Request and Response. Then libraries could be written around that and the platform / app would only have to take care of providing the tcp socket.

view this post on Zulip Niclas Ahden (Aug 19 2026 at 17:51):

George said:

But I am also not sure if passing all effects to the library is the way to go. When I imagine a backend with many different libraries (for database, Redis, SDKs for third-party services, etc.) it seems pretty annoying to have to wire all the code/effects from Tcp into them each time.

I think that's one of the experiments the Roc community will run, and we'll see how it plays out. There are strong pros of passing in all effects of course, such as: security, visibility, patching up API differences if your platform isn't exactly the same shape, the ability to modify effects before you pass them to the package etc.

However, sometimes it'd be nice to be able to just assume that you have effects available and write your package as so, then the user could pass in e.g. the whole platform and it'll only work if the required API is there (e.g. if a package uses File.read! and Http.get! then those two have to exist with the correct shape, while we disregard everything else).

view this post on Zulip Niclas Ahden (Aug 19 2026 at 17:52):

George said:

I wonder if it would make sense to externalize Tcp (or something similar that represents a bi-directional communication channel) into a separate package similar to how roc-lang/http does so for Request and Response. Then libraries could be written around that and the platform / app would only have to take care of providing the tcp socket.

Yes, I think things like this will happen a lot. Many things will probably be standardized/shared :thumbs_up:

view this post on Zulip George (Aug 19 2026 at 17:53):

George said:

I wonder if it would make sense to externalize Tcp (or something similar that represents a bi-directional communication channel) into a separate package similar to how roc-lang/http does so for Request and Response. Then libraries could be written around that and the platform / app would only have to take care of providing the tcp socket.

I am not sure how frowned upon @-ing is here but I'd be curious what @Richard Feldman thoughts on this are (or any of the other maintainers).

view this post on Zulip George (Aug 19 2026 at 17:57):

Because such an approach (as mentioned for http and Request/Response) works best if there is a blessed package that everyone uses

view this post on Zulip Richard Feldman (Aug 19 2026 at 18:50):

yeah, I'm actually less sure about Request and Response after doing a big investigation of those :smile:

view this post on Zulip Richard Feldman (Aug 19 2026 at 18:50):

to be clear, we are in uncharted territory and I think there are things that make sense to try, but I don't think we can know what the outcome of the experiments are until we have tried them

view this post on Zulip Richard Feldman (Aug 19 2026 at 18:51):

and definitely the thing that makes sense to try first is the most-flexible and least-invasive one, which is basically "we have enough primitives to offer platform-agnostic packages that have to be provided with the effects they need, so let's try that and see how it goes"

view this post on Zulip Richard Feldman (Aug 19 2026 at 18:56):

as an example of why I'm less sure about Request and Response belonging in builtins: what if I had this for an error-reporting package for Sentry?

Sentry.init : {
    send! : {
        url : Str,
        verb : [GET, POST, PUT],
        body : Str,
        headers : List((Str, Str)),
    } => Try({}, {
        status : U16,
        body : Str
    }),
} -> Sentry

view this post on Zulip Richard Feldman (Aug 19 2026 at 19:00):

so the way I would use this is something like this:

sentry = Sentry.init({
    send!: |{ url, verb, body, headers}| {
        if url.starts_with("https://api.sentry.com/report/") {
            pf.Http.request!({
                url,
                verb,
                body,
                headers: headers.append("X-API-Key", my_api_key)
            }).map(|resp| {
                { status: resp.status(), body: resp.body() }
            })
        } else {
            # Security alert! Suspicious package!
        }
    }
})

view this post on Zulip Richard Feldman (Aug 19 2026 at 19:02):

so, this is a bit of boilerplate, but it's literally one function call to set up the entire package being longer (no matter what, you always have to do something like this to set up the API key). Doesn't feel excessive to me.

view this post on Zulip Richard Feldman (Aug 19 2026 at 19:04):

and the advantages are nice I think:

view this post on Zulip Richard Feldman (Aug 19 2026 at 19:04):

is this certainly the long term optimal design? I don't know!

view this post on Zulip Richard Feldman (Aug 19 2026 at 19:05):

but I think it's the right thing to try first, because it already works today, and it seems like the tradeoffs are already nice in an example like this - so I'd be curious to see how nice we can get things with other use cases like TCP

view this post on Zulip Aurélien Geron (Aug 19 2026 at 20:34):

This looks great. :+1: Some questions/comments:

view this post on Zulip Aurélien Geron (Aug 19 2026 at 20:57):

I guess secrets could work like this:


Last updated: Sep 03 2026 at 15:16 UTC