Fork me on GitHub
#clojure
<
2023-09-15
>
Jan Šuráň08:09:07

I wonder if there ever was a discussion about why Clojure doesn't support writing integers with underscores, like 1_000_000 , while Java does.

Jan Šuráň09:09:14

Thanks, yeah, that seems reasonable.

practicalli-johnny09:09:18

The rational seems to be making numbers easier to parse for humans, so to me this feel more like a feature in an editor and not the language There are already editor features that display lambda functions and partials with fancy symbols. Many editors also colourise Hex colour codes. All of which does not require a change to the underlying langues The benefit of having this as an editor feature is that it could be configurable as to what separator to use, e.g _ , . With comma and dot useful for currency values.

☝️ 1
1
p-himik09:09:56

> There are already editor features that display lambda functions and partials with fancy symbols. Ohhh, you have no idea of the degree to which I cannot stand it. :D Fonts with ligatures get as little usage in my setup as feasible.

practicalli-johnny09:09:11

As these features are built into the editor rather than the language they never have to be enabled 😃 Personally I use hex colouring, font ligature, but not the fancy symbols. When the code is checked into a shared Git repository no one has to tolerate my editor preferences (I can also switch them off when pairing) 😆

reefersleep09:09:17

@U2FRKM4TW I'm with you on that one. Coloring text is one thing, but visually transforming it into something it's not? (like two characters into one) uggghhhh get me away from that

p-himik09:09:50

> As these features are built into the editor rather than the language they never have to be enabled True! But having the ability only on the editor side will make it the only option. Having the ability to add _ right in the source code will make it an option for everyone.

reefersleep09:09:56

But it's great to have options to view things as you want, @U05254DQM

practicalli-johnny10:09:31

Changing the language forces the change on everyone, whether they want it or not. So this would no longer be an option, but a feature of the Clojure language and something everyone would have to live with I would find _ in numbers as disturbing as others have said the optional editor features are to them. I would prefer to avoid making Clojure syntax more complex, as that reduces the desire to use the language

vemv10:09:05

> All of which does not require a change to the underlying langues Good insight! A non-intrusive approach could be to alternate the syntax color in groups of 3 e.g. assuming a black blackground, a given number chunk could be rendered in either white or light grey

octahedrion10:09:03

I'm with @U05254DQM on this and have nothing to add as he's said everything I would say

Noah Bogart13:09:34

I am 100% for adding support for this. code is meant to be read by humans, and making number literals easier to read and understand is an unreserved good. Plus, matching Java's reader allows for easier transition between the two languages.

p-himik13:09:14

> A non-intrusive approach could be to alternate the syntax color in groups of 3 That's a neat solution, I'd probably use it.

Joshua Suskalo14:09:22

Personally my main concern would be that libraries probably shouldn't be written with this feature if they were to be compatible with multiple clojure versions, but I guess that's just the same as depending on any feature of new clojure versions.

Noah Bogart14:09:33

when you push the solution to editors, you multiply the work required and you leave plain text representations out.

Jan Šuráň17:09:29

Yeah, that's a good point imo that the editor should do that, not the language itself.

Daniel Gerson11:11:54

