Suppose I am developing a plotting library that depends on roc-ray rr.Draw for rendering.
now the user code will also depend on roc-roc for things like rr.Color.
how do it write the imports for that ?
should the library be the single source of truth, declare the platform dependency and reexport roc-ray types ?
Should both declare platform dependency ?
how does that looks like in the following directory structure
/examples
/demo.roc
/src
/MyLibrary.roc
I tried to dig into https://github.com/roc-lang/roc/blob/main/docs/langref/modules.md but still not sure how it is supposed to fit together
# examples/demo.roc
app [Model, program] {
rr: platform "https://github.com/lukewilliamboswell/roc-ray/releases/download/0.6/YsrMnLJw2ahDsyFXNEpipwWQfiM5DSxq5Ve6SyHczN7.tar.zst",
plotlib: "../src/MyLibrary.roc"
}
import rr.Color
import plotlib.MyLibrary
xs = [0.0, 1.0, 2.0]
ys = [1.0, 0.0, 3.0]
color = Color.from_hex(0xFFFFFF)
MyLibrary.scatter_plot!(xs, ys, color)
...
# src/MyLibrary.roc
import rr.Draw # roc does not know what rr is
import rr.Color # roc does not know what rr is
MyLib :: [].{
scatter_plot! : List(F32), List(F32), Color => {}
scatter_plot! = |x, y, color| {
Draw.begin_frame!()
for i in 0..<x.len() {
pos = Draw.Vector2(x[i], y[i])
Draw.circle_raw(pos, 1.0)
}
Draw.end_frame!()
{}
}
}
I feel like I would shift the begin/end frame out and have that be a caller responsibility. And then for the draw fn's I'd try accepting them as arguments to the function.
Like we could eliminate the dependency on the platform types that would be good
We might need to think about changing the API for roc-ray a little to make this more ergonomic... so this exploration is very helpful I think
Actually my question is more far reaching than this toy example.
How can a project standardize an interface that a platform will satisfy. The goal is to allow a Roc ecosystem to grow around the interface.
You can swap the implementation of the interface by swapping platform, but at the same time the ecosystem can build on the interface on its own.
For example, suppose I develop a UI library. I would want to standardize some form of Element, Event and so that:
Is it doable if the interface is pure Roc. Is it doable if the interface is pure and effectful Roc ?
In Roc you have the platform module that exposes a lists of types the platform provides to the application.
So I suppose my question is how can a library work with the platform exports without being an application.
Or alternatively, how can a library define an interface it depends on that will be satisfied by something else (likely a plateform's exposes).
PS: this is the same problem that WIT is solving for WASM Component Model.
https://youtu.be/phodPLY8zNE?si=NoKyFaFrXuGzvCBe&t=946
I know that there are discussion to standardize Path/Http types in the stdlib but other ecosystem will want specialized standard for libraries to sit in between applications and platforms.
Could also fit into https://github.com/roc-lang/www.roc-lang.org/issues/77, to enhance such questions.
@Romain Lepert I think the plan loosely is that the platforms will depend on packages in the ecosystem that define common types
Where the effects dont match up you can manually wrap them
I really want to bring roc-lang/http and roc-lang/path online with a release so we can test this out in practice with basic-cli and basic-webserver and basic-ssg etc
We are still working our way up the stack so I don't have an example to point to yet.
right, roc-lang/http and roc-lang/path will help picture this :)
Yeah hopefully https://github.com/roc-lang/http/pull/3 passes CI -- and we can cut a release and begin to experiment with that
Romain Lepert said:
how can a library define an interface it depends on that will be satisfied by something else (likely a plateform's
exposes).PS: this is the same problem that WIT is solving for WASM Component Model.
https://youtu.be/phodPLY8zNE?si=NoKyFaFrXuGzvCBe&t=946
yeah so this should already be doable with static dispatch
so if you look at https://github.com/lukewilliamboswell/roc-ray/blob/4c08f773f982ace0ab81f8ea4aefeb4162cd96e5/platform/Draw.roc#L396
you can do this inside your package:
Chart(draw) :: { data_points : ChartDataPointsOrWhatever }.{
init : ChartDataPointsOrWhatever -> Chart(draw)
init = |points| { data_points: points }
render! : Chart(draw) => {} where [
line_raw! : LineRaw => {},
text_raw! : TextRaw => {},
# ...whatever else it needs from roc-ray's Draw
]
render! = |self| {
Draw : draw
Draw.text_raw!(self.title)
Draw.line_raw!(self.whatever)
}
]
...and then the user of the package can do something like:
Chart : chart.Chart(pf.Draw)
my_chart : Chart
my_chart = Chart.init(my_data_points)
my_chart.render!()
basically what's happening here is that the package is saying "if you want me to support rendering, I need you to tell me about some type that has the methods line_raw! and text_raw! and they need to have these types: ..."
and then it delegates to that type
so this means your package can work not only with roc-ray itself, but also with any type-compatible platform, e.g. someone can make a "drop-in replacement for roc-ray" and it should Just Work with your charting package
and if you're granular with the methods you require (e.g. if you don't need draw_texture_raw! you don't mention it in your where constraints), you can be compatible with platforms that don't implement everything roc-ray implements, as long as they implement what you need
also, if someone is using a platform that doesn't fit roc-ray's API exactly, but there's some way they can polyfill it (make an implementation in terms of their platform's APIs) then they can do that - so instead of doing Chart : chart.Chart(pf.Draw) they do Chart : chart.Chart(MyPolyfilledChart) and they're all set
also this of course means they can swap in simulated drawing implementations for testing etc.
Thanks ! I’ll try that out
Richard Feldman said:
and if you're granular with the methods you require (e.g. if you don't need
draw_texture_raw!you don't mention it in yourwhereconstraints), you can be compatible with platforms that don't implement everything roc-ray implements, as long as they implement what you need
Can the compiler warn you if you have where constraints that aren't actually needed? Would be cool if there was a way to automatically prune those, so you don't accumulate spurious requirements as you change your implementation.
I'm not sure if it's a good idea though...you may want to over-constrain because you anticipate wanting them in the future and don't want to have to publish a breaking change
Fair point. Seems like ideally there'd be a way to automatically detect them, but not have it bug you if you don't want to prune them.
@Richard Feldman sorry it took a while to get to this but I found out the syntax segfault currently
https://github.com/roc-lang/roc/issues/10062
Last updated: Jul 23 2026 at 13:15 UTC