Stream: ideas

Topic: STM (software transactional memory) builtin


view this post on Zulip Richard Feldman (Dec 22 2024 at 00:10):

this is a sketch of an idea, not proposal-level quality at this point, but I wanted to put it out there now because it's become relevant to multiple different threads

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:11):

the basic idea is to add a builtin with an API like this:

Store.init! a => Store a
Store.read! : Store a => a
Store.write! : Store a, a => a
Store.transact! : Store a, (a -> (a, ret)) => ret

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:13):

and then add a transact! method to structural records that works kinda like record builders:

{ a, b, c } = {
    a: store1,
    b: store2,
    c: store3,
}.transact!(|{ a, b, c }| {
    a: a + 1,
    b: a + b,
    c: !c,
})

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:15):

this API has the property that all of these operations can be thread-safe, and none of them can deadlock (!)

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:16):

(however, you can use them to create your own locks that can deadlock if you do the looping explicitly yourself)

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:23):

(brb, kid stuff, then will explain more later :sweat_smile:)

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:24):

a critical reason for it to have this property is that transact! only accepts pure functions, and all of these functions are effectful

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:28):

in the case of the transact! that works on records, and involves multiple stores, the way it avoids deadlocks is that it runs the pure function on all the current values of those stores, but doesn't lock them - and then at the end checks if any of them have changed since it started running. if any did change, then its answer is based on stale data and the pure function gets rerun on the new data

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:28):

since it's a pure function, it's harmless to rerun as many times as necessary

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:31):

as usual, if you have lots of contention it'll be slow due to rerunning a bunch, but lots of contention always slows down concurrent programs

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:33):

Clojure and Haskell both have STM primitives like this

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 00:35):

Can it be pure with captures?

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 00:35):

I assume so

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:41):

yeah they'd all have refcounts over 1 for the lifetime of the store

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:48):

one of the things we could use this for is that it would mean we wouldn't need the get! and set! primitives in tests

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:49):

you could just use this directly for simulated implementations of effectful functions

view this post on Zulip Richard Feldman (Dec 22 2024 at 00:59):

some things I haven't thought through all the way:

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:03):

One idea for change detection is a simple monotonic counter

view this post on Zulip Richard Feldman (Dec 22 2024 at 01:04):

yeah, although if it ever wraps, that's a huge problem :sweat_smile:

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:04):

Wait, I've had someone I work with solve this problem

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:05):

It's something about a byte array

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:05):

But I'm struggling to remember

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:06):

Oh, i'm wrong, I think what he did was track the function pointer of the last function to run on the state

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:07):

Why is change detection needed?

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:07):

I don't see any change related event api

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:07):

I'm assuming to prevent race conditions

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:08):

transact! in a multi-threaded scenario without deadlocks would need to check that the input is still valid

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:08):

before it commits its transaction

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:08):

I definitely don't follow. Wouldn't this be protected by an RWLock? Write and transact would both do grab the write lock. Read would grab the read lock

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:10):

So you are saying instead of just checking for a change and then re-running the transaction, you just wait for the write lock?

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:10):

Isn't that exactly a recipe for deadlocks?

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:10):

No, totally deadlock free with a fair RWLock

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:11):

Also, it depends on the cost of the update and how often there is contentions which is the best solution

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:11):

Doing a looping transaction has no guarantee to ever resolve. A fair RWLock is guaranteed to resolve.

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:12):

But what if the transaction function itself never terminates?

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:12):

For example a fast set of write transaction may completely block a slow looping transaction. So looping with various size transactions is generally not safe.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:13):

Anthony Bullard said:

But what if the transaction function itself never terminates?

Then you have a bug and it deadlocks 100% of the time.

This is like asking what if a user program gets stuck in an infinite loop. It has nothing to do with the transaction and is a general problem of software.

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:14):

I think with Clojure's STM primitives, one thread can block itself, but can't block the store itself

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:14):

