Stream: beginners

Topic: Why platforms?


view this post on Zulip Daniel Schierbeck (Oct 08 2024 at 11:02):

As someone looking into Roc for the first time, the whole concept of "platforms" seemed like a big obstacle. I couldn't find a place that described the vision of having this concept in the language, or what kind of value it will bring. It seems to me more like it makes it harder to provide an integrated experience, e.g. the different shenanigans needed to make Task just work as you would expect?

view this post on Zulip Anton (Oct 08 2024 at 11:09):

Hi @Daniel Schierbeck,
Platforms are described in detail here

view this post on Zulip Peter Marreck (Oct 08 2024 at 14:18):

@Daniel Schierbeck
I think it's great. If you know about hexagonal architecture, which I recommend you do look into if you haven't yet (https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)), the platform is analogous to the "adapter" layer. The idea is that your "modeling" code, which you have hopefully separated from your "interface" code, is agnostic with respect to what it's actually connecting to or how.
At least, that's my understanding so far (as a Roc noob myself).

view this post on Zulip Daniel Schierbeck (Oct 08 2024 at 15:37):

I do have some experience with Hexagonal Architecture, but unfortunately it’s all negative :grimacing: basically, I’ve never ever needed to swap out the platform of any project, and would much rather avoid the limitations and complications of having an adapter abstraction

view this post on Zulip Daniel Schierbeck (Oct 08 2024 at 15:41):

So I guess what differentiates platforms from frameworks is that they allow compiling to different targets, eg Node.js?

view this post on Zulip Shaiden Spreitzer (Oct 08 2024 at 15:51):

Roc is the first "compiled scripting" language which is not only a new, unique approach to providing extensibility to software projects, it is also one of the most interesting and revolutionary ideas in the world of software in many years!!! I hope that platform dev will be flashed out in the near future and once things have settled down, sorted out and become more stable we can enjoy the Elm Experience in all aspects of our code :+1:

view this post on Zulip Richard Feldman (Oct 08 2024 at 16:38):

Daniel Schierbeck said:

So I guess what differentiates platforms from frameworks is that they allow compiling to different targets, eg Node.js?

