How do you make a callable object in Clojure? - clojure

How do you make a callable type or object in Clojure?
For example, how could I define a record Foo taking a single value :bar which could be called to print that value?
user=> (def foo (Foo. "Hello world"))
user=> (foo)
Hello World
user=> (:bar foo)
"Hello World"

(defrecord Foo [bar]
clojure.lang.IFn
(invoke [_] (println bar)))
((Foo. "Hello, world!"))
;; => Hello, world!
(:bar (Foo. "Hello, world!"))
;; => "Hello, world!"
...Whether doing this is a good idea is another question.
Records implementing IFn
(defrecord Foo [bar]
clojure.lang.IFn
(invoke [_] (println bar))
(applyTo [this args] (clojure.lang.AFn/applyToHelper this args)))

Related

Clojure multi method dispatch on class of all arguments

I am trying to write a multi-method which dispatches based on the types of all the arguments passed to it, but I am struggling to figure out how to write such a distpatch fn.
By that I mean, given:
(defmulti foo (fn [& args] ...))
(defmethod foo String
[& args]
(println "All strings"))
(defmethod foo Long
[& args]
(println "All longs"))
(defmethod foo Number
[& args]
(println "All numbers"))
(defmethod foo :default
[& args]
(println "Default"))
Then we would get:
(foo "foo" "bar" "baz") => "All strings"
(foo 10 20 30) => "All longs"
(foo 0.5 10 2/3) => "All numbers"
(foo "foo" 10 #{:a 1 :b 2}) => "Default"
(defmulti foo (fn [& args] (into #{} (map class args))))
(defmethod foo #{String}
[& args]
(println "All strings"))
(defmethod foo #{Long}
[& args]
(println "All longs"))
(defmethod foo #{Number}
[& args]
(println "All numbers"))
(defmethod foo :default
[& args]
(println "Default"))
Results in:
(foo "foo" "bar" "baz") => "All strings"
(foo 10 20 30) => "All longs"
(foo 0.5 10 2/3) => "Default"
(foo "foo" 10 #{:a 1 :b 2}) => "Default"
So the Number example doesn't work.
Here is the dispatcher fn:
(defmulti foo (fn [& args]
(let [m (distinct (map type args))]
(when (= 1 (count m))
(first m)))))

Clojure destructure map using :keys with qualified keywords not working

I've tried this with both 1.7.0 and 1.8.0 and it appears that Clojure does not destructure maps using :keys whose keys are fully qualified. I don't think it has to do with being in the tail on the arguments as it doesn't work when I switch around the function argument positions as well.
(ns foo.sandbox)
(def foo ::foo)
(def bar ::bar)
(defn normalize-vals
[mmap & [{:keys [foo bar] :as ops}]]
(println "normalize-vals " ops " and foo " foo " bar" bar))
(normalize-vals {} {foo 1 bar 2})
=> normalize-vals {:foo.sandbox/foo 1, :foo.sandbox/bar 2} and foo nil bar nil
However; this works:
(defn normalize-vals
[mmap & [{a foo b bar :as ops}]]
(println "normalize-vals " ops " and foo " a " bar" b))
(normalize-vals {} {foo 1 bar 2})
=> normalize-vals {:cmt.sandbox/foo 1, :cmt.sandbox/bar 2} and foo 1 bar 2
Is this a defect?
Let's take your function as is:
(defn normalize-vals
[mmap & [{:keys [foo bar] :as ops}]]
(println "normalize-vals " ops " and foo " foo " bar" bar))
Note that foo & bar above are local bindings, they do not refer to anything outside of the function.
...and rewrite the other bits of code a bit:
(def foo-const ::foo)
(def bar-const ::bar)
Don't pay too much attention to the naming here, the point is to use different names.
(normalize-vals {} {foo 1 bar 2})
;; error: ...Unable to resolve symbol: foo in this context...
(normalize-vals {} {foo-const 1 bar-const 2})
;; prints: normalize-vals {:user/foo 1, :user/bar 2} and foo nil bar nil
The lesson should be to use unique names as much as possible.
Why did the 2nd variant work?
In the destructuring form {a foo b bar :as ops};
a & b are new local bindings. If we had a var named a or b these would override them within the scope of this function.
foo & bar are resolved from the environment. If we don't suffix them with -const as above we would get a CompilerException just like the one above.
You are destructuring using unqualified keywords, so instead of:
[mmap & [{:keys [foo bar] :as ops}]]
you should use
[mmap & [{:keys [::foo ::bar] :as ops}]]
You can use clojure.walk/macroexpand-all to expand normalize-vals:
(clojure.walk/macroexpand-all '(defn normalize-vals
[mmap & [{:keys [foo bar] :as ops}]]
(println "normalize-vals " ops " and foo " foo " bar" bar)))
=> (def normalize-vals (fn* ([mmap & p__26720] (let* [vec__26721 p__26720 map__26722 (clojure.core/nth vec__26721 0 nil) map__26722 (if (clojure.core/seq? map__26722) (. clojure.lang.PersistentHashMap create (clojure.core/seq map__26722)) map__26722) ops map__26722 foo (clojure.core/get map__26722 :foo) bar (clojure.core/get map__26722 :bar)] (println "normalize-vals " ops " and foo " foo " bar" bar)))))
The important part of the expansion to note is:
foo (clojure.core/get map__26722 :foo)
bar (clojure.core/get map__26722 :bar)
The keys in the map destructuring are therefore converted to keywords at compile time and the values from the foo and bar vars in the namespace will not be used.

Passing variables and metadata

I wrote a short function for debugging:
(defn printvar
"Print information about given variables in `name : value` pairs"
[& vars]
(dorun (map #(println (name %) ":" (eval %)) vars)))
Then I tried to test it:
(defn -main [arg1 arg2]
(def moustache true) (def answer 42) (def ocelots-are "awesome!")
(printvar 'moustache 'answer 'ocelots-are)
(printvar 'arg1 'arg2))
But ran into some really confusing behaviour:
$ lein repl
> (-main "one" "two")
# moustache : true
# answer : 42
# ocelots-are : awesome!
# CompilerException java.lang.RuntimeException: Unable to resolve symbol: arg1 in this context, compiling:(/tmp/form-init4449285856851838419.clj:1:1)
$ lein run "one" "two"
# Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol: moustache in this context, compiling:(/tmp/form-init4557344131005109247.clj:1:113)
Experimenting a bit more, I discovered this:
(defn -main [arg1 arg2]
(meta #'arg1))
# Exception in thread "main" java.lang.RuntimeException: Unable to resolve var: arg1 in this context, compiling:(dict_compress/core.clj:9:11)
(defn -main [arg1 arg2]
(def arg1 arg1)
(meta #'arg1))
# {:ns #<Namespace dict-compress.core>, :name arg1, :file dict_compress/core.clj, :column 2, :line 10}
Now I'm totally confused.
What exactly are you passing when you do (f 'var) and (f var)?
Why are there different behaviours when run from the REPL versus directly?
What's the difference between a received argument versus a defined variable?
How can I fix my code?
Am I going about debugging the wrong way?
Inside printvar the def'ed vars moustache answer and ocelots-are are correctly printed because def defines them as "globals".
Meaning there is a moustache var that the printvar function can "see".
Think about it this way, this works:
(def moustache 43)
(defn printvar []
(println moustache)
(defn main [arg1]
(printvar))
This doesn't work:
(defn printvar []
(println arg1))
(defn main [arg1]
(printvar))
Which is exactly what you're doing, passing the parameter name to eval does nothing for the parameter scope (printvar won't be able to see it).
A couple of issues with your code:
You shouldn't be defing inside a function, local bindings are defined with let
If you want to eval you need to consider scope of what you're evaling.
Just to elaborate on #Guillermo's comment, here is a macro that does the printing of any variable, locally or globally bound.
(defmacro printvar
([])
([v & more]
`(let [v# ~v]
(println '~v "=" v#)
(when (seq '~more)
(printvar ~#more)))))
With this you can try the sequence :
user> (def glob-var "foo")
#'user/glob-var
user> (defn -main [loc1 loc2]
(printvar glob-var loc1 loc2))
#'user/-main
user> (-main "bar" 42)
glob-var = foo
loc1 = bar
loc2 = 42
nil
user>

Setting a debug function from the command line in Clojure

I have a namespace like this:
(ns foo.core)
(def ^:dynamic *debug-fn*
"A function taking arguments [bar baz]"
nil)
(defn bar-info
[bar _]
(println bar))
(defn baz-info
[_ baz]
(println baz))
(defn do-stuff
[bar baz]
(when *debug-fn* (*debug-fn* bar baz)))
(defn -main
[& {:keys [debug-fn]}]
(binding [*debug-fn* (symbol debug-fn)] ;; THIS WON'T WORK!
(do-stuff 27 42)))
What I would like to do is allow a debug function to be specified from the command line like this: lein run bar-info or lein run baz-info.
I'm not sure how to take the string specified as a command-line argument and turn it into the namespace-qualified function to bind. Do I need a macro to do this?
Use ns-resolve, you will need to specify namespace where your function is defined though.
user=> (defn f [n] (* n n n))
#'user/f
user=> ((ns-resolve *ns* (symbol "f")) 10)
1000
Use alter-var-root:
user=> (doc alter-var-root)
-------------------------
clojure.core/alter-var-root
([v f & args])
Atomically alters the root binding of var v by applying f to its
current value plus any args
nil
user=> (alter-var-root #'*debug-fn* (fn [v] (fn [x] (println x) x)))
#<user$eval171$fn__172$fn__173 user$eval171$fn__172$fn__173#7c93d88e>
user=> (*debug-fn* 1)
1
1
Though I've accepted Guillermo's answer above, I figured that it might also be useful to add the solution I ended up going with:
(def debug-fns
{:bar-info (fn [bar _] (println bar))
:baz-info (fn [_ baz] (println baz))
(def active-debug-fns (atom []))
(defn activate-debug-fn!
[fn-key]
(let [f (debug-fns fn-key)]
(if f
(swap! active-debug-fns conj f)
(warn (str "Debug function " fn-key " not found! Available functions are: "
(join " " (map name (keys debug-fns))))))))
(defn debug-fn-keys
[args]
(if (args "--debug")
(split (or (args "--debug") "") #",")
[]))
(defn do-stuff
[bar baz]
(doseq [f #active-debug-fns]
(f bar baz)))
(defn -main
[& args]
(let [args (apply hash-map args)]
(doseq [f (debug-fn-keys args)]
(activate-debug-fn! (keyword k)))
(do-stuff 27 42)))
So now you can say something like lein run --debug bar-info to get info on bars, or lein run --debug bar,baz to get info on both bars and bazes.
Any suggestions to make this more idiomatic will be happily accepted and edited in. :)

Get string name of passed in var in clojure?

How do I get the string name of a 2nd nested var like so?
(def bar "abc")
(defn string-var [foo]
(...))
(= "bar" (string-var bar))
You can do this with macro
(def bar "abc")
(defmacro string-var [foo]
(name foo))
(string-var bar)
=> "bar"