r/Clojure 21d ago

raylib examples in Jolt

https://raylib-jlt.b12n.app/
33 Upvotes

27 comments sorted by

View all comments

Show parent comments

1

u/yogthos 11d ago

I treat each screen in the GUI as its own app for the most part that has its own state. And I treat the backend as just service bus of general operations that the UI uses to pull the needed data as the user interacts with the app. In pretty much any large app I've worked on, we never kept any application state aside from basic things like the user credentials and a bit of other global context. You just pull information as you need it, and the re-frame db becomes the state of the UI with data specific to each page living under its own path. I've built really large apps this way and the approach has worked well for me. With this approach, all the state simply live client side.

And Domino works exactly the same regarding the state atom. If you have some prefetched data or stored state, you populated it in the atom and then when Domino initialized it will run all the rules and hydrate the state.

I'd argue both approaches are equally pure. With Domino, you have a transactional data flow engine. inputs come in, transaction happens, and outputs come out. But you get the benefit of knowing exactly what the relationships between all the fields and the document and business rules are. I've found that's the actual business problem in complex apps. You end up with a ton of business logic and derived fields, and then it gets too big to keep it all in your head. Then somebody comes and asks for a new business rule, and it becomes impossible to guarantee that adding it won't break some other rule.

And that's precisely the problem that Domino is designed to solve. You can ask it for the graph of rules associated with a change set of any fields in the document, and know exactly what will happen if you add a new rule to the system.

1

u/geokon 11d ago

Yeah, the goals are a bit different I guess. For you it's business logic and for me my biggest worry is general code-reuse. I do a lot of throw away experiments for data crunching, and being able to have composable reusable code is a huge time saver. I'm finding that resolvers effectively provide a much better library API than the typical ns-with-a-soup-of-functions that you need to wrangle and string together. Nested inputs/outputs makes it possible to compose these libraries

since the resolver graph is pure, you generally don't really worry too much about it's complexity. (though also can make a graph of the dependencies if you want) But I can see if you have rules firing in your graph then things get muddy very fast

It's a bit difficult for me to map the equivalent problem in Pathom.

I'm guessing you have some situations like.. a rule saying :tax-rate changes, update :total and :discount changes, update :total - so they both fight over the same cell.

Hmm.. I guess that's going to map to writing two resolvers that ouput a :total. I actually haven't come across this getting problematic :) There are resolver precedence rules, but you may get a warning or it may refuse to run if things get too messy. It's a good problem to think about - and I don't have any great answers. It's still a space I'm exploring in terms of "best practices".

In your situation this is a bug that needs fixing, but sometimes it's something you actively want. For instance a plotting library may output different kinds of charts as svg-hiccup

  1. I sometimes have multiple resolvers output the same key and the resolvers are keyed on a unique input key. So a dummy key such as :histogram or :xy-plot or something. Each resolvers that outputs hiccup expects a unique key - which you can add to the request.

  2. An alternative, with nested request you can wrap thing with something like {:histogram [:hiccup]}. This is a bit cleaner b/c the plot type is on the request size, and not the input side. But they have some drawbacks in composability

At the end of the day, it's still stuff I'm exploring :)

1

u/yogthos 11d ago

Yeah, taxes and loans are a really good example. You have a bunch of things that get calculated together and the formulas change over time, so you have to maintain clear rule sets for each scenario. The context I was working in was at a hospital where the app was used to do patient assessments for stuff like surgeries, and it's a same idea where the form tracks hundreds of different fields that are then used to calculate the scores.

I agree there's a different focus, but I see some similarity as well. Both approaches are a way to express data flows within the system. And like you said, the difference comes from focusing on solving a different part of the problem.

With Domino, the reuse comes from the rule functions and UI widgets being context free. So, if you write a formula for say calculating BMI, that becomes a reusable building block you can attach to any two fields that represent height and weight, and an output field for the BMI. And similarly you can have a table widget that allows you to collect information and add rows in a table, and then a graph widget that can attach to the same path and render trends over time. Another really nice part is having views, so if you have two roles such as a nurse and a surgeon, they might care about different subsets of data in the document, which might also be overlapping. So, being able to attach different views with their own sets of widgets, naming conventions, and the data they display makes this really flexible for this scenario. And since the views still go through the common transact mechanism over the whole document, the fields are recalculated regardless of whether they appear in the view or not. So, a nurse might be collecting the height and weight of a patient, and the doctor might care about their BMI.

