Fork me on GitHub
#clojure
<
2019-11-08
>
vdmit1100:11:58

Is there a way to somehow iterate over lexical bindings inside body of a function or a let form (that should happen in the context of macro expansion of course)?

andy.fingerhut01:11:57

I'm pretty sure I have seen some Clojure debugger-type library thingy that might be implemented by doing that, or something similar. Let's see if I can remember which one it is...

andy.fingerhut01:11:17

I haven't tried this, but the code that includes a definition of 'local-bindings' in this StackOverflow Q&A might be relevant? https://stackoverflow.com/questions/2352020/debugging-in-clojure

andy.fingerhut01:11:40

That code looks to me to be specific to Clojure/JVM, and wouldn't work for ClojureScript, because it is reaching into internals of Clojure/JVM compiler specific to it.

vdmit1101:11:24

@andy.fingerhut Thanks, this is exactly what I was looking for.

andy.fingerhut01:11:59

Google search for clojure.lang.compiler/LOCAL_ENV turns up other places it has been used for similar purposes, too, in case you want other examples.

sogaiu01:11:53

i was playing with alex and george's debug repl recently - fwiw, the latest version doesn't use compiler internals. still i didn't find it to work as-is for cljs -- though it seems likely it would work for clojure clr. i believe it uses &env now: https://github.com/GeorgeJahad/debug-repl/blob/master/src/alex_and_georges/debug_repl.clj#L29_L46

sogaiu01:11:40

fwiw. the speculation bit there seems fairly in line with what i saw combing through past clojure irc logs 🙂

vdmit1101:11:19

@sogaiu thanks, I just learned about &env

lilactown03:11:21

yes &env I think is what you're looking for

lilactown03:11:33

unfortunately, using &env breaks using macroexpand to test 😕

dominicm08:11:22

What makes you think that? It isn't mentioned on https://clojure.org/reference/macros

lilactown17:11:12

I should have been more clear: :locals is never populated in &env when using macroexpand, so if your macro depends on it, you wont’ be able to test it that way

dominicm17:11:55

Ah, that's fair

dominicm17:11:08

Most of the time it should be okay though

lilactown17:11:44

yeah, edited comment. didn’t mean to say one shouldn’t use it

lilactown17:11:03

more just complaining on my end that I can’t use macroexpand to test my macro I’m working on <_<

Jakub Holý (HolyJak)13:11:54

Hello folks! When I run my function in the REPL I get > Execution error (StackOverflowError) at (REPL:1). but the stack trace contains nothing of my code, only many clojure.core$concat, $seq and clojure.lang.LazySeq. How do I troubleshoot this? How do I find out where in my code am I creating this??? The stack trace:

[[clojure.lang.LazySeq sval "LazySeq.java" 42]
 [clojure.lang.LazySeq seq "LazySeq.java" 51]
 [clojure.lang.RT seq "RT.java" 535]
 [clojure.core$seq__5402 invokeStatic "core.clj" 137]
 [clojure.core$concat$fn__5493 invoke "core.clj" 725]
 [clojure.lang.LazySeq sval "LazySeq.java" 42]
 ... (these few repeated many times over)
 [clojure.lang.LazySeq seq "LazySeq.java" 51]
 [clojure.lang.RT seq "RT.java" 535]
 [clojure.core$seq__5402 invokeStatic "core.clj" 137]])

kulminaator13:11:05

can you show the code or the error message at least ? 😄

kulminaator13:11:17

only having the stack trace is very little to go on

kulminaator13:11:08

ah, now i get it, the stackoverflow is your error message ... any chance you built endless recursion there ?

dpsutton13:11:17

https://stuartsierra.com/2015/04/26/clojure-donts-concat this might give you some insight. if you built up a sequence using concats, you end up with too many lazy thunks to realize the sequence later and run out of stack

Jakub Holý (HolyJak)14:11:17

