fulcro 2024-10-15

@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

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.

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

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

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)

👍 1

Thank you for being willing to dig into other ppls libraries to make your tooling better 😄

😆 1

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!

🎉 1

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!!!

🎉 1

That’s the intended (and easiest) pattern

👍 1

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 thing2

So, 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

[{: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 functin

so 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