Stream: ideas

Topic: Platform extensibility using bundle of effects pattern


view this post on Zulip Luke Boswell (Sep 01 2026 at 05:46):

@Karl @Romain Lepert

We were discussing the general friction around extending platforms with the record "bundle of effects" pattern (as opposed to specifying where constraints on everything).

I've refactored roc-ray to follow this pattern in this spike https://github.com/lukewilliamboswell/roc-ray/pull/186, and also included a downstream package authoring guide here https://github.com/lukewilliamboswell/roc-ray/blob/3fe44434388204756b28e1603dceb6943f59b06c/docs/package-authors.md

It's only a minor change really from an app authors perspective, but I think it completely unlocks downstream package authors.

view this post on Zulip Luke Boswell (Sep 01 2026 at 05:48):

I would appreciate any feedback etc around this... I feel like we give it a try in the 0.10.0 version of roc-ray and if packages like Romain's terrocotta are still nice to build, then we consider doing something similar for basic-cli and basic-webserver etc.

The goal would be to really reduce the coupling of those cli and webserver to a particular higher level API and reduce the boundary to the minimal or lowest level interface, so that downstream packages across the ecosystem can build on and innovate from that foundation.

view this post on Zulip Notification Bot (Sep 01 2026 at 05:50):

2 messages were moved here from #announcements > roc-ray 0.9.0 by Luke Boswell.

view this post on Zulip Romain Lepert (Sep 01 2026 at 11:42):

I'll give some feedback this evening

view this post on Zulip Richard Feldman (Sep 01 2026 at 14:38):

:thinking: I don't understand why the app author experience would need to change

view this post on Zulip Richard Feldman (Sep 01 2026 at 14:38):

couldn't the previous API be implemented in terms of this behind the scenes?

view this post on Zulip Richard Feldman (Sep 01 2026 at 14:43):

(I mean for using the platform API directly as opposed to using a package)

view this post on Zulip Richard Feldman (Sep 01 2026 at 14:45):

hm, I also don't see why accepting Drawing.Effects is better than accepting a where with a method requirements alias :thinking:

view this post on Zulip Richard Feldman (Sep 01 2026 at 15:39):

the overall design principles that I think will lead to the best experience:

view this post on Zulip Richard Feldman (Sep 01 2026 at 15:51):

honestly I think the *-types package is a trap and we shouldn't do it. It makes package code more concise, at the cost of:

view this post on Zulip Richard Feldman (Sep 01 2026 at 15:54):

so to reiterate, what I really strongly think we should be doing is:

view this post on Zulip Richard Feldman (Sep 01 2026 at 15:55):

if we try this and it turns out it there's some blocker that means it can't be done this way (I can't think of any but maybe there's one I'm not thinking of!) then we should address things like that on a case-by-case basis imo!

view this post on Zulip Romain Lepert (Sep 01 2026 at 19:48):

I also don't see why accepting Drawing.Effects is better than accepting a where with a method requirements alias

I agree I don't see the particular benefit, whether for app authors or package authors. In particular because the example is Drawing.Effects which is what I'd like to get rid off to begin with :sweat_smile:. I would prefer that frame just carries the subset of Host.roc effects that are supported in the render! lifecycle phase. That is all that is needed. Then packages can do where clause on frame and build whatever API they want on top.

In this case it is not an option, it is a requirement. roc-ray host can creates resources (Font, Texture, Shader, etc.) that are addressed through an opaque nominal handle.

Texture := { handle : Handle, width : F32, height : F32 }.{
    Handle :: Box(U64)
}

Only effects can create them (e.g. load_texture! : Str -> Texture) so it is impossible for an app to manufacture a fake handle that does not have an associated hosted resource.
Packages can't match Texture structurally because of the nominal handle, and can't import Texture platform types. So package can't work with basic platform data structure. However if Texture lives in *-types then it can be imported, which means packages and platform can agree on Texture type. That is the reason *-types was introduced.

You say "[packages] can specify all the relevant types itself (e.g. using where clauses when it needs nominal types)". I don't understand how where helps here :thinking:

Now as to the other arguments against *-types package:

All that to say that i'll give it a shot nonetheless :laughing: , but it is not without friction and eyebrow raising moments.

view this post on Zulip Richard Feldman (Sep 01 2026 at 21:24):

Romain Lepert said:

In this case it is not an option, it is a requirement. roc-ray host can creates resources (Font, Texture, Shader, etc.) that are addressed through an opaque nominal handle.

Texture := { handle : Handle, width : F32, height : F32 }.{
    Handle :: Box(U64)
}

Only effects can create them (e.g. load_texture! : Str -> Texture) so it is impossible for an app to manufacture a fake handle that does not have an associated hosted resource.
Packages can't match Texture structurally because of the nominal handle, and can't import Texture platform types. So package can't work with basic platform data structure. That is the reason *-types was introduced.

You say "[packages] can specify all the relevant types itself (e.g. using where clauses when it needs nominal types)". I don't understand how where helps here :thinking:

ah! so my thinking here is that you use type variables to relate these - e.g.

where [
    assets.load_texture! : Str => Try(texture, _),
    draw.texture_at : texture, Vec2 -> texture_draw,
    draw.texture! : Frame, texture_draw => {},
]

view this post on Zulip Richard Feldman (Sep 01 2026 at 21:25):

in other words, "we don't care specifically what a texture is (although if we need methods from it, we put where clauses on its texture type variable" aside from "it's the thing that load_texture! produces and texture_at receives"

view this post on Zulip Richard Feldman (Sep 01 2026 at 21:25):

does that make sense?

view this post on Zulip Luke Boswell (Sep 02 2026 at 01:00):

Romain Lepert said:

I think we should consider this ... it would solve a few problems. The platform is just a package like any other, it exposes types. Why couldn't a package import a platform package? there would be nothing it can do with the hosted effects, I would expect a compiler error if a package tried to use one of those.

This would eliminate the need for roc-ray/types package because then downstream packages like terracotta can see the definitions for Mouse Keys etc and reuse those.

view this post on Zulip Jasper Woudenberg (Sep 02 2026 at 07:19):

Question: in this design, is it the platform or the package or the platform that's end-responsible for the app-authoring experience?

I'm wondering because I see sort of two different possible designs here:

  1. You can conceive of the platform as a set of lower level primitives, that a package builds upon to create a nice user experience for writing apps. That's the Rails model, and it seems maybe the relation roc-ray and terrocotta have (is that fair?). I think that in this model "platform" might better be called "library", and the package has a better claim to the name "platform".
  2. You can conceive of the platform as the primary interface for the app author, with packages having a smaller role for adding a little extra functionality here and there (similar to Elm). This is how I understand Roc's design goals.

My sense is these proposals are motivated by a desire to make the first model work better. Is that fair?

view this post on Zulip Romain Lepert (Sep 02 2026 at 07:39):

Yes. The design is around the first use case. Platform provides the host data types and effects. Package provides the app authoring experience (~framework).

view this post on Zulip Jasper Woudenberg (Sep 02 2026 at 08:33):

Gotcha, thanks! I guess my follow-up question then is: for creating some nice app authoring experience, why choose to do a package over a platform? Platforms can do anything a package can, plus there's extra benefits:

Isn't that the nicer experience for the app author and the platform/package designer?

view this post on Zulip Romain Lepert (Sep 02 2026 at 08:40):

Richard Feldman said:

in other words, "we don't care specifically what a texture is (although if we need methods from it, we put where clauses on its texture type variable" aside from "it's the thing that load_texture! produces and texture_at receives"

Ok, that approach actually highlights the genericity complaint that I have.

Here is a PR that would make terrocotta generic over texture instead of using a concrete rrt.Texture.

Because a layout node might hold a texture (image node), then LayoutNode becomes LayoutNode(texture) and it bleeds to everything upstream of LayoutNode

LayoutNode(texture)
ElementOp(msg, texture)
Layout(texture)
View(msg, texture)
Program.State(model, msg, texture)

1) It is not great for the package authoring (see ./package diff).
There only place that make use of the texture: it is the frame.texture!(texture) call in Renderer.draw_image!(). This one legitimately could warrant the genericity, but the genericity bleeds all the way up.