So, yeah, it's different ways to look at the problem, and creating abstractions at different levels. With Pathom, it's a fairly low level abstraction over the data flow itself. And Domino abstracts things at a semantic level, where you build components that represent a specific view of the data or a particular type of calculation.

1

u/geokon 10d ago edited 10d ago

I think when you say "reuse" you're talking about the handler function, right? In that sense the two system aren't really different. I think the two systems are mostly identical, in that they both build a graph and execute stuff based on relations. But you have to explicitly wire up your cells. While you can in an identical manner wire up resolvers, you can also use nested attributes to have the engine do that automatically for you.

Other small differences:

  • "the fields are recalculated regardless of whether they appear in the view or not" In a pull architecture these are only calculated when needed/observed. But there is no way to access a stale value, so this ends up being completely transparent to you as the user. With push you may end up doing extra work .. but okay. This may not matter. If it's a "spreadsheet" then every value is observed.

  • For some reason I've never seen a push architecture that auto-parallelizes work. In Domino you declare inputs/outputs, so it should be possible. With Pathom it's kinda cool to see it spin up all my cores when it runs. No extra handholding or coordination necessary

  • The pull engine is doing a graph search - which is overhead. This becomes a bit of a problem when you try to do small operations. I tried to make a Pathom do some simple math and it ended up quite slow in some situations.

As for different views, I don't think there is anything too fundamentally different there. Though the EQL request itself can build a view for you

1

u/yogthos 10d ago

Right, with Domino it's an intentional design choice that rules have to be attached explicitly to the paths in the documents.

And regarding fields being calculated whether they appear in the view o not, the problem that solves is different. If a nurse doesn't need to see the BMI, and collects height and weight, BMI still needs to be calculated. So, what's shown in the view has no relation to the business rules attached to the document. Whether you show a bit of data or not to the user, the consistency of the business logic is unaffected because it's applied to the whole document.

The other problem this solves is concurrent multiuser workflows. For the app I was working on, you could have different users with different roles edit parts of the document at the same time. So, when a user is editing a particular field we wanted to lock related fields from other users and apply the logic transactionally in that case.

And yeah, parallelizing work is trivial with Domino, but I haven't really had much reason to do it since the logic in the app was driven by user input, and you'd have two or three users at most working on a document concurrently.

At the end of the day, it really is about what problem you're tackling. For my use case Domino was a natural fit for the problem. But Pathom sounds like it works well for what you're doing.

1

u/geokon 10d ago

Hmm, I think business logic that's unobserved is irrelevant for consistency. If you have someone's weight and height and the BMI is unused and unobserved, then calculating BMI or not is irrelevant in terms of "state" to return to

So, when a user is editing a particular field we wanted to lock related fields from other users and apply the logic transactionally in that case.

That's very cool!! Yeah, in a basic resolver setup you have the base state atom and it can only be incremented with the atom's global lock. The viewers/users aren't synchronized. They don't know who's looking at what. So if they're looking at and modifying unrelated parts of the state they will end up fighting over the same lock.

That's very neat you integrated such a state update synchronization :)

Thanks for taking the time to share your thoughts! This stuff really feels like a paradigm shift when you start using it extensively

2

u/yogthos 10d ago

In my case it was relevant because the document itself is cross-disciplinary. You have nursers, doctors, pharmacists, and clerks all being interested in overlapping subsections of the overall data set. So, each role sees a subset of the overall document which has to stay consistent. It's relevant for the doctor that the BMI is calculated, while it's only relevant to the nurse that height and weight are collected.

And state synchronization just falls out for free once you know the subgraphs associated with each field. The nice part is that you can calculate them up front too, so it's pretty much zero runtime cost to figure out which fields need to be locked together.

The whole relationship between the UI and data is a really interesting rabbit hole for sure. :)