Fork me on GitHub
#beginners
<
2020-06-08
>
Kazuki Yokoyama01:06:33

Which one is more idiomatic: (. System (getProperties)) or (. System getProperties) ? I know they're equivalent and I find the latter more readable. Any opinions? Thank you.

phronmophobic01:06:16

not that you need more options, but for static methods, I use (System/getProperties)

hiredman01:06:12

That is the correct idiomatic form

Kazuki Yokoyama02:06:34

I'm aware of this third alternative, but I'm actually thinking in broader scenarios like when using with the .. or doto macros.

phronmophobic02:06:03

I don’t think I’ve ever used the . or .. syntax. if it’s a static call, I use the above. here’s an example of calling a non static method: (.getBytes ^String line "utf-8")

phronmophobic02:06:21

here’s an example with doto:

(doto (new java.util.HashMap) 
  (.put "a" 1) 
  (.put "b" 2))

Alex Miller (Clojure team)02:06:02

Regarding your original question, no preference

Alexander Moskvichev11:06:52

Hi. I'm trying to use a React component that takes a generator function as a parameter. Can I use lazy-sec for it? The component is written in typescript, I use reagent.

Alexander Moskvichev13:06:12

Well, I've made a try. Got TypeError: gen.next is not a function. Are there any possibilities?

Alexander Moskvichev05:06:14

Found a way, using GeneratorFunction constructor, but I'll keep it as a last resort.

nick12:06:35

How can I become proficient in mocking/stubbing in Clojure? Any deep dive course/book that covers it? From what I see there is no lack of great manuals/courses on core.test or test.check/generative testing but not much info on mocking. I’m trying to understand why is that. Perhaps it is not as helpful/important as in other languages? Or is it as simple as just learning how to use with-redefs/with-redefs-fn?

ep12:06:42

have you seen this lib: https://github.com/alexanderjamesking/spy? docs are pretty good IMO

👍 8
jkrasnay12:06:47

In my experience, Java devs use mocking to unit test some business logic that calls a stateful resource like a database, often because their code is a big, dependency-injected object graph. Clojure nudges you towards putting your business logic into pure functions that can easily be unit tested without mocking things.

👍 4
nick12:06:51

Haven't seen it before. I saw mockery, clj-fakes + simple with-redefs.. It's kind of confusing where to start and what to use for a beginner. Anyway, thanks! I'll check out alexanderjamesking/spy

ep12:06:39

ive used it and plenty of devs at my workplace use it and it works great

🙏 4
jkrasnay12:06:08

Places in my code where I do access external resources tend to be small functions that delegate the heavy lifting to pure functions. I test these with integration tests.

👍 4
folcon12:06:38

Personally, I’ve not seen the need for mocking much in my clojure code, as things like databases convert data into clojure data structures as a first step. Everything further in the system just manipulates / works with those, so your mock is just a normal clojure datastructure, perhaps with some particular shape. Actions like sending emails etc are a simple function of datastructure to effect which a function at the edge consumes to do the task, so anything generating those effects you just need to test the produced structure is correct. Eric’s book Grokking Simplicity covers some of this type of thinking really well I think =)…

👍 8
adam13:06:04

Is there a shortcut for getting a value from a string indexed map? ("key" m) doesn't work like keyword indexed map. Is it only (get m "key")?

dpsutton13:06:02

maps are invocable: ({"a" "b"} "a") -> "b"

adam13:06:52

Oh that's cool! Thanks.

adam13:06:47

why they made it work both ways for keyword indexed maps though (`(:key m)` (m :key))?

Dan Boitnott13:06:19

They're both super-useful. For instance, it's very handy to be able to pass a keyword to a higher-order function like map.

dpsutton13:06:33

its too convenient for you?

😂 8
adam13:06:53

lol just trying to figure out which one to use when I have string keys in the same function... perhaps I should aim for consistency in my case

dpsutton13:06:03

haha fair. i'm sure several people always use (get m k) for this reason

dpsutton13:06:40