I really miss this feature! Worked in a huge JDK codebase (largest I've ever seen) in a vendor for banks before and this was a great Java feature! I don't agree with the "everyone has to live with this argument". Great, don't use it in your code base then, but let everyone who wants to use it. This is not a colouring of functions feature, it will only affect your own local code. We should all have the choice over how our local code looks. It means it looks consistent across Github reviews, Emacs, VsCode etc. There is no plugin that can solve Github for example, which is necessary part of many organisations and probably the majority of where code is viewed across teams.

grav10:09:15

Is this expected?

user=> #inst"0001-01-01"
#inst "0001-01-01T00:00:00.000-00:00"
user=> (.toInstant #inst"0001-01-01")
#object[java.time.Instant 0xe362c57 "0000-12-30T00:00:00Z"]

grav10:09:09

To me, it seems the "absolute" value of the date changes with the conversion.

grav10:09:47

Then again, I'm not sure what guarantees java.util.Date (#inst) and java.time.Instant gives for dates pre-unix epoc.

grav10:09:29

Oh, and the conversion back to #inst seems to be the inverse, so that's good:

user=> (java.util.Date/from (.toInstant #inst"0001-01-01"))
#inst "0001-01-01T00:00:00.000-00:00"

reefersleep13:09:04

Brought to you by devs who wanted an easy, but useless way to time travel

reefersleep13:09:00

It's a question of how you want to represent 00:00:00-00:00 o'clock within each type, right? And the makers of each type didn't agree. (Maybe the same guy at different times. Opinions may mutate when you're traveling forwards in time a second per second.)

p-himik14:09:07

About LocalDate but same explanation applies to Instant: https://stackoverflow.com/a/23978037/564509

lukasz15:09:57

Just a note, I'd recommend against using #inst for anything but the simplest use cases, because you get java.util.Date out of it - using java.time.* directly is not that bad, and there's a couple of wrappers that help.

1
reefersleep15:09:21

I wish we'd realized that much earlier in our app development

lukasz15:09:09

Oh yeah, it's a serious foot gun. Same as using java.util.concurrent.ScheduledThreadPoolExecutor/scheduleWithFixedDelay for precise scheduling for tasks, it drifts and doesn't sync with properly with system time.

reefersleep16:09:27

Just to be clear, in which ways do you consider it a footgun?

lukasz16:09:38

There's a lot written about it, but depending on what you're doing - the fact that it's not exactly a UTC timestamp (even though it seems to act like it) because it ignores leap seconds - javadoc for util.Date goes into more depth. Second thing is that pretty much all methods are deprecated as for JDK 1.1 :-)

👍 1
reefersleep16:09:21

Thank you 😊

Daw-Ran Liou16:09:17

Hey all, just want to share some mildly-interesting discovery. Today, I stumble upon this interesting/surprising behaviour of clojure.core/seq?, which checks whether a given object implements the clojure.lang.ISeq interface:

user> (seq? ())
true
user> (seq? '(1 2 3))
true
user> (seq? [])
false                   ;; <-- !?
user> (seq? [1 2 3])
false                   ;; <-- !?
user> (seq? (seq [1 2 3]))
true
Vectors are not ISeqs! This is a bit surprising because I always thought the clojure.lang.ISeq is the shared interface that makes the sequence abstraction in Clojure possible. As it turned out, vectors are clojure.lang.IPersistentCollections. ISeq is actually a sub-interface to IPersistenCollection, not the other way around. And the trick for the clojure runtime to convert from vector to an ISeq is in the super-interface – the clojure.lang.Seqable interface, which defines a single method that converts itself to an ISeq:
public interface Seqable {
    ISeq seq();
}
Thanks for reading!

1
Noah Bogart18:09:41

additionally, the 1.9 function https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/seqable? which is referenced at the bottom of that article

Daw-Ran Liou18:09:55

Thanks @U064X3EF3! I need to update my terminology now - most of the time when I said seq I actually meant Seqable. This also summarized very well in the article: > The Clojure list implementation (`clojure.lang.PersistentList`) is a list data structure and thus is both a concrete data structure and also implements the ISeq abstraction directly. All of the other collections are Seqable, but not ISeq. > […] > Some other important sequence predicates: > • seq? - checks whether an instance implements ISeq > • sequential? - checks whether an instance implements Sequential >

Noah Bogart18:09:13

@U064X3EF3 should you update that post to note that seqable? was added to core? (not to make more work for you...)

Jan Šuráň18:09:41

Was there ever a discussion about adding a transducer version of clojure.string/join to the core library? I feel like it could be nice, you often transform some data and then you want to write it to some file with new lines etc.

hiredman18:09:57

transducers tend to be concerned with performance, building strings is usually bad for memory, so bad for performance

phronmophobic18:09:59

If you want to join a string and use transducers to produce the input, you can do something like:

(clojure.string/join ", " (eduction xforms coll))
If you're trying to write to an output stream, you could do something like:
(with-open [w (io/writer "myfile.txt" )]
  (binding [*out* w]
    (transduce
     (interpose \newline)
     (completing
      (fn [_ s]
        (print s)))
     nil
     [1 2 3 4])))

Alex Miller (Clojure team)18:09:32

Seems like you’d really want that as the final f in a transduce call rather than a transducer

👍 2
phronmophobic18:09:30

Yea, you might just be looking for interpose.

phronmophobic18:09:20

or (comp (map str) (interpose sep))

🎯 1
Jan Šuráň18:09:34

It would be nice to be able to call (str/join xf separator coll)

phronmophobic18:09:01

complected in a bad way? I have used the str/join + eduction idiom a few times.

(clojure.string/join ", " (eduction xforms coll))
Having a shorthand seems plausible.

Alex Miller (Clojure team)18:09:06

I'd rather have some way to do (transduce xf str-accumulator coll) and then use interpose as one of the xf if needed

Alex Miller (Clojure team)18:09:39

"append to string" is an accumulator function like conj

Alex Miller (Clojure team)18:09:02

you're making a conveyor of string parts via transducers, then accumulating into a final string

Alex Miller (Clojure team)18:09:27

that would be a good thing to have

Jan Šuráň18:09:37

Not at my PC atm and I didn't have time to check it, but maybe something like that?

(defn join [xf sep coll]
  (let [first (volatile! true)
        rf (fn
             ([sb] (.toString sb))
             ([^StringBuilder sb input]
               (if (identical? @first) true)
                 (do (.append sb (str input))
                     (vreset! first false))
                 (.append sb sep input)))]
    (transduce xf rf (StringBuilder.) coll)))

Alex Miller (Clojure team)18:09:01

you're putting all the parts in one box. I want the parts

Alex Miller (Clojure team)18:09:15

or rather, we have all the parts except the string accumulator, I want that missing part

hiredman18:09:07

like conj, where the 0 arity would return a stringbuilder, the completing arity would toString it

hiredman18:09:20

just the rf part

Alex Miller (Clojure team)18:09:25

then you can (transduce (interpose ",") str! coll)

Alex Miller (Clojure team)18:09:53

there's all kinds of things you might want to string accumulate

mpenet18:09:41

I have done that in the past, transduce + a string-builder rf + xforms to build strings

1
Alex Miller (Clojure team)18:09:04

you might also want the transducer arity of join, but it should be built on this

phronmophobic18:09:56

right, you can do both • have a convenience function for common tasks • build it out of simpler pieces and make those available

hiredman18:09:37

https://clojurians.slack.com/archives/C03S1KBA2/p1686622008346419 if you want to avoid concatenating intermediate strings

mpenet18:09:02

It’s full of horrors as well

Alex Miller (Clojure team)18:09:55

cgrand's str! is basically what I would want

👍 2
Joshua Suskalo19:09:21

Haven't caught up on the whole thread but to me this just sounds like a combination of the interpose transducer and a string-building transducing context, which sounds useful to me.

Alex Miller (Clojure team)19:09:26

transduce itself is sufficient, but yeah, I guess you could fold the builder together potentially (like into does with conj)

Alex Miller (Clojure team)19:09:10

could potentially even fold into into depending how weird we want into to be :)

notbad 1
Joshua Suskalo19:09:40

Yeah, I'd agree it's sufficient, but we do have shorthand for sequence and collecting to a specified collection type with into, so having a shorthand for "into string" could be nice. Just having a string-accumulating rf would definitely be good enough though. It'd do what the str multi-arity does already.

Alex Miller (Clojure team)19:09:41

I'm putting it in our queue for 1.13, we'll think about it then

👍 2
Alex Miller (Clojure team)19:09:25

str could be thought of as similar to into except it takes elements, not a coll

Jan Šuráň19:09:19

Btw, fixed:

(defn join [xf sep coll]
  (let [first (volatile! true)
        rf (fn
             ([^StringBuilder sb] (.toString sb))
             ([^StringBuilder sb input]
               (if @first
                 (do (vreset! first false)
                     (.append sb (str input)))
                 (.. sb (append sep) (append (str input))))))]
    (transduce xf rf (StringBuilder.) coll)))

Sam Ferrell19:09:02

cgrand/xforms is for me a necessary extension for effective use of transducers

Jan Šuráň19:09:00

Although as I see from the conversation, you want to do it in a different way... didn't have time to read it too much yet, sorry...

Jan Šuráň19:09:46

(tested with empty coll, 1 item coll and longer coll)

Jan Šuráň19:09:13

It seems like a pretty clean way to do that, but everyone could have a different opinion on that...

Jan Šuráň13:09:24

Ok, I agree that this job can be forwarded to the str! transducer... adding it to str/join would complect the functionality...

(defn str!
  ([] (StringBuilder.))
  ([result] (str result))
  ([^StringBuilder result input] (.append result (str input))))

Jan Šuráň14:09:38

Thanks for adding it to the 1.13 queue!

tomc20:09:49

Hi all, I'm having some trouble streaming a csv to s3 with clojure.data.csv and cognitect aws-api. This is my incorrect code:

(defn write-rows-to-s3 [rows bucket file-key]
  (with-open [to-s3 (io/input-stream )
              output (io/output-stream to-s3)
              writer (io/writer output)]
    (let [s3 (get-s3-client)
          _ (csv/write-csv writer rows)
          res (aws/invoke s3 {:op :PutObject
                              :request {:Body to-s3
                                        :ContentEncoding "utf8"
                                        :Bucket bucket
                                        :Key file-key}})]
      (if (:Error res)
        (prn "ERROR:" res)
        (prn "Successfully copied csv to s3.")))))
I am handling the input and output streams wrong. How can I write the csv to something I can pass along as the :Body of the request?

phronmophobic20:09:33

I don't believe the cognitect aws API allows streaming, https://github.com/cognitect-labs/aws-api/issues/14

phronmophobic20:09:01

> We do accept and return InputStreams, but the underlying http-client does not.

lukasz20:09:18

ah, that's right - I just remembered that I had to use AWS Java SDK to efficiently sync big files to S3 (gigs in size)

phronmophobic20:09:31

yea, I recently switched to https://github.com/mcohen01/amazonica for this exact reason.

tomc20:09:51

Whether aws-api actually streams the request is less important to me here than whether I can avoid putting a file on the server that creates the CSV. I'm struggling more with the part where I get a csv into an input stream.

phronmophobic20:09:10

ah, yea, that should be possible.

lukasz20:09:10

Are you using clojure.data.csv?

phronmophobic20:09:43

What is the input you're starting with? A file? An in memory representation?

tomc20:09:13

yes, clojure.data.csv, and starting w/ a sequence of rows from next.jdbc

lukasz20:09:17

I def did the same thing few years ago, so: • get your CSV data like this https://github.com/clojure/data.csv#example-usage • then pass (slurp writer) to the s3 put request

tomc20:09:45

Thanks for the help. I ended up borrowing piped-input-stream from ring (https://github.com/ring-clojure/ring/blob/1.9.0/ring-core/src/ring/util/io.clj#L11) and then my code is just:

(defn write-rows-to-s3 [rows bucket file-key]
  (with-open [to-s3 (piped-input-stream (fn [outstream]
                                          (with-open [writer (io/writer outstream)]
                                            (csv/write-csv writer rows))))]
    (let [s3 (get-s3-client)
          res (aws/invoke s3 {:op :PutObject
                              :request {:Body to-s3
                                        :ContentEncoding "utf8"
                                        :Bucket bucket
                                        :Key file-key}})]
      (if (:Error res)
        (prn "ERROR:" res)
        (prn "Successfully copied csv to s3.")))))

👍 1
lukasz20:09:17

Neat, and sorry - I missed the fact that you want to avoid the file

tomc20:09:37

I appreciate getting any help at all 👍

👍 1
hiredman20:09:26

weird behavior after a typo

user=> (Thread//sleep 10)
nil
user=> (name (nth  (macroexpand '(Thread//sleep 10)) 2))
"sleep"
user=> (namespace (nth  (macroexpand '(Thread//sleep 10)) 2))
""
user=> 

seancorfield20:09:51

user=> (macroexpand '(Thread//sleep 10))
(. Thread /sleep 10)
That's funny...

seancorfield20:09:33

I suppose it sort of makes sense since clojure.core// is a thing.

hiredman20:09:09

yeah, a readable symbol can have any number of '/' in it, but cannot have / as the first character, and everything before the first / is read as the namespace

seancorfield20:09:54

And we have +', -', and *', but no /' -- that's unreadable 🙂

hiredman20:09:39

what is happening is inside the transformation from (Thread//sleep 10) to (. Thread /sleep 10) the code taking apart the symbol Thread//sleep into the namespace Thread and the name /sleep

hiredman20:09:51

and then build new symbols from those parts

seancorfield20:09:26

Definitely surprising until I macroexpanded it. Clojure can still surprise us after all these years :rolling_on_the_floor_laughing:

hiredman20:09:44

but /sleep is interpreted by the symbol making stuff as being a symbol with "" as the namespace name and "sleep" as the name

hiredman20:09:02

and the . special form ignores the namespace part of the method symbol

Joshua Suskalo20:09:27

/' wouldn't make sense though, there's nothing it could do that requires a promotion that would be detectable I think. Like I don't think you can tell if you're losing precision when doing a floating point division, can you?

Joshua Suskalo20:09:56

Although I realize that it being unreadable would pose a problem for its use anyway

seancorfield20:09:48

...because / on its own has a special reader rule...

seancorfield20:09:41

The promoting ops was just where my mind went when played with the original macroexpansion... 🙂

seancorfield20:09:51

user=> (macroexpand '(Thread/bar/baz 10))
(. Thread bar/baz 10)

seancorfield20:09:10

So of course, this actually works too:

user=> (Thread/ignore-me/sleep 10)
nil

hiredman20:09:55

user=> (-> 'home/kevin/Downloads name symbol name)
"Downloads"
user=>

hiredman20:09:37

each symbol -> name removes one path segment

😆 1
andy.fingerhut01:09:09

@U5NCUG8NR There is one case with 64-bit wide long / that requires promotion to give the correct answer: (/ Long/MIN_INT -1) is outside of the range of a 64-bit long

andy.fingerhut01:09:55

And yes, I have filed an issue or two years back in Clojure JIRA, at least one of which resulted in a change in Clojure code.