Fork me on GitHub
#beginners
<
2020-06-04
>
Chris McCormick05:06:26

hopefully beginners is the right place to ask this. i am seeking a resource explaining how to package up some .cljs source to be distributed as a jar that can be included in :dependencies - does anybody know of a good tutorial?

Daniel Östling06:06:34

Reading back this and other channels in this slack space, I must say a big thank you for all the great help people are getting, even asking quite basic questions (me being one of basic question-askers). A very nice and professional community. I don't take that for granted, so thanks again to those who help out getting newcomers started :thumbsup:

👍 16
clj 44
Lukas09:06:58

Hey, I have some difficulties understanding spec. I would like to model some data that can either be a scalar type like string or a number or a vector containing the scalar types defined before. For the scalar type I started with (s/def ::answer-scalar (s/or string? int?)) but s/or requires key predicate pairs as input. Any pointers and or material to study are really appreciated 😊

Daniel Stephens09:06:06

in order for conform to work you have to give some details of what to call each branch, so I think (s/def ::answer-scalar (s/or :string string? :integer int?)) will work for you

Lukas09:06:04

ah thanks

👍 4
Daniel Stephens09:06:41

no problem! I usually scan through https://clojure.org/guides/spec#_composing_predicates for examples which might help

✔️ 4
practicalli-john14:06:19

@UP2FUP0TT I have just started a video series on Clojure spec, just covering the basics but building up to more. https://www.youtube.com/playlist?list=PLpr9V-R8ZxiBWGAuncfBRYhZtY5-Bp75s Also starting a chapter on spec in the book https://practicalli.github.io/clojure/clojure-spec/

❤️ 4
Lukas14:06:55

Awesome thanks for letting me now

Daniel Stephens09:06:56

Does anything already exist to do the following, basically doing update but ignoring ops on non-existant keys

(defn update-existing
  [m k f & args]
  (if (contains? m k)
    (apply update m k f args)
    m))

joelsanchez09:06:00

what if I told you this exact same function is so common it has been included in a very popular utility library called medley? https://github.com/weavejester/medley/blob/master/src/medley/core.cljc#L48

👍 4
Daniel Stephens09:06:44

ahh, nothing I'm missing from core then, thanks for the link looks useful!

flowthing10:06:58

There’s also Void, which is more tightly focused on that sort of thing: https://github.com/dvlopt/void.cljc

👍 4
Jim Newton14:06:48

Does anyone have a good resource about how to program with abstract data types in Clojure?

Jim Newton14:06:12

why do you recommend this? is it a good reference? I actually have that book on my shelf, but I abandoned after chapter 3.

alexmiller14:06:15

what do you mean by abstract data types? what problem are you actually trying to solve?

Jim Newton14:06:05

The problem I have in general with clojure, although without question I see clojure as very flexible and powerful, is how to create opaque objects, which know what they are. Sure I can create a list or a map, but clojure doesn't know anything about the semantics of two different maps. My program is responsible for managing that itself. My program can do that in many ways, such as using spec and considering an X is something which looks like an X. But there's no way for an object to be an X because it was constructed by the create-X function.

Jim Newton14:06:54

Another common feature of ADTs, (in Scala and Common Lisp) is some sort of print-object, toString function which tells the printer how I want the object printed.

alexmiller14:06:38

so you want your own type

Jim Newton14:06:35

I hesitate to use the work "type" because it comes with baggage, and to me a "type" is any set of objects.

alexmiller14:06:01

well I really mean Java's concept of type

alexmiller14:06:20

perhaps class would be more specific

Jim Newton14:06:27

I don't mean to be evasive. sorry if it seems this way. I will read the recommended section about about records and protocols.

alexmiller14:06:55

if you want effectively a map with a specific class, you can use defrecord for that. If you want more of a custom object, you can use deftype and implement the Clojure collection interfaces

Jim Newton14:06:05

As I understand java uses something called polymorphic nominal typing. I don't really understand that is. I have a vague idea, but not a real understanding.

alexmiller14:06:05

In Clojure Applied, chapter 1-2 I cover both of these

Jim Newton14:06:37

What is Clojure Applied, is that a book or a website? or both?

Jim Newton14:06:28

paper or ebook?

Jim Newton14:06:53

I'll add it to my amazon wish list

alexmiller14:06:10

I think you may have to use another site to get the physical book now

Jim Newton14:06:01

seems it's in the amazon catalog. not sure if it will let me order it though.

