fulcro

borkdude 2024-10-15T12:05:35.627789Z

@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

tony.kay 2024-10-15T14:40:50.402569Z

Good catch. Another feature of the library that I have not actually used 😛 Clearly missing an update there.

tony.kay 2024-10-15T14:53:11.790539Z

Fixed in 1.2.6, and added a test for it facepalm

borkdude 2024-10-15T14:53:44.298289Z

awesome

tony.kay 2024-10-15T14:53:57.205909Z

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 😄

borkdude 2024-10-15T14:54:08.103179Z

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 :)

tony.kay 2024-10-15T14:55:20.673089Z

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

borkdude 2024-10-15T14:56:00.161289Z

with "chart" should I think of something that is an image or plot?

tony.kay 2024-10-15T14:57:03.853689Z

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.

tony.kay 2024-10-15T14:57:31.449289Z

is the original paper

tony.kay 2024-10-15T14:57:55.242679Z

and then the w3c made a standard based on this https://www.w3.org/TR/scxml/

borkdude 2024-10-15T14:58:02.820679Z

ok, but does this lib create .png output or something or does it facilitate to run a state machine in your app?

tony.kay 2024-10-15T14:58:03.867579Z

which is what my implementation is based upon

tony.kay 2024-10-15T14:58:17.037129Z

no no…it is the system of logic that runs such a chart

borkdude 2024-10-15T14:58:23.057879Z

right

borkdude 2024-10-15T14:58:41.322189Z

the name "chart" gets me in the wrong expectation I think

tony.kay 2024-10-15T14:59:00.313079Z

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

borkdude 2024-10-15T14:59:07.340479Z

cool

borkdude 2024-10-15T14:59:24.088489Z

well, thanks for making these changes, I think we're well on our way to have this working in bb

tony.kay 2024-10-15T14:59:25.299209Z

The idea is that you can visually think about the logic (and write tools to go bidirectional on that)

👍 1
tony.kay 2024-10-15T14:59:45.563369Z

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

😆 1
michaelwhitford 2024-10-15T15:38:53.171509Z

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

borkdude 2024-10-15T15:45:55.470749Z

@michael819 ok cool. I ran bb with 1.2.6 from maven and it seemed to work

michaelwhitford 2024-10-15T15:46:56.930609Z

@borkdude Was it from the example repro repository I shared? Because in the bb.edn there I have it pointed at the fork.

borkdude 2024-10-15T15:47:12.344859Z

I changed the bb edn to use the deps.edn and changed it there

borkdude 2024-10-15T15:47:34.282819Z

borkdude 2024-10-15T15:47:53.469929Z

borkdude 2024-10-15T15:48:15.782149Z

at least I didn't get any runtime errors, but I can't judge whether any runtime results were the correct ones

michaelwhitford 2024-10-15T15:48:26.985459Z

Oh that is great news, thank you very much for taking the time on this.

michaelwhitford 2024-10-15T15:48:57.895399Z

On a side note how do you specify in bb.edn to use deps.edn? I have been maintaining 2 versions independently...

borkdude 2024-10-15T15:49:10.222619Z

see first screenshot

borkdude 2024-10-15T15:49:34.711249Z

the first entry in deps with local/root

michaelwhitford 2024-10-15T15:56:34.384609Z

Thanks again, I was able to duplicate that, just using statecharts 1.2.6 and bb works!

🎉 1
borkdude 2024-10-15T15:58:42.888139Z

👍

2024-10-15T03:11:15.594249Z

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).

2024-10-21T04:52:44.528679Z

Yep, a typo. I've updated it for anyone following along in the future. Thanks again!

2024-10-16T17:34:34.806419Z

This ended up working great for us:

