Fork me on GitHub
#clojure
<
2020-02-15
>
Ahmed Hassan07:02:07

What are good guides, tips to run migrations on Postgresql? I want to use Migratus (https://github.com/yogthos/migratus)

p-himik09:02:05

I've been using Migratus for about 2 years now, no complaints whatsoever.

👍 4
katox23:02:56

If you want an instant startup you can try https://github.com/leafclick/pgmig (native-image migratus)

👍 4
Ahmed Hassan06:02:56

What is difference between using Migratus and native-image?

Ahmed Hassan06:02:24

@U2FRKM4TW how do you run migrations while deploying jar application?

p-himik06:02:18

Migratus is a tiny library, you can just embed it.

Ahmed Hassan07:02:41

@U2FRKM4TW so migrations are run when you start jar application in production?

Ahmed Hassan07:02:28

I'll be using Dokku (http://dokku.viewdocs.io/dokku/) to run application.

p-himik08:02:55

I deploy either self-hosted or Heroku apps, so I don't use jars at all. But it is definitely possible.

p-himik08:02:43

And no, you don't have to run migrations on app start, although that probably makes sense - to run them right before running the main app code. But you can create a different main class for migrations. Or you can have a CLI option that runs migrations specifically.

👍 4
Ahmed Hassan08:02:02

Like I can put code to run migrations before main class. right?

4
Ahmed Hassan08:02:22

So, migrations run before main application.

katox08:02:09

@UCMNZLJ93 Migratus can be run as a Java process (or as a part of the Clojure app). Assuming a Linux system used for building then pgmig is a standalone native Linux process (elf binary), no Java or anything else needed. Dockered build is included for cloud deployments.

👍 4
p-himik08:02:19

Migratus is embeddable - you can do anything with it. You can embed it and alter it to your needs. E.g. run migrations offered by unauthenticated web users in real time, if you like having fun. :)

4
dharrigan08:02:04

I use Flyway with our postgresql instances. Although there is no clojure specific wrapper for it, writing the interop is pretty trivial.

dharrigan08:02:16

(thanks to the most excellent interop support in clojure)

borkdude17:02:43

What's the reason one cannot mutate the value of a dynamic var with set! if it's not already thread-bound?

borkdude17:02:09

One reason might be that you want to be assured that the effect of set-ing something is always thread-local?

👍 4
borkdude17:02:14

I'm trying to implement "immutable vars" in sci, so they can be re-used for multiple sessions. E.g. one chatbot session cannot alter-var-root! a var so the next session would be troubled by it. But set! seems to be safe in that regard.

didibus17:02:16

set! is thread safe

didibus17:02:39

Since it can only set thread local vars

didibus17:02:52

But set! on the root wouldn't be.

didibus17:02:04

Which is why I believe its not allowed, forcing you to use the thread safe atomic alter-var-root instead

didibus17:02:21

Are you going to diverge from Clojure semantics in sci?

didibus18:02:06

My impression from Clojure is that it uses immutability only in order to protect from race conditions and data races.

andy.fingerhut18:02:01

My impression, and several of Rich Hickey's talks if I recall correctly, is that immutability can be immensely helpful in single-threaded programs, too.

andy.fingerhut18:02:47

e.g. you have nested collections to represent some data. For some sub-collection, you want to insert a new key/value pair, then put that updated one back into its 'parent collection', but not change anything else. Do you need to deep-copy the sub-collection you want to update, or not? In Clojure, never. In languages with default mutable collections, often you do.

andy.fingerhut18:02:29

If you forget, you may have just modified occurrences of that object used as a sub-collection in N other places, too, without intending to.

didibus19:02:54

Those would qualify as data races as well no?

hindol19:02:25

No IMO. A data race is only possible in the context of multiple continuations/concurrency.

hindol19:02:06

But technicalities aside, I do get your point.

didibus20:02:52

Hum, well actually I think its a point worth talking about

didibus20:02:10

You're right, this is neither a data race nor a race condition

didibus20:02:23

The behaviour is fully deterministic

didibus20:02:42

