Fork me on GitHub
#clojure
<
2023-05-02
>
pesterhazy08:05:27

I have some code doing a bit of date arithmetic, and I'm thinking of using java.time.* for everything, instead of java.util.Date Two questions: 1. Does it make sense to go for java.time everywhere in my Clojure code? I feel like the Clojure world is pretty conservative and I still see jdk7 java.util.Date everywhere 2. Is this the best way to create a java.time.Instant? (.toInstant #inst "2023-04-25T10:00:00")

iperdomo08:05:39

you can use (java.time.Instant/parse "<str>") but your <str> must use ISO-8601 format - https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html#parse-java.lang.CharSequence-

pesterhazy09:05:15

Good point on parse I always liked the fact that the #inst parser is more lenient, as you can do stuff like #inst "2020", which is convenient in tests

pesterhazy09:05:45

I guess it's not so bad

(Instant/parse "2023-04-25T10:00:00Z")

javahippie09:05:02

I’d recommend not using java.util.Date anywhere, tbh. Especially not in Clojure, as it’s not immutable while java.time.* is

👍 2
👆 6
Alex Miller (Clojure team)11:05:31

#inst is a literal format and not tied strictly to a particular object representation. If you want to make Instants (a good choice), I would use the Java apis or a wrapper around them, not the #inst literal as a parser

pesterhazy11:05:27

> #inst is a literal format and not tied strictly to a particular object representation. Not sure I understand. Isn't clojure.instant/read-instant-date (bound to #inst) tied to java.util.Date?

borkdude12:05:59

@U06F82LES FWIW: > Does it make sense to go for java.time everywhere in my Clojure code? This is certainly bb's take on date

iperdomo12:05:51

I guess what @U064X3EF3 suggests is that #inst can be mapped to a different parsing, thus different resulting object, by changing the default binding - https://github.com/clojure/clojure/blob/master/changes.md#211-instant-literals

Alex Miller (Clojure team)12:05:20

Correct, and there is a backing protocol with ties to inst? and inst-ms

Alex Miller (Clojure team)12:05:47

I believe Instant also prints to #inst for example

Alex Miller (Clojure team)12:05:03