(remote [env] (m/returning env (comp/registry-key->class `my.ns/MyComponent)))
Thank you!!!

🎉 1
tony.kay 2024-10-16T17:39:56.329999Z

That’s the intended (and easiest) pattern

👍 1
tony.kay 2024-10-16T17:40:31.358679Z

obvs you mean to have env in the call to returning…I’m guessing that’s just a typo in your response

janezj 2024-10-15T06:24:00.938939Z

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 :)

tony.kay 2024-10-15T06:39:51.917019Z

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

tony.kay 2024-10-15T06:40:31.202749Z

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.

tony.kay 2024-10-15T06:41:18.849879Z

and there is a component registry for looking up components to avoid the circular refs

tony.kay 2024-10-15T06:41:29.052539Z

https://book.fulcrologic.com/#_the_component_registry

michaelwhitford 2024-10-15T06:28:11.692489Z

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

tony.kay 2024-10-15T07:00:00.385479Z

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

tony.kay 2024-10-15T07:00:19.656989Z

If you use a script element instead, then you can return an op to assign that value

tony.kay 2024-10-15T07:00:35.536579Z

(return a vector of ops, one of which would be the assign you want)

michaelwhitford 2024-10-15T07:00:44.513499Z

so like [:op assign :data somedatahere]?

tony.kay 2024-10-15T07:02:23.545079Z

use the data-model.operations ns

tony.kay 2024-10-15T07:02:30.493199Z

there’s a wrapper function for that

tony.kay 2024-10-15T07:02:36.235549Z

but yes

tony.kay 2024-10-15T07:02:45.122689Z

[{:op :assign :data data}]

michaelwhitford 2024-10-15T07:02:50.557119Z

Yeah but that wrapper does not give me access to env

tony.kay 2024-10-15T07:03:03.653169Z

or better: [(ops/assign :location value)]

tony.kay 2024-10-15T07:03:20.154579Z

it’s the return value of a lambda

tony.kay 2024-10-15T07:03:38.644359Z

(script {:expr (fn [env] ... [(ops/assign ...)])})

tony.kay 2024-10-15T07:05:03.719479Z

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.

tony.kay 2024-10-15T07:07:28.872949Z

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"]

michaelwhitford 2024-10-15T07:07:35.667299Z

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.

tony.kay 2024-10-15T07:07:38.847439Z

so actually the chart is right

tony.kay 2024-10-15T07:07:47.598119Z

OH…yeah, the warning can be ignored

tony.kay 2024-10-15T07:08:01.083189Z

that is a bug, but it is harmless

michaelwhitford 2024-10-15T07:09:06.855599Z

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.

michaelwhitford 2024-10-15T07:09:32.743779Z

I was just trying to understand why it was warning, thank you for the explanation.

tony.kay 2024-10-15T07:10:05.471159Z

yeah, using seq will prevent it from detecting it as an ops list

tony.kay 2024-10-15T07:10:22.205619Z

the detection, as you can ssee above, looks for expressions to return a vector

michaelwhitford 2024-10-15T07:29:14.307519Z

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}))

tony.kay 2024-10-15T06:44:25.599539Z

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.

tony.kay 2024-10-15T06:45:13.020989Z

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.

michaelwhitford 2024-10-15T06:45:27.000569Z

It's not fulcro, stand alone.

tony.kay 2024-10-15T06:45:44.661819Z

yeah, so look at the implementation of the data model and expression engine you’re using.

tony.kay 2024-10-15T06:46:01.011559Z

The ones I wrote are not terribly complicated

michaelwhitford 2024-10-15T06:47:11.067909Z

All settings for the data model and expression engine are the defaults.

michaelwhitford 2024-10-15T06:48:08.165009Z

I'm trying to create a minimum repro statechart for babashka interop.

tony.kay 2024-10-15T06:48:14.150419Z

So what do you expect in args?

michaelwhitford 2024-10-15T06:48:52.032919Z

Args is a vector created from command line args.

tony.kay 2024-10-15T06:49:26.437309Z

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…

tony.kay 2024-10-15T06:49:32.993559Z

but like I said I don’t remember 😄

michaelwhitford 2024-10-15T06:49:45.023379Z

It's elements/assign, not operations/assign

tony.kay 2024-10-15T06:50:00.924569Z

I know that

tony.kay 2024-10-15T06:50:58.822339Z

(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))

michaelwhitford 2024-10-15T06:51:50.123189Z

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.

michaelwhitford 2024-10-15T06:53:39.285909Z

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"]

tony.kay 2024-10-15T06:53:52.513809Z

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)

tony.kay 2024-10-15T06:54:22.314889Z

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

tony.kay 2024-10-15T06:55:42.916899Z

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

tony.kay 2024-10-15T06:56:34.042939Z

so the data is expected to be a map, which is what the coercion in assign! does (path-value-pairs)

tony.kay 2024-10-15T06:58:23.252529Z

Ah, the lambda execution model is trying to use the return value AS an operation

michaelwhitford 2024-10-15T06:59:09.764039Z

I can swap it to a map, but then it changes my data-model