note that if your map is nil (nil :key) will throw, if your key is nil (k m) will throw. But in both cases (get m k) will do just fine

☝️ 4
adam13:06:45

I see. Thanks for the clarifications.

dpsutton13:06:18

its simply what should happen if a keyword is in the first position (:foo ...) It could either throw an error as not a function, or it look up

Jim Newton14:06:58

I'm trying to write a (defrecord ...) , can someone tell me where the docstrings to? I don't see it in the https://clojuredocs.org/clojure.core/defrecord. I suspect I can include a dostring for the defrecord itself and also one docstring per field ?

andy.fingerhut14:06:06

I don't have a complete list handy, but not all constructs in Clojure enable providing doc strings.

😞 4
dpsutton14:06:47

i believe this is one of those. emit-defrecord doesn't mention anything about docstrings

😞 3
andy.fingerhut18:06:18

You can alter the metadata on one or both of the record constructor functions that defrecord creates, modifying its doc string.

Jim Newton09:06:59

@U0CMVHBL2. sorry I didn't understand your comment.

andy.fingerhut12:06:08

Demonstration of my meaning in a REPL session:

user=> (defrecord MyRec1 [a b])
user.MyRec1
user=> (->MyRec1 1 2)
#user.MyRec1{:a 1, :b 2}
user=> (doc ->MyRec1)
-------------------------
user/->MyRec1
([a b])
  Positional factory function for class user.MyRec1.