Sure, you could do that with a looping primitive, but the looping primitive is trivial to break fairness

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:15):

And broken fairness may mean that certain classes of transactions will never resolve

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:16):

I'm wrong, a deadlocked writer can't block readers, but it looks like it can block would-be writers to the same store

Screenshot 2024-12-21 at 7.15.28 PM.png

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:19):

Oh, clojure is a lot more complex. And I don't think it's model will work on roc

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:20):

It is really design to assume immutable data structures that are appended to. Roc does not have those.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:21):

They also look to break apart stores such that writers can interact with different parts of the store without blocking eachother or causing retries.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:22):

Also, I see how it is a bit different from a simple RWLock. The fundamental difference is that a reader can always read stale data and that is ok.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:22):

So a writer doesn't actually have to block readers at all.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:23):

This could be done in roc by using an atomic pointer swap after the write creates a new version of the data structure

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:23):

This would have absolutely terrible performance with roc lists and strs. They would be duplicated in every single write transaction

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:28):

Also, yeah, based on my reading of clojure, a stream of fast transactions could permanently block a single slow transaction. So it doesn't have fairness. Though maybe they have a trick to fix that that isn't obviously described

view this post on Zulip Anthony Bullard (Dec 22 2024 at 01:28):

Oh right, I forgot we don't use persistent data structures

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:35):

That said, stm seems to intentionally be quite optimistic in general preferring redoing work over contention

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:36):

Sounds like some stm systems have priority to deal with slow transactions that would get blocked by a stream of fast transactions

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:36):

Not exactly sure how it works though

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 01:37):

Oh, and it definitely requires atomic refcounting in the data in the stm share, but maybe that was already a given.

view this post on Zulip Richard Feldman (Dec 22 2024 at 02:07):

that's pretty interesting - I hadn't heard of fair RWLocks before!

view this post on Zulip Richard Feldman (Dec 22 2024 at 02:08):

I'm not sure whether those would permit removing the restriction that the transaction function has to be sure :thinking:

view this post on Zulip Richard Feldman (Dec 22 2024 at 02:08):

or if that would reintroduce the possibility of deadlocks even when you're not looping

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 02:09):

It's big problem is that it means way more contention. All reads have to be paused for a write to go through, but the write gets a unique view of the data and can mutate in place.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 02:09):

It is one writer or n readers active at a time

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 02:14):

This probably is best for roc with large data in a transaction (due to avoiding copies), but very pessimistic for small data (copy is cheap) and is not great for high contention scenarios. Retries are probably cheaper in many cases.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 02:15):

I wonder if there is a form of transactional memory that works better without persistent data structures.

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:16):

Actors :laughing:

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:18):

But all access with actors would either be async or blocking. But this all depends on your concurrency model

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 02:18):

Oh, so messages and each actor owns a unique copy of it's own data

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 02:18):

Yeah, that would play nice with the roc data model

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:18):

But that means that Actors are your concurrency model

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 02:19):

Yeah, essentially coroutines and messaging queue with coroutines owning data. Essentially the go model.

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:19):

But shared data has a ref count of 1 when the actor works with it

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 02:19):

Obviously could do more explicit actors as well.

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:19):

But a copy for each time the data is shared

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:20):

I like both

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:21):

Don’t communicate by sharing data, Share data by communicating

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:22):

But my lang was to be a purely functional Actor language, so I have my biases

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 02:22):

Anthony Bullard said:

But a copy for each time the data is shared

Only a copy if someone tries to mutate

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 02:22):

Can just increase the refcount by default when sharing

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:29):

Ooh, that’s right! Advantage to recounting which BEAM doesn’t use(from my memory)

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:29):

That makes this model make even more sense!

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:30):

Well,,,

view this post on Zulip Anthony Bullard (Dec 22 2024 at 02:30):

Then the owner doesn’t have that 1 refcount anymore

