beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
Mitch 2020-10-20T15:05:30.215800Z

We are using clojure.java.shell/sh for a long running command. Is there a way to stream the output of the command to stdout as it is running? We are hoping to get visual feedback of the command's progress instead of having to wait for it to complete to check the return map's :out key.

walterl 2020-10-20T15:16:18.215900Z

I've not used it myself, but maybe @borkdude's https://github.com/babashka/process can help

borkdude 2020-10-20T15:17:13.216200Z

@mitchell_clojure Yes. Use (babashka.process/process ["your-program" "arg"] {:inherit true})

Mitch 2020-10-20T15:19:20.216400Z

you are the best! tyvm everyone

Mitch 2020-10-20T16:20:36.217Z

So if I wanted to capture the output to be processed by my script while also outputting it to stdout, I would have to open a reader which prints to stdout and also appends to a string

borkdude 2020-10-20T16:21:07.217200Z

correct

1
borkdude 2020-10-20T16:21:56.217400Z

you could just use a loop to read from the out stream

borkdude 2020-10-20T16:22:15.217700Z

and read lines from that and append to some string writer + stdout

borkdude 2020-10-20T16:23:01.217900Z

Something like:

(binding [*in*  (io/reader (:out tail))
          *out* (io/writer (:in cat-and-grep))]
  (loop []
    (when-let [x (read-line)]
      (println x)
      (recur))))

borkdude 2020-10-20T16:23:21.218100Z

(this is from the README)

William Skinner 2020-10-20T16:41:20.219100Z

What's the idiomatic way to say (if (str/blank? dir) nil dir)

Darin Douglass 2020-10-20T16:46:06.220900Z

Seems fine to me. There’s also:

(when-not (str/blank? dir) dir)

2020-10-20T16:50:16.222Z

this is also easy in normal clojure via interop:

hammurabi$ clj
Clojure 1.10.1
(cmd)user=> (-> ["ls"] (ProcessBuilder.) (.inheritIO) (.start) (.waitFor))
CHANGELOG.md  examples	project.clj  resources	target	todo.otl
doc	      LICENSE	README.md    src	test
0
user=> 

2020-10-20T16:51:12.222200Z

the .waitFor makes the repl wait until the process exits before printing the next prompt

2020-10-20T16:52:36.222800Z

there's also (not-empty dir)

(ins)user=> (not-empty "")
nil
(ins)user=> (not-empty "foo")
"foo"

πŸ‘ 1
2020-10-20T16:53:22.223400Z

I'd argue not-empty is most idiomatic as it is specifically designed for this use case (nil on empty, without changing input type to seq)

adityaathalye 2020-10-20T16:55:25.224500Z

not-empty won't defend against a blank string, which dir could be

2020-10-20T16:55:56.224700Z

did you look at my example?

2020-10-20T16:56:08.225100Z

it explicitly returns nil for empty string

2020-10-20T16:56:19.225300Z

oh - you mean whitespace?

2020-10-20T16:56:54.226100Z

right, if whitespace is a concern not-empty? doesn't help

adityaathalye 2020-10-20T16:58:30.227200Z

yes, I assumed they wanted to defend against a "blank" string as opposed to an empty one

borkdude 2020-10-20T17:15:29.227300Z

yes, but if you want to read from the process's stdout you should not use inheritIO

borkdude 2020-10-20T17:15:48.227500Z

he wants to get the string, but also write to out

2020-10-20T17:24:40.227700Z

ahh, that's different, yes

clojuregeek 2020-10-20T17:29:12.230Z

Hi .. i want to change the log level on the command line when i run tests (sometimes) I tried JVM_OPTS='-Dlog.console.threshold=INFO" lein testΒ and also usingΒ `LEIN_JAVA_OPTS instead of JVM_OPTS`

2020-10-20T17:31:59.230100Z

definitely more complex

(let [pb (ProcessBuilder. ["ls"])
      p (.start pb)
      in (.getInputStream p)
      result (StringBuilder.)]
  (loop []
    (let [b (.read in)]
      (if-not (pos? b)
        (str result)
        (let [c (char b)]
          (.append result c)
          (print c)
          (recur))))))

2020-10-20T17:39:58.232200Z

@clojuregeek I would usually handle this by making a separate lein profile with :quiet {:jvm-opts ["-Dlog.console.threshold=INFO"]} and use with-profile to add that profile lein with-profile +quiet test

clojuregeek 2020-10-20T17:42:40.232600Z

oh thats a good idea

2020-10-20T17:43:06.233100Z

that way it combines correctly with other additions you might want to make to the java command line

borkdude 2020-10-20T17:52:42.233300Z

