Fork me on GitHub
#beginners
<
2020-03-31
>
OrdoFlammae00:03:06

I'm trying to learn Reagent, but I'm kind of struggling with how most of the variables are mutable. Isn't the idea with ClojureScript to have immutable values? How do you program functionally when most of your functions are impure? Or am I just completely on the wrong track?

noisesmith00:03:49

the fundamental thing that react does is provide a way to re-render when your data changes - reagent engages with that directly

noisesmith00:03:28

your code should avoid mutating state, but it should render a form using data that could update (and you can define a form to re-render when that data updates)

OrdoFlammae00:03:08

So how does that work? If you need to mutate state, how do you avoid mutating state? I'm sorry if I'm being confusing, I'm a little new to this.

noisesmith00:03:49

I intentionally say "avoid" instead of don't - it's something we intentionally minimize but can't eliminate

OrdoFlammae00:03:06

OK. Thanks for clarifying.

noisesmith00:03:12

most changes to state are going to come from the browser: some user input changes something, your app has a defined way to react

noisesmith00:03:41

so there's inherently a tracking of state in each widget that's wired into react - that should be mostly hidden from your code

noisesmith00:03:25

what your code does is access the current value out of app state from inside a component, and when that happens the react engine will know to come back and re-render that component if the state it reads from changes

noisesmith00:03:02

thus instead of a bunch of commands saying "re-render this, re-render its children" or whatever, the framework can decide what needs rendering based on how you define and use your data

noisesmith00:03:14

that's the big picture tl;dr at least

OrdoFlammae00:03:33

So only make things mutable if they directly involve what's being put on the screen?

OrdoFlammae00:03:45

I think I get the idea.

noisesmith00:03:55

yeah - that's pretty much it - you can actually treat the mutables as immutable args to your rendering function - and reagent will make sure your rendering function is called again if the args change

noisesmith00:03:03

so it's a lot like data flow if you've heard of that

OrdoFlammae00:03:39

OK. Thanks @U051SS2EU.

Adrian Smith18:04:55

There's a good talk on this: https://www.youtube.com/watch?v=vK1DazRK_a0 where the mutation progressively gets minimized throughout the talk

Michael Stokley00:03:00

question about nesting defn - you could put defn inside of a let form, but that wouldn't be idiomatic, would it?

hiredman00:03:43

I dunno the context, but if you are coming from scheme, it is important to know in clojure def always creates a global "top level" definition regardless of the scope it is in

motform17:03:45

If you need a named fn inside your form, there is a let-fn

Michael Stokley22:04:42

does the clojure community have opinions about (if pred x) versus (when pred x)? should when be used if there's no else?

Michael Stokley22:04:08

