Fork me on GitHub
#clojure
<
2020-12-31
>
pinkfrog08:12:28

@seancorfield I am using your clj-new, which is great. One thing I don’t understand is, I see a clj_new.clj file in the jar archive. But that file does not exist in the github repo, https://github.com/seancorfield/clj-new. Which magic makes that file into the jar?

seancorfield17:12:06

@UGC0NEP4Y Glad you find it useful! There's a #clj-new channel if you get stuck or want to deep dive on anything.

pinkfrog03:01:37

Thanks. I see that now.

sveri10:12:10

Hi, so we have that nice if-let binding. However, when I check on strings, if-let works on an empty string, which makes sense. Is there a similar macro for strings that only creates a let binding if a string is not null or empty?

p-himik10:12:52

You can wrap something that returns a string (or any other collection) in a call to not-empty. Just in case - also note that if-some exists.

delaguardo10:12:31

(defn not-empty-str [s]
  (when-not (string/blank? s) s))

(if-let [x (not-empty-str "")]
  x
  "FAIL")

p-himik11:12:50

string/blank? is different from "an empty string": "True is s is nil, empty, or contains only whitespace."

p-himik11:12:27

If you know for sure that something is a string, there's absolutely no reason to not use not-empty in the described scenario.

vemv12:12:52

> there's absolutely no reason {...} there is: as you hinted, string/blank? is a stronger predicate, and the one that one would likely want to use in a custom macro. (Whether a custom macro is worth it for such a specific use case is a different topic)

dharrigan12:12:30

I also use seq which works with strings, collections and so on.

dharrigan12:12:01

which, not-empty uses under the covers

p-himik12:12:17

@U45T93RA6 str/blank? is not stronger, it's completely different:

(str/blank? "   ")
=> true
(not-empty "   ")
=> "   "

vemv12:12:39

This is the original q: > only creates a let binding if a string is not null or empty? str/blank? satisfies that. It's stronger than not-empty because it rejects more invalid strings. A custom let that considered "" blank but " " not blank would seem fairly contradictory.

sveri12:12:43