Thank. I am pretty sure I do not build a sequence manually anywhere but I do operate on data using lazy sequence operations. I cannot really share the code as there is few hundred lines of code involved. I just call my (fetch-and-process-all-invoices). The error message is there ☝️ and it is useless: Execution error (StackOverflowError) at (REPL:1).

Jakub Holý (HolyJak)14:11:36

The only hypothesis is that the error actually happens after my function run, when REPL tries to display its output, that could explain why there is only clojure.* in the stacktrace.

Alex Miller (Clojure team)14:11:28

if that was true, you would see a different error message (not Execution error)

Alex Miller (Clojure team)14:11:44

Execution error indicates it happening during the E of REPL

4
Jakub Holý (HolyJak)14:11:56

You are right. But why doesn't the stack trace have anything of my code? How do I find where is this happening?

Alex Miller (Clojure team)14:11:15

do you use concat anywhere?

Jakub Holý (HolyJak)14:11:08

Yes, at a few places, I will look into that / alternatives. (And reread the article!) Ah, I guess I am hitting this > and the accumulated stack frames of seq prevent us from seeing where the error originated

Jakub Holý (HolyJak)14:11:59

Hm, looking through the code I do not see any suspicious use of concat with multiple arguments, mostly just (merge-with concat ...) etc. I have to re-check...

Alex Miller (Clojure team)15:11:10

are you doing that in a loop / recursion?

Jakub Holý (HolyJak)18:11:16

Thank you, no, I am not. I now deployed some (I am close to 100% sure unrelated) changes and the problem went away it seems. :hand_with_index_and_middle_fingers_crossed: it was just a fluke...

Jakub Holý (HolyJak)18:11:16

I know . I have no idea what's happening. So everything is as normally:) Thank you for your help! I'll see if it comes back..

youvere16:11:17

why clojure doesn’t have a list literal or a symbol literal? > Clojure has no literal representation for Lists. If you write a List out in your program, it is interpreted as code. At first glance, it appears that quoting the List is a good solution. However, if you quote a List, the elements of the List won’t be evaluated. That means that a quoted List is only useful for Lists of literals. https://purelyfunctional.tv/guide/clojure-collections/ Clojure is data oriented, I was wondering why there is no literal for a symbol or a list of data. One supposition would be homoiconicity could not be achieved, but I could not explain why.

#inst "1985-04-12"
=> #inst"1985-04-12T00:00:00.000-00:00"
#{:a}
=> #{:a}
[1 2 3]
=> [1 2 3]
{:a 1}
=> {:a 1}
'(1 2 3)
=> (1 2 3)
(1 2 3) 
=> error

andy.fingerhut16:11:13

I don't understand the question. '(a b c) is a list literal containing 3 symbol literals

andy.fingerhut16:11:06

It needs quoting to avoid being evaluated in a REPL, unlike a map or set literal, that is true, because code is inside of lists, too.

andy.fingerhut16:11:44

Are you asking why it needs quoting at the REPL to avoid evaluation, and maps and sets do not?

andy.fingerhut16:11:45

Maybe a little more -- all of those expressions are being read, then evaluated, and the result of evaluation printed, at the REPL.

andy.fingerhut16:11:51

maps and sets are read, and during evaluation, they evaluate to themselves, as do keywords, numbers, and #inst values. All subexpressions of the map and set literals are also evaluated, recursively, too, so if a map or set literal contained a parenthesized expression, it would be evaluated, and present the same difference in behavior as if the parenthesized expression were typed as the whole expression.

andy.fingerhut16:11:55

If lists also always evaluated to themselves, like maps and sets did, then they could not be used to contains code for evaluation -- you would need some other syntax for that? (not sure if that makes sense)

youvere16:11:03

yes that make sense! Seems like it would be handy* to have another syntax for that

youvere16:11:19

but I was wondering if it could be possible

jumpnbrownweasel18:11:27

Maybe this is obvious but I didn't see it mentioned: you can use (list ...)

