clojurescript iterate over the keys of an object - clojurescript

I am using clojurescript 0.0-2371 and I am trying to write some code that will clone an object. I have this code where I want to clone a node and calls a clone-object function:
(def animate
(js/React.createClass
#js
{:getInitialState
(fn []
(this-as this
{:children
(->
(.. this -props -children)
(js/React.Children.map (fn [child] child))
(js->clj :keywordize-keys false))}))
:render
(fn []
(this-as this
(let [children (:children (.. this -state))]
(doseq [[k v] children]
(clone-object (aget children k))))))}))
clone-object looks like this:
(defn clone-object [obj]
(log/debug obj)
(doseq [[k v] obj]
(log/debug k)))
And if I call clone-object like this:
(doseq [[k v] children]
(clone-object v))
I get this error:
Uncaught Error: [object Object] is not ISeqable

The answer was to use goog.object.forEach:
(defn clone-object [key obj]
(goog.object/forEach obj
(fn [val key obj]
(log/debug key))))

You don't strictly need Google Closure for looping through keys:
(defn obj->vec [obj]
"Put object properties into a vector"
(vec (map (fn [k] {:key k :val (aget obj k)}) (.keys js/Object obj)))
)
; a random object
(def test-obj (js-obj "foo" 1 "bar" 2))
; let's make a vector out of it
(def vec-obj (obj->vec test-obj))
; get the first element of the vector and make a key value string
(str (:key (first vec-obj)) ":" (:val (first vec-obj)))
About obj->vec:
vec convert a sequence to a vector (optional in case you are ok with a sequence)
map execute (fn [k] ...) for each element of the list.
(fn [k] ...) Take the k element of the list and put it in a key/value map data structure, aget takes the obj property referred by the key k.
(.keys js/Object obj) Get all the keys from a js object.

Related

How to pass a symbol to a function to create a function in clojure