great question! I'd say the main three things are:

  1. Unlike frameworks, platforms are in charge of memory management, which means you can make things like a webserver that never allocates - this isn't really feasible in Node, Go, etc. Today you'd need to write it in something like Rust, Zig, C, or C++.
  2. Unlike frameworks, platforms can offer only the I/O primitives that make sense for the domain in question. This means that you can have a platform called safe-script (I'm hoping to ship this actual platform in the next few months) which is a drop-in replacement for basic-cli except that it prompts you whenever it would do anything potentially scary to your system, e.g. disk I/O to particular directories. So I can download a basic-cli app from the Internet, swap out one line of code to make it use safe-script instead of CLI, and now I can run it on my machine with zero concern that it will give me malware or anything like that, because safe-script sandboxes all the I/O primitives and there's no way for the application to escape that sandbox.
  3. Unlike frameworks, since platforms are also in charge of all concurrency, they can implement concurrency in a way that makes sense for their domain without the application author having to micromanage that themselves. For example, one platform might implement concurrency using Go-like coroutines, but for a platform targeting embedded systems, where even spawning any OS threads at all is not worth it, that platform silently just decide to make all concurrent operations run sequentially instead.

view this post on Zulip Daniel Schierbeck (Oct 08 2024 at 18:37):

I mean – those sound like very cool visions, but they do seem _very_ specific and niche. It would seem to me that most languages can get away with use case specific frameworks on top of standardized I/O stacks and be... pretty fine? For pure languages in particular, you could even disallow I/O in the user code, requiring the framework to translate e.g. a custom I/O tag union type into concrete I/O calls.

I'm of course not the architect of the language, but as a Roc beginner, but light Elm user and longtime user of many other languages, I've never really felt a strong need to use a _single_ language across very different stacks. The examples I've seen often end up suffering from the lowest common denominator, e.g. trying to use a single technology to write both Web, iOS, and Android apps, with none of them actually being good.

view this post on Zulip Richard Feldman (Oct 08 2024 at 18:46):

oh I'm not trying to persuade you that Roc's way is the right way for you, I just wanted to explain what differentiates platforms from frameworks! :big_smile:

view this post on Zulip Richard Feldman (Oct 08 2024 at 18:48):

for context, a bunch of people have said to me that platforms are the thing that excites them the most about Roc, and that they are shocked other languages haven't tried it before now - but I can also see the "why bother, just do it the traditional way?" perspective haha

view this post on Zulip Richard Feldman (Oct 08 2024 at 18:48):

it's an experiment, since no other language has ever tried it in this way before, so we'll see how it turns out!

view this post on Zulip Niklas Konstenius (Oct 09 2024 at 14:58):

Hope its ok to hijack this thread a bit?

I'm also a little bit confused about this plattform-concept. Let's say I want to build a REST-application using PostgreSQL as the database together with some Kafka-stuff. Do I need a specific plattform that provides the exact stack I'm using (HTTP-server, PostgreSQL and Kafka)?

view this post on Zulip Anton (Oct 09 2024 at 15:03):

I think for Postgres and Kafka you can use pure roc packages

view this post on Zulip Niklas Konstenius (Oct 09 2024 at 15:06):

Anton said:

I think for Postgres and Kafka you can use pure roc packages

Cool, combined with which plattform?

view this post on Zulip Brendan Hansknecht (Oct 09 2024 at 15:06):

To expand on that a tiny bit. The platform just needs to expose a set of primitives that a pure roc library can use to talk to postgres or Kafka. In this case, that would just be socket connection primitives. The platform could probably help out a bit more by offering full TCP primitives in these specific cases.

view this post on Zulip Brendan Hansknecht (Oct 09 2024 at 15:08):

So any platform with TCP connection primitives would be able to speak postgres, MySQL, mssql, Kafka, etc. but it does depend on roc libraries for these services. Before those exist, a forked or fully custom platform may be the easiest way to speak to those services. Just depend on a library in the host language instead of requiring an implementation to exist in roc

view this post on Zulip Brendan Hansknecht (Oct 09 2024 at 15:08):

A basic implementation for postgres already exists in roc for example

view this post on Zulip Niklas Konstenius (Oct 09 2024 at 17:07):

Thanks for taking the time to explain this @Brendan Hansknecht . It makes a little bit more sense now.

I just checked out basic-cli and basic-webserver and both seems to expose the exact same Tcp-primitives so I guess a roc-library that depends on those primitives would compile on both basic-cli and basic-webserver?

Is it just a coincidence that the Tcp module in those platforms are exactly the same or is there a standardised Tcp "interface" (for lack of a beter word) that a plattform must adhere to to qualify as a "roc plattform with tcp support"?

I also noticed that basic-cli and basic-webserver both have a File module that does differ between the plattforms. How would a library author that want to make a library that depends on file primitives know which file "interface" to use? As a library author I guess you want to target as many plattforms as possible?

view this post on Zulip Brendan Hansknecht (Oct 09 2024 at 17:17):

I guess a roc-library that depends on those primitives would compile on both basic-cli and basic-webserver?

yep

Is it just a coincidence that the Tcp module in those platforms are exactly the same

Not completely, we try to keep those platforms matched in api if possible

is there a standardised Tcp "interface"

Not currently. That said, as long as your interface is close enough, mapping can also be done in roc.

How would a library author that want to make a library that depends on file primitives know which file "interface" to use?

Yeah, currently we have a small ecosystem that is manually synced and changes are often made in one place without being ported to others.

How would a library author that want to make a library that depends on file primitives know which file "interface" to use?

Currently we don't have this story fully built out. At some point, I assume we may create interface packages that enable this kind of stuff to be codified.

If I were writing a library that depended on file io, I would try to write it today in terms of higher order functions that are relatively basic/obvious primitives (open, close, append, etc). Hopefully any platform primitives would be able to map to the higher order functions that I create. That would be the best way to make a library flexible today.

That said, the best case is if a library can be a level removed from that. Instead of files, just string iterations for example.

view this post on Zulip Niklas Konstenius (Oct 09 2024 at 17:37):

is there a standardised Tcp "interface"

Not currently. That said, as long as your interface is close enough, mapping can also be done in roc.

Could you elaborate on this? I don't think I get it. Let say I would like to use a library that depends on certain Tcp primitives that does not exist by the same name in the platform I'm using. How would the mapping be done?

view this post on Zulip Brendan Hansknecht (Oct 09 2024 at 17:43):

So for a library to depend on a platform primitive, they have to be explicitly passed into the library. This is generally done via module params which expose the function to the entire module of the library instead of just a single function.

As a platform author I can pass anything to that as long as it has the same type signiture.

If the library wants:

tcpWrite: stream, Str -> Task {} err

And my platform gives me:

tcpWrite: stream, List U8 -> Task {} err

I can write an adapter and pass that into the library

tcpWriteForLib = \stream, str -> tcpWrite stream (Str.toUtf8 str)

view this post on Zulip Niklas Konstenius (Oct 09 2024 at 18:06):

Ah, didn't know about module params. Sound like some kind of manual dependency injection?

So it is me as an application developer that have to do this passing/mapping of the platform primitives to the library I want to use?

view this post on Zulip Brendan Hansknecht (Oct 09 2024 at 18:27):

yes

view this post on Zulip Brendan Hansknecht (Oct 09 2024 at 18:27):

and yes

view this post on Zulip Niklas Konstenius (Oct 09 2024 at 18:34):

Thanks a lot for taking the time to explain this @Brendan Hansknecht . I now have a much better understanding about the relationship between platforms, libraries and applications. :+1:

It'll be very interesting to see how these ideas will work in practice in the future.

view this post on Zulip Luke Boswell (Oct 09 2024 at 21:48):

The current implementation of basic-cli and basic-webserver was done before we had module params or Task as builtin. One of the projects I have in the back of my mind is to start pulling out the common functionality from these platforms into platform-agnostic packages -- which we can build now :tada:

For example, Richard discussed a design for a Path package which would provide a nice set of primitive types and function for working with filesystem paths. There is still a bit of work to get that package operational, and then rework the platforms to use that package (and it's common types). This will be really helpful not just for these platforms, but any future platform that also wants to work with Paths (files, directory etc) and or Packages that want to build on top of these primitives.

There are others which will probably follow in this same direction, like Tcp etc.

If this work is interesting to anyone, let me know as there's lot of things we can now experiment with now we have Task as a builtin and module params.

view this post on Zulip Jared Cone (Oct 10 2024 at 00:53):

What are module params?

view this post on Zulip Brendan Hansknecht (Oct 10 2024 at 01:04):

Functions passed in when importing a roc module. They are specified in the header. On phone don't have links to a doc at the moment

view this post on Zulip Luke Boswell (Oct 10 2024 at 01:12):

Jared Cone said:

What are module params?

Here's the original design proposal https://docs.google.com/document/d/110MwQi7Dpo1Y69ECFXyyvDWzF4OYv1BLojIm08qDTvg/edit?usp=sharing

The actual implementation has varied a little from this.

view this post on Zulip Luke Boswell (Oct 10 2024 at 01:14):

We should probably write up a guide to Module Params... or something similar to document what they are, the syntax, and potentially how/why to use them etc.

view this post on Zulip Brendan Hansknecht (Oct 10 2024 at 01:18):

Yeah, should go under docs on the roc website

view this post on Zulip Luke Boswell (Oct 10 2024 at 02:01):

Made a new Good First Issue for this https://github.com/roc-lang/roc/issues/7155

view this post on Zulip Daniel Schierbeck (Oct 10 2024 at 09:11):

That module params design proposal seems to exemplify the tradeoff between abstraction and simplicity, though – having to pass in the primitive dependencies when using a module does seem like much more hassle than having the module integrate directly with the primitives in a singular platform. I can see some of the _theoritical_ benefits of this dependency injection, but DI is not exactly a new concept, and the benefits enumerated in the proposal are the same ones I saw mentioned 20 years ago – yet DI has become something of a scary word for OOP oldies :laughing: it makes complex things much simpler, but simple things more complex.

view this post on Zulip Brendan Hansknecht (Oct 10 2024 at 15:06):

I think DI is often "scary" in oop cause it is hidden from the code authors and feels magic

This is really no different from passing a lambda into a function. Just has a bit of sugar to pass the lambda into all functions in a module. It also has a very clear chain to follow that is explicit. I would guess that in practice it won't feel the same as DI.

view this post on Zulip Daniel Schierbeck (Oct 11 2024 at 08:46):

I still think the overhead of having to figure out which params to pass to a module to get it to work (and _why_ that is necessary when it's not in other languages) is going to be an obstacle.

view this post on Zulip Sam Mohr (Oct 11 2024 at 09:45):

Yes, that will be an ergonomics question that library/platform devs will need to try to simplify in the nascent months/years of this feature

view this post on Zulip Niklas Konstenius (Oct 11 2024 at 10:24):

My 2 cents:

As a long time Java / Spring developer DI is not scary at all. At least on the levels of abstraction that I usually encounter it. I do think Module Params could be a really good mean of doing DI in Roc.

What scares me a little bit though is the thought of needing to manually pass all tcp primitives (9 functions and 3 types in basic-cli) as modul parameters to every 3d party library that needs some networking capability. It's even scarier to think of also needing to write mapping functions when the platform primitives does not match the expected signatures in the libraries.

But I'm maybe misunderstanding how this is supposed to work?

view this post on Zulip Sam Mohr (Oct 11 2024 at 11:24):

Niklas Konstenius said:

What scares me a little bit though is the thought of needing to manually pass all tcp primitives (9 functions and 3 types in basic-cli) as modul parameters to every 3d party library that needs some networking capability. It's even scarier to think of also needing to write mapping functions when the platform primitives does not match the expected signatures in the libraries.

Yes, if that's required, then no one would want to use this, and we'd eventually push towards basic-cli being the kitchen sink platform that everyone writes stuff for/into. We would probably want something like this:

import basicCli.Tcp

import ws.Websocket { tcp: Tcp.prelude }

view this post on Zulip Sam Mohr (Oct 11 2024 at 11:24):

Or something along those lines that just provides the "standard bundle" of useful TCP functions

view this post on Zulip Sam Mohr (Oct 11 2024 at 11:25):

It's more annoying to use if you don't have the TCP bundle of functions in the right format in your package, but still better than needing to always pass everything in a giant record

view this post on Zulip Niklas Konstenius (Oct 11 2024 at 11:39):

That looks much better than the dark scary scenario I was describing. :smile:

But if we already have decided on "standard bundles" of low level "capabilities" it still feels a little bit strange that I (as an application writer/ library consumer) need to do this "DI" by hand.

For me as a library consumer it would be much nicer if I could just read the documentation of a library and see that it requires the networking capability. And since I know that the plattform I'm using provides the networking capability I know I'm good and can just import and use the package without passing any dependencies.

view this post on Zulip Richard Feldman (Oct 11 2024 at 12:28):

I appreciate the feedback about how it sounds, but I think it's more important how it feels to use in practice - so I think until we've actually spent some time building (and testing) things we should be mindful of going down a rabbit hole of what it might feel like to use without having actually tried it or even seen real-world code using it :big_smile:

view this post on Zulip Niklas Konstenius (Oct 11 2024 at 12:39):

That's a good approach for sure. I'll be happy to try these things out for real in the future.


Last updated: Jul 06 2025 at 12:14 UTC