view this post on Zulip Karl (Dec 22 2024 at 02:45):

This concept featured pretty heavily in Rich Hickey's earlier presentations about Clojure's multithreading story. In practice the community wound up not really using it and instead just use atoms and usually one big atom.

view this post on Zulip Karl (Dec 22 2024 at 02:47):

I'm sure they exist but I certainly never worked on a production system that used anything besides atoms and I can't recall anybody presenting about it at the handful of conj I went to or the NYC meetups over a couple years

view this post on Zulip Richard Feldman (Dec 22 2024 at 02:47):

so I guess the actor API would be something like:

Actor.init! : state, (state, msg => state) => Actor state msg
Actor.send! : Actor state msg, msg => {}

view this post on Zulip Luke Boswell (Dec 22 2024 at 02:49):

Is there any way to get information out of an actor?

view this post on Zulip Richard Feldman (Dec 22 2024 at 02:53):

not in that API :big_smile:

view this post on Zulip Richard Feldman (Dec 22 2024 at 02:53):

I guess there probably should be though? :sweat_smile:

view this post on Zulip Richard Feldman (Dec 22 2024 at 03:03):

I currently have a very shallow understanding of the actor model

view this post on Zulip Richard Feldman (Dec 22 2024 at 03:03):

it seems like the general idea is to sort of build your whole program out of actors, and then give them ways to talk to each other

view this post on Zulip Richard Feldman (Dec 22 2024 at 03:04):

in this design, I think that could be accomplished by the update function being given its own "address" so that it can send itself to other actors

view this post on Zulip Richard Feldman (Dec 22 2024 at 03:04):

something like this:

Actor.init! :
    state,
    (Actor state msg, state, msg => state)
    => Actor state msg

Actor.send! : Actor state msg, msg => {}

view this post on Zulip Richard Feldman (Dec 22 2024 at 03:06):

so then if one actor wants to ask another actor about some piece of information, it says "hey here is my address (my Actor value, which internally is some address integer) and I want to know this information...when you have the answer, send it to me"

view this post on Zulip Richard Feldman (Dec 22 2024 at 03:11):

and yeah, definitely a cool thing about that design is that state would always have a refcount of 1 in normal operation (unless you did something silly like put your state in an outgoing message)

view this post on Zulip Richard Feldman (Dec 22 2024 at 03:11):

so it would just get mutated in-place every update

view this post on Zulip Richard Feldman (Dec 22 2024 at 03:12):

I think you could build this using the Go-style channel primitive we talked about on another thread :thinking:

view this post on Zulip Richard Feldman (Dec 22 2024 at 03:12):

also, this doesn't seem like it would be much use for tests :sweat_smile:

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 03:25):

Richard Feldman said:

I think you could build this using the Go-style channel primitive we talked about on another thread :thinking:

Yeah, it is basically coroutines and channels but with a tiny bit more organization and base structure.

view this post on Zulip Richard Feldman (Dec 22 2024 at 04:45):

Karl said:

This concept featured pretty heavily in Rich Hickey's earlier presentations about Clojure's multithreading story. In practice the community wound up not really using it and instead just use atoms and usually one big atom.

oh interesting - looking at the docs for Clojure atoms, they actually match the original API I posted at the top - except without the .transact! that works on records

view this post on Zulip Richard Feldman (Dec 22 2024 at 04:47):

the Store.transact! I posted at the top appears to be a generalized version of Clojure's swap! (generalized because instead of always returning the value that was stored in there previously, it gives you the option of returning something different if you like)

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 04:58):

swap sounds the same as transact

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 04:59):

But I guess it is a slightly different model

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 04:59):

Still a spin loop over a persistent data structure and then only writing back if nothing has changed. This is done via a spin loop and an atomic swap

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 05:00):

I think it will still have the same problems with our mutable or copy data structures.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 05:00):

But for our primitive types would be reasonable

view this post on Zulip Sam Mohr (Dec 22 2024 at 08:52):

