Fork me on GitHub
#clojure-dev
<
2017-04-25
>
Yehonathan Sharvit08:04:07

A question related to multimethods

Yehonathan Sharvit08:04:29

Why there is not add-method while there is remove-method?

bronsa12:04:00

it's def vs intern

bronsa12:04:45

likely because top level do are hoisted to handle the gilardi scenario

bronsa12:04:03

let keeps the whole body compiled as a single expression

nathanmarz12:04:45

hmmm, not following

nathanmarz12:04:50

why does that matter for this case?

nathanmarz12:04:33

I noticed this initially at the repl

user=> (def ^:dynamic *foo* 1)
#'user/*foo*
user=> (push-thread-bindings {#'*foo* 2})
nil
user=> *foo*
1

bronsa12:04:35

the repl pushes and pops thread bindings

Yehonathan Sharvit12:04:55

@alexmiller Is it ok to call defmethod at run time (e.g. in a handler function)?

Yehonathan Sharvit12:04:43

I mean is this code considered clean code or not?

Yehonathan Sharvit12:04:47

(defmulti foo (fn [x] x) )

(defn bar []
  (defmethod foo :a [x] :a))

Yehonathan Sharvit12:04:10

To me it looks weird to have defmethod inside defn!?!?!

bronsa12:04:35

@nathanmarz to be more explicit: both compile and eval push some thread bindings before evaluationg the expression and then pop them after evaluation finishes -- what's happening is the following: - push-thread-binding from the compiler - push-thread-binding from the user code - pop-thread-binding from the compiler

bronsa12:04:01

so the user code thread-binding gets popped and the compiler thread-binding stays on the thread local frame stack

bronsa13:04:06

the difference between (let [] (push-thread-binding ..) ..) and (do (push-thread-binding ..) ..) is that the former is executed as a single expression so it's evaluated before the compiler pops its bindings while the latter is executed as multiple expressions and the compiler interferes

bronsa13:04:49

this means that you can never push thread bindings in a top level form, and as a side-effect of the fix for the gilardi scenario, never from a top-level do either

bronsa13:04:53

because it will be unrolled

nathanmarz13:04:27

cool, thanks for the explanation

Yehonathan Sharvit14:04:11

Reasking the question because previously it was in the middle of another thread

Yehonathan Sharvit14:04:26

@alexmiller Is it ok to call defmethod at run time (e.g. in a handler function)?

(defmulti foo (fn [x] x) )

(defn bar []
  (defmethod foo :a [x] :a))

tbaldridge14:04:44

@viebel defmethod doesn't actually def a var, so using defmethod inside a defn is safe

tbaldridge14:04:31

@viebel you can also call .addMethod directly on the multimethod if you wanted, just like defmethod does: https://github.com/clojure/clojure/blob/clojure-1.9.0-alpha14/src/clj/clojure/core.clj#L1773-L1777

tbaldridge14:04:13

yep, so a defmethod inside a defn is the portable solution

bronsa14:04:58

the issue with using defmethod like that is that you can't parametrize the dispatch value

bronsa14:04:14

so using defmethod inside a defn is hardly useful

tbaldridge14:04:58

@bronsa I don't think that's correct. I've parameterized the dispatch value before

bronsa14:04:13

you might be right

bronsa14:04:29

you're definitely right

bronsa14:04:31

i brainfarted

tbaldridge14:04:41

well to be fair, most macros don't work that way, but in this case it does.

Yehonathan Sharvit15:04:05

@tbaldridge you allude that calling defn inside defn is not safe?

bronsa15:04:57

it has some gotchas and it's rarely what you want

bronsa15:04:17

user=> x
CompilerException java.lang.RuntimeException: Unable to resolve symbol: x in this context, compiling:(NO_SOURCE_PATH:0:0)
user=> (defn y [] (defn x []))
#'user/y
user=> x
#object[clojure.lang.Var$Unbound 0x573fd745 "Unbound: #'user/x"]

Yehonathan Sharvit15:04:09

What does it mean?

alanforr16:04:04

I would guess that it means x isn’t defined until y is invoked because before that the x definition hasn’t run.

tbaldridge16:04:49

The problem is x is created when the defn is compiled, but it isn't bound until y is called.