Fork me on GitHub
Ivan Fedorov07:01:29

Fellow clojurians, can anyone run this snippet

(let [zdt (ZonedDateTime/of (LocalDateTime/of 2020 12 31 0 0) (ZoneId/of "America/New_York"))
      f (DateTimeFormatter/ofPattern "YYYY-MM-dd")]
  (.format zdt f))
and say what it outputs? I’m getting “2021-12-31” on JDK10 and 11 for some reason UPDATE: ok, solved. It’s just my formatting mistake. – Y is for week-based-year – y is for era year So yyyy and all works


Please add the imports for anyone too lazy to figure those out

Louis Kottmann14:01:31

Hello, after working on bots in ruby, for the past few months I've been working on a Slack bot in clojure, using the latest Slack events API & interaction API, clojure's async capabilities and other good stuff it provides help and command completion via back-and-forth with users for prompting values in the form of buttons/datepickers/etc. It allows you to define steps to generate an output with pedestal-backed interceptors. Registering a command looks like this:


I'd say that simply sharing it cannot hurt! there's nothing wrong with a tentative or 'playground' repo you might find specific help or perhaps some a kind code review over #pedestal

Louis Kottmann14:01:51

A more complex command looks like this (with parameters and prompting):

Louis Kottmann14:01:57

I'm wondering now if it could be useful to others, and if I should try and release a library out of it Mind you, I'm new to Clojure and the code is probably not idiomatic, I could use some help in the process

Joe R. Smith15:01:51

Anyone have any luck installing Clojure CLI tools via HomeBrew on an Apple Silicon machine? Fails for me when trying to build an aarch64 OpenJDK. I’d like it to skip that step, but didn’t have any luck telling it to just ignore the dep (it goes looking for it even though it didn’t pull it in as a dep).

Alex Miller (Clojure team)18:01:36

Did you use the Clojure tap?

Alex Miller (Clojure team)18:01:16