Actually in my context I indeed look for str/blank. I ended up copying the if-let macro and adding an additional predicate on the test: (if (and temp# (not (str/blank? temp#))) instead of (if temp#)

p-himik12:12:09

Sorry but I don't follow you at all @U45T93RA6 . The original question does not use the word "blank" but uses the word "empty". Maybe we have differences in our understanding of the word "empty". I treat it as empty?, but maybe you treat it as "not visible if rendered with no context" or something like that? But given the elaboration from sveri, this is now but a discussion of semantics. :)

sveri12:12:49

Sorry for being unclear in my original question. For me > if a string is not null or empty? refers to str/blank?, but thats just my interpretation.

👍 3
emccue20:12:21

If you want, you could define "truthyness" differently

emccue20:12:05

(defn truthy? [v]
   (not (or (nil? v)
            (false? v)
            (empty? v))))

emccue20:12:13

and then build something around that

didibus20:12:56

The answer is no. If-let checks for truthy values, and truthy values are all values except for nil and false. So a String, no matter what characters it contains or doesn't is always truthy. So like others said, you need to wrap the string in some other function that returns the string or falsy.

Timofey Sitnikov12:12:55

Good Morning Clojurians. How come the https://maven.apache.org/guides/mini/guide-mirror-settings.html lists the official maven repo as and my clj seems to think that (https://repo1.maven.org/maven2/) is the central repo?

p-himik12:12:24

I think it's the same repo but available at different URLs. Check out the top <url> tag here: https://repo.maven.apache.org/maven2/.meta/repository-metadata.xml

dharrigan12:12:28

Both ultimately resolve to a caching provider (fastly) with the same subnet, so I wouldn't be surprised if they are simply the same service.

3
borkdude12:12:44

I'm using a transit library from 2016 (by @russ767, the golang one) I see this warning on the transit-java page: NOTE: Transit is intended primarily as a wire protocol for transferring data between applications. If storing Transit data durably, readers and writers are expected to use the same version of Transit and you are responsible for migrating/transforming/re-storing that data when and if the transit format changes. Does this mean a go transit lib from 2016 might not be compatible with a clj/java transit lib from 2020?

Alex Miller (Clojure team)16:12:32

This refers to the transit format spec, which has not changed.

borkdude16:12:03

OK, I feel lucky

borkdude12:12:01

I mean, what kind of standard is transit, if you cannot rely on this?

didibus21:12:31

A wire protocol? It just says that new version of transit could be incompatible with the old versions. And since wire is the focus, my guess is if for any reason an incompatible change improves performance a lot of addresses some other major issue they'd do it.

borkdude21:12:37

@U0K064KQV sure, but this means that a web service in language A talking transit 0.8 (of company Y) and a web service in language B (of company Z) upgrades to 0.9 suddenly can't talk to each other anymore?

borkdude21:12:18

that seems like a very brittle way of doing things. what if JSON makes a breaking change?

didibus21:12:07

I get it, but I don't believe this is the use case for transit.

didibus21:12:05

The use case is fast communication between services. I think it's meant for micro-service and client/server communication. Where if you upgraded, you'd upgrade everything together.

didibus21:12:39

I also think what it would mean, is that the two services when communicating would need to agree on the version of transit to use. So maybe transit 0.9 can still communicate in transit 0.8 to a 0.8 client.

didibus21:12:09

But I don't know if that's the case, or maybe since the protocol never actually changed yet, this problem and it's solutions haven't come up

borkdude21:12:32

One problem I'm specifically having: babashka can communicate with pods (standalone programs) via transit. If babashka would upgrade its transit lib but the sqlite pod (written in Go) doesn't, there may be a problem

borkdude21:12:43

and this go lib doesn't even mention a transit version

borkdude21:12:49

In this case I would prefer stability over performance, so maybe transit isn't the right pick then

borkdude21:12:06

I picked transit because it has support for byte arrays which JSON hasn't

borkdude21:12:34

sending a byte array from clojure to a pod works seemlessly without having to think about base64, or whatever

didibus22:12:38

Hum... Well I think it is up to you to negotiate a version of transit to use maybe? But this "negotiation" mechanism isn't in the Spec. And so Transit libraries for various languages don't have to support it or anything. I don't know, are there a lot of data encodings out there that took a stand on cross version compatibility?

borkdude22:12:08

> This implementation's major.minor version number corresponds to the version of the Transit specification it supports. I have trouble finding out which major and minor version this lib has: https://github.com/russolsen/transit

borkdude22:12:56

I think it would be appropriate to have different namespace names in transit libraries if there are breaking changes, so you can support multiple at the same time

borkdude22:12:10

transit08.clj, transit09.clj

borkdude22:12:41

but as Alex has said, the format hasn't changed since 2014 so maybe it's a non-issue

👍 3
didibus22:12:29

Ya, the thing is the libs don't really matter

didibus22:12:42

Current version is 0.8

didibus22:12:44

That said, the spec does say: "Each library's major.minor version number corresponds to the version of this specification it implements."

didibus22:12:08

So I think a well designed lib for transit should have versions mapping to the spec it implements

didibus22:12:37

But ya, I guess namespaces would be nice, since then you could support multiple versions

borkdude22:12:04

> Ya, the thing is the libs don't really matter What do you mean?

borkdude22:12:50

So they way they propose to use transit is in controlled environments where you have control over each component - not as something you expose to the outside world, I guess?

didibus22:12:48

I mean that the library can easily be designed badly, or behind, in some given language. The transit version I believe is part of the payload, since transit is self-describing. So in theory, a good lib can decode transit of any versions, or throw if it knows it can't.

didibus22:12:08

But two systems exchanging messages, should also make sure they use the same protocol

borkdude23:12:21

I don't think the transit version is part of the payload

didibus23:12:00

I mean, you can expose it to the outside world. But say I had an API, and I took and returned Transit 0.8, I'd tell my clients. And if I'm about to upgrade to 0.9 and no longer support 0.8, that be for me to communicate it to them, and help them migrate, or I can provide another API that takes 0.9, etc.

didibus23:12:12

> I don't think the transit version is part of the payload Hum...

didibus23:12:45

If that assumption I'm making is wrong, then I guess ya, Transit might be meant for closed systems that you control end to end

didibus23:12:16

Because that means there'd be no way to handle a 0.9 that is incompatible with 0.8 if it ever happens

didibus23:12:58

Maybe that's still a non-issue. Like Alex said, who knows if we will ever need a 0.9, and if we do, maybe it will be made backward compatible anyways.

borkdude23:12:16

> Transit was released as version 0.8 July 2014 and has not been changed since

didibus23:12:57

Ya, but if the encoding isn't describing of its protocol version, that does mean it can't gracefully handle breaking changes.

didibus23:12:40

So we have to rely on the rationale that most likely at this point, they'd never break compatibility since so many people use it.

didibus23:12:43

Maybe they'd do a Transit2 instead of a 0.9 if they needed a breaking change. I wouldn't be surprised. Clojure core devs and Rich hates breaking people's code, so I think its fine not to worry about this scenario

didibus23:12:50

My conclusion is not to worry about it. I really suspect this is a similar case to Clojure libs being stuck in 0.x forever. And I'm like 99.9% convinced they'd never introduce a breaking 0.9 transit version.

didibus23:12:40

Also, I see the spec specifies MIME types. So maybe the payload doesn't tell you the protocol, but it seems to be intended to use a MIME type with it. So I think it be easy to add a new MIME type if Transit changed in incompatible ways.

borkdude23:12:24

right. right now I use transit+json as the "mime" type in babashka pods

borkdude23:12:44

this could also be transit0.9+json in the future

didibus23:12:28

Ya exactly

didibus23:12:47

I think its safe to use for pods.

borkdude12:12:22

Is the spec versioned?

borkdude12:12:55

It seems so? Version: 0.8

borkdude12:12:45

Are changes to transit versions documented?

Alex Miller (Clojure team)16:12:43

Yes, but there have been no changes since it's initial release in 2014

borkdude16:12:38

Btw, I'm using the russolsen go transit lib for real now.

roklenarcic13:12:44

If I want to represent a pair in clojure, which representation has the least memory used: vector, list or something else?

p-himik13:12:33

defrecord, according to a small experiment with https://github.com/clojure-goes-fast/clj-memory-meter

p-himik13:12:17

If you don't care about immutability then regular Object[] will be even smaller.

didibus21:12:46

An array most likely

roklenarcic19:01:08

thanks people, I wanted equality semantics so I didn’t go with array, but rather with (List/of (object-array [val1 val2]))

didibus23:01:10

Does that really take less memory?

didibus23:01:54

Also, forgot to mention before, if you're storing primitives, you can try vector-of it is a space efficient version of vector, the purpose of it is space efficiency.

didibus23:01:31

It's basically a vector interface implemented over a typed homogeneous array of primitives

vemv14:12:00

I encountered this exact thing (for IPV6 specifically) https://support.cloudbees.com/hc/en-us/articles/360035734031-Stuck-thread-looking-up-IPv6-hostname in a clj program of mine Is -.preferIPv4Stack=true a good idea then? Or at least a not-terrible one I hope? (I've seen it in various clojure projects, I never happened to know what was being fixed there)

vemv23:12:00

using this flag didn't seem exactly harmless as it triggered this kind of error: > I get the above exception when I set bind_host to :: but specify -.preferIPv4Stack=true -.preferIPv4Addresses. https://github.com/elastic/elasticsearch/issues/15143#issuecomment-160945522

Timofey Sitnikov14:12:54

I am trying to run test for https://github.com/fulcrologic/fulcro-rad-sql but getting No matching ctor found for class org.flywaydb.core.Flyway error. like so:

[I] /home/sporty/clojure/fulcro/fulcro-rad-sql~> clj -M:test
Clojure 1.10.1
user=> (require 'com.fulcrologic.rad.database-adapters.sql.query-test)
Syntax error (IllegalArgumentException) compiling new at (com/fulcrologic/rad/database_adapters/sql/migration.clj:170:28).
No matching ctor found for class org.flywaydb.core.Flyway
I am I missing something in the setup?

vemv14:12:43

Sounds like a dependency resolution issue. Are you on Lein or deps.edn?

vemv14:12:17

ok, I lack expertise there but the underlying issue, I think, is that Fulcro expects one specific version of the Flyway .jar, but deps.edn is fetching another Different jars can have different versions of the java code, which would explain the not-found constructor

3
Timofey Sitnikov14:12:51

Thats crazy, but but yes, that was the problem. I make all the deps to use the same older version and now it works. Thank you.

🙌 3
Timofey Sitnikov14:12:35

Phew, I have so much to learn with this huge Clujre stack.

vemv14:12:22

10 years on the JVM and still learning (as you can see in the question I posted above yours)... 😃 at least us clojurians tend to not break APIs so much. I don't remember an issue like yours (which is relatively common in Java) with clojure .jars

Askri Ghassen14:12:52

Hello every one i am here for searching a good documents that's helps me build a web api using pedestal integrant and vase

simongray15:12:03

There’s no way to add support for with-metadata to instances of a Java class, is there?

rutledgepaulv06:01:24

it sort of depends on the class. if there's an interface for it you can use java proxies to maintain the interface while adding additional stuff.

rutledgepaulv06:01:42

or if you can do a delegate pattern

simongray16:01:31

Thank you for that example. I’m not sure I understand what the delegate part is in the example and how it differs from java proxies, since the the example seems to use proxy? Anyway, if my understanding is correct, I will need the proxied class to have constructor that can take an existing instance of the class and create a new instance from it, right? It is a great hack, but unfortunately it’s not general enough to wrap e.g. random Java objects from a Java library, which is really what I’m looking for… :/

rutledgepaulv03:01:05

my apologies for not being very clear. proxy the clojure function isn't the same as a java.lang.reflect.Proxy instance although they're similar in purpose. You can use Proxy instances to support classes that don't necessarily have a "delegate" constructor by adding a layer of indirection between the object consumers see and the real object instance underneath (this is what a java.lang.reflect.Proxy does). To use java.lang.reflect.Proxy you'll need to have interfaces that you can rely on for each type of object (one of which would be IObj), otherwise I believe you'd need to dip even deeper into things like javaassist / cglib to pull it off if your objects don't have interfaces and instead are using abstract classes or things like that

rutledgepaulv04:01:48

would be interested in hearing more about what you're trying to solve by adding metadata to java classes though - there may be another way to approach the problem that is almost certainly worth pursuing over doing funny things with javaassist / cglib

simongray07:01:14

Sure. The issue is the following: In Stanford CoreNLP you define a pipeline based on a list of annotators. Then when you annotate a piece of text, you get back an instance of an Annotation class. This is basically just a glorified map containing other annotations. As an aggregate, they form a tree of different kinds of annotations. You can navigate down the tree using a set of functions picking different kinds of annotations. One issue is that there is nothing in particular marking what type of annotation the instance is - you only know this from the key that you got it from. You also don’t know what kind of pipeline setup it was constructed from or what it was a child of, so I can’t navigate back up the tree like you would do with something like clojure.zip. One way of solving this lack of information is to wrap the Annotation in a Clojure map when you get it, but that would introduce new complexity when interfacing with the Java API, since it’s now an instance of a different class. Another way of solving it would be to mutate the Annotation object by setting my own key using its setter function, but that would 1) add mutation and 2) pollute the domain with implementation code that doesn’t really belong there. So I thought that maybe I could use metadata to solve this issue with a lack of, well, metadata.

Schpaa16:12:49

(core/defmacro or
  "Evaluates exprs one at a time, from left to right. If a form
  returns a logical true value, or returns that value and doesn't
  evaluate any of the other expressions, otherwise it returns the
  value of the last expression. (or) returns nil."
This sentence does not compute

dpsutton16:12:36

read the or on the second line not as a logical construct but as the macro under discussion. "If a form returns a logical true value, this form returns that value and doesn't evaluate any of the other expressions..."

Schpaa16:12:35

this is the fragment that is particularly confusing ”

, or returns that value and doesn't
  evaluate any of the other expressions,

Schpaa16:12:53

But what do I know - I am not a native speaker, this could be James Joyce quality for what I know

noisesmith16:12:28

haha, James Joyce was famous for prose that made no clear sense :D

bronsa16:12:59

"if a form returns a logical true value, or returns that value (the logical true one) and doesn't evaluate any of the other expressions"

dpsutton16:12:48

yeah. substitute that "or" with "this macro". It is certainly confusing because we need a way to specify if we're talking about the conjunction "or" or the macro or

Schpaa16:12:50

funny how the description of boolean stuff sounds so unboolean in english

dpsutton16:12:19

also, there's an "and" after it which probably primes our brain to interpret the preceding or as the conjunctive part of speech rather than the macro this docstring is describing

Schpaa16:12:54

I agree, it need more parens

😁 3
dpsutton16:12:39

I dont see how parens would help. I think bronsa put the best solution which is to put backticks around the confusing or

lilactown18:12:40

anyone have experience drawing ASCII art in a REPL?

lilactown18:12:14

I'm guessing that there's going to be issues dealing across various displays (e.g. TTY, Cursive, Emacs buffers)

cfleming20:12:36

Cursive does implement some ASCII escape codes, but it’s very far from implementing a full terminal. I can get a list of the supported codes if that’s useful.

noisesmith18:12:01

if all you do is print line by line, and you use a standard character set, it should just plain work (though emacs bugs out with wide lines)

noisesmith18:12:59

if you are trying to do cursor control, you need a cursor control lib, and those don't target cursive or emacs (though at least emacs does have buffers that are vt100 compatible...)

lilactown18:12:23

yeah I just need to be read only for now

lilactown18:12:32

and print line by line

phronmophobic18:12:56

I've been using https://github.com/mabe02/lanterna for terminal graphics

noisesmith18:12:03

I think any output that displays fixed width properly would just work

noisesmith18:12:17

yeah, it looks like lanterna is great if you need cursor control

phronmophobic18:12:31

or mouse support!

phronmophobic18:12:52

it's also graalvm compatible if you want to have fast start up

lilactown18:12:27

I think that printing to a REPL buffer is as much as I want to do to keep it simple enough to: • View remotely (e.g. across a socket REPL/nREPL connection) • Use in CLJS / browser

👍 3
lilactown18:12:13

if I was only interested in Clojure and doing local dev then lanterna would probably be better

noisesmith18:12:17

yeah, I've had good luck viewing ascii formatted text in a browser, just make sure to use <pre> or otherwise ensure fixed width

noisesmith18:12:36

(I used this for tabulating cljs test output)

noisesmith18:12:58

(who wants to mess with browser rendering in your test suite...)

J19:12:53

Hey all I'm trying to wrap a defn in a macro. Can anyone explain to me why this doesn't work? A little new to macros. (defmacro m [x] (defn f [x] (x)))`

noisesmith19:12:10

@buterajay this might be better for #beginners but to start, macros return lists and the list gets compiled, in your list the function argument is nonsensical

noisesmith19:12:48

this one is small enough to work out by hand: (m 5) would become (defn f [5] (5))

noisesmith19:12:16

(well it would work as (m conj) for example, except it would create a function that takes an argument named conj and then calls its arg with no args...)

noisesmith19:12:49

@buterajay do you understand this syntax error?

(ins)user=> (defmacro m [x] `(defn f [~x] (~x)))
#'user/m
(ins)user=> (m conj)
Syntax error macroexpanding clojure.core/defn at (REPL:1:1).
user/f - failed: simple-symbol? at: [:fn-name] spec: :clojure.core.specs.alpha/defn-args

J19:12:14

Hm ok that makes sense, but what if my input is a simple symbol like (m 'v)

noisesmith19:12:38

it would break, as 'v is (quote v) which is not a valid argument name

noisesmith19:12:43

also, your macro gets one step closer to working as follows (but probably isn't doing what you want)

(cmd)user=> (defmacro m [x] `(defn ~'f [~x] (~x)))
#'user/m
(cmd)user=> (m foo)
#'user/f
(cmd)user=> (f conj)
[]
(ins)user=> (f +)
0

noisesmith19:12:08

the arg name to m doesn't matter at all, as it is just a placeholder inside f

noisesmith19:12:57

(what I did to fix the previous error is unquote then quote f, so that ` wouldn't namespace it and break the defn)

J19:12:34

Ooh gotcha

noisesmith19:12:47

have you used macroexpand ?

J19:12:15

Ya but my macros often are too broken for it to work lol

noisesmith19:12:57

yeah, that's frustrating, here's a small workaround for debugging:

(ins)user=> (defmacro m [x] `(_defn f [~x] (~x)))
#'user/m
(cmd)user=> (macroexpand '(m foo))
(user/_defn user/f [foo] (foo))

J19:12:17

I thought with a macro, the inputs are not evaluated, so passing in a symbol would just be the symbol and not (quote v)

noisesmith19:12:23

it's interesting that the schema check runs by just expanding the macro, kind of agressive...

Alex Miller (Clojure team)20:12:13

the check happens at macro expansion time so I don’t actually think it’s aggressive at all

noisesmith22:12:44

I guess my surprise is that I'm used to thinking of macro expansion as manipulating symbols of lists, and defmacro wasn't expanding a defn but rather constructing one - it' s a minor thing to be sure

noisesmith22:12:15

my mental model was that (macroexpand '(defn ...)) would go through defn's validators as it expands defn itself, but I didn't expect the result list to also get validated based on the first symbol

noisesmith19:12:40

'v prevents evaluation outside macros

noisesmith19:12:03

it expands to (quote v) which is a reader syntax for not evaluating

noisesmith19:12:22

but if you already aren't evaluating, you just get (quote v) when you use ' too

noisesmith19:12:37

user=> ''''''''foo
(quote (quote (quote (quote (quote (quote (quote foo)))))))

noisesmith19:12:36

because quote doesn't mean "make a symbol when reading" it means "don't evaluate this form", so when you nest it, you get the inner quote as is

J19:12:46

Ok that makes sense. So then how would you define a variable name like x? Should I evaluate a string into a list?

noisesmith19:12:03

what should this macro do?

J19:12:15

Basically (m v) should return (defn f [v] (v))

noisesmith19:12:47

I asked that because I don't understand the question "how would you define a variable name" - usually in macros the variable names are auto-generated and what matters is that they match the binding

noisesmith19:12:07

the binding block on f there means that v is meaningless

noisesmith19:12:12

it's just a throwaway name

noisesmith19:12:21

(for an arg, that gets called with no args)

J19:12:51

I see what you mean, maybe I need to rethink exactly what it is that I need..

noisesmith19:12:03

so as you specify, (m asdfljasdfk) and (m foo) and (m conj) all do the same thing

J19:12:31

Essentially I'm looking for a way to compress an expression into a single macro with free variables as inputs. If you have a free moment, I have a video of visual clojure editor I'm working on. Basically this macro would help me to compress larger expressions into single reusable blocks. https://photos.app.goo.gl/Yh2emBVrgsEcDxhF9

noisesmith19:12:04

@buterajay if I understand what you are trying to do, I think your best bet is to set aside ` entirely, and look at what a macro does your own program is doing some of the work that ` does, while missing the variable capture safety both your program and `, are tools for constructing lists from templates, you can just return the list you want compiled from a macro

noisesmith19:12:18

(ins)user=> (defmacro silly [] (list 'defn 'f ['x 'y] (list '+ 'x 'y)))
#'user/silly
(ins)user=> (silly)
#'user/f
(ins)user=> (f 1 2)
3
• edited in an attempt to make it clearer what I'm demonstrating

noisesmith19:12:52

for a non "silly" example, you would be doing something other than just returning that defn list - you'd be doing some sort of list interpolation / splicing / substitution

noisesmith19:12:24

I don't think ` will help you much here - you are making an alternate way of doing that - which is valid, it's just a series of tree transforms

noisesmith19:12:05

you might want to skip defmacro entirely, and just construct lists to pass to eval

noisesmith19:12:33

perhaps the domain of your program is that a user interacts with the GUI to construct a namespace, this means the goal would be to translate the GUI state into a series of forms that clojure could load to create a namespace

J19:12:16

Wow ok I think I'm seeing what you mean. I knew it would just take someone with a clojure mindset haha. I need to process this a bit more. Thank you very much