This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-05-02
Channels
- # announcements (1)
- # babashka (4)
- # beginners (39)
- # calva (36)
- # cherry (11)
- # cider (23)
- # clj-on-windows (3)
- # clojure (105)
- # clojure-brasil (1)
- # clojure-chicago (3)
- # clojure-conj (8)
- # clojure-denver (4)
- # clojure-europe (18)
- # clojure-germany (5)
- # clojure-hungary (13)
- # clojure-nl (1)
- # clojure-norway (31)
- # clojure-sweden (9)
- # clojure-uk (2)
- # clojurescript (22)
- # core-async (4)
- # cursive (8)
- # data-science (25)
- # datomic (14)
- # devops (1)
- # emacs (9)
- # events (5)
- # holy-lambda (32)
- # hyperfiddle (26)
- # introduce-yourself (2)
- # kaocha (1)
- # leiningen (11)
- # lsp (17)
- # malli (8)
- # off-topic (84)
- # pedestal (4)
- # polylith (2)
- # re-frame (17)
- # reitit (1)
- # releases (1)
- # remote-jobs (1)
- # shadow-cljs (8)
- # sql (4)
- # tools-deps (8)
- # transit (5)
- # vim (1)
- # vscode (1)
- # xtdb (45)
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")
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-
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
I guess it's not so bad
(Instant/parse "2023-04-25T10:00:00Z")
I’d recommend not using java.util.Date
anywhere, tbh. Especially not in Clojure, as it’s not immutable while java.time.* is
#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
> #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?
@U06F82LES FWIW: > Does it make sense to go for java.time everywhere in my Clojure code? This is certainly bb's take on date
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
Correct, and there is a backing protocol with ties to inst? and inst-ms
I believe Instant also prints to #inst for example
So (.toInstant #inst …) assumes a concrete type that may not actually be a Date
Using the parsers in clojure.instant would be fine though
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
> So (.toInstant #inst …) assumes a concrete type Good point, hadn't considered that
the functions in clojure.instant are designed to be swappable alternatives for the #inst reader that may produce alternate types
I saw that there's already support for sql timestamps etc
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]
yeah, I was going from memory, I couldn't remember if we did that or not
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.
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.
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
But is very explicit about what kind of time conversion you are doing!
I do agree that java.time is nice, just have to figure out what to use for what once, then its good 😉
@U06F82LES preach
+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
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) 🙂
https://clojurians.slack.com/archives/C06MAR553/p1677842754963519?thread_ts=1677842754.963519&cid=C06MAR553 has been pretty helpful for me to understand the several different options available and how they compare.
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?
Have you tried https://github.com/babashka/cli?
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.
@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
@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
Ah nice, did not know that babashka libraries could be used with regular clojure projects!
Oh yes, that's the whole idea of babashka, that you can run those scripts with the JVM and vice versa :)
(Almost) everything baked into babashka you can run on the JVM, including its babashka.* libs
According to @U5NCUG8NR tools.cli
will not be a match probably, so @U04V15CAJ any quick comparison of babashka.cli
vs cli-matic
?
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
@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.
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.
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.
babashka.cli offers a close alternative to -X that takes away the need for quoting
Thanks @U04V15CAJ - I'll have a look thank you!
the subcommand feature looks very similar to what you have here: https://github.com/wardle/hermes/blob/d70dfc2f50597b7f83f75d27f9d6aa6c2eef3c69/cmd/com/eldrix/hermes/cmd/cli.clj#L123
it's a bit more flexible since you can have arbitrary long subcommands, like foo bar baz --opt 2 --flag
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.
I've also had a look at SBCL, a lot of Common Lisp code, virtually nobody puts the invariant first. 😄
> virtually nobody puts the invariant first
On the contrary when it comes to tests - (is (= expected actual))
. But certainly differs between testing libraries/frameworks.
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.
I'm not sure why others do it, but I do it because of habits built writing c and c++.
I guess it's also relevant in java and javascript?
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.
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 😕
> Reads a bit like Yoda speak. That's actually a common name for it, https://en.wikipedia.org/wiki/Yoda_conditions
this is xunit style, pretty pervasive from that
but really, I do both, usually preferring the shorter one first (which is often the expected value)
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)
Hehe, OH|'d the Yoda term, didn't know about the popularity.
oh yeah i guess same reason alex just said
(hahaha... I just realized my code examples were prefix, like Clojure, and I specifically meant to write them infix so I updated my posts)
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.
in this case, I would probably do the latter if it makes it easier for you and those who follow to understand it
And if somebody uses =
to assign sth., the ship is sunken anyway.
However, thanks for your replies. This is a nice bike shedding topic. 🙂
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.
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
> 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.
That's true, it can contribute to difficulty reading in a new codebase.
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.
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.
Is there a guiding principle behind this meme? I’m not sure I get it. https://twitter.com/borkdude/status/1653501527250685980
maybe because (partial func opts)
is like a little factory for specialized funcs? total shot in the dark
I suspect it links up with https://clojurians.slack.com/archives/CLX41ASCS/p1682973319244789?thread_ts=1682973105.819799&cid=CLX41ASCS
Usually you put the things that are less likely to change first, right? I didnt know there was any debate 😓
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
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
it still works, just put anything in there! {:optional-opts+args {:options {} :arguments []} ...}