Fork me on GitHub
#beginners
<
2019-07-19
>
John01:07:36

Another doubt! I'm trying to change an atom that represents my state, but I need to make sure that the new state is "valid". So far I'm doing it with a simple check before calling swap!, but this obviously is vulnerable to race conditions. I know the alternative is to attach a validator for this atom, the problem is that if the validators fails, the program will throw an exception, and I actually want to leave the state the way it was, and return a response with 403 status code (it's a REST API). Is there something like a try-catch thing that I could use? or what would be the clojure-way to do this right?

andy.fingerhut01:07:30

one possibility that comes to mind -- you could perform the validation check inside the function that you pass to swap!, with a try / catch inside in case any exceptions occur, and make that function return the current value if anything goes wrong.

andy.fingerhut01:07:45

you would need some way to record the fact that a problem occurred while that function was called, which I don't have any great suggestions for

andy.fingerhut01:07:06

perhaps error conditions could be stored in a second atom, one dedicated solely for the purpose of recording errors that occur in that updater function.

John01:07:30

Great, that's an idea, would it be fine in this case to violate the purity of the function passed to swap! in this case? since I will be modifying another atom. Hmm... now that I think more about it, it might be difficult to know if the error came from the current thread's call to swap! or from some other.

Crispin01:07:30

You can't synchronise across atoms. In these cases, you either want one atom (so both your processing/processed state, and the error condition is inside the same atom), or you use refs, which can be synchronised across refs.

Crispin01:07:50

its a bad idea to modify one atom inside another atoms function. Remember those functions can be run multiple times by the STM for a single CAS operation.

andy.fingerhut01:07:23

If err-atom is an atom that only ever has calls made to update it, from the update function of data-atom, and nowhere else, then I think you can guarantee their updates are synchronized. Not for general sets of atoms, but in that special case, perhaps?

noisesmith01:07:19

@maxiredigonda there's a built in function: clojure.core/set-validator!

noisesmith01:07:33

user=> (doc set-validator!
)
-------------------------
clojure.core/set-validator!
([iref validator-fn])
  Sets the validator-fn for a var/ref/agent/atom. validator-fn must be nil or a
  side-effect-free fn of one argument, which will be passed the intended
  new state on any state change. If the new state is unacceptable, the
  validator-fn should return false or throw an exception. If the current state (root
  value if var) is not acceptable to the new validator, an exception
  will be thrown and the validator will not be changed.
nil

John02:07:06

@noisesmith yes! I knew about this function but sadly it doesn't solve my problem directly, since it throws an exception instead of politely inform me that the swap has failed so I can return that to the user in the form of a 403 http response.

noisesmith02:07:41

that's just a question of catching an exception and returning 403 from the catch

John02:07:34

Combining @crispin and @andy.fingerhut ideas I might be able to make it work now, there is just one small thing that I'm left to figure out now, but I think I'll ask about it later if it becomes a problem, thank you all guys!

Crispin02:07:49

also remember that swap! returns the new contents of the atom. Might be helpful.

noisesmith02:07:51

ins)user=> (def a (atom 0))
#'user/a
(ins)user=> (set-validator! a (fn [x] (even? x)))
nil
(ins)user=> (reset! a 2)
2
(cmd)user=> (reset! a 3)
Execution error (IllegalStateException) at user/eval146 (REPL:1).
Invalid reference state
(ins)user=> @a
2
(ins)user=> (try (reset! a 3) (catch IllegalStateException _ {:status 403}))
{:status 403}

👍 8
noisesmith02:07:04

it's much more reliable than trying to join up two atoms

Crispin02:07:18

nice. the exception is thrown with the reset!

noisesmith02:07:34

right, because the validator returned false

noisesmith02:07:11

lol I should have just done (set-validator! a even?)

John02:07:37

Oh, that's great, I will try it out, thanks a lot!

FiVo09:07:16

What is the standard way of parameterizing clojurscript builds? I am using Leiningen and I would like for example change some API endpoint or

