Fork me on GitHub
#beginners
<
2020-06-26
>
traviscjefferson02:06:58

Whew I’m struggling! I’ve got a tiny clojure program running on a remote server, using deps.edn and (hopefully) clj. It’s a daemon/run-forever type script, and I’m having a lot of trouble figuring out how to “escape” the repl and leave the process running in the background

dpsutton02:06:51

how are you starting your program?

traviscjefferson02:06:02

I’ve tried a number of things, but I started here clj -m <ns>

traviscjefferson02:06:33

I have a -main function in that ns, and it’s called correctly (the code even works!)

dpsutton02:06:08

/t/run ❯❯❯ cat deps.edn
{:paths ["."]}
/t/run ❯❯❯ cat foo.clj
(ns foo)

(defn -main [] (println "hello and goodbye!"))
/t/run ❯❯❯ clj -m foo
hello and goodbye!
/t/run ❯❯❯

dpsutton02:06:30

my program doesn't start a repl.

traviscjefferson02:06:05

indeed! I’ve got an infinite sequence thing going on

traviscjefferson02:06:11

let me share a snippet

traviscjefferson02:06:34

(defn start-schedule []
  (-> (chime/periodic-seq (Instant/now) (Duration/ofMinutes (:interval-minutes env)))
      (chime/chime-at (fn [time]
                        (println "Checking for dogs at " time)
                        (check-for-new-dogs)))))

(defn -main []
  (start-schedule))

dpsutton02:06:04

ok. and what is your expectation?

traviscjefferson02:06:29

I’d like for -main to stay alive and continue to trigger per chime’s schedule

traviscjefferson02:06:54

but I’d also like to detach from this terminal’s process and kill my ssh connection

traviscjefferson02:06:12

leaving clojure/java happily churning away on my remote machine

dpsutton02:06:30

does it currently exit? i'm not familiar with chime

traviscjefferson02:06:38

it does not, it consumes the shell and leaves me no choice but to CTRL-C it

dpsutton02:06:59

so your only problem is how to run this process in the background

dpsutton02:06:05

it correctly "runs forever" already?

traviscjefferson02:06:41

yes. I’m open to opinionated suggestions! I’m not married to clj -m

traviscjefferson02:06:45

I’ve tried bash-style backgrounding with & but that does something bad to the input/output streams

dpsutton02:06:06

do you need input and output streams?

traviscjefferson02:06:53

no to input. I do have some println statements; I’d like those be available if possible

dpsutton02:06:02

and you want those to just pop up randomly in a terminal that's doing other things?

traviscjefferson02:06:40

it’s a remote server, so I suppose ideally they’d go to syslog or redirect to a file or somethin

traviscjefferson02:06:36

they’re not particularly important overall

traviscjefferson03:06:36

I think I’m having more luck now

traviscjefferson03:06:53

I’m running the same command clj -m <ns> but now it’s through systemd

traviscjefferson03:06:07

that appears to be sufficiently “in the background”

clojurians-slack10010:06:41

> ... through systemd That's the Right Way :+1:

noisesmith15:06:45

there are various tools for this, in reverse order of hackiness: • run a repl inside tmux/screen and detach it from its controlling tty • use nohup to run from the background and log to a file • use jsvc (the unix version of Commons Daemon) a tool that lets a java program act as a proper service in a cross platform way • dedicated service integration tailored to your target OS (eg. systemd)

noisesmith15:06:08

I have a shim java project that makes it easy to have jsvc use a specific clojure namespace, without needing AOT or interop in the Clojure code. https://github.com/noisesmith/clj-jsvc-adapter

noisesmith15:06:35

it requires you to use a system property to register a clojure namespace containing regular functions to be used for each jsvc lifecycle method

noisesmith15:06:37

oh and another option is to use Docker plus a microservice distsys app like mesos / k8s

noisesmith15:06:46

(what my company actually does)

traviscjefferson15:06:27

cool, thanks for enumerating all those options

traviscjefferson15:06:38

I was ready to go docker plus some orchestrator

traviscjefferson15:06:03

but it's just a lil DO droplet, didn't wanna burn any more resources than I already was

traviscjefferson15:06:25

I really wanted a socket repl server to work

traviscjefferson15:06:44

but that seems like it doesn't wanna be background-ed either (unless you do something fancy like your tmux suggestion)

traviscjefferson15:06:45

nohup sounds exactly like what I wanted

traviscjefferson15:06:02

thanks all for chiming in!

noisesmith15:06:36

