Fork me on GitHub
#beginners
<
2019-12-20
>
FiVo12:12:00

(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?

sh13:12:52

(defn test-bar []
  (doall (map (fn [_] test) (repeat 3 0))))

sh13:12:47

binding will pop-thread-bindings before return. Since map returns a lazy sequence, it is materialized (by repl) after bindings are cleared.

sh13:12:48

(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))))

👌 2
potetm13:12:58

lazy evaluation

potetm13:12:56

bonus: there is a slightly different form of test-bar that would do what you’re expecting

potetm13:12:37

the question is: how do you bind the value of test earlier?

dakra15:12:57

What's a good websocket client for clojure (not clojurescript? https://github.com/stalefruits/gniazdo ?)

bertofer15:12:02

That’s the one I’ve been using so far

dakra15:12:21

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

bertofer15:12:16

I see, I haven’t used it in high volume usage so I am not sure of the limitations

skroth15:12:25

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.

dakra15:12:25

ah nice. I've heard of aleph before. Definitely looks more mature but also a way heavier dependency for only a websocket client

bertofer16:12:02

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.

bertofer16:12:05

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

dakra16:12:11

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.

👍 1
bertofer16:12:30

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

ghadi16:12:59

@daniel415 depending on how comfortable you feel with interop, there’s java.net.http built in to java>=11

👍 1
ghadi16:12:17

Includes websocket functionality

dakra16:12:09

I'm comfortable in Java. using http://java.net.http seems like a good idea

dakra16:12:19

Thanks for all the tips and hints 🙂

dakra16:12:05

There's even already https://github.com/schmee/java-http-clj with websocket support

👍 1
schmee21:12:18

@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:

ghadi16:12:54

I have a client too but i haven’t open sourced it yet

dakra16:12:57

maybe I even replace my clj-http dependency with that

ghadi16:12:21

Yeah clj-http is heavy and old, and stable

Emanuele19:12:44

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?

noisesmith20:12:07

I haven't looked closely at the code yet, but clojure's flatten is pretty much always the wrong thing

noisesmith20:12:41

the usage of records without implementing protocols is a code smell, you probably want maps here

noisesmith20:12:10

(x,y) is a weird notation - idiomatic for sequential data is [x y] , for maps {:some-key x :other-key y}

noisesmith20:12:39

> 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

noisesmith20:12:51

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

noisesmith20:12:42

you can literally change (cons x y) to (lazy-seq (cons x y)) to get a lazy result

noisesmith20:12:08

any loop that steps element by element (using count and rest) can be replaced by map or reduce, which will significantly simplify the code

noisesmith20:12:26

here I think you need reduce since you have an accumulator

noisesmith20:12:17

you can avoid using flatten by changing conj - likely you want into rather than conj

noisesmith20:12:34

perhaps into would also need a cat transducer, but probably not here

Emanuele20:12:50

Thank you @noisesmith, will work on what you suggested, what do you mean by "implementing a protocol"?

noisesmith20:12:12

the reason we have defrecord and deftype is in order to have type based method dispatch

noisesmith20:12:45

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

noisesmith20:12:22

protocols define methods that dispatch on the implementation of various types

👍 1
didibus22:12:41

In theory, you could use defrecord as a schema for your maps, but it is recommended to use spec for that instead.

Emanuele22:12:19

I wanted to give a name to the structure basically

noisesmith22:12:46

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

noisesmith22:12:24

@dierre_spam I'm sure this sounds odd but we don't idiomatically name our data structures - it's a lisp thing

didibus22:12:32

Ya, Spec is the recommended way to name your structures.

noisesmith22:12:04

even then, you contextually assert a datum matches a spec, you don't attach a spec to a structure at rest

noisesmith22:12:25

that is, at the point where the data are consumed or generated, and not where they are defined

didibus22:12:16

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

didibus22:12:14

In effect, I'd suggest your name be the name of the variable/argument.

didibus22:12:53

Basically, you need to start thinking in terms of functions that do things, and things that you want done.

didibus22:12:12

Not in term of Nouns and objects and what they can do

Emanuele22:12:53

