Fork me on GitHub
#beginners
<
2020-03-25
>
Brandon Olivier00:03:45

It was a Jackson issue (I think)

Brandon Olivier00:03:10

I installed version 2.10.3 of Jackson and now it's functioning correctly

Brandon Olivier00:03:22

I'm not sure I quite understand what went wrong

Brandon Olivier00:03:31

Why didn't leiningen pick up that I needed it?

noisesmith00:03:36

tl:dr jackson is terrible

noisesmith00:03:04

it uses your recursive deps, the version of jackson someting else used was not compatible with the versin amazonica needed

noisesmith00:03:19

by adding a version explicitly, you forced it to use the version amazonica could use

noisesmith00:03:42

luckily you didn't also break some other lib in the process (that can happen sometimes)

Brandon Olivier00:03:03

Does that mean that somewhere a project had an implicit dependency on jackson, or that lein chose a different version to use?

noisesmith00:03:50

that's why I suggested running lein deps :tree - that exposes the recursive dep path (the short version is lein goes depth first and registers versions in order of your dep vector)

noisesmith00:03:33

it also warns you if different libs wanted different versions of the same lib, usually this is innocuous because libs maintain some compatibility, but jackson really doesn't do that

Brandon Olivier00:03:05

interesting. Thanks for the help 🙂

seancorfield00:03:26

@noisesmith I'll agree that Jackson is terrible (from a version conflict p.o.v.) but it's so widely used it's hard to get away from. Are there non-Jackson JSON options that behave better?

seancorfield00:03:31

(asking for a friend, honest!)

ghadi00:03:32

clojure.data.json

12
ghadi00:03:17

it's not perfect, but will never get you into dependency hell

noisesmith00:03:25

I recall that once upon a time, in the distant early years, many of us changed from data.json to cheshire because data.json had a release that renamed a few functions. But it was long enough ago I could be remembering wrong.

seancorfield01:03:18

I just went and looked at metosin/jsonista hoping it wasn't based on Jackson. It is.

seancorfield01:03:11

