Hey Leon It was really a matter of practicalities.
There doesn't seem to be hook to inject derived state into OM cleanly just now. I have tried various other approaches and my code was much less managable. This approach is proving really convenient. One really strong win with this approach is that derived state happens before OM snapshots state to decide what's changed. Before I had this I had the issue of having to manually observe many paths to ensure that components updated. Now it just happens. You might need to have experienced the pain to fully appreciate that. It's big. The change to OM would be small but I figure it's better to prove the approach in some code rather than ask for extensions. I think the change would be either a new interface so that OM can derive state when fetching the atom's value. Somewhere around here <https://github.com/omcljs/om/blob/master/src/om/core.cljs#L251> is the deref I needed to inject my derived state. As for memoize-last. Well, memoize seems like a memory leak which worried me in a browser... no way to clear out old cached info. CLJS doesn't seem have a LRU memo function just yet (at least I couldn't get core.memoize to work). memoize-last just happens to fit perfectly with this particular use case. On Mon, Feb 16, 2015 at 9:16 PM, Leon Grapenthin <[email protected]> wrote: > Can you elaborate why you implement a new IDeref? Why not just calculate > and pass the derived state during rendering? Memoization in any fashion > could still happen. > > Also, is memoize-last just intended as an optimzation to consume less > memory, or does it serve another purpose? > > On Sunday, February 15, 2015 at 11:58:17 PM UTC+1, Oliver George wrote: > > My approach to this stuff is to think in terms of state and derived > state. > > > > > > > > I have a few helpers to facilitate implementing that which amounts to > > Use an atom for stateUse a function to derive stateUse a simple cache to > reduce load of derivation in render loopHack deref to make atom pass back > derived-state > > If you're new to OM I'd encourage you not to get carried away with > picking up dodgy bits of code like the ones I'm sharing here until you're > confident you're making best use of OM as it comes out of the box. > > > > > > That being said... > > > > > > Here's my derived-atom! function. > > > > > > > > > > (defn derived-atom! > > > > > > " > > > > > > This allows us to access derived values associated with atom state. > > > > > > > > > > > > > > The implementation of atom does not use deref so we aren't interfering > > > > > > with how state transitions, they use .-state to access the pure > attribute > > > > > > value. > > > > > > > > > > > > > > The implementation of om ref cursors use deref to resolve the current > value > > > > > > of state so we are able to apply our logic and have the result > available > > > > > > via (value c) which means reference cursors will trigger updates based > > > > > > on changes to derived data too. > > > > > > " > > > > > > [iref derived-fn] > > > > > > (specify! iref > > > > > > IDeref (-deref [_] (derived-fn (.-state iref)))) > > > > > > iref) > > > > > > > > This is how i used it as my om app-state > > > > > > > > > > (defonce app-state (derived-atom! (atom {}) (memoize-last > derived-state))) > > > > > > > > memoize-last is a cutdown version of memoize which just remembers the > last version. That essentially means that we only calculate derived-state > once for each OM state > > > > > > > > > > (defn memoize-last > > > > > > "Returns a memoized version of a referentially transparent function. > > > > > > > > > > > > > > The memoized version of the function keeps a cache of the *most recent > call* > > > > > > from arguments to result. > > > > > > " > > > > > > [f] > > > > > > (let [mem (atom {}) > > > > > > lookup-sentinel (js-obj)] > > > > > > (fn [& args] > > > > > > (let [v (get @mem args lookup-sentinel)] > > > > > > (if (identical? v lookup-sentinel) > > > > > > (let [ret (apply f args)] > > > > > > (reset! mem {args ret}) > > > > > > ret) > > > > > > v))))) > > > > > > > > Finally, here's me using derived state logic to do things like lookups, > validation and other things. It's all very app specific but amounts to > doing (assoc-in state [:a :b] :something) a lot. At this risk of confusing > things here's my generalised required field validation logic which is > implemented as derived state. > > > > > > > > > > (def empty-values #{nil "" [] {} #{}}) > > > > > > > > > > > > > > (defn validate-required-field [field] > > > > > > (let [{:keys [required value errors]} field] > > > > > > (if (and required (contains? empty-values value)) > > > > > > (assoc field :errors (conj errors "This field is required")) > > > > > > field))) > > > > > > > > > > > > (defn is-required-field? > > > > > > "Identifies walker nodes which are fields relevant to require logic" > > > > > > [m] > > > > > > (and (map? m) > > > > > > (contains? m :required) > > > > > > (contains? m :value))) > > > > > > > > > > > > > > (defn validate-required-fields > > > > > > "Derive errors associated with missing required fields" > > > > > > [state] > > > > > > (postwalk > > > > > > #(if (is-required-field? %) (validate-required-field %) %) > > > > > > state)) > > > > > > > > > > > > > > (defn derived-state > > > > > > "Used to include derived state for use by components." > > > > > > [state] (-> state > > > > > > ;derive-vertical-required > > > > > > ;end-position-logic > > > > > > ;maint-freq-logic > > > > > > validate-required-fields)) > > > > > > > > > > > > > > > > > > > > > > > > On Mon, Feb 16, 2015 at 4:17 AM, Leon Grapenthin <[email protected]> > wrote: > > If they were changing, you could implement your own reference system, > where you have one stateful lookup map that you make globally available via > a ref cursor. Instead of the actual changing values, you'd use lookup keys > in your rendered cursors. Changes to values in the stateful lookup map > would would reflect everywhere they are looked up. > > > > > > > > > > > > On Sunday, February 15, 2015 at 3:54:36 PM UTC+1, Scott Nelson wrote: > > > > > I’m working on an Om application that includes a music browser and > player with a simple artist -> albums -> songs hierarchy. When a user is > viewing an artist section I layout all the artist’s albums and songs > hierarchically and this works great since there is basically a 1-to-1 > correspondence between the component hierarchy and the data hierarchy. > > > > > > > > > > Here’s a basic example of what a piece of the application state might > look like under some sort of :current-artist key: > > > > > > > > > > {:name “The Beatles” > > > > > :albums [{:name “Abbey Road”, > > > > > :songs [{:name “Come Together”} > > > > > {:name “Something”}]} > > > > > {:name "Rubber Soul", > > > > > :songs [{:name "Drive My Car"} > > > > > {:name "Norwegian Wood"}]}]} > > > > > > > > > > When a user plays one of these songs I build an ordered playlist of > all the artist’s songs. There is a player component that plays the > playlist (even if the user navigates away from the current artist section) > and displays the currently playing artist, album and song name. To achieve > this I ended up duplicating a bunch of artist/album/song data elsewhere in > the application state. > > > > > > > > > > Here’s what the :playlist path of the application state might look > like after a user clicked the play button next to “Drive My Car”: > > > > > > > > > > {:play-index 2 > > > > > :songs [{:name “Come Together” > > > > > :album {:name “Abbey Road” > > > > > :artist {:name "The Beatles"}}} > > > > > {:name “Something” > > > > > :album {:name “Abbey Road” > > > > > :artist {:name "The Beatles"}}} > > > > > {:name “Drive My Car” > > > > > :album {:name “Rubber Soul” > > > > > :artist {:name "The Beatles"}}} > > > > > {:name “Norwegian Wood” > > > > > :album {:name "Rubber Soul" > > > > > :artist {:name "The Beatles"}}}]} > > > > > > > > > > I think this sort of denormalization works fine in my case since the > artist/album/song data is not changing but I'm wondering if there is a > better way to accomplish this. If the data were changing then I would > imagine it could become a challenge to keep the playlist data in sync. > > > > > > > > -- > > > > Note that posts from new members are moderated - please be patient with > your first post. > > > > --- > > > > You received this message because you are subscribed to the Google > Groups "ClojureScript" group. > > > > To unsubscribe from this group and stop receiving emails from it, send > an email to [email protected]. > > > > To post to this group, send email to [email protected]. > > > > Visit this group at http://groups.google.com/group/clojurescript. > > > > > > > > > > > > -- > > > > > > Oliver George > > Director, Condense > > 0428 740 978 > > -- > Note that posts from new members are moderated - please be patient with > your first post. > --- > You received this message because you are subscribed to the Google Groups > "ClojureScript" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send email to [email protected]. > Visit this group at http://groups.google.com/group/clojurescript. > -- Oliver George Director, Condense 0428 740 978 -- Note that posts from new members are moderated - please be patient with your first post. --- You received this message because you are subscribed to the Google Groups "ClojureScript" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at http://groups.google.com/group/clojurescript.
