@tony.kay I see a couple of vswap! usages in the statecharts lib that will never work out at runtime. It was detected by SCI which detects at "compile" time whether a keyword is called with 1 or 2 arguments and throws on the rest This happens here: [1] https://github.com/fulcrologic/statecharts/blob/8f0251807824b404b3d672162104f97092e1cc47/src/main/com/fulcrologic/statecharts/data_model/working_memory_data_model.cljc#L68 [2] https://github.com/fulcrologic/statecharts/blob/8f0251807824b404b3d672162104f97092e1cc47/src/main/com/fulcrologic/statecharts/data_model/working_memory_data_model.cljc#L159 My guess would be, looking at the rest of the code, that the state-id argument is the one that shouldn't be there, but that's just a guess
Good catch. Another feature of the library that I have not actually used đ Clearly missing an update there.
Fixed in 1.2.6, and added a test for it facepalm
awesome
Itâs an odd feature for statecharts in CLJ, and I threw it in because it is part of the SCXML specâŚbut Iâve never wanted/needed it. But it should actually work đ
btw, I was looking at this because someone tried to use statecharts in bb. I have no idea what the library is for to be honest :)
Itâs an implementation of statecharts for CLJ and CLJS. Harel did the original research on them, and then they made their way into UML and other things. They are hierarchical state machines. Very useful for creating the logic of systems without the state explosion of FSM
with "chart" should I think of something that is an image or plot?
I use it for all sorts of things: ⢠Complex UI sequencesâŚe.g. login sequences, where thereâs a ton of async kinds of things like onboarding and such, change password, agree to terms, etc. ⢠My back-end subscription system, which needs long-term tracking of the userâs state, expiration dates, grace periods for payments, etc.
https://www.sciencedirect.com/science/article/pii/0167642387900359
is the original paper
and then the w3c made a standard based on this https://www.w3.org/TR/scxml/
ok, but does this lib create .png output or something or does it facilitate to run a state machine in your app?
which is what my implementation is based upon
no noâŚit is the system of logic that runs such a chart
right
the name "chart" gets me in the wrong expectation I think
there are tools that can take you from a diagram TO the XML (for SCXML), and Iâve got a partially-written thing that can take a clojure chart (in code) into a form where you can use the xstate graphical tool to visualize it
cool
well, thanks for making these changes, I think we're well on our way to have this working in bb
The idea is that you can visually think about the logic (and write tools to go bidirectional on that)
Thank you for being willing to dig into other ppls libraries to make your tooling better đ
That was me that asked about bb support for statecharts. Technically for this to work I had to fork the statecharts library and upgrade the encore and timbre deps to the newest versions: com.taoensso/timbre {:mvn/version "6.6.0-RC1"} com.taoensso/encore {:mvn/version "3.122.0"} https://github.com/michaelwhitford/statecharts/tree/mw_update_deps
@michael819 ok cool. I ran bb with 1.2.6 from maven and it seemed to work
@borkdude Was it from the example repro repository I shared? Because in the bb.edn there I have it pointed at the fork.
I changed the bb edn to use the deps.edn and changed it there
at least I didn't get any runtime errors, but I can't judge whether any runtime results were the correct ones
Oh that is great news, thank you very much for taking the time on this.
On a side note how do you specify in bb.edn to use deps.edn? I have been maintaining 2 versions independently...
see first screenshot
the first entry in deps with local/root
Thanks again, I was able to duplicate that, just using statecharts 1.2.6 and bb works!
đ
I suspect I'm missing something basic here. I have a defmutation in a model file for a RAD project. It returns the data I expect, but I need to merge it back into the client db. What's the best way to do this? Here are some things I've explored:
(swap! state assoc-in [:message/id :singleton] mutation-return-value): This works great (even updates the UI), but the docs say to not call swap! directly because it isn't watched (source: https://book.fulcrologic.com/#_evolving_the_graph).
(merge/merge-component! app MyComponent mutation-return-value): This works great when run manually, but when it's in my model file it results in a circular dependency because my component needs to refer to this mutation when loading. I suppose I could pull this single mutation into the same file as the component, but then I don't think I could have my cljs mutations next to the clj side, which seems confusing.
(merge/merge! app mutation-return-value query): I haven't gotten this one to work becuase I'm not sure what query should be. Using the result of comp/get-query on the component doesn't seem to work. Additionally, it sounds like this function should be avoided (https://clojurians.slack.com/archives/C68M60S4F/p1608331819160600?thread_ts=1608327854.149900&cid=C68M60S4F).
It's probably worth noting, the component that displays this data is a normal defsc component (not RAD).
Yep, a typo. I've updated it for anyone following along in the future. Thanks again!
This ended up working great for us:
(remote [env] (m/returning env (comp/registry-key->class `my.ns/MyComponent)))
Thank you!!!Thatâs the intended (and easiest) pattern
obvs you mean to have env in the call to returningâŚIâm guessing thatâs just a typo in your response
swap! in mutation action is fine for simple update. Tony said that in the thread :Yes, if you mean âmutation actionsâ merge! is to normalize data, to flip a flag swap is fine. ;;I hope so, otherwise I will have to change a lot of code :)
umâŚâit returns the data I expectââŚ.do you mean that it is a remote mutation returning a value? See m/returning and the remote section of the client side of the mutation, and this https://book.fulcrologic.com/#ASTMutationJoins
The only thing you have to do is return a map that has the exact shape of the component that youâve stated in returning.
and there is a component registry for looking up components to avoid the circular refs
I am using the com.fulcrologic.statecharts library. I am seeing something I don't understand. With the following state:
(state {:id :app/middle}
(on-entry {}
; assign arguments into local memory
(assign {:location [:arguments]
; why does seq work here but vec does not?!?!
:expr (fn [{:keys [args] :as env} data] (seq args))}))
(on-exit {}
; tap after assignment
(script-fn [env data]
(tap> {:from :app/middle :env env :data data})))
(transition {:target :app/end}))
Why does the args vector here require seq? If I leave off seq, I get warnings like:
2024-10-15T06:30:21.932Z esmerelda WARN [com.fulcrologic.statecharts.data-model.working-memory-data-model:116] - Operation not understood thing1
2024-10-15T06:30:21.932Z esmerelda WARN [com.fulcrologic.statecharts.data-model.working-memory-data-model:116] - Operation not understood thing2So, thatâs technically a bug. The result of the expression from a script should be used as ops, but because the code to do that is i nthe execution model (which it should not be đ ) youâll have to work around it
If you use a script element instead, then you can return an op to assign that value
(return a vector of ops, one of which would be the assign you want)
so like [:op assign :data somedatahere]?
use the data-model.operations ns
thereâs a wrapper function for that
but yes
[{:op :assign :data data}]
Yeah but that wrapper does not give me access to env
or better: [(ops/assign :location value)]
itâs the return value of a lambda
(script {:expr (fn [env] ... [(ops/assign ...)])})On your assign element: technically youâll get the warning, but since the internal code is:
(defmethod execute-element-content! :assign [env {:keys [location expr]}]
(let [v (run-expression! env expr)]
(log/trace "Assign" location " = " v)
(env/assign! env location v)))
the two warnings about trying to use your data as ops should not prevent it from working, so I am a little puzzled as to why youâre having the problem still.In fact your logs say that it tried:
2024-10-15T06:30:21.933Z esmerelda TRACE [com.fulcrologic.statecharts.algorithms.v20150901-impl:284] - Assign [:arguments] = ["thing1" "thing2"]It's not exactly a problem, the working memory gets updated, but I see the warning and was trying to understand why it sees it as an operation like that.
so actually the chart is right
OHâŚyeah, the warning can be ignored
that is a bug, but it is harmless
I thought it was causing the babashka error I see on vswap! but since I added seq it quieted the warning but babashka still warns an vswap! for the volatile.
I was just trying to understand why it was warning, thank you for the explanation.
yeah, using seq will prevent it from detecting it as an ops list
the detection, as you can ssee above, looks for expressions to return a vector
Thank you for the explanation this is now working as expected without warnings:
(state {:id :app/middle}
(on-entry {}
; assign arguments into local memory
(script-fn [{:keys [args] :as env} data]
[(ops/assign :arguments args)]))
(on-exit {}
; tap after assignment
(script-fn [env data]
(tap> {:from :app/middle :env env :data data})))
(transition {:target :app/end}))Good questionâŚI donât have an off-hand answer. I donât use the assign node, so it could just be that my handler for it has a bug đ . I pretty much just use script for everything, and return a vector of ops from itâŚ.Now, it IS true that the return of your script should be a vector (of ops) or nil.
You also didnât mention if youâre using Fulcro integration, or how you set up things. The expression engine and data model are pluggable.
It's not fulcro, stand alone.
yeah, so look at the implementation of the data model and expression engine youâre using.
The ones I wrote are not terribly complicated
All settings for the data model and expression engine are the defaults.
I'm trying to create a minimum repro statechart for babashka interop.
So what do you expect in args?
Args is a vector created from command line args.
because the complaint âoperation not understoodâ is a message from the operation multimethodâŚI would not have expected assign to want you to return opsâŚ
but like I said I donât remember đ
It's elements/assign, not operations/assign
I know that
(defmethod run-op :assign [all-data context-id {:keys [data]}]
(reduce-kv
(fn [acc path value]
(cond
(and context-id (keyword? path))
(do
(log/trace "Assigning" value "to" [context-id path])
(assoc-in acc [context-id path] value))
(keyword? path)
(do
(log/error "Internal error: Unknown context for assignment to" path)
acc)
(and (vector? path) (= (count path) 2))
(do
(log/trace "Assigning" value "to" path)
(assoc-in acc path value))
:else (do
(log/warn "Cannot assign value. Illegal path expression" path)
acc)))
all-data
data))
I will create the repo for this babashka repro. With encore recent upgrades I think statecharts might work in babashka. The error I am getting from babashka is now volatile related from the guts of statecharts, instead of encore related at least.
2024-10-15T06:30:21.931Z esmerelda TRACE [com.fulcrologic.statecharts.algorithms.v20150901-impl:81] - Running expression #function[example-statechart/fn--37692]
2024-10-15T06:30:21.932Z esmerelda TRACE [com.fulcrologic.statecharts.execution-model.lambda:15] - expr => => ["thing1" "thing2"]
2024-10-15T06:30:21.932Z esmerelda TRACE [com.fulcrologic.statecharts.execution-model.lambda:22] - trying vector result as a data model update ["thing1" "thing2"]
2024-10-15T06:30:21.932Z esmerelda WARN [com.fulcrologic.statecharts.data-model.working-memory-data-model:116] - Operation not understood thing1
2024-10-15T06:30:21.932Z esmerelda WARN [com.fulcrologic.statecharts.data-model.working-memory-data-model:116] - Operation not understood thing2
2024-10-15T06:30:21.932Z esmerelda TRACE [com.fulcrologic.statecharts.algorithms.v20150901-impl:82] - (sp/run-expression! execution-model env expr) => ["thing1" "thing2"]
2024-10-15T06:30:21.933Z esmerelda TRACE [com.fulcrologic.statecharts.algorithms.v20150901-impl:284] - Assign [:arguments] = ["thing1" "thing2"]The assign node runs this internal code:
(defn assign!
"Side effect against the data model in `env`, with the given path-value pairs."
[{::sc/keys [data-model] :as env} & {:as path-value-pairs}]
(sp/update! data-model env {:ops (ops/set-map-ops path-value-pairs)})
nil)
traceback to :
(defmethod execute-element-content! :assign [env {:keys [location expr]}]
(let [v (run-expression! env expr)]
(log/trace "Assign" location " = " v)
(env/assign! env location v)))
and
(defn set-map-ops
"Returns an operation that will set all of the k-v pairs from `m` into the
data model."
[m]
[{:op :assign
:data m}])
turns into an assign, which runs that first functinso the data is expected to be a map, which is what the coercion in assign! does (path-value-pairs)
Ah, the lambda execution model is trying to use the return value AS an operation
I can swap it to a map, but then it changes my data-model