2) It is not great for the app authoring (see ./examples diff).
These two types are user facing: Program.State(model, msg, texture), View(msg, texture)

button : Str, Msg -> View(Msg, texture)
button = |label, msg| {
    box({ style: |status| style.width(Fit({})), events: [OnClick(Increment)] }, [
        text(label),
        ])
}

This is the function/type coloring problem all over again, but this time for something that seems even more basic than async or lifetimes (both of which you actively designed to avoid :wink: ).

view this post on Zulip Romain Lepert (Sep 02 2026 at 08:49):

Now this is because of the Image(texture) node, but terrocotta also has Text(font) nodes and eventually wants to support Canvas(frame) nodes (charts, games, vector drawing editor, etc.) and Shader(shader)nodes (hue wheel, audio spectrogram, heatmap, etc.).

These will duplicate the same genericity bleed and user will end up with View(Msg, texture, font, shader, frame) and Program.State(model, msg, texture, font, shader, frame).

view this post on Zulip Romain Lepert (Sep 02 2026 at 08:52):

today i can avoid the texture, font and shader type parameters thanks to the *-types public package which gives the concrete types. However I can't avoid the frame generics because it is just a type carrying effects methods and *-types package can't describe that.

view this post on Zulip Richard Feldman (Sep 02 2026 at 11:54):

I see, and I agree - in practice that doesn't seem nice. Thanks for making that PR so we can see how it looks fleshed out!

view this post on Zulip Richard Feldman (Sep 02 2026 at 11:58):

Romain Lepert said:

Yes. The design is around the first use case. Platform provides the host data types and effects. Package provides the app authoring experience (~framework).

I think this is probably the root of the friction here: arguably the most unique thing that Roc does which no other language does is to be designed around the "platform is responsible for app authoring experience, and so building frameworks on top of platforms do not make sense" :smile:

view this post on Zulip Richard Feldman (Sep 02 2026 at 11:59):

so I think this supports the conclusion that the root of the problem here is trying to fit a (platform+package) shaped solution into a platform-shaped hole :smile:

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:01):

concretely, I think the earlier direction we discussed briefly - of these layout primitives making more sense in roc-ray as opposed to in a separate package - was the right direction after all!

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:02):

I was definitely under the impression that we thought platforms would trend towards lower level or more common abstractions and the packages would sit above that. This is how I have been framing this research.

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:02):

oh definitely not, that's a miscommunication on my part then!

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:03):

the vision for platforms has always been "the platform author is in charge of crafting a batteries-included app authoring experience for a particular domain" - just like Elm does

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:03):

the whole platform concept came out of me trying to answer the question "how could I get a curated, Elm-like experience in a long tail of domains?"

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:04):

So where does something like the whole bugsnag discussion fit in ... like making packages that are cross-platform?

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:04):

those are things that aren't really domain-specific

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:05):

like error logging services are a thing you can want when doing a GUI, a CLI, a server, a raspberry pi that controls a thermostat... :smile:

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:06):

Ah ok ... so if e.g. terrocotta was simply the clay layout algorithm that would be cross-platform, but when it also includes the drawing and other things it becomes more coupled to the domain and the platform.

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:09):

maybe another way to think of it: here are some types of GUI apps people build outside of Roc

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:09):

each of these approaches could be a Roc platform

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:13):

Ok, here's another angle ... what if I wanted to make a Calendar widget in a package for roc-ray? should I be able to do that, or how would I distribute that?

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:15):

I guess in this case the type var would be less of an issue

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:18):

yeah that's worth trying out concretely to see how it would look I think

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:22):

It sounds like in this "new world order" terrocotta should instead be focussed on the core algorithms and data structures and then roc-ray would be a consumer of instead of a dependency.

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:26):

At the risk of oversimplifying

Screenshot 2026-09-02 at 22.26.21.png

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:30):

Or another framing... all packages should probably be aimed at a cross-platform API, if it's coupled to a single platform then that is probably a smell

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:34):

I'm just trying to make sense of things here... not claiming this is "the way" or anything. We've got a few directions to experiment in

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:36):

yeah one way I've been thinking about it is "reducing layers"

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:38):

like one of the reasons I chose the name "platform" instead of "framework" (which was a name I strongly considered at one point!) was that a Roc platform can in theory provide everything all the way down to - and including - the operating system.

So like a unikernel for a server, or a video game console game development platform.

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:41):

and one of the reasons I think that's valuable is that I think we can attribute a good amount of software slowness to "it's fundamentally hard to make things faster when you have this many layers" because each layer can only optimize critical paths down to the layer right below it, and any optimizations that require going deeper than that are just blocked by the abstraction boundary

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:43):

like for example, I'd question the premise that terroccotta should be a package as opposed to a name for a design

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:43):

why not have that be the name of the design that's used inside roc-ray, and might also be used in other places, like The Elm Architecture is?

view this post on Zulip Richard Feldman (Sep 02 2026 at 12:45):

then none of the problems we've been discussing exist, plus I have to imagine it becomes possible to achieve better performance than what's possible if it's in a separate package :smiley:

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:47):

I think most of terrocotta's algorithm is useful in a package and could be used by any platform.

I feel like maybe the Program part which wraps the app's model and messages is where things get a little blurry.

view this post on Zulip Luke Boswell (Sep 02 2026 at 12:48):

Like maybe the drawing in terrocotta would return a list of draw commands the app should then be responsible for translating into the platform's API -- as opposed to taking the effects and using those.

view this post on Zulip Jonathan (Sep 02 2026 at 12:58):

I don't quite understand how this would play out for the ecosystem. Won't this lead to anaemic Roc packages? If they can't take advantage of a platform then they have to get merged into the platform itself. This requires agreement from the existing devs/users of the platform, and so a fork is more likely instead. The outcome is a broad collection of deep platforms, each may be reusing many of the same ideas and code.

On the other hand, even if it becomes bad practice, packages may still be written for a specific platform but in a less efficient way. Almost the exact opposite in what is intended by reducing layers: packages will interface with their target platforms abstractly and with more intermediary glue.

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:15):

@Jonathan what specific packages do you have in mind though?

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:23):