(require '[babashka.process :refer [pipeline pb]]
         '[<http://clojure.java.io|clojure.java.io> :as io])

(let [[catp teep] (pipeline (pb ["cat"]) (pb ["tee" "log.txt"] {:out :inherit}))]
  ;; write to cat every 100 milliseconds 10 times
  (future
    (with-open [writer (io/writer (:in catp))]
      (loop [i 0]
        (when (&lt; i 10)
          (binding [*out* writer]
            (println i)
            (flush))
          (Thread/sleep 100)
          (recur (inc i))))))

  @teep ;; wait for the tee process to finish
  ;; finally we print the file's content
  (println (slurp "log.txt")))

(shutdown-agents)

borkdude 2020-10-20T17:53:13.233500Z

This works if you have tee on your system

borkdude 2020-10-20T17:54:06.233700Z

the first process is cat, to which we write 10 times (just to simulate some output)

borkdude 2020-10-20T17:54:17.233900Z

and this output is captured by tee, which writes it to stdout AND to a file

borkdude 2020-10-20T17:56:28.234100Z

I think it could be useful to have this built into the process lib

j 2020-10-20T18:30:51.235900Z

I'm trying to setup kaocha, and I tried running the following line in the very beginning of the kaocha install docs and got this error:

❯ clojure -Sdeps '{:deps {lambdaisland/kaocha {:mvn/version "1.0.700"}}}' -m kaocha.runner --test-help
Execution error (FileNotFoundException) at kaocha.specs/fn (specs.clj:82).
Could not locate clojure/test/check/generators__init.class, clojure/test/check/generators.clj or clojure/test/check/generators.cljc on classpath.
Can some one give me some tips? I tried adding the test alias in the docs to deps.edn, and it give me the same error

2020-10-20T18:31:54.236500Z

you need to pull in the generative deps sepearately to use that feature of spec

2020-10-20T18:32:30.237Z

org.clojure/test.check {:mvn/version "1.1.0"}

j 2020-10-20T18:38:00.237300Z

That worked! Thanks @noisesmith! I didn't see that library dependency anywhere in the kaocha docs. It looks like clojure/test/check is a reference to the test.check library? Can I assume that future error messages that has that kind of path will help me identify missing dependencies?

2020-10-20T18:39:06.237500Z

right - the deal is that clojure.spec contains code that dynamically looks for that code

2020-10-20T18:39:29.237700Z

it's not a "hard dependency" because if you don't want to use those features you don't need to include the extra dep

2020-10-20T18:40:01.237900Z

it's a gotcha because most libs don't work this way

j 2020-10-20T18:40:23.238100Z

Got it! I just started playing with kaocha, and I didn't know that kaocha.runner needed spec πŸ˜•

2020-10-20T18:40:47.238300Z

IMHO spec should intercept the error and include a message like "to use this optional spec feature you need the following package ..."

2020-10-20T18:40:57.238500Z

spec comes with clojure

2020-10-20T18:41:09.238700Z

certain spec features require this other optional dep that doesn't come with clojure

j 2020-10-20T18:41:39.238900Z

oh! my bad! I guess kaocha.runner calls spec, then spec in turns calls test.check?

2020-10-20T18:41:45.239100Z

right

j 2020-10-20T18:42:00.239300Z

cool, understood! Thanks for the tip! πŸ˜„

Day Bobby 2020-10-20T19:38:25.240300Z

I posted some questions on reddit. Thought I'd post them here too so I get more answers https://www.reddit.com/r/Clojure/comments/jed5z9/new_clojurians_ask_anything/g9gpmly?utm_source=share&amp;utm_medium=web2x&amp;context=3

Day Bobby 2020-10-20T19:38:43.240700Z

How big of a problem is dependency conflict in Clojure? I've seen a lot of libraries that have not been updated for 3 - 4 years. I'd imagine in a scenario where use one such library which depends on an older version of another library (library-x for example), and I also use a more updated library which depends on a recent version of library-x, would that have high probability of causing troubles?

I've found that in Clojure, I always want to make the perfect API upfront because being a dynamic typed language, it's harder to refactor after. I come from a background of typescript where I just write function that work first then come back to change the interface, maybe even much later along the road, because it's very easy. What have been your experience?

I corrupt the REPL state a lot, especially on newer projects when everything is not so figured out (dependencies, etc...). Can you share tips on working with REPL so that it doesn't hinder productivity.

What need to happen so that a typical clojure web project (jdbc, ring api) can be run under graalvm? I know in Java land, there are Quarkus and Micronaut which support graal pretty well. Fast startup and low memory usage are sought after features these days because of microservices. Do you think we Clojurians will have these in the future?

Thank you very much~

ordnungswidrig 2020-10-21T07:17:20.247200Z

I only ever run into dependency conflicts down deep in the java library ecosystem, typically some apache commons library or one of the inevitable logging libs. The only time I remember where I could not easily resolve this by pinning either version was for some dependency in AWS java SDK.

πŸ‘ 1
Day Bobby 2020-10-21T08:04:33.247400Z

Thank you all for your answers. To clarify some of my points earlier: I've never had a problem with dependency conflicts, though the programs I'm writing to learn Clojure are very small and don't have a lot of dependencies. From my experience in other languages, issues with dependencies are often huge timesinks so I just want to know before fully committing to writing my next side project in Clojure. It's reassuring to know most of the issues come from Java packages, I will be on the look out for those. On the REPL issue, I have always been suspecting that I'm doing something wrong. Rarely goes a day when I don't have to restart it. Sometimes its not a state issue though: a library that I use dumps its error logs (a massive map that contains its debug values) onto the REPL rendering cider near unresponsive -- But now that I think more about it, clearing the cider output instead of restarting would probably work too. On the refactor issue, for example in TS I can rename the keys of a map a function expects, and my editor would automatically fix the keys at every callsite. In Clojure, what I usually do is to do a search and fix them manually. In TS, if I change the order of a function arguments, my editor will tell me that action results in errors in these different places, and I just follow the red squiggles to fix them all.

Day Bobby 2020-10-21T09:01:24.247900Z

Guess what I'm trying to say is that I feel more confident evolving my API in a static typed language knowing that the existing parts of my application will be kept intact. I understand that in dynamic languages we have the freedom to change our APIs much more easily due to lack of constraints. The only constraint is test which is a luxury in some cases (i.e early startups), and maybe spec too but specs are often not strict. In my case, I haven't utilized that freedom due to fear of breaking stuff. Also refactoring with tests and specs does not have the same editor support as with static types.

Day Bobby 2020-10-21T09:21:12.248100Z

Thanks everyone for taking the time to answer so far. I have to admit I used to have strong opinion on static typing. I used ruby from 2008 to 2015, jumped on the static type bandwagon and never looked back, or so I thought πŸ˜› Since then I been doing frontend stuff using React in TS which can be coded in a very functional way (a bit Clojure-y if you can even believe it). I turned to Clojure because it is a functional language with sane data structures, not because it's a dynamic language. I believe the benefits will outweight the mental shift. I guess I just need some guidance and a lot more time building serious stuff in Clojure.

kennytilton 2020-10-21T11:35:28.249200Z

My big gripe with static is that it makes me propagate a change throughout the entire app before it will even compile. In a dynamic language I can start a refactoring on a small wodge of code and see how it goes, change course, maybe even abandon altogether. I know the rest of the app is broken now, but I defer the overhaul until I know where I am going.

πŸ‘ 1
1
simongray 2020-10-21T11:42:07.251500Z

I think most people taking on Clojure will have had some experience with static typing, so they probably expect some kind of mess now that the guard rails are off. There is no single thing in Clojure that replaces what you get with static typing, but I would say it's a combination of functional programming patterns typically resulting in much less coupled code (and therefore much less to refactor), spec or some other validation method for your external inputs, a tight feedback loop provided by REPL-driven development, a preference for commonsense plain data literals over complex blobs of code (when you've already reduced your data to the bare essentials what is there to refactor?), and perhaps the higher maturity level of the average Clojure programmer, a culture that Rich Hickey definitely helped instill in this young language. Using namespace-qualified keywords is also helpful.