The issue is more with the programmer. Its hard to realize that the function you passed the collection into will mean that if you use the collection afterwards it might have been modified

didibus20:02:28

I wonder what category of bug this would fall under

hindol20:02:37

Bugs related to mutability?

didibus20:02:21

Excluding concurrent ones?

hindol20:02:24

When we say x is 3 what we really mean is that x is an alias to a location which currently holds the value 3. But there is also a time aspect to it. x can attain a new value anytime - with or without our knowledge.

hindol20:02:48

I think, by focusing on immutable values and collections, Clojure effectively eliminates this whole class of bugs. None of these is related to concurrency.

didibus20:02:31

But, without concurrency, it is with predictable time, and "without our knowledge" is more, without us realizing, since the code does define a deterministic ordering. So I'm thinking it almost feels like a "Hard to read" bug

didibus20:02:07

Bug due to implicit, hard to follow code.

didibus20:02:23

What's funny is I have beginners telling me Clojure is hard to read and follow all the time. Yet, I think that's false, compared to mutable imperative programming, its way easier to read and follow.

hindol20:02:49

Once you build things around immutability, the resulting thing is definitely easier to reason about.

👍 4
hindol20:02:58

But there is also the elegance of LISP syntax. It's an acquired taste I guess, because many (beginners) seem to hate it.

👍 4
andy.fingerhut20:02:38

hard vs. easy to read is based on what you already know and are familiar with, and changes over time and learning.

andy.fingerhut20:02:56

or at least, in some cases it can change.

andy.fingerhut21:02:12

I don't know a name for such a category of bugs, but it is mutating an object in place, and while in principle it is possible from complete knowledge of a program and its behavior to predict that such mutation is wanted, versus unwanted, is possible, it can be very challenging to keep that straight. It can be a very complex question.

andy.fingerhut21:02:30

Some people create such 'used/referenced in multiple places' mutable object on purpose, by design, because they want updating the object in one place to make it appear updated everywhere it is referenced. It is keeping the wanted versus unwanted cases separate that can get complex, when the programs and/or data structures are large and have many cases.

Kamuela23:02:47

This is what I understand to be a benefit of immutability: reading a value, as a programmer/human being, and not expecting it to change. However, Rust allows “shadowing” simply by reusing the let keyword. I asked why it was designed like that and told “if you don’t like it, you don’t have to use it.” Then the conversation went deep into how immutability has nothing to do with not being able to reuse an identifier

didibus23:02:14

Well, depends what we mean by "read". Hard to reason, hard to follow, hard to comprehend.

didibus23:02:58

To me it sounds like those things. Very different from, hard to read because I don't know the syntax/semantics of this language

didibus23:02:21

Seems we're saying there is a class of bugs introduced from mutability making it hard to understand what a variable contains at different point in the code.

didibus23:02:50

Thus the programmer makes mistakes due to it being hard to reason about

didibus23:02:38

I might settle on hard to reason

Kamuela00:02:30

I meant everything you said about “read,” and I argued it in that way as well, not just a simple miscommunication

andy.fingerhut00:02:46

I mean, if someone was programming in assembler for a processor that had 16 general purpose CPU registers named 'r0' through 'r15', and someone said "I as a person have difficulty reading code and remembering what register is being used for what purpose", I would agree that can be a difficulty. But it is something you either accept as part of that language, perhaps using documentation/macros/etc. to make the problem somewhat more tractable for people to understand, or you choose to use a different programming language where the problem does not exist (e.g. C). Mutability of collection in C, Java, Python, C++, Ruby, etc. is at a higher level of programming than register allocation, but it is a thing that a programmer working on a large program can have difficulty with remembering, and/or reasoning about. And immutable data, even in a single-threaded program, is one way to avoid that problem entirely.

Kamuela00:02:50

I meant read as hard to reason about because you have to track every time a value changes

Kamuela00:02:57

To me, the hard to reason about as the same identifier shifts values throughout the code, is what is meant by mutable. Apparently not to anyone in the Rust community

Kamuela00:02:37

Identifier reuse within the same scope is completely not an issue there

andy.fingerhut00:02:12