my point here is not "all packages should be merged into platforms" but rather "layout primitives are so heavily intertwined with a platform's responsibility that it makes more sense for them to be integrated into the platform rather than separated as a different package"

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:30):

and I think it's really important to be careful with how we evolve the ecosystem, especially in the early days - a lot of package ecosystems end up with really undesirable characteristics (e.g. normal npm packages having gigantic build graphs of hundreds of megabytes of microdependencies) because things were done for expedience early on, and they became cultural norms

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:32):

so when we find a point of friction I think it's really important to look at the specifics there and not jump to generalizing that specific friction point to the entire future ecosystem :smile:

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:33):

because it's predictable that if we talk through things on a case-by-case basis, each situation will turn out to have different tradeoffs

view this post on Zulip Jonathan (Sep 02 2026 at 13:34):

So far it has mostly been personal utilities, things that don't make sense to share because they are coupled to my specific projects and basic-cli. Nonetheless, I access the sqlite jobs/results db in different scripts for different purposes (loading in data, enqueuing jobs, subsequent analysis), and all these are having to be merged under one app so that I can reuse the helpers. That is fine but it's been a small pressure so far.

A bigger one would be some kind of query builder and SQlite api. I would like something somewhat like Ecto.Query from Elixir because I find it invaluable for running quick queries, and I imagine Roc would be able to make that more enjoyable with e.g. some applicative builder pattern. But to use it, it would either be coupled to the api of the platforms Sqlite module via extensive where clauses (I have been trying this and it basically meant shadowing the exact API of basic-cli.Sqlite, which fortunately uses aliases not nominal types), or I would have to provide the glue. I am trying out Roc over standard scripting languages because I love most of its ergonomics, but it seems like layering and abstractions are important here because the code is not for long-term maintenance and the upfront cost is not worth it.

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:36):

oh interesting! I think a query builder should be able to be totally platform-agnostic, and honestly probably relational database agnostic too :smiley:

view this post on Zulip Jonathan (Sep 02 2026 at 13:36):

I.e., opinionated abstractions over platforms save me time, because my aim is not to produce faster software, but correct software, fast.

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:39):

like for example, suppose you have this:

to_sql : Query -> (Str, List(List(U8)))

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:40):

in other words, it turns the query you've been building into a string query with query params question marks in the right places, and then a list of the bytes of the actual query param values

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:43):

or maybe alternatively you could offer to_sqlite and to_pg etc. because different databases use different binary representations for integers etc

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:45):

or you could have it return (Str, List([Bool(Bool), Str(Str), I8(I8), U8(U8), ...etc]) and then hand that off to a specific database to convert into its format

view this post on Zulip Jonathan (Sep 02 2026 at 13:45):

But because this is abstract, it is already not taking advantage of basic-cli.Sqlite's parameter bindings, where indeed another platform's db interface might not offer bindings and just take raw strings.

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:46):

I think query builder would be a good example where I don't think you'd want any effectful functions in the entire package, and it would be a natural fit for being platform-agnostic

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:47):

but ok let's explore the hypothetical of "packages can depend on platforms"

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:48):

so let's say you can make a package called like basic-cli-sqlite-query-builder

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:49):

how would its API be different from one that didn't depend on basic-cli?

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:49):

in other words, what specific types or functions from basic-cli would it make use of from that dependency?

view this post on Zulip Jonathan (Sep 02 2026 at 13:55):

I think one is that the decoders/encoders could be built automatically. If the query builder is aware of the output shapes and types and bindings, perhaps it could also construct the decoders to reduce the risk of me (happened already) trying to decode a Text column to an I64. This would require knowledge of the shape of function that is expected of a decoder, and I'm not sure how universal that is.

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:56):

gotcha - but that's about SQLite, right? not necessarily basic-cli

view this post on Zulip Richard Feldman (Sep 02 2026 at 13:56):

as in, the encoding and decoding is going to/from the bytes that SQLite expects, and any time you'd be doing this with SQLite you'd have the same encoders and decoders even if it wasn't basic-cli specifically

view this post on Zulip Jonathan (Sep 02 2026 at 13:58):

Perhaps, but doesn't it also depend on the type signature of the decoder that basic-cli expects you to provide?

view this post on Zulip Jonathan (Sep 02 2026 at 14:00):

I mean, if it is, it does seem like it would be somewhat trivial in this specific case to write glue. Glue to connect the generic queries to the specific arguments that query_many! expects. Glue to connect the decoders, and also to wrap the variables in the tags basic-cli expects.

view this post on Zulip Jonathan (Sep 02 2026 at 14:01):

Even still, that is the same glue that I am writing for every script that connects the builder to basic-cli. Perhaps I am just too allergic to the idea of vendoring and copy-pasting code.

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:05):

oh I don't think any glue would be needed in this situation

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:06):

so in basic-cli, Sqlite.query_many! accepts a query Str and then a list of bindings, where the each binding is { name : Str, value: SqliteValue }

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:06):

and then SqliteValue is:

    SqliteValue : [
        Null,
        Real(F64),
        Integer(I64),
        String(Str),
        Bytes(List(U8)),
    ]

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:07):

so that's plain old structural tag union, meaning any package can produce that without needing to depend on basic-cli or anything else

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:07):

so earlier I had this example:

to_sql : Query -> (Str, List(List(U8)))

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:07):

and then I noted another way the query builder could go is:

Richard Feldman said:

or you could have it return (Str, List([Bool(Bool), Str(Str), I8(I8), U8(U8), ...etc]) and then hand that off to a specific database to convert into its format

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:09):

so let's say the query builder package specifically offers this function:

to_sql : QueryBuilder -> {
    query : Str,
    bindings: List([
        Null,
        Real(F64),
        Integer(I64),
        String(Str),
        Bytes(List(U8)),
    ]),
}

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:10):

now in your application you can do this:

{ query, bindings } = my_query_builder.to_sql()

Sqlite.query_many!({ path, query, bindings, rows })

(I'm assuming in this example you already have path and rows defined, since those are unrelated to query-building)

this can be implemented in a zero-dependency query builder package that's just pure functions and data structures, no effects and no platform or package dependencies, and no glue code or vendoring needed either! :smiley:

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:10):

does that make sense?

view this post on Zulip Jonathan (Sep 02 2026 at 14:34):

It does yeh, although it does still reflect the shape of sqlite as it is. I can imagine a different platform providing its own structured query type, or different nominal or structural value tag names (I realise this is incidentally lining up with basic-cli, vs explicitly depending on its types). But as for the decoder (rows), that was one of things I wanted to investigate being built by the query builder.

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:37):

Jonathan said:

It does yeh, although it does still reflect the shape of sqlite as it is. I can imagine a different platform providing its own structured query type, or different nominal or structural value tag names (I realise this is incidentally lining up with basic-cli, vs explicitly depending on its types).

yeah, so I think that distinction is really important!

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:38):

for example, let's say another platform uses a different representation for the bindings, like instead of Real(F64) and Integer(I64), they use F64(F64) and Int(I64)

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:38):

this is where anyone can write a tiny function that translates between the two

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:39):

or, as a convenience, the query builder package can offer multiple versions - e.g. to_sqlite() can return the SQLite format, and to_pg() can return the postgres format, etc.

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:43):

