Fork me on GitHub
#beginners
<
2020-12-15
>
AudA00:12:31

Using next-jdbc and hikari-cp to connect to my Postgres database, when I query a row, I’m getting an #inst "2020-08-14T00:26:30.000000000-00:00" on the timestamp value. I’m trying to get it to transform the value to an OffsetDateTime. This is my row builder.

(jdbc/execute-one! @data-source 
                   query
                   {:builder-fn (result-set/as-maps-adapter
                                 result-set/as-unqualified-maps
                                 result-set/read-column-by-index)
                    :return-keys true}

AudA00:12:01

When I query, it gives me this value now

#object[com.zaxxer.hikari.pool.HikariProxyResultSet 0x45deee29 "[email protected] wrapping [email protected]"]

hiredman00:12:03

an #inst tagged literally is the printed form of a few different kinds of date time objects

hiredman00:12:23

so that doesn't actually tell you how to proceed to get what you want

hiredman00:12:35

but it is almost certainly a java.sql.Timestamp

seancorfield00:12:46

@audyarandela For the builder, why are you not just using result-set/as-unqualified-maps?

hiredman00:12:51

which is a subclass of java.util.Date

seancorfield00:12:46

result-set/read-column-by-index is not a valid second argument for as-maps-adapter so your values will not be read correctly.

hiredman00:12:17

so you just figure out how to go from java.util.Date to whatever you want

AudA00:12:09

So I did just use as-unqualified-maps but the value in dB = 2020-12-11 16:02:57.217602 but want it to be the OffSetDateTime 2020-12-11T16:02:57.217602-06:00 . Do I just take that value and do a java-time conversion outside of next-jdbc, or can next-jdbc convert it before returning the row?

hiredman00:12:59

you are running into a timezone issue as well

AudA00:12:09

Yes, I hate working with time lol

hiredman00:12:31

you are storing dates without a timezone, and your timezone and your databases configured timezone don't agree

seancorfield00:12:44

The answer is complicated. #sql would be the best channel to follow-up in. Yeah, timezones are hard 😞

hiredman00:12:10

you are going to have a bad time, because if I recall most jdbc drivers default to assuming a date is in utc if no timezone is stored

😅 1
AudA00:12:56

Yea thats starting to make more sense now. Thank you both for the replies!

seancorfield00:12:13

PostgreSQL has both timestamp and timestamp-with-tz types (making the problem worse, in my opinion).

seancorfield00:12:24

The only sane approach with databases and timezones is to have your server set to UTC, your JVM set to UTC, and your database set to UTC, and to work entirely in UTC, converting to/from zoned-times just at the edges. And even then it's actually a bit more complicated than that.

😅 1
👍 1
Timur Latypoff05:12:24

I like storing and manipulating all dates as long Unix time stamps with milliseconds (always UTC), it saves from lots of troubles.

seancorfield05:12:54

Yeah, I've seen that as advice for dealing with dates/times. It seems a bit extreme when you can have the affordance of actual date/time values in UTC.

Timur Latypoff05:12:44

Yeah, I guess if the SQL queries use date functions, it’s in many ways beneficial to use proper date types. Funnily though, from my experience in finance, every time a developer doesn’t store dates as Unix time stamps, someone’s going to stay up at night at least twice a year, when summer/winter time change.

😂 1
seancorfield06:12:17

Aye, finance is its own peculiar world...

AudA00:12:34

aye yi yi

AudA00:12:19

Ok, i’ll have to go back to the drawing board. Thanks again!

seancorfield00:12:10

There's a next.jdbc.date-time namespace that might help you a bit, if you're not already requiring that -- see the Tips & Tricks page for the PostgreSQL section (it's also briefly mentioned near the bottom of the Getting Started guide).

👍 1
andarp10:12:04

I’m soon done with Brave and True. A great and fun introduction to the language! But I would now like read a book that’s a bit more direct and demanding in terms of learning Clojure. Possibly aimed at experienced programmers who aren’t new to immutability, FP etc but just to LISPs. Any good recommendations? I’m especially experienced with C# so any “Clojure for Java devs” type book would probably work well too.

dharrigan10:12:45

For a very very good explaination of each of the core functions, the standard library so-to-speak, then this book is , well, essential! 🙂

andarp10:12:02

Thanks a lot!

okwori12:12:51

//**foo.js**

$(document).ready(function ($) {
initBarD();
 //...
});

//**bar.js**

function initBarD() {
  $('.aclass').on('click', function () {
    $(this).toggleClass('is-active');
    $(this).find('.cm').slideToggle();
  });
 //...
}
** index.html for cljs(reagent/re-frame) code **
<body>
<div id="app"></div>
 <!-- ... -->
 <div class="aclass"><div class="cm"></div></div>
 <!-- ... --> 
 <script src="/js/bar.js"></script>
 <script src="/js/foo.js"></script>
 <script src="/js/app.js"></script>
</body>

okwori12:12:55

For some reason the method initBarD is not working. I tired to set extern but still same thing....

andarp12:12:05

Is defrecord considered good practice? I was kind of surprised to see it in the language. Not even sure why. Just felt a bit... type-y?

borkdude12:12:35

@anders152 defrecord is usually a way to get good performance for often used keys, while also having the flexibility of adding other keys. Also you can implement protocols with it, unlike with regular maps. Well, you can now, via metadata

Timur Latypoff13:12:38

I see defrecord as one of the many examples for Clojure being a very practical language. Like, yeah, this is a "dirty" performance trick tied to internals of JVM, but sometimes you need this performance.

Ricardo Cabral17:12:58

(defn operation2 [f & args] (apply f args))

(operate2 str "It " "should " "concatenate" ) ;; it works
(operate2 + [1 2 3] ) ;;not working
(operate2 + 1 2 3 ) ;;not working
Hello all, I am learning the basics of Clojure and there is a small exercise with I did not understand, could someone let me know why the function operate2 does not work with “+” function and it works with the “str”, considering that both are functions, it should work, shouldn’t it?

pyry17:12:24

It should work alright. I notice that you have defined operation2 in the defn block while what you're calling in the examples is actually operate2 - any chance there's an old function definition lurking behind operate2 ?

1
Ricardo Cabral17:12:58

You are right. Thank you very much. it is time to take a break 🙂. too much information. Just a quick question. Now it works if I pass the numbers (operation2 + 1 2 3) but it does not work when I pass a vector (operation2 + [1 2 3]). Should it work passing a vector?

dorab17:12:57

No. Because that would be the equivalent of (+ [1 2 3) which would not work.

👍 1
pyry17:12:03

It shouldn't. The & args passes the rest of the arguments to the method in wrapped in a collection, so (operate2 + [1 2 3]) would end up doing something like (apply + [[1 2 3]]) .

Ricardo Cabral17:12:18

understood Thank you so much dorab and pyry

Joaco17:12:31

Hi, I have a REPL question I started a REPL and trying to do a http request with HttpClient and repl throws java.lang.IllegalStateException: Client/Server mode has not yet been set. but if I build the jar and run it’s works fine Do I need run REPL with some kind of flag or something?

manutter5117:12:46

That sounds like a problem I was having recently where there was a version incompatibility between a couple of libraries I was using.

manutter5118:12:38

If I remember rightly, I fixed it by upgrading to http-kit 2.5.0

🙌 1
Joaco18:12:26

😮 @manutter51 yes! I updated to 2.5.0 and the problem has gone thank you!

👍 1
Miguel Soares18:12:34

I’m building a function that will conj to a list of maps but if there is a duplicated entry it should replace it. I got it working but it looks ugly 😞 What is the idiomatic way of solving this? Is there a function that already does this?

(defn conj-on-conflict [state new-map]
  (let [id         (:id new-map)
        maps       (:maps state)
        duplicated (first (filter #(= (:id %) id) maps))
        maps       (if duplicated
                     (reduce (fn [accum map]
                               (if (= (:id map) id)
                                 (conj accum new-map)
                                 (conj accum map)))
                             [] maps)
                     (conj maps new-map))]
    (assoc state :maps maps)))

(comment
  (let [state       {:maps [{:id 1 :val 1}]}
        new-map     {:id 1 :val 2}

        empty-state {:maps []}]
    (conj-on-conflict state new-map)                        ;=> {:maps [{:id 1, :val 2}]}
    (conj-on-conflict empty-state new-map)                  ;=> {:maps [{:id 1, :val 2}]}
    )
  )

Renato Alencar18:12:17

You could map the sequence of maps replacing things with the same id, it would be more general and avoid a lot of code.

Renato Alencar18:12:15

Something like:

(defn conj-on-conflict
  [{:keys [maps] :as state} {:keys [id] :as new-map}]
  (assoc state
         :maps (map #(if (= (:id %) id)
                       new-map
                       %)
                    maps)))

Renato Alencar18:12:41

Probably, a little bit more organized than that

Renato Alencar18:12:17

Actually, you would have to add a step verify duplicates, but that could be done with some instead of filter, since some would return when it finds the first true.

Miguel Soares18:12:19

What if maps its a empty list?

Miguel Soares18:12:34

then it will not conj to the list

Renato Alencar18:12:06

So, instead of (first (filter #(= (:id %) id) maps)) , you could use (some #(= (:id %) id) maps).

Renato Alencar18:12:06

Sure, I missed that. Wait a sec.

Renato Alencar18:12:40

You could convert your map sequence to a map, where the keys would be the ids and the values would be the maps. Then you assoc the new-map using its id as key. Now, if you do a vals in this final map, you will have a sequence of maps with no duplicates.

Renato Alencar19:12:28

(defn conj-on-conflict
  [{:keys [maps] :as state} {:keys [id] :as new-map}]
  (assoc state
         :maps (vals
                (assoc (into {} (map #(vector (:id %) %) maps))
                       id new-map))))

Miguel Soares20:12:57

Yes, that makes a lot of sense and its way better than what I had, thank you 🙏

Renato Alencar20:12:49

I'm glad to help

Renato Alencar21:12:18

And, I just saw another discussion below, and noticed something. #(vector (:id %) %) could actually be replaced by only (juxt :id identity)

curlyfry18:12:47

@miguel994 Does the order in the list matter? Otherwise you can use a set to solve your problem

Miguel Soares18:12:24

No it doesn’t, can you elaborate? duplicated in this case means that it has the same id.

curlyfry18:12:38

I see, so not the entire entry. That's what a set is; an unordered collection of entries that is guaranteed to contain no duplicates (and provides very fast lookup for contains?)

curlyfry18:12:57

In that case, why not use a map that contains the other maps?

curlyfry18:12:07

Using the ids as keys

Miguel Soares18:12:46

That would require to transform the data to be a map of maps and then transforming it back again to a list of maps, which is fine I guess .

curlyfry18:12:31

Depends on what you want to do with them! What's the use case?

Miguel Soares18:12:26

I want to assoc back to the state which has a spec that requires a list of maps

curlyfry18:12:08

Hmmm, ok. What I'm curious about is why it has to be a list? I would say that the idiomatic way to solve this "overwrite entry by id" problem is to use a map

curlyfry18:12:31

Often in Clojure data structure choice is a big part of problem solving

Miguel Soares18:12:12

Yes, probably i’m just being stubborn. 😅 Thanks for the inputs

curlyfry18:12:53

A cool thing about Clojure is that most collection data structures implement seq, meaning you can use map, filter, reduce etc on them without any modifications! So if the order doesn't matter, you'll most probably be fine with a map if you iterate over the state later in your program.

Miguel Soares18:12:43

I would like something like INSERT ON CONFLICT from postgres.

andarp20:12:55

I'm going through some Advent of Code days just to get a feel for Clojure (and loving it so far). Tried rewriting a solution using ->> but keep stumbling into an issue. This is the code:

(->> "day1.txt"
     io/resource
     slurp
     str/split-lines
     (map #(Integer. %))
     #(for [l1 % l2 % l3 %] [(+ l1 l2 l3) (* l1 l2 l3)]))
If I remove the last function with a list comprehension everything works as expected. But that line makes my program complain about:
Execution error (IllegalArgumentException) at test.day1/eval1660 (day1.clj:12).
Don't know how to create ISeq from: test.day1$eval1660$fn__1667
I'm sure I'm missing something obvious here...

noisesmith20:12:42

@anders152 #(...) expands to (fn [..] ...), so you get (fn [...] (for ...) <result of computation>)

andarp20:12:53

Ah. I need to wrap it in parens?

noisesmith20:12:05

that works but isn't what most people would recommend

andarp20:12:17

I'm all ears! 😄

noisesmith20:12:35

usually we restructure, eg. putting the rest of the chain inside the binding block of the for

noisesmith20:12:50

or making a function with a name calling for, and putting that in the chain

andarp20:12:10

Second one I understand - first one... could you show me an example?

andarp20:12:18

Or link me to something to read on it

noisesmith20:12:18

in your case you'd need let first since you use the same input multiple times

noisesmith20:12:56

but in the normal case (for [x (->> ...)] ...) - instead of for being inside ->>, ->> can be inside for

manutter5120:12:15

What about something like this?

andarp20:12:16

Ah! I understand. I'll just define a separate function in this case. Definitely makes it easier to read too.

manutter5120:12:19

(let [list-comp #(for [l1 % l2 % l3 %] [(+ l1 l2 l3) (* l1 l2 l3)])]
  (->> "day1.txt"
       io/resource
       slurp
       str/split-lines
       (map #(Integer. %))
       list-comp))

andarp20:12:45

even better, no need to do a def of course

noisesmith20:12:58

once it has a name, I'd no longer use #() and a local binding to define it

☝️ 1
manutter5120:12:50

That was just a quick refactor to get around the quirky interaction between ->> and #()

manutter5120:12:05

Normally I’d go for a defn.

noisesmith20:12:27

right, while we are reviewing the code, I'd use #(Long/parseLong %) instead of #(Integer. %) too

bnstvn21:12:08

I’ve seen (read-string %) variants for parsing ints a couple of times — is it general?

noisesmith21:12:53

it works for longs, it also has the ability to execute arbitrary code, or create arbitrary object types

👍 1
noisesmith21:12:12

also, of course, #(read-string %) can be replaced by read-string

andarp20:12:50

nice, thanks!

noisesmith20:12:54

it's more specialized, and gives the same numeric datatype you'd get for a standard literal in a file

andarp20:12:58

any other things that jump out just let me know

noisesmith20:12:24

also [(+ l1 l2 l3) (* l1 l2 l3)] can be simplified to ((juxt + *) l1 l2 l3)

noisesmith20:12:35

user=> ((juxt + *) 2 3 4)
[9 24]

andarp20:12:04

Interesting, reading on juxt now. Looks useful and powerful

noisesmith20:12:28

my experience is its fun, and a delight to find uses for, but relatively rare :D

andarp20:12:00

Definitely fits the "I want to apply a series of functions to a series of values and get the summary of all the function executions in a nice format" type problem...

phronmophobic20:12:13

I use juxt mostly with map

(map (juxt :id :name) some-hashmaps)

Evan Bowling20:12:42

I like it for sort-by as well

👍 2
andarp20:12:12

instead of #([(:id %) (:name %)]) ?

phronmophobic20:12:42

instead of #(vector (:id %) (:name %))

noisesmith20:12:44

that's invalid, it turns into calling a [] with zero args, which blows up

andarp20:12:05

oh right, that was even mentioned as a gotcha somewhere...

noisesmith20:12:44

#() and ->/->> are much weirder than they first seem, because they operate in the realm of syntax transforms, not program logic

andarp20:12:07

Yeah, I mostly wanted to see if I understood them correctly. Not sure they really fit my solution here

noisesmith20:12:23

user=> (->> (+ a b) (let [a 19 b 23]))
42

👀 1
😱 2
andarp20:12:09

I do have one more question. What's the standard way of getting values out of a map when passed to a function? For vectors I can obviously just match on the position, but on maps?

noisesmith20:12:54

you can destructure using :keys, :syms, :strs or the full map destructure syntax {var "some-key"}

1
andarp20:12:52

Thank you, will read

Jeff Evans21:12:10

is it possible to have one arity of an anonymous fn call another arity? what name do you use in that situation?

noisesmith21:12:01

the arity itself solves it (you need a direct call by name, recur doesn't work)

noisesmith21:12:31

you can give an anonymous fn a name like this: (fn fn-name ([x] ...) ([x y] ...))

noisesmith21:12:58

giving it a name that way also makes stack traces a bit more useful

leif21:12:28

Is it possible to tell the clojure command line app where your deps.edn file is?

clyfe22:12:08

CLJ_CONFIG=/path/to/dir_with_deps_edn clj ...
May help

clyfe22:12:32

But that's the user deps, not project

leif21:12:59

(If say, you want to run clojure from a different directory. Maybe something like:

clojure -d ./sub-dir/deps.edn -m cljs.main --compile foo

leif21:12:23

Or do I have to do something like:

(cd sub-dir && clojure -m cljs.main --compile foo)

noisesmith21:12:40

I think it's easier to cd to the dir with the deps.edn, and tell it your source tree is on some other path, yeah

noisesmith21:12:15

but maybe there's a new option for specifying a deps file, since I last checked

leif21:12:57

@noisesmith Sad, okay, thanks. 🙂

alexmiller21:12:24

no, there's not