Kamuela, I don't know Rust well enough to be sure I know what you are talking about. Is it the same thing as reusing the same symbol like tmp to name different values, perhaps as an argument to a function, then shadow it in a let inside of that function, then perhaps a nested let inside of that shadows it again?

Kamuela00:02:27

let myName = name; let myName = newName; Allowed ^ even though myName = newThing would cause a compiler error because it’s “immutable”

andy.fingerhut00:02:34

If that is what you are talking about, yes that can be confusing to a developer trying to understand the code's behavior. It is not a sign of mutability, but of names shadowing other names. You can pretty easily make lint tools that warn you about such shadowing of names, and change your code to avoid using them, if you find it confusing.

andy.fingerhut00:02:34

I do not know if Rust developers/language-designers consider your Rust code snippet above as an example of names shadowing other names, or something else. In Clojure the corresponding thing would be called shadowing.

Kamuela00:02:52

Yes it’s called shadowing there as well, and in most languages

Kamuela00:02:21

I’m not a fan for all the aforementioned difficulties with reasoning about it. Because nothing stops it from happening 150 lines away

andy.fingerhut00:02:45

And lint tools can be developed (or have been) that can catch it an arbitrary number of lines away

didibus00:02:51

I like shadowing of names

Kamuela00:02:52

Interesting. Well I’m not gonna die on this hill, just think it solves only a very narrow problem at the cost of opening up a can of worms

didibus00:02:21

I often use it when I'm transforming the same thing

Kamuela00:02:32

That’s exactly the narrow problem it solves

Kamuela00:02:05

I don’t tend to have an issue either inline chaining or renaming intermediate identifiers

andy.fingerhut00:02:13

Allowing shadowing of names I could imagine being a thing that some language might disallow entirely, but it seems unlikely today. Even Haskell as the "most pure" language allows it, I believe.

andy.fingerhut00:02:43

Find/use a lint tool that warns you about occurrences, would be my recommendation for removing them from your own code, or others that you are allowed to change, if it bothers you.

didibus00:02:44

(let [name (get-name)
      name (str/trim name)]
      ...)

Kamuela00:02:01

It’s hard to believe it does, but I don’t know Haskell so I am not disputing it. Just slightly disappointed haha

didibus00:02:10

Erlang disallows it

Kamuela00:02:31

Erlang is my people I guess

didibus00:02:35

But Elixir allows it

andy.fingerhut00:02:25

This discussion on warnings for name shadowing in a Haskell compiler strongly suggests that Haskell allows it. I am not an expert in Haskell by any means: https://www.reddit.com/r/haskell/comments/5oepn9/better_warnings_for_name_shadowing/

Kamuela00:02:47

That Clojure form restricts it to a very tiny surface. I like that

andy.fingerhut00:02:13

But those two lines could be 150 lines apart ... 🙂

andy.fingerhut00:02:16

Not saying that is good or recommended style for anyone to use -- just that the language itself won't stop you.

Kamuela00:02:22

Could they actually? I don’t mean theoretically, but can you shadow later in the form or only in that preamble?

Kamuela00:02:29

Feel free to teach me better words haha

andy.fingerhut00:02:54

You can have 149 bindings of other names in a single let before binding the same name again.

Kamuela00:02:11

Ah ok so yes, theoretically

Kamuela00:02:24

But still it’s in the tiny bit that’s almost specifically for that thing

andy.fingerhut00:02:27

You can have let nested inside of another let , where the inner let rebinds the same name used in the outer let , or a function parameter name, or a "global" name earlier in your code.

Kamuela00:02:43

Ahhh ok that’s different

didibus00:02:48

Ya, I mean, in practice in Clojure you thread more than you let

andy.fingerhut00:02:55

It is all shadowing of names, according to the Clojure compiler 🙂

didibus00:02:59

So I don't see shadowing happening a lot

didibus00:02:27

In erlang, people get used to just versioning

didibus00:02:29

So they go

andy.fingerhut00:02:04

