This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-04-23
Channels
- # announcements (3)
- # babashka (68)
- # beginners (297)
- # calva (13)
- # cider (4)
- # clj-kondo (8)
- # cljs-dev (10)
- # cljsrn (26)
- # clojure (100)
- # clojure-europe (4)
- # clojure-germany (1)
- # clojure-italy (9)
- # clojure-nl (5)
- # clojure-spec (9)
- # clojure-uk (41)
- # clojurescript (69)
- # conjure (70)
- # cursive (44)
- # data-science (20)
- # datascript (2)
- # datomic (55)
- # emacs (1)
- # exercism (3)
- # graalvm (2)
- # kaocha (11)
- # leiningen (6)
- # meander (9)
- # mental-health (1)
- # off-topic (73)
- # pathom (6)
- # pedestal (1)
- # re-frame (3)
- # reagent (52)
- # reitit (8)
- # rum (39)
- # shadow-cljs (152)
- # spacemacs (10)
- # tools-deps (28)
- # xtdb (5)
Or you could just make the function to call configurable and not call it at all
What would be an idiomatic way to do memoization-type dynamic programming in Clojure, where it seems like mutability would help? For example the coding example on https://www.dailycodingproblem.com/
There's a staircase with N steps, and you can climb 1 or 2 steps at a time. Given N, write a function that returns the number of unique ways you can climb the staircase. The order of the steps matters.
For example, if N is 4, then there are 5 unique ways:
1, 1, 1, 1
2, 1, 1
1, 2, 1
1, 1, 2
2, 2
What if, instead of being able to climb 1 or 2 steps at a time, you could climb any number from a set of positive integers X? For example, if X = {1, 3, 5}, you could climb 1, 3, or 5 steps at a time. Generalize your function to take in X.
The python solution is pretty crisp:
def staircase(n, X):
cache = [0 for _ in range(n + 1)]
cache[0] = 1
for i in range(1, n + 1):
cache[i] += sum(cache[i - x] for x in X if i - x >= 0)
return cache[n]
there's clojure.core/memoize
I had a go using sequences and reduce, but not sure if it's more or less readable
(defn staircase [n segments]
(letfn [(valid-paths [cache i]
(->> (map - (repeat i) segments)
(filter #(<= 0 %))
(map cache)
(reduce +)
(conj cache)))]
(last (reduce valid-paths [1] (range 1 (inc n))))))
@noisesmith Yeah I know about memoize, but you'd need to make it a function call, whereas here the technique (or at least python equiv) is just a simple mutable vector without the overhead of recursion
actually, to me that staircase function from python looks precisely like a lazy-seq
That was exactly my first thought, but then you'd get O(n) lookup performance when you needed to reference the earlier elements for building each new one
but you always read them start to end in order anyway
one moment, I'll try a lazy-seq version that uses an accumulator instead of linear walks...
Many dynamic programming techniques are effectively like memoization, but they fill in some kind of a vector, 2d array, etc. in a particular order, such that whenever you are calculating the value to fill in one entry, all subproblems have been calculated earlier, and can be retrieved from the saved vector/2d-array/etc.
In such a case, doing any kind of loop/reduce/etc. where one of the loop values (or values accumulated from one reduce step to another), is that vector/2d-array/etc.
That need not be considered mutation any more than the rebinding of a loop variable's value from one iteration to the next is (which means, not mutation at all, except perhaps in the underlying machine code implementation)
@U0CMVHBL2 Thanks, makes a lot of sense! I settled on the reduce implementation on similar thinking, though it did seem that the machinery around needing to setup a function/direct the flow of implicit returns sometimes ends up being more verbose than a side-effecting python assignment. Was just wondering if there were more elegant approaches to tackle this domain of problems
I have not looked at the particular Python solution proposed, but a single mutating assignment to a Python dict can become a single call to assoc on a Clojure map, for example
(defn steps-dynamic [n]
(let [cache (merge (zipmap (range (inc n)) (repeat 0))
{0 1})
cache' (reduce (fn [cache n']
(update cache n' + (apply + (vals (select-keys cache (take 2 (reverse (range n'))))))))
cache
(rest (range (inc n))))]
(get cache' n)))
(defn steps-tail-recursive [n]
(letfn [(iter [x prev prev']
(if (= x n)
(+ prev prev')
(recur (inc x) (+ prev prev') prev')))]
(iter 0 0 1)))
(defn steps-recursive [n]
(cond (< n 0) 0
(zero? n) 1
:else (+ (steps-recursive (dec n))
(steps-recursive (dec (dec n))))))
aren't those missing the X arg?
> What if, instead of being able to climb 1 or 2 steps at a time, you could climb any number from a set of positive integers X? For example, if X = {1, 3, 5}, you could climb 1, 3, or 5 steps at a time. Generalize your function to take in X.
seems like you could use for with X as an inner comprehension to get every combination of steps
My solution renamed X to segments, but yeah, I tried the for loop approach initially to mirror the python code but it was pretty unwieldy to slot into a large reduce, ended up going all sequence functions. Would be cool to see a really crisp clojure implementation that runs just as fast
@U11BV7MTK Those look nice for the #{1 2} case! Though that particular case does devolve exactly into the Fibonacci sequence
Generally, a naive and non optimal solution would be (this is for 1,2,3 steps):
(defn ways
[n]
(cond
(< n 0) 0
(= n 0) 1
:else (+ (ways (- n 1))
(ways (- n 2))
(ways (- n 3)))))
You can trivially cache it with memoize:
(declare ways)
(declare ways*)
(def ways (memoize ways*))
(defn ways*
[n]
(cond
(< n 0) 0
(= n 0) 1
:else (+ (ways (- n 1))
(ways (- n 2))
(ways (- n 3)))))
Both of these are easy to generalize to any number and size of steps
If you want to do it with tail recursion, you can create a sliding binding:
(defn dways
[n]
(cond
(<= n 0) 0
(= n 1) 1
(= n 2) 2
(= n 3) 4
:else
(loop
[a 1, b 2, c 4, i 3]
(if (< i n)
(recur b c (+ a b c) (inc i))
c))))
I think if you want to parametrise that over the possible steps you might need a macrohas anyone had experience with Ring to inspect an incoming request? i am able to get the body but it is in an input stream and i would like it in a map. essentially what im trying to do is intercept an incoming request to do a type conversion if an ID is present
intercept a request between what and where? if you have an input stream, you can use slurp
to read the full inputstream into a string, but you’ll need to parse the string if you want a map.
so my angular app is making a POST request. when the request hits the server i want to intercept this request before it hits my controller / routes in order to do the modification
this sounds like a job for middleware!
at this level i get the :body
as an input stream which i am unsure if this is the right place
(defn wrap-database-component [handler database]
(fn [req]
(handler (assoc req :database database))))
(defn wrap-uuid-type [handler]
(fn [req]
(handler req)))
(defn routes-handler [database]
(-> routes/define-main-routes
(wrap-database-component database)
(wrap-uuid-type)))
seems reasonable
wrap-uuid-type
is what im trying to create. i have come across (`request/body-string)` ring util but since it is just a string i dont think it makes sense to do that. would i convert the input stream here into a map and modify the type then?
does it have to be converted back to an input stream? i am unsure and maybe trial and error is needed
typically, you just add more data to the request map to help later handlers. something like:
(defn wrap-uuid-type [handler]
(fn [req]
(handler (assoc req :uuid (make-uuid)))))
right i was hoping i didnt have to push the logic down more cause then i would have to copy-pasta for each route to do the modification on the :id
key
shouldn’t it add the uuid to the request route for every handler/route in define-main-routes
?
well i dont want to add a key called :uuid
i want to convert the id
key in the incoming json to type of uuid
well, there’s two options: 1. add a new key of uuid that is the conversion:
(defn wrap-uuid-type [handler]
(fn [req]
(handler (assoc req :uuid (extract-uuid (:body req)))))
2. replace id in the actual body
(defn wrap-uuid-type [handler]
(fn [req]
(handler (assoc req :body (convert-uuid-in-body (:body req)))))
I would typically recommend the first, but there’s still a lot I don’t know about the use case. is the response body json or some other format?option 2 is what im attempt but intercepting the incoming POST request body to make the conversion seems to be way more problematic than i thought
do you have middle ware that parses the json?
(defn wrap-routes
[routes]
(-> routes
(wrap-json-to-camel-case)
(wrap-json-response)
(wrap-json-body {:keywords? true})
(wrap-params)
(wrap-multipart-params)
(wrap-cors :access-control-allow-origin [#""]
:access-control-allow-methods [:get :put :post :delete])))
you’ll probably want your conversion middleware after the wrap-json*
stuff
my understanding is there are two middleware locations: for incoming requests and outgoing responses
thank you so much @smith.adriane
it’s been a while since I’ve messed with middleware, but I think you can wrap before, after or both with any middleware
interceptors are a popular alternative to middleware and I think interceptors are opinionated about before and after
the body still seems to be a stream at this point which i hope the wrapper would have converted it
:body #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x1a637ea3 HttpInputOverHTTP@1a637ea3[c=0,q=0,[0]=null,s=STREAM]]
is https://github.com/ring-clojure/ring-json/blob/master/src/ring/middleware/json.clj#L41 the wrap-json-body
you’re using?
it seems to only replace the body if the content-type
header matches #"^application/(.+\+)?json"
and the body is converted to json by the time it hits a normal handler later?
i’m not sure where wrap-uuid-type
is specified in your middleware list, but I would try it right before wrap-json-body
and if that doesn’t work, try right after
i always forget which order things need to go in 😬
it’s super counter intuitive
because ->
does go in order, but something about how the wrapping works makes it go in the other order
i’ve reasoned through it before, but I usually just try one and then try the other if it doesn’t work
but there’s one extra level because you’re creating functions that return functions that call the next function
it’s the same thing with transducers
like if you do (-> 10 (+ 1) (* 2))
you get 22
the thread macro is doing the same thing
(defn wrap-uuid-type [handler]
(fn [req]
(handler (assoc req :body (convert-uuid-in-body (:body req)))))
so if you’re doing (-> (wrap-json) (wrap-uuid-type))wrap-uuid is creating a function that’s going to call wrap-json
so wrap-uuid
is the “outside” wrap
and it will call wrap-json
which will call the next inner layer
;; Arguably a bit cumbersome to read:
user=> (first (.split (.replace (.toUpperCase "a b c d") "A" "X") " "))
"X"
;; Perhaps easier to read:
user=> (-> "a b c d"
.toUpperCase
(.replace "A" "X")
(.split " ")
first)
oh whoops
the docs are right
remember, there’s 4 functions
1. wrap-json
,
2. wrap-json’s inner function.
3. wrap-uuid-type
and
4. wrap-uuid-type’s inner function
so wrap-json
gets called first, but wrap json’s inner function gets called last
and it’s worth noting the inner function doesn’t get called when you create the route, but the inner functions get called every time a request comes in
i’ve been told that I’m not good at explaining things so don’t feel bad if I’m not making any sense
this is wild and im still confused however i have made a note about this to do some debugging to better understand it
thank you for taking time out of your day to rubber duck with me and share your knowledge with me
bubbling this to the top but big s/o to @smith.adriane in the thread above. thank you again and thank you for this community!
Hi, I'm trying to use http://clojure.java.io/resource to work with a file. The file is definitely there because I can slurp it directly, but when I try to use (def something (io/resource "filename")) it evaluates to nil.
so I'm failing to understand something about classpaths. Common Lisp was so much less confusing 🙂
If it’s in the resources
folder it should be in the classpath though
do I need to put http://clojure.java.io in my deps.edn and restart the REPL or something?
A classpath is kind of like the Linux path. Just like an executable in PATH is magically found, Java will find and access code/resources from the classpath.
No, http://clojure.java.io is part of core. It will come along with Clojure.
You can use this: https://github.com/clojure/java.classpath as a dev dependency and print out the classpath.
Yeah looks like your resources directory isn’t getting added to the classpath somehow, I’d have expected that to be a default (but I’m mostly using leiningen, not really up to speed on deps stuff)
;; debugging classpath issue with io/resource (require '[clojure.java.classpath :as cp]) (cp/classpath)
{:paths ["src" "test" "resources"] :deps {org.clojure/clojure {:mvn/version "1.10.1"} metasoarous/oz {:mvn/version "1.6.0-alpha6"} org.clojure/data.csv {:mvn/version "1.0.0"} semantic-csv {:mvn "0.2.1-alpha1"} }
Because I just tried, and I get this,
(#object[.File 0x32aa7f46 "dev"]
#object[.File 0x74fb4367 "classes"]
#object[.File 0x5f17bffe "src"]
#object[.File 0x704b5a50 "resources"]
...
Do you get a lot of people struggling with the Java underneath? I find it quite disorienting
If Clojure is their first exposure to Java, yeah, people do have struggles sometimes
Of course, they have the same struggles if Java is their first exposure to Java. It’s just more expected there. 😉
I guess the horrible debug experience compared to Common Lisp is the price paid for interop.
Anytime
It might be a problem with the relative path, is it inside a resources
folder in your project root?
what relative path are you using?
Ok, so slurp
will take a path like resources/data/myfile
, but io/resource
just takes data/myfile
(iirc)
I'm doing (require '[http://clojure.java.io :as io]) then (io/resource "data/myvile)
Hmm, ok, that should work, I think, let me double check my own code
hi. is there a dedicated channel for all online meetups that might be happenning for clojure here? #remote-meetup history is empty
found something? @U011QKW5RGF
If I wanted to build a REST API what's the go to technology? I've worked out that ring is the defacto choice for HTTP but I still need to pick things on top of it. There's a list in the wiki but most are outdated. Compojure seems relevant: https://github.com/ring-clojure/ring/wiki/Third-Party-Libraries
Luminus has a template that implements swagger out of the box. https://luminusweb.com/docs/services.html. I’m learning that this kind of thing seems to be what Clojure’s web stacks are aimed at, and less at routine CRUD + progressively enhanced server-side rendering stuff that would probably be better off done with rails.
Thanks. Luminus looks like it has a lot of answers for most web development needs... quite a few steps above these other libraries. I might keep it simple and stick to the reitit and find a problem to solve then build on things from there
https://github.com/metosin/compojure-api is a project template for building an API in Clojure
What else is relevant? How can I find relevant libraries? I've found https://www.clojure-toolbox.com/ but the categories are wide
I think if you’re familiar with compojure you’ve got a good basis for looking at the other alternatives. I’m using reitit right now, and I like it, but it hasn’t been around as long as compojure
On the plus side, it does have built-in support for swagger, which could be helpful in building a REST API.
I'm trying to create a datascript connection, but i always get the error no namespace d.
What am i doing wrong here....
(ns cs50-proj1.core
(:require [ring.adapter.jetty :as webserver]
[ring.handler.dump :refer [handle-dump]]
[ring.util.response :refer [response]]
[ring.middleware.reload :refer [wrap-reload]]
[compojure.core :refer [defroutes GET]]
[compojure.route :refer [not-found]]
[datascript.core :as d]))
(def conn (d/create-conn))
how are you loading the namespace? have you reloaded since adding the datascript dependency?
i just added it as a dependency in my http://project.cl
(defproject cs50-proj1 "0.1.0-SNAPSHOT"
:description "a book database exercise from CS50"
:url ""
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url " "}
:dependencies [[org.clojure/clojure "1.10.1"]
[ring "1.8.0"]
[compojure "1.6.1"]
[hiccup "1.0.5"]
[datascript "0.18.11"]]
:repl-options {:init-ns cs50-proj1.core}
:main cs50-proj1.core
:profiles {:dev
{:main cs50-proj1.core/-dev-main}})
you need to restart your entire repl if it's new in project.clj
(and reloading the namespace should hve errored)
project.clj "doesn't exist" in a repl - it's a config that decides (among other things) how a repl is started
ok, thats the main keypoint...(how its started)
any changes i make in the http://project.cl
right?
not just reload - restart
but yes
tks a lot^^
I only clarify that because there's things like (require 'some.ns :reload)
and (wrap-reload)
that are about seeing new changes to your own project source files, but those don't make project.clj changes visible
ok, noted
@jose.ferreira.ptm2 There are tools that let you load new dependencies into an already running REPL so you can avoid that restart -- but they are not exactly beginner-level tools and have several caveats.
If you use Boot, you'll find that it has built-in functionality to load new dependencies into its REPL. That functionality is also available for CLI/`deps.edn` but relies on a branch of tools.deps.alpha
(this is what I use for adding new deps while I'm working). I expect there are plugins for Leiningen, or some code you find online that would expose the dependency-loading machinery, so you could also do it with lein repl
but I haven't used Leiningen for years now so I'm not up to date on the latest options there.
I have recently found https://github.com/igrishaev/etaoin Pure Clojure Webdriver protocol implementation and it just blew my mind. I always hated writing(and maintaining) Phantomjs/Selenium tests because it is rather hard to write & debug them in traditional(non-lisp) languages. But having real-time access to drive such specs from IDE/REPL is so much easier. The higher the abstraction level of spec the more benefit you get from REPL driven development. Clojure definitely sparks joy
We went with clj-webdriver because that was the most mature years ago when we started writing "browser-level" tests but it's no longer maintained so I've looked at etaoin a couple of times and it does look really good.
At this point tho', our front end team maintain their own tests and everything's in JS there, and our webdriver test suite is shrinking as we convert legacy apps to JS/Clojure apps (and so the front end team takes on more of that work).
We also use HtmlUnit for one application and drive that from Clojure. I haven't spent much time with those tests but, on the surface, etaoin looks much nicer.
I hope the author of etaoin wouldn't lose the interest in maintaining it because all those "bug reports" in Github issues could be really discouraging. For some reason the author ignored donation offers https://github.com/igrishaev/etaoin/issues/183
Yeah, that's always a risk with projects such as this -- and often it's not so much a loss of interest but more often employment changes and the maintainer ends up using a different tech instead.
I have given up maintaining a number of projects in the tech I used before Clojure. I maintained them for a few years after I moved on, but it gets harder and harder when you're no longer a user of your own projects.
It's how I ended up maintaining CongoMongo, clj-time, and HoneySQL -- the original maintainers moved on but I was using those projects and had a vested interest in maintaining them. Then we stopped using MongoDB at work so I stepped away from CongoMongo. Joda Time (what clj-time is based on) has been deprecated so clj-time is also deprecated (so my maintenance work there is nominal, and we're actively moving away from clj-time at work).
@seancorfield I'm using leiningen for now. It's good to know that there are ways to 'hot reload' deps, but for now, it's really not a problem for me. I'll keep things slow to not get to much overwhelmed...^^
I've noted that the community seems to be parting from Leiningen in favor of CLI
do you feel this is the case? should i start using CLI as well? are they exact matches, or is there an advantage to lein in some cases?
I went from lein
to boot
(2015) to CLI/`deps.edn` (2018) and I prefer the simplicity of the latter. lein
is definitely easy but it is not simple. Given that CLI/`deps.edn` is the official offering for running Clojure programs, and is well-documented on http://clojure.org and well-supported, I think anyone new to Clojure should learn it.
But the vast majority to books and tutorials will remain based on Leiningen for a long time since it was the first tool for running Clojure.
I'll take a look at CLI/deeps.edn for the next project i start
if its the official way, eventually everything will gravitate towards that
Could anyone help me print the error message from the try catch block on the else clause?
(defn h []
(if-let [k (try (/ 1 0) (catch Exception e nil))]
(+ k 1)
e))
currently if i try to run this bit i get Unable to resolve symbol: e in this context, which i guess is expected since the namescope would be in the try blocke is only accessable inside the catch block, maybe you want to return it, or wrap the entire body and not just the division and return e there
for example (try (+ (/ 1 0) 1) (catch Exception e e))
(where I assume in real code (/ 1 0) is something more interesting using local bindings)
or (let [k (try (/ 1 0) (catch Exception e e))] (if (number? k) (+ k 1) k)
no, but you can look at the call stack
user=> (seq (.getStackTrace (Thread/currentThread)))
([java.lang.Thread getStackTrace "Thread.java" 1606] [user$eval147 invokeStatic "NO_SOURCE_FILE" 1] [user$eval147 invoke "NO_SOURCE_FILE" 1] [clojure.lang.Compiler eval "Compiler.java" 7177] [clojure.lang.Compiler eval "Compiler.java" 7132] [clojure.core$eval invokeStatic "core.clj" 3214] [clojure.core$eval invoke "core.clj" 3210] [clojure.main$repl$read_eval_print__9086$fn__9089 invoke "main.clj" 437] [clojure.main$repl$read_eval_print__9086 invoke "main.clj" 437] [clojure.main$repl$fn__9095 invoke "main.clj" 458] [clojure.main$repl invokeStatic "main.clj" 458] [clojure.main$repl_opt invokeStatic "main.clj" 522] [clojure.main$main invokeStatic "main.clj" 667] [clojure.main$main doInvoke "main.clj" 616] [clojure.lang.RestFn invoke "RestFn.java" 397] [clojure.lang.AFn applyToHelper "AFn.java" 152] [clojure.lang.RestFn applyTo "RestFn.java" 132] [clojure.lang.Var applyTo "Var.java" 705] [clojure.main main "main.java" 40])
Callers can attach metadata using with-meta
Is https://github.com/cognitect-labs/test-runner the best way to run tests (using clojure.test) if you're using deps.edn?
it's one option. another option that you can consider is https://github.com/lambdaisland/kaocha
what specific object do you have in mind, when you talk about attaching metadata? a function call evaluates to some result, which may or may not be able to support metadata being attached
I don't understand the mechanics you are looking for either - would it be adding metadata to a function argument, to a function return value?
the thread always knows the current call stack, as I demonstrated above
you'd need to do a bit of parsing / interpreting to get the real info, but it's all there
(if the thread didn't know the call stack, your function wouldn't be able to return to its caller)
Mostly the things you can add metadata to are collections or symbols. (More precisely, any objects that implement the IObj
interface, if I recall correctly, but most of those are the Clojure collections and symbols. Metadata cannot be added to much else, but that may be all you need.)
Trying to squash a reflection warning on .getTimeInMillis
(from java.util.Calendar) but adding a type hint isn't allowed because obviously Can't type hint a local with a primitive initializer
. Any suggestions for getting rid of the reflection?
what did you put the hint on?
You should paste that code with hint
that should be fine
kcmds)user=>(#(.getTimeInMillis %) (java.util.Calendar/getInstance))
Reflection warning, NO_SOURCE_PATH:1:3 - reference to field getTimeInMillis can't be resolved.
1587673121787
(ins)user=> (#(.getTimeInMillis ^java.util.Calendar %) (java.util.Calendar/getInstance))
1587673131884
that error should go with a primitive, I don't see how it would apply to what you are doing there
are you sure the error isn't related to the Long hint?
that's the only hint I see there that clojure would derive a primitive from, and the compiler claims the error is related to a primitive
Well now that's interesting. It will compile if I remove either of those type hints. So it works fine if only the ^Long type hint is there. Same with ^Calendar. Oh well, since ^Long can be inferred I'll remove it. Strange that it wouldn't throw that exception when only the Long hint is there though, no??
it could be that it would prefer to compile a primitive long
rather than a boxed Long
so the inference on the method and your annotation are in conflict
Interesting! Thanks @noisesmith!!
try
(let [time ... extract and hint Calendar
msec ... get millis and hint])
the hint would cause a runtime crash if the types didn't match, but no compilation error
@U011LEAEURE Thanks for the suggestion, but the issue is at eval time, not run time. The entire form fails to evaluate if both hints are present, and works if just one or the other is present.
You dont need ^Long
hint since .getTimeInMillis
returns primitive long... compiler knows that because you specified ^java.util.Calendar
Thanks, yeah. I've read that SO entry. The thing I couldn't figure is that it doesn't complain about ^Long by itself unless ^Calendar is also there. But @noisesmith gave a good suggestion on that front. Tks!
I've never had this problem... Not hinting much 😛
I'm having a go-loop
which works fine, and which has an and-condition. However, when I run from the command line clj -m hosa
, the process doesn't terminate:
(defn start-capturing [message-ch]
(let [...
(go-loop [wait-seconds (rand-int 5)]
(let [action (alt!
(timeout (* 1000 wait-seconds)) :timeout
message-ch ([m] m))]
(let [...
...
(if (= action :stop)
(do (println "stop!")
(async/close! message-ch))
(recur (rand-int 5)))))))
(println "loop started"))
(defn -main [& _]
(let [message-ch (chan)
stop-fn #(>!! message-ch :stop)]
(start-capturing message-ch)
(async/go
(async/<! (timeout 30000))
(stop-fn))
))
So this will so some stuff (I removed the uninteresting parts with ...
) every max 5 seconds. After 30 seconds. It should stop, but the process doesn't terminate. What am I missing?oh well, just adding (System/exit 0)
works fine. Not sure if that's a proper way, though.
if you use clojure's built in thread pools you should use shutdown-agents
though calling System/exit explicitly is a workaround
(nb. don't use shutdown-agents unless you are trying to make the vm exit, it will cause strange errors otherwise)
there are two different operations here - System/exit
means you want the vm to exit, and don't care what other threads are doing right now
shutdown-agents
means that the vm can exit once all non-daemon threads (including futures / go blocks etc.) exit, but nobody is forcefully shut down
if you don't use shutdown-agents, you'll still get an exit after 30 seconds, as seen above, it's just that that timeout passes before the threads are fully reclaimed so exit can happen
Yes, that's true. shutdown-agents
will not exit immediatly, it will wait for all tasks already sent to the threads to complete first, while System/exit will quit immediatly
Hey I was curious curious if anyone could tell me the advantages to learning clojure over java? Trying to figure out what to learn. Do I need to learn Java first to learn Clojure? Does Clojure have disadvantages to Java? I know a little Python already (I know they're not related just providing context to my knowledge)
You do not need to learn Java in order to learn Clojure. It is fairly common when using Clojure for projects, that one will need to know how to read the API documentation for how to use one or more Java libraries that do useful things related to your project.
So at least having a basic working knowledge of what Java means by terms like class, interface, method, primitive types, are good things to know. You certainly don't need to know enough to design a Java class library, or develop a full Java application.
I think an advantage of Clojure over Java is that Clojure is a much simpler language than Java (while still having access to all the Java libraries) and because it has immutable data structures, there are a lot of errors you can make in Java that can't happen in Clojure (especially if you start getting into concurrent code).
A "disadvantage" of Clojure compared to Java is that it's more of a niche language so there are fewer jobs and less documentation/books/etc.
On the other hand, there are some fantastic books and code examples for ancestors of Clojure, e.g. PAIP, SICP etc.
then there's the fact that "simpler" doesn't mean "easier" if you already know languages descended from Algol. Clojure's ancestors are more obscure so learning Clojure is harder if you already know how to program in a mainstream language. People will argue this is a benefit :D
but it's definitely an upfront cost for people coming from mainstream languages