clojure-dev

Issues: https://clojure.atlassian.net/browse/CLJ | Guide: https://insideclojure.org/2015/05/01/contributing-clojure/
denik 2021-02-27T00:13:24.047500Z

got it to work with a combination of walking the expression and replacing namespaced symbols and evaling the fn form to catch errors

denik 2021-02-27T00:13:46.047700Z

(defn resolve-db-syms [form]
  (clojure.walk/postwalk
    (fn [x]
      ;; FIXME need something that ignores all local bindings
      (if (and (symbol? x) (namespace x))
        ;; inline form if namespaced sym found
        (if-let [{:keys [form]} (get-db-var x)]
          form
          x)
        x))
    form))

(defmacro snipf [name & f-bodies]
  (let [f-bodies# (resolve-db-syms f-bodies)
        f#        (conj f-bodies# 'fn)]
    ;; eval fn def to throw syntax errors
    (sci/eval-form sci-ctx f#)
    `(db/transact-entity!
       code-conn
       {:var  (~namespace-sym '~name)
        :form '~f#})))

seancorfield 2021-02-27T21:15:45.050600Z

Can someone help me out here? https://clojure.atlassian.net/browse/CMEMOIZE-25 says "Due to differences in locking etc, there are some subtle differences in semantics between the clojure.core/memoize function and core.memoize/memo function, and these should be explained in the README." -- which is verbatim what someone said to me about those two functions, but they did not provide any details about the "subtle differences" and I'm not sure what they meant (and, yes, I should have asked for clarification at the time but now I can't even remember who said it!).

borkdude 2021-02-27T21:19:11.051400Z

Is this about core memoize potentially doing calculations multiple times (based on atoms, swap!), whereas with a locking based solution it would happen only once?

seancorfield 2021-02-27T21:23:14.052500Z

Could be. memoize closes over an atom but only evals the function call once, then swap!'s it into the atom as a plain value.

borkdude 2021-02-27T21:24:36.052800Z

That is true, but multiple concurrent calls might still happen

seancorfield 2021-02-27T21:30:56.054600Z

I'm staring at the core.memoize and core.cache code -- the core.memoize/memo function uses the "basic" cache which is just a hash map, no eviction, wrapped in an atom so I'm not seeing the difference in semantics...

seancorfield 2021-02-27T21:31:58.055700Z

Both function do a check on the args collection being in the atom and then a get on the atom, else a swap! assoc to update the atom.

seancorfield 2021-02-27T21:37:21.057600Z

I'm having a hard time envisioning under what circumstances the semantics might be different -- using a basic cache -- since there's no eviction. That locking is actually on the anonymous function in through* which in turn calls (apply f args) for the original memoized f.

seancorfield 2021-02-27T21:39:44.058400Z

(so, yeah, whatever potential differences there might be are certainly subtle but I can't express what they are 🙂 )

borkdude 2021-02-27T21:40:38.058700Z

Maybe search the archives for this JIRA issue number? ;)

seancorfield 2021-02-27T21:42:14.060800Z

Haha... I've tried that before and not turned anything up. Focusing on concurrent calls, I guess if two happen with memoize, f can be called twice -- after both threads check and do not find the cached value -- but for core.memoize/memo that isn't possible? Am I reading the d-lay code right in that respect?

borkdude 2021-02-27T21:43:07.061300Z

That's what I was thinking of

seancorfield 2021-02-27T21:43:14.061500Z

I suppose this should be easy enough to verify with a little bit of test code...

borkdude 2021-02-27T21:46:04.061700Z

user=> (defn foo* [x] (prn x) x)
#'user/foo*
user=> (def foo (memoize foo*))
#'user/foo
user=> (run! (fn [i] (future (Thread/sleep 10) (foo :bar))) (range 10))
nil
user=> :bar:bar:bar
:bar

:bar:bar

:bar
:bar
:bar
:bar

borkdude 2021-02-27T21:50:48.062200Z

I cannot reproduce this with core.memoize

seancorfield 2021-02-27T21:52:21.062800Z

user=> (def p1 (promise))
#'user/p1
user=> (def p2 (promise))
#'user/p2
user=> (def a1 (atom 0))
#'user/a1
user=> (def a2 (atom 0))
#'user/a2
user=> (defn f1 [& _] (swap! a1 inc) _)
#'user/f1
user=> (defn f2 [& _] (swap! a2 inc) _)
#'user/f2
user=> (let [ff1 (memoize f1)] (dotimes [n 100] (future @p1 (ff1 1 2 3 4))))
nil
user=> (deliver p1 :go)
#object[clojure.core$promise$reify__8526 0x5d1e09bc {:status :ready, :val :go}]
;; wait a while
user=> @a1
3
user=> (require '[clojure.core.memoize :refer [memo]])
nil
user=> (let [ff2 (memo f2)] (dotimes [n 100] (future @p2 (ff2 1 2 3 4))))
nil
user=> (deliver p2 :go)
#object[clojure.core$promise$reify__8526 0x6c008c24 {:status :ready, :val :go}]
;; wait a while
user=> @a2
1
user=> 
Yup, that seems to confirm it.

seancorfield 2021-02-27T21:55:38.063800Z

So the "subtle" issue is that, unlike memoize, clojure.core.memoize/memo prevents multiple invocations of the function with a given collection of arguments.

seancorfield 2021-02-27T21:55:48.064Z

Thanks, @borkdude

👍 1