Stream: beginners

Topic: Complex packages


view this post on Zulip Ryan Barth (Jul 02 2024 at 21:07):

I have been playing with the platform stuff for a week or so now and a couple questions have been nagging at me.

In the Roc FAQ it says:

it's possible to write Roc code that can be used across multiple platforms. Applications can use platform-agnostic packages, as well as packages involving I/O, as long as their platform supports the I/O operations in question

This seems problematic for very complex packages that most other languages hand off to some shared library or the OS. Some that come to mind are:

While it seems like no problem to create a platform that supports any one of these the issue would seem to arise in composition. With the ecosystem today if I want a sqlite database for my application I need to use basic-webserver. But what if I just need it in a cli context? At that point am I forking basic-cli to add it? Is every reasonably robust "general purpose" platform expected to embed sqlite? I don't see a world where sqlite or QT get re-implemented in roc. Maybe this isn't as big a problem as i think since the Rust package ecosystem acts as a pseudo package manager for platforms? Could some of the module param work I have seen happening provide "feature flags" for platforms?

If you have read this far know that I am not trying to be antagonistic. I love the platform concept and understand that every design has its limitations. I am just trying to understand if I am missing something.

The above quote implies that so long as there is a mechanism for it in the platform packages can be effectful. Has there been ideas around standardized effects across platforms? POSIX, Python's WSGI, and Java's ODBC come to mind.

view this post on Zulip Sam Mohr (Jul 02 2024 at 21:31):

I think your concern will be shored up pretty soon, if I understand you correctly.

Given that IO and other effectful operations can only be done by specific platforms, the language as it currently is does not support packages that do IO. However, as is described in this proposal, by adding the Task (e.g. IO monad) as a standard library feature built-in, we make it possible for packages to take a function that runs IO and return a Task that composes an operation using said Task. For example,

loadJsonDataFromApi : Str, (Str -> Task (List U8) err) -> Task Json err
loadJsonDataFromApi = \url, getData ->
    data = getData! url
    parseJson data

This would now be able to go in a platform-agnostic package, and all a caller would have to do is use some Http.get from their platform and do

import cli.Http
import Package exposes [loadJsonDataFromApi]

myData = loadJsonDataFromApi! "http://api.com" Http.get

There's already a PR for this, we just need to finish testing and then you can do the above!

view this post on Zulip Sam Mohr (Jul 02 2024 at 21:36):

You will notice that this is a bit clunky, though. What if you don't want to pass Http.get or Sql.query to every invocation of an effectful function imported from a package? Well, we have another proposal for that for module params, which lets us pass variables/functions to modules when we import them. That means we can define our package module as

module [loadJsonDataFromApi] { getData : Str -> Task (List U8) err }

loadJsonDataFromApi : Str -> Task Json err
loadJsonDataFromApi = \url ->
    data = getData! url
    parseJson data

and then just use it from a platform as such

import cli.Http
import Package exposes [loadJsonDataFromApi] { getData: Http.get }

myData = loadJsonDataFromApi! "http://api.com"

There's also a PR that just got put out today by the industrious @Agus Zubiaga for that, so wait a couple weeks and you can do this yourself

view this post on Zulip Sam Mohr (Jul 02 2024 at 21:41):

Once these two features (Task as a built-in and Module Params) get merged in, the ecosystem will be fully modular! I think the only thing beyond that I'd personally be worried about is Roc's understanding of whether two packages with different versions are API-compatible. What if the platform uses roc-url v0.2.0 and I use v0.2.1? Currently, I can't pass my own opaque Url because it's a type error, but once Roc understands they're the same thing basically, then it will be less annoying to use common external packages between a platform and an application. Relevant proposal

view this post on Zulip Ryan Barth (Jul 02 2024 at 21:43):

Thank you for the detailed answer. I have seen a lot of these PRs flying and I have been struggling to develop the image for where that makes the language end up when they are all landed.

view this post on Zulip Luke Boswell (Jul 02 2024 at 21:44):

Also, I think forking platforms to add new effects and customise things is encouraged :smiley:

view this post on Zulip Sam Mohr (Jul 02 2024 at 22:05):

Ryan Barth said:

Thank you for the detailed answer. I have seen a lot of these PRs flying and I have been struggling to develop the image for where that makes the language end up when they are all landed.

I think the language will have all the tools it needs, but we'll need to wait for a pattern to emerge on passing Task context for complex scenarios. What if I want an ORM-style package that supports 3 different SQL implementations? How do I organize taking DB connection config if I want to allow a Str or a record? I have some ideas on how I will do it in Weaver and roc-uuid, but I'm not sure what will be the standard in the ecosystem yet.

view this post on Zulip Brendan Hansknecht (Jul 03 2024 at 00:27):

I don't think Task as builtin or module params fix this.

view this post on Zulip Brendan Hansknecht (Jul 03 2024 at 00:28):

I think they help in a specific case, but they don't fix it.

view this post on Zulip Brendan Hansknecht (Jul 03 2024 at 00:28):

If you need SQLite, that has to be in the platform. If you need GPU driver bindings, that has to be in the platform. If you need sockets, that has to be in the platform.

view this post on Zulip Brendan Hansknecht (Jul 03 2024 at 00:29):

While module params may allow you to build functionality based on a flexible set of primitives, it does not fix the limitations of every primitive needing to be exposed by the platform.

view this post on Zulip Brendan Hansknecht (Jul 03 2024 at 00:29):

There is no arbitrary c ffi and that definitely is a limitation.

view this post on Zulip Luke Boswell (Jul 03 2024 at 00:30):

YouTube personalities are going to be talking about how they "re-wrote it in roc" in the future :smiley:

view this post on Zulip Brendan Hansknecht (Jul 03 2024 at 00:32):

Theoretically, someone could make a wrapper around libffi to alleviate this, but it likely will be a complex to get write and nice to use. We maybe even should look into supporting it at the language level (still forcing tasks). Theoretically with tasks as builtins, we could allow arbitrary cffi via shared libraries. basically allow a package to define it's own hosted like module, but for shared libraries. That is probably something we should seriously look into for more flexibility.

view this post on Zulip Brendan Hansknecht (Jul 03 2024 at 00:34):

I see module params more as alleviating mapping problems. It allows for a generic print function that might print to the terminal or might send an http request to a logging server. It can abstract over graphics primitives such that many different gui libraries and platform graphics solution can communicate with each other.

But module params and builtin task to not allow for fixing the issue of need primitives that are not part of a platform. The lack of platform composability makes development harder.

view this post on Zulip Brendan Hansknecht (Jul 03 2024 at 00:35):

Theoretically, ready made host language packages and feature flags could be added to something like basic cli to support more of theses features, but today, forking is the solution.

view this post on Zulip Albert (Jul 06 2024 at 16:30):

Luke Boswell said:

uTube personalities are going to be talking about how they "re-wrote it in roc" in the fut

What's my plan hahah


Last updated: Jul 06 2025 at 12:14 UTC