Fork me on GitHub
#beginners
<
2021-08-05
>
Ruslan Glaznyov08:08:42

Hello everyone! I start to learn Clojure and I found clojure koans, I love jetBrains products. I want to run clojure koan in cursive, but when I chose start repl I get empty repl without interactive koans I want to see questions like on first screen, but I see empty repl 2 screen How can I run koans in cursive to see questions? Thanks!

delaguardo08:08:48

https://github.com/functional-koans/clojure-koans#running-the-koans there is a line in README about how to start koans from the repl. Execute (exec "run") in lower-right part of your screen and enjoy )

Stas Makarov10:08:10

Hi! I have a hard time trying to detect input file MIME type with apache tika in my web app. I'm sending file to my app with curl ... --data-binary "@/tmp/path-to-file, so it's not a multipart form. In a handler request body type is org.eclipse.jetty.server.HttpInputOverHTTP. When I'm calling (io/copy request-body (io/file '/tmp/foo')) in clj it works as expected. When I'm trying to use org.apache.tika.io.TikaInputStream on a body with tika facade it doesn't work:

...
(:import
   (org.apache.tika Tika)
   ( TikaInputStream))
... 

(def tika (Tika.))
(def tis (TikaInputStream/get body)) ;; also tried (TikaInputStream/get body)
(.detect tika tis) ;; also tried (.detect tika body); this doesn't work as expected -- doesn't return mime type as a string, but throws the exception: 
exception dump: https://gist.githubusercontent.com/jehaby/a0e37044f81f0b405284ef1aba047807/raw/724735922886cff5c2f39b961416858c02ff966c/tika-exception ps: this code works: (.detect tika "/tmp/some-file-in-fs") upd: I managed to get it working with this code:
...
  (:import
   (org.apache.tika.mime MimeTypes)
   (org.apache.tika.metadata Metadata)
   ( TikaInputStream)
... 

(def tika-mt-detector (MimeTypes/getDefaultMimeTypes))
(def empty-metadata (Metadata.))

(.detect tika-mt-detector (TikaInputStream/get body) empty-metadata)
But as I understand (TikaInputStream/get body) stores whole request body as a file in server's temp folder. I wonder is there a way to avoid this?

noisesmith18:08:26

> But as I understand (TikaInputStream/get body) stores whole request body as a file in server's temp folder. I wonder is there a way to avoid this? on a normal server system (AKA normal linux setup) /tmp is not a disk file system, it's a chunk of RAM that's treated as if it was a file system

noisesmith18:08:48

(if that has any relevance to your concern...)

Stas Makarov10:08:01

Btw: is using tika for this task (MIME type detection) the best option currently? Are there some other ways worth considering?

Jelle Licht11:08:39

In the tools.build documentation, folks refer to “function chaining at the command line” (https://clojure.org/guides/tools_build#_parameterized_build). What is meant by this? Is this a Clojure thing?

Drew Verlee12:08:39

@wordempire I assume it's referring to "piping". I suppose it's just passing the output from one function to the input of another right? That's usually something every language has. Clojure makes this very easy to do because it's encouraged and easy to use collections (they are all seqs) as that in-between medium, and the core lib has a rich set of functions for working on them.

seancorfield14:08:56

@drewverlee @wordempire For a brief while you could specify multiple functions to execute via -X and it worked by threading, as if you said (-> params (my-func) (next-func) (another-func)) where the params came from exec args and the command-line.

seancorfield14:08:46

That was only available on the prerelease branch and was removed before the stable release. So with a build script, you can only invoke one function, but it can use -> itself to call multiple functions.

🙏 3
zackteo14:08:13

Hello, is there a way to parseFloats in javscript? I have been trying js/parseFloat but that seems to return me a javascript object (?)

dpsutton14:08:56

clojure -A:cljs -M -m cljs.main -re node -r
ClojureScript 1.10.773
cljs.user=> (js/parseFloat "1.23")
1.23
cljs.user=>

dpsutton14:08:22

i hate to say it works for me, but i get back a numeric type here

zackteo14:08:00

Right - okay i'm a moderately confused as to why I can't seem to get it working when in re-frame. Guess js/parseFloat should work

dpsutton14:08:02

cljs.user=> (type (js/parseFloat "1.23"))
#object[Number]
cljs.user=> (type 1.23)
#object[Number]
cljs.user=>

zackteo14:08:09

And I did it in the repl too

dpsutton14:08:13

can you show what you are trying?

zackteo14:08:06

My example isn't amazing code but did this locally

(def test4 {:results [{:id 0 :objective-results [{:coverage-score "10.1" :p-median-score 2  :centering-score 3}]}
                      {:id 2 :objective-results [{:coverage-score "3" :p-median-score 6  :centering-score 1}]}
                      {:id 1 :objective-results [{:coverage-score "5" :p-median-score 2  :centering-score 1}]}]})

(update-in test4 [:results] (fn [e] (sort-by (comp :coverage-score first :objective-results) #(compare  (js/parseFloat
                                                                                                          %2)
                                                                                                        (js/parseFloat
                                                                                                          %1)) e)))

