Fork me on GitHub
#beginners
<
2020-04-21
>
Chris K04:04:11

If I want to make a website with clojure, where do I start?

seancorfield04:04:23

@sunchaesk It depends what you mean by that. Do you want to create a static website using Clojure-based tools? Or do you mean a web server that renders HTML pages from server side? Or do you mean a Single Page Application that renders everything client-side?

teodorlu07:04:12

@sunchaesk I'd check out the #luminus documentation, and see if that fits your needs. https://luminusweb.com/

Jim Newton08:04:07

clj-kondo suggests I use (seq x) rather than (not (empty? x)). That sounds like bad advise for me. asking whether something is empty is much clearer than asking whether it is a sequence. at least in my opinion.

joelsanchez08:04:01

empty? is basically (not (seq x)) so you'd be saying (not (not (seq)) which is pointless this recommendation comes from clojure.core itself https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L6208

joelsanchez08:04:49

you're free to (def not-empty? seq) but come on

joelsanchez08:04:54

also seq does not tell you whether something is a sequence. you have other functions for that, such as coll? or sequential?

✔️ 4
Jim Newton08:04:15

I find it bizarre. what does seq stand for?

borkdude08:04:25

@U010VP3UY9X This is from the docstring of empty?

✔️ 4
Jim Newton08:04:42