I will try to give it a try. Thanks

didibus22:12:21

Be patient, its one of the big mental shifts that Clojure requires, and it takes a lot of time.

Emanuele22:12:45

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))))

Emanuele22:12:23

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...

noisesmith22:12:53

flatten returns a list, conj order on lists is different from vectors

noisesmith22:12:27

(this may not be the issue)

noisesmith22:12:31

yeah - it is the issue - you are flattening at every step, where before you flattened at the end

noisesmith22:12:59

(ins)user=> (conj [:a :b :c] :d)
[:a :b :c :d]
(ins)user=> (conj (flatten [:a :b :c]) :d)
(:d :a :b :c)

noisesmith22:12:16

if you need to flatten, do it outside the reduce, ideally you can rewrite such that you never call flatten

noisesmith22:12:31

calling into instead of conj will probably give you the right result without any flatten call

noisesmith22:12:06

(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]

didibus22:12:33

Ya, favor into over flatten, it is faster as well

Alper Cugun22:12:32

Does the order in which I define functions in a file matter?

didibus22:12:09

You can only reference things defined before

didibus22:12:27

So you need to order functions and defs based on their dependencies

didibus22:12:43

So if B uses A, you need to have A first and B after in the file

didibus22:12:06

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

didibus22:12:24

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.

hiredman22:12:58

You have some flipped arrows of causation

hiredman22:12:56

Clojure evaluates files form by form top to bottom to match the behavior of typing one form after another into the repl

didibus22:12:34

Hum, I'm not sure I'm seeing where I have flipped arrows ?

Alper Cugun22:12:33

This is a bit different from other languages, right?

Alper Cugun23:12:47

Also now I have this: Syntax error macroexpanding clojure.core/defn at (core.clj:60:1)

Alper Cugun23:12:54

Which is… 🤯

Alper Cugun23:12:52

I changed something small somewhere else in the file.

hiredman23:12:14

It is reminiscent of some standard ml implementations and kind of matches the definition of standard ml

didibus23:12:33

It's different than most OO languages, which compile classes together yes

didibus23:12:57

That means you are not using defn correctly

didibus23:12:06

You have a syntax error in it

didibus23:12:20

Review your syntax to defn

Alper Cugun23:12:33

I did, it may be:

"For a vector of steps of the form ["R" 10] etc., generates a vector of points starting from origin."

Alper Cugun23:12:37

That was my docstring.

Alper Cugun23:12:42

Which was fine up until just now.

Alper Cugun23:12:51

If I remove the docstring, it does not complain anymore.

didibus23:12:01

You need to escape the inner quote, or use the single quote instead

hiredman23:12:14

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

Alper Cugun23:12:37

Is that why this has been working up until now?

Alper Cugun23:12:44

Also doing fine reloading the file.

hiredman23:12:50

It hasn't been

didibus23:12:53

"For a vector of steps of the form ['R' 10] etc., generates a vector of points starting from origin."
or

didibus23:12:59

"For a vector of steps of the form [\"R\" 10] etc., generates a vector of points starting from origin."

hiredman23:12:27

You might be think it was working fine, but it wasn't

didibus23:12:07

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

hiredman23:12:30

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

hiredman23:12:25

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

hiredman23:12:55

So, I dunno what circumstances would lead you to believe the above was working, but I can tell you it was not

Alper Cugun23:12:10

If I start the repl again, it does not automatically load the core.clj?

noisesmith23:12:35

that depends on the config of job starting your repl

hiredman23:12:50

Generally no

Alper Cugun23:12:56

I'm new, I just created a project.

noisesmith23:12:07

clojure itself only autoloads user.clj, tools can make it load other things at startup for your convenience

didibus23:12:58

How are you starting the REPL ?

didibus23:12:16

Normally, most REPL start you in a user namespace, which requires clojure.core automatically

didibus23:12:55

In other namespaces, if you have a ns declaration, it will require clojure.core automatically as well

hiredman23:12:49

He has a new lein project, core.clj is referring to the file lein generated for him,not clojure.core

Alper Cugun23:12:07

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?

