I am trying to do an exercise from Chapter 3 from the excellent book "Seven Concurrency Models in Seven Weeks" by Paul Butcher. The relevant code (provided in the book) looks like this:
(ns server.core
(:require [server.sentences :refer [strings->sentences]]
[server.charset :refer [wrap-charset]]
[compojure.core :refer :all]
[compojure.handler :refer [api]]
[ring.util.response :refer [charset response]]
[ring.adapter.jetty :refer [run-jetty]]
[clj-http.client :as client]))
(def snippets (repeatedly promise))
// another service handling translations - up and running OK
(def translator "http://localhost:3001/translate")
(defn translate [text]
(future
(:body (client/post translator {:body text}))))
// strings->sentences simply splits and joins the snippets into full sentences
// e.g. ("full. not", "full. sentence.") will be mapped to ("full.", "not full.", "sentence.")
(def translations
(delay
(map translate (strings->sentences (map deref snippets)))))
(defn accept-snippet [n text]
(deliver (nth snippets n) text))
(defn get-translation [n]
#(nth #translations n))
(defroutes app-routes
(PUT "/snippet/:n" [n :as {:keys [body]}]
(accept-snippet (Integer. n) (slurp body))
(response "OK"))
(GET "/translation/:n" [n]
(response (get-translation (Integer. n)))))
(defn -main [& args]
(run-jetty (wrap-charset (api app-routes)) {:port 3000}))
The translations value is a delay which maps snippets to full sentences and ultimately their translations.
The objective is to change the behaviour of the /translation endpoint so that it doesn't block when the required translation is not yet available but returns a status 409 instead.
My idea was to "peek" at translations to check if the wanted element was realized. I tried to do it this way:
(defn translation-realized? [n]
(if
(and
(realized? translations)
(realized? (nth #translations n)))
true
false))
...
(GET "/translation/:n" [n]
(if (translation-realized? n)
((println "found translation") (response (get-translation (Integer. n))))
{:status 409
:body "Not translated yet."})))
When I run the test client (which sends some snippets to be mapped and translated and then recovers translations) it seems to be blocked when trying to recover the first translation. When additionally I run a second one it gets 409 no matter which translation it's trying to get. Additionally, I set up some logging in the external service (the one providing translations) and it doesn't receive any requests.
I suppose that my solution isn't very idiomatic functional programming, so I have two questions:
Why doesn't it work? I assumed that any deref applied to translations will block when there's nothing mapped yet. Thus my first predicate (realized? translations). But then the first client should not be blocked.
What's the idiomatic way to do this?
EDIT:
I may have misstated the the problem.
If I understand correctly, snippets can arrive in any order and translations can be requested in any order. In the original code, if a client requests for a translation that is not yet present it will block. The goal is to respond with 409 immediately.
I achieved some improvement by changing translations to be a future instead of a delay and leaving only the second clause in translation-realized? but the behaviour is still incorrect. First request for a translation always returns a 409, even if the required snippets had been delivered beforehand. Subsequent requests for the same translation return correct responses.
Related
I would like a macro this-ns such that it returns the namespace of the location where it is being called. For instance, if I have this code
(ns nstest.main
(:require [nstest.core :as nstest]))
(defn ns-str [x]
(-> x (.getName) name))
(defn -main [& args]
(println "The ns according to *ns*:" (ns-str *ns*))
(println "The actual ns:" (ns-str (nstest/this-ns))))
I would expect that calling lein run would produce this output:
The ns according to *ns*: user
The actual ns: nstest.main
What I came up with as implementation was the following code:
(ns nstest.core)
(defmacro this-ns []
(let [s (gensym)]
`(do (def ~s)
(-> (var ~s)
(.ns)))))
It does seem to work, but it feels very hacky. Notably, in the above example it will expand to def being invoked inside the -main function which does not feel very clean.
My question: Is there a better way to implement this-ns to obtain the namespace where this-ns is called?
here is one more variant:
(defmacro this-ns []
`(->> (fn []) str (re-find #"^.*?(?=\$|$)") symbol find-ns))
the thing is the anonymous function is compiled to a class named something like
playground.core$_main$fn__181#27a0a5a2, so it starts with the name of the actual namespace the function gets compiled in.
Can't say it looks any less hacky, then your variant, still it avoids the side effect, introduced by def in your case.
Interesting question. I would never have guessed that your code would output user for the first println statement.
The problem is that only the Clojure compiler knows the name of an NS, and that is only when a source file is being compiled. This information is lost before any functions in the NS are called at runtime. That is why we get user from the code: apparently lein calls demo.core/-main from the user ns.
The only way to save the NS information so it is accessible at runtime (vs compile time) is to force an addition to the NS under a known name, as you did with your def in the macro. This is similar to Sean's trick (from Carcingenicate's link):
(def ^:private my-ns *ns*) ; need to paste this into *each* ns
The only other approach I could think of was to somehow get the Java call stack, so we could find out who called our "get-ns" function. Of course, Java provides a simple way to examine the call stack:
(ns demo.core
(:use tupelo.core)
(:require
[clojure.string :as str]))
(defn caller-ns-func []
(let [ex (RuntimeException. "dummy")
st (.getStackTrace ex)
class-names (mapv #(.getClassName %) st)
class-name-this (first class-names)
class-name-caller (first
(drop-while #(= class-name-this %)
class-names))
; class-name-caller is like "tst.demo.core$funky"
[ns-name fn-name] (str/split class-name-caller #"\$")]
(vals->map ns-name fn-name)))
and usage:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[clojure.string :as str]
[demo.core :as core]))
(defn funky [& args]
(spyx (core/caller-ns-func)))
(dotest
(funky))
with result:
(core/caller-ns-func) => {:ns-name "tst.demo.core", :fn-name "funky"}
And we didn't even need a macro!
Here's a simple re-frame app that I tried to create based on the existing example project in re-frame's github repo. But it is only displaying things from the html file. Seems like no event is being dispatched. Can anyone point out what am I doing wrong? Thanks.
(ns simple.core
(:require [reagent.core :as reagent]
[re-frame.core :as rf]
[clojure.string :as str]))
(rf/reg-event-db
:rand
(fn [db [_ _]]
(assoc db :winner ( + 2 (rand-int 3)))))
(rf/reg-sub
:winner
(fn [db _]
(:winner db)))
(def participants ["Alice" "Bob" "Ellie"])
(defn winners-name
[idx]
(get participants idx))
(defn show-winner
[]
[:h1
(winners-name
(#(rf/subscribe [:winner])))])
(defn ui
[]
[:div
[:h1 "Lottery"]
[show-winner]])
(defn ^:export run
[]
(rf/dispatch-sync [:rand])
(reagent/render [ui]
(js/document.getElementById "app")))
The :rand handler will produce nil most times since you are adding 2 to the generated value and the participants vector only has 3 entries.
The issue is caused because of a pair of extra parenthesis around the deref thing. So the function winners-name is treating it as a list instead of an integer.
(winners-name
(#(rf/subscribe [:winner]))
I'd like to analyze a file of foreign clojure code. I'm currently using clojure.tools.reader to read all the forms:
(require '[clojure.tools.reader :as reader])
(defn read-all-forms [f]
(let [rdr (indexing-push-back-reader (slurp f))
EOF (Object.)
opts {:eof EOF}]
(loop [ret []]
(let [form (reader/read opts rdr)]
(if (= EOF form)
ret
(recur (conj ret form)))))))
This generally works, except when it encounters a double-colon keyword that refers to an aliased ns. Example:
(ns foo
(:require [foo.bar :as bar]))
::bar/baz
Fails with:
ExceptionInfo Invalid token: ::bar/baz
Is there a way to use clojure.tools.reader to read the file and resolve keywords like this? Am I supposed to somehow keep track of the *alias-map* myself?
tools.reader uses clojure.tools.reader/*alias-map* if it's bound, otherwise it uses (ns-aliases *ns*) to resolve aliases. So if you have auto-resolved keywords in your file, you will need to use one of those approaches to allow auto-resolved aliases to be resolved.
I am trying to write a test like this:
(deftest login-form-rendering
(async done (with-redefs [http/post fake-login-success]
(with-mounted-component (c/login-form login!)
(fn [c div]
(.click (.getElementById js/document "Login"))
(is (= (:page #app-state) :location)))))
(done)))
I have this mock:
(defn fake-login-success [& _]
(let [ch (chan)
response {:status 200}]
(go (>! ch response)
ch)))
The login function does this:
(defn login! [username password]
(go (let [result (->> {:form-params {:username #username :password #password}}
(http/post "/api/login")
(<!))
{status :status body :body} result]
(case status
401 (swap! app-state assoc :page :bad-login)
200 (swap! app-state assoc :page :location)))))
The login form is a reagent component that takes a callback for onClick. app-state is a globally accessible atom.
The problem I'm facing is that the go block inside login! is never executed. Is there something I need to do to flush the channel or something?
I see there is also this unanswered question that's similar: Testing core.async code in ClojureScript. One difference seems to be that I don't have an explicit channel in my code under test. That's being generated by the cljs-http post.
I believe your problem is a misplaced parenthesis.
(let [ch (chan)
response {:status 200}]
(go (>! ch response)
ch))
The return value of this let is the go block's channel. It is a channel containing nothing, since:
The go block is trying to put on an unbuffered channel, requiring some other operation to "meet" it and do the complementary take in order for its own execution to proceed.
ch is lexically hidden from the outside world, so no operation can exist which can perform the complementary take.
If something somehow did take from it (or we added a buffer to ch so the first put succeeds), ch would be put on the returned channel.
(let [ch (chan)
response {:status 200}]
(go (>! ch response))
ch)
The return value of this let is ch, with no additional channel wrapping it. The go-block is parked trying to put on ch until something like your test gets around to taking from it.
However, since we're ultimately just looking to construct a channel with a constant on it, go itself seems like the simplest solution:
(defn fake-login-success [& _]
(go {:status 200})
I have written a function which takes a directory as input and returns a list of files.
(ns musicdb.filesystem)
(import '(java.io.File) '(java.net.url) '(java.io))
(use 'clojure.java.browse)
(require '[clojure.string :as str])
(defn getFiles
"get a list of all files"
[searchPath]
(def directory (clojure.java.io/file searchPath))
(def files (file-seq directory))
(def fonly (filter (fn [x]
(. x isFile)) files))
(def names [])
(doseq [x fonly]
(conj names (. x toString)) ;doesn't seem to work
(println (. x toString))) ;but this DOES print the file path
names)
The only thing that doesn't work here, is the conj call.
Here is my test
(ns musicdb.core-test
(:require [clojure.test :refer :all]
[musicdb.core :refer :all]
[musicdb.filesystem :refer :all]))
(deftest test_0
(testing "getFiles returns valid result"
(is (> (count (getFiles "/home/ls/books/books")) 1))
(doseq [i (take 5 (getFiles "/home/ls/books/books"))] (searchBook i))))
This test fails and shows that the return value of getFiles is empty.
names is an immutable vector. (conj names (. x toString)) creates a new vector but doesn't do anything with it. There are other problems with your code:
you don't want to use doseq. It's for side effects, such as printing things out. If you're creating a collection you usually don't need to iterate in clojure, or if you do you can use an immutable accumulator, loop and recur.
You don't want to use nested defs. You're defining globals, and what you want are function locals. Use let instead.
The clojure naming style is to use dashes instead of camel case (minor, just a convention).
You don't seem to be using your java.io importa in this code.
use in general is not a good idea, unless you restrict it to a few explicitly named functions with :only. This is to avoid confusion when looking at an unqualified name in your code, because you wouldn't know where it came from.
You want something like this:
(defn get-files [search-path]
(let [directory (clojure.java.io/file search-path)
files (file-seq directory)
fonly (filter #(.isFile %) files)]
(map #(.toString %) fonly)))