Anyway, if the language recommends using (seq ...) rather than (not (empty? ...), shouldn't the compiler just do that for me?

joelsanchez08:04:49

this is a style recommendation, technically it will be fine but it's just poor style. the compiler has no business with that

joelsanchez08:04:15

you are also free to uppercase all your fns like (defn DO-STUFF) and the compiler won't judge but....ugh

Jim Newton08:04:16

Joel, sorry I don't mean to be argumentative, but if the recommendation is there because (not (not ...)) is inefficient, then it seems to me that it is precisely the compiler's responsibility do take care of trivial optimizations like that.

Jim Newton08:04:16

In my opinion, (not (not x)) in a Boolean context, should compile the same as x .

joelsanchez08:04:21

just because perhaps the compiler will reduce your (not (not (not (not (not... function to just not , it doesn't mean that your code is well written, or readable to others

hindol08:04:28

Since you bring readability, I am also in the camp that (not (empty? is more readable than (seq. I tend to flip the if and else part sometimes just to avoid making this decision.

(if (empty? coll) ... ...)

Jim Newton08:04:54

the logic is now circular. Is (not (empty? ..)) poor style because it is equivalent to (not (not ...)) or because it is difficult for a human to understand? If the former, then the compiler should handle it in its optimization stage, if the latter, then I'd diagree. Asking whether something is not empty is a very understandable expression in every human language I know of. On the other hand I've never heard of a human language where a person asks the emptyness of something by asking seq

joelsanchez08:04:06

hah well, I agree with the docstring that (not (empty? is pointless and for me it's more difficult to parse. I know what seq does as it's a core part of clojure. for (not (empty? I need to think about empty? and then negate it

borkdude08:04:33

@U010VP3UY9X you can argue about names in Clojure, but they are not going to change. Clojure has a strong philosophy of not making breaking changes. Another one that confuses people is contains?. Just take it as a given and move on I'd say if you don't want to waste your time

✔️ 4
hindol08:04:11

One more will be any?. It is not related to every? in any way.

joelsanchez08:04:58

the difference between empty, empty? some? and some (all of them very common) can be a pain for newcomers I think

borkdude08:04:54

As a newcomer it would be useful to collect confusing names and blog about it (in a constructive/non-ranty tone preferably), so other newcomers can profit from it and don't have to spend their time on this a lot.

joelsanchez08:04:09

imagine using (not (some? instead of nil? 😛 that's how I see (not (empty?. (`some?` is just (not (nil? )

hindol09:04:37

That's not the right analogy. (not (empty? reads like English and is very easy to parse.

borkdude09:04:08

if some? was called something? it would read like English, minor difference

borkdude09:04:42

A better name for some? would have been whatever?, objectively though. 😉

😁 8
nikolavojicic09:04:24

You can also use https://clojuredocs.org/clojure.core/not-empty which is not a predicate (returns coll / nil instead of true / false)... But seq is idiomatic in Clojure

👍 4
❤️ 4
Jim Newton09:04:06

Joel, I agree with you in the extreme. If someone asks whether something is not false or not nil, (which is common in the scheme community). My code is set-theoretic. I'm using sequences to represent data and I want to know whether the set is empty or not. i.e., "set" in the mathematical sense, not the clojure sense. Perhaps I should use empty? and inhabited?

hindol09:04:53

For language designers, correcting a mistake is a mistake. That's how I see it.

joelsanchez09:04:22

not-emptyis super useful to avoid passing empty collections around when you actually prefer to pass nil. so instead of checking in the users of the collection-producing function, you make the fn not return an empty coll

borkdude09:04:25

@U010VP3UY9X you're free to name the functions for your domain the way you want them to 🙂

Jim Newton09:04:54

At my old company, we had a proprietary lisp which is now some 30 years old, maybe more. The manual was full of advise for users to do certain things to avoid inefficiencies. I was never able to convince the R&D department just to make the compiler do those simple optimizations for me, rather than spending time maintaining tools and manuals for warning me about it.

borkdude09:04:13

@U010VP3UY9X Sometimes making optimizations means breaking existing programs. This is what Clojure avoids at all costs

Jim Newton09:04:51

@U04V15CAJ, yes I feel the same way. There is a balance, I understand, between being a good citizen, and making your code sane. I brought it up because I just started using clj-kondo as a linter. every linter is opinionated. I know when I've written linters in the past, my users often complained because they didn't agree with my advise.

joelsanchez09:04:35

@U010VP3UY9X to avoid checking for empty collections, you can append not-empty at the end of your set-producing fns. say you have a find-users-by-pred function. if no users are found, you want to return nil to, perhaps, return a 404 http error. so you do (not-empty (query-the-db...)) and there you go, it's either coll with users or nil

borkdude09:04:38

@U010VP3UY9X clj-kondo is a bit opiniated, but not stubborn: you can configure it and turn linters off if you don't agree with them. Even in clojure.core libraries I have found usage of (not (empty? ...)) btw

andy.fingerhut10:04:30

The doc string for empty? says "Please use the idiom (seq x) rather than (not (empty? x))". It is a polite request, not a requirement. You can use (not (empty? Coll)) every day of the week, and twice on Sunday, if you wish. You will find many Clojure developers who do follow that recommendation and use (seq coll) instead, and it is good to know what it means.

noisesmith15:04:31

does clj-kondo fail if you use (not (empty? ...)) or just warn? this might be a tooling ergonomics issue

borkdude15:04:11

just a warning

noisesmith15:04:47

also (if (not-empty x) ...) and (if (not (empty? x)) ...) would behave the same in practice

noisesmith15:04:59

I think seq returning falsey for empty input is a common enough idiom that it makes sense to learn it if you are writing clojure, even if it looks odd

andy.fingerhut18:04:48

It definitely makes sense to learn it for reading and understanding other people's Clojure code, whatever you happen to use yourself. And if you are working on a team on a code base and trying to merge in changes to an established code base, do not be surprised if others prefer and request changes to use (seq coll) instead of (not (empty? coll))

andy.fingerhut19:04:30

Then some form of this discussion can be repeated among the interested parties of that code base 🙂

Jim Newton08:04:18

If I refer to a function by full qualified name, do I need to use a :require in the ns declaration?

Jim Newton08:04:35

just a [clojure.string] without any decorators?

hindol08:04:40

Or an :import if it's a Java class.

hindol08:04:48

Yes, exactly.

Jim Newton08:04:52

(ns clojure-rte.dot
  (:require [clojure.pprint :refer [cl-format]]
            [clojure.java.shell :refer [sh]]))

(def ^:dynamic *dot-path*
  "Full path to the graphviz dot program"
  (let [m (sh "which" "dot")]
    (cond
      (= 0 (:exit m)) (clojure.string/trim (:out m))
      :else "dot")))

Jim Newton09:04:16

What is the correct idiom for writing code which is OS dependent? I am using mac-os, and if my function is run on mac-os, then I know how to do a certain thing, otherwise I don't know. If someone every enhances my code, I'm happy for them to extend it to another OS. What is the correct idiom?

(defn dfa-to-dot 
  "docstring here"
  [dfa & {:keys [title view]
          :or {title "no-title"
               view false}}]
  (cond
    view (let [png-file-name (str *dot-tmp-dir* "/" title ".png")]
           (sh *dot-path* "-Tpng" "-o" png-file-name
               :in (dfa-to-dot dfa :title title :view false))
           (if (= "Mac OS X" (System/getProperty "os.name"))
             (sh "open" png-file-name)))
    :else
...stuff deleted

didibus09:04:19

If you use the Java APIs it'll be cross platform automatically

didibus09:04:31

Instead of shelling out

Jim Newton09:04:45

I don't know the Java API. and which shelling out are you referring to? For example, I don't know how to open a graphic file for viewing on non-mac. but on mac I just issue the open shell command

Jim Newton09:04:16

There are two shell commands I need (or think I need). 1. run graph-viz dot to create a png file. 2. open it for viewing

didibus09:04:26

I was talking about the use of sh

didibus09:04:41

Hum well for using graphviz I guess you might not have a choice

Jim Newton09:04:23

I suppose there's a third OS dependency I have. I'm creating a file, and assuming a UNIX file system.

didibus09:04:56

I guess it all depends how much you care about cross-platform here. But in theory, if you use the Java file APIs to create files and folders they'll handle different OS automatically

didibus09:04:15

And you can use Swing to display an imagine on Java, which will also support cross platform automatically

didibus09:04:35

For graphviz, I think there is a Java implementation of it, but I don't know how good it is

didibus09:04:26

So, I'd say these are the "correct idiom". But in your case, having an if or cond might be simpler

andy.fingerhut10:04:35

I am not sure there is an idiom for this. Lots of things that create and run child processes will work on both macOS and Linux, like your example code, as long as the dot command is installed. Windows tends to be different more often. Using the os.name JVM system property is a good way to determine which OS you are running on.

didibus21:04:33

Going off the old Java idiom: "Write once, run everywhere" 😋

Jim Newton10:04:47

after creating the png file, i'd like to display it. is there a better way than using a sub-process? anyway the "open" command has lots of problems.

didibus17:04:12

Well, you can use one of the many Java GUI toolkits to display the png. That be one way.

Jim Newton09:04:26

in Common Lisp there is a convention to use (if ..) either when there is a then/else part or when the return value is being used, and to use (when ...) only for side effect, I.e., don't take advantage of the fact that (when ...) returns nil if the condition is false. Is there any such convention in clojure?

didibus09:04:13

Ya it's the same

didibus09:04:52

Well, what I know is just use when when there is no else clause, if otherwise

didibus09:04:06

Doesn't matter if you care or not about the return

didibus09:04:38

So I think people would take advantage of the fact it returns nil to mean false on some cases

Jim Newton09:04:55

I was skeptical of this rule at first, but I liked it after I adopted it. it seems to help to express the intent.

didibus09:04:46

I've heard people do the same in Clojure, but I think it's more common to stick to the less strict rule of just using when for elseless cases

hindol10:04:08

clj-kondo will warn if there is an if without the else part. So it seems to suggest when is not just for side effects, it is also an if without the else. Some people strictly use when for side effects. There is no "rule" here.

noisesmith15:04:22

I've definitely seen when in the tail of functions used with lazy-seq, where (when (more-elements x) (frob x))implicitly terminates the seq by returning nil if more-elements returns falsey

Jim Newton09:04:39

I have a test case which generates some random data to test with. sometimes the test fail. Is there a way I can ask about the random number seed before running the test, so that if the test fails, I can print the seed, to help reproduce the error. It usually fails in the ci/cd pipeline in a situation where it is completely in batch mode. what is the model for getting the seed and re-seeding to the same place?

hindol10:04:10

I am guessing Clojure will use the random related facilities from Java and if yes, there is no way to query the seed. However, you can seed it yourself and remember the seed.

andy.fingerhut10:04:51

Clojure's test.check library has methods to generate random data that uses a seed, and reports the seed in failing tests.

💯 4
andy.fingerhut10:04:23

Also, Clojure's rand function on Clojure/JVM calls JVM method random in package java.lang.Math: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#random--

andy.fingerhut10:04:15

which is documented to behave on the first call is if it called java.util.Random(): https://docs.oracle.com/javase/8/docs/api/java/util/Random.html

andy.fingerhut10:04:06

The documentation for that Java class has a method setSeed that allows you to assign a value to the seed if you wish: https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#setSeed-long-

andy.fingerhut10:04:57

There is no need to ask for the seed, if instead you can force it to be whatever you want, which is of course also what you would want to do if you wanted to reproduce the random sequence again later.

hindol10:04:54

Regarding setSeed, it is much more common in Java to just pass the seed to the constructor new Random(seed). Setting it after you have used it a bit will break the "randomness", I think.

andy.fingerhut10:04:11

The documentation claims that it is a requirement that after assigning a value to the seed, the sequence of values returned is guaranteed to be the same every time.

andy.fingerhut10:04:25

If you have a counterexample to that, it sounds like a bug in the implementation.

andy.fingerhut10:04:45

Here is a sentence copied from the Java docs for the setSeed method: "Sets the seed of this random number generator using a single `long` seed. The general contract of `setSeed` is that it alters the state of this random number generator object so as to be in exactly the same state as if it had just been created with the argument `seed` as a seed."

hindol10:04:09

What I mean is, suppose I have seeded a generator with x and taken out 10 values and then re-seeded the same generator with y and taken 10 values, then if you view the 20 values together, that won't be random.

andy.fingerhut10:04:53

If you set the seed, I would think the only reason for doing so is because you explicitly want to repeat a sequence from a particular starting point.

andy.fingerhut10:04:01

If you call it at other times, then yes, you are shooting yourself in the foot.

hindol10:04:14

So, you create different generators with different seeds. I have never seen someone use setSeed.

andy.fingerhut10:04:07

I don't think that JVM package/class lets you create more than one instance of a pseudo-RNG. It is global to the JVM, shared by all threads, as implied by other statements in the Java docs of one of the pages I linked above. You are free of course not to use that class, as there are other pseudo-RNG implementations in Java readily available as open source, too.

andy.fingerhut10:04:06

The test.check Clojure library I mentioned implements its own pseudo-RNG in Clojure, if I recall correctly, and avoids using Java's.

hindol10:04:59

Yes, you are right, I used the wrong words there. By "generator" I mean instances of Random. I have never seen the seed of the same instance of Random getting updated using setSeed().

Chris K10:04:35

@seancorfield I was looking for something similar to flask in python. Nothing too complex. I think anything of the last two choices r the type I'm looking for

Aviv Kotek10:04:53

hey, is there any way to ignore that is-nil check? my method must return boolean.

(defn in-page? [page url]
  (if-not url
    false
    (->> url
         (re-matches (re-pattern (str ".*/" page ".*")))
         (some?))))
and re-matches can throw exception on nil vals.

hindol11:04:34

I think you can just "flow" the nil downstream.

(when url
  ...do stuff...)
If URL is nil, the return from in-page? will be nil. The caller will treat nil as false, that's how most of Clojure works anyways.

Aviv Kotek11:04:34

having a question-markmethod, i'd expect* boolean as return

hindol11:04:22

You need not. nil is falsey almost everywhere. Do the simplest thing that works.

hindol11:04:47

It is okay to return true or nil.

Aviv Kotek11:04:39

according to styling guide Pred With Question Mark The names of predicate methods (methods that return a boolean value) should end in a question mark (e.g., even?).

hindol11:04:03

It is very common to use #{} (a set) as predicate.

(remove #{1 3 5} (range 6)) ;; => (0 2 4)
But (#{1 3 5} [5]) ;; => 5 and (#{1 3 5} [4]) ;; => nil . So, any value = truthy, nil = falsey. No-one worries about types normally.

Aviv Kotek11:04:39

see the thread above

hindol11:04:05

I will, after I finish laughing at the "eh" suffix. 😁

Aviv Kotek11:04:29

can also wrap it with some->>

Aviv Kotek11:04:36

(defn in-page? [page url]
  (some->> url
           (re-matches (re-pattern (str ".*/" page ".*")))))

4
hindol11:04:42

some->> will not fix your nil issue. Maybe just attach http://clojuredocs.org/clojure.core/boolean in the end.

hindol11:04:42

Thinking about it, why even accept a nil url? Shouldn't that be an exception?

Aviv Kotek12:04:15

yes I'll add boolean

Aviv Kotek12:04:35

hmm it's not a url, just used for the example

Aviv Kotek12:04:55

or this

(defn in-page? [page url]
  ((fnil clojure.string/includes? "") url (str "/" page)))

Aviv Kotek12:04:59

I think that fnil will do (without the nil as return)

7sharp911:04:26

Hi, Im reading the joy of clojure and I was confused about this part:

(defmethod print-method clojure.lang.PersistentQueue
  [q, w]
  (print-method '<- w) (print-method (seq q) w) (print-method '-< w))

jsn11:04:01

what do you find confusing? you can just (print-method '<- *out*) in REPL and see what happens

7sharp911:04:51

The book must be assuming you know what print-method is

andy.fingerhut11:04:34

I haven't read Joy of Clojure -- it does not describe at all what print-method is except for using it in that example code snippet?

andy.fingerhut11:04:35

I am not sure, but they may be assuming a level of Clojure implementation curiosity that you will search through the Clojure source code itself if you do not recognize something. It is not typically cited as a beginner level book on Clojure.

7sharp913:04:38

I tend to read books standalone so it was not obvious reading it that print-method was a clojure entity

noisesmith15:04:19

@UJA8PA70W in general, clojure.repl (in scope in new repls by default) helps a lot:

(cmd)user=> (doc print-method)                                                                                                                            
-------------------------                                                                                                                                
clojure.core/print-method                                                                                                                                
nil                                                                                                                                                      
(ins)user=> (apropos #"^print")                                                                                                                          
(clojure.core/print clojure.core/print-ctor clojure.core/print-dup clojure.core/print-method clojure.core/print-simple clojure.core/print-str clojure.core/printf clojure.core/println clojure.core/println-str clojure.pprint/print-length-loop clojure.pprint/print-table)
(ins)user=> (find-doc #"\wprint\w")                                                                                                                      
nil                                                                                                                                                      
(cmd)user=> (find-doc #"\Wprint\W")                                         
-------------------------  
clojure.pprint/*print-base*                                                 
  The base to use for printing integers and rationals.                 
-------------------------                                                 
clojure.pprint/*print-circle*                                       
  Mark circular structures (N.B. This is not yet used)                                                                                                   
-------------------------                              
...

noisesmith15:04:45

oh it's weird that clojure.core/print-method has no doc-string - is it somehow hard to give doc strings to multimethods?

noisesmith15:04:09

there's always this

user=> (source clojure.core/print-method)
(defmulti print-method (fn [x writer]
                         (let [t (get (meta x) :type)]
                           (if (keyword? t) t (class x)))))
nil

noisesmith15:04:26

but that just tells you the dispatch, not what it's for...

7sharp915:04:25

At that point in the books its not yet covered defmulti

noisesmith15:04:19

at least (doc defmulti) is useful, I'm really surprised there's no built in doc for print-method though

7sharp911:04:46

Specifically the (print-method '<- w) bits with the ’ quote operator

andy.fingerhut11:04:57

Most likely that is a call to print-method instructing it to print out the characters <- to the writer w

andy.fingerhut11:04:32

So that when you print a PersistentQueue using that method, it comes out looking like <-2 3 5 7 11-<

andy.fingerhut11:04:25

<- is a symbol, and it is quoted so that after evaluation, the result is the symbol <- , without attempting to look up the current value of a Var whose name is <-

andy.fingerhut11:04:36

Just as if you wanted to quote any other symbol, e.g. (print-method 'foo w) . Granted, <- is a much less common sequence of characters for naming a symbol than the usual sequence of letters, digits, and dashes, but it is a legal symbol in Clojure.

👍 4
7sharp913:04:09

I think the thing that confused me was not knowing print-method was a clojure entity, reading the code it sort of looked like the function was calling itself.

dev.4openID15:04:52

noobie here! and struggling! using the curl command work just fine (note sanitised) curl -uXXXXXXXXyyyyyyyy1111111122222222333331111: https://api.example.com/search/qwerty\?q\=08887777\&items_per_page\=2 My code looks like - (def co {:URL https://api.example.com  :FUNCTION “search/companies?=”  :API_KEY "Basic "  :KEY “XXXXXXXXyyyyyyyy1111111122222222333331111”}) (let [RegNum “08887777”] (try   (djson/read-str (:body (client/get (str (co :URL) (co :FUNCTION))                     {:query-params {"q" RegNum  "items_per_page" "2"}                     :body-encoding "UTF-8"                     :accept :json                     :headers {(co :API_KEY) (co :KEY)}})))  (catch clojure.lang.ExceptionInfo………. I have read lots of docs and my eyes are swimming now. 1. I understand the query string is "automatically" composed with the :query-params. This results in the \?q\=08887777\&amp;items_per_page\=2 query string. Is this correct? The way I have laid it out is correct? This is because I did not quote my URL in a zsh so I can remove the \s: 'https://api.example.com/search/qwerty?q=08887777&amp;items_per_page=2' 2. In the case of the equivalent of the curl -uXXXXXXXXyyyyyyyy1111111122222222333331111, which is a secret, :API_KEY is “Basic” and the KEY is “XXXXXXXXyyyyyyyy1111111122222222333331111”. There is no UID/PWD but secret in this case. Is this correct? clj-http was :basic-auth "Basic" and it was not :Headers Learnt a lot about header that's for sure.

dev.4openID15:04:24

Double question apologies - but are related

Old account15:04:22

Hi! Why am I getting nil but not 1 here?

(def prx (proxy [java.lang.Object java.lang.Runnable] []
        (run 
          ([] 1))))

(.run prx)

noisesmith16:04:15

run is void so your return value is ignored, maybe you want call on the Callable interface - very similar but allows returning a value

noisesmith16:04:26

user=> (def prx (proxy [java.util.concurrent.Callable] []
        (call
          ([] 1))))
#'user/prx
(ins)user=> (.call prx)
1

Old account16:04:55

this is correct, but also weird why this macro let my implementation to return something. or at least write code as it appears...

noisesmith16:04:50

there's no such thing as "returning something" explicitly in clojure - what would you expect clojure to do?

noisesmith16:04:18

I guess it could error if the last line of your method isn't nil ?

Old account16:04:23

I would expect to "return"/evaluate to 1 there

noisesmith16:04:06

but you can't make a void method return something

seancorfield16:04:03

Every expression in a function body is evaluated. Normally, the last value is returned, except for a void function where nothing is returned, a.k.a. nil.

Alex Miller (Clojure team)16:04:06

void methods only exist via Java interop, they are not a "normal" thing in Clojure, so you're seeing impedance matching at the interface here

noisesmith16:04:11

I'm trying to imagine what use case would require a callable and also require a return value when called

noisesmith16:04:35

also - fn implements Runnable and Callable so you don't need proxy

Alex Miller (Clojure team)16:04:48

executors can use callables

noisesmith16:04:03

(ins)user=> (.run (fn [] 12))
nil
(ins)user=> (.call (fn [] 12))
12

Old account17:04:16

How to use 'this in proxy macro?

(def prx (proxy [java.lang.Runnable] []
    (run 
      ([] (println "0000" 'this) 1))
    (toString ([this] (str "------" this)))))

(.toString prx)
gives me an error about arity. Documentation mentions 'this but not clear how to refer at it

noisesmith17:04:59

that toString is bad because str already calls toString

noisesmith17:04:32

don't use 'this - that's a symbol, use this it's an implicit binding (don't put it in your binding list either)

noisesmith17:04:40

user=> ((proxy [clojure.lang.IFn] [] (toString [] "an anonymouns function") (invoke [] (str this))))
"an anonymouns function"

✔️ 4
noisesmith17:04:08

that implements IFn by returning its string, and implements toString with an arbitrary string

ryan echternacht17:04:07

Dang… having to do non-clojure work without being able to use -> is really hard

✔️ 4
justin18:04:18

Is their a "transaction-like" mechanism for atoms, to allow composability of side-effect functions which use swap! ? in my scenario, the functions are operating on a single atom (it's a reagent atom). I have a single page application, and need to update ui-related data (tracking what was last clicked), and core data.

Alex Miller (Clojure team)18:04:54

no, that's kind of the whole point of atoms

Alex Miller (Clojure team)18:04:20

maybe you could use watchers, I don't know much about the domain

Alex Miller (Clojure team)18:04:10

I mean, comp can compose functions into a single function that could be given to swap!

justin18:04:13

true...i have to re-think the architecture. some of the side-effect functions uses reagent cursors to update only portions of a global hashmap. get's messy when you want to compose these functions to update different sub-trees of the hashmap.

justin18:04:02

i was trying to avoid sprinkling swap! around the code base, or creating two functions for side effect calls, one which performs the mutation, and one that wraps the mutation with a swap!

justin18:04:36

i feel like introducing channels could work to queue changes...but seems a bit hacky and could lead to some (minor) race conditions

phronmophobic18:04:58

have you looked at https://github.com/Day8/re-frame? it’s made to work with reagent and solve some of the state management issues you may be running into.

justin18:04:52

hm thanks i'll take a look.

phronmophobic18:04:06

and the #re-frame channel is pretty helpful and active if you run into any issues

Scott Starkey20:04:45

OK, guys - I’m trying to make the following code more efficient:

(def initial-state [{:b [], :i 0} {:b [], :i 0}])
(def st [:gc :gc :pc :pc :oc :oc])
  (as-> initial-state $
        (f $)
        (update-in $ [0 :b] into st)
        (update-in $ [1 :b] into st)
        )
; => [{:b [:gc :gc :pc :pc :oc :oc], :i 0} {:b [:gc :gc :pc :pc :oc :oc], :i 0}]
The above works perfectly, but I was thinking the two update-in lines could be more efficient. Aha, I said to my newbie self - here I could use my newly learned map function! But that causes a problem, in that I get a double entry, with the first updating 0 and the second filling 1:
(as-> initial-state $
      (f $)
      (map #(update-in $ [% :b] into st) [0 1]))

; => ([{:b [:gc :gc :pc :pc :oc :oc], :i 0} {:b [], :i 0}]
      [{:b [], :i 0} {:b [:gc :gc :pc :pc :oc :oc], :i 0}])
Oof. Is there a way of doing this, and still preserve my as-> thread?

noisesmith20:04:50

how would map make this more efficient?

bfabry20:04:54

by more efficient do you mean less code? using map there will make it less efficient in terms of space and time, and less efficient in terms of time taken to understand when reading it as a human imo

4
Scott Starkey20:04:28

Yeah, it just seemed a little klutzy having two lines of code that were almost identical. 😄

Scott Starkey20:04:20

I didn’t know if there was a proper style that one should adopt as a Clojurist.

hindol20:04:30

-> can safely replace the as-> since all functions in the chain accept the param in the first position.

bfabry20:04:54

I would say clojurists in general look suspiciously on removals of duplication/boilerplate that make the code harder to understand

Scott Starkey20:04:10

@hindol.adhya I simplified the above example. There’s going to be more than this in the chain.

dpsutton20:04:18

(map #(update-in $ and not % makes me super nervous

Scott Starkey20:04:51

OK, I’ll just leave it as the “easier to read and do” version.

noisesmith20:04:54

@scotto what about replacing (update-in ...) with a function?

4
👍 4
noisesmith20:04:16

I still don't think map makes sense here - its shape of input and output don't match your domain

Scott Starkey20:04:24

@noisesmith Hmmmm… :thinking_face:

Scott Starkey20:04:24

I’ll keep it as-is. Thanks, guys! I was trying to be too clever. 😄

noisesmith20:04:36

(reduce (fn [m path] (update-in m path into st))) initial-state [[0 :b] [1 :b]])

noisesmith20:04:54

I think reduce actually matches the shape of what you are doing

noisesmith20:04:11

(it's also a variant of "put the update-in in a function")