As a minimal example of what I want to do:
(defn mkfn [func]
(fn func [a] (print "I am a function")))
(mkfn 'x) ; => #function[user/mkfn/func--10871]
(type x)
(x)
The last two both result in:
Syntax error compiling at (conjure-log-12628.cljc:1:1).
Unable to resolve symbol: x in this context
I'm not sure why this doesn't work since fn takes symbols as input and 'x is a symbol. I'm also not sure how to accomplish this task.
For that matter:
user=> (def (eval 'y) 3)
Syntax error compiling def at (conjure-log-12628.cljc:1:1).
user=> (def 'y 3)
Syntax error compiling def at (conjure-log-12628.cljc:1:1).
First argument to def must be a Symbol
First argument to def must be a Symbol
user=> (type 'y)
clojure.lang.Symbol
Other things that don't work:
(defn mkfn [func]
(fn (sympol func) [a] (print "i am a function")))
(symbol "y") ; => y ; a symbol
(def (symbol "y") 3) ; => an err
You will probably need a macro. It seems that you want to call that function by the provided name, so you also have to replace fn with defn.
And you have to be careful about a number of arguments, because function x with argument vector [a] must be called with one argument, and not like (x).
(defmacro mkfn [func]
`(defn ~func [~'a]
(print "I am a function")))
(mkfn x)
=> #'user/x
(x 1)
I am a function=> nil
There is also other way, using intern, so you can completely avoid writing macros:
(intern *ns* 'x (fn [a] (print "I am a function")))
=> #object...
(x 1)
I am a function=> nil
Example with intern:
(defn mkfn [func]
(intern *ns* func (fn [a] (print "I am a function"))))
=> #'user/mkfn
(mkfn 'y)
=> #'user/y
(y 1)
I am a function=> nil
As for your errors, def is a special form, so it has different evaluation rules. It doesn't evaluate the first argument, which has to be a symbol- and (unevaluated) (eval 'y), 'y or (symbol "y") aren't symbols, while y is.
You gonna need a macro for that since you need code writing code.
(defmacro mkfn [func]
`(fn ~func [~'a] ...))
There 2 ways of doing it, either function plus eval or macro. If you really want to programatically create a new function with your chosen name, the macro solution is the way to go.
The function + eval solution is instructive, but you'll have to either quote the function name when you call it (via a 2nd eval) or save the created function in another variable when you create it.
If you are interested in writing macros, please see this other question first: How do I write a Clojure threading macro?
For the function + eval, we can start with my favorite template project and add the following:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn maker-eval
[fn-sym]
(let [ll (list 'fn 'anon-fn [] (str "I am " fn-sym))]
(spyx :eval ll)
(eval ll)))
(verify
(let [anon-fn-1 (maker-eval 'aaa)]
(is= "I am aaa" (anon-fn-1))) ; need the temp local variable
(let [anon-fn-2 (maker-eval 'bbb)]
(is= "I am bbb" (anon-fn-2))) ; need the temp local variable
)
and we can see the creation and use of the function, along with printed output:
:eval ll => (fn anon-fn [] "I am aaa")
:eval ll => (fn anon-fn [] "I am bbb")
For the macro version, we type
(defn maker-macro-impl
[fn-sym]
(let [ll `(defn ~fn-sym [] (str "I am " (str (quote ~fn-sym))))]
(spyx :macro ll)
ll))
(defmacro maker-macro
[fn-sym] (maker-macro-impl fn-sym))
(verify
(let [anon-fn-3 (maker-macro-impl 'ccc)]
(is= anon-fn-3 (quote
(clojure.core/defn ccc [] (clojure.core/str "I am " (clojure.core/str (quote ccc)))))))
(maker-macro ddd)
(is= (ddd) "I am ddd"))
and see printed:
:macro ll => (clojure.core/defn ccc [] (clojure.core/str "I am " (clojure.core/str (quote ccc))))
Note that the local variable anon-fn-3 was only used to test the maker-macro-impl function, but was not needed to call the newly-created function ddd
at the end of the unit test.

How to return a promise from a go block?

The question is how to send to a nodejs app the result of a go block
i found a solution with callback
but i need a solution with promise
Promise solution?
Clojurescript app
(defn foo []
(go 1))
;;how to change foo,wrap to promise?, so node app can await to get the 1
;;i used 1 for simplicity in my code i have something like
;;(go (let [x (<! ...)] x))
Node app
async function nodefoo() {
var x = await foo();
console.log(x); // i want to see 1
}
Callback solution (the one that works now)
So far i only found a way to pass a cb function, so this 1 goes back to node.js app
Clojurescript app
(defn foo1 [cb]
(take! (go 1)
(fn [r] (cb r))))
Node app
var cb=function () {....};
foo1(cb); //this way node defined cb function will be called with argument 1
But i dont want to pass a callback function, i want node.js to wait and get the value.
I want to return a promise.
This function takes a channel and returns a Javascript Promise that resolves with the first value the channel emits:
(defn wrap-as-promise
[chanl]
(new js/Promise (fn [resolve _]
(go (resolve (<! chanl))))))
Then to show usage:
(def chanl (chan 1))
(def p (wrap-as-promise chanl))
(go
(>! chanl "hello")
(.then p (fn [val] (println val))))
If you compile that and run it in your browser (assuming you called enable-console-print!) you'll see "hello".
It is also possible to extend the ManyToManyChannel type with extend-type.
Here's a naif implementation using a similar wrap-as-promise function
(require '[clojure.core.async.impl.channels :refer [ManyToManyChannel]])
(defn is-error? [val] (instance? js/Error val))
(defn wrap-as-promise
[channel callback]
(new js/Promise
(fn [resolve reject]
(go
(let [v (<! channel)]
(if (is-error? v)
(reject v)
(resolve (callback v))))))))
(extend-type ManyToManyChannel
Object
(then
[this f]
(wrap-as-promise this f)))
(def test-chan (chan 1))
(put! test-chan (new js/Error "ihihi"))
(put! test-chan :A)
(defn put-and-close! [port val]
(put! port val)
(async/close! port))
(-> test-chan
(.then (fn [value] (js/console.log "value:" value)))
(.catch (fn [e] (js/console.log "error" e)))
(.finally #(js/console.log "finally clause.")))

Pass params in reframe subscriptions to a chain function clojurescript

(re-frame/reg-sub
::current-effects-list
(fn [[_ effect-type]]
[(re-frame/subscribe [::available-effects])
effect-type])
(fn [[available-effects effect-type]]
(filter (fn [{:keys [text value selected? type]}]
(= effect-type type))
available-effects)))
I want to pass the param effect-type to the next chain function as an argument, but I am new at Clojure, thus the effect-type is coming as null in the second chain function.
try this:
(re-frame/reg-sub
::current-effects-list
(fn [[_ effect-type]]
(re-frame/subscribe [::available-effects])) ; there is no need to return effect-type from here
(fn [available-effects [_ effect-type]]
(filter (fn [{:keys [text value selected? type]}]
(= effect-type type))
available-effects)))

ClojureScript - assoc is not working inside a promise

I have an array of art pieces. I want to find the route length and associate it with each art pieces.
My code will look like:
(defn load-art-routes [art-list ctx]
(doall (map-indexed (fn [index art]
(let [user-location (select-keys (:coords (sub> ctx :geolocation)) [:latitude :longitude])
art-location (:geoLocation art)]
(->> (map-request {:origin (str (:latitude user-location) "," (:longitude user-location))
:destination (str (:lat art-location) "," (:lon art-location))
:mode (name (sub> ctx :transition-mode))})
(p/map (fn [data]
(let [route-length (ocall js/Math "round" (/ (get-in data [:routes 0 :legs 0 :distance :value]) (* 0.621371192 1000)) 2)
route-duration (ocall js/Math "floor" (/ (get-in data [:routes 0 :legs 0 :duration :value]) 60))]
(js/console.log "load-art-routes route-length " route-length")
(assoc art :route-length route-length))))
(p/error (fn [error]
(util/log (str "GOOGLE DIRECTIONS API ERRORS" params) error)
))))) art-list))
art-list)
(defn map-request [params]
(when params
(let [endpoint google-directions-api-endpoint]
(->> (make-get-req (str endpoint "&" (encode-query-params params))
{})
(p/map (fn [data]
(util/log "GOOGLE DIRECTIONS API " data)
data))
(p/error (fn [error]
(util/log (str "GOOGLE DIRECTIONS API ERRORS" params ) error)
))))))
The route length calculation is correct but, assoc is not working. It is not actually associating it. I don't know what the issue is. Can anyone help me?
Please simplify this example! In the process, you will discover the problem.
First, update your question to include the require that shows what p/map, p/error, etc are. Also, put map-request before load-art-routes just like it must be in your source file.
Then, you should start by removing the thread-last ->> operator and use let with names for intermediate values:
(let [aa (map-request ...)
bb (p/map (fn [data] ...) aa)
cc (p/error (fn [error] ...) bb) ]
<bb or cc here?> )
My suspicion is that your p/error call is swallowing the results of p/map and returning nil.
This looks like you are trying to write "mutable" code.
Reformatting the code and fixing one error makes this more obvious:
(defn load-art-routes [art-list ctx]
(doall (map-indexed (fn [index art]
(let [user-location (select-keys (:coords (sub> ctx :geolocation)) [:latitude :longitude])
art-location (:geoLocation art)]
(->> (map-request {:origin (str (:latitude user-location) "," (:longitude user-location))
:destination (str (:lat art-location) "," (:lon art-location))
:mode (name (sub> ctx :transition-mode))})
(p/map (fn [data]
(let [route-length (ocall js/Math "round" (/ (get-in data [:routes 0 :legs 0 :distance :value]) (* 0.621371192 1000)) 2)
route-duration (ocall js/Math "floor" (/ (get-in data [:routes 0 :legs 0 :duration :value]) 60))]
(js/console.log "load-art-routes route-length " route-length)
(assoc art :route-length route-length))))
(p/error (fn [error]
(util/log (str " GOOGLE DIRECTIONS API ERRORS " params) error)
))))) art-list))
art-list)
load-art-routes simply returns the original art-list and kicks of some work in promises. These promises only update their versions of the list but given that we are using immutable data structures the returned art-list themselves remain unchanged. There is also a suspicious second art-list in the p/error call?
You'll probably want to restructure this to something that either returns a Promise or accepts a callback that will be called once all route-length have been computed.

In Clojure creating a function that creates 'n' JButtons each with different 'actionPerformed' method in other namespace

I have adapted this method from p.216 Joy of Clojure.
(defn makeButton [txt func panel]
(let [btn (JButton. txt) ] ;bfun (symbol func) ]
(doto btn
(.addActionListener
(proxy [ActionListener] [] (actionPerformed [_]
(func))) ;(bfun)))
))
(.add panel btn)
))
Plus this test code:
(defn fba [] (prn "fba"))
(defn fbb [] (prn "fbb"))
(def funs [fba fbb])
(def btnames ["B1" "B2"])
Buttons & actionPerformed are created thusly in a gui setup function:
(doseq [i (range (count btnames)) ]
(makeButton (get btnames i) (nth funs i) aJpanel))
This works as I want; clicking "B1" prints string "fba", etc. My difficulty arises when I want to attach to 'actionPerformed' something such as: ns1.ns2/myFunction instead of these simple cases (i.e. use a list/vector of several namespaced qualified functions). The exception reported is
wrong number of args (0) passed to Symbol
From the 'makeButton' function above my attempt to solve this is commented out. What is an idiomatic clojure solution to this?
This code works for me in the REPL. I'm using clojure.pprint/pprint as an example of a function that is in a different namespace:
(import '[javax.swing JFrame JPanel JButton]
'[java.awt.event ActionListener])
(def panel (JPanel.))
(def frame (doto (JFrame. "Hello Frame")
(.setContentPane panel)
(.setSize 200 200)
(.setVisible true)))
(defn make-button [label f panel]
(let [button (JButton. label)]
(doto button
(.addActionListener
(proxy [ActionListener] []
(actionPerformed [evt]
(f evt)))))
(.add panel button)))
(make-button "One" clojure.pprint/pprint panel)
(.revalidate frame)