Fork me on GitHub
#beginners
<
2022-07-24
>
jimmy05:07:34

How do I turn a RANGE into a LIST? (vec (range 5)) -> [0 1 2 3 4] ;whereas (list (range 5)) -> ((0 1 2 3 4)) ;not what I expected.

seancorfield05:07:49

`(range 5)` is going to produce a sequence (depending on what you do with it). Why do you think you specifically need a list?

seancorfield05:07:47

In Clojure, the approach is generally to program to the core abstractions and that's often the "sequence" as opposed to a concrete "collection" type @U0395MW13FT

seancorfield05:07:30

For example:

``````user=> (range 5)
(0 1 2 3 4)
user=> (list 0 1 2 3 4)
(0 1 2 3 4)
user=> (vector 0 1 2 3 4)
[0 1 2 3 4]
user=> (seq (vector 0 1 2 3 4))
(0 1 2 3 4)
user=>``````
Even tho' the underlying concrete types are different here, all four results are seqable (or are already sequences).

jimmy05:07:54

@U04V70XH6 i don’t really need the list. Reading “The joy of Clojure” I was toying with queues and surprised by the different resuls of (pop (vec (range 5))) vs (pop (list (range 5)))

seancorfield06:07:54

Ah, because vectors are optimized for adding/removing items at the end whereas lists are optimized for adding/removing items at the start.

🎯 1
seancorfield06:07:54

`vec` is to `vector` as `list*` is to `list`:

``````user=> (vec (range 5))
[0 1 2 3 4]
user=> (vector (range 5))
[(0 1 2 3 4)]
user=> (list* (range 5))
(0 1 2 3 4)
user=> (list (range 5))
((0 1 2 3 4))
user=>``````
We just don't use `list*` much outside of macros, because we would typically use `seq` to produce a sequence from a collection.

seancorfield06:07:28

`list*` can be useful in macros because of this:

``````user=> (list* 1 2 3 [4 5])
(1 2 3 4 5)
user=>``````

seancorfield06:07:14

(a bit like `apply` which is also variadic and unrolls its last argument)

jimmy06:07:03

Great hint, list* Tho, (pop (list* (range 5))) fails as (type (list* (range 5))) is also a range. I guess the learning point is to start with the proper type.

sova-soars-the-sora12:07:21

yeah, what's the collection that serves best here

Chase15:07:44

@U0395MW13FT For a little bit of closure (hehe) even though the conclusion is not to use a `list` in this instance, you can use `(apply list (range 5))` to get the list type you want and have `pop` work on it (also seeing how `pop` works differently for `lists` and `vecs` ).

👍 1
sarna08:07:39

what's `count` returning for a string? number of bytes? code points? something else?

alpox09:07:20

It should be the number of characters in the string.

Ed09:07:06

Clojure strings are Java strings, so I would be very surprised if it didn't call `.length` https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#length() ... The docs for that say > The length is equal to the number of Unicode code units in the string.

sarna09:07:42

oh I see, thanks. is there any way to get a count of bytes in a string? besides casting it into a byte array

jkxyz10:07:53

You need to encode a string into a byte array using a specific charset because the number of bytes used for each character is going to be different in e.g. UTF-8 and UTF-16. And in UTF-8 the code units can be encoded as 1-4 bytes so you pretty much have to turn it into a byte array. You can do that in Clojure like this: `(alength (.getBytes “Hello World” “UTF-8”))`

sarna10:07:11

ah, gotcha. makes sense that different encodings will give different results. thank you :))

skylize21:07:21

Java stores strings in UTF-16 (ignoring under-the hood optimizations). When `count` delegates to `.length`, you get the number of UTF-16 code points in your string. If somehow lucky enough to be certain all your code points will stay within U+0000-U+D7FF and U+E000-U+FFFD (think Latin-1 charset), then you can consider that the same as counting "chararcters". More likely, you will need to deal with surrogate pairs, where the the first of two points names a lookup table from which to interpret the second. So the count of code points might be wildly different from your intuitive expectation. This Applies to all the emojis, to full support of most non-English languages, and to any support at all for most non-European languages. The complication goes even deeper, as many interpreted surrogate pairs still function merely as modifiers for other characters, e.g. diacritics or emoji skin tone colors... plus numerous other complications arising from different writing systems around the world. As a tiny introduction, this article describes solving a small string problem while at least keeping surrogate pairs intact: https://lambdaisland.com/blog/2017-06-12-clojure-gotchas-surrogate-pairs.

sarna09:07:40

very useful, thank you! I'll give it a read

bortexz11:07:02

Does anybody know if I can use https://github.com/cognitect-labs/test-runner to run cljc tests, and run both in clj and cljs ? If not, how would a minimal setup look like for testing cljc libraries?

delaguardo12:07:27

this test runner is for clj only. the best solution i found so far is to use kaocha library. it's is relatively easy to configure two separate test environments. here is one of my libs for the reference - https://github.com/DotFox/matchete