There are occasionally bugs that can confuse people in their Clojure code where they use let to bind a symbol like name , then try to call Clojure.core/name inside of that let body, but don't use clojure.core/name , but only name , forgetting that they have shadowed name

didibus00:02:05

name1 name2 name3

andy.fingerhut00:02:10

The Eastwood linter will warn about such things

didibus00:02:47

I think I'd be okay with no shadowing and versioning my names

Kamuela00:02:05

Yeah I’m a preformattedName, capitalizedName, etc kinda coder myself

Kamuela00:02:28

I come up with new identifiers that I think are more descriptive anyway

andy.fingerhut00:02:46

Well, at least you believe they are new 🙂

Kamuela00:02:55

And then I get to use preformattedName later on! Like magic! Not swallowed forever by shadowing

didibus00:02:28

True, if you intend to use both

didibus00:02:09

But if the code is already using name everywhere, and there was a bug, and you need to add a trim half way in?

didibus00:02:28

That's where shadowing can be handy

didibus00:02:22

Anyways, I do see your point. I think I can be convinced that shadowing can make things harder to follow and reason about

didibus00:02:48

But I see it more like polymorphism

Kamuela00:02:53

I’ll also concede that it seems like people love it nonetheless

didibus00:02:04

Its natural for human to think contextually

didibus00:02:46

Cause I feel the same argument can be made to polymorphism. We should have get-vec, get-map, get-list, etc

didibus00:02:08

Yet we prefer using one word get for all three and let context decide

didibus00:02:13

Which one is meant

Kamuela00:02:27

I wouldn’t make that argument. I love me some magic functions that swallow any argument and do the thing

didibus00:02:58

But isn't it kind of similar to shadowing?

didibus00:02:09

It isn't as clear which one is happening

didibus00:02:17

Cause the name is overloaded

Kamuela00:02:27

Depends on the implementation I guess. I tend to assume a magic function isn’t going to trim white space or divide by 100 depending on a subtle argument difference

Kamuela00:02:55

But pass a collection in and I can imagine whitespace being trimmed on dozens of strings

didibus00:02:57

You have another same debate in Java. They say type inference is harder to reason about. Because it isn't obvious what the type will be

Kamuela00:02:36

Explicit is better than implicit. Except when it’s not

didibus00:02:42

Right, but this is where that line becomes hard to argue where it really needs to be. Does it allow shadowing? Does it allow type inference? Does it allow polymorphism? Overload? Etc.

didibus00:02:20

I think I go by number of times it caused me to introduce a bug that I can remember

didibus00:02:40

And all these are low, shadowing included 😋

didibus00:02:48

But mutability is high

Kamuela00:02:39

I go by how much my head overheats trying to follow someone else’s algorithm

Kamuela00:02:56

Necessary complexity is bad enough

didibus00:02:39

Hypothetical: Is it possible for your head to overheat, yet your change never or rarely ends up introducing a bug and the actual time to make the change wasn't any longer?

Kamuela00:02:16

Yes definitely is. But my head not hurting and no bug is my preferred outcome

didibus00:02:17

Hum... interesting

didibus00:02:50

What if your head hurt because of your lack of familiarity, experience and having to learn new things?

didibus00:02:21

Or, the other hypothetical: Is it possible for your head not to overheat, yet your change ends up introducing a bug often, and the change ends up taking longer then expected to make?

didibus00:02:47

I say that, because head hurting to me is the classic hard to read. For example, Clojure makes everyone who don't know FP and Lisp head's hurt when reading it.

didibus00:02:08

But hard to reason I feel is more about whatever you end up thinking this does, most of the time you'd be wrong, even if you knew the syntax and semantics.

didibus01:02:02

If you almost always end up being right about what something does, no matter if your head hurts or not. I feel that's good, then its easy to reason about. Could still be hard to read if your head hurt, but that would either be because of unfamiliarity with syntax and semantic, or because the inherent logic is hard, not accidental complexity

didibus01:02:07

Anyway, that's just me. I think on practice, easy to reason is easy to read as well. And vice versa, once you know the language

Kamuela01:02:50