I'm somewhat on the fence about this. It seems like if we want to enable a means to have something like mutable state in Roc, this is a very good way to do it. But whether that is a good thing to make easy is maybe not a good idea. I don't have much Ocaml experience, but when reading through Ayaz' cor experiment for lambda sets, the use of unannotated refs meant that I had to build a mental map of when state might "shift under my feet" instead of being able to read it for myself.

In Roc, we have ! with purity inference, so it'll be obvious that something effectful is happening, but it's not tracked what in particular is happening, in particular we don't know that Roc-local state is being mutated. I would hope that this PI-based "danger coloring" of Store usage would incentivize people towards immutability, but maybe like my recent threads on encouraging readable code, this isn't really a problem in practice.

In another language, I'd push harder that we really consider before introducing mutable state to a functional language, but Roc has removed ill-used/ill-received features in the past. So long as we are willing to do that for this as well, then I think experimenting isn't a bad thing.

view this post on Zulip Sam Mohr (Dec 22 2024 at 08:53):

To be clear, I think this change very likely good for Roc, but I think we should consider how and how much it might be exploited by people that other Roc devs need to engage with the code of.

view this post on Zulip Sam Mohr (Dec 22 2024 at 08:54):

If anyone has real experience with codebases that use something like Ocaml's ref, I'd love to hear about your opinion on how much that helps/harms the average codebase. My experience has been much more on the reading side than the writing side (basically no writing).

view this post on Zulip Luke Boswell (Dec 22 2024 at 09:39):

Sam Mohr said:

To be clear, I think this change very likely good for Roc, but I think we should consider how and how much it might be exploited by people that other Roc devs need to engage with the code of.

Think through the mis-use cases

view this post on Zulip Oskar Hahn (Dec 22 2024 at 09:48):

Do I understand correctly, that with this feature, you could do something like this in basic-webserver?

app [Model, init!, respond!] { pf: platform "https://github.com/roc-lang/basic-webserver/releases/..." }

Model : Actor U64 {}

init! = \_ ->
  Actor.init! 0 (\state, _ => state + 1) |> Ok

respond! = \req, model ->
  Actor.send! model {}
  count = Actor.read! model
  Ok {status: 200, headers: [], body: Str.toUtf8 "Count: $(Num.toStr count)<br>"}

Update: I removed the try

view this post on Zulip Sam Mohr (Dec 22 2024 at 09:48):

That's probably a better wording, yes. I contorted my sentence that much because I'm trying to be particular here: this feature is powerful for individual devs, but would it help or harm someone trying to read their code?

view this post on Zulip Sam Mohr (Dec 22 2024 at 09:50):

@Oskar Hahn that looks like a viable usage to me, except you don't need to use try since the example API Richard gave was infallible.

view this post on Zulip Oskar Hahn (Dec 22 2024 at 09:58):

What I don't understand is, how does this scale?

Currently, you can start multiple basic-webservers on different machines. The current usecase of Model in basic-webserver is to hold stuff like database connections. The current API does not allow you to change the Model. So basic-webserver can be scaled over multiple servers.

With the Actor, this is no longer possible. Roc would need some way to share the state between all instances.

view this post on Zulip Oskar Hahn (Dec 22 2024 at 10:00):

Maybe the original version of the idea was more suited for scalability, since Store.transact uses a pure function that a platform could call on all instances.

A RWMutex is much simpler, but it only works on one machine.

view this post on Zulip Oskar Hahn (Dec 22 2024 at 10:03):

There is the nice talk from @Richard Feldman Distributed Pure Functions

The idea, that as an application author, you don't have to care, if your code is scaled over multiple CPUs, multiple machines or multiple data centers is appealing. Is this still possible with a Store/Actor?

view this post on Zulip Eli Dowling (Dec 22 2024 at 10:44):

Sam Mohr said:

