This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-12-20
Channels
- # adventofcode (38)
- # announcements (8)
- # aws (4)
- # babashka (131)
- # beginners (263)
- # calva (2)
- # clj-kondo (12)
- # cljdoc (12)
- # cljsrn (3)
- # clojure (122)
- # clojure-europe (3)
- # clojure-finland (2)
- # clojure-nl (13)
- # clojure-uk (80)
- # clojured (1)
- # clojuredesign-podcast (3)
- # clojurescript (78)
- # core-async (19)
- # cursive (19)
- # datomic (7)
- # duct (10)
- # events (1)
- # fulcro (7)
- # graalvm (12)
- # graphql (3)
- # juxt (4)
- # malli (10)
- # music (3)
- # nrepl (4)
- # off-topic (25)
- # pathom (4)
- # pedestal (1)
- # re-frame (78)
- # reagent (8)
- # shadow-cljs (91)
- # sql (8)
- # vim (3)
- # xtdb (2)
(def ^:dynamic test 10000)
(defn test-foo []
((fn [] test)))
(defn test-bar []
(map (fn [_] test) (repeat 3 0)))
(binding [test 1]
(test-foo)) ;; => 1
(binding [test 1]
(test-bar)) ;; => '(10000 10000 10000)
Can some explain why in the second case binding doesn't work?binding
will pop-thread-bindings
before return. Since map
returns a lazy sequence, it is materialized (by repl) after bindings are cleared.
(macroexpand-1 '(binding [test 1] (test-bar)))
=>
(clojure.core/let
[]
(clojure.core/push-thread-bindings (clojure.core/hash-map (var test) 1))
(try (test-bar) (finally (clojure.core/pop-thread-bindings))))
bonus: there is a slightly different form of test-bar
that would do what you’re expecting
What's a good websocket client for clojure (not clojurescript? https://github.com/stalefruits/gniazdo ?)
ok, thanks. I'll give it a try then. I was just worried because it says`Its main purpose is testing WebSockets services without a browser`which doesn't sound like it's ready for constant/stable/high volume production usage
looks like you can use aleph for this: https://github.com/ztellman/aleph > WebSocket clients can be created via `(aleph.http/websocket-client url)`, which returns a deferred which yields a duplex stream that can send and receive messages from the server.
ah nice. I've heard of aleph before. Definitely looks more mature but also a way heavier dependency for only a websocket client
gniazdo is only 160~ loc wrapper for the jetty java websocket client, can serve as inspiration to code a custom client if you need something that gniazdo doesn’t support.
Most of the clients seem to be for cljs, not many options for clojure itself, there’s some space for a good wrapper around some java websocket library with more functionality than gniazdo
I'm just getting started with clojure myself. I'll start with gniazdo and go from there. I would think that it's "powered by jetty" makes it pretty stable.
That’s what I thought when I started some time ago with a personal project that needs websocket on clojure, and haven’t yet found anything I cannot do with gniazdo, just haven’t really put it into high volume production mode to tell
@daniel415 depending on how comfortable you feel with interop, there’s java.net.http built in to java>=11
I'm comfortable in Java. using http://java.net.http seems like a good idea
@daniel415 if you end up giving this a try, please let me know if it works for you or if there is anything you are missing! :thumbsup:
Hello, I'm using advent of code to teach me a little bit of clojure. I'm resolving Day 3: Crossed Wires. This is the code so far and I would like a little bit of feedback on two things (and on anything that you think could be improved):
(ns cljbrave.core)
(defrecord Movement [direction length])
(defrecord Coordinate [x y])
(def origin (->Coordinate 0 0))
(defn movements
"R6 becomes (:R 6)"
[string-coordinates]
(->Movement (keyword (str (first string-coordinates)))
(Character/digit (last string-coordinates) 10)))
(defn get-movements
"R6,U7 becomes [(:R, 6),(:U, 7)]"
[string-of-coordinates]
(map movements (.split string-of-coordinates ",")))
(defn moveUp [p] (+ p 1))
(defn moveDown [p] (- p 1))
(def directionsFn {
:R {:x moveUp :y identity}
:U {:x identity :y moveUp}
:L {:x moveDown :y identity}
:D {:x identity :y moveDown}
})
(defn moves1
[movement length directionFn]
(if (= length 0)
[]
(let [next (->Coordinate ((:x directionFn) (:x movement))
((:y directionFn) (:y movement)))]
(cons next (moves1 next (- length 1) directionFn)))))
(defn moves
"(0,0) and (:R, 2) will give ((1,0), (2,0))"
[start movement]
(moves1 start (:length movement) ((:direction movement) directionsFn)))
(defn generate-coordinates
"R1,D1 becomes [(1,0),(1,-1)]"
[raw-instructions]
(let [instructions (get-movements raw-instructions)]
(loop [start origin
step (count instructions)
reminder instructions
result []]
(if (= step 0)
(flatten result)
(let [path (moves start (first reminder))]
(recur (last path) (dec step) (rest reminder) (conj result path)))))))
• can generate-coordinates
be done better? Ideally what it should do is a (flatten (map (partial moves origin) (get-movements raw-instructions)
where moves
is not actually partial but it should use the last element of the list generated by the step before.
• am I using correctly defrecord
or should I use deftype
in this case?I haven't looked closely at the code yet, but clojure's flatten is pretty much always the wrong thing
the usage of records without implementing protocols is a code smell, you probably want maps here
(x,y)
is a weird notation - idiomatic for sequential data is [x y]
, for maps {:some-key x :other-key y}
> am I using correctly defrecord
or should I use deftype
in this case?
use neither, idiomatic for this kind of thing would be hash-maps with keyword keys
moves1 (and any other function that calls cons as its tail) should be a lazy-seq to better utilize the stack and integrate with clojure's other collection functions
you can literally change (cons x y)
to (lazy-seq (cons x y))
to get a lazy result
any loop that steps element by element (using count and rest) can be replaced by map
or reduce
, which will significantly simplify the code
here I think you need reduce since you have an accumulator
you can avoid using flatten by changing conj
- likely you want into rather than conj
perhaps into would also need a cat
transducer, but probably not here
Thank you @noisesmith, will work on what you suggested, what do you mean by "implementing a protocol"?
the reason we have defrecord and deftype is in order to have type based method dispatch
if you dont' have methods that dispatch on type, you don't need a defrecord or deftype, and we just use hash-maps, lazy-seqs, sets, vectors etc. directly
In theory, you could use defrecord as a schema for your maps, but it is recommended to use spec for that instead.
also it's not much of a schema - you can remove keys and it turns into a map, fail to attach them and they are just nil, add them and it quietly adds them regardless of defined fields
@dierre_spam I'm sure this sounds odd but we don't idiomatically name our data structures - it's a lisp thing
even then, you contextually assert a datum matches a spec, you don't attach a spec to a structure at rest
that is, at the point where the data are consumed or generated, and not where they are defined
But, getting used to most things not having name is important. Normally, only shared domain entities would be specced and given a name and a clear specification
I actually just wrote a kind of lengthy reply talking a bit about this: https://clojureverse.org/t/separating-implementation-from-contracts/5255/2?u=didibus As a beginner, it might be hard for you to follow what I'm talking about, but it might still help a little to move the needle in your head.
I've read your post. My view on this is that, while I was writing the code, first of all I noticed that you need to be really clear with the naming of the variable because that's basically the type information. What I don't like though is that I need to read a good amount of code to understand what this argument is: I need to see where the function is called and what is actually passed. It's true that I probably care only about this argument is used inside the function (which are probably the first citizen of this language), but practically speaking to me it's faster to understand the code if I can put something like ^Record in front of the variable name so I can click on it and see what is that about.
Thanks for reading. I understand your feeling. I had it too when I started, but it has mostly dissipated. I'm not sure what made it dissipate to he honest. I think it was just knowing Clojure better. I got better at following things along and didn't felt I needed record definitions for everything anymore. When I started, I was definitely making use of records more, and adding unnecessary type hints, and hoping for more type info.
I think my question would be... Why do you need to know what is actually passed to the function?
Ok, in case of the clojure sdk I feel the names are enough. A quick search of the function signatures like map
, reduce
always solves my problem because there aren't these many concepts in clojure, but I think that when it comes to the domain of the application, it's easier to reason if you have types. I'm not saying I want to do type driven development, but for now I feel it's not wrong to give types to your domain.
One thing that is common in Clojure is hard to explain. But its a style where your data is stored in a data-store. This can be a persistent one, like an actual DB, or an in-memory one, which could just be a map inside an atom.
They all just take a partial set of the data from the store as input directly. Those normally end up being clear enough just by having good arg names, doc-strings, destructuring, etc. Though you can also choose to spec them fully or partially.
And now you have other functions, which act as orchestrators. So if your domain has 10 logical use cases/operations. You have one function for each. And its that orchestrating function which will query the data-store and update it accordingly, and it'll make use of the other functions I just talked about to implement the business logic
you mean this, correct? https://clojure.org/guides/spec
Basically, you need to start thinking in terms of functions that do things, and things that you want done.
Be patient, its one of the big mental shifts that Clojure requires, and it takes a lot of time.
So I changed from this
(defn generate-coordinates
"R1,D1 becomes [(1,0),(1,-1)]"
[raw-instructions]
(let [instructions (get-movements raw-instructions)]
(loop [start origin
step (count instructions)
reminder instructions
result []]
(if (= step 0)
(flatten result)
(let [path (moves start (first reminder))]
(recur (last path) (dec step) (rest reminder) (conj result path)))))))
to this:
(defn path-accumulator
[result current]
(let [current-path (moves (:start result) current)]
{:start (last current-path) :acc (flatten (conj (:acc result) current-path))}))
(defn generate-coordinates-dos
"R1,D1 becomes [(1,0),(1,-1)]"
[raw-instructions]
(:acc (reduce path-accumulator {:start origin :acc []} (get-movements raw-instructions))))
I only have a slight problem which is this:
(deftest can-generate-a-list-of-coordinates
(testing "R2,D1 becomes [(1,0),(2,0),(2,-1)]"
(is (= (generate-coordinates "R2,D1") [(->Coordinate 1 0), (->Coordinate 2 0), (->Coordinate 2 -1)]))))
(deftest can-generate-a-list-of-coordinates-dos
(testing "R2,D1 becomes [(1,0),(2,0),(2,-1)]"
(is (= (generate-coordinates-dos "R2,D1") [(->Coordinate 2 -1), (->Coordinate 1 0), (->Coordinate 2 0)]))))
The order is not the same...but the values are...flatten returns a list, conj order on lists is different from vectors
(this may not be the issue)
yeah - it is the issue - you are flattening at every step, where before you flattened at the end
(ins)user=> (conj [:a :b :c] :d)
[:a :b :c :d]
(ins)user=> (conj (flatten [:a :b :c]) :d)
(:d :a :b :c)
if you need to flatten, do it outside the reduce, ideally you can rewrite such that you never call flatten
calling into
instead of conj
will probably give you the right result without any flatten call
(ins)user=> (conj [:a :b :c] [:d :e])
[:a :b :c [:d :e]]
(cmd)user=> (into [:a :b :c] [:d :e])
[:a :b :c :d :e]
Does the order in which I define functions in a file matter?
And be careful, at the REPL, it might work even in the wrong order, if you have evaluated the functions in the right order. But it won't work when in production
Only things that have been evaluated before exist and can be used. And Clojure evaluates things in a file from top to bottom when required. But at the REPL, you can force evaluate things in any order, which is why it could work in the REPL, but it won't in prod, where Clojure require will evaluate things from top to bottom.
Clojure evaluates files form by form top to bottom to match the behavior of typing one form after another into the repl
This is a bit different from other languages, right?
Also now I have this: Syntax error macroexpanding clojure.core/defn at (core.clj:60:1)
Which is… 🤯
I changed something small somewhere else in the file.
It is reminiscent of some standard ml implementations and kind of matches the definition of standard ml
I did, it may be:
"For a vector of steps of the form ["R" 10] etc., generates a vector of points starting from origin."
That was my docstring.
Which was fine up until just now.
If I remove the docstring, it does not complain anymore.
The repl is stateful, and you are accumulating state in it as you work(all your definitions and redefinitions), if you end up in some bad unknown state it can be good to start a new repl so you are working from a known state
Is that why this has been working up until now?
Also doing fine reloading the file.
"For a vector of steps of the form ['R' 10] etc., generates a vector of points starting from origin."
or"For a vector of steps of the form [\"R\" 10] etc., generates a vector of points starting from origin."
Reloading the file doesn't remove the old file from the running repl, so things would still work. Unless you use a library which can first unload things and then reload, such as tools.namespace
You might have had an R name defined in which case a string followed by R followed by another string might not throw an error
Presumably you are here because you are encountering errors, so if that code was later in the file then code producing the error it would never get evaluated
So, I dunno what circumstances would lead you to believe the above was working, but I can tell you it was not
If I start the repl again, it does not automatically load the core.clj?
that depends on the config of job starting your repl
I'm new, I just created a project.
clojure itself only autoloads user.clj, tools can make it load other things at startup for your convenience
Normally, most REPL start you in a user namespace, which requires clojure.core automatically
In other namespaces, if you have a ns
declaration, it will require clojure.core automatically as well
lein repl
He has a new lein project, core.clj is referring to the file lein generated for him,not clojure.core
I can't get it to work anymore, so that means whatever I had that was working was in the REPL and not in my source file?
But your source file is not rebuilding that state do it doesn't work in a fresh repl
vscode.
But I kill the REPL and restart and then it should be clean?
And all the reload
I was doing was for nothing?
For example you define some name but then remove that definition (or name include it) in your source file, which doesn't stop subsequent code from referring to that name
Now it's complaining: No matching method abs found taking 1 args
even though Math/abs
exists and works.
(use 'fwd.core :reload)
But how do I quickly edit/run cycle then?
Restarting the repl takes too long.
it will depend, but usally you just send a form at a time to be evaluated to your repl
a lot of editors with some kind of clojure mode will have short cuts for that kind of thing, but I find myself just using copy and paste more and more
copy-paste?!
That's a bit peculiar 😛 Why not use editor send to repl, which is effectively the same but more convenient ?
In calva, you want to do: ctrl+alt+c ctrl+alt+n
for Load current namespace in the REPL window
Or you want to do ctrl+alt+c ctrl+alt+e
if you want to evaluate only the current form
the accumulating state nature of the repl is something you get use to and getting it in to some unknown state where you need to restart it to recover is something you'll need to do less often
Lein run throws a huge exception: Exception in thread "main" Syntax error compiling at (fwd/core.clj:75:5)
But if I paste the offending code in, it's fine.
Most editors for use with Clojure have a way to customize them, or someone has already customized it for you, such that you can start a REPL session, or connect to an existing REPL session, within them, and a keystroke is bound to the action "wherever my cursor is, take the surrounding (or perhaps preceding) expression, and send it to the REPL for me"
with no copy/paste on your part
I haven't bothered to learn code's support yet.
I was under the impression that what I was doing was working.
Hum... maybe I'm getting the Calva commands wrong, it might be alt+ctrl+c enter
and alt+ctrl+c e
not sure, I'm going off the documentation :cop:
That particular keystroke/workflow I would say is a very good one to find in your IDE/editor of choice.
basically the form that causes the error changes some state in the repl while being compiled that causes it not to error again
one way to confirm that is to take a fresh repl, and evaluate each form from the namespace top to bottom, one by one, instead of loading the namespace all at once
you will get to the form that causes an error, get the error, and then try that form again immediately afterwards and get no error
So I copy pasted all my source code in a fresh repl.
(Because I want to go to bed but I want this to work first.)
I am just explaining the process you can go through yourself in the future when you encounter something like this to try and figure it out
Form?
I mean yes, I read the line and pasted that code in the repl and that was fine.
or a number, or a keyword, or a standalone reader macro
or a hash map, etc.
it's anything clojure's reader can consume as a unit
No, fresh repl.
The error is from lein run
Then I do lein repl
And paste in:
(defn find-overlaps
"Takes two lists of points and returns the ones that are in both."
[points1 points2]
(remove
#(= origin %)
(clojure.set/intersection (set points1) (set points2))))
(def origin {:x 0 :y 0})
It doesn't complain about a syntax error then.
So I guess there is no syntax error?
is origin defined before that line? do you ever call require
on clojure.set?
Do I need to call require
?
clojure.set is not guaranteed to be present, you should require namespaces if you use them
if you use something defined in another namespace, you need to require that namespace
But in the repl it works?
repls can load namespaces
I mean yes, that would explain it.
that doesn't mean they are always present
require ensures they are present
How do I require in a file?
This only shows repl: https://clojuredocs.org/clojure.core/require
for a namespace, go by this https://stuartsierra.com/2016/clojure-how-to-ns.html
That gets complicated 😛 Are you trying to require your own source file? Or one from clojure ?
for the repl, you can one-off require (require 'foo.bar)
with a '
The only error I’ve seen pasted had fwd/something in it. Have you pasted the actual error you’re seeing?
Is there no way to require java.lang.Math
?
that's a class, not a namespace
they are auto-loaded when referenced
It was working nicely in the repl.
if you want to use abs, use Math/abs
, it auto-loads
Not if I do lein run
.
show us the actual error
Caused by: java.lang.IllegalArgumentException: No matching method abs found taking 1 args
OK, what does your abs call look like? what's the type of the argument?
for example
user=> (Math/abs :a)
Execution error (IllegalArgumentException) at user/eval177 (REPL:1).
No matching method abs found taking 1 args
(Math/abs (get point :x))
OK, what does (get point :x)
return? try printing it, I bet it isn't a number
the distinction here is that it's not saying abs doesn't exist, it's saying that abs isn't implemented for the type provided
You're right.
It is for a while and then suddenly it's not.
But yeah, I'm not going to be able to fix this tonight anymore.
I'll read up tomorrow whether a decent edit/run/test cycle is possible on my setup.
One that isn't lying to me.
And also doesn't take the 5 second delays of lein run
.
Stack Overflow said not to use reload
.
Isn't it a bug if it doesn't work properly?
I mean in haskell this was working cleanly at least.
Keep reloading the source file you're working on into the repl.
Anyway, I'll read up more tomorrow.
The language is nice.
The stuff around it…