or should it merely be used instead of (if (do

hiredman22:04:21

it is a divisive issue, at my last job you would bring that up if you wanted a break from arguing about git branching

hiredman22:04:30

I like when for any single branched if, but some common lisp style guides (which a lot of cues for clojure style came from) suggest only using when if there are side effects

Michael Stokley22:04:39

to me, when means single branched if. i had no idea about the side effects.

hiredman22:04:05

the reasoning goes when has an implicit do, and do is for side effects

Michael Stokley22:04:43

i had no idea do is for side effects! I thought it was just for combining multiple expressions... which i guess you only need if you're doing side-effects?

Michael Stokley00:03:26

i feel like i mostly see let within def or defn, not the other way around

noisesmith00:03:18

the normal thing is to put the bindings that would be in the outer let into a def

noisesmith00:03:37

clojure code tends to avoid data hiding, this is viable when your values are mostly immutable

markx02:03:46

Trying again in this channel to see if I can get the answer.

dpsutton03:03:31

Looks like it. The ratoms put the components that are watching them in a queue and they just get batch rerendered. No did update, etc

markx03:03:52

I tried with this simple component, and apparently it does trigger the component-did-update! What’s wrong here?

(defn test-comp []
  (let [counter (r/atom 0)]
    (r/create-class
      {:display-name "test-comp"
       :component-did-update
       (fn [this]
         (prn "component did update"))

       :reagent-render
       (fn []
         [:div
          [:div @counter]
          [:button {:on-click  #(swap! counter inc)} "click"]])})))

dpsutton03:03:49

https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/batching.cljs#L35-L38 https://reactjs.org/docs/react-component.html#forceupdate > Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.

dpsutton03:03:57

just reading the docs. I also wonder if react has changed what it does since those docs for reagent were first written

markx04:03:28

That’s odd. So it really should trigger life cycle methods.

solf08:03:41

When using something like mount, should I wrap all my state in it? Even small things like an atom that contains a timestamp

tolitius11:03:08

you should not. only create a state for something low level and inherently stateful: i.e. I/O, queues/topics, connections, etc. while it might feel "uncomfortable" at first it will encourage you to learn to design and keep your functions stateless. for example a function that needs an "atom with a timestamp": it can take it as an argument or destructure a timestamp from configuration state that is passed in or, most likely, it could create one inside itself in a let block, use it and forget it. it would depend on a business use case of course.

solf08:03:56

Any downsides to it?

ruben09:03:41

I tried following the clojurescript guide, getting started guide. The browser showing the landing page is appeared but the repl never show its prompt. I have waited for about 5 minutes I think and never showed up.. Any flag/running option I can try to spit out which causing the error.

hindol09:03:47

Can you paste a link to the guide?

ruben10:03:32

add repl-opts launch-browser false seems solve the problem

ruben10:03:36

for figwheel add open-url false solve the issue too.

hindol11:03:45

If you are new to ClojureScript, I recommend jumping straight to shadow-cljs. I am new too and I find it super easy. It just works.

ruben11:03:04

I will try shadow cljs another time.

Gabriel Saliev10:03:29

Hi guys, I'm currently reading clojure for the brave and true and I encountered a simple problem that I cannot solve, it is related to namespaces. When I use the "require {namespace-name} " it simply doesn't work, it throws syntax check error although I have the correct name of the namespace, any ideas what it might be? I can send the code if it would be more helpful, thanks!

fricze10:03:11

surely, send the code 🙂

Gabriel Saliev10:03:35

(ns the-divine-cheese-code.visualization.svg)

(defn latlng->point
  "Convert lat/lng map to comma-separated string" 
  [latlng]
  (str (:lat latlng) "," (:lng latlng)))

(defn points
  [locations]
  (clojure.string/join " " (map latlng->point locations)))
that is the code in my /visualization folder in the project directory

Gabriel Saliev10:03:50

and this is in my core.clj

Gabriel Saliev10:03:15

(ns the-divine-cheese-code.core)
;; Ensure that the SVG code is evaluated
(require 'the-divine-cheese-code.visualization.svg)
;; Refer the namespace so that you don't have to use the 
;; fully qualified name to reference svg functions
(refer 'the-divine-cheese-code.visualization.svg)
I have the error on the line that I require the divine cheese visualization

fricze11:03:17

can you share the exact error?

fricze11:03:24

and maybe you have the code on github?

Gabriel Saliev11:03:56

it is filenotfound exception

fricze11:03:41

move your visualization dir to src

fricze11:03:30

so you’d have dir structure:

src/
  core.clj
  visualization/
    svg.clj

fricze11:03:03

it should do the trick

Gabriel Saliev11:03:52

Thanks very much!

fricze11:03:12

no worries

fricze11:03:31

glad it works

Phil Hunt11:03:14

Another REPL question. I'm used to SLIME / Lisp and CIDER is confusing me a bit. if I do C-c C-c in a source file, it seems to evaluate there rather than in the Repl.

Phil Hunt11:03:35

so if I (def my-data blah) .. and eval it, the REPL doesn't seem to know about it

Phil Hunt11:03:09

Am I making an obvious mistake?

Phil Hunt11:03:31

I started the REPL in the source file using C-c C-x C-x

Phil Hunt11:03:03

Ahhh, never mind. I think I fixed it. I was trying to use (in-ns ... ) to get to the right namespace, but apparently I have a Prelude menu command for that instead that works.

hindol12:03:04

@ruben #off-topic or the newly formed #exercism will be a good place for this.

pez14:03:38

How can I create a non-namespaced map if I have namespaced keys in it? I get this:

(map (fn [item] {(first item) {:foo/bar (second item)}})
       '[[foo "bar"]])
  ;; => ({foo #:foo{:bar "bar"}})
But would want ({foo {:foo/bar "bar"}})

alexmiller14:03:46

those are the same identical object, just different syntaxes

pez14:03:16

But I want a different syntax than what I get. 😃

pez14:03:27

(It's for deps.edn )

alexmiller14:03:05

if you want to turn off namespace map printing you can do (set! *print-namespace-maps* false)

alexmiller14:03:34

that's actually off by default, but turned on by most repls

pez14:03:14

It's actually off in my repl...

dpsutton14:03:38

i think nrepl might have trouble with this one actually

alexmiller14:03:46

if you're printing edn to a file or something, you can also (binding [*print-namespace-maps* false] (spit "foo.edn" a-map))

pez14:03:32

duh, I was also pretty printing. Setting it to false now works...

pez14:03:31

> i think nrepl might have trouble with this one actually So, not in this case, this was going through nrepl and (set! *print-namespace-maps* false) works as it should.

ryan echternacht15:03:36

If I want to get 1 item out of a list (like search a list for the item with the correct id), is there a func for that?

ryan echternacht15:03:56

I know I can (->> items (filter …) first) but is there something better?

ryan echternacht15:03:10

also that would short circuit when it finds the match

pez15:03:11

That looks excellent.

ryan echternacht15:03:30

oh, good point. I don’t think of some like that, but I guess that makes sense

pez15:03:59

I think you can also do something like (some #{1} [2 3 1 4]), depending on what your list looks like.

pez15:03:33

(And some->> doesn't seem to shortcut like you wanted it to, sorry about that, @ryan072)

ryan echternacht15:03:35

(some->> pred coll ...) is just shorthand for (->> coll (some pred) ...) right?

ryan echternacht15:03:35

(some->> pred coll ...) is just shorthand for (->> coll (some pred) ...) right?

noisesmith15:03:57

no - it's not, some->> is like ->> but stops if something is nil

user=> (some->> nil (+ 1))
nil

noisesmith15:03:35

there's little relationship between some some? and some-> / some->>

pez15:03:55

(some #{7} (range)) => 7 , but (some->> (range) (filter #(= 7 %))) never returns.

noisesmith15:03:16

they are totally different functions, yes

kelveden15:03:51

For me, the original code is clearest in its intent. And does it matter about the short-circuiting given that filter is lazy anyway?

solf16:03:02

Ahahah the power of the remote nrepl socket in prod (well actually staging, but next step?), it's corrupting meeeeeee

solf16:03:18

It would have taken me forever to find out that a dumb error on my staging conf was setting my redis uri to true instead of the actual uri

noisesmith16:03:55

yeah, it's like a superpower

noisesmith16:03:30

though in practice what I'm looking up is almost always an environment variable or config, so you can get 80% with a function that just dumps all env and configs readably

ryan echternacht16:03:39

@kelveden good point on filter being lazy. I guess which is more clear 1⃣ (first (filter #() items)) 2⃣ (some #(when (filter #()) %) items)

ryan echternacht16:03:45

obviously the second one has nested anonymous functions which doesn’t work

kelveden16:03:07

As with a lot of Clojure (and other languages) I guess it partly comes down to what you're used to. I always use the former but thread it usually; so: (->> items (filter #()) first).

kelveden16:03:56

That, to me looks more like the "pipeline" I imagine in my head when I have to do this sort of thing.

kelveden16:03:53

I'd also argue that as a function name, some doesn't intuitively describe what it does. But again, that could be just me.

motform17:03:45

I’m have some questions regarding instants in clj. I have a map of entities, all of which are properly timestamped and instantified, and I want to sort it in descending recency. In cljs, I can do (sort-by :date > coll), but it seems that clj does not have a built in comparator for insts. What are my options?

noisesmith17:03:18

you could try (comp #(.getTime %) :date) instead of :date to make it use a numeric type

noisesmith17:03:24

getTime gets the epoch ms

noisesmith17:03:39

compare should be working though

(ins)user=> (java.util.Date. 0)
#inst "1970-01-01T00:00:00.000-00:00"
(ins)user=> (java.util.Date. 1)
#inst "1970-01-01T00:00:00.001-00:00"
(ins)user=> (compare *2 *1)
-1

motform20:03:04

Ah, totaly forgot about compare! Thank you so much for the help, did just the trick!

motform20:03:22

I guess ill have to go read up on some javadocs for more on how to handle the insts

noisesmith20:03:12

the #inst notation is clojure specific, there's more than one type that prints that way

noisesmith20:03:50

but if you consume a #inst tagged object, you'll get a java.util.Date

kwrooijen19:03:13

Hi. If I understand corretly, defmethods are slow because it takes time to resolve them (please correct me if I'm wrong). I'm wondering if I were to use get-method, would that result in just a regular fn ? Meaning the resulting value of get-method would have the same performance as calling an fn?

kwrooijen19:03:59

Here's basically an example of what I'm trying to do. Both versions technically do the same. But is there an extra performance hit on the second version? I would think not but I don't really have anything to support that claim

alexmiller19:03:13

I think you're working awfully hard in the absence of any data

Darin Douglass19:03:53

feels like the caching of the lookup would be negligeable in a real system

alexmiller19:03:18

I don't think putting it in anothervar is going to help you at all perf wise

alexmiller19:03:51

I would challenge your presumption that the first one is slow enough that you need to do anything special

kwrooijen19:03:23

I need to use it in a game loop, so I need to squeeze every bit of performance I can get

kwrooijen19:03:14

And I'm using keyword hierarchy to build my system

alexmiller19:03:37

as a pretty good programmer I know named Rich once said to me, why guess when you can measure?

kwrooijen19:03:57

Very good advice simple_smile

kwrooijen19:03:17

I'll do that since I guess it's a bit of a weird edge case requirement. Thanks!

alexmiller19:03:08

my suspicion is that something like (let [fun (get-method some-multi :dispatch-k)] (loop [] ...)) is probably better than the var

kwrooijen19:03:32

Yeah that's what my code looks like right now, I'm not defining it globally

kwrooijen19:03:42

Ok well.. Surprisingly my second version averages faster than my first version.

kwrooijen21:03:39

After some more testing they actually seem equal in performance

kwrooijen19:03:36

Thanks for the tip, don't know why I didn't just benchmark it right away simple_smile

seancorfield21:03:09

@kevin.van.rooijen What are you using for timing stuff for benchmarks?

kwrooijen21:03:30

Hi, I was using criterium/quick-bench . But I made a mistake in my test. Looks like these two cases are pretty much equal in performance

aarkerio21:03:23

hi! Is there a way to add a new map element in the tail?

noisesmith21:03:04

hash-maps are not ordered, there's a library with an insertion-ordered variant though

noisesmith21:03:50

IOW adding a new element can change the order of everything in the map

noisesmith21:03:05

user=> (def m {:a 0 :b 1 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8})
#'user/m
user=> m
{:a 0, :b 1, :c 3, :d 4, :e 5, :f 6, :g 7, :h 8}
user=> (assoc m :i 9)
{:e 5, :g 7, :c 3, :h 8, :b 1, :d 4, :f 6, :i 9, :a 0}

ghadi21:03:09

maps don't have order (Python is wrong™)

bfabry22:03:07

ruby too iirc

jaihindhreddy22:03:53

Why the sudden hot take on Python 🙃? PS: I'm not offended, I'm usually the one doing this in my friend circle 😛

ghadi22:03:25

I just wrote some Python this afternoon 🙂 I think they are conflating promises that a specific map impl makes with the fundamentals of associative memory

ghadi22:03:38

(bad for the ecosystem, IMHO)

ghadi22:03:04

both w.r.t. beginners' mental models, and code found in the wild

Bill Phillips22:03:02

I think the problem they ran into was that, in practice, beginners relied on the implementation’s order no matter what it was

Bill Phillips22:03:32

so they felt that not having an order at all was priming beginners for nasty mysterious bugs

johnj00:04:41

meh, beginners are going to abuse anything until they learn better, just like in clojure some beginners abuse records when they should be using maps

Bill Phillips01:04:20

well, this is one reason that python is a better language for beginners than clojure: they think about different traps beginners can get into, and if one of them is a serious trap that doesn’t fail fast, they fix it

bfabry17:04:26

that's very debateable. it also teaches a habit of expecting maps to have an order which will burn them in any language where they opted not to take the performance hit (pretty much everything except ruby and python I think)

OrdoFlammae21:03:01

I think there's a sorted-map or something, if that's what you're looking for.

hindol03:04:01

Sorted map is sorted. Ordered map retains insertion order.

noisesmith22:03:09

that uses a sorting function, yeah (and of course there is an insertion ordered mutable hash-map that comes with the JVM)

Mario C.23:03:11

I have some client side code that displays a tab-navigated component (A user clicks on a tab and it shows them different content depending on that tab clicked.). So far so good. The problem I am facing is that sometimes the content is very large. And when a user clicks on a new tab it takes a few seconds to display the content. I thought perhaps if I, instead, only render the content that needs to be displayed then it might be slightly faster since there would be less DOM elements on the page. I was wrong since the process to actually build and render the content is a lot slower than if I just show/hide via css. Am I just SoL or are there ways that I can speed this thing up?

phronmophobic23:03:19

can you generate the html serverside?

phronmophobic23:03:41

is the full table visible on the screen? you might be able to only generate the part of the table that will be visible on screen and generate the non visible part in the background

Mario C.23:03:44

I am not sure if I could do the rendering serverside. But would that help with the laggy tab switching? I need to look into only showing the visible parts of the table. Not sure how tricky that will be though

phronmophobic23:03:53

it should make the tab switching faster, but might slow down the page load

phronmophobic23:03:47

although, if your content is gzipped, then it might not be that big a slowdown

phronmophobic23:03:24

and there might be speedup opportunities depending on how you’re generating the html on the tab switch

Mario C.23:03:46

Well currently it builds everything and then it dynamically hides everything but the active tab's content. I guess am not understanding how building the html serverside would help.

phronmophobic23:03:37

so the tab switch is still slow even if the html is already built?

Mario C.23:03:08

Yes, exactly

phronmophobic23:03:27

that must be a ton of html.

Mario C.23:03:54

Not as slow as if I was building each tab from scratch but def noticeably slow.

phronmophobic23:03:55

yea, I would look into chunking the table into smaller pieces

Mario C.23:03:06

It is. Each tab might have a table with 10K-30K cells

phronmophobic23:03:15

yup, that would do 🙂

phronmophobic23:03:54

I remember watching a tech talk about someone who had a similar problem. trying to see if I can find it

Mario C.23:03:51

Looks interesting gonna take a look

chepprey00:04:18

UX wise, what kind of data is in a table with 10-30k cells such that having a user scroll through that much stuff is the optimal way to present information?

chepprey01:04:40

In our apps at my job, we cap all tables to a 500 record limit. No paging controls either. We provide filters and other controls to narrow down whatever it is the user needs.

chepprey01:04:42

I know this approach doesn't necessarily work for all scenarios (like "infinite scrollers" a la Facebook where you're just browsing, not lifting something specific), but it definitely works for our apps. I've always disliked "pagers", and huuuuuuge (yet finite) lists/tables. Narrow down what you want, no human can digest 30k cells of data in a single shot.

Mario C.16:04:48

Its an HTML representation of an excel workbook. Users upload an excel workbook that can contain multiple sheets. We then extract information cell-by-cell including cell-styling, content, formatting, etc and build a data-structure that looks more or less like this

{"sheet-name" {[1 4] {:backGroundColor "#000"}} 
Where [1 4] is the cell on column 1 and row 4 on the "sheet-name" worksheet. The client receives this structure and builds out a table by looping over the dimensions of the worksheet, say 500 rows by 500 columns.

Mario C.16:04:42

I believe there is a better way to create a projection of that workbook but this is how it was built so I am working with this set up for now until we get time to refactor. lol

Mario C.23:03:11

The contents in question are HTML tables if that makes a difference