but this "be shape-compatible rather than depending on the platform's nominal types" design has some nice characteristics:

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:51):

Jonathan said:

But as for the decoder (rows), that was one of things I wanted to investigate being built by the query builder.

I haven't looked into this specifically, but in general parsing (aka decoding) packages should tend to work well as platform-agnostic packages, because they're providing pure functions that take arbitrary bytes (or strings) as inputs and then parse them, so they need to be aware of the format of those bytes but not any specific nominal types (from any platform or other package)

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:54):

that said, looking briefly at Sqlite in basic-cli, it seems like it already provides all the functionality to make an auto-derived parser, so I think a nicer answer there might be to have it work more like how Json works

view this post on Zulip Richard Feldman (Sep 02 2026 at 14:55):

so that you wouldn't have to provide a parser/decoder at all, but rather just let the compiler infer one from how you're using the outputs of query_many! - which would mean it would be more ergonomic and there'd be no need for a package at all to do that :smile:

@Luke Boswell have you ever looked into that?

view this post on Zulip Jonathan (Sep 02 2026 at 15:03):

Yeh I was just looking into this, except the decoders are intertwined with the host effect of looking up the value, rather than being a decoder on some bytes directly.

view this post on Zulip Richard Feldman (Sep 02 2026 at 15:22):

gotcha, yeah I think the best way to improve that experience is to make it so that nobody needs to write those decoders by hand anymore :smiley:

view this post on Zulip Richard Feldman (Sep 02 2026 at 15:23):

but will see what Luke says when he's up - it's the middle of the night in Australia :flag_australia:

view this post on Zulip Jonathan (Sep 02 2026 at 15:31):

Say for example that Luke disagreed or at least was not yet sure, is this not a case for being able to depend on a platform's functions? So that people can experiment or abstract over what a platform provides? :laughing:

view this post on Zulip Jonathan (Sep 02 2026 at 15:32):

That is a hypothetical again, though :face_in_clouds:

view this post on Zulip Richard Feldman (Sep 02 2026 at 15:34):

this is mostly a case of "I can't think of a reason this wouldn't be better across the board, but maybe Luke already tried it and there's some blocker I'm unaware of"

view this post on Zulip Richard Feldman (Sep 02 2026 at 15:37):

Jonathan said:

So that people can experiment or abstract over what a platform provides

to be clear, you can already abstract over what a platform provides - we've discussed a few ways of doing it above - I think the main thing is that the ecosystem is in its infancy right now, so we don't have established precedents for "here is the right way to organize things around scenarios like this"

view this post on Zulip Jonathan (Sep 02 2026 at 15:40):

Yes that wasn't very precise - I meant particularly while using hosted functions and nominal types (and was feeling mischievous :laughing:)

view this post on Zulip Richard Feldman (Sep 02 2026 at 15:40):

for example, from discussing these 3 scenarios, my conclusion is:

view this post on Zulip Richard Feldman (Sep 02 2026 at 15:47):

I also think that all 3 of those make things nicer for app authors:

view this post on Zulip Richard Feldman (Sep 02 2026 at 16:01):

so I think the best next step is to try out the above strategies and see how they feel in practice - if we don't like how they feel, we can always discuss having learned more! :smiley:

view this post on Zulip Matthieu Pizenberg (Sep 02 2026 at 16:15):

I haven’t had the time to read the whole discussion, mostly the first half so sorry if I argue against something already settled. IMO though it is idealistic/naive to think that a platform will be able to cover a full domain nicely. If the domain is small, sure. But for big projects, certainly not. Think game engines, even web platforms.

A platform authors for a game engine certainly cannot be an expert in arts + 3d modeling + rendering + specific console hardware + physics mechanics + artificial intelligence (and other machine learning extensions) etc.

A web platform author cannot support all of the web apis accross audio, bluetooth, crypto, video, streaming, gpu, etc, for the same reason. Elm is a perfect example. Evan abandoned the idea of increasing the web api support in Elm, so everything that you need to go through ports for, is in practice a terrible user experience. Not every web api fits well the "client/server" architecture of a port.

So IMO having flexibility in the platforms to be extended will just be a natural demand for all the things that require platform access whether for side effects, performance, security, etc.

view this post on Zulip Richard Feldman (Sep 02 2026 at 16:31):

oh I think game engines are a great example of what I have in mind as the scope!

view this post on Zulip Richard Feldman (Sep 02 2026 at 16:32):

like I'd say a game engine has a pretty clear story for "here is how you build a game in Unreal Engine" or "here is how you build a game in Unity"

view this post on Zulip Richard Feldman (Sep 02 2026 at 16:32):

and part of that story is where to draw the line on what primitives they provide, sure

view this post on Zulip Richard Feldman (Sep 02 2026 at 16:34):

but if you draw the line at "Unreal Engine provides C++ bindings to cross-platform rendering and audio primitives and that's it" then I wouldn't say it's really offering a story for how to build games - it's just taking some low-level C libraries and providing a C++ API on top of them

view this post on Zulip Richard Feldman (Sep 02 2026 at 16:35):

so maybe you don't draw the line at physics (for example) but you do provide an entity-component system as part of the batteries you're including

view this post on Zulip Richard Feldman (Sep 02 2026 at 16:53):

Matthieu Pizenberg said:

A web platform author cannot support all of the web apis accross audio, bluetooth, crypto, video, streaming, gpu, etc, for the same reason. Elm is a perfect example. Evan abandoned the idea of increasing the web api support in Elm, so everything that you need to go through ports for, is in practice a terrible user experience. Not every web api fits well the "client/server" architecture of a port.

I remember talking with Evan in person about this back when he was still working at Prezi, and the conclusion at the time was that it made the most sense to have first-class Elm APIs for all of them, while acknowledging that it would be a long road to get full coverage. So I think the reason Elm doesn't have full API coverage over the Web isn't that it's fundamentally unachievable, or a bad idea, but rather prioritization of other projects ahead of that one.

the scope of Roc platforms is much, much smaller than the scope of Elm (which also includes a compiler, package manager, now Acadia too, etc.), so I don't think we should assume a Roc platform targeting the web would hit the same problem.

I assume it's not controversial to say that the best app author experience is one where all the APIs they want to use on the Web are available as first-class Roc APIs, as opposed to "these things are forever your responsibility to bind to using JavaScript" or similar. You could look at that and say "yes, which is why people in the community should be able to go off and build third-party Roc bindings to the Web APIs and publish them as separate packages," but I think it's better for app authors if they do almost exactly that...except replace that very last step of "and publish them as separate packages" with "and add them to the platform." :smile:

view this post on Zulip Richard Feldman (Sep 02 2026 at 16:57):

as an aside, a tricker web-specific problem I don't have a good solution for is the fact that (despite my attending a bunch of TC39 meetings on the Temporal proposal for the sole purpose of repeatedly explaining Elm's use case and why they should offer a way to access time zone data as plain data and/or pure functions, as opposed to a mutation-y JS API, which is what they ended up doing anyway), some Web APIs like Temporal and Intl have a bunch of data (time zones, internationalization info) that's already in the browser and which you don't want to re-bundle in your application binary...but it's not easily accessible in the format you'd want (constants and/or pure functions), which means accessing them in Roc unavoidably requires effectful functions

