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} ~@args-binding] ~call-form)
        ~@(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} ~@args-binding] ~call-form)
        ~@(merge
            (map
              (fn [[dep-key dep-call]]
                {dep-key `(fn [~'ctx ~@args-binding] ~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 ~@(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 ~@ 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?

Alex Miller (Clojure team)02: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))} ...)?

Alex Miller (Clojure team)02:10:55

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

Alex Miller (Clojure team)02: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

Alex Miller (Clojure team)02:10:03

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

Alex Miller (Clojure team)02:10:15

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

👍 4
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 "clojure.lang.MultiFn@1506f20f"]
user=> (foo 1)
:a
user=> (defmethod foo :b [_] :b)
#object[clojure.lang.MultiFn 0x1506f20f "clojure.lang.MultiFn@1506f20f"]
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

Alex Miller (Clojure team)12:10:15

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

Alex Miller (Clojure team)12:10:50

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

Alex Miller (Clojure team)12: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!

awb9915:10:27

I think I somehow fucked up my clojure.data library. I have an expression that worked yesterday, and today the repl gives me errors....

tavistock15:10:20

without more info i can’t be certain, but did you happen to do it yesterday in cljs?

awb9915:10:54

It is pure clojure, not clojurescript

awb9915:10:55

I am using intellij/cursive

awb9915:10:10

And CTRO+Q gives me the function definition as normal,

awb9915:10:20

and clojure.data/diff works.

awb9915:10:31

I think perhaps it destroyed the library?

tavistock15:10:09

it’s just throwing an error parsing it because diff-associative-key is private, as you can see by the defn- instead of the normal defn, https://github.com/clojure/clojure/blob/master/src/clj/clojure/data.clj#L33

dpsutton15:10:48

any chance you were in the namespace of clojure.data yesterday? that would explain why you could use a private function yesterday

tavistock15:10:20

depending on what you’re trying to do I could suggest alternatives, but i can’t explain why it was working yesterday and not today

awb9915:10:42

@U0C7D2H1U thanks for pointing out the private vs public function issue.

awb9915:10:28

it is straange that the function is private,

awb9915:10:50

as it is a common functionality to compare sets by u sing only a few keys.

awb9915:10:33

@U0C7D2H1U: I was only aware of private functions that end with "-" so I did not have this issue in mind...

tavistock15:10:26

using the var directly may have bypassed the private check

tavistock15:10:24

via #'clojure.data/diff-associative-key, i don’t suggest doing this

awb9916:10:38

I basically want to compare two sets and only use one field for the difference check

awb9916:10:32

When I tested it yesterday it all worked. I now have copied the code from the clojure.data project to my repl and it does not longer work also. Very bizarre

tavistock16:10:09

maybe maping over the hashmaps in the sets and select-keys then using clojure.set operations, or copying and pasting the diff-associative-key and editing it to do what you want would work

dadair16:10:34

Would something like this work for you @UCSJVFV35?

(def as [{:s "0" :name "bongo"} {:s "b" :name "bongo"}])
(def bs [{:s "0"} {:s "b"} {:s "c"}])

(def xf (map :s))

(clojure.set/difference (into #{} xf bs) (into #{} xf as))

awb9916:10:10

@U0E2EQFME I need the original data in one of the sets to further do calculations. This is why I went for diff-associative. With difference function all other fields get lost.

awb9916:10:05

@U0C7D2H1U: thanks, I am trying to do something along this lines... But for now it clojure does not love me... 😞

awb9918:10:04

(defn diff [l r fl fr] (let [r-project (into #{} (map fr r))] (set (remove #(contains? r-project (fl %)) l))))

awb9918:10:12

I found a snippet that works.

awb9918:10:16

Thanks guys for your help!!

👍 4
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

👍 4
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