It’s accidental complexity because of bad naming. That simple. Shadowing/mutating, to me, is just the same vein as calling something x, mutating it 100 times, and then returning it. When given the tools, people prefer being terse to being clear

Kamuela01:02:40

Knowing the syntax of a language doesn’t come into it. Knowing the domain of the problem being solved in the algorithm probably comes into it a lot

didibus01:02:14

I mean, what's the difference between:

x = 1 
x++
x++
and
(-> x 
    (inc)
    (inc))

jumpnbrownweasel01:02:17

I don't think you mentioned this, so I'll mention one thing about shadowing when doing a series of calculations that I think is an advantage, for example:

(let [x (some-calc y z 123)
      x (if (odd? x) (blah x) (bleh x)
      x (+ 23 x)] 
   ...)
The nice thing about using shadowing here, as opposed to using a new name each time (e.g., x, x', x'') is that it is very clear that the earlier versions of x are no longer needed or used after each step. I don't use shadowing in other situations because it adds confusion, but in the case I described I think it makes the code easier to think about than if multiple names were used.

jumpnbrownweasel01:02:16

I can confirm that Haskell allows shadowing, and I think one of the main use cases is the one I mentioned.

jumpnbrownweasel01:02:52

Another way of saying it is that you can't accidentally use an earlier version of x because the earlier versions are shadowed.

didibus01:02:10

Ya, I dunno. Like I said I think I wouldn't mind too much either way. Since I've never had bugs due to shadowing that I remember, I feel its pretty safe and easy to reason about. But I don't think just going x1 x2, x3, etc. is a big deal either.

didibus01:02:12

But since I never used a non shadowing lang, I can't say, maybe non shadowing actually could introduce more bugs if I keep accidentally using the wrong "latest" name

jumpnbrownweasel01:02:22

If I was reading code that used x1, x2, x3, I would be wasting energy trying to figure out why the earlier versions were preserved and how they were used in the following steps. I'd have to look very carefully to confirm that only the latest version is used.

Kamuela01:02:15

I feel like you’re also always conjuring up a scenario where your scope never ends. Maybe I’m not in intense enough domains, but I tend to close up scopes in around 300 lines max

Kamuela01:02:50

Like I’m not sure how I’d get to x32 and forget that I needed x27

Kamuela01:02:19

Like, were you really gonna shadow something 30 times?

andy.fingerhut01:02:38

I think he is saying that he is so accustomed to shadowing, that avoiding shadowing implies to him that the earlier versions need to be used later for something

andy.fingerhut01:02:19

And if the code does not use the earlier ones, why did they bother creating new names? It is a confusion introduced not by shadowing, but by different expectations and practices between code author and code reader.

Kamuela01:02:21

I think making that decision as you’re coding is premature optimization. Truth is I don’t know if I’m going to need the intermediates until I’m done and tested

jumpnbrownweasel01:02:35

To make code more readable we can reduce the possible things the reader has to think about. This is just one way to do that. In general I think it's very important to do everything we can to make it readable. My future self is my biggest problem. :-)

didibus01:02:47

I admit, as a shadower myself 😋, I have that expectation. Especially in Clojure. I will just nest expressions or thread them if the intermediate don't need to be used later.

didibus01:02:23

And when I shadow otherwise, it's normally shadowing the arg name

didibus01:02:12

Like say a function takes a start-time as a string

didibus01:02:15

I might do

(defn foo [start-time]
  (let [start-time (Date/parse start-time]
  ...))

didibus01:02:51

Now sure I could rename them start-time-str and start-time-date

didibus01:02:31

But see, this is where I felt this had overlap with the polymorphism argument, why not get-vec and get-map ?

Kamuela01:02:15

I can see now in that situation why I don’t like it. It’s because I’m used to using languages that would end up mutating the incoming argument. So I guess to keep my idioms useful for my lowest common denominator languages, I’d be very careful not to touch incoming arguments

didibus01:02:25

Right, that does make sense

Kamuela01:02:29

I do agree though, with more use of exclusively Clojure and other immutable languages, I would probably relax my feelings about it

didibus01:02:35

I think in practice, you will not see a lot of that sort of let shadowing anyways

didibus01:02:56

I'm willing to change my mind if I start to realize that it is a common source of bug though, or confusion

Kamuela01:02:27

I think it’s just a smell of other problems/not being conscientious of future maintainers. I think the feature itself, approached with care, is fine. Just used to abuse

didibus01:02:38

I feel shadowing in a let, and shadowing in a nested context might be different too. Actually, I'm not even sure its called shadowing when in a let

didibus01:02:20

Normally, the reason shadowing is not mutation, is because the old value of the variable is restored on the way out

didibus01:02:23

(let [x 10] 
  (let [x 20]
    (println x)) 
  (println x))
20 
10

andy.fingerhut01:02:30

If you mean binding the same name x multiple times in the same let , yes that is also called shadowing. This (let [x expr1 x expr2] ...) is equivalent in meaning and behavior (modulo maybe some minor differences in JVM byte code produced by the compiler) to this: (let [x expr1] (let [x expr2] ...))

andy.fingerhut01:02:06

OK, yes, the nesting one does let you use the outer binding again after the body of the inner one is done.

didibus01:02:20

Ya, I mean more in a practical sense.

andy.fingerhut01:02:08

But neither involve mutation of data, "only rebinding of the values bound to names", which I agree can seem like a subtle and/or confusing difference.

didibus01:02:40

Is it mutating the variable?

andy.fingerhut01:02:29

There is no variable to mutate, is one reasonably correct view on answering that question.

andy.fingerhut01:02:44

There are two names that are spelled the same 🙂

jumpnbrownweasel01:02:20

I would say that a nested let that shadows, and then use of the un-shadowed binding further below, would qualify as abuse of shadowing, because it would make the code more difficult to understand than if two separate names were used. It all comes down to whether it makes it more or less difficult for the reader.

andy.fingerhut01:02:54

I am almost sure I have written a Clojure fn inside of a function defined with defn , where I have without even thinking about it had a parameter to the inner fn shadow something in the outer scope, which I then used the shadowed-only-inside-the-fn-form name after the fn body was complete. I don't know for sure that I did this or not, because everything worked with no warnings 🙂

andy.fingerhut01:02:14

One-line fn forms have a very tiny scope

didibus01:02:29

I don't know, its normally super obvious. I feel it happens when using generic names. Like the nested form is to be reasoned with independently. So its not always confusing. Like say a nested loop inside a nother, and you use e in both for element, even though they're not going to be bound the same element.

Kamuela01:02:00

In JavaScript back when CPS was the only way to format async stuff, nested functions’ arguments would shadow upper arguments pretty much constantly. There was no way to do it without potentially doing your err1 err2 thing. Nobody did that

jumpnbrownweasel01:02:05

Yeah, I have done it too. But I still think it would be more clear to have a different name, and I've been trying to make myself do that.

didibus01:02:41

Hum, ya, that's possible

didibus01:02:00

I remember having code generating a table of tables. So I definitely had row and col used in some inner nesting, and in the outer one as well 😛

jumpnbrownweasel01:02:06

I've been trying to shadow only when I think of it as a pipeline, i.e., where the earlier versions are not used again. In the example with start-time, I can think of it like that.

didibus01:02:01

Wouldn't be too hard to write a non-shadowing let, I'm curious to try it and see how it feels to use it

andy.fingerhut01:02:41

By non-shadowing let do you mean one that gives a compile-time error if the same name is bound multiple times in the same let ? If yes, agreed that should be easy to write as a Clojure macro. If you mean one that does not allow binding of names already in use in the enclosing scope, that would be a bit more challenging.

didibus01:02:31

But, anyway, my point was. If its in a nested scope, it feels much more like polymorphism. Because you still have two distinct variables pointing to two distinct values. Just they share a name. And I can see how that is unlike mutation, but more like polymorphism. That said, inside the same let, if I shadow a name, it feels a lot more like mutation. But, it only "feels" like it, because hold and behold:

(let [a 10
      a 20]
  a)
compiles too:
final long a = 10L;
final long a2 = 20L;
return Numbers.num(a2);

andy.fingerhut01:02:46

Maybe still possible to do as a Clojure macro -- I have never dug what exactly goes into the hidden &env arg of a Clojure macro, but might have the needed data in there.

andy.fingerhut02:02:07

Yes, just because the "two things" are spelled the same in the Clojure source code, doesn't mean they have to be spelled the same in the code generated by the compiler. They really are two (or more) names. Not variables.

andy.fingerhut02:02:33

Or another way of saying it: In a default-mutable language like Java/Python/etc., local variables are names of mutable places. In a Clojure let or function argument names, they are names of values, which have a delimited lexical scope. They are not names of mutable places.

jumpnbrownweasel02:02:40

Right, they're bindings.

didibus02:02:43

Yea, you can do it with &env and ns-interns

jumpnbrownweasel02:02:59

Here's another way I think of shadowing, just a small point. Sometimes code written with mutation can be simpler to read. One such case is when the prior versions of the variable are not used. Shadowing gives us a way to achieve the same simplicity without mutation. It's a narrow use case, but one that comes up quite a bit for me.

didibus03:02:01

(defmacro letnoshadow [binding-vec & body]
  (let [bindings (apply hash-map binding-vec)
        global-syms (set (keys (ns-map *ns*)))
        local-syms (set (keys &env))
        syms (-> (into #{} global-syms) (into local-syms))
        shadowed-syms (reduce (fn [acc sym]
                                (if (contains? syms sym)
                                  (conj acc sym)
                                  acc))
                              []
                              (keys bindings))
        bindings (remove nil? (map-indexed (fn[idx e] (if (odd? idx) nil e)) binding-vec))
        frequency-of-bindings (frequencies bindings)
        shadowed-syms (reduce-kv (fn [acc k v]
                                   (if (> v 1)
                                     (conj acc k)
                                     acc))
                                 shadowed-syms
                                 frequency-of-bindings)]
    (when-not (empty? shadowed-syms)
      (throw (ex-info (str "Can't shadow existing symbol inside letnoshadow, symbol(s) " shadowed-syms " already bound.") {:shadowed-syms shadowed-syms})))
    `(let ~binding-vec ~@body)))

jumpnbrownweasel03:02:25

I notice in your macro you take advantage of shadowing by binding shadowed-syms and bindings twice. So you'd have to change the names if the real let didn't support shadowing. ;-)

😂 4
didibus03:02:32

An ironic macro indeed

didibus18:02:30

And it allows mutation when it is safe from data and race conditions

MatthewLisp18:02:47

Hello clojurians! I'm using CLJ-HTTP to make some requests. I'm receiving a string inside body response, and the header says "Content-Type" = "application/csv;charset=ISO-8859-1" And the string is ok but coming with lot's of interrogation marks and numbers (as if it couldn't understand some characters) and there's no Content-Encoding header on the response, so CLJ-HTTP isn't doing anything special i think. furthermore, when i make the same request on the browser the server gives me a base64 encoded string, representing the csv, but with CLJ-HTTP i receive it deserialized. Ideas?

borkdude18:02:39

> Are you going to diverge from Clojure semantics in sci? No, but in CLJS this rule does not apply. I do even enforce it in sci on JS

borkdude19:02:33

e.g.:

clojure -e "(def ^:dynamic *foo*) (set! *foo* 2)"
#'user/*foo*
Execution error (IllegalStateException) at user/eval1 (REPL:1).
Can't change/establish root binding of: *foo* with set
$ lumo -e "(def ^:dynamic *foo*) (set! *foo* 2) *foo*"
#'cljs.user/*foo*
2
2
vs:
$ clojure -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "1.10.597"}}}' -m cljs.main -re node -e "(require '[sci.core :as sci]) (sci/eval-string \"(def ^:dynamic *foo*) (set! *foo* 2) *foo*\")"
Can't change/establish root binding of #'user/*foo* with set

didibus19:02:51

Hum interesting. Without threads, set! is thread safe, so.maybe that's why cljs allows it

borkdude19:02:45

well, it's not safe in the sense that the original binding can be changed if you allow multiple expressions to operate on the same dynamic var, e.g. in a chatbot context

john-shaffer20:02:29

Running clj -Spom, I get Unknown option: "--trace" even with a blank deps.edn. Has anyone run into this?

p-himik20:02:24

Does it happen with other options? Do you have any aliases for clj set up in your shell? Do you have anything in the users or global deps.edn?

john-shaffer20:02:29

It doesn't happen with anything else. No aliases. I had a very slightly modified user deps.edn but I removed it and it was replaced with the default

p-himik20:02:06

I cannot reproduce it at all. Do you have strace? If so, you could use it to figure out what's going on.

john-shaffer20:02:10

The execve calls look fine

execve("/usr/local/bin/clj", ["clj", "-Spom"], 0x7ffbffffade8 /* 64 vars */) = 0
...
execve("/usr/bin/bash", ["bash", "/usr/local/bin/clj", "-Spom"], 0x7ffbffffadd0 /* 64 vars */) = 0
...
execve("/usr/bin/rlwrap", ["rlwrap", "-r", "-q", "\\\"", "-b", "(){}[],^%#@\";:'", "clojure", "-Spom"], 0x565557687ef0 /* 63 vars */) = 0
Neither bash nor rlwrap produce the error in that format. Not sure what else to look at. I've never programmed in C

p-himik20:02:12

Can you run strace -s 100 -f -o clj.strace clj -Spom and attach the clj.strace file?

john-shaffer20:02:01

Hmm, that java call didn't show up earlier. I was just running strace clj -Spom 2> spom

p-himik20:02:45

-f is useful. :)

john-shaffer20:02:31

Works great if I run that java command without the --trace

p-himik20:02:33

Upgrade tools.deps.alpha.

john-shaffer21:02:45

I'm not sure how to do that, since putting it in deps.edn doesn't help and that's after the last Clojure release.

p-himik21:02:31

Hmm. > after the last Clojure release So you have the latest Clojure release, installed from that sh script, right?

p-himik21:02:42

What if you put the dependency on it into your user's deps.edn?

john-shaffer21:02:05

I tried that as well

john-shaffer21:02:23

I'm fine with the raw command though, thanks a lot!

Alex Miller (Clojure team)21:02:37

what clj version are you using? (you'll see it with clj -Sdescribe) Did you try clj -Spom -Sforce? I'm not sure that I can imagine how you're ending up with a bad trace option. I haven't seen this elsewhere.

john-shaffer21:02:05

1.10.1.489, and -Sforce shows the same error

Alex Miller (Clojure team)21:02:07

oh, there was a bug there that was fixed in 1.10.1.492

Alex Miller (Clojure team)21:02:59

I forgot about that one, but you should update

Alex Miller (Clojure team)21:02:52

latest (yesterday) is 1.10.1.510

john-shaffer21:02:30

I'm on Ubuntu actually, is that just Mac?

john-shaffer21:02:00

Oh I see the script

john-shaffer21:02:13

Okay, I didn't know about that. clj -Spom works with 1.10.1.510. Thanks!

seancorfield21:02:02

brew is available on Linux and that's what I use to help stay up to date on clojure, since I can just run brew upgrade clojure periodically (on both macOS and Linux).

seancorfield21:02:20

(and on Windows I use scoop update clojure to stay up to date)

john-shaffer22:02:39

Thanks, Sean. That is definitely easier.

didibus20:02:35

True, I don't know how to call that. I guess its just the programming style makes order hard to reason about in that case. But the behavior should still be deterministic, so its not a "race" behaviour.

didibus20:02:34

Still a common source of bug though

gerred23:02:00

has anyone written up anything about using deps.edn with https://github.com/features/packages ?

p-himik23:02:18

deps.edn can both use an explicitly specified Maven repository and a Git dependency that itself has deps.edn file. Not sure there's anything more to it.

gerred04:02:19

thanks, not what I was asking - oddly github packages can be really finicky with authentication. 🙂 so was going to write something up about it but didn't want to be redundant. worth checking out the link, github packages is using maven (but in their own weird form), not a direct git dependency.