ClojureScript - assoc is not working inside a promise - clojurescript

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.

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.")))

How to get query parameters in clojurescript?

I'm using secretary and reagent. This is my code :
(def view (atom nil))
(defn layout [view]
[:div #view])
(reagent/render-component [layout view] (.getElementById js/document "message"))
(secretary/set-config! :prefix "")
(secretary/defroute home-path "/" [query-params]
(timbre/info "Path : /, query params : " query-params)
(let [warning (:warning query-params)
success (:success query-params)
login-failed (:login_failed query-params)]
(when warning
(timbre/info "Warning found : " warning)
(reset! view [:h4 [:span.label.label-warning warning]]))
(when success
(timbre/info "Success found : " success)
(reset! view [:h4 [:span.label.label-info success]]))
(when login-failed
(timbre/info "Login failed")
(reset! view [:h4 [:span.label.label-warning "Login Failed."]]))))
(let [h (History.)]
(goog.events/listen h EventType.NAVIGATE #(secretary/dispatch! (.-token %)))
(doto h
(.setEnabled true)))
Disregarding the :prefix value (I tried "", "#" and also not setting the :prefix at all) this code only works with routes like :
http://localhost:8080/login#/?success=SuccessMessage
But it doesn't work with routes like :
http://localhost:8080/login?success=SuccessMessage
What I'm actually trying to achieve is to parse the login failure from friend, which in case of failure redirects me to
http://localhost:8080/login?&login_failed=Y&username=someUser
and display login failed message to the user. I don't need to use secretary for this, anything that works to parse the query-parameters would be ok for me.
The hard way would be to parse the query string which I can get with:
(-> js/window .-location .-search)
I believe that this is already done well in some library.
I found it. Using https://github.com/cemerick/url (works for both clojure and clojurescript), one can do :
(require '[cemerick.url :as url])
(:query (url/url (-> js/window .-location .-href)))
From the docs:
If a URI contains a query string it will automatically be extracted to :query-params for string route matchers and to the last element for regular expression matchers.
(defroute "/users/:id" [id query-params]
(js/console.log (str "User: " id))
(js/console.log (pr-str query-params)))
(defroute #"/users/(\d+)" [id {:keys [query-params]}]
(js/console.log (str "User: " id))
(js/console.log (pr-str query-params)))
;; In both instances...
(secretary/dispatch! "/users/10?action=delete")
;; ... will log
;; User: 10
;; "{:action \"delete\"}"

clojurescript iterate over the keys of an object

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.

Occasional null results with clojure's http.async.client and google

I have a result set in json with city names, and I'd like to get the lat-long for each. The following function works to an extent:
(:require [http.async.client :as client])
(defn get-geo-fact [row]
(let [
n (string/replace (:cityname row) " " "+")
url (str "http://maps.googleapis.com/maps/api/geocode/json?address="
n "&sensor=false")
resp (client/GET url) ]
(client/await resp)
(make-geo-fact row (json/read-json (client/string resp))) ))
That last call to make-geo-fact just returns an RDF rendering of the city coordinates. The problem I'm running into is that a run (of about 40 calls to this function) returns a few (3-5 lat-long pairs) null results for lat-longs. The cities that return null values differ from run to run - sometimes San Jose gets coordinates, and sometimes it doesn't.
I originally used slurp to grab the body of the url and got similarly-occasional nulls. I figured I wasn't waiting for the response properly, but switching to http.async.client doesn't seem to be doing the trick. Any ideas?
edit:
Here's the make-geo-fact function, which takes a "this team is located in this city" fact and a response from google maps, returning a vector of two triples that convey the latitude and longitude:
(defn make-geo-fact [row response]
(let [ g (:location (:geometry (first (:results response))))
lat (str "<" (:team row) ">"
" <http://www.nhl.com/latitude> \"" (:lat g)
"\"^^<http://www.w3.org/2001/XMLSchema#decimal> ." )
lon (str "<" (:team row) ">"
" <http://www.nhl.com/longitude> \"" (:lng g)
"\"^^<http://www.w3.org/2001/XMLSchema#decimal> ." ) ]
[lat lon] ))
And here's the function I call to kick the whole thing off:
(defn make-geo-facts []
(let [ a (bounce team-city (build "files/team-city.nt"))
f "files/geo-facts.nt" ]
(spit f (string/join "\n" (flatten (map get-geo-fact (:rows a)))))
f ))
Where the bounce function issues a SPARQL select query against an RDF model, which is instantiated with the build function.
edit 2
Here's a re-factor where make-geo-fact isn't needed:
(defn get-geo-fact [row]
(let [ n (string/replace (:cityname row) " " "+")
url (str "http://maps.googleapis.com/maps/api/geocode/json?address=" n "&sensor=false")
resp (client/GET url) ]
(-> (client/GET url)
client/await
client/string
json/read-json
:results
first
:geometry
:location )))
(defn make-geo-facts []
(let [ a (bounce tc-query (build "files/team-city.nt"))
f "files/geo-facts.nt"
*client* (client/create-client)]
(try (spit f (string/join "\n" (flatten (map get-geo-fact (:rows a))))))
(finally (client/close *client*)) ))
As you've said changing client implementations didn't make a difference.
I double checked and create a test for development version of http.async.client.
And always got responses with body.
Please provide make-geo-fact implementation.
Turns out my code needed a little sleep. I added (Thread/sleep 1000) to my core function and now I don't get null results:
(defn get-geo-fact [row]
(let [ n (string/replace (:cityname row) " " "+")
url (str "http://maps.googleapis.com/maps/api/geocode/json?address=" n "&sensor=false")
resp (client/GET url) ]
(Thread/sleep 1000)
(-> (client/GET url)
client/await
client/string
json/read-json
(make-geo-fact ,,, row ) )))