bortexz12:07:52

Looks good, thank you!

seancorfield16:07:05

@U6CN6JQ22 Here's a cljc project that has its tests run as clj via Cognitect's test runner and as cljs via clj-test-runner https://github.com/seancorfield/honeysql/blob/develop/deps.edn -- it has GitHub Actions, and auto-builds/deploys JARs to Clojars, uses `tools.build` etc.

seancorfield16:07:38

(I don't do any cljs work so this was the simplest setup for me to ensure my cljc files get tested both ways)

bortexz16:07:40

Thanks sean, I didn’t know about cljs-test-runner

Chase16:07:09

So what is the current simplest way to build an uberjar with deps.edn these days? I'm trying to go off this guide: https://clojure.org/guides/tools_build

Chase16:07:35

I think what is happening is I add a `build` alias in my `deps.edn` calling in this `tools.build` dependency. Then I create a `build.clj` and use that example code included under the uberjar section?

Chase16:07:53

I'm also creating a Dockerfile to run all this and package it up so in that Dockerfile I would have `RUN clj -T:build uber` ? If I'm using `FROM clojure:latest` in the same Dockerfile, which I think brings in the latest JDK, will that have the proper clj tools installed? Is that what having that `tools.build` dependency in my `deps.edn` ensures?

seancorfield16:07:54

I'm a little biased but I think https://github.com/seancorfield/build-clj is the "simplest" way -- it's a wrapper around `tools.build` that has sane defaults and removes (hides) some of the boilerplate code.

seancorfield16:07:10

I don't have public examples of building uber JARs with it but we use it at work for building all our production artifacts. You can look at some of my other projects to see how I use `build-clj` to build library JARs for deployment to Clojars. Happy to answer any follow-up Qs you might have.

seancorfield16:07:00

I can't answer your Docker Qs (I think using Docker for dev/build stuff just makes life more complicated -- Docker is great for standing up testing services, such as databases and search engines, but I see no benefit in trying to use it to wrap Clojure CLI stuff).

Chase16:07:06

I'll check this out, thanks Sean. I have it building the uberjar but it's not finding my `main` class. It's a full stack app, so the backend repo is setup like `src/backend/TODO/core.clj` with a `-main` function kicking it all off. So in my `build.clj` I put the `:src-dirs ["src/backend"]` and the main function path as `:main 'TODO.core.-main` . That doesn't work and my other tweaks haven't worked.

Chase16:07:53

I don't want or use Docker either and won't be using it during development. But it's the only way to currently deploy Java (clojure) apps on services like and which I do use and like.

seancorfield16:07:02

Make sure you have `:gen-class` in your main namespace's `ns` form.

Chase16:07:18

I do have that in there, yep.

Chase16:07:40

Oh wait, I think I see it. Let me try as `:main 'TODO.core`

Chase16:07:32

That was it! Thanks for helping me "rubber duck" this with you. haha

1
Ben Lieberman18:07:48

is there some config file I can modify to play around with external jars/libraries inside the `clj` REPL? Also if that

dpsutton18:07:56

dpsutton18:07:37

dpsutton18:07:55

Otherwise, if you are wiling to restart your repl you can just use `deps.edn`

Ben Lieberman18:07:07

I was referring specifically to the REPL started by the `clj` command outside of a project and not depending on any build tool. But, being a newbie, I'm not sure if that has any value over and above just starting a new project and adding my deps there

Ben Lieberman18:07:34

but I will check this link out, thanks

dpsutton18:07:05

i always just `mkdir /tmp/<whatever>` and make a deps edn there

dpsutton18:07:30

you can also pass a literal deps edn: `clj -Sdeps '{:deps {some/lib {:mvn/version "1.2.3"}}}`

Ben Lieberman18:07:55

oh cool, yes this is what I want. thanks!

dpsutton18:07:19

check out `clj --help` for some other useful goodies

👀 1
skylize22:07:56

The user deps.edn in your Clojure config dir is available everywhere. Use aliases for containment, to avoid accidentally polluting the classpath of every project you run. You can use multiple aliases at once.

``````;; \$HOME/.clojure/deps.edn

{:aliases
{:rebel
:spec
{:extra-deps {org.clojure/spec.alpha {:mvn/version "0.3.218"}}}}``````
``````\$ clj -A:spec
\$ clj -A:spec -m my.project.main
\$ clojure -M:rebel:spec``````

Ben Lieberman23:07:07

and this works for Java code too right? and off any of the repos?

Ben Lieberman23:07:42

I'm looking to experiment with the Java bluetooth ecosystem

skylize23:07:41

It all runs on the JVM 🙂

skylize23:07:07

Just remember that you `require` clojure deps, but you `import` java

Ben Lieberman02:07:38

Thanks @U90R0EPHA. I'm still marveling over how Clojure uses maps for so many different things. It makes data structures in other languages feel almost like an afterthought, honestly.

Ben Lieberman18:07:03

*Also if that's not strictly useful, that's cool too