(we switched from data.json to Cheshire because the former failed to correctly format some things we had -- but Cheshire worked flawlessly... of course, I should have reported a bug against data.json which I don't think I did, because I needed a working JSON converter "immediately" to resolve a problem at work)

jumpnbrownweasel02:03:09

Cheshire depends on Jackson also. I guess it uses it in a different more performant way, but that wouldn't prevent dependency problems.

seancorfield02:03:06

Right, and lots of things use Cheshire under the hood so it's very hard to avoid Jackson 😞

seancorfield02:03:24

clj-http, for example, if you use any of its built-in JSON stuff.

jumpnbrownweasel02:03:37

Yes, I'm using etaoin and it also uses Cheshire. At work we've also had lots of problems with Jackson breakage.

didibus06:03:20

Ya, I was surprised that no Clojure wrapper for Gson existed. Maybe the Gson model doesn't let you build a Clojure parser, and is too specific to Java. But Gson in Java land is much more dependency friendly.

noisesmith15:03:59

oooh - that might be a worthwhile evening hack :D

noisesmith16:03:19

looking at the API, seems very sensible so far (should be easy to use directly...)

noisesmith23:03:20

regarding the Gson discussion the other day: as far as I'm concerned this just works and doesn't require a wrapper lib:

user=> (def gson (Gson.))
#'user/gson
user=> (.toJson gson {:a 0 :b 1 :c [1 2 3] :d #{4}})
"{\":a\":0,\":b\":1,\":c\":[1,2,3],\":d\":[4]}"
custom type registry that isn't global:
user=> (def gson-custom (com.google.gson.GsonBuilder.))
#'user/gson-custom
user=> (.registerTypeAdapter gson-custom clojure.lang.Atom (reify com.google.gson.JsonSerializer (serialize [this src _type context] (com.google.gson.JsonPrimitive. @src))))
#object[com.google.gson.GsonBuilder 0x145414f "com.google.gson.GsonBuilder@145414f"]
user=> (def gson-2 (.create gson-custom))
#'user/gson-2
user=> (.toJson gson (atom 12))
"{\"state\":{\"value\":12},\"watches\":{}}"
user=> (.toJson gson-2 (atom 12))
"12"

didibus09:03:30

What about from Json ?

noisesmith17:03:07

I didn't actually experiment with that half yet, but it definitely looked straightforward

didibus06:03:38

Another possibly "cool?" option. I wonder if Cheshire could bundle the Jackson libs in a shaded state.

didibus06:03:38

It's like taking the appropriate Jackson libs, renaming the package to something unique, have Cheshire import those renamed packages, and bundle them with Cheshire.

didibus06:03:52

Neither know how easy, or if licenses would allow

Chester ONeill18:03:21

Can cider just load functions from a local file? I've set up emacs and I can start cider-jack-in but it gives me an error that im not using a project. I just want to have the repl load in my functions and be able to edit them and run. any ideas?

practicalli-johnny10:03:04

cider-scratch can be used if you dont have a project. However, as you are creating a file, then it will simplify things greatly if you create a Clojure project. Leiningen is a build tool that has been around for a long time, so lots of people use it to create a project. Clojure CLI and tools deps is the approach many people are taking for new projects.

practicalli-johnny10:03:09

As people have mentioned, the BraveClojure Emacs setup setup is about a dozen major releases out of date and significant changes have been made to CIDER.

practicalli-johnny10:03:05

For Emacs & Clojure, Prelude, Doom and Spacemacs seem to be the most popular community configurations. Prelude provides the essentials, Doom provides a bit more opinionated features and Spacemacs is a full batteries included experience. I use Spacemacs and am writing this guide on Clojure development with Spacemacs/Emacs https://practicalli.github.io/spacemacs/

noisesmith18:03:10

@me1159 it wants to start an nrepl from a specific project, you can set emacs' current directory before running jack-in, or start emacs from that dir, or use cider-connect and point it to a port for a repl you already started

Chester ONeill18:03:25

You mightve mentioned the wrong person 😃 ha

Chester ONeill18:03:13

how would I set emacs current dir after its started?

Chester ONeill18:03:20

I guess I can google that

noisesmith18:03:48

I think default-directory is actually the thing you want though, q's assumptions notwithstanding

yuhan18:03:58

@me1159 sesman-link-* commands are also useful if you already have a repl and just want to connect it to a file in a different dir

Chester ONeill18:03:34

so i tried using the cider-connect-clj to connect it to the same instance that cider-jack-in-clj started but im not really clear how to fun the function I have in my file

Chester ONeill18:03:34

maybe the way I want to do this is just wrong. I basically just want to have a scratch pad of functions I can just rapidly iterate on and run in a repl without needing lein and stuff.

Chester ONeill18:03:56

Ive just been copying things out of a file and into the repl and running them but thats tedious

noisesmith18:03:23

if your file didn't define a namespace form, the function you defined should end up in the repl's default ns, or user if there is none

noisesmith18:03:00

you can use (apropos "function-name") also

noisesmith18:03:28

user=> (apropos "merge")
(clojure.core/merge clojure.core/merge-with clojure.spec.alpha/merge clojure.spec.alpha/merge-spec-impl)

noisesmith18:03:19

@me1159 clojure namespaces everything, so even for "scratch" code, it's less friction to just put (ns scratch) at the top of the file

noisesmith18:03:40

or whatever you want to call your default / dummy / wip ns

Chester ONeill18:03:22

so my function is just

(defn foo
  "test"
  []
  (println "foo)) 

Chester ONeill18:03:27

ill try namespacing it

noisesmith18:03:46

if you do (apropos "foo") it will show you where it ended up

Chester ONeill19:03:57

so I get this

user> (apropos "foo")
()

dpsutton19:03:16

i made a directory in /tmp/blah, made a file called foo.clj, cider-jack-in and everything works for me

dpsutton19:03:31

can you list the steps you are doing?

Chester ONeill19:03:53

does it also matter I loaded up the clojure for the brave and true defaults for emacs. Im also using the gui emacs not the emacs that comes preloaded on mac

dpsutton19:03:45

the above is gui emacs. can you link me to the clojure for the brave and true setup you're using?

dpsutton19:03:37

that's an old CIDER. can i recommend using a well done emacs startup called prelude? It's by the maintainer of CIDER and is tastefully done: https://github.com/bbatsov/prelude

Chester ONeill19:03:39

im totally cool with that

dpsutton19:03:05

awesome. let meknow if you run into any problems

Chester ONeill19:03:28

thanks. yea ill try that and see if it that works. I knew there was no way it could be this difficult

Chester ONeill19:03:13

btw just curious where did you find the cider version that project was using? I know he takes PR's to keep that stuff updated

Chester ONeill19:03:42

don't kno why but I installed prelude and theres no cider options available when M-x

Chester ONeill19:03:19

sry to spam you. I got cider installed. stil the same issue. cider just doens't bring in the file. im going to try something else.

dpsutton20:03:46

how are you trying to "bring in the file"?

Chester ONeill20:03:26

im just simply starting cider-jack-in from the buffer where the file is

Chester ONeill20:03:04

I literally setup the same structure as you

Chester ONeill20:03:24

foo.clj , ns foo, defn foo that adds a number

dpsutton20:03:48

ok. so you need to eval the file

dpsutton20:03:59

clojure works on what has been loaded. it isn't loaded without some action

Chester ONeill20:03:22

ok. is that (load-file) then?

dpsutton20:03:36

cider-load-file

Chester ONeill20:03:51

M-x that in the cider buffer?

Chester ONeill20:03:21

sorry im like drinking from the clojure/emacs/cider firehouse right now trying to set up an envrionment

dpsutton20:03:05

but perhaps reading some documentation wuold be helpful?

Chester ONeill20:03:34

god damnit =( PEBKAC

Chester ONeill20:03:47

thank you for your help. going too fast here.

Chester ONeill20:03:13

This is making more sense now. The whole repl idea and live coding is insane but this is neat. thanks again so much

noisesmith19:03:38

that means the current clojure process didn't get a foo defined anywhere

Santiago19:03:12

on my API i’m checking if some parameters are present

(or (empty? customer-uuid) (empty? debtor-uuid))
(error-response "Customer or Debtor UUID is missing!")
the error-response is a bit meh.. how would you get the parameter that is missing (or both), and add that to the message so that is reads like "customer_uuid` is missing, but expected UUID"`

seancorfield19:03:09

I'd probably use cond and have each error condition tested separately (so they each can have a separate error response), then :else (happy-path) at the end of the checks.

Santiago19:03:29

@U04V70XH6 so you test 3 conditions? 1 - neither are present 2 - customer is not present 3 - debtor is not present

seancorfield20:03:17

(cond (empty? customer-uuid) (error-response "`customer-uuid` is missing")
      (empty? debtor-uuid) (error-response "`debtor-uuid` is missing")
      :else (happy-path))
depending on exactly what error messages I wanted from which conditions.

Brandon Olivier19:03:42

Is there an idiomatic way to write to files in directories that may not exist?

hiredman19:03:13

(doto f (-> .getParentFile .mkdirs))

👍 4
jsn20:03:14

why doto though? (-> f .getParentFile .mkdirs) seems to work just fine

jsn20:03:18

you need either doto or -> , not both

Alex Miller (Clojure team)20:03:55

if you want to avoid interop

hiredman20:03:49

doto on the assumption you have an expression that contains f

hiredman20:03:06

you can drop in the doto expression in place of f, without the doto you cannot

Gulli21:03:17

Wondering about best practice when it comes to protocols. In Java I'd declare an Interface, let's say MessagingService, in its own self-contained file. I'd then create two other files, RabbitMQService and DummyService, and in each implement the Interface. In Clojure, do most declare these things all in the same file?

noisesmith21:03:26

it's really up to you - one place for protocols vs. various implementations, vs all in one place - it's more a question of your app's needs, but it is usually simpler to put the implementations with the protocol if they are created to be mutual alternatives rather than something open ended for user extension

👍 4
val_waeselynck00:03:49

On contrary, I'd recommend putting the protocol in a separate namespace, not only for code organization but to avoid issues with code reloading in the REPL

noisesmith00:03:37

@U06GS6P1N it's the opposite - if every extension of the protocol is in the same ns as the protocol, you don't end up with the protocol and extensions going out of sync

noisesmith00:03:54

the protocol and instances implementing it can go out of sync just as easily with both approaches

noisesmith00:03:56

actually, I guess if you never reload the protocol ns, that can help with values going out of sync with the protocols they implement

OrdoFlammae21:03:04

I have a 2-d vector with values in each position, and I'd like to set values inside a certain range of x and y coordinates. Is there an idiomatic way to do this functionally? I know how I'd do it imperatively, but I can't figure out a simple way to do this functionally.

OrdoFlammae21:03:18

Something like a cumulative for loop?

noisesmith21:03:01

clojure for is a list comprehension, not a loop - it's useful for generating lists but not transforming inputs per se

noisesmith21:03:57

@ordoflammae I would use a reduce with (range i j) as a collection input providing all the indexes

noisesmith21:03:28

something like (reduce (fn [v idx] (update v idx f)) my-vec (range i j))

noisesmith21:03:45

where if takes current value at index idx and returns the replacement

noisesmith21:03:09

(you can change the update call if f should get different input, of course)

OrdoFlammae21:03:23

Ah. Thanks @noisesmith for the help.

OrdoFlammae21:03:29

That makes so much more sense.

noisesmith21:03:19

(ins)user=> (def i 2)
#'user/i
(ins)user=> (def j 5)
#'user/j
(ins)user=> (reduce (fn [v idx] (update v idx (fn [old] (+ old idx)))) [1 1 1 1 1 1 1 1] (range i j))
[1 1 3 4 5 1 1 1]

noisesmith21:03:48

for indexes i through j-1 (2,4) it updates value at index by summing with its index

noisesmith23:03:20

regarding the Gson discussion the other day: as far as I'm concerned this just works and doesn't require a wrapper lib:

user=> (def gson (Gson.))
#'user/gson
user=> (.toJson gson {:a 0 :b 1 :c [1 2 3] :d #{4}})
"{\":a\":0,\":b\":1,\":c\":[1,2,3],\":d\":[4]}"
custom type registry that isn't global:
user=> (def gson-custom (com.google.gson.GsonBuilder.))
#'user/gson-custom
user=> (.registerTypeAdapter gson-custom clojure.lang.Atom (reify com.google.gson.JsonSerializer (serialize [this src _type context] (com.google.gson.JsonPrimitive. @src))))
#object[com.google.gson.GsonBuilder 0x145414f "com.google.gson.GsonBuilder@145414f"]
user=> (def gson-2 (.create gson-custom))
#'user/gson-2
user=> (.toJson gson (atom 12))
"{\"state\":{\"value\":12},\"watches\":{}}"
user=> (.toJson gson-2 (atom 12))
"12"