Fork me on GitHub
#beginners
<
2017-05-20
>
matan12:05:01

a records (`defrecord`) question: why does the map-> constructor actually silently agree to wrong key names and to invalid arities for the record type's definition, whereas ordinary record instantiation enforces the correct arity?

matan13:05:28

also ― how do you edit a multi-line function on the repl? when I paste a long function to the repl, it seems (by pressing the up-arrow) I can only bring up each of its lines on its own ― I'm probably doing it all wrong in terms of repl'ing 😉

alexmiller13:05:25

On records, not completely sure I understand your question but records are not limited to just the well known keys - you can assoc any key into a record

alexmiller13:05:02

The positional constructor doesn't have a way to do that but that's supported in the map constructor

alexmiller13:05:11

On the repl, it will depend on the repl, but in many you can't. I typically use the repl indirectly by evaluating code from inside a source file where I have full editing capability for stuff like that.

vitruvia15:05:29

if you have a lein repr open on a terminal do you need to open a second terminal to use system commands?

dpsutton15:05:04

quick answer is yes, just have another tab/window open. Longer answer is learn about jobs in unix so you can "minimize" the repl and work in other jobs and return to the repl when you're ready (http://linuxcommand.org/lts0080.php) and then perhaps one more suggestion is make sure that you have a code environment setup that integrates text editing and repl interaction. For example CIDER, cursive, light table, vs code with plugins, etc

vitruvia16:05:51

yes I'm setting up vim to use acid right now

matan16:05:04

vitruvia: TL;DR control-z to suspend the repl, fg to resume. I'd stick to that minimal usage as fiddling many more jobs on the same window might lead to shooting yourself in the foot.

vitruvia16:05:05

I imagine this should do the third thing you mentioned.

vitruvia16:05:22

by the way thanks for this link on job control, I just read it and it is exactly what I was looking for

matan16:05:01

@alexmiller thanks, that makes sense about the repl.

matan16:05:08

@alexmiller about records, I am not too sure why it was considered useful to allow associating keys outside a corresponding defrecord's set of keys. I guess the clojure designers clan always prefer flexibility & extensibility over "safety", although this is fairly inconsistent here that one constructor is safe and the other is flexible; The inconsistency is further that the simple constructor requires all keys be supplied, whereas the map-> one allows omitted keys defaulting to nil. I might just be looking for symmetry/consistency in the wrong place: Symmetry/consistency is always cognitively helpful, but it can only be limited to certain subsets of the concepts of a system.

noisesmith17:05:04

@matan if you want a specific set of fields what you want is exactly deftype

noisesmith17:05:24

defrecord is if you want default fields, plus everything a hashmap does

john17:05:59

custom maps

alexmiller18:05:02

@matan I think it's unreasonable to expect consistency between these two constructors - they are different because they give different affordances. Those differences are the benefits of the two approaches.

mobileink18:05:06

"affordances" 👍

mobileink18:05:06

@matan: fwiw i have found it useful to read https://clojure.org/reference/datatypes repeatedly. my reading is that it's all about striking a balance between abstraction and performance in the host env.. defrecord supports assocking strange keys because that's what the abstraction calls for (they could have called it "defmap").

lilactown21:05:17

is it improver to use a go-loop inside of a go block when using core.async?

lilactown21:05:04

i'm wondering if it's preventing me from closing it correctly

noisesmith21:05:01

no - but the macro-expansion of a nested go-loop inside go could easily grow too large

noisesmith21:05:31

and that's easy to fix by making a function that starts the go-loop

lilactown21:05:24

do you mean for readability purposes? or can a macro-expansion that's too large cause issues?

noisesmith21:05:42

a macro that expands too large can create code that's too big to fit in a method, which means the code won't run

lilactown21:05:42

i'm trying to use close! to stop the channel I created and it's continuing to loop

noisesmith21:05:01

how does closing the channel stop the go-loop ?

lilactown21:05:05

I suppose that's the part I'm confused about 😅 how do I stop the go-loop?

noisesmith21:05:11

by not calling recur

noisesmith21:05:26

if you read from a channel, you can detect that it is closed if it returns nil, and not recur

noisesmith21:05:39

it really depends on what you are doing in the loop though

lilactown21:05:52

hm so this is more complex than I thought

lilactown21:05:17

I'm reading from a websocket connection (CLJS)

noisesmith21:05:49

a common pattern for a loop you want to be able to shut down, is to pass it a poison channel, and have it stop if the channel closes

noisesmith21:05:32

you can use alts! to read from your normal channel, or the poison channel (whichever has data) and if you read from the poison channel, you take that as a signal to stop

noisesmith21:05:43

alts! will let you know which channel you read from

lilactown21:05:09

so right now my code is structured kind of like:

(defn create-stream [handler]
  (go (let [messages (<! (create-ws-connection "url"))]
    (go-loop [] (when-let [{:keys message} (<! messages)] (handler message)))))

lilactown21:05:31

it's mostly a read-only stream so I think this is OK

noisesmith21:05:44

you know that could easily be a loop inside a go...

noisesmith21:05:27

also, that go-loop doesn't loop

noisesmith21:05:35

it reads one message, runs a handler, and exits

lilactown21:05:35

ah I forgot the recur 🙂

lilactown21:05:58

it's not the exact code, just trying to write down the gist

noisesmith21:05:13

anyway, if you create a poison channel (just a normal channel that you close when you want the loop to exit) you can just turn the <! into an alts! and leave the rest the same

lilactown21:05:31

and I can change the go-loop to just loop?

noisesmith21:05:40

since it's already inside go

lilactown21:05:08

cool, literally started reading the docs of core.async yesterday 😛

lilactown21:05:25

so is this the rough idea?

(defn create-stream [handler]
  (go (let [messages (<! (create-ws-connection "url"))
            poison (chan)]
    (go-loop [] (let [[{:keys message} ch] (alts! messages poison)]
                  (if-not (= ch poison)
                          (do (handler message)
                              (recur)))))))

lilactown21:05:54

oh but I need to return the poison channel

noisesmith21:05:57

right, but you can literally change the go-loop to loop

noisesmith21:05:12

and the args to alts! go in a vector

noisesmith21:05:28

you don't need to check for poison

noisesmith21:05:47

if the result is nil that means either your ws connection is closed, or poison is closed

noisesmith21:05:49

both mean stop

noisesmith21:05:05

so you can just check if what you read was nil

lilactown21:05:22

I think I need to create the poison chan outside of the initial go, as well?

lilactown21:05:25

(defn create-stream [handler]
  (let [poison (chan)]
    (go (let [messages (<! (create-ws-connection "url"))]
          (loop [] (let [[val] (alts! [messages poison])]
                     (if-not (nil? val)
                             (do (handler message)
                                 (recur)))))))
    poison)

noisesmith21:05:48

(defn create-stream
  [handler]
  (let [poison (chan)]
    (go (let [messages (<! (create-ws-connection "url"))]
      (loop []
        (let [[{:keys [message] :as m}] (alts! [messages poison])]
          (when m
            (handler message)
           (recur))))))
  poison)

noisesmith21:05:20

so yeah, we ended up with pretty much the same thing, so looks good

lilactown21:05:30

awesome, thanks! I really appreciate the help