hiredman23:12:34

Yes, you had some accumulated state in your repl

didibus23:12:37

Ya, make sense

didibus23:12:55

What IDE do you use?

hiredman23:12:06

But your source file is not rebuilding that state do it doesn't work in a fresh repl

didibus23:12:24

If you use Cider, you can run C-c M-n r to sync your REPL to your source files

hiredman23:12:46

The most common state to cause that kind of problem is names

didibus23:12:02

With Calva ?

Alper Cugun23:12:11

But I kill the REPL and restart and then it should be clean?

Alper Cugun23:12:26

And all the reload I was doing was for nothing?

hiredman23:12:29

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

hiredman23:12:44

But in a new repl will cause an error

hiredman23:12:13

reload is not a feature of clojure, so I am not sure what you were doing

Alper Cugun23:12:30

Now it's complaining: No matching method abs found taking 1 args even though Math/abs exists and works.

Alper Cugun23:12:59

(use 'fwd.core :reload)

hiredman23:12:40

Yeah, that just accumulates more state

Alper Cugun23:12:53

But how do I quickly edit/run cycle then?

Alper Cugun23:12:59

Restarting the repl takes too long.

hiredman23:12:42

it will depend, but usally you just send a form at a time to be evaluated to your repl

hiredman23:12:19

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

hiredman23:12:03

well, kill and yank

hiredman23:12:32

but I am more minimalist about tooling than most

didibus23:12:39

That's a bit peculiar 😛 Why not use editor send to repl, which is effectively the same but more convenient ?

didibus23:12:07

In calva, you want to do: ctrl+alt+c ctrl+alt+n for Load current namespace in the REPL window

hiredman23:12:42

because I get a more consistent always working experience with copy and paste

didibus23:12:45

Or you want to do ctrl+alt+c ctrl+alt+e if you want to evaluate only the current form

didibus23:12:56

But in the latter case, you need to understand order

hiredman23:12:16

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

Alper Cugun23:12:26

Lein run throws a huge exception: Exception in thread "main" Syntax error compiling at (fwd/core.clj:75:5)

Alper Cugun23:12:36

But if I paste the offending code in, it's fine.

andy.fingerhut23:12:43

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"

andy.fingerhut23:12:47

with no copy/paste on your part

hiredman23:12:57

likely it is only fine after the error

hiredman23:12:10

e.g. in a fresh repl where you haven't gotten the error it won't work

Alper Cugun23:12:17

I haven't bothered to learn code's support yet.

Alper Cugun23:12:26

I was under the impression that what I was doing was working.

didibus23:12:32

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:

andy.fingerhut23:12:42

That particular keystroke/workflow I would say is a very good one to find in your IDE/editor of choice.

hiredman23:12:45

basically the form that causes the error changes some state in the repl while being compiled that causes it not to error again

didibus23:12:39

You have to tell us what you are doing though, if you need further assistance

hiredman23:12:18

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

hiredman23:12:46

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

Alper Cugun23:12:01

So I copy pasted all my source code in a fresh repl.

hiredman23:12:15

I would expect the error you get to include a little more information

Alper Cugun23:12:16

(Because I want to go to bed but I want this to work first.)

hiredman23:12:37

that won't make it work

hiredman23:12:06

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

hiredman23:12:42

your problem is something on the form starting on line 75

Alper Cugun23:12:23

I mean yes, I read the line and pasted that code in the repl and that was fine.

hiredman23:12:50

but you pasted it after you had already tried to load your code

hiredman23:12:05

and that loading changes the state of the repl

Alper Cugun23:12:05

No, fresh repl.

Alper Cugun23:12:09

The error is from lein run

Alper Cugun23:12:19

Then I do lein repl

hiredman23:12:28

oh, lein run is a whole other thing

Alper Cugun23:12:33

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))))

Alper Cugun23:12:44

(def origin {:x 0 :y 0})

Alper Cugun23:12:58

It doesn't complain about a syntax error then.

Alper Cugun23:12:04

So I guess there is no syntax error?

noisesmith23:12:07

is origin defined before that line? do you ever call require on clojure.set?