(list 1 2 (+ 1 2))
=> (1 2 3)

jumpnbrownweasel18:11:36

With (list ...) the values inside the list are not quoted, like they are when you quote the list itself:

'(1 2 (+ 1 2))
=> (1 2 (+ 1 2))

jumpnbrownweasel18:11:53

If you're familiar with other Lisps, you can also use the more traditional cons to build lists in Clojure:

(cons 1 (cons 2 (cons (+ 1 2) nil)))
=> (1 2 3)

andy.fingerhut16:11:49

I should be a little more precise, even without the quote in front (a b c) is a list literal containing 3 symbol literals. If you only do read on that, no eval, you get a list.

andy.fingerhut16:11:18

It is the eval that causes an error if any of a b or c are not defined with values, or if the value of a is not something that can be called as a function or macro.

andy.fingerhut16:11:56

user=> (read-string "(a b c)")
(a b c)
user=> (eval (read-string "(a b c)"))
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: a in this context

andy.fingerhut16:11:23

As opposed to this, where the set evaluates to itself, and so do 1, 2, and 3:

user=> (read-string "#{1 2 3}")
#{1 3 2}
user=> (eval (read-string "#{1 2 3}"))
#{1 3 2}

andy.fingerhut16:11:38

In the next example, reading returns a set containing 3 symbols. Without eval, no problem, you get back such a set. Eval of the set returns a set, but also recursively calls eval on all elements, which is when it finds that a is undefined:

user=> (read-string "#{a b c}")
#{a c b}
user=> (eval (read-string "#{a b c}"))
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: a in this context

andy.fingerhut16:11:24

Reading a list or set or map are all pretty consistent behavior, and the syntax for each are all literal syntax for those data structures. The rules for eval behavior on a list are different than the rules for eval behavior on a set or map -- the first element of the list is special.

thanks2 4
chrisn17:11:44

I am stuck trying to proxy an java.io.OutputStream. It has two different write methods, one overloaded with a byte array and one overloaded with an integer. I seem to type hint things correctly to tell the compiler how to implement the write methods.

chrisn17:11:10

My solution right now is to write a java class that derives from output stream and names those methods differently as abstract methods.

ghadi17:11:37

@chris441 you may have to test types inside the method impl

chrisn17:11:02

That would lead to an abstract method error as you will have only overridden one of the two methods.

chrisn17:11:07

Most likely the byte[] one.

hiredman17:11:22

that isn't true

ghadi17:11:48

(def x
 (proxy [java.io.OutputStream] []
  (write
    ([o] (println (class o)))
    ([o s len]))))