with my lib, jsvc isn't hard to use, and the advantage of jsvc is you get restarting / monitoring / proper system level logging

noisesmith15:06:13

but if nohup is enough and you don't mind manually checking and restarting, cheers 🍻

traviscjefferson15:06:32

whale I'm not gonna UNDO my systemd work

noisesmith15:06:42

socket-repl and nohup are compatible

traviscjefferson15:06:14

unless you think there's a limitation of systemd I might run into?

noisesmith15:06:37

oh no, beyond portability concerns (which don't seem like an issue, and you'll know what they are) systemd is fine

traviscjefferson15:06:06

thanks much @ this was educational :brain:

clojurians-slack10019:06:08

> * run a repl inside tmux/screen and detach it from its controlling tty > * use nohup to run from the background and log to a file Hasn't there been issues of systemd killing such procs when they lose their tty, and weren't run with systemd-run?

noisesmith19:06:05

last I used those techniques was pre-systemd honestly

noisesmith19:06:23

I mean, I'm a professional after all and those are hacky solutions :D

clojurians-slack10019:06:53

The world runs on hacky solutions 😜

noisesmith19:06:27

worse is better, but I strive for better than that, to coin a phrase

clojurians-slack10019:06:14

Indeed. We're here for [a] lisp, after all 🙂

rameezkhan.sa05:06:11

If I have the following map {:players {:player1 {:hand :rock}, :player2 {:hand :rock}}} as input to the following:

(defn determine-winner
  [game]
  (let [player1-hand (get-in game [:players :player1 :hand])
        player2-hand (get-in game [:players :player2 :hand])]
    (if (= player1-hand player2-hand)
      :draw
      (if (= (player1-hand dominated-by) player2-hand)
        :player1
        :player2))))

rameezkhan.sa05:06:03

How can I better destructure the input? In other words, do you reckon the let block is fine in this case or is there a more idiomatic way to do this?

seancorfield05:06:37

@rameezkhan.sa I'd probably declare the argument to be [{:keys [players]}] and then your let bindings are one level less.

seancorfield05:06:52

