> Database management
> syncing with the backend server
Do features like these, when implemented in Racket, consume more resources (battery, CPU, etc.) than if they were implemented using the native API equivalents (e.g., NSURLSession or whichever is more applicable)?
In the larger context of cross-platform apps with a common core written in a non-native programming stack, I often wonder this about network and disk I/O management. I understand using the native APIs for other "I/O" like UI, hardware interfaces (bluetooth, accelerometer, etc.), because they often don't have an
equivalent API in the programming stack used to implement the common core.
As far as I know, Capacitor wraps over the native APIs.
Ultimately, you end up calling some system API for I/O, so the only difference is how efficient the implementations of those Frameworks are compared to the embedded language's implementations. On iOS, embedding Racket requires using an interpreted mode (as opposed to Racket's usual native compilation mode), so there is a small hit, but it's not one that is really noticeable in battery or CPU consumption. In fact, Podcatcher seems to do better in battery consumption compared to the competition in my (and my friend's) testing, but I would guess that's not necessarily _because_ of Racket; it probably has more to do with how the system as a whole pays attention to perf.
That makes sense. Do you keep the in-memory data in Racket data structures? I imagine keeping the data fed to Swift UI in sync with data maintained by GC'd Racket would involve some work.
Yes, the app uses Noise under the hood, and the way to think about it is a request-response model[1]. Swift makes an async request to the Racket backend, it constructs some data (either by querying SQLite, or making a request to the backend, etc.) and returns a response. If it doesn't retain any of the data, then it gets GC'd. The ser/de is relatively low overhead -- if you try the app and go to Settings -> Support and take a look at the logs after using it a little, that should give you an idea of how long the requests take. The lines that start with `#` refer to Swift->Racket RPCs. Here's an example from my logs:
Some things on the Racket side are long running, like the download manager. It's like an actor that keeps track of what's being downloaded and the progress of each download. Whenever a download makes progress, it notifies the Swift side by making a callback from Racket->Swift. In this example, there is some duplication since both the Swift and Racket sides each have a view of the same data, but it's negligible.
What's not as great from a memory use perspective is how large Racket's baseline memory use is. Loading the Racket runtime and all the app code takes up about 180MB of RAM, but then anything the app does is marginal on top of that (unless there's a bug, of course).
[1]: I did this precisely because, as you say, keeping keeping data in sync in memory between the two languages would be very hard, especially since the Racket GC is allowed to move values in memory. A value you grab at t0 might no longer be available at t1 if the Racket VM was given a chance to run between t0 and t1, so it's better to just let Racket run in its own thread and communicate with it via pipes. Probably, the same would be true for OCaml.
- Both sides pass messages to each other. Both of them talk through C ABI. My model was synchronous though. Async is certainly better.
- I used the protobuf binary protocol for message passing. Faster and probably more efficient than, say, JSON. But both sides may have copies of the same data for this reason.
I've written down my approach, which roughly aligns with yours, in the project's README.
What I wanted to do was for OCaml side to allocate data in memory, and for Swift to access the same memory through some commonly agreed upon protocol (protobuf itself maybe?). But I haven't yet explored how difficult this could be with GC coming in play. I think OCaml does have a few tricks to tell GC to not collect objects being shared through C ABI (https://ocaml.org/manual/5.3/intfc.html#s:c-gc-harmony), but I haven't looked into this enough to be sure.
Your projects will sure help me figure out the concepts!
Nice! Yeah, using protobufs seems reasonable. Re. GC, Racket has support for "freezing" values in place to prevent the GC from moving them, but freezing too many values can impact the GC's operation so I'd watch out for that if that's possible in OCaml.
Thank you for posting this here, and for all the corrections. I have updated the post with them.
My idea behind a mental model is to describe how I visualise a concept in my mind when I think about it.
The level of abstraction of a mental model is supposed to keep out the details of implementation, ensuring which I failed at. Because the deeper you dig from this level, the more inaccurate things will turn out to be.
A mental model for me is meant to be only a starting point for a concept.
In other words, the aim is such that when you think about a concept, its mental model should put you into the right perspective. Right, this is how something works, now let's check out the manual to see how I can put it to use.
I've now configured my site to add this explainer to a post classified under Mental model.
My purpose for the I/O sub-section was only to have a convenient logic of memorising how I/O operations work on soft links by default. Memorising techniques don't really gun for technical accuracy. However, I have struck it out because the logic is also blatantly inaccurate and has been rightly called out here.
> all data manipulation (∆) happens outside of React components,
I do something similar. State and its management lives outside the React components, which only consume this state via hooks. Keeping the state in chunks, and outside the tree lets me access a chunk of state only in the components that need it.
This results into minimum amount of re-rendering of components. Component code also looks cleaner and easier to read.
`okcontract/cells` seems to be missing one of the two FRP primitives though: streams (of events).
One of the best FRP environment for JavaScript is/was FlapJax: https://en.m.wikipedia.org/wiki/Flapjax, which incorporates both the primitives and their operations.
Do features like these, when implemented in Racket, consume more resources (battery, CPU, etc.) than if they were implemented using the native API equivalents (e.g., NSURLSession or whichever is more applicable)?
In the larger context of cross-platform apps with a common core written in a non-native programming stack, I often wonder this about network and disk I/O management. I understand using the native APIs for other "I/O" like UI, hardware interfaces (bluetooth, accelerometer, etc.), because they often don't have an equivalent API in the programming stack used to implement the common core.
As far as I know, Capacitor wraps over the native APIs.
reply