If anyone has real experience with codebases that use something like Ocaml's ref, I'd love to hear about your opinion on how much that helps/harms the average codebase. My experience has been much more on the reading side than the writing side (basically no writing).

I do!
I love them. Seems like an excellent compromise between pragmatically allowing mutation where it's needed but also making it super obvious when it's happening and about the right amount of annoying.
refs have to be assigned with := and dereferenced with ! so they are a pain to use as normal variables... so they don't get used too often, which is good.

I have found them rare in ocaml codebases and I tend to use them rarely myself.
Mostly they are used internally when building data structures and such so users aren't readily exposed to them.

view this post on Zulip Jasper Woudenberg (Dec 22 2024 at 13:04):

I don't know OCaml's refs, but I do have some experience with these kinds of 'mutable variables using IO' APIs in Haskell. There's a select set of circumstances where these can be useful, and I don't have the sense they're overused outside of that.

With regards to the 'how will this scale across multiple machines' question, I wonder if this is a trade-off that the platform can make. Some platforms might optimize ease of prototyping and offer an API like this and compromise on scalability. Another platform might optimize for scalability and not offer this API, compromising on convenience.

view this post on Zulip Sam Mohr (Dec 22 2024 at 14:50):

I'm glad to hear that both of you have had a good experience with ref, it sounds like existing languages have provided a good experience with it. I think a worry I hadn't been overtly considering here is that Roc has the potential to reach a lot of people, including lots of beginners. It definitely feels like macroeconomics trying to predict what will be a good decision in a few years. If people have good experience here, I'll take a bit of faith in our team's expertise that we aren't making a footgun for schmucks.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 15:05):

Oskar Hahn said:

The idea, that as an application author, you don't have to care, if your code is scaled over multiple CPUs, multiple machines or multiple data centers is appealing. Is this still possible with a Store/Actor?

That definitely is possible, but I don't think that is the goal here. Erlang is an actor system that seamlessly scales to many machines. I believe there is a rust library that can do the same as well.

My understanding was that this actor suggestion was more about suggesting a go style tooling for more concurrency and state based message passing power. It would be equally limited as go routines and channels are today. So single machine

Also, basic webserver model is not mutable cause we don't have atomic refcounting and we have not built a good API for mutating it. It really should be mutable.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 15:08):

General question:

Is any of this something that really should be built into the language? Most of this feels like it probably should be platform primitives.

Actors and message passing, a mutable long-lasting state store, these don't feel like they belong in roc. They feel like they belong in the platform.

Not as sure about ref, but my gut feeling is the same for that as well.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 15:10):

Oh, looking more at ref it is really unrelated to the rest of this conversation.

view this post on Zulip Anthony Bullard (Dec 22 2024 at 15:10):

I have a lot of thoughts about this conversation, but I want to finish this bugfix before I weigh in. My response will probably be blog-post length :rofl:

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 15:11):

It doesn't deal with synchronization or sharing accross threads safely which is the main focus of the rest of this conversation.

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 15:11):

I think it probably should be moved to another thread

view this post on Zulip Anthony Bullard (Dec 22 2024 at 15:11):

Brendan Hansknecht said:

Is any of this something that really should be built into the language? Most of this feels like it probably should be platform primitives.

Actors and message passing, a mutable long-lasting state store, these don't feel like they belong in roc. They feel like they belong in the platform

But I think I agree largely with this take. One of my goals in contributing to Roc is to work on a Actor platform

view this post on Zulip Richard Feldman (Dec 22 2024 at 15:29):

the original motivation for this thread is basically to have a builtin that can be used in tests for simulating effects, and which might also be useful in other situations such as the ones where ref in OCaml (or similar Haskell primitives that are thread safe) are used.

if we're going to have a primitive like that, it must not introduce data races into the language, which is why concurrency is in scope for discussing how it would work.

view this post on Zulip Richard Feldman (Dec 22 2024 at 15:30):