view this post on Zulip Richard Feldman (Sep 02 2026 at 16:59):

that might be only a mild annoyance in practice, given that you'd probably be wanting to do them in the same place as where you'd be doing other effects like I/O, but it bugs me that the browser ships with a large quantity of immutable data and pure functions but doesn't offer APIs to allow access to them as plain old readonly data and/or pure functions :disappointed:

view this post on Zulip Richard Feldman (Sep 02 2026 at 17:06):

a related annoyance is that browsers also have all the Unicode info you could ever want, but all their APIs on them only work on UTF-16 encoded strings :upside_down:

view this post on Zulip Karl (Sep 02 2026 at 18:39):

My concern with Roc is that the current platform design decisions push it towards being a deeply
parochial language. That is, every shop that uses the language uses its own version of the language
and while you techncially can share code in practice the friction is high enough that people tend
not to. The main example of this is C++ compared to Rust. Other languages with deep ecosystem splts,
and I'm mainly thining OCaml here, have enough confusion around what to pick that it's a barrier to
entry. I think in part it's the domain since I don't think Lua has a particularly large ecosystem
even though it's fairly popular. I've written some OpenResty code and know LuaRocks is around
but my general impression is that Clojure (to pick a similarly popular dynamic language) has a
significantly larger ecosystem on top of riding on its host platform for long tail coverage. Roc
platforms, as they exist today, are a push towards this future.

The core problem is that platforms are a total world and you have to take it or leave it.
There's no way to add to a platform: if I'm writing an app server and want sqlite, it either has to
be built into the platform or I have to fork the platform. I can't piece together functionality from
multiple platforms. There's no way to replace just a piece of a platform. If I decide I want Turso
instead of sqlite because I want to do repilication, I have to fork the platform. I'm not completely
sure about this, but I can't shim a platform effect in Roc code. The story for leaving things out
of the platform is dead code elimination. This is likely a psychological issue more than a practical
one but years of Java logging system exploits have left me wary of surprise networking in libraries
so it really bugs me that fetch is in my UI platform and available to a calculator app even though
I'm pretty sure there's no lurking exploit in the calculator. All in all, either the platform author
fully anticipates all your needs or you fork the platform.

There's a lot of platform forking motivations in the previous paragraph but I strongly suspect most
platforms won't have a carefully thought out domain. In my case I've done a half dozen and my
general decision for placing it in Rust or in Roc is which is easier or which will let me avoid
a copy. I expect this sort of motivation to be the norm which makes forking subject to the usual
subtle incompatibilities of an implementation-defined contract. As a specific example, my seahaven
platform mostly mirrors basic-cli because I wrote the app against basic-cli and wanted to add
sandboxing. Since I want seahaven to be cross-platform I switched paths from OsStr to Str which
trades the ability to fully represent every possible path on a platform for a stable cross-platform
representation. I don't think it'll make much of a difference in practice and it does show up in the
type signature but it's representative. It becomes difficult to come up with a better version of
basic-cli or basic-webserver because the person potentially swapping platforms doesn't really
have support for what could be off. It amounts to "good luck, hope your test suite is good enough."

The good thing about all this is that the WASM people are in more or less the same situation and
they've been working getting their component model standardized for years. I'm not arguing that Roc
needs to match their decisions but I do want to try to TL;DR the general ideas and I'll use their
terminology since there's a decent amount of thought behind them.

The WASM component model starts with WIT
which is their interface definition language and, in brief, it's simplified Rust types and a richer
cross-language ABI than C. Individual definitions get bundled into groups called interfaces
and the interfaces get bundled into worlds.
A wasm component model world more or less corresponds to a Roc platform. The main difference is that
worlds can both import interfaces and export interfaces. This gives clear boundaries for what a
program actually requires. I know there's been effort on getting HTTP types working in Roc so I'll
point to the wasi:http world
which has clean support for just making requests wasi:http/client, serving requests wasi:http/handler,
and a full service with clocks/random/stdout/stderr in wasi:http/service. There are similar specs
for other relevant domains.

This is significantly more complicated than just importing a package but I think a Roc version of
the same concept wouldn't have to present itself as more complex to new users. They'd just grab
http/service and write their server. I see this mostly as a way to have a coherent library system
across platforms and to allow me to produce a UI platform without a constant struggle around capabilities.
I have 10 apps in my project repo exercising the APIs with three platform forks servicing them.

view this post on Zulip Jasper Woudenberg (Sep 02 2026 at 19:06):

I think the platform design might actually help with fragmentation a bit. Two of the most fragmented language ecosystems I have personal experience with, JS and Haskell, both have big package ecosystems. This gives everyone a large menu to put together their own stack: choose a database, a REST library, a logging library, a templating language, etc. In the case of Haskell your choice of language extensions to enable also plays into this.

Ruby, which is more framework oriented, arguably sees less fragmentation. You have one dominant framework (Rails) and maybe a couple smaller ones. Roc might end up similar, with one or two dominant platforms in each domain. It'd be great for app authors too, if the effort to add support for some useful functionality (say: replication) ends up being contributed to the platform most people are using, instead of being in a separate package that app authors need to bolt on themselves.

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

yeah, I thought about this back when the language was in the stage of "design but no code exists" and my conclusion was that "this is just how ecosystems work anyway" - e.g.

those aren't programming languages, they're engines/frameworks/platforms/etc. built on top of languages, and they emerge organically from languages that don't have a formal concept of these things

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

the idea is that Roc is taking something that has emerged in an ad-hoc way, and unlocking benefits (primarily performance and security) from making it first-class

view this post on Zulip Karl (Sep 02 2026 at 19:43):

The difference is that in all of those examples is that they're not a closed system. I don't have to fork Rails if I want my app to do image processing.

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

:thinking: why would you need to do that in Roc?

view this post on Zulip Karl (Sep 02 2026 at 19:46):

I have a native UI platform and my set of apps include audio playback, sqlite, pen EMR interaction, and web access. What set of effects do I ship in my platform?

view this post on Zulip Karl (Sep 02 2026 at 19:47):

Do I really need to re-implement libpng in Roc?

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

what's pen EMR?

view this post on Zulip Karl (Sep 02 2026 at 19:48):

It's the technology behind wacom tablets. The pen magnetically resonates with the surface allowing for hover/tilt/rotation in addition to pressure.

view this post on Zulip Karl (Sep 02 2026 at 19:53):

When working on a platform I have like the main idea of what I want to accomplish but there are lots of other things that are incidental but because platforms are total I have to care about. UI apps are going to need file access. I don't really have any value to add there. Do I copy basic-cli code?

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

Karl said:

Do I really need to re-implement libpng in Roc?

I don't think platform authors should, but I actually did some proof-of-concept implementations of image parsing in pure Roc awhile back, and I've already ported libdeflate to pure Roc (I'm currently working on closing the perf gap; decompression is between 6% and 12% slower on libdeflate's own test suite than libdeflate itself, whereas compression is still much further off - we're at like half the throughput).

basically anything that only requires pure functions on bytes can be implemented in pure Roc, and there are security and ergonomics benefits to doing it that way, so I think that's long-term what we can optimize for. (in the short term if a platform author wants to bundle libpng, or if an app author wants to fork the platform to add that, obviously that's fine but also less ergonomic than someone making a pure-Roc implementation).