brew install clojure/tools/clojure` Or brew install clojure

Alex Miller (Clojure team)18:01:29

Those formulas declare java as a dep differently, not actually sure either will work

Joe R. Smith23:01:16

Thanks @U064X3EF3, the tap worked


Is there a way to redefine a function that is called inside the function I want to test? Say function A calls B which calls an external API. I want to override B to return something so I can test A without having to call the API


the best choice is to parameterize, second best is to not use any threads and use with-redefs


parameterizing could mean taking an implementation of some protocol as an argument, and using a test-implementation in your test, or just taking a function to call as part of the argument list


this combines nicely with things like stuartsierra/component or integrant that do state management as well


(nb I mention not using threads explicitly because with-redefs is not thread safe)


there's also the in-between choice of using a dynamic var plus binding to override - at least that's thread safe


Where does it make the most sense to put your protocol definitions, supposing you have lots of them? You can put them all in a core namespace, but doesn’t this complect the core namespace? You can put them in lots of separate namespaces according to a separation of needs, but doesn’t this obfuscate their existence somewhat? And won’t it potentially result in many unclear requires?


Lots of separate namespaces according to a separation of needs; and these be in the private api; separate public api with plain functions that calls into them. Or so the mailing list says...


this is part of why I hate the name "core" - the top level ns shouldn't contain the protocol definitions as they belong at the bottom of the require tree, and the top level ns belongs... at the top

👍 3

then the top level definitions combine implementations of the protocols with functions calling protocol methods


there are plenty of clojure devs who avoid private definition, that's an implementation detail here


I’ve always wondered about that, too. I always put them in a separate namespace, so the implementation namespaces can require them, and the core api has functions calling the protocol methods, and requires the implementation namespaces so they get compiled. But is there an easier way?


One caution (that @U051SS2EU refers to) is to never directly expose a protocol to the outside world. Do it via a function that then calls the protocol method. That way, you can change the protocol without impacting external callers. This way also helps if you need to later add any pre- or post-processing. If the protocol is only ever used internally, by namespaces you control, then the above is not needed.


@U0AT6MBUL This is mostly about implementing the Datafy protocol for a bunch of different Java 0bjects.


^^ I wonder if you answers are still the same? @U051SS2EU @UCCHXTXV4


in that case you aren't defining any new protocols, right?


@U051SS2EU no, I guess not. By definitions I meant function definitions (as opposed to calling a function).


one thing I've done for this is to require without a vector when requiring a namespace for its protocol extensions


most style guides say to never use require without the vector though


(ns my.ns
            [ :as directly]))


the require is happening for a side effect (the fact that something gets extended by that ns)


so none of the require features that you'd use a vector for actually make sense


in fact, since they are just being used for side effects, the files they are in don't need ns forms, and you could use load instead of require


(that's not really a great design choice, but it would emphasize the role of that file)


I need a few more large, java-heavy oss clj projects to try a certain tool on. e.g. Crux, Metabase. suggestions? :)


Not sure if they're java-heavy enough but: (for an old one)

🍻 3

bet they are transitively java-heavy at the very least ;p thanks!

👍 6
Mike Martin21:01:41

hello! 👋 Can anyone help me understand why this code works the way it does?

(get 5 :foo) ; => nil
The documentation for get indicates that a map should be provided, but if you provide any non-map data structure it still works without throwing an exception. I attempted to look at the source for get but found that its definition was seemingly recursive and somehow bottoms out in clojure.lang.RT.

Mike Martin21:01:16

a little more poking around in clojure.lang.RT yielded this section which I believe contains the answer I’m looking for

Mike Martin21:01:31

so uh.. thanks for letting me rubber duck in here 😅


as that code shows, it works with anything that is ILookup, but also anything getFrom can handle


so it works on built in types clojure doesn't redefine:

)user=> (get "hello" 1)


it turns out that getFrom is quite permissive and returns nil instead of erroring on absurd input


a silly consequence of this is that any usage of get can be replaced with (get get get get) (even recursively, of course)

Mike Martin22:01:24

thank you for the additional detail! Normally I never would have tried to feed get non-map input but the example in chapter 5 of clojure for the brave and true incidentally processes a map where an integer gets fed into get and I was really surprised that it just worked


yeah, it can be the source of strange deeper bugs


see for example

user=> (get (atom {:a 0}) :a)
oops! forgot to deref

😫 3
Mike Martin22:01:37

yikes that would be a pain to debug. Any idea what the rationale is for get returning nil instead of throwing an exception? I suppose it’s better to be permissive so that dynamic usage of get doesn’t blow up in the wild maybe?


We've talked about writing this up to add it to the FAQ on since it seems to trip every new Clojure developer up at least once, and there isn't currently a single place we can point folks to, by way of explanation.


Part of it is consistency: you can always call get on something without an exception -- you just get nil back if the "key" isn't found in the "collection". (get {:a 1} :b) returns nil, (get {:a 1} :b :c) returns :c (the "not found" arity). This is consistent with (coll :b) returning nil and (coll :b :c) returning :c -- since some collections can act as functions and look their argument up in themselves -- and also with (:b coll) and (:b coll :c) which is because keywords can also act as functions and look themselves up in their argument.


Returning nil for "not found" (or the supplied "not found" value) means that you can omit a lot of special case checking and validation on the "coll" argument in all of this and know that if you get non-`nil` (or you don't get the "not found" value) then the "coll" does indeed contain the key and your code can continue. All other cases just reduce to nil-punning without needing exception handling.


If you want an exception for non-collection arguments, you can check for the presence of the key with contains? first: (contains? 5 :foo) throws an exception.

❤️ 3

There's also nth -- compare against get behavior:

dev=> (get [1 2 3 4] 10)
dev=> (nth [1 2 3 4] 10)
Execution error (IndexOutOfBoundsException) at dev/eval10311 (dev.clj:1).
dev=> (nth 5 10)
Execution error (UnsupportedOperationException) at dev/eval10315 (dev.clj:1).
nth not supported on this type: Long

❤️ 3

Is that helpful background/motivation @U01HEUC34JK?

Mike Martin22:01:59

that is very helpful, thank you for taking the time to explain that. It’s also a good reminder that I should go and read up on nil-punning. I have a cursory understanding of it but this situation has certainly shed some more light on the topic


Its also important to remember that the standard library has a policy of "if you give the wrong thing, who knows what will happen?"


If you use the map as a function, you get an error as well. That's what I do when I want it to fail if not a map


Granted, there's an off chance a different type also can be used as a function of one argument, but that's rare.