FiVo09:07:42

javascript path based on the build.

borkdude10:07:03

if any beginner feels like extending this CLI utility with pretty printing: https://github.com/borkdude/jet/issues/4 it might be a fun exercise

4
Anik Chowhdury14:07:55

Hello everyone, i am new to clojure. I have question regarding CSV. How to append rows in csv in clojure? i have followed https://github.com/clojure/data.csv

Alex Miller (Clojure team)14:07:41

append, like add to an existing file?

Alex Miller (Clojure team)14:07:33

I think if you just open your writer on an existing file in append mode, it will do that

Alex Miller (Clojure team)14:07:49

(with-open [writer (io/writer "existing-file.csv" :append true)]
  (csv/write-csv writer
                 [["abc" "def"]
                  ["ghi" "jkl"]]))

👍 4
Sam Ferrell15:07:22

any resources/advice for idiomatic argument positions?

dorab15:07:46

^^ This post from Rich Hickey might be helpful https://groups.google.com/forum/#!topic/clojure/iyyNyWs53dc

👍 4
donaldball16:07:24

I generally order from least to most likely to change

Anik Chowhdury16:07:11

@alexmiller actually i don't want to append data in the end. Rather i want to append data in each row.

Alex Miller (Clojure team)16:07:42

in that case, you'll probably need to read the csv, modify the data, then write the csv. data.csv doesn't have any support for modifying an existing csv file like that.

👍 4
Freddy16:07:27