that might sound like an absurd proposition until you realize that "don't just wrap libwhatever, actually reimplement" has been successfully accomplished at scale in the JS ecosystem because browsers (like Roc) don't let you do arbitrary C FFI for obvious security reasons. As an example, you mentioned libpng and that one has been done in png-js in pure JS. A pure Roc one would run much faster. :smile:

view this post on Zulip Romain Lepert (Sep 02 2026 at 19:55):

Luke Boswell said:

Like maybe the drawing in terrocotta would return a list of draw commands the app should then be responsible for translating into the platform's API -- as opposed to taking the effects and using those.

that does not remove the View(Msg, texture, font, shader, frame) generic bloat because the layout still have to carry the "unknown types"

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:00):

Karl said:

UI apps are going to need file access. I don't really have any value to add there. Do I copy basic-cli code?

I think that's the right approach today. I'm open to the possibility that someday there is a better way, but I think it's important that at this stage of the language we empirically test out the approach of "platforms do not depend on other platforms" to see how it works in practice.

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:01):

Romain Lepert said:

Luke Boswell said:

Like maybe the drawing in terrocotta would return a list of draw commands the app should then be responsible for translating into the platform's API -- as opposed to taking the effects and using those.

that does not remove the View(Msg, texture, font, shader, frame) generic bloat because the layout still have to carry the "unknown types"

oh I'm proposing that when you look at the docs for roc-ray, you basically see the union of all the modules that are currently in roc-ray and currently in terrocotta - like, they would just be fused together, so the generic thing would go away

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:02):

so that way if someone is forking the platform, they get it because it's a part of the fork itself

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:02):

oops, I just realized you were responding to Luke's specific comment from earlier, sorry!

view this post on Zulip Romain Lepert (Sep 02 2026 at 20:08):

Luke Boswell said:

Ok, here's another angle ... what if I wanted to make a Calendar widget in a package for roc-ray?

This is not possible in roc.

Here is the dependency hierarchy (arrows cannot go up).

image.png

Let's suppose terrocotta is in the roc-ray platform as Richard says. Aka terrocotta is part of pf-package.

Then package A (e.g. widgets package) cannot import the most basic building blocks of terrocotta (e.g. import rr.Element exposing [box, image]).

That means a widget package is impossible.

Now :

it simply does not exist.

view this post on Zulip Romain Lepert (Sep 02 2026 at 20:15):

all that is possible is copying example code from others like the C community.

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:20):

@Romain Lepert as discussed earlier, you can do it with type variables, where clauses, and structural types...but what would be your preferred way of doing it? allow packages to depend on platforms directly?

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:21):

(implying that the calendar widget would work with exactly roc-ray but not on any forks or API-compatible alternatives to roc-ray)

view this post on Zulip Romain Lepert (Sep 02 2026 at 20:21):

yes, allow packages to import pf-package. That unlocks the rest

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:21):

oh interesting

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:22):

so not declare a dependency on a specific platform, but rather let them import "whatever the app's platform is"

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:22):

and then as long as everything type-checks, you're good? :thinking:

view this post on Zulip Jasper Woudenberg (Sep 02 2026 at 20:30):

Or maybe something like a "plugin" bundle-type, separate from app/platform/package, that is a "library" written for one specific platform with access to that platform's API? That way you could support widgets, while maintaining the concept of packages for cross-platform use-cases.

view this post on Zulip Romain Lepert (Sep 02 2026 at 20:30):

I was thinking the specific platform's pf-package but what you are saying is more flexible, albeit a bit unclear.

with this hypothetical

import platform.Element exposing [box, text, View]

Msg : [Increment]

button : Str, Msg -> View(Msg)
button = |label, msg| {
    box({ events: [OnClick(msg)] }, [
        text(label),
    ])
}

what does it mean to that box, text and View type check ?

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:31):

yeah there would be a few problems with that design, I guess - e.g. you could only type-check it if you had an actual app present

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:33):

actually, I wonder if this overlaps with a separate design I've had in mind for a long time

view this post on Zulip Romain Lepert (Sep 02 2026 at 20:33):

Jasper Woudenberg said:

Or maybe something like a "plugin" bundle-type, separate from app/platform/package, that is a "library" written for one specific platform with access to that platform's API? That way you could support widgets, while maintaining the concept of packages for cross-platform use-cases.

that is basically the pf-package in my diagram, isn't it ?

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:33):

which was letting app authors override dependency URLs, for purposes of doing things like patching an indirect dependency to see if a bugfix works

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:34):

like if I depend on 2 packages which both depend on "https://foo.com/whatever/..." and I want to override that foo.com/whatever dependency with a local version, and have my 2 packages that depend on it get my overridden version, it would be good to have a way to do that

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:34):

and of course as soon as you do that, as the app author, you're taking responsibility for the override working properly with those 2 packages you didn't write

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:34):

and I think the "make it work with a fork of the platform" could be the same situation :thinking:

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:35):

e.g. the calendar widget formally depends on a specific version of a specific platform, but the app author can override that and say "actually anyone who depends on that platform, use my platform instead"

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:35):

so then the app author is taking responsibility for any compatibility issues etc.

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:37):

so, tradeoffs compared to a platform-agnostic calendar package:

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:39):

a thing worth noting here is that a part of how we ended up with the current "make all packages platform-agnostic" approach was starting from a position of asking questions about how to make packages that could work across multiple platforms even if they had different I/O primtiives, the canonical example being "browsers do network requests totally differently from how operating systems do, so how can I make an error-reporting package that works with Roc apps running in a browser and also Roc apps running on native desktop apps and also mobile apps etc.?"

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:39):

and the conclusion was that the best option is to make them parameterizable

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:40):

but it's notable that for GUIs, that sort of sharing isn't really a thing

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:40):

e.g. nobody expects to write a React calendar picker that also works on iOS

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:41):

so that coupling to the platform feels like it might be more innate when it comes to GUI widgets

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:41):

which would be better justification for a language feature than the other things we've talked about imo

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:47):

I'm curious what anyone thinks of this idea!

view this post on Zulip Romain Lepert (Sep 02 2026 at 20:54):

Richard Feldman said:

Romain Lepert as discussed earlier, you can do it with type variables, where clauses, and structural types...but what would be your preferred way of doing it? allow packages to depend on platforms directly?

In the example, terrocotta is part of the platform, in which case no package can import box which means they can't make a widget. where clauses and structural types can't help here.

A widget package is probably NOT the best motivating example. I would concede that most non-toy apps should own their widgets (shadcn showed the validity and desire for it). The only tricky ones that people don't want to own are the complicated ones like text inputs and calendar. However the main internals of these complicated widgets could be a package that every app reuses (e.g. unicode aware cursor navigation, d/m/year navigation)

But the point stands that you can't have a ecosystem around a platform if you can't import its basic building blocks.

view this post on Zulip Karl (Sep 02 2026 at 20:55):