Jim Newton14:06:38

how does chapters 1 and 2 of your book compare with the brave and true book? https://www.braveclojure.com/multimethods-records-protocols/

alexmiller14:06:20

I don't know how to answer that usefully

Jim Newton14:06:23

historically Common Lisp has defstruct and also defclass. I think a class can do everything that a struct can do, but structs are generally seen as light weight.

Jim Newton14:06:39

i suspect that most implementations treat them the same under the hood at the compiler level.

alexmiller14:06:21

Clojure also has a defstruct but it's generally seen as deprecated in favor of records

alexmiller14:06:47

https://clojure.org/reference/datatypes is probably the definitive reference page about this stuff

Jim Newton14:06:15

I recently say a video from Rich Hickey talking about the closure script compiler. He said the first thing the clojure code for the clojure script compiler was that it defines a bunch of protocols.

Jim Newton14:06:06

to me that says, he doesn't use maps for everything, but he defines some sort of abstract notions which make the compiler easier to programm.

Jim Newton14:06:18

just my guess, I haven't dared to look at the code.

alexmiller14:06:15

protocols were (at least partially) added to provide a high-performance path to the kind of polymorphism that is heavily optimized by the JVM

alexmiller14:06:01

they are inherently more limited than multimethods (can only dispatch on the class of the first argument) but because the JVM is super optimized for this case, they are also very fast

Jim Newton15:06:50

do records or deftypes let me define something like a toString method which the system will call to get the printed representation of the object?

alexmiller15:06:56

you can install multimethods for print-method / print-dup in the printing system to do that, yes

alexmiller15:06:14

the printing system keys off of class

alexmiller15:06:08

you can also implement .toString in a defrecord/deftype (that's used in some specific cases)

Jim Newton15:06:53

I see the documentation for print-method, but it seems the doc of print-dup is missing, just some examples. https://clojuredocs.org/clojure.core/print-dup

alexmiller15:06:07

yeah, this is under-documented for sure

Jim Newton15:06:21

oh i guess print-method is also missing documentation

Jim Newton15:06:36

could you help me understand what this example means?

(defmethod print-method XYZ [v ^java.io.Writer w]
  (.write w "<<-XYZ->>"))

Jim Newton15:06:54

v doesn't seem to be used.

Jim Newton15:06:05

perhaps w is the stream to print to?

alexmiller15:06:24

w is the writer, v is the value (your record/type object)

Jim Newton15:06:29

i guess v is the object to be printed?

Jim Newton15:06:51

and what is the circumflex?

Jim Newton15:06:18

^ is a circumflex

alexmiller15:06:27

oh, type hint

Jim Newton15:06:55

does that [ae]ffect the semantics in any way?

alexmiller15:06:12

no, used in the compiler to avoid emitting reflective calls

Jim Newton15:06:19

I don't understand the difference between affect and effect, and I've never been able to understand for the past 54 years.

Jim Newton15:06:01

Does it promise the compile that this variable will only every be bound to something of the specified type?

alexmiller15:06:14

it will fallback to making a reflective call otherwise

andy.fingerhut15:06:20

The compiler, I believe only for the purpose of deciding which among several possible Java methods to call (if there are several available), will assume it is of that class.

andy.fingerhut15:06:56

"otherwise" here meaning "if you do not provide a type hint in the source code"?

Jim Newton15:06:15

what is the consequence of me giving the wrong ^hint in my code? will I crash the JVM ?

alexmiller15:06:16

no, otherwise meaning "if you actually pass it something else"

alexmiller15:06:40

no bad consequence, just fall back to reflective call

alexmiller15:06:28

(with some caveats around the specific primitives ^long and ^double which can actually have some negative consequences if used incorrectly)

Jim Newton15:06:36

what does dup mean in print-dup ... I though first that it was away to handle printing duplicates, by referring back to a previous one printed...

alexmiller15:06:10

no, it really I think refers to being able to duplicate an object as code (I'm not actually sure!)

alexmiller15:06:34

print-dup is expected to print something readable by the Clojure reader

alexmiller15:06:52

print-method is more for human consumption

alexmiller15:06:13

in most cases for what you're talking about here, you will want them to do the same thing

Jim Newton15:06:44

ahh, so I can make my class readable, that's cool.

alexmiller15:06:15

for a defrecord, you often want to emit a call to the record constructor

alexmiller15:06:33

well, for both

Jim Newton15:06:39

in CL this feature is usually used to make the reader signal an error if it tries to read a particular printed representation.

alexmiller15:06:09

unless you are also defining a custom reader tag like #foo and in that case you'd want to emit the reader tag representation

Jim Newton15:06:12

e.g., you can't serialize a data-base-connection etc.

alexmiller15:06:33

yes, you could have it intentionally print something not readable I suppose

Jim Newton15:06:33

this is really interesting. another application of reader encoding is for floating point numbers. There is an internal binary representation for EVERY floating point number currently in the VM. however, when you print it in base 10 and re-read it you might not get a number which is equal. In one proprietary lisp that I used in the past, it was possible to print a float in a way which more or less marshalled the C++ bit representation. So we could know that serializing and deserializing maintained equality.

alexmiller15:06:13

you could ensure such things if you used a custom reader tag #my/float "0101010101010..."

alexmiller15:06:03

I think you'd want to be very delicate in the scope with which you modified the printer for that though :)

Jim Newton15:06:24

Does clojure have a reader representation that causes the reader to call a specified function? E.g., in common lisp if #.(foo bar) is encountered, then the reader returns the value returned by calling (foo bar) ... this is obviously a securty issue so there's a way to turn it off, but most people forget to do so.

alexmiller15:06:32

there is an undocumented #=

alexmiller15:06:46

read-time eval

alexmiller15:06:54

generally I would recommend you not use it :)

alexmiller15:06:38

you can also use #foo.Bar to invoke the constructor of a Java class, again I would say generally to avoid that

alexmiller15:06:15

it's preferred to define a custom reader tag and provide a tag to reader function mapping

alexmiller15:06:40

those reader tag mappings can be provided in a static file, or dynamically via binding, or by passing explicitly to a read call

Jim Newton15:06:53

Yes I read that section 5 minutes ago, but I didn't really understand it very well. Anyway, not something I'm planning on using today. but really interesting to know about this feature.

Jim Newton15:06:39

https://clojure.org/reference/reader#tagged_literals says the #something is followed by a [bracked array], whereas the github documentation says it's followed by a {braced map}

alexmiller15:06:50

re read-time eval, it's not allowed in the edn reader and can be controlled more via *read-eval* , see https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/*read-eval*

alexmiller15:06:37

tagged literals actually take data, so can be either

Jim Newton15:06:01

the common lisp variable *read-eval* has the same purpose.

alexmiller15:06:21

so #foo [1 2 3] or #foo {:a 1 :b 2} or even #foo 5

alexmiller15:06:57

in any case, it's one value

alexmiller15:06:20

ie, we make no guarantees about its future existence :)

