Fork me on GitHub

I am trying out cljs 1.9.671 and getting this warning for:

(let [mycell (cell :a)]
      (case-tpl mycell
        :a "hello"
        :b "world"
WARNING: Wrong number of args (0) passed to cljs.core/atom


@flyboarder i'm only up to 1.9.521


waiting for the spec.alpha stuff to die down a bit


i've had a lot of problems in general trying to stay on the latest cljs version so i only update occasionally


Ok, I wanted to try up to 1.9.660 for faster build times. 1.9.660 seems to cut it by 15 seconds!


1.9.660 Doesn't give that warning on the case-tpl. It does give some warnins about hoplon.core and ui.attrs.


Does anyone have insight into why the last 2 elements don't update?

    [ data            (cell [1 2 3])
      query-fn        (fn [] (last @data))
      reactive-query  (cell= (query-fn))
      update-data     (fn [] (swap! data #(conj % 7)))]
      (elem :click #(update-data) "Update Data")
      (elem reactive-query)
      (elem (cell= (query-fn)))])
If I pass the cell explicitly in the formula it updates. But if the cell is buried in the called (query) function, it doesn't.


@chromalchemy you want to refactor and not have children in cells


Really you want their state in cells and use template macros to expand that state on an element


@chromalchemy the cell= macro can only see the symbols inside it, it's not recursively looking through all the called functions and trying to figure out whether the data in their scope is the same as data in the macro's scope


if you want to have a function trigger when data changes, use do-watch or just a regular add-watch


just make your query-fn take data as an argument


hoplon works a lot better if you try to write everything so that you pass in arguments rather than try to couple things to the scope you happen to be writing them in


    [ data            (cell [1 2 3])
      query-fn        (fn [data] (last data))
      reactive-query  (cell= (query-fn data))
      update-data     (fn [] (swap! data #(conj % 7)))]
      (elem :click #(update-data) "Update Data")
      (elem reactive-query)
      (elem (cell= (query-fn data)))])


Thanks guys! @thedavidmeister I simply passed the data as an argument, as you said, to fix the bug. I'll look at do-watch and add-watch too. It was a subtle bug that took me a long time to identify because everything would render properly on page load and reload, just no reactive update 😣


@chromalchemy my personal rule is to try and never rely on state/data outside the scope of the function i'm working on


as well as avoiding bugs like this, it means that a huge amount of my work is portable to other projects too


cell= is a macro, it can only see the cells in the immediate expansion of the code within itself


I agree with the David (meister) here πŸ™‚


Good point. I'll take that to heart. Isn't that one of the proposed benefits of Javelin though, referencing state in a more flat way?


not sure what you mean by "more flat way"


it’s just a consequence of how macros work


I don't have good intuition about how macros expand. Is defc= any different?


state and scope are different things


definitely you can chunk your state up into little re-usable and composable bits


but scope is a whole other discussion


generally no, javelin isn't encouraging global scope, or avoiding referential transparency in your functions


actually it works 10x better if you leverage local scope as much as possible


@thedavidmeister I guess what I meant by "flat" is that if you define a cell a the top level, you can reference it anywhere, like a global let, kinda. So is that cell outside the scope of some other function you define further down the page?


that's not really the goal, no


of course you can do that


but it has downsides and the upsides tend to fade away as you get into more complex setups


the idea is that you can take the state of your program, which is really just data describing "what is going on right now"


and instead of having either this mega thing that's super complicated and dangerous (because everything can edit everything else)


or having state implicit in the behaviour of your application (all kinds of bad things happen)


you can break the state down into small chunks of read/write data cell and pure input/output functions cell=


all the links between the cells and functions can get realllly complicated quite fast


but you don't have to worry because javelin is doing the plumbing for you


traditionally you have to bind events or something similar to keep your incoming data, processing and desired DOM mutations all in sync


and hand wire them all together, it takes a long time to do that by hand and is very error prone


the situation is even worse if you don't adopt 1-way data flow and treat the DOM you're mutating as a source of incoming data as the "outputs" and "inputs" get all tangled together and you end up with some pretty crazy bugs


so that's the goal of javelin πŸ™‚


the problems caused by writing functions that "reach out" to external scope will be the same no matter what framework you use unfortunately


- no way to test/predict the behaviour of a function just by looking at it in isolation


- functions are tied to the structure and original context of the app they were written in


- can't compose functions together within the same app without refactoring their internals or arguments every time you change something


@thedavidmeister Thank you for the thoughtful (re)orientation. This is something I have wrestled with, especially as my app grows in complexity. I have started leaning on Specter functions to edit a "mega thing" map that can be trivially synced to Firebase. The Specter macros don't expand properly inside formula cells, which is why I have seperate "query" functions, that I call inside the formula. But I think this is maybe a hint of not pushing the Javelin dataflow model far enough, making the working state more atomic, and making the master state that I want to serialize, the final result of the javelin flow, reactively building back up the state that was split apart at the beginning of the flow. In any case, I will try to better adopt the best practices you outlined, to better leverage simplicity, composability, testability, etc.


i did the same thing you did then refactored my whole app at some point...


you should still be able to write query functions, i think


just pass the data as an argument


i have query functions for datascript and i save datoms to my backing db


seems along the lines of what you described


Good to know that I'm not too nieve:grin: I guess I was concerned that If I enact both decomposition and re-composition of a data structure in a set of javelin cells, it would in effect double my working memory footprint and lead to performance hangups and radically constrain how much data I could work with in the client.. How does that intuition square?


why do you have to do this recomposition?


if you're using something to represent a database


you can run queries against it with javelin cells


and then write to the database more easily with something like do-watch! rather than cell=


or just against event handlers like :input


Ex. I have a set of maps that I can sync to FB. That's the db. Even if I filter out some of the maps into a subset with a formula cell and a query function, I still "write" changes to the master set, with a "setter" function on a :click attr. Or maybe put that "setter function into the formula cell to create a lens.


yeah either seems fine


but a lens already has read and write in it


why have two different sets of cells when you could just turn the read only cells into lenses


as both read and write are just functions i don't see a huge impact on memory just from that?


I think originally I loved the Idea of splitting all state into simple reactive bits via javelin. But I couldn't get my head around how I could package the state up for serialization, then re-hydrate the dataflow from dumb deserialization. I think my thinking had not evolved to consider Javelin lenses as the obvious solution. Maybe cause I was wary of writing crafty "setter" functions, when working with the split-up javelin data is so simple and obvious (the lens functions typically have to go levels deeper...?). That's one reason I'm attracted to Specter. It rebuilds the original data structure for you, with transformations inlined.


oh, well give it a shot then


i haven't used Specter


i tend to go with the simplest solution i can think of until i run into a performance issue


then i profile it and see what i can do πŸ™‚


It works, I think I'm muddying the water a bit though. I think your right, getting a better working sense of the lens side of Javelin will streamline my code and avoid a lot of this state composition confusion.


i use lenses a lot


but i keep the write side of things very "dumb"


usually just some simple data wrangling, like juggling strings and keywords or whatever


Is this statement accurate?: In a Javelin lens, the read (getter) function and the write (setter) functions, can have totally different working data and architectural focus. So when you define them both in the same formula-cell, you can be threading very different things into one function declaration. While this composition is valuable, promoting succinct expressions downstream, it can be a hurdle for readability and understanding how the dataflow is working, considering each lens can have this large conceptual duality.


but it's no worse than a lot of other techniques that are considered "normal" that are floating around πŸ˜›


i tend to use it in pretty obvious ways


(let [c (j/cell false)]
 (j/cell= c #(reset! c (boolean %))))


and even more so, i tend to wrap up the more re-usable lenses in functions


so, while a boolean lens is pretty contrived, i'd write it more like


(defn boolean-lens
 (j/cell= c #(reset! c (boolean %))))


here's a more realistic example


(defn throttle-cell
 "Returns a javelin cell that will only update its value at most once per X ms"
 ([v ms] (throttle-cell v ms (j/cell false)))
 ([v ms locked?]
  {:pre [(number? ms) (j/cell? locked?)]}
  (let [c (j/cell v)
        lock! #(reset! locked? true)
        unlock! #(reset! locked? false)]
   ; Set the lock whenever c changes.
   (add-watch c (gensym) lock!)
   ; Unlock automatically after ms.
   (add-watch locked? (gensym) (fn [_ _ _ n] (when n (h/with-timeout ms (unlock!)))))
   ; Return the throttled cell.
   (j/cell= c #(when-not @locked? (reset! c %))))))


what's happening to the data being dropped by the throttle is not clear, i suppose...


Cool. Thx for the examples, and filling out the context. I will go about upping my (composable) lens game! πŸ€‘


yeah, i avoided it for a long time


now i kind of wish i'd just bit the bullet earlier and forced myself to learn it


@thedavidmeister so you know, I also added the history-cell to hoplon/brew there could also be an HTML5History version if we wanted