Not clear to me what the idea is. Widgets have a couple axes of compatibility. The current trend for visual coherence is design systems consisting of design tokens. For actual painting it's either raw draw commands or some sort of property system; I use the tailwind system because frontend devs are aware of it. For behavior the ARIA grouping is the best platform agnostic system I know about. The final piece is how state is handled, both the app domain and the internal widget state.

view this post on Zulip Richard Feldman (Sep 02 2026 at 20:59):

the idea is to let packages depend on a platform

view this post on Zulip Richard Feldman (Sep 02 2026 at 21:00):

e.g. today if you make a package and try to use the platform keyword in your dependencies, you get an error saying that's not supported

view this post on Zulip Richard Feldman (Sep 02 2026 at 21:00):

the idea would be to say that it is now supported, and the enforcement would move to being that your app can only depend on packages which use that keyword if they are using the same platform as what you (the app) are using

view this post on Zulip Richard Feldman (Sep 02 2026 at 21:01):

so that's the idea of the language change, and then that would mean you can now publish platform-specific packages, whereas today it's only supported to publish platform-agnostic ones (which you could of course still do just like today)

view this post on Zulip Richard Feldman (Sep 02 2026 at 21:03):

the separate (but related) idea is to make it possible for app authors to say "if any of my dependencies are using this URL, instead use this other drop-in replacement dependency, and I will take responsibility for any incompatibilities" - which would mean that if someone publishes, say, a calendar widget for the roc-ray platform, someone else who is using a fork of roc-ray (or drop-in replacement), they can override that calendar widget's roc-ray dependency to use their API-compatible platform instead, and it can Just Work as long as there is no actual API incompatibility in practice

view this post on Zulip Karl (Sep 02 2026 at 21:05):

I have a Regex implementation cooking and one of the problems there is SIMD scanning for letters. I had planned on just not doing it and waiting until the discussion of the primitives came up on zulip but being able to depend on a scanner from the platform would let me do it now.

Perhaps related but over the weekend I was doing perf tuning for my web platform. Doing everything with encoders/decoders lets me get down to only needing copies into the SQLite results buffer and into the transmission buffer but the SQLite buffer is (obviously) host side and getting it into Roc requires a copy. I settled on making a non-effectful host function which works but gives a warnint.

view this post on Zulip Richard Feldman (Sep 02 2026 at 21:06):

we have SIMD builtins right now - are they not sufficient?

view this post on Zulip Richard Feldman (Sep 02 2026 at 21:06):

e.g. https://www.roc-lang.org/docs/main/Num/#U8x16

view this post on Zulip Romain Lepert (Sep 02 2026 at 21:08):

so let me try to see if i understood

1) calendar package depends on rocray: platform "https://roc-ray.com/v1.0"
calendar can import rocray.Element exposing [box]
2) Bob forks rocray into bobray
4) Jack wants calendar but with bobray platform
bobray: platform https://bob-ray.com/v1.0"
calendar: https://calendar/v1.0"
Jack patches indirect dependencies "https://roc-ray.com/v1.0" with "https://bob-ray.com/v1.0"

Is that about right ?


view this post on Zulip Richard Feldman (Sep 02 2026 at 21:09):

exactly, assuming terrocotta is a separate package from roc-ray (which for the record I still don't think is the best experience for app authors, but it's up to you :smile:)

view this post on Zulip Luke Boswell (Sep 02 2026 at 21:11):

Good morning everyone... looks like there's a bit to catch up on here :grinning_face_with_smiling_eyes:

Richard Feldman said:

the idea would be to say that it is now supported, and the enforcement would move to being that your app can only depend on packages which use that keyword if they are using the same platform as what you (the app) are using

Well I guess I know what Im building today now... I can spike this out and test it out on terrocotta.

I feel this is a natural fit with the package ecosystem. I was thinking about the discussion overnight and there seemed a hole here that this will fill nicely.

view this post on Zulip Notification Bot (Sep 02 2026 at 21:13):

A message was moved from this topic to #performance > SQLite owned buffer by Richard Feldman.

view this post on Zulip Romain Lepert (Sep 02 2026 at 21:14):

Richard Feldman said:

exactly, assuming terrocotta is a separate package from roc-ray (which for the record I still don't think is the best experience for app authors, but it's up to you :smile:)

right, i modified the example with calendar so we focus on the substance :sweat_smile:

view this post on Zulip Luke Boswell (Sep 02 2026 at 21:15):

Theres an old google doc floating around with this design I think. (the patching deps thing)

view this post on Zulip Jonathan (Sep 02 2026 at 22:33):

Does it make sense to make this strictly a platform dependency concern? I was thinking about the problem with the upwards polluting generics (View(msg, texture,...)), and if the generics escalate to the top level, essentially your whole package depends on some set of types. Why not instead say "this whole library is parameterised by these modules (that must provide these functions)". Then, when a consumer requires the generic library in their package header, they specify the module to be mapped to. I feel like this suits Roc well because

view this post on Zulip Jonathan (Sep 02 2026 at 22:39):

E.g.

app [main!] {
    pf: platform "...roc-ray...",
    calendar: "http....",
    with [calendar(pf.Element, MsgWrapperWithLogging, ...)]
}

view this post on Zulip Jonathan (Sep 02 2026 at 22:43):

Just for what it's worth :sweat_smile: I had been mulling over it for a bit. Don't know how it would work with non-opaque nominal types though or any use case that requires the flexibility of a concrete type. Essentially it is syntax sugar that hides wide spreading generics.

view this post on Zulip Luke Boswell (Sep 02 2026 at 23:12):

We previously had a design "module params" and then removed that in favour of record bundle of effects

view this post on Zulip Luke Boswell (Sep 02 2026 at 23:17):

@Romain Lepert -- I've started working on a branch to test out the "platform packages" concept. I'll patch terrocotta to use that instead of roc-ray/types and we can use that to help explore the design space

view this post on Zulip Luke Boswell (Sep 03 2026 at 01:30):

Here's the PR porting terracotta across to use the platform instead of the package... :sweat_smile:

https://github.com/obust/terrocotta/pull/60

image.png

view this post on Zulip Luke Boswell (Sep 03 2026 at 01:30):

I don't quite know what I expected to be honest... just more I guess :smile:

view this post on Zulip Luke Boswell (Sep 03 2026 at 01:39):

Here's the spike for Roc https://github.com/roc-lang/roc/pull/11079

view this post on Zulip Luke Boswell (Sep 03 2026 at 01:40):

I'm not really sure where to go from here ... I guess I could look at pulling the types package back into the platform and make another RC?

@Romain Lepert I'll wait for you to have a look at all this.

view this post on Zulip Matthieu Pizenberg (Sep 03 2026 at 07:24):

I think the reason Elm doesn't have full API coverage over the Web isn't that it's fundamentally unachievable, or a bad idea, but rather prioritization of other projects ahead of that one.

@Richard Feldman this is my point actually. Nothing is literally impossible. Things are just too complicated, costly, time consuming to accept the weight of doing and maintaining them (or the opportunity cost here more specifically). And from the point of view of a contributor, if they want to add a new capability to a platform, the difference between "I can just do my thing and we good to go" and "how do I patch the whole platform to integrate my thing?" is the difference between an existing contributor, and someone who decided it’s not worth their time and move on to something else.

