Fork me on GitHub
#clojure
<
2021-09-21
>
John Bradens00:09:26

Has anyone worked through the exercises at the end of the book in Web Development with Clojure Third Edition by Dmitri Sotnikov and Scot Brown? I'm trying to work through them and would love to be able to compare my code to something when I'm having trouble figuring out what I'm doing wrong.

seancorfield00:09:37

There's a #code-reviews channel if you're specifically looking for feedback on your code -- but if it's just failing and you need help, maybe #beginners is a good place to ask, and paste your code into a thread on your initial post.

seancorfield00:09:19

I have the book but haven't worked through all the examples...

John Bradens00:09:55

Sorry if I should have posted this in the beginners channel

chrisblom08:09:51

Does anyone know why the second line fails here?

(defprotocol IWork (foo [this {:keys [a b c] :as m}]))
(defprotocol IFail (bar [this {:some-ns/keys [a b c] :as m}]))
(defprotocol IAlsoWork (bar [this {:some-ns/keys [a b c]}]))
i get this error when i eval the IFail definition:
#:clojure.error{:phase :macro-syntax-check,
                   :line 190,
                   :column 1,... 
                   :symbol clojure.core/fn}

chrisblom08:09:32

with this cause:

chrisblom08:09:36

Call to clojure.core/fn did not conform to spec.
   #:clojure.spec.alpha{:problems
                        ({:path [:fn-tail :arity-1 :params],
                          :pred clojure.core/vector?,
                          :val
                          ([gf__this__85111
                            gf__{:some-ns/keys [a b c], :as m}__85112]
                           (.
                            gf__this__85111
                            (bar
                             gf__{:some-ns/keys [a b c], :as m}__85112))),
                          :via
                          [:clojure.core.specs.alpha/params+body
                           :clojure.core.specs.alpha/param-list
                           :clojure.core.specs.alpha/param-list],
                          :in [0]}
                         {:path [:fn-tail :arity-n :params],
                          :reason "Extra input",
                          :pred
                          (clojure.spec.alpha/cat
                           :params
                           (clojure.spec.alpha/*
                            :clojure.core.specs.alpha/binding-form)
                           :var-params
                           (clojure.spec.alpha/?
                            (clojure.spec.alpha/cat
                             :ampersand
                             #{'&}
                             :var-form
                             :clojure.core.specs.alpha/binding-form))),
                          :val
                          (gf__{:some-ns/keys [a b c], :as m}__85112),
                          :via
                          [:clojure.core.specs.alpha/params+body
                           :clojure.core.specs.alpha/params+body
                           :clojure.core.specs.alpha/params+body
                           :clojure.core.specs.alpha/param-list
                           :clojure.core.specs.alpha/param-list],
                          :in [0 0 1]}),
                        :spec
                        #object[clojure.spec.alpha$regex_spec_impl$reify__2509 0x26a99cc4 "clojure.spec.alpha$regex_spec_impl$reify__2509@26a99cc4"],
                        :value
                        (([gf__this__85111
                           gf__{:some-ns/keys [a b c], :as m}__85112]
                          (.
                           gf__this__85111
                           (bar
                            gf__{:some-ns/keys [a b c], :as m}__85112)))),
                        :args
                        (([gf__this__85111
                           gf__{:some-ns/keys [a b c], :as m}__85112]
                          (.
                           gf__this__85111
                           (bar
                            gf__{:some-ns/keys [a b c], :as m}__85112))))}

chrisblom08:09:14

The strange thing is that if I macroexpand the IFail example manually using M-x cider-macroexpand-1 and eval the result, it does not throw any errors

chrisblom08:09:38

hmm, maybe cider-macroexpand skips the spec checks

chrisblom08:09:20

i'm guessing on off the specs used to check macro expansion does not support both namespaced key destructuring ( :some-ns/keys [] ) in combination with :as

delaguardo08:09:41

defprotocol takes just a signature of the methods. You should not add destructuring there

chrisblom08:09:03

that is not an answer. I want to add destructuring here as documentation

p-himik08:09:18

You cannot. Notice gf__{:some-ns/keys [a b c], :as m}__85112 in your macroexpand output.

delaguardo08:09:02

protocol is a high level abstraction. real documentation comes from implementation and it can be different for different implementations

chrisblom08:09:23

not in my case

p-himik08:09:30

And there are also docstrings which should be used in this case.

delaguardo08:09:54

for some specific cases you can implement a macro that will wrap around defprotocol

chrisblom08:09:43

my point is that defprotocol works with sig like [this {:keys [a b c] :as d}} and [this {:some-ns/keys [a b c]}] to why not [this {:some-ns/keys [a b c] :as d}}

chrisblom08:09:47

seems like a bug to me

delaguardo08:09:50

and also you can use http://ask.clojure.org to file your desired behaviour

delaguardo08:09:57

because after making a string from destructuring form there are no commas

chrisblom08:09:20

i don't think this anything to do with commas

delaguardo08:09:33

for example this will njot work

(defprotocol IFail (bar [this {:some-ns/keys [a b c] :another-ns/keys [d e]}]))

delaguardo08:09:44

no it is exactly because of commas

p-himik08:09:29

Clojure reader ignores commas. Can you elaborate why they are important in this case?

chrisblom08:09:35

commas are whitespace to clojure

delaguardo08:09:02

{:some-ns/keys [a b c] :another-ns/keys [d e]}
this form treated as a symbol in the context of gensym

p-himik08:09:23

And it has nothing to do with commas, still.

chrisblom08:09:59

it still seems inconsistent to me, for example (defprotocol IWork (foo [this {:keys [a b c] :strs [e f g] :syms [h i j] :as m}])) does work

delaguardo08:09:11

wait please, I’m trying to make an example )

delaguardo08:09:56

I figured, yes, it is not because of commas but because of the namespaced maps syntax.

delaguardo08:09:07

gf__#:some-ns{:keys [a b c]}__111008 forms like that injected after macro expansion

delaguardo08:09:41

clojure stops reading such symbols after closing } and starts reading next symbol 111008 as a new symbol. So one symbol becomes two and it violates the spec for fn

p-himik08:09:31

Even if a protocol definition with destructuring works, it actually doesn't produce the right code specifically because of that reason - the signatures become wrong.

chrisblom08:09:53

it works fine with :keys [a b c] destructuring

delaguardo08:09:04

but it won’t work in case of :keys [a b c] :strs [d e]

chrisblom08:09:20

it does actually

delaguardo08:09:36

did you try call the implementation?

delaguardo08:09:59

(defprotocol X (foo [this {:keys [a b] :strs [c d]}]))

(foo (reify X (foo [_ _] 42))) ;; => No single method: foo of interface: user.X found for function: foo of protocol: X

p-himik08:09:34

You didn't provide the second argument.

delaguardo08:09:58

I did (foo [_ _])

delaguardo08:09:25

ah, I got you ) sorry

chrisblom08:09:19

(defprotocol IWork
  (f [this {:keys [a b c] :str [d e] :as m}]))
(defrecord Work []
  IWork
  (f [this b] b))

(f (->Work) {:a 1})

(defprotocol IWorkNamespaced
  (g [this {:some-na/keys [a b c] }]))
(defrecord WorkNamespaced []
  IWorkNamespaced
  (g [this b] b))

(g (->WorkNamespaced) {:a 1})

(defprotocol IDontWork (h [this {:some-na/keys [a b c] :as m}]))
;; Fails: 
;; #:clojure.error{:phase :macro-syntax-check,
;;                    :line 198,
;;                    :column 1,
;;                    :source
;;                    "example.clj",
;;                    :symbol clojure.core/fn}

chrisblom08:09:57

i'm convinced this is a bug, i'll report it to ask.clojure

p-himik08:09:17

I'm not convinced yet, because passing {a :a} into gensym (exactly what defprotocol does) is not a valid thing to do. But I can't come up with an example that would break it.

chrisblom08:09:13

yes, that is strange indeed

delaguardo09:09:41

gensym is used to produce unique symbols for fn’s arguments but if you have / somewhere inside of the prefix for gensym result won’t be a simple symbol anymore

{:some-na/keys [a b c]}              ;; works because it "printed" as #:some-ns{:keys [a b c]}

(set! *print-namespace-maps* false)

{:some-na/keys [a b c]}              ;; doesn't work because the way the map is "printed" affected by *print-namespace-maps*

chrisblom10:09:36

that is indeed the cause, thanks for looking at this!

p-himik09:09:07

What does printing have to do with anything? The form that's fed into the macro is not with # . Apart from that, there's still a suffix produced by gensym.

p-himik09:09:48

Not sure what's going on with defprotocol and why it works with {a :a}. If I macroexpand it and use the result in its place, it fails with the expected Wrong number of args (2) passed to [...] - I'm pretty sure exactly because {a :a} is turned into something like gf__ {a :a} __5667.

chrisblom09:09:52

ah ok, that was why you were talking about commas: the destructuring binding is passed to gensym

chrisblom09:09:06

and this happes to generate valid symbols in some cases

chrisblom09:09:33

and breaks in some other cases, when namespaced keys/symbols are involved

chrisblom09:09:21

so invalid symbols end up in the generated form, which then cause the spec check to fail

p-himik10:09:30

Perhaps worth adding {:some-ns/keys [a]} to the test - exactly since it becomes a symbol without / even though the original form includes /.

p-himik10:09:26

TIL that you can't just copy and paste the result of macroexpand - exactly because gensym will become a single symbol, regardless of its contents.

chrisblom12:09:07

you're welcome

mkvlr13:09:05

I’ve created the following patch to support :as-alias in tools.namespace but seems like my Jira account is lacking the permissions to submit it. Any other means for me to submit it? (I’ve previously signed the contributors agreement.)

Alex Miller (Clojure team)13:09:05

(btw, #clojure-dev is a better place for stuff like this and I'm more likely to see stuff there)

mkvlr13:09:56

thanks, will ask there next time!

yuhan16:09:54

(def a (atom {:x 0 :y 0}))

(defn inner [x]
  (swap! a update :y inc)
  (inc x))

(defn outer []
  (swap! a update :x inner))

(outer)
Is this undefined behaviour? I was helping a beginner who ran into quite a subtle bug trying to do nested mutations in this manner. I realise it's ill-advised to do this at all, and the documentation for swap! says that f should be side effect free - just wondering if the semantics of this even make any sense.

gratitude 2
yuhan16:09:18

Clojurescript simply discards the inner swap! operation (resulting in @a = {:x 1 :y 0}, whereas running this on JVM Clojure does not terminate, applying inner endlessly.

Alex Miller (Clojure team)16:09:19

as you have violated the constraint on side effects

dpsutton16:09:10

and to recap how swap! on atoms work: • get the value of the atom as it is right now • compute the new value it will be after applying the function • if the underlying atom's value is still the same as when we started, "commit" the new value to it. If the underlying value has changed, need to rerun the function on the new state The infinite loop should be apparent from this

gratitude 2
Ed16:09:53

the outer swap can never finish, because it's always changed by calling inner within it's "transaction" ... so swap! will retry, assuming that you've not tried to do exactly what you've done ... I think it's exactly the defined behaviour of atoms

yuhan16:09:46

I think I understand that better now after reading https://clojure.org/reference/atoms, but what about the CLJS behaviour? Does it skip the compare step because of assumptions about the single-threaded runtime? (and is that acceptable because it's undefined in the first place?)

dpsutton16:09:15

yes. things can't change underneath because there's only a single thread of execution

yuhan16:09:53

thanks a lot for the clarification! :)

Ed16:09:39

just adding this cos I went away and typed it while trying to explain ...

(let [a     (atom {:x 0 :y 0})
        swap1 @a
        swap2 @a]
    (compare-and-set! a swap2 update :x inc)
    (compare-and-set! a swap1 update :y inc) ;; will always fail because of the previous line
    )
I think that's equivalent to the code you pasted ... but it sounds like you got it ... so feel free to ignore me 😉

👍 4
tstout23:09:57

What editors support prepl in addition to, or instead of nrepl/cider?

hiredman23:09:21

depends what you mean by prepl and supporting

hiredman23:09:14

that function is designed for use with clojure's built in socket repl machinery

hiredman23:09:48

but using https://clojuredocs.org/clojure.core.server/io-prepl you can turn any clojure.main/repl into a prepl

hiredman23:09:36

so it would not surprise me if editors just say they use clojure's built in socket repl (based on clojure.main/repl) and turn it into a prepl as needed

tstout23:09:38

Seems most editors don't support clojure's built-in socket repl.

hiredman23:09:36

(it supports a clojure.main/repl)

hiredman23:09:49

https://github.com/jebberjeb/clojure-socketrepl.nvim is some kind of neovim plugin or something for it

hiredman23:09:30

fairly certain that clojure intelij plugin supports it

hiredman23:09:01

"nREPL and clojure.main based REPLs"

hiredman23:09:01

if you are just looking for an example of prepl usage, I don't know if any of those editors use prepl. or just use the clojure.main/repl (which is what https://clojuredocs.org/clojure.core.server/repl runs)

flowthing10:09:26

https://github.com/eerohele/Tutkain/ supports the socket REPL exclusively. FWIW, it actually starts a prepl-like REPL on top of the socket REPL, but that's a bit of an implementation detail.