Fork me on GitHub
#clojure
<
2021-03-26
>
lilactown02:03:40

(defmacro uh-oh []
  (let [id (gensym "id")
        m {:id id}]
   m))

(uh-oh)
;; Syntax error compiling at (*cider-repl Code/flex:localhost:50966(clj)*:35:7).
;; Unable to resolve symbol: id20798 in this context
any tricks to emit data with a symbol in it from a macro?

lilactown02:03:21

ah I unqoute then quote it

lilactown02:03:53

(defmacro uh-oh []
  (let [id (gensym "id")
        m {:id id}]
   `'~m))

(uh-oh)
;; => {:id id20812}

lilactown02:03:17

ah but that prevents eval of any of m:

(defmacro uh-oh
  [f]
  (let [id (gensym "id")
        m {:id id :f f}]
   `'~m))

(uh-oh (fn [] "foo"))
;; => {:id id20820, :f (fn [] "foo")}

phronmophobic02:03:23

(defmacro uh-oh []
  (let [id (gensym "id")
        m {:id (list 'quote id)}]
   m))
There's several ways to quote the symbol. I find the (list 'quote my-sym) idiom is easiest for me to follow.

lilactown02:03:03

aha! thank you

dpsutton02:03:43

(defmacro foo [] {:id 'id#})`

dpsutton02:03:16

the backquote is there before the map but it's quite confused the parser

lilactown03:03:08

see above why backqouting the whole map doesn't quite work

imre11:03:49

Is there any guidance on when one should use simple/qualified keywords?

1
imre11:03:58

for example {:my.company/errors [:timeout vs :my.company/timeout]}

imre11:03:21

Or even (s/keys :req [::foo]) :req being simple

teodorlu12:03:44

My rules of thumb: 1. qualified keywords for extension mechanisms, so that you can register your own stuff under your own namespace and avoid conflicts 2. simple keywords for internal implementation details The argument against qualified keywords is mostly brevity.

teodorlu12:03:02

I'd love to see seasoned Clojurians like @U064X3EF3 and @U04V70XH6 reply. Ideally, I believe this should be addressed in a rationale on http://clojure.org.

teodorlu12:03:39

Perhaps here:

imre12:03:42

Yeah, I was searching for something like that at first

imre12:03:58

perhaps this is something for ask clojure

3
p-himik12:03:12

IMHO the higher the probability of name clashing, the more reasons there are to use namespaces. In a controlled environment, like the confines of my own project, I almost exclusively use plain keywords. It also makes reusability much smoother if some entities are similar, like

(defn name-with-description [{:keys [name description]}]
  [:span {:title description} name])
The above can be used everywhere on entities that have :name and optionally :description. But in an uncontrolled environment, like an API, I think namespaced keywords make more sense, especially if it's something extendable.

👍 1
teodorlu13:03:58

> perhaps this is something for ask clojure I'd give that questions as many upvotes as I can.

teodorlu14:03:18

Unfortunately, I'm unable to provide more than a single upvote!

potetm14:03:39

It’s a question of: 1. scoping 2. use case

potetm14:03:15

if the scope is the argument to a single function, then an unqualified keyword does no harm and is shorter to type

👍 1
potetm14:03:49

if the use case is, “keys in a hashmap” you always want namespaced keys

potetm14:03:13

if the use case is values in a hashmap, you might want unqualified keywords

1
potetm14:03:25

e.g. {:http/method :post} is fine

potetm14:03:57

:post in this context is somewhat universal and is never intended as the key in a map, so it cannot clash

potetm14:03:52

On the other hand, if you’re developing a new library with new terms, you might consider a namespace qualifier. {:my/mode :my.mode/fast}

potetm14:03:35

^ does little harm and is slightly easier to use tooling to search for

potetm14:03:12

but the upside is comparatively marginal if :my.mode/fast cannot clash because it’s always a value

imre15:03:52

Thanks for the input!

kenny15:03:22

A big downside to unqualified is the impact on refactors. We have a large code base. If someone uses an extremely common keyword (e.g., :type), you need to sort through hundreds of uses. It is very likely you'll miss one and potentially break the code in a very nuanced way (yay optional keys).

1
simongray15:03:47

True, and editors like Cursive and probably other setups too allow refactoring namespaced keywords like vars

✔️ 2
borkdude15:03:41

I always wonder why tools.deps does :git/url but then :sha unqualified

2
alexmiller15:03:43

The original idea was to have at least one key qualified so we could determine procurer type, but not the others for conciseness

alexmiller15:03:16

In retrospect, that was dumb

alexmiller15:03:55

May allow both in the future

borkdude16:03:28

Right now I'm designing a task runner "DSL". First I had:

{:task/type :shell (or some other task dispatch key)
 :task/args ...
 ... other task specific opts}
but this was getting too verbose. Since type + args were always there, I could just go with hiccup style:
[:shell { optional opts map } ... args ...]
But because every task can have some general options like :description I didn't want to put those in the task options, so I went with:
^{:description ...} [:shell { optional opts map } ... args ...]
Kind of like docstring meta. But that's also a bit weird maybe. So I'm considering:
[:shell {:task/description ...  other opts } ... args ...]
now where :task/* are general task opts which can never conflict with the other task specific opts because of the namespace... :thinking_face: .

👍 1
borkdude16:03:22

Full example:

{:tasks {:clean [:shell "rm" "-rf" "target"]
         :uberjar [:shell "clojure" "-X:uberjar" ":jar" "foo.jar"]
         :all [:do {:task/description "Execute all steps"}
               [:clean]
               [:uberjar]]}}

👍 1
borkdude16:03:13

It still feels a bit wrong though, to put the :task/description in that spot.

borkdude16:03:02

well maybe it's ok, since in HTML you also put "id" etc on random HTML elements of different types

teodorlu16:03:18

So you'd want to avoid conflicts between :clean, :shell, :uberjar, :do and the like? Some options come predefined (shell, clean, do), others are defined by the user (clean, uberjar)?

borkdude16:03:26

Avoid conflicts between options passed to the tasks, e.g.:

[:shell {:task/description ... :shell/opts {...}}]
I don't necessarily want to namespace every single keyword

borkdude16:03:25

yes, there is also the issue of user defined clashing with built-ins like :shell, but I don't like [:task/shell ....] [:task/babashka ....] etc, I think

teodorlu16:03:42

Mhm, it gets too verbose quickly.

borkdude16:03:46

instead users can choose namespaced keywords

borkdude16:03:21

if there are clashes, or just choose a different name. The user-defined task will always be chosen first, so if they override, they just make something inaccessible for themselves, which is not the end of the world

👍 1
teodorlu16:03:31

> instead users can choose namespaced keywords That might make sense, especially if one is referring more to the predefined symbols than ones own additions

borkdude16:03:06

it's a similar problem with overriding vars with local symbols in clojure

teodorlu16:03:16

yeah, I was thinking the same thing.

teodorlu16:03:23

I really like Clojure's namespacing mechanism.

teodorlu16:03:55

And as long as you allow the user to override definitions, you won't break users if you add new symbols to the default environment

borkdude16:03:05

exactly

✔️ 1
borkdude16:03:29

maybe I should add a way to always refer to the built-in, e.g. :task/shell will always refer to the built-in, no matter if you overrode (?) it with :shell, like clojure.core/name always refers to the built-in var

teodorlu16:03:46

I've make a mistake of "namespacing everything with too much cruft" before myself. I had just learned about namespaced keywords. I was designing a data interface with EDN, so I just namespaced everything. How did people like it? It was a hassle. Didn't help that I just used ::key-name in my Clojure file, so there were multiple layers in the namespace as well. I don't want to do that mistake again.

teodorlu16:03:13

> maybe I should add a way to always refer to the built-in I like that idea.

teodorlu16:03:52

Could make reusing parts of task runner specifications less problematic.

wilkerlucio16:03:18

I think using qualified keywords add a lot of leverage in your codebase, as mentioned before, its great for refactoring, but also for understanding the code base, a find usages in a qualified keyword can give you an accurate view about what that property means across the system (and how its being used). For enumerations is also great, in editors like Cursive, instead of going to look up in a documentation about what are the options, if you have something like :my.task.type/foo and :my.task.type/bar, on typing :my.task.type, you can see the options right there

borkdude16:03:05

note that dots in the name part of keywords are not officially supported (if you have :my.task.type as a standalone keyword)

imre16:03:34

I think he meant typing the namespace part will bring up auto-complete

wilkerlucio16:03:35

I mean't more as a prefix for auto-complete, not as an actual keyword (the last one)

borkdude16:03:43

right, my bad

teodorlu16:03:25

@U066U8JQJ I'm guessing you're speaking from experience? I'd love to hear examples / instances where qualified keywords have enabled nice workflows.

teodorlu16:03:44

and also where you'd consider them not necessary.

wilkerlucio16:03:13

I do a lot of fully qualified keywords in Pathom for example, and in Pathom I use a more hard-core attribute driven philosophy (and Pathom itself is an expression of this idea), so for example, in the planner (https://github.com/wilkerlucio/pathom3/blob/master/src/main/com/wsscode/pathom3/connect/planner.cljc) or runner (https://github.com/wilkerlucio/pathom3/blob/master/src/main/com/wsscode/pathom3/connect/runner.cljc) you can see a lot of keyword definitions at start. those keywords are mostly used inside the same namespace, but are not limited to it. so when I need get back on feet in some complex part of the code that I haven't touched in a while, I can see which keywords participate in the process, and by following their usages I can quickly remember all the places in which its used. I believe that having consistent property names (close the same way we regard our functions, as an independent being) enables consistent re-usage of these same keywords, and them their semantics can flow over the system

💯 1
wilkerlucio16:03:38

about short keywords, I do use them as well, but I try to avoid most of the time

👍 1
teodorlu16:03:17

Thanks for explaining!

wilkerlucio16:03:45

hope it makes sense, I'm deep in this rabbit hole :P

teodorlu16:03:08

(>def ::node-id "docstring" pos-int?)
i'm guessing does: 1. Document the "attribute" 2. "establish it" (don't use not-established qualified namespaces) 3. specs the value ?

wilkerlucio16:03:20

correct, I'm using Guardrails, which is some syntax on top of spec, this doc is purely for the code reader (you can't access it at runtime)

👍 1
wilkerlucio16:03:01

I wasn't aware of that, not sure why its on deprecate list (@U0CKQ19AQ maintains Guardrails), gotta understand why

👍 1
🤞 1
teodorlu16:03:44

For the record, I've spent enough time guessing what "name" can actually be for JSON documents in codebases to be curious about a better way.

teodorlu16:03:43

@U08BJGV6E sorry for derailing your thread 😅

imre16:03:35

This is the sort of discussion I was hoping for 😄

😊 1
imre16:03:04

I might link this thread into the askclojure once it's available in the log

👍 1
wilkerlucio17:03:48

@U3X7174KS on your "name", issue, that's a great case to start seeing it, unqualified names always require some context to understand, but a lot of times this context is implicit, and then its up for the reader to interpret it. being fare, its easy in a lot of cases, but the worst is when you are confident, but wrong at the same time. this is the kind of problem qualified keywords can eliminate, if they are big enough (same considerations as for functions), you can always be confident about their meaning, no need to know the context

👍 2
wilkerlucio17:03:00

but its a hard battle, JSON dominates everything, and I find unlikely that the industry is changing in that direction anytime soon, so for those wanting this path, there is a lot of "naming expansion" to be done while interacting with external sources of data, but I believe in Clojure world we are much closer, and we could have a nice ecosystem of qualified names here

👍 2
borkdude18:03:01

FWIW JSON totally accepts "foo/bar" keys

borkdude18:03:42

GraphQL however, maybe not, I found their support for these kinds of things lacking in some respects when I checked it out some years ago. A step back from RDF.

☝️ 2
😭 1
jjttjj19:03:11

The whole "hard-core attribute driven philosophy" makes a ton of sense to me when I hear people talk about it but then I do tend to struggle with how exactly to implement it when I sit down to code something. Thus I can never get enough of these kinds of discussions 🙂

1
rgm20:03:20

My general conventions here are informed by a lot of re-frame use and Clojurescript having weaker support for namespace aliasing: 1. Internal to a namespace: unqualified generally 2. If it’s being shared out (eg. a re-frame handler key) then it gets namespaced. 3. Auto-namespacing is only to be used for internal reference to a global thing (eg. the re-frame database … (assoc db ::data-belonging-to-this-ns ,,,) … ugly, but it helps to track down its provenance in tools like re-frame-10x 4. No auto-namespacing ever for anything intended to be shared. It breaks grep and :: looks too much like : when I’m tired. I know things like clojure-lsp and cursive can overcome this but I like to keep lowest-common-denominator outside-the-editor tools like grep working OK.

rgm20:03:56

(I also would put in a big upvote for related guidance on the idea that code namespaces and keyword namespaces don’t necessarily need to be conflated … when should they be kept in sync and when is it wiser to let them drift. IMO the conflation of the two leads to some pretty ugly and un-readable namespaced keywords. Sometimes I find a domain concept namespace hierarchy on keywords helpful and this doesn’t necessarily match up cleanly to the location of code files in a directory tree).

wilkerlucio21:03:31

Im really looking forward to https://clojure.atlassian.net/plugins/servlet/mobile?originPath=%2Fbrowse%2FCLJ-2123#prompt?redirectUri=%2Fbrowse%2FCLJ-2123, that will reduce the annoying part of big names, by making it easier to alias, specially for the pure domain cases

💯 4
aratare14:03:33

Hi there. So I’m playing around with spec and plumatic’s schema at the moment and I’m not sure how to approach this. In Schema, you can have (s/set-fn-validation! true) run and any function schema I define thereafter will be checked automatically. I notice that in spec I can do (stest/instrument) but it only works if I call it after the spec has been defined. Is there a way I can turn instrumentation on first and define specs later? Alternatively, what is the most standard workflow to use spec when deving?

borkdude14:03:32

@rextruong one way is to hook this in your (dev) component / integrant-like system, so when you restart it, the specs are instrumented

aratare14:03:21

So every time I define a new function spec I’ll need to restart my dev component you mean?

borkdude14:03:44

similar to routes and a web server I guess

aratare14:03:08

Ah I see. That’s indeed one way to do it.

aratare14:03:30

Actually I may adopt that. Thanks 🙂

kirill.salykin14:03:05

Hi there is overloaded java method

serviceConfiguration(Consumer<S3Configuration.Builder> serviceConfiguration) 
serviceConfiguration(S3Configuration serviceConfiguration) 
how I can type hint param so correct method is picked? I am trying this - but no luck
(.serviceConfiguration ^S3Configuration (-> (S3Configuration/builder)
                                                             (.pathStyleAccessEnabled true)
                                                             (.build)))
https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3BaseClientBuilder.html please help thanks

p-himik15:03:39

The second overload of (.serviceConfiguration ...) that you need receives not one but two aguments - this and serviceConfiguration. You're providing only serviceConfiguration, thus replacing this with it.

kirill.salykin15:03:53

my bad it is a bit trickier

(cond-> (S3Client/builder)

             (and access-key secret-key)
             (.credentialsProvider (StaticCredentialsProvider/create
                                    (AwsBasicCredentials/create access-key
                                                                secret-key)))


             endpoint
             (-> (.serviceConfiguration ^S3Configuration (-> (S3Configuration/builder)
                                                             (.pathStyleAccessEnabled true)
                                                             (.build)))
                 (.endpointOverride (URI. endpoint)))

             true
             (.build))

kirill.salykin15:03:03

so this seems be provided

kirill.salykin15:03:32

and the error i recieve indicates that it expects Consumer

p-himik15:03:46

Hmm, right. No idea why, sorry.

lassemaatta16:03:43

(fyi: there's always https://github.com/cognitect-labs/aws-api which I found really nice to use)

kirill.salykin19:03:06

aws-api rather limited in its possibilities anyway, I’d like to understand how to type hint param so clojure can be proper method

vemv20:03:56

maybe I'd remove -> and use simple chains (or let). -> can introduce some uncertainty if you aren't intimately familiar with it as a second trick, you can println the class of the object being built. maybe it's not what you think it is and as a last resource, you could use java's reflection api directly instead of clojure interop syntax