Fork me on GitHub
#clojure
<
2020-10-21
>
cpmcdaniel00:10:54

Is there a way to tell if RT.init() has already been called? RT.INIT boolean is private.

Alex Miller (Clojure team)00:10:20

Generally you shouldn’t need to ask that question so maybe back up and explain why you want to

ag01:10:42

How do you use a list inside a unquote splicing thing? Like if I have a macro, something like this:

(defmacro foo
  [entities-list]
  (let [es (gensym 'entities)]
    `(let [~es  ~entities-list
           result [email protected](for [e [email protected] ;;; <- this is wrong. how do I refer to this list?]
                      (boolean e))]
       result)))

hiredman03:10:38

es is not a list, because the for is unquoted, that means the code is evaluated at macro expansion time, not runtime, and es is at runtime a list, but at macro expand time it is a symbol

ag01:10:41

[email protected](for [e ~es]
says: Attempting to call unbound fn: #'clojure.core/unquote
[email protected](for [e `~es]
says: Don't know how to create ISeq from: clojure.lang.Symbol
[email protected](for [e `[email protected]]
says: splice not in list

onetom01:10:14

Can the output of clojure.pprint/pprint considered EDN? (of course when it contains some non-clojure with custom print method implementation, then it isn't EDN.)

Alex Miller (Clojure team)01:10:28

@ag you can't unquote inside an unquote

ag02:10:58

damn it.... but... I really want to.

onetom01:10:08

in other words, is (clojure.data.edn/read (with-out-str (clojure.pprint/pprint x))) always does the same as (clojure.data.edn/read (pr-str x)?

Alex Miller (Clojure team)01:10:40

@onetom no it's not necessarily edn, and no it's not necessarily readable

Alex Miller (Clojure team)01:10:05

pprint may for example truncate long collections depending on your settings

👍 3
Alex Miller (Clojure team)02:10:41

@ag try just using autogensym inside the syntax quote (no need to unquote then, it's in the emitted code)

`(let [es# ~entities-list
       result [email protected](for [e es#] ... 

ag02:10:01

I think I tried that, but let me try again

ag02:10:45

it can't resolve symbol if done like that

ag02:10:42

I think, I'm gonna have to write a nested macro call

Alex Miller (Clojure team)02:10:28

can't resolve what symbol?

Alex Miller (Clojure team)02:10:07

I mean what are you actually trying to do here? do you even need a macro?

ag02:10:09

eh... probably no, maybe I don't need it. It's a legacy code done with a macro, I'm trying to extend it without breaking it.

Alex Miller (Clojure team)02:10:25

I mean, it looks like (first (map boolean entities)) but maybe I'm missing something

ag02:10:24

eh, no, it has something to do with generating compojure routes, it's a bit hairy. It's alright, I'll figure it out. Thanks Alex!

hiredman03:10:12

Compojure routes are ring handlers with the addition of returning nil if the route doesn't match, you definitely don't need a macro to make those

ag03:10:52

I know, but how do I generate something like this:

(context
    "/foo" []
  (context "/bar" []
    :tags ["Bar"]
    (bar-routes params))
  (context "/zap" []
    :tags ["Zap"]
    (zap-routes params))
  ,,,
  )
dynamically?

ag03:10:03

I can do something like:

(compojure.api.sweet/routes
   (compojure.core/make-context
     (:route-context entity) ;; something like "/bar" 
     (:routes entity) ;; => a bunch of compojure.api.core/routes
but I can't figure out how to pass the tags

hiredman03:10:38

there is no reason to bother contexts at all

hiredman04:10:07

like, they exist to remove repetition for people writing routes out manually

hiredman04:10:19

I dunno what that tags things is, it isn't part of compojure, must be something in that compojure.api.sweet thing

hiredman04:10:10

most of what that compojure-api project does, is not compojure at all, which is useful to know when asking for help

hiredman04:10:00

which might explain why you are having trouble, truing to use compojure.core/make-context, when you need to use whatever the equivalent in that compojure-api project is

ag04:10:31

oh, that example I think extremely useful for my case right now. Thanks!

seancorfield04:10:49

Yeah, I've always sort of wondered about compojure-api because it really isn't compojure at all...

👍 3
ikitommi07:10:51

aside from magic and bad-naming of compojure-api, context in Compojure (and in compojure-api) creates the child routes at reqest-time, which is a surpise for many.

ikitommi07:10:14

(require '[compojure.core :refer :all])

(defn my-routes []
  (println "created routing table")
  (GET "/ping" [] (constantly "pong")))

(def app
  (context "/foo" []
    (context "/bar" []
      (my-routes))
    (context "/zap" []
      (my-routes))))

(app {:request-method :get, :uri "/foo/bar/ping"})
;created routing table
;=> {:status 200, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "pong"}

(app {:request-method :get, :uri "/foo/bar/ping"})
;created routing table
;=> {:status 200, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "pong"}

ikitommi08:10:29

(actually, c-api has an optimization for it, creates static contexts only once)

ikitommi08:10:08

if static routing is what you want, here’s valid generated reitit-ring-syntax, no dependencies / macros:

(defn my-routes []
  ["/ping" {:get (constantly {:status 200, :body "pong"})}])

["/foo"
 (for [[name routes] {"bar" (my-routes)
                      "zap" (my-routes)}]
   [(str "/" name) {:swagger {:tags #{name}}} routes])]
;["/foo"
; (["/bar" {:swagger {:tags #{"bar"}}}
;   ["/ping" {:get #object[...]}]]
;  ["/zap" {:swagger {:tags #{"zap"}}}
;   ["/ping" {:get #object[...]}]])]

simongray09:10:12

What is best way to keep a user logged in securely? I know that I can send a session cookie with each request, but now there is a bunch of alternatives (e.g. JWT) available and it’s all a bit confusing to me. Adding to that, is the fact that the ring session middleware seems to be quite hegemonic in the Clojure world, but there is also buddy-auth? I would like to use reitit and it seems to plug in the ring middleware too for session management, but with a caveat: https://github.com/metosin/reitit/issues/205 What are people here doing and what should I watch out for?

p-himik10:10:51

If you have a persistent data storage on your backend, just use sessions and cookies with session IDs, that's it. With 99.9% certainty you don't need any other solution.

👍 6
mavbozo11:10:12

also use https and your session cookie should have secure attribute

simongray11:10:49

What's the secure attribute?

mavbozo11:10:57

"A cookie with the `Secure` attribute is sent to the server only with an encrypted request over the HTTPS protocol, never with unsecured HTTP" https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies

mavbozo11:10:57

you dont' want your session cookie intercepted midway, use https and ensure that your session cookie transmitted only using https

metal 3
simongray11:10:57

Right. Thanks a lot! This will of course be https.

Calum Boal12:10:32

if you're using cookies you can set same-site these days which will help prevent csrf attacks, also you'll want to use httponly to stop it being read by malicious javascript injected via cross site scripting in addition to the secure flag

metal 3
3
simongray14:10:31

Guess I’ll set all of those. Useful stuff.

William Skinner15:10:10

Was -X replaced with -A in the clojure cli?

Alex Miller (Clojure team)16:10:28

-A's use is moving towards being applicable primarily for repl invocation with -M for main invocation and -X for function execution, but for now it can still be used to supply main-opts (like -M)

Alex Miller (Clojure team)16:10:19

that particular use case (-A for main execution) is effectively deprecated and will warn (eventually will be turned off)

Alex Miller (Clojure team)16:10:34

where eventually is probably ~months

dpsutton15:10:22

i think its largely more along the lines that -A is being replaced with -X

bmaddy16:10:33

Here's a macro puzzler for someone: I need to call a macro like this:

(rules-dsl/parse-rule [[Product (= sku "123")]] (prn "hii"))
That works fine. However, the vector part will be stored in a var because I'm loading it from a database, so it will really be more like this:
(let [form '[[Product (= sku "123")]]]
  (rules-dsl/parse-rule form (prn "hii")))
This fails because the macro rules-dsl/parse-rule is needing the expression stored in form, but actually just gets the symbol form. I suspect the solution to this is to write a wrapper macro and look up the value of form in the let's scope using &env. However, I can't figure out how to use the clojure.lang.Compiler$LocalBindings that &env provides. Any ideas on how to do this?

hiredman16:10:08

Make parse-rule a function

bmaddy16:10:38

I didn't. It's part of clara-rules. We just need to use it.

hiredman16:10:50

(also let bindings don't involve vars)

bmaddy16:10:22

Right, I forgot. vars are only top level. 👍

hiredman16:10:23

Ask in clara-rules for the function equivalent

hiredman16:10:54

Which is maybe the dsl/build-rule function

bmaddy16:10:20

It looks like that works. Thanks @hiredman! Somehow I missed that function. Sorry for the post in the wrong-channel. I'm still curious though, does anyone know how to pass a form to a macro that's stored in a local binding? Or perhaps how to use the LocalBindings in &env?

hiredman16:10:46

The value of the binding only exists at runtime, and macros are expanded away already

hiredman16:10:19

Because on clojure (and other lisps) the macro language and the regular language are the same it is easy to confuse them

hiredman16:10:59

It can be held up to think of the macro language as clojure-0 and the language as clojure-1

dpsutton16:10:36

think of this example:

(let [x ['y 3]]
  (let x (+ y 2)))
wanting to use the value of x instead of the for [y 3] as the binding

bmaddy18:10:18

This is an excellent simplification of what I was struggling with--thanks!

👍 3
hiredman16:10:36

Clojure-0 is a language where all the functions (macros) take as arguments and return clojure-1 expressions

hiredman16:10:33

And clojure-1 is what you think of as regular clojure.

bmaddy17:10:00

It not being possible surprises me. &env clearly has something in it for form when called normally, whereas it's nil when called with macroexpand-1.

hiredman17:10:23

The value of the let binding is a regular clojure expression, not a clojure form

bmaddy17:10:26

I just can't figure out if the LocalBinding has what I expect in it or how to use it.

hiredman17:10:24

So clojure-0 cannot do anything with it, because clojure-0 only operates on clojure expressions, not the values those expressions evaluate to

hiredman17:10:14

&env can tell you a symbol is bound (that is statically known at compile time when macros are expanded) but it can't tell you what value it is bound to, because that value is determined at runtime after the expression has been compiled

bmaddy17:10:41

but wouldn't you be able to look it up in the expression emitted from the macro?

bmaddy17:10:47

Well, I'm sure you're right--don't mean to imply otherwise. Maybe this is just an opportunity for me to do more research on how the clojure compiler works.

andy.fingerhut17:10:51

The expression emitted by the macro will be compiled and it can use run-time values when it is executed, yes, but during compile time you do not know those run-time values.

andy.fingerhut17:10:34

Maybe too trivial of an example, but you can write a very short macro that takes an expression (my-custom-when some-conditional-expr then-expr) and transforms it into (if some-conditional-expr then-expr) . At run time, the resulting expression (if some-conditional-expr then-expr) will evaluate the value of some-conditional-expr , and at that time decide whether to evaluate then-expr, or not. While your macro named my-custom-when is executing, it only has access to the data structures that represent the source code of some-conditional-expr and then-expr , not what they will evaluate to, and not what any of their subexpressions will evaluate to.

hiredman17:10:29

Writing macros (meta logic) without keeping straight the distinction between logic(clojure without macros) and meta logic is a usually a bad idea. There are kind of recipes you can follow that will usually work without having to bother with that distinction, but they only work for simpler macros.

hiredman17:10:45

But simpler macros are the best anyway

andy.fingerhut17:10:06

Every macro has those same restrictions. If you think of them as source-code-to-source-code translations, the only inaccurracy in that is it isn't the text of the source code, but the data structures that you get from reading the text of the source code, that they manipulate.

hiredman17:10:36

The recipe is write a function that does what you want to do, which may need to take arguments as functions so you can control evaluation etc, then write a macro that just translates the syntax you want into a call to that function

hiredman17:10:46

So if you wanted to write the with-open macro:

hiredman17:10:16

(defn with-open-fn [resource body-fn] (try (body-fn resource) (finally (.close resource))))

hiredman17:10:27

Ugh, on my phone

hiredman17:10:28

Then the macro is just (defmacro with-open [[n exp] body] (with-open-fn exp (fn [n] body)))`

bmaddy17:10:43

Yeah, I get the idea of keeping your macros small, do most of your stuff in normal functions, and macros are for code-to-code transformations. It just surprised me that it's not possible when you have a macro you can't change that relies on the form passed to it (even if it is a bad idea).

hiredman17:10:10

You can change based on the form

hiredman17:10:36

Which in this case is a symbol

hiredman17:10:09

But you want what the form evaluates to, which is whatever you setup in your local binding

hiredman17:10:15

Which you cannot

👍 3
bmaddy17:10:56

Anyway, thanks for the help and discussion everyone.

bmaddy18:10:34

After thinking about your comments some more, I think I understand it better now. To put it in my own words, getting the value of form requires an evaluation before making the s-expression that starts with the other macro, then another separate evaluation would be required to actually run it. Because two evaluations need to happen, it can only be done by explicitly calling eval (which should be avoided). Thanks again for the help @hiredman and @andy.fingerhut.

hiredman18:10:05

Another way to think about it, for the expression (fn [x] (m x)) if m is a macro, mechanical, how can it ever know what x is bound to

👍 3
hiredman18:10:03

And (let [x ...] (m x)) is more or less ((fn [x] (m x)) ...) , It isn't literally the same thing in clojure for reasons that have to do with how it compiles to jvm byte code, but in there are lisps where it is

jmckitrick22:10:47

What’s the best place for a simple emacs editing question regarding paredit in clojure?