Fork me on GitHub
#hoplon
<
2017-03-11
>
chromalchemy14:03:43

I've been trying to use Specter to transform and filter data structures that I'm going to use in my UI. So far the state management functions are performing great, even within Javelin formula cells. But I ran into a wall 😬 Seems like any formula cells that rely on Specter code for their output will not update reactively in the dom. They render their initial values properly on pageload. Printing their results to the console works fine in the normal reactive way. When I define a formula cell with specter code in the formula. I can't use the (defc=) or (cell=) macros, getting the error "formula expansion contains unsupported def form" But by using the (formula) fn It seemed to work. Here is my test code:

(defc seed [1])
(defc= sum (apply + seed))

; (defc= transformula (s/transform [s/ALL] inc seed))
  ; doesn't compile, error: "formula expansion contains unsupported def form"))

; (defn transformula []
;   (cell= (s/transform [s/ALL] inc seed)))
    ; doesn't compile, same error as above))

(defn transformula []
  ( (formula
      #(s/transform [s/ALL] inc %))
    seed))

; (defc= trans-sum (apply + @(transformula)))
  ;;; doesn't update in console output

(defn trans-sum []
  ( (formula
      #(apply + %))
    @(transformula)))

(cell=
  (do
    (.clear js/console)
    (prn seed)
    (prn sum)
    (prn @(transformula))
    (prn @(trans-sum))))
;;; all functions update properly in console

  
(elem :p 20 :gv 10
  (elem :sh (r 1 1)
        :click #(reset! seed (conj @seed 1))
        "seed: " (cell= (str seed)))
        
  (elem :sh (r 1 1)
        :click #(reset! seed [1])
        "sum: " sum)
        
  (elem :sh (r 1 1)
        "transformula: "
          ; @(transformula)
            ; doesn't update in dom
          ; (cell= (str @(transformula)))))
            ; doesn't update in dom
          @((formula #(str %)) (transformula)))
           ; still doesn't update in dom

  (elem :sh (r 1 1)
        :click #(reset! seed [1])
        "trans-sum: "
        @(trans-sum)))
           ;doesnt update in dom, same as above...
I'm using UI for the rendering code. But I imagine it would be the same errors with vanilla Hoplon (should test this tho)

chromalchemy15:03:17

Specter uses macros (https://github.com/nathanmarz/specter/wiki/List-of-Macros), and has some kind of caching mechanism to optimize performance (path precompilation, etc). https://github.com/nathanmarz/specter/wiki/Specter's-inline-caching-implementation https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs. I hope that's not a dealbreaker to use with Hoplon. I would like to use them both together. I appreaciate Javelin's bottom-up state-building reactive ways. But I'm fond of Specter's top-down declarative data wizardry. I understand the Clojure experts prefer not too much magic, but I feel empowered by it, as a lot of "simple" data-structure wrangling code still looks like Klingon to me 🙃 Thanks for any insight!

chromalchemy15:03:51

I got it updating in the dom!! seems there were some unnecessary derefs in the mix.

chromalchemy16:03:05

This works:

(defc seed [1])

(defn transformula []
  ( (formula
      #(transform [ALL] inc %))
    seed))

(cell=
  (do
    (.clear js/console)
    (prn seed)
    (prn @(transformula))))

(elem :p 20 :gv 10

  (elem :sh (r 1 1)
        :click #(swap! seed conj 1)
        "seed: " (cell= seed))

  (elem :sh (r 1 1)
        "transformula: " (transformula)))
But I'm still a bit confused why (transformula) has to be derefed to print to console, even though it is wrapped in (cell=) (cell= (prn @(transformula) prints and updates normally And why if @(transformula) is not required in an (elem) and doesn't update in the dom when derefed there. And why (elem (cell= @(transformula) does not update in the dom, like it does in the console?

chromalchemy16:03:54

Looks like if I don't define the Specter function within a formula cell, then I can use the Javelin formula cell macros. This is working:

(defc seed [1])

(defn specter-trans [v]
 (transform [ALL] inc v))

(defc= transformula
  (specter-trans seed))

(cell=
  (do
    (.clear js/console)
    (prn seed)
    (prn transformula)))

(elem :p 20 :gv 10

  (elem :sh (r 1 1)
        :click #(swap! seed conj 1)
        "seed: "  seed))

  (elem :sh (r 1 1)
        "transformula: " transformula))

  (elem :sh (r 1 1)
        "transformula in another formula: " (cell= (conj transformula 100))

alandipert16:03:36

@chromalchemy are you aware of the javelin.core/formula function?

chromalchemy16:03:12

Yes, I've been using it, It was the only thing that let me code Specter functions directly in a formula cell. If I did (cell= (s/fn ...)) in would say "formula expansion contains unsupported def form"

chromalchemy16:03:02

That got me going. But it made the code a lot messier, and I had to keep using (formula..) when composing values in the (elems) or they wouldn't update reactively.

alandipert16:03:17

i see, transform is a macro

alandipert16:03:04

the key to smooth javelin interop is finding the function that does what you want

alandipert16:03:21

i can see how you got around it by making a function of your own, to "hide" the macro

alandipert16:03:26

another legit way

chromalchemy16:03:49

Yes, it is a macro. It seems like things are working now, as long as I define my specter functions outside of formulae. And don't get tripped up by the subtleties where/when to apply deref.

alandipert16:03:04

if the clj versino of specter is any indication, there is a function directly underneath

alandipert16:03:31

ie (transform [ALL] inc [1 2 3]) expands to (com.rpl.specter.impl/compiled-transform* (com.rpl.specter/path [com.rpl.specter/ALL]) inc [1 2 3])

chromalchemy16:03:10

probably part of it's precompilation/optimization scheme

alandipert16:03:43

which means you could do i.e. (defn transform-cell [& args] (apply (formula compiled-transform*) args))

alandipert16:03:05

and then do (def transformula (transform-cell [ALL] inc seed))

alandipert16:03:33

anyway yeah, it all depends on the cljs impl i suppose

alandipert16:03:43

hopefully there is some clear, de-optimized way to call a function that does the specter things

chromalchemy16:03:22

Hmm. Is that like deconstrucing the macro to fit the javelin cell better?

alandipert16:03:55

yeah - macros have composition problems in general

alandipert16:03:09

so combining two libraries at their respective macro veneer levels will be pain

alandipert16:03:19

esp. in cljs

alandipert16:03:29

hence the search for the way to do it with a function

alandipert16:03:33

then use commensurate function in javelin

alandipert16:03:42

then build your own macro veneer layer atop that in your app if necessary

alandipert16:03:15

in this case i'd say its mostly javelin to blame, because cell= needs to fully expand its arguments. walk all code

chromalchemy16:03:03

Well defining the Specter function on it's own, then invoking it in a formula cell seems to be working like normal.

(defn specter-fn [v} ....)
(defc= transformula (specter-fn  v))
(transformula)

chromalchemy16:03:45

It just seems like the derefing is a bit inconsistent, I guess cause of the macro stuff

chromalchemy16:03:06

Is there any difference btw (defn foo [] (cell= (fn ...)) and (def foo (cell= (fn ...))?

alandipert16:03:25

yeah - the function returns a new cell every time called

alandipert16:03:28

foo is forever one cell

alandipert16:03:48

i think tho what you have should be equivalent to what i proposed

alandipert16:03:58

wrapping the macro is equivalent to calling the function it expands to

alandipert16:03:13

so weirdness is mysterious still

chromalchemy16:03:27

Maybe that's what was going on. I was using (defn []) to wrap the formula. Perhaps that's why things weren't updating in the dom as expected, but would print ok to the console.

chromalchemy16:03:37

So If I want a cannonical, reactive formula cell to reference in my ui, and I need to wrap the definition of it, I should use the def form?

chromalchemy17:03:57

(def transformula
  ( (formula
      #(transform [ALL] inc %))
    seed))
Seems to work as expected. Composes in the elems!!! @alandipert Thank you for your help with macro/interop intuition. Now I wont feel so helpless if a macro borks on the app level.

alandipert18:03:33

@chromalchemy nice! and yeah, it's good in general to avoid situations where you're creating cells dynamically

alandipert18:03:18

ideally your app has a static number of cells, and the app state is represented as values in those cells

alandipert18:03:38

per wise you risk leaking cells and slowing down your app, unless you develop rules for creating them or deallocating. like what for-tpl etc do

alandipert18:03:07

i.e. working with cells on a level best avoided if possibles