Jim Newton15:06:35

This is a cool feature. I follow the Scala developers list, and there is a big problem of how people can write code which loads, compiles, and works in Scala 2.12, 2.13 and 3.x. Reader conditions are an excellent solution to this problem, in my opinion. But I don't see how it could work in Scala, as there's no concept of s-expression.

Jim Newton15:06:58

is https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/*read-eval* not the public API? Perhaps someone needs to remove #= from that document, because it seems to me to be part of the public API.

alexmiller15:06:40

we're happy with it as is

Jim Newton15:06:54

As you like, but it seems to be part of the public API. that's great for CL-like users.

Phil Hunt14:06:17

Hi there. Lein run fails with the following "Caused by: java.lang.RuntimeException: could not find a non empty configuration file to load. looked in the classpath (as a "resource") and on a file system via "conf" system property" Any guidance for this Java noob on where to start looking (or educating myself to understand such incantations) would be appreciated.

Phil Hunt14:06:48

it's a Luminus app if that's any help. I'm guessing the issue is my lack of comprehension of Java though (which is nearly always the blocker for me with Clojure)

Phil Hunt14:06:26

It sounds like it's looking for #'guestbook.config/env but can't find it due to some other Java thing

Phil Hunt16:06:05

I can see that #'guestbook.config/env is a state that mount is trying to load, but as it doesn't depend on other namespaces being loaded, I'm puzzled about why it fails.

Phil Hunt11:06:26

Hmm, well. Still dunno what the issue was, but I tried again after upgrading to OpenJDK 11 (for entirely separate reasons) and it started working.

Phil Hunt17:06:46

It wasn't totally working, only lein repl, not lein run.

Phil Hunt17:06:59

I eventually figured it out though. Somehow (maybe a github replication mishap given it's in the .gitignore) - I was missing the dev-config.edn file created by the Luminus template.

👍 4
Phil Hunt17:06:40

As a result, when cprop tried to get the environment variable for the db from that file, it couldn't find it

llsouder00:06:17

I had this issue when I started and it drove me crazy. I am glad you figured it out.

Jim Newton14:06:11

what does "AOT compiled" mean?

alexmiller14:06:19

you do the compilation ahead of time (into Java .class files), rather than at load time (from .clj files)

Jim Newton14:06:45

Ahead Of Time, as opposed to Just In Time... ah ok.

zane16:06:25

Let’s say I have a polymorphic function f that dispatches on its first argument, x: (f x …). In addition to x, f can accept additional arguments a, b, c, but depending on which x is provided different combinations of a, b, and c are valid. For instance, for some types of x f requires a, b, and c, but for others only a and b are required. What would folks say is the right way to implement f in Clojure? My instinct tells me to break up f into different functions based on which sets of arguments are required vs optional vs disallowed, but I wanted to double-check.

noisesmith16:06:51

I think this implements it exactly:

(defmulti f (fn [x & args] (type x)))
(defmethod f t [x a b] ....)
(defemthod f t2 [x a b c] ...)
(defmethod f t3 [x c] ...)

noisesmith16:06:35

if the args are mismatched to the type dispatch, the error will point to the specific impl that failed

noisesmith16:06:14

of course, you might want some way of picking dispatch other than type, that's the common case though, so that's what I pick as an example

zane17:06:43

That’s useful @U051SS2EU. Thanks!

zane17:06:27

I suppose my question is more about whether one should do such a thing, or whether it would be better to break up the function in question. It feels wrong for the set of required arguments to differ based on the type of the first argument, and I’ve struggled to find other examples of similar functions that do this in the wild. But perhaps this is is less of a red flag than I’m imagining?

noisesmith17:06:50

clojure code usually worries less about the type signatures, and more about the composable operations provided - you can absolutely use spec to check the args separately in each defmethod

noisesmith17:06:27

we have a lot of prior art for functions that return different types for different arg lists - see for example (map f) vs. (map f coll)

noisesmith17:06:46

they are related, it makes sense to call map a single function, but their return values in those cases are very different

zane17:06:41

That’s an example of where the return type differs based on which arguments are used, not an example of where the full set of required arguments differs based on the type of the first argument, no?

zane17:06:54

But what I’m hearing from you is that you don’t think this is a red flag. 🙂

noisesmith17:06:27

precisely

4
Hlodowig17:06:00

Hi guys, got the following code:

(ns maisicuel.core
  (:require [maisicuel.config :as cfg])
  (:gen-class))

 (defn write-files [path results]
    (for [app-results results
          :let [today (.format (java.text.SimpleDateFormat. "yyyy-MM-dd") (new java.util.Date))
                app (name (key app-results))
                figures (val app-results)
                line (clojure.string/join "," (vals figures))]]
      (spit (str path app ".csv") (str today "," line "\n") :append true)))

  (let [output-path "/Users/hlodowig/Docs/usage/"]
    (write-files output-path cfg/results))
I'm getting
Execution error (IllegalStateException) at maisicuel.core/eval1528 (form-init2559437174217991231.clj:2).
Attempting to call unbound fn: #'maisicuel.core/write-files
If I take the code outside of the defn it works just fine. I've done some googling but I'm lost. It fails in REPL and uberjar. Would appreciate your help.

phronmophobic17:06:56

what do your project dependencies look like?

Hlodowig17:06:55

:dependencies [[org.clojure/clojure "1.10.0"]
                 [org.clojure/java.jdbc "0.7.11"]
                 [mysql/mysql-connector-java "5.1.6"]]

phronmophobic17:06:01

is write-files used anywhere else in your project?

phronmophobic17:06:52

and what happens if you wrap the bottom write-files call in a -main function:

(defn -main [& args]
                   (let [output-path "/Users/hlodowig/Docs/usage/"]
                     (write-files output-path cfg/results)))

Hlodowig17:06:31

didn't paste the complete file, it is within the -main function indeed 😕

phronmophobic17:06:36

the unbound fn error happens if you try to call write-files before it is defined

phronmophobic17:06:42

in a language like java, it doesn’t matter if the source code for write-files is before or after it’s called, but in clojure, it’s as if each top level expression is evaluated in order from the top of the file to the bottom of the file

Hlodowig17:06:21

Ah I see, defn within defn :face_palm: Thanks @U7RJTCH6J

sheepy 4