πŸ‘ 1
simongray 2020-10-21T11:44:56.251700Z

Tangentially relevant Rich Hickey rant: https://youtu.be/aSEQfqNYNAc

πŸ‘ 2
Day Bobby 2020-10-21T13:04:00.252500Z

@hiskennyness You made very interesting/valid point. Thats a clear win for dynamic languages. Guess I just never thought like that before. @simongray To be fair, with JS/TS you can do stuff in a functional way using only simple data structures: arrays, objects which are like maps,... There are things like Set and Map which have a class based API, but you can get very far ignoring those and just resort to arrays and objects (of course they are mutable so most of the times you will have to use spread operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to manipulate them in a non destructive way πŸ˜₯). I know those are hacky measures and that was one reason why I turned to Clojure. In my most recent project which contains almost 100k LOCs of TS, there is only one class (wrapper of an external HTTP client). I feel it's more because of the way I develop, not because of the complexity of the data structures I use. I tend to move stuff around a lot. Some of the changes that I often make are the opposite of "provide more/require less" @seancorfield mentioned: things like "remove this default behavior of this component and make user specifically ask for it" or "make this component a headless component by taking in a render function instead of an element so the user has total responsibility of how it looks". I haven't incorporated spec into my workflow yet but I will do, and right now I'm also looking at ghostwheel (from what I gathered, a lot of large teams use tools like this). I don't mind changing the way I do things now to write more stable APIs but that might take some time to get used to. Thank you for the tip on ns qualifed keywords.

