This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-06-23
Channels
- # announcements (16)
- # asami (4)
- # babashka (49)
- # beginners (70)
- # calva (25)
- # clojars (3)
- # clojure (40)
- # clojure-europe (82)
- # clojure-france (15)
- # clojure-gamedev (16)
- # clojure-nl (2)
- # clojured (7)
- # clojurescript (13)
- # conjure (11)
- # cursive (4)
- # data-science (12)
- # datalevin (2)
- # figwheel-main (5)
- # gratitude (5)
- # honeysql (5)
- # hyperfiddle (4)
- # jobs (4)
- # joyride (3)
- # lsp (10)
- # malli (2)
- # missionary (14)
- # nbb (2)
- # off-topic (3)
- # pathom (16)
- # rdf (5)
- # releases (4)
- # sci (35)
- # shadow-cljs (16)
- # tools-deps (22)
- # xtdb (7)
[:renderer] Compiling ...
[:renderer] Build failure:
------ ERROR -------------------------------------------------------------------
File: C:\Users\richie\Documents\org\projects\electric-clj-play\src\app\renderer\ui09.cljs:192:3
--------------------------------------------------------------------------------
189 | (defn poll
190 | "A discrete flow running given task repeatedly forever."
191 | [task]
192 | (mi/ap (loop [] (mi/amb (mi/? task) (recur)))))
---------^----------------------------------------------------------------------
Encountered error when macroexpanding cloroutine.core/cr.
null
Can't recur here at line 192 app/renderer/ui09.cljs
copied from https://gist.github.com/leonoel/c5e32b65ec7b6ab4b5b45772082a3d85#file-pump-clj-L146
How do I make a flow (I think a signal) out of an input field? In the pump.clj example, it makes a mailbox beforehand and then posts the input to the mailbox when there's input. Is that the best way? How else would I do it?
Also, why doesn't this work?
Thanks!I found something in the slack chat history that should work.
(defn forever [task]
(m/ap (m/? (m/?> (m/seed (repeat task))))))
To build a continuous flow from an input field, the idiom is to listen for changes with observe
, then process event with eduction
, then provide an initial value with reductions {}
, then discard stale values with relieve {}
Thank you for those hints about reductions {}
and relieve {}
. I'm using observe now instead of mbx.
I have another question that's not related to my previous question.
(defn help-attributes
[node attributes]
(doseq [[k v] (s/unform ::hiccup-attributes attributes)]
(let [k (if (keyword? k) (name k) k)]
(when (contains? did-you-mean k) (println (<< "Setting '~{k}'. Did you mean '~(did-you-mean k)'?")))
(if (= "on-" (subs k 0 3))
#_(mi/stream!
(mi/eduction (map v)
(mi/observe
(fn mount [send!]
(.addEventListener node (subs k 3) send!)
(fn unmount [] (.removeEventListener node (subs k 3) send!))))))
;; Uncaught Error: Can't process event - consumer is not ready.
;; at G__34260 (Observe.cljs:33)
;; at HTMLInputElement.G__35068__1 (core.cljs:4318)
;; at HTMLInputElement.G__35068 (core.cljs:4321)
(mi/stream!
(mi/ap
(v
(mi/?<
(mi/observe
(fn mount [send!]
(.addEventListener node (subs k 3) send!)
(fn unmount [] (.removeEventListener node (subs k 3) send!))))))))
(set-property! node k v)))))
I have this function that adds things to a dom node. node
here is a dom node and attributes
is a map. It's really properties not attributes. If the key is on-click
then it will add an event listener for click events and expect the value to be a function that should get called on each event.
When I use the commented form (commented with #_) then clicking my button prints the commented error (Can't process event - consumer is not ready) to the browser console.
It works fine when I use the uncommented code. i.e. It doesn't work with eduction (map v) ...
but it works with (ap (v (?< ...
I failed to recreate it in a self contained example:
(let [my-fun #(println "fun!" %)
node (gdom/createDom "input" (clj->js {:type "button"}))
dispose! ((mi/reactor
(mi/stream!
(mi/eduction (map my-fun)
(mi/observe
(fn mount [send!]
(println "adding event listener")
(.addEventListener node "click" send!)
(fn unmount [] (.removeEventListener node "click" send!)))))))
prn prn)]
(.dispatchEvent node (js/Event. "click"))
(.dispatchEvent node (js/Event. "click"))
(.dispatchEvent node (js/Event. "click"))
(dispose!))
(let [my-fun #(println "fun!" %)
node (gdom/createDom "input" (clj->js {:type "button"}))
dispose! ((mi/reactor
(mi/stream!
(mi/ap
(my-fun
(mi/?<
(mi/observe
(fn mount [send!]
(println "adding event listener")
(.addEventListener node "click" send!)
(fn unmount [] (.removeEventListener node "click" send!)))))))))
prn prn)]
(.dispatchEvent node (js/Event. "click"))
(.dispatchEvent node (js/Event. "click"))
(.dispatchEvent node (js/Event. "click"))
(dispose!))
These two example work fine.
Hmm. Now I'm seeing a different error...
I wrote some code that builds dom elements from a hiccup-like syntax and my hiccup-like example is not working. I implemented the same app without my hiccup-like code in order to help me find what's going wrong.
This is what I'm trying to get:
((mi/reactor
(let [db (atom nil)
dispatch! (fn [event]
(m/match event
[:field ?s] (reset! db ?s)
?x (throw (ex-info "unmatched" {:value ?x}))))
>field (->> (mi/observe (fn mount [send!]
(let [id (random-uuid)]
(add-watch db id (fn [k r o n] (send! n)))
(send! @db)
(fn unmount [] (remove-watch db id)))))
(mi/reductions {} nil)
(mi/relieve {}))
>valid? (mi/eduction (map seq) >field)
button-node (doto (gdom/createDom "input" (clj->js {:type "button"
:value "Submit"}))
(as-> node (mi/stream!
(mi/ap
(let [disabled (mi/?< (mi/eduction (map not) >valid?))]
(gdom/setProperties node (clj->js {:disabled disabled})))))))
input-node (gdom/createDom "input" (clj->js {:type "text"}))]
(mi/stream!
(mi/ap
(mi/?<
(mi/observe (fn mount [send!]
(.addEventListener button-node "click" send!)
(fn unmount [] (.removeEventListener button-node "click" send!)))))
(println "field:" (mi/? (first-or >field)))))
(mi/stream!
(mi/ap
(let [x (mi/?<
(mi/observe (fn mount [send!]
(.addEventListener input-node "input" send!)
(fn unmount [] (.removeEventListener input-node "input" send!)))))]
(dispatch! [:field (.. x -target -value)]))))
(gdom/replaceNode
(gdom/createDom "div" (clj->js {:id "app-container"})
(gdom/createDom "div" nil input-node)
(gdom/createDom "div" nil button-node))
(gdom/getElement "app-container"))))
prn prn)
And this is the hiccup-like syntax that I want:
(binding [*in-reactor* true]
((mi/reactor
(let [db (atom nil)
dispatch! (fn [event]
(m/match event
[:field ?s] (reset! db ?s)
?x (throw (ex-info "unmatched" {:value ?x}))))
>field (observe db)
>valid? (mi/eduction (map seq) >field)]
(gdom/replaceNode
(as-element [:div#app-container
[:div [:input {:type "text"
:on-input #(dispatch! [:field (.. % -target -value)])}]]
[:div [:input {:type "button"
:value "Submit"
:disabled (mi/eduction (map not) >valid?)
:on-click #(println "field:" (mi/? (first-or >field)))}]]])
(gdom/getElement "app-container"))))
prn prn))
Here's the bit of code to support it:
(def ^:dynamic *in-reactor* false)
(def re-tag #"([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?")
(s/def ::hiccup-leader
(s/and keyword?
(s/conformer #(if (namespace %)
::s/invalid
(let [[match tag id class] (re-matches re-tag (name %))]
(if-not match
::s/invalid
(into {}
[(when (seq tag) [:tag tag])
(when (seq id) [:id id])
(when (seq class)
[:class (string/replace class #"\." " ")])]))))
#(let [{:keys [tag id class]} %]
(keyword
(str tag
(when (seq id) (str "#" id))
(when (seq class) (str "." (string/replace class #" " ".")))))))))
(s/def ::hiccup-attributes
(s/map-of (s/or :keyword keyword? :string string?)
(s/or :fn fn? :string string? :number number? :boolean boolean?)
:conform-keys true))
(s/def ::hiccup
(s/or :text string?
:number number?
:flow fn?
:hiccup (s/cat :leader ::hiccup-leader
:attributes (s/? ::hiccup-attributes)
:children (s/* ::hiccup))))
(defn observe
[a]
(->> (mi/observe (fn mount [send!]
(let [id (random-uuid)]
(add-watch a id (fn [k r o n] (send! n)))
(send! @a)
(fn unmount [] (remove-watch a id)))))
(mi/reductions {} nil) ; provide initial value
(mi/relieve {}) ; discard stale values)
))
(defn set-property!
[node k v]
(if (and *in-reactor* (fn? v))
(mi/stream!
(mi/ap
(gdom/setProperties node (clj->js {k (mi/?< v)}))))
(gdom/setProperties node (clj->js {k v}))))
(def did-you-mean {"onclick" "on-click"
"click" "on-click"
"readonly" "readOnly"})
(defn help-attributes
[node attributes]
(doseq [[k v] (s/unform ::hiccup-attributes attributes)]
(let [k (if (keyword? k) (name k) k)]
(when (contains? did-you-mean k) (println (<< "Setting '~{k}'. Did you mean '~(did-you-mean k)'?")))
(if (= "on-" (subs k 0 3))
#_(mi/stream!
(mi/eduction (map v)
(mi/observe
(fn mount [send!]
(.addEventListener node (subs k 3) send!)
(fn unmount [] (.removeEventListener node (subs k 3) send!))))))
(mi/stream!
(mi/ap
(v
(mi/?<
(mi/observe
(fn mount [send!]
(.addEventListener node (subs k 3) send!)
(fn unmount [] (.removeEventListener node (subs k 3) send!))))))))
(set-property! node k v)))))
(defn as-element
[form]
(when-not (s/valid? ::hiccup form)
(throw (ex-info (s/explain ::hiccup form) (s/explain-data ::hiccup form))))
(let [conformed (s/conform ::hiccup form)]
(m/match conformed
[:text ?text]
(gdom/createTextNode ?text)
[:number ?number]
(gdom/createTextNode (str ?number))
(m/and (m/guard *in-reactor*) [:flow ?flow])
;; use mi/reductions instead
(let [old (volatile! nil)]
(mi/stream! (mi/ap (let [elem (as-element (mi/?< ?flow))]
(do (when @old
(gdom/replaceNode elem @old))
(vreset! old elem)))))
@old)
[:hiccup {:leader {:tag ?tag
:id ?id
:class ?class}
:attributes ?attributes
:children (m/seqable (m/cata !children) ...)}]
(doto (gdom/createDom ?tag)
(as-> node (do
(when ?id (set-property! node "id" ?id))
(when ?class (set-property! node "class" ?class))
(help-attributes node ?attributes)
(doseq [child !children]
(.appendChild node child)))))
?x (throw (ex-info "non exhaustive pattern match" {:value ?x})))))
I'm just making a dom node and then I have some special cases when I add properties. When the key is "on-click" I listen for click events. Otherwise, if the value is a function then I treat it like a missionary flow and run it for the effect of setting a dom node property.
Instead of the "consumer is not ready" error from before, I'm now getting #object[Error Error: Unsupported operation.]
. If I use (ap (v (?< ...
instead then I get field: #object[missionary.impl.Ambiguous.Branch]
.
I'm sorry that I don't have a clear question or sample problem.
What's the difference between eduction (map f)
vs (stream! (ap (f (?< ...
?
I'm running flows for the effect of setting a property on a dom node. When would I use stream!
vs signal!
? I think the value for the property is actually a signal since it's a continuous thing. Button clicks would be a stream since those are discrete events.I'm using stream!
everywhere since it doesn't seem to work with signal!
. My reasoning is that signals are lazy and I need the effect so I can't have a lazy thing.
Hey, I have a more focused example. My problem is something to do with where I put the function. IIUC ?< forks the program so I should have the println "field" (? (first-or ...))
after the fork. If it were before the fork then it would only run once; I want it to run again every time the program forks. I figure it should work in the other cases since the println ? first-or
is inside of a function. It's ok if it builds that function once as long as it runs it in each fork.
Thanks!
Can you start a new thread with the question in two sentences for me to read and the gist?
> What's the difference between eduction (map f) vs (stream! (ap (f (?< ...))))?
(eduction (map f) <x)
is semantically equivalent to (ap (f (?< <x)))
. If you observed a difference I suspect you're in undefined behavior land, look at the definition of f
and check for dangling parking operators.
> When would I use stream! vs signal!? I think the value for the property is actually a signal since it's a continuous thing. Button clicks would be a stream since those are discrete events.
> I'm using stream! everywhere since it doesn't seem to work with signal! . My reasoning is that signals are lazy and I need the effect so I can't have a lazy thing.
Yes. stream!
is discrete and eager, signal!
is continuous and lazy. Use signal!
when you need to share a continuous value among multiple consumers, use stream!
when you want to perform an effect in reaction to a change.
Hey, I have a more focused example. My problem is something to do with where I put the function. IIUC ?< forks the program so I should have the println "field" (? (first-or ...))
after the fork. If it were before the fork then it would only run once; I want it to run again every time the program forks. I figure it should work in the other cases since the println ? first-or
is inside of a function. It's ok if it builds that function once as long as it runs it in each fork.
Thanks!