I agree that actors and channels shouldn't be builtins

view this post on Zulip Sam Mohr (Dec 22 2024 at 15:35):

If it's just for tests, cool. My gripes all come from its use in normal, production code

view this post on Zulip Anthony Bullard (Dec 22 2024 at 15:49):

I would think if this is just for the test usecase, the simpler API (without transact!) might be better and then we don't have to worry about deadlocking at all

view this post on Zulip Brendan Hansknecht (Dec 22 2024 at 16:06):

Tests can only ever be single threaded, so that does make the api requirements a lot simpler

view this post on Zulip Oskar Hahn (Dec 22 2024 at 16:56):

I understood the original idea, that Store was something, that could be used in normal applications, not only in tests. I like the idea and hope, that there is a solution.

If the platform should be responsible to decide, how to handle the datarace, would it be possible to solve it like the memory-management, that a platform can define functions, that get called by roc? Like roc_rwmutexor something, that is more abstract? Then the platform can decide, if is just wants to support this on a single machine, or via network in a datacenter.

view this post on Zulip Richard Feldman (Dec 22 2024 at 17:02):

if it's just for tests then it wouldn't be a builtin. we have a separate design for that - basically just allow tests to be a lambda which receives functions to get and set some state:

expect |{ get!, set! }|
    # test goes here

view this post on Zulip Richard Feldman (Dec 22 2024 at 17:03):

if we had a builtin that could do this, then we wouldn't need a separate thing for tests

view this post on Zulip Richard Feldman (Dec 22 2024 at 17:09):

I think it's an interesting idea to explore the use case of "I want this Roc program to be able to be distributed seamlessly across machines," but that's not what this particular thread is about, so I think it's worth starting a separate thread if we want to discuss that idea :big_smile:

view this post on Zulip Richard Feldman (Dec 29 2024 at 03:51):

I realized two problems with this idea:

  1. Because it stores state outside the platform (which currently does not happen), it would make it much harder for platforms to offer hot code reloading. Currently that can be done by platforms swapping in a dylib of the new app (via some coordination with the compiler that the compiler doesn't offer yet), because the compiled app is just a set of functions and zero state. That seems very valuable to preserve!
  2. It would be pretty easy to accidentally put helpers that use stores outside the tests, which would make them accidentally shared state across tests, which would be a disaster. The expect |{ get!, set! }| idea does not have this problem, because the state-getting and state-setting functions only exist within each test, and don't exist outside the tests.

view this post on Zulip Richard Feldman (Dec 29 2024 at 03:51):

so my conclusion about this idea is that it seems like the wrong direction. :big_smile:

view this post on Zulip Sam Mohr (Dec 29 2024 at 04:10):

I think we'll be totally fine with something like expect |get!, set!|. It wouldn't be that hard to make a community-driven package called something like test-constructs that you can build complex records with get! and set!. I can try putting something like that together in the near future to ensure it'd work, but with that, people could just import a File-like, or a Utc-like, etc.

view this post on Zulip Sam Mohr (Dec 29 2024 at 04:12):

We should also definitely explore @Anthony Bullard 's idea (I think it was his) for implementing a hosted module's function defs using get! and set!. If there was a way to ensure your test functions match get! and set!, then you could very easily run tests without making your effectful API a layer further mocked/abstracted

view this post on Zulip Sam Mohr (Dec 29 2024 at 04:12):

#ideas > Platform mocking in a PI world

view this post on Zulip Sky Rose (Dec 29 2024 at 23:19):

To address 1., could it be an optional part of the platform? Like make a common STM interface that's managed by the platform, has the same interface for all platforms, and platforms can either provide their own (e.g. for hot code reloading) or use the default?

view this post on Zulip Anthony Bullard (Dec 29 2024 at 23:26):

Some platforms may not want it, but i agree the interface should be shared amongst those that do


Last updated: Jun 16 2026 at 16:19 UTC