beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
João Galrito 2020-09-23T02:00:50.000800Z

what is the best practice to keep a thread running inside a with-open block

seancorfield 2020-09-23T02:02:24.001500Z

You'd have to wait for it to complete inside the with-open block.

seancorfield 2020-09-23T02:02:54.002200Z

Otherwise, the resource the thread is using could be closed before it was done.

João Galrito 2020-09-23T02:03:21.002800Z

I'm aware of that, the question was more on which mechanism to use for the waiting

João Galrito 2020-09-23T02:03:31.003100Z

i'm spawning 2 threads inside the with-open

João Galrito 2020-09-23T02:03:48.003400Z

future + deref?

seancorfield 2020-09-23T02:04:28.004100Z

There are lots of ways to do it. Hard to say what's best without knowing a bit more about exactly what you're doing.

João Galrito 2020-09-23T02:05:10.005200Z

i'm opening a stream, reading from it into a buffer on one thread, and the other is locked waiting for a signal to start processing from the buffer

seancorfield 2020-09-23T02:05:52.006200Z

future/`deref` is a simple but fairly uncontrolled way to do it. Using Java threads directly or an executor or... all sorts of possibilities depending on how much control you wanted over this.

João Galrito 2020-09-23T02:06:47.006400Z

control in terms of?

João Galrito 2020-09-23T02:07:45.006900Z

these threads should be running in the background

João Galrito 2020-09-23T02:08:09.007300Z

with no interaction other than the data they're receiving and processing

seancorfield 2020-09-23T02:13:02.008700Z

Per the docstring: "Sets ns to the namespace named by the symbol, creating it if needed."

seancorfield 2020-09-23T02:13:28.008900Z

So it will create a completely empty namespace if it doesn't already exist. It won't have any clojure.core functions referred in.

2020-09-23T02:13:46.009100Z

Is there a usecase for that? I'd hate to be derailed by an errant typo

2020-09-23T02:14:07.009300Z

Ahh, hmm so how does it differ from ns then?

seancorfield 2020-09-23T02:15:28.009500Z

ns does a whole bunch of stuff: creates the ns if needed, refer in clojure.core and a bunch of other stuff, requires specified namespaces, imports specified classes, etc.

seancorfield 2020-09-23T02:15:40.009700Z

in-ns is very low-level.

2020-09-23T02:15:41.009900Z

Wait, you meant *ns* right?

2020-09-23T02:15:45.010100Z

I see, I see

seancorfield 2020-09-23T02:16:31.010300Z

I mean the ns macro. Not *ns*. The latter is a dynamic Var.