zackteo14:08:06

But when I do this in my app

(rf/reg-event-db
 :sort-results
 (fn [db [_ k]]
   (update-in db [:results]
             (fn [e]
                (sort-by (comp :coverage-score first :objective-results)
                         #(compare (js/parseFloat %2) ((js/parseFloat %1))) e)
               ))))

zackteo14:08:28

it doesn't seem to work

dpsutton14:08:36

did you mean to use update-in ?

zackteo14:08:01

whoops yes it was update-in was using assoc-in for testing

dpsutton14:08:02

assoc-in db path function will just stick that function there, not call it on the thing in the map

dpsutton14:08:25

and have you verified that e is what you expect?

zackteo14:08:26

Hold on hmmm

seancorfield14:08:43

((js/parseFloat %1)) looks like you're calling the floating point result there.

zackteo14:08:58

Right - think that flew in somehow as we were trying various things >< but still not the issue atm

dpsutton14:08:48

fair. but one thing it gets very hard to help people when you cannot trust that the code here is representative of code you are actually running. I don't know how you are copying and pasting things that use different functions and random parentheses

zackteo14:08:08

Let me try to think this through again first. Sorry about that

dpsutton14:08:35

no worries. trying to help of course 🙂 . Its just that if we aren't seeing code you are actually running we cannot help finding subtle issues

zackteo14:08:28

Yeap! Totally understand

zackteo14:08:48

Okay we got it working 😅

zackteo14:08:41

we were putting a function back in the db rather than the value

zackteo14:08:05

Did this in the end

(defn sort-results [results k]
  (sort-by (comp k first :objective-results)
                         #(compare (js/parseFloat %2) (js/parseFloat %1)) results))

(rf/reg-event-db
 :sort-results
 (fn [db [_ k]]
   (update-in db [:results] #(sort-results (get-in db [:results]) k))))

zackteo15:08:06

Probably need to refactor but it works for now The idea we were going for was this

(rf/reg-event-db
  :sort-results
  (fn [db [_ k]]
    (update-in db [:results]
               (fn [e]
                 (sort-by (comp k first :objective-results)
                          #(compare (js/parseFloat %2) (js/parseFloat %1)) e)))))
where I was thinking we can just feed in a function similar to how we can feed in inc ^ this doesn't work

zackteo15:08:28

But I think main issue was trying to do too much in a single update-in . probably need to dissect it a fair bit to figure out where exactly was there an issue

sheluchin15:08:34

Do I need to pay much attention to spec2 at this point? I'm presuming I should just write everything using spec1 for the time being.

dgb2315:08:51

Here’s something I don’t quite get. From the docstring of empty? :

Please use the idiom (seq x) rather than (not (empty? x))
And from the style guide at https://guide.clojure.style/#nil-punning > Use seq as a terminating condition to test whether a sequence is empty (this technique is sometimes called nil punning). I don’t quite get it! What is the benefit here? This seems to obscure the intent.

sheluchin15:08:56

If you look at the implementation of empty?:

(defn empty?
  "Returns true if coll has no items - same as (not (seq coll)).
  Please use the idiom (seq x) rather than (not (empty? x))"
  {:added "1.0"
   :static true}
  [coll] (not (seq coll)))
You'll see that (not (empty? x)) is equivalent to (not (not (seq x))). The double-negative just cancels itself out.

dpsutton15:08:30

i used to butt up against this one. But now i see calls to seq as much more natural

jumar15:08:33

Many people disagree with that advice and prefer (not (empty?)) Use what you like more and perhaps stay consistent

☝️ 3
dgb2315:08:19

Thank you for the feedback. I guess now that i read the source of empty? I cannot unsee it…

dpsutton15:08:26

that's why the seq idiom is the more natural one. seq is exactly that

dpsutton15:08:57

render=> (if (seq [1]) :present :absent)
:present
render=> (if (seq []) :present :absent)
:absent

sheluchin15:08:46

Eh, it seems a bit unnatural to me because it performs a transformation rather than just returning a bool.

dgb2315:08:43

I just checked, in the last instance I needed this, I used first

dgb2315:08:09

There is nothing stopping you from doing (def present? seq) obviously. And tbf I like that better. Although first is fine too I think.

dpsutton15:08:43

first doesn't quite work the same.

render=> (if (first [nil]) :present :absent)
:absent

dpsutton15:08:07

of course also if the first element is false

🙏 3
dpsutton15:08:12

no worries. the language makes it quite easy to quickly define little helpers like this. one irritation might be that you can always reach for clojure.core/seq but you will need to bring in your ns.utils/present? helper for that to be widespread

emccue15:08:20

not-empty exists

dpsutton15:08:59

yeah that's where we started 🙂

dgb2316:08:22

Actually I didn’t know of not-empty !

dgb2316:08:34

because I was lead to using seq because of the doc of empty?

seancorfield16:08:58

not-empty can be very useful when you want to retain the underlying type of the collection. Also for strings: (not-empty "foo") => "foo", (seq "foo") => (\f \o \o), (not-empty "") => nil

seancorfield16:08:46

(not-empty [1 2 3]) => [1 2 3], (seq [1 2 3]) => (1 2 3)

seancorfield16:08:15

But, yeah, you just get used to the fact that (if (seq x) ..) is idiomatic Clojure.

dgb2316:08:00

I think I don’t really get nil , not fully: https://clojure.org/reference/data_structures#nil I know how it is used, kind of, or rather I learn more about how it is used. But I don’t quite understand what it really means. It feels overloaded to me, but I suspect that is because I don’t understand it.

Russell Mull16:08:32

There is a lisp idiom, which Clojure embraces, called "nil punning". Many functions accept nil as a parameter and do something "sensible" with it. Your feeling that nil is overloaded is quite appropriate, because it kind of is.

Russell Mull16:08:43

Oh, I see that the thread started out with that term 🙂

Russell Mull16:08:46

As far as internalizing its use goes, my recommendation would be to just keep writing code. As you spend more time with the standard library, you'll get a better sense of how nils are handled and how it helps the various functions work together smoothly.

seancorfield17:08:59

It may help to think of nil as representing a "nothing", i.e., the absence of a value.

dgb2318:08:02

Thank you for the answers. I will take @U7ZL911B3’s advice and start being mindful about where nil happens and kind of absorb it the sense of it. However when I think about @U04V70XH6’s suggestion that nil means “nothing”, then I’m slightly confused, in the above discussion we talked about how seq returns nil when a thing is empty. But from my understanding of what an empty thing is this doesn’t make sense. In mathematics, relational algebra, set theory and such we talk about an empty set to be a distinct value right? (I don’t have a degree, self-taught, so my understanding might be wrong). Is the advice that I should take it as “this is how Cojure does it” or is there some deeper connection, explanation, or maybe misunderstanding that I’m not seeing. I’m fine with either, just would help me to know.

noisesmith18:08:16

in clojure nil is two things in one entity- the lispy idioms around nil and collections / booleans, plus it has a very special status in the jvm (eg. you can't call methods on nil, even something as basic as .toString (which clojure covers via special cases throughout the stdlib))

noisesmith18:08:10

and you are correct, the lispy idioms around nil are not the mainstream way mathematicians deal with collections

noisesmith18:08:57

(though in LISP's defense when a list is the only possible kind of collection, treating nil and () as the same entity kind of sorts that out, it just gets more complicated when you want actual collection types beyond lists)

dgb2318:08:28

Thank you @U051SS2EU! Would it help me to learn what the representation of nil or null is? Is there any?

noisesmith18:08:38

it's a bytecode value

noisesmith18:08:57

it's not an object of any sort, it's just the vm's marker for "nothing here"

dgb2318:08:15

And there would be a similar “hardcoded” value in a instruction set?

noisesmith18:08:39

right, it's a simple constant on the bytecode level

noisesmith18:08:19

it probably ends up being a null pointer but even thinking about designs that assume that fact is a sign you are in trouble :D

dgb2318:08:42

Does it emerge from bytecode, or is it put there deliberately by a language implementation?

noisesmith18:08:25

how precisely would anything emerge in bytecode without a compiler putting it there?

noisesmith18:08:09

it might help to get more concrete - in a stack trace with an NPE, how did the NPE get there?

noisesmith18:08:41

some operation resulted in an unexpected / unchecked null, which was passed down one or more layers of method call arguments until someone tried to actually use it as a value

dgb2318:08:25

Ok thank you for these! My brain runs in circles a bit, I think I might read up on that and investigate further.

noisesmith18:08:01

clojure.core is peppered with code that special cases null so it can be punned eg. (str nil) -> "" (conj nil 1) -> (1)

🙏 3
noisesmith18:08:13

each of those is a defensive branch in the code

noisesmith18:08:07

so most of the time as a clojure user you can assume clojure.core does the right thing and your code will work with that if you write code idiomatically

dgb2318:08:16

Nice, I can search for these and look how this is used

dgb2318:08:19

Ohhhh, I get it! The core lib protects us from nil pointer exceptions and we can count on that fact and write code that uses this knowledge

noisesmith18:08:31

right, exactly

🚀 3
noisesmith18:08:05

this is why we use str instead of .toString , conj instead of .cons etc.

jumar18:08:43

Note that functions in clojure.string namespace are typical examples of functions that blow up on nil args

noisesmith18:08:28

yeah - I really did mean clojure.core itself, the rest of stdlib is less nil safe

dgb2318:08:18

Right so getting in the habit of passing nil into functions at the REPL might be useful

seancorfield18:08:59

@U01EFUL1A8M What's your background, prior to Clojure? What programming languages? I'm wondering if there's an analogy we can draw that might help you here.

seancorfield19:08:46

Good ol' JavaScript! WAT? 🙂

dgb2319:08:38

I’m a web developer, so JS is in there too 🙂

dgb2319:08:31

I just asked myself “why not use next” then I saw why: https://github.com/clojure/clojure/blob/clojure-1.10.1/src/jvm/clojure/lang/RT.java#L711 next just calls seq anyway 😄

seancorfield19:08:41

I've managed to avoid JS for almost my entire career so I can't offer you any analogies there, unfortunately 😐

seancorfield19:08:31

A lot of functions that operate on sequences (as an abstraction) call seq on their argument so that they will work on concrete collections. map, filter, etc all do that.

dgb2319:08:49

With JS its incredibly messy. You use null as the explicit absence of a value, but there is also undefined when its, just not defined prior

dgb2319:08:30

But with Clojure I always assume that there are good reasons for things, so I ask these questions to really absorb what the meaning of things are.

seancorfield19:08:09

https://clojure.org/reference/data_structures talks about how nil and the various abstractions and concrete collections all fit together but I think you said you'd already read that?

dgb2319:08:25

Yes, its descriptive but I try to get why.

dgb2319:08:09

I’m much much closer now so ty all

seancorfield19:08:19

nil has long roots in Lisps -- and several FP languages that didn't go down the statically-typed path (a lot of the FP langs that were around when I was working in that area in the '80s all had nil of some sort and it often stood in for both "nothing" and "empty (collection)").

dgb2319:08:31

I found another nice hint to my original question: https://clojure.org/reference/lazy#_the_victim_nil_punning The whole article talks about why there is rest next , what they mean and how seq became the empty list check of choice

seancorfield19:08:50

Clojure's nil is both the Lisp heritage and the Java heritage (of null, as in null pointer).

👍 6
seancorfield19:08:36

Oooh, that's a nice read! I don't think I'd seen that page before (but I vaguely remember some of it coming up "historically" on the mailing list, years ago... back before Slack 🙂 ).

dgb2319:08:56

in scheme the cdr of an empty list is nil, just like the next (which calls seq) of an empty list is nil. rest needs to return an empty seq, rather than nil so it doesn’t need to evaluate the thing, so we end up with seq for a check of whether a thing still has stuff in it.

dgb2319:08:12

Nice, so the whole thing has context in the evolution of Clojure!

seancorfield19:08:20

The next/`rest` thing is pretty subtle. We have just four calls to next in our entire codebase (115k lines), compared to 63 calls to rest it seems.

dpsutton19:08:11

i default to rest for sure. and i forget which one calls seq. it's just not ingrained in me for some reason

seancorfield19:08:15

And two of those next calls should probably be rest calls instead and the check in the loop changed to call seq.

seancorfield19:08:02

(loop [servers ws-servers]
    (when servers
      (or (lookup-places-on-server (first servers) params)
          (recur (next servers)))))
(`ws-servers` is never empty but that's not necessarily a good assumption)

dgb2319:08:18

Right, so I always assumed that rest is the default thing too, but it very much is an optimization for laziness.

dgb2319:08:52

I feel like the option type is very useful in languages that have very strict semantics, where you want to be forced to follow every branch. I dabbled in Rust a bit and there it felt really fitting for the language.

dgb2319:08:48

In Clojure this wouldn’t nicely fit. We have sets and keywords to achieve the semantics of that, but we stay dynamic and open.

dgb2319:08:41

Oh I like this

dgb2319:08:59

> everything has that implicit union type

dgb2320:08:26

I spent way too much time on this for today 😄 For some other time I found this talk on infoq from Tony Hoare, might be interesting to put NULL in historical context: https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/ Ty again for the help and discussion!

Pablo18:08:32

Hello 👋 How can I define a custom generator only on CLJ but keeping the definition of the spec on CLJ and CLJS?

(s/def ::foo
  (s/with-gen (s/nilable map?)
    #(gen/fmap foo (s/gen ::bar))))

Pablo18:08:46

foo does not exist on CLJS

hiredman18:08:42

(s/def ::foo (-> (s/nilable map?) #+:cljs (s/with-gen ...))) or whatever the syntax for those conditionals is

sova-soars-the-sora19:08:58

So in Compojure land I destructure a request into its URL params... but apparently some of them are passed in as nil so is there a way I can do "default values" for query params that are not set?

dpsutton19:08:46

destructuring offers a handy way to provide defaults for nils

sova-soars-the-sora19:08:12

Thanks I'll check that out. For now I did something like:

(let [token (if (empty? token) "" token) ...]

sova-soars-the-sora19:08:38

Are you talking about the {:or token ""} bit?

sova-soars-the-sora19:08:48

That is cool and I had not noticed that before.

dpsutton19:08:09

that's exactly what i was talking about

hiredman19:08:14

something to keep in mind is :or in associative destructuring doesn't behave like clojure.core/or , rather it behaves like passing a default value to get

hiredman19:08:21

user=> (let [{:keys [a] :or {a "foo"}} {}] a)
"foo"
user=> (let [{:keys [a] :or {a "foo"}} {:a nil}] a)
nil
user=>

Michael Stokley20:08:31

(defn f
    [{foo :foo :as m}] ;; <- can i stomp/shadow `foo` for arbitrary `m`?
    ...)

Michael Stokley20:08:29

with-redefs needs a known var, i think

dpsutton20:08:11

(f {:foo "this is an arbitrary value here"}) you just call the function with whatever you want

Michael Stokley20:08:26

i could. maybe a better question is, can i with-redefs local bindings

dpsutton20:08:09

no. you'll need to design in a different way

Michael Stokley20:08:25

right on, thank you

hiredman20:08:35

using with-redefs is always a bad idea

💯 3
👑 3
hiredman20:08:14

the only safe way to use with-redefs is stubbing for testing, and even that is only safe in a limited way

hiredman20:08:40

with-redefs was introduced when clojure stopped making vars dynamically bindable by default because people had been using the dynamic bindability to mock things in tests

hiredman20:08:01

it is not thread safe, the redefs are visible globally, and the way it sets the var back to the original value at the end is not safe on multiple threads, it is a race condition

didibus23:08:44

I think something that be nice is to update most of the doc for core functions to add info about if they are thread-safe or not. Java tend to do that and its quite nice,

noisesmith18:08:17

the list of non thread safe functions is pretty small and probably deserves to be its own doc: • things that use volitiles • things that use transients • things that use stateful transducers • with-redefs • proxy-super (there's probably a few others I forgot off hand)

Michael Stokley20:08:02

for production code i'd use dependency injection

Michael Stokley20:08:29

this is just fooling around

dgb2320:08:10

@michael740 not sure if I understand your question tbh, but does this help?

(defn f
  [{foo :foo :as m}]
  [(let [foo 1]
    foo) foo])

(f {:foo 2}) => [1 2]

Michael Stokley20:08:28

no, i was hoping to just overwrite foo without touching the function f. to be able to use f as is. but thank you!

👍 3