Fork me on GitHub
#beginners
<
2021-05-14
>
kj11:05:17

I've got a couple of related functions in a file that I'd like to be able to call directly from the command line. What's the recommended way of doing that? Would it just be to parse some command line options and call the appropriate function from -main? If so, are there any Clojure libraries similar to https://github.com/google/python-fire or https://click.palletsprojects.com/en/8.0.x/ in Python, for quickly putting together simple CLIs?

pithyless19:05:50

@kiran.joshi just in case you're writing command line scripts and you're not aware of https://babashka.org/, I suggest you check it out. Otherwise, sorry for the spam and carry on. :)

👍 3
Michaël Salihi12:05:04

@kiran.joshi If you use Clojure CLI, the -X exec option should be what your looking for. For your second question AFAIK: • https://github.com/clojure/tools.clihttps://github.com/l3nz/cli-matic

👍 3
Rob Haisfield12:05:13

How do Datalog and Specter queries compare? I don’t fully understand why I would use one or the other.

👀 3
sova-soars-the-sora15:05:52

Datalog is a query language similar to GraphQL and Specter is used to conveniently run up and down nested data structures using keywords specific to specter

Rob Haisfield17:05:31

I guess my question then would be: when is it more useful to run a datalog query and when should I run a specter query?

Rob Haisfield17:05:41

I guess Specter might just be more precise? Like I know exactly what I’m looking for and where it is, since I need to manually specify all elements of the tree. But datalog helps me find things without looking at the tree?

Rob Haisfield17:05:49

In other words, I get to skip a few steps I would have needed to take in Specter

Lukas12:05:44

Hello everyone, I'm trying to convert a list of maps into eav-triplets. There is one catch: some values are in a vector, and they need to get unwrapped. This is my current solution:

(defn map->eav
  ([data]
   (let [ids (atom 0)]
     (map->eav data (fn [] (swap! ids inc)))))
  ([data id-gen]
   (->> data
        (mapcat
         (fn [e]
           (let [id (id-gen)]
             (for [[k v] e]
               [id k v]))))
        (reduce
         (fn [acc [e a v]]
           (if (vector? v)
             (into acc (for [n v] [e a n]))
             (conj acc [e a v])))
         []))))

(map->eav [{:list ["a" "b" "c"]}]) 
;; => [[9984 :list "a"] [9984 :list "b"] [9984 :list "c"]] 
This works, but I wonder if there isn't a more concise solution to the problem?

Alex Miller (Clojure team)12:05:33

Well don’t call RT.nextID - that’s implementation stuff you shouldn’t use directly

Lukas12:05:19

😋 okay ty will do

Ivan Koz12:05:42

@lucas.sousa

(def id-gen
  (let [a (AtomicLong. 1)]
    (fn [] (.getAndIncrement a))))
=> #'user/id-gen
(id-gen)
=> 1
(id-gen)
=> 2
or use UUID

👍 3
Franco Gasperino17:05:47

good morning. When using java interop, im having difficulty invoking the correct overloaded function based on parameter type.

(. java.nio.file.Paths (get "/tmp"))
=> ; Execution error (ClassCastException) at eventflow.ingress.file/eval11660 (form-init15515817518546427981.clj:62).
; class java.lang.String cannot be cast to class java.net.URI (java.lang.String and java.net.URI are in module java.base of loader 'bootstrap')

(class "/tmp")
=> java.lang.String

Franco Gasperino17:05:38

it sure reads as if it's invoking the static method accepting a URI, not a string

dpsutton17:05:39

(java.nio.file.Paths/get "/tmp" (make-array String 0)) should work for you. There is no signature that is (String). it's (String, ...String)

Franco Gasperino17:05:31

thanks, will give that a try

Adrian Sci17:05:31

Which clojure function could I use to run a function on each element in a collection? I'm working in core.logic and I'm trying to check a collection of things being in another collection. For example, checking if each individual element in the collection [:one :two] is in [:one :two :three] The current line looks something like this, however everyg won't work because in isn't a collection of lvars (I think), it's something like [:one :two] (everyg #(membero % state) in))

hiredman17:05:36

you should be able to just tell what in is

hiredman17:05:56

it can't be an lvar

hiredman17:05:05

even a ground lvar

hiredman17:05:49

what you need is clp(set) which doesn't exist for core.logic

hiredman17:05:09

you can maybe try something like:

(defn for-all [goal lst]
  (l/conde
   [(l/== lst ())]
   [(l/fresh [fst rst]
      (l/conso fst rst lst)
      (goal fst)
      (for-all goal rst))]))

