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.

Reply via email to