Alper Cugun23:12:26

Do I need to call require ?

didibus23:12:33

What is the error? Can you paste it here ?

noisesmith23:12:48

clojure.set is not guaranteed to be present, you should require namespaces if you use them

didibus23:12:54

Yes, you have to require every other namespace you use before using them

hiredman23:12:56

if you use something defined in another namespace, you need to require that namespace

Alper Cugun23:12:56

But in the repl it works?

noisesmith23:12:08

repls can load namespaces

Alper Cugun23:12:10

I mean yes, that would explain it.

noisesmith23:12:14

that doesn't mean they are always present

didibus23:12:14

Some REPLs auto-require clojure.set as a convenience

hiredman23:12:17

the set of loaded namespaces is part of the repls state

noisesmith23:12:19

require ensures they are present

Alper Cugun23:12:27

How do I require in a file?

didibus23:12:17

That gets complicated 😛 Are you trying to require your own source file? Or one from clojure ?

noisesmith23:12:20

for the repl, you can one-off require (require 'foo.bar)

hiredman23:12:37

you can require in a file the same way you do in the repl

hiredman23:12:09

but the convention is to do it all at the top as part of the ns declaration

dpsutton23:12:27

The only error I’ve seen pasted had fwd/something in it. Have you pasted the actual error you’re seeing?

Alper Cugun23:12:52

Is there no way to require java.lang.Math?

noisesmith23:12:04

that's a class, not a namespace

noisesmith23:12:11

they are auto-loaded when referenced

Alper Cugun23:12:34

It was working nicely in the repl.

noisesmith23:12:34

if you want to use abs, use Math/abs, it auto-loads

Alper Cugun23:12:44

Not if I do lein run.

noisesmith23:12:54

show us the actual error

Alper Cugun23:12:29

Caused by: java.lang.IllegalArgumentException: No matching method abs found taking 1 args

noisesmith23:12:44

OK, what does your abs call look like? what's the type of the argument?

noisesmith23:12:03

for example

user=> (Math/abs :a)
Execution error (IllegalArgumentException) at user/eval177 (REPL:1).
No matching method abs found taking 1 args

Alper Cugun23:12:14

(Math/abs (get point :x))

noisesmith23:12:34

OK, what does (get point :x) return? try printing it, I bet it isn't a number

noisesmith23:12:57

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

Alper Cugun23:12:01

You're right.

Alper Cugun23:12:16

It is for a while and then suddenly it's not.

Alper Cugun23:12:43

But yeah, I'm not going to be able to fix this tonight anymore.

Alper Cugun23:12:59

I'll read up tomorrow whether a decent edit/run/test cycle is possible on my setup.

Alper Cugun23:12:16

One that isn't lying to me.

didibus23:12:30

You just need practice

Alper Cugun23:12:33

And also doesn't take the 5 second delays of lein run.

didibus23:12:40

Eventually you'll learn how to be productive in the REPL

Alper Cugun23:12:55

Stack Overflow said not to use reload.

didibus23:12:14

I would agree

Alper Cugun23:12:39

Isn't it a bug if it doesn't work properly?

didibus23:12:04

It does work properly

didibus23:12:09

You just arn't using it correctly

didibus23:12:03

That's why you need to learn more about it, and practice using it some more

didibus23:12:56

Feel free to ask questions about it here, we'll do our best to make it clear

Alper Cugun23:12:11

I mean in haskell this was working cleanly at least.

didibus23:12:35

I don't know what that means to be working cleanly

didibus23:12:48

Clearly, if you were not using Haskell correctly, it would also not work

Alper Cugun23:12:01

Keep reloading the source file you're working on into the repl.

Alper Cugun23:12:10

Anyway, I'll read up more tomorrow.

Alper Cugun23:12:13

The language is nice.

Alper Cugun23:12:18

The stuff around it…

didibus23:12:31

It doesn't seem you were doing that in Clojure though

didibus23:12:14

Specifically, if I remember correctly, GHCi always clears bindings and reloads the new file.

didibus23:12:38

This has the problem that all state is lost, and its a big downside when working in GHCi