#'user/x
user=> (.write ^java.io.OutputStream x 2)
java.lang.Integer
nil
user=> (.write ^java.io.OutputStream x ^bytes (byte-array 0))
[B

hiredman17:11:57

I've proxy'ed outputstreams a few times, and it is always the same, type check in the write method

chrisn17:11:52

huh. Well, you are overriding the object method (byte[]) one I think. I wonder how that is working or if just no one calls the int method.

hiredman17:11:39

like, ghadi literal posted a transcript of a repl session showing you it working for both the int write method and the byte[] write method

hiredman17:11:55

stop saying it doesn't work

chrisn17:11:34

We don't know what the reflected method called.

hiredman17:11:03

1. it isn't reflected

hiredman17:11:16

2. even if it was, it prints out the type so we know

chrisn17:11:25

So then when you proxied did it override all methods with one signature and call your code for both?

ghadi17:11:34

in the output you can see that it's calling the code I provided in both cases of input

chrisn17:11:41

Yes for sure, agreed.

hiredman17:11:48

clojure's proxy is strictly name based

ghadi17:11:49

I think so -- overrides all by the same name

hiredman17:11:21

e.g. you give it a function body (which may have multiple arities) and all methods with that name will call that function

josh_tackett17:11:25

@lukaszkorecki Any chance you have some sample code? This stuff is confusing

lukasz18:11:47

@josh_tackett sure, let me find a version I can share

lukasz18:11:38

also, this works only with java 8 - because it relies on javax.xml.bind DatatypeConverter

josh_tackett18:11:10

I'm going to try to extend buddy-core to handle :aes256-cbc-hmac-sha256

lukasz18:11:19

so hex-pair-to-byte would have to be tweaked for java 11 I think

josh_tackett18:11:35

got it, i will circle back to this if I can't extend buddy-core

lukasz18:11:33

I don't think I ever used it - usually BouncyCastle does everything I needed, including PGP and the API is pretty straightforward, what's confusing is the overall cryptography stuff 😉

ghadi18:11:32

beware -- neither BouncyCastle nor buddy are "misuse resistant" libraries like libsodium or google's tink

kulminaator19:11:25

pgp tends to be less straight forward once you get to multiple objects, subkeys etc. 🙂 at least if you do it with bouncy castle, there are lots of pitfalls

kulminaator19:11:08

well it's also the fact that it's not just crypto

kulminaator19:11:16

it's about containers and objects inside of them

kulminaator19:11:25

way more than "this is just encrypted data" 🙂

kulminaator19:11:51

and a big part of that in my mind is just overengineering at it's finest

kulminaator19:11:37

then again ... it does the job ... so i don't complain 🙂

lukasz19:11:24

Luckily the PGP stuff I was working on was pretty light weight and in a controlled environment, implementing production-grade, customer facing features is indeed a task that needs a lot of attention and verification.

josh_tackett21:11:38

@lukaszkorecki I'm getting this error: java.lang.ClassNotFoundException: javax.crypto

josh_tackett21:11:53

@lukaszkorecki actually it can't find any of the libs I'm trying to import haha

hiredman21:11:55

because you aren't using the his ns verbatim, you are trying to turn it into an (import ...) call in the repl, but failed to quote the imported classes

hiredman21:11:57

apparently recent enough clojure versions don't require the quoting, which is wild

hiredman21:11:01

what a world

💯 4
josh_tackett21:11:35

(:import java.security.SecureRandom (javax.crypto Cipher SecretKeyFactory) (javax.crypto.spec SecretKeySpec IvParameterSpec PBEKeySpec) (javax.xml.bind DatatypeConverter) (org.bouncycastle.jce.provider BouncyCastleProvider) (java.security Security)))

josh_tackett21:11:40

so he does it with an import

hiredman21:11:53

but that isn't import

hiredman21:11:56

that is :import

hiredman21:11:24

:import is a directive to the ns macro, import is a macro you can call at the repl

josh_tackett21:11:57

(ns skyscraper.scrape.encryption
  (:import javax.crypto Cipher KeyGenerator SecretKey
           javax.crypto.spec SecretKeySpec
           java.security SecureRandom
           org.apache.commons.codec.binary Base64))

(defn bytes [s]
  (.getBytes s "UTF-8"))

(defn base64 [b]
  (Base64/encodeBase64String b))

(defn debase64 [s]
  (Base64/decodeBase64 (bytes s)))

(defn get-raw-key [seed]
  (let [keygen (KeyGenerator/getInstance "AES")
        sr (SecureRandom/getInstance "SHA1PRNG")]
    (.setSeed sr (bytes seed))
    (.init keygen 128 sr)
    (.. keygen generateKey getEncoded)))

(defn get-cipher [mode seed]
  (let [key-spec (SecretKeySpec. (get-raw-key seed) "AES")
        cipher (Cipher/getInstance "AES")]
    (.init cipher mode key-spec)
    cipher))

(defn encrypt [text key]
  (let [bytes (bytes text)
        cipher (get-cipher Cipher/ENCRYPT_MODE key)]
    (base64 (.doFinal cipher bytes))))

(defn decrypt [text key]
  (let [cipher (get-cipher Cipher/DECRYPT_MODE key)]
    (String. (.doFinal cipher (debase64 text)))))

