This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-10-08
Channels
- # announcements (20)
- # aws (16)
- # babashka (63)
- # beginners (75)
- # calva (35)
- # cider (2)
- # clj-commons (5)
- # clj-kondo (2)
- # cljs-dev (1)
- # clojure (90)
- # clojure-australia (3)
- # clojure-europe (16)
- # clojure-france (1)
- # clojure-nl (4)
- # clojure-uk (5)
- # clojurescript (7)
- # data-science (2)
- # datahike (1)
- # datomic (39)
- # emacs (31)
- # events (2)
- # figwheel-main (1)
- # fulcro (15)
- # gratitude (8)
- # helix (17)
- # holy-lambda (1)
- # introduce-yourself (1)
- # jobs (3)
- # kaocha (2)
- # liquid (1)
- # malli (1)
- # nrepl (2)
- # other-languages (1)
- # portal (76)
- # react (19)
- # reagent (9)
- # remote-jobs (1)
- # rewrite-clj (9)
- # shadow-cljs (31)
- # tools-deps (5)
- # xtdb (11)
There is a shortcut way in clojure which I always forget to avoid explicit calls to gensym
. Can someone remind me of a link to this technique.
I.e., here is a macro I often use to shadow the clojure.test
macro of the same name. But as I understand I can write this without an explicit call to gensym
(defmacro testing
[string & body]
(let [verbose (gensym)]
`(gns/call-with-genus-env
(let [~verbose false]
(fn []
(when ~verbose (println [:testing ~string :starting (java.util.Date.)]))
(clojure.test/testing ~string [email protected])
(when ~verbose (println [:finished (java.util.Date.)])))))))
And you can always see something like (source when-some)
and notice how it uses temp#
.
thanks, I'm not sure what you mean by "just like (doc gensym) describes it. here is what i see from (doc gensym)
clojure-rte.rte-core> (doc gensym)
-------------------------
clojure.core/gensym
([] [prefix-string])
Returns a new symbol with a unique name. If a prefix string is
supplied, the name is prefix# where # is some unique number. If
prefix is not supplied, the prefix is 'G__'.
nil
clojure-rte.rte-core>
It doesn't describe how to use #
, but it mentions prefix#
- can be used as a shortcut to remember it.
so what is happening? is the reader manipulating foo# into a unique symbol, or is this something defmacro
does?
for example, sometimes for complicated macros, I write helper function to generate parts of the expansion. can I use foo# in those non-macro helper functions?
outside syntax quote foo# is just normal symbol
tldr; syntax quote is just a template for the code with few rules like ~
, [email protected]
and symbol#
And every new syntax quoted form introduces a new context for #
:
user=> (macroexpand `(a# a#))
(a__170__auto__ a__170__auto__)
user=> (macroexpand '(`a# `a#))
((quote a__175__auto__) (quote a__176__auto__))
Notice how we have the same symbol in the case of one backtick and different symbols in the case of two.ahhh it's the syntax quote which does it. I wouldn't have guessed that
so in the rare case that my macro does not use syntax-quote, I have to use gensym
more like “syntax-quote rules are meaningless outside of syntax-quote” so you have to use gensym. But at the end syntax-quote is just some sugar to make macro code looks readable )
yes you can do that but that can get really confusing really quick ("what does a symbol pointing to a symbol mean")
You can use the symbol pointing to a symbol as part of the expression that the macro returns when called. The symbol's value (which is the (gensym)
) is returned as part of the returned expression. Here is a silly example:
user> (defmacro test
[value]
(let [x (gensym)]
(list 'def x value)))
#'user/test
user> (test 10)
#'user/G__31039
user> G__31039
10
user>
λ clj -Srepro
Clojure 1.10.3
user=> (-> [1] (iterate inc))
Error printing return value (IllegalArgumentException) at clojure.lang.APersistentVector/null (APersistentVector.java:294).
Key must be integer
(#object[clojure.core$inc 0xbcb09a6 "[email protected]"] user=>
What's the reason behind the printing anomaly there? Specifically, the (#object[clojure.core$inc 0xbcb09a6 "[email protected]"]
followed by no newline before the user=>
prompt. I imagine it relates to macro expansion somehow, but not sure.no, it's printing the lazy seq and encounters an error in the middle of printing
does anyone know if hiccup can output these conditional html comments
<!--[if !mso]><!-->
<style>
...
</style>
<!--<![endif]-->
You can use hiccup.util/raw-string
for that. But, of course, there would be no nesting.
For proper nesting, you'd have to extend the HtmlRenderer
protocol with some type that would represent such a comment.
Hey all!
I am trying to use the instance?
function over an instance of a record defined in a separate namespace but I'm having issues during compilation:
(ns myapp.a)
(defrecord Foo [])
(ns myapp.b
(:import (myapp.a Foo)))
(defn is-foo?
"Returns true if x is an instance of Foo, false otherwise."
[x]
(instance? Foo x))
When I try to compile this I get an error like:
Syntax error compiling at (myapp/b.clj:1:1). java.lang.ClassNotFoundException: myapp.a.Foo
I've tried adding myapp.a
in the :aot
of project.clj
with no luck.
Please, has anyone already faced this kind of issue?@clement.ronzon you also need to :require
the namespace in order to just in time define the class
Not entirely related, but if you define is-foo?
in the same ns where you use defrecord
, you avoid this problem completely and also group related code together.
Thank you @borkdude, I'll try that! 🙂
@p-himik yes, that's something I considered but my code is way more complex than that and it would fall in a circular dependency issue ... which is something I might want to look at fixing in the first place ... 😁
not directly your issue, but I think it would be more idiomatic in clojure code generally to check via satisfies?
for the protocol you care about, rather than conditionally checking for some required class (since we so rarely use concrete inheritance, checking for instantiation is usually not helpful)
Thank you @U051SS2EU! I'll check that out! 🙂
@borkdude & @p-himik I tried both proposed solution and I keep seeing the ClassNotFoundException
when I run lein repl
and lein uberjar
. The test-refresh
plugin though seems not have this issue at all.
NVM, I found why, I was requiring it this way: :require [myapp.a :refer [Foo]]
which wasn't working. Though it works with :require [myapp.a :refer :all]
.
ok, thanks for the tips, I'll try that
I noticed that in my ns
declaration, if I have the :import
before the :require
, it fails. The other way around has no problem. I never thought the order would matter!
Dang, that was the root cause of my problem then 😄
@clement.ronzon btw, clj-kondo is a linter for clojure which can warn you about :refer :all
and also about unused namespaces, but it won't warn you if you don't use :refer
or :alias
, because then the namespace was supposedly required for side effects, like the above case
Hey y’all I’m curious about popular opinion on code like this:
;; ## HTTP Status codes
;;
;; This section creates a series of variables representing legal HTTP
;; status codes. e.g. `status-ok` == 200, `status-bad-request` == 400,
;; etc.
(def http-constants
(->> java.net.HttpURLConnection
r/reflect
:members
(map :name)
(map str)
(filter #(.startsWith % "HTTP_"))))
(defn http-constant->sym
"Convert the name a constant from the java.net.HttpURLConnection class into a
symbol that we will use to define a Clojure constant."
[name]
(-> name
(s/split #"HTTP_")
second
((partial str "status-"))
(.replace "_" "-")
(.toLowerCase)
symbol))
;; Define constants for all of the HTTP status codes defined in the
;; java class
(doseq [name http-constants]
(let [key (http-constant->sym name)
val (-> (.getField java.net.HttpURLConnection name)
(.get nil))]
(intern *ns* key val)))
To me, this seems like a very bad anti-pattern, creating vars this way. Does anyone actually do stuff like this in your code bases?Doesn't seem that crazy. I would probably just opt to accept keywords (eg. :http.status-code/ok
or something better) if you want to name the status codes in code rather than using my.http/ok
. I might additionally create a var in namespace that lists all the known status codes and potentially provides a doc string.
But I have a decades'-long belief that reflection should be reserved for frameworks and low-level libraries and used sparingly there.
It is more useful if you're forced to write interop code with some java library that requires some special enums
@U01GXCWSRMW I have the same reaction! Our code base only uses like, 15 of these HTTP status vars so it literally would be less code to make this hard coded. I’m not willing to throw static analysis in the trash can so fast.
Also this increases startup time! This seems like code you should use in the REPL to create a bunch of def expressions. Which is exactly what I’m doing 🤓
> Also this increases startup time Every single def increases startup time, the Clojure compiler is quite slow. Here it won't make a difference that a human could possibly notice. Also, reflection is slow if you're going to do it repeatedly, at runtime in a production app but that's not what's happening there. The work is performed once, at compile-time. The impact is a one-off cost of maybe 1ms which is negligible if your app startup time is say 30s or 60s
The code in question seems fine if it has the explicit purpose of easing interacting with HttpURLConnection. If you're creating a generic http lib instead, what seems off is using a random class' implementation details as the basis for that. It's not like HTTP codes will change over time.
@U0NCTKEV8 IIRC usually you're a proponent of interop and you speak against thin wrappers. What's different about this case?
Not a clojure developer, just keen reader, but in typescript world this would be considered an anti-pattern, mainly cause it's overly complex. Maintaining this code seems more expensive than simple hardcoded version, because: 1. Imagine you have bug involving status codes, it seems the debugging would be much harder than for a hardcoded version - this function introduces one more point of failure 2. Imagine a new developer comes - this is a piece of code that you need to share knowledge about with him/her. I can also easily imagine a new developer on the project banging the head against the wall trying to fix bug with status codes and not understanding where they come from, cause you can't find them in the codebase anymore 3. What if someone wants to refactor it? That's additional costs + you need to somehow make sure that refactored version works correctly, it means that ideally you need to write tests
@U019RSW97UZ Another way to go about this would be to not write this as dynamic code but as code that generates code in a file on disk, so it would literally spit out "(def foo ...)"
. That avoids doing the dynamic analysis at startup each time and also has the benefit that static analysis tools like clj-kondo would just understand them.
@p-himik the thin wrapper thing is about dependencies mostly. A dependency needs to add substantial value to justify itself(managing dependencies in long running projects is a lot of work)
I might prefer to generate predicates for the different status codes, but that is usage dependent. At work I have some code that reflectively generates predicates for all the different Braintree transaction statuses
I've also statically generated a ton of wrapper interop stuff around http://authorize.net's java sdk
Generating the stuff on disk was to some degree a crutch while figuring out our code to talk to http://authorize.net, it was nicer to look the generated code to figure out what was available and what args different calls needed then digging through their library
I see. In similar cases, when I know the set of the functions I will be working with, I tend to avoid generating any names and instead provide them explicitly, so that there's better tooling support and it's easy to reason about the code by just reading the source. Something like
(defmodel DatetimeAxis datetime-axis datetime-axis? datetime-axis-spec)
Indeed, but that's exactly my point - the tooling support suffers, the ability to just read the code suffers (you gotta remember that defmodel
also generates a bunch of symbols that you don't see explicitly, and that incurs tiny but unnecessary cognitive load).
I think clojurists, for some reason are much accepting of macros that expand to defs, then code like the above that just calls intern
With the model above, I can easily navigate to where it's defined by any of the symbols in there.
That point you are letting the automation your tools support dictate the kind of code you write instead of writing code and making tools to support it
But there is nothing stopping you from, for example, attaching file and line metadata to intern'ed vars
If that stuff is in it's own namespace, and you only ever :require :as it, do you really need to jump to definition?
> That point you are letting the automation your tools support dictate the kind of code you write
Absolutely, everything is a trade-off. I'm also letting English dictate the way I name things, even though there are many places where I could use my native Russian. :)
> attaching file and line metadata to intern'ed vars
Which still requires tooling support and run time. Also doubt it would work in CLJS since there are no true vars there.
> do you really need to jump to definition?
I do but of course YMMV.
Note that that defmodel
example above is an abridged version. It might include many more things that don't have anything to do with words "datetime axis".
I've even got some test code that doseqs over a sequence of values and interns a function marked as a test for each item (I forget exactly why I did it that way, maybe to take advantage of existing fixtures?), effectively generating deftests
Tests are different though, they aren't some API that you use on a daily basis. I wouldn't mind test code generation at all - as long as a test failure makes it crystal clear what's wrong, where, and why.
> But I have a decades'-long belief that reflection should be reserved for frameworks and low-level libraries and used sparingly there. > Reflection is just another source of data for data driven code
Reflection will also save your bacon over and over again, avoiding rewriting dependencies or maintaining forks, and getting access to key functionality
We use http://netty-socket.io at work, and it didn't (and I think still doesn't) count characters outside of the bmp correctly, which it needs to do for the http://socket.io protocol
So at work we reach through layers and layers of private and package private stuff to use our own counter
Alternatively I could spend the time and energy of maintaining our own fork, of a project I couldn't even get a clean test run of when I tried to run the tests prior to trying to submit a patch
I mean, I've been doing a lot of Google play billing work recently, and their java library is a joke for androidpublisher clearly just minimally generated code from their data model
And that is a a serious line of business for a huge company that employs a ton of developers, and a ton of outside businesses depend on it
I guess maybe I'm the only one who's had to rewrite a bunch of code the day software was deployed to an environment with the Java SecurityManager turned on preventing reflection?
reflection can break use of a library in a native-image context. You wouldn't be able to run the above code via babashka, for example.