Fork me on GitHub
#beginners
<
2018-10-23
>
jstaab00:10:42

Hey there, I'm trying to write a macro that binds a value to a name in the local scope, which means I don't want gensym. Is there a way to do this? My variable is always called ctx by convention, and I want to make calls to the macro very concise. Here's my code:

(defmacro defnode
  [node-sym args-binding call-form & dep-forms]
  (let [ctx-sym (gensym 'ctx)
        dep-pairs (apply hash-map dep-forms)
        dep-keys (vec (map #(-> % first name) dep-pairs))]
    `(def ~node-sym
      (node
        (fn [~ctx-sym {:keys ~dep-keys} [email protected]] ~call-form)
        [email protected](map
            (fn [[dep-key dep-call]]
              {dep-key `(fn [~ctx-sym ~args-binding] ~dep-call)})
            (apply hash-map dep-forms))))))
A call to (macroexpand '(defnode x-item [idx] (nth (:items ctx) idx))) gives me (def x-item (user/node (clojure.core/fn [ctx242 {:keys []} idx] (nth (:items ctx) idx)))) which is very close (ignore user/node, I'm in the repl), but I want ctx to be the bound name.

moxaj00:10:50

@jstaab037 like this:

`(... ~'ctx ...)

moxaj00:10:12

or simply bind 'ctx to ctx-sym, no need to use gensym

jstaab00:10:44

oh hey quotes, great idea

jstaab00:10:53

This is my first macro

jstaab00:10:01

It's soo much easier to write macros than I had expected

moxaj00:10:58

also a tip, you can convert the varargs into a map in the signature: [... & {:as dep-pairs}]

jstaab00:10:10

Gotcha, thanks!

jstaab00:10:32

One more thing, I now have:

(defmacro defnode
  [node-sym args-binding call-form & dep-forms]
  (let [dep-pairs (apply hash-map dep-forms)
        dep-keys (vec (map #(-> % first name symbol) dep-pairs))]
    `(def ~node-sym
      (node
        (fn [~'ctx {:keys ~dep-keys} [email protected]] ~call-form)
        [email protected](merge
            (map
              (fn [[dep-key dep-call]]
                {dep-key `(fn [~'ctx [email protected]] ~dep-call)})
              (apply hash-map dep-forms)))))))
and
(macroexpand '(defnode x-item-pct [idx] (/ item total) :total (x-total ctx) :item (x-item ctx idx)))
gives me
(def x-item-pct (user/node (clojure.core/fn [ctx {:keys [item total]} idx] (/ item total)) {:item (clojure.core/fn [ctx idx] (x-item ctx idx))} {:total (clojure.core/fn [ctx idx] (x-total ctx))}))
I want the two maps at the end to be merged into a single map, is there a reason that's not happening?

moxaj00:10:23

@jstaab I think you should do [email protected](apply merge ...)

jstaab00:10:54

Hmm that gives me (def x-item-pct (user/node (clojure.core/fn [ctx {:keys [item total]} idx] (/ item total)) [:item (clojure.core/fn [ctx idx] (x-item ctx idx))] [:total (clojure.core/fn [ctx idx] (x-total ctx))]))

jstaab00:10:23

Oh but yeah you're right, it's the [email protected] that's causing the problem now

moxaj00:10:41

oh right, no need to splice then

jstaab00:10:48

Right, cool that's working

arnoha02:10:59

hi, trying to figure out why this bit of code is not considered to be in the tail end position:

; create a nested tree of maps
; ["a" "b" "c"] -> {:key "a" :children {:key "b" :children {:key "c" :children nil}}}
(defn tree [xs]
	(if (seq xs)
		{:key (first xs)
		 :children (recur (rest xs))} ))
when I change the recur to tree then it works just fine. the creation of the map literal is the last form being evaluated in this function as far as I can tell. is there a simpler / more idiomatic way to do this?

alexmiller02:10:35

It’s not in tail position because it has to build the map last to return it

arnoha02:10:18

thanks @alexmiller, is there a better alternative to this approach without blowing through the stack (by just recursing without recur, that is, calling … :children (tree (rest xs))} ...)?

alexmiller02:10:55

without an accumulator, you can’t make this a loop/recur

alexmiller02:10:00

and probably you want to build from the bottom, not from the top

arnoha02:10:17

will look into this approach, thanks again

alexmiller02:10:03

(defn tree [xs]
    (loop [r nil
           t (reverse xs)]
      (let [[k & ks] t]
        (if k
          (recur {:key k :children r} ks)
          r))))

alexmiller02:10:15

r is the accumulator, t holds the remainder of the keys to look at. whether k exists is your termination check.

👍 1
Yehonathan Sharvit10:10:21

A question related to defmulti and defmethod. It seems that running defmulti a second time with the same variable name (after defmulti has been already called) has not effect. In other words, one cannot change the displatch function without reloading the whole process (e.g. without leaving the REPL). What is the rationale behind this limitation?

souenzzo11:10:16

@viebel you can avoid restart your repl doing

(def my-multi "")
(def my-multi :my-new-dispatch-fn)
When you redefine your multimethod, you will need to reload all namespaces that do some defmethod

Yehonathan Sharvit11:10:49

Let me be more explicit

user=> (defmulti foo (constantly :a))
#'user/foo
user=> (defmethod foo :a [_] :a)
#object[clojure.lang.MultiFn 0x1506f20f "[email protected]"]
user=> (foo 1)
:a
user=> (defmethod foo :b [_] :b)
#object[clojure.lang.MultiFn 0x1506f20f "[email protected]"]
user=> (foo 1)
:a
user=> (defmulti foo (constantly :b))
nil
user=> (foo 1)
:a

Yehonathan Sharvit11:10:18

I was expecting the last call to (foo 1) to return :b, because the dispatch function has changed

tavistock11:10:57

looking at it in macroexpand it first checks if it .hasRoot and if it’s symbol deref’d is a clojure.lang.MultiFn then defines it if wither of them is false.

user=> (macroexpand '(defmulti foo (constantly :a)))
(let* [v__5445__auto__ (def foo)] (clojure.core/when-not (clojure.core/and (.hasRoot v__5445__auto__) (clojure.core/instance? clojure.lang.MultiFn (clojure.core/deref v__5445__auto__))) (def foo (new clojure.lang.MultiFn "foo" (constantly :a) :default #'clojure.core/global-hierarchy))))

tavistock11:10:25

and

user=> (defmulti foo (constantly :a))
#'user/foo
user=> (.hasRoot #'user/foo)
true
user=> (clojure.core/instance? clojure.lang.MultiFn @#'user/foo)
true
you could instead define it directly, eg (def foo (new clojure.lang.MultiFn "foo" (constantly :b) :default #'clojure.core/global-hierarchy)), but i seems like defmulti may not have been intended to be used that way

Yehonathan Sharvit11:10:06

This is exactly what I am asking: why defmulti has not be intended to be used that way?

tavistock11:10:40

it seems to do with rebuilding a dispatch table

tavistock12:10:14

I found this thread, which talks about it a bit: https://groups.google.com/forum/#!topic/clojure/MXf8zWAv3vI . I’m sorry I haven’t answered your question

alexmiller12:10:15

It’s mentioned in that thread - if you have defmethods defined across multiple files, a reload will kill the dispatch table.

alexmiller12:10:50

That is, a multi method has global state and reloading locally loses state created from other places

alexmiller12:10:50

If someone wanted to work from a problem statement towards a change that took this into account, I think it’s possible there is a middle ground

Yehonathan Sharvit15:10:32

What about including the current limitation information in defmulti docstring?

Pontus12:10:10

Hello. I'm trying to sort a list of entities by two values. The values are whether an entity is "pinned to top" and then by it's creation date. The "pinned" should always be respected and be the primary sorting value, and then I want to sort by date after that. It also needs to support ascending/descending. I got it working before by doing this:

(defn- sort-entities [sort-direction entities]
  (let [pinned-entities (filter pinned? entities)
        non-pinned-entities (filter (complement pinned?) entities)]

    (if (= sort-direction :sort-by-direction/ascending)
      (concat
        (->> pinned-entities (sort-by :created-at))
        (->> non-pinned-entities (sort-by :created-at)))

      (concat
        (->> pinned-entities (sort-by :created-at) reverse)
        (->> non-pinned-entities (sort-by :created-at) reverse)))))
I recently found out about juxt so I'm trying to see if I optimise what I had before a bit. What I've got is this:
(defn- sort-entities [sort-direction entities]                  
  (if (= sort-direction :sort-by-direction/ascending)
    (->> entities (sort-by (juxt (complement :pinned?) :created-at)))
    ; how to do this for descending?
    ))) 
 
I don't know how to make descending sorting work. What I want is a way to sort the values in a descending direction separately on their own, but not together (which is why I used concat in my previous solution). So both pinned/non-pinned would be sorted in a descending direction, but the pinned values are always on top. Is that possible somehow?

manutter5112:10:04

Maybe (->> entities (sort-by (juxt :pinned? :created-at)) reverse)? Sort by pinned? instead of (complement :pinned?) to put the pinned stuff at the bottom, then reverse it and pinned will be at the top again.

Pontus13:10:31

yeah that seems to work, good idea thanks!

stopa18:10:26

hey all, cache question for you: I want to create a cache with the following semantics --

(-> {}
                         (cache/lru-cache-factory :threshold 10000) ;; bound the size of the cache to 10K most used 
                         (cache/ttl-cache-factory :ttl 1500) ;; do not cache infrequently fetched keys (they often need to be renewed)
                         **help** ;; invalidate items that have been in the cache > 30 seconds ;; this is so the most frequently used keys get a chance to refresh
                         )
Any thoughts on how I could achieve the last piece?

noisesmith18:10:12

doesn't the ttl-cache already remove values older than 1.5 seconds?

noisesmith18:10:02

beware that the printed form of a ttl cache map will show entries that don't show up with contains? or get

noisesmith18:10:15

I think it's the middle part ;; do not cache infrequently fetched keys (they often need to be renewed) that is tricky here

noisesmith18:10:26

and that line doesn't do what the comment describes

stopa18:10:04

I thought what ttl would do is: remove values that have not been queried for over 1.5 seconds

noisesmith18:10:24

ttl-cache-factory doesn't take access into account, only the time the key was created

noisesmith18:10:43

does the lru cache also allow a timeout?

noisesmith18:10:15

it doesn't - you might need to create a custom cache type?

stopa18:10:19

gotcha, thanks for the clarity noisesmith if only access is taken into account, maybe just the lru + ttl composition would do what i want right? (would not be able to specify 30 seconds, but perhaps 1.5s for both infrequent requests and common ones will be ok)

noisesmith18:10:27

right, I think that between timing out entries by creation time, and removing the least frequently accessed items, you'll get pretty close to the right thing

noisesmith18:10:33

worth testing though

noisesmith18:10:17

a problem I ran into with a cache the other day: using a cache to represent a "refreshable" resource (auth token), the problem is that with an atom as container there's a race condition where you can drop the freshest token and keep using an old one

👍 1
noisesmith18:10:26

I wonder if an agent would fix that...

seancorfield19:10:46

It's also worth noting that the TTL cache has an "interesting" edge case in that it can expire items on access as well as update, so it is possible for a "has?" check to succeed but a subsequent "get" to return nil -- some times that matters, some times it does not.

noisesmith20:10:08

@seancorfield yeah - I think this is another version of the classic bug where you deref an atom twice in the same block of code, therefore introducing a race condition

noisesmith20:10:42

(or deref and reset!, etc. - basically two accesses in the same logical flow)

noisesmith20:10:59

another fun thing is that if you pr the atom, it will show expired keys, because unlike lookup, printing doesn't check validity

Mario C.21:10:40

Which method is generally preferred? A. (-> ctx :request :request-method) B. (get-in ctx [:request :request-method])

mfikes22:10:09

@mario.cordova.862 Without additional info, I’d lean towards A

CyberSapiens9723:10:49

is there a more elegant way to do this?

CyberSapiens9723:10:56

(defn web-search
  "Search the given String on Google and Bing,
   returns the 'name' of the first page that loads first."
  [search-term]
  {:pre  [(string? search-term)]
   :post [#(string? %)]}
  (let [search-google (future (slurp (str "" search-term)))
        search-bing   (future (slurp (str "" search-term)))
        check-search (fn [google bing]
                       (cond
                         (realized? google) "google"
                         (realized? bing) "bing"
                         :else (recur s1 s2)))]
    (check-search search-google search-bing)))

hiredman23:10:41

core.async is much better at making a choice like that

hiredman23:10:01

futures are not good at it

CyberSapiens9723:10:17

didn't get into core.async yet, but i will

hiredman23:10:24

without pulling in core.async you might want to use a juc queue

hiredman23:10:27

or maybe an arrayblockingqueue of size 1

hiredman23:10:50

each search future offers the result to the queue, then the check-search takes one

hiredman23:10:19

which I guess would be the same as using a promise

CyberSapiens9723:10:29

i see. this is more a problem of parallelism kind right?

hiredman23:10:36

no, the problem is futures don't have a good way to choose between them

hiredman23:10:39

ideally you'd say something like (one-of (future ...) (future ...)) and get a future back that would have the value of whichever of the two completed first

hiredman23:10:12

but the future api (and the java interace it implements) doesn't provide a way to do that