josh_tackett21:11:02

ya I'm using :import

hiredman21:11:28

that :import is incorrect

josh_tackett21:11:41

ok, got it. how do I fix it?

hiredman21:11:03

you want (:import (PACKAGENAME SIMPLECLASSNAME1 SIMPLECLASSNAME2 ))

hiredman21:11:14

you removed all the grouping of classes in packages

josh_tackett21:11:52

(ns skyscraper.scrape.encryption
  (:import (javax.crypto Cipher KeyGenerator SecretKey)
           (javax.crypto.spec SecretKeySpec)
           (java.security SecureRandom)
           (org.apache.commons.codec.binary Base64)))

josh_tackett21:11:17

👍 thank you

hiredman21:11:47

you don't need apache commons for base64, java has a built in class for it

sogaiu21:11:27

is it a bad idea to be using vectors like so?

(ns skyscraper.scrape.encryption
  (:import [javax.crypto Cipher KeyGenerator SecretKey]
           [javax.crypto.spec SecretKeySpec]
           [java.security SecureRandom]
           [org.apache.commons.codec.binary Base64]))
i see vectors used in clojure.instant, but lists elsewhere

bfabry21:11:22

it's fine, but it's just not common style

bfabry21:11:15

I think cursive defaults to [] for :require and () for :import so that's what I use

Alex Miller (Clojure team)21:11:22

I think both are common in practice

sogaiu21:11:38

thank you both 🙂

Alex Miller (Clojure team)21:11:26

I disagree only with using () for imports :)

sogaiu21:11:15

thanks again!

Alex Miller (Clojure team)21:11:39

and fyi, Rich thought Stuart's reasoning for lists in that article was bogus :)

👀 8
vemv21:11:02

https://github.com/gfredericks/how-to-ns implements that article its low star count doesn't do justice to how well it works

sogaiu21:11:02

interesting -- may be clj-kondo does some of this already?

vemv21:11:03

kondo lints, how-to ns formats (and lints; but personally I don't use the linting part)

sogaiu21:11:43

ah i see -- the description at the top just says linter 🙂

👍 4
henrik22:11:04

I’ve taken to categorising requires where they grow very large (which I bet will make some people frown). I.e., you’ll see stuff like:

;; Mutations
[poc.mutations.search :as search-mut]

;; Components
[ :refer [app]]
[poc.components.token :refer [Token]]

;; Fulcro
[com.fulcrologic.fulcro.mutations :as mutations :refer [defmutation]]
[com.fulcrologic.fulcro.algorithms.merge :as merge]

henrik22:11:17

…insde the require clause.

sogaiu23:11:04

oh, i hadn't seen that sort of arrangement before -- thanks for sharing. by coincidence i happen to be doing things alphabetically like the stuart sierra article says, iiuc.

henrik23:11:50

I’ve mostly gone alphabetically, but started getting some namespaces with quite a lot of dependencies due to breaking things up in fairly small pieces, and then it became more useful to organize them primarily by purpose.

sogaiu23:11:28

that sounds reasonable -- haven't worked on anything that big 🙂

henrik23:11:58

Though I still tend to stick standard lib stuff at the top prior to everything else. But when I’m pulling in a bunch of UI components, it seems to make more sense to group them than to sprinkle them around routing, clojure.string, third party libs, and so on.

sogaiu23:11:58

that seems like it would be easier to maintain

alexbaranosky03:11:52

My preference is whatever my editor uses when formatting my code. 🙂 I think clj-refactor.el uses vectors.

valerauko14:11:41

i use vectors to keep it the same as require

valerauko14:11:27

i also group my requireds by their "level", clojure language stuff at the top and tiny-scope app namespaces at the bottom

pyr23:11:15

@jumar a compatible alternative to jasypt in clojure: https://github.com/pyr/jcipher

jumar03:11:09

Nice, thanks!