That being said, I’m not opposed to still try it and see how it goes. It’s just that from my point of view, I feel there are intrinsic reasons why easy extensibility is needed.

view this post on Zulip Matthieu Pizenberg (Sep 03 2026 at 07:26):

@Karl thanks for mentioning wasm WIT! I think it’s a very good comparison. Pure wasm, with side effects provided by the "platform" in an extensible way.

view this post on Zulip Niclas Ahden (Sep 03 2026 at 08:12):

@Karl In the chat after the last meetup we discussed your thoughts on wasm WIT. Would you want to write out a brief/suggestion around that in a separate thread? Based on our discussion it did sound valuable to bring up with everyone.

view this post on Zulip Romain Lepert (Sep 03 2026 at 08:31):

WIT is certainly a good comparison point. Putting aside the

A platform quite litterally builds an interface. Aka just types and function definitions, no implementation.

e.g. roc-ray Host interface which is then implemented through FFI binding.

Host := [].{
    Handle :: Box(U64).{
        stub = Handle.(Box.box(U64.highest))
    }  # only platform can manufacture Handles

    Texture := { handle: Handle, width: F32 height: F32 }.{
        stub : Texture
        stub = { handle: Handle.stub, width: 0, height: 0 }  # for tests
    }

    draw_texture! : { texture : Texture, position: { x: F32, y: F32 } } => {}

    ...
}

So a package could define the interface it requires and as long a the app picks a platform that satisfied this interface, then the package is compatible and it can use the interface.

That would be essentially what @Richard Feldman mentioned here:

so not declare a dependency on a specific platform, but rather let them import "whatever the app's platform is, and then as long as everything type-checks, you're good?"

However there is 2 issues with that:
1) Current Roc cannot match package declared Texture.Handle with platform declared Texture.Handle because of the nominal in nominal. I don't know if that restriction can be alleviated or not.
2) It does not play well with roc-ray intended effect usage. roc-ray apps have a lifecycle init!, update!, render! and draw_texture! calls are valid only in the render! phase, otherwise it will crash the app. roc-ray can design its API so that it is hard/impossible to call draw_texture! outside of render! but if it lets a third party package call draw_texture! whenever it wants, then it can crash the app. It would be either a bad package design from the package author or a bad package usage from the app author, but still it is harder for the platform to own the UX.

Given point 2) I would not go that route just yet. Let's see what platforms will come up with first. But curious to know if people think there is a way without compromise.

view this post on Zulip Romain Lepert (Sep 03 2026 at 08:38):

Luke Boswell said:

I don't quite know what I expected to be honest... just more I guess :smile:

ahah well that is because roc-ray/types was filling this gap.

what roc-ray/types could not expose was Draw.Frame with the drawing effects. That means that now a package can import Draw.Frame and write a render function using the frame effects. No where clause required. This make is straightforward to build a Chart package for roc-ray for example.

view this post on Zulip Jonathan (Sep 03 2026 at 11:24):

Luke Boswell said:

We previously had a design "module params" and then removed that in favour of record bundle of effects

I think what I’m suggesting is a generalisation of the idea in this thread: depending on a platform is effectively parameterising your library by the modules of a platform - why not make that dependency explicit, precise, and remove the limitation to platform modules? Doing so buys you the ability to easily test these coupled libraries, as well as provide adapters for when they don't exactly line up. This is similar to Haskell’s Backpack, and is distinct from (and does not tackle the problems solved by) module params/bundle of effects.

I did a bit of Zulip archaeology for the context of the module params proposal and subsequent abandonment. As far as I understand it: module params were a way of feeding in values to modules, e.g., keys, or effectful functions. At the time (pre static dispatch), they were especially useful because there were no custom types or ergonomic ways to destructure and call a function contained in an open record. Then, with Richard's realword exploration, the bundle of effects pattern was found to solve these same problems, and even provide more flexibility, e.g., log configuration by passing in a closure, or request safety by passing in a pre specified domain. Module params were removed thereafter as part of the rewrite, because it seemed like static dispatch could replace them, as well as abilities and default record fields.

But what is now being explored, is that for a sufficiently platform-specific library, neither the value-level (ergonomic bundle of effects) nor the type-level (static dispatch and generics) options are satisfactory. I.e. good tools to have but not sufficient, because it requires either passing these values down the stack, or where-constrained generics all the way up the stack.

For this kind of library that is tightly coupled to a platform, where it is burdensome to pass around a complex set of effects, the main idea in this thread is to be able to import from the platform. Essentially, parameterise your library by the set of modules that a platform exposes.

I’m arguing that this idea can be generalised: libraries could be parameterised by modules, rather than just platforms. So, no way to do precise (dynamic) configuration, but answers the problem of "what if my library assumes this other set of modules that I want to import”, without limiting it to the subset of "what if my library assumes this platform”, or having to pass around the types/values.

The use cases for this generalisation are

As for complexity budget, it is simpler than module params or functors, because you parameterise once at the library import level. Compared to importing platform types, it makes the dependency on the module more explicit.

view this post on Zulip Romain Lepert (Sep 03 2026 at 11:31):

Hypothetically it could work if the limitation mentioned here was somehow lifted #ideas > Platform extensibility using bundle of effects pattern @ 💬

1) Current Roc cannot match package declared Texture.Handle with platform declared Texture.Handle because of the nominal in nominal. I don't know if that restriction can be alleviated or not.

view this post on Zulip Richard Feldman (Sep 03 2026 at 11:48):

I think those are the same thing - the modules you're talking about are basically types :smile:

view this post on Zulip Richard Feldman (Sep 03 2026 at 11:50):

in other words, saying "I depend on a module named Draw which exposes these associated types and functions" is the same thing in Roc as saying "I depend on a type named Draw which has these associated types and functions"

view this post on Zulip Jonathan (Sep 03 2026 at 11:51):

I think they're close; I suppose you could pass in a handle to the module that provides access to the module via methods on the handle. I do mean this at the library level though, rather than parameterising the modules individually, so that the library authors can import and use as normal.

view this post on Zulip Richard Feldman (Sep 03 2026 at 11:53):

so something like "to depend on this package, you need to specify an existing Draw, which has these methods, and Texture, which has these other methods, and..."

view this post on Zulip Richard Feldman (Sep 03 2026 at 11:53):

and then those can be nominal types

view this post on Zulip Jonathan (Sep 03 2026 at 11:54):

It also reduces the onus on the app developer to wire together the precise set of effects, instead just specifying the module. This configuration for tightly coupled libraries was one of the complaints of module params I saw.

view this post on Zulip Jonathan (Sep 03 2026 at 11:54):

Richard Feldman said:

and then those can be nominal types

And importantly you can reference them normally in the signatures of your library functions

view this post on Zulip Jonathan (Sep 03 2026 at 11:55):

Jonathan said:

It also reduces the onus on the app developer to wire together the precise set of effects [...]

but you can still do so with bundle-of-effects, which should still be the goal for most packages that are agnostic.


Last updated: Sep 03 2026 at 15:16 UTC