simongray 2020-10-21T13:10:21.253800Z

@bday2099 The idioms of the language and the norms the ecosystem you find yourself in matter a lot more than what is technically possible to do in the language. I don’t really have much experience with TypeScript, but I’ve had to debug some in som third party code and it just looked like JavaScript with more boilerplate πŸ˜‰

πŸ‘ 2
2020-10-21T16:44:04.255300Z

I actually agree that refactoring in a strongly typed language (in my case OCaml) allows a freedom that you don't have without static checks - the compiler literally tells you which things you need to change to make your code work again after your change.

πŸ‘ 1
2020-10-21T16:44:34.255500Z

so I can do drastic changes in an OCaml codebase that would be foolish to do in Clojure

seancorfield 2020-10-21T16:45:53.255700Z

@noisesmith But are they the same sorts of changes you would need/want to make in a similar project in Clojure?

seancorfield 2020-10-21T16:48:06.255900Z

I ask, because I don't find myself doing a lot of refactoring-in-the-large in Clojure: my refactorings tend to be very localized. I do sometimes change a function signature but I might do that initially via an additional arity so I don't have to update every call. And I do rely heavily on a linter (`clj-kondo`) to pick up incorrect numbers of arguments and some other things.

πŸ‘ 1
2020-10-21T16:48:17.256100Z

they are general purpose data model / algorithm changes (like changing arg list to add more data, or using a different collection type with a different set of functions)

2020-10-21T16:49:00.256300Z

things I do frequently in Clojure, but need to be very cautious with once a code base reaches a certain size (or I need extensive test coverage which I simply don't need in the OCaml case)

2020-10-21T16:49:41.256500Z

and yes, linters add static checking to a language that lacks it, almost :D

2020-10-21T16:50:10.256700Z

I still prefer Clojure all told, but I'd be lying if I said the refactoring experience wasn't comparatively painful

2020-10-21T16:51:02.257Z

big picture, being more careful about your initial API has a lot of benefits, and Clojure pushes you in that direction...

πŸ‘ 2
2020-10-20T19:53:46.240800Z

most clojure libraries change API very little, even as versions are updated. The main version problems come from poorly designed deps coming from java (eg. jackson, a recurring problem for many projects). If fast startup of your server is a goal I'd switch to another language, graal native image comes with a huge set of problems for clojure usage

πŸ‘ 1
2020-10-20T19:55:21.241Z

My experience with REPL state is that I change dependencies very rarely compared to the amount of development I do within one REPL with one dependency set.

1
2020-10-20T19:58:02.241200Z

NB about server startup: my baseline assumption is that you are using an uberjar, and running your server via the java command or a java process runner, if you use eg. deps.edn or lein on your server itself you are doing it wrong

πŸ‘ 1
2020-10-20T20:05:23.241400Z

one more about the microservices: reduced memory usage is not a feature under development (both the vm itself and clojure have multiple design decisions implemented that trade more memory usage for speed)

πŸ‘ 1
Day Bobby 2020-10-20T20:13:27.242Z

thank you for taking the time to answer, these are valuable insights

2020-10-20T20:40:32.242900Z

To answer the first, clojure library authors tend to prefer stability. Meaning that a library gets very little breaking changes, and versions that are years old will still work perfectly fine now. Some libraries go as far as just starting a new namespace when they introduce breaking changes, meaning you can use either the β€˜old’ api, or the new

πŸ‘ 1
2020-10-20T20:44:18.243200Z

Compared to the JS/TS communities, Clojure libraries tend to include less breaking changes (if any), and, I’d personally say thats also part of the … how do you say … style? culture? of Clojure

πŸ‘ 1
2020-10-20T20:45:29.243400Z

Its like the Python community has their zen of Python right, part of the zen of Clojure is to write slow moving software πŸ™‚ If that makes sense

πŸ‘ 2
Day Bobby 2020-10-20T20:57:51.245400Z

That makes total sense. And I think that also answers my second question: it's normal to want to put more thoughts into the initial API, in the spirit of writing stable software. I am gonna have to be much more disciplined when writing Clojure code and ditch that "just gonna put this thing here for now" attitude that I'm so used to. Thank you!

2020-10-20T21:49:27.246Z

you can get very far with using immutable data structures with descriptive keys for nearly everything, the spec system ignores unknown keys so it's automatically "future proof" in that respect

πŸ‘ 1
2020-10-20T21:49:47.246200Z

overuse of deftype / defrecord is something to look out for coming from ts