This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-07-23
Channels
- # beginners (20)
- # boot (7)
- # cider (115)
- # cljsrn (13)
- # clojars (1)
- # clojure (122)
- # clojure-italy (23)
- # clojure-spec (60)
- # clojurescript (74)
- # data-science (7)
- # datomic (26)
- # emacs (8)
- # graphql (1)
- # lumo (26)
- # music (1)
- # off-topic (1)
- # re-frame (9)
- # ring (3)
- # rum (1)
- # spacemacs (4)
- # uncomplicate (6)
- # vim (7)
Going back to my question from this morning to introspect a Java object, here's a fn I've written to expose the fields of an object via reflection (clojure.reflect really didn't help). I've crammed it all into a single fn and it looks quite ugly. Any suggestions on how to improve the code style. I'm still a clnewb?
(defn java->map
"Turns fields of a Java object into a map, up to 'level' deep"
([obj] (java->map obj 1))
([obj level]
(when (some? obj)
(let [c (class obj)]
(cond
;;;(.isPrimitive c) obj never works because clojure implicitly wraps primitives
(contains?
#{java.lang.Long java.lang.Character java.lang.Byte
java.lang.Double java.lang.Float java.lang.Short java.lang.Integer} c) obj
(= 0 level) (.toString obj)
(instance? java.lang.String obj) obj
(.isArray c) (concat
(->> obj
(take 5)
(map (fn [e] (java->map e (dec level)))))
(when (> (count obj) 5) [:more (count obj)]))
:else
(assoc (into {} (->> (concat (.getDeclaredFields c)
(.getFields c))
(filter #(= (bit-and (.getModifiers %) java.lang.reflect.Modifier/STATIC) 0)) ;;; ignore static fields
(map
#(do (.setAccessible % true)
[(keyword (.getName %))
(java->map (.get % obj) (dec level))]))))
:-type c ;;; add the type as well
))))))
Examples:
(java->map (java.util.Date.) 4)
=> {:fastTime 1500771066642, :cdate nil, :-type java.util.Date}
(java->map (java.text.AttributedString. "bubu") 2)
=>
{:text "bubu",
:runArraySize 0,
:runCount 0,
:runStarts nil,
:runAttributes nil,
:runAttributeValues nil,
:-type java.text.AttributedString}
I'm having trouble adding metadata inside a macro. It's a bit weird (as usual) since it overrides defn
in order to capture the ast after compilation, but works otherwise:
(defmacro defn [name & decls] `(def ^{:ast ~decls} ~name (fn ~decls)))
defmacro never sees the reader macro, it's applied before it sees the form
you need to use with-meta or vary-meta to add metadata to the symbol you emit
^{:foo :bar} baz
translates to (with-meta baz {:foo :bar})
ok, so yeah so for some reason the actual fn
part doesn't compile when i write it like that:
(defmacro defn [name & decls] `(def ~(with-meta ~name {:ast ~decls}) (fn ~decls)))
nested ~ isn't valid like that
it needs to be one of those positions or the other, not both
splicing isn't valid there either
the next problem is that decls isn't going to be a valid data literal
so it needs to be escaped or quoted in some manner
right but the decls are a data literal in the metadata - so they need to be a valid one
+user=> (defmacro defn [name & decls] `(def ~(with-meta name {:ast (cons 'quote decls)}) (fn ~decls)))
#'user/defn
+user=> (defn baz [])
#'user/baz
+user=> (meta #'baz)
{:ast [], :line 19, :column 1, :file "NO_SOURCE_PATH", :name baz, :ns #object[clojure.lang.Namespace 0x373ebf74 "user"]}
user=>
wait I think cons ins the wrong way to do that... checking
yeah, my bad
+user=> (defn baz ([]) ([_]))
CompilerException clojure.lang.ExceptionInfo: Wrong number of args (2) passed to quote {:form (quote ([]) ([_]))}, compiling:(NO_SOURCE_PATH:21:1)
this fixes an issue with multi arities in the original too
+user=> (defmacro defn [name & decls] `(def ~(with-meta name {:ast (cons 'quote (list decls))}) (fn ~@decls)))
#'user/defn
+user=> (defn baz ([]) ([_]))
#'user/baz
+user=> (meta #'baz)
{:ast (([]) ([_])), :line 25, :column 1, :file "NO_SOURCE_PATH", :name baz, :ns #object[clojure.lang.Namespace 0x373ebf74 "user"]}
there's probably a better way to rewrite`(cons 'quote (list decls))` - that's super ugly
oh, right, much nicer
yeah, there's an art to this stuff, and seeing simple examples makes a difference in learning it
it's funny because in scheme i'm pretty sure i'd do some fancy transformation like closure conversion, but that's not possible since clojure is JIT compiled at the function level. but otoh, i don't think i could do it this way with scheme-style macros
now i get to play around with techniques for parsing the asts. i'll check out the muir library, but suspect i'll end up having to hack away at it myself
is there a "built-in" absolute value function? I couldn't find it on https://clojure.org/api/cheatsheet . I came up with
(if (neg? x) (- x) x)
lol of course
(I'm not a java programmer)
indeed
i was definitely expecting abs
that would be nice because it could work on longs, floats, rationals, and bignums
@chrisbroome @spangler the rationale of just using java.lang.Math is most people using those things extensively are doing performance critical stuff where the overhead of a clojure wrapper that dispatched on and preserved types would be a problem
that makes sense. one thing it might be useful to have a clojure-defined abs
, sin
, etc for is cross-target support. yeah they might be slower but at least the code would work without modification
by cross-target I mean clj, cljs, cljn (is that the .net suffix)?
Math/foo works for clj and cljs already - not sure what the .net scenario is (since js already wanted to be like java it implemented the math functions with the same api)
Does anybody have a good online resource / talk / book to recommend to learning clojure’s internals?
@hmaurer I recommend reading through https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj from start to finish
it's everything implementing the language, except the special forms and the parser and the bytecode emitter
@U3L6TFEJF ok perfect then, thanks! 7000LOC isn’t so bad actually
which calls into https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Ref.java
@U3L6TFEJF thank you!
this talk is decent https://www.youtube.com/watch?v=6DaBmz_6y0s
here’s another good one about namespaces: https://www.youtube.com/watch?v=8NUI07y1SlQ
I have a function (`refresh`) which returns either :ok
or an exception (without throwing it). I am calling refresh
in a do
block. If refresh
returns :ok
, I would like to continue the execution, but if refresh
returns an exception I would like to return that exception
I could do this with a (let [result (refresh)] (if (= result :ok) ...
but am wondering if there is a more idiomatic way
What the refresh
function actually does is I think irrelevant to answer my question, but in case anyone is wondering, it’s the one from https://github.com/clojure/tools.namespace
hey guys, I’m building a bot application as a service (websocket based , slack bots ) using clojure. I’m struggling on how to manage the sockets for multiple users. using the repl/exploratory code I’m able to connect, receive message and post message to slack, no problem at all, but I’m stuck on how to make it able to multiple users. that is my function to connect on slack websocket ( will return the ws socket )
(slack/ws-connect token
:on-message dispatch
:on-error #(.getMessage %1))
how can manage multiple sockets on my app ( creating a global map and assoc the sockets there ? )
a global map with a unique ID per websocket (and the id also attached to client account, if you have any such concept) is the normal way
that's what eg. sente has under the hood
if you have minimal state to track, you can also just have handler functions that take a request and a websocket object, and call them from the listener you attach to the socket
but if things get stateful at all the map is good
> you can also just have handler functions that take a request and a websocket object, and call them from the listener you attach to the socket can you pseudo-code that ? didn’t get
(defn do-something-for-socket [connection args] (let [result (frob args)] (send-to-connection connection result))
assuming you get something from the request that you can send data to by some method or function (that's usually how this is set up)
connection is the ws socket, right ?
how the client of do-something-for-socket
will grab the ws socket ?
I'm trying to create an uberjar for yada and are getting interesting error messages like: 'namespace 'cheshire.factory' not found'
in clojure/jdbc, are all sql statements compiled down to STRINGS, sent to SQL, then reparsed ?
I'm wondering if there is a way to send an AST or something, instead of directly sending the strings
@beders total newbie here, but is your cheshire dependency in “dependencies”, or under dev dependencies?
it is brought in by yada indirectly, but also when adding it explicitly it barfs when trying to create the uberjar
I am operating blind here, but lookign at Yada’s project.clj it is under the “test” profile dependencies
Exception in thread "main" java.lang.Exception: namespace 'cheshire.factory' not found, compiling:(cheshire/core.clj:1:1)
even weirder: the JAR file for cheshire in the requested version does have a factory.clj
yeah, I'm feeling pretty clueless myself. But running lein deps :tree
has interesting warnings. Looking at those now. Thank you!
@hmaurer I was able to get it working. I had to exclude cheshire manually and then re-add
@beders AOT is the root of a lot of problems and is best avoided, if possible. As for dependency conflicts, that's pretty common the JVM world so I'm not surprised you had to add :exclusions
-- Cheshire is a popular library and lots of other libraries depend on it so you can get any one of a whole bunch of different versions pulled in if you're not careful.