nil
user=> (alter-meta! #'->MyRec1 assoc :doc "My preferred doc string")
{:arglists ([a b]), :doc "My preferred doc string", :line 1, :column 1, :file "NO_SOURCE_PATH", :name ->MyRec1, :ns #object[clojure.lang.Namespace 0x75d0911a "user"]}
user=> (doc ->MyRec1)
-------------------------
user/->MyRec1
([a b])
  My preferred doc string
nil

andy.fingerhut12:06:56

You can change the :doc key of the metadata map of any Var in this way, changing its doc string.

andy.fingerhut12:06:23

That gives you one doc string per record, but not one per field.

Jim Newton14:06:44

I'm reading chapter 13 of https://www.braveclojure.com the hard copy. It mentions three ways to instantiate a record. MyRecord. , ->MyRecord, and map->MyRecord. However, the https://clojuredocs.org/clojure.core/defrecord mentions the second two and recommends (in the examples) writing your own what looks like a factory function make-MyRecord . I using the MyRecord. notation recommended against?

andy.fingerhut14:06:53

I believe some people really do not like to use any kinds of APIs that rely on positional arguments when the number of those goes over 2 or 3, due to difficulty in people remembering the purpose of that many positions. The MyRecord. notation would also require an import statement if you wanted to avoid using the fully qualified name when called from other Clojure namespaces.

✔️ 8
Alex Miller (Clojure team)14:06:40

The MyRecord. notation is doing Java interop on the implementation details of records - it is preferred to use the generated constructor functions

Jim Newton14:06:55

In my experimentation (map->MyRecord ...) seems very readable as I'm not relying on positional arguments.

Jim Newton15:06:48

Once I've defined a record MyRecord how can I ask whether a given object is a MyRecord ?

Alex Miller (Clojure team)15:06:07

(instance? MyRecord obj)

Jim Newton15:06:10

Ok, so defrecord doesn't define a trival type predicate for me? That's fine.

Alex Miller (Clojure team)15:06:22

no, although we've talked about that

Jim Newton15:06:41

easy for the user to write.

Jim Newton14:06:24

I don't understand the use model of extend-type . The model in  https://slack-redir.net/link?url=https%3A%2F%2Fwww.braveclojure.com is that for each type, you define all the functions specializing on it per protocol. It seems more logical, in my case to turn it 90 degrees.

Jim Newton14:06:42

I.e. for each function in my application, define what that function does for type1 and for type2, next to each other in the code. I.e., I've defined a record, I'd like to define what the function does for MyRecord, and then what the function does for clojure.lang.Sequential . Do I need to create 10 different protocols each with a single function?

dpsutton14:06:47

that sounds close to extend-protocol

Jim Newton14:06:22

Or perhaps I don't need a protocol at all, just 10 multi methods which all share the same dispatch function. i.e., to distinguish between MyRecord and clojure.lang.Sequential

Alex Miller (Clojure team)14:06:07

you have n protocols and m types - extend does 1:1, extend-protocol extends one protocol to many types. extend-type extends many protocols to one type (they are nary in different dimensions)

Jim Newton14:06:28

One of the purposes why I'm experimenting with defining a record is because previously I had represented my data as an array of maps. But an array of maps is a sequence. clojure.lan.Sequential and several times it spent a long time debugging because i was running the sequence code on the array of maps, rather than recognizing it as its own type.

Alex Miller (Clojure team)14:06:04

I'm assuming you don't really mean "array" (like Java array)

Alex Miller (Clojure team)14:06:34

but really sequence or other sequential collection

Alex Miller (Clojure team)14:06:36

basically I'm not sure I understand what you're saying. creating a record to represent a sequential collection seems like a bad idea

Jim Newton14:06:39

alex, when you say "extends one protocol" do you mean a-la-fois extends all 10 functions of the protocol for a type? Or can each call to extend-type just mention one of the functions? I.e. make 10 calls to extend-type in different places/files in my code?

Alex Miller (Clojure team)14:06:11

each time you extend, you are replacing anything that has been extended for that type before, so you can't extend one method here and one method elsewhere

Jim Newton14:06:17

Alex the fact that it is a sequence is accidental. I'm representing a finite state machine as an array of states, and each transition tells the index of the destination state. Semantically a state machine is not a sequence, it only happens to be because my choice of implementation.

Jim Newton14:06:06

so the second extend is not really an extend, but an unextend-then-extend.

Alex Miller (Clojure team)14:06:20

yes (for either extend-type or extend-protocol)

Jim Newton14:06:47

Perhaps I need to use multimethods, as I can place the method definitions anywhere in the code in any order and in any file.

Alex Miller (Clojure team)14:06:11

if you want that, that is more the multimethod model

Jim Newton14:06:13

As far as I understand now, I only have two interesting types. MyRecord and everything else.

Jim Newton14:06:08

perhaps 3 cases. MyRecord, sequences, and everthing else is an error, but that 3rd case can be handled directly in the dispatch function.

Jim Newton14:06:47

protocols don't seem very lispy, at least at first glance.

Jim Newton14:06:10

well i'm only a 10 minute protocols-expert at this point in time.

Alex Miller (Clojure team)15:06:56

they are designed to tap into what's super optimized on the jvm, so they are definitely coming from a different perspective (type-based first arg dispatch)

capybaras14:06:18

Need to collab with a team that has never seen clojure. Best resource to get them started so they can at least read and understand the code? (versus developing new features)

dpsutton15:06:08

if you post this on reddit, i bet you will get some answers. i think yogthos is active there and he has lots of experience ramping up new-to-clojure coworkers

👍 8
Jim Newton15:06:24

Is there a lien channel on clojurians?

dpsutton15:06:41

channels auto complete for me. i can hit cmd-k lein and it shows me the matching channels

Jim Newton15:06:17

apparently there are many more channels than the ones appearing in my reader.

Andrew Nichols16:06:40

Has anyone seen a version of the animal guessing game written in Clojure?

Eric Ihli16:06:49

How would I do what it looks like I intend the following macro to do?

(defmacro pprint
  [& args]
  `(do
     (map #(println % ": " ~%) '~args)))
That errors with an "Unable to resolve symbol" on the ~%.

Alex Miller (Clojure team)16:06:09

I think you'd want ~'% in both places, but maybe better to use the fn form

Alex Miller (Clojure team)16:06:06

and you probably don't want a ' on '~args

Alex Miller (Clojure team)16:06:48

and in general, I'd try to avoid map for side effects - run! prob preferred here

Alex Miller (Clojure team)16:06:20

are you trying to print both the name of the arg and the value of it?

Eric Ihli16:06:27

Yes. Didn't know about run!. Thanks.

Eric Ihli17:06:18

What do you mean about having ~'% in both places? How would that print 2 different things?

Eric Ihli17:06:26

Also, without the ' on '~args, it expands to (arg1 arg2) and blows up because it tries to call the function arg1 rather than treating it like a list of data to iterate over.

Eric Ihli17:06:00

I thought this would be closer, but it still doesn't work. It expands to something that looks correct.

(defmacro pprint
  [& args]
  `(do
     (run! #(println % ": " (eval %)) '~args)))

(let [a 1 b 2]
  (macroexpand `(pprint a b)))
;; => (do
;;     (clojure.core/run!
;;      (fn*
;;       [p1__31102__31103__auto__]
;;       (clojure.core/println
;;        p1__31102__31103__auto__
;;        ": "
;;        (clojure.core/eval p1__31102__31103__auto__)))
;;      '(ezmonic.moniq/a ezmonic.moniq/b)))

(let [a 1 b 2]
  (pprint a b))
;; => 1. Caused by java.lang.RuntimeException
;;    Unable to resolve symbol: a in this context

Alex Miller (Clojure team)17:06:31

you need to think more carefully about what happens at compile time vs runtime

Eric Ihli17:06:43

Is the runtime a in (let [a 1] ,,,) not equal to ezmonic.moniq/a?

Alex Miller (Clojure team)17:06:09

no, you don't want that to be qualified

Alex Miller (Clojure team)17:06:44

at compile time you can capture the local names of the symbols (~args), and you want to emit code with those (unqualified) symbols and that then evaluates those symbols at runtime

Kenneth Cheung17:06:39

I think you can use Unquote splicing here

[~@args]

Chase17:06:38

Does anyone have a good tutorial explaining how to use try/catch for error handling? Specifically in Clojure, of course, but I don't have much experience using it in any language either.

Chase17:06:14

As an example, I am prompting a user to enter a number. If it is not a number I just want to loop back up to the start of the function and prompt again until they input it correctly.

Chase17:06:36

Something like

(defn my-fn [] 
  (let [input (read-line)] 
    (try   
      (Long/parseLong input) 
        (catch Exception e (my-fn)

Chase17:06:23

But I am so unclear on the syntax and semantics of such a thing. Unfortunately someone helped me on this before but I lost the code and can't search back for it.

phronmophobic17:06:24

exceptions are kind of annoying to work with. however, some java calls require using them. I still prefer to try to work with values as much as possible. I would probably write my-fn like:

(defn my-fn [] 
  (let [input (read-line)
        num (try   
              (Long/parseLong input) 
              (catch NumberFormatException e
                nil))]
    (if num
      num
      (recur))))

Chase17:06:47

Ahh, I think that is what I had before. Thank you! So how would you test for a valid number input "your way" using values instead of exceptions?

phronmophobic17:06:04

same as above. basically, making the try/catch as small as possible

phronmophobic17:06:18

ideally, Long/parseLong would return a number and whether or the parse succeeded rather than throwing an exception

Chase17:06:19

ok, ok. And for other problems instead of the exact error like NumberFormatException can I use Exception as a catch-all for any errors.

phronmophobic17:06:09

typically, you don’t want to catch Exception

phronmophobic17:06:05

you usually want to be specific as possible. it’s unlikely that your code will know how to generically recover from an unknown exception

phronmophobic17:06:38

often, it’s better to abort than to blindly continue like nothing had happened

Chase17:06:44

Ahh ok. I guess I was asking because in my laziness I usually just re-prompt the user if anything isn't to my liking. haha

Chase17:06:55

I'm currently doing Harvard's CS50 using Clojure and the guidance for many exercises is something like "If user doesn't give you a valid number, just re-prompt until they do"

phronmophobic17:06:58

well, like everything, there’s a always an exception. that’s actually a pretty good example of where catching Exception and just printing the error makes sense

Chase17:06:26

Good stuff. Thank you so much!

👍 4
Alex Miller (Clojure team)17:06:22

@ericihli something like this would print in map form if that worked

(defmacro print-args [& args] `(println (zipmap '[~@args] [~@args])))

Eric Ihli18:06:05

Thanks. I kept fiddling with it and learned a lot. I had forgotten that syntax quoting expanded things to fully qualified symbols and regular quoting didn't.

Jim Newton08:06:31

Hi Alex, I tried to modify your macro, and I don't understand the macro expansion error. Can you help?

(defmacro print-args [& args] `(map (fn [expr value] (println [expr value]))  '[~@args] [~@args]))
When I try to evaluate the following:
(print-args (+ 1 10) (+ 2 20) (+ 3 30 300))
it expands (at least according to cider) as I expect to:
(map
  (fn [expr value] (println [expr value]))
  '[(+ 1 10) (+ 2 20) (+ 3 30 300)]
  [(+ 1 10) (+ 2 20) (+ 3 30 300)])
But I get the error:
Syntax error macroexpanding clojure.core/fn at (clojure-rte:localhost:49674(clj)*:270:19).
(clojure-rte.core/expr clojure-rte.core/value) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
clojure-rte.core/expr - failed: vector? at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list

Jim Newton08:06:27

I can evaluate the expression which is the printed macro expansion:

(map
  (fn [expr value] (println [expr value]))
  '[(+ 1 10) (+ 2 20) (+ 3 30 300)]
  [(+ 1 10) (+ 2 20) (+ 3 30 300)])
So I'm guessing there is a problem with one of the symbols is different than what I think it is????

Jim Newton08:06:38

Perhaps I need a dorun around the map? But I don't think that is the problem of the macro expansion error

David Pham19:06:16

Are there any guide on using web workers with ClojureScript? Like is it possible to connect a repl to a web worker?

folcon19:06:31

There's a couple of examples: https://github.com/jtkDvlp/cljs-workers I've not had a chance to try it yet, but it might be useful :)...

Jim Newton19:06:23

It looks to me like the repl does not call print-method to print records. Could this be the case? Is that a bug?

Alex Miller (Clojure team)20:06:04

there no special exclusion of anything like that so I doubt that is the actual story

Alex Miller (Clojure team)20:06:45

there may be default printers for IRecord or eventually everything falls into Object

Jim Newton08:06:46

why do I say that. OK probably I'm just very confused. But here is what I see. When I define a record, and a print-method for it, then simply allocate the record in the repl it prints as a hash map, rather than printing with my print-method.

Jim Newton08:06:48

clojure-rte.core> (defrecord Foo [x])
clojure_rte.core.Foo
clojure-rte.core> (defmethod print-method Foo [v w]  (.write w (format "#<Foo x=%s>" (:x v))))

#multifn[print-method 0x7665aecc]
clojure-rte.core> (map->Foo {:x 100})
{:x 100}
clojure-rte.core> 

Jim Newton08:06:07

I'd expect`#<Foo x=100>` to be printed

Matej Vasek11:06:14

this is odd, it works for me using nrepl

Matej Vasek11:06:15

you could try also implement print-dup

Alex Miller (Clojure team)12:06:30

works as expected for me in clj too

Matej Vasek12:06:16

could repl call pprint instead of print for whatever reason?

Jim Newton13:06:43

for those for whom it works, are you using cider? is it possible that cider has its own printer which subverts print-method?

Matej Vasek13:06:40

(defmethod clojure.pprint/simple-dispatch Foo
  [obj]
  (.write *out* (format "#<Foo x=%s>" (:x obj))))

Jim Newton13:06:52

@UN3BYANJV as neither print-method nor print-dup are documented, which one SHOULD I implement if I do not intend the printed representation to be readable by the Clojure reader?

Alex Miller (Clojure team)13:06:08

cider definitely has tweaks to the printer (I am not using cider)

Matej Vasek13:06:51

does explicit print (print (map->Foo {:x 100})) works?

Matej Vasek13:06:54

also try clojure.pprint/simple-dispatch -- this should work for pprint

Jim Newton13:06:03

clojure-rte.core> (pprint (map->Foo {:x 100}))
{:x 100}
nil
clojure-rte.core> (print (map->Foo {:x 100}))
#<Foo x=100>nil
clojure-rte.core> 
pprint doesn't work, but print does. Is that normal?

Matej Vasek13:06:10

I am no expert on this. It seem you have to implement different multimethod for pprint:

(defmethod clojure.pprint/simple-dispatch Foo
  [obj]
  (.write *out* (format "#<Foo x=%s>" (:x obj))))

Matej Vasek13:06:45

> to be readable by the Clojure reader are you trying to create custom tagged literal?

Jim Newton13:06:13

No, I'm not trying to create a tagged literal. Re pprint If I implement the clojure.pprint/simple-dispatch function, I probably should implement the 1-ary and 2-ary versions.

Jim Newton13:06:04

BTW how can I define multiple arity defmethods?

Jim Newton13:06:30

Here is how pprint seems to be defined

(defn pprint 
  "Pretty print object to the optional output writer. If the writer is not provided, 
print the object to the currently bound value of *out*."
  {:added "1.2"}
  ([object] (pprint object *out*)) 
  ([object writer]
     (with-pretty-writer writer
       (binding [*print-pretty* true]
         (binding-map (if (or (not (= *print-base* 10)) *print-radix*) {#'pr pr-with-base} {}) 
           (write-out object)))
       (if (not (= 0 (get-column *out*)))
         (prn)))))

Jim Newton13:06:53

The https://clojuredocs.org/clojure.pprint/simple-dispatch of simple-dispatch is not very enlightening.

Matej Vasek14:06:03

before diving into anything, try run :

(defmethod clojure.pprint/simple-dispatch Foo
  [obj]
  (.write *out* (format "#<Foo x=%s>" (:x obj))))
does it fix printing for you?

Matej Vasek14:06:39

simple-dispatch takes single argument -- the object to print

Matej Vasek14:06:33

or maybe you could try set cider printer fn

Jim Newton14:06:24

I tested this with clj, and it works as advertised. the repl uses print-method , so it seems cider is confused.

Jim Newton14:06:07

if simple-dispatch takes a single argument, how can it print to a file if pprint is called with a 2nd argument?

Matej Vasek14:06:08

I am not using cider so I can't help

Matej Vasek14:06:27

(setq cider-repl-use-pretty-printing nil) I just found this does it change anyting?

✔️ 3
Jim Newton14:06:33

re: cider, yes I've opened a thread on the cider channel ...

Matej Vasek14:06:34

my guess is that it binds *out* dynamic var

👍 4
Jim Newton14:06:36

setting the emacs variable cider-real-use-pretty-printing works for this case. But this is a bit scary.

Jim Newton14:06:45

I'll experiment further and discuss in the cider Chanel.

Jim Newton14:06:48

thanks for the advice.

Alex Miller (Clojure team)20:06:11

maybe unroll to why you say that

Eric Ihli20:06:35

(peek (map identity (range 20))) That raises. Can't peek into a lazy seq, right? Why does (peek (doall (map identity (range 20)))) also fail for the same reason? What am I missing about doall?

Alex Miller (Clojure team)20:06:03

what are you missing about peek?

Alex Miller (Clojure team)20:06:25

peek works on instances of IPersistentStack, which are lists, vectors, and queues (not seqs)

phronmophobic20:06:19

doall will force evaluation, but it won’t convert a seq into something besides a seq

Alex Miller (Clojure team)20:06:21

if you just want to look at the first thing in a seq, use first

Eric Ihli20:06:15

Ah. I see. I thought the problem was that the lazy thing wasn't realized. Not that realizing a lazy seq still gave you a seq and peek didn'k work on seqs.

Alex Miller (Clojure team)20:06:24

reading that back, I meant that question with a 😀 not sure if that came across!

🤙 4
Alex Miller (Clojure team)20:06:38

generally, I would not use peek/pop unless you're actually using a collection as a stack