So (.toInstant #inst …) assumes a concrete type that may not actually be a Date

Alex Miller (Clojure team)12:05:48

Using the parsers in clojure.instant would be fine though

pesterhazy13:05:01

Got it Rebinding #inst feels risky (especially if third-party code is using it) So even if it takes a few more keystrokes, writing my own wrapper or using java.time.Instant/parse is worth it

pesterhazy13:05:29

> So (.toInstant #inst …) assumes a concrete type Good point, hadn't considered that

Alex Miller (Clojure team)13:05:11

the functions in clojure.instant are designed to be swappable alternatives for the #inst reader that may produce alternate types

pesterhazy13:05:29

I saw that there's already support for sql timestamps etc

pesterhazy13:05:53

BTW Instant doesn't seem to print to #inst for me (which I think is a good thing?):

user=> (println (java.time.Instant/now))
#object[java.time.Instant 0x6ca320ab 2023-05-02T13:06:45.436353Z]

Alex Miller (Clojure team)13:05:13

yeah, I was going from memory, I couldn't remember if we did that or not

pesterhazy14:05:11

Loved the talk by @U050ECB92 linked above: > I personally like java.time a lot – I think it's really well designed. There hasn't been a Clojure library that wraps Java time that I'm super satisfied with. > > A lot of Clojure wrappers – I call them like white gloves libraries because it's like, "I don't want to touch the Java Interop, it's nasty, let me just make some closure API on it". > > But sometimes the Clojure libraries get certain elements wrong. They might hide the detail that's available in the library. They might distort some of the design elements. Or they might do something kind of pernicious like giving new names to concepts that are already very, very clearly named in the in the underlying library.

2
👍 2
4
Lennart Buit15:05:55

Yeah java.time.* made me understand why it isn’t trivial to go between instants, local times, times with offsets and times with zones — its API taught me that, that sounds like a great design to me.

thheller15:05:24

Also note that you don't have to resort to string parsing, there are method to construct pretty much everything directly, although sometimes have to convert between a few types. (-> (OffsetDateTime/of 2023 4 25 10 0 0 0 ZoneOffset/UTC) (.toInstant)). might not always be more readable than the string variant though

💡 2
Lennart Buit15:05:23

But is very explicit about what kind of time conversion you are doing!

thheller16:05:11

I do agree that java.time is nice, just have to figure out what to use for what once, then its good 😉

lukasz16:05:52

+1 what @U06F82LES said - but also I found https://github.com/dm3/clojure.java-time the easiest to work with - it doesn't hide the underlying java.time.* stuff but makes the code more succinct

seancorfield17:05:22

As a former maintainer (and heavy user) of clj-time and also as a former heavy user of date-clj, I can say that we've switched at work to a mixture of raw Java interop for Java Time and, where we need to do some heavy lifting arithmetic, some uses of that clojure.java-time library that @U0JEFEZH6 mentioned. We've removed all uses of the former two libraries from our code. We use c.j-t in 58 files (out of 1,159) and we are slowly removing many of the early uses of it that I originally introduced, in favor of plain interop where we're doing a lot of arithmetic. The "problem" with c.j-t is that it tries to be "convenient" by automating some of the type conversions for you, letting you go from type A to type D while hiding the internal A -> B -> C -> D chain of conversions. I liked that at first ("easy") but have learned to value the explicitness of code that calls out each step (composing "simple" stuff) 🙂

⬆️ 8
🧡 4
lukasz18:05:19

Yeah, I'm pretty much in the same spot - it's 50% Java interop and 50% c.j-t

fuad18:05:29

https://clojurians.slack.com/archives/C06MAR553/p1677842754963519?thread_ts=1677842754.963519&amp;cid=C06MAR553 has been pretty helpful for me to understand the several different options available and how they compare.

👍 4
2
agorgl17:05:09

What do you use for non-trivial cli argument parsing? I wanna build something that will have many subcommands with verbs + options in each subcommand (similar cli interface to podman / docker). Should I prefer plain tools.cli, cli-matic or something else?

Joshua Suskalo17:05:27

tools.cli definitely has a very opinionated way for arguments to work, so unless it matches the api you want to build very well it won't be of all that much help and you'll end up doing stuff manually for subcommands etc. yourself anyway.

agorgl17:05:31

@U04V15CAJ I wanted to make the project a big babashka script in the beginning, but I pivoted to a full clojure project after some thought as providing native binaries would be a requirement

borkdude17:05:01

@U03PYN9FG77 I meant the babashka CLI library. #babashka-neil is an example of a CLI with non-trivial subcommands it's built with #babashka-cli

agorgl17:05:40

Ah nice, did not know that babashka libraries could be used with regular clojure projects!

borkdude17:05:02

Oh yes, that's the whole idea of babashka, that you can run those scripts with the JVM and vice versa :)

borkdude17:05:33

(Almost) everything baked into babashka you can run on the JVM, including its babashka.* libs

agorgl17:05:43

According to @U5NCUG8NR tools.cli will not be a match probably, so @U04V15CAJ any quick comparison of babashka.cli vs cli-matic?

borkdude17:05:53

I optimized babashka.cli for binary size (native-image) so I'm not using any dependencies inside of it, I'm not even using clojure.spec. Hopefully the API is intuitive. I can't make the comparison with cli-matic since I haven't used it a lot

agorgl17:05:19

Ok, I'll give it a shot

agorgl17:05:18

I love all borkdude™ projects ^^

borkdude17:05:49

hehehe... let me know in #C03KJHCNZ99 if you have any grievances

2
Drew Verlee18:05:53

@U03PYN9FG77 fwiw, I personally try to avoid building complicated command line programs. clojure and the repl is always simpler. If the cli program your thinking of is for talking to k8s, then i would consider using the k8s API directly.

Mark Wardle18:05:01

I haven't tried babashka cli... but I have used https://github.com/clojure/tools.cli and built subcommands and dynamic help and options based on those commands... e.g. see https://github.com/wardle/hermes/blob/main/cmd/com/eldrix/hermes/cmd/cli.clj This namespace turns the CLI options into a domain-specific map that I then use to actually do anything, simplifying testing for example. https://github.com/wardle/hermes/blob/main/test/com/eldrix/hermes/cmd_test.clj Like many Clojure libraries, tools.cli is a library that provides good basic functionality but doesn't stop you adding your own domain-specific functionality when you need that. So in that example, I simply look for subcommands, take them out of the args, and then dynamically queue the options for each subcommand.

Mark Wardle18:05:52

The benefit of this approach is when one is deploying jar files... in other cases I've just used -X and parameters... although escaping parameters is not ideal for non-Clojurians to grok.

borkdude18:05:18

babashka.cli offers a close alternative to -X that takes away the need for quoting

👍 2
borkdude18:05:28

also it supports subcommands

Mark Wardle18:05:14

Thanks @U04V15CAJ - I'll have a look thank you!

borkdude18:05:52

it's a bit more flexible since you can have arbitrary long subcommands, like foo bar baz --opt 2 --flag

☝️ 2
timrichardt20:05:07

In the Clojure source, there is a pattern having the invariant always the first in exps like (= 5 x) , or (= :tag some-key). Is there a reason behind that? Reads a bit like Yoda speak. Expressions with < on the other hand usually have the invariant last.

timrichardt20:05:13

I've also had a look at SBCL, a lot of Common Lisp code, virtually nobody puts the invariant first. 😄

p-himik20:05:12

> virtually nobody puts the invariant first On the contrary when it comes to tests - (is (= expected actual)). But certainly differs between testing libraries/frameworks.

👍 2
seancorfield20:05:16

I expect it's a throwback to working in languages with an assignment = and equals being == so you'd type (constant == expr) to avoid accidentally writing (constant = expr) which the compiler could catch.

👆 2
phronmophobic20:05:30

I'm not sure why others do it, but I do it because of habits built writing c and c++.

phronmophobic20:05:56

I guess it's also relevant in java and javascript?

p-himik20:05:18

Also, somewhat related - in Java it's common to write "some-string".equals(value) instead of the reverse because of the cases when value is null.

👍 2
seancorfield20:05:41

I picked up that habit years ago, due to languages that had assignment expressions -- so (a = b) wasn't a statement, it was a side-effecting expression 😕

phronmophobic20:05:54

> Reads a bit like Yoda speak. That's actually a common name for it, https://en.wikipedia.org/wiki/Yoda_conditions

😄 2
😀 2
Alex Miller (Clojure team)20:05:55

this is xunit style, pretty pervasive from that

Alex Miller (Clojure team)20:05:56

but really, I do both, usually preferring the shorter one first (which is often the expected value)

👍 2
David Ackerman20:05:17

not sure if anyone else feels this way, but I do it because i find it slightly easier to understand the comparison and scan the code when the constant is first (rather than way at the end of the line, or even wrapped to the next line)

timrichardt20:05:24

Hehe, OH|'d the Yoda term, didn't know about the popularity.

David Ackerman20:05:25

oh yeah i guess same reason alex just said

Alex Miller (Clojure team)20:05:37

(is (= 10 (call-to-some (thing-that-is 'complicated))))

4
👍 2
seancorfield20:05:55

(hahaha... I just realized my code examples were prefix, like Clojure, and I specifically meant to write them infix so I updated my posts)

timrichardt21:05:56

I see your points. I feel I have to think more, when the invariant is first. When I read (if (= :red lights) (stop!) …, I build the picture and first read: If . red ... - then automatically think of all things that can be red, then read light and calm down. :) If the order is (= lights :red) a more specific thing comes first. Feels better for me.

Alex Miller (Clojure team)21:05:47

in this case, I would probably do the latter if it makes it easier for you and those who follow to understand it

timrichardt21:05:49

And if somebody uses = to assign sth., the ship is sunken anyway.

timrichardt21:05:53

However, thanks for your replies. This is a nice bike shedding topic. 🙂

Joshua Suskalo21:05:31

I do it this way because the variables are usually in the context of my brain already so it's mostly obvious what I'm comparing with, but I want it to be up front what I'm comparing it to so that I can see immediately what it's doing.

alpox21:05:40

I usually write the invariant last (in JS etc. too) mostly because its closer to my thoughts that say "when x is 5 then". "when 5 is x" takes me a while to process

timrichardt21:05:45

> I do it this way because the variables are usually in the context of my brain already so it's mostly obvious what I'm comparing with Yep, I definitely see the point now seeing it from a testing perspective. Or some comfort while developing. But when you encounter some code that you are not familiar with, (= 5 x) is more confusing.

Joshua Suskalo21:05:03

That's true, it can contribute to difficulty reading in a new codebase.

Vincent00:05:55

invariant first because it's furniture to orient around

Vincent00:05:18

except for monotonically decreasing/increasing, in which case, aesthetics! x)

skylize17:05:20

The invariant is more often than not a short simple thing like a constant or a symbol, while the variant can easily explode into a huge multiline series of steps building up a calculation. If you put the tiny little invariant on the right in that case, it all-but disappears from view when quick scanning through the code. To look at it a completely different way: a simple-to-read invariant placed first serves as a summary or header, explaining what all the (potentially complicated mess of) code that follows is going to do.

skylize17:05:03

And in that case, doing the same with short comparisons is just a matter of consistency. It might seem slightly disorienting at first. But once you get used to it, your brain will already be in sync with the format you should hope to see in more complicated examples.

cddr21:05:27

Is there a guiding principle behind this meme? I’m not sure I get it. https://twitter.com/borkdude/status/1653501527250685980

borkdude21:05:18

I don't get it either, what a lame meme

😁 14
isak21:05:07

This is meme pollution

isak21:05:34

The high and low are supposed to agree for one thing

🙂 4
2
borkdude21:05:56

yeah, I know

borkdude21:05:48

it's kinda symmetrical though

😁 2
eval-on-point21:05:55

maybe because (partial func opts) is like a little factory for specialized funcs? total shot in the dark

💡 4
2
escherize21:05:30

Usually you put the things that are less likely to change first, right? I didnt know there was any debate 😓

borkdude21:05:04

There is no debate. Don't read too much into a lame meme ;)

escherize21:05:32

i mean, i didn’t think having arguments was an option…

😂 14
escherize21:05:45

(“humor”)

eval-on-point21:05:49

I always thought that you generally want to think about it in terms of how you would use the function in a threading macro or weather you will use partial to parameterize it. I figured that was why database connection is the first arg for jdbc.next, for example

hifumi12321:05:14

that is also how i order the first/last argument of my functions

hifumi12321:05:51

if I intend to use it with partial, thread-first, ..., then i put the most "important" argument first; if i intend to use it with thread-lasf, then i put the argument last

potetm22:05:19

Galaxy brain: All fns take a single hashmap.

potetm22:05:33

To mix meme-aphors.

escherize22:05:59

{:options {} :arguments []} 🌌 🧠

donotwant 4
😂 2
potetm22:05:56

I am amazed at how quickly you destroyed my meme.

escherize22:05:38

it still works, just put anything in there! {:optional-opts+args {:options {} :arguments []} ...}

potetm22:05:16

I can't tell if you're trolling me or not lol

potetm22:05:31

(I'm literally lolling)

2
escherize22:05:35

(me too haha)

✔️ 2
Vincent00:05:50

The clojure equivalent might be (fn [args & more]) and someone using just a field name more instead

simongray09:05:51

(fn opts & args) is great for partial