clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
cpmcdaniel 2020-10-21T00:24:54.371500Z

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

alexmiller 2020-10-21T00:32:20.372500Z

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

ag 2020-10-21T01:49:42.374700Z

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 ~@(for [e ~@es ;;; <- this is wrong. how do I refer to this list?]
                      (boolean e))]
       result)))

ag 2020-10-21T01:52:41.376200Z

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

onetom 2020-10-21T01:57:14.377900Z

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

alexmiller 2020-10-21T01:57:28.378400Z

@ag you can't unquote inside an unquote

onetom 2020-10-21T01:58:08.379100Z

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

alexmiller 2020-10-21T01:58:40.379700Z

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

alexmiller 2020-10-21T01:59:05.380200Z

pprint may for example truncate long collections depending on your settings

👍 1
alexmiller 2020-10-21T02:03:41.382900Z

@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 ~@(for [e es#] ... 

ag 2020-10-21T02:03:58.383Z

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

ag 2020-10-21T02:06:01.383500Z

I think I tried that, but let me try again

ag 2020-10-21T02:08:45.383800Z

it can't resolve symbol if done like that

ag 2020-10-21T02:09:42.384800Z

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

alexmiller 2020-10-21T02:10:28.385Z

can't resolve what symbol?

alexmiller 2020-10-21T02:16:07.385500Z

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

ag 2020-10-21T02:27:09.386700Z

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.

alexmiller 2020-10-21T02:44:25.387200Z

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

ag 2020-10-21T02:45:24.388200Z

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!

2020-10-21T03:48:12.390Z

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

2020-10-21T03:50:38.390100Z

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

ag 2020-10-21T03:51:52.391Z

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?

ag 2020-10-21T03:55:03.392400Z

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

2020-10-21T03:59:38.392800Z

there is no reason to bother contexts at all

2020-10-21T04:00:07.393400Z

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

2020-10-21T04:01:19.394Z

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

2020-10-21T04:04:10.395400Z

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

2020-10-21T04:06:00.396300Z

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

ag 2020-10-21T04:06:31.396700Z

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

seancorfield 2020-10-21T04:18:49.397400Z

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

👍 1
ikitommi 2020-10-21T07:58:51.401100Z

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.

ikitommi 2020-10-21T07:59:14.401400Z

(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"}

ikitommi 2020-10-21T08:01:29.402500Z

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

ikitommi 2020-10-21T08:09:08.404200Z

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[...]}]])]

simongray 2020-10-21T09:33:12.409800Z

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?

zilti 2020-10-21T09:36:14.410100Z

You shouldn't use JWT for that: https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid

➕ 1
🤘 1
p-himik 2020-10-21T10:19:51.410500Z

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.

👍 2
mavbozo 2020-10-21T11:11:12.410800Z

also use https and your session cookie should have secure attribute

simongray 2020-10-21T11:12:49.411Z

What's the secure attribute?

mavbozo 2020-10-21T11:23:57.411400Z

"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

mavbozo 2020-10-21T11:30:57.411700Z

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

🤘 1
simongray 2020-10-21T11:47:57.411900Z

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

Calum Boal 2020-10-21T12:33:32.412200Z

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

1
🤘 1
simongray 2020-10-21T14:07:31.412800Z

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

William Skinner 2020-10-21T15:06:10.413700Z

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

dpsutton 2020-10-21T15:32:22.414200Z

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

2020-10-21T16:13:33.418800Z

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?

2020-10-21T16:16:36.419500Z

Don't

2020-10-21T16:17:08.419900Z

Make parse-rule a function

2020-10-21T16:17:38.420800Z

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

2020-10-21T16:17:50.421200Z

(also let bindings don't involve vars)

2020-10-21T16:18:22.422200Z

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

2020-10-21T16:18:23.422300Z

Ask in clara-rules for the function equivalent

2020-10-21T16:22:54.422900Z

Which is maybe the dsl/build-rule function

2020-10-21T16:40:20.425300Z

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?

alexmiller 2020-10-21T16:47:28.425400Z

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

alexmiller 2020-10-21T16:48:19.425600Z

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

2020-10-21T16:48:25.425900Z

You can't

alexmiller 2020-10-21T16:48:34.426200Z

where eventually is probably ~months

2020-10-21T16:49:46.427600Z

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

2020-10-21T16:52:19.430300Z

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

2020-10-21T16:52:59.431500Z

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

dpsutton 2020-10-21T16:53:36.432500Z

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

2020-10-21T16:58:36.434900Z

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

2020-10-21T16:59:33.436400Z

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

2020-10-21T17:00:00.437300Z

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.

2020-10-21T17:00:23.438100Z

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

2020-10-21T17:00:26.438400Z

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

2020-10-21T17:01:24.440Z

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

2020-10-21T17:03:14.442300Z

&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

2020-10-21T17:05:41.443300Z

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

2020-10-21T17:05:49.443600Z

No

2020-10-21T17:06:47.446600Z

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.

2020-10-21T17:06:51.446800Z

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.

2020-10-21T17:10:34.453300Z

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.

2020-10-21T17:11:29.454700Z

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.

2020-10-21T17:11:45.455500Z

But simpler macros are the best anyway

2020-10-21T17:12:06.456200Z

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.

2020-10-21T17:13:36.458400Z

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

2020-10-21T17:14:46.459300Z

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

2020-10-21T17:16:16.461200Z

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

2020-10-21T17:16:27.461700Z

Ugh, on my phone

2020-10-21T17:18:28.465800Z

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

2020-10-21T17:18:43.466100Z

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

2020-10-21T17:19:10.466700Z

You can change based on the form

2020-10-21T17:19:36.467500Z

Which in this case is a symbol

2020-10-21T17:20:09.468600Z

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

2020-10-21T17:20:15.468800Z

Which you cannot

👍 1
2020-10-21T17:21:56.469200Z

Anyway, thanks for the help and discussion everyone.

2020-10-21T18:32:34.470400Z

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.

2020-10-21T18:33:18.470500Z

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

👍 1
2020-10-21T18:52:05.474600Z

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

👍 1
2020-10-21T18:58:03.480600Z

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

jmckitrick 2020-10-21T22:33:47.482800Z

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

ghadi 2020-10-21T22:36:54.483Z

#emacs