(since you're not using game anywhere else in the function)

rameezkhan.sa05:06:27

@seancorfield Cool! Any way to destructure "deeper" than that even?

seancorfield05:06:53

There is, but I'm not sure I'd find it easier to read than the code you already have

seancorfield05:06:18

Nested destructuring can be very hard to read, in my opinion.

rameezkhan.sa05:06:26

Fair enough. Thanks @seancorfield 🙂

rameezkhan.sa05:06:51

Next question. 🙂 I'm learning spec and have the following:

(ns rock-paper-scissors.core
  (:require [clojure.spec.alpha :as s]
            [clojure.spec.gen.alpha :as g]))

(def hand? #{:rock :paper :scissors})

(s/def ::hand hand?)
(s/def ::player (s/keys :req [::hand]))
(s/def ::player1 ::player)
(s/def ::player2 ::player)
(s/def ::players (s/keys :req [::player1 ::player2]))
(s/def ::game (s/keys :req [::players]))

(def dominates
  {:rock     :scissors
   :paper    :rock
   :scissors :paper})

(g/sample (s/gen ::game) 5)

rameezkhan.sa05:06:22

g/sample returns

Class: clojure.lang.LazySeq
Contents: 
  0. { :rock-paper-scissors.core/players { :rock-paper-scissors.core/player1 { :rock-paper-scissors.core/hand :scissors }, :rock-paper-scissors.core/player2 { :rock-paper-scissors.core/hand :scissors } } }
  1. { :rock-paper-scissors.core/players { :rock-paper-scissors.core/player1 { :rock-paper-scissors.core/hand :scissors }, :rock-paper-scissors.core/player2 { :rock-paper-scissors.core/hand :rock } } }
  2. { :rock-paper-scissors.core/players { :rock-paper-scissors.core/player1 { :rock-paper-scissors.core/hand :rock }, :rock-paper-scissors.core/player2 { :rock-paper-scissors.core/hand :rock } } }
  3. { :rock-paper-scissors.core/players { :rock-paper-scissors.core/player1 { :rock-paper-scissors.core/hand :rock }, :rock-paper-scissors.core/player2 { :rock-paper-scissors.core/hand :scissors } } }
  4. { :rock-paper-scissors.core/players { :rock-paper-scissors.core/player1 { :rock-paper-scissors.core/hand :scissors }, :rock-paper-scissors.core/player2 { :rock-paper-scissors.core/hand :scissors } } }

rameezkhan.sa05:06:38

How do i extract the data from that as an actual map?

seancorfield06:06:57

g/sample produces a sequence of maps. I'm not sure what you're asking.

rameezkhan.sa06:06:59

Sorry, to be more clear, what I'm asking if those sequence of maps can return {:players {:player1 {:hand :scissors}, :player2 {:hand :scissors}}}`` for item 0 above. Does that make sense?

seancorfield06:06:29

No, I don't understand your question.

seancorfield06:06:16

Your specs use qualified keywords. What did you expect to get from g/sample?

seancorfield06:06:31

The output above is correct.

seancorfield06:06:14

(I don't know what you're using to run the code -- a regular REPL produces much nicer output than that)

rameezkhan.sa06:06:51

Ah wait, I think I understand. I didn't realize :rock-paper-scissors.core/players was an actual keyword. I was expecting just :players. But I get it now.

rameezkhan.sa06:06:23

Thanks for the help @seancorfield!

seancorfield06:06:07

In a normal REPL those maps would be a lot easier to read

seancorfield06:06:20

Hmm, maybe not because they are nested. Anyways, they are exactly what I'd expect to get -- qualified keywords.

endrebak8509:06:40

When running a program with lein run :hi "there" is there a way to tell the main function to get the args as a dict? Or do I have to write that code myself? '(":hi" "there") My current code for turning the args into a dict is

(into {} (for [[k v] (partition 2 args)] [(symbol k) v])
dunno if there is a better way to do it :)

jumar10:06:33

I think you have to parse it - something like:

lein run '{:a 1 :b 2}'

;; the in code
(clojure.edn/read-string arg)

dromar5610:06:40

I'm not saying it's better, but here's another way:

(->> '(":hi" "there" ":foo" "bar")
     (partition 2)
     (map (juxt (comp symbol first) second))
     (into {}))
;;=> {:hi "there", :foo "bar"}

endrebak8510:06:45

Thanks both of you 🙂

ben.sless10:06:09

You can, but why not use a library for it? https://github.com/clojure/tools.cli

endrebak8511:06:03

Does tools.cli work easily with lein run?

ben.sless17:06:10

Never tried, I usually run my stuff as uberjar in the end.

endrebak8511:06:07

I have code like this:

(def rules (atom {}))

(defmacro defrule
  "TODO: test me."
  [name & body]
  `(do
     (let ["bla" "bla]
       (swap! rules assoc kw-name# (handle-docs body#)))))
What the code does is allow users to define rules. With the macro, they are all collected into the atom rules. Is there a way to do this in a stateless way - i.e. without the atom?

roguas13:06:24

If they are dynamically(during runtime) changing the rules i dont think there is stateless way. However if the rules are only initialized at startup or obtained somewhere but without dependency on a particular point in time(like every 5 minutes?) - I recommend memoize

clojurians-slack10013:06:44

Yeah. State requires... well... statefulness.

clojurians-slack10013:06:49

Another way that you can store that state, is as metadata in def/`defn` statements that you output from that macro. You can then iterate over the symbols in the ns and filter for the metadata.

clojurians-slack10013:06:09

(I saw some lib doing this, but I can't recall which one.)

clojurians-slack10021:06:01

I just happened to find it: Metabase's defendpoint generates compojure routes, but with :is-endpoint? true in the metadata https://github.com/metabase/metabase/blob/75331072c0bbfb31b2edd5a2390324b5a537439c/src/metabase/api/common.clj#L263

endrebak8510:06:30

Thanks mm and walterl. @ I think clara-rules also stores its rules with meta-data. I do not see the advantage over just having an atom though. See https://github.com/cerner/clara-rules/blob/8e5714216fa2102cd8b09e3cc73f01048154878f/src/main/clojure/clara/rules.cljc#L384

clojurians-slack10010:06:29

The difference, I think, is that metadata is "more static" than using an atom, and I would suggest using what fits best. If the macro string metadata (conceptual, rather than Clojure-specific), store it in (Clojure) metadata, if it's collecting data you're working with, an atom is probably more suitable.

clojurians-slack10010:06:38

I think what bothers me about hard-coding an atom in a macro like that, is that it makes "magic" out of the data collection being performed by the macro, by not referencing the atom in its usage. I.e. when reading a (defrule ...) usage, there is no hint that the rules atom is being used.

endrebak8510:06:04

Good points. I will collect metadata instead

roguas13:06:06

Hi, I am currently refactoring some of clojure codebase, one of the easy targets for me is to provide a way to access same config in different flavours in lein. I saw that you can use :profile in project.clj. I want to have 2 configs: :test and :dev how would I go about that in code, so when the lein test is called it loads a different file from profile.

roguas13:06:22

thats great, thanks - how do I later access it?

clojurians-slack10013:06:42

Aliases like those are used directly with lein. So you just run lein test, and it's "expanded" (internally) to lein with-profile +test test

roguas13:06:21

that's fine - but how do i later access that variable?

:profiles {:dev      {:env {:config-path "conf/app.conf"}
           :test     {:env {:config-path "conf/app.test.conf"}}
I presume it will be available in env?

roguas13:06:17

[environ "0.5.0"]
right?

yokoyama.km14:06:04

@, don't forget to add the lein-environ plugin. See more in https://cljdoc.org/d/lein-environ/lein-environ/1.2.0/doc/readme

somedude31413:06:39

@mm lein with-profile +test test

roguas13:06:04

could you elaborate 🙂?

somedude31413:06:35

with-profile +test runs your tests with test profile. with-profile +abc runs your tests with abc profile

yokoyama.km14:06:39

Using +abc adds the abc profile to the default ones, right? If you run it without the plus sign, than it effectively replaces the profiles.

somedude31414:06:22

I believe it overrides it unless the explicitly specified profile extends another one

yokoyama.km14:06:34

Maybe my interpretation is wrong, but I saw that "The above invocations [without plus sign] activate the given profiles in place of the defaults. To activate a profile in addition to the defaults, prepend it with a +" (https://github.com/technomancy/leiningen/blob/master/doc/PROFILES.md#activating-profiles). What am I missing?

somedude31415:06:07

Ah you are completely right. Just tested it. The default profiles load in addition to the profiles specified in with-profile. Good to know 🙂

somedude31415:06:50

Now the + makes sense lol

yokoyama.km15:06:20

Thank you for testing. I was kind in doubt after you said it overrides hehe Cool it is now clarified.

gtzogana14:06:58

hello everyone, does any library exist that would make it easier to consume bytes from a websocket until a particular delimiter is reached, rather than just looking at individual frames?

gtzogana14:06:25

i suppose it would be reasonably straightforward to build such a thing with core.async but i’d rather not duplicate the effort if possible

dev.4openid15:06:01

I have installed clojure into Ubuntu. I have : openjdk 14.0.1 2020-04-14 OpenJDK Runtime Environment (build 14.0.1+7-Ubuntu-1ubuntu1) OpenJDK 64-Bit Server VM (build 14.0.1+7-Ubuntu-1ubuntu1, mixed mode, sharing) I have a simple.core.clj file in /simple/src/simple and when I run clojure -m simple.core, I receive the following: clojure -m simple.core Execution error (FileNotFoundException) at clojure.main/main (main.java:40). Could not locate simple/core__init.class, simple/core.clj or simple/core.cljc on classpath. Full report at: /tmp/clojure-15616630179164666709.edn I installed apt install clojure however I suspect I did not generate the .clojure folders and all the edn files. Well, I cant see them - e.g. the should be a deps.edn in the /simple folder

noisesmith15:06:28

which "clojure" tool are you using?

noisesmith15:06:26

usually they would expect simple.core to be in ./src/simple/core.clj or alternatively ./src/clj/simple/core.clj

andy.fingerhut15:06:04

I would suspect that sudo apt install clojure is something that very few production Clojure users ever touch or recommend.

noisesmith15:06:10

yeah, debian's clojure doesn't use deps.edn, or even project.clj - it's a hacky standalone

noisesmith15:06:37

the proper way to use clojure (as with any java lib) is with a project manager that manages your deps and classpath

noisesmith15:06:07

it's not impossible to manage your own classpath, but there's little benefit

andy.fingerhut15:06:03

I would recommend sudo apt purge clojure , followed by using the instructions here: https://clojure.org/guides/getting_started. If you want to use Leiningen, that is another common way, but only necessary if you want to interact with a project that someone developed using Leiningen (indicated by presence of a project.clj file in the code base)

dev.4openid15:06:50

OK I will follow the guide

dev.4openid15:06:06

Funny enough the linuxbrew did not work either. I am trying to learn how to move away from leiningen

andy.fingerhut15:06:09

I know it might seem strange, but I believe that the packaged versions in Debian/Ubuntu, and a few other package managers, are created by enthusiasts of those packaging systems, and also of Clojure, but what they install often has little to do with the http://clojure.org instructions.

andy.fingerhut15:06:57

or worse, puts commands in your command path that have the same names as the installers on http://clojure.org, which thus shadow/conflict with the recommended ones.

andy.fingerhut15:06:00

When you say the linuxbrew did not work either, do you mean the instructions on http://clojure.org did not work for you on Linux?

dev.4openid15:06:37

Followed https://clojure.org/guides/getting_started. and doing the obvious works 😠

kbosompem20:06:10

i've created a simple ring app and am wondering how do i pull a list of all routes

hiredman20:06:53

ring doesn't actually say anything about routes, routing will come from some other library

kbosompem20:06:07

ah i'm using compojure-api

hiredman20:06:07

the most common for a "simple ring app" being compojure

hiredman20:06:30

compojure routes are functions, which are not really queryable

hiredman20:06:54

all you can do is invoke the function on a request and either get a response when it matches, or nil when it doesn't

kbosompem20:06:22

the documentation mentions a routing table but I can't find a call to it

hiredman20:06:35

ah, it does look like compojure-api adds some stuff on top of compojure for this

hiredman20:06:19

https://github.com/metosin/compojure-api/blob/master/src/compojure/api/routes.clj#L45-L52 will work, as long as you only use the stuff from comojure-api, if you use vanilla compojure or wrap things in middelware it won't

hiredman20:06:58

the top of the compojure-api readme says "Psst! If you're starting a new project, why not try out https://github.com/metosin/reitit?"

kbosompem20:06:57

Glad you mention reitit! My first project I used reitit but could not generate a war file! Still unable to. so for my second i did compojure and that worked out well. I like the work the metosin guys do so when i saw they created compojure-api i wanted to use that. Able to create a war file but my latest problem is

seancorfield21:06:19

@ I'm curious: why are you trying to generate a WAR file?

kbosompem23:06:29

I figured the war file would be the least intrusive for our devops team. We are predominantly dotnet shop and use azure service fabric. I'm not officially a developer just love to dabble so I like to make sure what i deliver is hassle free but my primary reason for it is i assume that tomcat manages memory and keeps apps alive especially after reboots. If embedding jetty gets me there without complication then I can definitely pitch that.

seancorfield00:06:09

You could use embedded Jetty and package the app as an uberjar, so it could be run with java -jar my-clojure-app.jar if devops would go for that.

kbosompem20:06:52

to resolve this issue i comment out just these 2 and everything works (there are 65 others) (GET "/tasks/" req      `:return [Task]`      `:query-params [subprojectid :- Int]`      `:summary "Returns a list of tasks for a given subproject"`      `(get-tasks db (:params req)))`      `(GET "/tasks/:taskid" req`      `:return Task`      `:path-params [taskid :- Int]`      `:summary "Returns task details"`      `(get-task db (:params req)))`

seancorfield21:06:11

You can use triple backticks around a multi-line block of code. It's more readable than having each line formatted with single backticks.

seancorfield21:06:21

like this
block
of text

kbosompem23:06:14

(GET "/tasks/" req
     :return [Task]
     :query-params [subprojectid :- Int]
     :summary "Returns a list of tasks for a given subproject"
     (get-tasks db (:params req)))
 
   (GET "/tasks/:taskid" req
     :return Task
     :path-params [taskid :- Int]
     :summary "Returns task details"
     (get-task db (:params req)))

kbosompem23:06:20

cool thanks!

kbosompem20:06:40

this is why i was wondering if there was a way to pull the "routing table" and see if i have any duplicates/conflicts

somedude31422:06:45

Why this is returning nil instead of ""

(re-matches #"[a-z]{2}?" "")
This works as expected:
(re-matches #"[a-z]{2}?" "fr")

somedude31422:06:45

Why this is returning nil instead of ""

(re-matches #"[a-z]{2}?" "")
This works as expected:
(re-matches #"[a-z]{2}?" "fr")

smith.adriane22:06:33

that regex doesn't match that string

noisesmith22:06:05

this one does

(ins)user=> (re-matches #"(?:[a-z]{2})?" "")
""
(ins)user=> (re-matches #"(?:[a-z]{2})?" "fr")
"fr"

noisesmith22:06:45

(?: ... ) is a "non capturing group", relatively obscure but exactly the right thing here

somedude31422:06:55

Thanks that’s exactly it

dennisa23:06:45

I cannot use macros as filter predicate?

noisesmith23:06:14

note directly, as you can't properly use a macro as an argument to a function

noisesmith23:06:45

of course you can use #(or ...) etc.

brandon14923:06:14

Does anybody else have trouble getting Firefox to read edn properly in the response devtools?