Fork me on GitHub
#clojure
<
2021-10-08
>
Jim Newton08:10:16

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 ~@body)
          (when ~verbose (println [:finished  (java.util.Date.)])))))))

p-himik08:10:50

Add # to names.

p-himik08:10:53

Just like (doc gensym) describes it - it works within macros' bodies.

p-himik08:10:24

And you can always see something like (source when-some) and notice how it uses temp#.

Jim Newton08:10:01

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> 

p-himik08:10:50

It doesn't describe how to use #, but it mentions prefix# - can be used as a shortcut to remember it.

Jim Newton08:10:15

so what is happening? is the reader manipulating foo# into a unique symbol, or is this something defmacro does?

Jim Newton08:10:00

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?

delaguardo08:10:08

outside syntax quote foo# is just normal symbol

delaguardo08:10:34

tldr; syntax quote is just a template for the code with few rules like ~, ~@ and symbol#

p-himik08:10:57

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.

Jim Newton08:10:54

ahhh it's the syntax quote which does it. I wouldn't have guessed that

Jim Newton08:10:16

so in the rare case that my macro does not use syntax-quote, I have to use gensym

delaguardo08:10:47

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 )

✔️ 1
valerauko10:10:27

for nested syntax quote gensyms there's potemkin's unify-gensyms too

p-himik10:10:03

Or you can just (let [s (gensym)] ...) outside of the quoted forms.

valerauko11:10:08

yes you can do that but that can get really confusing really quick ("what does a symbol pointing to a symbol mean")

Narendra12:10:30

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> 

flowthing13:10:23

λ 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 "clojure.core$inc@bcb09a6"] user=>
What's the reason behind the printing anomaly there? Specifically, the (#object[clojure.core$inc 0xbcb09a6 "clojure.core$inc@bcb09a6"] followed by no newline before the user=> prompt. I imagine it relates to macro expansion somehow, but not sure.

Alex Miller (Clojure team)13:10:41

no, it's printing the lazy seq and encounters an error in the middle of printing

flowthing13:10:24

Oh, I see. Makes sense. Thanks!

emccue14:10:14

does anyone know if hiccup can output these conditional html comments

<!--[if !mso]><!-->
<style>
...
</style>
<!--<![endif]-->

p-himik14:10:21

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.

emccue23:10:18

That worked perfectly, thanks

Clément Ronzon15:10:11

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?

borkdude15:10:04

@clement.ronzon you also need to :require the namespace in order to just in time define the class

p-himik15:10:01

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.

Clément Ronzon15:10:58

Thank you @borkdude, I'll try that! 🙂

Clément Ronzon15:10:08

@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 ... 😁

👍 1
noisesmith16:10:28

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)

Clément Ronzon16:10:24

Thank you @U051SS2EU! I'll check that out! 🙂

Clément Ronzon19:10:29

@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.

borkdude19:10:07

what exactly did you try?

Clément Ronzon20:10:00

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].

p-himik20:10:29

Just use :require myapp.a with that :import - should work.

p-himik20:10:52

Never use :refer :all unless you really know why you're doing that.

Clément Ronzon20:10:57

ok, thanks for the tips, I'll try that

Clément Ronzon20:10:39

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!

p-himik20:10:23

Yeah, the ns macro turns all its subforms into corresponding calls, all in order.

Clément Ronzon20:10:41

Dang, that was the root cause of my problem then 😄

Clément Ronzon20:10:53

TY for helping me through this!!

👍 2
borkdude20:10:26

@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

Stel Abrego22:10:55

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?

R.A. Porter22:10:56

My opinion is most succinctly summarized as, :face_vomiting:

👎 1
phronmophobic22:10:57

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.

R.A. Porter22:10:12

But I have a decades'-long belief that reflection should be reserved for frameworks and low-level libraries and used sparingly there.

👍 1
phronmophobic22:10:15

It is more useful if you're forced to write interop code with some java library that requires some special enums

👍 1
☝️ 1
hiredman23:10:02

it is great

Stel Abrego00:10:55

@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.

Stel Abrego00:10:11

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 🤓

vemv05:10:01

> 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

vemv05:10:36

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.

p-himik06:10:03

@U0NCTKEV8 IIRC usually you're a proponent of interop and you speak against thin wrappers. What's different about this case?

vmajsuk07:10:18

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

☝️ 1
borkdude08:10:32

@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.

👍 4
hiredman16:10:10

@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)

hiredman16:10:27

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

hiredman16:10:45

I've also statically generated a ton of wrapper interop stuff around http://authorize.net's java sdk

hiredman16:10:33

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

p-himik16:10:16

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)

hiredman16:10:14

You could wrap the reflection up in a macro and generate defs

p-himik16:10:35

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).

hiredman16:10:52

I think clojurists, for some reason are much accepting of macros that expand to defs, then code like the above that just calls intern

p-himik16:10:04

With the model above, I can easily navigate to where it's defined by any of the symbols in there.

hiredman16:10:01

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

hiredman16:10:00

But there is nothing stopping you from, for example, attaching file and line metadata to intern'ed vars

hiredman16:10:00

If that stuff is in it's own namespace, and you only ever :require :as it, do you really need to jump to definition?

p-himik16:10:43

> 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".

hiredman16:10:48

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

p-himik16:10:47

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.

hiredman16:10:52

Also, reflection is great

hiredman16:10:28

> 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

hiredman16:10:53

Reflection will also save your bacon over and over again, avoiding rewriting dependencies or maintaining forks, and getting access to key functionality

p-himik16:10:11

How does that work?

p-himik16:10:29

You mean accessing internals?

hiredman16:10:44

For some reason people who write java reflexively mark the best code as private

hiredman16:10:11

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

hiredman16:10:53

So at work we reach through layers and layers of private and package private stuff to use our own counter

hiredman16:10:33

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

p-himik17:10:14

That's a nice example. Although a nightmare situation to be in.

hiredman17:10:02

Software development for business is a series of nightmare situations

p-himik17:10:31

:D Depends on the business, but yeah.

hiredman17:10:01

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

hiredman17:10:01

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

hiredman17:10:09

Just a nightmare

R.A. Porter03:10:23

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?

waffletower19:12:11

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.