does anyone know what I could use instead of (s/*) to make this conform pass ?

(s/def ::record
  (s/cat :f1 int?
         :f2 string?
         :f3 string?))

(s/conform (s/* ::record) [[1 "a" "b"] [2 "c" "d"]])

Freddy16:07:07

( s is clojure.spec.alpha btw 😄 )

Alex Miller (Clojure team)16:07:13

use (s/conform (s/* (s/spec ::record)) [[1 "a" "b"] [2 "c" "d"]])

Alex Miller (Clojure team)16:07:53

regex ops merge together to describe the same collection, so your spec above is looking for [1 "a" "b" 2 "c" "d"]

Alex Miller (Clojure team)16:07:11

you can use s/spec to introduce a spec boundary and describe a nested collection

Alex Miller (Clojure team)16:07:45

an alternate solution (and possibly better here) is to use (s/coll-of ::record)

Freddy16:07:06

That's exactly the answer I needed 🙂 many thanks

Alex Miller (Clojure team)16:07:51

and another is to use s/tuple instead of s/cat - (s/def ::record (s/tuple int? string? string?)) - that will conform to a vector instead of a map. one may be better for you.

Freddy16:07:01

The coll-of isn't an option for me unfortunately because the actual vector of records appears in a s/cat

Freddy16:07:34

The tuple is what I was using until now but I wanted to add field names in the conform

Alex Miller (Clojure team)16:07:47

gotcha, then given all that I'd use s/cat and the s/spec around s/*

Freddy16:07:05

Yes, perfect 🙂 thanks again

Alex Miller (Clojure team)16:07:20

fyi, there is #clojure-spec which is better for these kinds of questions in the future

Freddy16:07:29

Oh, ok sorry

bringe17:07:58

Hello. I'm using lein and trying to set my repl to always pprint results by default. I tried using

(clojure.main/repl :print pprint)
and while this works, it somewhat messes up my repl. I can't exit after that. Ctrl+D prints ^D and (exit) gives "unable to resolve symbol."

bringe17:07:40

Any suggestions for making this work? Is it a windows powershell thing that's causing the issue?

noisesmith17:07:30

try (System/exit 0)

noisesmith17:07:00

I don't think (exit) ever means anything

bringe17:07:02

Well that worked, threw a socketexception but worked

bringe17:07:04

Usually running (exit) or (quit) closes the repl

bringe17:07:11

nREPL server started on port 54179 on host 127.0.0.1 - 
REPL-y 0.4.3, nREPL 0.5.3
Clojure 1.9.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_211-b12
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (exit)
Bye for now!

noisesmith17:07:22

that must be a reply thing or something, it's not clojure

noisesmith17:07:46

which is why running clojure.main/repl means it wouldn't work, I assume

bringe17:07:07

Ah, maybe that's a leiningen thing?

noisesmith17:07:24

it's REPL-y, the lib lein uses for repl client

bringe17:07:32

oh makes sense

noisesmith17:07:34

(as shown in the splash printout)

noisesmith17:07:15

it's amusing that none of the three methods they suggest for exiting are actually a thing that clojure itself identifies as a reason to exit

bringe17:07:22

Interesting. I need to venture outside of lein soon. It seems the tooling outside of it is improving? And I don't mean boot, but clojure main itself? I may not know what I'm talking about 😋

noisesmith17:07:01

clojure has a new dep resolution tool, and people are creating build tools to go with it, since dep resolution is the hard part

noisesmith17:07:47

it's good to know which tool does which layer of functionality though (even if you use lein, you should only be using it for dev and build for example, it shouldn't run on your server, so it's important to know which things are it and which are clojure itself)

👍 4
noisesmith17:07:27

(control-d signals eof, which might mean exit but... as we saw it's possible to create a situation where it doesn't mean that)

Janne Sauvala17:07:37

Hey 👋:skin-tone-2: I’m creating a server that provides an API. Would you recommend using Ring or Pedestal? I’m planning to add GraphQl support with Lacinia later on

noisesmith17:07:46

@janne.sauvala I think if you plan to provide lacinia graphql, it's easier ot start that way

Janne Sauvala18:07:24

I think Lacinia still needs other libraries to create the HTTP endpoints. Lacinia has a supporting library for Pedestal, so maybe I use those two

noisesmith18:07:38

right - you'd still need to set up ring or pedestal, but the way you define handlers is different with lacinia

csd19:07:34

i'm using lein repl and am trying to in-ns into a test namespace, but the repl isn't seeing it (e.g. using tab completion) for some reason. the test namespace lives under test/, and my project.clj does include :test-paths ["test"]. what do I need to do to get the repl to be aware of this ns?

noisesmith19:07:16

you need to require the namespace - for tests, they aren't loaded up by default on startup

csd19:07:45

thanks. is how this works documented anywhere (or do you know which code handles this?). e.g., how does the repl populate this list of available ns-es?

noisesmith19:07:23

the repl itself only loads user, leiningen will also load your init-ns or main-ns if defined

csd19:07:03

so i guess the precedence order for where lein repl dumps you is 1) [:repl-options :init-ns] 2) :main 3) user

noisesmith19:07:53

yeah - and any other namespaces are loaded if they are transitively pulled in by init-ns, main, or user (via user.clj)

noisesmith19:07:37

user.clj stuff is pulled in even if you start in another ns - loaded but not neccessarily the the ns you are in

csd19:07:09

is user.clj an actual file and which the user can override, or is it just a convention?

csd20:07:32

sorry if this is documented anywhere, i didn't see reference to it on http://clojure.org, aside from the recent bug fix

noisesmith20:07:31

yeah - many suggest not using it, but if it is found, the first user.clj found is used

csd20:07:13

is it basically similar to how project.clj's :injections behaves?

noisesmith20:07:58

pretty much, except it's a whole file and not just one clause in a file, yeah

csd21:07:38

ok. thanks!

noisesmith19:07:28

also, watch out for calling in-ns on a ns that hasn't been created yet (eg. via require) - it can land you in a broken namespace (you can get back out via (clojure.core/refer-clojure) then another in-ns call, or just (clojure.core/in-ns 'working-ns))

Sam Ferrell19:07:57

Any resources on using #?(:cljs ...)?

Sam Ferrell19:07:22

or terms I should be searching for?

noisesmith19:07:33

the keyword to look for is cljc - it only works in cljc files