Fork me on GitHub
#beginners
<
2022-10-07
>
Abhi Saxena01:10:30

Hi Clojurians - I am writing a small test to post a record with Postgres, using ring.mock.request, something like this- (deftest test-post-group-targets (testing "test post group targets” (let [response (app (-> (request :put (str "/api/target-id”)) (header “header-key “90909090”) (json-body {:targetval_ 110 :targetdate "_2019-09-09" })))] (is (= 201 (:status response)))))) However, it's not able to serialize the date field I am passing and throwing exception by middleware - "message":"{:cause Don't know how to write JSON of class java.sql.Date, :via [{:type java.lang.Exception, :message Don't know how to write JSON of class java.sql.Date, :at [clojure.data.json$write_generic invokeStatic json.clj 100]}], :trace [[clojure.data.json$write_generic invokeStatic json.clj 110] [clojure.data.json$write_generic invoke json.clj 382] [clojure.data.json$fn__506$G__501__513 invoke json.clj 286] [clojure.data.json$write_object invokeStatic json.clj 335] [clojure.data.json$write_object invoke json.clj 319] [clojure.data.json$fn__506$G__501__513 invoke json.clj 286] [clojure.data.json$write invokeStatic json.clj 475] [clojure.data.json$write doInvoke json.clj 424] [clojure.lang.RestFn invoke RestFn.java 425] [clojure.lang.AFn applyToHelper AFn.java 156] [clojure.lang.RestFn applyTo RestFn.java 132] I am not sure if I need to write a custom write to handle this?

seancorfield01:10:06

First off, when you're posting code or output, it makes it a lot more readable to use triple backticks around it, so it looks like this:

(some code here)
;; and some output
Second, you show code that calls json-body which is in ring-mock but that uses Chesire to serialize data to JSON -- but your stacktrace shows clojure.data.json as failing so you're not showing us the code that is actually failing. I'm not sure why you're getting that message since clojure.data.json knows how to serialize java.sql.Date:
user=> (require '[clojure.data.json :as json])
nil
user=> (json/write-str {:date (java.sql.Date. 10000)})
"{\"date\":\"1969-12-31T08:00:00Z\"}"
user=>
So I wonder if you are using a old version of org.clojure/data.json that dates back to before java.sql.Date support was added? The enhanced date support was added in version 2.2.0 which was released six and a half years ago.

Abhi Saxena13:10:43

yes, the version was old (0.2.6), I finally had to use below macro to expand the call and it worked -

(extend-type java.sql.Date
  json/JSONWriter
  (-write [date out]
    (json/-write (str date) out)))
thanks for your help.

Kevin Izevbigie11:10:11

I am struggling to find prettier for clojure or an alternative to use with VSCode. What you you boys and girls use?

Ferdinand Beyer11:10:46

You mean a code formatter? cljfmt is the de-facto standard

🙌 1
Ferdinand Beyer11:10:24

If you are using VS-Code with Clojure, you probably use Calva, which includes cljfmt

Kevin Izevbigie11:10:49

I guess I need to figure out how to activate cljfmt on save

Ferdinand Beyer11:10:41

It should work out-of-the-box when you activate VS-Code's Format on Save option. Calva will provide a Clojure Formatter to VS-Code

🙌 1
Ferdinand Beyer11:10:15

Try the Format Document command from the command palette - it should format your file

Ed12:10:49

My personal preference is to not use automatic code formatting tools. I've never met one that didn't do a bad job in one edge case or another and prefer to treat the indentation as a tool to help you understand the code, like commit messages, tests, comments and documentation. I realise this is an unpopular opinion, but I'm always confused when I see these sorts of requests, like it's normal to ask for a tool that deletes all the documentation from your code ...

Ferdinand Beyer12:10:13

I've been like you, Ed. But in teams, especially larger teams, it is almost impossible to agree on a certain style. It is far easier to agree on some documented style, and use an automated tool to keep the code formatted according to this style. Most languages do have a very small number of "idiomatic" styles, and Clojure is one of them. Of course, tools should not "delete all the documentation". Which tools does something like that? Definitely not cljfmt

Ed12:10:43

I think that education is a better long term approach - esp. for large teams. Formatting tools often take things that are intentionally laid out, for example as a table, and strip out the whitespace to remove that visual cue. In something like Clojure, where the code is data, these sorts of visual queues can be useful with getting a point across quickly. I tend to use things like emacs' align-regex to tidy up small sections of the file where needed. Yes, I agree that it's nice to have some coding conventions followed for consistency, but that's all part of your definition of done - "is it readable?" should be one of the questions your ask yourself before saying that the task is done, and indentation is part of that.

Ferdinand Beyer12:10:30

"readable" is subjective though. If your team agrees to lay out code in a table, wouldn't it be better to configure your formatter to apply this agreed-upon style? I agree that too aggressive formatters can be a pain. I've encountered that in Java a lot, where formatters often broke my manual layout, and forced me to write "suppression" comments. Have not had this issue in Clojure though, as cljfmt is not super strict.

pez13:10:30

With default settijngs cljfmt will not de-align tables like that. It is quite friendly with the existing formatting and mostly does indentation. Calva has a separate command for vertically aligning maps and binding vectors. And it's formatter (which is using cljfmt) will leave these alone.

pez13:10:01

Also, formatting the current enclosing form in Calva is as easy as pressing tab. So most often Format on Save is not necessary. At least not to me.

🙌 1
Kevin Izevbigie15:10:04

Yeah I have seen now that I can press tab to make the whole form pretty. It’s a small thing but when I press enter after a conditional, the cursor jumps the the start of the line instead of behind the paren.

pez16:10:29

I think you might be using the VIM extension? It’s moving the cursor there for unknown reasons. Some people get away with using Calva’s old indentation engine, which is a tad slower and thus can undo VIMs move. Ymmv.

Kevin Izevbigie04:10:30

Yeah correct. I use the vim extension. I find the emacs experience to be better but emacs is just…. too much haha.

Lidor Cohen12:10:29

Hello! I'm looking into storing logic as data in some dynamic store (db?) so that I can configure and select the logic being used at run time. I just want to survey the solution space for now so I don't want to impose any more constraints. If anyone has any ideas or suggestions please join the brainstorming 🙏 P.S If there's a better channel for this question, please point me there Thank you! 🙏 😊

walterl12:10:27

Sounds like app config, so EDN file.

Lidor Cohen12:10:43

But how do I store logic in edn? Is there a tool for serializing, let's say, functions, to edn and back?

Dane Filipczak13:10:30

a rules engine like odoyle will allow you to separate logic into a db-ish entity that is serializable, as well as dynamically modifiable. https://github.com/oakes/odoyle-rules

🙏 1
👍 1
Lidor Cohen13:10:48

Thank you very much! This is a great lead, do you have any experience/ examples I could follow?

Ben Sless14:10:57

You can also look at #C08TC9JCS . Clara has examples in its repo, odoyle is used as a rule engine for some example games

Lidor Cohen14:10:24

At odoyle is mentioned that since the rule engine uses macros you can't load them at run time, there's also a regular function option but it seems to revert to use functions instead of data. 😕

Lidor Cohen14:10:12

Clara seem to have the same issue (macros)

fvides16:10:38

In the case of Clara, the macros are optional. You can generate rules with data and load them in working space. An even more interesting idea is to store the working memory with rules preloaded and since is immutable (yay) yo can use it to fire rules and generate new facts. Disclaimer: all this is theoretical, I haven't really used, but I intend to.

Lidor Cohen16:10:44

Thanks I'll take a second look and see how it can be used

skylize16:10:52

Is there a way to declare a var for public consumption of a namespace without clobbering clojure core names internally?

;; The defining namespace use separate
;; names for * and star
(ns foo)
(defn star [x] (foobar x))
(star (* 1 2))

;; But in another namespace rely on
;;  ns qualifier to avoid clobbering.
(require [foo])
(foo/* bar)

rolt16:10:17

as in (ns foo (:refer-clojure :exclude [*]))

skylize16:10:00

I want to provide foo/* as a public var for use outside the namespace foo. But inside the namespace foo, I want to not clobber clojure/*, such that a bare * works as expected in all cases.

rolt17:10:01

i don't think that's possible you'll have to use the fully qualified version clojure.core/ inside foo, but you can also rename it so it's less verbose (:refer-clojure :rename { .*})

rolt17:10:45

(you could also use a second namespace)

(ns foo-impl)
(defn star [x y] (* x y 2)
(ns foo (:require [foo-impl] :refer-clojure :exclude [*]))
(def * foo-impl/star)

skylize22:10:26

:face_holding_back_tears:

Zachary Hanham20:10:15

Hey I have a quick question: What would be the best way to go about providing a reference to something that should be immutable. Simple example, given some data X, I want to make a separate map {:x X}, but I don't want to copy X into the map, I just want the map to reference X. I want it to be a reference for performance/memory reasons. The data will never be changed, so theres no need to copy. Should I just use (ref X)? Or an atom? But then they would be potentially mutable? Not sure of the convention here. Thank you

respatialized20:10:19

Isn’t this what def / ordinary vars accomplish?

ahungry20:10:09

I think you're over/early optimizing - I believe what you want is already the default (but an implementation detail), via clojure's persistent immutable data structures - but if you really wanted to ensure it was always the same place in memory, probably like you suggest, ref/atom, something you would deref to take the value of, but you'd always reference by place instead of value

Zachary Hanham20:10:09

Hm yeah it may be an over optimization, I will type more about the specific use

phronmophobic20:10:29

> I want to make a separate map {:x X}, but I don't want to copy X into the map making a separate map with X in it doesn't make a copy of X.

ahungry20:10:38

You could explore and create yourself a simple scenario to alleviate your concerns - make something like a string that is just filled with a bunch of vals until its 1G in size in your initial map - then make a derived map with conj - is your program now taking 2G of mem? Do it 10 more times - did your machine lock up due to lack of ram yet? 😄

Zachary Hanham20:10:38

@U7RJTCH6J O huh yeah I guess it won't really. I was thinking it needed to copy to guarantee immutibility, like if you mutate {:x X} then the original X wouldn't change, but because things are truly immutable anyways then it doesn't matter. Under the hood, (:x {:x X}) and X are just references to the underlying data of X.

👍 2
Zachary Hanham20:10:23

So i was just misunderstanding clojure's variables I think

Zachary Hanham20:10:46

And i can use {:x X} with no worries really

👍 2
clojure-spin 3
ahungry20:10:31

it's a pretty core thing that allows immutability in clojure to be nearly as fast as mutable stuff in other languages, because as you noted, copying would be that guarantee in other langs (trying to do full immutability in some non-clojure)

👍 1
Zachary Hanham20:10:24

yet another mind blowing moment of realization while learning about clojure lol. I'm coming from functional-style javascript - so copying was the norm there

phronmophobic20:10:50

Yea, I remember it taking a while to fully grok the immutable data structures when I was first learning clojure.

Abhi Saxena20:10:29

Hi there, I have a update db function, as follows:

(defn update-client-global-target [client_id arguments]
  (update table_name
          (set-fields {:target_enroll (:target_usa arguments)
                       :target_lpi (str-to-date (:target_date arguments))})
          (where {:client_id client_id})))
How can I dynamically set fields based on fields that are not nil and in a specific format.

pppaul20:10:10

are you sure your DB, or interface to it, supports such a thing?

Abhi Saxena20:10:50

Yes, DB (Postgres in my case), supports this, I mean I want to set the field only if it's available otherwise not update it

pppaul20:10:35

do you want to do something, where the context of the DB and DB interface is not important?

pppaul20:10:23

cus, how are we supposed to know what "set-fields" is? or "where"

Abhi Saxena20:10:51

these are the macros from Korma.core

pppaul20:10:01

is that important?

Abhi Saxena20:10:13

I think I would need a closure.spec for validating these fields

pppaul20:10:01

you never need clojure.spec, unless that's what you want to use. i don't see how it would help in this context unless the db interface also wants you to use that

pppaul20:10:23

right now, i'm not sure if you are having issues with clojure, or the DB interface

pppaul20:10:28

if you could extract your problem from the DB code, then it'll be a lot easier to solve

Abhi Saxena20:10:33

I am not having issues with either, I am very new to clojure and not sure how to set fields dynamically

Abhi Saxena20:10:28

I have to use the same update function, whether there are 2 fields to update or only one

pppaul20:10:30

by field, are you referring to fields in a clojure map, or the DB?

Abhi Saxena20:10:52

(set-fields {:target_enroll (:target_usa arguments)
                       :target_lpi (str-to-date (:target_date arguments))})

pppaul20:10:45

"How can I dynamically set fields based on fields that are not nil and in a specific format." in that sentence, you use field 2 times, does it mean the same thing both times, or do they mean different things?

Abhi Saxena20:10:23

sorry by set fields I mean set-fields (Macro)

pppaul20:10:10

ok, in korma the db functions pass around a query object, what does that look like?

pppaul20:10:13

from what i'm reading, it looks like a clojure map that is build up to describe what should be done to the db

pppaul20:10:41

how about you pretty print that object, and put it in here, and then we can talk about that object without talking about the DB at all

pppaul20:10:32

do the same for the "arguments" in the "update-client-global-target" fn

Abhi Saxena20:10:59

here is what arguments map looks like - {:targetenroll_ 680 :targetlpi_ "2020-08-21"}

pppaul20:10:28

{:type :update
                   :fields {}
                   :where []
                   :post-queries [first]}
the korma/update function makes something like this

pppaul20:10:26

from your {_:target_enroll_ 680 _:target_lpi_ "2020-08-21"} object, what do you want to do that is "dynamic" with respect to the data in it?

pppaul20:10:46

can you give some examples?

Abhi Saxena20:10:03

I want to only set the field that is not nil and if its a date its in proper format

Abhi Saxena20:10:13

IF I pass something like this - {_:target_enroll_ 680 _:target_lpi nil_} then update call should onl update target_enroll and be done with it

Abhi Saxena20:10:42

same behavior for {_:target_enroll_ 680}

pppaul20:10:52

the example arguments object doesn't work with the function you gave, it's missing 2 keys

pppaul21:10:06

:target_usa :target_date

pppaul21:10:22

(let [arguments {:target_enroll 680 :target_lpi "2020-08-21" :target_usa "whatever" :target_date #inst "2000"}
      {:keys [target_enroll target_id target_usa target_date]}]
  (cond-> query
    (and target_enroll target_usa) (set-fields {:target_enroll target_usa})
    (and target_id target_date) (set-fields {:target_lpi (str-to-date target_date)})
    ))

Abhi Saxena21:10:59

sorry my bad, it should be {_:target_usa_ 680 _:target_lpi nil_}

Abhi Saxena21:10:35

{_:target_enroll_ 680 _:target_date nil_}

pppaul21:10:37

you can do the same thing with if statements, i don't see clojure being a barrier in this case, other than if statements returning something (some languages don't do that)

Abhi Saxena21:10:11

I Tried When and it was returning nil, I will try with if

pppaul21:10:26

when returns nil

pppaul21:10:30

do does if

pppaul21:10:37

many functions in clojure return nil

pppaul21:10:21

when is a macro, you can expand it to see the code that it writes for you

Abhi Saxena21:10:28

so should I try with if or try the clojure-spec route

pppaul21:10:02

i think if you are having trouble with basic control flow, do not approach a validation library

Abhi Saxena21:10:32

okay, I will go with if control flow

pppaul21:10:35

any validation library is going to give you a few extra layers of stuff to debug, and they are usually very unfriendly for troubleshooting

Abhi Saxena21:10:58

thanks much Paul, it was a good discussion for me

pppaul21:10:22

i think i wrote expands into this

(let [G__93269 query
      G__93269 (if (and target_enroll target_usa)
                 (-> G__93269 (set-fields {:target_enroll target_usa}))
                 G__93269)]
  (if (and target_id target_date)
    (-> G__93269 (set-fields {:target_lpi (str-to-date target_date)}))
    G__93269))
only 2 if statements

pppaul21:10:14

cond and cond-> cond->> are usually a lot better to use than if or when because they are a bit more self documenting

pppaul21:10:49

whatever editor you are using, learn how to do macro expand, and also use the documentation lookup function. they are very useful

pppaul21:10:28

clojure functions/macros have some name issues and i find myself looking at the docs a lot, even after ~10 years of programming in clojure

pppaul21:10:14

https://clojuredocs.org/ this is a useful resource as well

Abhi Saxena21:10:27

I just started Clojure last month

pppaul21:10:42

got a long road ahead

pppaul21:10:14

the examples on clojuredocs are really good. https://practical.li/clojure/ is a more tutorial focused site.