(for-all #(membero % state) in)

Stuart17:05:50

What would be the idiomatic way to do the equivalent of this in clojure

private Either<Error, Output> Parse(string input) { ; either return an Output or an Error } 

Parse(someInput).Match(left: ProcessError,
                       right: ProcessOutput)
Return a map, something like
{:errors []
 :output []}
then check if errors is empty or nil? And if its not errors trumps output and deal with them, else deal with the output ?

Stuart18:05:44

ANy libraries that support stuff like

Foo().Then(Bar).Then(Quax).Match(left: ProcessErrors, right: ProcessFinalResult)
That will fail quick into ProcessErrors if any of Foo, Bar or Quax return the equivalent to a Left Either

Stuart18:05:16

Or is this just silly to do in clojure and there are better ways?

Stuart18:05:48

I thought maybe (cond->) could help me here?

hiredman18:05:55

I do it lots in data processing pipelines, but it is such a small thing to implement I tend to just write it as needed

Alex Miller (Clojure team)18:05:58

there are people that do that (and there are some libraries in this vein)

Alex Miller (Clojure team)18:05:23

Clojure leans on exceptions for error reporting and that's what I consider idiomatic

Stuart18:05:24

oh, failjure looks really neat, thanks

Adrian Sci18:05:10

For more context, I was trying to extend a story generator from a core.logic tutorial I found to accept multiple inputs as well as multiple outputs per action, the entire function looks like this ;;function to find an action that can be used, consume the elements in the state, and produce new story state (defn actiono [state new-state action]     `(fresh [in out temp]`     `;;one branch for multi input, the other for a single input.`      `(conda`         `[(all`         `(== (coll? in) true)`         `(everyg #(membero % state) in))`          `(ploto in out)`         `(everyg #(rembero % state temp) in)]`          `[(all`         `(membero in state))`         `(ploto in out)`         `(rembero in state temp)]`     `)`      `(appendo out temp new-state) ;;add the elements to the new state`     `(== action [in out])))`

Adrian Sci18:05:45

I'm quite new to this so it'll take me some time to go through you little code block hiredman so I can know what it does, but thank you.

hiredman18:05:54

in is a logic var there, which you cannot pass as the collection to everyg

hiredman18:05:15

(== (coll? in) true) will always fail too

hiredman18:05:38

because in is not a collection it is lvar

hiredman18:05:30

the way core.logic is written as a dsl (and minikaren which it is based on) lets you freely mix clojure and core.logic code, but they are distinct languages, clojure is kind of the meta language for core.logic. So you can't really throw clojure code like coll? into core.logic and have it work, but you can use clojure code to generate core.logic

Adrian Sci18:05:30

Alright I think that makes sense because in is created as an lvar. I got confused because the input that I'm trying to extend it for is collection

hiredman18:05:12

everyg is an example of this, it is actually kind of regular clojure code, it can't handle lvars, but it generates a core.logic goal

Adrian Sci18:05:53

Yeah I will be careful about when I use clojure functions while writing core.logic. I'll try to rethink that block and see what I can come up with

Todd20:05:34

Does the Clojure community value unit tests? ClojureScript?

Michael Stokley21:05:27

anecdotally, there certainly are clojure devs that downplay the importance or usefulness of unit tests. i think there's some merit to the idea that the repl gets you much of what a unit test would get you (at least at dev time). i think this gets complicated by the fact that as a dynamic language, clojure code bases stand to benefit /more/ from unit tests

seancorfield20:05:03

Based on our code base at work, I’d say we value unit tests:

Clojure build/config 20 files 233 total loc,
    45 deps.edn files, 774 loc.
Clojure source 355 files 89158 total loc,
    3551 fns, 925 of which are private,
    569 vars, 30 macros, 90 atoms,
    85 agents, 24 protocols, 67 records,
    857 specs, 33 function specs.
Clojure tests 378 files 23747 total loc,
    4 specs, 1 function specs.

👍 2
Robert Mitchell20:05:44

What tool did you use to generate this report?

seancorfield20:05:36

It’s a shell script that does a bunch of find/`grep`/`wc` commands.

👍 3
seancorfield20:05:00

I tend to develop tests initially via REPL interactions, starting with a Rich Comment Form (RCF — (comment ..)) as I explore a problem space and evolve a solution and the code in the comment gets refactored out into source functions and tests for those functions.

seancorfield20:05:43

(and, to be clear, I do not type into a REPL: I always edit code in a file and eval expressions into the REPL running in the background)

Todd11:05:03

Iike that method…I do something similar where I start a test file and put the function right above the test and build the function as I write tests with the REPL going. very similar to me TDDish flow in other languages

Todd11:05:57

I figured the core team values them but I know lots of the devs that use Clojure in the wild seem to be more startupy, full stack apps. Closer the Ruby/Rails startup stuff where I’ve seen fewer tests and more write lots a code, deploy and fix issues found on the fly. Not being necessarily critical of this method as I’m sure in some environments getting slightly buggy code out the door that you fix quickly might be more valuable than spending more time writing tests and exercising a more extensive software engineering disciplined approach.

Todd11:05:39

I have more interest in working on larger systems that have to work and perform well. Deploying a bug is bad. Deploying slow code is bad. I probably end up writing more test code than actual code. But also realize tests are code that need maintanence so they should be well written. But having some extra tests you don’t need is much easier to remedy (delete them) than missing tests that you DO need.

Alex Miller (Clojure team)21:05:45

I value useful tests, which come in many forms

👍 7
jimmysit022:05:30

How does the compiler keep atoms from being corrupted, knowing that at the beginning it could change the order of execution of things and now not exactly?

dpsutton22:05:51

what do you mean?

jimmysit022:05:37

Well atoms are mutable, how an atom does not get corrupted, since these are mutable, what process should the compiler follow

jimmysit022:05:51

I'm not sure if I'm clear, I may be having issues understand atoms

jimmysit022:05:30

I haven't, I'll give it a look. Thanks

seancorfield22:05:56

Atoms are operated on via swap! (mostly) and reset! (occasionally), and those functions ensure that the semantics are retained by design.

Ivan Koz06:05:46

@jimmyssj3 Atom is implemented in such a way that JVM ensures that any successful write will be seen by any subsequent reads in other threads. Lock-free update mechanics are possible because of CAS, compare and swap. 1. Thread reads "current value" of the atom as a "last known" 2. Runs a function to produce a "new value" 3. Tries to swap "current value" by the "new value" 4. If "current value" equals to "last known" value, "new value" is written, else, another thread was faster to update the value and current thread should retry the whole process. (Hence no side-effects requirement for a "new value" function)

Ivan Koz06:05:43

Here is an example of 100 threads doing 10k increments each to the integer stored in the atom.

(defn test-atom [nthreads niters]
  (let [ref (atom 0)
        pool  (Executors/newFixedThreadPool nthreads)
        tasks (repeat nthreads #(dotimes [_ niters]
                                  (swap! ref inc)))]
    (doto pool
      (.invokeAll tasks)
      (.shutdown)
      (.awaitTermination 60 TimeUnit/SECONDS))
    @ref))
=> #'user/test-atom
(test-atom 100 10000)
=> 1000000

Ivan Koz06:05:21

You may wan't to dig deeper into JVM memory model to understand the foundation clojure is built on, but that isn't strictly necessary. https://shipilev.net/blog/2014/jmm-pragmatics/

hiredman22:05:39

the compiler does nothing

dpsutton22:05:09

if you are familiar with java, the source of swap! might be revealing as well. it's an infinite loop, get the value before, apply the function, compare and set. it the compare and set was successful, done, otherwise loop.

Evan Haveman23:05:18

are there any good guides on starting a simple clojurescript project that doesn’t need the browser (eg a library of functions). i know i can use node. but i cant seem figure out what my deps.edn should look like, what the command line is to open a repl and play with my code, and what the command line is to run my tests.

Evan Haveman00:05:14

clj -M --main cljs.main --compile foo.core --repl works fine to open a repl connected to a browser and im able to run my code but when i try clj -M --main cljs.main --repl-env node --compile foo.core --repl the repl loads (without the browser) but i can’t run my code. `Execution error (ReferenceError) at (<cljs repl>:1). foo is not defined`

Noah Bogart01:05:14

Maybe ask in #clojurescript

Evan Haveman01:05:03

i did at first but felt it was such a newb question i moved it here! 🙂

👍 3
Evan Haveman01:05:02

i also wonder if the answer is “if you’re not using the browser, just use clojure”…

Noah Bogart01:05:14

Hah that is definitely an answer. You could write everything in cljc and use the Clojure repl, provided you don’t need any features of node (fs, event emitter etc)

Evan Haveman02:05:05

yeah i don’t need any of that

seancorfield04:05:55

@U021TB0SSA2 Something worth bearing in mind is that ClojureScript has to go through a compile-to-JS phase, which requires the ClojureScript compiler which is running on the JVM anyway. So if you don’t need a browser and you don’t need Node.js features, I would probably just write it as .cljc files, run it on the JVM as Clojure while you’re developing stuff, and use Olical’s cljs-test-runner to make sure it compiles and runs as ClojureScript. That’s what I do for HoneySQL (on the v2 branch).

👍 2
Evan Haveman05:05:56

thank you - i’ll look into cljs-test-runner!

Evan Haveman20:05:29

yeah - was shared in another thread i started in #clojurescript - i found clj -M -m cljs.main --repl --repl-env node also works.