user=> (in-ns 'foo.bar)
#object[clojure.lang.Namespace 0xdf5f5c0 "foo.bar"]
foo.bar=> (ns-publics *ns*)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: ns-publics in this context
foo.bar=> (clojure.core/ns-publics *ns*)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: *ns* in this context
foo.bar=> (clojure.core/ns-publics clojure.core/*ns*)
{}
foo.bar=> (clojure.core/ns-refers clojure.core/*ns*)
{}
foo.bar=> (clojure.core/ns-aliases clojure.core/*ns*)
{}
foo.bar=> 

2020-09-23T02:17:12.010500Z

BTW I saw your post earlier this year on whether or not switching to deps.edn is a good idea and it's the main reason I'm trying to do it now. Thanks!

seancorfield 2020-09-23T02:17:23.010700Z

See how I have to specify clojure.core/ in front of various symbol names? Normally -- when you use the ns macro -- those are automatically referred in.

1👍
2020-09-23T02:17:33.010900Z

I mean in your first reply, since *ns* appears as ns

seancorfield 2020-09-23T02:18:11.011200Z

Oh, this? Per the docstring: "Sets `*ns*` to the namespace named by the symbol, creating it if needed." Yeah, I just pasted in the docstring and forgot it would auto-format that!

1
João Galrito 2020-09-23T02:19:18.011900Z

(defn watch-collection [collection-name signal]
  (println (str "Waiting for changes in " collection-name))
  (with-open [watch-cursor (.watch (get-collection collection-name))]
    (let [buffered-channel (async/chan (async/sliding-buffer 100))]
      (map deref [(future (-> watch-cursor
                              .iterator
                              iterator-seq
                              (map #(async/>! buffered-channel %))))
                  (future (do (async/<! signal)
                              (loop []
                                (when-let [change (async/<! buffered-channel)]
                                  (println change)
                                  (recur)))))]))))

seancorfield 2020-09-23T02:19:35.012200Z

map is lazy so that is not safe.

João Galrito 2020-09-23T02:19:50.012700Z

need a dorun after

seancorfield 2020-09-23T02:19:56.012900Z

Safer to use run! there since you want the side-effects.

João Galrito 2020-09-23T02:20:00.013200Z

ah yes

seancorfield 2020-09-23T02:20:04.013400Z

(run! defer [...])

2020-09-23T02:20:08.013500Z

Just checked ClojureDocs, I should probably check it more often. In any case, thanks for the detailed responses! Really starting to get the hang of this thing.

João Galrito 2020-09-23T02:20:22.013900Z

other than that

João Galrito 2020-09-23T02:20:27.014300Z

would something like the above work?

seancorfield 2020-09-23T02:20:30.014400Z

Don't forget you can use doc and source right there in your REPL.

ghadi 2020-09-23T02:20:38.014900Z

No. You cannot do <! outside a go block

ghadi 2020-09-23T02:20:47.015200Z

Or >!

João Galrito 2020-09-23T02:20:53.015300Z

ah right

João Galrito 2020-09-23T02:21:18.016100Z

i'm calling (go (watch-collection

ghadi 2020-09-23T02:21:18.016200Z

doseq + !>

João Galrito 2020-09-23T02:21:30.016700Z

should the go go inside the function body?

João Galrito 2020-09-23T02:21:49.017600Z

or use another mechanism

ghadi 2020-09-23T02:22:04.018200Z

go blocks only work on immediate lexical scope; the call inside #() is a new scope

2020-09-23T02:23:15.018800Z

Oh yeah, that's really helpful. Should probably get used to them.

ghadi 2020-09-23T02:23:54.019900Z

Consider letting the caller of this function supply a channel onto which changes are published

seancorfield 2020-09-23T02:24:31.021Z

(I always cringe a bit when I see beginners trying to use core.async 👀 -- it is really not easy to learn/use and there are so many ways to shoot yourself in the foot with it...)

João Galrito 2020-09-23T02:25:09.021500Z

🙃

seancorfield 2020-09-23T02:25:11.021700Z

It's always felt like one of those "Here Be Dragons!" things in Clojure to me...

seancorfield 2020-09-23T02:26:06.022300Z

I have doc bound to ctrl-; d and source bound to ctrl-; S in my editor so they are easily available 🙂

João Galrito 2020-09-23T02:26:48.023300Z

@ghadi but both the pushing and pulling from the channel are happening inside this function

seancorfield 2020-09-23T02:26:58.023400Z

(aside from helping beginners with REPL sessions like the above, I never actually type into my REPL -- I always type into a source file, sometimes in a comment form, and then eval code directly into the REPL from the editor)

1
ghadi 2020-09-23T02:27:28.024200Z

I am reading on my phone and missed the context... sorry! What is it you are doing?

João Galrito 2020-09-23T02:27:43.024400Z

@ghadi i'm opening a stream, reading from it into a buffer on one thread, and the other is locked waiting for a signal to start processing from the buffer

João Galrito 2020-09-23T02:29:21.025400Z

and I need to keep the threads alive inside the with-open block

João Galrito 2020-09-23T02:29:59.025900Z

@seancorfield well I'm not one to shy away from a new challenge 🙂 that's why I came to clojure

João Galrito 2020-09-23T03:11:49.029500Z

@ghadi my current attempt

(defn watch-collection [collection-name signal]
  (println "Start watching" collection-name)
  (with-open [watch-cursor (.watch (get-collection collection-name))]
    (let [buffered-channel (async/chan (async/sliding-buffer 1000))] ;buffer size might need tweaking
      (run! #(async/&lt;!! %) [(async/thread
                              (-&gt;&gt; watch-cursor
                                   .iterator
                                   iterator-seq
                                   (run! #(async/&gt;!! buffered-channel %))))
                            (async/thread
                              (async/&lt;!! signal)
                              (println (str "Waiting for changes in " collection-name))
                              (loop []
                                (when-let [change (async/&lt;!! buffered-channel)]
                                  (println collection-name change)
                                  (recur))))]))))

Nazar Trut 2020-09-23T03:27:04.031100Z

I need help, For example, if my sequence is '((if a b)(if a c), and i want to find "if" and then "b" and want to return true because i found "b" with a "if", would i use some or filter?

Nazar Trut 2020-09-23T03:28:47.032100Z

(filter #(and (sequential? %) (= (first %) 'if) (= (second %) expr)) kb)
For example with this code, how would i change (second %) so it looks for the third index of a sequence?

seancorfield 2020-09-23T03:29:07.032500Z

What do you mean by "third index of a sequence"?

seancorfield 2020-09-23T03:29:14.032900Z

Sequences are not indexed.

Nazar Trut 2020-09-23T03:29:19.033100Z

(if a b), i want to look at b

seancorfield 2020-09-23T03:30:39.033700Z

Instead of (second %) you want (nth % 2)

seancorfield 2020-09-23T03:31:02.034200Z

Or (second (rest %)) would also work

Nazar Trut 2020-09-23T03:31:30.034700Z

I was doing (nth 2 %) the whole time

Nazar Trut 2020-09-23T03:31:32.035Z

my bad

João Galrito 2020-09-23T03:32:06.035500Z

a lot of the functions that manipulate colls take them as the last arg

seancorfield 2020-09-23T03:32:55.036100Z

@ntrut you need to get used to doing things like (doc nth)

seancorfield 2020-09-23T03:33:37.036800Z

@joao.galrito No, sequence functions take the sequence as the last argument. Collection functions take the collection as the first argument.

João Galrito 2020-09-23T03:34:30.037600Z

oh ok

João Galrito 2020-09-23T03:34:37.038Z

I still mix the two a bit

seancorfield 2020-09-23T03:34:42.038200Z

Most of the time, if the first argument is a collection, the function will have some specific guarantees about performance and/or return type.

seancorfield 2020-09-23T03:35:22.039100Z

If the last argument is a sequence, and you pass a collection, it will be coerced to a sequence first. For example (map inc [1 2 3]) will call seq on the vector and then process it as a sequence.

Nazar Trut 2020-09-23T03:37:33.039600Z

(filter #(and (sequential? %) (= (first %) 'if) (= (second (rest %) ) 'b)) '(if a b))
returning nil, any ideas why?

Nazar Trut 2020-09-23T03:38:27.040100Z

@seancorfield thanks for the help btw

Nazar Trut 2020-09-23T03:38:34.040500Z

i owe you guys

seancorfield 2020-09-23T03:39:01.041100Z

Because you're passing an expression instead of a list of expressions (again 🙂 )

seancorfield 2020-09-23T03:39:26.041800Z

filter expecs ( (if a b) ) remember @ntrut?

João Galrito 2020-09-23T03:39:43.041900Z

it's calling sequential? on 'if, then 'a, then 'b

Nazar Trut 2020-09-23T03:41:30.042200Z

Oh i see my mistake now

seancorfield 2020-09-23T03:47:28.043500Z

@ntrut Some suggested viewing for you https://www.youtube.com/watch?v=Qx0-pViyIDU and https://www.youtube.com/watch?v=FihU5JxmnBg -- two talks on debugging techniques by Stuart Halloway of Cognitect. I think you'll find they'll help you figure out some of the problems you are running into.

seancorfield 2020-09-23T03:48:15.044100Z

(you might also want to track down Stu's "REPL-Driven Development" talk as well)

teodorlu 2020-09-23T06:30:37.044700Z

I don't see anything that's wrong. Do you observe your (println "Inserted " (count operations)) output?

teodorlu 2020-09-23T06:31:31.044900Z

> anyway in my case I can take advantage of parallelism so ended up using `pmap` and `dorun` Looks like you worked it out!

2020-09-23T08:55:09.045300Z

Maybe you want a default result:

(defn judge [x] 
  (get {true 1 false 0} x -1))

2020-09-23T08:56:36.045500Z

maps have the same interface as get) ({true 1 false 0} x -1) also will give you default value

2020-09-23T08:58:47.045700Z

Thank you @delaguardo, I have read from somewhere that only get can append a default value.

Mark Wardle 2020-09-23T12:02:13.049200Z

Hi all. Any recommendations for simple websockets client and server implementations in clojure on the jvm for both client and server? Nothing fancy, no need for fallbacks etc. Needed as a way of linking two servers to exchange information where firewalls block other types of socket and bidirectional flow would be better than polling.

João Galrito 2020-09-23T12:04:17.049500Z

https://github.com/ptaoussanis/sente

João Galrito 2020-09-23T12:04:28.049800Z

never used it, just saw it yesterday somewhere

João Galrito 2020-09-23T12:05:56.050Z

not sure if that's what you need

Mark Wardle 2020-09-23T12:08:53.050200Z

Thanks I have looked at it but as I’m not an expert in this, on first inspection it has more functionality than I think I need. Of course, in its favour, it does seem to be the top hit on search - and is maintained, and the API looks good!

João Galrito 2020-09-23T12:12:47.050400Z

in the README there's also a couple other alternatives he recommends

Mark Wardle 2020-09-23T12:21:43.050600Z

Thanks. I’ve seen a variety of options including sente, direct http-kit usage - although the examples / docs use now deprecated methods, immutant, aleph, and I’ve looked at wrapping java libraries directly (e.g. Java-WebSocket) which actually I think was just added to sente for the java client bit. Up until then, it looks as if the main tooling was cljs for client and clj for server, because of the browser focus, but websockets now being used more and falling back to AJAX (as per sente) and starting connection using HTTP POST isn’t necessary for my use case. Hence just asking for advice. Thank you

jacklombard 2020-09-23T12:35:10.051300Z

Is there an any type in lacinia that I can use while defining the schema?

João Galrito 2020-09-23T13:07:33.052Z

when using Cursive, do I only get method name resolution if I import the corresponding classes?

Test This 2020-09-23T14:31:35.055200Z

Are there any simple examples of using reitit, ring and http-kit where some routes are regular routes and a websocket route?

teodorlu 2020-09-23T14:33:08.055300Z

Not sure if you'll find an exact match, but the reitit repository provides plenty of examples: https://github.com/metosin/reitit/tree/master/examples

Test This 2020-09-23T14:36:36.055500Z

May be I am not seeing properly. But none of them are with websockets. Is reitit capable of routing websocket requests? Thank you.

Test This 2020-09-23T14:39:50.055700Z

@wls What do you use for websockets? Which routing library and server are you using? Thank you.

ikitommi 2020-09-23T14:54:38.056100Z

haven't seen a public example, but we have been using reitit + ws since day1. Just mount a ws-endpoint into a route like you would do with any other routing lib, like compojure. Sente has examples of this

Test This 2020-09-23T15:15:08.056300Z

Thank you @ikitommi. Can you please see the ws route below. I understand that the route is not set up to handle websocket connections. Also when I visit using the url using a regular browser window, I know it will fails. But I expect the httpkit/websocket? to work as described on page <https://http-kit.github.io/http-kit/org.httpkit.server.html#var-websocket.3F> Would that now print out`false`. But instead I get an error saying: java.lang.IllegalArgumentException: No implementation of method: :websocket? of protocol: #'org.httpkit.server/Channel found for class: clojure.lang.PersistentHashMap. I am very new to clojure. Just trying to figure out what to use for webapps. Thank you. My code:

(ns sampleapp 
  (:require
   [reitit.ring :as ring]
   [ring.middleware.params :as params]
   [ring.middleware.multipart-params :as multparams]
   [org.httpkit.server :as httpkit]
  ;  [ring.adapter.jetty :as jetty]
  ;  [ring.adapter.jetty9 :as jetty]
    )
  (:gen-class))

(defn wrap [handler val]
  (fn [request]
    (let [currval (get request :val [])]
      (handler (assoc request :val (conj currval val))))))



(defn jerryhandler [req]
  (println (str "Wrapped value: " (:val req)))
  {:status 200
   :headers {"Content-type" "text/plain"}
   :body (str "Hello Jerry!!\nI got values " (:val req))})

(defn busterhandler [req]
  (println (str "Parameters: " (:params req)))
  {:status 200
   :headers {"Content-type" "text/plain"}
   :body "Hello Buster!!"})

(defn fileupload [req]
  (println (str "File info: " (get-in req [:params "file"])))
  (let [fileobj (get-in req [:params "file" :tempfile])
        pathtofile (.getAbsolutePath fileobj)
        contents (slurp fileobj)]
    (println contents)
    (println pathtofile)
    {:status 200
     :headers {"Content-type" "text/plain"}
     :body "File uploaded..."})
  )

(defn wshandler [req] 
  (println (httpkit/websocket? req))
  {:status 200 :headers {"Content-type" "text/plain" :body "Not valid ws request..."}})

(def router
  (ring/router 
   [["/hellojerry" {:get {:middleware [[wrap 1] [wrap 2]] :handler jerryhandler}}]
    ["/hellobuster" {:get (params/wrap-params busterhandler)}]
    ["/fileupload" {:post (multparams/wrap-multipart-params fileupload)}]
    ["/ws" wshandler]]
   ))

(def app
  (ring/ring-handler
   router
   (constantly {:status 404 :body "Not found!"}))
  )


(defn -main
  [&amp; args]
  (httpkit/run-server app {:port 3000})
  ; (jetty/run-jetty app {:port 3000 :h2? true}))
)

  

Test This 2020-09-23T15:19:09.057800Z

Does https://github.com/ptaoussanis/sente require using it for both server and client, or can you use it only on the server with standard Javascript websocket on the client? Thank you.

ikitommi 2020-09-23T15:21:54.058Z

I believe you need to pull out the channel linked to the request to get web-sockets with http-kit. There seems to be an example here: https://http-kit.github.io/server.html#channel

Test This 2020-09-23T15:37:47.058300Z

Thank you @ikitommi. I believe the linked approach is deprecated. However, the example given here: https://http-kit.github.io/http-kit/org.httpkit.server.html#var-as-channel, works well. It appears that my mistake was using (httpkit/websocket?, when, in fact, I was supposed to be using just (`:websocket?` ).

practicalli-john 2020-09-23T15:48:09.058500Z

Compojure is the most common library for routing on the server side. reitit is a new project that takes a data driven approach to routing and other aspects of webapps. Its very interesting, but has more of a learning curve. Http-kit can do we sockets, although not something I've used recently

phronmophobic 2020-09-23T16:36:08.058800Z

I'm not generally against having helper functions that provide a way to explicitly list what the common operations are, but I would warn against pretending that it "encapsulates" your data. I also wouldn't start with defining getters/setters and would only consider adding them on a case by case basis. In my experience, getters/setters provide little benefit for data at rest or being sent over the network which is the important part to get right for maintaining long running programs and systems To address "what keys?" and "what entity?", I would just use spec. As he mentions in the talk, it's meant to address these problems. The big difference between spec's approach and defining getters/setters is that spec is data focused and getters/setters are operations focused. The differences between a data specification and an operational specification are really critical. Most programs would be better served by focusing on the data than on the operations. Depending on your domain, it may be useful to use something like specter or getters/setters to help declutter deeply nested code. In my experience, there are many domains where an extra layer of abstraction is unnecessary. I really wish computer science would be renamed data-ology.

2020-09-23T16:58:02.059Z

i think this is an interesting discussion. i don't have anything to add except to note that re-frame basically has getters and setters on a big map (`app-db`) in the form of subscriptions and events. for a while i really felt like i was back in oop land, writing a getter and setter for each piece of data in app-db. in that context, i do think there's something to be said for separating out the data from "exactly where in this huge nested map does it belong"

2020-09-23T16:58:28.059200Z

i've run into many snags trying to reorganize the structure of app-db and unknowingly breaking code that depended on that exact configuration

phronmophobic 2020-09-23T17:05:00.059500Z

I think if you're running into "what's the shape of the data supposed to be?", I would reach for spec or defining the data specification rather than getters/setters

1👍
phronmophobic 2020-09-23T17:05:39.059800Z

it would be great to hear other's thoughts on the subject though

1👍
Test This 2020-09-23T17:16:37.060200Z

Hi Mark, I am very new to clojure, so this is not coming from any experience in production. But I am in the same boat as you. My experiments suggest that reitit ring and http-kit can make for a fairly easy setup.

Clypto 2020-09-23T17:37:41.060600Z

there are examples for connecting other types of infrastructure https://github.com/theasp/sente-nodejs-example

Clypto 2020-09-23T17:38:14.060900Z

if you wanted a non-clojure or non-clojurescript participant, you'd have to conform to the message passing. It looks like they do have some emulation on web sockets to make up for web sockets short comings, so that may be a little tricky

Walt Stoneburner 2020-09-23T17:56:44.061200Z

@curiouslearn, I'm using aleph.http/websocket-connection ... I had to do a little fiddling, though.

Mark Wardle 2020-09-23T18:35:51.063600Z

Thanks. I have an aleph client and server talking nicely to one another - but there’s a lot of new stuff/patterns to learn for me to fully understand how it’s working! My comparison is a golang based library : http://nhooyr.io/websocket

Test This 2020-09-23T19:27:03.064200Z

Thank you @jr0cket. Appreciate the input. Also, thank you @wls. Will look into it; although it looks difficult; I have no idea what a manifold is.

Nazar Trut 2020-09-23T19:39:35.065400Z

What is a way that i can check if there is two "nots" in my sequence like this '(not (not b)), so therefore, if there is two "not" like this example.

Nazar Trut 2020-09-23T19:40:07.065800Z

(not(not a)) would be true

Nazar Trut 2020-09-23T19:40:15.066100Z

(not a) would be false

seancorfield 2020-09-23T19:52:42.067900Z

I would define a predicate that returned true if an expression was (not X) and then you can do (and (has-not? expr) (has-not? (second expr))) if you make has-not? robust (check for sequential expr and then check first item is 'not)

practicalli-john 2020-09-23T21:47:18.068200Z

Manifold is a very interesting approach, especially when you dont want to be tied to the classic web server approach. However, there is more of a learning curve compared to ring and compojure. If you are creating API's then there is the compojure-api, https://github.com/metosin/compojure-api/ which includes swagger for documenting the API automatically. Luminus is a very useful template for web apps and full stack apps https://luminusweb.com/

2020-09-23T23:18:39.070600Z

clojure collections are not falsey. in another dynamic language i might test for the presence or absence of a given value of an unknown type with if . is there a conventional way to do this in clojure? so far i have (or (nil? x) (and (seqable? x) (empty? x)))

phronmophobic 2020-09-23T23:21:32.071100Z

I think you can just do (if (empty? x) ...)

2020-09-23T23:21:42.071500Z

should i use (seq x) instead of (empty? x) ?

2020-09-23T23:21:54.071600Z

that throws on integers

2020-09-23T23:22:43.071800Z

i'm reminded of the little schemer where the first question you always ask is atom?...

phronmophobic 2020-09-23T23:23:13.072Z

indeed. what are you trying to do?

2020-09-23T23:24:18.072200Z

i'm writing a general purpose "is this var value missing?" predicate for use with values of an unknown type

2020-09-23T23:24:33.072400Z

maybe i shouldn't be doing that...

2020-09-23T23:25:39.072600Z

i could do additional work to bring through the type, i suppose

Test This 2020-09-23T23:25:53.072800Z

@jr0cket, thank you very much for the pointers. I bought the second edition of the Luminusweb book a couple of years ago. However, it has too much magic for my taste. I usually don't like when lot of files are created for me in the source code. Will look into compojure-api. But so far reitit with http-kit seems a good option. I hope that http-kit continues to be maintained.

phronmophobic 2020-09-23T23:27:11.073100Z

trying to define a universal "missing?" predicate always reminds of https://bugs.python.org/issue13936

2020-09-23T23:29:17.073300Z

ok, maybe i should rework such that i'm starting with a map where absence is represented by a missing key

Test This 2020-09-23T23:29:34.073500Z

@jr0cket, Thank you so much for this. I was looking for a better repl than the standard one. I believe your deps.edn has that. Need to copy from there into Sean's that I downloaded 🙂. Will look into Kaocha too. Never heard of that before.

2020-09-23T23:29:43.073700Z

that would be more clojuristic

2020-09-23T23:29:45.073900Z

maybe

phronmophobic 2020-09-23T23:30:28.074100Z

basically, it's highly context dependent and it might be ok if you're defining the context, but it's trouble if you're defining the context for someone else and may be confusing for others to have to figure out what your definition of "missing" is

1👍
phronmophobic 2020-09-23T23:31:26.074300Z

I much prefer clojure's approach compared to something like javascript: > All values are truthy unless they are defined as https://developer.mozilla.org/en-US/docs/Glossary/Falsy (i.e., except for false, 0, -0, `0n`, "", null, undefined, and NaN). 🤢

phronmophobic 2020-09-23T23:37:57.074700Z

https://www.youtube.com/watch?v=YR5WdGrpoug seems apropos

1✅
alexmiller 2020-09-23T23:38:27.075100Z

yes